Skip to content

Commit 65ab771

Browse files
Update SA1121 and SA1404 to detect global using aliases
#3594
1 parent 6327501 commit 65ab771

File tree

16 files changed

+427
-6
lines changed

16 files changed

+427
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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.CSharp10.Lightup
5+
{
6+
using StyleCop.Analyzers.Test.CSharp9.Lightup;
7+
8+
public partial class IImportScopeWrapperCSharp10UnitTests : IImportScopeWrapperCSharp9UnitTests
9+
{
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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.CSharp11.Lightup
5+
{
6+
using System.Collections.Generic;
7+
using System.Collections.Immutable;
8+
using Microsoft.CodeAnalysis;
9+
using Moq;
10+
using StyleCop.Analyzers.Lightup;
11+
using StyleCop.Analyzers.Test.CSharp10.Lightup;
12+
using Xunit;
13+
14+
public partial class IImportScopeWrapperCSharp11UnitTests : IImportScopeWrapperCSharp10UnitTests
15+
{
16+
[Theory]
17+
[InlineData(0)]
18+
[InlineData(1)]
19+
[InlineData(2)]
20+
public void TestCompatibleInstance(int numberOfAliasSymbols)
21+
{
22+
var obj = CreateImportScope(numberOfAliasSymbols);
23+
Assert.True(IImportScopeWrapper.IsInstance(obj));
24+
var wrapper = IImportScopeWrapper.FromObject(obj);
25+
Assert.Equal(obj.Aliases, wrapper.Aliases);
26+
}
27+
28+
private static IImportScope CreateImportScope(int numberOfAliasSymbols)
29+
{
30+
var aliasSymbolMocks = new List<IAliasSymbol>();
31+
for (var i = 0; i < numberOfAliasSymbols; i++)
32+
{
33+
aliasSymbolMocks.Add(Mock.Of<IAliasSymbol>());
34+
}
35+
36+
var importScopeMock = new Mock<IImportScope>();
37+
importScopeMock.Setup(x => x.Aliases).Returns(aliasSymbolMocks.ToImmutableArray());
38+
return importScopeMock.Object;
39+
}
40+
}
41+
}

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
}

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/StyleCop.Analyzers.Test.CSharp11.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
<ItemGroup>
2121
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.4.0" />
22+
<PackageReference Include="Moq" Version="4.20.70" />
2223
<PackageReference Include="xunit" Version="2.4.1" />
2324
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" PrivateAssets="all" />
2425
</ItemGroup>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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.CSharp12.Lightup
5+
{
6+
using StyleCop.Analyzers.Test.CSharp11.Lightup;
7+
8+
public partial class IImportScopeWrapperCSharp12UnitTests : IImportScopeWrapperCSharp11UnitTests
9+
{
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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.CSharp7.Lightup
5+
{
6+
using StyleCop.Analyzers.Test.Lightup;
7+
8+
public partial class IImportScopeWrapperCSharp7UnitTests : IImportScopeWrapperUnitTests
9+
{
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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.CSharp8.Lightup
5+
{
6+
using StyleCop.Analyzers.Test.CSharp7.Lightup;
7+
8+
public partial class IImportScopeWrapperCSharp8UnitTests : IImportScopeWrapperCSharp7UnitTests
9+
{
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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.CSharp9.Lightup
5+
{
6+
using StyleCop.Analyzers.Test.CSharp8.Lightup;
7+
8+
public partial class IImportScopeWrapperCSharp9UnitTests : IImportScopeWrapperCSharp8UnitTests
9+
{
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
using System;
7+
using StyleCop.Analyzers.Lightup;
8+
using Xunit;
9+
10+
public class IImportScopeWrapperUnitTests
11+
{
12+
[Fact]
13+
public void TestNull()
14+
{
15+
object? obj = null;
16+
Assert.False(IImportScopeWrapper.IsInstance(obj!));
17+
var wrapper = IImportScopeWrapper.FromObject(obj!);
18+
Assert.Throws<NullReferenceException>(() => wrapper.Aliases);
19+
}
20+
21+
[Fact]
22+
public void TestIncompatibleInstance()
23+
{
24+
var obj = new object();
25+
Assert.False(IImportScopeWrapper.IsInstance(obj));
26+
Assert.Throws<InvalidCastException>(() => IImportScopeWrapper.FromObject(obj));
27+
}
28+
}
29+
}

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

+11-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,23 @@ 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+
}
96101

97-
return nodes.OfType<UsingDirectiveSyntax>().Any(x => x.Alias != null);
102+
// Check for global using aliases
103+
var scopes = semanticModel.GetImportScopes(0, cancellationToken);
104+
return scopes.Any(x => x.Aliases.Length > 0);
98105
}
99106
}
100107
}
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)