public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var(document, classDeclarationSyntax) = await context.FindSyntaxForCurrentSpan <ClassDeclarationSyntax>(); if (document == null || classDeclarationSyntax == null) { return; } if (!ClassDeclarationSyntaxAnalysis.IsRecordLike(classDeclarationSyntax)) { return; } var properties = ClassDeclarationSyntaxAnalysis.GetPropertyDeclarations(classDeclarationSyntax).ToList(); if (properties.Count == 0 || properties.Any(PropertyDeclarationSyntaxExtensions.IsStatic)) { return; } context.RegisterRefactoring( new DelegateCodeAction( "Generate to string from parameters", (c) => GenerateToString(document, classDeclarationSyntax, properties, c))); return; }
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var(document, classDeclarationSyntax) = await context.GetClassIfOnClassIdentifier(); if (classDeclarationSyntax == null) { return; } if (!ClassDeclarationSyntaxAnalysis.CanBeDiscriminatedUnionBaseType(classDeclarationSyntax)) { return; } if (classDeclarationSyntax.Parent == null || (!classDeclarationSyntax.Parent.IsKind(SyntaxKind.NamespaceDeclaration) && !classDeclarationSyntax.Parent.IsKind(SyntaxKind.CompilationUnit))) { return; } var duMembers = GetCandidateMethods(classDeclarationSyntax); if (duMembers.Count == 0) { return; } context.RegisterRefactoring( new DelegateCodeAction( "Generate discriminated union from static methods", (c) => GenerateDiscriminatedUnion(document, classDeclarationSyntax, c))); }
private static async Task <Document> GenerateToString( Document document, ClassDeclarationSyntax classDeclarationSyntax, IList <PropertyDeclarationSyntax> properties, CancellationToken cancellationToken) { InvocationExpressionSyntax GenerateNameOfCall(SyntaxToken identifier) => ExpressionGenerationHelper.Invocation( "nameof", SyntaxHelpers.ArgumentFromIdentifier(identifier)); var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); if (properties.Count == 0) { return(document); } var firstEl = Enumerable.Repeat( (InterpolatedStringContentSyntax)SyntaxFactory.Interpolation( GenerateNameOfCall(classDeclarationSyntax.Identifier)), 1); var interpolatedStringExpression = InterpolatedStringGenerationHelper.InterpolatedString( firstEl.Concat( properties.SelectMany(p => new InterpolatedStringContentSyntax[] { InterpolatedStringGenerationHelper.Text($" "), SyntaxFactory.Interpolation(GenerateNameOfCall(p.Identifier)), InterpolatedStringGenerationHelper.Text($"="), SyntaxFactory.Interpolation(SyntaxFactory.IdentifierName(p.Identifier.WithoutTrivia())) }))); var methodExpression = MethodGenerationHelper.Builder(ToStringMethodName) .Modifiers(Modifiers.Public, Modifiers.Override) .ReturnType(Types.String) .ArrowBody(ExpressionGenerationHelper.Arrow(interpolatedStringExpression)) .Build(); var previousToString = ClassDeclarationSyntaxAnalysis.GetMembers <MethodDeclarationSyntax>( classDeclarationSyntax) .FirstOrDefault(m => m.Identifier.ValueText.Equals(ToStringMethodName)); var newClassDeclaration = previousToString != null? classDeclarationSyntax.ReplaceNode(previousToString, methodExpression) : classDeclarationSyntax.AddMembers(methodExpression); var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false); var newRoot = root.ReplaceNode(classDeclarationSyntax, newClassDeclaration); var newDocument = document.WithSyntaxRoot(newRoot); return(newDocument); }
public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var(document, classDeclarationSyntax) = await context.FindSyntaxForCurrentSpan <ClassDeclarationSyntax>(); if (document == null || classDeclarationSyntax == null) { return; } if (!ClassDeclarationSyntaxAnalysis.IsRecordLike(classDeclarationSyntax)) { return; } var(atMostOneConstructor, nonTrivialConstructorCandidate) = ClassDeclarationSyntaxAnalysis.HasAtMostOneNoneTrivialConstructor(classDeclarationSyntax); if (!atMostOneConstructor || nonTrivialConstructorCandidate == null) { return; } var properties = ClassDeclarationSyntaxAnalysis.GetPropertyDeclarations(classDeclarationSyntax).ToList(); if (properties.Count == 0 || properties.Any(PropertyDeclarationSyntaxExtensions.IsStatic)) { return; } var cancellationToken = context.CancellationToken; var semanticModel = await document.GetSemanticModelAsync(cancellationToken); var constructorSymbol = semanticModel.GetDeclaredSymbol(nonTrivialConstructorCandidate); var propertySymbols = properties.Select(p => semanticModel.GetDeclaredSymbol(p, cancellationToken)).ToArray(); var analyser = new ConstructorPropertyRelationshipAnalyser( Array.Empty <IFieldSymbol>(), propertySymbols); var result = analyser.Analyze(semanticModel, nonTrivialConstructorCandidate); var(idxMappings, isExhaustive) = result.GetIndexMapping(propertySymbols, constructorSymbol); if (!isExhaustive) { return; } context.RegisterRefactoring( new DelegateCodeAction( "Generate with methods for parameters", (c) => GenerateWithMethods(document, classDeclarationSyntax, properties, idxMappings, c))); return; }
private static async Task <Document> GenerateCreateMethod( Document document, ConstructorDeclarationSyntax constructor, CancellationToken cancellationToken) { var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var classDeclaration = constructor .Parent.FirstAncestorOrSelf <ClassDeclarationSyntax>(); if (classDeclaration == null) { return(document); } var classType = SyntaxFactory.IdentifierName( classDeclaration.Identifier.WithoutTrivia()); var createObjectExpression = ExpressionGenerationHelper.CreateObject( classType, constructor.ParameterList.ToArgumentArray()); var createMethodExpression = MethodGenerationHelper.Builder(CreateMethodName) .Modifiers(Modifiers.Public, Modifiers.Static) .Parameters(constructor.ParameterList.Parameters.ToArray()) .ReturnType(SyntaxFactory.IdentifierName(classDeclaration.Identifier.WithoutTrivia())) .ArrowBody(ExpressionGenerationHelper.Arrow(createObjectExpression)) .Build(); // TODO: validate argument types as well ? var previousCreate = ClassDeclarationSyntaxAnalysis.GetMembers <MethodDeclarationSyntax>(classDeclaration) .FirstOrDefault(m => m.Identifier.ValueText.Equals(CreateMethodName) && m.ParameterList.Parameters.Count == createMethodExpression.ParameterList.Parameters.Count); // TODO: align with "with" method generation var newClassDeclaration = classDeclaration; if (previousCreate == null) { createMethodExpression = createMethodExpression .NormalizeWhitespace(elasticTrivia: false) .WithLeadingTrivia( Settings.EndOfLine, SyntaxFactory.ElasticSpace) .WithTrailingTrivia(Settings.EndOfLine); newClassDeclaration = newClassDeclaration.InsertNodesAfter(constructor, new[] { createMethodExpression }); } else { createMethodExpression = createMethodExpression .NormalizeWhitespace(elasticTrivia: false) .WithTriviaFrom(previousCreate); newClassDeclaration = newClassDeclaration.ReplaceNode(previousCreate, createMethodExpression); } var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false); var newRoot = root.ReplaceNode(classDeclaration, newClassDeclaration); var newDocument = document.WithSyntaxRoot(newRoot); return(newDocument); }
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var(document, classDeclarationSyntax) = await context.FindSyntaxForCurrentSpan <ClassDeclarationSyntax>(); if (document == null || classDeclarationSyntax == null) { return; } if (ClassDeclarationSyntaxAnalysis.IsPartial(classDeclarationSyntax) || ClassDeclarationSyntaxAnalysis.IsStatic(classDeclarationSyntax)) { return; } var(atMostOneNonTrivialConstructor, nonTrivialConstructorCandidate) = ClassDeclarationSyntaxAnalysis.HasAtMostOneNoneTrivialConstructor(classDeclarationSyntax); if (!atMostOneNonTrivialConstructor) { return; } var(_, propertyDeclaration) = await context.FindSyntaxForCurrentSpan <PropertyDeclarationSyntax>(); var(_, fieldDeclaration) = await context.FindSyntaxForCurrentSpan <FieldDeclarationSyntax>(); // TODO: Skip properties like string Prop => "something"; if ((fieldDeclaration == null && propertyDeclaration == null) || (fieldDeclaration != null && fieldDeclaration.IsStatic()) || (propertyDeclaration != null && propertyDeclaration.IsStatic())) { return; } var(_, fieldVariableDeclaration) = await context.FindVariableDeclaratorForCurrentSpan(); if (fieldDeclaration != null && fieldVariableDeclaration == null) { return; } switch (nonTrivialConstructorCandidate) { case ConstructorDeclarationSyntax candidate: { var cancellationToken = context.CancellationToken; var semanticModel = await document.GetSemanticModelAsync(cancellationToken); HandleCandidate(semanticModel, candidate, propertyDeclaration, fieldDeclaration, fieldVariableDeclaration); } break; case null: { var trivialConstructor = ClassDeclarationSyntaxAnalysis.GetConstructors(classDeclarationSyntax)?.FirstOrDefault(); if (trivialConstructor != null) { var cancellationToken = context.CancellationToken; var semanticModel = await document.GetSemanticModelAsync(cancellationToken); HandleCandidate(semanticModel, trivialConstructor, propertyDeclaration, fieldDeclaration, fieldVariableDeclaration); } else { // TODO: register add constructor refactoring } } break; } void HandleCandidate( SemanticModel model, ConstructorDeclarationSyntax candidate, PropertyDeclarationSyntax pDeclaration, FieldDeclarationSyntax fDeclaration, VariableDeclaratorSyntax variableDeclarator) { bool isProp = pDeclaration != null; var constructorSymbol = model.GetDeclaredSymbol(candidate) as IMethodSymbol; ISymbol memberSymbol = isProp ? model.GetDeclaredSymbol(pDeclaration) : model.GetDeclaredSymbol(variableDeclarator); if (constructorSymbol == null || memberSymbol == null) { return; } var analyser = new ConstructorPropertyRelationshipAnalyser( isProp ? Array.Empty <IFieldSymbol>() : new[] { memberSymbol as IFieldSymbol }, isProp ? new[] { memberSymbol as IPropertySymbol } : Array.Empty <IPropertySymbol>()); var result = analyser.Analyze(model, candidate); var assignmentResult = result.GetResult(memberSymbol); AnalysedDeclaration ad = isProp ? (AnalysedDeclaration) new PropertyDeclaration(memberSymbol as IPropertySymbol, pDeclaration) : new FieldDeclaration(memberSymbol as IFieldSymbol, fDeclaration, variableDeclarator); switch (assignmentResult) { case AssignmentExpressionAnalyserResult r: // register remove as assignment exists context.RegisterRefactoring( new DelegateCodeAction( $"Remove {ad.Identifier.ValueText}", (c) => RefactoringActions.RemoveParameter( document, classDeclarationSyntax, ad, candidate, c))); break; case EmptyAssignmentAnalyserResult _: // register add to constructor context.RegisterRefactoring( new DelegateCodeAction( $"Add {ad.Identifier.ValueText} to constructor", (c) => RefactoringActions.AddParameterToConstructor( document, classDeclarationSyntax, ad, constructorSymbol, candidate, c))); break; case MultipleAssignments _: case ParsingError _: default: // Something went wrong so it might be better to do nothing. break; } } }
private static async Task <Document> GenerateWithMethods( Document document, ClassDeclarationSyntax classDeclarationSyntax, IList <PropertyDeclarationSyntax> properties, int[] propertyToParameterIdx, CancellationToken cancellationToken) { int MappedIdx(int i) { int mappedIdx = propertyToParameterIdx[i]; if (mappedIdx == -1) { throw new ArgumentException($"{nameof(propertyToParameterIdx)} contains invalid mapping"); } return(mappedIdx); } ArgumentSyntax[] ReorderArgumentsToMapping(IReadOnlyList <ArgumentSyntax> arguments) { var result = new ArgumentSyntax[arguments.Count]; for (int i = 0; i < arguments.Count; ++i) { result[MappedIdx(i)] = arguments[i]; } return(result); } T ExecuteWithTempArg <T>(ArgumentSyntax[] args, int argIdx, ArgumentSyntax tempArg, Func <ArgumentSyntax[], T> operation) { var oldArg = args[argIdx]; args[argIdx] = tempArg; var result = operation(args); args[argIdx] = oldArg; return(result); } if (properties.Count != propertyToParameterIdx.Length) { throw new ArgumentException("properties.Count != propertyToParameterIdx.Length"); } var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); if (properties.Count == 0) { return(document); } var classType = SyntaxFactory.IdentifierName(classDeclarationSyntax.Identifier.WithoutTrivia()); var newClassDeclaration = classDeclarationSyntax; var argumentsArray = properties.Select(p => p.ToArgument()).ToArray(); argumentsArray = ReorderArgumentsToMapping(argumentsArray); for (int i = 0; i < properties.Count; ++i) { var p = properties[i]; var id = p.Identifier.WithoutTrivia(); var lcId = SyntaxHelpers.LowercaseIdentifierFirstLetter(id); var methodName = WithRefactoringUtils.MethodName(p.Identifier); var arg = SyntaxHelpers.ArgumentFromIdentifier(lcId); var objectCreation = ExecuteWithTempArg(argumentsArray, MappedIdx(i), arg, args => ExpressionGenerationHelper.CreateObject(classType, args)); var withMethodExpression = MethodGenerationHelper.Builder(methodName) .Modifiers(Modifiers.Public) .Parameters(SyntaxHelpers.Parameter(p.Type, lcId)) .ReturnType(SyntaxFactory.IdentifierName(classDeclarationSyntax.Identifier.WithoutTrivia())) .ArrowBody(ExpressionGenerationHelper.Arrow(objectCreation)) .Build(); var previousWith = ClassDeclarationSyntaxAnalysis.GetMembers <MethodDeclarationSyntax>( newClassDeclaration) .FirstOrDefault(m => m.Identifier.ValueText.Equals(methodName)); if (previousWith == null) { // TODO: Group with members next to each other rather than adding at the end withMethodExpression = withMethodExpression .NormalizeWhitespace(elasticTrivia: false) .WithLeadingTrivia( Settings.EndOfLine, SyntaxFactory.ElasticSpace) .WithTrailingTrivia(Settings.EndOfLine); newClassDeclaration = newClassDeclaration.AddMembers(withMethodExpression); } else { withMethodExpression = withMethodExpression .NormalizeWhitespace(elasticTrivia: false) .WithTriviaFrom(previousWith); newClassDeclaration = newClassDeclaration.ReplaceNode(previousWith, withMethodExpression); } } var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false); var newRoot = root.ReplaceNode(classDeclarationSyntax, newClassDeclaration); var newDocument = document.WithSyntaxRoot(newRoot); return(newDocument); }