Skip to content

Commit

Permalink
handle params array in the argument list
Browse files Browse the repository at this point in the history
  • Loading branch information
jmarolf committed Aug 2, 2021
1 parent 87c20b9 commit 6b4eec3
Show file tree
Hide file tree
Showing 8 changed files with 745 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Analyzer.Utilities.Lightup;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.NetCore.Analyzers.Runtime;
using NullableAnnotation = Analyzer.Utilities.Lightup.NullableAnnotation;

namespace Microsoft.NetCore.CSharp.Analyzers.Runtime
{
[ExportCodeFixProvider(LanguageNames.CSharp)]
public sealed class CSharpForwardCancellationTokenToInvocationsFixer : ForwardCancellationTokenToInvocationsFixer
public sealed partial class CSharpForwardCancellationTokenToInvocationsFixer : ForwardCancellationTokenToInvocationsFixer<ArgumentSyntax>
{
protected override bool TryGetInvocation(
SemanticModel model,
Expand Down Expand Up @@ -46,18 +50,46 @@ protected override SyntaxNode GetConditionalOperationInvocationExpression(Syntax
protected override bool TryGetExpressionAndArguments(
SyntaxNode invocationNode,
[NotNullWhen(returnValue: true)] out SyntaxNode? expression,
out ImmutableArray<SyntaxNode> arguments)
out ImmutableArray<ArgumentSyntax> arguments)
{
if (invocationNode is InvocationExpressionSyntax invocationExpression)
{
expression = invocationExpression.Expression;
arguments = ImmutableArray.CreateRange<SyntaxNode>(invocationExpression.ArgumentList.Arguments);
arguments = invocationExpression.ArgumentList.Arguments.ToImmutableArray();
return true;
}

expression = null;
arguments = ImmutableArray<SyntaxNode>.Empty;
arguments = ImmutableArray<ArgumentSyntax>.Empty;
return false;
}


protected override SyntaxNode GetTypeSyntaxForArray(IArrayTypeSymbol type)
{
var typeName = Visitor.GenerateTypeSyntax(type.ElementType);
if (type.ElementType.IsReferenceType)
{
var additionalAnnotation = type.NullableAnnotation() switch
{
NullableAnnotation.None => NullableSyntaxAnnotationEx.Oblivious,
NullableAnnotation.Annotated => NullableSyntaxAnnotationEx.AnnotatedOrNotAnnotated,
NullableAnnotation.NotAnnotated => NullableSyntaxAnnotationEx.AnnotatedOrNotAnnotated,
_ => null,
};

if (additionalAnnotation is not null)
{
typeName = typeName.WithAdditionalAnnotations(additionalAnnotation);
}
}

return typeName;
}

protected override IEnumerable<SyntaxNode> GetExpressions(ImmutableArray<ArgumentSyntax> newArguments)
{
return newArguments.Select(x => x.Expression);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Analyzer.Utilities.Lightup;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Simplification;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using NullableAnnotation = Analyzer.Utilities.Lightup.NullableAnnotation;

namespace Microsoft.NetCore.CSharp.Analyzers.Runtime
{
public sealed partial class CSharpForwardCancellationTokenToInvocationsFixer
{
private class Visitor : SymbolVisitor<TypeSyntax>
{
public static TypeSyntax GenerateTypeSyntax(INamespaceOrTypeSymbol symbol)
{
return symbol.Accept(new Visitor()).WithAdditionalAnnotations(Simplifier.Annotation);
}

public override TypeSyntax DefaultVisit(ISymbol symbol)
=> throw new NotImplementedException();

public override TypeSyntax VisitAlias(IAliasSymbol symbol)
{
return AddInformationTo(ToIdentifierName(symbol.Name));
}

public override TypeSyntax VisitDynamicType(IDynamicTypeSymbol symbol)
{
return AddInformationTo(IdentifierName("dynamic"));
}

public override TypeSyntax VisitNamedType(INamedTypeSymbol symbol)
{
if (TryCreateNativeIntegerType(symbol, out var typeSyntax))
return typeSyntax;

typeSyntax = CreateSimpleTypeSyntax(symbol);
if (!(typeSyntax is SimpleNameSyntax))
return typeSyntax;

var simpleNameSyntax = (SimpleNameSyntax)typeSyntax;
if (symbol.ContainingType is not null)
{
if (symbol.ContainingType.TypeKind != TypeKind.Submission)
{
var containingTypeSyntax = symbol.ContainingType.Accept(this);
if (containingTypeSyntax is NameSyntax name)
{
typeSyntax = AddInformationTo(
QualifiedName(name, simpleNameSyntax));
}
else
{
typeSyntax = AddInformationTo(simpleNameSyntax);
}
}
}
else if (symbol.ContainingNamespace is not null)
{
if (symbol.ContainingNamespace.IsGlobalNamespace)
{
if (symbol.TypeKind != TypeKind.Error)
{
typeSyntax = AddGlobalAlias(simpleNameSyntax);
}
}
else
{
var container = symbol.ContainingNamespace.Accept(this)!;
typeSyntax = AddInformationTo(QualifiedName(
(NameSyntax)container,
simpleNameSyntax));
}
}

if (symbol.NullableAnnotation() == NullableAnnotation.Annotated &&
!symbol.IsValueType)
{
typeSyntax = AddInformationTo(NullableType(typeSyntax));
}

return typeSyntax;
}

public override TypeSyntax VisitNamespace(INamespaceSymbol symbol)
{
var syntax = AddInformationTo(ToIdentifierName(symbol.Name));
if (symbol.ContainingNamespace == null)
{
return syntax;
}

if (symbol.ContainingNamespace.IsGlobalNamespace)
{
return AddGlobalAlias(syntax);
}
else
{
var container = symbol.ContainingNamespace.Accept(this)!;
return AddInformationTo(QualifiedName(
(NameSyntax)container,
syntax));
}
}

public override TypeSyntax VisitTypeParameter(ITypeParameterSymbol symbol)
{
TypeSyntax typeSyntax = AddInformationTo(ToIdentifierName(symbol.Name));
if (symbol.NullableAnnotation() == NullableAnnotation.Annotated)
typeSyntax = AddInformationTo(NullableType(typeSyntax));

return typeSyntax;
}

private TypeSyntax CreateSimpleTypeSyntax(INamedTypeSymbol symbol)
{
if (symbol.IsTupleType && symbol.TupleUnderlyingType != null && !symbol.Equals(symbol.TupleUnderlyingType))
{
return CreateSimpleTypeSyntax(symbol.TupleUnderlyingType);
}

if (string.IsNullOrEmpty(symbol.Name) || symbol.IsAnonymousType)
{
return CreateSystemObject();
}

if (symbol.TypeParameters.Length == 0)
{
if (symbol.TypeKind == TypeKind.Error && symbol.Name == "var")
{
return CreateSystemObject();
}

return ToIdentifierName(symbol.Name);
}

var typeArguments = symbol.IsUnboundGenericType
? Enumerable.Repeat((TypeSyntax)OmittedTypeArgument(), symbol.TypeArguments.Length)
: symbol.TypeArguments.Select(t => GenerateTypeSyntax(t));

return GenericName(
ToIdentifierToken(symbol.Name),
TypeArgumentList(SeparatedList(typeArguments)));
}

private static QualifiedNameSyntax CreateSystemObject()
{
return QualifiedName(
AliasQualifiedName(
CreateGlobalIdentifier(),
IdentifierName("System")),
IdentifierName("Object"));
}

private static TTypeSyntax AddInformationTo<TTypeSyntax>(TTypeSyntax syntax)
where TTypeSyntax : TypeSyntax
{
syntax = syntax.WithLeadingTrivia(ElasticMarker).WithTrailingTrivia(ElasticMarker);
return syntax;
}

/// <summary>
/// We always unilaterally add "global::" to all named types/namespaces. This
/// will then be trimmed off if possible by the simplifier
/// </summary>
private static TypeSyntax AddGlobalAlias(SimpleNameSyntax syntax)
{
return AddInformationTo(AliasQualifiedName(CreateGlobalIdentifier(), syntax));
}

private static IdentifierNameSyntax ToIdentifierName(string identifier)
=> IdentifierName(ToIdentifierToken(identifier));

private static IdentifierNameSyntax CreateGlobalIdentifier()
=> IdentifierName(Token(SyntaxKind.GlobalKeyword));

private static bool TryCreateNativeIntegerType(INamedTypeSymbol symbol, [NotNullWhen(true)] out TypeSyntax? syntax)
{
if (symbol.IsNativeIntegerType())
{
syntax = IdentifierName(symbol.SpecialType == SpecialType.System_IntPtr ? "nint" : "nuint");
return true;
}

syntax = null;
return false;
}

private static SyntaxToken ToIdentifierToken(string identifier, bool isQueryContext = false)
{
var escaped = EscapeIdentifier(identifier, isQueryContext);

if (escaped.Length == 0 || escaped[0] != '@')
{
return Identifier(escaped);
}

var unescaped = identifier.StartsWith("@", StringComparison.Ordinal)
? identifier[1..]
: identifier;

var token = Identifier(
default, SyntaxKind.None, "@" + unescaped, unescaped, default);

if (!identifier.StartsWith("@", StringComparison.Ordinal))
{
token = token.WithAdditionalAnnotations(Simplifier.Annotation);
}

return token;
}

private static string EscapeIdentifier(string identifier, bool isQueryContext = false)
{
var nullIndex = identifier.IndexOf('\0');
if (nullIndex >= 0)
{
identifier = identifier.Substring(0, nullIndex);
}

var needsEscaping = SyntaxFacts.GetKeywordKind(identifier) != SyntaxKind.None;

// Check if we need to escape this contextual keyword
needsEscaping = needsEscaping || (isQueryContext && SyntaxFacts.IsQueryContextualKeyword(SyntaxFacts.GetContextualKeywordKind(identifier)));

return needsEscaping ? "@" + identifier : identifier;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Analyzer.Utilities;
Expand All @@ -13,7 +15,8 @@

namespace Microsoft.NetCore.Analyzers.Runtime
{
public abstract class ForwardCancellationTokenToInvocationsFixer : CodeFixProvider
public abstract class ForwardCancellationTokenToInvocationsFixer<TArgumentSyntax> : CodeFixProvider
where TArgumentSyntax : SyntaxNode
{
// Attempts to retrieve the invocation from the current operation.
protected abstract bool TryGetInvocation(
Expand All @@ -26,14 +29,17 @@ protected abstract bool TryGetInvocation(
protected abstract bool TryGetExpressionAndArguments(
SyntaxNode invocationNode,
[NotNullWhen(returnValue: true)] out SyntaxNode? expression,
out ImmutableArray<SyntaxNode> arguments);
out ImmutableArray<TArgumentSyntax> arguments);

// Verifies if the specified argument was passed with an explicit name.
protected abstract bool IsArgumentNamed(IArgumentOperation argumentOperation);

// Retrieves the invocation expression for a conditional operation, which consists of the dot and the method name.
protected abstract SyntaxNode GetConditionalOperationInvocationExpression(SyntaxNode invocationNode);

protected abstract SyntaxNode GetTypeSyntaxForArray(IArrayTypeSymbol type);
protected abstract IEnumerable<SyntaxNode> GetExpressions(ImmutableArray<TArgumentSyntax> newArguments);

public override ImmutableArray<string> FixableDiagnosticIds =>
ImmutableArray.Create(ForwardCancellationTokenToInvocationsAnalyzer.RuleId);

Expand Down Expand Up @@ -83,14 +89,15 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)

string title = MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToInvocationsTitle;

if (!TryGetExpressionAndArguments(invocation.Syntax, out SyntaxNode? expression, out ImmutableArray<SyntaxNode> newArguments))
if (!TryGetExpressionAndArguments(invocation.Syntax, out SyntaxNode? expression, out ImmutableArray<TArgumentSyntax> newArguments))
{
return;
}

var paramsArrayType = invocation.Arguments.SingleOrDefault(a => a.ArgumentKind == ArgumentKind.ParamArray)?.Value.Type as IArrayTypeSymbol;
Task<Document> CreateChangedDocumentAsync(CancellationToken _)
{
SyntaxNode newRoot = TryGenerateNewDocumentRoot(doc, root, invocation, argumentName, parameterName, expression, newArguments);
SyntaxNode newRoot = TryGenerateNewDocumentRoot(doc, root, invocation, argumentName, parameterName, expression, newArguments, paramsArrayType);
Document newDocument = doc.WithSyntaxRoot(newRoot);
return Task.FromResult(newDocument);
}
Expand All @@ -110,10 +117,25 @@ private static SyntaxNode TryGenerateNewDocumentRoot(
string invocationTokenArgumentName,
string ancestorTokenParameterName,
SyntaxNode expression,
ImmutableArray<SyntaxNode> newArguments)
ImmutableArray<TArgumentSyntax> currentArguments,
IArrayTypeSymbol? paramsArrayType)
{
SyntaxGenerator generator = SyntaxGenerator.GetGenerator(doc);

ImmutableArray<SyntaxNode> newArguments;
if (paramsArrayType is not null)
{
// current callsite is a params array, we need to wrap all these arguments to preserve sematnics
var typeSyntax = GetTypeSyntaxForArray(paramsArrayType);
var expressions = GetExpressions(currentArguments);
newArguments = ImmutableArray.Create(generator.ArrayCreationExpression(typeSyntax, expressions));
}
else
{
// not a params array just pass the existing arguments along
newArguments = currentArguments.CastArray<SyntaxNode>();
}

SyntaxNode identifier = generator.IdentifierName(invocationTokenArgumentName);
SyntaxNode cancellationTokenArgument;
if (!string.IsNullOrEmpty(ancestorTokenParameterName))
Expand Down
Loading

0 comments on commit 6b4eec3

Please sign in to comment.