Skip to content

Commit fa11b30

Browse files
committed
Add analyzer for public types in Support namespace
1 parent 9479165 commit fa11b30

File tree

14 files changed

+726
-11
lines changed

14 files changed

+726
-11
lines changed

.globalconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
roslyn_correctness.assembly_reference_validation = relaxed
2+

src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/CodeFixResources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,7 @@ under the License.
122122
<value>Use {0}</value>
123123
<comment>Title for code fix; {0} is the code element to utilize.</comment>
124124
</data>
125+
<data name="MakeXInternal" xml:space="preserve">
126+
<value>Make {0} internal</value>
127+
</data>
125128
</root>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
using System.Collections.Immutable;
19+
using System.Composition;
20+
using System.Linq;
21+
using System.Threading;
22+
using System.Threading.Tasks;
23+
using Lucene.Net.CodeAnalysis.Dev.CodeFixes;
24+
using Lucene.Net.CodeAnalysis.Dev.CodeFixes.Utility;
25+
using Lucene.Net.CodeAnalysis.Dev.Utility;
26+
using Microsoft.CodeAnalysis;
27+
using Microsoft.CodeAnalysis.CodeFixes;
28+
using Microsoft.CodeAnalysis.CSharp.Syntax;
29+
using SyntaxFactory = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
30+
using SyntaxKind = Microsoft.CodeAnalysis.CSharp.SyntaxKind;
31+
32+
namespace Lucene.Net.CodeAnalysis.Dev;
33+
34+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(LuceneDev1005_LuceneNetSupportPublicTypesCsCodeFixProvider)), Shared]
35+
public class LuceneDev1005_LuceneNetSupportPublicTypesCsCodeFixProvider : CodeFixProvider
36+
{
37+
// Specify the diagnostic IDs of analyzers that are expected to be linked.
38+
public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } =
39+
[Descriptors.LuceneDev1005_LuceneNetSupportPublicTypes.Id];
40+
41+
public override FixAllProvider? GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
42+
43+
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
44+
{
45+
// We link only one diagnostic and assume there is only one diagnostic in the context.
46+
var diagnostic = context.Diagnostics.Single();
47+
48+
// 'SourceSpan' of 'Location' is the highlighted area. We're going to use this area to find the 'SyntaxNode' to rename.
49+
var diagnosticSpan = diagnostic.Location.SourceSpan;
50+
51+
// Get the root of Syntax Tree that contains the highlighted diagnostic.
52+
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
53+
54+
// Find SyntaxNode corresponding to the diagnostic.
55+
var diagnosticNode = root?.FindNode(diagnosticSpan);
56+
57+
if (diagnosticNode is MemberDeclarationSyntax declaration)
58+
{
59+
var name = declaration switch
60+
{
61+
BaseTypeDeclarationSyntax baseTypeDeclaration => baseTypeDeclaration.Identifier.ToString(),
62+
DelegateDeclarationSyntax delegateDeclaration => delegateDeclaration.Identifier.ToString(),
63+
_ => null
64+
};
65+
66+
if (name == null)
67+
{
68+
return;
69+
}
70+
71+
// Register a code action that will invoke the fix.
72+
context.RegisterCodeFix(
73+
CodeActionHelper.CreateFromResource(
74+
CodeFixResources.MakeXInternal,
75+
createChangedSolution: c => MakeDeclarationInternal(context.Document, declaration, c),
76+
"MakeDeclarationInternal",
77+
name),
78+
diagnostic);
79+
}
80+
}
81+
82+
private async Task<Solution> MakeDeclarationInternal(Document document,
83+
MemberDeclarationSyntax memberDeclaration,
84+
CancellationToken cancellationToken)
85+
{
86+
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
87+
if (syntaxRoot == null) return document.Project.Solution;
88+
89+
// Remove existing accessibility modifiers
90+
var newModifiers = SyntaxFactory.TokenList(
91+
memberDeclaration.Modifiers
92+
.Where(modifier => !modifier.IsKind(SyntaxKind.PrivateKeyword) &&
93+
!modifier.IsKind(SyntaxKind.ProtectedKeyword) &&
94+
!modifier.IsKind(SyntaxKind.InternalKeyword) &&
95+
!modifier.IsKind(SyntaxKind.PublicKeyword))
96+
).Insert(0, SyntaxFactory.Token(SyntaxKind.InternalKeyword)); // Ensure 'internal' is the first modifier
97+
98+
var newMemberDeclaration = memberDeclaration.WithModifiers(newModifiers);
99+
var newRoot = syntaxRoot.ReplaceNode(memberDeclaration, newMemberDeclaration);
100+
return document.Project.Solution.WithDocumentSyntaxRoot(document.Id, newRoot);
101+
}
102+
}

src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/Utility/CodeActionHelper.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,18 @@ public static CodeAction CreateFromResource(
3737
var title = string.Format(resourceValue, args);
3838
return CodeAction.Create(title, createChangedDocument, equivalenceKey);
3939
}
40+
41+
/// <summary>
42+
/// Create a CodeAction using a resource string and formatting arguments.
43+
/// </summary>
44+
public static CodeAction CreateFromResource(
45+
string resourceValue,
46+
Func<CancellationToken, Task<Solution>> createChangedSolution,
47+
string equivalenceKey,
48+
params object[] args)
49+
{
50+
var title = string.Format(resourceValue, args);
51+
return CodeAction.Create(title, createChangedSolution, equivalenceKey);
52+
}
4053
}
4154
}

src/Lucene.Net.CodeAnalysis.Dev.Sample/Lucene.Net.CodeAnalysis.Dev.Sample.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ under the License.
3232
<_PackageVersionPropsFilePath>$(_NuGetPackageOutputPath)\Lucene.Net.CodeAnalysis.Dev.Version.props</_PackageVersionPropsFilePath>
3333
<!-- We install the analyzer package in a local directory so we don't pollute the
3434
.nuget cache on the dev machine with temporary builds -->
35-
<RestorePackagesPath>obj\LocalNuGetPackages</RestorePackagesPath>
36-
<_RestorePackagesPath>$(RestorePackagesPath)\lucene.net.codeanalsis.dev</_RestorePackagesPath>
35+
<RestorePackagesPath>obj/LocalNuGetPackages</RestorePackagesPath>
36+
<_RestorePackagesPath>$(RestorePackagesPath)/lucene.net.codeanalysis.dev</_RestorePackagesPath>
3737
</PropertyGroup>
3838

3939
<PropertyGroup Condition="Exists('$(_NuGetPackageOutputPath)')">
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+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
### New Rules
22

3-
Rule ID | Category | Severity | Notes
3+
Rule ID | Category | Severity | Notes
44
---------------|----------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------
5+
LuceneDev1005 | Design | Warning | Types in the Lucene.Net.Support namespace should not be public
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
using Microsoft.CodeAnalysis;
21+
using Microsoft.CodeAnalysis.CSharp;
22+
using Microsoft.CodeAnalysis.CSharp.Syntax;
23+
using Microsoft.CodeAnalysis.Diagnostics;
24+
using System.Collections.Immutable;
25+
using Lucene.Net.CodeAnalysis.Dev.Utility;
26+
27+
namespace Lucene.Net.CodeAnalysis.Dev
28+
{
29+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
30+
public class LuceneDev1005_LuceneNetSupportPublicTypesCSCodeAnalyzer : DiagnosticAnalyzer
31+
{
32+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Descriptors.LuceneDev1005_LuceneNetSupportPublicTypes];
33+
34+
public override void Initialize(AnalysisContext context)
35+
{
36+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
37+
context.EnableConcurrentExecution();
38+
context.RegisterSyntaxNodeAction(AnalyzeSyntax,
39+
SyntaxKind.ClassDeclaration,
40+
SyntaxKind.EnumDeclaration,
41+
SyntaxKind.InterfaceDeclaration,
42+
SyntaxKind.RecordDeclaration,
43+
SyntaxKind.StructDeclaration,
44+
SyntaxKind.RecordStructDeclaration,
45+
SyntaxKind.DelegateDeclaration);
46+
}
47+
48+
private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
49+
{
50+
// any public types in the Lucene.Net.Support (or child) namespace should raise the diagnostic
51+
if (context.Node.Parent is not BaseNamespaceDeclarationSyntax namespaceDeclarationSyntax
52+
|| !namespaceDeclarationSyntax.Name.ToString().StartsWith("Lucene.Net.Support"))
53+
{
54+
return;
55+
}
56+
57+
if (context.Node is DelegateDeclarationSyntax delegateDeclarationSyntax
58+
&& delegateDeclarationSyntax.Modifiers.Any(SyntaxKind.PublicKeyword))
59+
{
60+
const string typeKind = "Delegate";
61+
context.ReportDiagnostic(Diagnostic.Create(Descriptors.LuceneDev1005_LuceneNetSupportPublicTypes, delegateDeclarationSyntax.GetLocation(), typeKind, delegateDeclarationSyntax.Identifier.ToString()));
62+
}
63+
else if (context.Node is BaseTypeDeclarationSyntax baseTypeDeclarationSyntax
64+
&& baseTypeDeclarationSyntax.Modifiers.Any(SyntaxKind.PublicKeyword))
65+
{
66+
var typeKind = context.Node switch
67+
{
68+
ClassDeclarationSyntax => "Class",
69+
EnumDeclarationSyntax => "Enum",
70+
InterfaceDeclarationSyntax => "Interface",
71+
RecordDeclarationSyntax record when record.ClassOrStructKeyword.IsKind(SyntaxKind.StructKeyword) => "Record struct",
72+
RecordDeclarationSyntax => "Record",
73+
StructDeclarationSyntax => "Struct",
74+
_ => "Type", // should not happen
75+
};
76+
77+
context.ReportDiagnostic(Diagnostic.Create(Descriptors.LuceneDev1005_LuceneNetSupportPublicTypes, baseTypeDeclarationSyntax.GetLocation(), typeKind, baseTypeDeclarationSyntax.Identifier.ToString()));
78+
}
79+
}
80+
}
81+
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,16 @@ under the License.
197197
<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>
198198
<comment>The title of the diagnostic.</comment>
199199
</data>
200+
<data name="LuceneDev1005_AnalyzerTitle" xml:space="preserve">
201+
<value>Types in the Support namespace should not be public</value>
202+
</data>
203+
<data name="LuceneDev1005_AnalyzerDescription" xml:space="preserve">
204+
<value>Types in the Lucene.Net.Support namespace should not be public.</value>
205+
</data>
206+
<data name="LuceneDev1005_AnalyzerMessageFormat" xml:space="preserve">
207+
<value>{0} '{1}' should not have public accessibility in the Support namespace</value>
208+
</data>
209+
<data name="LuceneDev1005_CodeFixTitle" xml:space="preserve">
210+
<value>Make {0} internal</value>
211+
</data>
200212
</root>

0 commit comments

Comments
 (0)