private async Task <SyntaxNode> GetChangedCallerAsync(Document document, TInvocationSyntax calleeInvocationNode, IMethodSymbol calleeMethodSymbol, ISymbol callerSymbol, SyntaxNode callerDeclarationNode, TStatementSyntax?statementContainsInvocation, TExpressionSyntax rawInlineExpression, MethodParametersInfo methodParametersInfo, InlineMethodContext inlineMethodContext, CancellationToken cancellationToken) { var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var syntaxGenerator = SyntaxGenerator.GetGenerator(document); var callerNodeEditor = new SyntaxEditor(callerDeclarationNode, syntaxGenerator); if (inlineMethodContext.ContainsAwaitExpression) { // If the inline content has 'await' expression, then make sure the caller is changed to 'async' method // if its return type is awaitable. In all other cases, do nothing. if (callerSymbol is IMethodSymbol callerMethodSymbol && callerMethodSymbol.IsOrdinaryMethod() && !callerMethodSymbol.IsAsync && (callerMethodSymbol.ReturnsVoid || callerMethodSymbol.IsAwaitableNonDynamic(semanticModel, callerDeclarationNode.SpanStart))) { var declarationModifiers = DeclarationModifiers.From(callerSymbol).WithAsync(true); callerNodeEditor.SetModifiers(callerDeclarationNode, declarationModifiers); } } if (statementContainsInvocation != null) { foreach (var statement in inlineMethodContext.StatementsToInsertBeforeInvocationOfCallee) { // Add a CarriageReturn to make sure for VB the statement would be in different line. callerNodeEditor.InsertBefore(statementContainsInvocation, statement.WithAppendedTrailingTrivia(_syntaxFacts.ElasticCarriageReturnLineFeed)); } } var(nodeToReplace, inlineNode) = GetInlineNode( calleeInvocationNode, calleeMethodSymbol, statementContainsInvocation, rawInlineExpression, methodParametersInfo, inlineMethodContext, semanticModel, syntaxGenerator, cancellationToken); callerNodeEditor.ReplaceNode(nodeToReplace, (node, generator) => inlineNode); return(callerNodeEditor.GetChangedRoot()); }
private async Task <InlineMethodContext> GetInlineMethodContextAsync( Document document, TMethodDeclarationSyntax calleeMethodNode, TInvocationSyntax calleeInvocationNode, IMethodSymbol calleeMethodSymbol, TExpressionSyntax rawInlineExpression, MethodParametersInfo methodParametersInfo, CancellationToken cancellationToken) { var inlineExpression = rawInlineExpression; var syntaxGenerator = SyntaxGenerator.GetGenerator(document); var callerSemanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var calleeDocument = document.Project.Solution.GetRequiredDocument(calleeMethodNode.SyntaxTree); var calleeSemanticModel = await calleeDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); // Generate a map which the key is the symbol need renaming, value is the new name. // After inlining, there might be naming conflict because // case 1: caller's identifier is introduced to callee. // Example (for identifier): // Before: // void Caller() // { // int i = 10; // Callee(i) // } // void Callee(int j) // { // DoSomething(out var i, j); // } // After inline it should be: // void Caller() // { // int i = 10; // DoSomething(out var i1, i); // } // void Callee(int j) // { // DoSomething(out var i, j); // } // Case 2: callee's parameter is introduced to caller // Before: // void Caller() // { // int i = 0; // int j = 1; // Callee(Foo()) // } // void Callee(int i) // { // Bar(i, out int j); // } // After: // void Caller() // { // int i = 10; // int j = 1; // int i1 = Foo(); // Bar(i1, out int j2); // } // void Callee(int i) // { // Bar(i, out int j); // } var renameTable = ComputeRenameTable( _semanticFactsService, callerSemanticModel, calleeSemanticModel, calleeInvocationNode, rawInlineExpression, methodParametersInfo.ParametersToGenerateFreshVariablesFor .SelectAsArray(parameterAndArgument => parameterAndArgument.parameterSymbol), cancellationToken); // Generate all the statements need to be put in the caller. // Use the parameter's name to generate declarations in caller might cause conflict in the caller, // so the rename table is needed to provide with a valid name. // Example: // Before: // void Caller(int i, int j) // { // Callee(Foo(), Bar()); // } // void Callee(int i, int j) // { // DoSomething(i, j); // } // After: // void Caller(int i, int j) // { // int i1 = Foo(); // int j1 = Bar(); // DoSomething(i1, j1) // } // void Callee(int i, int j) // { // DoSomething(i, j); // } // Here two declaration is generated. // Another case is // void Caller() // { // Callee(out int i) // } // void Callee(out int j) // { // DoSomething(out j, out int i); // } // After: // void Caller() // { // int i = 10; // DoSomething(out i, out int i2); // } // void Callee(out int j) // { // DoSomething(out j, out int i); // } var localDeclarationStatementsNeedInsert = GetLocalDeclarationStatementsNeedInsert( syntaxGenerator, methodParametersInfo.ParametersToGenerateFreshVariablesFor, methodParametersInfo.MergeInlineContentAndVariableDeclarationArgument ? ImmutableArray <(IParameterSymbol, string)> .Empty : methodParametersInfo.ParametersWithVariableDeclarationArgument, renameTable); // Get a table which the key is the symbol needs replacement. Value is the replacement syntax node // Included 3 cases: // 1. Type arguments (generics) // Example: // Before: // void Caller() // { // Callee<int>(); // } // void Callee<T>() => Print(typeof<T>); // After: // void Caller() // { // Print(typeof<int>); // } // void Callee<T>() => Print(typeof<int>); // 2. Literal replacement // Example: // Before: // void Caller() // { // Callee(20) // } // void Callee(int i, int j = 10) // { // Bar(i, j); // } // After: // void Caller() // { // Bar(20, 10); // } // void Callee(int i, int j = 10) // { // Bar(i, j); // } // 3. Identifier // Example: // Before: // void Caller() // { // int a, b, c; // Callee(a, b) // } // void Callee(int i, int j) // { // Bar(i, j, out int c); // } // After: // void Caller() // { // int a, b, c; // Bar(a, b, out int c1); // } // void Callee(int i, int j = 10) // { // Bar(i, j, out int c); // } var replacementTable = ComputeReplacementTable( calleeMethodSymbol, methodParametersInfo.ParametersWithVariableDeclarationArgument, methodParametersInfo.ParametersToReplace, syntaxGenerator, renameTable); var containsAwaitExpression = ContainsAwaitExpression(rawInlineExpression); // Do the replacement work within the callee's body so that it can be inserted to the caller later. inlineExpression = await ReplaceAllSyntaxNodesForSymbolAsync( calleeDocument, inlineExpression, syntaxGenerator, replacementTable, cancellationToken).ConfigureAwait(false); return(new InlineMethodContext( localDeclarationStatementsNeedInsert, inlineExpression, containsAwaitExpression)); }
private (SyntaxNode nodeToReplace, SyntaxNode inlineNode) GetInlineNode( TInvocationSyntax calleeInvocationNode, IMethodSymbol calleeMethodSymbol, TStatementSyntax?statementContainsInvocation, TExpressionSyntax rawInlineExpression, MethodParametersInfo methodParametersInfo, InlineMethodContext inlineMethodContext, SemanticModel semanticModel, SyntaxGenerator syntaxGenerator, CancellationToken cancellationToken) { if (statementContainsInvocation != null) { if (methodParametersInfo.MergeInlineContentAndVariableDeclarationArgument) { var rightHandSideValue = _syntaxFacts.GetRightHandSideOfAssignment(inlineMethodContext.InlineExpression); var(parameterSymbol, name) = methodParametersInfo.ParametersWithVariableDeclarationArgument.Single(); var declarationNode = (TStatementSyntax)syntaxGenerator .LocalDeclarationStatement(parameterSymbol.Type, name, rightHandSideValue); return(statementContainsInvocation, declarationNode.WithTriviaFrom(statementContainsInvocation)); } if (_syntaxFacts.IsThrowStatement(rawInlineExpression.Parent) && _syntaxFacts.IsExpressionStatement(calleeInvocationNode.Parent)) { var throwStatement = (TStatementSyntax)syntaxGenerator .ThrowStatement(inlineMethodContext.InlineExpression); return(statementContainsInvocation, throwStatement.WithTriviaFrom(statementContainsInvocation)); } if (_syntaxFacts.IsThrowExpression(rawInlineExpression) && _syntaxFacts.IsExpressionStatement(calleeInvocationNode.Parent)) { // Example: // Before: // void Caller() { Callee(); } // void Callee() => throw new Exception(); // After: // void Caller() { throw new Exception(); } // void Callee() => throw new Exception(); // Note: Throw expression is converted to throw statement var throwStatement = (TStatementSyntax)syntaxGenerator .ThrowStatement(_syntaxFacts.GetExpressionOfThrowExpression(inlineMethodContext.InlineExpression)); return(statementContainsInvocation, throwStatement.WithTriviaFrom(statementContainsInvocation)); } if (_syntaxFacts.IsExpressionStatement(calleeInvocationNode.Parent) && !calleeMethodSymbol.ReturnsVoid && !IsValidExpressionUnderExpressionStatement(inlineMethodContext.InlineExpression)) { // If the callee is invoked as ExpressionStatement, but the inlined expression in the callee can't be // placed under ExpressionStatement // Example: // void Caller() // { // Callee(); // } // int Callee() // { // return 1; // }; // After it should be: // void Caller() // { // int temp = 1; // } // int Callee() // { // return 1; // }; // One variable declaration needs to be generated. var unusedLocalName = _semanticFactsService.GenerateUniqueLocalName( semanticModel, calleeInvocationNode, containerOpt: null, TemporaryName, cancellationToken); var localDeclarationNode = (TStatementSyntax)syntaxGenerator .LocalDeclarationStatement(calleeMethodSymbol.ReturnType, unusedLocalName.Text, inlineMethodContext.InlineExpression); return(statementContainsInvocation, localDeclarationNode.WithTriviaFrom(statementContainsInvocation)); } } if (_syntaxFacts.IsThrowStatement(rawInlineExpression.Parent)) { // Example: // Before: // void Caller() => Callee(); // void Callee() { throw new Exception(); } // After: // void Caller() => throw new Exception(); // void Callee() { throw new Exception(); } // Note: Throw statement is converted to throw expression if (CanBeReplacedByThrowExpression(calleeInvocationNode)) { var throwExpression = (TExpressionSyntax)syntaxGenerator .ThrowExpression(inlineMethodContext.InlineExpression) .WithTriviaFrom(calleeInvocationNode); return(calleeInvocationNode, throwExpression.WithTriviaFrom(calleeInvocationNode)); } } var inlineExpression = inlineMethodContext.InlineExpression; if (!_syntaxFacts.IsExpressionStatement(calleeInvocationNode.Parent) && !calleeMethodSymbol.ReturnsVoid && !_syntaxFacts.IsThrowExpression(inlineMethodContext.InlineExpression)) { // Add type cast and parenthesis to the inline expression. // It is required to cover cases like: // Case 1 (parenthesis added): // Before: // void Caller() { var x = 3 * Callee(); } // int Callee() { return 1 + 2; } // // After // void Caller() { var x = 3 * (1 + 2); } // int Callee() { return 1 + 2; } // // Case 2 (type cast) // Before: // void Caller() { var x = Callee(); } // long Callee() { return 1 } // // After // void Caller() { var x = (long)1; } // int Callee() { return 1; } // // Case 3 (type cast & additional parenthesis) // Before: // void Caller() { var x = Callee()(); } // Func<int> Callee() { return () => 1; } // After: // void Caller() { var x = ((Func<int>)(() => 1))(); } // Func<int> Callee() { return () => 1; } inlineExpression = (TExpressionSyntax)syntaxGenerator.AddParentheses( syntaxGenerator.CastExpression( GenerateTypeSyntax(calleeMethodSymbol.ReturnType, allowVar: false), syntaxGenerator.AddParentheses(inlineMethodContext.InlineExpression))); } return(calleeInvocationNode, inlineExpression.WithTriviaFrom(calleeInvocationNode)); }
private void AddMethods(IHTMLElement htext) { var content = new IHTMLDiv().AttachTo(DocumentBody); content.Hide(); var IsInterface = new IHTMLInput(ScriptCoreLib.Shared.HTMLInputTypeEnum.checkbox); new IHTMLDiv( new IHTMLLabel("is an interface: ", IsInterface), IsInterface ).AttachTo(content); var DelegatesParams = new IHTMLInput(ScriptCoreLib.Shared.HTMLInputTypeEnum.checkbox); new IHTMLDiv( new IHTMLLabel("delegates parameters to base constructor: ", DelegatesParams), DelegatesParams ).AttachTo(content); var update = default(Action); // var a = new IHTMLTextArea().AttachTo(content); IHTMLButton.Create( "Example code", delegate { a.value = @"addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void Registers an event listener object with an EventDispatcher object so that the listener receives notification of an event. "; update(); } ).AttachTo(content); var b = new IHTMLTextArea().AttachTo(content); htext.onclick += delegate { content.ToggleVisible(); }; a.style.display = ScriptCoreLib.JavaScript.DOM.IStyle.DisplayEnum.block; a.style.width = "100%"; a.style.height = "20em"; b.style.display = ScriptCoreLib.JavaScript.DOM.IStyle.DisplayEnum.block; b.style.width = "100%"; b.style.height = "20em"; b.readOnly = true; Action update_output = delegate { var w = new StringBuilder(); var w2 = new StringBuilder(); var lines = a.Lines.ToArray(); w.AppendLine("#region Methods"); w2.AppendLine("#region Constructors"); for (int i = 0; i < lines.Length; i += 3) { if ((i + 1) < lines.Length) { var StaticKeyword = "[static]"; var Summary = lines[i + 1].Trim(); var MethodSig = lines[i].Trim(); if (!MethodSig.Contains("AIR-only")) { var q0 = MethodSig.Split(')'); var q1 = q0[0].Split('('); var MethodName = FixVariableName(q1[0].Trim()); var MethodParameters = new MethodParametersInfo(q1[1].Trim()); var MethodReturnType = ""; if (q0[1].StartsWith(":")) MethodReturnType = FixTypeName(q0[1].Substring(1).Trim()); var IsConstructor = string.IsNullOrEmpty(MethodReturnType); foreach (var v in MethodParameters.Variations) { if (IsConstructor) { w2.AppendLine("/// <summary>"); w2.AppendLine("/// " + Summary); w2.AppendLine("/// </summary>"); if (DelegatesParams.@checked) w2.AppendLine("public " + MethodName + "(" + v + ") : base(" + v.NamesToString() + ")"); else w2.AppendLine("public " + MethodName + "(" + v + ")"); w2.AppendLine("{"); w2.AppendLine("}"); w2.AppendLine(); } else { if (v.Parameters.Length == 0 && MethodName == "toString") { } else { w.AppendLine("/// <summary>"); w.AppendLine("/// " + Summary); w.AppendLine("/// </summary>"); var StaticModifier = Summary.Contains(StaticKeyword) ? "static " : ""; if (IsInterface.@checked) { w.AppendLine(MethodReturnType + " " + MethodName + "(" + v + ");"); } else { w.AppendLine("public " + StaticModifier + MethodReturnType + " " + MethodName + "(" + v + ")"); w.AppendLine("{"); if (MethodReturnType != "void") w.AppendLine(" return default(" + MethodReturnType + ");"); w.AppendLine("}"); } w.AppendLine(); } } } } } } w.AppendLine("#endregion"); w2.AppendLine("#endregion"); if (!IsInterface.@checked) { w.AppendLine(); w.Append(w2.ToString()); } b.value = w.ToString(); }; update = delegate { try { update_output(); htext.style.color = Color.Blue; } catch (Exception ex) { htext.style.color = Color.Red; b.value = "error: " + ex.Message; } }; IsInterface.onchange += delegate { update(); }; DelegatesParams.onchange += delegate { update(); }; a.onchange += delegate { update(); }; }