Skip to content

Commit 7ffc577

Browse files
Update SA1121 and SA1404 to detect global using aliases
#3594
1 parent be49652 commit 7ffc577

File tree

10 files changed

+381
-6
lines changed

10 files changed

+381
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
// Tests calls where nullability rules are not obeyed
5+
#pragma warning disable CS8604 // Possible null reference argument.
6+
7+
namespace StyleCop.Analyzers.Test.Lightup
8+
{
9+
using System;
10+
using System.Collections.Immutable;
11+
using Microsoft.CodeAnalysis;
12+
using StyleCop.Analyzers.Lightup;
13+
using Xunit;
14+
15+
public class IImportScopeWrapperCSharp11UnitTests
16+
{
17+
[Fact]
18+
public void TestNull()
19+
{
20+
object? obj = null;
21+
Assert.False(IImportScopeWrapper.IsInstance(obj));
22+
var wrapper = IImportScopeWrapper.FromObject(obj);
23+
Assert.Throws<NullReferenceException>(() => wrapper.Aliases);
24+
}
25+
26+
[Fact]
27+
public void TestIncompatibleInstance()
28+
{
29+
var obj = new object();
30+
Assert.False(IImportScopeWrapper.IsInstance(obj));
31+
Assert.Throws<InvalidCastException>(() => IImportScopeWrapper.FromObject(obj));
32+
}
33+
34+
[Fact]
35+
public void TestCompatibleInstance()
36+
{
37+
var obj = new TestImportScope();
38+
Assert.True(IImportScopeWrapper.IsInstance(obj));
39+
var wrapper = IImportScopeWrapper.FromObject(obj);
40+
Assert.Empty(wrapper.Aliases);
41+
}
42+
43+
private class TestImportScope : IImportScope
44+
{
45+
public ImmutableArray<IAliasSymbol> Aliases => ImmutableArray<IAliasSymbol>.Empty;
46+
47+
public ImmutableArray<IAliasSymbol> ExternAliases => throw new NotImplementedException();
48+
49+
public ImmutableArray<ImportedNamespaceOrType> Imports => throw new NotImplementedException();
50+
51+
public ImmutableArray<ImportedXmlNamespace> XmlNamespaces => throw new NotImplementedException();
52+
}
53+
}
54+
}

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1404CSharp11UnitTests.cs

+49
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,58 @@
33

44
namespace StyleCop.Analyzers.Test.CSharp11.MaintainabilityRules
55
{
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using StyleCop.Analyzers.MaintainabilityRules;
69
using StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules;
10+
using Xunit;
11+
12+
using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier<
13+
StyleCop.Analyzers.MaintainabilityRules.SA1404CodeAnalysisSuppressionMustHaveJustification,
14+
StyleCop.Analyzers.MaintainabilityRules.SA1404CodeFixProvider>;
715

816
public partial class SA1404CSharp11UnitTests : SA1404CSharp10UnitTests
917
{
18+
// NOTE: This tests a fix for a c# 10 feature, but the Roslyn API used to solve it wasn't available in the version
19+
// we use in the c# 10 test project, so the test was added here instead.
20+
[Fact]
21+
[WorkItem(3594, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3594")]
22+
public async Task TestUsingNameChangeInGlobalUsingInAnotherFileAsync()
23+
{
24+
var testCode1 = @"
25+
global using MySuppressionAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute;";
26+
27+
var testCode2 = @"
28+
public class Foo
29+
{
30+
[[|MySuppression(null, null)|]]
31+
public void Bar()
32+
{
33+
34+
}
35+
}";
36+
37+
var fixedCode2 = @"
38+
public class Foo
39+
{
40+
[MySuppression(null, null, Justification = """ + SA1404CodeAnalysisSuppressionMustHaveJustification.JustificationPlaceholder + @""")]
41+
public void Bar()
42+
{
43+
44+
}
45+
}";
46+
47+
await new CSharpTest()
48+
{
49+
TestSources = { testCode1, testCode2 },
50+
FixedSources = { testCode1, fixedCode2 },
51+
RemainingDiagnostics =
52+
{
53+
Diagnostic().WithLocation("/0/Test1.cs", 4, 32),
54+
},
55+
NumberOfIncrementalIterations = 2,
56+
NumberOfFixAllIterations = 2,
57+
}.RunAsync(CancellationToken.None).ConfigureAwait(false);
58+
}
1059
}
1160
}

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/ReadabilityRules/SA1121CSharp11UnitTests.cs

+34
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,43 @@
33

44
namespace StyleCop.Analyzers.Test.CSharp11.ReadabilityRules
55
{
6+
using System.Threading;
7+
using System.Threading.Tasks;
68
using StyleCop.Analyzers.Test.CSharp10.ReadabilityRules;
9+
using Xunit;
10+
11+
using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier<
12+
StyleCop.Analyzers.ReadabilityRules.SA1121UseBuiltInTypeAlias,
13+
StyleCop.Analyzers.ReadabilityRules.SA1121CodeFixProvider>;
714

815
public partial class SA1121CSharp11UnitTests : SA1121CSharp10UnitTests
916
{
17+
// NOTE: This tests a fix for a c# 10 feature, but the Roslyn API used to solve it wasn't available in the version
18+
// we use in the c# 10 test project, so the test was added here instead.
19+
[Fact]
20+
[WorkItem(3594, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3594")]
21+
public async Task TestUsingNameChangeInGlobalUsingInAnotherFileAsync()
22+
{
23+
var source1 = @"
24+
global using MyDouble = System.Double;";
25+
26+
var oldSource2 = @"
27+
class TestClass
28+
{
29+
private [|MyDouble|] x;
30+
}";
31+
32+
var newSource2 = @"
33+
class TestClass
34+
{
35+
private double x;
36+
}";
37+
38+
await new CSharpTest()
39+
{
40+
TestSources = { source1, oldSource2 },
41+
FixedSources = { source1, newSource2 },
42+
}.RunAsync(CancellationToken.None).ConfigureAwait(false);
43+
}
1044
}
1145
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.Test.Lightup
5+
{
6+
public partial class IImportScopeWrapperCSharp12UnitTests : IImportScopeWrapperCSharp11UnitTests
7+
{
8+
}
9+
}

StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs

+16-4
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public static bool IsWhitespaceOnly(this SyntaxTree tree, CancellationToken canc
7272
&& TriviaHelper.IndexOfFirstNonWhitespaceTrivia(firstToken.LeadingTrivia) == -1;
7373
}
7474

75-
internal static bool ContainsUsingAlias(this SyntaxTree tree, ConcurrentDictionary<SyntaxTree, bool> cache)
75+
internal static bool ContainsUsingAlias(this SyntaxTree tree, ConcurrentDictionary<SyntaxTree, bool> cache, SemanticModel semanticModel, CancellationToken cancellationToken)
7676
{
7777
if (tree == null)
7878
{
@@ -85,16 +85,28 @@ internal static bool ContainsUsingAlias(this SyntaxTree tree, ConcurrentDictiona
8585
return result;
8686
}
8787

88-
bool generated = ContainsUsingAliasNoCache(tree);
88+
bool generated = ContainsUsingAliasNoCache(tree, semanticModel, cancellationToken);
8989
cache.TryAdd(tree, generated);
9090
return generated;
9191
}
9292

93-
private static bool ContainsUsingAliasNoCache(SyntaxTree tree)
93+
private static bool ContainsUsingAliasNoCache(SyntaxTree tree, SemanticModel semanticModel, CancellationToken cancellationToken)
9494
{
95+
// Check for "local" using aliases
9596
var nodes = tree.GetRoot().DescendantNodes(node => node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration) || node.IsKind(SyntaxKindEx.FileScopedNamespaceDeclaration));
97+
if (nodes.OfType<UsingDirectiveSyntax>().Any(x => x.Alias != null))
98+
{
99+
return true;
100+
}
101+
102+
// Check for global using aliases
103+
if (SemanticModelExtensions.SupportsGetImportScopes)
104+
{
105+
var scopes = semanticModel.GetImportScopes(0, cancellationToken);
106+
return scopes.Any(x => x.Aliases.Length > 0);
107+
}
96108

97-
return nodes.OfType<UsingDirectiveSyntax>().Any(x => x.Alias != null);
109+
return false;
98110
}
99111
}
100112
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.Lightup
5+
{
6+
using System;
7+
using System.Collections.Immutable;
8+
using Microsoft.CodeAnalysis;
9+
10+
internal readonly struct IImportScopeWrapper
11+
{
12+
internal const string WrappedTypeName = "Microsoft.CodeAnalysis.IImportScope";
13+
private static readonly Type WrappedType;
14+
15+
private static readonly Func<object?, ImmutableArray<IAliasSymbol>> AliasesAccessor;
16+
17+
private readonly object node;
18+
19+
static IImportScopeWrapper()
20+
{
21+
WrappedType = WrapperHelper.GetWrappedType(typeof(IImportScopeWrapper));
22+
23+
AliasesAccessor = LightupHelpers.CreateSyntaxPropertyAccessor<object?, ImmutableArray<IAliasSymbol>>(WrappedType, nameof(Aliases));
24+
}
25+
26+
private IImportScopeWrapper(object node)
27+
{
28+
this.node = node;
29+
}
30+
31+
public ImmutableArray<IAliasSymbol> Aliases => AliasesAccessor(this.node);
32+
33+
// NOTE: Referenced via reflection
34+
public static IImportScopeWrapper FromObject(object node)
35+
{
36+
if (node == null)
37+
{
38+
return default;
39+
}
40+
41+
if (!IsInstance(node))
42+
{
43+
throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'");
44+
}
45+
46+
return new IImportScopeWrapper(node);
47+
}
48+
49+
public static bool IsInstance(object obj)
50+
{
51+
return obj != null && LightupHelpers.CanWrapObject(obj, WrappedType);
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)