Example #1
0
        private static void ApplyEdit(
            SyntaxEditor editor, SemanticModel semanticModel, INamedTypeSymbol expressionTypeOpt,
            ISyntaxFactsService syntaxFacts, ISemanticFactsService semanticFacts,
            Diagnostic diagnostic, CancellationToken cancellationToken)
        {
            var root = editor.OriginalRoot;
            var conditionalExpression = root.FindNode(diagnostic.AdditionalLocations[0].SourceSpan, getInnermostNodeForTie: true);
            var conditionalPartHigh   = root.FindNode(diagnostic.AdditionalLocations[1].SourceSpan);
            var whenPart = root.FindNode(diagnostic.AdditionalLocations[2].SourceSpan);

            syntaxFacts.GetPartsOfConditionalExpression(
                conditionalExpression, out var condition, out var whenTrue, out var whenFalse);

            var conditionalPartLow = syntaxFacts.WalkDownParentheses(conditionalPartHigh);

            editor.ReplaceNode(conditionalExpression,
                               (c, g) =>
            {
                syntaxFacts.GetPartsOfConditionalExpression(
                    c, out var currentCondition, out var currentWhenTrue, out var currentWhenFalse);

                var coalesceExpression = GetCoalesceExpression(
                    syntaxFacts, g, whenPart, whenTrue, conditionalPartLow,
                    currentWhenTrue, currentWhenFalse);

                if (semanticFacts.IsInExpressionTree(
                        semanticModel, conditionalExpression, expressionTypeOpt, cancellationToken))
                {
                    coalesceExpression = coalesceExpression.WithAdditionalAnnotations(
                        WarningAnnotation.Create(FeaturesResources.Changes_to_expression_trees_may_result_in_behavior_changes_at_runtime));
                }

                return(coalesceExpression.WithAdditionalAnnotations(Formatter.Annotation));
            });
Example #2
0
            private ExpressionSyntax SimplifyInvocation(InvocationExpressionSyntax invocation)
            {
                var expression = invocation.Expression;

                if (expression is MemberAccessExpressionSyntax memberAccess)
                {
                    var symbolMap = SemanticMap.From(
                        _document.SemanticModel,
                        memberAccess.Expression,
                        _cancellationToken
                        );
                    var anySideEffects = symbolMap.AllReferencedSymbols.Any(
                        s => s.Kind == SymbolKind.Method || s.Kind == SymbolKind.Property
                        );

                    if (anySideEffects)
                    {
                        var annotation = WarningAnnotation.Create(
                            "Warning: Expression may have side effects. Code meaning may change."
                            );
                        expression = expression.ReplaceNode(
                            memberAccess.Expression,
                            memberAccess.Expression.WithAdditionalAnnotations(annotation)
                            );
                    }
                }

                return(expression.Parenthesize().WithAdditionalAnnotations(Formatter.Annotation));
            }
Example #3
0
        private async Task <Document> InlineTemporaryAsync(Document document, VariableDeclaratorSyntax declarator, CancellationToken cancellationToken)
        {
            var workspace = document.Project.Solution.Workspace;

            // Annotate the variable declarator so that we can get back to it later.
            var updatedDocument = await document.ReplaceNodeAsync(declarator, declarator.WithAdditionalAnnotations(DefinitionAnnotation), cancellationToken).ConfigureAwait(false);

            var semanticModel = await updatedDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            var variableDeclarator = await FindDeclaratorAsync(updatedDocument, cancellationToken).ConfigureAwait(false);

            // Create the expression that we're actually going to inline.
            var expressionToInline = await CreateExpressionToInlineAsync(variableDeclarator, updatedDocument, cancellationToken).ConfigureAwait(false);

            // Collect the identifier names for each reference.
            var local      = (ILocalSymbol)semanticModel.GetDeclaredSymbol(variableDeclarator, cancellationToken);
            var symbolRefs = await SymbolFinder.FindReferencesAsync(local, updatedDocument.Project.Solution, cancellationToken).ConfigureAwait(false);

            var references = symbolRefs.Single(r => Equals(r.Definition, local)).Locations;
            var syntaxRoot = await updatedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            // Collect the topmost parenting expression for each reference.
            var nonConflictingIdentifierNodes = references
                                                .Select(loc => (IdentifierNameSyntax)syntaxRoot.FindToken(loc.Location.SourceSpan.Start).Parent)
                                                .Where(ident => !HasConflict(ident, variableDeclarator));

            // Add referenceAnnotations to identifier nodes being replaced.
            updatedDocument = await updatedDocument.ReplaceNodesAsync(
                nonConflictingIdentifierNodes,
                (o, n) => n.WithAdditionalAnnotations(ReferenceAnnotation),
                cancellationToken).ConfigureAwait(false);

            semanticModel = await updatedDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            variableDeclarator = await FindDeclaratorAsync(updatedDocument, cancellationToken).ConfigureAwait(false);

            // Get the annotated reference nodes.
            nonConflictingIdentifierNodes = await FindReferenceAnnotatedNodesAsync(updatedDocument, cancellationToken).ConfigureAwait(false);

            var topmostParentingExpressions = nonConflictingIdentifierNodes
                                              .Select(ident => GetTopMostParentingExpression(ident))
                                              .Distinct().ToList();

            var originalInitializerSymbolInfo = semanticModel.GetSymbolInfo(variableDeclarator.Initializer.Value, cancellationToken);

            // Make each topmost parenting statement or Equals Clause Expressions semantically explicit.
            updatedDocument = await updatedDocument.ReplaceNodesAsync(topmostParentingExpressions, (o, n) =>
            {
                var node = Simplifier.Expand(n, semanticModel, workspace, cancellationToken: cancellationToken);

                // warn when inlining into a conditional expression, as the inlined expression will not be executed.
                if (semanticModel.GetSymbolInfo(o).Symbol is IMethodSymbol {
                    IsConditional : true
                })
                {
                    node = node.WithAdditionalAnnotations(
                        WarningAnnotation.Create(CSharpFeaturesResources.Warning_Inlining_temporary_into_conditional_method_call));
                }
                return(node);
            }, cancellationToken).ConfigureAwait(false);
        private static async Task <Document> GetTransformedDocumentAsync(
            Document document,
            CompilationUnitSyntax compilationUnit,
            IEnumerable <UsingDirectiveSyntax> allUsingDirectives,
            AddImportPlacement placement,
            CancellationToken cancellationToken)
        {
            var bannerService = document.GetRequiredLanguageService <IFileBannerFactsService>();

            // Expand usings so that they can be properly simplified after they are relocated.
            var compilationUnitWithExpandedUsings = await ExpandUsingDirectivesAsync(document, compilationUnit, allUsingDirectives, cancellationToken).ConfigureAwait(false);

            // Remove the file header from the compilation unit so that we do not lose it when making changes to usings.
            var(compilationUnitWithoutHeader, fileHeader) = RemoveFileHeader(compilationUnitWithExpandedUsings, bannerService);

            // A blanket warning that this codefix may change code so that it does not compile.
            var warningAnnotation = WarningAnnotation.Create(CSharpAnalyzersResources.Warning_colon_Moving_using_directives_may_change_code_meaning);

            var newCompilationUnit = placement == AddImportPlacement.InsideNamespace
                ? MoveUsingsInsideNamespace(compilationUnitWithoutHeader, warningAnnotation)
                : MoveUsingsOutsideNamespaces(compilationUnitWithoutHeader, warningAnnotation);

            // Re-attach the header now that using have been moved and LeadingTrivia is no longer being altered.
            var newCompilationUnitWithHeader = AddFileHeader(newCompilationUnit, fileHeader);
            var newDocument = document.WithSyntaxRoot(newCompilationUnitWithHeader);

            // Simplify usings now that they have been moved and are in the proper context.
            var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);

            return(await Simplifier.ReduceAsync(newDocument, Simplifier.Annotation, options, cancellationToken).ConfigureAwait(false));
        }
Example #5
0
        private async Task <Document> MoveDeclarationNearReferenceAsync(
            Document document, State state, SyntaxNode root, CancellationToken cancellationToken)
        {
            var editor = new SyntaxEditor(root, document.Project.Solution.Workspace);

            var crossesMeaningfulBlock = CrossesMeaningfulBlock(state);
            var warningAnnotation      = crossesMeaningfulBlock
                ? WarningAnnotation.Create(FeaturesResources.Warning_colon_Declaration_changes_scope_and_may_change_meaning)
                : null;

            editor.RemoveNode(state.DeclarationStatement);

            var canMergeDeclarationAndAssignment = await CanMergeDeclarationAndAssignmentAsync(document, state, cancellationToken).ConfigureAwait(false);

            if (canMergeDeclarationAndAssignment)
            {
                MergeDeclarationAndAssignment(
                    document, state, editor, warningAnnotation);
            }
            else
            {
                await MoveDeclarationToFirstReferenceAsync(
                    document, state, editor, warningAnnotation, cancellationToken).ConfigureAwait(false);
            }

            var newRoot = editor.GetChangedRoot();

            return(document.WithSyntaxRoot(newRoot));
        }
        private async Task <Document> ConvertToGeneratedDllImport(
            Document doc,
            MethodDeclarationSyntax methodSyntax,
            IMethodSymbol methodSymbol,
            AttributeData dllImportAttr,
            INamedTypeSymbol generatedDllImportAttrType,
            char?entryPointSuffix,
            CancellationToken cancellationToken)
        {
            DocumentEditor editor = await DocumentEditor.CreateAsync(doc, cancellationToken).ConfigureAwait(false);

            SyntaxGenerator generator = editor.Generator;

            var dllImportSyntax = (AttributeSyntax)dllImportAttr !.ApplicationSyntaxReference !.GetSyntax(cancellationToken);

            // Create GeneratedDllImport attribute based on the DllImport attribute
            SyntaxNode generatedDllImportSyntax = GetGeneratedDllImportAttribute(
                editor,
                generator,
                dllImportSyntax,
                methodSymbol.GetDllImportData() !,
                generatedDllImportAttrType,
                methodSymbol.Name,
                entryPointSuffix,
                out SyntaxNode? unmanagedCallConvAttributeMaybe);

            // Add annotation about potential behavioural and compatibility changes
            generatedDllImportSyntax = generatedDllImportSyntax.WithAdditionalAnnotations(
                WarningAnnotation.Create(string.Format(Resources.ConvertToGeneratedDllImportWarning, "[TODO] Documentation link")));

            // Replace DllImport with GeneratedDllImport
            SyntaxNode generatedDeclaration = generator.ReplaceNode(methodSyntax, dllImportSyntax, generatedDllImportSyntax);

            if (!methodSymbol.MethodImplementationFlags.HasFlag(System.Reflection.MethodImplAttributes.PreserveSig))
            {
                generatedDeclaration = await RemoveNoPreserveSigTransform(editor, generatedDeclaration, methodSymbol, cancellationToken).ConfigureAwait(false);
            }

            if (unmanagedCallConvAttributeMaybe is not null)
            {
                generatedDeclaration = generator.AddAttributes(generatedDeclaration, unmanagedCallConvAttributeMaybe);
            }

            // Replace extern keyword with partial keyword
            generatedDeclaration = generator.WithModifiers(
                generatedDeclaration,
                generator.GetModifiers(methodSyntax)
                .WithIsExtern(false)
                .WithPartial(true));

            // Replace the original method with the updated one
            editor.ReplaceNode(methodSyntax, generatedDeclaration);

            return(editor.GetChangedDocument());
        }
Example #7
0
        private static async Task <Document> AddWarningAnnotation(Document document, ISymbol symbolFromEarlierSnapshot, CancellationToken cancellationToken)
        {
            var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var fixedDeclaration = root.DescendantNodes().Single(n => n.HasAnnotation(s_annotationForFixedDeclaration));
            var annotation       = WarningAnnotation.Create(string.Format(MicrosoftQualityGuidelinesAnalyzersResources.MarkMembersAsStaticCodeFix_WarningAnnotation, symbolFromEarlierSnapshot.Name));

            return(document.WithSyntaxRoot(root.ReplaceNode(fixedDeclaration, fixedDeclaration.WithAdditionalAnnotations(annotation))));
        }
Example #8
0
        private async Task <Document> MoveDeclarationNearReferenceAsync(Document document, State state, CancellationToken cancellationToken)
        {
            var innermostStatements =
                state.InnermostBlock.Statements.Where(s => s != state.DeclarationStatement).ToList();
            var innermostAffectedIndex = innermostStatements.IndexOf(state.FirstStatementAffectedInInnermostBlock);

            var crossesMeaningfulBlock = CrossesMeaningfulBlock(state);
            var warningAnnotation      = crossesMeaningfulBlock
                ? WarningAnnotation.Create(CSharpFeaturesResources.Warning_colon_Declaration_changes_scope_and_may_change_meaning)
                : null;

            var canMergeDeclarationAndAssignment = await CanMergeDeclarationAndAssignmentAsync(document, state, cancellationToken).ConfigureAwait(false);

            if (canMergeDeclarationAndAssignment)
            {
                // Replace the first reference with a new declaration.
                var declarationStatement = CreateMergedDeclarationStatement(state, state.FirstStatementAffectedInInnermostBlock);
                declarationStatement = warningAnnotation == null
                    ? declarationStatement
                    : declarationStatement.WithAdditionalAnnotations(warningAnnotation);

                innermostStatements[innermostAffectedIndex] = declarationStatement.WithAdditionalAnnotations(Formatter.Annotation);
            }
            else
            {
                // If we're not merging with an existing declaration, make the declaration semantically
                // explicit to improve the chances that it won't break code.
                var explicitDeclarationStatement = await Simplifier.ExpandAsync(state.DeclarationStatement, document, cancellationToken : cancellationToken).ConfigureAwait(false);

                // place the declaration above the first statement that references it.
                var declarationStatement = warningAnnotation == null
                    ? explicitDeclarationStatement
                    : explicitDeclarationStatement.WithAdditionalAnnotations(warningAnnotation);

                innermostStatements.Insert(innermostAffectedIndex, declarationStatement.WithAdditionalAnnotations(Formatter.Annotation));
            }

            var newInnermostBlock = state.InnermostBlock.WithStatements(
                SyntaxFactory.List <StatementSyntax>(innermostStatements)).WithAdditionalAnnotations(Formatter.Annotation);

            var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);

            var rewriter = new Rewriter(state.InnermostBlock, newInnermostBlock, state.OutermostBlock, state.DeclarationStatement);
            var newRoot  = rewriter.Visit(tree.GetRoot(cancellationToken));

            return(document.WithSyntaxRoot(newRoot));
        }
Example #9
0
        public async Task <Document> MoveDeclarationNearReferenceAsync(
            Document document, SyntaxNode localDeclarationStatement, CancellationToken cancellationToken)
        {
            var state = await ComputeStateAsync(document, localDeclarationStatement, cancellationToken).ConfigureAwait(false);

            if (state == null)
            {
                return(document);
            }

            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var editor = new SyntaxEditor(root, document.Project.Solution.Workspace.Services);

            var crossesMeaningfulBlock = CrossesMeaningfulBlock(state);
            var warningAnnotation      = crossesMeaningfulBlock
                ? WarningAnnotation.Create(WorkspaceExtensionsResources.Warning_colon_Declaration_changes_scope_and_may_change_meaning)
                : null;

            var canMergeDeclarationAndAssignment = await CanMergeDeclarationAndAssignmentAsync(document, state, cancellationToken).ConfigureAwait(false);

            if (canMergeDeclarationAndAssignment)
            {
                editor.RemoveNode(state.DeclarationStatement);
                MergeDeclarationAndAssignment(
                    document, state, editor, warningAnnotation);
            }
            else
            {
                var statementIndex = state.OutermostBlockStatements.IndexOf(state.DeclarationStatement);
                if (statementIndex + 1 < state.OutermostBlockStatements.Count &&
                    state.OutermostBlockStatements[statementIndex + 1] == state.FirstStatementAffectedInInnermostBlock)
                {
                    // Already at the correct location.
                    return(document);
                }

                editor.RemoveNode(state.DeclarationStatement);
                await MoveDeclarationToFirstReferenceAsync(
                    document, state, editor, warningAnnotation, cancellationToken).ConfigureAwait(false);
            }

            var newRoot = editor.GetChangedRoot();

            return(document.WithSyntaxRoot(newRoot));
        }
            private ExpressionSyntax SimplifyInvocation(InvocationExpressionSyntax invocation)
            {
                var expression = invocation.Expression;

                if (expression is MemberAccessExpressionSyntax memberAccess)
                {
                    var symbolMap      = SemanticMap.From(_document.SemanticModel, memberAccess.Expression, _cancellationToken);
                    var anySideEffects = symbolMap.AllReferencedSymbols.Any(s =>
                                                                            s.Kind is SymbolKind.Method or SymbolKind.Property);

                    if (anySideEffects)
                    {
                        var annotation = WarningAnnotation.Create(CSharpFeaturesResources.Warning_Expression_may_change_code_meaning);
                        expression = expression.ReplaceNode(memberAccess.Expression, memberAccess.Expression.WithAdditionalAnnotations(annotation));
                    }
                }

                return(expression.Parenthesize()
                       .WithAdditionalAnnotations(Formatter.Annotation));
            }
Example #11
0
            public override SyntaxNode VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
            {
                node = (MemberAccessExpressionSyntax)(base.VisitMemberAccessExpression(node) ?? throw ExceptionUtilities.Unreachable);

                if (_extensionMethods.Contains(node.Name.Identifier.Text))
                {
                    // If an extension method is used as a delegate rather than invoked directly,
                    // there is no semantically valid transformation that will fully qualify the extension method.
                    // For example `Func<int> f = x.M;` is not the same as Func<int> f = () => Extensions.M(x);`
                    // since one captures x by value, and the other by reference.
                    //
                    // We will not visit this node if the parent node was an InvocationExpression,
                    // since we would have expanded the parent node entirely, rather than visiting it.
                    // Therefore it's possible that this is an extension method being used as a delegate so we warn.
                    node = node.WithAdditionalAnnotations(WarningAnnotation.Create(string.Format(
                                                                                       WorkspacesResources.Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access,
                                                                                       node.Name.Identifier.Text)));
                }

                return(node);
            }
Example #12
0
        public async Task TestWarnsWithMatchingExtensionMethodUsedAsDelegate(bool useSymbolAnnotations)
        {
            var source = @"using System;
using B;

namespace A
{
    static class AExtensions
    {
        public static void M(this int a){}
    }
    public class C1 {}
}

namespace B
{
    static class BExtensions
    {
        public static void M(this object a){}
    }
}

class C
{
    Action M(A.C1 c1) => 42.M;
}";

            await TestAsync(
                source,
                @"using System;
using A;
using B;

namespace A
{
    static class AExtensions
    {
        public static void M(this int a){}
    }
    public class C1 {}
}

namespace B
{
    static class BExtensions
    {
        public static void M(this object a){}
    }
}

class C
{
    Action M(A.C1 c1) => 42.M;
}",

                @"using System;
using A;
using B;

namespace A
{
    static class AExtensions
    {
        public static void M(this int a){}
    }
    public class C1 {}
}

namespace B
{
    static class BExtensions
    {
        public static void M(this object a){}
    }
}

class C
{
    Action M(C1 c1) => 42.M;
}", safe : true, useSymbolAnnotations);

            var doc = await GetDocument(source, useSymbolAnnotations);

            OptionSet options = await doc.GetOptionsAsync();

            var imported = await ImportAdder.AddImportsFromSyntaxesAsync(doc, true, options);

            var root = await imported.GetSyntaxRootAsync();

            var nodeWithWarning = root.GetAnnotatedNodes(WarningAnnotation.Kind).Single();

            Assert.Equal("42.M", nodeWithWarning.ToFullString());

            var warning = nodeWithWarning.GetAnnotations(WarningAnnotation.Kind).Single();
            var expectedWarningMessage = string.Format(WorkspacesResources.Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access, "M");

            Assert.Equal(expectedWarningMessage, WarningAnnotation.GetDescription(warning));
        }
        public static async Task MakeLocalFunctionStaticAsync(
            Document document,
            LocalFunctionStatementSyntax localFunction,
            ImmutableArray <ISymbol> captures,
            SyntaxEditor syntaxEditor,
            CodeGenerationOptionsProvider fallbackOptions,
            CancellationToken cancellationToken)
        {
            var root                = (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false)) !;
            var semanticModel       = (await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false)) !;
            var localFunctionSymbol = semanticModel.GetDeclaredSymbol(localFunction, cancellationToken);

            Contract.ThrowIfNull(localFunctionSymbol, "We should have gotten a method symbol for a local function.");
            var documentImmutableSet = ImmutableHashSet.Create(document);

            // Finds all the call sites of the local function
            var referencedSymbols = await SymbolFinder.FindReferencesAsync(
                localFunctionSymbol, document.Project.Solution, documentImmutableSet, cancellationToken).ConfigureAwait(false);

            // Now we need to find all the references to the local function that we might need to fix.
            var shouldWarn = false;

            using var builderDisposer = ArrayBuilder <InvocationExpressionSyntax> .GetInstance(out var invocations);

            foreach (var referencedSymbol in referencedSymbols)
            {
                foreach (var location in referencedSymbol.Locations)
                {
                    // We limited the search scope to the single document,
                    // so all reference should be in the same tree.
                    var referenceNode = root.FindNode(location.Location.SourceSpan);
                    if (referenceNode is not IdentifierNameSyntax identifierNode)
                    {
                        // Unexpected scenario, skip and warn.
                        shouldWarn = true;
                        continue;
                    }

                    if (identifierNode.Parent is InvocationExpressionSyntax invocation)
                    {
                        invocations.Add(invocation);
                    }
                    else
                    {
                        // We won't be able to fix non-invocation references,
                        // e.g. creating a delegate.
                        shouldWarn = true;
                    }
                }
            }

            var parameterAndCapturedSymbols = CreateParameterSymbols(captures);

            // Fix all invocations by passing in additional arguments.
            foreach (var invocation in invocations)
            {
                syntaxEditor.ReplaceNode(
                    invocation,
                    (node, generator) =>
                {
                    var currentInvocation        = (InvocationExpressionSyntax)node;
                    var seenNamedArgument        = currentInvocation.ArgumentList.Arguments.Any(a => a.NameColon != null);
                    var seenDefaultArgumentValue = currentInvocation.ArgumentList.Arguments.Count < localFunction.ParameterList.Parameters.Count;

                    var newArguments = parameterAndCapturedSymbols.Select(
                        p => (ArgumentSyntax)generator.Argument(
                            seenNamedArgument || seenDefaultArgumentValue ? p.symbol.Name : null,
                            p.symbol.RefKind,
                            p.capture.Name.ToIdentifierName()));

                    var newArgList = currentInvocation.ArgumentList.WithArguments(currentInvocation.ArgumentList.Arguments.AddRange(newArguments));
                    return(currentInvocation.WithArgumentList(newArgList));
                });
            }

            // In case any of the captured variable isn't camel-cased,
            // we need to change the referenced name inside local function to use the new parameter's name.
            foreach (var(parameter, capture) in parameterAndCapturedSymbols)
            {
                if (parameter.Name == capture.Name)
                {
                    continue;
                }

                var referencedCaptureSymbols = await SymbolFinder.FindReferencesAsync(
                    capture, document.Project.Solution, documentImmutableSet, cancellationToken).ConfigureAwait(false);

                foreach (var referencedSymbol in referencedCaptureSymbols)
                {
                    foreach (var location in referencedSymbol.Locations)
                    {
                        var referenceSpan = location.Location.SourceSpan;
                        if (!localFunction.FullSpan.Contains(referenceSpan))
                        {
                            continue;
                        }

                        var referenceNode = root.FindNode(referenceSpan);
                        if (referenceNode is IdentifierNameSyntax identifierNode)
                        {
                            syntaxEditor.ReplaceNode(
                                identifierNode,
                                (node, generator) => generator.IdentifierName(parameter.Name.ToIdentifierToken()).WithTriviaFrom(node));
                        }
                    }
                }
            }

            var codeGenerator = document.GetRequiredLanguageService <ICodeGenerationService>();
            var options       = await document.GetCodeGenerationOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false);

            var info = options.GetInfo(CodeGenerationContext.Default, document.Project);

            // Updates the local function declaration with variables passed in as parameters
            syntaxEditor.ReplaceNode(
                localFunction,
                (node, generator) =>
            {
                var localFunctionWithNewParameters = codeGenerator.AddParameters(
                    node,
                    parameterAndCapturedSymbols.SelectAsArray(p => p.symbol),
                    info,
                    cancellationToken);

                if (shouldWarn)
                {
                    var annotation = WarningAnnotation.Create(CSharpCodeFixesResources.Warning_colon_Adding_parameters_to_local_function_declaration_may_produce_invalid_code);
                    localFunctionWithNewParameters = localFunctionWithNewParameters.WithAdditionalAnnotations(annotation);
                }

                return(AddStaticModifier(localFunctionWithNewParameters, CSharpSyntaxGenerator.Instance));
            });
        }
Example #14
0
        private async Task <Document> ConvertToGeneratedDllImport(
            Document doc,
            MethodDeclarationSyntax methodSyntax,
            IMethodSymbol methodSymbol,
            AttributeData dllImportAttr,
            INamedTypeSymbol generatedDllImportAttrType,
            bool usePreprocessorDefines,
            CancellationToken cancellationToken)
        {
            DocumentEditor editor = await DocumentEditor.CreateAsync(doc, cancellationToken).ConfigureAwait(false);

            SyntaxGenerator generator = editor.Generator;

            var dllImportSyntax = (AttributeSyntax)dllImportAttr !.ApplicationSyntaxReference !.GetSyntax(cancellationToken);

            // Create GeneratedDllImport attribute based on the DllImport attribute
            SyntaxNode generatedDllImportSyntax = GetGeneratedDllImportAttribute(
                editor,
                generator,
                dllImportSyntax,
                methodSymbol.GetDllImportData() !,
                generatedDllImportAttrType,
                out SyntaxNode? unmanagedCallConvAttributeMaybe);

            // Add annotation about potential behavioural and compatibility changes
            generatedDllImportSyntax = generatedDllImportSyntax.WithAdditionalAnnotations(
                WarningAnnotation.Create(string.Format(Resources.ConvertToGeneratedDllImportWarning, "[TODO] Documentation link")));

            // Replace DllImport with GeneratedDllImport
            SyntaxNode generatedDeclaration = generator.ReplaceNode(methodSyntax, dllImportSyntax, generatedDllImportSyntax);

            if (unmanagedCallConvAttributeMaybe is not null)
            {
                generatedDeclaration = generator.AddAttributes(generatedDeclaration, unmanagedCallConvAttributeMaybe);
            }

            // Replace extern keyword with partial keyword
            generatedDeclaration = generator.WithModifiers(
                generatedDeclaration,
                generator.GetModifiers(methodSyntax)
                .WithIsExtern(false)
                .WithPartial(true));

            if (!usePreprocessorDefines)
            {
                // Replace the original method with the updated one
                editor.ReplaceNode(methodSyntax, generatedDeclaration);
            }
            else
            {
                // #if DLLIMPORTGENERATOR_ENABLED
                generatedDeclaration = generatedDeclaration.WithLeadingTrivia(
                    generatedDeclaration.GetLeadingTrivia()
                    .AddRange(new[] {
                    SyntaxFactory.Trivia(SyntaxFactory.IfDirectiveTrivia(SyntaxFactory.IdentifierName("DLLIMPORTGENERATOR_ENABLED"), isActive: true, branchTaken: true, conditionValue: true)),
                    SyntaxFactory.ElasticMarker
                }));

                // #else
                generatedDeclaration = generatedDeclaration.WithTrailingTrivia(
                    generatedDeclaration.GetTrailingTrivia()
                    .AddRange(new[] {
                    SyntaxFactory.Trivia(SyntaxFactory.ElseDirectiveTrivia(isActive: false, branchTaken: false)),
                    SyntaxFactory.ElasticMarker
                }));

                // Remove existing leading trivia - it will be on the GeneratedDllImport method
                MethodDeclarationSyntax updatedDeclaration = methodSyntax.WithLeadingTrivia();

                // #endif
                updatedDeclaration = updatedDeclaration.WithTrailingTrivia(
                    methodSyntax.GetTrailingTrivia()
                    .AddRange(new[] {
                    SyntaxFactory.Trivia(SyntaxFactory.EndIfDirectiveTrivia(isActive: true)),
                    SyntaxFactory.ElasticMarker
                }));

                // Add the GeneratedDllImport method
                editor.InsertBefore(methodSyntax, generatedDeclaration);

                // Replace the original method with the updated DllImport method
                editor.ReplaceNode(methodSyntax, updatedDeclaration);
            }

            return(editor.GetChangedDocument());
        }
Example #15
0
        public async Task TestWarnsWithMatchingExtensionMethodUsedAsDelegate(bool useSymbolAnnotations)
        {
            var source = @"using System;
using B;

namespace A
{
    static class AExtensions
    {
        public static void M(this int a){}
    }
    public class C1 {}
}

namespace B
{
    static class BExtensions
    {
        public static void M(this object a){}
    }
}

class C
{
    Action M(A.C1 c1) => 42.M;
}";

            await TestAsync(
                source,
                @"using System;
using A;
using B;

namespace A
{
    static class AExtensions
    {
        public static void M(this int a){}
    }
    public class C1 {}
}

namespace B
{
    static class BExtensions
    {
        public static void M(this object a){}
    }
}

class C
{
    Action M(A.C1 c1) => 42.M;
}",

                @"using System;
using A;
using B;

namespace A
{
    static class AExtensions
    {
        public static void M(this int a){}
    }
    public class C1 {}
}

namespace B
{
    static class BExtensions
    {
        public static void M(this object a){}
    }
}

class C
{
    Action M(C1 c1) => 42.M;
}", safe : true, useSymbolAnnotations);

            var doc = await GetDocument(source, useSymbolAnnotations);

            OptionSet options = await doc.GetOptionsAsync();

            var imported = await ImportAdder.AddImportsFromSyntaxesAsync(doc, true, options);

            var root = await imported.GetSyntaxRootAsync();

            var nodeWithWarning = root.GetAnnotatedNodes(WarningAnnotation.Kind).Single();

            Assert.Equal("42.M", nodeWithWarning.ToFullString());

            var warning = nodeWithWarning.GetAnnotations(WarningAnnotation.Kind).Single();

            Assert.Equal("Adding imports will bring an extension method into scope with the same name as 'M'", WarningAnnotation.GetDescription(warning));
        }
Example #16
0
        private async Task <Document> ConvertToLibraryImport(
            Document doc,
            MethodDeclarationSyntax methodSyntax,
            IMethodSymbol methodSymbol,
            AttributeData dllImportAttr,
            INamedTypeSymbol generatedDllImportAttrType,
            char?entryPointSuffix,
            CancellationToken cancellationToken)
        {
            DocumentEditor editor = await DocumentEditor.CreateAsync(doc, cancellationToken).ConfigureAwait(false);

            SyntaxGenerator generator = editor.Generator;

            var dllImportSyntax = (AttributeSyntax)await dllImportAttr !.ApplicationSyntaxReference !.GetSyntaxAsync(cancellationToken).ConfigureAwait(false);

            // Create GeneratedDllImport attribute based on the DllImport attribute
            SyntaxNode generatedDllImportSyntax = GetGeneratedDllImportAttribute(
                editor,
                generator,
                dllImportSyntax,
                methodSymbol,
                generatedDllImportAttrType,
                entryPointSuffix,
                out SyntaxNode? unmanagedCallConvAttributeMaybe);

            // Add annotation about potential behavioural and compatibility changes
            generatedDllImportSyntax = generatedDllImportSyntax.WithAdditionalAnnotations(
                WarningAnnotation.Create(string.Format(Resources.ConvertToLibraryImportWarning, "[TODO] Documentation link")));

            // Replace DllImport with GeneratedDllImport
            SyntaxNode generatedDeclaration = generator.ReplaceNode(methodSyntax, dllImportSyntax, generatedDllImportSyntax);

            if (!methodSymbol.MethodImplementationFlags.HasFlag(System.Reflection.MethodImplAttributes.PreserveSig))
            {
                generatedDeclaration = await RemoveNoPreserveSigTransform(editor, generatedDeclaration, methodSymbol, cancellationToken).ConfigureAwait(false);
            }

            if (unmanagedCallConvAttributeMaybe is not null)
            {
                generatedDeclaration = generator.AddAttributes(generatedDeclaration, unmanagedCallConvAttributeMaybe);
            }

            // Replace extern keyword with partial keyword
            generatedDeclaration = generator.WithModifiers(
                generatedDeclaration,
                generator.GetModifiers(methodSyntax)
                .WithIsExtern(false)
                .WithPartial(true));

            foreach (IParameterSymbol parameter in methodSymbol.Parameters)
            {
                if (parameter.Type.SpecialType == SpecialType.System_Boolean &&
                    !parameter.GetAttributes().Any(attr => attr.AttributeClass?.ToDisplayString() == TypeNames.System_Runtime_InteropServices_MarshalAsAttribute))
                {
                    MethodDeclarationSyntax generatedDeclarationSyntax = (MethodDeclarationSyntax)generatedDeclaration;
                    ParameterSyntax         generatedParameterSyntax   = generatedDeclarationSyntax.ParameterList.Parameters[parameter.Ordinal];
                    generatedDeclaration = generator.ReplaceNode(generatedDeclaration, generatedParameterSyntax, generator.AddAttributes(generatedParameterSyntax,
                                                                                                                                         GenerateMarshalAsUnmanagedTypeBoolAttribute(generator)));
                }
            }

            if (methodSymbol.ReturnType.SpecialType == SpecialType.System_Boolean &&
                !methodSymbol.GetReturnTypeAttributes().Any(attr => attr.AttributeClass?.ToDisplayString() == TypeNames.System_Runtime_InteropServices_MarshalAsAttribute))
            {
                generatedDeclaration = generator.AddReturnAttributes(generatedDeclaration,
                                                                     GenerateMarshalAsUnmanagedTypeBoolAttribute(generator));
            }

            // Replace the original method with the updated one
            editor.ReplaceNode(methodSyntax, generatedDeclaration);

            return(editor.GetChangedDocument());
        }
Example #17
0
 protected static SyntaxAnnotation CreatePossibleInvalidCodeWarning()
 {
     return(WarningAnnotation.Create(MicrosoftNetCoreAnalyzersResources.MakeMethodDeclaredOnImplementationTypeStaticMayProduceInvalidCode));
 }
Example #18
0
            protected override async Task <Document?> FixAllAsync(FixAllContext fixAllContext, Document document, ImmutableArray <Diagnostic> diagnostics)
            {
                DocumentEditor editor = await DocumentEditor.CreateAsync(document, fixAllContext.CancellationToken).ConfigureAwait(false);

                SyntaxGenerator generator = editor.Generator;

                SyntaxNode?root = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false);

                if (root == null)
                {
                    return(document);
                }

                foreach (Diagnostic diagnostic in diagnostics)
                {
                    // Get the syntax node tied to the diagnostic and check that it is a method declaration
                    if (root.FindNode(diagnostic.Location.SourceSpan) is not MethodDeclarationSyntax methodSyntax)
                    {
                        continue;
                    }
                    if (editor.SemanticModel.GetDeclaredSymbol(methodSyntax, fixAllContext.CancellationToken) is not IMethodSymbol methodSymbol)
                    {
                        continue;
                    }

                    SyntaxNode generatedDeclaration = await ConvertMethodDeclarationToLibraryImport(methodSyntax, editor, generator, methodSymbol, GetSuffixFromEquivalenceKey(fixAllContext.CodeActionEquivalenceKey), fixAllContext.CancellationToken).ConfigureAwait(false);

                    if (!methodSymbol.MethodImplementationFlags.HasFlag(System.Reflection.MethodImplAttributes.PreserveSig))
                    {
                        bool shouldWarn = await TransformCallersOfNoPreserveSigMethod(editor, methodSymbol, fixAllContext.CancellationToken).ConfigureAwait(false);

                        if (shouldWarn)
                        {
                            generatedDeclaration = generatedDeclaration.WithAdditionalAnnotations(WarningAnnotation.Create(SR.ConvertNoPreserveSigDllImportToGeneratedMayProduceInvalidCode));
                        }
                    }

                    // Replace the original method with the updated one
                    editor.ReplaceNode(methodSyntax, generatedDeclaration);

                    MakeEnclosingTypesPartial(editor, methodSyntax);
                }

                return(editor.GetChangedDocument());
            }
Example #19
0
        public override async Task <SelectionResult> GetValidSelectionAsync(CancellationToken cancellationToken)
        {
            if (!this.ContainsValidSelection)
            {
                return(NullSelection);
            }

            var text  = this.SemanticDocument.Text;
            var root  = this.SemanticDocument.Root;
            var model = this.SemanticDocument.SemanticModel;
            var doc   = this.SemanticDocument;

            // go through pipe line and calculate information about the user selection
            var selectionInfo = GetInitialSelectionInfo(root, text, cancellationToken);

            selectionInfo = AssignInitialFinalTokens(selectionInfo, root, cancellationToken);
            selectionInfo = AdjustFinalTokensBasedOnContext(selectionInfo, model, cancellationToken);
            selectionInfo = AssignFinalSpan(selectionInfo, text, cancellationToken);
            selectionInfo = ApplySpecialCases(selectionInfo, text, cancellationToken);
            selectionInfo = CheckErrorCasesAndAppendDescriptions(selectionInfo, root, model, cancellationToken);

            // there was a fatal error that we couldn't even do negative preview, return error result
            if (selectionInfo.Status.FailedWithNoBestEffortSuggestion())
            {
                return(new ErrorSelectionResult(selectionInfo.Status));
            }

            var controlFlowSpan = GetControlFlowSpan(selectionInfo);

            if (!selectionInfo.SelectionInExpression)
            {
                var statementRange = GetStatementRangeContainedInSpan <StatementSyntax>(root, controlFlowSpan, cancellationToken);
                if (statementRange == null)
                {
                    selectionInfo = selectionInfo.WithStatus(s => s.With(OperationStatusFlag.None, CSharpFeaturesResources.Can_t_determine_valid_range_of_statements_to_extract));
                    return(new ErrorSelectionResult(selectionInfo.Status));
                }

                var isFinalSpanSemanticallyValid = IsFinalSpanSemanticallyValidSpan(model, controlFlowSpan, statementRange, cancellationToken);
                if (!isFinalSpanSemanticallyValid)
                {
                    // check control flow only if we are extracting statement level, not expression
                    // level. you can not have goto that moves control out of scope in expression level
                    // (even in lambda)
                    selectionInfo = selectionInfo.WithStatus(s => s.With(OperationStatusFlag.BestEffort, CSharpFeaturesResources.Not_all_code_paths_return));
                }
            }

            // Warn if local functions are in selection since data flow analysis
            // cannot correctly analyze them
            // https://github.com/dotnet/roslyn/issues/14214
            if (SpanInvolvesLocalFunction(selectionInfo.FinalSpan, model, root))
            {
                selectionInfo = selectionInfo.WithStatus(s => s.With(
                                                             OperationStatusFlag.Succeeded | OperationStatusFlag.BestEffort,
                                                             CSharpFeaturesResources.Warning_Extracting_a_local_function_reference_may_produce_invalid_code));
                var commonRoot = selectionInfo.CommonRootFromOriginalSpan;
                var annotated  = commonRoot.WithAdditionalAnnotations(
                    WarningAnnotation.Create(CSharpFeaturesResources.Warning_Extracting_a_local_function_reference_may_produce_invalid_code));
                doc = await doc.WithSyntaxRootAsync(
                    root.ReplaceNode(commonRoot, annotated),
                    cancellationToken).ConfigureAwait(false);

                selectionInfo.FirstTokenInOriginalSpan = doc.Root.FindToken(selectionInfo.FirstTokenInOriginalSpan.SpanStart);
                selectionInfo.LastTokenInOriginalSpan  = doc.Root.FindToken(selectionInfo.LastTokenInOriginalSpan.SpanStart);
                selectionInfo.FirstTokenInFinalSpan    = doc.Root.FindToken(selectionInfo.FirstTokenInFinalSpan.SpanStart);
                selectionInfo.LastTokenInFinalSpan     = doc.Root.FindToken(selectionInfo.LastTokenInFinalSpan.SpanStart);
            }

            return(await CSharpSelectionResult.CreateAsync(
                       selectionInfo.Status,
                       selectionInfo.OriginalSpan,
                       selectionInfo.FinalSpan,
                       this.Options,
                       selectionInfo.SelectionInExpression,
                       doc,
                       selectionInfo.FirstTokenInFinalSpan,
                       selectionInfo.LastTokenInFinalSpan,
                       cancellationToken).ConfigureAwait(false));
        }
Example #20
0
 protected SyntaxAnnotation CreateWarningAnnotation()
 => WarningAnnotation.Create(FeaturesResources.Warning_colon_semantics_may_change_when_converting_statement);
        private static async Task <Document> InlineTemporaryAsync(Document document, VariableDeclaratorSyntax declarator, CancellationToken cancellationToken)
        {
            var workspace = document.Project.Solution.Workspace;

            // Annotate the variable declarator so that we can get back to it later.
            var updatedDocument = await document.ReplaceNodeAsync(declarator, declarator.WithAdditionalAnnotations(DefinitionAnnotation), cancellationToken).ConfigureAwait(false);

            var semanticModel = await updatedDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            var variableDeclarator = await FindDeclaratorAsync(updatedDocument, cancellationToken).ConfigureAwait(false);

            // Create the expression that we're actually going to inline.
            var expressionToInline = await CreateExpressionToInlineAsync(variableDeclarator, updatedDocument, cancellationToken).ConfigureAwait(false);

            // Collect the identifier names for each reference.
            var local      = (ILocalSymbol)semanticModel.GetDeclaredSymbol(variableDeclarator, cancellationToken);
            var symbolRefs = await SymbolFinder.FindReferencesAsync(local, updatedDocument.Project.Solution, cancellationToken).ConfigureAwait(false);

            var referencedSymbol = symbolRefs.SingleOrDefault(r => Equals(r.Definition, local));
            var references       = referencedSymbol == null?SpecializedCollections.EmptyEnumerable <ReferenceLocation>() : referencedSymbol.Locations;

            var syntaxRoot = await updatedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            // Collect the topmost parenting expression for each reference.
            var nonConflictingIdentifierNodes = references
                                                .Select(loc => (IdentifierNameSyntax)syntaxRoot.FindToken(loc.Location.SourceSpan.Start).Parent)
                                                .Where(ident => !HasConflict(ident, variableDeclarator));

            // Add referenceAnnotations to identifier nodes being replaced.
            updatedDocument = await updatedDocument.ReplaceNodesAsync(
                nonConflictingIdentifierNodes,
                (o, n) => n.WithAdditionalAnnotations(ReferenceAnnotation),
                cancellationToken).ConfigureAwait(false);

            semanticModel = await updatedDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            variableDeclarator = await FindDeclaratorAsync(updatedDocument, cancellationToken).ConfigureAwait(false);

            // Get the annotated reference nodes.
            nonConflictingIdentifierNodes = await FindReferenceAnnotatedNodesAsync(updatedDocument, cancellationToken).ConfigureAwait(false);

            var topmostParentingExpressions = nonConflictingIdentifierNodes
                                              .Select(ident => GetTopMostParentingExpression(ident))
                                              .Distinct().ToList();

            var originalInitializerSymbolInfo = semanticModel.GetSymbolInfo(variableDeclarator.Initializer.Value, cancellationToken);

            // Checks to see if inlining the temporary variable may change the code's meaning. This can only apply if the variable has two or more
            // references. We later use this heuristic to determine whether or not to display a warning message to the user.
            var mayContainSideEffects = references.Count() > 1 &&
                                        MayContainSideEffects(variableDeclarator.Initializer.Value);

            // Make each topmost parenting statement or Equals Clause Expressions semantically explicit.
            updatedDocument = await updatedDocument.ReplaceNodesAsync(topmostParentingExpressions, (o, n) =>
            {
                var node = Simplifier.Expand(n, semanticModel, workspace, cancellationToken: cancellationToken);

                // warn when inlining into a conditional expression, as the inlined expression will not be executed.
                if (semanticModel.GetSymbolInfo(o).Symbol is IMethodSymbol {
                    IsConditional : true
                })
                {
                    node = node.WithAdditionalAnnotations(
                        WarningAnnotation.Create(CSharpFeaturesResources.Warning_Inlining_temporary_into_conditional_method_call));
                }

                // If the refactoring may potentially change the code's semantics, display a warning message to the user.
                if (mayContainSideEffects)
                {
                    node = node.WithAdditionalAnnotations(
                        WarningAnnotation.Create(CSharpFeaturesResources.Warning_Inlining_temporary_variable_may_change_code_meaning));
                }

                return(node);
            }, cancellationToken).ConfigureAwait(false);