Skip to content

Commit 76544ed

Browse files
authored
Merge pull request #4304 from rkapka/rkapka-master
Duplicated Annotation Inspection
2 parents e95bfbc + 4677c69 commit 76544ed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+702
-185
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using Rubberduck.Inspections.Abstract;
4+
using Rubberduck.Inspections.Results;
5+
using Rubberduck.Parsing.Inspections.Abstract;
6+
using Rubberduck.Parsing.VBA;
7+
using Rubberduck.Resources.Inspections;
8+
9+
namespace Rubberduck.Inspections.Concrete
10+
{
11+
public sealed class DuplicatedAnnotationInspection : InspectionBase
12+
{
13+
public DuplicatedAnnotationInspection(RubberduckParserState state) : base(state)
14+
{
15+
}
16+
17+
protected override IEnumerable<IInspectionResult> DoGetInspectionResults()
18+
{
19+
var issues = new List<DeclarationInspectionResult>();
20+
21+
foreach (var declaration in State.AllUserDeclarations)
22+
{
23+
var duplicateAnnotations = declaration.Annotations
24+
.GroupBy(annotation => annotation.AnnotationType)
25+
.Where(group => !group.First().AllowMultiple && group.Count() > 1);
26+
27+
issues.AddRange(duplicateAnnotations.Select(duplicate =>
28+
{
29+
var result = new DeclarationInspectionResult(
30+
this,
31+
string.Format(InspectionResults.DuplicatedAnnotationInspection, duplicate.Key.ToString()),
32+
declaration);
33+
34+
result.Properties.AnnotationType = duplicate.Key;
35+
36+
return result;
37+
}));
38+
}
39+
40+
return issues;
41+
}
42+
}
43+
}

Rubberduck.CodeAnalysis/Inspections/Concrete/IllegalAnnotationInspection.cs

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,6 @@ protected override IEnumerable<IInspectionResult> DoGetInspectionResults()
3535

3636
public class IllegalAttributeAnnotationsListener : VBAParserBaseListener, IInspectionListener
3737
{
38-
private static readonly AnnotationType[] AnnotationTypes = Enum.GetValues(typeof(AnnotationType)).Cast<AnnotationType>().ToArray();
39-
40-
private IDictionary<Tuple<QualifiedModuleName, AnnotationType>, int> _annotationCounts =
41-
new Dictionary<Tuple<QualifiedModuleName, AnnotationType>, int>();
42-
4338
private readonly RubberduckParserState _state;
4439

4540
private Lazy<Declaration> _module;
@@ -55,32 +50,19 @@ public IllegalAttributeAnnotationsListener(RubberduckParserState state)
5550

5651
public IReadOnlyList<QualifiedContext<ParserRuleContext>> Contexts => _contexts;
5752

58-
public QualifiedModuleName CurrentModuleName
59-
{
60-
get => _currentModuleName;
61-
set
62-
{
63-
_currentModuleName = value;
64-
foreach (var type in AnnotationTypes)
65-
{
66-
_annotationCounts.Add(Tuple.Create(value, type), 0);
67-
}
68-
}
69-
}
53+
public QualifiedModuleName CurrentModuleName { get; set; }
7054

7155
private bool _isFirstMemberProcessed;
7256

7357
public void ClearContexts()
7458
{
75-
_annotationCounts = new Dictionary<Tuple<QualifiedModuleName, AnnotationType>, int>();
7659
_contexts.Clear();
7760
_isFirstMemberProcessed = false;
7861
}
7962

8063
#region scoping
8164
private Declaration _currentScopeDeclaration;
8265
private bool _hasMembers;
83-
private QualifiedModuleName _currentModuleName;
8466

8567
private void SetCurrentScope(string memberName = null)
8668
{
@@ -168,8 +150,6 @@ public override void ExitAnnotation(VBAParser.AnnotationContext context)
168150
{
169151
var name = Identifier.GetName(context.annotationName().unrestrictedIdentifier());
170152
var annotationType = (AnnotationType) Enum.Parse(typeof (AnnotationType), name, true);
171-
var key = Tuple.Create(_currentModuleName, annotationType);
172-
_annotationCounts[key]++;
173153

174154
var moduleHasMembers = _members.Value.Any();
175155

@@ -183,9 +163,7 @@ public override void ExitAnnotation(VBAParser.AnnotationContext context)
183163
&& (_currentScopeDeclaration?.DeclarationType.HasFlag(DeclarationType.Member) ?? false);
184164

185165
var isIllegal = !(isMemberAnnotation && moduleHasMembers && !_isFirstMemberProcessed) &&
186-
(isModuleAnnotation && _annotationCounts[key] > 1
187-
|| isMemberAnnotatedForModuleAnnotation
188-
|| isModuleAnnotatedForMemberAnnotation);
166+
(isMemberAnnotatedForModuleAnnotation || isModuleAnnotatedForMemberAnnotation);
189167

190168
if (isIllegal)
191169
{
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Rubberduck.Inspections.Abstract;
5+
using Rubberduck.Inspections.Concrete;
6+
using Rubberduck.Parsing.Annotations;
7+
using Rubberduck.Parsing.Grammar;
8+
using Rubberduck.Parsing.Inspections.Abstract;
9+
using Rubberduck.Parsing.Rewriter;
10+
using Rubberduck.Parsing.VBA;
11+
12+
namespace Rubberduck.Inspections.QuickFixes
13+
{
14+
public class RemoveDuplicatedAnnotationQuickFix : QuickFixBase
15+
{
16+
private readonly RubberduckParserState _state;
17+
18+
public RemoveDuplicatedAnnotationQuickFix(RubberduckParserState state)
19+
: base(typeof(DuplicatedAnnotationInspection))
20+
{
21+
_state = state;
22+
}
23+
24+
public override void Fix(IInspectionResult result)
25+
{
26+
var rewriter = _state.GetRewriter(result.QualifiedSelection.QualifiedName);
27+
28+
var duplicateAnnotations = result.Target.Annotations
29+
.Where(annotation => annotation.AnnotationType == result.Properties.AnnotationType)
30+
.OrderBy(annotation => annotation.Context.Start.StartIndex)
31+
.Skip(1);
32+
33+
var duplicatesPerAnnotationList = duplicateAnnotations
34+
.Select(annotation => (VBAParser.AnnotationListContext) annotation.Context.Parent)
35+
.Distinct()
36+
.ToDictionary(list => list, _ => 0);
37+
38+
foreach (var annotation in duplicateAnnotations)
39+
{
40+
var annotationList = (VBAParser.AnnotationListContext)annotation.Context.Parent;
41+
42+
RemoveAnnotationMarker(annotationList, annotation, rewriter);
43+
RemoveWhiteSpaceAfterAnnotation(annotationList, annotation, rewriter);
44+
45+
rewriter.Remove(annotation.Context);
46+
47+
duplicatesPerAnnotationList[annotationList]++;
48+
}
49+
50+
foreach (var pair in duplicatesPerAnnotationList)
51+
{
52+
if (OnlyQuoteRemainedFromAnnotationList(pair))
53+
{
54+
rewriter.Remove(pair.Key);
55+
rewriter.Remove(((VBAParser.CommentOrAnnotationContext) pair.Key.Parent).NEWLINE());
56+
}
57+
}
58+
}
59+
60+
public override string Description(IInspectionResult result) =>
61+
Resources.Inspections.QuickFixes.RemoveDuplicatedAnnotationQuickFix;
62+
63+
public override bool CanFixInProcedure => true;
64+
public override bool CanFixInModule => true;
65+
public override bool CanFixInProject => true;
66+
67+
private static void RemoveAnnotationMarker(VBAParser.AnnotationListContext annotationList,
68+
IAnnotation annotation, IModuleRewriter rewriter)
69+
{
70+
var index = Array.IndexOf(annotationList.annotation(), annotation.Context);
71+
rewriter.Remove(annotationList.AT(index));
72+
}
73+
74+
private static void RemoveWhiteSpaceAfterAnnotation(VBAParser.AnnotationListContext annotationList,
75+
IAnnotation annotation, IModuleRewriter rewriter)
76+
{
77+
var whitespace = annotationList.whiteSpace().FirstOrDefault(ws =>
78+
ws.Start.StartIndex == annotation.Context.Stop.StopIndex + 1);
79+
80+
if (whitespace != null)
81+
{
82+
rewriter.Remove(whitespace);
83+
}
84+
}
85+
86+
private static bool OnlyQuoteRemainedFromAnnotationList(KeyValuePair<VBAParser.AnnotationListContext, int> pair)
87+
{
88+
return pair.Key.annotation().Length == pair.Value && pair.Key.commentBody() == null;
89+
}
90+
}
91+
}

Rubberduck.CodeAnalysis/Rubberduck.CodeAnalysis.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
<Compile Include="Inspections\Concrete\ApplicationWorksheetFunctionInspection.cs" />
7272
<Compile Include="Inspections\Concrete\AssignedByValParameterInspection.cs" />
7373
<Compile Include="Inspections\Concrete\DefTypeStatementInspection.cs" />
74+
<Compile Include="Inspections\Concrete\DuplicatedAnnotationInspection.cs" />
7475
<Compile Include="Inspections\Concrete\EmptyModuleInspection.cs" />
7576
<Compile Include="Inspections\Concrete\EmptyBlockInspectionListenerBase.cs" />
7677
<Compile Include="Inspections\Concrete\EmptyCaseBlockInspection.cs" />
@@ -148,6 +149,7 @@
148149
<Compile Include="Inspections\Concrete\ProcedureNotUsedInspection.cs" />
149150
<Compile Include="Properties\AssemblyInfo.cs" />
150151
<Compile Include="QuickFixes\AccessSheetUsingCodeNameQuickFix.cs" />
152+
<Compile Include="QuickFixes\RemoveDuplicatedAnnotationQuickFix.cs" />
151153
<Compile Include="QuickFixes\ReplaceIfElseWithConditionalStatementQuickFix.cs" />
152154
<Compile Include="QuickFixes\AddIdentifierToWhiteListQuickFix.cs" />
153155
<Compile Include="QuickFixes\AddStepOneQuickFix.cs" />

Rubberduck.Core/Properties/Settings.Designer.cs

Lines changed: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Rubberduck.Core/Properties/Settings.settings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@
278278
&lt;CodeInspection Name="SheetAccessedUsingStringInspection" Severity="Suggestion" InspectionType="LanguageOpportunities" /&gt;
279279
&lt;CodeInspection Name="ObsoleteMemberUsageInspection" Severity="Warning" InspectionType="MaintainabilityAndReadabilityIssues" /&gt;
280280
&lt;CodeInspection Name="ObsoleteCallingConventionInspection" Severity="Warning" InspectionType="CodeQualityIssues" /&gt;
281+
&lt;CodeInspection Name="DuplicatedAnnotationInspection" Severity="Error" InspectionType="RubberduckOpportunities" /&gt;
281282
&lt;/CodeInspections&gt;
282283
&lt;WhitelistedIdentifiers /&gt;
283284
&lt;RunInspectionsOnSuccessfulParse&gt;true&lt;/RunInspectionsOnSuccessfulParse&gt;

Rubberduck.Core/app.config

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,8 @@
400400
InspectionType="MaintainabilityAndReadabilityIssues" />
401401
<CodeInspection Name="ObsoleteCallingConventionInspection" Severity="Warning"
402402
InspectionType="CodeQualityIssues" />
403+
<CodeInspection Name="DuplicatedAnnotationInspection" Severity="Error"
404+
InspectionType="RubberduckOpportunities" />
403405
</CodeInspections>
404406
<WhitelistedIdentifiers />
405407
<RunInspectionsOnSuccessfulParse>true</RunInspectionsOnSuccessfulParse>

Rubberduck.Parsing/Annotations/AnnotationBase.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
1-
using Rubberduck.VBEditor;
1+
using Rubberduck.Parsing.Grammar;
2+
using Rubberduck.VBEditor;
23

34
namespace Rubberduck.Parsing.Annotations
45
{
56
public abstract class AnnotationBase : IAnnotation
67
{
78
public const string ANNOTATION_MARKER = "'@";
89

9-
protected AnnotationBase(AnnotationType annotationType, QualifiedSelection qualifiedSelection)
10+
protected AnnotationBase(AnnotationType annotationType, QualifiedSelection qualifiedSelection, VBAParser.AnnotationContext context)
1011
{
1112
AnnotationType = annotationType;
1213
QualifiedSelection = qualifiedSelection;
14+
Context = context;
1315
}
1416

1517
public AnnotationType AnnotationType { get; }
1618
public QualifiedSelection QualifiedSelection { get; }
19+
public VBAParser.AnnotationContext Context { get; }
20+
21+
public virtual bool AllowMultiple { get; } = false;
1722

1823
public override string ToString() => $"Annotation Type: {AnnotationType}";
1924
}

Rubberduck.Parsing/Annotations/AnnotationType.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ namespace Rubberduck.Parsing.Annotations
88
public enum AnnotationType
99
{
1010
/// <summary>
11-
/// A flag indicating that the annotation type is valid once per module.
11+
/// A flag indicating that the annotation type is valid for module.
1212
/// </summary>
1313
ModuleAnnotation = 1 << 1,
1414
/// <summary>
15-
/// A flag indicating that the annotation type is valid once per member.
15+
/// A flag indicating that the annotation type is valid for member.
1616
/// </summary>
1717
MemberAnnotation = 1 << 2,
1818

Rubberduck.Parsing/Annotations/DefaultMemberAnnotation.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections.Generic;
22
using System.Linq;
3+
using Rubberduck.Parsing.Grammar;
34
using Rubberduck.VBEditor;
45

56
namespace Rubberduck.Parsing.Annotations
@@ -9,8 +10,8 @@ namespace Rubberduck.Parsing.Annotations
910
/// </summary>
1011
public sealed class DefaultMemberAnnotation : AnnotationBase, IAttributeAnnotation
1112
{
12-
public DefaultMemberAnnotation(QualifiedSelection qualifiedSelection, IEnumerable<string> parameters)
13-
: base(AnnotationType.DefaultMember, qualifiedSelection)
13+
public DefaultMemberAnnotation(QualifiedSelection qualifiedSelection, VBAParser.AnnotationContext context, IEnumerable<string> parameters)
14+
: base(AnnotationType.DefaultMember, qualifiedSelection, context)
1415
{
1516
Description = parameters.FirstOrDefault();
1617
}

0 commit comments

Comments
 (0)