Skip to content

Commit

Permalink
Update SA1121 and SA1404 to detect global using aliases
Browse files Browse the repository at this point in the history
  • Loading branch information
bjornhellander committed Jun 18, 2023
1 parent b80f0c2 commit 0101151
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ public static ConcurrentDictionary<SyntaxTree, bool> GetOrCreateUsingAliasCache(
Compilation cachedCompilation;
if (!cache.Item1.TryGetTarget(out cachedCompilation) || cachedCompilation != compilation)
{
var replacementCache = Tuple.Create(new WeakReference<Compilation>(compilation), new ConcurrentDictionary<SyntaxTree, bool>());
var containsGlobalUsingAlias = ContainsGlobalUsingAliasNoCache(compilation);
var replacementDictionary = containsGlobalUsingAlias ? null : new ConcurrentDictionary<SyntaxTree, bool>();
var replacementCache = Tuple.Create(new WeakReference<Compilation>(compilation), replacementDictionary);

while (true)
{
var prior = Interlocked.CompareExchange(ref usingAliasCache, replacementCache, cache);
Expand Down Expand Up @@ -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))
{
Expand All @@ -96,5 +105,18 @@ private static bool ContainsUsingAliasNoCache(SyntaxTree tree)

return nodes.OfType<UsingDirectiveSyntax>().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<UsingDirectiveSyntax>().ToArray();
return relevantNodes.Any(x => x.Alias != null && !x.GlobalKeyword().IsKind(SyntaxKind.None));
}
}
}
Original file line number Diff line number Diff line change
@@ -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<UsingDirectiveSyntax, SyntaxToken> GlobalKeywordAccessor;
private static readonly Func<UsingDirectiveSyntax, SyntaxToken, UsingDirectiveSyntax> WithGlobalKeywordAccessor;

static UsingDirectiveSyntaxExtensions()
{
GlobalKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor<UsingDirectiveSyntax, SyntaxToken>(typeof(UsingDirectiveSyntax), nameof(GlobalKeyword));
WithGlobalKeywordAccessor = LightupHelpers.CreateSyntaxWithPropertyAccessor<UsingDirectiveSyntax, SyntaxToken>(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);
}
}
}

0 comments on commit 0101151

Please sign in to comment.