private async Task <Document> IntroduceLocalAsync( Document document, TExpressionStatementSyntax expressionStatement, CancellationToken cancellationToken) { var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var generator = SyntaxGenerator.GetGenerator(document); var syntaxFacts = document.GetLanguageService <ISyntaxFactsService>(); var expression = (TExpressionSyntax)syntaxFacts.GetExpressionOfExpressionStatement(expressionStatement); var nameToken = await GenerateUniqueNameAsync(document, expression, cancellationToken).ConfigureAwait(false); var type = semanticModel.GetTypeInfo(expression, cancellationToken).Type; var localDeclaration = (TLocalDeclarationStatementSyntax)generator.LocalDeclarationStatement( generator.TypeExpression(type), nameToken.WithAdditionalAnnotations(RenameAnnotation.Create()), expression.WithoutLeadingTrivia()); localDeclaration = localDeclaration.WithLeadingTrivia(expression.GetLeadingTrivia()); // Because expr-statements and local decl statements are so close, we can allow // each language to do a little extra work to ensure the resultant local decl // feels right. For example, C# will want to transport the semicolon from the // expr statement to the local decl if it has one. localDeclaration = FixupLocalDeclaration(expressionStatement, localDeclaration); var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var newRoot = root.ReplaceNode(expressionStatement, localDeclaration); return(document.WithSyntaxRoot(newRoot)); }
protected override async Task FixAllAsync( Document document, ImmutableArray <Diagnostic> diagnostics, SyntaxEditor editor, CancellationToken cancellationToken ) { // If we're only introducing one name, then add the rename annotation to // it so the user can pick a better name if they want. var annotation = diagnostics.Length == 1 ? RenameAnnotation.Create() : null; var semanticModel = await document .GetSemanticModelAsync(cancellationToken) .ConfigureAwait(false); foreach (var diagnostic in diagnostics) { await FixOneAsync( document, semanticModel, diagnostic, editor, annotation, cancellationToken ) .ConfigureAwait(false); } }
protected override async Task<Document> IntroduceLocalAsync( SemanticDocument document, ExpressionSyntax expression, bool allOccurrences, bool isConstant, CancellationToken cancellationToken) { var containerToGenerateInto = expression.Ancestors().FirstOrDefault(s => s is BlockSyntax || s is ArrowExpressionClauseSyntax || s is LambdaExpressionSyntax); var newLocalNameToken = GenerateUniqueLocalName( document, expression, isConstant, containerToGenerateInto, cancellationToken); var newLocalName = SyntaxFactory.IdentifierName(newLocalNameToken); var modifiers = isConstant ? SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.ConstKeyword)) : default; var declarationStatement = SyntaxFactory.LocalDeclarationStatement( modifiers, SyntaxFactory.VariableDeclaration( GetTypeSyntax(document, expression, cancellationToken), SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator( newLocalNameToken.WithAdditionalAnnotations(RenameAnnotation.Create()), null, SyntaxFactory.EqualsValueClause(expression.WithoutTrivia()))))); // If we're inserting into a multi-line parent, then add a newline after the local-var // we're adding. That way we don't end up having it and the starting statement be on // the same line (which will cause indentation to be computed incorrectly). var text = await document.Document.GetTextAsync(cancellationToken).ConfigureAwait(false); if (!text.AreOnSameLine(containerToGenerateInto.GetFirstToken(), containerToGenerateInto.GetLastToken())) { declarationStatement = declarationStatement.WithAppendedTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed); } switch (containerToGenerateInto) { case BlockSyntax block: return await IntroduceLocalDeclarationIntoBlockAsync( document, block, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken).ConfigureAwait(false); case ArrowExpressionClauseSyntax arrowExpression: // this will be null for expression-bodied properties & indexer (not for individual getters & setters, those do have a symbol), // both of which are a shorthand for the getter and always return a value var method = document.SemanticModel.GetDeclaredSymbol(arrowExpression.Parent) as IMethodSymbol; var createReturnStatement = !method?.ReturnsVoid ?? true; return RewriteExpressionBodiedMemberAndIntroduceLocalDeclaration( document, arrowExpression, expression, newLocalName, declarationStatement, allOccurrences, createReturnStatement, cancellationToken); case LambdaExpressionSyntax lambda: return IntroduceLocalDeclarationIntoLambda( document, lambda, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken); } throw new InvalidOperationException(); }
private void ReplaceWithObjectCreation( SyntaxEditor editor, INamedTypeSymbol classSymbol, TAnonymousObjectCreationExpressionSyntax startingCreationNode, TAnonymousObjectCreationExpressionSyntax childCreation) { // Use the callback form as anonymous types may be nested, and we want to // properly replace them even in that case. editor.ReplaceNode( childCreation, (currentNode, g) => { var currentAnonymousObject = (TAnonymousObjectCreationExpressionSyntax)currentNode; // If we hit the node the user started on, then add the rename annotation here. var className = classSymbol.Name; var classNameToken = startingCreationNode == childCreation ? g.Identifier(className).WithAdditionalAnnotations(RenameAnnotation.Create()) : g.Identifier(className); var classNameNode = classSymbol.TypeParameters.Length == 0 ? (TNameSyntax)g.IdentifierName(classNameToken) : (TNameSyntax)g.GenericName(classNameToken, classSymbol.TypeParameters.Select(tp => g.IdentifierName(tp.Name))); return(CreateObjectCreationExpression(classNameNode, currentAnonymousObject) .WithAdditionalAnnotations(Formatter.Annotation)); }); }
protected static async Task <SyntaxToken> GenerateUniqueNameAsync( Document document, TExpressionSyntax expression, CancellationToken cancellationToken ) { var semanticModel = await document .GetSemanticModelAsync(cancellationToken) .ConfigureAwait(false); var semanticFacts = document.GetLanguageService <ISemanticFactsService>(); var baseName = semanticFacts.GenerateNameForExpression( semanticModel, expression, capitalize: false, cancellationToken ); var uniqueName = semanticFacts .GenerateUniqueLocalName( semanticModel, expression, containerOpt: null, baseName, cancellationToken ) .WithAdditionalAnnotations(RenameAnnotation.Create()); return(uniqueName); }
private ClassDeclarationSyntax CreateTestClass(string name) { return(SyntaxFactory.ClassDeclaration( SyntaxFactory.Identifier(name + "_Should") .WithAdditionalAnnotations(RenameAnnotation.Create())) .WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword))) .AddAttributeLists(SyntaxFactory.AttributeList(SyntaxFactory.SeparatedList(new[] { SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("TestClass")) }))) .AddMembers(this.ScaffoldTestMethod())); }
private static async Task <Document> AddRenameAnnotationAsync(Document document, SyntaxToken invocationNameToken, CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var finalRoot = root.ReplaceToken( invocationNameToken, invocationNameToken.WithAdditionalAnnotations(RenameAnnotation.Create())); return(document.WithSyntaxRoot(finalRoot)); }
private static ForEachStatementSyntax ConvertToForeach( ForStatementSyntax forStatement, SemanticModel semanticModel) { ExpressionSyntax collectionExpression; SimpleNameSyntax lengthMember; TryExtractCollectionInfo( (BinaryExpressionSyntax)forStatement.Condition, out collectionExpression, out lengthMember); var collectionType = semanticModel.GetTypeInfo(collectionExpression).Type; ITypeSymbol elementType = SymbolHelper.GetCollectionElementTypeSymbol(collectionType); string elementTypeName = elementType.ToMinimalDisplayString(semanticModel, forStatement.SpanStart); ISymbol collectionSymbol; string collectionPartName; DataFlowAnalysisHelper.TryGetCollectionInfo( collectionExpression, semanticModel, out collectionPartName, out collectionSymbol); var iterationVarName = NameHelper.GetIterationVariableName( collectionPartName, elementType, forStatement.Statement.SpanStart, semanticModel); var iterationIdentifier = SyntaxFactory .IdentifierName(iterationVarName) .WithAdditionalAnnotations(RenameAnnotation.Create()); var rewriter = new ForToForeachLoopBodyRewriter( iterationIdentifier, collectionPartName, collectionSymbol, semanticModel); var newBody = (StatementSyntax)forStatement.Statement.Accept(rewriter); var foreachStatement = SyntaxFactory.ForEachStatement( SyntaxFactory.ParseTypeName(elementTypeName), iterationIdentifier.Identifier.WithAdditionalAnnotations(RenameAnnotation.Create()), collectionExpression, newBody); foreachStatement = foreachStatement.WithTriviaFrom(forStatement); return(foreachStatement); }
private ClassDeclarationSyntax BuildClass(SyntaxToken typeName, GeneratedPropertyInfo[] propertySources) => ClassDeclaration(String.Empty) .WithIdentifier(typeName.WithAdditionalAnnotations(RenameAnnotation.Create())) .WithModifiers(SyntaxTokenList.Create(Token(SyntaxKind.PrivateKeyword))) .AddMembers(propertySources.Select(p => BuildProperty(p)).ToArray()) .OverrideToString(propertySources) .OverrideGetHashCode(propertySources) .ImplementEquatable(propertySources) .OverrideEquals(propertySources) .AddEqualityOperators(propertySources) .WithAdditionalAnnotations(Simplifier.Annotation);
protected override async Task<Document> IntroduceLocalAsync( SemanticDocument document, ExpressionSyntax expression, bool allOccurrences, bool isConstant, CancellationToken cancellationToken) { var containerToGenerateInto = expression.Ancestors().FirstOrDefault(s => s is BlockSyntax || s is ArrowExpressionClauseSyntax || s is LambdaExpressionSyntax); var newLocalNameToken = GenerateUniqueLocalName( document, expression, isConstant, containerToGenerateInto, cancellationToken); var newLocalName = SyntaxFactory.IdentifierName(newLocalNameToken); var modifiers = isConstant ? SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.ConstKeyword)) : default; var options = await document.Document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); var declarationStatement = SyntaxFactory.LocalDeclarationStatement( modifiers, SyntaxFactory.VariableDeclaration( this.GetTypeSyntax(document, options, expression, isConstant, cancellationToken), SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator( newLocalNameToken.WithAdditionalAnnotations(RenameAnnotation.Create()), null, SyntaxFactory.EqualsValueClause(expression.WithoutTrivia()))))); switch (containerToGenerateInto) { case BlockSyntax block: return await IntroduceLocalDeclarationIntoBlockAsync( document, block, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken).ConfigureAwait(false); case ArrowExpressionClauseSyntax arrowExpression: // this will be null for expression-bodied properties & indexer (not for individual getters & setters, those do have a symbol), // both of which are a shorthand for the getter and always return a value var method = document.SemanticModel.GetDeclaredSymbol(arrowExpression.Parent) as IMethodSymbol; var createReturnStatement = !method?.ReturnsVoid ?? true; return RewriteExpressionBodiedMemberAndIntroduceLocalDeclaration( document, arrowExpression, expression, newLocalName, declarationStatement, allOccurrences, createReturnStatement, cancellationToken); case LambdaExpressionSyntax lambda: return IntroduceLocalDeclarationIntoLambda( document, lambda, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken); } throw new InvalidOperationException(); }
protected override Task <Document> IntroduceQueryLocalAsync( SemanticDocument document, ExpressionSyntax expression, bool allOccurrences, CancellationToken cancellationToken) { var oldOutermostQuery = expression.GetAncestorsOrThis <QueryExpressionSyntax>().LastOrDefault(); var newLocalNameToken = GenerateUniqueLocalName( document, expression, isConstant: false, container: oldOutermostQuery, cancellationToken: cancellationToken); var newLocalName = SyntaxFactory.IdentifierName(newLocalNameToken); var letClause = SyntaxFactory.LetClause( newLocalNameToken.WithAdditionalAnnotations(RenameAnnotation.Create()), expression).WithAdditionalAnnotations(Formatter.Annotation); var matches = FindMatches(document, expression, document, oldOutermostQuery, allOccurrences, cancellationToken); var innermostClauses = new HashSet <SyntaxNode>( matches.Select(expr => expr.GetAncestorsOrThis <SyntaxNode>().First(IsAnyQueryClause))); if (innermostClauses.Count == 1) { // If there was only one match, or all the matches came from the same // statement, then we want to place the declaration right above that // statement. Note: we special case this because the statement we are going // to go above might not be in a block and we may have to generate it return(Task.FromResult(IntroduceQueryLocalForSingleOccurrence( document, expression, newLocalName, letClause, allOccurrences, cancellationToken))); } var oldInnerMostCommonQuery = matches.FindInnermostCommonNode <QueryExpressionSyntax>(); var newInnerMostQuery = Rewrite( document, expression, newLocalName, document, oldInnerMostCommonQuery, allOccurrences, cancellationToken); var allAffectedClauses = new HashSet <SyntaxNode>(matches.SelectMany(expr => expr.GetAncestorsOrThis <SyntaxNode>().Where(IsAnyQueryClause))); var oldClauses = oldInnerMostCommonQuery.GetAllClauses(); var newClauses = newInnerMostQuery.GetAllClauses(); var firstClauseAffectedInQuery = oldClauses.First(allAffectedClauses.Contains); var firstClauseAffectedIndex = oldClauses.IndexOf(firstClauseAffectedInQuery); var finalClauses = newClauses.Take(firstClauseAffectedIndex) .Concat(letClause) .Concat(newClauses.Skip(firstClauseAffectedIndex)).ToList(); var finalQuery = newInnerMostQuery.WithAllClauses(finalClauses); var newRoot = document.Root.ReplaceNode(oldInnerMostCommonQuery, finalQuery); return(Task.FromResult(document.Document.WithSyntaxRoot(newRoot))); }
private MethodDeclarationSyntax ScaffoldTestMethod() { var method = SyntaxFactory.MethodDeclaration(SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)), "MyTestMethod") .WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword))) .AddAttributeLists(SyntaxFactory.AttributeList(SyntaxFactory.SeparatedList(new[] { SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("TestMethod")) }))) .WithBody(SyntaxFactory.Block().WithCloseBraceToken(SyntaxFactory.Token(SyntaxKind.CloseBraceToken) .WithLeadingTrivia( SyntaxFactory.TriviaList( SyntaxFactory.SyntaxTrivia(SyntaxKind.SingleLineCommentTrivia, "// Arrange."), SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.SyntaxTrivia(SyntaxKind.SingleLineCommentTrivia, "// Act."), SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.SyntaxTrivia(SyntaxKind.SingleLineCommentTrivia, "// Assert."), SyntaxFactory.CarriageReturn)))); return(method.WithIdentifier(method.Identifier.WithAdditionalAnnotations(RenameAnnotation.Create()))); }
public RenameRewriterParameters( RenameAnnotation renamedSymbolDeclarationAnnotation, Document document, SemanticModel semanticModel, SyntaxNode syntaxRoot, string replacementText, string originalText, ICollection <string> possibleNameConflicts, Dictionary <TextSpan, RenameLocation> renameLocations, ImmutableDictionary <TextSpan, ImmutableSortedSet <TextSpan>?> stringAndCommentTextSpans, ISet <TextSpan> conflictLocationSpans, Solution originalSolution, ISymbol renameSymbol, bool replacementTextValid, RenamedSpansTracker renameSpansTracker, bool isRenamingInStrings, bool isRenamingInComments, AnnotationTable <RenameAnnotation> renameAnnotations, CancellationToken cancellationToken) { RenamedSymbolDeclarationAnnotation = renamedSymbolDeclarationAnnotation; Document = document; SemanticModel = semanticModel; SyntaxRoot = syntaxRoot; OriginalSyntaxTree = semanticModel.SyntaxTree; ReplacementText = replacementText; OriginalText = originalText; PossibleNameConflicts = possibleNameConflicts; RenameLocations = renameLocations; StringAndCommentTextSpans = stringAndCommentTextSpans; ConflictLocationSpans = conflictLocationSpans; OriginalSolution = originalSolution; RenameSymbol = renameSymbol; ReplacementTextValid = replacementTextValid; CancellationToken = cancellationToken; RenameSpansTracker = renameSpansTracker; IsRenamingInStrings = isRenamingInStrings; IsRenamingInComments = isRenamingInComments; RenameAnnotations = renameAnnotations; }
protected static void IntroduceCollectionStatement( ForEachInfo foreachInfo, SyntaxEditor editor, SyntaxNode type, SyntaxNode foreachCollectionExpression, SyntaxNode collectionVariable ) { if (!foreachInfo.RequireCollectionStatement) { return; } // TODO: refactor introduce variable refactoring to real service and use that service here to introduce local variable var generator = editor.Generator; // attach rename annotation to control variable var collectionVariableToken = generator .Identifier(collectionVariable.ToString()) .WithAdditionalAnnotations(RenameAnnotation.Create()); // this expression is from user code. don't simplify this. var expression = foreachCollectionExpression.WithoutAnnotations( SimplificationHelpers.DontSimplifyAnnotation ); var collectionStatement = generator.LocalDeclarationStatement( type, collectionVariableToken, foreachInfo.RequireExplicitCastInterface ? generator.CastExpression(foreachInfo.ExplicitCastInterface, expression) : expression ); // attach trivia to right place collectionStatement = collectionStatement.WithLeadingTrivia( foreachInfo.ForEachStatement.GetFirstToken().LeadingTrivia ); editor.InsertBefore(foreachInfo.ForEachStatement, collectionStatement); }
public RenameRewriterParameters( RenameAnnotation renamedSymbolDeclarationAnnotation, Document document, SemanticModel semanticModel, SyntaxNode syntaxRoot, string replacementText, string originalText, ICollection <string> possibleNameConflicts, Dictionary <TextSpan, RenameLocation> renameLocations, ImmutableDictionary <TextSpan, ImmutableSortedSet <TextSpan>?> stringAndCommentTextSpans, ISet <TextSpan> conflictLocationSpans, Solution originalSolution, ISymbol renameSymbol, bool replacementTextValid, RenamedSpansTracker renameSpansTracker, RenameOptionSet optionSet, AnnotationTable <RenameAnnotation> renameAnnotations, CancellationToken cancellationToken ) { this.RenamedSymbolDeclarationAnnotation = renamedSymbolDeclarationAnnotation; this.Document = document; this.SemanticModel = semanticModel; this.SyntaxRoot = syntaxRoot; this.OriginalSyntaxTree = semanticModel.SyntaxTree; this.ReplacementText = replacementText; this.OriginalText = originalText; this.PossibleNameConflicts = possibleNameConflicts; this.RenameLocations = renameLocations; this.StringAndCommentTextSpans = stringAndCommentTextSpans; this.ConflictLocationSpans = conflictLocationSpans; this.OriginalSolution = originalSolution; this.RenameSymbol = renameSymbol; this.ReplacementTextValid = replacementTextValid; this.CancellationToken = cancellationToken; this.RenameSpansTracker = renameSpansTracker; this.OptionSet = optionSet; this.RenameAnnotations = renameAnnotations; }
public static async Task <Solution> RenameSymbolAsync(Document document, SyntaxNode root, SyntaxToken declarationToken, string newName, CancellationToken cancellationToken) { var annotatedRoot = root.ReplaceToken(declarationToken, declarationToken.WithAdditionalAnnotations(RenameAnnotation.Create())); var annotatedSolution = document.Project.Solution.WithDocumentSyntaxRoot(document.Id, annotatedRoot); var annotatedDocument = annotatedSolution.GetDocument(document.Id); annotatedRoot = await annotatedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var annotatedToken = annotatedRoot.FindToken(declarationToken.SpanStart); var semanticModel = await annotatedDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var symbol = semanticModel.GetDeclaredSymbol(annotatedToken.Parent, cancellationToken); var newSolution = await Renamer.RenameSymbolAsync(annotatedSolution, symbol, newName, null, cancellationToken).ConfigureAwait(false); foreach (var project in newSolution.Projects) { var compilation = await project.GetCompilationAsync(cancellationToken); var diagnostics = compilation.GetDiagnostics(); foreach (var diagnostic in diagnostics) { if (NamingConflictDiagnosticIds.Contains(diagnostic.Id)) { // If we got here, it means there was a naming conflict // I believe every warning contains the name of the conflicting member in its description // Therefore we can look whether the description contains the new identifier and if it does, return the annotated solution if (diagnostic.GetMessage().Contains(newName)) { return(newSolution); } } } } // If we got here it means there weren't any new errors introduced by renaming // So we can just return the renamed solution without rename IDE helper var originalSemanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var originalSymbol = originalSemanticModel.GetDeclaredSymbol(declarationToken.Parent, cancellationToken); return(await Renamer.RenameSymbolAsync(document.Project.Solution, originalSymbol, newName, null, cancellationToken)); }
private async Task <Document> ConvertForToForEachAsync( Document document, TForStatementSyntax forStatement, SyntaxToken iterationVariable, TExpressionSyntax collectionExpression, INamedTypeSymbol containingType, ITypeSymbol collectionType, ITypeSymbol iterationType, CancellationToken cancellationToken) { var syntaxFacts = document.GetRequiredLanguageService <ISyntaxFactsService>(); var semanticFacts = document.GetRequiredLanguageService <ISemanticFactsService>(); var generator = SyntaxGenerator.GetGenerator(document); var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var editor = new SyntaxEditor(root, generator); // create a dummy "list[i]" expression. We'll use this to find all places to replace // in the current for statement. var indexExpression = generator.ElementAccessExpression( collectionExpression, generator.IdentifierName(iterationVariable)); // See if the first statement in the for loop is of the form: // var x = list[i] or // // If so, we'll use those as the iteration variables for the new foreach statement. var(typeNode, foreachIdentifier, declarationStatement) = TryDeconstructInitialDeclaration(); if (typeNode == null) { // user didn't provide an explicit type. Check if the index-type of the collection // is different from than .Current type of the enumerator. If so, add an explicit // type so that the foreach will coerce the types accordingly. var indexerType = GetIndexerType(containingType, collectionType); if (!Equals(indexerType, iterationType)) { typeNode = (TTypeNode)generator.TypeExpression( indexerType ?? semanticModel.Compilation.GetSpecialType(SpecialType.System_Object)); } } // If we couldn't find an appropriate existing variable to use as the foreach // variable, then generate one automatically. if (foreachIdentifier.RawKind == 0) { foreachIdentifier = semanticFacts.GenerateUniqueName( semanticModel, forStatement, containerOpt: null, baseName: "v", usedNames: Enumerable.Empty <string>(), cancellationToken); foreachIdentifier = foreachIdentifier.WithAdditionalAnnotations(RenameAnnotation.Create()); } // Create the expression we'll use to replace all matches in the for-body. var foreachIdentifierReference = foreachIdentifier.WithoutAnnotations(RenameAnnotation.Kind).WithoutTrivia(); // Walk the for statement, replacing any matches we find. FindAndReplaceMatches(forStatement); // Finally, remove the declaration statement if we found one. Move all its leading // trivia to the next statement. if (declarationStatement != null) { editor.RemoveNode(declarationStatement, SyntaxGenerator.DefaultRemoveOptions | SyntaxRemoveOptions.KeepLeadingTrivia); } editor.ReplaceNode( forStatement, (currentFor, _) => ConvertForNode( (TForStatementSyntax)currentFor, typeNode, foreachIdentifier, collectionExpression, iterationType)); return(document.WithSyntaxRoot(editor.GetChangedRoot())); // local functions (TTypeNode?, SyntaxToken, TStatementSyntax) TryDeconstructInitialDeclaration() { var bodyStatements = GetBodyStatements(forStatement); if (bodyStatements.Count >= 1) { var firstStatement = bodyStatements[0]; if (syntaxFacts.IsLocalDeclarationStatement(firstStatement)) { var variables = syntaxFacts.GetVariablesOfLocalDeclarationStatement(firstStatement); if (variables.Count == 1) { var firstVariable = (TVariableDeclaratorSyntax)variables[0]; if (IsValidVariableDeclarator(firstVariable)) { var firstVariableInitializer = syntaxFacts.GetValueOfEqualsValueClause( syntaxFacts.GetInitializerOfVariableDeclarator(firstVariable)); if (syntaxFacts.AreEquivalent(firstVariableInitializer, indexExpression)) { var type = (TTypeNode?)syntaxFacts.GetTypeOfVariableDeclarator(firstVariable)?.WithoutLeadingTrivia(); var identifier = syntaxFacts.GetIdentifierOfVariableDeclarator(firstVariable); var statement = firstStatement; return(type, identifier, statement); } } } } } return(default);
protected override async Task <Document> IntroduceLocalAsync( SemanticDocument document, ExpressionSyntax expression, bool allOccurrences, bool isConstant, CancellationToken cancellationToken) { var containerToGenerateInto = GetContainerToGenerateInto(document, expression, cancellationToken); var newLocalNameToken = GenerateUniqueLocalName( document, expression, isConstant, containerToGenerateInto, cancellationToken); var newLocalName = SyntaxFactory.IdentifierName(newLocalNameToken); var modifiers = isConstant ? SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.ConstKeyword)) : default; var options = await document.Document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); var declarationStatement = SyntaxFactory.LocalDeclarationStatement( modifiers, SyntaxFactory.VariableDeclaration( this.GetTypeSyntax(document, options, expression, isConstant, cancellationToken), SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator( newLocalNameToken.WithAdditionalAnnotations(RenameAnnotation.Create()), null, SyntaxFactory.EqualsValueClause(expression.WithoutTrailingTrivia().WithoutLeadingTrivia()))))); switch (containerToGenerateInto) { case BlockSyntax block: return(await IntroduceLocalDeclarationIntoBlockAsync( document, block, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken).ConfigureAwait(false)); case ArrowExpressionClauseSyntax arrowExpression: return(RewriteExpressionBodiedMemberAndIntroduceLocalDeclaration( document, arrowExpression, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken)); case LambdaExpressionSyntax lambda: return(IntroduceLocalDeclarationIntoLambda( document, lambda, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken)); } throw new InvalidOperationException(); }
protected override Task <Document> IntroduceLocalAsync( SemanticDocument document, ExpressionSyntax expression, bool allOccurrences, bool isConstant, CancellationToken cancellationToken) { var options = document.Project.Solution.Workspace.Options; var newLocalNameToken = (SyntaxToken)GenerateUniqueLocalName(document, expression, isConstant, cancellationToken); var newLocalName = SyntaxFactory.IdentifierName(newLocalNameToken); var modifiers = isConstant ? SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.ConstKeyword)) : default(SyntaxTokenList); var declarationStatement = SyntaxFactory.LocalDeclarationStatement( modifiers, SyntaxFactory.VariableDeclaration( this.GetTypeSyntax(document, expression, isConstant, options, cancellationToken), SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator( newLocalNameToken.WithAdditionalAnnotations(RenameAnnotation.Create()), null, SyntaxFactory.EqualsValueClause(expression.WithoutTrailingTrivia().WithoutLeadingTrivia()))))); var anonymousMethodParameters = GetAnonymousMethodParameters(document, expression, cancellationToken); var lambdas = anonymousMethodParameters.SelectMany(p => p.ContainingSymbol.DeclaringSyntaxReferences.Select(r => r.GetSyntax(cancellationToken)).AsEnumerable()) .Where(n => n is ParenthesizedLambdaExpressionSyntax || n is SimpleLambdaExpressionSyntax) .ToSet(); var parentLambda = GetParentLambda(expression, lambdas); if (parentLambda != null) { return(Task.FromResult(IntroduceLocalDeclarationIntoLambda( document, expression, newLocalName, declarationStatement, parentLambda, allOccurrences, cancellationToken))); } else { return(IntroduceLocalDeclarationIntoBlockAsync( document, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken)); } }
static RenameResult RenameSymbol(Document document, SyntaxNode root, SyntaxNode startNode, VariableDeclaratorSyntax declarationNode, string newName) { var identifierToken = declarationNode.Identifier; var methodAnnotation = new SyntaxAnnotation(SELECTED_METHOD_ANNOTATION); var changeDic = new Dictionary <SyntaxNode, SyntaxNode>(); if (startNode != null) { changeDic.Add(startNode, startNode.WithAdditionalAnnotations(methodAnnotation)); } changeDic.Add(declarationNode, declarationNode.WithIdentifier(identifierToken.WithAdditionalAnnotations(RenameAnnotation.Create()))); var annotatedRoot = root.ReplaceNodes(changeDic.Keys, (x, y) => changeDic[x]); var newSolution = RenameSymbol(document, annotatedRoot, identifierToken, methodAnnotation, newName).Result; return(GetNewStartNode(newSolution, document, methodAnnotation, startNode)); }
protected override Task <Tuple <Document, SyntaxNode, int> > IntroduceFieldAsync( SemanticDocument document, ExpressionSyntax expression, bool allOccurrences, bool isConstant, CancellationToken cancellationToken) { var oldTypeDeclaration = expression.GetAncestorOrThis <TypeDeclarationSyntax>(); #if SCRIPTING var oldType = oldTypeDeclaration != null ? document.SemanticModel.GetDeclaredSymbol(oldTypeDeclaration, cancellationToken) as INamedTypeSymbol : document.SemanticModel.Compilation.ScriptClass; #else var oldType = document.SemanticModel.GetDeclaredSymbol(oldTypeDeclaration, cancellationToken) as INamedTypeSymbol; #endif var newNameToken = (SyntaxToken)GenerateUniqueFieldName(document, expression, isConstant, cancellationToken); var newQualifiedName = oldTypeDeclaration != null ? SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.ParseName(oldType.ToNameDisplayString()), SyntaxFactory.IdentifierName(newNameToken)) : (ExpressionSyntax)SyntaxFactory.IdentifierName(newNameToken); newQualifiedName = newQualifiedName.WithAdditionalAnnotations(Simplifier.Annotation); var newFieldDeclaration = SyntaxFactory.FieldDeclaration( default(SyntaxList <AttributeListSyntax>), MakeFieldModifiers(isConstant, inScript: oldType.IsScriptClass), SyntaxFactory.VariableDeclaration( GetTypeSymbol(document, expression, cancellationToken).GenerateTypeSyntax(), SyntaxFactory.SingletonSeparatedList( SyntaxFactory.VariableDeclarator( newNameToken.WithAdditionalAnnotations(RenameAnnotation.Create()), null, SyntaxFactory.EqualsValueClause(expression))))).WithAdditionalAnnotations(Formatter.Annotation); if (oldTypeDeclaration != null) { var newTypeDeclaration = Rewrite( document, expression, newQualifiedName, document, oldTypeDeclaration, allOccurrences, cancellationToken); var insertionIndex = isConstant ? DetermineConstantInsertPosition(oldTypeDeclaration.Members, newTypeDeclaration.Members) : DetermineFieldInsertPosition(oldTypeDeclaration.Members, newTypeDeclaration.Members); var finalTypeDeclaration = InsertMember(newTypeDeclaration, newFieldDeclaration, insertionIndex); SyntaxNode destination = oldTypeDeclaration; var newRoot = document.Root.ReplaceNode(oldTypeDeclaration, finalTypeDeclaration); return(Task.FromResult(Tuple.Create(document.Document.WithSyntaxRoot(newRoot), destination, insertionIndex))); } else { var oldCompilationUnit = (CompilationUnitSyntax)document.Root; var newCompilationUnit = Rewrite( document, expression, newQualifiedName, document, oldCompilationUnit, allOccurrences, cancellationToken); var insertionIndex = isConstant ? DetermineConstantInsertPosition(oldCompilationUnit.Members, newCompilationUnit.Members) : DetermineFieldInsertPosition(oldCompilationUnit.Members, newCompilationUnit.Members); SyntaxNode destination = oldCompilationUnit; var newRoot = newCompilationUnit.WithMembers(newCompilationUnit.Members.Insert(insertionIndex, newFieldDeclaration)); return(Task.FromResult(Tuple.Create(document.Document.WithSyntaxRoot(newRoot), destination, insertionIndex))); } }
public static SyntaxToken WithRenameAnnotation(this SyntaxToken token) { return(token.WithAdditionalAnnotations(RenameAnnotation.Create())); }
private static ForStatementSyntax ConvertToFor( ForEachStatementSyntax forEachStatement, SemanticModel semanticModel, string lengthMemberName) { var collectionExpression = forEachStatement.Expression; var collectionType = semanticModel.GetTypeInfo(collectionExpression).Type; string counterName = NameHelper.GetLoopCounterName(forEachStatement.Statement.SpanStart, semanticModel); var counterIdentifier = SyntaxFactory .IdentifierName(counterName) .WithAdditionalAnnotations(RenameAnnotation.Create()); var initializer = SyntaxFactory.EqualsValueClause( SyntaxFactory.LiteralExpression( SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(0) ) ); var declarator = SyntaxFactory.VariableDeclarator( SyntaxFactory.Identifier(counterName) .WithAdditionalAnnotations(RenameAnnotation.Create()), null, initializer); var counterDeclaration = SyntaxFactory.VariableDeclaration( SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.IntKeyword)), SyntaxFactory.SingletonSeparatedList(declarator)); var lengthAccess = SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, collectionExpression, SyntaxFactory.IdentifierName(lengthMemberName) ); var condition = SyntaxFactory.BinaryExpression( SyntaxKind.LessThanExpression, counterIdentifier, lengthAccess); var counterIncrementor = SyntaxFactory.PostfixUnaryExpression( SyntaxKind.PostIncrementExpression, counterIdentifier); var elementAccess = SyntaxFactory.ElementAccessExpression( collectionExpression, SyntaxFactory.BracketedArgumentList( SyntaxFactory.SingletonSeparatedList( SyntaxFactory.Argument(counterIdentifier) ) ) ); var rewriter = new ForeachToForLoopBodyRewriter( elementAccess, forEachStatement.Identifier.Text, semanticModel.GetDeclaredSymbol(forEachStatement), semanticModel); var newLoopBody = (StatementSyntax)forEachStatement.Statement.Accept(rewriter); var forStatement = SyntaxFactory.ForStatement( counterDeclaration, SyntaxFactory.SeparatedList <ExpressionSyntax>(), condition, SyntaxFactory.SingletonSeparatedList <ExpressionSyntax>(counterIncrementor), newLoopBody); forStatement = forStatement .WithTriviaFrom(forEachStatement) .WithAdditionalAnnotations(Simplifier.Annotation); return(forStatement); }
private void InsertNewVariableDeclaration( BinaryExpressionSyntax asExpression, SyntaxToken newIdentifier, SyntaxNode nodeLocation, DocumentEditor editor, ref bool variableAlreadyExtracted) { if (variableAlreadyExtracted) { return; } var newEqualsClause = SyntaxFactory.EqualsValueClause(asExpression); var newDeclarator = SyntaxFactory.VariableDeclarator(newIdentifier.WithAdditionalAnnotations(RenameAnnotation.Create()), null, newEqualsClause); var newDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.IdentifierName("var"), SyntaxFactory.SeparatedList(new[] { newDeclarator })); var newLocal = SyntaxFactory.LocalDeclarationStatement(newDeclaration).WithAdditionalAnnotations(Formatter.Annotation); // If we are in an else statement, we have to add the new local before the initial if-statement. e.g.: // if(o is int) { } // else if(o is string) { } // If we are currently handling the second statement, we have to add the local before the first // However because there can be multiple chained if-else statements, we have to go up to the first one and add it there. nodeLocation = GetOuterIfStatement(nodeLocation); editor.InsertBefore(nodeLocation, newLocal); variableAlreadyExtracted = true; }
private static InvocationExpressionSyntax Merge( InvocationExpressionSyntax outerMostInvocation, MemberAccessExpressionSyntax innerMostWhereAccess, List <ExpressionSyntax> whereArguments, SemanticModel semanticModel) { var firstArgument = whereArguments[0]; string parameterName; ParameterSyntax firstParameter; IdentifierNameSyntax firstParameterIdentifier; ExpressionSyntax filterExpression; if (firstArgument.IsKind(SyntaxKind.SimpleLambdaExpression)) { var lambda = (SimpleLambdaExpressionSyntax)firstArgument; firstParameter = lambda.Parameter; parameterName = firstParameter.Identifier.Text; firstParameterIdentifier = SyntaxFactory.IdentifierName(firstParameter.Identifier); filterExpression = MakeExpressionFromLambdaBody(lambda.Body); } else { parameterName = NameHelper.GetLambdaParameterName( outerMostInvocation.SpanStart, semanticModel); var parameterIdentifier = SyntaxFactory .Identifier(parameterName) .WithAdditionalAnnotations(RenameAnnotation.Create()); firstParameter = SyntaxFactory.Parameter(parameterIdentifier); firstParameterIdentifier = SyntaxFactory.IdentifierName(parameterIdentifier); filterExpression = ExtendedSyntaxFactory.MakeInvocation( firstArgument, firstParameterIdentifier); } for (int i = 1; i < whereArguments.Count; ++i) { ExpressionSyntax andOperand; if (whereArguments[i].IsKind(SyntaxKind.SimpleLambdaExpression)) { var currentLambda = (SimpleLambdaExpressionSyntax)whereArguments[i]; var currentParameter = currentLambda.Parameter; var currentParameterName = currentParameter.Identifier.Text; if (currentParameterName != parameterName) { var parameterSymbol = semanticModel.GetDeclaredSymbol(currentParameter); var substituteRewriter = new SubstituteRewriter( currentParameterName, parameterSymbol, semanticModel, firstParameterIdentifier); var newBody = (CSharpSyntaxNode)currentLambda.Body.Accept(substituteRewriter); andOperand = MakeExpressionFromLambdaBody(newBody); } else { andOperand = MakeExpressionFromLambdaBody(currentLambda.Body); } } else { andOperand = ExtendedSyntaxFactory.MakeInvocation( whereArguments[i], firstParameterIdentifier); } filterExpression = SyntaxFactory.BinaryExpression( SyntaxKind.LogicalAndExpression, filterExpression, andOperand); } var newLambda = SyntaxFactory.SimpleLambdaExpression( firstParameter, filterExpression); var newInvocation = ExtendedSyntaxFactory.MakeInvocation( innerMostWhereAccess, newLambda); return(newInvocation); }
internal static async Task <Solution> RenameSymbolAsync(Document document, SyntaxNode root, SyntaxToken declarationToken, string newName, CancellationToken cancellationToken) { var annotatedRoot = root.ReplaceToken(declarationToken, declarationToken.WithAdditionalAnnotations(RenameAnnotation.Create())); var annotatedSolution = document.Project.Solution.WithDocumentSyntaxRoot(document.Id, annotatedRoot); var annotatedDocument = annotatedSolution.GetDocument(document.Id); if (annotatedDocument is null) { throw new InvalidOperationException($"Did not find a document matching {document.Name}"); } annotatedRoot = await annotatedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var annotatedToken = annotatedRoot.FindToken(declarationToken.SpanStart); var semanticModel = await annotatedDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var symbol = semanticModel.GetDeclaredSymbol(annotatedToken.Parent, cancellationToken); var newSolution = await Renamer.RenameSymbolAsync(annotatedSolution, symbol, newName, null, cancellationToken).ConfigureAwait(false); // TODO: return annotatedSolution instead of newSolution if newSolution contains any new errors (for any project) return(newSolution); }
/// <summary> /// Takes a <see cref="Document"/> and a <see cref="Diagnostic"/> and returns a new <see cref="Document"/> with the replaced /// nodes in order to apply the code fix to the diagnostic. /// </summary> /// <param name="document">The original document.</param> /// <param name="root">The root of the syntax tree.</param> /// <param name="nodeToFix">The node to fix. This is where the diagnostic was produced.</param> /// <param name="diagnostic">The diagnostic to fix.</param> /// <param name="cancellationToken">The cancellation token for the async operation.</param> /// <returns>The new document with the replaced nodes after applying the code fix.</returns> private static async Task <Document> ConvertToSourceGenerator(Document document, SyntaxNode root, SyntaxNode nodeToFix, Diagnostic diagnostic, CancellationToken cancellationToken) { // We first get the compilation object from the document SemanticModel?semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); if (semanticModel is null) { return(document); } Compilation compilation = semanticModel.Compilation; // We then get the symbols for the Regex and GeneratedRegexAttribute types. INamedTypeSymbol?regexSymbol = compilation.GetTypeByMetadataName(RegexTypeName); INamedTypeSymbol?generatedRegexAttributeSymbol = compilation.GetTypeByMetadataName(GeneratedRegexTypeName); if (regexSymbol is null || generatedRegexAttributeSymbol is null) { return(document); } // Save the operation object from the nodeToFix before it gets replaced by the new method invocation. // We will later use this operation to get the parameters out and pass them into the Regex attribute. IOperation?operation = semanticModel.GetOperation(nodeToFix, cancellationToken); if (operation is null) { return(document); } // Get the parent type declaration so that we can inspect its methods as well as check if we need to add the partial keyword. SyntaxNode?typeDeclarationOrCompilationUnit = nodeToFix.Ancestors().OfType <TypeDeclarationSyntax>().FirstOrDefault(); if (typeDeclarationOrCompilationUnit is null) { typeDeclarationOrCompilationUnit = await nodeToFix.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); } // Calculate what name should be used for the generated static partial method string methodName = DefaultRegexMethodName; INamedTypeSymbol?typeSymbol = typeDeclarationOrCompilationUnit is TypeDeclarationSyntax typeDeclaration? semanticModel.GetDeclaredSymbol(typeDeclaration, cancellationToken) : semanticModel.GetDeclaredSymbol((CompilationUnitSyntax)typeDeclarationOrCompilationUnit, cancellationToken)?.ContainingType; if (typeSymbol is not null) { IEnumerable <ISymbol> members = GetAllMembers(typeSymbol); int memberCount = 1; while (members.Any(m => m.Name == methodName)) { methodName = $"{DefaultRegexMethodName}{memberCount++}"; } } // Walk the type hirerarchy of the node to fix, and add the partial modifier to each ancestor (if it doesn't have it already) // We also keep a count of how many partial keywords we added so that we can later find the nodeToFix again on the new root using the text offset. int typesModified = 0; root = root.ReplaceNodes( nodeToFix.Ancestors().OfType <TypeDeclarationSyntax>(), (_, typeDeclaration) => { if (!typeDeclaration.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) { typesModified++; return(typeDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.PartialKeyword)).WithAdditionalAnnotations(Simplifier.Annotation)); } return(typeDeclaration); }); // We find nodeToFix again by calculating the offset of how many partial keywords we had to add. nodeToFix = root.FindNode(new TextSpan(nodeToFix.Span.Start + (typesModified * "partial".Length), nodeToFix.Span.Length), getInnermostNodeForTie: true); if (nodeToFix is null) { return(document); } // We need to find the typeDeclaration again, but now using the new root. typeDeclarationOrCompilationUnit = typeDeclarationOrCompilationUnit is TypeDeclarationSyntax? nodeToFix.Ancestors().OfType <TypeDeclarationSyntax>().FirstOrDefault() : await nodeToFix.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); Debug.Assert(typeDeclarationOrCompilationUnit is not null); SyntaxNode newTypeDeclarationOrCompilationUnit = typeDeclarationOrCompilationUnit; // We generate a new invocation node to call our new partial method, and use it to replace the nodeToFix. DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); SyntaxGenerator generator = editor.Generator; // Generate the modified type declaration depending on whether the callsite was a Regex constructor call // or a Regex static method invocation. SyntaxNode replacement = generator.InvocationExpression(generator.IdentifierName(methodName)); ImmutableArray <IArgumentOperation> operationArguments; if (operation is IInvocationOperation invocationOperation) // When using a Regex static method { operationArguments = invocationOperation.Arguments; IEnumerable <SyntaxNode> arguments = operationArguments .Where(arg => arg.Parameter.Name is not(UpgradeToGeneratedRegexAnalyzer.OptionsArgumentName or UpgradeToGeneratedRegexAnalyzer.PatternArgumentName)) .Select(arg => arg.Syntax); replacement = generator.InvocationExpression(generator.MemberAccessExpression(replacement, invocationOperation.TargetMethod.Name), arguments); } else { operationArguments = ((IObjectCreationOperation)operation).Arguments; } newTypeDeclarationOrCompilationUnit = newTypeDeclarationOrCompilationUnit.ReplaceNode(nodeToFix, WithTrivia(replacement, nodeToFix)); // Initialize the inputs for the GeneratedRegex attribute. SyntaxNode?patternValue = GetNode(operationArguments, generator, UpgradeToGeneratedRegexAnalyzer.PatternArgumentName); SyntaxNode?regexOptionsValue = GetNode(operationArguments, generator, UpgradeToGeneratedRegexAnalyzer.OptionsArgumentName); // Generate the new static partial method MethodDeclarationSyntax newMethod = (MethodDeclarationSyntax)generator.MethodDeclaration( name: methodName, returnType: generator.TypeExpression(regexSymbol), modifiers: DeclarationModifiers.Static | DeclarationModifiers.Partial, accessibility: Accessibility.Private); // Allow user to pick a different name for the method. newMethod = newMethod.ReplaceToken(newMethod.Identifier, SyntaxFactory.Identifier(methodName).WithAdditionalAnnotations(RenameAnnotation.Create())); // We now need to check if we have to pass in the cultureName parameter. This parameter will be required in case the option // RegexOptions.IgnoreCase is set for this Regex. To determine that, we first get the passed in options (if any), and then, // we also need to parse the pattern in case there are options that were specified inside the pattern via the `(?i)` switch. SyntaxNode? cultureNameValue = null; RegexOptions regexOptions = regexOptionsValue is not null?GetRegexOptionsFromArgument(operationArguments) : RegexOptions.None; string pattern = GetRegexPatternFromArgument(operationArguments); regexOptions |= RegexParser.ParseOptionsInPattern(pattern, regexOptions); // If the options include IgnoreCase and don't specify CultureInvariant then we will have to calculate the user's current culture in order to pass // it in as a parameter. If the user specified IgnoreCase, but also selected CultureInvariant, then we skip as the default is to use Invariant culture. if ((regexOptions & RegexOptions.IgnoreCase) != 0 && (regexOptions & RegexOptions.CultureInvariant) == 0) { // If CultureInvariant wasn't specified as options, we default to the current culture. cultureNameValue = generator.LiteralExpression(CultureInfo.CurrentCulture.Name); // If options weren't passed in, then we need to define it as well in order to use the three parameter constructor. if (regexOptionsValue is null) { regexOptionsValue = generator.MemberAccessExpression(SyntaxFactory.IdentifierName("RegexOptions"), "None"); } } // Generate the GeneratedRegex attribute syntax node with the specified parameters. SyntaxNode attributes = generator.Attribute(generator.TypeExpression(generatedRegexAttributeSymbol), attributeArguments: (patternValue, regexOptionsValue, cultureNameValue) switch { ({ }, null, null) => new[] { patternValue },
private static InvocationExpressionSyntax Merge( InvocationExpressionSyntax outerMostInvocation, MemberAccessExpressionSyntax innerMostWhereAccess, List <ExpressionSyntax> selectArguments, SemanticModel semanticModel) { var firstArgument = selectArguments[0]; string parameterName; ParameterSyntax firstParameter; IdentifierNameSyntax firstParameterIdentifier; InvocationExpressionSyntax resultInvocation; if (firstArgument.IsKind(SyntaxKind.SimpleLambdaExpression)) { var lambda = (SimpleLambdaExpressionSyntax)firstArgument; firstParameter = lambda.Parameter; parameterName = firstParameter.Identifier.Text; firstParameterIdentifier = SyntaxFactory.IdentifierName(firstParameter.Identifier); resultInvocation = (InvocationExpressionSyntax)lambda.Body; } else { parameterName = NameHelper.GetLambdaParameterName( outerMostInvocation.SpanStart, semanticModel); var parameterIdentifier = SyntaxFactory .Identifier(parameterName) .WithAdditionalAnnotations(RenameAnnotation.Create()); firstParameter = SyntaxFactory.Parameter(parameterIdentifier); firstParameterIdentifier = SyntaxFactory.IdentifierName(parameterIdentifier); resultInvocation = ExtendedSyntaxFactory.MakeInvocation( firstArgument, firstParameterIdentifier); } for (int i = 1; i < selectArguments.Count; ++i) { if (selectArguments[i].IsKind(SyntaxKind.SimpleLambdaExpression)) { var currentLambda = (SimpleLambdaExpressionSyntax)selectArguments[i]; var currentParameter = currentLambda.Parameter; var currentParameterName = currentParameter.Identifier.Text; var parameterSymbol = semanticModel.GetDeclaredSymbol(currentParameter); var substituteRewriter = new SubstituteRewriter( currentParameterName, parameterSymbol, semanticModel, resultInvocation); resultInvocation = (InvocationExpressionSyntax)currentLambda .Body .Accept(substituteRewriter); } else { resultInvocation = ExtendedSyntaxFactory.MakeInvocation( selectArguments[i], resultInvocation); } } var newLambda = SyntaxFactory.SimpleLambdaExpression( firstParameter, resultInvocation); var newInvocation = ExtendedSyntaxFactory.MakeInvocation( innerMostWhereAccess, newLambda); return(newInvocation); }
public static async Task <Solution> RenameSymbolAsync(Document document, SyntaxNode root, SyntaxToken declarationToken, string newName, CancellationToken cancellationToken) { var annotatedRoot = root.ReplaceToken(declarationToken, declarationToken.WithAdditionalAnnotations(RenameAnnotation.Create())); var annotatedSolution = document.Project.Solution.WithDocumentSyntaxRoot(document.Id, annotatedRoot); var annotatedDocument = annotatedSolution.GetDocument(document.Id); annotatedRoot = await annotatedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var annotatedToken = annotatedRoot.FindToken(declarationToken.SpanStart); var semanticModel = await annotatedDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var symbol = annotatedToken.Parent is IdentifierNameSyntax ? semanticModel.GetSymbolInfo(annotatedToken.Parent, cancellationToken) .Symbol : semanticModel.GetDeclaredSymbol(annotatedToken.Parent, cancellationToken); var newSolution = await Renamer.RenameSymbolAsync(annotatedSolution, symbol, newName, null, cancellationToken).ConfigureAwait(false); // TODO: return annotatedSolution instead of newSolution if newSolution contains any new errors (for any project) return(newSolution); }
/// <summary> /// Takes a <see cref="Document"/> and a <see cref="Diagnostic"/> and returns a new <see cref="Document"/> with the replaced /// nodes in order to apply the code fix to the diagnostic. /// </summary> /// <param name="document">The original document.</param> /// <param name="root">The root of the syntax tree.</param> /// <param name="nodeToFix">The node to fix. This is where the diagnostic was produced.</param> /// <param name="diagnostic">The diagnostic to fix.</param> /// <param name="cancellationToken">The cancellation token for the async operation.</param> /// <returns>The new document with the replaced nodes after applying the code fix.</returns> private static async Task <Document> ConvertToSourceGenerator(Document document, SyntaxNode root, SyntaxNode nodeToFix, Diagnostic diagnostic, CancellationToken cancellationToken) { // We first get the compilation object from the document SemanticModel?semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); if (semanticModel is null) { return(document); } Compilation compilation = semanticModel.Compilation; // We then get the symbols for the Regex and RegexGeneratorAttribute types. INamedTypeSymbol?regexSymbol = compilation.GetTypeByMetadataName(RegexTypeName); INamedTypeSymbol?regexGeneratorAttributeSymbol = compilation.GetTypeByMetadataName(RegexGeneratorTypeName); if (regexSymbol is null || regexGeneratorAttributeSymbol is null) { return(document); } // Save the operation object from the nodeToFix before it gets replaced by the new method invocation. // We will later use this operation to get the parameters out and pass them into the RegexGenerator attribute. IOperation?operation = semanticModel.GetOperation(nodeToFix, cancellationToken); if (operation is null) { return(document); } // Get the parent type declaration so that we can inspect its methods as well as check if we need to add the partial keyword. SyntaxNode?typeDeclarationOrCompilationUnit = nodeToFix.Ancestors().OfType <TypeDeclarationSyntax>().FirstOrDefault(); if (typeDeclarationOrCompilationUnit is null) { typeDeclarationOrCompilationUnit = await nodeToFix.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); } // Calculate what name should be used for the generated static partial method string methodName = DefaultRegexMethodName; INamedTypeSymbol?typeSymbol = typeDeclarationOrCompilationUnit is TypeDeclarationSyntax typeDeclaration? semanticModel.GetDeclaredSymbol(typeDeclaration, cancellationToken) : semanticModel.GetDeclaredSymbol((CompilationUnitSyntax)typeDeclarationOrCompilationUnit, cancellationToken)?.ContainingType; if (typeSymbol is not null) { IEnumerable <ISymbol> members = GetAllMembers(typeSymbol); int memberCount = 1; while (members.Any(m => m.Name == methodName)) { methodName = $"{DefaultRegexMethodName}{memberCount++}"; } } // Walk the type hirerarchy of the node to fix, and add the partial modifier to each ancestor (if it doesn't have it already) // We also keep a count of how many partial keywords we added so that we can later find the nodeToFix again on the new root using the text offset. int typesModified = 0; root = root.ReplaceNodes( nodeToFix.Ancestors().OfType <TypeDeclarationSyntax>(), (_, typeDeclaration) => { if (!typeDeclaration.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) { typesModified++; return(typeDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.PartialKeyword)).WithAdditionalAnnotations(Simplifier.Annotation)); } return(typeDeclaration); }); // We find nodeToFix again by calculating the offset of how many partial keywords we had to add. nodeToFix = root.FindNode(new TextSpan(nodeToFix.Span.Start + (typesModified * "partial".Length), nodeToFix.Span.Length), getInnermostNodeForTie: true); if (nodeToFix is null) { return(document); } // We need to find the typeDeclaration again, but now using the new root. typeDeclarationOrCompilationUnit = typeDeclarationOrCompilationUnit is TypeDeclarationSyntax? nodeToFix.Ancestors().OfType <TypeDeclarationSyntax>().FirstOrDefault() : await nodeToFix.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); Debug.Assert(typeDeclarationOrCompilationUnit is not null); SyntaxNode newTypeDeclarationOrCompilationUnit = typeDeclarationOrCompilationUnit; // We generate a new invocation node to call our new partial method, and use it to replace the nodeToFix. DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); SyntaxGenerator generator = editor.Generator; ImmutableDictionary <string, string?> properties = diagnostic.Properties; // Generate the modified type declaration depending on whether the callsite was a Regex constructor call // or a Regex static method invocation. if (operation is IInvocationOperation invocationOperation) // When using a Regex static method { ImmutableArray <IArgumentOperation> arguments = invocationOperation.Arguments; // Parse the idices for where to get the arguments from. int?[] indices = new[] { TryParseInt32(properties, UpgradeToRegexGeneratorAnalyzer.PatternIndexName), TryParseInt32(properties, UpgradeToRegexGeneratorAnalyzer.RegexOptionsIndexName) }; foreach (int?index in indices.Where(value => value != null).OrderByDescending(value => value)) { arguments = arguments.RemoveAt(index.GetValueOrDefault()); } SyntaxNode createRegexMethod = generator.InvocationExpression(generator.IdentifierName(methodName)); SyntaxNode method = generator.InvocationExpression(generator.MemberAccessExpression(createRegexMethod, invocationOperation.TargetMethod.Name), arguments.Select(arg => arg.Syntax).ToArray()); newTypeDeclarationOrCompilationUnit = newTypeDeclarationOrCompilationUnit.ReplaceNode(nodeToFix, WithTrivia(method, nodeToFix)); } else // When using a Regex constructor { SyntaxNode invokeMethod = generator.InvocationExpression(generator.IdentifierName(methodName)); newTypeDeclarationOrCompilationUnit = newTypeDeclarationOrCompilationUnit.ReplaceNode(nodeToFix, WithTrivia(invokeMethod, nodeToFix)); } // Initialize the inputs for the RegexGenerator attribute. SyntaxNode?patternValue = null; SyntaxNode?regexOptionsValue = null; // Try to get the pattern and RegexOptions values out from the diagnostic's property bag. if (operation is IObjectCreationOperation objectCreationOperation) // When using the Regex constructors { patternValue = GetNode((objectCreationOperation).Arguments, properties, UpgradeToRegexGeneratorAnalyzer.PatternIndexName, generator, useOptionsMemberExpression: false, compilation, cancellationToken); regexOptionsValue = GetNode((objectCreationOperation).Arguments, properties, UpgradeToRegexGeneratorAnalyzer.RegexOptionsIndexName, generator, useOptionsMemberExpression: true, compilation, cancellationToken); } else if (operation is IInvocationOperation invocation) // When using the Regex static methods. { patternValue = GetNode(invocation.Arguments, properties, UpgradeToRegexGeneratorAnalyzer.PatternIndexName, generator, useOptionsMemberExpression: false, compilation, cancellationToken); regexOptionsValue = GetNode(invocation.Arguments, properties, UpgradeToRegexGeneratorAnalyzer.RegexOptionsIndexName, generator, useOptionsMemberExpression: true, compilation, cancellationToken); } // Generate the new static partial method MethodDeclarationSyntax newMethod = (MethodDeclarationSyntax)generator.MethodDeclaration( name: methodName, returnType: generator.TypeExpression(regexSymbol), modifiers: DeclarationModifiers.Static | DeclarationModifiers.Partial, accessibility: Accessibility.Private); // Allow user to pick a different name for the method. newMethod = newMethod.ReplaceToken(newMethod.Identifier, SyntaxFactory.Identifier(methodName).WithAdditionalAnnotations(RenameAnnotation.Create())); // Generate the RegexGenerator attribute syntax node with the specified parameters. SyntaxNode attributes = generator.Attribute(generator.TypeExpression(regexGeneratorAttributeSymbol), attributeArguments: (patternValue, regexOptionsValue) switch { ({ }, null) => new[] { patternValue },