33// BSD-style license that can be found in the LICENSE file.
44
55import 'package:analysis_server/src/protocol_server.dart' hide Element;
6+ import 'package:analysis_server/src/services/correction/dart/convert_null_check_to_null_aware_element_or_entry.dart' ;
67import 'package:analysis_server/src/services/correction/status.dart' ;
78import 'package:analysis_server/src/services/correction/util.dart' ;
89import 'package:analysis_server/src/services/refactoring/legacy/refactoring.dart' ;
@@ -47,7 +48,7 @@ SourceRange _getLocalsConflictingRange(AstNode node) {
4748
4849/// Returns the source which should replace given invocation with given
4950/// arguments.
50- Future <String > _getMethodSourceForInvocation (
51+ Future <_InlineMethodResult > _getMethodSourceForInvocation (
5152 RefactoringStatus status,
5253 ChangeBuilder builder,
5354 String file,
@@ -58,9 +59,14 @@ Future<String> _getMethodSourceForInvocation(
5859 AstNode contextNode,
5960 Expression ? targetExpression,
6061 List <Expression > arguments,
62+ Set <String > previouslyIntroducedVariableNames,
63+ Map <FormalParameterElement , String > parameterToVariableName,
6164) async {
6265 // prepare edits to replace parameters with arguments
66+ var conflictingNames = _getNamesConflictingAt (contextNode);
67+ conflictingNames.addAll (previouslyIntroducedVariableNames);
6368 var edits = < SourceEdit > [];
69+ var variableDeclarations = < _VariableDeclaration > [];
6470 await builder.addDartFileEdit (file, (builder) {
6571 part._typeParameters.forEach ((
6672 TypeParameterElement element,
@@ -126,6 +132,42 @@ Future<String> _getMethodSourceForInvocation(
126132 argumentSource = parameter.defaultValueCode;
127133 argumentSource ?? = 'null' ;
128134 }
135+
136+ var existingVariableName = parameterToVariableName[parameter];
137+ var addVariable =
138+ existingVariableName != null ||
139+ _shouldIntroduceVariable (argument, occurrences.length);
140+ if (addVariable) {
141+ // Only add a new variable if we haven't already produced one in a
142+ // previous call to this method (we may be called twice, once for
143+ // statements and once for the return expression).
144+ if (existingVariableName == null ) {
145+ var originalName = parameter.displayName;
146+ if (originalName.isEmpty) {
147+ originalName = 'p' ;
148+ }
149+ var uniqueName = existingVariableName = _getUniqueName (
150+ originalName,
151+ conflictingNames,
152+ );
153+ conflictingNames.add (uniqueName);
154+ parameterToVariableName[parameter] = uniqueName;
155+
156+ // Record the variable declaration to be inserted above the inline code.
157+ // We can't insert it now because this might be an expression and not a
158+ // block of statements (for example `var a = inlineMe();`).
159+ variableDeclarations.add (
160+ _VariableDeclaration (uniqueName, argumentSource),
161+ );
162+ }
163+
164+ // Update the values used for replacing the expression in the body
165+ // to this newly-created variable.
166+ argumentPrecedence = Precedence .primary;
167+ argumentSource = existingVariableName;
168+ argument = null ;
169+ }
170+
129171 // replace all occurrences of this parameter
130172 for (var occurrence in occurrences) {
131173 AstNode nodeToReplace = occurrence.identifier;
@@ -134,7 +176,7 @@ Future<String> _getMethodSourceForInvocation(
134176 if (occurrence.identifier.parent
135177 case InterpolationExpression interpolation) {
136178 switch (argument) {
137- case SimpleIdentifier ():
179+ case SimpleIdentifier () || null :
138180 occurrenceArgumentSource = argumentSource;
139181 case SingleStringLiteral (canDiscardSingleQuotes: true ):
140182 nodeToReplace = interpolation;
@@ -150,6 +192,18 @@ Future<String> _getMethodSourceForInvocation(
150192 } else {
151193 occurrenceArgumentSource = argumentSource;
152194 }
195+
196+ // If we're replacing this occurrence with a variable and it's
197+ // parenthesized (and the parenthesized expression is only this node)
198+ // then we can remove the parens.
199+ if (addVariable) {
200+ var parent = occurrence.identifier.parent;
201+ if (parent is ParenthesizedExpression &&
202+ parent.expression == occurrence.identifier) {
203+ nodeToReplace = parent;
204+ }
205+ }
206+
153207 // do replace
154208 var nodeToReplaceRange = range.offsetBy (
155209 range.node (nodeToReplace),
@@ -181,19 +235,10 @@ Future<String> _getMethodSourceForInvocation(
181235 }
182236 }
183237 // prepare edits to replace conflicting variables
184- var conflictingNames = _getNamesConflictingAt (contextNode);
185238 part._variables.forEach ((VariableElement variable, List <SourceRange > ranges) {
186239 var originalName = variable.displayName;
187240 // prepare unique name
188- String uniqueName;
189- {
190- uniqueName = originalName;
191- var uniqueIndex = 2 ;
192- while (conflictingNames.contains (uniqueName)) {
193- uniqueName = originalName + uniqueIndex.toString ();
194- uniqueIndex++ ;
195- }
196- }
241+ var uniqueName = _getUniqueName (originalName, conflictingNames);
197242 // update references, if name was change
198243 if (uniqueName != originalName) {
199244 for (var range in ranges) {
@@ -203,7 +248,8 @@ Future<String> _getMethodSourceForInvocation(
203248 });
204249 // prepare source with applied arguments
205250 edits.sort ((SourceEdit a, SourceEdit b) => b.offset - a.offset);
206- return SourceEdit .applySequence (part._source, edits);
251+ var source = SourceEdit .applySequence (part._source, edits);
252+ return _InlineMethodResult (source, variableDeclarations);
207253}
208254
209255/// Returns the names which will shadow or will be shadowed by any declaration
@@ -243,6 +289,43 @@ Set<String> _getNamesConflictingAt(AstNode node) {
243289 return result;
244290}
245291
292+ /// Returns a unique name that can be used instead of [originalName] by
293+ /// appending a number.
294+ String _getUniqueName (String originalName, Set <String > conflictingNames) {
295+ var uniqueName = originalName;
296+ var uniqueIndex = 2 ;
297+ while (conflictingNames.contains (uniqueName)) {
298+ uniqueName = originalName + uniqueIndex.toString ();
299+ uniqueIndex++ ;
300+ }
301+ return uniqueName;
302+ }
303+
304+ /// Returns whether the use of this expression should be assigned to a variable
305+ /// to avoid potential side-effects of evaluating the expression multiple times
306+ /// or re-ordering by inlining further down the code.
307+ bool _shouldIntroduceVariable (Expression ? expression, int occurrenceCount) {
308+ // If it's already a variable, never introduce a new one.
309+ if (expression? .canonicalElement is VariableElement ) {
310+ return false ;
311+ }
312+ // If it's a literal, introduce a variable if it's used more than once.
313+ if (expression is Literal ) {
314+ return occurrenceCount > 1 ;
315+ }
316+ // If it's parenthesized, check the expression.
317+ if (expression is ParenthesizedExpression ) {
318+ return _shouldIntroduceVariable (expression.expression, occurrenceCount);
319+ }
320+ // If it's a binary expression, check if either side needs a variable.
321+ if (expression is BinaryExpression ) {
322+ return _shouldIntroduceVariable (expression.leftOperand, occurrenceCount) ||
323+ _shouldIntroduceVariable (expression.rightOperand, occurrenceCount);
324+ }
325+ // Otherwise, introduce a variable unless there was no expression.
326+ return expression != null ;
327+ }
328+
246329/// [InlineMethodRefactoring] implementation.
247330class InlineMethodRefactoringImpl extends RefactoringImpl
248331 implements InlineMethodRefactoring {
@@ -270,6 +353,13 @@ class InlineMethodRefactoringImpl extends RefactoringImpl
270353 final List <_ReferenceProcessor > _referenceProcessors = [];
271354 final Set <Element > _alreadyMadeAsync = < Element > {};
272355
356+ /// Tracks variable names being introduced by the block they're being added
357+ /// to.
358+ ///
359+ /// Used to prevent reusing the same new variable names when inlinine multiple
360+ /// calls into the same block.
361+ final Map <AstNode , Set <String >> _introducedVariablesByBlock = {};
362+
273363 InlineMethodRefactoringImpl (this .searchEngine, this .unitResult, this .offset)
274364 : sessionHelper = AnalysisSessionHelper (unitResult.session),
275365 utils = CorrectionUtils (unitResult);
@@ -561,6 +651,15 @@ class InlineMethodRefactoringImpl extends RefactoringImpl
561651 }
562652}
563653
654+ /// The result of inlining a method is a combination of new code to inline and
655+ /// a set of variables to be declared.
656+ class _InlineMethodResult {
657+ final String source;
658+ final List <_VariableDeclaration > variableDeclarations;
659+
660+ _InlineMethodResult (this .source, this .variableDeclarations);
661+ }
662+
564663class _ParameterOccurrence {
565664 final int baseOffset;
566665 final SimpleIdentifier identifier;
@@ -677,10 +776,29 @@ class _ReferenceProcessor {
677776 }
678777 // can we inline method body into "methodUsage" block?
679778 if (_canInlineBody (usage)) {
779+ // Get any variables we're previously introduced in this scope so we don't
780+ // redeclare the same name.
781+ var surroundingBlock = usage.thisOrAncestorMatching (
782+ (node) => node is Block || node is LibraryFragment ,
783+ );
784+ var introducedVariableNames = surroundingBlock != null
785+ ? ref._introducedVariablesByBlock.putIfAbsent (
786+ surroundingBlock,
787+ () => {},
788+ )
789+ // Types allow for no block but in practice we should never hit this
790+ // so just track into an empty list.
791+ : < String > {};
792+
793+ // Track which parameters have been assigned to which variables, so we
794+ // can reuse the same variable when the same parameter appears in both
795+ // the statements part and the expression part.
796+ var parameterToVariableName = < FormalParameterElement , String > {};
797+
680798 // insert non-return statements
681799 if (ref._methodStatementsPart != null ) {
682800 // prepare statements source for invocation
683- var source = await _getMethodSourceForInvocation (
801+ var result = await _getMethodSourceForInvocation (
684802 status,
685803 ChangeBuilder (
686804 session: ref.sessionHelper.session,
@@ -694,9 +812,19 @@ class _ReferenceProcessor {
694812 usage,
695813 target,
696814 arguments,
815+ introducedVariableNames,
816+ parameterToVariableName,
697817 );
698- source = _refUtils.replaceSourceIndent (
699- source,
818+
819+ // If there are variable declarations, insert them before the statement.
820+ _insertVariableDeclarations (
821+ result.variableDeclarations,
822+ introducedVariableNames,
823+ _refLineRange,
824+ );
825+
826+ var source = _refUtils.replaceSourceIndent (
827+ result.source,
700828 ref._methodStatementsPart! ._prefix,
701829 _refPrefix,
702830 includeLeading: true ,
@@ -712,7 +840,7 @@ class _ReferenceProcessor {
712840 // replace invocation with return expression
713841 if (ref._methodExpressionPart != null ) {
714842 // prepare expression source for invocation
715- var source = await _getMethodSourceForInvocation (
843+ var result = await _getMethodSourceForInvocation (
716844 status,
717845 ChangeBuilder (
718846 session: ref.sessionHelper.session,
@@ -726,8 +854,19 @@ class _ReferenceProcessor {
726854 usage,
727855 target,
728856 arguments,
857+ introducedVariableNames,
858+ parameterToVariableName,
859+ );
860+
861+ // If there are variable declarations, insert them before the statement.
862+ _insertVariableDeclarations (
863+ result.variableDeclarations,
864+ introducedVariableNames,
865+ _refLineRange,
729866 );
730867
868+ var source = result.source;
869+
731870 // If we inline the method expression into a string interpolation,
732871 // and the expression is not a single identifier, wrap it into `{}`.
733872 AstNode nodeToReplace = usage;
@@ -786,6 +925,30 @@ class _ReferenceProcessor {
786925 _addRefEdit (edit);
787926 }
788927
928+ /// Inserts [variableDeclarations] in front of the code at [refLineRange] .
929+ void _insertVariableDeclarations (
930+ List <_VariableDeclaration > variableDeclarations,
931+ Set <String > introducedVariableNames,
932+ SourceRange ? refLineRange,
933+ ) {
934+ if (variableDeclarations.isEmpty) return ;
935+
936+ var offset = _refLineRange? .offset ?? _node.offset;
937+ var insertOffset = _refUtils.getLineThis (offset);
938+
939+ var buffer = StringBuffer ();
940+ for (var variable in variableDeclarations) {
941+ // Record the name as used so we don't reuse it at other call sites in the
942+ // block
943+ introducedVariableNames.add (variable.name);
944+
945+ buffer.write (
946+ '${_refPrefix }var ${variable .name } = ${variable .initializer };${_refUtils .endOfLine }' ,
947+ );
948+ }
949+ _addRefEdit (SourceEdit (insertOffset, 0 , buffer.toString ()));
950+ }
951+
789952 Future <void > _process (RefactoringStatus status) async {
790953 var nodeParent = _node.parent;
791954 // may be only single place should be inlined
@@ -1049,6 +1212,14 @@ class _SourcePart {
10491212 }
10501213}
10511214
1215+ /// Represents a variable that needs to be declared before inlining.
1216+ class _VariableDeclaration {
1217+ final String name;
1218+ final String initializer;
1219+
1220+ _VariableDeclaration (this .name, this .initializer);
1221+ }
1222+
10521223/// A visitor that fills [_SourcePart] with fields, parameters and variables.
10531224class _VariablesVisitor extends GeneralizingAstVisitor <void > {
10541225 /// The [ExecutableElement] being inlined.
0 commit comments