From 0101151cb68950ae53617ff0fb31f9147f69a53a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Sun, 18 Jun 2023 17:53:30 +0200 Subject: [PATCH] Update SA1121 and SA1404 to detect global using aliases #3594 --- .../SA1404CSharp10UnitTests.cs | 48 +++++++++++++++++++ .../SA1121CSharp10UnitTests.cs | 26 ++++++++++ .../Helpers/SyntaxTreeHelpers.cs | 24 +++++++++- .../Lightup/UsingDirectiveSyntaxExtensions.cs | 31 ++++++++++++ 4 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers/Lightup/UsingDirectiveSyntaxExtensions.cs diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/MaintainabilityRules/SA1404CSharp10UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/MaintainabilityRules/SA1404CSharp10UnitTests.cs index 0deae8537..1182adc31 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/MaintainabilityRules/SA1404CSharp10UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/MaintainabilityRules/SA1404CSharp10UnitTests.cs @@ -3,9 +3,57 @@ namespace StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules { + using System.Threading; + using System.Threading.Tasks; + using StyleCop.Analyzers.MaintainabilityRules; using StyleCop.Analyzers.Test.CSharp9.MaintainabilityRules; + using Xunit; + using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.MaintainabilityRules.SA1404CodeAnalysisSuppressionMustHaveJustification, + StyleCop.Analyzers.MaintainabilityRules.SA1404CodeFixProvider>; public class SA1404CSharp10UnitTests : SA1404CSharp9UnitTests { + [Fact] + public async Task TestUsingNameChangeInGlobalUsingInAnotherFileAsync() + { + var testCode1 = @" +global using MySuppressionAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute;"; + + var testCode2 = @" +public class Foo +{ + [[|MySuppression(null, null)|]] + public void Bar() + { + + } +}"; + + var fixedCode2 = @" +public class Foo +{ + [MySuppression(null, null, Justification = """ + SA1404CodeAnalysisSuppressionMustHaveJustification.JustificationPlaceholder + @""")] + public void Bar() + { + + } +}"; + + var test = new CSharpTest + { + RemainingDiagnostics = + { + Diagnostic().WithLocation("/0/Test1.cs", 4, 32), + }, + NumberOfIncrementalIterations = 2, + NumberOfFixAllIterations = 2, + }; + test.TestState.Sources.Add(testCode1); + test.TestState.Sources.Add(testCode2); + test.FixedState.Sources.Add(testCode1); + test.FixedState.Sources.Add(fixedCode2); + await test.RunAsync(CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/ReadabilityRules/SA1121CSharp10UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/ReadabilityRules/SA1121CSharp10UnitTests.cs index e0373a85f..f48030af5 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/ReadabilityRules/SA1121CSharp10UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/ReadabilityRules/SA1121CSharp10UnitTests.cs @@ -42,5 +42,31 @@ class Bar FixedCode = newSource, }.RunAsync(CancellationToken.None).ConfigureAwait(false); } + + [Fact] + public async Task TestUsingNameChangeInGlobalUsingInAnotherFileAsync() + { + var source1 = @" +global using MyDouble = System.Double;"; + + var oldSource2 = @" +class TestClass +{ + private [|MyDouble|] x; +}"; + + var newSource2 = @" +class TestClass +{ + private double x; +}"; + + var test = new CSharpTest(); + test.TestState.Sources.Add(source1); + test.TestState.Sources.Add(oldSource2); + test.FixedState.Sources.Add(source1); + test.FixedState.Sources.Add(newSource2); + await test.RunAsync(CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs index c917df7a5..ff0b17281 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs @@ -33,7 +33,10 @@ public static ConcurrentDictionary GetOrCreateUsingAliasCache( Compilation cachedCompilation; if (!cache.Item1.TryGetTarget(out cachedCompilation) || cachedCompilation != compilation) { - var replacementCache = Tuple.Create(new WeakReference(compilation), new ConcurrentDictionary()); + var containsGlobalUsingAlias = ContainsGlobalUsingAliasNoCache(compilation); + var replacementDictionary = containsGlobalUsingAlias ? null : new ConcurrentDictionary(); + var replacementCache = Tuple.Create(new WeakReference(compilation), replacementDictionary); + while (true) { var prior = Interlocked.CompareExchange(ref usingAliasCache, replacementCache, cache); @@ -79,6 +82,12 @@ internal static bool ContainsUsingAlias(this SyntaxTree tree, ConcurrentDictiona return false; } + if (cache == null) + { + // NOTE: This happens if any syntax tree in the compilation contains a global using alias + return true; + } + bool result; if (cache.TryGetValue(tree, out result)) { @@ -96,5 +105,18 @@ private static bool ContainsUsingAliasNoCache(SyntaxTree tree) return nodes.OfType().Any(x => x.Alias != null); } + + private static bool ContainsGlobalUsingAliasNoCache(Compilation compilation) + { + return compilation.SyntaxTrees.Any(ContainsGlobalUsingAliasNoCache); + } + + private static bool ContainsGlobalUsingAliasNoCache(SyntaxTree tree) + { + var nodes = tree.GetRoot().DescendantNodes(node => node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration) || node.IsKind(SyntaxKindEx.FileScopedNamespaceDeclaration)); + + var relevantNodes = nodes.OfType().ToArray(); + return relevantNodes.Any(x => x.Alias != null && !x.GlobalKeyword().IsKind(SyntaxKind.None)); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/UsingDirectiveSyntaxExtensions.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/UsingDirectiveSyntaxExtensions.cs new file mode 100644 index 000000000..02f27de3e --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/UsingDirectiveSyntaxExtensions.cs @@ -0,0 +1,31 @@ +// 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.CSharp.Syntax; + + internal static class UsingDirectiveSyntaxExtensions + { + private static readonly Func GlobalKeywordAccessor; + private static readonly Func WithGlobalKeywordAccessor; + + static UsingDirectiveSyntaxExtensions() + { + GlobalKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(UsingDirectiveSyntax), nameof(GlobalKeyword)); + WithGlobalKeywordAccessor = LightupHelpers.CreateSyntaxWithPropertyAccessor(typeof(UsingDirectiveSyntax), nameof(GlobalKeyword)); + } + + public static SyntaxToken GlobalKeyword(this UsingDirectiveSyntax syntax) + { + return GlobalKeywordAccessor(syntax); + } + + public static UsingDirectiveSyntax WithGlobalKeyword(this UsingDirectiveSyntax syntax, SyntaxToken globalKeyword) + { + return WithGlobalKeywordAccessor(syntax, globalKeyword); + } + } +}