From 857819aaf0564c50eef1b67e241e82d59eb8055e Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Wed, 13 Feb 2019 11:00:52 -0600 Subject: [PATCH 1/3] Fix incorrect qualification of types nested within generic types Fixes #2879 --- .../ReadabilityRules/SA1135UnitTests.cs | 13 ++++ .../Helpers/SymbolNameHelpers.cs | 69 +++++++++++-------- 2 files changed, 52 insertions(+), 30 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1135UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1135UnitTests.cs index 59bfdbea0..b4383c88d 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1135UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1135UnitTests.cs @@ -319,5 +319,18 @@ namespace MyNamespace { await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } + + [Fact] + [WorkItem(2879, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2879")] + public async Task TestAliasTypeNestedInGenericAsync() + { + var testCode = @" +namespace TestNamespace +{ + using Example = System.Collections.Immutable.ImmutableDictionary.Builder; +} +"; + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SymbolNameHelpers.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SymbolNameHelpers.cs index 29ecf347e..724dae8b0 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SymbolNameHelpers.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SymbolNameHelpers.cs @@ -39,8 +39,44 @@ public static string ToQualifiedString(this ISymbol symbol) } AppendQualifiedSymbolName(builder, namedTypeSymbol); + } + else + { + AppendQualifiedSymbolName(builder, symbol); + } + + return ObjectPools.StringBuilderPool.ReturnAndFree(builder); + } + + private static bool AppendQualifiedSymbolName(StringBuilder builder, ISymbol symbol) + { + switch (symbol) + { + case IArrayTypeSymbol arraySymbol: + AppendQualifiedSymbolName(builder, arraySymbol.ElementType); + builder + .Append("[") + .Append(',', arraySymbol.Rank - 1) + .Append("]"); + return true; + + case INamespaceSymbol namespaceSymbol: + if (namespaceSymbol.IsGlobalNamespace) + { + return false; + } + + builder.Append(namespaceSymbol.ToDisplayString()); + return true; + + case INamedTypeSymbol namedTypeSymbol: + if (AppendQualifiedSymbolName(builder, symbol.ContainingSymbol)) + { + builder.Append("."); + } - if (namedTypeSymbol.IsGenericType) + builder.Append(symbol.Name); + if (namedTypeSymbol.IsGenericType && !namedTypeSymbol.TypeArguments.IsEmpty) { builder.Append(GenericTypeParametersOpen); @@ -61,39 +97,12 @@ public static string ToQualifiedString(this ISymbol symbol) builder.Remove(builder.Length - GenericSeparator.Length, GenericSeparator.Length); builder.Append(GenericTypeParametersClose); } - } - else - { - AppendQualifiedSymbolName(builder, symbol); - } - - return ObjectPools.StringBuilderPool.ReturnAndFree(builder); - } - private static void AppendQualifiedSymbolName(StringBuilder builder, ISymbol symbol) - { - switch (symbol) - { - case IArrayTypeSymbol arraySymbol: - builder - .Append(arraySymbol.ElementType.ContainingNamespace.ToDisplayString()) - .Append(".") - .Append(arraySymbol.ElementType.Name) - .Append("[") - .Append(',', arraySymbol.Rank - 1) - .Append("]"); - break; + return true; default: - if (!symbol.ContainingNamespace.IsGlobalNamespace) - { - builder - .Append(symbol.ContainingNamespace.ToDisplayString()) - .Append("."); - } - builder.Append(symbol.Name); - break; + return true; } } } From e9c8dc52276ed562387dd3a2f1d5b9531d00a368 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Thu, 14 Feb 2019 10:44:06 -0600 Subject: [PATCH 2/3] Additional tests and fixes for qualified using directives --- .../ReadabilityRules/SA1135CodeFixProvider.cs | 2 +- .../SA1135CSharp7UnitTests.cs | 32 +++++ .../ReadabilityRules/SA1135UnitTests.cs | 21 +++ .../Helpers/SymbolNameHelpers.cs | 109 +++++++++------ .../StyleCop.Analyzers/Lightup/CSharp7.md | 2 +- .../Lightup/IFieldSymbolExtensions.cs | 23 +++ .../SA1135UsingDirectivesMustBeQualified.cs | 132 ++++++++++++++---- 7 files changed, 252 insertions(+), 69 deletions(-) create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers/Lightup/IFieldSymbolExtensions.cs diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1135CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1135CodeFixProvider.cs index 26d123983..b9dc63276 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1135CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1135CodeFixProvider.cs @@ -65,7 +65,7 @@ private static async Task GetTransformedDocumentAsync(Document documen private static SyntaxNode GetReplacementNode(SemanticModel semanticModel, UsingDirectiveSyntax node, CancellationToken cancellationToken) { SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(node.Name, cancellationToken); - var symbolNameSyntax = SyntaxFactory.ParseName(symbolInfo.Symbol.ToQualifiedString()); + var symbolNameSyntax = SyntaxFactory.ParseName(symbolInfo.Symbol.ToQualifiedString(node.Name)); var newName = GetReplacementName(symbolNameSyntax, node.Name); return node.WithName((NameSyntax)newName); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/ReadabilityRules/SA1135CSharp7UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/ReadabilityRules/SA1135CSharp7UnitTests.cs index 82beb5e4d..639ce7a5a 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/ReadabilityRules/SA1135CSharp7UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/ReadabilityRules/SA1135CSharp7UnitTests.cs @@ -3,9 +3,41 @@ namespace StyleCop.Analyzers.Test.CSharp7.ReadabilityRules { + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.Test.ReadabilityRules; + using Xunit; + using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1135UsingDirectivesMustBeQualified, + StyleCop.Analyzers.ReadabilityRules.SA1135CodeFixProvider>; public class SA1135CSharp7UnitTests : SA1135UnitTests { + [Fact] + [WorkItem(2879, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2879")] + public async Task TestTupleTypeInUsingAliasAsync() + { + var testCode = @" +namespace TestNamespace +{ + using Example = System.Collections.Generic.List<(int, int)>; +} +"; + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + [WorkItem(2879, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2879")] + public async Task TestTupleTypeWithNamedElementsInUsingAliasAsync() + { + var testCode = @" +namespace TestNamespace +{ + using Example = System.Collections.Generic.List<(int x, int y)>; +} +"; + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1135UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1135UnitTests.cs index b4383c88d..58d1ec7b3 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1135UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1135UnitTests.cs @@ -332,5 +332,26 @@ namespace TestNamespace "; await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } + + [Fact] + [WorkItem(2879, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2879")] + public async Task TestValueTupleInUsingAliasAsync() + { + var testCode = @" +namespace System +{ + using Example = System.Collections.Generic.List>; +} +"; + var fixedCode = @" +namespace System +{ + using Example = System.Collections.Generic.List>; +} +"; + + var expected = Diagnostic(SA1135UsingDirectivesMustBeQualified.DescriptorType).WithLocation(4, 5).WithArguments("System.Collections.Generic.List>"); + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SymbolNameHelpers.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SymbolNameHelpers.cs index 724dae8b0..ac0683660 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SymbolNameHelpers.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SymbolNameHelpers.cs @@ -17,43 +17,29 @@ internal static class SymbolNameHelpers private const string GenericTypeParametersClose = ">"; private const string GenericSeparator = ", "; + private const string TupleTypeOpen = "("; + private const string TupleTypeClose = ")"; + private const string TupleElementSeparator = ", "; + /// /// Generates the qualified name for the given symbol. /// /// The symbol to use. + /// The syntax node which resolves to the symbol. /// The generated qualified name. - public static string ToQualifiedString(this ISymbol symbol) + public static string ToQualifiedString(this ISymbol symbol, NameSyntax name) { var builder = ObjectPools.StringBuilderPool.Allocate(); - - if (symbol is INamedTypeSymbol namedTypeSymbol) - { - if (SpecialTypeHelper.TryGetPredefinedType(namedTypeSymbol.SpecialType, out PredefinedTypeSyntax specialTypeSyntax)) - { - return specialTypeSyntax.ToFullString(); - } - - if (namedTypeSymbol.IsTupleType()) - { - namedTypeSymbol = namedTypeSymbol.TupleUnderlyingType(); - } - - AppendQualifiedSymbolName(builder, namedTypeSymbol); - } - else - { - AppendQualifiedSymbolName(builder, symbol); - } - + AppendQualifiedSymbolName(builder, symbol, name); return ObjectPools.StringBuilderPool.ReturnAndFree(builder); } - private static bool AppendQualifiedSymbolName(StringBuilder builder, ISymbol symbol) + private static bool AppendQualifiedSymbolName(StringBuilder builder, ISymbol symbol, TypeSyntax type) { switch (symbol) { case IArrayTypeSymbol arraySymbol: - AppendQualifiedSymbolName(builder, arraySymbol.ElementType); + AppendQualifiedSymbolName(builder, arraySymbol.ElementType, (type as ArrayTypeSyntax)?.ElementType); builder .Append("[") .Append(',', arraySymbol.Rank - 1) @@ -70,36 +56,79 @@ private static bool AppendQualifiedSymbolName(StringBuilder builder, ISymbol sym return true; case INamedTypeSymbol namedTypeSymbol: - if (AppendQualifiedSymbolName(builder, symbol.ContainingSymbol)) + if (SpecialTypeHelper.TryGetPredefinedType(namedTypeSymbol.SpecialType, out var specialTypeSyntax)) { - builder.Append("."); + builder.Append(specialTypeSyntax.ToFullString()); + return true; } - - builder.Append(symbol.Name); - if (namedTypeSymbol.IsGenericType && !namedTypeSymbol.TypeArguments.IsEmpty) + else if (namedTypeSymbol.IsTupleType()) { - builder.Append(GenericTypeParametersOpen); - - foreach (var typeArgument in namedTypeSymbol.TypeArguments) + if (TupleTypeSyntaxWrapper.IsInstance(type)) { - if (typeArgument is INamedTypeSymbol namedTypeArgument && typeArgument.IsTupleType()) + var tupleType = (TupleTypeSyntaxWrapper)type; + + builder.Append(TupleTypeOpen); + var elements = namedTypeSymbol.TupleElements(); + for (int i = 0; i < elements.Length; i++) { - builder.Append(namedTypeArgument.TupleUnderlyingType().ToQualifiedString()); + var field = elements[i]; + var fieldType = tupleType.Elements.Count > i ? tupleType.Elements[i] : default; + + if (i > 0) + { + builder.Append(TupleElementSeparator); + } + + AppendQualifiedSymbolName(builder, field.Type, fieldType.Type); + if (field != field.CorrespondingTupleField()) + { + builder.Append(" ").Append(field.Name); + } } - else + + builder.Append(TupleTypeClose); + return true; + } + else + { + return AppendQualifiedSymbolName(builder, namedTypeSymbol.TupleUnderlyingType(), type); + } + } + else + { + if (AppendQualifiedSymbolName(builder, symbol.ContainingSymbol, (type as QualifiedNameSyntax)?.Left)) + { + builder.Append("."); + } + + builder.Append(symbol.Name); + if (namedTypeSymbol.IsGenericType && !namedTypeSymbol.TypeArguments.IsEmpty) + { + builder.Append(GenericTypeParametersOpen); + var arguments = namedTypeSymbol.TypeArguments; + var argumentTypes = type is QualifiedNameSyntax qualifiedName + ? (qualifiedName.Right as GenericNameSyntax)?.TypeArgumentList + : (type as GenericNameSyntax)?.TypeArgumentList; + + for (int i = 0; i < arguments.Length; i++) { - builder.Append(typeArgument.ToQualifiedString()); + var argument = arguments[i]; + var argumentType = argumentTypes.Arguments.Count > i ? argumentTypes.Arguments[i] : null; + + if (i > 0) + { + builder.Append(GenericSeparator); + } + + AppendQualifiedSymbolName(builder, argument, argumentType); } - builder.Append(GenericSeparator); + builder.Append(GenericTypeParametersClose); } - builder.Remove(builder.Length - GenericSeparator.Length, GenericSeparator.Length); - builder.Append(GenericTypeParametersClose); + return true; } - return true; - default: builder.Append(symbol.Name); return true; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/CSharp7.md b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/CSharp7.md index 3ef479ad4..fc6245b21 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/CSharp7.md +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/CSharp7.md @@ -61,7 +61,7 @@ See [dotnet/roslyn@c2af711](https://github.com/dotnet/roslyn/commit/c2af71127234 * [ ] `Microsoft.CodeAnalysis.IArrayTypeSymbol.Sizes.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.IDiscardSymbol` * [ ] `Microsoft.CodeAnalysis.IDiscardSymbol.Type.get -> Microsoft.CodeAnalysis.ITypeSymbol` -* [ ] `Microsoft.CodeAnalysis.IFieldSymbol.CorrespondingTupleField.get -> Microsoft.CodeAnalysis.IFieldSymbol` +* [x] `Microsoft.CodeAnalysis.IFieldSymbol.CorrespondingTupleField.get -> Microsoft.CodeAnalysis.IFieldSymbol` * [ ] `Microsoft.CodeAnalysis.ILocalSymbol.IsRef.get -> bool` * [ ] `Microsoft.CodeAnalysis.IMethodSymbol.RefCustomModifiers.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.IMethodSymbol.ReturnsByRef.get -> bool` diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/IFieldSymbolExtensions.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/IFieldSymbolExtensions.cs new file mode 100644 index 000000000..56dbb4df8 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/IFieldSymbolExtensions.cs @@ -0,0 +1,23 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Lightup +{ + using System; + using Microsoft.CodeAnalysis; + + internal static class IFieldSymbolExtensions + { + private static readonly Func CorrespondingTupleFieldAccessor; + + static IFieldSymbolExtensions() + { + CorrespondingTupleFieldAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(IFieldSymbol), nameof(CorrespondingTupleField)); + } + + public static IFieldSymbol CorrespondingTupleField(this IFieldSymbol symbol) + { + return CorrespondingTupleFieldAccessor(symbol); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1135UsingDirectivesMustBeQualified.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1135UsingDirectivesMustBeQualified.cs index 1285ade4e..130b1cc81 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1135UsingDirectivesMustBeQualified.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1135UsingDirectivesMustBeQualified.cs @@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.ReadabilityRules { using System; using System.Collections.Immutable; + using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -98,7 +99,7 @@ private static void CheckUsingDeclaration(SyntaxNodeAnalysisContext context, Usi symbol = typeSymbol.TupleUnderlyingType(); } - string symbolString = symbol.ToQualifiedString(); + string symbolString = symbol.ToQualifiedString(usingDirective.Name); string usingString = UsingDirectiveSyntaxToCanonicalString(usingDirective); if ((symbolString != usingString) && !usingDirective.StartsWithAlias(context.SemanticModel, context.CancellationToken)) @@ -124,37 +125,114 @@ private static void CheckUsingDeclaration(SyntaxNodeAnalysisContext context, Usi private static string UsingDirectiveSyntaxToCanonicalString(UsingDirectiveSyntax usingDirective) { var builder = StringBuilderPool.Allocate(); - var insideArrayDeclaration = false; + AppendCanonicalString(builder, usingDirective.Name); + return StringBuilderPool.ReturnAndFree(builder); + } - // NOTE: this does not cover embedded comments. It is very unlikely that comments are present - // within a multiline using statement and handling them requires a lot more effort (and keeping of state). - foreach (var c in usingDirective.Name.ToString()) + private static bool AppendCanonicalString(StringBuilder builder, TypeSyntax type) + { + switch (type) { - switch (c) + case AliasQualifiedNameSyntax aliasQualifiedName: + AppendCanonicalString(builder, aliasQualifiedName.Alias); + builder.Append("::"); + AppendCanonicalString(builder, aliasQualifiedName.Name); + return true; + + case IdentifierNameSyntax identifierName: + builder.Append(identifierName.Identifier.Text); + return true; + + case GenericNameSyntax genericName: + builder.Append(genericName.Identifier.Text); + builder.Append("<"); + + var typeArgumentList = genericName.TypeArgumentList; + for (int i = 0; i < typeArgumentList.Arguments.Count; i++) { - case ' ': - case '\t': - case '\r': - case '\n': - break; - case '[': - insideArrayDeclaration = true; - builder.Append(c); - break; - case ']': - insideArrayDeclaration = false; - builder.Append(c); - break; - case ',': - builder.Append(insideArrayDeclaration ? "," : ", "); - break; - default: - builder.Append(c); - break; + if (i > 0) + { + builder.Append(", "); + } + + AppendCanonicalString(builder, typeArgumentList.Arguments[i]); } - } - return StringBuilderPool.ReturnAndFree(builder); + builder.Append(">"); + return true; + + case QualifiedNameSyntax qualifiedName: + AppendCanonicalString(builder, qualifiedName.Left); + builder.Append("."); + AppendCanonicalString(builder, qualifiedName.Right); + return true; + + case PredefinedTypeSyntax predefinedType: + builder.Append(predefinedType.Keyword.Text); + return true; + + case ArrayTypeSyntax arrayType: + AppendCanonicalString(builder, arrayType.ElementType); + foreach (var rankSpecifier in arrayType.RankSpecifiers) + { + builder.Append("["); + builder.Append(',', rankSpecifier.Rank - 1); + builder.Append("]"); + } + + return true; + + case NullableTypeSyntax nullableType: + AppendCanonicalString(builder, nullableType.ElementType); + builder.Append("?"); + return true; + + case OmittedTypeArgumentSyntax _: + return false; + + default: + if (TupleTypeSyntaxWrapper.IsInstance(type)) + { + var tupleType = (TupleTypeSyntaxWrapper)type; + + builder.Append("("); + + var elements = tupleType.Elements; + for (int i = 0; i < elements.Count; i++) + { + if (i > 0) + { + builder.Append(", "); + } + + AppendCanonicalString(builder, elements[i].Type); + if (!elements[i].Identifier.IsKind(SyntaxKind.None)) + { + builder.Append(" ").Append(elements[i].Identifier.Text); + } + } + + builder.Append(")"); + return true; + } + else if (RefTypeSyntaxWrapper.IsInstance(type)) + { + var refType = (RefTypeSyntaxWrapper)type; + builder.Append(refType.RefKeyword.Text); + if (refType.ReadOnlyKeyword.IsKind(SyntaxKind.ReadOnlyKeyword)) + { + builder.Append(" ").Append(refType.ReadOnlyKeyword.Text); + } + + builder.Append(" "); + AppendCanonicalString(builder, refType.Type); + return true; + } + else + { + return false; + } + } } } } From 1b6e1c00f7f0b173695cd1718baea1053edf1dc9 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Fri, 15 Feb 2019 12:32:45 -0600 Subject: [PATCH 3/3] Additional tests and fixes for qualified using directives --- .../ReadabilityRules/SA1135UnitTests.cs | 62 +++++++++++++++++++ .../Helpers/SymbolNameHelpers.cs | 34 +++++++--- .../SA1135UsingDirectivesMustBeQualified.cs | 14 ----- 3 files changed, 88 insertions(+), 22 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1135UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1135UnitTests.cs index 58d1ec7b3..99bfc9f53 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1135UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1135UnitTests.cs @@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.Test.ReadabilityRules { using System.Threading; using System.Threading.Tasks; + using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.ReadabilityRules; using Xunit; @@ -320,6 +321,67 @@ namespace MyNamespace { await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } + [Fact] + [WorkItem(2879, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2879")] + public async Task TestOmittedTypeInGenericAsync() + { + var testCode = @" +namespace TestNamespace +{ + using Example = System.Collections.Generic.List<>; +} +"; + + var expected = new DiagnosticResult("CS7003", DiagnosticSeverity.Error).WithLocation(4, 48).WithMessage("Unexpected use of an unbound generic name"); + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + [WorkItem(2879, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2879")] + public async Task TestNullableTypeInGenericAsync() + { + var testCode = @" +namespace TestNamespace +{ + using Example = System.Collections.Generic.List; +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + [WorkItem(2879, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2879")] + public async Task TestGlobalQualifiedTypeInGenericAsync() + { + var testCode = @" +namespace TestNamespace +{ + using Example = System.Collections.Generic.List; +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + [WorkItem(2879, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2879")] + public async Task TestTypeInGlobalNamespaceAsync() + { + var testCode = @" +namespace TestNamespace +{ + using Example = MyClass; +} + +class MyClass +{ +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + [Fact] [WorkItem(2879, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2879")] public async Task TestAliasTypeNestedInGenericAsync() diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SymbolNameHelpers.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SymbolNameHelpers.cs index ac0683660..0f4c3c488 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SymbolNameHelpers.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SymbolNameHelpers.cs @@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.Helpers { using System.Text; using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using StyleCop.Analyzers.Lightup; @@ -36,9 +37,10 @@ public static string ToQualifiedString(this ISymbol symbol, NameSyntax name) private static bool AppendQualifiedSymbolName(StringBuilder builder, ISymbol symbol, TypeSyntax type) { - switch (symbol) + switch (symbol.Kind) { - case IArrayTypeSymbol arraySymbol: + case SymbolKind.ArrayType: + var arraySymbol = (IArrayTypeSymbol)symbol; AppendQualifiedSymbolName(builder, arraySymbol.ElementType, (type as ArrayTypeSyntax)?.ElementType); builder .Append("[") @@ -46,7 +48,8 @@ private static bool AppendQualifiedSymbolName(StringBuilder builder, ISymbol sym .Append("]"); return true; - case INamespaceSymbol namespaceSymbol: + case SymbolKind.Namespace: + var namespaceSymbol = (INamespaceSymbol)symbol; if (namespaceSymbol.IsGlobalNamespace) { return false; @@ -55,7 +58,8 @@ private static bool AppendQualifiedSymbolName(StringBuilder builder, ISymbol sym builder.Append(namespaceSymbol.ToDisplayString()); return true; - case INamedTypeSymbol namedTypeSymbol: + case SymbolKind.NamedType: + var namedTypeSymbol = (INamedTypeSymbol)symbol; if (SpecialTypeHelper.TryGetPredefinedType(namedTypeSymbol.SpecialType, out var specialTypeSyntax)) { builder.Append(specialTypeSyntax.ToFullString()); @@ -94,6 +98,12 @@ private static bool AppendQualifiedSymbolName(StringBuilder builder, ISymbol sym return AppendQualifiedSymbolName(builder, namedTypeSymbol.TupleUnderlyingType(), type); } } + else if (namedTypeSymbol.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) + { + AppendQualifiedSymbolName(builder, namedTypeSymbol.TypeArguments[0], (type as NullableTypeSyntax)?.ElementType); + builder.Append("?"); + return true; + } else { if (AppendQualifiedSymbolName(builder, symbol.ContainingSymbol, (type as QualifiedNameSyntax)?.Left)) @@ -113,14 +123,17 @@ private static bool AppendQualifiedSymbolName(StringBuilder builder, ISymbol sym for (int i = 0; i < arguments.Length; i++) { var argument = arguments[i]; - var argumentType = argumentTypes.Arguments.Count > i ? argumentTypes.Arguments[i] : null; + var argumentType = argumentTypes != null && argumentTypes.Arguments.Count > i ? argumentTypes.Arguments[i] : null; if (i > 0) { builder.Append(GenericSeparator); } - AppendQualifiedSymbolName(builder, argument, argumentType); + if (!argumentType.IsKind(SyntaxKind.OmittedTypeArgument)) + { + AppendQualifiedSymbolName(builder, argument, argumentType); + } } builder.Append(GenericTypeParametersClose); @@ -130,8 +143,13 @@ private static bool AppendQualifiedSymbolName(StringBuilder builder, ISymbol sym } default: - builder.Append(symbol.Name); - return true; + if (symbol != null) + { + builder.Append(symbol.Name); + return true; + } + + return false; } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1135UsingDirectivesMustBeQualified.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1135UsingDirectivesMustBeQualified.cs index 130b1cc81..cddfef1ed 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1135UsingDirectivesMustBeQualified.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1135UsingDirectivesMustBeQualified.cs @@ -3,7 +3,6 @@ namespace StyleCop.Analyzers.ReadabilityRules { - using System; using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; @@ -215,19 +214,6 @@ private static bool AppendCanonicalString(StringBuilder builder, TypeSyntax type builder.Append(")"); return true; } - else if (RefTypeSyntaxWrapper.IsInstance(type)) - { - var refType = (RefTypeSyntaxWrapper)type; - builder.Append(refType.RefKeyword.Text); - if (refType.ReadOnlyKeyword.IsKind(SyntaxKind.ReadOnlyKeyword)) - { - builder.Append(" ").Append(refType.ReadOnlyKeyword.Text); - } - - builder.Append(" "); - AppendCanonicalString(builder, refType.Type); - return true; - } else { return false;