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 Dec 17, 2023
1 parent be49652 commit 7ffc577
Show file tree
Hide file tree
Showing 10 changed files with 381 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

// Tests calls where nullability rules are not obeyed
#pragma warning disable CS8604 // Possible null reference argument.

namespace StyleCop.Analyzers.Test.Lightup
{
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using StyleCop.Analyzers.Lightup;
using Xunit;

public class IImportScopeWrapperCSharp11UnitTests
{
[Fact]
public void TestNull()
{
object? obj = null;
Assert.False(IImportScopeWrapper.IsInstance(obj));
var wrapper = IImportScopeWrapper.FromObject(obj);
Assert.Throws<NullReferenceException>(() => wrapper.Aliases);
}

[Fact]
public void TestIncompatibleInstance()
{
var obj = new object();
Assert.False(IImportScopeWrapper.IsInstance(obj));
Assert.Throws<InvalidCastException>(() => IImportScopeWrapper.FromObject(obj));
}

[Fact]
public void TestCompatibleInstance()
{
var obj = new TestImportScope();
Assert.True(IImportScopeWrapper.IsInstance(obj));
var wrapper = IImportScopeWrapper.FromObject(obj);
Assert.Empty(wrapper.Aliases);
}

private class TestImportScope : IImportScope
{
public ImmutableArray<IAliasSymbol> Aliases => ImmutableArray<IAliasSymbol>.Empty;

public ImmutableArray<IAliasSymbol> ExternAliases => throw new NotImplementedException();

public ImmutableArray<ImportedNamespaceOrType> Imports => throw new NotImplementedException();

public ImmutableArray<ImportedXmlNamespace> XmlNamespaces => throw new NotImplementedException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,58 @@

namespace StyleCop.Analyzers.Test.CSharp11.MaintainabilityRules
{
using System.Threading;
using System.Threading.Tasks;
using StyleCop.Analyzers.MaintainabilityRules;
using StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules;
using Xunit;

using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier<
StyleCop.Analyzers.MaintainabilityRules.SA1404CodeAnalysisSuppressionMustHaveJustification,
StyleCop.Analyzers.MaintainabilityRules.SA1404CodeFixProvider>;

public partial class SA1404CSharp11UnitTests : SA1404CSharp10UnitTests
{
// NOTE: This tests a fix for a c# 10 feature, but the Roslyn API used to solve it wasn't available in the version
// we use in the c# 10 test project, so the test was added here instead.
[Fact]
[WorkItem(3594, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3594")]
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()
{
}
}";

await new CSharpTest()
{
TestSources = { testCode1, testCode2 },
FixedSources = { testCode1, fixedCode2 },
RemainingDiagnostics =
{
Diagnostic().WithLocation("/0/Test1.cs", 4, 32),
},
NumberOfIncrementalIterations = 2,
NumberOfFixAllIterations = 2,
}.RunAsync(CancellationToken.None).ConfigureAwait(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,43 @@

namespace StyleCop.Analyzers.Test.CSharp11.ReadabilityRules
{
using System.Threading;
using System.Threading.Tasks;
using StyleCop.Analyzers.Test.CSharp10.ReadabilityRules;
using Xunit;

using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier<
StyleCop.Analyzers.ReadabilityRules.SA1121UseBuiltInTypeAlias,
StyleCop.Analyzers.ReadabilityRules.SA1121CodeFixProvider>;

public partial class SA1121CSharp11UnitTests : SA1121CSharp10UnitTests
{
// NOTE: This tests a fix for a c# 10 feature, but the Roslyn API used to solve it wasn't available in the version
// we use in the c# 10 test project, so the test was added here instead.
[Fact]
[WorkItem(3594, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3594")]
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;
}";

await new CSharpTest()
{
TestSources = { source1, oldSource2 },
FixedSources = { source1, newSource2 },
}.RunAsync(CancellationToken.None).ConfigureAwait(false);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// 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.Lightup
{
public partial class IImportScopeWrapperCSharp12UnitTests : IImportScopeWrapperCSharp11UnitTests
{
}
}
20 changes: 16 additions & 4 deletions StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public static bool IsWhitespaceOnly(this SyntaxTree tree, CancellationToken canc
&& TriviaHelper.IndexOfFirstNonWhitespaceTrivia(firstToken.LeadingTrivia) == -1;
}

internal static bool ContainsUsingAlias(this SyntaxTree tree, ConcurrentDictionary<SyntaxTree, bool> cache)
internal static bool ContainsUsingAlias(this SyntaxTree tree, ConcurrentDictionary<SyntaxTree, bool> cache, SemanticModel semanticModel, CancellationToken cancellationToken)
{
if (tree == null)
{
Expand All @@ -85,16 +85,28 @@ internal static bool ContainsUsingAlias(this SyntaxTree tree, ConcurrentDictiona
return result;
}

bool generated = ContainsUsingAliasNoCache(tree);
bool generated = ContainsUsingAliasNoCache(tree, semanticModel, cancellationToken);
cache.TryAdd(tree, generated);
return generated;
}

private static bool ContainsUsingAliasNoCache(SyntaxTree tree)
private static bool ContainsUsingAliasNoCache(SyntaxTree tree, SemanticModel semanticModel, CancellationToken cancellationToken)
{
// Check for "local" using aliases
var nodes = tree.GetRoot().DescendantNodes(node => node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration) || node.IsKind(SyntaxKindEx.FileScopedNamespaceDeclaration));
if (nodes.OfType<UsingDirectiveSyntax>().Any(x => x.Alias != null))
{
return true;
}

// Check for global using aliases
if (SemanticModelExtensions.SupportsGetImportScopes)
{
var scopes = semanticModel.GetImportScopes(0, cancellationToken);
return scopes.Any(x => x.Aliases.Length > 0);
}

return nodes.OfType<UsingDirectiveSyntax>().Any(x => x.Alias != null);
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 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 Microsoft.CodeAnalysis;

internal readonly struct IImportScopeWrapper
{
internal const string WrappedTypeName = "Microsoft.CodeAnalysis.IImportScope";
private static readonly Type WrappedType;

private static readonly Func<object?, ImmutableArray<IAliasSymbol>> AliasesAccessor;

private readonly object node;

static IImportScopeWrapper()
{
WrappedType = WrapperHelper.GetWrappedType(typeof(IImportScopeWrapper));

AliasesAccessor = LightupHelpers.CreateSyntaxPropertyAccessor<object?, ImmutableArray<IAliasSymbol>>(WrappedType, nameof(Aliases));
}

private IImportScopeWrapper(object node)
{
this.node = node;
}

public ImmutableArray<IAliasSymbol> Aliases => AliasesAccessor(this.node);

// NOTE: Referenced via reflection
public static IImportScopeWrapper FromObject(object node)
{
if (node == null)
{
return default;
}

if (!IsInstance(node))
{
throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'");
}

return new IImportScopeWrapper(node);
}

public static bool IsInstance(object obj)
{
return obj != null && LightupHelpers.CanWrapObject(obj, WrappedType);
}
}
}
Loading

0 comments on commit 7ffc577

Please sign in to comment.