Skip to content

Commit 6c8f3ce

Browse files
committed
Add analyzer for public types in Support namespace
1 parent 8ff0cbc commit 6c8f3ce

File tree

7 files changed

+370
-0
lines changed

7 files changed

+370
-0
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
namespace Lucene.Net.Support.BlockScoped
2+
{
3+
public class PublicClass
4+
{
5+
}
6+
7+
public interface PublicInterface
8+
{
9+
}
10+
11+
public enum PublicEnum
12+
{
13+
}
14+
15+
public delegate void PublicDelegate();
16+
17+
public struct PublicStruct
18+
{
19+
}
20+
21+
public record PublicRecord
22+
{
23+
}
24+
25+
public record struct PublicRecordStruct
26+
{
27+
}
28+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
namespace Lucene.Net.Support;
2+
3+
public class PublicClass
4+
{
5+
}
6+
7+
public interface PublicInterface
8+
{
9+
}
10+
11+
public enum PublicEnum
12+
{
13+
}
14+
15+
public delegate void PublicDelegate();
16+
17+
public struct PublicStruct
18+
{
19+
}
20+
21+
public record PublicRecord
22+
{
23+
}
24+
25+
public record struct PublicRecordStruct
26+
{
27+
}

src/Lucene.Net.CodeAnalysis.Dev/AnalyzerReleases.Unshipped.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
LuceneDev1002 | Design | Warning | Floating point type arithmetic needs to be checked
88
LuceneDev1003 | Design | Warning | Method parameters that accept array types should be analyzed to determine whether they are better suited to be ref or out parameters
99
LuceneDev1004 | Design | Warning | Methods that return array types should be analyzed to determine whether they are better suited to be one or more out parameters or to return a ValueTuple
10+
LuceneDev1005 | Design | Warning | Types in the Lucene.Net.Support namespace should not be public
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using Lucene.Net.CodeAnalysis.Dev.Helpers;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.CodeAnalysis.CSharp;
4+
using Microsoft.CodeAnalysis.CSharp.Syntax;
5+
using Microsoft.CodeAnalysis.Diagnostics;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Collections.Immutable;
9+
using System.Linq;
10+
using System.Reflection.Metadata;
11+
using System.Threading;
12+
13+
namespace Lucene.Net.CodeAnalysis.Dev
14+
{
15+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
16+
public class LuceneDev1005_LuceneNetSupportPublicTypesCSCodeAnalyzer : DiagnosticAnalyzer
17+
{
18+
public const string DiagnosticId = "LuceneDev1005";
19+
20+
// You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat.
21+
// See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Localizing%20Analyzers.md for more on localization
22+
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.LuceneDev1005_AnalyzerTitle), Resources.ResourceManager, typeof(Resources));
23+
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.LuceneDev1005_AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
24+
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.LuceneDev1005_AnalyzerDescription), Resources.ResourceManager, typeof(Resources));
25+
private const string Category = "Design";
26+
27+
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
28+
29+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule];
30+
31+
public override void Initialize(AnalysisContext context)
32+
{
33+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
34+
context.EnableConcurrentExecution();
35+
context.RegisterSyntaxNodeAction(AnalyzeSyntax,
36+
SyntaxKind.ClassDeclaration,
37+
SyntaxKind.EnumDeclaration,
38+
SyntaxKind.InterfaceDeclaration,
39+
SyntaxKind.RecordDeclaration,
40+
SyntaxKind.StructDeclaration,
41+
SyntaxKind.RecordStructDeclaration,
42+
SyntaxKind.DelegateDeclaration);
43+
}
44+
45+
private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
46+
{
47+
// any public types in the Lucene.Net.Support (or child) namespace should raise the diagnostic
48+
if (context.Node.Parent is not BaseNamespaceDeclarationSyntax namespaceDeclarationSyntax
49+
|| !namespaceDeclarationSyntax.Name.ToString().StartsWith("Lucene.Net.Support"))
50+
{
51+
return;
52+
}
53+
54+
if (context.Node is DelegateDeclarationSyntax delegateDeclarationSyntax
55+
&& delegateDeclarationSyntax.Modifiers.Any(SyntaxKind.PublicKeyword))
56+
{
57+
const string typeKind = "Delegate";
58+
context.ReportDiagnostic(Diagnostic.Create(Rule, delegateDeclarationSyntax.GetLocation(), typeKind, delegateDeclarationSyntax.Identifier.ToString()));
59+
}
60+
else if (context.Node is BaseTypeDeclarationSyntax baseTypeDeclarationSyntax
61+
&& baseTypeDeclarationSyntax.Modifiers.Any(SyntaxKind.PublicKeyword))
62+
{
63+
var typeKind = context.Node switch
64+
{
65+
ClassDeclarationSyntax => "Class",
66+
EnumDeclarationSyntax => "Enum",
67+
InterfaceDeclarationSyntax => "Interface",
68+
RecordDeclarationSyntax record when record.ClassOrStructKeyword.IsKind(SyntaxKind.StructKeyword) => "Record struct",
69+
RecordDeclarationSyntax => "Record",
70+
StructDeclarationSyntax => "Struct",
71+
_ => "Type", // should not happen
72+
};
73+
74+
context.ReportDiagnostic(Diagnostic.Create(Rule, baseTypeDeclarationSyntax.GetLocation(), typeKind, baseTypeDeclarationSyntax.Identifier.ToString()));
75+
}
76+
}
77+
}
78+
}

src/Lucene.Net.CodeAnalysis.Dev/Resources.Designer.cs

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

src/Lucene.Net.CodeAnalysis.Dev/Resources.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,4 +177,13 @@
177177
<value>Methods that return array types should be analyzed to determine whether they are better suited to be one or more out parameters or to return a ValueTuple</value>
178178
<comment>The title of the diagnostic.</comment>
179179
</data>
180+
<data name="LuceneDev1005_AnalyzerTitle" xml:space="preserve">
181+
<value>Types in the Support namespace should not be public</value>
182+
</data>
183+
<data name="LuceneDev1005_AnalyzerDescription" xml:space="preserve">
184+
<value>Types in the Lucene.Net.Support namespace should not be public.</value>
185+
</data>
186+
<data name="LuceneDev1005_AnalyzerMessageFormat" xml:space="preserve">
187+
<value>{0} '{1}' should not have public accessibility in the Support namespace</value>
188+
</data>
180189
</root>
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
using Lucene.Net.CodeAnalysis.Dev;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.CodeAnalysis.Diagnostics;
4+
using NUnit.Framework;
5+
using TestHelper;
6+
7+
namespace Lucene.Net.Tests.CodeAnalysis.Dev
8+
{
9+
public class TestLuceneDev1005_LuceneNetSupportPublicTypesCSCodeAnalyzer : DiagnosticVerifier
10+
{
11+
protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer()
12+
{
13+
return new LuceneDev1005_LuceneNetSupportPublicTypesCSCodeAnalyzer();
14+
}
15+
16+
//No diagnostics expected to show up
17+
[Test]
18+
public void TestEmptyFile()
19+
{
20+
const string test = "";
21+
22+
VerifyCSharpDiagnostic(test);
23+
}
24+
25+
[Test]
26+
[TestCase("class")]
27+
[TestCase("struct")]
28+
[TestCase("interface")]
29+
[TestCase("record")]
30+
[TestCase("record struct")]
31+
[TestCase("enum")]
32+
public void TestDiagnostic_FileScopedNamespace_PositiveTest(string typeKind)
33+
{
34+
string typeKindDesc = typeKind[0].ToString().ToUpper() + typeKind[1..];
35+
string typeName = $"Public{typeKindDesc.Replace(" ", "")}";
36+
37+
string test =
38+
$"""
39+
namespace Lucene.Net.Support;
40+
public {typeKind} {typeName};
41+
""";
42+
43+
var expected = new DiagnosticResult
44+
{
45+
Id = LuceneDev1005_LuceneNetSupportPublicTypesCSCodeAnalyzer.DiagnosticId,
46+
Message = $"{typeKindDesc} '{typeName}' should not have public accessibility in the Support namespace",
47+
Severity = DiagnosticSeverity.Warning,
48+
Locations =
49+
[
50+
new DiagnosticResultLocation("Test0.cs", 2, 1),
51+
]
52+
};
53+
54+
VerifyCSharpDiagnostic(test, expected);
55+
}
56+
57+
[Test]
58+
[TestCase("class")]
59+
[TestCase("struct")]
60+
[TestCase("interface")]
61+
[TestCase("record")]
62+
[TestCase("record struct")]
63+
[TestCase("enum")]
64+
public void TestDiagnostic_FileScopedNamespace_NegativeTest(string typeKind)
65+
{
66+
string typeKindDesc = typeKind[0].ToString().ToUpper() + typeKind[1..];
67+
string typeName = $"Public{typeKindDesc.Replace(" ", "")}";
68+
69+
string test =
70+
$"""
71+
namespace Lucene.Net.SomethingElse;
72+
public {typeKind} {typeName};
73+
""";
74+
75+
VerifyCSharpDiagnostic(test);
76+
}
77+
78+
[Test]
79+
public void TestDiagnostic_FileScopedNamespace_Delegate_PositiveTest()
80+
{
81+
const string test =
82+
"""
83+
namespace Lucene.Net.Support;
84+
public delegate void PublicDelegate();
85+
""";
86+
87+
var expected = new DiagnosticResult
88+
{
89+
Id = LuceneDev1005_LuceneNetSupportPublicTypesCSCodeAnalyzer.DiagnosticId,
90+
Message = "Delegate 'PublicDelegate' should not have public accessibility in the Support namespace",
91+
Severity = DiagnosticSeverity.Warning,
92+
Locations =
93+
[
94+
new DiagnosticResultLocation("Test0.cs", 2, 1),
95+
]
96+
};
97+
98+
VerifyCSharpDiagnostic(test, expected);
99+
}
100+
101+
[Test]
102+
public void TestDiagnostic_FileScopedNamespace_Delegate_NegativeTest()
103+
{
104+
const string test =
105+
"""
106+
namespace Lucene.Net.SomethingElse;
107+
public delegate void PublicDelegate();
108+
""";
109+
110+
VerifyCSharpDiagnostic(test);
111+
}
112+
113+
[Test]
114+
[TestCase("class")]
115+
[TestCase("struct")]
116+
[TestCase("interface")]
117+
[TestCase("record")]
118+
[TestCase("record struct")]
119+
[TestCase("enum")]
120+
public void TestDiagnostic_BlockScopedNamespace_PositiveTest(string typeKind)
121+
{
122+
string typeKindDesc = typeKind[0].ToString().ToUpper() + typeKind[1..];
123+
string typeName = $"Public{typeKindDesc.Replace(" ", "")}";
124+
125+
string test =
126+
$$"""
127+
namespace Lucene.Net.Support
128+
{
129+
public {{typeKind}} {{typeName}};
130+
}
131+
""";
132+
133+
var expected = new DiagnosticResult
134+
{
135+
Id = LuceneDev1005_LuceneNetSupportPublicTypesCSCodeAnalyzer.DiagnosticId,
136+
Message = $"{typeKindDesc} '{typeName}' should not have public accessibility in the Support namespace",
137+
Severity = DiagnosticSeverity.Warning,
138+
Locations =
139+
[
140+
new DiagnosticResultLocation("Test0.cs", 3, 5),
141+
]
142+
};
143+
144+
VerifyCSharpDiagnostic(test, expected);
145+
}
146+
147+
[Test]
148+
[TestCase("class")]
149+
[TestCase("struct")]
150+
[TestCase("interface")]
151+
[TestCase("record")]
152+
[TestCase("record struct")]
153+
[TestCase("enum")]
154+
public void TestDiagnostic_BlockScopedNamespace_NegativeTest(string typeKind)
155+
{
156+
string typeKindDesc = typeKind[0].ToString().ToUpper() + typeKind[1..];
157+
string typeName = $"Public{typeKindDesc.Replace(" ", "")}";
158+
159+
string test =
160+
$$"""
161+
namespace Lucene.Net.SomethingElse
162+
{
163+
public {{typeKind}} {{typeName}};
164+
}
165+
""";
166+
167+
VerifyCSharpDiagnostic(test);
168+
}
169+
170+
[Test]
171+
public void TestDiagnostic_BlockScopedNamespace_Delegate_PositiveTest()
172+
{
173+
const string test =
174+
"""
175+
namespace Lucene.Net.Support
176+
{
177+
public delegate void PublicDelegate();
178+
}
179+
""";
180+
181+
var expected = new DiagnosticResult
182+
{
183+
Id = LuceneDev1005_LuceneNetSupportPublicTypesCSCodeAnalyzer.DiagnosticId,
184+
Message = "Delegate 'PublicDelegate' should not have public accessibility in the Support namespace",
185+
Severity = DiagnosticSeverity.Warning,
186+
Locations =
187+
[
188+
new DiagnosticResultLocation("Test0.cs", 3, 5),
189+
]
190+
};
191+
192+
VerifyCSharpDiagnostic(test, expected);
193+
}
194+
195+
[Test]
196+
public void TestDiagnostic_BlockScopedNamespace_Delegate_NegativeTest()
197+
{
198+
const string test =
199+
"""
200+
namespace Lucene.Net.SomethingElse
201+
{
202+
public delegate void PublicDelegate();
203+
}
204+
""";
205+
206+
VerifyCSharpDiagnostic(test);
207+
}
208+
}
209+
}

0 commit comments

Comments
 (0)