Skip to content

Commit 4a6976f

Browse files
authored
Merge pull request #4641 from MDoerner/AddMissingAttributeQuickfix
Adds quickfix for missing attribute inspection
2 parents 7e17257 + 94b8f77 commit 4a6976f

33 files changed

+1940
-164
lines changed
Lines changed: 27 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,59 @@
11
using System.Collections.Generic;
22
using System.Linq;
3-
using Antlr4.Runtime;
43
using Rubberduck.Inspections.Abstract;
54
using Rubberduck.Inspections.Results;
65
using Rubberduck.Parsing;
76
using Rubberduck.Parsing.Annotations;
8-
using Rubberduck.Parsing.Grammar;
97
using Rubberduck.Parsing.Inspections;
108
using Rubberduck.Parsing.Inspections.Abstract;
119
using Rubberduck.Parsing.Symbols;
1210
using Rubberduck.Parsing.VBA;
13-
using Rubberduck.Parsing.VBA.Parsing;
1411
using Rubberduck.Resources.Inspections;
1512

1613
namespace Rubberduck.Inspections.Concrete
1714
{
1815
[CannotAnnotate]
19-
public sealed class MissingAttributeInspection : ParseTreeInspectionBase
16+
public sealed class MissingAttributeInspection : InspectionBase
2017
{
2118
public MissingAttributeInspection(RubberduckParserState state)
2219
: base(state)
23-
{
24-
Listener = new MissingMemberAttributeListener(state);
25-
}
26-
27-
public override CodeKind TargetKindOfCode => CodeKind.AttributesCode;
28-
29-
public override IInspectionListener Listener { get; }
20+
{}
3021

3122
protected override IEnumerable<IInspectionResult> DoGetInspectionResults()
3223
{
33-
return Listener.Contexts.Select(context =>
34-
{
35-
var name = string.Format(InspectionResults.MissingAttributeInspection, context.MemberName.MemberName,
36-
((VBAParser.AnnotationContext)context.Context).annotationName().GetText());
37-
return new QualifiedContextInspectionResult(this, name, context);
38-
});
39-
}
40-
41-
public class MissingMemberAttributeListener : ParseTreeListeners.AttributeAnnotationListener
42-
{
43-
public MissingMemberAttributeListener(RubberduckParserState state) : base(state) { }
44-
45-
public override void ExitAnnotation(VBAParser.AnnotationContext context)
24+
var declarationsWithAttributeAnnotations = State.DeclarationFinder.AllUserDeclarations
25+
.Where(declaration => declaration.Annotations.Any(annotation => annotation.AnnotationType.HasFlag(AnnotationType.Attribute)));
26+
var results = new List<DeclarationInspectionResult>();
27+
foreach (var declaration in declarationsWithAttributeAnnotations)
4628
{
47-
var annotationType = context.AnnotationType;
48-
49-
if (!annotationType.HasFlag(AnnotationType.Attribute))
29+
foreach(var annotation in declaration.Annotations.OfType<IAttributeAnnotation>())
5030
{
51-
return;
52-
}
31+
if (MissesCorrespondingAttribute(declaration, annotation))
32+
{
33+
var description = string.Format(InspectionResults.MissingAttributeInspection, declaration.IdentifierName,
34+
annotation.AnnotationType.ToString());
5335

54-
var isMemberAnnotation = annotationType.HasFlag(AnnotationType.MemberAnnotation);
55-
var isModuleScope = CurrentScopeDeclaration.DeclarationType.HasFlag(DeclarationType.Module);
36+
var result = new DeclarationInspectionResult(this, description, declaration,
37+
new QualifiedContext(declaration.QualifiedModuleName, annotation.Context));
38+
result.Properties.Annotation = annotation;
5639

57-
if (isModuleScope && !isMemberAnnotation)
58-
{
59-
// module-level annotation
60-
var module = State.DeclarationFinder.UserDeclarations(DeclarationType.Module).Single(m => m.QualifiedName.QualifiedModuleName.Equals(CurrentModuleName));
61-
if (!module.Attributes.HasAttributeFor(context.AnnotationType))
62-
{
63-
AddContext(new QualifiedContext<ParserRuleContext>(CurrentModuleName, context));
40+
results.Add(result);
6441
}
6542
}
66-
else if (isMemberAnnotation)
67-
{
68-
// member-level annotation is above the context for the first member in the module..
69-
if (isModuleScope)
70-
{
71-
CurrentScopeDeclaration = FirstMember;
72-
}
43+
}
7344

74-
var member = Members.Value.Single(m => m.Key.Equals(CurrentScopeDeclaration.QualifiedName.MemberName));
75-
if (!member.Value.Attributes.HasAttributeFor(context.AnnotationType, member.Key))
76-
{
77-
AddContext(new QualifiedContext<ParserRuleContext>(CurrentModuleName, context));
78-
}
79-
}
80-
else
81-
{
82-
// annotation is illegal. ignore.
83-
}
45+
return results;
46+
}
47+
48+
private static bool MissesCorrespondingAttribute(Declaration declaration, IAttributeAnnotation annotation)
49+
{
50+
if (string.IsNullOrEmpty(annotation.Attribute))
51+
{
52+
return false;
8453
}
54+
return declaration.DeclarationType.HasFlag(DeclarationType.Module)
55+
? !declaration.Attributes.HasAttributeFor(annotation)
56+
: !declaration.Attributes.HasAttributeFor(annotation, declaration.IdentifierName);
8557
}
8658
}
8759
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using Rubberduck.Inspections.Abstract;
2+
using Rubberduck.Inspections.Concrete;
3+
using Rubberduck.Parsing.Annotations;
4+
using Rubberduck.Parsing.Inspections.Abstract;
5+
using Rubberduck.Parsing.Rewriter;
6+
using Rubberduck.Parsing.Symbols;
7+
using Rubberduck.Parsing.VBA;
8+
using Rubberduck.Parsing.VBA.Parsing;
9+
10+
namespace Rubberduck.Inspections.QuickFixes
11+
{
12+
public sealed class AddMissingAttributeQuickFix : QuickFixBase
13+
{
14+
private readonly IAttributesUpdater _attributesUpdater;
15+
16+
public AddMissingAttributeQuickFix(IAttributesUpdater attributesUpdater)
17+
: base(typeof(MissingAttributeInspection))
18+
{
19+
_attributesUpdater = attributesUpdater;
20+
}
21+
22+
public override void Fix(IInspectionResult result, IRewriteSession rewriteSession)
23+
{
24+
var declaration = result.Target;
25+
IAttributeAnnotation annotation = result.Properties.Annotation;
26+
27+
var attributeName = declaration.DeclarationType.HasFlag(DeclarationType.Module)
28+
? annotation.Attribute
29+
: $"{declaration.IdentifierName}.{annotation.Attribute}";
30+
31+
_attributesUpdater.AddAttribute(rewriteSession, declaration, attributeName, annotation.AttributeValues);
32+
}
33+
34+
public override string Description(IInspectionResult result) => Resources.Inspections.QuickFixes.AddMissingAttributeQuickFix;
35+
36+
public override CodeKind TargetCodeKind => CodeKind.AttributesCode;
37+
38+
public override bool CanFixInProcedure => true;
39+
public override bool CanFixInModule => true;
40+
public override bool CanFixInProject => true;
41+
}
42+
}

Rubberduck.Parsing/Annotations/AnnotationType.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,11 @@ public enum AnnotationType
6464
PredeclaredId = 1 << 16 | Attribute | ModuleAnnotation,
6565
[AttributeAnnotation("VB_Exposed", "True")]
6666
Exposed = 1 << 17 | Attribute | ModuleAnnotation,
67-
Obsolete = 1 << 18 | MemberAnnotation | VariableAnnotation
67+
Obsolete = 1 << 18 | MemberAnnotation | VariableAnnotation,
68+
[AttributeAnnotation("VB_Description")]
69+
ModuleDescription = 1 << 19 | Attribute | ModuleAnnotation,
70+
ModuleAttribute = 1 << 20 | Attribute | ModuleAnnotation,
71+
MemberAttribute = 1 << 21 | Attribute | MemberAnnotation
6872
}
6973

7074
[AttributeUsage(AttributeTargets.Field)]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.Collections.Generic;
2+
using Rubberduck.Parsing.Grammar;
3+
using Rubberduck.VBEditor;
4+
5+
namespace Rubberduck.Parsing.Annotations
6+
{
7+
public abstract class AttributeAnnotationBase : AnnotationBase, IAttributeAnnotation
8+
{
9+
protected AttributeAnnotationBase(AnnotationType annotationType, QualifiedSelection qualifiedSelection, VBAParser.AnnotationContext context, IReadOnlyList<string> attributeValues)
10+
:base(annotationType, qualifiedSelection, context)
11+
{
12+
AttributeValues = attributeValues ?? new List<string>();
13+
}
14+
15+
public abstract string Attribute { get; }
16+
public IReadOnlyList<string> AttributeValues { get; }
17+
}
18+
}

Rubberduck.Parsing/Annotations/DefaultMemberAnnotation.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ namespace Rubberduck.Parsing.Annotations
88
/// <summary>
99
/// Used for specifying a member's <c>VB_UserMemId</c> attribute value.
1010
/// </summary>
11-
public sealed class DefaultMemberAnnotation : AnnotationBase, IAttributeAnnotation
11+
public sealed class DefaultMemberAnnotation : AttributeAnnotationBase
1212
{
1313
public DefaultMemberAnnotation(QualifiedSelection qualifiedSelection, VBAParser.AnnotationContext context, IEnumerable<string> parameters)
14-
: base(AnnotationType.DefaultMember, qualifiedSelection, context)
14+
: base(AnnotationType.DefaultMember, qualifiedSelection, context, new List<string> { "0" })
1515
{
16-
Description = parameters.FirstOrDefault();
16+
Description = parameters?.FirstOrDefault() ?? string.Empty;
1717
}
1818

1919
public string Description { get; }
20-
public string Attribute => "VB_UserMemId = 0";
20+
public override string Attribute => "VB_UserMemId";
2121
}
2222
}

Rubberduck.Parsing/Annotations/DescriptionAnnotation.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ namespace Rubberduck.Parsing.Annotations
88
/// <summary>
99
/// Used for specifying a member's <c>VB_Description</c> attribute.
1010
/// </summary>
11-
public sealed class DescriptionAnnotation : AnnotationBase, IAttributeAnnotation
11+
public sealed class DescriptionAnnotation : AttributeAnnotationBase
1212
{
1313
public DescriptionAnnotation(QualifiedSelection qualifiedSelection, VBAParser.AnnotationContext context, IEnumerable<string> parameters)
14-
: base(AnnotationType.Description, qualifiedSelection, context)
14+
: base(AnnotationType.Description, qualifiedSelection, context, parameters?.Take(1).ToList())
1515
{
16-
Description = parameters?.FirstOrDefault();
16+
Description = AttributeValues?.FirstOrDefault();
1717
if ((Description?.StartsWith("\"") ?? false) && Description.EndsWith("\""))
1818
{
1919
// strip surrounding double quotes
@@ -22,6 +22,6 @@ public DescriptionAnnotation(QualifiedSelection qualifiedSelection, VBAParser.An
2222
}
2323

2424
public string Description { get; }
25-
public string Attribute => $"VB_Description = \"{Description.Replace("\"", "\"\"")}\"";
25+
public override string Attribute => "VB_Description";
2626
}
2727
}

Rubberduck.Parsing/Annotations/EnumeratorMemberAnnotation.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,12 @@ namespace Rubberduck.Parsing.Annotations
88
/// <summary>
99
/// Used for specifying a member's <c>VB_UserMemId</c> attribute value.
1010
/// </summary>
11-
public sealed class EnumeratorMemberAnnotation : AnnotationBase, IAttributeAnnotation
11+
public sealed class EnumeratorMemberAnnotation : AttributeAnnotationBase
1212
{
1313
public EnumeratorMemberAnnotation(QualifiedSelection qualifiedSelection, VBAParser.AnnotationContext context, IEnumerable<string> parameters)
14-
: base(AnnotationType.Enumerator, qualifiedSelection, context)
15-
{
16-
Description = parameters.FirstOrDefault();
17-
}
14+
: base(AnnotationType.Enumerator, qualifiedSelection, context, new List<string> { "-4" })
15+
{}
1816

19-
public string Description { get; }
20-
public string Attribute => ".VB_UserMemId = -4";
17+
public override string Attribute => "VB_UserMemId";
2118
}
2219
}

Rubberduck.Parsing/Annotations/ExposedModuleAnnotation.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ namespace Rubberduck.Parsing.Annotations
77
/// <summary>
88
/// Used for specifying a module's <c>VB_Exposed</c> attribute.
99
/// </summary>
10-
public sealed class ExposedModuleAnnotation : AnnotationBase, IAttributeAnnotation
10+
public sealed class ExposedModuleAnnotation : AttributeAnnotationBase
1111
{
1212
public ExposedModuleAnnotation(QualifiedSelection qualifiedSelection, VBAParser.AnnotationContext context, IEnumerable<string> parameters)
13-
: base(AnnotationType.Exposed, qualifiedSelection, context)
13+
: base(AnnotationType.Exposed, qualifiedSelection, context, new List<string> { Tokens.True })
1414
{
1515

1616
}
1717

18-
public string Attribute => "VB_Exposed = True";
18+
public override string Attribute => "VB_Exposed";
1919
}
2020
}
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
using System.Collections.Generic;
2+
13
namespace Rubberduck.Parsing.Annotations
24
{
3-
public interface IAttributeAnnotation
5+
public interface IAttributeAnnotation : IAnnotation
46
{
57
string Attribute { get; }
8+
IReadOnlyList<string> AttributeValues { get; }
69
}
710
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using Rubberduck.Parsing.Grammar;
4+
using Rubberduck.VBEditor;
5+
6+
namespace Rubberduck.Parsing.Annotations
7+
{
8+
public class MemberAttributeAnnotation : AttributeAnnotationBase
9+
{
10+
public MemberAttributeAnnotation(QualifiedSelection qualifiedSelection, VBAParser.AnnotationContext context, IReadOnlyList<string> parameters)
11+
:base(AnnotationType.MemberAttribute, qualifiedSelection, context, parameters?.Skip(1).ToList())
12+
{
13+
Attribute = parameters?.FirstOrDefault() ?? string.Empty;
14+
}
15+
16+
public override string Attribute { get; }
17+
}
18+
}

0 commit comments

Comments
 (0)