예제 #1
0
        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);
        }
예제 #2
0
        private static void AddTaskUtilities(
            RecordBuilder recordBuilder,
            TypeSyntax tcsType)
        {
            var tcs = GeneratorHelper.GenericName(
                "TaskCompletionSource",
                tcsType);

            var initializer = ExpressionGenerationHelper.CreateObject(tcs);

            recordBuilder.AddField(tcs, "result", initializer);

            var resolveMethod = new MethodBuilder(GH.IdentifierToken("Resolve"))
                                .Modifiers(Modifiers.Public)
                                .AddParameter(tcsType, GH.IdentifierToken("value"))
                                .Body(new BodyBuilder()
                                      .AddVoidMemberInvocation(
                                          GH.Identifier("result"),
                                          GH.Identifier("TrySetResult"),
                                          SF.Argument(GH.Identifier("value")))
                                      .Build())
                                .Build();

            var cancelMethod = new MethodBuilder(GH.IdentifierToken("Cancel"))
                               .Modifiers(Modifiers.Public)
                               .Body(new BodyBuilder()
                                     .AddVoidMemberInvocation(
                                         GH.Identifier("result"),
                                         GH.Identifier("TrySetCanceled"))
                                     .Build())
                               .Build();

            var rejectMethod = new MethodBuilder(GH.IdentifierToken("Reject"))
                               .Modifiers(Modifiers.Public)
                               .AddParameter(GH.Identifier("Exception"), GH.IdentifierToken("exc"))
                               .Body(new BodyBuilder()
                                     .AddVoidMemberInvocation(
                                         GH.Identifier("result"),
                                         GH.Identifier("TrySetException"),
                                         SF.Argument(GH.Identifier("exc")))
                                     .Build())
                               .Build();

            recordBuilder.AddMethod(resolveMethod);
            recordBuilder.AddMethod(cancelMethod);
            recordBuilder.AddMethod(rejectMethod);
        }
예제 #3
0
        public static async Task <Document> AddParameterToConstructor(
            Document document,
            ClassDeclarationSyntax classDeclaration,
            AnalysedDeclaration analysedDeclaration, // must be either field variable or property
            IMethodSymbol constructorSymbol,
            ConstructorDeclarationSyntax constructorDeclaration,
            CancellationToken cancellationToken)
        {
            const int AppendPosition = int.MaxValue;

            var semanticModel = await document.GetSemanticModelAsync(cancellationToken);

            var classMemberAnalysis = new ClassMembersAnalysis(classDeclaration, semanticModel);
            var analyser            = new ConstructorPropertyRelationshipAnalyser(
                classMemberAnalysis.Fields.Select(f => f.Symbol).ToArray(),
                classMemberAnalysis.Properties.Select(f => f.Symbol).ToArray());
            var result = analyser.Analyze(semanticModel, constructorDeclaration);

            // Filter to consider only fields and properties that are assigned in constructor.
            var filteredClassMembers = classMemberAnalysis.WithFilteredProperties(
                (in FieldInfo fi) => result.GetResult(fi.Symbol) is AssignmentExpressionAnalyserResult,
                (in PropertyInfo pi) => result.GetResult(pi.Symbol) is AssignmentExpressionAnalyserResult);

            // Find closest declaration among the ones that are assigned in the constructor
            var(closestSymbol, isBeforeFoundSymbol) = filteredClassMembers.GetClosestFieldOrProperty(analysedDeclaration.Symbol);
            int constructorInsertPosition = AppendPosition;

            if (closestSymbol != null)
            {
                // There is another member that is assigned in constructor
                constructorInsertPosition = result.GetMatchingParameterIdx(constructorSymbol, closestSymbol);
                if (!isBeforeFoundSymbol)
                {
                    ++constructorInsertPosition;
                }
            }

            // TODO: resolve name clashes if parameter with a given name already exists?
            var addedParameter            = SyntaxHelpers.LowercaseIdentifierFirstLetter(analysedDeclaration.Identifier);
            var newConstructorDeclaration = constructorDeclaration.InsertParameter(
                SyntaxHelpers.Parameter(
                    analysedDeclaration.Type,
                    addedParameter),
                constructorInsertPosition);

            AssignmentExpressionSyntax closestSymbolAssignment = null;

            if (closestSymbol != null && result.GetResult(closestSymbol) is AssignmentExpressionAnalyserResult res)
            {
                closestSymbolAssignment = res.Assignment;
            }

            var fieldIdentifier     = SyntaxFactory.IdentifierName(analysedDeclaration.Identifier);
            var parameterIdentifier = SyntaxFactory.IdentifierName(addedParameter);
            var leftSide            = addedParameter.ValueText.Equals(analysedDeclaration.Identifier.ValueText, StringComparison.Ordinal) ?
                                      (ExpressionSyntax)ExpressionGenerationHelper.ThisMemberAccess(fieldIdentifier)
                : fieldIdentifier;
            var assignment = ExpressionGenerationHelper.SimpleAssignment(leftSide, parameterIdentifier);

            var statementToAdd = SyntaxFactory.ExpressionStatement(assignment);
            var body           = constructorDeclaration.Body;

            var closestStatement = closestSymbolAssignment?.Parent as ExpressionStatementSyntax;

            if (closestStatement == null)
            {
                newConstructorDeclaration = newConstructorDeclaration
                                            .AddBodyStatements(statementToAdd)
                                            .WithAdditionalAnnotations(Formatter.Annotation);
            }
            else if (isBeforeFoundSymbol)
            {
                var newBody = body.InsertBefore(closestStatement, statementToAdd);
                newConstructorDeclaration = newConstructorDeclaration.WithBody(
                    newBody).WithAdditionalAnnotations(Formatter.Annotation);
            }
            else
            {
                var newBody = body.InsertAfter(closestStatement, statementToAdd);
                newConstructorDeclaration = newConstructorDeclaration.WithBody(
                    newBody).WithAdditionalAnnotations(Formatter.Annotation);
            }

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

            var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false);

            var newRoot     = root.ReplaceNode(constructorDeclaration, newConstructorDeclaration);
            var newDocument = document.WithSyntaxRoot(newRoot);

            return(newDocument);
        }
예제 #4
0
        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);
        }
예제 #5
0
        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);
        }
예제 #6
0
        public async Task Test2()
        {
            // Arrange
            var text = @"
using System;
using System.Threading.Tasks;

namespace RefactorClasses.Analysis.Test
{
    [StateMachine(ContextType = typeof(ContextBase), StateType = typeof(StateBase), TriggerType = (typeof(TriggerBase)))]
    public class StateMachineImpl
    {
        public void DoSomething(
            int a,
            Test1 testClass, string fdeee)
        {
        }

        public async Task<int> TaskMethodReturningSomething(int a, float b)
        {
            return 10;
        }

        public System.Threading.Tasks.Task AsyncOperationsSupport(int a, float b)
        {
            return Task.CompletedTask;
        }

        public async Task TaskMethod(int a, float b)
        {
            return;
        }

        public async Task TaskMethodWithArrays(int[] a, float[] b)
        {
            return;
        }

        public async Task TaskMethodWithTuples((int, float) a, float[] b)
        {
            return;
        }

        private void PrintSomething() {}
    }

    public class TriggerBase { }

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
    public sealed class StateMachineAttribute : Attribute
    {
        public Type StateType { get; set; }

        public Type TriggerType { get; set; }

        public Type ContextType { get; set; }
    }
}";

            var tree             = CSharpSyntaxTree.ParseText(text);
            var compilation      = TestHelpers.CreateCompilation(tree);
            var semanticModel    = compilation.GetSemanticModel(tree);
            var classDeclaration = TestHelpers.FindFirstClassDeclaration(await tree.GetRootAsync());

            var classInspector    = new ClassInspector(classDeclaration);
            var semanticInspector = classInspector.CreateSemanticQuery(semanticModel);

            bool foundAttribute = semanticInspector.TryFindFirstAttributeMatching(
                "StateMachineAttribute", out var atData);
            var triggerType = atData
                              ?.NamedArguments
                              .FirstOrDefault(kvp => kvp.Key.Equals("TriggerType"));

            if (triggerType == null)
            {
                return;
            }

            var methods = classInspector.FindMatchingMethods(
                mi => mi.Check(m => m.IsPublic() && !m.IsStatic()).Passed);

            foreach (var method in methods)
            {
                var msq          = method.CreateSemanticQuery(semanticModel);
                var returnType   = msq.GetReturnType();
                var isTaskReturn = IsTask(returnType.Symbol);

                var parameters = method.Parameters.Select(par => par.Type).ToList();

                // TODO: will throw if array
                var triggerTypeName = triggerType.Value.Value.Value as INamedTypeSymbol;
                if (triggerTypeName == null)
                {
                    return;
                }

                var recordBuilder = new RecordBuilder(method.Name)
                                    .AddModifiers(Modifiers.Public)
                                    .AddBaseTypes(GeneratorHelper.Identifier(triggerTypeName.Name))
                                    .AddProperties(
                    method.Parameters
                    .Select(p => (p.Type, p.Name)).ToArray());

                if (isTaskReturn.Value.IsTask())
                {
                    var boolTcs = GeneratorHelper.GenericName(
                        "TaskCompletionSource",
                        Types.Bool);

                    var initializer = ExpressionGenerationHelper.CreateObject(boolTcs);
                    recordBuilder.AddField(boolTcs, "result", initializer);

                    var resolveMethod = new MethodBuilder(GH.IdentifierToken("Resolve"))
                                        .Body(new BodyBuilder()
                                              .AddVoidMemberInvocation(
                                                  GH.Identifier("result"),
                                                  GH.Identifier("TrySetResult"),
                                                  SF.Argument(GH.Identifier("true")))
                                              .Build())
                                        .Build();

                    var cancelMethod = new MethodBuilder(GH.IdentifierToken("Cancel"))
                                       .Body(new BodyBuilder()
                                             .AddVoidMemberInvocation(
                                                 GH.Identifier("result"),
                                                 GH.Identifier("TrySetCanceled"))
                                             .Build())
                                       .Build();

                    var rejectMethod = new MethodBuilder(GH.IdentifierToken("Cancel"))
                                       .AddParameter(GH.Identifier("Exception"), GH.IdentifierToken("exc"))
                                       .Body(new BodyBuilder()
                                             .AddVoidMemberInvocation(
                                                 GH.Identifier("result"),
                                                 GH.Identifier("TrySetException"),
                                                 SF.Argument(GH.Identifier("exc")))
                                             .Build())
                                       .Build();

                    recordBuilder.AddMethod(resolveMethod);
                    recordBuilder.AddMethod(cancelMethod);
                    recordBuilder.AddMethod(rejectMethod);

                    int ddddd = 0;
                }
                else if (isTaskReturn.Value.IsTypedTask(out var taskType))
                {
                    var typedTcs = GeneratorHelper.GenericName(
                        "TaskCompletionSource",
                        GeneratorHelper.Identifier(taskType.Name));

                    var initializer = ExpressionGenerationHelper.CreateObject(typedTcs);
                    recordBuilder.AddField(typedTcs, "result", initializer);
                }

                var record = recordBuilder.Build();

                // TODO: if task is returned -> generate TaskCompletionSource
                // and matching methods

                var rs = record.ToString();

                int a = 10;
            }

            // Act

            // Assert

            IsTaskResult?IsTask(ISymbol symbol)
            {
                var namedSymbol = symbol as INamedTypeSymbol;

                if (namedSymbol == null)
                {
                    return(null);
                }

                if (namedSymbol.Name == "Task" &&
                    namedSymbol?.ContainingNamespace?.ToString() == "System.Threading.Tasks")
                {
                    var firstTypeArg = namedSymbol.TypeArguments.FirstOrDefault();
                    if (firstTypeArg != null)
                    {
                        return(IsTaskResult.TypedTask(firstTypeArg));
                    }
                    else
                    {
                        return(IsTaskResult.Task());
                    }
                }

                return(IsTaskResult.NotATask());
            }

            //var tcs = new TaskCompletionSource<int>();
            //tcs.TrySetException()
        }