From bfbb4ca4a80280f49e08df51423ea8db9dbc4222 Mon Sep 17 00:00:00 2001 From: Kevin-Andrew Date: Sat, 5 Sep 2020 21:34:48 -0700 Subject: [PATCH 01/16] Issue #2801: SA1500 fires for the while clause of do/while statement Provide new configuration setting, `layoutRules.allowDoWhileOnClosingBrace` such that when enabled, the following will be allowed: ``` do { Console.WriteLine("test"); } while (false); ``` --- .../LayoutRules/SA1500CodeFixProvider.cs | 18 ++- .../SA1500/SA1500UnitTests.DoWhiles.cs | 149 ++++++++++++++++++ .../Settings/SettingsUnitTests.cs | 2 + ...sForMultiLineStatementsMustNotShareLine.cs | 20 ++- .../Settings/ObjectModel/LayoutSettings.cs | 13 ++ documentation/Configuration.md | 8 + documentation/SA1500.md | 2 + 7 files changed, 203 insertions(+), 9 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1500CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1500CodeFixProvider.cs index 7c0f544f9..05c39b36c 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1500CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1500CodeFixProvider.cs @@ -57,13 +57,13 @@ private static async Task GetTransformedDocumentAsync(Document documen var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, cancellationToken); var braceToken = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start); - var tokenReplacements = GenerateBraceFixes(settings.Indentation, ImmutableArray.Create(braceToken)); + var tokenReplacements = GenerateBraceFixes(settings, ImmutableArray.Create(braceToken)); var newSyntaxRoot = syntaxRoot.ReplaceTokens(tokenReplacements.Keys, (originalToken, rewrittenToken) => tokenReplacements[originalToken]); return document.WithSyntaxRoot(newSyntaxRoot); } - private static Dictionary GenerateBraceFixes(IndentationSettings indentationSettings, ImmutableArray braceTokens) + private static Dictionary GenerateBraceFixes(StyleCopSettings settings, ImmutableArray braceTokens) { var tokenReplacements = new Dictionary(); @@ -72,7 +72,7 @@ private static Dictionary GenerateBraceFixes(Indentati var braceLine = LocationHelpers.GetLineSpan(braceToken).StartLinePosition.Line; var braceReplacementToken = braceToken; - var indentationSteps = DetermineIndentationSteps(indentationSettings, braceToken); + var indentationSteps = DetermineIndentationSteps(settings.Indentation, braceToken); var previousToken = braceToken.GetPreviousToken(); @@ -102,19 +102,23 @@ private static Dictionary GenerateBraceFixes(Indentati AddReplacement(tokenReplacements, previousToken, previousToken.WithTrailingTrivia(previousTokenNewTrailingTrivia)); } - braceReplacementToken = braceReplacementToken.WithLeadingTrivia(IndentationHelper.GenerateWhitespaceTrivia(indentationSettings, indentationSteps)); + braceReplacementToken = braceReplacementToken.WithLeadingTrivia(IndentationHelper.GenerateWhitespaceTrivia(settings.Indentation, indentationSteps)); } // Check if we need to apply a fix after the brace. No fix is needed when: // - The closing brace is followed by a semi-colon or closing paren // - The closing brace is the last token in the file + // - The closing brace is followed by the while expression of a do/while loop and the + // allowDoWhileOnClosingBrace setting is enabled. var nextToken = braceToken.GetNextToken(); var nextTokenLine = nextToken.IsKind(SyntaxKind.None) ? -1 : LocationHelpers.GetLineSpan(nextToken).StartLinePosition.Line; var isMultiDimensionArrayInitializer = braceToken.IsKind(SyntaxKind.OpenBraceToken) && braceToken.Parent.IsKind(SyntaxKind.ArrayInitializerExpression) && braceToken.Parent.Parent.IsKind(SyntaxKind.ArrayInitializerExpression); + var allowDoWhileOnClosingBrace = settings.LayoutRules.AllowDoWhileOnClosingBrace && nextToken.IsKind(SyntaxKind.WhileKeyword) && (braceToken.Parent?.IsKind(SyntaxKind.Block) ?? false) && (braceToken.Parent.Parent?.IsKind(SyntaxKind.DoStatement) ?? false); if ((nextTokenLine == braceLine) && (!braceToken.IsKind(SyntaxKind.CloseBraceToken) || !IsValidFollowingToken(nextToken)) && - !isMultiDimensionArrayInitializer) + !isMultiDimensionArrayInitializer && + !allowDoWhileOnClosingBrace) { var sharedTrivia = nextToken.LeadingTrivia.WithoutTrailingWhitespace(); var newTrailingTrivia = braceReplacementToken.TrailingTrivia @@ -135,7 +139,7 @@ private static Dictionary GenerateBraceFixes(Indentati newIndentationSteps = Math.Max(0, newIndentationSteps - 1); } - AddReplacement(tokenReplacements, nextToken, nextToken.WithLeadingTrivia(IndentationHelper.GenerateWhitespaceTrivia(indentationSettings, newIndentationSteps))); + AddReplacement(tokenReplacements, nextToken, nextToken.WithLeadingTrivia(IndentationHelper.GenerateWhitespaceTrivia(settings.Indentation, newIndentationSteps))); } braceReplacementToken = braceReplacementToken.WithTrailingTrivia(newTrailingTrivia); @@ -284,7 +288,7 @@ protected override async Task FixAllInDocumentAsync(FixAllContext fi var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, fixAllContext.CancellationToken); - var tokenReplacements = GenerateBraceFixes(settings.Indentation, tokens); + var tokenReplacements = GenerateBraceFixes(settings, tokens); return syntaxRoot.ReplaceTokens(tokenReplacements.Keys, (originalToken, rewrittenToken) => tokenReplacements[originalToken]); } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1500/SA1500UnitTests.DoWhiles.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1500/SA1500UnitTests.DoWhiles.cs index 4bb6ce55c..5ce882b31 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1500/SA1500UnitTests.DoWhiles.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1500/SA1500UnitTests.DoWhiles.cs @@ -305,5 +305,154 @@ private void Bar() await VerifyCSharpFixAsync(testCode, expectedDiagnostics, fixedTestCode, CancellationToken.None).ConfigureAwait(false); } + + /// + /// Verifies that no diagnostics are reported for the do/while loop when the + /// expression is on the same line as the closing brace and the setting is enabled. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestDoWhileOnClosingBraceWithAllowSettingAsync() + { + var testSettings = @" +{ + ""settings"": { + ""layoutRules"": { + ""allowDoWhileOnClosingBrace"": true + } + } +}"; + + var testCode = @"public class Foo +{ + private void Bar() + { + var x = 0; + + do + { + x = 1; + } while (x == 0); + } +}"; + + var test = new CSharpTest + { + TestCode = testCode, + Settings = testSettings, + }; + + await test.RunAsync(CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Verifies that diagnostics will be reported for the invalid loop that + /// is on the same line as the closing brace which is not part of a do/while loop. This + /// ensures that the allowDoWhileOnClosingBrace setting is only applicable to a do/while + /// loop and will not mistakenly allow a plain loop after any arbitrary + /// closing brace. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestJustWhileLoopOnClosingBraceWithAllowDoWhileOnClosingBraceSettingAsync() + { + var testSettings = @" +{ + ""settings"": { + ""layoutRules"": { + ""allowDoWhileOnClosingBrace"": true + } + } +}"; + + var testCode = @"public class Foo +{ + private void Bar() + { + var x = 0; + + while (x == 0) + { + x = 1; + } while (x == 0) + { + x = 1; + } + } +}"; + + var test = new CSharpTest + { + TestCode = testCode, + ExpectedDiagnostics = + { + Diagnostic().WithLocation(10, 9), + }, + Settings = testSettings, + }; + + await test.RunAsync(CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Verifies that no diagnostics are reported for the do/while loop when the + /// expression is on the same line as the closing brace and the setting is allowed. + /// + /// + /// The "Invalid do ... while #6" code in the unit test + /// should account for the proper fix when the allowDoWhileOnClosingBrace is , + /// which is the default. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestFixDoWhileOnClosingBraceWithAllowSettingAsync() + { + var testSettings = @" +{ + ""settings"": { + ""layoutRules"": { + ""allowDoWhileOnClosingBrace"": true + } + } +}"; + + var testCode = @"public class Foo +{ + private void Bar() + { + var x = 0; + + do + { + x = 1; } while (x == 0); + } +}"; + + var fixedTestCode = @"public class Foo +{ + private void Bar() + { + var x = 0; + + do + { + x = 1; + } while (x == 0); + } +}"; + + var test = new CSharpTest + { + TestCode = testCode, + ExpectedDiagnostics = + { + Diagnostic().WithLocation(9, 20), + }, + FixedCode = fixedTestCode, + Settings = testSettings, + }; + + await test.RunAsync(CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Settings/SettingsUnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Settings/SettingsUnitTests.cs index 77eb222e5..a5e69077e 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Settings/SettingsUnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Settings/SettingsUnitTests.cs @@ -37,6 +37,8 @@ public void VerifySettingsDefaults() Assert.NotNull(styleCopSettings.LayoutRules); Assert.Equal(OptionSetting.Allow, styleCopSettings.LayoutRules.NewlineAtEndOfFile); + Assert.True(styleCopSettings.LayoutRules.AllowConsecutiveUsings); + Assert.False(styleCopSettings.LayoutRules.AllowDoWhileOnClosingBrace); Assert.NotNull(styleCopSettings.SpacingRules); Assert.NotNull(styleCopSettings.ReadabilityRules); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1500BracesForMultiLineStatementsMustNotShareLine.cs b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1500BracesForMultiLineStatementsMustNotShareLine.cs index c17f25ec2..5e1f04f53 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1500BracesForMultiLineStatementsMustNotShareLine.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1500BracesForMultiLineStatementsMustNotShareLine.cs @@ -230,7 +230,7 @@ private static void CheckBraces(SyntaxNodeAnalysisContext context, SyntaxToken o CheckBraceToken(context, openBraceToken); if (checkCloseBrace) { - CheckBraceToken(context, closeBraceToken); + CheckBraceToken(context, closeBraceToken, openBraceToken); } } @@ -242,7 +242,7 @@ private static bool InitializerExpressionSharesLine(InitializerExpressionSyntax return (index > 0) && (parent.Expressions[index - 1].GetEndLine() == parent.Expressions[index].GetLine()); } - private static void CheckBraceToken(SyntaxNodeAnalysisContext context, SyntaxToken token) + private static void CheckBraceToken(SyntaxNodeAnalysisContext context, SyntaxToken token, SyntaxToken openBraceToken = default) { if (token.IsMissing) { @@ -279,6 +279,22 @@ private static void CheckBraceToken(SyntaxNodeAnalysisContext context, SyntaxTok // last token of this file return; + case SyntaxKind.WhileKeyword: + // Because the default Visual Studio code completion snippet for a do-while loop + // places the while expression on the same line as the closing brace, some users + // may want to allow that and not have SA1500 report it as a style error. + if (context.Options.GetStyleCopSettings(context.CancellationToken).LayoutRules.AllowDoWhileOnClosingBrace) + { + var openBracePreviousToken = openBraceToken.GetPreviousToken(includeZeroWidth: true); + + if (openBracePreviousToken.IsKind(SyntaxKind.DoKeyword)) + { + return; + } + } + + break; + default: break; } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/LayoutSettings.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/LayoutSettings.cs index 85f3e0fe9..6970657f2 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/LayoutSettings.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/LayoutSettings.cs @@ -17,6 +17,11 @@ internal class LayoutSettings /// private readonly bool allowConsecutiveUsings; + /// + /// This is the backing field of the property. + /// + private readonly bool allowDoWhileOnClosingBrace; + /// /// Initializes a new instance of the class. /// @@ -24,6 +29,7 @@ protected internal LayoutSettings() { this.newlineAtEndOfFile = OptionSetting.Allow; this.allowConsecutiveUsings = true; + this.allowDoWhileOnClosingBrace = false; } /// @@ -45,6 +51,10 @@ protected internal LayoutSettings(JsonObject layoutSettingsObject) this.allowConsecutiveUsings = kvp.ToBooleanValue(); break; + case "allowDoWhileOnClosingBrace": + this.allowDoWhileOnClosingBrace = kvp.ToBooleanValue(); + break; + default: break; } @@ -56,5 +66,8 @@ protected internal LayoutSettings(JsonObject layoutSettingsObject) public bool AllowConsecutiveUsings => this.allowConsecutiveUsings; + + public bool AllowDoWhileOnClosingBrace => + this.allowDoWhileOnClosingBrace; } } diff --git a/documentation/Configuration.md b/documentation/Configuration.md index 420b3add4..9b7af7f3e 100644 --- a/documentation/Configuration.md +++ b/documentation/Configuration.md @@ -418,6 +418,7 @@ The following properties are used to configure layout rules in StyleCop Analyzer | --- | --- | --- | --- | | `newlineAtEndOfFile` | `"allow"` | 1.0.0 | Specifies the handling for newline characters which appear at the end of a file | | `allowConsecutiveUsings` | `true` | 1.1.0 | Specifies if SA1519 will allow consecutive using statements without braces | +| `allowDoWhileOnClosingBrace` | `false` | >1.2.0 | Specifies if SA1500 will allow the `while` expression of a `do-while` loop to be on the same line as the closing brace, as is generated by the default code snippet of Visual Studio | ### Lines at End of File @@ -439,6 +440,13 @@ The `allowConsecutiveUsings` property specifies the behavior: This only allows omitting the braces for a using followed by another using statement. A using statement followed by any other type of statement will still require braces to used. +### Do-While Loop Placement + +The behavior of [SA1500](SA1500.md) can be customized regarding the manner in which the `while` expression of a `do-while` loop is allowed to be placed. The `allowDoWhileOnClosingBrace` property specified the behavior: + +* `true`: the `while` expression of a `do-while` loop may be placed on the same line as the closing brace +* `false`: the `while` expression of a `do-while` loop must be on a separate line from the closing brace + ## Documentation Rules This section describes the features of documentation rules which can be configured in **stylecop.json**. Each of the described properties are configured in the `documentationRules` object, which is shown in the following sample file. diff --git a/documentation/SA1500.md b/documentation/SA1500.md index eaa039f65..652728001 100644 --- a/documentation/SA1500.md +++ b/documentation/SA1500.md @@ -19,6 +19,8 @@ The opening or closing brace within a C# statement, element, or expression is not placed on its own line. +> :memo: The behavior of this rule can change based on the configuration of the `allowDoWhileOnClosingBrace` property in **stylecop.json**. See [Configuration.md](Configuration.md#Layout-Rules) for more information. + ## Rule description A violation of this rule occurs when the opening or closing brace within a statement, element, or expression is not placed on its own line. For example: From b5228ba9d8a1ac9aa3e16120a7d48ce2c1d29782 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Fri, 2 Oct 2020 11:33:19 -0700 Subject: [PATCH 02/16] Support defining options in .editorconfig Closes #2745 Closes #3045 --- .editorconfig | 2 + .../FileHeaderCodeFixProvider.cs | 2 +- .../SA1642SA1643CodeFixProvider.cs | 2 +- .../LayoutRules/SA1500CodeFixProvider.cs | 4 +- .../LayoutRules/SA1501CodeFixProvider.cs | 4 +- .../LayoutRules/SA1502CodeFixProvider.cs | 2 +- .../LayoutRules/SA1504CodeFixProvider.cs | 4 +- .../LayoutRules/SA1518CodeFixProvider.cs | 10 +- .../SA1402CodeFixProvider.cs | 2 +- .../ElementOrderCodeFixProvider.cs | 8 +- .../OrderingRules/UsingCodeFixProvider.cs | 2 +- .../ReadabilityRules/SA1102CodeFixProvider.cs | 2 +- .../ReadabilityRules/SA1103CodeFixProvider.cs | 2 +- .../SA1104SA1105CodeFixProvider.cs | 2 +- .../ReadabilityRules/SA1116CodeFixProvider.cs | 2 +- .../ReadabilityRules/SA1127CodeFixProvider.cs | 2 +- .../ReadabilityRules/SA1128CodeFixProvider.cs | 4 +- .../ReadabilityRules/SA1133CodeFixProvider.cs | 4 +- .../ReadabilityRules/SA1134CodeFixProvider.cs | 2 +- .../ReadabilityRules/SA1136CodeFixProvider.cs | 2 +- .../SpacingRules/SA1027CodeFixProvider.cs | 7 +- .../Settings/SettingsCSharp7UnitTests.cs | 11 + ...ingsFileCodeFixProviderCSharp7UnitTests.cs | 11 + .../Settings/SettingsCSharp8UnitTests.cs | 204 ++++++++++++++++ ...ingsFileCodeFixProviderCSharp8UnitTests.cs | 11 + .../Settings/SettingsCSharp9UnitTests.cs | 45 ++++ ...ingsFileCodeFixProviderCSharp9UnitTests.cs | 11 + .../HelperTests/IndentationHelperTests.cs | 8 +- .../Settings/SettingsUnitTests.cs | 29 +-- .../StyleCop.Analyzers.Test.csproj | 1 + .../StyleCop.Analyzers/AnalyzerExtensions.cs | 24 +- .../PropertySummaryDocumentationAnalyzer.cs | 2 +- ...ntDocumentationMustNotBeCopiedAndPasted.cs | 2 +- ...yDocumentationMustBeginWithStandardText.cs | 2 +- ...yDocumentationMustBeginWithStandardText.cs | 2 +- .../Helpers/KeyValuePairExtensions.cs | 16 ++ .../AnalyzerConfigOptionsProviderWrapper.cs | 101 ++++++++ .../Lightup/AnalyzerConfigOptionsWrapper.cs | 77 +++++++ .../Lightup/AnalyzerOptionsExtensions.cs | 23 ++ .../Lightup/LightupHelpers.cs | 218 +++++++++++++++++- .../Lightup/TryGetValueAccessor`3.cs | 7 + .../Lightup/WrapperHelper.cs | 42 ++++ ...TupleElementNamesShouldUseCorrectCasing.cs | 4 +- .../ObjectModel/AnalyzerConfigHelper.cs | 86 +++++++ .../ObjectModel/DocumentationSettings.cs | 93 ++++++-- .../ObjectModel/IndentationSettings.cs | 29 ++- .../Settings/ObjectModel/LayoutSettings.cs | 25 +- .../ObjectModel/MaintainabilitySettings.cs | 19 +- .../Settings/ObjectModel/NamingSettings.cs | 65 ++++-- .../Settings/ObjectModel/OrderingSettings.cs | 46 +++- .../ObjectModel/ReadabilitySettings.cs | 14 +- .../Settings/ObjectModel/SpacingSettings.cs | 7 +- .../Settings/ObjectModel/StyleCopSettings.cs | 30 ++- .../Settings/SettingsHelper.cs | 120 +++------- .../SpecialRules/SA0002InvalidSettingsFile.cs | 3 +- 55 files changed, 1221 insertions(+), 238 deletions(-) create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/Settings/SettingsCSharp7UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/Settings/SettingsFileCodeFixProviderCSharp7UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/Settings/SettingsCSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/Settings/SettingsFileCodeFixProviderCSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/Settings/SettingsCSharp9UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/Settings/SettingsFileCodeFixProviderCSharp9UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers/Helpers/KeyValuePairExtensions.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers/Lightup/AnalyzerConfigOptionsProviderWrapper.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers/Lightup/AnalyzerConfigOptionsWrapper.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers/Lightup/AnalyzerOptionsExtensions.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers/Lightup/TryGetValueAccessor`3.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers/Lightup/WrapperHelper.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/AnalyzerConfigHelper.cs diff --git a/.editorconfig b/.editorconfig index 84afcc9ea..04710b12f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,6 +12,8 @@ indent_size = 4 # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false +csharp_using_directive_placement = inside_namespace:none # Always use "this." and "Me." when applicable; let StyleCop Analyzers provide the warning and fix dotnet_style_qualification_for_field = true:none diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/FileHeaderCodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/FileHeaderCodeFixProvider.cs index 071bb6f89..bb7a4b869 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/FileHeaderCodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/FileHeaderCodeFixProvider.cs @@ -77,7 +77,7 @@ private static async Task GetTransformedDocumentAsync(Document documen private static async Task GetTransformedSyntaxRootAsync(Document document, CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var settings = document.Project.AnalyzerOptions.GetStyleCopSettings(cancellationToken); + var settings = document.Project.AnalyzerOptions.GetStyleCopSettings(root.SyntaxTree, cancellationToken); var fileHeader = FileHeaderHelpers.ParseFileHeader(root); SyntaxNode newSyntaxRoot; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/SA1642SA1643CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/SA1642SA1643CodeFixProvider.cs index a7d2967dd..110f77678 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/SA1642SA1643CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/SA1642SA1643CodeFixProvider.cs @@ -82,7 +82,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) internal static ImmutableArray GenerateStandardText(Document document, BaseMethodDeclarationSyntax methodDeclaration, BaseTypeDeclarationSyntax typeDeclaration, CancellationToken cancellationToken) { bool isStruct = typeDeclaration.IsKind(SyntaxKind.StructDeclaration); - var settings = document.Project.AnalyzerOptions.GetStyleCopSettings(cancellationToken); + var settings = document.Project.AnalyzerOptions.GetStyleCopSettings(methodDeclaration.SyntaxTree, cancellationToken); var culture = new CultureInfo(settings.DocumentationRules.DocumentationCulture); var resourceManager = DocumentationResources.ResourceManager; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1500CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1500CodeFixProvider.cs index 7c0f544f9..32cccab6b 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1500CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1500CodeFixProvider.cs @@ -55,7 +55,7 @@ private static async Task GetTransformedDocumentAsync(Document documen { var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, cancellationToken); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, cancellationToken); var braceToken = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start); var tokenReplacements = GenerateBraceFixes(settings.Indentation, ImmutableArray.Create(braceToken)); @@ -282,7 +282,7 @@ protected override async Task FixAllInDocumentAsync(FixAllContext fi .OrderBy(token => token.SpanStart) .ToImmutableArray(); - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, fixAllContext.CancellationToken); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, fixAllContext.CancellationToken); var tokenReplacements = GenerateBraceFixes(settings.Indentation, tokens); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1501CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1501CodeFixProvider.cs index c362694b5..b04b289ee 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1501CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1501CodeFixProvider.cs @@ -58,7 +58,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) private static async Task GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, cancellationToken); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, cancellationToken); if (!(syntaxRoot.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true) is StatementSyntax statement)) { return document; @@ -301,8 +301,8 @@ protected override async Task FixAllInDocumentAsync(FixAllContext fi } var tokenReplaceMap = new Dictionary(); - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, fixAllContext.CancellationToken); SyntaxNode syntaxRoot = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, fixAllContext.CancellationToken); foreach (var diagnostic in diagnostics.Sort(DiagnosticComparer.Instance)) { diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1502CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1502CodeFixProvider.cs index 2b5ea7cba..5d3b3d508 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1502CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1502CodeFixProvider.cs @@ -53,7 +53,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) private async Task GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, cancellationToken); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, cancellationToken); var newDocument = this.CreateCodeFix(document, settings.Indentation, diagnostic, syntaxRoot); return newDocument; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1504CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1504CodeFixProvider.cs index 4d78e14ab..606ace939 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1504CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1504CodeFixProvider.cs @@ -109,7 +109,7 @@ private static bool IsAllowedTrivia(SyntaxTrivia trivia) private static async Task GetTransformedDocumentForSingleLineAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, cancellationToken); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, cancellationToken); var node = syntaxRoot.FindNode(diagnostic.Location.SourceSpan); var accessorList = GetAccessorList(node); @@ -188,7 +188,7 @@ private static BlockSyntax ReformatBodyAsSingleLine(BlockSyntax body) private static async Task GetTransformedDocumentForMutipleLinesAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, cancellationToken); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, cancellationToken); var node = syntaxRoot.FindNode(diagnostic.Location.SourceSpan); var accessorList = GetAccessorList(node); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1518CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1518CodeFixProvider.cs index c1d3fda34..d273f5437 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1518CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1518CodeFixProvider.cs @@ -32,9 +32,10 @@ public override FixAllProvider GetFixAllProvider() } /// - public override Task RegisterCodeFixesAsync(CodeFixContext context) + public override async Task RegisterCodeFixesAsync(CodeFixContext context) { - var settings = SettingsHelper.GetStyleCopSettings(context.Document.Project.AnalyzerOptions, context.CancellationToken); + var syntaxTree = await context.Document.GetSyntaxTreeAsync(context.CancellationToken).ConfigureAwait(false); + var settings = SettingsHelper.GetStyleCopSettings(context.Document.Project.AnalyzerOptions, syntaxTree, context.CancellationToken); foreach (var diagnostic in context.Diagnostics) { context.RegisterCodeFix( @@ -44,8 +45,6 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) nameof(SA1518CodeFixProvider)), diagnostic); } - - return SpecializedTasks.CompletedTask; } /// @@ -78,7 +77,8 @@ protected override async Task FixAllInDocumentAsync(FixAllContext fi return null; } - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, fixAllContext.CancellationToken); + var syntaxTree = await document.GetSyntaxTreeAsync(fixAllContext.CancellationToken).ConfigureAwait(false); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxTree, fixAllContext.CancellationToken); Document updatedDocument = await FixEndOfFileAsync(document, diagnostics[0], settings.LayoutRules.NewlineAtEndOfFile, fixAllContext.CancellationToken).ConfigureAwait(false); return await updatedDocument.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false); } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/MaintainabilityRules/SA1402CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/MaintainabilityRules/SA1402CodeFixProvider.cs index 44ea38116..6735f7179 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/MaintainabilityRules/SA1402CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/MaintainabilityRules/SA1402CodeFixProvider.cs @@ -64,7 +64,7 @@ private static async Task GetTransformedSolutionAsync(Document documen DocumentId extractedDocumentId = DocumentId.CreateNewId(document.Project.Id); string suffix; FileNameHelpers.GetFileNameAndSuffix(document.Name, out suffix); - var settings = document.Project.AnalyzerOptions.GetStyleCopSettings(cancellationToken); + var settings = document.Project.AnalyzerOptions.GetStyleCopSettings(root.SyntaxTree, cancellationToken); string extractedDocumentName = FileNameHelpers.GetConventionalFileName(memberDeclarationSyntax, settings.DocumentationRules.FileNamingConvention) + suffix; List nodesToRemoveFromExtracted = new List(); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/ElementOrderCodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/ElementOrderCodeFixProvider.cs index 4dfd72f55..5f6c18e6f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/ElementOrderCodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/ElementOrderCodeFixProvider.cs @@ -57,9 +57,9 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) private static async Task GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, cancellationToken); - var elementOrder = settings.OrderingRules.ElementOrder; var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, cancellationToken); + var elementOrder = settings.OrderingRules.ElementOrder; var memberDeclaration = syntaxRoot.FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf(); if (memberDeclaration == null) @@ -263,9 +263,9 @@ protected override async Task FixAllInDocumentAsync(FixAllContext fi return null; } - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, fixAllContext.CancellationToken); + var syntaxRoot = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, fixAllContext.CancellationToken); var elementOrder = settings.OrderingRules.ElementOrder; - var syntaxRoot = await document.GetSyntaxRootAsync().ConfigureAwait(false); var trackedDiagnosticMembers = new List(); foreach (var diagnostic in diagnostics) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/UsingCodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/UsingCodeFixProvider.cs index 692dce85b..70f5357cf 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/UsingCodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/UsingCodeFixProvider.cs @@ -80,8 +80,8 @@ private static async Task GetTransformedDocumentAsync(Document documen var fileHeader = GetFileHeader(syntaxRoot); var compilationUnit = (CompilationUnitSyntax)syntaxRoot; - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, cancellationToken); var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, semanticModel.SyntaxTree, cancellationToken); var usingDirectivesPlacement = forcePreservePlacement ? UsingDirectivesPlacement.Preserve : DeterminePlacement(compilationUnit, settings); var usingsHelper = new UsingsSorter(settings, semanticModel, compilationUnit, fileHeader); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1102CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1102CodeFixProvider.cs index d8ebf057c..7637a6431 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1102CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1102CodeFixProvider.cs @@ -52,7 +52,7 @@ private static async Task GetTransformedDocumentAsync(Document documen var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var token = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start); - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, cancellationToken); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, cancellationToken); var indentationTrivia = QueryIndentationHelpers.GetQueryIndentationTrivia(settings.Indentation, token); var precedingToken = token.GetPreviousToken(); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1103CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1103CodeFixProvider.cs index 7d31f5433..51ec36850 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1103CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1103CodeFixProvider.cs @@ -118,7 +118,7 @@ private static async Task GetTransformedDocumentForMultipleLinesAsync( var nodeList = CreateQueryNodeList(queryExpression); - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, cancellationToken); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, cancellationToken); var indentationTrivia = QueryIndentationHelpers.GetQueryIndentationTrivia(settings.Indentation, queryExpression); for (var i = 1; i < nodeList.Length; i++) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1104SA1105CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1104SA1105CodeFixProvider.cs index 7097d71a4..da216df7c 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1104SA1105CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1104SA1105CodeFixProvider.cs @@ -54,7 +54,7 @@ private static async Task GetTransformedDocumentAsync(Document documen var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var token = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start); - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, cancellationToken); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, cancellationToken); var indentationTrivia = QueryIndentationHelpers.GetQueryIndentationTrivia(settings.Indentation, token); var precedingToken = token.GetPreviousToken(); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1116CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1116CodeFixProvider.cs index 95bac3e7e..45ec8d6c8 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1116CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1116CodeFixProvider.cs @@ -70,7 +70,7 @@ private static async Task GetTransformedDocumentAsync(Document documen } } - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, cancellationToken); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, tree, cancellationToken); SyntaxTriviaList newTrivia = SyntaxFactory.TriviaList( SyntaxFactory.CarriageReturnLineFeed, diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1127CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1127CodeFixProvider.cs index b87b26e47..b9d3afdeb 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1127CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1127CodeFixProvider.cs @@ -60,7 +60,7 @@ private static async Task GetTransformedDocumentAsync(Document documen var afterEndToken = endToken.GetNextToken(); var parentIndentation = GetParentIndentation(whereToken); - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, cancellationToken); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, cancellationToken); var indentationTrivia = SyntaxFactory.Whitespace(parentIndentation + IndentationHelper.GenerateIndentationString(settings.Indentation, 1)); var replaceMap = new Dictionary() diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1128CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1128CodeFixProvider.cs index 6eca3e3d7..f2b47c5b9 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1128CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1128CodeFixProvider.cs @@ -55,7 +55,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) private static async Task GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, cancellationToken); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, cancellationToken); var newLine = FormattingHelper.GetNewLineTrivia(document); var constructorInitializer = (ConstructorInitializerSyntax)syntaxRoot.FindNode(diagnostic.Location.SourceSpan); @@ -108,7 +108,7 @@ protected override async Task FixAllInDocumentAsync(FixAllContext fi } var syntaxRoot = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false); - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, fixAllContext.CancellationToken); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, fixAllContext.CancellationToken); var newLine = FormattingHelper.GetNewLineTrivia(document); var nodes = diagnostics.Select(diagnostic => syntaxRoot.FindNode(diagnostic.Location.SourceSpan).Parent); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1133CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1133CodeFixProvider.cs index b12dfafe4..200572f15 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1133CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1133CodeFixProvider.cs @@ -59,7 +59,7 @@ private static async Task GetTransformedDocumentAsync(Document documen var nodeInSourceSpan = syntaxRoot.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); AttributeListSyntax attributeList = nodeInSourceSpan.FirstAncestorOrSelf(); - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, cancellationToken); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, cancellationToken); var indentationSteps = IndentationHelper.GetIndentationSteps(settings.Indentation, attributeList); var indentationTrivia = IndentationHelper.GenerateWhitespaceTrivia(settings.Indentation, indentationSteps); @@ -111,8 +111,8 @@ protected override async Task FixAllInDocumentAsync(FixAllContext fi return null; } - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, fixAllContext.CancellationToken); var syntaxRoot = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, fixAllContext.CancellationToken); var nodes = diagnostics.Select(diagnostic => syntaxRoot.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true).FirstAncestorOrSelf()); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1134CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1134CodeFixProvider.cs index 66ca83f45..ee28caff7 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1134CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1134CodeFixProvider.cs @@ -55,7 +55,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) private static async Task GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, cancellationToken); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, cancellationToken); var attributeListSyntax = (AttributeListSyntax)syntaxRoot.FindNode(diagnostic.Location.SourceSpan); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1136CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1136CodeFixProvider.cs index 4c10c7483..85b93a5ed 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1136CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1136CodeFixProvider.cs @@ -51,7 +51,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) private static async Task GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, cancellationToken); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, cancellationToken); var enumMemberDeclaration = (EnumMemberDeclarationSyntax)syntaxRoot.FindNode(diagnostic.Location.SourceSpan); var enumDeclaration = (EnumDeclarationSyntax)enumMemberDeclaration.Parent; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/SpacingRules/SA1027CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/SpacingRules/SA1027CodeFixProvider.cs index 3e6de8a95..8a026af97 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/SpacingRules/SA1027CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/SpacingRules/SA1027CodeFixProvider.cs @@ -52,7 +52,8 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) private static async Task GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, cancellationToken); + var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxTree, cancellationToken); SourceText sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); return document.WithText(sourceText.WithChanges(FixDiagnostic(settings.Indentation, sourceText, diagnostic))); } @@ -157,7 +158,8 @@ protected override async Task FixAllInDocumentAsync(FixAllContext fi return null; } - var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, fixAllContext.CancellationToken); + SyntaxTree tree = await document.GetSyntaxTreeAsync(fixAllContext.CancellationToken).ConfigureAwait(false); + var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, tree, fixAllContext.CancellationToken); SourceText sourceText = await document.GetTextAsync(fixAllContext.CancellationToken).ConfigureAwait(false); List changes = new List(); @@ -168,7 +170,6 @@ protected override async Task FixAllInDocumentAsync(FixAllContext fi changes.Sort((left, right) => left.Span.Start.CompareTo(right.Span.Start)); - SyntaxTree tree = await document.GetSyntaxTreeAsync(fixAllContext.CancellationToken).ConfigureAwait(false); return await tree.WithChangedText(sourceText.WithChanges(changes)).GetRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false); } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/Settings/SettingsCSharp7UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/Settings/SettingsCSharp7UnitTests.cs new file mode 100644 index 000000000..09e4f39fb --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/Settings/SettingsCSharp7UnitTests.cs @@ -0,0 +1,11 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp7.Settings +{ + using StyleCop.Analyzers.Test.Settings; + + public class SettingsCSharp7UnitTests : SettingsUnitTests + { + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/Settings/SettingsFileCodeFixProviderCSharp7UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/Settings/SettingsFileCodeFixProviderCSharp7UnitTests.cs new file mode 100644 index 000000000..bfcbcc52d --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/Settings/SettingsFileCodeFixProviderCSharp7UnitTests.cs @@ -0,0 +1,11 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp7.Settings +{ + using StyleCop.Analyzers.Test.Settings; + + public class SettingsFileCodeFixProviderCSharp7UnitTests : SettingsFileCodeFixProviderUnitTests + { + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/Settings/SettingsCSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/Settings/SettingsCSharp8UnitTests.cs new file mode 100644 index 000000000..a7da3dda5 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/Settings/SettingsCSharp8UnitTests.cs @@ -0,0 +1,204 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.Settings +{ + using System.Collections.Immutable; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.Diagnostics; + using Microsoft.CodeAnalysis.Text; + using StyleCop.Analyzers.Settings.ObjectModel; + using StyleCop.Analyzers.Test.CSharp7.Settings; + using StyleCop.Analyzers.Test.Verifiers; + using Xunit; + + public class SettingsCSharp8UnitTests : SettingsCSharp7UnitTests + { + private const string TestProjectName = "TestProject"; + + [Fact] + public async Task VerifyEditorConfigSettingsAreReadCorrectlyAsync() + { + var settings = @"root = true + +[*] +file_header_template = Custom copyright text. +insert_final_newline = true +csharp_using_directive_placement = outside_namespace:none +dotnet_separate_import_directive_groups = false + +stylecop.documentation.companyName = TestCompany +stylecop.documentation.documentationCulture = ru-RU +stylecop.documentation.unrecognizedValue = 3 + +stylecop.naming.allowCommonHungarianPrefixes = false +stylecop.naming.allowedHungarianPrefixes = a, ab, ignoredTooLong +stylecop.naming.allowedNamespaceComponents = eBay, iMac +stylecop.naming.unrecognizedValue = 3 + +stylecop.layout.unrecognizedValue = 3 + +stylecop.ordering.unrecognizedValue = 3 + +stylecop.maintainability.unrecognizedValue = 3 + +stylecop.indentation.unrecognizedValue = 3 + +stylecop.unrecognizedValue = 3 +"; + var context = await this.CreateAnalysisContextFromEditorConfigAsync(settings).ConfigureAwait(false); + + var styleCopSettings = context.GetStyleCopSettings(CancellationToken.None); + + Assert.Equal("TestCompany", styleCopSettings.DocumentationRules.CompanyName); + Assert.Equal("Custom copyright text.", styleCopSettings.DocumentationRules.GetCopyrightText("unused")); + Assert.Equal("ru-RU", styleCopSettings.DocumentationRules.DocumentationCulture); + Assert.False(styleCopSettings.NamingRules.AllowCommonHungarianPrefixes); + Assert.Equal(new[] { "a", "ab" }, styleCopSettings.NamingRules.AllowedHungarianPrefixes); + Assert.Equal(new[] { "eBay", "iMac" }, styleCopSettings.NamingRules.AllowedNamespaceComponents); + + Assert.NotNull(styleCopSettings.LayoutRules); + Assert.Equal(OptionSetting.Require, styleCopSettings.LayoutRules.NewlineAtEndOfFile); + + Assert.NotNull(styleCopSettings.OrderingRules); + Assert.Equal(UsingDirectivesPlacement.OutsideNamespace, styleCopSettings.OrderingRules.UsingDirectivesPlacement); + Assert.Equal(OptionSetting.Omit, styleCopSettings.OrderingRules.BlankLinesBetweenUsingGroups); + } + + [Theory] + [CombinatorialData] + public async Task VerifyBooleanDocumentationSettingsFromEditorConfigAsync(bool value) + { + string valueText = value.ToString().ToLowerInvariant(); + var settings = $@"root = true + +[*] +stylecop.documentation.documentExposedElements = {valueText} +stylecop.documentation.documentInternalElements = {valueText} +stylecop.documentation.documentPrivateElements = {valueText} +stylecop.documentation.documentInterfaces = {valueText} +stylecop.documentation.documentPrivateFields = {valueText} +"; + var context = await this.CreateAnalysisContextFromEditorConfigAsync(settings).ConfigureAwait(false); + + var styleCopSettings = context.GetStyleCopSettings(CancellationToken.None); + + Assert.Equal(value, styleCopSettings.DocumentationRules.DocumentExposedElements); + Assert.Equal(value, styleCopSettings.DocumentationRules.DocumentInternalElements); + Assert.Equal(value, styleCopSettings.DocumentationRules.DocumentPrivateElements); + Assert.Equal(value, styleCopSettings.DocumentationRules.DocumentInterfaces); + Assert.Equal(value, styleCopSettings.DocumentationRules.DocumentPrivateFields); + } + + [Theory] + [InlineData("TestCompany")] + [InlineData("TestCompany2")] + public async Task VerifySettingsWillUseCompanyNameInDefaultCopyrightTextFromEditorConfigAsync(string companyName) + { + var settings = $@"root = true + +[*] +stylecop.documentation.companyName = {companyName} +"; + var context = await this.CreateAnalysisContextFromEditorConfigAsync(settings).ConfigureAwait(false); + + var styleCopSettings = context.GetStyleCopSettings(CancellationToken.None); + + Assert.Equal(companyName, styleCopSettings.DocumentationRules.CompanyName); + Assert.Equal($"Copyright (c) {companyName}. All rights reserved.", styleCopSettings.DocumentationRules.GetCopyrightText("unused")); + } + + [Fact] + public async Task VerifyCircularReferenceBehaviorFromEditorConfigAsync() + { + var settings = @"root = true + +[*] +file_header_template = {copyrightText} +"; + var context = await this.CreateAnalysisContextFromEditorConfigAsync(settings).ConfigureAwait(false); + + var styleCopSettings = context.GetStyleCopSettings(CancellationToken.None); + + Assert.Equal("[CircularReference]", styleCopSettings.DocumentationRules.GetCopyrightText("unused")); + } + + [Fact] + public async Task VerifyInvalidReferenceBehaviorFromEditorConfigAsync() + { + var settings = @"root = true + +[*] +file_header_template = {variable} +"; + var context = await this.CreateAnalysisContextFromEditorConfigAsync(settings).ConfigureAwait(false); + + var styleCopSettings = context.GetStyleCopSettings(CancellationToken.None); + + Assert.Equal("[InvalidReference]", styleCopSettings.DocumentationRules.GetCopyrightText("unused")); + } + + protected virtual AnalyzerConfigOptionsProvider CreateAnalyzerConfigOptionsProvider(AnalyzerConfigSet analyzerConfigSet) + => new TestAnalyzerConfigOptionsProvider(analyzerConfigSet); + + private async Task CreateAnalysisContextFromEditorConfigAsync(string editorConfig) + { + var projectId = ProjectId.CreateNewId(); + var documentId = DocumentId.CreateNewId(projectId); + var analyzerConfigDocumentId = DocumentId.CreateNewId(projectId); + + var solution = GenericAnalyzerTest.CreateWorkspace() + .CurrentSolution + .AddProject(projectId, TestProjectName, TestProjectName, LanguageNames.CSharp) + .AddDocument(documentId, "/0/Test0.cs", SourceText.From(string.Empty)) + .AddAnalyzerConfigDocument(analyzerConfigDocumentId, "/.editorconfig", SourceText.From(editorConfig), filePath: "/.editorconfig"); + + var document = solution.GetDocument(documentId); + var syntaxTree = await document.GetSyntaxTreeAsync(CancellationToken.None).ConfigureAwait(false); + + var analyzerConfigSet = AnalyzerConfigSet.Create(new[] { AnalyzerConfig.Parse(SourceText.From(editorConfig), "/.editorconfig") }); + var additionalFiles = ImmutableArray.Empty; + var optionsProvider = this.CreateAnalyzerConfigOptionsProvider(analyzerConfigSet); + var analyzerOptions = new AnalyzerOptions(additionalFiles, optionsProvider); + + return new SyntaxTreeAnalysisContext(syntaxTree, analyzerOptions, reportDiagnostic: _ => { }, isSupportedDiagnostic: _ => true, CancellationToken.None); + } + + protected class TestAnalyzerConfigOptions : AnalyzerConfigOptions + { + private readonly AnalyzerConfigOptionsResult analyzerConfigOptionsResult; + + public TestAnalyzerConfigOptions(AnalyzerConfigOptionsResult analyzerConfigOptionsResult) + { + this.analyzerConfigOptionsResult = analyzerConfigOptionsResult; + } + + public override bool TryGetValue(string key, out string value) + { + return this.analyzerConfigOptionsResult.AnalyzerOptions.TryGetValue(key, out value); + } + } + + private sealed class TestAnalyzerConfigOptionsProvider : AnalyzerConfigOptionsProvider + { + private readonly AnalyzerConfigSet analyzerConfigSet; + + public TestAnalyzerConfigOptionsProvider(AnalyzerConfigSet analyzerConfigSet) + { + this.analyzerConfigSet = analyzerConfigSet; + } + + public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) + { + return new TestAnalyzerConfigOptions(this.analyzerConfigSet.GetOptionsForSourcePath(tree.FilePath)); + } + + public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) + { + return new TestAnalyzerConfigOptions(this.analyzerConfigSet.GetOptionsForSourcePath(textFile.Path)); + } + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/Settings/SettingsFileCodeFixProviderCSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/Settings/SettingsFileCodeFixProviderCSharp8UnitTests.cs new file mode 100644 index 000000000..d9e964c77 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/Settings/SettingsFileCodeFixProviderCSharp8UnitTests.cs @@ -0,0 +1,11 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.Settings +{ + using StyleCop.Analyzers.Test.CSharp7.Settings; + + public class SettingsFileCodeFixProviderCSharp8UnitTests : SettingsFileCodeFixProviderCSharp7UnitTests + { + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/Settings/SettingsCSharp9UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/Settings/SettingsCSharp9UnitTests.cs new file mode 100644 index 000000000..63e1214b5 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/Settings/SettingsCSharp9UnitTests.cs @@ -0,0 +1,45 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp9.Settings +{ + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.Diagnostics; + using StyleCop.Analyzers.Test.CSharp8.Settings; + + public class SettingsCSharp9UnitTests : SettingsCSharp8UnitTests + { + protected override AnalyzerConfigOptionsProvider CreateAnalyzerConfigOptionsProvider(AnalyzerConfigSet analyzerConfigSet) + { + return new TestAnalyzerConfigOptionsProvider(analyzerConfigSet); + } + + private sealed class TestAnalyzerConfigOptionsProvider : AnalyzerConfigOptionsProvider + { + private readonly AnalyzerConfigSet analyzerConfigSet; + + public TestAnalyzerConfigOptionsProvider(AnalyzerConfigSet analyzerConfigSet) + { + this.analyzerConfigSet = analyzerConfigSet; + } + + public override AnalyzerConfigOptions GlobalOptions + { + get + { + return new TestAnalyzerConfigOptions(this.analyzerConfigSet.GlobalConfigOptions); + } + } + + public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) + { + return new TestAnalyzerConfigOptions(this.analyzerConfigSet.GetOptionsForSourcePath(tree.FilePath)); + } + + public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) + { + return new TestAnalyzerConfigOptions(this.analyzerConfigSet.GetOptionsForSourcePath(textFile.Path)); + } + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/Settings/SettingsFileCodeFixProviderCSharp9UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/Settings/SettingsFileCodeFixProviderCSharp9UnitTests.cs new file mode 100644 index 000000000..55d2ecc8e --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/Settings/SettingsFileCodeFixProviderCSharp9UnitTests.cs @@ -0,0 +1,11 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp9.Settings +{ + using StyleCop.Analyzers.Test.CSharp8.Settings; + + public class SettingsFileCodeFixProviderCSharp9UnitTests : SettingsFileCodeFixProviderCSharp8UnitTests + { + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/HelperTests/IndentationHelperTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/HelperTests/IndentationHelperTests.cs index ff5632ce7..9a45c818e 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/HelperTests/IndentationHelperTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/HelperTests/IndentationHelperTests.cs @@ -112,9 +112,9 @@ public async Task VerifyGetIndentationStepsAsync(string indentationString, int e { var testSource = $"{indentationString}public class TestClass {{}}"; var document = await CreateTestDocumentAsync(testSource, indentationSize, false, tabSize, CancellationToken.None).ConfigureAwait(false); - StyleCopSettings settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, CancellationToken.None); + var syntaxRoot = await document.GetSyntaxRootAsync(CancellationToken.None).ConfigureAwait(false); + StyleCopSettings settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, CancellationToken.None); - var syntaxRoot = await document.GetSyntaxRootAsync().ConfigureAwait(false); var firstToken = syntaxRoot.GetFirstToken(); Assert.Equal(expectedIndentationSteps, IndentationHelper.GetIndentationSteps(settings.Indentation, firstToken)); @@ -130,9 +130,9 @@ public async Task VerifyGetIndentationStepsForTokenNotAtStartOfLineAsync() { var testSource = " public class TestClass {}"; var document = await CreateTestDocumentAsync(testSource, cancellationToken: CancellationToken.None).ConfigureAwait(false); - StyleCopSettings settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, CancellationToken.None); + var syntaxRoot = await document.GetSyntaxRootAsync(CancellationToken.None).ConfigureAwait(false); + StyleCopSettings settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, syntaxRoot.SyntaxTree, CancellationToken.None); - var syntaxRoot = await document.GetSyntaxRootAsync().ConfigureAwait(false); var secondToken = syntaxRoot.GetFirstToken().GetNextToken(); Assert.Equal(0, IndentationHelper.GetIndentationSteps(settings.Indentation, secondToken)); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Settings/SettingsUnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Settings/SettingsUnitTests.cs index 77eb222e5..f6fd17baf 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Settings/SettingsUnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Settings/SettingsUnitTests.cs @@ -171,25 +171,28 @@ public async Task VerifyDocumentationVariablesAsync() /// /// Verifies that the settings will use the read company name in the default copyright text. /// + /// The company name to test. /// A representing the asynchronous unit test. - [Fact] - public async Task VerifySettingsWillUseCompanyNameInDefaultCopyrightTextAsync() + [Theory] + [InlineData("TestCompany")] + [InlineData("TestCompany2")] + public async Task VerifySettingsWillUseCompanyNameInDefaultCopyrightTextAsync(string companyName) { - var settings = @" -{ - ""settings"": { - ""documentationRules"": { - ""companyName"": ""TestCompany"" - } - } -} + var settings = $@" +{{ + ""settings"": {{ + ""documentationRules"": {{ + ""companyName"": ""{companyName}"" + }} + }} +}} "; var context = await CreateAnalysisContextAsync(settings).ConfigureAwait(false); var styleCopSettings = context.GetStyleCopSettings(CancellationToken.None); - Assert.Equal("TestCompany", styleCopSettings.DocumentationRules.CompanyName); - Assert.Equal("Copyright (c) TestCompany. All rights reserved.", styleCopSettings.DocumentationRules.GetCopyrightText("unused")); + Assert.Equal(companyName, styleCopSettings.DocumentationRules.CompanyName); + Assert.Equal($"Copyright (c) {companyName}. All rights reserved.", styleCopSettings.DocumentationRules.GetCopyrightText("unused")); } [Fact] @@ -374,7 +377,7 @@ private static async Task CreateAnalysisContextAsync( var additionalFiles = ImmutableArray.Create(stylecopJSONFile); var analyzerOptions = new AnalyzerOptions(additionalFiles); - return new SyntaxTreeAnalysisContext(syntaxTree, analyzerOptions, rd => { }, isd => { return true; }, CancellationToken.None); + return new SyntaxTreeAnalysisContext(syntaxTree, analyzerOptions, reportDiagnostic: _ => { }, isSupportedDiagnostic: _ => true, CancellationToken.None); } private class AdditionalTextHelper : AdditionalText diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj b/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj index 70accc5c3..b93f65b2a 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj @@ -22,6 +22,7 @@ + diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/AnalyzerExtensions.cs b/StyleCop.Analyzers/StyleCop.Analyzers/AnalyzerExtensions.cs index 2273b3cc3..90a634d20 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/AnalyzerExtensions.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/AnalyzerExtensions.cs @@ -24,10 +24,10 @@ internal static class AnalyzerExtensions public static void RegisterSyntaxTreeAction(this AnalysisContext context, Action action) { context.RegisterSyntaxTreeAction( - c => + context => { - StyleCopSettings settings = context.GetStyleCopSettings(c.Options, c.CancellationToken); - action(c, settings); + StyleCopSettings settings = context.GetStyleCopSettings(context.CancellationToken); + action(context, settings); }); } @@ -40,10 +40,10 @@ public static void RegisterSyntaxTreeAction(this AnalysisContext context, Action public static void RegisterSyntaxTreeAction(this CompilationStartAnalysisContext context, Action action) { context.RegisterSyntaxTreeAction( - c => + context => { - StyleCopSettings settings = context.GetStyleCopSettings(c.Options, c.CancellationToken); - action(c, settings); + StyleCopSettings settings = context.GetStyleCopSettings(context.CancellationToken); + action(context, settings); }); } @@ -79,10 +79,10 @@ public static void RegisterSyntaxNodeAction(this AnalysisCont where TLanguageKindEnum : struct { context.RegisterSyntaxNodeAction( - c => + context => { - StyleCopSettings settings = context.GetStyleCopSettings(c.Options, c.CancellationToken); - action(c, settings); + StyleCopSettings settings = context.GetStyleCopSettings(context.CancellationToken); + action(context, settings); }, syntaxKinds); } @@ -119,10 +119,10 @@ public static void RegisterSyntaxNodeAction(this CompilationS where TLanguageKindEnum : struct { context.RegisterSyntaxNodeAction( - c => + context => { - StyleCopSettings settings = context.GetStyleCopSettings(c.Options, c.CancellationToken); - action(c, settings); + StyleCopSettings settings = context.GetStyleCopSettings(context.CancellationToken); + action(context, settings); }, syntaxKinds); } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/PropertySummaryDocumentationAnalyzer.cs b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/PropertySummaryDocumentationAnalyzer.cs index c3c021d05..80d134ff3 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/PropertySummaryDocumentationAnalyzer.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/PropertySummaryDocumentationAnalyzer.cs @@ -61,7 +61,7 @@ protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, bool { var propertyDeclaration = (PropertyDeclarationSyntax)context.Node; var propertyType = context.SemanticModel.GetTypeInfo(propertyDeclaration.Type.StripRefFromType()); - var settings = context.Options.GetStyleCopSettings(context.CancellationToken); + var settings = context.GetStyleCopSettings(context.CancellationToken); var culture = new CultureInfo(settings.DocumentationRules.DocumentationCulture); var resourceManager = DocumentationResources.ResourceManager; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1625ElementDocumentationMustNotBeCopiedAndPasted.cs b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1625ElementDocumentationMustNotBeCopiedAndPasted.cs index fd5db6d54..fb2046aff 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1625ElementDocumentationMustNotBeCopiedAndPasted.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1625ElementDocumentationMustNotBeCopiedAndPasted.cs @@ -119,7 +119,7 @@ protected override void HandleCompleteDocumentation(SyntaxNodeAnalysisContext co { var objectPool = SharedPools.Default>(); HashSet documentationTexts = objectPool.Allocate(); - var settings = context.Options.GetStyleCopSettings(context.CancellationToken); + var settings = context.GetStyleCopSettings(context.CancellationToken); var culture = new CultureInfo(settings.DocumentationRules.DocumentationCulture); var resourceManager = DocumentationResources.ResourceManager; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1642ConstructorSummaryDocumentationMustBeginWithStandardText.cs b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1642ConstructorSummaryDocumentationMustBeginWithStandardText.cs index 1f41767fc..fd6e8d9aa 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1642ConstructorSummaryDocumentationMustBeginWithStandardText.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1642ConstructorSummaryDocumentationMustBeginWithStandardText.cs @@ -124,7 +124,7 @@ private static void HandleConstructorDeclaration(SyntaxNodeAnalysisContext conte { var constructorDeclarationSyntax = (ConstructorDeclarationSyntax)context.Node; - var settings = context.Options.GetStyleCopSettings(context.CancellationToken); + var settings = context.GetStyleCopSettings(context.CancellationToken); var culture = new CultureInfo(settings.DocumentationRules.DocumentationCulture); var resourceManager = DocumentationResources.ResourceManager; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1643DestructorSummaryDocumentationMustBeginWithStandardText.cs b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1643DestructorSummaryDocumentationMustBeginWithStandardText.cs index 1a68ba222..c91e71077 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1643DestructorSummaryDocumentationMustBeginWithStandardText.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1643DestructorSummaryDocumentationMustBeginWithStandardText.cs @@ -77,7 +77,7 @@ public override void Initialize(AnalysisContext context) private static void HandleDestructor(SyntaxNodeAnalysisContext context) { - var settings = context.Options.GetStyleCopSettings(context.CancellationToken); + var settings = context.GetStyleCopSettings(context.CancellationToken); var culture = new CultureInfo(settings.DocumentationRules.DocumentationCulture); var resourceManager = DocumentationResources.ResourceManager; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/KeyValuePairExtensions.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/KeyValuePairExtensions.cs new file mode 100644 index 000000000..239bd7782 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/KeyValuePairExtensions.cs @@ -0,0 +1,16 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Helpers +{ + using System.Collections.Generic; + + internal static class KeyValuePairExtensions + { + public static void Deconstruct(this KeyValuePair pair, out TKey key, out TValue value) + { + key = pair.Key; + value = pair.Value; + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/AnalyzerConfigOptionsProviderWrapper.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/AnalyzerConfigOptionsProviderWrapper.cs new file mode 100644 index 000000000..5b1819682 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/AnalyzerConfigOptionsProviderWrapper.cs @@ -0,0 +1,101 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Lightup +{ + using System; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.Diagnostics; + + internal readonly struct AnalyzerConfigOptionsProviderWrapper + { + internal const string WrappedTypeName = "Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider"; + private static readonly Type WrappedType; + + private static readonly Func GlobalOptionsAccessor; + private static readonly Func GetOptionsSyntaxTreeAccessor; + private static readonly Func GetOptionsAdditionalTextAccessor; + + private readonly object node; + + static AnalyzerConfigOptionsProviderWrapper() + { + WrappedType = WrapperHelper.GetWrappedType(typeof(AnalyzerConfigOptionsProviderWrapper)); + + GlobalOptionsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, nameof(GlobalOptions)); + GetOptionsSyntaxTreeAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, typeof(SyntaxTree), nameof(GetOptions)); + GetOptionsAdditionalTextAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, typeof(AdditionalText), nameof(GetOptions)); + } + + private AnalyzerConfigOptionsProviderWrapper(object node) + { + this.node = node; + } + + public AnalyzerConfigOptionsWrapper GlobalOptions + { + get + { + if (this.node == null && WrappedType == null) + { + // Gracefully fall back to a collection with no values + return AnalyzerConfigOptionsWrapper.FromObject(null); + } + + return AnalyzerConfigOptionsWrapper.FromObject(GlobalOptionsAccessor(this.node)); + } + } + + public static AnalyzerConfigOptionsProviderWrapper FromObject(object node) + { + if (node == null) + { + return default; + } + + if (!IsInstance(node)) + { + throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); + } + + return new AnalyzerConfigOptionsProviderWrapper(node); + } + + public static bool IsInstance(object obj) + { + return obj != null && LightupHelpers.CanWrapObject(obj, WrappedType); + } + + public AnalyzerConfigOptionsWrapper GetOptions(SyntaxTree tree) + { + if (this.node == null && WrappedType == null) + { + // Gracefully fall back to a collection with no values + if (tree == null) + { + throw new ArgumentNullException(nameof(tree)); + } + + return AnalyzerConfigOptionsWrapper.FromObject(null); + } + + return AnalyzerConfigOptionsWrapper.FromObject(GetOptionsSyntaxTreeAccessor(this.node, tree)); + } + + public AnalyzerConfigOptionsWrapper GetOptions(AdditionalText textFile) + { + if (this.node == null && WrappedType == null) + { + // Gracefully fall back to a collection with no values + if (textFile == null) + { + throw new ArgumentNullException(nameof(textFile)); + } + + return AnalyzerConfigOptionsWrapper.FromObject(null); + } + + return AnalyzerConfigOptionsWrapper.FromObject(GetOptionsAdditionalTextAccessor(this.node, textFile)); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/AnalyzerConfigOptionsWrapper.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/AnalyzerConfigOptionsWrapper.cs new file mode 100644 index 000000000..7e22658ad --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/AnalyzerConfigOptionsWrapper.cs @@ -0,0 +1,77 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Lightup +{ + using System; + + internal readonly struct AnalyzerConfigOptionsWrapper + { + internal const string WrappedTypeName = "Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptions"; + private static readonly Type WrappedType; + + private static readonly Func KeyComparerAccessor; + private static readonly TryGetValueAccessor TryGetValueAccessor; + + private readonly object node; + + static AnalyzerConfigOptionsWrapper() + { + WrappedType = WrapperHelper.GetWrappedType(typeof(AnalyzerConfigOptionsWrapper)); + + KeyComparerAccessor = LightupHelpers.CreateStaticPropertyAccessor(WrappedType, nameof(KeyComparer)); + TryGetValueAccessor = LightupHelpers.CreateTryGetValueAccessor(WrappedType, typeof(string), nameof(TryGetValue)); + } + + private AnalyzerConfigOptionsWrapper(object node) + { + this.node = node; + } + + public static StringComparer KeyComparer + { + get + { + if (WrappedType is null) + { + // Gracefully fall back to a collection with no values + return StringComparer.Ordinal; + } + + return KeyComparerAccessor(); + } + } + + public static AnalyzerConfigOptionsWrapper FromObject(object node) + { + if (node == null) + { + return default; + } + + if (!IsInstance(node)) + { + throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); + } + + return new AnalyzerConfigOptionsWrapper(node); + } + + public static bool IsInstance(object obj) + { + return obj != null && LightupHelpers.CanWrapObject(obj, WrappedType); + } + + public bool TryGetValue(string key, out string value) + { + if (this.node is null && WrappedType is null) + { + // Gracefully fall back to a collection with no values + value = null; + return false; + } + + return TryGetValueAccessor(this.node, key, out value); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/AnalyzerOptionsExtensions.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/AnalyzerOptionsExtensions.cs new file mode 100644 index 000000000..9b7941c8b --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/AnalyzerOptionsExtensions.cs @@ -0,0 +1,23 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Lightup +{ + using System; + using Microsoft.CodeAnalysis.Diagnostics; + + internal static class AnalyzerOptionsExtensions + { + private static readonly Func AnalyzerConfigOptionsProviderAccessor; + + static AnalyzerOptionsExtensions() + { + AnalyzerConfigOptionsProviderAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(AnalyzerOptions), nameof(AnalyzerConfigOptionsProvider)); + } + + public static AnalyzerConfigOptionsProviderWrapper AnalyzerConfigOptionsProvider(this AnalyzerOptions options) + { + return AnalyzerConfigOptionsProviderWrapper.FromObject(AnalyzerConfigOptionsProviderAccessor(options)); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/LightupHelpers.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/LightupHelpers.cs index a2bfc224b..2b5b9a42b 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/LightupHelpers.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/LightupHelpers.cs @@ -14,7 +14,10 @@ namespace StyleCop.Analyzers.Lightup internal static class LightupHelpers { - private static readonly ConcurrentDictionary> SupportedWrappers + private static readonly ConcurrentDictionary> SupportedObjectWrappers + = new ConcurrentDictionary>(); + + private static readonly ConcurrentDictionary> SupportedSyntaxWrappers = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> SupportedOperationWrappers @@ -40,6 +43,34 @@ private static readonly ConcurrentDictionary SupportsCSharp73; + internal static bool CanWrapObject(object obj, Type underlyingType) + { + if (obj == null) + { + // The wrappers support a null instance + return true; + } + + if (underlyingType == null) + { + // The current runtime doesn't define the target type of the conversion, so no instance of it can exist + return false; + } + + ConcurrentDictionary wrappedObject = SupportedObjectWrappers.GetOrAdd(underlyingType, _ => new ConcurrentDictionary()); + + // Avoid creating the delegate if the value already exists + bool canCast; + if (!wrappedObject.TryGetValue(obj.GetType(), out canCast)) + { + canCast = wrappedObject.GetOrAdd( + obj.GetType(), + kind => underlyingType.GetTypeInfo().IsAssignableFrom(obj.GetType().GetTypeInfo())); + } + + return canCast; + } + internal static bool CanWrapNode(SyntaxNode node, Type underlyingType) { if (node == null) @@ -54,7 +85,7 @@ internal static bool CanWrapNode(SyntaxNode node, Type underlyingType) return false; } - ConcurrentDictionary wrappedSyntax = SupportedWrappers.GetOrAdd(underlyingType, _ => new ConcurrentDictionary()); + ConcurrentDictionary wrappedSyntax = SupportedSyntaxWrappers.GetOrAdd(underlyingType, _ => new ConcurrentDictionary()); // Avoid creating the delegate if the value already exists bool canCast; @@ -203,6 +234,35 @@ ImmutableArray FallbackAccessor(TOperation syntax) return expression.Compile(); } + internal static Func CreateStaticPropertyAccessor(Type type, string propertyName) + { + static TProperty FallbackAccessor() + { + return default; + } + + if (type == null) + { + return FallbackAccessor; + } + + var property = type.GetTypeInfo().GetDeclaredProperty(propertyName); + if (property == null) + { + return FallbackAccessor; + } + + if (!typeof(TProperty).GetTypeInfo().IsAssignableFrom(property.PropertyType.GetTypeInfo())) + { + throw new InvalidOperationException(); + } + + Expression> expression = + Expression.Lambda>( + Expression.Call(null, property.GetMethod)); + return expression.Compile(); + } + internal static Func CreateSyntaxPropertyAccessor(Type type, string propertyName) { TProperty FallbackAccessor(TSyntax syntax) @@ -251,6 +311,160 @@ TProperty FallbackAccessor(TSyntax syntax) return expression.Compile(); } + internal static Func CreateSyntaxPropertyAccessor(Type type, Type argumentType, string accessorMethodName) + { + static TProperty FallbackAccessor(TSyntax syntax, TArg argument) + { + if (syntax == null) + { + // Unlike an extension method which would throw ArgumentNullException here, the light-up + // behavior needs to match behavior of the underlying property. + throw new NullReferenceException(); + } + + return default; + } + + if (type == null) + { + return FallbackAccessor; + } + + if (!typeof(TSyntax).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) + { + throw new InvalidOperationException(); + } + + if (!typeof(TArg).GetTypeInfo().IsAssignableFrom(argumentType.GetTypeInfo())) + { + throw new InvalidOperationException(); + } + + var methods = type.GetTypeInfo().GetDeclaredMethods(accessorMethodName); + MethodInfo method = null; + foreach (var candidate in methods) + { + var parameters = candidate.GetParameters(); + if (parameters.Length != 1) + { + continue; + } + + if (Equals(argumentType, parameters[0].ParameterType)) + { + method = candidate; + break; + } + } + + if (method == null) + { + return FallbackAccessor; + } + + if (!typeof(TProperty).GetTypeInfo().IsAssignableFrom(method.ReturnType.GetTypeInfo())) + { + throw new InvalidOperationException(); + } + + var syntaxParameter = Expression.Parameter(typeof(TSyntax), "syntax"); + var argParameter = Expression.Parameter(typeof(TArg), "arg"); + Expression instance = + type.GetTypeInfo().IsAssignableFrom(typeof(TSyntax).GetTypeInfo()) + ? (Expression)syntaxParameter + : Expression.Convert(syntaxParameter, type); + Expression argument = + argumentType.GetTypeInfo().IsAssignableFrom(typeof(TArg).GetTypeInfo()) + ? (Expression)argParameter + : Expression.Convert(argParameter, argumentType); + + Expression> expression = + Expression.Lambda>( + Expression.Call(instance, method, argument), + syntaxParameter, + argParameter); + return expression.Compile(); + } + + internal static TryGetValueAccessor CreateTryGetValueAccessor(Type type, Type keyType, string methodName) + { + static bool FallbackAccessor(TSyntax syntax, TKey key, out TValue value) + { + if (syntax == null) + { + // Unlike an extension method which would throw ArgumentNullException here, the light-up + // behavior needs to match behavior of the underlying property. + throw new NullReferenceException(); + } + + value = default; + return false; + } + + if (type == null) + { + return FallbackAccessor; + } + + if (!typeof(TSyntax).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) + { + throw new InvalidOperationException(); + } + + if (!typeof(TKey).GetTypeInfo().IsAssignableFrom(keyType.GetTypeInfo())) + { + throw new InvalidOperationException(); + } + + var methods = type.GetTypeInfo().GetDeclaredMethods(methodName); + MethodInfo method = null; + foreach (var candidate in methods) + { + var parameters = candidate.GetParameters(); + if (parameters.Length != 2) + { + continue; + } + + if (Equals(keyType, parameters[0].ParameterType) + && Equals(typeof(TValue).MakeByRefType(), parameters[1].ParameterType)) + { + method = candidate; + break; + } + } + + if (method == null) + { + return FallbackAccessor; + } + + if (method.ReturnType != typeof(bool)) + { + throw new InvalidOperationException(); + } + + var syntaxParameter = Expression.Parameter(typeof(TSyntax), "syntax"); + var keyParameter = Expression.Parameter(typeof(TKey), "key"); + var valueParameter = Expression.Parameter(typeof(TValue).MakeByRefType(), "value"); + Expression instance = + type.GetTypeInfo().IsAssignableFrom(typeof(TSyntax).GetTypeInfo()) + ? (Expression)syntaxParameter + : Expression.Convert(syntaxParameter, type); + Expression key = + keyType.GetTypeInfo().IsAssignableFrom(typeof(TKey).GetTypeInfo()) + ? (Expression)keyParameter + : Expression.Convert(keyParameter, keyType); + + Expression> expression = + Expression.Lambda>( + Expression.Call(instance, method, key, valueParameter), + syntaxParameter, + keyParameter, + valueParameter); + return expression.Compile(); + } + internal static Func> CreateSeparatedSyntaxListPropertyAccessor(Type type, string propertyName) { SeparatedSyntaxListWrapper FallbackAccessor(TSyntax syntax) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/TryGetValueAccessor`3.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/TryGetValueAccessor`3.cs new file mode 100644 index 000000000..72f025485 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/TryGetValueAccessor`3.cs @@ -0,0 +1,7 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Lightup +{ + internal delegate bool TryGetValueAccessor(T instance, TKey key, out TValue value); +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/WrapperHelper.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/WrapperHelper.cs new file mode 100644 index 000000000..8c2cdd216 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/WrapperHelper.cs @@ -0,0 +1,42 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// 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.Reflection; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp; + + internal static class WrapperHelper + { + private static readonly ImmutableDictionary WrappedTypes; + + static WrapperHelper() + { + var codeAnalysisAssembly = typeof(SyntaxNode).GetTypeInfo().Assembly; + var builder = ImmutableDictionary.CreateBuilder(); + + builder.Add(typeof(AnalyzerConfigOptionsProviderWrapper), codeAnalysisAssembly.GetType(AnalyzerConfigOptionsProviderWrapper.WrappedTypeName)); + builder.Add(typeof(AnalyzerConfigOptionsWrapper), codeAnalysisAssembly.GetType(AnalyzerConfigOptionsWrapper.WrappedTypeName)); + + WrappedTypes = builder.ToImmutable(); + } + + /// + /// Gets the type that is wrapped by the given wrapper. + /// + /// Type of the wrapper for which the wrapped type should be retrieved. + /// The wrapped type, or null if there is no info. + internal static Type GetWrappedType(Type wrapperType) + { + if (WrappedTypes.TryGetValue(wrapperType, out Type wrappedType)) + { + return wrappedType; + } + + return null; + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1316TupleElementNamesShouldUseCorrectCasing.cs b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1316TupleElementNamesShouldUseCorrectCasing.cs index cae10cb9e..9aa0c7568 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1316TupleElementNamesShouldUseCorrectCasing.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1316TupleElementNamesShouldUseCorrectCasing.cs @@ -59,7 +59,7 @@ private static void HandleTupleTypeAction(SyntaxNodeAnalysisContext context) return; } - var settings = context.Options.GetStyleCopSettings(context.CancellationToken); + var settings = context.GetStyleCopSettings(context.CancellationToken); var tupleType = (TupleTypeSyntaxWrapper)context.Node; foreach (var tupleElement in tupleType.Elements) @@ -75,7 +75,7 @@ private static void HandleTupleExpressionAction(SyntaxNodeAnalysisContext contex return; } - var settings = context.Options.GetStyleCopSettings(context.CancellationToken); + var settings = context.GetStyleCopSettings(context.CancellationToken); if (!settings.NamingRules.IncludeInferredTupleElementNames) { return; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/AnalyzerConfigHelper.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/AnalyzerConfigHelper.cs new file mode 100644 index 000000000..832806616 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/AnalyzerConfigHelper.cs @@ -0,0 +1,86 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Settings.ObjectModel +{ + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Linq; + using StyleCop.Analyzers.Lightup; + + internal static class AnalyzerConfigHelper + { + internal static bool? TryGetBooleanValue(AnalyzerConfigOptionsWrapper analyzerConfigOptions, string key) + { + if (analyzerConfigOptions.TryGetValue(key, out var value) + && value != "unset" + && bool.TryParse(value, out var boolValue)) + { + return boolValue; + } + + return null; + } + + internal static int? TryGetInt32Value(AnalyzerConfigOptionsWrapper analyzerConfigOptions, string key) + { + if (analyzerConfigOptions.TryGetValue(key, out var value) + && value != "unset" + && int.TryParse(value, out var intValue)) + { + return intValue; + } + + return null; + } + + internal static string TryGetStringValue(AnalyzerConfigOptionsWrapper analyzerConfigOptions, string key, bool allowExplicitUnset = true) + { + if (analyzerConfigOptions.TryGetValue(key, out var value)) + { + if (allowExplicitUnset && value == "unset") + { + return null; + } + + return value; + } + + return null; + } + + internal static KeyValuePair? TryGetStringValueAndNotification(AnalyzerConfigOptionsWrapper analyzerConfigOptions, string key, bool allowExplicitUnset = true) + { + if (analyzerConfigOptions.TryGetValue(key, out var value)) + { + if (allowExplicitUnset && value == "unset") + { + return null; + } + + var colonIndex = value.IndexOf(':'); + if (colonIndex >= 0) + { + return new KeyValuePair(value.Substring(0, colonIndex), value.Substring(colonIndex + 1)); + } + } + + return null; + } + + internal static ImmutableArray? TryGetStringListValue(AnalyzerConfigOptionsWrapper analyzerConfigOptions, string key, bool allowExplicitUnset = true) + { + if (analyzerConfigOptions.TryGetValue(key, out var value)) + { + if (allowExplicitUnset && value == "unset") + { + return null; + } + + return value.Split(',').Select(static x => x.Trim()).ToImmutableArray(); + } + + return null; + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/DocumentationSettings.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/DocumentationSettings.cs index a3f6c0741..8d3d9603f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/DocumentationSettings.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/DocumentationSettings.cs @@ -7,6 +7,7 @@ namespace StyleCop.Analyzers.Settings.ObjectModel using System.Collections.Immutable; using System.Text.RegularExpressions; using LightJson; + using StyleCop.Analyzers.Lightup; internal class DocumentationSettings { @@ -48,7 +49,7 @@ internal class DocumentationSettings /// /// This is the backing field for the property. /// - private readonly ImmutableDictionary.Builder variables; + private readonly ImmutableDictionary variables; /// /// This is the backing field for the property. @@ -108,7 +109,7 @@ protected internal DocumentationSettings() this.companyName = DefaultCompanyName; this.copyrightText = DefaultCopyrightText; this.headerDecoration = null; - this.variables = ImmutableDictionary.Empty.ToBuilder(); + this.variables = ImmutableDictionary.Empty; this.xmlHeader = true; this.documentExposedElements = true; @@ -128,46 +129,63 @@ protected internal DocumentationSettings() /// Initializes a new instance of the class. /// /// The JSON object containing the settings. - protected internal DocumentationSettings(JsonObject documentationSettingsObject) - : this() + /// The .editorconfig options to use if + /// stylecop.json does not provide values. + protected internal DocumentationSettings(JsonObject documentationSettingsObject, AnalyzerConfigOptionsWrapper analyzerConfigOptions) { + bool? documentExposedElements = null; + bool? documentInternalElements = null; + bool? documentPrivateElements = null; + bool? documentInterfaces = null; + bool? documentPrivateFields = null; + string companyName = null; + string copyrightText = null; + string headerDecoration = null; + ImmutableDictionary.Builder variables = null; + bool? xmlHeader = null; + FileNamingConvention? fileNamingConvention = null; + string documentationCulture = null; + ImmutableArray.Builder excludeFromPunctuationCheck = null; + foreach (var kvp in documentationSettingsObject) { switch (kvp.Key) { case "documentExposedElements": - this.documentExposedElements = kvp.ToBooleanValue(); + documentExposedElements = kvp.ToBooleanValue(); break; case "documentInternalElements": - this.documentInternalElements = kvp.ToBooleanValue(); + documentInternalElements = kvp.ToBooleanValue(); break; case "documentPrivateElements": - this.documentPrivateElements = kvp.ToBooleanValue(); + documentPrivateElements = kvp.ToBooleanValue(); break; case "documentInterfaces": - this.documentInterfaces = kvp.ToBooleanValue(); + documentInterfaces = kvp.ToBooleanValue(); break; case "documentPrivateFields": - this.documentPrivateFields = kvp.ToBooleanValue(); + documentPrivateFields = kvp.ToBooleanValue(); break; case "companyName": - this.companyName = kvp.ToStringValue(); + companyName = kvp.ToStringValue(); break; + case "copyrightText": - this.copyrightText = kvp.ToStringValue(); + copyrightText = kvp.ToStringValue(); break; case "headerDecoration": - this.headerDecoration = kvp.ToStringValue(); + headerDecoration = kvp.ToStringValue(); break; case "variables": kvp.AssertIsObject(); + variables = ImmutableDictionary.CreateBuilder(); foreach (var child in kvp.Value.AsJsonObject) { string name = child.Key; @@ -179,38 +197,73 @@ protected internal DocumentationSettings(JsonObject documentationSettingsObject) string value = child.ToStringValue(); - this.variables.Add(name, value); + variables.Add(name, value); } break; case "xmlHeader": - this.xmlHeader = kvp.ToBooleanValue(); + xmlHeader = kvp.ToBooleanValue(); break; case "fileNamingConvention": - this.fileNamingConvention = kvp.ToEnumValue(); + fileNamingConvention = kvp.ToEnumValue(); break; case "documentationCulture": - this.documentationCulture = kvp.ToStringValue(); + documentationCulture = kvp.ToStringValue(); break; case "excludeFromPunctuationCheck": kvp.AssertIsArray(); - var excludedTags = ImmutableArray.CreateBuilder(); + excludeFromPunctuationCheck = ImmutableArray.CreateBuilder(); foreach (var value in kvp.Value.AsJsonArray) { - excludedTags.Add(value.AsString); + excludeFromPunctuationCheck.Add(value.AsString); } - this.excludeFromPunctuationCheck = excludedTags.ToImmutable(); break; default: break; } } + + documentExposedElements ??= AnalyzerConfigHelper.TryGetBooleanValue(analyzerConfigOptions, "stylecop.documentation.documentExposedElements"); + documentInternalElements ??= AnalyzerConfigHelper.TryGetBooleanValue(analyzerConfigOptions, "stylecop.documentation.documentInternalElements"); + documentPrivateElements ??= AnalyzerConfigHelper.TryGetBooleanValue(analyzerConfigOptions, "stylecop.documentation.documentPrivateElements"); + documentInterfaces ??= AnalyzerConfigHelper.TryGetBooleanValue(analyzerConfigOptions, "stylecop.documentation.documentInterfaces"); + documentPrivateFields ??= AnalyzerConfigHelper.TryGetBooleanValue(analyzerConfigOptions, "stylecop.documentation.documentPrivateFields"); + + companyName ??= AnalyzerConfigHelper.TryGetStringValue(analyzerConfigOptions, "stylecop.documentation.companyName"); + copyrightText ??= AnalyzerConfigHelper.TryGetStringValue(analyzerConfigOptions, "stylecop.documentation.copyrightText") + ?? AnalyzerConfigHelper.TryGetStringValue(analyzerConfigOptions, "file_header_template"); + headerDecoration ??= AnalyzerConfigHelper.TryGetStringValue(analyzerConfigOptions, "stylecop.documentation.headerDecoration"); + + xmlHeader ??= AnalyzerConfigHelper.TryGetBooleanValue(analyzerConfigOptions, "stylecop.documentation.xmlHeader"); + fileNamingConvention ??= AnalyzerConfigHelper.TryGetStringValue(analyzerConfigOptions, "stylecop.documentation.fileNamingConvention") switch + { + "stylecop" => FileNamingConvention.StyleCop, + "metadata" => FileNamingConvention.Metadata, + _ => null, + }; + + documentationCulture ??= AnalyzerConfigHelper.TryGetStringValue(analyzerConfigOptions, "stylecop.documentation.documentationCulture"); + excludeFromPunctuationCheck ??= AnalyzerConfigHelper.TryGetStringListValue(analyzerConfigOptions, "stylecop.documentation.excludeFromPunctuationCheck")?.ToBuilder(); + + this.documentExposedElements = documentExposedElements.GetValueOrDefault(true); + this.documentInternalElements = documentInternalElements.GetValueOrDefault(true); + this.documentPrivateElements = documentPrivateElements.GetValueOrDefault(false); + this.documentInterfaces = documentInterfaces.GetValueOrDefault(true); + this.documentPrivateFields = documentPrivateFields.GetValueOrDefault(false); + this.companyName = companyName ?? DefaultCompanyName; + this.copyrightText = copyrightText ?? DefaultCopyrightText; + this.headerDecoration = headerDecoration; + this.variables = variables?.ToImmutable() ?? ImmutableDictionary.Empty; + this.xmlHeader = xmlHeader.GetValueOrDefault(true); + this.fileNamingConvention = fileNamingConvention.GetValueOrDefault(FileNamingConvention.StyleCop); + this.documentationCulture = documentationCulture ?? DefaultDocumentationCulture; + this.excludeFromPunctuationCheck = excludeFromPunctuationCheck?.ToImmutable() ?? DefaultExcludeFromPunctuationCheck; } public string CompanyName @@ -233,7 +286,7 @@ public ImmutableDictionary Variables { get { - return this.variables.ToImmutable(); + return this.variables; } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/IndentationSettings.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/IndentationSettings.cs index 6bbbdddd9..7561bed18 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/IndentationSettings.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/IndentationSettings.cs @@ -4,6 +4,7 @@ namespace StyleCop.Analyzers.Settings.ObjectModel { using LightJson; + using StyleCop.Analyzers.Lightup; internal class IndentationSettings { @@ -36,29 +37,47 @@ protected internal IndentationSettings() /// Initializes a new instance of the class. /// /// The JSON object containing the settings. - protected internal IndentationSettings(JsonObject indentationSettingsObject) - : this() + /// The .editorconfig options to use if + /// stylecop.json does not provide values. + protected internal IndentationSettings(JsonObject indentationSettingsObject, AnalyzerConfigOptionsWrapper analyzerConfigOptions) { + int? indentationSize = null; + int? tabSize = null; + bool? useTabs = null; + foreach (var kvp in indentationSettingsObject) { switch (kvp.Key) { case "indentationSize": - this.indentationSize = kvp.ToInt32Value(); + indentationSize = kvp.ToInt32Value(); break; case "tabSize": - this.tabSize = kvp.ToInt32Value(); + tabSize = kvp.ToInt32Value(); break; case "useTabs": - this.useTabs = kvp.ToBooleanValue(); + useTabs = kvp.ToBooleanValue(); break; default: break; } } + + indentationSize ??= AnalyzerConfigHelper.TryGetInt32Value(analyzerConfigOptions, "indent_size"); + tabSize ??= AnalyzerConfigHelper.TryGetInt32Value(analyzerConfigOptions, "tab_width"); + useTabs ??= AnalyzerConfigHelper.TryGetStringValue(analyzerConfigOptions, "indent_style") switch + { + "tab" => true, + "space" => false, + _ => null, + }; + + this.indentationSize = indentationSize.GetValueOrDefault(4); + this.tabSize = tabSize.GetValueOrDefault(4); + this.useTabs = useTabs.GetValueOrDefault(false); } public int IndentationSize => diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/LayoutSettings.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/LayoutSettings.cs index 85f3e0fe9..fcec1e45b 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/LayoutSettings.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/LayoutSettings.cs @@ -4,6 +4,7 @@ namespace StyleCop.Analyzers.Settings.ObjectModel { using LightJson; + using StyleCop.Analyzers.Lightup; internal class LayoutSettings { @@ -30,25 +31,41 @@ protected internal LayoutSettings() /// Initializes a new instance of the class. /// /// The JSON object containing the settings. - protected internal LayoutSettings(JsonObject layoutSettingsObject) - : this() + /// The .editorconfig options to use if + /// stylecop.json does not provide values. + protected internal LayoutSettings(JsonObject layoutSettingsObject, AnalyzerConfigOptionsWrapper analyzerConfigOptions) { + OptionSetting? newlineAtEndOfFile = null; + bool? allowConsecutiveUsings = null; + foreach (var kvp in layoutSettingsObject) { switch (kvp.Key) { case "newlineAtEndOfFile": - this.newlineAtEndOfFile = kvp.ToEnumValue(); + newlineAtEndOfFile = kvp.ToEnumValue(); break; case "allowConsecutiveUsings": - this.allowConsecutiveUsings = kvp.ToBooleanValue(); + allowConsecutiveUsings = kvp.ToBooleanValue(); break; default: break; } } + + newlineAtEndOfFile ??= AnalyzerConfigHelper.TryGetBooleanValue(analyzerConfigOptions, "insert_final_newline") switch + { + true => OptionSetting.Require, + false => OptionSetting.Omit, + _ => null, + }; + + allowConsecutiveUsings ??= AnalyzerConfigHelper.TryGetBooleanValue(analyzerConfigOptions, "stylecop.layout.allowConsecutiveUsings"); + + this.newlineAtEndOfFile = newlineAtEndOfFile.GetValueOrDefault(OptionSetting.Allow); + this.allowConsecutiveUsings = allowConsecutiveUsings.GetValueOrDefault(true); } public OptionSetting NewlineAtEndOfFile => diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/MaintainabilitySettings.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/MaintainabilitySettings.cs index e50d514c3..37ec9b3d4 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/MaintainabilitySettings.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/MaintainabilitySettings.cs @@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.Settings.ObjectModel { using System.Collections.Immutable; using LightJson; + using StyleCop.Analyzers.Lightup; internal class MaintainabilitySettings { @@ -17,33 +18,37 @@ internal class MaintainabilitySettings /// /// This is the backing field for the property. /// - private readonly ImmutableArray.Builder topLevelTypes; + private readonly ImmutableArray topLevelTypes; /// /// Initializes a new instance of the class. /// protected internal MaintainabilitySettings() { - this.topLevelTypes = ImmutableArray.CreateBuilder(); + this.topLevelTypes = ImmutableArray.Empty; } /// /// Initializes a new instance of the class. /// /// The JSON object containing the settings. - protected internal MaintainabilitySettings(JsonObject maintainabilitySettingsObject) - : this() + /// The .editorconfig options to use if + /// stylecop.json does not provide values. + protected internal MaintainabilitySettings(JsonObject maintainabilitySettingsObject, AnalyzerConfigOptionsWrapper analyzerConfigOptions) { + ImmutableArray.Builder topLevelTypes = null; + foreach (var kvp in maintainabilitySettingsObject) { switch (kvp.Key) { case "topLevelTypes": kvp.AssertIsArray(); + topLevelTypes = ImmutableArray.CreateBuilder(); foreach (var value in kvp.Value.AsJsonArray) { var typeKind = value.ToEnumValue(kvp.Key); - this.topLevelTypes.Add(typeKind); + topLevelTypes.Add(typeKind); } break; @@ -52,13 +57,15 @@ protected internal MaintainabilitySettings(JsonObject maintainabilitySettingsObj break; } } + + this.topLevelTypes = topLevelTypes?.ToImmutable() ?? ImmutableArray.Empty; } public ImmutableArray TopLevelTypes { get { - return this.topLevelTypes.Count > 0 ? this.topLevelTypes.ToImmutable() : DefaultTopLevelTypes; + return this.topLevelTypes.Length > 0 ? this.topLevelTypes : DefaultTopLevelTypes; } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/NamingSettings.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/NamingSettings.cs index 9146fa42e..43d149932 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/NamingSettings.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/NamingSettings.cs @@ -7,27 +7,18 @@ namespace StyleCop.Analyzers.Settings.ObjectModel using System.Linq; using System.Text.RegularExpressions; using LightJson; + using StyleCop.Analyzers.Lightup; internal class NamingSettings { - /// - /// This is the backing field for the property. - /// - private readonly ImmutableArray.Builder allowedHungarianPrefixes; - - /// - /// This is the backing field for the property. - /// - private readonly ImmutableArray.Builder allowedNamespaceComponents; - /// /// Initializes a new instance of the class. /// protected internal NamingSettings() { this.AllowCommonHungarianPrefixes = true; - this.allowedHungarianPrefixes = ImmutableArray.CreateBuilder(); - this.allowedNamespaceComponents = ImmutableArray.CreateBuilder(); + this.AllowedHungarianPrefixes = ImmutableArray.Empty; + this.AllowedNamespaceComponents = ImmutableArray.Empty; this.IncludeInferredTupleElementNames = false; this.TupleElementNameCasing = TupleElementNameCase.PascalCase; @@ -37,19 +28,27 @@ protected internal NamingSettings() /// Initializes a new instance of the class. /// /// The JSON object containing the settings. - protected internal NamingSettings(JsonObject namingSettingsObject) - : this() + /// The .editorconfig options to use if + /// stylecop.json does not provide values. + protected internal NamingSettings(JsonObject namingSettingsObject, AnalyzerConfigOptionsWrapper analyzerConfigOptions) { + bool? allowCommonHungarianPrefixes = null; + ImmutableArray.Builder allowedHungarianPrefixes = null; + ImmutableArray.Builder allowedNamespaceComponents = null; + bool? includeInferredTupleElementNames = null; + TupleElementNameCase? tupleElementNameCasing = null; + foreach (var kvp in namingSettingsObject) { switch (kvp.Key) { case "allowCommonHungarianPrefixes": - this.AllowCommonHungarianPrefixes = kvp.ToBooleanValue(); + allowCommonHungarianPrefixes = kvp.ToBooleanValue(); break; case "allowedHungarianPrefixes": kvp.AssertIsArray(); + allowedHungarianPrefixes = ImmutableArray.CreateBuilder(); foreach (var prefixJsonValue in kvp.Value.AsJsonArray) { var prefix = prefixJsonValue.ToStringValue(kvp.Key); @@ -59,37 +58,57 @@ protected internal NamingSettings(JsonObject namingSettingsObject) continue; } - this.allowedHungarianPrefixes.Add(prefix); + allowedHungarianPrefixes.Add(prefix); } break; case "allowedNamespaceComponents": kvp.AssertIsArray(); - this.allowedNamespaceComponents.AddRange(kvp.Value.AsJsonArray.Select(x => x.ToStringValue(kvp.Key))); + allowedNamespaceComponents = ImmutableArray.CreateBuilder(); + allowedNamespaceComponents.AddRange(kvp.Value.AsJsonArray.Select(x => x.ToStringValue(kvp.Key))); break; case "includeInferredTupleElementNames": - this.IncludeInferredTupleElementNames = kvp.ToBooleanValue(); + includeInferredTupleElementNames = kvp.ToBooleanValue(); break; case "tupleElementNameCasing": - this.TupleElementNameCasing = kvp.ToEnumValue(); + tupleElementNameCasing = kvp.ToEnumValue(); break; default: break; } } + + allowCommonHungarianPrefixes ??= AnalyzerConfigHelper.TryGetBooleanValue(analyzerConfigOptions, "stylecop.naming.allowCommonHungarianPrefixes"); + allowedHungarianPrefixes ??= AnalyzerConfigHelper.TryGetStringListValue(analyzerConfigOptions, "stylecop.naming.allowedHungarianPrefixes") + ?.Where(value => Regex.IsMatch(value, "^[a-z]{1,2}$")) + .ToImmutableArray() + .ToBuilder(); + allowedNamespaceComponents ??= AnalyzerConfigHelper.TryGetStringListValue(analyzerConfigOptions, "stylecop.naming.allowedNamespaceComponents")?.ToBuilder(); + includeInferredTupleElementNames ??= AnalyzerConfigHelper.TryGetBooleanValue(analyzerConfigOptions, "stylecop.naming.includeInferredTupleElementNames"); + tupleElementNameCasing ??= AnalyzerConfigHelper.TryGetStringValue(analyzerConfigOptions, "stylecop.naming.tupleElementNameCasing") switch + { + "camelCase" => TupleElementNameCase.CamelCase, + "pascalCase" => TupleElementNameCase.PascalCase, + _ => null, + }; + + this.AllowCommonHungarianPrefixes = allowCommonHungarianPrefixes.GetValueOrDefault(true); + this.AllowedHungarianPrefixes = allowedHungarianPrefixes?.ToImmutable() ?? ImmutableArray.Empty; + this.AllowedNamespaceComponents = allowedNamespaceComponents?.ToImmutable() ?? ImmutableArray.Empty; + + this.IncludeInferredTupleElementNames = includeInferredTupleElementNames.GetValueOrDefault(false); + this.TupleElementNameCasing = tupleElementNameCasing.GetValueOrDefault(TupleElementNameCase.PascalCase); } public bool AllowCommonHungarianPrefixes { get; } - public ImmutableArray AllowedHungarianPrefixes - => this.allowedHungarianPrefixes.ToImmutable(); + public ImmutableArray AllowedHungarianPrefixes { get; } - public ImmutableArray AllowedNamespaceComponents - => this.allowedNamespaceComponents.ToImmutable(); + public ImmutableArray AllowedNamespaceComponents { get; } public bool IncludeInferredTupleElementNames { get; } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/OrderingSettings.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/OrderingSettings.cs index e62dd4d3f..5852567e6 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/OrderingSettings.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/OrderingSettings.cs @@ -5,6 +5,8 @@ namespace StyleCop.Analyzers.Settings.ObjectModel { using System.Collections.Immutable; using LightJson; + using StyleCop.Analyzers.Helpers; + using StyleCop.Analyzers.Lightup; internal class OrderingSettings { @@ -19,7 +21,7 @@ internal class OrderingSettings /// /// This is the backing field for the property. /// - private readonly ImmutableArray.Builder elementOrder; + private readonly ImmutableArray elementOrder; /// /// This is the backing field for the property. @@ -41,7 +43,7 @@ internal class OrderingSettings /// protected internal OrderingSettings() { - this.elementOrder = ImmutableArray.CreateBuilder(); + this.elementOrder = ImmutableArray.Empty; this.systemUsingDirectivesFirst = true; this.usingDirectivesPlacement = UsingDirectivesPlacement.InsideNamespace; this.blankLinesBetweenUsingGroups = OptionSetting.Allow; @@ -51,45 +53,71 @@ protected internal OrderingSettings() /// Initializes a new instance of the class. /// /// The JSON object containing the settings. - protected internal OrderingSettings(JsonObject orderingSettingsObject) - : this() + /// The .editorconfig options to use if + /// stylecop.json does not provide values. + protected internal OrderingSettings(JsonObject orderingSettingsObject, AnalyzerConfigOptionsWrapper analyzerConfigOptions) { + ImmutableArray.Builder elementOrder = null; + bool? systemUsingDirectivesFirst = null; + UsingDirectivesPlacement? usingDirectivesPlacement = null; + OptionSetting? blankLinesBetweenUsingGroups = null; + foreach (var kvp in orderingSettingsObject) { switch (kvp.Key) { case "elementOrder": kvp.AssertIsArray(); + elementOrder = ImmutableArray.CreateBuilder(); foreach (var value in kvp.Value.AsJsonArray) { - this.elementOrder.Add(value.ToEnumValue(kvp.Key)); + elementOrder.Add(value.ToEnumValue(kvp.Key)); } break; case "systemUsingDirectivesFirst": - this.systemUsingDirectivesFirst = kvp.ToBooleanValue(); + systemUsingDirectivesFirst = kvp.ToBooleanValue(); break; case "usingDirectivesPlacement": - this.usingDirectivesPlacement = kvp.ToEnumValue(); + usingDirectivesPlacement = kvp.ToEnumValue(); break; case "blankLinesBetweenUsingGroups": - this.blankLinesBetweenUsingGroups = kvp.ToEnumValue(); + blankLinesBetweenUsingGroups = kvp.ToEnumValue(); break; default: break; } } + + systemUsingDirectivesFirst ??= AnalyzerConfigHelper.TryGetBooleanValue(analyzerConfigOptions, "dotnet_sort_system_directives_first"); + usingDirectivesPlacement ??= AnalyzerConfigHelper.TryGetStringValueAndNotification(analyzerConfigOptions, "csharp_using_directive_placement") switch + { + ("inside_namespace", _) => UsingDirectivesPlacement.InsideNamespace, + ("outside_namespace", _) => UsingDirectivesPlacement.OutsideNamespace, + _ => null, + }; + blankLinesBetweenUsingGroups ??= AnalyzerConfigHelper.TryGetBooleanValue(analyzerConfigOptions, "dotnet_separate_import_directive_groups") switch + { + true => OptionSetting.Require, + false => OptionSetting.Omit, + _ => null, + }; + + this.elementOrder = elementOrder?.ToImmutable() ?? ImmutableArray.Empty; + this.systemUsingDirectivesFirst = systemUsingDirectivesFirst.GetValueOrDefault(true); + this.usingDirectivesPlacement = usingDirectivesPlacement.GetValueOrDefault(UsingDirectivesPlacement.InsideNamespace); + this.blankLinesBetweenUsingGroups = blankLinesBetweenUsingGroups.GetValueOrDefault(OptionSetting.Allow); } public ImmutableArray ElementOrder { get { - return this.elementOrder.Count > 0 ? this.elementOrder.ToImmutable() : DefaultElementOrder; + return this.elementOrder.Length > 0 ? this.elementOrder : DefaultElementOrder; } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/ReadabilitySettings.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/ReadabilitySettings.cs index cae801875..d6a91a30b 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/ReadabilitySettings.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/ReadabilitySettings.cs @@ -4,6 +4,7 @@ namespace StyleCop.Analyzers.Settings.ObjectModel { using LightJson; + using StyleCop.Analyzers.Lightup; internal class ReadabilitySettings { @@ -24,21 +25,28 @@ protected internal ReadabilitySettings() /// Initializes a new instance of the class. /// /// The JSON object containing the settings. - protected internal ReadabilitySettings(JsonObject readabilitySettingsObject) - : this() + /// The .editorconfig options to use if + /// stylecop.json does not provide values. + protected internal ReadabilitySettings(JsonObject readabilitySettingsObject, AnalyzerConfigOptionsWrapper analyzerConfigOptions) { + bool? allowBuiltInTypeAliases = null; + foreach (var kvp in readabilitySettingsObject) { switch (kvp.Key) { case "allowBuiltInTypeAliases": - this.allowBuiltInTypeAliases = kvp.ToBooleanValue(); + allowBuiltInTypeAliases = kvp.ToBooleanValue(); break; default: break; } } + + allowBuiltInTypeAliases ??= AnalyzerConfigHelper.TryGetBooleanValue(analyzerConfigOptions, "stylecop.readability.allowBuiltInTypeAliases"); + + this.allowBuiltInTypeAliases = allowBuiltInTypeAliases.GetValueOrDefault(false); } public bool AllowBuiltInTypeAliases => diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/SpacingSettings.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/SpacingSettings.cs index 86a055075..47bf91bb9 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/SpacingSettings.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/SpacingSettings.cs @@ -4,6 +4,7 @@ namespace StyleCop.Analyzers.Settings.ObjectModel { using LightJson; + using StyleCop.Analyzers.Lightup; internal class SpacingSettings { @@ -18,11 +19,13 @@ protected internal SpacingSettings() /// Initializes a new instance of the class. /// /// The JSON object containing the settings. - protected internal SpacingSettings(JsonObject spacingSettingsObject) - : this() + /// The .editorconfig options to use if + /// stylecop.json does not provide values. + protected internal SpacingSettings(JsonObject spacingSettingsObject, AnalyzerConfigOptionsWrapper analyzerConfigOptions) { // Currently unused _ = spacingSettingsObject; + _ = analyzerConfigOptions; } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/StyleCopSettings.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/StyleCopSettings.cs index b133ab292..209cfe664 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/StyleCopSettings.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/StyleCopSettings.cs @@ -4,6 +4,7 @@ namespace StyleCop.Analyzers.Settings.ObjectModel { using LightJson; + using StyleCop.Analyzers.Lightup; internal class StyleCopSettings { @@ -63,8 +64,7 @@ protected internal StyleCopSettings() this.documentationRules = new DocumentationSettings(); } - protected internal StyleCopSettings(JsonObject settingsObject) - : this() + protected internal StyleCopSettings(JsonObject settingsObject, AnalyzerConfigOptionsWrapper analyzerConfigOptions) { foreach (var kvp in settingsObject) { @@ -73,48 +73,58 @@ protected internal StyleCopSettings(JsonObject settingsObject) { case "indentation": kvp.AssertIsObject(); - this.indentation = new IndentationSettings(childSettingsObject); + this.indentation = new IndentationSettings(childSettingsObject, analyzerConfigOptions); break; case "spacingRules": kvp.AssertIsObject(); - this.spacingRules = new SpacingSettings(childSettingsObject); + this.spacingRules = new SpacingSettings(childSettingsObject, analyzerConfigOptions); break; case "readabilityRules": kvp.AssertIsObject(); - this.readabilityRules = new ReadabilitySettings(childSettingsObject); + this.readabilityRules = new ReadabilitySettings(childSettingsObject, analyzerConfigOptions); break; case "orderingRules": kvp.AssertIsObject(); - this.orderingRules = new OrderingSettings(childSettingsObject); + this.orderingRules = new OrderingSettings(childSettingsObject, analyzerConfigOptions); break; case "namingRules": kvp.AssertIsObject(); - this.namingRules = new NamingSettings(childSettingsObject); + this.namingRules = new NamingSettings(childSettingsObject, analyzerConfigOptions); break; case "maintainabilityRules": kvp.AssertIsObject(); - this.maintainabilityRules = new MaintainabilitySettings(childSettingsObject); + this.maintainabilityRules = new MaintainabilitySettings(childSettingsObject, analyzerConfigOptions); break; case "layoutRules": kvp.AssertIsObject(); - this.layoutRules = new LayoutSettings(childSettingsObject); + this.layoutRules = new LayoutSettings(childSettingsObject, analyzerConfigOptions); break; case "documentationRules": kvp.AssertIsObject(); - this.documentationRules = new DocumentationSettings(childSettingsObject); + this.documentationRules = new DocumentationSettings(childSettingsObject, analyzerConfigOptions); break; default: break; } } + + this.indentation ??= new IndentationSettings(new JsonObject(), analyzerConfigOptions); + + this.spacingRules ??= new SpacingSettings(new JsonObject(), analyzerConfigOptions); + this.readabilityRules ??= new ReadabilitySettings(new JsonObject(), analyzerConfigOptions); + this.orderingRules ??= new OrderingSettings(new JsonObject(), analyzerConfigOptions); + this.namingRules ??= new NamingSettings(new JsonObject(), analyzerConfigOptions); + this.maintainabilityRules ??= new MaintainabilitySettings(new JsonObject(), analyzerConfigOptions); + this.layoutRules ??= new LayoutSettings(new JsonObject(), analyzerConfigOptions); + this.documentationRules ??= new DocumentationSettings(new JsonObject(), analyzerConfigOptions); } public IndentationSettings Indentation => diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/SettingsHelper.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/SettingsHelper.cs index c2c027d98..4a2484233 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/SettingsHelper.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/SettingsHelper.cs @@ -7,10 +7,12 @@ namespace StyleCop.Analyzers using System.Collections.Immutable; using System.IO; using System.Threading; + using LightJson; using LightJson.Serialization; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; + using StyleCop.Analyzers.Lightup; using StyleCop.Analyzers.Settings.ObjectModel; /// @@ -21,9 +23,9 @@ internal static class SettingsHelper internal const string SettingsFileName = "stylecop.json"; internal const string AltSettingsFileName = ".stylecop.json"; - private static readonly SourceTextValueProvider SettingsValueProvider = + private static SourceTextValueProvider SettingsValueProvider { get; } = new SourceTextValueProvider( - text => GetStyleCopSettings(SettingsFileName, text, DeserializationFailureBehavior.ReturnDefaultSettings)); + text => GetStyleCopSettings(options: null, tree: null, SettingsFileName, text, DeserializationFailureBehavior.ReturnDefaultSettings)); /// /// Gets the StyleCop settings. @@ -37,7 +39,22 @@ internal static class SettingsHelper /// A instance that represents the StyleCop settings for the given context. internal static StyleCopSettings GetStyleCopSettings(this SyntaxTreeAnalysisContext context, CancellationToken cancellationToken) { - return context.Options.GetStyleCopSettings(cancellationToken); + return GetStyleCopSettings(context.Options, context.Tree, cancellationToken); + } + + /// + /// Gets the StyleCop settings. + /// + /// + /// If a or occurs while + /// deserializing the settings file, a default settings instance is returned. + /// + /// The context that will be used to determine the StyleCop settings. + /// The cancellation token that the operation will observe. + /// A instance that represents the StyleCop settings for the given context. + internal static StyleCopSettings GetStyleCopSettings(this SyntaxNodeAnalysisContext context, CancellationToken cancellationToken) + { + return GetStyleCopSettings(context.Options, context.Node.SyntaxTree, cancellationToken); } /// @@ -48,24 +65,26 @@ internal static StyleCopSettings GetStyleCopSettings(this SyntaxTreeAnalysisCont /// deserializing the settings file, a default settings instance is returned. /// /// The analyzer options that will be used to determine the StyleCop settings. + /// The syntax tree. /// The cancellation token that the operation will observe. /// A instance that represents the StyleCop settings for the given context. - internal static StyleCopSettings GetStyleCopSettings(this AnalyzerOptions options, CancellationToken cancellationToken) + internal static StyleCopSettings GetStyleCopSettings(this AnalyzerOptions options, SyntaxTree tree, CancellationToken cancellationToken) { - return GetStyleCopSettings(options, DeserializationFailureBehavior.ReturnDefaultSettings, cancellationToken); + return GetStyleCopSettings(options, tree, DeserializationFailureBehavior.ReturnDefaultSettings, cancellationToken); } /// /// Gets the StyleCop settings. /// /// The analyzer options that will be used to determine the StyleCop settings. + /// The syntax tree. /// The behavior of the method when a or /// occurs while deserializing the settings file. /// The cancellation token that the operation will observe. /// A instance that represents the StyleCop settings for the given context. - internal static StyleCopSettings GetStyleCopSettings(this AnalyzerOptions options, DeserializationFailureBehavior failureBehavior, CancellationToken cancellationToken) + internal static StyleCopSettings GetStyleCopSettings(this AnalyzerOptions options, SyntaxTree tree, DeserializationFailureBehavior failureBehavior, CancellationToken cancellationToken) { - return GetStyleCopSettings(options != null ? options.AdditionalFiles : ImmutableArray.Create(), failureBehavior, cancellationToken); + return GetStyleCopSettings(options, tree, options != null ? options.AdditionalFiles : ImmutableArray.Create(), failureBehavior, cancellationToken); } /// @@ -87,66 +106,10 @@ internal static bool IsStyleCopSettingsFile(string path) || string.Equals(fileName, AltSettingsFileName, StringComparison.OrdinalIgnoreCase); } - internal static StyleCopSettings GetStyleCopSettings(this AnalysisContext context, AnalyzerOptions options, CancellationToken cancellationToken) - { - return GetStyleCopSettings(context, options, DeserializationFailureBehavior.ReturnDefaultSettings, cancellationToken); - } - - internal static StyleCopSettings GetStyleCopSettings(this AnalysisContext context, AnalyzerOptions options, DeserializationFailureBehavior failureBehavior, CancellationToken cancellationToken) - { - string settingsFilePath; - SourceText text = TryGetStyleCopSettingsText(options, cancellationToken, out settingsFilePath); - if (text == null) - { - return new StyleCopSettings(); - } - - if (failureBehavior == DeserializationFailureBehavior.ReturnDefaultSettings) - { - StyleCopSettings settings; - if (!context.TryGetValue(text, SettingsValueProvider, out settings)) - { - return new StyleCopSettings(); - } - - return settings; - } - - return GetStyleCopSettings(settingsFilePath, text, failureBehavior); - } - - internal static StyleCopSettings GetStyleCopSettings(this CompilationStartAnalysisContext context, AnalyzerOptions options, CancellationToken cancellationToken) - { - return GetStyleCopSettings(context, options, DeserializationFailureBehavior.ReturnDefaultSettings, cancellationToken); - } - -#pragma warning disable RS1012 // Start action has no registered actions. - internal static StyleCopSettings GetStyleCopSettings(this CompilationStartAnalysisContext context, AnalyzerOptions options, DeserializationFailureBehavior failureBehavior, CancellationToken cancellationToken) -#pragma warning restore RS1012 // Start action has no registered actions. + private static StyleCopSettings GetStyleCopSettings(AnalyzerOptions options, SyntaxTree tree, string path, SourceText text, DeserializationFailureBehavior failureBehavior) { - string settingsFilePath; - SourceText text = TryGetStyleCopSettingsText(options, cancellationToken, out settingsFilePath); - if (text == null) - { - return new StyleCopSettings(); - } - - if (failureBehavior == DeserializationFailureBehavior.ReturnDefaultSettings) - { - StyleCopSettings settings; - if (!context.TryGetValue(text, SettingsValueProvider, out settings)) - { - return new StyleCopSettings(); - } - - return settings; - } - - return GetStyleCopSettings(settingsFilePath, text, failureBehavior); - } + var optionsProvider = options.AnalyzerConfigOptionsProvider().GetOptions(tree); - private static StyleCopSettings GetStyleCopSettings(string path, SourceText text, DeserializationFailureBehavior failureBehavior) - { try { var rootValue = JsonReader.Parse(text.ToString()); @@ -161,7 +124,7 @@ private static StyleCopSettings GetStyleCopSettings(string path, SourceText text var settingsObject = rootValue.AsJsonObject["settings"]; if (settingsObject.IsJsonObject) { - return new StyleCopSettings(settingsObject.AsJsonObject); + return new StyleCopSettings(settingsObject.AsJsonObject, optionsProvider); } else if (settingsObject.IsNull) { @@ -182,32 +145,21 @@ private static StyleCopSettings GetStyleCopSettings(string path, SourceText text return new StyleCopSettings(); } - private static SourceText TryGetStyleCopSettingsText(this AnalyzerOptions options, CancellationToken cancellationToken, out string settingsFilePath) + private static StyleCopSettings GetStyleCopSettings(AnalyzerOptions options, SyntaxTree tree, ImmutableArray additionalFiles, DeserializationFailureBehavior failureBehavior, CancellationToken cancellationToken) { - foreach (var additionalFile in options.AdditionalFiles) + foreach (var additionalFile in additionalFiles) { if (IsStyleCopSettingsFile(additionalFile.Path)) { - settingsFilePath = additionalFile.Path; - - return additionalFile.GetText(cancellationToken); + SourceText additionalTextContent = additionalFile.GetText(cancellationToken); + return GetStyleCopSettings(options, tree, additionalFile.Path, additionalTextContent, failureBehavior); } } - settingsFilePath = null; - - return null; - } - - private static StyleCopSettings GetStyleCopSettings(ImmutableArray additionalFiles, DeserializationFailureBehavior failureBehavior, CancellationToken cancellationToken) - { - foreach (var additionalFile in additionalFiles) + if (tree != null) { - if (IsStyleCopSettingsFile(additionalFile.Path)) - { - SourceText additionalTextContent = additionalFile.GetText(cancellationToken); - return GetStyleCopSettings(additionalFile.Path, additionalTextContent, failureBehavior); - } + var optionsProvider = options.AnalyzerConfigOptionsProvider().GetOptions(tree); + return new StyleCopSettings(new JsonObject(), optionsProvider); } return new StyleCopSettings(); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/SpecialRules/SA0002InvalidSettingsFile.cs b/StyleCop.Analyzers/StyleCop.Analyzers/SpecialRules/SA0002InvalidSettingsFile.cs index 7a560257d..f1437fbaa 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/SpecialRules/SA0002InvalidSettingsFile.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/SpecialRules/SA0002InvalidSettingsFile.cs @@ -6,6 +6,7 @@ namespace StyleCop.Analyzers.SpecialRules using System; using System.Collections.Immutable; using System.Globalization; + using System.Linq; using LightJson.Serialization; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -50,7 +51,7 @@ private static void HandleCompilation(CompilationAnalysisContext context) { try { - SettingsHelper.GetStyleCopSettings(context.Options, DeserializationFailureBehavior.ThrowException, context.CancellationToken); + SettingsHelper.GetStyleCopSettings(context.Options, context.Compilation?.SyntaxTrees.FirstOrDefault(), DeserializationFailureBehavior.ThrowException, context.CancellationToken); } catch (Exception ex) when (ex is JsonParseException || ex is InvalidSettingsException) { From 4b609a0e540ab4163c7eb13c76f7a1253918a898 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Mon, 15 Mar 2021 13:55:01 +0200 Subject: [PATCH 03/16] Update SA1305 doc to mention allowedHungarianPrefixes in stylecop.json --- documentation/SA1305.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/documentation/SA1305.md b/documentation/SA1305.md index 69350b55f..2e079ae5a 100644 --- a/documentation/SA1305.md +++ b/documentation/SA1305.md @@ -29,9 +29,17 @@ In addition, modern code editors such as Visual Studio make it easy to identify StyleCop assumes that any variable name that begins with one or two lower-case letters followed by an upper-case letter is making use of Hungarian notation, and will flag a violation of this rule in each case. It is possible to declare certain prefixes as legal, in which case they will be ignored. For example, a variable named *onExecute* will appear to StyleCop to be using Hungarian notation, when in reality it is not. Thus, the *on* prefix should be flagged as an allowed prefix. -To configure the list of allowed prefixes, bring up the StyleCop settings for a project, and navigate to the Hungarian tab, as shown below: - -![](Images/HungarianSettings.JPG) +To configure the list of allowed prefixes, use *stylecop.json* like the following: + +```json +{ + "settings": { + "namingRules": { + "allowedHungarianPrefixes": [ "aa", "bb" ], + }, + } +} +``` Adding a one or two letter prefix to this list will cause StyleCop to ignore variables or fields which begin with this prefix. From d5c6fd228fbbf3d5ed1ebc4993f713cfd3372d80 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Mon, 15 Mar 2021 14:10:30 +0200 Subject: [PATCH 04/16] Update documentation/SA1305.md Co-authored-by: Sam Harwell --- documentation/SA1305.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/SA1305.md b/documentation/SA1305.md index 2e079ae5a..def384f0a 100644 --- a/documentation/SA1305.md +++ b/documentation/SA1305.md @@ -29,7 +29,7 @@ In addition, modern code editors such as Visual Studio make it easy to identify StyleCop assumes that any variable name that begins with one or two lower-case letters followed by an upper-case letter is making use of Hungarian notation, and will flag a violation of this rule in each case. It is possible to declare certain prefixes as legal, in which case they will be ignored. For example, a variable named *onExecute* will appear to StyleCop to be using Hungarian notation, when in reality it is not. Thus, the *on* prefix should be flagged as an allowed prefix. -To configure the list of allowed prefixes, use *stylecop.json* like the following: +To configure the list of allowed prefixes, use **stylecop.json** like the following: ```json { From b1c6db9041646dc638c61ca9f4bc5e1104d23649 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Wed, 17 Mar 2021 12:56:56 -0700 Subject: [PATCH 05/16] Implement a custom Fix All provider for SA1513 --- .../LayoutRules/SA1513CodeFixProvider.cs | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1513CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1513CodeFixProvider.cs index 48f9a4098..7351ed3a6 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1513CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1513CodeFixProvider.cs @@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.LayoutRules { using System.Collections.Immutable; using System.Composition; + using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -30,7 +31,7 @@ internal class SA1513CodeFixProvider : CodeFixProvider /// public override FixAllProvider GetFixAllProvider() { - return CustomFixAllProviders.BatchFixer; + return FixAll.Instance; } /// @@ -51,15 +52,31 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) private static async Task GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { - var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var token = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.End); + var newRoot = await GetTransformedDocumentAsync(document, ImmutableArray.Create(diagnostic), cancellationToken).ConfigureAwait(false); + return document.WithSyntaxRoot(newRoot); + } + + private static async Task GetTransformedDocumentAsync(Document document, ImmutableArray diagnostics, CancellationToken cancellationToken) + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + return root.ReplaceTokens( + diagnostics.Select(diagnostic => root.FindToken(diagnostic.Location.SourceSpan.End)), + (originalToken, rewrittenToken) => + { + var newTrivia = rewrittenToken.LeadingTrivia.Insert(0, SyntaxFactory.CarriageReturnLineFeed); + return rewrittenToken.WithLeadingTrivia(newTrivia); + }); + } + + private class FixAll : DocumentBasedFixAllProvider + { + public static FixAllProvider Instance { get; } = + new FixAll(); - var newTrivia = token.LeadingTrivia.Insert(0, SyntaxFactory.CarriageReturnLineFeed); - var newToken = token.WithLeadingTrivia(newTrivia); - var newSyntaxRoot = syntaxRoot.ReplaceToken(token, newToken); - var newDocument = document.WithSyntaxRoot(newSyntaxRoot); + protected override string CodeActionTitle => LayoutResources.SA1513CodeFix; - return newDocument; + protected override async Task FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray diagnostics) + => await GetTransformedDocumentAsync(document, diagnostics, fixAllContext.CancellationToken).ConfigureAwait(false); } } } From 38007355f0233ef7322ab9f4ddf1676c3b59d3dd Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Wed, 17 Mar 2021 13:16:08 -0700 Subject: [PATCH 06/16] Implement a custom Fix All provider for SA1514 --- .../LayoutRules/SA1514CodeFixProvider.cs | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1514CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1514CodeFixProvider.cs index 7c1200577..fc648de11 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1514CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1514CodeFixProvider.cs @@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.LayoutRules { using System.Collections.Immutable; using System.Composition; + using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -27,7 +28,7 @@ internal class SA1514CodeFixProvider : CodeFixProvider /// public override FixAllProvider GetFixAllProvider() { - return CustomFixAllProviders.BatchFixer; + return FixAll.Instance; } /// @@ -47,23 +48,44 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) } private static async Task GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) + { + var newRoot = await GetTransformedDocumentAsync(document, ImmutableArray.Create(diagnostic), cancellationToken).ConfigureAwait(false); + return document.WithSyntaxRoot(newRoot); + } + + private static async Task GetTransformedDocumentAsync(Document document, ImmutableArray diagnostics, CancellationToken cancellationToken) { var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var documentationHeader = syntaxRoot.FindTrivia(diagnostic.Location.SourceSpan.Start); - var triviaList = documentationHeader.Token.LeadingTrivia; - var documentationHeaderIndex = triviaList.IndexOf(documentationHeader); + var documentationHeaders = diagnostics.Select(diagnostic => syntaxRoot.FindTrivia(diagnostic.Location.SourceSpan.Start)).ToArray(); + return syntaxRoot.ReplaceTokens( + documentationHeaders.Select(header => header.Token), + (originalToken, rewrittenToken) => + { + var triviaList = rewrittenToken.LeadingTrivia; + var documentationHeaderIndex = originalToken.LeadingTrivia.IndexOf(originalToken.LeadingTrivia.First(documentationHeaders.Contains)); - // Keep any leading whitespace with the documentation header - var index = documentationHeaderIndex - 1; - while ((index >= 0) && triviaList[index].IsKind(SyntaxKind.WhitespaceTrivia)) - { - index--; - } + // Keep any leading whitespace with the documentation header + var index = documentationHeaderIndex - 1; + while ((index >= 0) && triviaList[index].IsKind(SyntaxKind.WhitespaceTrivia)) + { + index--; + } + + var newLeadingTrivia = rewrittenToken.LeadingTrivia.Insert(index + 1, SyntaxFactory.CarriageReturnLineFeed); + return rewrittenToken.WithLeadingTrivia(newLeadingTrivia); + }); + } + + private class FixAll : DocumentBasedFixAllProvider + { + public static FixAllProvider Instance { get; } = + new FixAll(); + + protected override string CodeActionTitle => LayoutResources.SA1514CodeFix; - var newLeadingTrivia = documentationHeader.Token.LeadingTrivia.Insert(index + 1, SyntaxFactory.CarriageReturnLineFeed); - var newSyntaxRoot = syntaxRoot.ReplaceToken(documentationHeader.Token, documentationHeader.Token.WithLeadingTrivia(newLeadingTrivia)); - return document.WithSyntaxRoot(newSyntaxRoot); + protected override async Task FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray diagnostics) + => await GetTransformedDocumentAsync(document, diagnostics, fixAllContext.CancellationToken).ConfigureAwait(false); } } } From d84d836b72298ef824d79f718d537702d62b3b3f Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Wed, 17 Mar 2021 13:22:08 -0700 Subject: [PATCH 07/16] Implement a custom Fix All provider for SA1511 --- .../LayoutRules/SA1511CodeFixProvider.cs | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1511CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1511CodeFixProvider.cs index 69eeaa939..09c451b0f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1511CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1511CodeFixProvider.cs @@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.LayoutRules { using System.Collections.Immutable; using System.Composition; + using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -26,7 +27,7 @@ internal class SA1511CodeFixProvider : CodeFixProvider /// public override FixAllProvider GetFixAllProvider() { - return CustomFixAllProviders.BatchFixer; + return FixAll.Instance; } /// @@ -46,13 +47,31 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) } private static async Task GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) + { + var newRoot = await GetTransformedDocumentAsync(document, ImmutableArray.Create(diagnostic), cancellationToken).ConfigureAwait(false); + return document.WithSyntaxRoot(newRoot); + } + + private static async Task GetTransformedDocumentAsync(Document document, ImmutableArray diagnostics, CancellationToken cancellationToken) { var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + return syntaxRoot.ReplaceTokens( + diagnostics.Select(diagnostic => syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start)), + (originalToken, rewrittenToken) => + { + return rewrittenToken.WithoutLeadingBlankLines(); + }); + } + + private class FixAll : DocumentBasedFixAllProvider + { + public static FixAllProvider Instance { get; } = + new FixAll(); - var whileToken = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start); + protected override string CodeActionTitle => LayoutResources.SA1511CodeFix; - var newSyntaxRoot = syntaxRoot.ReplaceToken(whileToken, whileToken.WithoutLeadingBlankLines()); - return document.WithSyntaxRoot(newSyntaxRoot); + protected override async Task FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray diagnostics) + => await GetTransformedDocumentAsync(document, diagnostics, fixAllContext.CancellationToken).ConfigureAwait(false); } } } From 92a0b27db95abba63c63099e742628b7b7d746fe Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Wed, 17 Mar 2021 13:25:18 -0700 Subject: [PATCH 08/16] Implement a custom Fix All provider for SA1510 --- .../LayoutRules/SA1510CodeFixProvider.cs | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1510CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1510CodeFixProvider.cs index f2861164a..0a6311a32 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1510CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1510CodeFixProvider.cs @@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.LayoutRules { using System.Collections.Immutable; using System.Composition; + using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -26,7 +27,7 @@ internal class SA1510CodeFixProvider : CodeFixProvider /// public override FixAllProvider GetFixAllProvider() { - return CustomFixAllProviders.BatchFixer; + return FixAll.Instance; } /// @@ -46,13 +47,31 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) } private static async Task GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) + { + var newRoot = await GetTransformedDocumentAsync(document, ImmutableArray.Create(diagnostic), cancellationToken).ConfigureAwait(false); + return document.WithSyntaxRoot(newRoot); + } + + private static async Task GetTransformedDocumentAsync(Document document, ImmutableArray diagnostics, CancellationToken cancellationToken) { var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + return syntaxRoot.ReplaceTokens( + diagnostics.Select(diagnostic => syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start)), + (originalToken, rewrittenToken) => + { + return rewrittenToken.WithoutLeadingBlankLines(); + }); + } + + private class FixAll : DocumentBasedFixAllProvider + { + public static FixAllProvider Instance { get; } = + new FixAll(); - var token = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start); + protected override string CodeActionTitle => LayoutResources.SA1510CodeFix; - var newSyntaxRoot = syntaxRoot.ReplaceToken(token, token.WithoutLeadingBlankLines()); - return document.WithSyntaxRoot(newSyntaxRoot); + protected override async Task FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray diagnostics) + => await GetTransformedDocumentAsync(document, diagnostics, fixAllContext.CancellationToken).ConfigureAwait(false); } } } From f6a148e7705b168648c966b19be8276d20727d3f Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Wed, 17 Mar 2021 13:26:51 -0700 Subject: [PATCH 09/16] Implement a custom Fix All provider for SA1509 --- .../LayoutRules/SA1509CodeFixProvider.cs | 47 +++++++++++++------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1509CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1509CodeFixProvider.cs index 1c4097cee..0d0e1c7c4 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1509CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1509CodeFixProvider.cs @@ -29,7 +29,7 @@ internal class SA1509CodeFixProvider : CodeFixProvider /// public override FixAllProvider GetFixAllProvider() { - return CustomFixAllProviders.BatchFixer; + return FixAll.Instance; } /// @@ -40,7 +40,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) context.RegisterCodeFix( CodeAction.Create( LayoutResources.SA1509CodeFix, - token => this.GetTransformedDocumentAsync(context.Document, diagnostic, token), + token => GetTransformedDocumentAsync(context.Document, diagnostic, token), nameof(SA1509CodeFixProvider)), diagnostic); } @@ -48,25 +48,33 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) return SpecializedTasks.CompletedTask; } - private async Task GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) + private static async Task GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { - var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - - var openBrace = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start); - var leadingTrivia = openBrace.LeadingTrivia; + var newRoot = await GetTransformedDocumentAsync(document, ImmutableArray.Create(diagnostic), cancellationToken).ConfigureAwait(false); + return document.WithSyntaxRoot(newRoot); + } - var newTriviaList = SyntaxFactory.TriviaList(); + private static async Task GetTransformedDocumentAsync(Document document, ImmutableArray diagnostics, CancellationToken cancellationToken) + { + var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + return syntaxRoot.ReplaceTokens( + diagnostics.Select(diagnostic => syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start)), + (originalToken, rewrittenToken) => + { + var openBrace = rewrittenToken; + var leadingTrivia = openBrace.LeadingTrivia; - var previousEmptyLines = this.GetPreviousEmptyLines(openBrace).ToList(); - newTriviaList = newTriviaList.AddRange(leadingTrivia.Except(previousEmptyLines)); + var newTriviaList = SyntaxFactory.TriviaList(); - var newOpenBrace = openBrace.WithLeadingTrivia(newTriviaList); - var newSyntaxRoot = syntaxRoot.ReplaceToken(openBrace, newOpenBrace); + var previousEmptyLines = GetPreviousEmptyLines(openBrace).ToList(); + newTriviaList = newTriviaList.AddRange(leadingTrivia.Except(previousEmptyLines)); - return document.WithSyntaxRoot(newSyntaxRoot); + var newOpenBrace = openBrace.WithLeadingTrivia(newTriviaList); + return newOpenBrace; + }); } - private IEnumerable GetPreviousEmptyLines(SyntaxToken openBrace) + private static IEnumerable GetPreviousEmptyLines(SyntaxToken openBrace) { var result = new List(); @@ -92,5 +100,16 @@ private IEnumerable GetPreviousEmptyLines(SyntaxToken openBrace) return result; } + + private class FixAll : DocumentBasedFixAllProvider + { + public static FixAllProvider Instance { get; } = + new FixAll(); + + protected override string CodeActionTitle => LayoutResources.SA1509CodeFix; + + protected override async Task FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray diagnostics) + => await GetTransformedDocumentAsync(document, diagnostics, fixAllContext.CancellationToken).ConfigureAwait(false); + } } } From 6ac3ef3f53841f60f8238ade6d6c9d3b94888e50 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Wed, 17 Mar 2021 13:30:47 -0700 Subject: [PATCH 10/16] Implement a custom Fix All provider for SA1506 --- .../LayoutRules/SA1506CodeFixProvider.cs | 100 +++++++++++------- 1 file changed, 60 insertions(+), 40 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1506CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1506CodeFixProvider.cs index 3eec32937..38176992e 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1506CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1506CodeFixProvider.cs @@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.LayoutRules { using System.Collections.Immutable; using System.Composition; + using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -27,7 +28,7 @@ internal class SA1506CodeFixProvider : CodeFixProvider /// public override FixAllProvider GetFixAllProvider() { - return CustomFixAllProviders.BatchFixer; + return FixAll.Instance; } /// @@ -48,53 +49,72 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) private static async Task GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { - var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var newRoot = await GetTransformedDocumentAsync(document, ImmutableArray.Create(diagnostic), cancellationToken).ConfigureAwait(false); + return document.WithSyntaxRoot(newRoot); + } - var token = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start); - var triviaList = token.LeadingTrivia; + private static async Task GetTransformedDocumentAsync(Document document, ImmutableArray diagnostics, CancellationToken cancellationToken) + { + var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + return syntaxRoot.ReplaceTokens( + diagnostics.Select(diagnostic => syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start)), + (originalToken, rewrittenToken) => + { + var triviaList = rewrittenToken.LeadingTrivia; - var index = triviaList.IndexOf(SyntaxKind.SingleLineDocumentationCommentTrivia); + var index = triviaList.IndexOf(SyntaxKind.SingleLineDocumentationCommentTrivia); - int currentLineStart = index + 1; - bool onBlankLine = true; - for (int currentIndex = currentLineStart; currentIndex < triviaList.Count; currentIndex++) - { - switch (triviaList[currentIndex].Kind()) - { - case SyntaxKind.EndOfLineTrivia: - if (onBlankLine) + int currentLineStart = index + 1; + bool onBlankLine = true; + for (int currentIndex = currentLineStart; currentIndex < triviaList.Count; currentIndex++) { - triviaList = triviaList.RemoveRange(currentLineStart, currentIndex - currentLineStart + 1); - currentIndex = currentLineStart - 1; - continue; - } - else - { - currentLineStart = currentIndex + 1; - onBlankLine = true; - break; - } + switch (triviaList[currentIndex].Kind()) + { + case SyntaxKind.EndOfLineTrivia: + if (onBlankLine) + { + triviaList = triviaList.RemoveRange(currentLineStart, currentIndex - currentLineStart + 1); + currentIndex = currentLineStart - 1; + continue; + } + else + { + currentLineStart = currentIndex + 1; + onBlankLine = true; + break; + } - case SyntaxKind.WhitespaceTrivia: - break; + case SyntaxKind.WhitespaceTrivia: + break; - default: - if (triviaList[currentIndex].HasBuiltinEndLine()) - { - currentLineStart = currentIndex + 1; - onBlankLine = true; - break; - } - else - { - onBlankLine = false; - break; + default: + if (triviaList[currentIndex].HasBuiltinEndLine()) + { + currentLineStart = currentIndex + 1; + onBlankLine = true; + break; + } + else + { + onBlankLine = false; + break; + } + } } - } - } - var newSyntaxRoot = syntaxRoot.ReplaceToken(token, token.WithLeadingTrivia(triviaList)); - return document.WithSyntaxRoot(newSyntaxRoot); + return rewrittenToken.WithLeadingTrivia(triviaList); + }); + } + + private class FixAll : DocumentBasedFixAllProvider + { + public static FixAllProvider Instance { get; } = + new FixAll(); + + protected override string CodeActionTitle => LayoutResources.SA1506CodeFix; + + protected override async Task FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray diagnostics) + => await GetTransformedDocumentAsync(document, diagnostics, fixAllContext.CancellationToken).ConfigureAwait(false); } } } From 27e6bf842329cbf12359aa0754ba2b24183ae032 Mon Sep 17 00:00:00 2001 From: Chris Blyth Date: Wed, 28 Apr 2021 10:55:35 +0100 Subject: [PATCH 11/16] Handle new() syntax on SA1118 --- .../SA1118CSharp9UnitTests.cs | 32 +++++++++++++++++++ ...SA1118ParameterMustNotSpanMultipleLines.cs | 1 + 2 files changed, 33 insertions(+) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/ReadabilityRules/SA1118CSharp9UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/ReadabilityRules/SA1118CSharp9UnitTests.cs index bcbec918d..9974f21a6 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/ReadabilityRules/SA1118CSharp9UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/ReadabilityRules/SA1118CSharp9UnitTests.cs @@ -75,5 +75,37 @@ r with TestCode = testCode, }.RunAsync(CancellationToken.None).ConfigureAwait(false); } + + [Fact] + [WorkItem(3339, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3339")] + public async Task TestNewExpressionAsync() + { + var testCode = @" + public class MyClass + { + public class MyObject + { + public string MyValue { get; init; } + } + + public void MyTestFunction() + { + MyCallingFunction(0, new() + { + MyValue = ""Test"" + }); + } + + public void MyCallingFunction(int index, MyObject myObject) + { + } + }"; + + await new CSharpTest(LanguageVersion.CSharp9) + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + TestCode = testCode, + }.RunAsync(CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1118ParameterMustNotSpanMultipleLines.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1118ParameterMustNotSpanMultipleLines.cs index 5deaafa00..cb29b67e0 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1118ParameterMustNotSpanMultipleLines.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1118ParameterMustNotSpanMultipleLines.cs @@ -84,6 +84,7 @@ internal class SA1118ParameterMustNotSpanMultipleLines : DiagnosticAnalyzer SyntaxKind.ArrayCreationExpression, SyntaxKind.ImplicitArrayCreationExpression, SyntaxKindEx.WithExpression, + SyntaxKindEx.ImplicitObjectCreationExpression, }; /// From ddd167b9179d902e1b5985bf4ed7eb03204c65e5 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Wed, 28 Apr 2021 08:10:00 -0700 Subject: [PATCH 12/16] Update to Microsoft.CodeAnalysis.Testing 1.0.1-beta1.21202.2 --- .../StyleCop.Analyzers.Test.csproj | 2 +- .../Verifiers/StyleCopCodeFixVerifier`2.cs | 23 +++++++++++-------- .../Verifiers/StyleCopDiagnosticVerifier`1.cs | 18 ++++++++++----- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj b/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj index 28420c7b2..89e5db1ae 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj @@ -18,7 +18,7 @@ - + diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/StyleCopCodeFixVerifier`2.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/StyleCopCodeFixVerifier`2.cs index b63b7cd31..02242ec7b 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/StyleCopCodeFixVerifier`2.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/StyleCopCodeFixVerifier`2.cs @@ -99,6 +99,7 @@ public CSharpTest() public CSharpTest(LanguageVersion? languageVersion) { this.ReferenceAssemblies = GenericAnalyzerTest.ReferenceAssemblies; + this.LanguageVersion = languageVersion; this.OptionsTransforms.Add(options => options @@ -109,15 +110,6 @@ public CSharpTest(LanguageVersion? languageVersion) this.TestState.AdditionalFilesFactories.Add(GenerateSettingsFile); this.CodeActionValidationMode = CodeActionValidationMode.None; - if (languageVersion != null) - { - this.SolutionTransforms.Add((solution, projectId) => - { - var parseOptions = (CSharpParseOptions)solution.GetProject(projectId).ParseOptions; - return solution.WithProjectParseOptions(projectId, parseOptions.WithLanguageVersion(languageVersion.Value)); - }); - } - this.SolutionTransforms.Add((solution, projectId) => { var corlib = solution.GetProject(projectId).MetadataReferences.OfType() @@ -237,6 +229,8 @@ public CSharpTest(LanguageVersion? languageVersion) /// public List ExplicitlyEnabledDiagnostics { get; } = new List(); + private LanguageVersion? LanguageVersion { get; } + protected override CompilationOptions CreateCompilationOptions() { var compilationOptions = base.CreateCompilationOptions(); @@ -250,6 +244,17 @@ protected override CompilationOptions CreateCompilationOptions() return compilationOptions.WithSpecificDiagnosticOptions(specificDiagnosticOptions); } + protected override ParseOptions CreateParseOptions() + { + var parseOptions = base.CreateParseOptions(); + if (this.LanguageVersion is { } languageVersion) + { + parseOptions = ((CSharpParseOptions)parseOptions).WithLanguageVersion(languageVersion); + } + + return parseOptions; + } + protected override IEnumerable GetCodeFixProviders() { var codeFixProvider = new TCodeFix(); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/StyleCopDiagnosticVerifier`1.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/StyleCopDiagnosticVerifier`1.cs index e879fd209..9cc4f2b33 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/StyleCopDiagnosticVerifier`1.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/StyleCopDiagnosticVerifier`1.cs @@ -62,14 +62,20 @@ public CSharpTest() public CSharpTest(LanguageVersion? languageVersion) { - if (languageVersion != null) + this.LanguageVersion = languageVersion; + } + + private LanguageVersion? LanguageVersion { get; } + + protected override ParseOptions CreateParseOptions() + { + var parseOptions = base.CreateParseOptions(); + if (this.LanguageVersion is { } languageVersion) { - this.SolutionTransforms.Add((solution, projectId) => - { - var parseOptions = (CSharpParseOptions)solution.GetProject(projectId).ParseOptions; - return solution.WithProjectParseOptions(projectId, parseOptions.WithLanguageVersion(languageVersion.Value)); - }); + parseOptions = ((CSharpParseOptions)parseOptions).WithLanguageVersion(languageVersion); } + + return parseOptions; } } } From f8aeb9d275bf37b382c94e7f6c3db087f069b445 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Wed, 28 Apr 2021 08:32:38 -0700 Subject: [PATCH 13/16] Set TLS defaults for testing --- .../Verifiers/StyleCopCodeFixVerifier`2.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/StyleCopCodeFixVerifier`2.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/StyleCopCodeFixVerifier`2.cs index b63b7cd31..3fad55d21 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/StyleCopCodeFixVerifier`2.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/StyleCopCodeFixVerifier`2.cs @@ -7,6 +7,7 @@ namespace StyleCop.Analyzers.Test.Verifiers using System.Collections.Generic; using System.IO; using System.Linq; + using System.Net; using System.Threading; using System.Threading.Tasks; using global::LightJson; @@ -91,6 +92,16 @@ internal class CSharpTest : CSharpCodeFixTest Date: Wed, 19 May 2021 14:42:01 -0700 Subject: [PATCH 14/16] Simplify testing and cover code fixes --- .../SA1500/SA1500UnitTests.DoWhiles.cs | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1500/SA1500UnitTests.DoWhiles.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1500/SA1500UnitTests.DoWhiles.cs index 65653a4b8..b0a617a91 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1500/SA1500UnitTests.DoWhiles.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1500/SA1500UnitTests.DoWhiles.cs @@ -373,7 +373,24 @@ private void Bar() while (x == 0) { x = 1; - } while (x == 0) + [|}|] while (x == 0) + { + x = 1; + } + } +}"; + + var fixedCode = @"public class Foo +{ + private void Bar() + { + var x = 0; + + while (x == 0) + { + x = 1; + } + while (x == 0) { x = 1; } @@ -383,10 +400,7 @@ private void Bar() var test = new CSharpTest { TestCode = testCode, - ExpectedDiagnostics = - { - Diagnostic().WithLocation(10, 9), - }, + FixedCode = fixedCode, Settings = testSettings, }; @@ -423,7 +437,7 @@ private void Bar() do { - x = 1; } while (x == 0); + x = 1; [|}|] while (x == 0); } }"; @@ -443,10 +457,6 @@ private void Bar() var test = new CSharpTest { TestCode = testCode, - ExpectedDiagnostics = - { - Diagnostic().WithLocation(9, 20), - }, FixedCode = fixedTestCode, Settings = testSettings, }; From 036d8e9760b18cd8a7db59f88b2e3bb5413081d8 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Wed, 19 May 2021 14:45:55 -0700 Subject: [PATCH 15/16] Simplify syntax checks by using Parent instead of GetPreviousToken --- .../SA1500BracesForMultiLineStatementsMustNotShareLine.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1500BracesForMultiLineStatementsMustNotShareLine.cs b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1500BracesForMultiLineStatementsMustNotShareLine.cs index d1b641f95..101612fcd 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1500BracesForMultiLineStatementsMustNotShareLine.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1500BracesForMultiLineStatementsMustNotShareLine.cs @@ -290,9 +290,8 @@ private static void CheckBraceToken(SyntaxNodeAnalysisContext context, SyntaxTok // may want to allow that and not have SA1500 report it as a style error. if (context.GetStyleCopSettings(context.CancellationToken).LayoutRules.AllowDoWhileOnClosingBrace) { - var openBracePreviousToken = openBraceToken.GetPreviousToken(includeZeroWidth: true); - - if (openBracePreviousToken.IsKind(SyntaxKind.DoKeyword)) + if (openBraceToken.Parent.IsKind(SyntaxKind.Block) + && openBraceToken.Parent.Parent.IsKind(SyntaxKind.DoStatement)) { return; } From c58307c75a03b1bc377d2bf99ff82edb75eda5f2 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Wed, 19 May 2021 14:50:18 -0700 Subject: [PATCH 16/16] Clarify documentation --- documentation/Configuration.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/Configuration.md b/documentation/Configuration.md index 7ef4f4407..8f0ce75b0 100644 --- a/documentation/Configuration.md +++ b/documentation/Configuration.md @@ -420,7 +420,7 @@ The following properties are used to configure layout rules in StyleCop Analyzer | --- | --- | --- | --- | | `newlineAtEndOfFile` | `"allow"` | 1.0.0 | Specifies the handling for newline characters which appear at the end of a file | | `allowConsecutiveUsings` | `true` | 1.1.0 | Specifies if SA1519 will allow consecutive using statements without braces | -| `allowDoWhileOnClosingBrace` | `false` | >1.2.0 | Specifies if SA1500 will allow the `while` expression of a `do-while` loop to be on the same line as the closing brace, as is generated by the default code snippet of Visual Studio | +| `allowDoWhileOnClosingBrace` | `false` | >1.2.0 | Specifies if SA1500 will allow the `while` expression of a `do`/`while` loop to be on the same line as the closing brace, as is generated by the default code snippet of Visual Studio | ### Lines at End of File @@ -444,10 +444,10 @@ require braces to used. ### Do-While Loop Placement -The behavior of [SA1500](SA1500.md) can be customized regarding the manner in which the `while` expression of a `do-while` loop is allowed to be placed. The `allowDoWhileOnClosingBrace` property specified the behavior: +The behavior of [SA1500](SA1500.md) can be customized regarding the manner in which the `while` expression of a `do`/`while` loop is allowed to be placed. The `allowDoWhileOnClosingBrace` property specified the behavior: -* `true`: the `while` expression of a `do-while` loop may be placed on the same line as the closing brace -* `false`: the `while` expression of a `do-while` loop must be on a separate line from the closing brace +* `true`: the `while` expression of a `do`/`while` loop may be placed on the same line as the closing brace or on a separate line +* `false`: the `while` expression of a `do`/`while` loop must be on a separate line from the closing brace ## Documentation Rules