diff --git a/NuGet.config b/NuGet.config index ee3e7274e..82b7378eb 100644 --- a/NuGet.config +++ b/NuGet.config @@ -11,4 +11,7 @@ + + + diff --git a/README.md b/README.md index 309f3ed85..ae10cb1b6 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,12 @@ -# StyleCop Analyzers for the .NET Compiler Platform - -[![NuGet](https://img.shields.io/nuget/v/StyleCop.Analyzers.svg)](https://www.nuget.org/packages/StyleCop.Analyzers)[![NuGet Beta](https://img.shields.io/nuget/vpre/StyleCop.Analyzers.svg)](https://www.nuget.org/packages/StyleCop.Analyzers) - -[![Join the chat at https://gitter.im/DotNetAnalyzers/StyleCopAnalyzers](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/DotNetAnalyzers/StyleCopAnalyzers?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -[![Build status](https://ci.appveyor.com/api/projects/status/8jw2lq431kgg44jl/branch/master?svg=true)](https://ci.appveyor.com/project/sharwell/stylecopanalyzers/branch/master) - -[![codecov.io](https://codecov.io/github/DotNetAnalyzers/StyleCopAnalyzers/coverage.svg?branch=master)](https://codecov.io/github/DotNetAnalyzers/StyleCopAnalyzers?branch=master) +# New StyleCop Analyzers for the .NET Compiler Platform This repository contains an implementation of the StyleCop rules using the .NET Compiler Platform. Where possible, code fixes are also provided to simplify the process of correcting violations. -## Using StyleCop.Analyzers +**This project has been created by cloning the [StyleCopAnalyzers](https://github.com/DotNetAnalyzers/StyleCopAnalyzers) project and merging some of the pull requests that were created. Hopefully as a temporary solution until maintenance of the original project starts working well again. Changes are kept small until the status of the original project is clearer.** + +## Using NewStyleCop.Analyzers -The preferable way to use the analyzers is to add the nuget package [StyleCop.Analyzers](http://www.nuget.org/packages/StyleCop.Analyzers/) +The preferable way to use the analyzers is to add the nuget package [NewStyleCop.Analyzers](http://www.nuget.org/packages/NewStyleCop.Analyzers/) to the project where you want to enforce StyleCop rules. The severity of individual rules may be configured using [rule set files](https://docs.microsoft.com/en-us/visualstudio/code-quality/using-rule-sets-to-group-code-analysis-rules) @@ -33,18 +27,6 @@ Not all versions of StyleCop.Analyzers support all features of each C# language | 7.0 - 7.3 | v1.1.0-beta or higher | VS2017+ | | 8.0 | v1.2.0-beta or higher | VS2019 | -## Installation - -StyleCopAnalyzers can be installed using the NuGet command line or the NuGet Package Manager in Visual Studio 2015. - -**Install using the command line:** -```bash -Install-Package StyleCop.Analyzers -``` - -**Install using the package manager:** -![Install via nuget](https://cloud.githubusercontent.com/assets/1408396/8233513/491f301a-159c-11e5-8b7a-1e16a0695da6.png) - ## Team Considerations If you use older versions of Visual Studio in addition to Visual Studio 2015 or Visual Studio 2017, you may still install these analyzers. They will be automatically disabled when you open the project back up in Visual Studio 2013 or earlier. diff --git a/SetVersion.ps1 b/SetVersion.ps1 new file mode 100644 index 000000000..9d1a30620 --- /dev/null +++ b/SetVersion.ps1 @@ -0,0 +1,14 @@ +param([String]$version) + +function Update-Nuspec-File { + param ($FileName) + + $content = [System.IO.File]::ReadAllText($FileName) -replace '(?<=).*(?=)', $version + $encoding = New-Object System.Text.UTF8Encoding $True + [System.IO.File]::WriteAllText($FileName, $content ,$encoding) +} + +Update-Nuspec-File -FileName "StyleCop.Analyzers\\StyleCop.Analyzers.CodeFixes\\StyleCop.Analyzers.nuspec" + +git commit -m "Created new release $version" . +git tag v$version diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/SA1649CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/SA1649CodeFixProvider.cs index 05cc9b7e8..5af8ee0ac 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/SA1649CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/SA1649CodeFixProvider.cs @@ -53,24 +53,30 @@ private static async Task GetTransformedSolutionAsync(Document documen { var solution = document.Project.Solution; var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var expectedFileName = diagnostic.Properties[SA1649FileNameMustMatchTypeName.ExpectedFileNameKey]; - var newPath = document.FilePath != null ? Path.Combine(Path.GetDirectoryName(document.FilePath), expectedFileName) : null; - - var newDocumentId = DocumentId.CreateNewId(document.Id.ProjectId); - var newSolution = solution - .RemoveDocument(document.Id) - .AddDocument(newDocumentId, expectedFileName, syntaxRoot, document.Folders, newPath); + var newSolution = ReplaceDocument(solution, document, document.Id, syntaxRoot, expectedFileName); - // Make sure to also add the file to linked projects + // Make sure to also update other projects which reference the same file foreach (var linkedDocumentId in document.GetLinkedDocumentIds()) { - DocumentId linkedExtractedDocumentId = DocumentId.CreateNewId(linkedDocumentId.ProjectId); - newSolution = newSolution.AddDocument(linkedExtractedDocumentId, expectedFileName, syntaxRoot, document.Folders); + newSolution = ReplaceDocument(newSolution, null, linkedDocumentId, syntaxRoot, expectedFileName); } return newSolution; } + + private static Solution ReplaceDocument(Solution solution, Document document, DocumentId documentId, SyntaxNode syntaxRoot, string expectedFileName) + { + document ??= solution.GetDocument(documentId); + + var newDocumentFilePath = document.FilePath != null ? Path.Combine(Path.GetDirectoryName(document.FilePath), expectedFileName) : null; + var newDocumentId = DocumentId.CreateNewId(documentId.ProjectId); + + var newSolution = solution + .RemoveDocument(documentId) + .AddDocument(newDocumentId, expectedFileName, syntaxRoot, document.Folders, newDocumentFilePath); + return newSolution; + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/StyleCop.Analyzers.nuspec b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/StyleCop.Analyzers.nuspec index 66360fb4c..53d49a280 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/StyleCop.Analyzers.nuspec +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/StyleCop.Analyzers.nuspec @@ -2,7 +2,7 @@ NewStyleCop.Analyzers - 0.0.2 + 1.0.0 NewStyleCop.Analyzers Björn Hellander et. al. Björn Hellander diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/Lightup/IImportScopeWrapperCSharp10UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/Lightup/IImportScopeWrapperCSharp10UnitTests.cs new file mode 100644 index 000000000..79bde5f0c --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/Lightup/IImportScopeWrapperCSharp10UnitTests.cs @@ -0,0 +1,11 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp10.Lightup +{ + using StyleCop.Analyzers.Test.CSharp9.Lightup; + + public partial class IImportScopeWrapperCSharp10UnitTests : IImportScopeWrapperCSharp9UnitTests + { + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/ReadabilityRules/SA1135CSharp10UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/ReadabilityRules/SA1135CSharp10UnitTests.cs index 29c4b9b08..15a4ded3f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/ReadabilityRules/SA1135CSharp10UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/ReadabilityRules/SA1135CSharp10UnitTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Contributors to the New StyleCop Analyzers project. // Licensed under the MIT License. See LICENSE in the project root for license information. -#nullable disable - namespace StyleCop.Analyzers.Test.CSharp10.ReadabilityRules { using System.Threading; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/Lightup/IImportScopeWrapperCSharp11UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/Lightup/IImportScopeWrapperCSharp11UnitTests.cs new file mode 100644 index 000000000..cc5bd8cea --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/Lightup/IImportScopeWrapperCSharp11UnitTests.cs @@ -0,0 +1,41 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp11.Lightup +{ + using System.Collections.Generic; + using System.Collections.Immutable; + using Microsoft.CodeAnalysis; + using Moq; + using StyleCop.Analyzers.Lightup; + using StyleCop.Analyzers.Test.CSharp10.Lightup; + using Xunit; + + public partial class IImportScopeWrapperCSharp11UnitTests : IImportScopeWrapperCSharp10UnitTests + { + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + public void TestCompatibleInstance(int numberOfAliasSymbols) + { + var obj = CreateImportScope(numberOfAliasSymbols); + Assert.True(IImportScopeWrapper.IsInstance(obj)); + var wrapper = IImportScopeWrapper.FromObject(obj); + Assert.Equal(obj.Aliases, wrapper.Aliases); + } + + private static IImportScope CreateImportScope(int numberOfAliasSymbols) + { + var aliasSymbolMocks = new List(); + for (var i = 0; i < numberOfAliasSymbols; i++) + { + aliasSymbolMocks.Add(Mock.Of()); + } + + var importScopeMock = new Mock(); + importScopeMock.Setup(x => x.Aliases).Returns(aliasSymbolMocks.ToImmutableArray()); + return importScopeMock.Object; + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1404CSharp11UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1404CSharp11UnitTests.cs index 3d69c2863..e2684bb5a 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1404CSharp11UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1404CSharp11UnitTests.cs @@ -3,9 +3,58 @@ namespace StyleCop.Analyzers.Test.CSharp11.MaintainabilityRules { + using System.Threading; + using System.Threading.Tasks; + using StyleCop.Analyzers.MaintainabilityRules; using StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules; + using Xunit; + + using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.MaintainabilityRules.SA1404CodeAnalysisSuppressionMustHaveJustification, + StyleCop.Analyzers.MaintainabilityRules.SA1404CodeFixProvider>; public partial class SA1404CSharp11UnitTests : SA1404CSharp10UnitTests { + // NOTE: This tests a fix for a c# 10 feature, but the Roslyn API used to solve it wasn't available in the version + // we use in the c# 10 test project, so the test was added here instead. + [Fact] + [WorkItem(3594, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3594")] + public async Task TestUsingNameChangeInGlobalUsingInAnotherFileAsync() + { + var testCode1 = @" +global using MySuppressionAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute;"; + + var testCode2 = @" +public class Foo +{ + [[|MySuppression(null, null)|]] + public void Bar() + { + + } +}"; + + var fixedCode2 = @" +public class Foo +{ + [MySuppression(null, null, Justification = """ + SA1404CodeAnalysisSuppressionMustHaveJustification.JustificationPlaceholder + @""")] + public void Bar() + { + + } +}"; + + await new CSharpTest() + { + TestSources = { testCode1, testCode2 }, + FixedSources = { testCode1, fixedCode2 }, + RemainingDiagnostics = + { + Diagnostic().WithLocation("/0/Test1.cs", 4, 32), + }, + NumberOfIncrementalIterations = 2, + NumberOfFixAllIterations = 2, + }.RunAsync(CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/ReadabilityRules/SA1121CSharp11UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/ReadabilityRules/SA1121CSharp11UnitTests.cs index d6e9d7cd1..cc1f450c8 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/ReadabilityRules/SA1121CSharp11UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/ReadabilityRules/SA1121CSharp11UnitTests.cs @@ -3,9 +3,43 @@ namespace StyleCop.Analyzers.Test.CSharp11.ReadabilityRules { + using System.Threading; + using System.Threading.Tasks; using StyleCop.Analyzers.Test.CSharp10.ReadabilityRules; + using Xunit; + + using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1121UseBuiltInTypeAlias, + StyleCop.Analyzers.ReadabilityRules.SA1121CodeFixProvider>; public partial class SA1121CSharp11UnitTests : SA1121CSharp10UnitTests { + // NOTE: This tests a fix for a c# 10 feature, but the Roslyn API used to solve it wasn't available in the version + // we use in the c# 10 test project, so the test was added here instead. + [Fact] + [WorkItem(3594, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3594")] + public async Task TestUsingNameChangeInGlobalUsingInAnotherFileAsync() + { + var source1 = @" +global using MyDouble = System.Double;"; + + var oldSource2 = @" +class TestClass +{ + private [|MyDouble|] x; +}"; + + var newSource2 = @" +class TestClass +{ + private double x; +}"; + + await new CSharpTest() + { + TestSources = { source1, oldSource2 }, + FixedSources = { source1, newSource2 }, + }.RunAsync(CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/StyleCop.Analyzers.Test.CSharp11.csproj b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/StyleCop.Analyzers.Test.CSharp11.csproj index 01aef2454..f907fcaf5 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/StyleCop.Analyzers.Test.CSharp11.csproj +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/StyleCop.Analyzers.Test.CSharp11.csproj @@ -14,6 +14,7 @@ + diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp12/Lightup/IImportScopeWrapperCSharp12UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp12/Lightup/IImportScopeWrapperCSharp12UnitTests.cs new file mode 100644 index 000000000..dfc5697dc --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp12/Lightup/IImportScopeWrapperCSharp12UnitTests.cs @@ -0,0 +1,11 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp12.Lightup +{ + using StyleCop.Analyzers.Test.CSharp11.Lightup; + + public partial class IImportScopeWrapperCSharp12UnitTests : IImportScopeWrapperCSharp11UnitTests + { + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp12/ReadabilityRules/SA1135CSharp12UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp12/ReadabilityRules/SA1135CSharp12UnitTests.cs index 66d31cd9d..43ef49d6b 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp12/ReadabilityRules/SA1135CSharp12UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp12/ReadabilityRules/SA1135CSharp12UnitTests.cs @@ -3,9 +3,55 @@ namespace StyleCop.Analyzers.Test.CSharp12.ReadabilityRules { + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.Test.CSharp11.ReadabilityRules; + using Xunit; + + using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1135UsingDirectivesMustBeQualified, + StyleCop.Analyzers.ReadabilityRules.SA1135CodeFixProvider>; public partial class SA1135CSharp12UnitTests : SA1135CSharp11UnitTests { + public static IEnumerable CorrectAliasableTypes => new[] + { + new[] { "string" }, + new[] { "(string, int)" }, + new[] { "(System.String, System.Int32)" }, + new[] { "bool[]" }, + new[] { "System.Boolean[]" }, + }; + + [Theory] + [MemberData(nameof(CorrectAliasableTypes))] + [WorkItem(3882, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3882")] + public async Task TestAliasAnyTypeOutsideNamespaceAsync(string type) + { + var testCode = $@" +using MyType = {type}; + +namespace TestNamespace +{{ +}}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [MemberData(nameof(CorrectAliasableTypes))] + [WorkItem(3882, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3882")] + public async Task TestAliasAnyTypeInsideNamespaceAsync(string type) + { + var testCode = $@" +namespace TestNamespace +{{ + using MyType = {type}; +}}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp12/SpacingRules/SA1008CSharp12UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp12/SpacingRules/SA1008CSharp12UnitTests.cs index a065f8fa1..abf2e4dd5 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp12/SpacingRules/SA1008CSharp12UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp12/SpacingRules/SA1008CSharp12UnitTests.cs @@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.Test.CSharp12.SpacingRules { using System.Threading; using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.Test.CSharp11.SpacingRules; using Xunit; @@ -28,5 +29,69 @@ public async Task TestTupleUsingAliasAsync() var expected = Diagnostic(DescriptorPreceded).WithLocation(0); await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); } + + [Fact] + [WorkItem(3931, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3931")] + public async Task TestParenthesizedLambdaInCollectionExpressionAsync() + { + var testCode = @" +class TestClass +{ + private System.Action[] actions = [ [|(|]) => {}]; +} +"; + + var fixedCode = @" +class TestClass +{ + private System.Action[] actions = [() => {}]; +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + [WorkItem(3894, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3894")] + public async Task TestCollectionExpressionAsync() + { + var testCode = @" +namespace TestNamespace +{ + public class TestClass + { + public void TestMethod() + { + int[] x = [ {|#0:(|} 0 + 0)]; + } + } +} +"; + + var fixedCode = @" +namespace TestNamespace +{ + public class TestClass + { + public void TestMethod() + { + int[] x = [(0 + 0)]; + } + } +} +"; + + DiagnosticResult[] expectedResults = + { + Diagnostic(DescriptorNotPreceded).WithLocation(0), + Diagnostic(DescriptorNotFollowed).WithLocation(0), + }; + + await VerifyCSharpFixAsync( + testCode, + expectedResults, + fixedCode, + CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp13/LayoutRules/SA1504CSharp13UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp13/LayoutRules/SA1504CSharp13UnitTests.cs index 3d6c1124a..907ce83cd 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp13/LayoutRules/SA1504CSharp13UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp13/LayoutRules/SA1504CSharp13UnitTests.cs @@ -3,9 +3,17 @@ namespace StyleCop.Analyzers.Test.CSharp13.LayoutRules { + using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.Test.CSharp12.LayoutRules; public partial class SA1504CSharp13UnitTests : SA1504CSharp12UnitTests { + protected override DiagnosticResult[] GetExpectedResultAccessorWithoutBody() + { + return new DiagnosticResult[] + { + DiagnosticResult.CompilerError("CS8652").WithMessage("The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.").WithLocation(4, 16), + }; + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp13/Lightup/IImportScopeWrapperCSharp13UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp13/Lightup/IImportScopeWrapperCSharp13UnitTests.cs new file mode 100644 index 000000000..c463782e6 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp13/Lightup/IImportScopeWrapperCSharp13UnitTests.cs @@ -0,0 +1,11 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp13.Lightup +{ + using StyleCop.Analyzers.Test.CSharp12.Lightup; + + public partial class IImportScopeWrapperCSharp13UnitTests : IImportScopeWrapperCSharp12UnitTests + { + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp13/StyleCop.Analyzers.Test.CSharp13.csproj b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp13/StyleCop.Analyzers.Test.CSharp13.csproj index 6d4dd969a..a44ac5353 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp13/StyleCop.Analyzers.Test.CSharp13.csproj +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp13/StyleCop.Analyzers.Test.CSharp13.csproj @@ -13,7 +13,7 @@ - + diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/Lightup/IImportScopeWrapperCSharp7UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/Lightup/IImportScopeWrapperCSharp7UnitTests.cs new file mode 100644 index 000000000..c2cffe9eb --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/Lightup/IImportScopeWrapperCSharp7UnitTests.cs @@ -0,0 +1,11 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp7.Lightup +{ + using StyleCop.Analyzers.Test.Lightup; + + public partial class IImportScopeWrapperCSharp7UnitTests : IImportScopeWrapperUnitTests + { + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/NamingRules/SA1316CSharp7UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/NamingRules/SA1316CSharp7UnitTests.cs index dd8805d9e..6595e264f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/NamingRules/SA1316CSharp7UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/NamingRules/SA1316CSharp7UnitTests.cs @@ -5,11 +5,13 @@ namespace StyleCop.Analyzers.Test.CSharp7.NamingRules { + using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.NamingRules; + using StyleCop.Analyzers.Test.Helpers; using Xunit; using static StyleCop.Analyzers.Test.Helpers.LanguageVersionTestExtensions; using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier< @@ -94,6 +96,14 @@ public class SA1316CSharp7UnitTests } "; + public static IEnumerable TypesWithOneLowerCaseTupleElement { get; } = new[] + { + new[] { "(int I, string foo)" }, + new[] { "(int I, (bool foo, string S))" }, + new[] { "(int foo, string S)[]" }, + new[] { "List<(string S, bool foo)>" }, + }; + /// /// Validates the properly named tuple element names will not produce diagnostics. /// @@ -401,5 +411,403 @@ public void MethodName((string Name, string Value) obj) await VerifyCSharpDiagnosticAsync(testCode, DefaultTestSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } + + [Theory] + [MemberData(nameof(TypesWithOneLowerCaseTupleElement))] + [WorkItem(3781, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3781")] + public async Task TestImproperTupleElementNameInOverriddenMethodsParameterTypeAsync(string type) + { + var testCode = $@" +using System.Collections.Generic; + +public class BaseType +{{ + public virtual void TestMethod({type.Replace("foo", "[|foo|]")} p) + {{ + }} +}} + +public class TestType : BaseType +{{ + public override void TestMethod({type} p) + {{ + }} +}} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DefaultTestSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [MemberData(nameof(TypesWithOneLowerCaseTupleElement))] + [WorkItem(3781, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3781")] + public async Task TestImproperTupleElementNameInExplicitlyImplementedMethodsParameterTypeAsync(string type) + { + var testCode = $@" +using System.Collections.Generic; + +public interface TestInterface +{{ + void TestMethod({type.Replace("foo", "[|foo|]")} p); +}} + +public class TestType : TestInterface +{{ + void TestInterface.TestMethod({type} p) + {{ + }} +}} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DefaultTestSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [MemberData(nameof(TypesWithOneLowerCaseTupleElement))] + [WorkItem(3781, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3781")] + public async Task TestImproperTupleElementNameInInterfaceImplementedMethodsParameterTypeAsync(string type) + { + var testCode = $@" +using System.Collections.Generic; + +public interface TestInterface +{{ + void TestMethod({type.Replace("foo", "[|foo|]")} p); +}} + +public class TestType : TestInterface +{{ + public void TestMethod({type} p) + {{ + }} +}} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DefaultTestSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [MemberData(nameof(TypesWithOneLowerCaseTupleElement))] + [WorkItem(3781, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3781")] + public async Task TestImproperTupleElementNameInOverriddenMethodsReturnTypeAsync(string type) + { + var testCode = $@" +using System.Collections.Generic; + +public class BaseType +{{ + public virtual {type.Replace("foo", "[|foo|]")} TestMethod() + {{ + throw new System.Exception(); + }} +}} + +public class TestType : BaseType +{{ + public override {type} TestMethod() + {{ + throw new System.Exception(); + }} +}} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DefaultTestSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [MemberData(nameof(TypesWithOneLowerCaseTupleElement))] + [WorkItem(3781, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3781")] + public async Task TestImproperTupleElementNameInExplicitlyImplementedMethodsReturnTypeAsync(string type) + { + var testCode = $@" +using System.Collections.Generic; + +public interface TestInterface +{{ + {type.Replace("foo", "[|foo|]")} TestMethod(); +}} + +public class TestType : TestInterface +{{ + {type} TestInterface.TestMethod() + {{ + throw new System.Exception(); + }} +}} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DefaultTestSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [MemberData(nameof(TypesWithOneLowerCaseTupleElement))] + [WorkItem(3781, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3781")] + public async Task TestImproperTupleElementNameInInterfaceImplementedMethodsReturnTypeAsync(string type) + { + var testCode = $@" +using System.Collections.Generic; + +public interface TestInterface +{{ + {type.Replace("foo", "[|foo|]")} TestMethod(); +}} + +public class TestType : TestInterface +{{ + public {type} TestMethod() + {{ + throw new System.Exception(); + }} +}} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DefaultTestSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [MemberData(nameof(TypesWithOneLowerCaseTupleElement))] + [WorkItem(3781, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3781")] + public async Task TestImproperTupleElementNameInOverriddenPropertysTypeAsync(string type) + { + var testCode = $@" +using System.Collections.Generic; + +public class BaseType +{{ + public virtual {type.Replace("foo", "[|foo|]")} TestProperty {{ get; set; }} +}} + +public class TestType : BaseType +{{ + public override {type} TestProperty {{ get; set; }} +}} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DefaultTestSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [MemberData(nameof(TypesWithOneLowerCaseTupleElement))] + [WorkItem(3781, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3781")] + public async Task TestImproperTupleElementNameInExplicitlyImplementedPropertysTypeAsync(string type) + { + var testCode = $@" +using System.Collections.Generic; + +public interface TestInterface +{{ + {type.Replace("foo", "[|foo|]")} TestProperty {{ get; set; }} +}} + +public class TestType : TestInterface +{{ + {type} TestInterface.TestProperty {{ get; set; }} +}} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DefaultTestSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [MemberData(nameof(TypesWithOneLowerCaseTupleElement))] + [WorkItem(3781, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3781")] + public async Task TestImproperTupleElementNameInInterfaceImplementedPropertysTypeAsync(string type) + { + var testCode = $@" +using System.Collections.Generic; + +public interface TestInterface +{{ + {type.Replace("foo", "[|foo|]")} TestProperty {{ get; set; }} +}} + +public class TestType : TestInterface +{{ + public {type} TestProperty {{ get; set; }} +}} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DefaultTestSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [MemberData(nameof(TypesWithOneLowerCaseTupleElement))] + [WorkItem(3781, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3781")] + public async Task TestImproperTupleElementNameInOverriddenEventsTypeAsync(string type) + { + var testCode = $@" +using System; +using System.Collections.Generic; + +public class BaseType +{{ + public virtual event Action<{type.Replace("foo", "[|foo|]")}> TestEvent {{ add {{}} remove {{}} }} +}} + +public class TestType : BaseType +{{ + public override event Action<{type}> TestEvent {{ add {{}} remove {{}} }} +}} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DefaultTestSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [MemberData(nameof(TypesWithOneLowerCaseTupleElement))] + [WorkItem(3781, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3781")] + public async Task TestImproperTupleElementNameInExplicitlyImplementedEventsTypeAsync(string type) + { + var testCode = $@" +using System; +using System.Collections.Generic; + +public interface TestInterface +{{ + event Action<{type.Replace("foo", "[|foo|]")}> TestEvent; +}} + +public class TestType : TestInterface +{{ + event Action<{type}> TestInterface.TestEvent {{ add {{}} remove {{}} }} +}} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DefaultTestSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [MemberData(nameof(TypesWithOneLowerCaseTupleElement))] + [WorkItem(3781, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3781")] + public async Task TestImproperTupleElementNameInInterfaceImplementedEventsTypeAsync(string type) + { + var testCode = $@" +using System; +using System.Collections.Generic; + +public interface TestInterface +{{ + event Action<{type.Replace("foo", "[|foo|]")}> TestEvent; +}} + +public class TestType : TestInterface +{{ + public event Action<{type}> TestEvent {{ add {{}} remove {{}} }} +}} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DefaultTestSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [MemberData(nameof(TypesWithOneLowerCaseTupleElement))] + [WorkItem(3781, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3781")] + public async Task TestImproperTupleElementNameInOverriddenIndexersReturnTypeAsync(string type) + { + var testCode = $@" +using System.Collections.Generic; + +public abstract class BaseType +{{ + public abstract {type.Replace("foo", "[|foo|]")} this[int i] {{ get; }} +}} + +public class TestType : BaseType +{{ + public override {type} this[int i] => throw new System.Exception(); +}} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DefaultTestSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [MemberData(nameof(TypesWithOneLowerCaseTupleElement))] + [WorkItem(3781, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3781")] + public async Task TestImproperTupleElementNameInExplicitlyImplementedIndexersReturnTypeAsync(string type) + { + var testCode = $@" +using System.Collections.Generic; + +public interface TestInterface +{{ + {type.Replace("foo", "[|foo|]")} this[int i] {{ get; }} +}} + +public class TestType : TestInterface +{{ + {type} TestInterface.this[int i] => throw new System.Exception(); +}} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DefaultTestSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [MemberData(nameof(TypesWithOneLowerCaseTupleElement))] + [WorkItem(3781, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3781")] + public async Task TestImproperTupleElementNameInInterfaceImplementedIndexersReturnTypeAsync(string type) + { + var testCode = $@" +using System.Collections.Generic; + +public interface TestInterface +{{ + {type.Replace("foo", "[|foo|]")} this[int i] {{ get; }} +}} + +public class TestType : TestInterface +{{ + public {type} this[int i] => throw new System.Exception(); +}} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DefaultTestSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + [WorkItem(3781, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3781")] + public async Task TestImproperVariableTupleElementNameInsideOverriddenMethodAsync() + { + var testCode = @" +using System.Collections.Generic; + +public abstract class BaseType +{ + public virtual void TestMethod() + { + } +} + +public class TestType : BaseType +{ + public override void TestMethod() + { + (int I, bool [|b|]) x; + } +} +"; + + var fixedCode = @" +using System.Collections.Generic; + +public abstract class BaseType +{ + public virtual void TestMethod() + { + } +} + +public class TestType : BaseType +{ + public override void TestMethod() + { + (int I, bool B) x; + } +} +"; + + await VerifyCSharpFixAsync(testCode, DefaultTestSettings, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/ReadabilityRules/SA1135CSharp7UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/ReadabilityRules/SA1135CSharp7UnitTests.cs index d609d7996..d3ccdafd3 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/ReadabilityRules/SA1135CSharp7UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/ReadabilityRules/SA1135CSharp7UnitTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Contributors to the New StyleCop Analyzers project. // Licensed under the MIT License. See LICENSE in the project root for license information. -#nullable disable - namespace StyleCop.Analyzers.Test.CSharp7.ReadabilityRules { using System.Threading; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/Lightup/IImportScopeWrapperCSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/Lightup/IImportScopeWrapperCSharp8UnitTests.cs new file mode 100644 index 000000000..74754ba0a --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/Lightup/IImportScopeWrapperCSharp8UnitTests.cs @@ -0,0 +1,11 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.Lightup +{ + using StyleCop.Analyzers.Test.CSharp7.Lightup; + + public partial class IImportScopeWrapperCSharp8UnitTests : IImportScopeWrapperCSharp7UnitTests + { + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1135CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1135CSharp8UnitTests.cs index 353989b64..8d8d97a34 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1135CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1135CSharp8UnitTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Contributors to the New StyleCop Analyzers project. // Licensed under the MIT License. See LICENSE in the project root for license information. -#nullable disable - namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules { using System.Threading; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1008CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1008CSharp8UnitTests.cs index c04331826..450d2cdbc 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1008CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1008CSharp8UnitTests.cs @@ -194,5 +194,48 @@ await VerifyCSharpFixAsync( fixedCode, CancellationToken.None).ConfigureAwait(false); } + + [Fact] + [WorkItem(3894, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3894")] + public async Task TestLeftOperandInRangeExpressionAsync() + { + var testCode = @" +namespace TestNamespace +{ + public class TestClass + { + public void TestMethod() + { + var x ={|#0:(|} 0 + 0)..1; + } + } +} +"; + + var fixedCode = @" +namespace TestNamespace +{ + public class TestClass + { + public void TestMethod() + { + var x = (0 + 0)..1; + } + } +} +"; + + DiagnosticResult[] expectedResults = + { + Diagnostic(DescriptorPreceded).WithLocation(0), + Diagnostic(DescriptorNotFollowed).WithLocation(0), + }; + + await VerifyCSharpFixAsync( + testCode, + expectedResults, + fixedCode, + CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/Lightup/IImportScopeWrapperCSharp9UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/Lightup/IImportScopeWrapperCSharp9UnitTests.cs new file mode 100644 index 000000000..ddd932841 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/Lightup/IImportScopeWrapperCSharp9UnitTests.cs @@ -0,0 +1,11 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp9.Lightup +{ + using StyleCop.Analyzers.Test.CSharp8.Lightup; + + public partial class IImportScopeWrapperCSharp9UnitTests : IImportScopeWrapperCSharp8UnitTests + { + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1623UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1623UnitTests.cs index a4f18eacf..df7ba5631 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1623UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1623UnitTests.cs @@ -341,6 +341,26 @@ public class TestClass /// public int TestProperty { get; set; } } +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [InlineData("")] + [InlineData("XYZ ")] + [InlineData(" XYZ")] + [WorkItem(3465, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3465")] + public async Task VerifyInheritdocInSummaryTagIsAllowedAsync(string summary) + { + var testCode = $@" +public class TestClass +{{ + /// + /// {summary} + /// + public int TestProperty {{ get; set; }} +}} "; await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1624UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1624UnitTests.cs index 4027c6f74..71427fc32 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1624UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1624UnitTests.cs @@ -152,6 +152,26 @@ public class TestClass /// public int TestProperty { get; set; } } +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [InlineData("")] + [InlineData("XYZ ")] + [InlineData(" XYZ")] + [WorkItem(3465, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3465")] + public async Task VerifyInheritdocInSummaryTagIsAllowedAsync(string summary) + { + var testCode = $@" +public class TestClass +{{ + /// + /// {summary} + /// + public int TestProperty {{ get; private set; }} +}} "; await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1649UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1649UnitTests.cs index 3b9c9d56d..90dbf90f4 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1649UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1649UnitTests.cs @@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.Test.DocumentationRules { + using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Testing; @@ -487,6 +488,59 @@ public class Class2 await VerifyCSharpDiagnosticAsync("Class1.cs", testCode, testSettings: null, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } + [Fact] + [WorkItem(1693, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1693")] + [WorkItem(3866, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3866")] + public async Task VerifyWithLinkedFileAsync() + { + var dirName = "0"; + var testCode = "public class [|Type1|] { }"; + + await new StyleCopCodeFixVerifier.CSharpTest() + { + TestState = + { + Sources = + { + (BuildPath(dirName, "TestFile.cs"), testCode), + }, + AdditionalProjects = + { + ["Project2"] = + { + Sources = + { + (BuildPath(dirName, "TestFile.cs"), testCode), + }, + }, + }, + }, + FixedState = + { + Sources = + { + (BuildPath(dirName, "Type1.cs"), testCode), + }, + AdditionalProjects = + { + ["Project2"] = + { + Sources = + { + (BuildPath(dirName, "Type1.cs"), testCode), + }, + }, + }, + }, + + // Fails without this. Hard to be sure why this is needed, since the error message is not so good, + // but one guess could be that the test framework does not respect the fact that both projects + // point to the same file, and only inserts '#pragma warning disable' in the primary project's file. + // Then we would still get a diagnostic in the additional project. + TestBehaviors = TestBehaviors.SkipSuppressionCheck, + }.RunAsync().ConfigureAwait(false); + } + protected static string GetTypeDeclaration(string typeKind, string typeName, int? diagnosticKey = null) { if (diagnosticKey is not null) @@ -550,5 +604,14 @@ protected static Task VerifyCSharpFixAsync(string oldFileName, string source, st test.ExpectedDiagnostics.AddRange(expected); return test.RunAsync(cancellationToken); } + + // NOTE: Added to simplify the tests. After the fix has executed, + // the file paths will contain backslashes when running tests on Windows. + // Not really needed when setting up the test state, but handy in the fixed state. + // Might make tests pass on Linux if anyone is developing there. + private static string BuildPath(string part1, string part2) + { + return Path.Combine(part1, part2); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1504UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1504UnitTests.cs index ac9d6bdac..f5eb4c71a 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1504UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1504UnitTests.cs @@ -303,11 +303,8 @@ public int Prop } } }"; - DiagnosticResult[] expected = - { - DiagnosticResult.CompilerError("CS0501").WithMessage("'Foo.Prop.get' must declare a body because it is not marked abstract, extern, or partial").WithLocation(6, 9), - }; + var expected = this.GetExpectedResultAccessorWithoutBody(); await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); } @@ -387,5 +384,13 @@ public int Prop CodeActionIndex = 1, }.RunAsync(CancellationToken.None).ConfigureAwait(false); } + + protected virtual DiagnosticResult[] GetExpectedResultAccessorWithoutBody() + { + return new DiagnosticResult[] + { + DiagnosticResult.CompilerError("CS0501").WithMessage("'Foo.Prop.get' must declare a body because it is not marked abstract, extern, or partial").WithLocation(6, 9), + }; + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Lightup/IImportScopeWrapperUnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Lightup/IImportScopeWrapperUnitTests.cs new file mode 100644 index 000000000..0367078ef --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Lightup/IImportScopeWrapperUnitTests.cs @@ -0,0 +1,29 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.Lightup +{ + using System; + using StyleCop.Analyzers.Lightup; + using Xunit; + + public class IImportScopeWrapperUnitTests + { + [Fact] + public void TestNull() + { + object? obj = null; + Assert.False(IImportScopeWrapper.IsInstance(obj!)); + var wrapper = IImportScopeWrapper.FromObject(obj!); + Assert.Throws(() => wrapper.Aliases); + } + + [Fact] + public void TestIncompatibleInstance() + { + var obj = new object(); + Assert.False(IImportScopeWrapper.IsInstance(obj)); + Assert.Throws(() => IImportScopeWrapper.FromObject(obj)); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1135UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1135UnitTests.cs index 35697d9b2..931322a5e 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1135UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1135UnitTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Contributors to the New StyleCop Analyzers project. // Licensed under the MIT License. See LICENSE in the project root for license information. -#nullable disable - namespace StyleCop.Analyzers.Test.ReadabilityRules { using System.Threading; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/PropertySummaryDocumentationAnalyzer.cs b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/PropertySummaryDocumentationAnalyzer.cs index 47808f018..6586f3e1f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/PropertySummaryDocumentationAnalyzer.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/PropertySummaryDocumentationAnalyzer.cs @@ -61,6 +61,18 @@ internal class PropertySummaryDocumentationAnalyzer : PropertyDocumentationBase /// protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, StyleCopSettings settings, bool needsComment, XmlNodeSyntax syntax, XElement completeDocumentation, Location diagnosticLocation) { + if (!(syntax is XmlElementSyntax summaryElement)) + { + // This is reported by SA1604 or SA1606. + return; + } + + if (summaryElement.Content.GetFirstXmlElement(XmlCommentHelper.InheritdocXmlTag) != null) + { + // Ignore nodes with an tag. + return; + } + var propertyDeclaration = (PropertyDeclarationSyntax)context.Node; var propertyType = context.SemanticModel.GetTypeInfo(propertyDeclaration.Type.StripRefFromType()); var culture = settings.DocumentationRules.DocumentationCultureInfo; @@ -70,7 +82,7 @@ protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, Styl { AnalyzeSummaryElement( context, - syntax, + summaryElement, diagnosticLocation, propertyDeclaration, resourceManager.GetString(nameof(DocumentationResources.StartingTextGetsWhether), culture), @@ -82,7 +94,7 @@ protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, Styl { AnalyzeSummaryElement( context, - syntax, + summaryElement, diagnosticLocation, propertyDeclaration, resourceManager.GetString(nameof(DocumentationResources.StartingTextGets), culture), @@ -92,7 +104,7 @@ protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, Styl } } - private static void AnalyzeSummaryElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax syntax, Location diagnosticLocation, PropertyDeclarationSyntax propertyDeclaration, string startingTextGets, string startingTextSets, string startingTextGetsOrSets, string startingTextReturns) + private static void AnalyzeSummaryElement(SyntaxNodeAnalysisContext context, XmlElementSyntax summaryElement, Location diagnosticLocation, PropertyDeclarationSyntax propertyDeclaration, string startingTextGets, string startingTextSets, string startingTextGetsOrSets, string startingTextReturns) { var diagnosticProperties = ImmutableDictionary.CreateBuilder(); ArrowExpressionClauseSyntax expressionBody = propertyDeclaration.ExpressionBody; @@ -116,12 +128,6 @@ private static void AnalyzeSummaryElement(SyntaxNodeAnalysisContext context, Xml } } - if (!(syntax is XmlElementSyntax summaryElement)) - { - // This is reported by SA1604 or SA1606. - return; - } - // Add a no code fix tag when the summary element is empty. // This will only impact SA1623, because SA1624 cannot trigger with an empty summary. if (summaryElement.Content.Count == 0) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs index 70e65c6cc..ea7b3074d 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs @@ -72,7 +72,7 @@ public static bool IsWhitespaceOnly(this SyntaxTree tree, CancellationToken canc && TriviaHelper.IndexOfFirstNonWhitespaceTrivia(firstToken.LeadingTrivia) == -1; } - internal static bool ContainsUsingAlias(this SyntaxTree tree, ConcurrentDictionary cache) + internal static bool ContainsUsingAlias(this SyntaxTree tree, ConcurrentDictionary cache, SemanticModel semanticModel, CancellationToken cancellationToken) { if (tree == null) { @@ -85,16 +85,23 @@ internal static bool ContainsUsingAlias(this SyntaxTree tree, ConcurrentDictiona return result; } - bool generated = ContainsUsingAliasNoCache(tree); + bool generated = ContainsUsingAliasNoCache(tree, semanticModel, cancellationToken); cache.TryAdd(tree, generated); return generated; } - private static bool ContainsUsingAliasNoCache(SyntaxTree tree) + private static bool ContainsUsingAliasNoCache(SyntaxTree tree, SemanticModel semanticModel, CancellationToken cancellationToken) { + // Check for "local" using aliases var nodes = tree.GetRoot().DescendantNodes(node => node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration) || node.IsKind(SyntaxKindEx.FileScopedNamespaceDeclaration)); + if (nodes.OfType().Any(x => x.Alias != null)) + { + return true; + } - return nodes.OfType().Any(x => x.Alias != null); + // Check for global using aliases + var scopes = semanticModel.GetImportScopes(0, cancellationToken); + return scopes.Any(x => x.Aliases.Length > 0); } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/IImportScopeWrapper.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/IImportScopeWrapper.cs new file mode 100644 index 000000000..c167229cb --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/IImportScopeWrapper.cs @@ -0,0 +1,54 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Lightup +{ + using System; + using System.Collections.Immutable; + using Microsoft.CodeAnalysis; + + internal readonly struct IImportScopeWrapper + { + internal const string WrappedTypeName = "Microsoft.CodeAnalysis.IImportScope"; + private static readonly Type WrappedType; + + private static readonly Func> AliasesAccessor; + + private readonly object node; + + static IImportScopeWrapper() + { + WrappedType = WrapperHelper.GetWrappedType(typeof(IImportScopeWrapper)); + + AliasesAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, nameof(Aliases)); + } + + private IImportScopeWrapper(object node) + { + this.node = node; + } + + public ImmutableArray Aliases => AliasesAccessor(this.node); + + // NOTE: Referenced via reflection + public static IImportScopeWrapper FromObject(object node) + { + if (node == null) + { + return default; + } + + if (!IsInstance(node)) + { + throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); + } + + return new IImportScopeWrapper(node); + } + + public static bool IsInstance(object obj) + { + return obj != null && LightupHelpers.CanWrapObject(obj, WrappedType); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/SemanticModelExtensions.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/SemanticModelExtensions.cs new file mode 100644 index 000000000..c1113eb91 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/SemanticModelExtensions.cs @@ -0,0 +1,150 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Lightup +{ + using System; + using System.Collections.Immutable; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using System.Threading; + using Microsoft.CodeAnalysis; + + internal static class SemanticModelExtensions + { + private static readonly Func> GetImportScopesAccessor; + + static SemanticModelExtensions() + { + GetImportScopesAccessor = CreateGetImportScopesAccessor(); + } + + public static ImmutableArray GetImportScopes(this SemanticModel semanticModel, int position, CancellationToken cancellationToken = default) + { + return GetImportScopesAccessor(semanticModel, position, cancellationToken); + } + + private static Func> CreateGetImportScopesAccessor() + { + var semanticModelType = typeof(SemanticModel); + + var codeAnalysisWorkspacesAssembly = semanticModelType.GetTypeInfo().Assembly; + var nativeImportScopeType = codeAnalysisWorkspacesAssembly.GetType("Microsoft.CodeAnalysis.IImportScope"); + if (nativeImportScopeType == null) + { + return FallbackAccessor; + } + + var method = semanticModelType.GetTypeInfo().GetDeclaredMethods("GetImportScopes").SingleOrDefault(IsCorrectGetImportScopesMethod); + if (method == null) + { + // This should not happen, since this missing method and the type we successfully managed to retrieve above was added in the same Roslyn version + return FallbackAccessor; + } + + var importScopeWrapperFromObjectMethod = typeof(IImportScopeWrapper).GetTypeInfo().GetDeclaredMethod("FromObject"); + var nativeImportScopeArrayType = typeof(ImmutableArray<>).MakeGenericType(nativeImportScopeType); + var nativeImportScopeArrayGetItemMethod = nativeImportScopeArrayType.GetTypeInfo().GetDeclaredMethod("get_Item"); + var wrapperImportScopeArrayType = typeof(ImmutableArray); + var wrapperImportScopeArrayBuilderType = typeof(ImmutableArray.Builder); + var wrapperImportScopeArrayBuilderAddMethod = wrapperImportScopeArrayBuilderType.GetTypeInfo().GetDeclaredMethod("Add"); + var wrapperImportScopeArrayBuilderToImmutableMethod = wrapperImportScopeArrayBuilderType.GetTypeInfo().GetDeclaredMethod("ToImmutable"); + var arrayCreateWrapperImportScopeBuilderMethod = typeof(ImmutableArray).GetTypeInfo().GetDeclaredMethods("CreateBuilder").Single(IsCorrectCreateBuilderMethod).MakeGenericMethod(typeof(IImportScopeWrapper)); + + var semanticModelParameter = Expression.Parameter(typeof(SemanticModel), "semanticModel"); + var positionParameter = Expression.Parameter(typeof(int), "position"); + var cancellationTokenParameter = Expression.Parameter(typeof(CancellationToken), "cancellationToken"); + + var nativeImportScopesVariable = Expression.Variable(nativeImportScopeArrayType, "nativeImportScopes"); + var nativeImportScopeVariable = Expression.Variable(nativeImportScopeType, "nativeImportScope"); + var countVariable = Expression.Variable(typeof(int), "count"); + var indexVariable = Expression.Variable(typeof(int), "index"); + var wrapperImportScopesBuilderVariable = Expression.Variable(wrapperImportScopeArrayBuilderType, "wrapperImportScopesBuilder"); + var wrapperImportScopesVariable = Expression.Variable(wrapperImportScopeArrayType, "wrapperImoprtScopes"); + var wrapperImportScopeVariable = Expression.Variable(typeof(IImportScopeWrapper), "wrapperImportScope"); + + var breakLabel = Expression.Label("break"); + var block = Expression.Block( + new[] { nativeImportScopesVariable, countVariable, indexVariable, wrapperImportScopesBuilderVariable, wrapperImportScopesVariable }, + //// nativeImportScopes = semanticModel.GetImportScopes(position, cancellationToken); + Expression.Assign( + nativeImportScopesVariable, + Expression.Call( + semanticModelParameter, + method, + new[] { positionParameter, cancellationTokenParameter })), + //// index = 0; + Expression.Assign(indexVariable, Expression.Constant(0)), + //// count = nativeImportScopes.Length; + Expression.Assign( + countVariable, + Expression.Property(nativeImportScopesVariable, "Length")), + //// wrapperImportScopesBuilder = ImmutableArray.CreateBuilder(); + Expression.Assign( + wrapperImportScopesBuilderVariable, + Expression.Call(null, arrayCreateWrapperImportScopeBuilderMethod)), + Expression.Loop( + Expression.Block( + new[] { nativeImportScopeVariable, wrapperImportScopeVariable }, + //// if (index >= count) break; + Expression.IfThen( + Expression.GreaterThanOrEqual(indexVariable, countVariable), + Expression.Break(breakLabel)), + //// nativeImportScope = nativeImportScopes[index]; + Expression.Assign( + nativeImportScopeVariable, + Expression.Call( + nativeImportScopesVariable, + nativeImportScopeArrayGetItemMethod, + indexVariable)), + //// wrapperImportScope = IImportScopeWrapper.FromObject(nativeImportScope); + Expression.Assign( + wrapperImportScopeVariable, + Expression.Call( + null, + importScopeWrapperFromObjectMethod, + nativeImportScopeVariable)), + //// wrapperImportScopesBuilder.Add(wrapperImportScope); + Expression.Call( + wrapperImportScopesBuilderVariable, + wrapperImportScopeArrayBuilderAddMethod, + new[] { wrapperImportScopeVariable }), + //// index++; + Expression.PostIncrementAssign(indexVariable)), + breakLabel), + //// wrapperImportScopes = wrapperImportScopesBuilder.ToImmutable(); + Expression.Assign( + wrapperImportScopesVariable, + Expression.Call( + wrapperImportScopesBuilderVariable, + wrapperImportScopeArrayBuilderToImmutableMethod))); + + var lambda = Expression.Lambda>>( + block, + new[] { semanticModelParameter, positionParameter, cancellationTokenParameter }); + + var accessor = lambda.Compile(); + return accessor; + + static bool IsCorrectGetImportScopesMethod(MethodInfo method) + { + var parameters = method.GetParameters(); + return parameters.Length == 2 + && parameters[0].ParameterType == typeof(int) + && parameters[1].ParameterType == typeof(CancellationToken); + } + + static bool IsCorrectCreateBuilderMethod(MethodInfo method) + { + var parameters = method.GetParameters(); + return parameters.Length == 0; + } + + static ImmutableArray FallbackAccessor(SemanticModel semanticModel, int position, CancellationToken cancellationToken) + { + return ImmutableArray.Empty; + } + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/WrapperHelper.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/WrapperHelper.cs index d04aa9f1c..44534e94d 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/WrapperHelper.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/WrapperHelper.cs @@ -22,6 +22,7 @@ static WrapperHelper() builder.Add(typeof(AnalyzerConfigOptionsProviderWrapper), codeAnalysisAssembly.GetType(AnalyzerConfigOptionsProviderWrapper.WrappedTypeName)); builder.Add(typeof(AnalyzerConfigOptionsWrapper), codeAnalysisAssembly.GetType(AnalyzerConfigOptionsWrapper.WrappedTypeName)); + builder.Add(typeof(IImportScopeWrapper), codeAnalysisAssembly.GetType(IImportScopeWrapper.WrappedTypeName)); WrappedTypes = builder.ToImmutable(); } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1404CodeAnalysisSuppressionMustHaveJustification.cs b/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1404CodeAnalysisSuppressionMustHaveJustification.cs index 9f23086b0..c72ef460e 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1404CodeAnalysisSuppressionMustHaveJustification.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1404CodeAnalysisSuppressionMustHaveJustification.cs @@ -96,7 +96,7 @@ public void HandleAttributeNode(SyntaxNodeAnalysisContext context) var attribute = (AttributeSyntax)context.Node; // Return fast if the name doesn't match and the file doesn't contain any using alias directives - if (!attribute.SyntaxTree.ContainsUsingAlias(this.usingAliasCache)) + if (!attribute.SyntaxTree.ContainsUsingAlias(this.usingAliasCache, context.SemanticModel, context.CancellationToken)) { if (!(attribute.Name is SimpleNameSyntax simpleNameSyntax)) { diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1316TupleElementNamesShouldUseCorrectCasing.cs b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1316TupleElementNamesShouldUseCorrectCasing.cs index 8692533ee..3b1e669bb 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1316TupleElementNamesShouldUseCorrectCasing.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1316TupleElementNamesShouldUseCorrectCasing.cs @@ -7,7 +7,10 @@ namespace StyleCop.Analyzers.NamingRules { using System; using System.Collections.Immutable; + using System.Diagnostics; using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using StyleCop.Analyzers.Helpers; using StyleCop.Analyzers.Lightup; @@ -90,7 +93,7 @@ private static void HandleTupleExpressionAction(SyntaxNodeAnalysisContext contex var inferredMemberName = SyntaxFactsEx.TryGetInferredMemberName(argument.NameColon?.Name ?? argument.Expression); if (inferredMemberName != null) { - CheckName(context, settings, inferredMemberName, argument.Expression.GetLocation(), false); + CheckName(context, settings, tupleElement: null, inferredMemberName, argument.Expression.GetLocation(), false); } } } @@ -102,10 +105,10 @@ private static void CheckTupleElement(SyntaxNodeAnalysisContext context, StyleCo return; } - CheckName(context, settings, tupleElement.Identifier.ValueText, tupleElement.Identifier.GetLocation(), true); + CheckName(context, settings, tupleElement.SyntaxNode, tupleElement.Identifier.ValueText, tupleElement.Identifier.GetLocation(), true); } - private static void CheckName(SyntaxNodeAnalysisContext context, StyleCopSettings settings, string tupleElementName, Location location, bool prepareCodeFix) + private static void CheckName(SyntaxNodeAnalysisContext context, StyleCopSettings settings, SyntaxNode tupleElement, string tupleElementName, Location location, bool prepareCodeFix) { if (tupleElementName == "_") { @@ -115,32 +118,131 @@ private static void CheckName(SyntaxNodeAnalysisContext context, StyleCopSetting var firstCharacterIsLower = char.IsLower(tupleElementName[0]); bool reportDiagnostic; - string fixedName; + char fixedFirstChar; switch (settings.NamingRules.TupleElementNameCasing) { case TupleElementNameCase.PascalCase: reportDiagnostic = firstCharacterIsLower; - fixedName = char.ToUpper(tupleElementName[0]) + tupleElementName.Substring(1); + fixedFirstChar = char.ToUpper(tupleElementName[0]); break; default: reportDiagnostic = !firstCharacterIsLower; - fixedName = char.ToLower(tupleElementName[0]) + tupleElementName.Substring(1); + fixedFirstChar = char.ToLower(tupleElementName[0]); break; } - if (reportDiagnostic) + if (!reportDiagnostic) { - var diagnosticProperties = ImmutableDictionary.CreateBuilder(); + return; + } + + if (!CanTupleElementNameBeChanged(context, tupleElement)) + { + return; + } + + var diagnosticProperties = ImmutableDictionary.CreateBuilder(); + + if (prepareCodeFix) + { + var fixedName = fixedFirstChar + tupleElementName.Substring(1); + diagnosticProperties.Add(ExpectedTupleElementNameKey, fixedName); + } + + context.ReportDiagnostic(Diagnostic.Create(Descriptor, location, diagnosticProperties.ToImmutableDictionary())); + } - if (prepareCodeFix) + /// + /// When overriding a base class or implementing an interface, the compiler requires the names to match + /// the original definition. This method checks if we are allowed to change the name of the specified tuple element. + /// + private static bool CanTupleElementNameBeChanged(SyntaxNodeAnalysisContext context, SyntaxNode tupleElement) + { + var memberDeclaration = GetAncestorMemberDeclaration(tupleElement); + if (memberDeclaration == null) + { + return true; + } + + if (IsOverrideOrExplicitInterfaceImplementation(memberDeclaration)) + { + return false; + } + + var symbol = context.SemanticModel.GetDeclaredSymbol(memberDeclaration); + if (NamedTypeHelpers.IsImplementingAnInterfaceMember(symbol)) + { + return false; + } + + return true; + } + + /// + /// Returns the ancestor , if the node is part of its "signature", + /// otherwise returns null. + /// + private static MemberDeclarationSyntax GetAncestorMemberDeclaration(SyntaxNode node) + { + // NOTE: Avoiding a simple FirstAncestorOrSelf() call here, since + // that would also return true for e.g. tuple variable declarations inside a method body. + while (node != null) + { + switch (node.Kind()) { - diagnosticProperties.Add(ExpectedTupleElementNameKey, fixedName); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.EventDeclaration: + case SyntaxKind.IndexerDeclaration: + return (MemberDeclarationSyntax)node; + + case SyntaxKind.Parameter: + case SyntaxKind.ParameterList: + case SyntaxKindEx.TupleElement: + case SyntaxKind.TypeArgumentList: + case SyntaxKind when node is TypeSyntax: + node = node.Parent; + break; + + default: + return null; } + } + + return null; + } - context.ReportDiagnostic(Diagnostic.Create(Descriptor, location, diagnosticProperties.ToImmutableDictionary())); + private static bool IsOverrideOrExplicitInterfaceImplementation(MemberDeclarationSyntax memberDeclaration) + { + bool result; + + switch (memberDeclaration.Kind()) + { + case SyntaxKind.MethodDeclaration: + var methodDeclaration = (MethodDeclarationSyntax)memberDeclaration; + result = + methodDeclaration.Modifiers.Any(SyntaxKind.OverrideKeyword) || + methodDeclaration.ExplicitInterfaceSpecifier != null; + break; + + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.EventDeclaration: + case SyntaxKind.IndexerDeclaration: + var basePropertyDeclaration = (BasePropertyDeclarationSyntax)memberDeclaration; + result = + basePropertyDeclaration.Modifiers.Any(SyntaxKind.OverrideKeyword) || + basePropertyDeclaration.ExplicitInterfaceSpecifier != null; + break; + + default: + Debug.Assert(false, $"Unhandled type {memberDeclaration.Kind()}"); + result = false; + break; } + + return result; } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1121UseBuiltInTypeAlias.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1121UseBuiltInTypeAlias.cs index 55faa593a..31b48e928 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1121UseBuiltInTypeAlias.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1121UseBuiltInTypeAlias.cs @@ -211,7 +211,7 @@ public void HandleIdentifierNameSyntax(SyntaxNodeAnalysisContext context, StyleC // Most source files will not have any using alias directives. Then we don't have to use semantics // if the identifier name doesn't match the name of a special type if (settings.ReadabilityRules.AllowBuiltInTypeAliases - || !identifierNameSyntax.SyntaxTree.ContainsUsingAlias(this.usingAliasCache)) + || !identifierNameSyntax.SyntaxTree.ContainsUsingAlias(this.usingAliasCache, context.SemanticModel, context.CancellationToken)) { switch (identifierNameSyntax.Identifier.ValueText) { diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1135UsingDirectivesMustBeQualified.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1135UsingDirectivesMustBeQualified.cs index bd73f9d8c..3a8d5236a 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1135UsingDirectivesMustBeQualified.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1135UsingDirectivesMustBeQualified.cs @@ -1,8 +1,6 @@ // Copyright (c) Contributors to the New StyleCop Analyzers project. // Licensed under the MIT License. See LICENSE in the project root for license information. -#nullable disable - namespace StyleCop.Analyzers.ReadabilityRules { using System.Collections.Immutable; @@ -82,6 +80,12 @@ private static void CheckUsingDeclaration(SyntaxNodeAnalysisContext context, Usi return; } + if (usingDirective.Name == null) + { + // This happens for e.g. "using X = string;" or "using T = (X, Y);" + return; + } + var symbol = context.SemanticModel.GetSymbolInfo(usingDirective.Name, context.CancellationToken).Symbol; if (symbol == null) { diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/SettingsHelper.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/SettingsHelper.cs index b40ce3332..3f28d7ce0 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/SettingsHelper.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/SettingsHelper.cs @@ -275,8 +275,18 @@ private static SettingsFile GetSettingsFile(AnalyzerOptions options, Func(() => throw new InvalidSettingsException( + $"Settings file at '{additionalFile.Path}' could not be read")); + return new SettingsFile(additionalFile.Path, content); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1008OpeningParenthesisMustBeSpacedCorrectly.cs b/StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1008OpeningParenthesisMustBeSpacedCorrectly.cs index 4f865daac..356a5d116 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1008OpeningParenthesisMustBeSpacedCorrectly.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1008OpeningParenthesisMustBeSpacedCorrectly.cs @@ -223,7 +223,7 @@ private static void HandleOpenParenToken(SyntaxTreeAnalysisContext context, Synt case SyntaxKind.ParenthesizedExpression: case SyntaxKindEx.TupleExpression: if (prevToken.Parent.IsKind(SyntaxKind.Interpolation) - || token.Parent.Parent.IsKind(SyntaxKindEx.RangeExpression)) + || (token.Parent.Parent.IsKind(SyntaxKindEx.RangeExpression) && ((RangeExpressionSyntaxWrapper)token.Parent.Parent).RightOperand == token.Parent)) { haveLeadingSpace = false; break; @@ -248,7 +248,8 @@ private static void HandleOpenParenToken(SyntaxTreeAnalysisContext context, Synt case SyntaxKind.ParameterList: var partOfLambdaExpression = token.Parent.Parent.IsKind(SyntaxKind.ParenthesizedLambdaExpression); - haveLeadingSpace = partOfLambdaExpression; + var startOfCollectionExpression = prevToken.IsKind(SyntaxKind.OpenBracketToken) && prevToken.Parent.IsKind(SyntaxKindEx.CollectionExpression); + haveLeadingSpace = partOfLambdaExpression && !startOfCollectionExpression; break; case SyntaxKindEx.TupleType: diff --git a/StyleCopAnalyzers.sln b/StyleCopAnalyzers.sln index f322c34a6..0a556544c 100644 --- a/StyleCopAnalyzers.sln +++ b/StyleCopAnalyzers.sln @@ -19,7 +19,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .github\workflows\build.yml = .github\workflows\build.yml CONTRIBUTING.md = CONTRIBUTING.md LICENSE = LICENSE + NuGet.config = NuGet.config README.md = README.md + SetVersion.ps1 = SetVersion.ps1 THIRD-PARTY-NOTICES.txt = THIRD-PARTY-NOTICES.txt EndProjectSection EndProject