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)); });
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)); }
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)); }
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()); }
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)))); }
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)); }
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)); }
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); }
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)); }); }
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()); }
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)); }
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()); }
protected static SyntaxAnnotation CreatePossibleInvalidCodeWarning() { return(WarningAnnotation.Create(MicrosoftNetCoreAnalyzersResources.MakeMethodDeclaredOnImplementationTypeStaticMayProduceInvalidCode)); }
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()); }
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)); }
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);