You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucenenet.apache.org by pa...@apache.org on 2026/10/19 02:48:25 UTC
(lucenenet-codeanalysis-dev) branch main updated: Add TestApiConsistency analyzers for #1359 (#44)
This is an automated email from the ASF dual-hosted git repository.
paulirwin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/lucenenet-codeanalysis-dev.git
The following commit(s) were added to refs/heads/main by this push:
new b298fbb Add TestApiConsistency analyzers for #1359 (#44)
b298fbb is described below
commit b298fbb41eeb4c1a37bb4592725bb6480c2a9490
Author: Paul Irwin <pa...@gmail.com>
AuthorDate: Sun Oct 18 20:48:20 2026 -0600
Add TestApiConsistency analyzers for #1359 (#44)
* Add TestApiConsistency analyzers for #1359
Port the LUCENENET TestApiConsistency (ApiScanTestBase) rules from unit
tests into Dev analyzers so violations surface in the IDE and at build
time. Adds 16 analyzers:
Naming (LuceneDev7000-LuceneDev7008): private/protected field names,
method parameter names, interface names, class names (incl. delegates),
members containing 'Comparator', members named 'Size', and member/type
names containing non-.NET numeric terms.
Design (LuceneDev1009-LuceneDev1015): public fields, setter-only
properties, properties returning arrays, methods returning writable
arrays, nullable enum members, IEnumerable members (disabled by default,
a one-time port aid), and List/Dictionary members.
Rename code fixes are provided for the protected-field ('m_' prefix) and
interface ('I' prefix) naming rules. Behavior mirrors the original
reflection-based tests, including the LUCENENET suppression attributes
([WritableArray], [ExceptionToClassNameConvention], etc.) matched by
name. LuceneDev1012 reproduces the original IL 'returns a field directly'
check via method-body syntax analysis. Includes samples and unit tests
for all rules.
Co-Authored-By: Claude Opus 4.8 (1M context) <no...@anthropic.com>
* Address PR review feedback for #1359
- LuceneDev1012: detect nullable-annotated array return types (e.g. int[]?)
- LuceneDev7003 code fix: offer rename for wrong-case 'I' prefix (e.g. iFoo -> IFoo)
- LuceneDev7005: include explicit interface implementations, matching the
original reflection-based ApiScanTestBase scan
Co-Authored-By: Claude Opus 5.5 <no...@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <no...@anthropic.com>
---
.../CodeFixResources.resx | 4 +
...eneDev7001_ProtectedFieldNameCodeFixProvider.cs | 82 ++++++++++
.../LuceneDev7003_InterfaceNameCodeFixProvider.cs | 82 ++++++++++
.../Utility/FieldRenameHelper.cs | 74 +++++++++
.../LuceneDev1009Sample.cs | 26 +++
.../LuceneDev1010Sample.cs | 31 ++++
.../LuceneDev1011Sample.cs | 28 ++++
.../LuceneDev1012Sample.cs | 31 ++++
.../LuceneDev1013Sample.cs | 32 ++++
.../LuceneDev1014Sample.cs | 31 ++++
.../LuceneDev1015Sample.cs | 31 ++++
.../LuceneDev7000Sample.cs | 28 ++++
.../LuceneDev7001Sample.cs | 28 ++++
.../LuceneDev7002Sample.cs | 26 +++
.../LuceneDev7003Sample.cs | 26 +++
.../LuceneDev7004Sample.cs | 26 +++
.../LuceneDev7005Sample.cs | 26 +++
.../LuceneDev7006Sample.cs | 26 +++
.../LuceneDev7007Sample.cs | 26 +++
.../LuceneDev7008Sample.cs | 26 +++
.../AnalyzerReleases.Unshipped.md | 16 ++
.../LuceneDev1009_PublicFieldsAnalyzer.cs | 83 ++++++++++
.../LuceneDev1010_PropertyWithNoGetterAnalyzer.cs | 75 +++++++++
.../LuceneDev1011_PropertyReturnsArrayAnalyzer.cs | 80 ++++++++++
...neDev1012_MethodReturnsWritableArrayAnalyzer.cs | 127 +++++++++++++++
.../LuceneDev1013_NullableEnumAnalyzer.cs | 158 ++++++++++++++++++
...14_MemberAcceptsOrReturnsIEnumerableAnalyzer.cs | 44 ++++++
...mberAcceptsOrReturnsListOrDictionaryAnalyzer.cs | 58 +++++++
.../LuceneDev1xxx/MembersUsingTypeAnalyzerBase.cs | 141 +++++++++++++++++
.../LuceneDev7000_PrivateFieldNameAnalyzer.cs | 76 +++++++++
.../LuceneDev7001_ProtectedFieldNameAnalyzer.cs | 87 ++++++++++
.../LuceneDev7002_MethodParameterNameAnalyzer.cs | 86 ++++++++++
.../LuceneDev7003_InterfaceNameAnalyzer.cs | 69 ++++++++
.../LuceneDev7004_ClassNameAnalyzer.cs | 78 +++++++++
...LuceneDev7005_MemberContainsComparerAnalyzer.cs | 140 ++++++++++++++++
.../LuceneDev7006_MemberNamedSizeAnalyzer.cs | 49 ++++++
...eDev7007_MemberContainsNonNetNumericAnalyzer.cs | 46 ++++++
...eneDev7008_TypeContainsNonNetNumericAnalyzer.cs | 68 ++++++++
.../MemberNamePredicateAnalyzerBase.cs | 143 +++++++++++++++++
src/Lucene.Net.CodeAnalysis.Dev/Resources.resx | 176 +++++++++++++++++++++
.../Utility/ApiConventionHelper.cs | 79 +++++++++
.../Utility/Descriptors.LuceneDev1xxx.cs | 52 ++++++
...ceneDev1xxx.cs => Descriptors.LuceneDev7xxx.cs} | 56 +++----
.../Utility/FieldNameHelper.cs | 64 ++++++++
.../Utility/NamingHelper.cs | 62 ++++++++
...eneDev7001_ProtectedFieldNameCodeFixProvider.cs | 69 ++++++++
...stLuceneDev7003_InterfaceNameCodeFixProvider.cs | 121 ++++++++++++++
.../TestLuceneDev1009_PublicFieldsAnalyzer.cs | 107 +++++++++++++
...stLuceneDev1010_PropertyWithNoGetterAnalyzer.cs | 98 ++++++++++++
...stLuceneDev1011_PropertyReturnsArrayAnalyzer.cs | 72 +++++++++
...neDev1012_MethodReturnsWritableArrayAnalyzer.cs | 146 +++++++++++++++++
.../TestLuceneDev1013_NullableEnumAnalyzer.cs | 121 ++++++++++++++
...14_MemberAcceptsOrReturnsIEnumerableAnalyzer.cs | 100 ++++++++++++
...mberAcceptsOrReturnsListOrDictionaryAnalyzer.cs | 138 ++++++++++++++++
.../TestLuceneDev7000_PrivateFieldNameAnalyzer.cs | 126 +++++++++++++++
...TestLuceneDev7001_ProtectedFieldNameAnalyzer.cs | 136 ++++++++++++++++
...estLuceneDev7002_MethodParameterNameAnalyzer.cs | 95 +++++++++++
.../TestLuceneDev7003_InterfaceNameAnalyzer.cs | 91 +++++++++++
.../TestLuceneDev7004_ClassNameAnalyzer.cs | 129 +++++++++++++++
...LuceneDev7005_MemberContainsComparerAnalyzer.cs | 158 ++++++++++++++++++
.../TestLuceneDev7006_MemberNamedSizeAnalyzer.cs | 113 +++++++++++++
...eDev7007_MemberContainsNonNetNumericAnalyzer.cs | 78 +++++++++
...eneDev7008_TypeContainsNonNetNumericAnalyzer.cs | 69 ++++++++
63 files changed, 4742 insertions(+), 28 deletions(-)
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/CodeFixResources.resx b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/CodeFixResources.resx
index 0c5634c..878bb89 100644
--- a/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/CodeFixResources.resx
+++ b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/CodeFixResources.resx
@@ -125,4 +125,8 @@ under the License.
<data name="MakeXInternal" xml:space="preserve">
<value>Make {0} internal</value>
</data>
+ <data name="RenameToX" xml:space="preserve">
+ <value>Rename to '{0}'</value>
+ <comment>Title for a rename code fix; {0} is the suggested new name.</comment>
+ </data>
</root>
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev7xxx/LuceneDev7001_ProtectedFieldNameCodeFixProvider.cs b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev7xxx/LuceneDev7001_ProtectedFieldNameCodeFixProvider.cs
new file mode 100644
index 0000000..faa8248
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev7xxx/LuceneDev7001_ProtectedFieldNameCodeFixProvider.cs
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Collections.Immutable;
+using System.Composition;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.CodeFixes;
+using Lucene.Net.CodeAnalysis.Dev.CodeFixes.Utility;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.Rename;
+
+namespace Lucene.Net.CodeAnalysis.Dev;
+
+/// <summary>
+/// Code fix for <see cref="Descriptors.LuceneDev7001_ProtectedFieldName"/>: renames a protected
+/// field to the conventional 'm_' prefix followed by camelCase.
+/// </summary>
+[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(LuceneDev7001_ProtectedFieldNameCodeFixProvider)), Shared]
+public class LuceneDev7001_ProtectedFieldNameCodeFixProvider : CodeFixProvider
+{
+ public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } =
+ [Descriptors.LuceneDev7001_ProtectedFieldName.Id];
+
+ // Renames touch the whole solution; the batch fixer is not appropriate.
+ public sealed override FixAllProvider? GetFixAllProvider() => null;
+
+ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ var diagnostic = context.Diagnostics.First();
+ var diagnosticSpan = diagnostic.Location.SourceSpan;
+
+ var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
+ var node = root?.FindNode(diagnosticSpan);
+ if (node is null)
+ return;
+
+ var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
+ if (semanticModel is null)
+ return;
+
+ var symbol = semanticModel.GetDeclaredSymbol(node, context.CancellationToken);
+ if (symbol is not IFieldSymbol fieldSymbol)
+ return;
+
+ var newName = FieldRenameHelper.ToProtectedFieldName(fieldSymbol.Name);
+ if (newName is null || newName == fieldSymbol.Name)
+ return;
+
+ context.RegisterCodeFix(
+ CodeActionHelper.CreateFromResource(
+ CodeFixResources.RenameToX,
+ createChangedSolution: c => RenameAsync(context.Document, fieldSymbol, newName, c),
+ $"RenameProtectedField_{newName}",
+ newName),
+ diagnostic);
+ }
+
+ private static async Task<Solution> RenameAsync(Document document, ISymbol symbol, string newName, CancellationToken cancellationToken)
+ {
+ var solution = document.Project.Solution;
+ var options = new SymbolRenameOptions();
+ return await Renamer.RenameSymbolAsync(solution, symbol, options, newName, cancellationToken).ConfigureAwait(false);
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev7xxx/LuceneDev7003_InterfaceNameCodeFixProvider.cs b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev7xxx/LuceneDev7003_InterfaceNameCodeFixProvider.cs
new file mode 100644
index 0000000..06e8bca
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev7xxx/LuceneDev7003_InterfaceNameCodeFixProvider.cs
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Collections.Immutable;
+using System.Composition;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.CodeFixes;
+using Lucene.Net.CodeAnalysis.Dev.CodeFixes.Utility;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.Rename;
+
+namespace Lucene.Net.CodeAnalysis.Dev;
+
+/// <summary>
+/// Code fix for <see cref="Descriptors.LuceneDev7003_InterfaceName"/>: renames an interface to
+/// begin with a capital 'I'.
+/// </summary>
+[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(LuceneDev7003_InterfaceNameCodeFixProvider)), Shared]
+public class LuceneDev7003_InterfaceNameCodeFixProvider : CodeFixProvider
+{
+ public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } =
+ [Descriptors.LuceneDev7003_InterfaceName.Id];
+
+ // Renames touch the whole solution; the batch fixer is not appropriate.
+ public sealed override FixAllProvider? GetFixAllProvider() => null;
+
+ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ var diagnostic = context.Diagnostics.First();
+ var diagnosticSpan = diagnostic.Location.SourceSpan;
+
+ var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
+ var node = root?.FindNode(diagnosticSpan);
+ if (node is null)
+ return;
+
+ var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
+ if (semanticModel is null)
+ return;
+
+ var symbol = semanticModel.GetDeclaredSymbol(node, context.CancellationToken);
+ if (symbol is not INamedTypeSymbol typeSymbol || typeSymbol.TypeKind != TypeKind.Interface)
+ return;
+
+ var newName = FieldRenameHelper.ToInterfaceName(typeSymbol.Name);
+ if (newName is null || newName == typeSymbol.Name)
+ return;
+
+ context.RegisterCodeFix(
+ CodeActionHelper.CreateFromResource(
+ CodeFixResources.RenameToX,
+ createChangedSolution: c => RenameAsync(context.Document, typeSymbol, newName, c),
+ $"RenameInterface_{newName}",
+ newName),
+ diagnostic);
+ }
+
+ private static async Task<Solution> RenameAsync(Document document, ISymbol symbol, string newName, CancellationToken cancellationToken)
+ {
+ var solution = document.Project.Solution;
+ var options = new SymbolRenameOptions();
+ return await Renamer.RenameSymbolAsync(solution, symbol, options, newName, cancellationToken).ConfigureAwait(false);
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/Utility/FieldRenameHelper.cs b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/Utility/FieldRenameHelper.cs
new file mode 100644
index 0000000..8f7b239
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/Utility/FieldRenameHelper.cs
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Globalization;
+
+namespace Lucene.Net.CodeAnalysis.Dev.CodeFixes.Utility;
+
+/// <summary>
+/// Helpers that compute conventional names used by the naming code fixes.
+/// </summary>
+internal static class FieldRenameHelper
+{
+ /// <summary>
+ /// Computes the conventional protected field name ('m_' prefix followed by camelCase) for the
+ /// given field name, or <c>null</c> when a sensible suggestion cannot be produced (e.g. the name
+ /// is an UPPER_CASE constant, which is already an allowed form).
+ /// </summary>
+ public static string? ToProtectedFieldName(string name)
+ {
+ if (string.IsNullOrEmpty(name))
+ return null;
+
+ // Strip any leading underscores and a leading 'm_' if already present.
+ var core = name.TrimStart('_');
+ if (core.StartsWith("m_", System.StringComparison.Ordinal))
+ core = core.Substring(2);
+
+ core = core.TrimStart('_');
+ if (core.Length == 0)
+ return null;
+
+ // camelCase the first letter.
+ var camel = char.ToLower(core[0], CultureInfo.InvariantCulture) + core.Substring(1);
+ return "m_" + camel;
+ }
+
+ /// <summary>
+ /// Computes the conventional interface name (capital 'I' prefix followed by PascalCase) for the
+ /// given interface name, or <c>null</c> when a sensible suggestion cannot be produced.
+ /// </summary>
+ public static string? ToInterfaceName(string name)
+ {
+ if (string.IsNullOrEmpty(name))
+ return null;
+
+ // Drop leading underscores, then PascalCase the first letter.
+ var core = name.TrimStart('_');
+ if (core.Length == 0)
+ return null;
+
+ var pascal = char.ToUpper(core[0], CultureInfo.InvariantCulture) + core.Substring(1);
+
+ // If it already starts with 'I' followed by an uppercase letter, the prefix is present and
+ // only the case or leading underscores needed fixing (e.g. 'iFoo' or '_IFoo' -> 'IFoo').
+ if (pascal.Length >= 2 && pascal[0] == 'I' && char.IsUpper(pascal[1]))
+ return pascal == name ? null : pascal;
+
+ return "I" + pascal;
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1009Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1009Sample.cs
new file mode 100644
index 0000000..58d77a7
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1009Sample.cs
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+namespace Lucene.Net.CodeAnalysis.Dev.Sample;
+
+public class LuceneDev1009Sample
+{
+ // VIOLATION: public instance field should be a property instead.
+ public int Value;
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1010Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1010Sample.cs
new file mode 100644
index 0000000..e2dc34c
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1010Sample.cs
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+namespace Lucene.Net.CodeAnalysis.Dev.Sample;
+
+public class LuceneDev1010Sample
+{
+ private int _value;
+
+ // VIOLATION: property has a setter but no getter.
+ public int Value
+ {
+ set => _value = value;
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1011Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1011Sample.cs
new file mode 100644
index 0000000..b9d8e5c
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1011Sample.cs
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+namespace Lucene.Net.CodeAnalysis.Dev.Sample;
+
+public class LuceneDev1011Sample
+{
+ private readonly int[] _values = new int[4];
+
+ // VIOLATION: property returns an array.
+ public int[] Values => _values;
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1012Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1012Sample.cs
new file mode 100644
index 0000000..ada2bcd
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1012Sample.cs
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+namespace Lucene.Net.CodeAnalysis.Dev.Sample;
+
+public class LuceneDev1012Sample
+{
+ private readonly int[] m_values = new int[4];
+
+ // VIOLATION: method returns the backing array directly, so callers can mutate it.
+ public int[] GetValues()
+ {
+ return m_values;
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1013Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1013Sample.cs
new file mode 100644
index 0000000..463c872
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1013Sample.cs
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+namespace Lucene.Net.CodeAnalysis.Dev.Sample;
+
+public enum LuceneDev1013Mode
+{
+ First,
+ Second
+}
+
+public class LuceneDev1013Sample
+{
+ // VIOLATION: public member that is a nullable enum.
+ public LuceneDev1013Mode? Mode { get; set; }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1014Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1014Sample.cs
new file mode 100644
index 0000000..926ae60
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1014Sample.cs
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System.Collections.Generic;
+
+namespace Lucene.Net.CodeAnalysis.Dev.Sample;
+
+public class LuceneDev1014Sample
+{
+ // VIOLATION (rule disabled by default): member accepts or returns IEnumerable<T>.
+ public IEnumerable<int> GetItems(IEnumerable<int> source)
+ {
+ return source;
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1015Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1015Sample.cs
new file mode 100644
index 0000000..6923d27
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1015Sample.cs
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System.Collections.Generic;
+
+namespace Lucene.Net.CodeAnalysis.Dev.Sample;
+
+public class LuceneDev1015Sample
+{
+ // VIOLATION: member returns the concrete List<T> instead of IList<T>.
+ public List<int> GetItems()
+ {
+ return new List<int>();
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7000Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7000Sample.cs
new file mode 100644
index 0000000..fe98d9d
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7000Sample.cs
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+namespace Lucene.Net.CodeAnalysis.Dev.Sample;
+
+public class LuceneDev7000Sample
+{
+ // VIOLATION: private field is not camelCase (PascalCase is not allowed).
+ private int FieldValue = 0;
+
+ public int GetValue() => FieldValue;
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7001Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7001Sample.cs
new file mode 100644
index 0000000..ba5f285
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7001Sample.cs
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+namespace Lucene.Net.CodeAnalysis.Dev.Sample;
+
+public class LuceneDev7001Sample
+{
+ // VIOLATION: protected field should use the 'm_' prefix (e.g. m_count).
+ protected int count;
+
+ protected LuceneDev7001Sample(int value) => count = value;
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7002Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7002Sample.cs
new file mode 100644
index 0000000..ce134e4
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7002Sample.cs
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+namespace Lucene.Net.CodeAnalysis.Dev.Sample;
+
+public class LuceneDev7002Sample
+{
+ // VIOLATION: method parameter is not camelCase (PascalCase is not allowed).
+ public int Add(int First, int second) => First + second;
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7003Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7003Sample.cs
new file mode 100644
index 0000000..ee97665
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7003Sample.cs
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+namespace Lucene.Net.CodeAnalysis.Dev.Sample;
+
+// VIOLATION: interface name does not begin with 'I'.
+public interface LuceneDev7003Sample
+{
+ void DoWork();
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7004Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7004Sample.cs
new file mode 100644
index 0000000..1e5c215
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7004Sample.cs
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+namespace Lucene.Net.CodeAnalysis.Dev.Sample;
+
+// VIOLATION: class name follows the interface naming convention (capital 'I' + capital letter).
+public class ILuceneDev7004Sample
+{
+ public void DoWork() { }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7005Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7005Sample.cs
new file mode 100644
index 0000000..bae6b66
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7005Sample.cs
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+namespace Lucene.Net.CodeAnalysis.Dev.Sample;
+
+public class LuceneDev7005Sample
+{
+ // VIOLATION: member name contains the Java term 'Comparator' (should be 'Comparer').
+ public int CompareWithComparator(int a, int b) => a.CompareTo(b);
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7006Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7006Sample.cs
new file mode 100644
index 0000000..aa86348
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7006Sample.cs
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+namespace Lucene.Net.CodeAnalysis.Dev.Sample;
+
+public class LuceneDev7006Sample
+{
+ // VIOLATION: member named 'Size' (should be 'Count' or 'Length').
+ public int Size { get; set; }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7007Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7007Sample.cs
new file mode 100644
index 0000000..20b6468
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7007Sample.cs
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+namespace Lucene.Net.CodeAnalysis.Dev.Sample;
+
+public class LuceneDev7007Sample
+{
+ // VIOLATION: member name contains the Java numeric term 'Long' (should be 'Int64').
+ public long ReadLong() => 0L;
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7008Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7008Sample.cs
new file mode 100644
index 0000000..4e73d2e
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev7008Sample.cs
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+namespace Lucene.Net.CodeAnalysis.Dev.Sample;
+
+// VIOLATION: type name contains the Java numeric term 'Long' (should be 'Int64').
+public class LongValueHolder
+{
+ public long Value { get; set; }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/AnalyzerReleases.Unshipped.md b/src/Lucene.Net.CodeAnalysis.Dev/AnalyzerReleases.Unshipped.md
index 4c74a14..b2f23a3 100644
--- a/src/Lucene.Net.CodeAnalysis.Dev/AnalyzerReleases.Unshipped.md
+++ b/src/Lucene.Net.CodeAnalysis.Dev/AnalyzerReleases.Unshipped.md
@@ -2,3 +2,19 @@
Rule ID | Category | Severity | Notes
--------------|----------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------
+LuceneDev1009 | Design | Warning | Fields should not be public; use a public property instead
+LuceneDev1010 | Design | Warning | Properties should not have a setter without a getter
+LuceneDev1011 | Design | Warning | Properties should not return arrays
+LuceneDev1012 | Design | Warning | Methods should not return writable arrays
+LuceneDev1013 | Design | Warning | Public members should not be nullable enums
+LuceneDev1014 | Design | Disabled | Members should not accept or return IEnumerable<T> (one-time port aid; disabled by default)
+LuceneDev1015 | Design | Warning | Members should not accept or return List<T> or Dictionary<K, V>; prefer interface types
+LuceneDev7000 | Naming | Warning | Private fields should be camelCase, optionally with a leading underscore
+LuceneDev7001 | Naming | Warning | Protected fields should use the 'm_' prefix followed by camelCase
+LuceneDev7002 | Naming | Warning | Method parameters should be camelCase
+LuceneDev7003 | Naming | Warning | Interface names should begin with 'I'
+LuceneDev7004 | Naming | Warning | Class names should use PascalCase
+LuceneDev7005 | Naming | Warning | Public members should not contain the word 'Comparator'; use 'Comparer'
+LuceneDev7006 | Naming | Warning | Public members should not be named 'Size'; use 'Count' or 'Length'
+LuceneDev7007 | Naming | Warning | Public members should not contain non-.NET numeric type names (Int/Long/Short/Float)
+LuceneDev7008 | Naming | Warning | Type names should not contain non-.NET numeric type names (Int/Long/Short/Float)
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1009_PublicFieldsAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1009_PublicFieldsAnalyzer.cs
new file mode 100644
index 0000000..8110fc0
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1009_PublicFieldsAnalyzer.cs
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System.Collections.Immutable;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Lucene.Net.CodeAnalysis.Dev.LuceneDev1xxx
+{
+ /// <summary>
+ /// Ports TestApiConsistency.TestPublicFields: public instance fields of classes are reported;
+ /// consider using a public property instead. Mirrors the original test, which scans only
+ /// instance fields (so const and static fields are not considered) and skips the
+ /// Lucene.Net.Support namespace.
+ /// </summary>
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public sealed class LuceneDev1009_PublicFieldsAnalyzer : DiagnosticAnalyzer
+ {
+ public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
+ ImmutableArray.Create(Descriptors.LuceneDev1009_PublicFields);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
+ context.EnableConcurrentExecution();
+ context.RegisterSymbolAction(AnalyzeField, SymbolKind.Field);
+ }
+
+ private static void AnalyzeField(SymbolAnalysisContext context)
+ {
+ var field = (IFieldSymbol)context.Symbol;
+ var containingType = field.ContainingType;
+
+ if (containingType?.TypeKind != TypeKind.Class)
+ return;
+
+ if (ApiConventionHelper.IsGeneratedType(containingType))
+ return;
+
+ if (ApiConventionHelper.IsCompilerGeneratedName(containingType.MetadataName))
+ return;
+
+ if (ApiConventionHelper.IsInSupportNamespace(containingType))
+ return;
+
+ // Original test binds Public | Instance only: static and const fields are not scanned.
+ if (field.IsStatic || field.IsConst)
+ return;
+
+ if (field.IsImplicitlyDeclared || ApiConventionHelper.IsCompilerGeneratedName(field.Name))
+ return;
+
+ if (field.DeclaredAccessibility != Accessibility.Public)
+ return;
+
+ foreach (var location in field.Locations)
+ {
+ if (location.IsInSource)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ Descriptors.LuceneDev1009_PublicFields, location, field.Name));
+ }
+ }
+ }
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1010_PropertyWithNoGetterAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1010_PropertyWithNoGetterAnalyzer.cs
new file mode 100644
index 0000000..fb92f0c
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1010_PropertyWithNoGetterAnalyzer.cs
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System.Collections.Immutable;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Lucene.Net.CodeAnalysis.Dev.LuceneDev1xxx
+{
+ /// <summary>
+ /// Ports TestApiConsistency.TestForPropertiesWithNoGetter: instance properties of classes that
+ /// have a setter but no getter are reported. Mirrors the original test, which considers
+ /// accessors of any accessibility.
+ /// </summary>
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public sealed class LuceneDev1010_PropertyWithNoGetterAnalyzer : DiagnosticAnalyzer
+ {
+ public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
+ ImmutableArray.Create(Descriptors.LuceneDev1010_PropertyWithNoGetter);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
+ context.EnableConcurrentExecution();
+ context.RegisterSymbolAction(AnalyzeProperty, SymbolKind.Property);
+ }
+
+ private static void AnalyzeProperty(SymbolAnalysisContext context)
+ {
+ var property = (IPropertySymbol)context.Symbol;
+ var containingType = property.ContainingType;
+
+ if (containingType?.TypeKind != TypeKind.Class)
+ return;
+
+ if (ApiConventionHelper.IsGeneratedType(containingType))
+ return;
+
+ // Original test binds instance properties only.
+ if (property.IsStatic)
+ return;
+
+ // Note: the original (reflection GetProperties) includes indexers, so a set-only
+ // indexer is intentionally flagged here as well.
+ if (property.SetMethod != null && property.GetMethod == null)
+ {
+ foreach (var location in property.Locations)
+ {
+ if (location.IsInSource)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ Descriptors.LuceneDev1010_PropertyWithNoGetter, location, property.Name));
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1011_PropertyReturnsArrayAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1011_PropertyReturnsArrayAnalyzer.cs
new file mode 100644
index 0000000..47ae278
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1011_PropertyReturnsArrayAnalyzer.cs
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System.Collections.Immutable;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Lucene.Net.CodeAnalysis.Dev.LuceneDev1xxx
+{
+ /// <summary>
+ /// Ports TestApiConsistency.TestForPropertiesThatReturnArray: instance properties of classes
+ /// with a public getter that returns an array type are reported. Properties decorated with
+ /// [WritableArray] are excluded, matching the original test.
+ /// </summary>
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public sealed class LuceneDev1011_PropertyReturnsArrayAnalyzer : DiagnosticAnalyzer
+ {
+ public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
+ ImmutableArray.Create(Descriptors.LuceneDev1011_PropertyReturnsArray);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
+ context.EnableConcurrentExecution();
+ context.RegisterSymbolAction(AnalyzeProperty, SymbolKind.Property);
+ }
+
+ private static void AnalyzeProperty(SymbolAnalysisContext context)
+ {
+ var property = (IPropertySymbol)context.Symbol;
+ var containingType = property.ContainingType;
+
+ if (containingType?.TypeKind != TypeKind.Class)
+ return;
+
+ if (ApiConventionHelper.IsGeneratedType(containingType))
+ return;
+
+ if (property.IsStatic)
+ return;
+
+ if (ApiConventionHelper.HasAttribute(property, ApiConventionHelper.WritableArrayAttribute))
+ return;
+
+ // Original test uses GetGetMethod() (public getter only).
+ var getMethod = property.GetMethod;
+ if (getMethod is null || getMethod.DeclaredAccessibility != Accessibility.Public)
+ return;
+
+ if (property.Type.TypeKind == TypeKind.Array)
+ {
+ foreach (var location in property.Locations)
+ {
+ if (location.IsInSource)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ Descriptors.LuceneDev1011_PropertyReturnsArray, location, property.Name));
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1012_MethodReturnsWritableArrayAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1012_MethodReturnsWritableArrayAnalyzer.cs
new file mode 100644
index 0000000..bcb580a
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1012_MethodReturnsWritableArrayAnalyzer.cs
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Lucene.Net.CodeAnalysis.Dev.LuceneDev1xxx
+{
+ /// <summary>
+ /// Ports TestApiConsistency.TestForMethodsThatReturnWritableArray. The original test inspected
+ /// the compiled IL to detect a method body that simply returns a field (so the consumer can
+ /// mutate the array). Because a source analyzer has no IL, this analyzer inspects the method
+ /// body syntax and reports when an array-returning method returns a field reference directly,
+ /// without cloning it (e.g. <c>return m_values;</c>). Methods decorated with [WritableArray]
+ /// are excluded, matching the original test.
+ /// </summary>
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public sealed class LuceneDev1012_MethodReturnsWritableArrayAnalyzer : DiagnosticAnalyzer
+ {
+ public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
+ ImmutableArray.Create(Descriptors.LuceneDev1012_MethodReturnsWritableArray);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
+ context.EnableConcurrentExecution();
+ context.RegisterSyntaxNodeAction(AnalyzeMethod, SyntaxKind.MethodDeclaration);
+ }
+
+ private static void AnalyzeMethod(SyntaxNodeAnalysisContext context)
+ {
+ var methodDeclaration = (MethodDeclarationSyntax)context.Node;
+
+ // Must be declared on a class.
+ if (methodDeclaration.Parent is not ClassDeclarationSyntax)
+ return;
+
+ // Return type must be an array (possibly nullable-annotated, e.g. int[]?).
+ if (methodDeclaration.ReturnType is not (ArrayTypeSyntax or NullableTypeSyntax { ElementType: ArrayTypeSyntax }))
+ return;
+
+ var semanticModel = context.SemanticModel;
+ var methodSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration, context.CancellationToken);
+ if (methodSymbol is null)
+ return;
+
+ if (methodSymbol.ReturnType.TypeKind != TypeKind.Array)
+ return;
+
+ if (ApiConventionHelper.HasAttribute(methodSymbol, ApiConventionHelper.WritableArrayAttribute))
+ return;
+
+ foreach (var returnedExpression in GetReturnedExpressions(methodDeclaration))
+ {
+ if (ReturnsFieldDirectly(returnedExpression, semanticModel, context.CancellationToken))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ Descriptors.LuceneDev1012_MethodReturnsWritableArray,
+ methodDeclaration.Identifier.GetLocation(),
+ methodSymbol.Name));
+ return; // one diagnostic per method
+ }
+ }
+ }
+
+ private static IEnumerable<ExpressionSyntax> GetReturnedExpressions(MethodDeclarationSyntax method)
+ {
+ // Expression-bodied member: e.g. int[] GetValues() => m_values;
+ if (method.ExpressionBody?.Expression is { } arrow)
+ {
+ yield return arrow;
+ yield break;
+ }
+
+ if (method.Body is null)
+ yield break;
+
+ foreach (var statement in method.Body.DescendantNodes().OfType<ReturnStatementSyntax>())
+ {
+ if (statement.Expression is { } expression)
+ {
+ yield return expression;
+ }
+ }
+ }
+
+ private static bool ReturnsFieldDirectly(ExpressionSyntax expression, SemanticModel semanticModel, System.Threading.CancellationToken cancellationToken)
+ {
+ // Unwrap parentheses.
+ var unwrapped = expression;
+ while (unwrapped is ParenthesizedExpressionSyntax parenthesized)
+ {
+ unwrapped = parenthesized.Expression;
+ }
+
+ // A direct field reference: 'field' or 'this.field'. Anything else (a method call such
+ // as arr.ToArray(), a 'new[]' allocation, etc.) returns a fresh array and is safe.
+ if (unwrapped is not (IdentifierNameSyntax or MemberAccessExpressionSyntax))
+ return false;
+
+ var symbol = semanticModel.GetSymbolInfo(unwrapped, cancellationToken).Symbol;
+ return symbol is IFieldSymbol;
+ }
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1013_NullableEnumAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1013_NullableEnumAnalyzer.cs
new file mode 100644
index 0000000..30b4589
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1013_NullableEnumAnalyzer.cs
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System.Collections.Immutable;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Lucene.Net.CodeAnalysis.Dev.LuceneDev1xxx
+{
+ /// <summary>
+ /// Ports TestApiConsistency.TestForPublicMembersWithNullableEnum: reports non-private methods
+ /// (return value and parameters), non-private constructors (parameters), non-private properties,
+ /// and protected fields whose type is a nullable enum (<c>Nullable<TEnum></c>). Members
+ /// decorated with [ExceptionToNullableEnumConvention] are excluded, matching the original test.
+ /// </summary>
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public sealed class LuceneDev1013_NullableEnumAnalyzer : DiagnosticAnalyzer
+ {
+ public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
+ ImmutableArray.Create(Descriptors.LuceneDev1013_NullableEnum);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
+ context.EnableConcurrentExecution();
+ context.RegisterSymbolAction(AnalyzeMethod, SymbolKind.Method);
+ context.RegisterSymbolAction(AnalyzeProperty, SymbolKind.Property);
+ context.RegisterSymbolAction(AnalyzeField, SymbolKind.Field);
+ }
+
+ private static bool IsNullableEnum(ITypeSymbol type)
+ {
+ if (type is INamedTypeSymbol named
+ && named.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T
+ && named.TypeArguments.Length == 1)
+ {
+ return named.TypeArguments[0].TypeKind == TypeKind.Enum;
+ }
+
+ return false;
+ }
+
+ private static bool MemberPreconditions(ISymbol member)
+ {
+ var containingType = member.ContainingType;
+ if (containingType is null || ApiConventionHelper.IsGeneratedType(containingType))
+ return false;
+
+ if (member.IsImplicitlyDeclared || ApiConventionHelper.IsCompilerGeneratedName(member.Name))
+ return false;
+
+ return !ApiConventionHelper.HasAttribute(member, ApiConventionHelper.ExceptionToNullableEnumConventionAttribute);
+ }
+
+ private void AnalyzeMethod(SymbolAnalysisContext context)
+ {
+ var method = (IMethodSymbol)context.Symbol;
+
+ // Original test handles ordinary methods (excluding accessors) and constructors.
+ if (method.MethodKind is not (MethodKind.Ordinary or MethodKind.Constructor))
+ return;
+
+ if (!MemberPreconditions(method))
+ return;
+
+ // Original test only considers non-private methods/constructors.
+ if (method.DeclaredAccessibility == Accessibility.Private)
+ return;
+
+ // Return value (ordinary methods only; constructors have no return type to flag).
+ if (method.MethodKind == MethodKind.Ordinary && IsNullableEnum(method.ReturnType))
+ {
+ ReportSymbol(context, method);
+ }
+
+ foreach (var parameter in method.Parameters)
+ {
+ if (IsNullableEnum(parameter.Type))
+ {
+ ReportParameter(context, parameter);
+ }
+ }
+ }
+
+ private void AnalyzeProperty(SymbolAnalysisContext context)
+ {
+ var property = (IPropertySymbol)context.Symbol;
+ if (!MemberPreconditions(property))
+ return;
+
+ // Original IsNonPrivateProperty: requires a public getter or setter.
+ if (!ApiConventionHelper.HasPublicAccessor(property))
+ return;
+
+ if (IsNullableEnum(property.Type))
+ {
+ ReportSymbol(context, property);
+ }
+ }
+
+ private void AnalyzeField(SymbolAnalysisContext context)
+ {
+ var field = (IFieldSymbol)context.Symbol;
+ if (!MemberPreconditions(field))
+ return;
+
+ // Original test flags protected (or protected internal) fields.
+ if (field.DeclaredAccessibility is not (Accessibility.Protected or Accessibility.ProtectedOrInternal))
+ return;
+
+ if (IsNullableEnum(field.Type))
+ {
+ ReportSymbol(context, field);
+ }
+ }
+
+ private static void ReportSymbol(SymbolAnalysisContext context, ISymbol symbol)
+ {
+ foreach (var location in symbol.Locations)
+ {
+ if (location.IsInSource)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ Descriptors.LuceneDev1013_NullableEnum, location, symbol.Name));
+ }
+ }
+ }
+
+ private static void ReportParameter(SymbolAnalysisContext context, IParameterSymbol parameter)
+ {
+ foreach (var location in parameter.Locations)
+ {
+ if (location.IsInSource)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ Descriptors.LuceneDev1013_NullableEnum, location, parameter.Name));
+ }
+ }
+ }
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1014_MemberAcceptsOrReturnsIEnumerableAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1014_MemberAcceptsOrReturnsIEnumerableAnalyzer.cs
new file mode 100644
index 0000000..aa96e9e
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1014_MemberAcceptsOrReturnsIEnumerableAnalyzer.cs
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Lucene.Net.CodeAnalysis.Dev.LuceneDev1xxx
+{
+ /// <summary>
+ /// Ports TestApiConsistency.TestForMembersAcceptingOrReturningIEnumerable: reports members
+ /// (methods, constructors, properties) that accept or return <c>IEnumerable<T></c>. This
+ /// rule was a one-time porting aid and is disabled by default.
+ /// </summary>
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public sealed class LuceneDev1014_MemberAcceptsOrReturnsIEnumerableAnalyzer : MembersUsingTypeAnalyzerBase
+ {
+ public LuceneDev1014_MemberAcceptsOrReturnsIEnumerableAnalyzer()
+ // Original test passes publiclyVisibleOnly: false (all members are scanned).
+ : base(Descriptors.LuceneDev1014_MemberAcceptsOrReturnsIEnumerable, publiclyVisibleOnly: false)
+ {
+ }
+
+ protected override bool IsTargetType(ITypeSymbol type) =>
+ type is INamedTypeSymbol named
+ && named.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T;
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1015_MemberAcceptsOrReturnsListOrDictionaryAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1015_MemberAcceptsOrReturnsListOrDictionaryAnalyzer.cs
new file mode 100644
index 0000000..56b5b0c
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1015_MemberAcceptsOrReturnsListOrDictionaryAnalyzer.cs
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Lucene.Net.CodeAnalysis.Dev.LuceneDev1xxx
+{
+ /// <summary>
+ /// Ports TestApiConsistency.TestForMembersAcceptingOrReturningListOrDictionary: reports
+ /// non-private members (methods, constructors, properties) that accept or return
+ /// <c>List<T></c> or <c>Dictionary<K, V></c>; these should be exposed as
+ /// <c>IList<T></c> and <c>IDictionary<K, V></c> respectively.
+ /// </summary>
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public sealed class LuceneDev1015_MemberAcceptsOrReturnsListOrDictionaryAnalyzer : MembersUsingTypeAnalyzerBase
+ {
+ private const string ListMetadataName = "System.Collections.Generic.List`1";
+ private const string DictionaryMetadataName = "System.Collections.Generic.Dictionary`2";
+
+ public LuceneDev1015_MemberAcceptsOrReturnsListOrDictionaryAnalyzer()
+ // Original test passes publiclyVisibleOnly: true (non-private members only).
+ : base(Descriptors.LuceneDev1015_MemberAcceptsOrReturnsListOrDictionary, publiclyVisibleOnly: true)
+ {
+ }
+
+ protected override bool IsTargetType(ITypeSymbol type)
+ {
+ if (type is not INamedTypeSymbol named)
+ return false;
+
+ // Compare the unbound generic definition by its full metadata name, which is stable
+ // across type arguments (e.g. "System.Collections.Generic.List`1").
+ var metadataName = named.OriginalDefinition.ContainingNamespace?.ToDisplayString() + "." + named.OriginalDefinition.MetadataName;
+ return metadataName == ListMetadataName || metadataName == DictionaryMetadataName;
+ }
+
+ protected override string DescribeType(ITypeSymbol type) =>
+ type.OriginalDefinition.Name; // "List" or "Dictionary"
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/MembersUsingTypeAnalyzerBase.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/MembersUsingTypeAnalyzerBase.cs
new file mode 100644
index 0000000..4350183
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/MembersUsingTypeAnalyzerBase.cs
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System.Collections.Immutable;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Lucene.Net.CodeAnalysis.Dev.LuceneDev1xxx
+{
+ /// <summary>
+ /// Base class for the analyzers that mirror TestApiConsistency.GetMembersAcceptingOrReturningType:
+ /// they report ordinary methods (return value and parameters), constructors (parameters), and
+ /// properties whose type is one of a set of concrete generic types. Derived classes specify the
+ /// metadata names of the target open generic types and whether only non-private members are
+ /// considered.
+ /// </summary>
+ public abstract class MembersUsingTypeAnalyzerBase : DiagnosticAnalyzer
+ {
+ private readonly DiagnosticDescriptor descriptor;
+ private readonly bool publiclyVisibleOnly;
+
+ protected MembersUsingTypeAnalyzerBase(DiagnosticDescriptor descriptor, bool publiclyVisibleOnly)
+ {
+ this.descriptor = descriptor;
+ this.publiclyVisibleOnly = publiclyVisibleOnly;
+ }
+
+ public sealed override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
+ ImmutableArray.Create(descriptor);
+
+ public sealed override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
+ context.EnableConcurrentExecution();
+ context.RegisterSymbolAction(AnalyzeMethod, SymbolKind.Method);
+ context.RegisterSymbolAction(AnalyzeProperty, SymbolKind.Property);
+ }
+
+ /// <summary>
+ /// Returns <c>true</c> when the type matches one of the concrete generic types this rule
+ /// targets (compared by its unbound generic definition).
+ /// </summary>
+ protected abstract bool IsTargetType(ITypeSymbol type);
+
+ /// <summary>The display name of the matched type, used in the diagnostic message.</summary>
+ protected virtual string DescribeType(ITypeSymbol type) => type.OriginalDefinition.Name;
+
+ private bool MemberPreconditions(ISymbol member)
+ {
+ var containingType = member.ContainingType;
+ if (containingType is null || ApiConventionHelper.IsGeneratedType(containingType))
+ return false;
+
+ return !member.IsImplicitlyDeclared && !ApiConventionHelper.IsCompilerGeneratedName(member.Name);
+ }
+
+ private void AnalyzeMethod(SymbolAnalysisContext context)
+ {
+ var method = (IMethodSymbol)context.Symbol;
+
+ if (method.MethodKind is not (MethodKind.Ordinary or MethodKind.Constructor))
+ return;
+
+ if (!MemberPreconditions(method))
+ return;
+
+ if (publiclyVisibleOnly && method.DeclaredAccessibility == Accessibility.Private)
+ return;
+
+ // Constructors have no meaningful return type to flag; only ordinary methods do.
+ if (method.MethodKind == MethodKind.Ordinary && IsTargetType(method.ReturnType))
+ {
+ Report(context, method, method.ReturnType);
+ }
+
+ foreach (var parameter in method.Parameters)
+ {
+ if (IsTargetType(parameter.Type))
+ {
+ ReportParameter(context, parameter, parameter.Type);
+ }
+ }
+ }
+
+ private void AnalyzeProperty(SymbolAnalysisContext context)
+ {
+ var property = (IPropertySymbol)context.Symbol;
+ if (!MemberPreconditions(property))
+ return;
+
+ if (publiclyVisibleOnly && !ApiConventionHelper.HasPublicAccessor(property))
+ return;
+
+ if (IsTargetType(property.Type))
+ {
+ Report(context, property, property.Type);
+ }
+ }
+
+ private void Report(SymbolAnalysisContext context, ISymbol member, ITypeSymbol matchedType)
+ {
+ foreach (var location in member.Locations)
+ {
+ if (location.IsInSource)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ descriptor, location, member.Name, DescribeType(matchedType)));
+ }
+ }
+ }
+
+ private void ReportParameter(SymbolAnalysisContext context, IParameterSymbol parameter, ITypeSymbol matchedType)
+ {
+ foreach (var location in parameter.Locations)
+ {
+ if (location.IsInSource)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ descriptor, location, parameter.Name, DescribeType(matchedType)));
+ }
+ }
+ }
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7000_PrivateFieldNameAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7000_PrivateFieldNameAnalyzer.cs
new file mode 100644
index 0000000..08cc254
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7000_PrivateFieldNameAnalyzer.cs
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System.Collections.Immutable;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx
+{
+ /// <summary>
+ /// Ports TestApiConsistency.TestPrivateFieldNames: private or internal (assembly) fields of
+ /// classes must be camelCase (optionally with a leading underscore) or an UPPER_CASE constant.
+ /// </summary>
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public sealed class LuceneDev7000_PrivateFieldNameAnalyzer : DiagnosticAnalyzer
+ {
+ public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
+ ImmutableArray.Create(Descriptors.LuceneDev7000_PrivateFieldName);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
+ context.EnableConcurrentExecution();
+ context.RegisterSymbolAction(AnalyzeField, SymbolKind.Field);
+ }
+
+ private static void AnalyzeField(SymbolAnalysisContext context)
+ {
+ var field = (IFieldSymbol)context.Symbol;
+
+ // Original test: only classes.
+ if (field.ContainingType?.TypeKind != TypeKind.Class)
+ return;
+
+ if (ApiConventionHelper.IsGeneratedType(field.ContainingType))
+ return;
+
+ // Ignore compiler-generated backing fields (names starting with '<').
+ if (field.IsImplicitlyDeclared || ApiConventionHelper.IsCompilerGeneratedName(field.Name))
+ return;
+
+ // Original test condition: field.IsPrivate || field.IsAssembly (internal).
+ if (field.DeclaredAccessibility is not (Accessibility.Private or Accessibility.Internal))
+ return;
+
+ if (!NamingHelper.PrivateFieldName.IsMatch(field.Name))
+ {
+ foreach (var location in field.Locations)
+ {
+ if (location.IsInSource)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ Descriptors.LuceneDev7000_PrivateFieldName, location, field.Name));
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7001_ProtectedFieldNameAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7001_ProtectedFieldNameAnalyzer.cs
new file mode 100644
index 0000000..4ecd4cf
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7001_ProtectedFieldNameAnalyzer.cs
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System.Collections.Immutable;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx
+{
+ /// <summary>
+ /// Ports TestApiConsistency.TestProtectedFieldNames: protected (or protected internal)
+ /// instance fields of classes must use the 'm_' prefix followed by camelCase, or be an
+ /// UPPER_CASE constant. Fields in the Lucene.Net.Support namespace and the AssemblyKeys
+ /// type are excluded, mirroring the original test.
+ /// </summary>
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public sealed class LuceneDev7001_ProtectedFieldNameAnalyzer : DiagnosticAnalyzer
+ {
+ public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
+ ImmutableArray.Create(Descriptors.LuceneDev7001_ProtectedFieldName);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
+ context.EnableConcurrentExecution();
+ context.RegisterSymbolAction(AnalyzeField, SymbolKind.Field);
+ }
+
+ private static void AnalyzeField(SymbolAnalysisContext context)
+ {
+ var field = (IFieldSymbol)context.Symbol;
+ var containingType = field.ContainingType;
+
+ if (containingType?.TypeKind != TypeKind.Class)
+ return;
+
+ if (ApiConventionHelper.IsGeneratedType(containingType))
+ return;
+
+ if (ApiConventionHelper.IsInSupportNamespace(containingType))
+ return;
+
+ if (containingType.Name == "AssemblyKeys")
+ return;
+
+ // Original test binds Instance fields only (static protected fields are not scanned).
+ if (field.IsStatic)
+ return;
+
+ if (field.IsImplicitlyDeclared || ApiConventionHelper.IsCompilerGeneratedName(field.Name))
+ return;
+
+ // Original test condition: field.IsFamily || field.IsFamilyOrAssembly.
+ if (field.DeclaredAccessibility is not (Accessibility.Protected or Accessibility.ProtectedOrInternal))
+ return;
+
+ if (!NamingHelper.ProtectedFieldName.IsMatch(field.Name))
+ {
+ foreach (var location in field.Locations)
+ {
+ if (location.IsInSource)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ Descriptors.LuceneDev7001_ProtectedFieldName, location, field.Name));
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7002_MethodParameterNameAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7002_MethodParameterNameAnalyzer.cs
new file mode 100644
index 0000000..d43bf30
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7002_MethodParameterNameAnalyzer.cs
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System.Collections.Immutable;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx
+{
+ /// <summary>
+ /// Ports TestApiConsistency.TestMethodParameterNames: parameters of methods declared on
+ /// classes must be camelCase and not begin or end with an underscore. All accessibilities
+ /// are scanned, matching the original test.
+ /// </summary>
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public sealed class LuceneDev7002_MethodParameterNameAnalyzer : DiagnosticAnalyzer
+ {
+ public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
+ ImmutableArray.Create(Descriptors.LuceneDev7002_MethodParameterName);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
+ context.EnableConcurrentExecution();
+ context.RegisterSymbolAction(AnalyzeMethod, SymbolKind.Method);
+ }
+
+ private static void AnalyzeMethod(SymbolAnalysisContext context)
+ {
+ var method = (IMethodSymbol)context.Symbol;
+ var containingType = method.ContainingType;
+
+ if (containingType?.TypeKind != TypeKind.Class)
+ return;
+
+ if (ApiConventionHelper.IsGeneratedType(containingType))
+ return;
+
+ // Original test scans c.GetMethods(...), skipping '<'-prefixed (compiler-generated)
+ // names. Reflection's GetMethods() also returns property/event accessors and operator
+ // methods; we intentionally restrict to ordinary methods. Accessor parameters are
+ // always compiler-named (e.g. 'value') and never violate, and operators/conversions
+ // do not exist in the ported Java API surface this rule targets.
+ if (method.MethodKind != MethodKind.Ordinary)
+ return;
+
+ if (method.IsImplicitlyDeclared || ApiConventionHelper.IsCompilerGeneratedName(method.Name))
+ return;
+
+ foreach (var parameter in method.Parameters)
+ {
+ if (string.IsNullOrEmpty(parameter.Name))
+ continue;
+
+ if (!NamingHelper.MethodParameterName.IsMatch(parameter.Name))
+ {
+ foreach (var location in parameter.Locations)
+ {
+ if (location.IsInSource)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ Descriptors.LuceneDev7002_MethodParameterName, location, parameter.Name));
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7003_InterfaceNameAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7003_InterfaceNameAnalyzer.cs
new file mode 100644
index 0000000..14ce35e
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7003_InterfaceNameAnalyzer.cs
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System.Collections.Immutable;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx
+{
+ /// <summary>
+ /// Ports TestApiConsistency.TestInterfaceNames: interface names must begin with a capital
+ /// 'I' followed by another capital letter (with an optional generic-arity suffix).
+ /// </summary>
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public sealed class LuceneDev7003_InterfaceNameAnalyzer : DiagnosticAnalyzer
+ {
+ public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
+ ImmutableArray.Create(Descriptors.LuceneDev7003_InterfaceName);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
+ context.EnableConcurrentExecution();
+ context.RegisterSymbolAction(AnalyzeType, SymbolKind.NamedType);
+ }
+
+ private static void AnalyzeType(SymbolAnalysisContext context)
+ {
+ var type = (INamedTypeSymbol)context.Symbol;
+
+ if (type.TypeKind != TypeKind.Interface)
+ return;
+
+ if (ApiConventionHelper.IsGeneratedType(type))
+ return;
+
+ // The original tests match on the metadata name, which carries the generic arity
+ // suffix (e.g. "IComparer`1"); MetadataName preserves that.
+ if (!NamingHelper.InterfaceName.IsMatch(type.MetadataName))
+ {
+ foreach (var location in type.Locations)
+ {
+ if (location.IsInSource)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ Descriptors.LuceneDev7003_InterfaceName, location, type.Name));
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7004_ClassNameAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7004_ClassNameAnalyzer.cs
new file mode 100644
index 0000000..e4a3eaa
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7004_ClassNameAnalyzer.cs
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System.Collections.Immutable;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx
+{
+ /// <summary>
+ /// Ports TestApiConsistency.TestClassNames: class names must be PascalCase and must not
+ /// follow the interface naming convention (capital 'I' followed by another capital letter).
+ /// Classes decorated with [ExceptionToClassNameConvention] are excluded.
+ /// </summary>
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public sealed class LuceneDev7004_ClassNameAnalyzer : DiagnosticAnalyzer
+ {
+ public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
+ ImmutableArray.Create(Descriptors.LuceneDev7004_ClassName);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
+ context.EnableConcurrentExecution();
+ context.RegisterSymbolAction(AnalyzeType, SymbolKind.NamedType);
+ }
+
+ private static void AnalyzeType(SymbolAnalysisContext context)
+ {
+ var type = (INamedTypeSymbol)context.Symbol;
+
+ // The original test scanned reflection's t.IsClass, which is true for both classes and
+ // delegates (delegates are CLR classes). Delegate names follow the same convention.
+ if (type.TypeKind is not (TypeKind.Class or TypeKind.Delegate))
+ return;
+
+ if (ApiConventionHelper.IsGeneratedType(type))
+ return;
+
+ // Ignore compiler-produced classes (e.g. anonymous types) whose names start with '<'.
+ if (ApiConventionHelper.IsCompilerGeneratedName(type.MetadataName))
+ return;
+
+ if (ApiConventionHelper.HasAttribute(type, ApiConventionHelper.ExceptionToClassNameConventionAttribute))
+ return;
+
+ // Original test: invalid when it is not a valid class name OR it looks like an interface name.
+ if (!NamingHelper.ClassName.IsMatch(type.MetadataName) || NamingHelper.InterfaceName.IsMatch(type.MetadataName))
+ {
+ foreach (var location in type.Locations)
+ {
+ if (location.IsInSource)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ Descriptors.LuceneDev7004_ClassName, location, type.Name));
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7005_MemberContainsComparerAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7005_MemberContainsComparerAnalyzer.cs
new file mode 100644
index 0000000..6fb4600
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7005_MemberContainsComparerAnalyzer.cs
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System.Collections.Immutable;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx
+{
+ /// <summary>
+ /// Ports TestApiConsistency.TestForPublicMembersContainingComparer: reports protected fields,
+ /// externally visible types, and members (ordinary methods, properties and events) whose name
+ /// contains the Java term "Comparator". In .NET these should use "Comparer".
+ /// </summary>
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public sealed class LuceneDev7005_MemberContainsComparerAnalyzer : DiagnosticAnalyzer
+ {
+ public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
+ ImmutableArray.Create(Descriptors.LuceneDev7005_MemberContainsComparer);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
+ context.EnableConcurrentExecution();
+ context.RegisterSymbolAction(AnalyzeType, SymbolKind.NamedType);
+ context.RegisterSymbolAction(AnalyzeField, SymbolKind.Field);
+ context.RegisterSymbolAction(AnalyzeMethod, SymbolKind.Method);
+ context.RegisterSymbolAction(AnalyzeProperty, SymbolKind.Property);
+ context.RegisterSymbolAction(AnalyzeEvent, SymbolKind.Event);
+ }
+
+ private static bool Matches(string name) => NamingHelper.ContainsComparer.IsMatch(name);
+
+ private void AnalyzeType(SymbolAnalysisContext context)
+ {
+ var type = (INamedTypeSymbol)context.Symbol;
+
+ if (ApiConventionHelper.IsGeneratedType(type))
+ return;
+
+ // Original test: only externally visible types are flagged (t.IsVisible).
+ if (Matches(type.MetadataName) && IsVisible(type))
+ Report(context, type);
+ }
+
+ private void AnalyzeField(SymbolAnalysisContext context)
+ {
+ var field = (IFieldSymbol)context.Symbol;
+ var containingType = field.ContainingType;
+
+ if (containingType?.TypeKind != TypeKind.Class || ApiConventionHelper.IsGeneratedType(containingType))
+ return;
+
+ if (field.IsStatic || field.IsImplicitlyDeclared || ApiConventionHelper.IsCompilerGeneratedName(field.Name))
+ return;
+
+ // Original test scans protected (or protected internal) instance fields.
+ if (field.DeclaredAccessibility is not (Accessibility.Protected or Accessibility.ProtectedOrInternal))
+ return;
+
+ if (Matches(field.Name))
+ Report(context, field);
+ }
+
+ private void AnalyzeMethod(SymbolAnalysisContext context)
+ {
+ var method = (IMethodSymbol)context.Symbol;
+ // The original test also sees explicit interface implementations, which reflection
+ // reports under their qualified name (e.g. "IComparator.Compare").
+ if (method.MethodKind is not (MethodKind.Ordinary or MethodKind.ExplicitInterfaceImplementation))
+ return;
+
+ if (ShouldExamineMember(method) && Matches(method.Name))
+ Report(context, method);
+ }
+
+ private void AnalyzeProperty(SymbolAnalysisContext context)
+ {
+ var property = (IPropertySymbol)context.Symbol;
+ if (ShouldExamineMember(property) && Matches(property.Name))
+ Report(context, property);
+ }
+
+ private void AnalyzeEvent(SymbolAnalysisContext context)
+ {
+ var @event = (IEventSymbol)context.Symbol;
+ if (ShouldExamineMember(@event) && Matches(@event.Name))
+ Report(context, @event);
+ }
+
+ private static bool ShouldExamineMember(ISymbol member)
+ {
+ var containingType = member.ContainingType;
+ if (containingType is null || ApiConventionHelper.IsGeneratedType(containingType))
+ return false;
+
+ return !member.IsImplicitlyDeclared && !ApiConventionHelper.IsCompilerGeneratedName(member.Name);
+ }
+
+ private static bool IsVisible(INamedTypeSymbol type)
+ {
+ for (var current = type; current is not null; current = current.ContainingType)
+ {
+ if (current.DeclaredAccessibility != Accessibility.Public)
+ return false;
+ }
+
+ return true;
+ }
+
+ private void Report(SymbolAnalysisContext context, ISymbol symbol)
+ {
+ foreach (var location in symbol.Locations)
+ {
+ if (location.IsInSource)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ Descriptors.LuceneDev7005_MemberContainsComparer, location, symbol.Name));
+ }
+ }
+ }
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7006_MemberNamedSizeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7006_MemberNamedSizeAnalyzer.cs
new file mode 100644
index 0000000..779d03e
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7006_MemberNamedSizeAnalyzer.cs
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx
+{
+ /// <summary>
+ /// Ports TestApiConsistency.TestForPublicMembersNamedSize: reports parameterless methods and
+ /// properties named "Size" (case-insensitive). In .NET these should be "Count" or "Length".
+ /// Events are not considered, matching the original test.
+ /// </summary>
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public sealed class LuceneDev7006_MemberNamedSizeAnalyzer : MemberNamePredicateAnalyzerBase
+ {
+ public LuceneDev7006_MemberNamedSizeAnalyzer()
+ : base(Descriptors.LuceneDev7006_MemberNamedSize)
+ {
+ }
+
+ protected override bool IsViolation(string name) =>
+ "Size".Equals(name, StringComparison.OrdinalIgnoreCase);
+
+ // Original test only flags parameterless methods named Size.
+ protected override bool IncludeMethod(IMethodSymbol method) => method.Parameters.Length == 0;
+
+ // Original test does not consider events.
+ protected override bool IncludeEvents => false;
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7007_MemberContainsNonNetNumericAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7007_MemberContainsNonNetNumericAnalyzer.cs
new file mode 100644
index 0000000..d67c2d8
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7007_MemberContainsNonNetNumericAnalyzer.cs
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx
+{
+ /// <summary>
+ /// Ports TestApiConsistency.TestForPublicMembersContainingNonNetNumeric: reports ordinary
+ /// methods, properties and events whose name contains a Java-style numeric term ('Int' not
+ /// followed by 16/32/64, 'Long', 'Short' or 'Float'). Members decorated with
+ /// [ExceptionToNetNumericConvention] are excluded.
+ /// </summary>
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public sealed class LuceneDev7007_MemberContainsNonNetNumericAnalyzer : MemberNamePredicateAnalyzerBase
+ {
+ public LuceneDev7007_MemberContainsNonNetNumericAnalyzer()
+ : base(Descriptors.LuceneDev7007_MemberContainsNonNetNumeric)
+ {
+ }
+
+ protected override bool IsViolation(string name) =>
+ NamingHelper.ContainsNonNetNumeric.IsMatch(name);
+
+ protected override bool ShouldSkipMember(ISymbol member) =>
+ ApiConventionHelper.HasAttribute(member, ApiConventionHelper.ExceptionToNetNumericConventionAttribute);
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7008_TypeContainsNonNetNumericAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7008_TypeContainsNonNetNumericAnalyzer.cs
new file mode 100644
index 0000000..207e661
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/LuceneDev7008_TypeContainsNonNetNumericAnalyzer.cs
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System.Collections.Immutable;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx
+{
+ /// <summary>
+ /// Ports TestApiConsistency.TestForTypesContainingNonNetNumeric: reports types whose name
+ /// contains a Java-style numeric term ('Int' not followed by 16/32/64, 'Long', 'Short' or
+ /// 'Float'). Types decorated with [ExceptionToNetNumericConvention] are excluded.
+ /// </summary>
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public sealed class LuceneDev7008_TypeContainsNonNetNumericAnalyzer : DiagnosticAnalyzer
+ {
+ public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
+ ImmutableArray.Create(Descriptors.LuceneDev7008_TypeContainsNonNetNumeric);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
+ context.EnableConcurrentExecution();
+ context.RegisterSymbolAction(AnalyzeType, SymbolKind.NamedType);
+ }
+
+ private static void AnalyzeType(SymbolAnalysisContext context)
+ {
+ var type = (INamedTypeSymbol)context.Symbol;
+
+ if (ApiConventionHelper.IsGeneratedType(type))
+ return;
+
+ if (ApiConventionHelper.HasAttribute(type, ApiConventionHelper.ExceptionToNetNumericConventionAttribute))
+ return;
+
+ if (NamingHelper.ContainsNonNetNumeric.IsMatch(type.MetadataName))
+ {
+ foreach (var location in type.Locations)
+ {
+ if (location.IsInSource)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ Descriptors.LuceneDev7008_TypeContainsNonNetNumeric, location, type.Name));
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/MemberNamePredicateAnalyzerBase.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/MemberNamePredicateAnalyzerBase.cs
new file mode 100644
index 0000000..3e029fa
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev7xxx/MemberNamePredicateAnalyzerBase.cs
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System.Collections.Immutable;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx
+{
+ /// <summary>
+ /// Base class for the member-name analyzers that mirror the LUCENENET TestApiConsistency
+ /// rules which scan all members of all types (regardless of accessibility) and report when a
+ /// member's name matches a predicate. Like the original tests, these examine ordinary methods
+ /// (excluding property/event accessors), properties and events. Derived classes provide the
+ /// descriptor and the name predicate, and may override member-kind handling.
+ /// </summary>
+ public abstract class MemberNamePredicateAnalyzerBase : DiagnosticAnalyzer
+ {
+ private readonly DiagnosticDescriptor descriptor;
+
+ protected MemberNamePredicateAnalyzerBase(DiagnosticDescriptor descriptor)
+ {
+ this.descriptor = descriptor;
+ }
+
+ public sealed override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
+ ImmutableArray.Create(descriptor);
+
+ public sealed override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
+ context.EnableConcurrentExecution();
+ context.RegisterSymbolAction(AnalyzeMethod, SymbolKind.Method);
+ context.RegisterSymbolAction(AnalyzeProperty, SymbolKind.Property);
+ if (IncludeEvents)
+ context.RegisterSymbolAction(AnalyzeEvent, SymbolKind.Event);
+ }
+
+ /// <summary>Returns <c>true</c> when the given name violates this rule.</summary>
+ protected abstract bool IsViolation(string name);
+
+ /// <summary>
+ /// Allows derived classes to further restrict which ordinary methods are considered
+ /// (e.g. only parameterless methods). Defaults to all ordinary methods.
+ /// </summary>
+ protected virtual bool IncludeMethod(IMethodSymbol method) => true;
+
+ /// <summary>
+ /// Whether event members participate in this rule. Defaults to <c>true</c>; the
+ /// 'Size' rule excludes events to match the original test.
+ /// </summary>
+ protected virtual bool IncludeEvents => true;
+
+ /// <summary>
+ /// Allows derived classes to skip a member entirely (e.g. when it carries a
+ /// LUCENENET suppression attribute). Defaults to never skipping.
+ /// </summary>
+ protected virtual bool ShouldSkipMember(ISymbol member) => false;
+
+ private void AnalyzeMethod(SymbolAnalysisContext context)
+ {
+ var method = (IMethodSymbol)context.Symbol;
+
+ // Original tests examine ordinary methods, excluding get_/set_ accessors.
+ if (method.MethodKind != MethodKind.Ordinary)
+ return;
+
+ if (!ShouldExamine(method, method.ContainingType))
+ return;
+
+ if (!IncludeMethod(method))
+ return;
+
+ ReportIfViolation(context, method);
+ }
+
+ private void AnalyzeProperty(SymbolAnalysisContext context)
+ {
+ var property = (IPropertySymbol)context.Symbol;
+ if (!ShouldExamine(property, property.ContainingType))
+ return;
+
+ ReportIfViolation(context, property);
+ }
+
+ private void AnalyzeEvent(SymbolAnalysisContext context)
+ {
+ var @event = (IEventSymbol)context.Symbol;
+ if (!ShouldExamine(@event, @event.ContainingType))
+ return;
+
+ ReportIfViolation(context, @event);
+ }
+
+ private static bool ShouldExamine(ISymbol member, INamedTypeSymbol? containingType)
+ {
+ if (containingType is null)
+ return false;
+
+ if (ApiConventionHelper.IsGeneratedType(containingType))
+ return false;
+
+ if (member.IsImplicitlyDeclared || ApiConventionHelper.IsCompilerGeneratedName(member.Name))
+ return false;
+
+ return true;
+ }
+
+ private void ReportIfViolation(SymbolAnalysisContext context, ISymbol member)
+ {
+ if (ShouldSkipMember(member))
+ return;
+
+ if (!IsViolation(member.Name))
+ return;
+
+ foreach (var location in member.Locations)
+ {
+ if (location.IsInSource)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(descriptor, location, member.Name));
+ }
+ }
+ }
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/Resources.resx b/src/Lucene.Net.CodeAnalysis.Dev/Resources.resx
index 2bf6cb7..e02157f 100644
--- a/src/Lucene.Net.CodeAnalysis.Dev/Resources.resx
+++ b/src/Lucene.Net.CodeAnalysis.Dev/Resources.resx
@@ -439,4 +439,180 @@ under the License.
<value>'{0}' is referenced by this StackTraceHelper.DoesStackTraceContainMethod call and should be marked with [MethodImpl(MethodImplOptions.NoInlining)] to prevent the JIT from inlining it out of the stack trace.</value>
</data>
+ <!-- 1009: Public fields -->
+ <data name="LuceneDev1009_AnalyzerTitle" xml:space="preserve">
+ <value>Fields should not be public</value>
+ </data>
+ <data name="LuceneDev1009_AnalyzerDescription" xml:space="preserve">
+ <value>Public fields break encapsulation and cannot be changed to properties later without breaking binary compatibility. Use a public property instead.</value>
+ </data>
+ <data name="LuceneDev1009_AnalyzerMessageFormat" xml:space="preserve">
+ <value>Field '{0}' should not be public; consider using a public property instead.</value>
+ </data>
+
+ <!-- 1010: Property with a setter but no getter -->
+ <data name="LuceneDev1010_AnalyzerTitle" xml:space="preserve">
+ <value>Properties should not have a setter without a getter</value>
+ </data>
+ <data name="LuceneDev1010_AnalyzerDescription" xml:space="preserve">
+ <value>A property with a setter but no getter is write-only, which is unintuitive. Provide a getter, or expose a SetXxx method instead.</value>
+ </data>
+ <data name="LuceneDev1010_AnalyzerMessageFormat" xml:space="preserve">
+ <value>Property '{0}' has a setter but no getter; provide a getter or use a SetXxx method instead.</value>
+ </data>
+
+ <!-- 1011: Property returns an array -->
+ <data name="LuceneDev1011_AnalyzerTitle" xml:space="preserve">
+ <value>Properties should not return arrays</value>
+ </data>
+ <data name="LuceneDev1011_AnalyzerDescription" xml:space="preserve">
+ <value>A property that returns an array allows callers to mutate the array contents, and may allocate a new array on each access. Return a read-only collection, or expose a method that makes the allocation explicit.</value>
+ </data>
+ <data name="LuceneDev1011_AnalyzerMessageFormat" xml:space="preserve">
+ <value>Property '{0}' returns an array; consider returning a read-only collection or exposing a method instead.</value>
+ </data>
+
+ <!-- 1012: Method returns a writable array -->
+ <data name="LuceneDev1012_AnalyzerTitle" xml:space="preserve">
+ <value>Methods should not return writable arrays</value>
+ </data>
+ <data name="LuceneDev1012_AnalyzerDescription" xml:space="preserve">
+ <value>A method that returns a writable array allows callers to mutate internal state. Return a read-only collection, or document and intentionally return a copy.</value>
+ </data>
+ <data name="LuceneDev1012_AnalyzerMessageFormat" xml:space="preserve">
+ <value>Method '{0}' returns a writable array; consider returning a read-only collection instead.</value>
+ </data>
+
+ <!-- 1013: Nullable enum member -->
+ <data name="LuceneDev1013_AnalyzerTitle" xml:space="preserve">
+ <value>Public members should not be nullable enums</value>
+ </data>
+ <data name="LuceneDev1013_AnalyzerDescription" xml:space="preserve">
+ <value>A nullable enum public member is usually an artifact of the Java port. Prefer a non-nullable enum with an explicit 'None'/default value, or reconsider the API design.</value>
+ </data>
+ <data name="LuceneDev1013_AnalyzerMessageFormat" xml:space="preserve">
+ <value>Member '{0}' is a nullable enum; consider using a non-nullable enum with an explicit default value instead.</value>
+ </data>
+
+ <!-- 1014: Member accepts or returns IEnumerable (disabled by default; one-time port aid) -->
+ <data name="LuceneDev1014_AnalyzerTitle" xml:space="preserve">
+ <value>Members should not accept or return IEnumerable<T></value>
+ </data>
+ <data name="LuceneDev1014_AnalyzerDescription" xml:space="preserve">
+ <value>This rule identifies members that were changed from ICollection, IList or ISet to IEnumerable<T> during the Java port, which may have changed the API contract. It is disabled by default and intended as a one-time review aid.</value>
+ </data>
+ <data name="LuceneDev1014_AnalyzerMessageFormat" xml:space="preserve">
+ <value>Member '{0}' accepts or returns IEnumerable<T>; verify whether ICollection, IList or ISet is more appropriate.</value>
+ </data>
+
+ <!-- 1015: Member accepts or returns List<T> or Dictionary<K,V> -->
+ <data name="LuceneDev1015_AnalyzerTitle" xml:space="preserve">
+ <value>Members should not accept or return List<T> or Dictionary<K, V></value>
+ </data>
+ <data name="LuceneDev1015_AnalyzerDescription" xml:space="preserve">
+ <value>Exposing concrete collection types like List<T> or Dictionary<K, V> in public APIs leaks implementation details. Prefer interfaces such as IList<T>, IReadOnlyList<T>, IDictionary<K, V> or IReadOnlyDictionary<K, V>.</value>
+ </data>
+ <data name="LuceneDev1015_AnalyzerMessageFormat" xml:space="preserve">
+ <value>Member '{0}' accepts or returns '{1}'; prefer an interface type such as IList<T> or IDictionary<K, V> instead.</value>
+ </data>
+
+ <!-- 7000: Private field name -->
+ <data name="LuceneDev7000_AnalyzerTitle" xml:space="preserve">
+ <value>Private fields should follow the naming convention</value>
+ </data>
+ <data name="LuceneDev7000_AnalyzerDescription" xml:space="preserve">
+ <value>Private fields should be named using camelCase, optionally prefixed with an underscore (e.g. 'field' or '_field'), or be UPPER_CASE for constants.</value>
+ </data>
+ <data name="LuceneDev7000_AnalyzerMessageFormat" xml:space="preserve">
+ <value>Private field '{0}' does not follow the naming convention; use camelCase, optionally with a leading underscore.</value>
+ </data>
+
+ <!-- 7001: Protected field name -->
+ <data name="LuceneDev7001_AnalyzerTitle" xml:space="preserve">
+ <value>Protected fields should follow the naming convention</value>
+ </data>
+ <data name="LuceneDev7001_AnalyzerDescription" xml:space="preserve">
+ <value>Protected (and protected internal) fields should be named using the 'm_' prefix followed by camelCase (e.g. 'm_field'), or be UPPER_CASE for constants.</value>
+ </data>
+ <data name="LuceneDev7001_AnalyzerMessageFormat" xml:space="preserve">
+ <value>Protected field '{0}' does not follow the naming convention; use the 'm_' prefix followed by camelCase.</value>
+ </data>
+
+ <!-- 7002: Method parameter name -->
+ <data name="LuceneDev7002_AnalyzerTitle" xml:space="preserve">
+ <value>Method parameters should follow the naming convention</value>
+ </data>
+ <data name="LuceneDev7002_AnalyzerDescription" xml:space="preserve">
+ <value>Method parameters should be named using camelCase, starting with a lowercase letter.</value>
+ </data>
+ <data name="LuceneDev7002_AnalyzerMessageFormat" xml:space="preserve">
+ <value>Method parameter '{0}' does not follow the naming convention; use camelCase.</value>
+ </data>
+
+ <!-- 7003: Interface name -->
+ <data name="LuceneDev7003_AnalyzerTitle" xml:space="preserve">
+ <value>Interface names should begin with 'I'</value>
+ </data>
+ <data name="LuceneDev7003_AnalyzerDescription" xml:space="preserve">
+ <value>Interface names should begin with a capital 'I' followed by PascalCase (e.g. 'IComparer').</value>
+ </data>
+ <data name="LuceneDev7003_AnalyzerMessageFormat" xml:space="preserve">
+ <value>Interface '{0}' does not follow the naming convention; interface names should begin with 'I'.</value>
+ </data>
+
+ <!-- 7004: Class name -->
+ <data name="LuceneDev7004_AnalyzerTitle" xml:space="preserve">
+ <value>Class names should follow the naming convention</value>
+ </data>
+ <data name="LuceneDev7004_AnalyzerDescription" xml:space="preserve">
+ <value>Class names should use PascalCase, starting with an uppercase letter.</value>
+ </data>
+ <data name="LuceneDev7004_AnalyzerMessageFormat" xml:space="preserve">
+ <value>Class '{0}' does not follow the naming convention; use PascalCase.</value>
+ </data>
+
+ <!-- 7005: Member contains 'Comparator' -->
+ <data name="LuceneDev7005_AnalyzerTitle" xml:space="preserve">
+ <value>Public members should not contain the word 'Comparator'</value>
+ </data>
+ <data name="LuceneDev7005_AnalyzerDescription" xml:space="preserve">
+ <value>'Comparator' is a Java term. In .NET the equivalent is 'Comparer'. Rename the member to use 'Comparer'.</value>
+ </data>
+ <data name="LuceneDev7005_AnalyzerMessageFormat" xml:space="preserve">
+ <value>Member '{0}' contains the word 'Comparator'; use the .NET term 'Comparer' instead.</value>
+ </data>
+
+ <!-- 7006: Member named 'Size' -->
+ <data name="LuceneDev7006_AnalyzerTitle" xml:space="preserve">
+ <value>Public members should not be named 'Size'</value>
+ </data>
+ <data name="LuceneDev7006_AnalyzerDescription" xml:space="preserve">
+ <value>'Size' is a Java naming convention. In .NET, prefer 'Count' for the number of elements or 'Length' for arrays and strings.</value>
+ </data>
+ <data name="LuceneDev7006_AnalyzerMessageFormat" xml:space="preserve">
+ <value>Member '{0}' is named 'Size'; use 'Count' or 'Length' instead, per .NET conventions.</value>
+ </data>
+
+ <!-- 7007: Member contains non-.NET numeric term -->
+ <data name="LuceneDev7007_AnalyzerTitle" xml:space="preserve">
+ <value>Public members should not contain non-.NET numeric type names</value>
+ </data>
+ <data name="LuceneDev7007_AnalyzerDescription" xml:space="preserve">
+ <value>Member names should use .NET numeric type names. Java-style numeric terms such as 'Int', 'Long', 'Short' and 'Float' should be replaced with 'Int32', 'Int64', 'Int16' and 'Single' respectively.</value>
+ </data>
+ <data name="LuceneDev7007_AnalyzerMessageFormat" xml:space="preserve">
+ <value>Member '{0}' contains a non-.NET numeric term; use .NET numeric type names such as Int32, Int64, Int16 or Single.</value>
+ </data>
+
+ <!-- 7008: Type contains non-.NET numeric term -->
+ <data name="LuceneDev7008_AnalyzerTitle" xml:space="preserve">
+ <value>Type names should not contain non-.NET numeric type names</value>
+ </data>
+ <data name="LuceneDev7008_AnalyzerDescription" xml:space="preserve">
+ <value>Type names should use .NET numeric type names. Java-style numeric terms such as 'Int', 'Long', 'Short' and 'Float' should be replaced with 'Int32', 'Int64', 'Int16' and 'Single' respectively.</value>
+ </data>
+ <data name="LuceneDev7008_AnalyzerMessageFormat" xml:space="preserve">
+ <value>Type '{0}' contains a non-.NET numeric term; use .NET numeric type names such as Int32, Int64, Int16 or Single.</value>
+ </data>
+
</root>
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/Utility/ApiConventionHelper.cs b/src/Lucene.Net.CodeAnalysis.Dev/Utility/ApiConventionHelper.cs
new file mode 100644
index 0000000..cbc1126
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/Utility/ApiConventionHelper.cs
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System.Linq;
+using Microsoft.CodeAnalysis;
+
+namespace Lucene.Net.CodeAnalysis.Dev.Utility
+{
+ /// <summary>
+ /// Shared helpers for the analyzers ported from the LUCENENET TestApiConsistency
+ /// (ApiScanTestBase) rules. These mirror behaviors of the original reflection-based
+ /// tests, including the LUCENENET-specific suppression attributes.
+ /// </summary>
+ public static class ApiConventionHelper
+ {
+ // The LUCENENET attributes that suppressed individual TestApiConsistency findings.
+ // They are matched by simple name so existing lucenenet suppressions keep working
+ // without this analyzer assembly having to reference the attribute types.
+ public const string WritableArrayAttribute = "WritableArrayAttribute";
+ public const string ExceptionToClassNameConventionAttribute = "ExceptionToClassNameConventionAttribute";
+ public const string ExceptionToNetNumericConventionAttribute = "ExceptionToNetNumericConventionAttribute";
+ public const string ExceptionToNullableEnumConventionAttribute = "ExceptionToNullableEnumConventionAttribute";
+
+ /// <summary>
+ /// Returns <c>true</c> when <paramref name="symbol"/> is decorated with an attribute
+ /// whose class is named <paramref name="attributeSimpleName"/> (e.g. "WritableArrayAttribute").
+ /// </summary>
+ public static bool HasAttribute(ISymbol symbol, string attributeSimpleName) =>
+ symbol.GetAttributes().Any(a => a.AttributeClass?.Name == attributeSimpleName);
+
+ /// <summary>
+ /// Mirrors the original tests' exclusion of compiler/auto-generated members, which were
+ /// identified by a name beginning with '<' (e.g. backing fields and local-function methods).
+ /// </summary>
+ public static bool IsCompilerGeneratedName(string name) =>
+ name.StartsWith("<", System.StringComparison.Ordinal);
+
+ /// <summary>
+ /// The original tests ignored types decorated with [GeneratedCode] or [CompilerGenerated].
+ /// </summary>
+ public static bool IsGeneratedType(INamedTypeSymbol type) =>
+ HasAttribute(type, "GeneratedCodeAttribute") || HasAttribute(type, "CompilerGeneratedAttribute");
+
+ /// <summary>
+ /// Returns <c>true</c> when the type is in the Lucene.Net.Support namespace (or a child),
+ /// which several of the original rules skipped.
+ /// </summary>
+ public static bool IsInSupportNamespace(INamedTypeSymbol type)
+ {
+ var ns = type.ContainingNamespace?.ToDisplayString();
+ return ns is not null && ns.StartsWith("Lucene.Net.Support", System.StringComparison.Ordinal);
+ }
+
+ /// <summary>
+ /// Mirrors the original tests' IsNonPrivateProperty helper, which used the parameterless
+ /// PropertyInfo.GetGetMethod()/GetSetMethod() overloads. Those return an accessor only when
+ /// it is <c>public</c>, so a property "counts" only when it has a public getter or setter.
+ /// </summary>
+ public static bool HasPublicAccessor(IPropertySymbol property) =>
+ property.GetMethod is { DeclaredAccessibility: Accessibility.Public }
+ || property.SetMethod is { DeclaredAccessibility: Accessibility.Public };
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.LuceneDev1xxx.cs b/src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.LuceneDev1xxx.cs
index b2ae98e..804a3ba 100644
--- a/src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.LuceneDev1xxx.cs
+++ b/src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.LuceneDev1xxx.cs
@@ -91,5 +91,57 @@ public static partial class Descriptors
Design,
Warning
);
+
+ public static readonly DiagnosticDescriptor LuceneDev1009_PublicFields =
+ Diagnostic(
+ "LuceneDev1009",
+ Design,
+ Warning
+ );
+
+ public static readonly DiagnosticDescriptor LuceneDev1010_PropertyWithNoGetter =
+ Diagnostic(
+ "LuceneDev1010",
+ Design,
+ Warning
+ );
+
+ public static readonly DiagnosticDescriptor LuceneDev1011_PropertyReturnsArray =
+ Diagnostic(
+ "LuceneDev1011",
+ Design,
+ Warning
+ );
+
+ public static readonly DiagnosticDescriptor LuceneDev1012_MethodReturnsWritableArray =
+ Diagnostic(
+ "LuceneDev1012",
+ Design,
+ Warning
+ );
+
+ public static readonly DiagnosticDescriptor LuceneDev1013_NullableEnum =
+ Diagnostic(
+ "LuceneDev1013",
+ Design,
+ Warning
+ );
+
+ // Disabled by default: this rule was only ever used as a one-time porting aid to identify
+ // members changed from ICollection/IList/ISet to IEnumerable during the Java port.
+ public static readonly DiagnosticDescriptor LuceneDev1014_MemberAcceptsOrReturnsIEnumerable =
+ Diagnostic(
+ "LuceneDev1014",
+ Design,
+ Warning,
+ isEnabledByDefault: false
+ );
+
+ public static readonly DiagnosticDescriptor LuceneDev1015_MemberAcceptsOrReturnsListOrDictionary =
+ Diagnostic(
+ "LuceneDev1015",
+ Design,
+ Warning
+ );
}
}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.LuceneDev1xxx.cs b/src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.LuceneDev7xxx.cs
similarity index 56%
copy from src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.LuceneDev1xxx.cs
copy to src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.LuceneDev7xxx.cs
index b2ae98e..e18a118 100644
--- a/src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.LuceneDev1xxx.cs
+++ b/src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.LuceneDev7xxx.cs
@@ -27,68 +27,68 @@ public static partial class Descriptors
{
// IMPORTANT: Do not make these into properties! The AnalyzerReleases release management
// analyzers do not recognize them and will report RS2002 warnings if it cannot read the
- // DignosticDescriptor instance through a field.
+ // DiagnosticDescriptor instance through a field.
- public static readonly DiagnosticDescriptor LuceneDev1000_FloatingPointEquality =
+ public static readonly DiagnosticDescriptor LuceneDev7000_PrivateFieldName =
Diagnostic(
- "LuceneDev1000",
- Design,
+ "LuceneDev7000",
+ Naming,
Warning
);
- public static readonly DiagnosticDescriptor LuceneDev1001_FloatingPointFormatting =
+ public static readonly DiagnosticDescriptor LuceneDev7001_ProtectedFieldName =
Diagnostic(
- "LuceneDev1001",
- Design,
+ "LuceneDev7001",
+ Naming,
Warning
);
- public static readonly DiagnosticDescriptor LuceneDev1002_FloatingPointArithmetic =
+ public static readonly DiagnosticDescriptor LuceneDev7002_MethodParameterName =
Diagnostic(
- "LuceneDev1002",
- Design,
+ "LuceneDev7002",
+ Naming,
Warning
);
- public static readonly DiagnosticDescriptor LuceneDev1003_ArrayMethodParameter =
+ public static readonly DiagnosticDescriptor LuceneDev7003_InterfaceName =
Diagnostic(
- "LuceneDev1003",
- Design,
+ "LuceneDev7003",
+ Naming,
Warning
);
- public static readonly DiagnosticDescriptor LuceneDev1004_ArrayMethodReturnValue =
+ public static readonly DiagnosticDescriptor LuceneDev7004_ClassName =
Diagnostic(
- "LuceneDev1004",
- Design,
+ "LuceneDev7004",
+ Naming,
Warning
);
- public static readonly DiagnosticDescriptor LuceneDev1005_LuceneNetSupportPublicTypes =
+ public static readonly DiagnosticDescriptor LuceneDev7005_MemberContainsComparer =
Diagnostic(
- "LuceneDev1005",
- Design,
+ "LuceneDev7005",
+ Naming,
Warning
);
- public static readonly DiagnosticDescriptor LuceneDev1006_FloatingPointFormatting =
+ public static readonly DiagnosticDescriptor LuceneDev7006_MemberNamedSize =
Diagnostic(
- "LuceneDev1006",
- Design,
+ "LuceneDev7006",
+ Naming,
Warning
);
- public static readonly DiagnosticDescriptor LuceneDev1007_GenericDictionaryIndexerValueType =
+ public static readonly DiagnosticDescriptor LuceneDev7007_MemberContainsNonNetNumeric =
Diagnostic(
- "LuceneDev1007",
- Design,
+ "LuceneDev7007",
+ Naming,
Warning
);
- public static readonly DiagnosticDescriptor LuceneDev1008_GenericDictionaryIndexerReferenceType =
+ public static readonly DiagnosticDescriptor LuceneDev7008_TypeContainsNonNetNumeric =
Diagnostic(
- "LuceneDev1008",
- Design,
+ "LuceneDev7008",
+ Naming,
Warning
);
}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/Utility/FieldNameHelper.cs b/src/Lucene.Net.CodeAnalysis.Dev/Utility/FieldNameHelper.cs
new file mode 100644
index 0000000..38f93e4
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/Utility/FieldNameHelper.cs
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Lucene.Net.CodeAnalysis.Dev.Utility
+{
+ /// <summary>
+ /// Helpers for classifying the declared accessibility of a field declaration by its
+ /// syntactic modifiers. Shared by the field-related analyzers (LuceneDev7000,
+ /// LuceneDev7001 and LuceneDev1009).
+ /// </summary>
+ public static class FieldNameHelper
+ {
+ /// <summary>
+ /// Returns <c>true</c> when the field has <c>protected</c> accessibility
+ /// (including <c>protected internal</c> and <c>private protected</c>).
+ /// </summary>
+ public static bool IsProtected(FieldDeclarationSyntax field) =>
+ field.Modifiers.Any(SyntaxKind.ProtectedKeyword);
+
+ /// <summary>
+ /// Returns <c>true</c> when the field has <c>public</c> accessibility.
+ /// </summary>
+ public static bool IsPublic(FieldDeclarationSyntax field) =>
+ field.Modifiers.Any(SyntaxKind.PublicKeyword);
+
+ /// <summary>
+ /// Returns <c>true</c> when the field is effectively private, i.e. it has no
+ /// accessibility modifier (the C# default for a field) or an explicit
+ /// <c>private</c> modifier, and is not <c>protected</c>, <c>internal</c> or
+ /// <c>public</c>.
+ /// </summary>
+ public static bool IsPrivate(FieldDeclarationSyntax field)
+ {
+ if (field.Modifiers.Any(SyntaxKind.PublicKeyword)
+ || field.Modifiers.Any(SyntaxKind.ProtectedKeyword)
+ || field.Modifiers.Any(SyntaxKind.InternalKeyword))
+ {
+ return false;
+ }
+
+ return true; // explicit 'private' or no accessibility modifier
+ }
+ }
+}
diff --git a/src/Lucene.Net.CodeAnalysis.Dev/Utility/NamingHelper.cs b/src/Lucene.Net.CodeAnalysis.Dev/Utility/NamingHelper.cs
new file mode 100644
index 0000000..0321dbe
--- /dev/null
+++ b/src/Lucene.Net.CodeAnalysis.Dev/Utility/NamingHelper.cs
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System.Text.RegularExpressions;
+
+namespace Lucene.Net.CodeAnalysis.Dev.Utility
+{
+ /// <summary>
+ /// Shared naming-convention helpers ported from the LUCENENET TestApiConsistency
+ /// (ApiScanTestBase) rules. Centralizes the regular expressions so the individual
+ /// naming analyzers (LuceneDev7000-LuceneDev7008) stay small and consistent.
+ /// </summary>
+ public static class NamingHelper
+ {
+ // Private field names: camelCase, optionally with a leading underscore, OR an UPPER_CASE constant.
+ public static readonly Regex PrivateFieldName = new Regex(
+ "^_?[a-z][a-zA-Z0-9_]*$|^[A-Z0-9_]+$", RegexOptions.Compiled);
+
+ // Protected field names: 'm_' prefix followed by camelCase, OR an UPPER_CASE constant.
+ public static readonly Regex ProtectedFieldName = new Regex(
+ "^m_[a-z][a-zA-Z0-9_]*$|^[A-Z0-9_]+$", RegexOptions.Compiled);
+
+ // Method parameter names: camelCase.
+ public static readonly Regex MethodParameterName = new Regex(
+ "^[a-z](?:[a-zA-Z0-9_]*[a-zA-Z0-9])?$", RegexOptions.Compiled);
+
+ // Interface names: 'I' prefix followed by PascalCase (optionally with a generic arity suffix).
+ public static readonly Regex InterfaceName = new Regex(
+ "^I[A-Z][a-zA-Z0-9_]*(?:`\\d+)?$", RegexOptions.Compiled);
+
+ // Class names: PascalCase (optionally with a generic arity suffix).
+ public static readonly Regex ClassName = new Regex(
+ "^[A-Z][a-zA-Z0-9_]*(?:`\\d+)?$", RegexOptions.Compiled);
+
+ // Names containing the Java term "comparator" (case-insensitive).
+ public static readonly Regex ContainsComparer = new Regex(
+ "[Cc]omparator", RegexOptions.Compiled);
+
+ // Names containing a Java-style numeric term (Int/Long/Short/Float) that is not a valid
+ // .NET numeric type name (Int16/Int32/Int64 etc.) or an innocuous substring (point, print,
+ // join, integer, longest, shortest, ...).
+ public static readonly Regex ContainsNonNetNumeric = new Regex(
+ "(?<![Pp]o|[Pp]r|[Jj]o)[Ii]nt(?!16|32|64|er|eg|ro)|[Ll]ong(?!est|er)|[Ss]hort(?!est|er)|[Ff]loat",
+ RegexOptions.Compiled);
+ }
+}
diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/LuceneDev7xxx/TestLuceneDev7001_ProtectedFieldNameCodeFixProvider.cs b/tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/LuceneDev7xxx/TestLuceneDev7001_ProtectedFieldNameCodeFixProvider.cs
new file mode 100644
index 0000000..19461f8
--- /dev/null
+++ b/tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/LuceneDev7xxx/TestLuceneDev7001_ProtectedFieldNameCodeFixProvider.cs
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx;
+using Lucene.Net.CodeAnalysis.Dev.TestUtilities;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Testing;
+using NUnit.Framework;
+
+namespace Lucene.Net.CodeAnalysis.Dev.CodeFixes;
+
+public class TestLuceneDev7001_ProtectedFieldNameCodeFixProvider
+{
+ [Test]
+ public async Task RenamesProtectedFieldToMPrefix()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ protected int count;
+
+ protected int Get() => count;
+ }
+ """;
+
+ const string fixedCode =
+ """
+ public class MyClass
+ {
+ protected int m_count;
+
+ protected int Get() => m_count;
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7001_ProtectedFieldName)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("count")
+ .WithLocation(3, 19);
+
+ var test = new InjectableCodeFixTest(
+ () => new LuceneDev7001_ProtectedFieldNameAnalyzer(),
+ () => new LuceneDev7001_ProtectedFieldNameCodeFixProvider())
+ {
+ TestCode = testCode.ReplaceLineEndings(),
+ FixedCode = fixedCode.ReplaceLineEndings(),
+ ExpectedDiagnostics = { expected }
+ };
+
+ await test.RunAsync();
+ }
+}
diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/LuceneDev7xxx/TestLuceneDev7003_InterfaceNameCodeFixProvider.cs b/tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/LuceneDev7xxx/TestLuceneDev7003_InterfaceNameCodeFixProvider.cs
new file mode 100644
index 0000000..f28e012
--- /dev/null
+++ b/tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/LuceneDev7xxx/TestLuceneDev7003_InterfaceNameCodeFixProvider.cs
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx;
+using Lucene.Net.CodeAnalysis.Dev.TestUtilities;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Testing;
+using NUnit.Framework;
+
+namespace Lucene.Net.CodeAnalysis.Dev.CodeFixes;
+
+public class TestLuceneDev7003_InterfaceNameCodeFixProvider
+{
+ [Test]
+ public async Task RenamesInterfaceToIPrefix()
+ {
+ const string testCode =
+ """
+ public interface Walkable
+ {
+ void Walk();
+ }
+
+ public class Dog : Walkable
+ {
+ public void Walk() { }
+ }
+ """;
+
+ const string fixedCode =
+ """
+ public interface IWalkable
+ {
+ void Walk();
+ }
+
+ public class Dog : IWalkable
+ {
+ public void Walk() { }
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7003_InterfaceName)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("Walkable")
+ .WithLocation(1, 18);
+
+ var test = new InjectableCodeFixTest(
+ () => new LuceneDev7003_InterfaceNameAnalyzer(),
+ () => new LuceneDev7003_InterfaceNameCodeFixProvider())
+ {
+ TestCode = testCode.ReplaceLineEndings(),
+ FixedCode = fixedCode.ReplaceLineEndings(),
+ ExpectedDiagnostics = { expected }
+ };
+
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task RenamesLowercaseIPrefixToUppercase()
+ {
+ const string testCode =
+ """
+ public interface iWalkable
+ {
+ void Walk();
+ }
+
+ public class Dog : iWalkable
+ {
+ public void Walk() { }
+ }
+ """;
+
+ const string fixedCode =
+ """
+ public interface IWalkable
+ {
+ void Walk();
+ }
+
+ public class Dog : IWalkable
+ {
+ public void Walk() { }
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7003_InterfaceName)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("iWalkable")
+ .WithLocation(1, 18);
+
+ var test = new InjectableCodeFixTest(
+ () => new LuceneDev7003_InterfaceNameAnalyzer(),
+ () => new LuceneDev7003_InterfaceNameCodeFixProvider())
+ {
+ TestCode = testCode.ReplaceLineEndings(),
+ FixedCode = fixedCode.ReplaceLineEndings(),
+ ExpectedDiagnostics = { expected }
+ };
+
+ await test.RunAsync();
+ }
+}
diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1009_PublicFieldsAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1009_PublicFieldsAnalyzer.cs
new file mode 100644
index 0000000..083dc4c
--- /dev/null
+++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1009_PublicFieldsAnalyzer.cs
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.LuceneDev1xxx;
+using Lucene.Net.CodeAnalysis.Dev.TestUtilities;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Testing;
+using NUnit.Framework;
+
+namespace Lucene.Net.CodeAnalysis.Dev;
+
+public class TestLuceneDev1009_PublicFieldsAnalyzer
+{
+ [Test]
+ public async Task PublicInstanceField_Diagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ public int Value;
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev1009_PublicFields)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("Value")
+ .WithLocation(3, 16);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1009_PublicFieldsAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task PublicConstField_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ public const int Value = 1;
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1009_PublicFieldsAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task PublicStaticField_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ public static int Value;
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1009_PublicFieldsAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task PrivateField_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ private int value;
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1009_PublicFieldsAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+}
diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1010_PropertyWithNoGetterAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1010_PropertyWithNoGetterAnalyzer.cs
new file mode 100644
index 0000000..86816a3
--- /dev/null
+++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1010_PropertyWithNoGetterAnalyzer.cs
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.LuceneDev1xxx;
+using Lucene.Net.CodeAnalysis.Dev.TestUtilities;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Testing;
+using NUnit.Framework;
+
+namespace Lucene.Net.CodeAnalysis.Dev;
+
+public class TestLuceneDev1010_PropertyWithNoGetterAnalyzer
+{
+ [Test]
+ public async Task SetterOnlyProperty_Diagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ private int _value;
+ public int Value { set => _value = value; }
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev1010_PropertyWithNoGetter)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("Value")
+ .WithLocation(4, 16);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1010_PropertyWithNoGetterAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task PropertyWithGetterAndSetter_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ public int Value { get; set; }
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1010_PropertyWithNoGetterAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task SetOnlyIndexer_Diagnostic()
+ {
+ // Reflection GetProperties includes indexers, so a set-only indexer is also flagged.
+ const string testCode =
+ """
+ public class MyClass
+ {
+ private int _value;
+ public int this[int index] { set => _value = value; }
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev1010_PropertyWithNoGetter)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("this[]")
+ .WithLocation(4, 16);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1010_PropertyWithNoGetterAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+}
diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1011_PropertyReturnsArrayAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1011_PropertyReturnsArrayAnalyzer.cs
new file mode 100644
index 0000000..46fd3b5
--- /dev/null
+++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1011_PropertyReturnsArrayAnalyzer.cs
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.LuceneDev1xxx;
+using Lucene.Net.CodeAnalysis.Dev.TestUtilities;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Testing;
+using NUnit.Framework;
+
+namespace Lucene.Net.CodeAnalysis.Dev;
+
+public class TestLuceneDev1011_PropertyReturnsArrayAnalyzer
+{
+ [Test]
+ public async Task PropertyReturnsArray_Diagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ private readonly int[] _values = new int[1];
+ public int[] Values => _values;
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev1011_PropertyReturnsArray)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("Values")
+ .WithLocation(4, 18);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1011_PropertyReturnsArrayAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task PropertyReturnsNonArray_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ public int Value => 0;
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1011_PropertyReturnsArrayAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+}
diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1012_MethodReturnsWritableArrayAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1012_MethodReturnsWritableArrayAnalyzer.cs
new file mode 100644
index 0000000..8e02a2b
--- /dev/null
+++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1012_MethodReturnsWritableArrayAnalyzer.cs
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.LuceneDev1xxx;
+using Lucene.Net.CodeAnalysis.Dev.TestUtilities;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Testing;
+using NUnit.Framework;
+
+namespace Lucene.Net.CodeAnalysis.Dev;
+
+public class TestLuceneDev1012_MethodReturnsWritableArrayAnalyzer
+{
+ [Test]
+ public async Task ReturnsFieldDirectly_Diagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ private readonly int[] m_values = new int[1];
+ public int[] GetValues()
+ {
+ return m_values;
+ }
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev1012_MethodReturnsWritableArray)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("GetValues")
+ .WithLocation(4, 18);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1012_MethodReturnsWritableArrayAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task ReturnsFieldDirectly_ExpressionBodied_Diagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ private readonly int[] m_values = new int[1];
+ public int[] GetValues() => m_values;
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev1012_MethodReturnsWritableArray)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("GetValues")
+ .WithLocation(4, 18);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1012_MethodReturnsWritableArrayAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task ReturnsClonedArray_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ using System.Linq;
+ public class MyClass
+ {
+ private readonly int[] m_values = new int[1];
+ public int[] GetValues() => m_values.ToArray();
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1012_MethodReturnsWritableArrayAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task ReturnsNewArray_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ public int[] GetValues() => new int[1];
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1012_MethodReturnsWritableArrayAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task ReturnsFieldDirectly_NullableArrayReturnType_Diagnostic()
+ {
+ const string testCode =
+ """
+ #nullable enable
+ public class MyClass
+ {
+ private readonly int[]? m_values = null;
+ public int[]? GetValues() => m_values;
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev1012_MethodReturnsWritableArray)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("GetValues")
+ .WithLocation(5, 19);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1012_MethodReturnsWritableArrayAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+}
diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1013_NullableEnumAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1013_NullableEnumAnalyzer.cs
new file mode 100644
index 0000000..c9fabe4
--- /dev/null
+++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1013_NullableEnumAnalyzer.cs
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.LuceneDev1xxx;
+using Lucene.Net.CodeAnalysis.Dev.TestUtilities;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Testing;
+using NUnit.Framework;
+
+namespace Lucene.Net.CodeAnalysis.Dev;
+
+public class TestLuceneDev1013_NullableEnumAnalyzer
+{
+ private const string EnumDef =
+ """
+ public enum Mode { First, Second }
+ """;
+
+ [Test]
+ public async Task NullableEnumProperty_Diagnostic()
+ {
+ string testCode =
+ $$"""
+ {{EnumDef}}
+ public class MyClass
+ {
+ public Mode? Mode { get; set; }
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev1013_NullableEnum)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("Mode")
+ .WithLocation(4, 18);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1013_NullableEnumAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task NullableEnumParameter_Diagnostic()
+ {
+ string testCode =
+ $$"""
+ {{EnumDef}}
+ public class MyClass
+ {
+ public void Configure(Mode? mode) { }
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev1013_NullableEnum)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("mode")
+ .WithLocation(4, 33);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1013_NullableEnumAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task NonNullableEnumProperty_NoDiagnostic()
+ {
+ string testCode =
+ $$"""
+ {{EnumDef}}
+ public class MyClass
+ {
+ public Mode Mode { get; set; }
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1013_NullableEnumAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task NullableIntProperty_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ public int? Value { get; set; }
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1013_NullableEnumAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+}
diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1014_MemberAcceptsOrReturnsIEnumerableAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1014_MemberAcceptsOrReturnsIEnumerableAnalyzer.cs
new file mode 100644
index 0000000..8c5de8b
--- /dev/null
+++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1014_MemberAcceptsOrReturnsIEnumerableAnalyzer.cs
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.LuceneDev1xxx;
+using Lucene.Net.CodeAnalysis.Dev.TestUtilities;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Testing;
+using NUnit.Framework;
+
+namespace Lucene.Net.CodeAnalysis.Dev;
+
+// Note: LuceneDev1014 is disabled by default, but the analyzer test harness runs the analyzer
+// directly, so its diagnostics are still observed here.
+public class TestLuceneDev1014_MemberAcceptsOrReturnsIEnumerableAnalyzer
+{
+ [Test]
+ public async Task MethodReturnsIEnumerable_Diagnostic()
+ {
+ const string testCode =
+ """
+ using System.Collections.Generic;
+ public class MyClass
+ {
+ public IEnumerable<int> GetItems() => null;
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev1014_MemberAcceptsOrReturnsIEnumerable)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("GetItems")
+ .WithLocation(4, 29);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1014_MemberAcceptsOrReturnsIEnumerableAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task MethodAcceptsIEnumerableParameter_Diagnostic()
+ {
+ const string testCode =
+ """
+ using System.Collections.Generic;
+ public class MyClass
+ {
+ public void SetItems(IEnumerable<int> items) { }
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev1014_MemberAcceptsOrReturnsIEnumerable)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("items")
+ .WithLocation(4, 43);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1014_MemberAcceptsOrReturnsIEnumerableAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task MethodReturnsIList_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ using System.Collections.Generic;
+ public class MyClass
+ {
+ public IList<int> GetItems() => null;
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1014_MemberAcceptsOrReturnsIEnumerableAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+}
diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1015_MemberAcceptsOrReturnsListOrDictionaryAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1015_MemberAcceptsOrReturnsListOrDictionaryAnalyzer.cs
new file mode 100644
index 0000000..54d7236
--- /dev/null
+++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1015_MemberAcceptsOrReturnsListOrDictionaryAnalyzer.cs
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.LuceneDev1xxx;
+using Lucene.Net.CodeAnalysis.Dev.TestUtilities;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Testing;
+using NUnit.Framework;
+
+namespace Lucene.Net.CodeAnalysis.Dev;
+
+public class TestLuceneDev1015_MemberAcceptsOrReturnsListOrDictionaryAnalyzer
+{
+ [Test]
+ public async Task MethodReturnsList_Diagnostic()
+ {
+ const string testCode =
+ """
+ using System.Collections.Generic;
+ public class MyClass
+ {
+ public List<int> GetItems() => null;
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev1015_MemberAcceptsOrReturnsListOrDictionary)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("GetItems", "List")
+ .WithLocation(4, 22);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1015_MemberAcceptsOrReturnsListOrDictionaryAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task MethodAcceptsDictionaryParameter_Diagnostic()
+ {
+ const string testCode =
+ """
+ using System.Collections.Generic;
+ public class MyClass
+ {
+ public void SetItems(Dictionary<int, string> items) { }
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev1015_MemberAcceptsOrReturnsListOrDictionary)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("items", "Dictionary")
+ .WithLocation(4, 50);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1015_MemberAcceptsOrReturnsListOrDictionaryAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task MethodReturnsIList_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ using System.Collections.Generic;
+ public class MyClass
+ {
+ public IList<int> GetItems() => null;
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1015_MemberAcceptsOrReturnsListOrDictionaryAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task PrivateMethodReturnsList_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ using System.Collections.Generic;
+ public class MyClass
+ {
+ private List<int> GetItems() => null;
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1015_MemberAcceptsOrReturnsListOrDictionaryAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task PropertyWithOnlyNonPublicAccessor_NoDiagnostic()
+ {
+ // The original IsNonPrivateProperty requires a public accessor; a property whose only
+ // accessors are internal/private must not be flagged.
+ const string testCode =
+ """
+ using System.Collections.Generic;
+ public class MyClass
+ {
+ internal List<int> Items { get; set; }
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1015_MemberAcceptsOrReturnsListOrDictionaryAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+}
diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7000_PrivateFieldNameAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7000_PrivateFieldNameAnalyzer.cs
new file mode 100644
index 0000000..912d52d
--- /dev/null
+++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7000_PrivateFieldNameAnalyzer.cs
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx;
+using Lucene.Net.CodeAnalysis.Dev.TestUtilities;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Testing;
+using NUnit.Framework;
+
+namespace Lucene.Net.CodeAnalysis.Dev;
+
+public class TestLuceneDev7000_PrivateFieldNameAnalyzer
+{
+ [Test]
+ public async Task TestEmptyFile()
+ {
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7000_PrivateFieldNameAnalyzer())
+ {
+ TestCode = ""
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ [TestCase("value")]
+ [TestCase("_value")]
+ [TestCase("MAX_VALUE")]
+ public async Task ValidNames_NoDiagnostic(string fieldName)
+ {
+ string testCode =
+ $$"""
+ public class MyClass
+ {
+ private int {{fieldName}};
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7000_PrivateFieldNameAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task PascalCasePrivateField_Diagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ private int FieldValue;
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7000_PrivateFieldName)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("FieldValue")
+ .WithLocation(3, 17);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7000_PrivateFieldNameAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task InternalField_AlsoChecked_Diagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ internal int FieldValue;
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7000_PrivateFieldName)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("FieldValue")
+ .WithLocation(3, 18);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7000_PrivateFieldNameAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task PublicField_NotChecked_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ public int FieldValue;
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7000_PrivateFieldNameAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+}
diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7001_ProtectedFieldNameAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7001_ProtectedFieldNameAnalyzer.cs
new file mode 100644
index 0000000..f98fcae
--- /dev/null
+++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7001_ProtectedFieldNameAnalyzer.cs
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx;
+using Lucene.Net.CodeAnalysis.Dev.TestUtilities;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Testing;
+using NUnit.Framework;
+
+namespace Lucene.Net.CodeAnalysis.Dev;
+
+public class TestLuceneDev7001_ProtectedFieldNameAnalyzer
+{
+ [Test]
+ [TestCase("m_value")]
+ [TestCase("MAX_VALUE")]
+ public async Task ValidNames_NoDiagnostic(string fieldName)
+ {
+ string testCode =
+ $$"""
+ public class MyClass
+ {
+ protected int {{fieldName}};
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7001_ProtectedFieldNameAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task ProtectedFieldWithoutPrefix_Diagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ protected int count;
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7001_ProtectedFieldName)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("count")
+ .WithLocation(3, 19);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7001_ProtectedFieldNameAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task ProtectedInternalField_AlsoChecked_Diagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ protected internal int count;
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7001_ProtectedFieldName)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("count")
+ .WithLocation(3, 28);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7001_ProtectedFieldNameAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task PrivateField_NotChecked_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ private int count;
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7001_ProtectedFieldNameAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task ProtectedFieldInSupportNamespace_Skipped_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ namespace Lucene.Net.Support
+ {
+ public class MyClass
+ {
+ protected int count;
+ }
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7001_ProtectedFieldNameAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+}
diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7002_MethodParameterNameAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7002_MethodParameterNameAnalyzer.cs
new file mode 100644
index 0000000..48700d5
--- /dev/null
+++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7002_MethodParameterNameAnalyzer.cs
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx;
+using Lucene.Net.CodeAnalysis.Dev.TestUtilities;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Testing;
+using NUnit.Framework;
+
+namespace Lucene.Net.CodeAnalysis.Dev;
+
+public class TestLuceneDev7002_MethodParameterNameAnalyzer
+{
+ [Test]
+ public async Task CamelCaseParameter_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ public int Add(int first, int second) => first + second;
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7002_MethodParameterNameAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task PascalCaseParameter_Diagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ public int Add(int First, int second) => First + second;
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7002_MethodParameterName)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("First")
+ .WithLocation(3, 24);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7002_MethodParameterNameAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task TrailingUnderscoreParameter_Diagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ public void Foo(int value_) { }
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7002_MethodParameterName)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("value_")
+ .WithLocation(3, 25);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7002_MethodParameterNameAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+}
diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7003_InterfaceNameAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7003_InterfaceNameAnalyzer.cs
new file mode 100644
index 0000000..38813b2
--- /dev/null
+++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7003_InterfaceNameAnalyzer.cs
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx;
+using Lucene.Net.CodeAnalysis.Dev.TestUtilities;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Testing;
+using NUnit.Framework;
+
+namespace Lucene.Net.CodeAnalysis.Dev;
+
+public class TestLuceneDev7003_InterfaceNameAnalyzer
+{
+ [Test]
+ [TestCase("IComparer")]
+ [TestCase("IList")]
+ public async Task ValidInterfaceName_NoDiagnostic(string name)
+ {
+ string testCode =
+ $$"""
+ public interface {{name}}
+ {
+ void DoWork();
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7003_InterfaceNameAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task InterfaceWithoutIPrefix_Diagnostic()
+ {
+ const string testCode =
+ """
+ public interface Comparer
+ {
+ void DoWork();
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7003_InterfaceName)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("Comparer")
+ .WithLocation(1, 18);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7003_InterfaceNameAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task GenericInterfaceWithIPrefix_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ public interface IComparer<T>
+ {
+ int Compare(T a, T b);
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7003_InterfaceNameAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+}
diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7004_ClassNameAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7004_ClassNameAnalyzer.cs
new file mode 100644
index 0000000..5fc3ecf
--- /dev/null
+++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7004_ClassNameAnalyzer.cs
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx;
+using Lucene.Net.CodeAnalysis.Dev.TestUtilities;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Testing;
+using NUnit.Framework;
+
+namespace Lucene.Net.CodeAnalysis.Dev;
+
+public class TestLuceneDev7004_ClassNameAnalyzer
+{
+ [Test]
+ public async Task PascalCaseClassName_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7004_ClassNameAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task ClassNameLikeInterface_Diagnostic()
+ {
+ const string testCode =
+ """
+ public class IMyClass
+ {
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7004_ClassName)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("IMyClass")
+ .WithLocation(1, 14);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7004_ClassNameAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task LowerCaseClassName_Diagnostic()
+ {
+ const string testCode =
+ """
+ public class myClass
+ {
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7004_ClassName)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("myClass")
+ .WithLocation(1, 14);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7004_ClassNameAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task DelegateNameLikeInterface_Diagnostic()
+ {
+ // Delegates are CLR classes (reflection t.IsClass), so they follow the class-name rule.
+ const string testCode =
+ """
+ public delegate void IMyHandler();
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7004_ClassName)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("IMyHandler")
+ .WithLocation(1, 22);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7004_ClassNameAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task ValidDelegateName_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ public delegate void MyHandler();
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7004_ClassNameAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+}
diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7005_MemberContainsComparerAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7005_MemberContainsComparerAnalyzer.cs
new file mode 100644
index 0000000..1aaa500
--- /dev/null
+++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7005_MemberContainsComparerAnalyzer.cs
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx;
+using Lucene.Net.CodeAnalysis.Dev.TestUtilities;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Testing;
+using NUnit.Framework;
+
+namespace Lucene.Net.CodeAnalysis.Dev;
+
+public class TestLuceneDev7005_MemberContainsComparerAnalyzer
+{
+ [Test]
+ public async Task MethodNamedComparer_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ public int GetComparer() => 0;
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7005_MemberContainsComparerAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task MethodContainingComparator_Diagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ public int GetComparator() => 0;
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7005_MemberContainsComparer)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("GetComparator")
+ .WithLocation(3, 16);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7005_MemberContainsComparerAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task TypeNamedComparator_Diagnostic()
+ {
+ const string testCode =
+ """
+ public class MyComparator
+ {
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7005_MemberContainsComparer)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("MyComparator")
+ .WithLocation(1, 14);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7005_MemberContainsComparerAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task ProtectedFieldContainingComparator_Diagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ protected int m_comparatorState;
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7005_MemberContainsComparer)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("m_comparatorState")
+ .WithLocation(3, 19);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7005_MemberContainsComparerAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task ExplicitInterfaceImplementationContainingComparator_Diagnostic()
+ {
+ // Matches the original reflection-based scan, which sees the explicit implementation
+ // under its qualified name (e.g. "IComparator.ComparatorMethod").
+ const string testCode =
+ """
+ public interface IComparator
+ {
+ void ComparatorMethod();
+ }
+
+ public class MyClass : IComparator
+ {
+ void IComparator.ComparatorMethod() { }
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7005_MemberContainsComparerAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics =
+ {
+ new DiagnosticResult(Descriptors.LuceneDev7005_MemberContainsComparer)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("IComparator")
+ .WithLocation(1, 18),
+ new DiagnosticResult(Descriptors.LuceneDev7005_MemberContainsComparer)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("ComparatorMethod")
+ .WithLocation(3, 10),
+ new DiagnosticResult(Descriptors.LuceneDev7005_MemberContainsComparer)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("IComparator.ComparatorMethod")
+ .WithLocation(8, 22),
+ }
+ };
+ await test.RunAsync();
+ }
+}
diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7006_MemberNamedSizeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7006_MemberNamedSizeAnalyzer.cs
new file mode 100644
index 0000000..ef09037
--- /dev/null
+++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7006_MemberNamedSizeAnalyzer.cs
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx;
+using Lucene.Net.CodeAnalysis.Dev.TestUtilities;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Testing;
+using NUnit.Framework;
+
+namespace Lucene.Net.CodeAnalysis.Dev;
+
+public class TestLuceneDev7006_MemberNamedSizeAnalyzer
+{
+ [Test]
+ public async Task PropertyNamedCount_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ public int Count { get; set; }
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7006_MemberNamedSizeAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task PropertyNamedSize_Diagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ public int Size { get; set; }
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7006_MemberNamedSize)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("Size")
+ .WithLocation(3, 16);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7006_MemberNamedSizeAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task ParameterlessMethodNamedSize_Diagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ public int Size() => 0;
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7006_MemberNamedSize)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("Size")
+ .WithLocation(3, 16);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7006_MemberNamedSizeAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task MethodNamedSizeWithParameters_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ public class MyClass
+ {
+ public int Size(int dimension) => dimension;
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7006_MemberNamedSizeAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+}
diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7007_MemberContainsNonNetNumericAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7007_MemberContainsNonNetNumericAnalyzer.cs
new file mode 100644
index 0000000..c6353ee
--- /dev/null
+++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7007_MemberContainsNonNetNumericAnalyzer.cs
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx;
+using Lucene.Net.CodeAnalysis.Dev.TestUtilities;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Testing;
+using NUnit.Framework;
+
+namespace Lucene.Net.CodeAnalysis.Dev;
+
+public class TestLuceneDev7007_MemberContainsNonNetNumericAnalyzer
+{
+ [Test]
+ [TestCase("ReadInt32")]
+ [TestCase("Intern")]
+ [TestCase("GetPoint")]
+ public async Task NetNumericOrInnocuousNames_NoDiagnostic(string methodName)
+ {
+ string testCode =
+ $$"""
+ public class MyClass
+ {
+ public int {{methodName}}() => 0;
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7007_MemberContainsNonNetNumericAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ [TestCase("ReadLong", 16)]
+ [TestCase("ReadInt", 16)]
+ [TestCase("ReadFloat", 16)]
+ [TestCase("ReadShort", 16)]
+ public async Task NonNetNumericMethodName_Diagnostic(string methodName, int column)
+ {
+ string testCode =
+ $$"""
+ public class MyClass
+ {
+ public int {{methodName}}() => 0;
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7007_MemberContainsNonNetNumeric)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments(methodName)
+ .WithLocation(3, column);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7007_MemberContainsNonNetNumericAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+}
diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7008_TypeContainsNonNetNumericAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7008_TypeContainsNonNetNumericAnalyzer.cs
new file mode 100644
index 0000000..c80f8b0
--- /dev/null
+++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev7xxx/TestLuceneDev7008_TypeContainsNonNetNumericAnalyzer.cs
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Lucene.Net.CodeAnalysis.Dev.LuceneDev7xxx;
+using Lucene.Net.CodeAnalysis.Dev.TestUtilities;
+using Lucene.Net.CodeAnalysis.Dev.Utility;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Testing;
+using NUnit.Framework;
+
+namespace Lucene.Net.CodeAnalysis.Dev;
+
+public class TestLuceneDev7008_TypeContainsNonNetNumericAnalyzer
+{
+ [Test]
+ public async Task NetNumericTypeName_NoDiagnostic()
+ {
+ const string testCode =
+ """
+ public class Int64Reader
+ {
+ }
+ """;
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7008_TypeContainsNonNetNumericAnalyzer())
+ {
+ TestCode = testCode
+ };
+ await test.RunAsync();
+ }
+
+ [Test]
+ public async Task NonNetNumericTypeName_Diagnostic()
+ {
+ const string testCode =
+ """
+ public class LongReader
+ {
+ }
+ """;
+
+ var expected = new DiagnosticResult(Descriptors.LuceneDev7008_TypeContainsNonNetNumeric)
+ .WithSeverity(DiagnosticSeverity.Warning)
+ .WithArguments("LongReader")
+ .WithLocation(1, 14);
+
+ var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev7008_TypeContainsNonNetNumericAnalyzer())
+ {
+ TestCode = testCode,
+ ExpectedDiagnostics = { expected }
+ };
+ await test.RunAsync();
+ }
+}