Пример #1
0
        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));
        }
Пример #2
0
        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();
        }
Пример #4
0
        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);
        }
Пример #6
0
 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()));
 }
Пример #7
0
        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));
        }
Пример #8
0
        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)));
        }
Пример #12
0
        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;
 }
Пример #14
0
        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);
        }
Пример #15
0
 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;
 }
Пример #16
0
        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();
        }
Пример #19
0
        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));
            }
        }
Пример #20
0
        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));
        }
Пример #21
0
        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)));
            }
        }
Пример #22
0
 public static SyntaxToken WithRenameAnnotation(this SyntaxToken token)
 {
     return(token.WithAdditionalAnnotations(RenameAnnotation.Create()));
 }
Пример #23
0
        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;
        }
Пример #25
0
        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);
        }
Пример #26
0
        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);
        }
Пример #27
0
        /// <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 },
Пример #28
0
        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);
        }
Пример #29
0
        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);
        }
Пример #30
0
        /// <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 },