Skip to content

Commit 5dc0e3c

Browse files
scheglovCommit Queue
authored andcommitted
Issue 57019. Fix for inlining method expression into interpolation.
Bug: #57019 Change-Id: Ib69093db3fdfb6e8f4cf3d7a8e6036fda59faf97 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/402587 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Konstantin Shcheglov <[email protected]>
1 parent cb93352 commit 5dc0e3c

File tree

2 files changed

+268
-13
lines changed

2 files changed

+268
-13
lines changed

pkg/analysis_server/lib/src/services/refactoring/legacy/inline_method.dart

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -93,18 +93,36 @@ String _getMethodSourceForInvocation(
9393
}
9494
// replace all occurrences of this parameter
9595
for (var occurrence in occurrences) {
96-
var range = occurrence.range;
96+
AstNode nodeToReplace = occurrence.identifier;
9797
// prepare argument source to apply at this occurrence
9898
String occurrenceArgumentSource;
99-
if (occurrence.inStringInterpolation && argument is! SimpleIdentifier) {
100-
occurrenceArgumentSource = '{$argumentSource}';
99+
if (occurrence.identifier.parent
100+
case InterpolationExpression interpolation) {
101+
switch (argument) {
102+
case SimpleIdentifier():
103+
occurrenceArgumentSource = argumentSource;
104+
case SingleStringLiteral(canDiscardSingleQuotes: true):
105+
nodeToReplace = interpolation;
106+
occurrenceArgumentSource = argumentSource.substring(
107+
1,
108+
argumentSource.length - 1,
109+
);
110+
default:
111+
occurrenceArgumentSource = '{$argumentSource}';
112+
}
101113
} else if (argumentPrecedence < occurrence.parentPrecedence) {
102114
occurrenceArgumentSource = '($argumentSource)';
103115
} else {
104116
occurrenceArgumentSource = argumentSource;
105117
}
106118
// do replace
107-
edits.add(newSourceEdit_range(range, occurrenceArgumentSource));
119+
var nodeToReplaceRange = range.offsetBy(
120+
range.node(nodeToReplace),
121+
-occurrence.baseOffset,
122+
);
123+
edits.add(
124+
newSourceEdit_range(nodeToReplaceRange, occurrenceArgumentSource),
125+
);
108126
}
109127
});
110128
// replace static field "qualifier" with invocation target
@@ -484,12 +502,14 @@ class InlineMethodRefactoringImpl extends RefactoringImpl
484502
}
485503

486504
class _ParameterOccurrence {
487-
final SourceRange range;
505+
final int baseOffset;
506+
final SimpleIdentifier identifier;
488507
final Precedence parentPrecedence;
489508
final bool inStringInterpolation;
490509

491510
_ParameterOccurrence({
492-
required this.range,
511+
required this.baseOffset,
512+
required this.identifier,
493513
required this.parentPrecedence,
494514
required this.inStringInterpolation,
495515
});
@@ -625,19 +645,38 @@ class _ReferenceProcessor {
625645
target,
626646
arguments,
627647
);
648+
649+
// If we inline the method expression into a string interpolation,
650+
// and the expression is not a single identifier, wrap it into `{}`.
651+
AstNode nodeToReplace = usage;
652+
if (usage.parent case InterpolationExpression interpolation) {
653+
if (interpolation.leftBracket.lexeme == r'$') {
654+
switch (ref._methodExpression) {
655+
case SimpleIdentifier():
656+
break;
657+
case SingleStringLiteral(canDiscardSingleQuotes: true):
658+
nodeToReplace = interpolation;
659+
source = source.substring(1, source.length - 1);
660+
default:
661+
source = '{$source}';
662+
}
663+
}
664+
}
665+
628666
if (getExpressionPrecedence(ref._methodExpression!) <
629667
getExpressionParentPrecedence(usage)) {
630668
source = '($source)';
631669
}
670+
632671
// do replace
633-
var methodUsageRange = range.node(usage);
672+
var nodeToReplaceRange = range.node(nodeToReplace);
634673
var awaitKeyword = Keyword.AWAIT.lexeme;
635674
if (usage.parent is AwaitExpression &&
636675
source.startsWith(awaitKeyword)) {
637676
// remove the duplicate await keyword and the following whitespace.
638677
source = source.substring(awaitKeyword.length + 1);
639678
}
640-
var edit = newSourceEdit_range(methodUsageRange, source);
679+
var edit = newSourceEdit_range(nodeToReplaceRange, source);
641680
_addRefEdit(edit);
642681
} else {
643682
var edit = newSourceEdit_range(_refLineRange!, '');
@@ -842,7 +881,7 @@ class _SourcePart {
842881

843882
void addParameterOccurrence({
844883
required FormalParameterElement parameter,
845-
required SourceRange identifierRange,
884+
required SimpleIdentifier identifier,
846885
required Precedence parentPrecedence,
847886
required bool inStringInterpolation,
848887
}) {
@@ -851,11 +890,11 @@ class _SourcePart {
851890
occurrences = [];
852891
_parameters[parameter] = occurrences;
853892
}
854-
identifierRange = range.offsetBy(identifierRange, -_base);
855893
occurrences.add(
856894
_ParameterOccurrence(
895+
baseOffset: _base,
857896
parentPrecedence: parentPrecedence,
858-
range: identifierRange,
897+
identifier: identifier,
859898
inStringInterpolation: inStringInterpolation,
860899
),
861900
);
@@ -966,11 +1005,10 @@ class _VariablesVisitor extends GeneralizingAstVisitor<void> {
9661005
return;
9671006
}
9681007
// OK, add occurrence
969-
var nodeRange = range.node(node);
9701008
var parentPrecedence = getExpressionParentPrecedence(node);
9711009
result.addParameterOccurrence(
9721010
parameter: parameterElement,
973-
identifierRange: nodeRange,
1011+
identifier: node,
9741012
parentPrecedence: parentPrecedence,
9751013
inStringInterpolation: node.parent is InterpolationExpression,
9761014
);
@@ -984,3 +1022,14 @@ class _VariablesVisitor extends GeneralizingAstVisitor<void> {
9841022
}
9851023
}
9861024
}
1025+
1026+
extension on SingleStringLiteral {
1027+
/// Whether this literal can be inlined as its content.
1028+
/// The literal can have interpolations itself.
1029+
bool get canDiscardSingleQuotes {
1030+
if (isMultiline || isRaw) {
1031+
return false;
1032+
}
1033+
return true;
1034+
}
1035+
}

pkg/analysis_server/test/services/refactoring/legacy/inline_method_test.dart

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,132 @@ void f() {
11461146
expect(refactoring.inlineAll, false);
11471147
}
11481148

1149+
Future<void> test_intoStringInterpolation2_integerLiteral() async {
1150+
await indexTestUnit(r'''
1151+
void f() {
1152+
'a: $test';
1153+
}
1154+
1155+
int get test => 0;
1156+
''');
1157+
_createRefactoring('test =>');
1158+
// validate change
1159+
return _assertSuccessfulRefactoring(r'''
1160+
void f() {
1161+
'a: ${0}';
1162+
}
1163+
''');
1164+
}
1165+
1166+
Future<void>
1167+
test_intoStringInterpolation2_literal_simpleStringLiteral_raw() async {
1168+
await indexTestUnit(r'''
1169+
void f() {
1170+
'a: $test';
1171+
}
1172+
1173+
String get test => r'\n';
1174+
''');
1175+
_createRefactoring('test =>');
1176+
// validate change
1177+
return _assertSuccessfulRefactoring(r'''
1178+
void f() {
1179+
'a: ${r'\n'}';
1180+
}
1181+
''');
1182+
}
1183+
1184+
Future<void> test_intoStringInterpolation2_propertyAccess() async {
1185+
await indexTestUnit(r'''
1186+
void f() {
1187+
'a: $test';
1188+
}
1189+
1190+
int get test => 0.sign;
1191+
''');
1192+
_createRefactoring('test =>');
1193+
// validate change
1194+
return _assertSuccessfulRefactoring(r'''
1195+
void f() {
1196+
'a: ${0.sign}';
1197+
}
1198+
''');
1199+
}
1200+
1201+
Future<void> test_intoStringInterpolation2_propertyAccess_already() async {
1202+
await indexTestUnit(r'''
1203+
void f() {
1204+
'a: ${test}';
1205+
}
1206+
1207+
int get test => 0.sign;
1208+
''');
1209+
_createRefactoring('test =>');
1210+
// validate change
1211+
return _assertSuccessfulRefactoring(r'''
1212+
void f() {
1213+
'a: ${0.sign}';
1214+
}
1215+
''');
1216+
}
1217+
1218+
Future<void> test_intoStringInterpolation2_simpleIdentifier() async {
1219+
await indexTestUnit(r'''
1220+
void f() {
1221+
'a: $test';
1222+
}
1223+
1224+
const value = 0;
1225+
int get test => value;
1226+
''');
1227+
_createRefactoring('test =>');
1228+
// validate change
1229+
return _assertSuccessfulRefactoring(r'''
1230+
void f() {
1231+
'a: $value';
1232+
}
1233+
1234+
const value = 0;
1235+
''');
1236+
}
1237+
1238+
Future<void> test_intoStringInterpolation2_simpleStringLiteral() async {
1239+
await indexTestUnit(r'''
1240+
void f() {
1241+
'a: $test';
1242+
}
1243+
1244+
String get test => 'b';
1245+
''');
1246+
_createRefactoring('test =>');
1247+
// validate change
1248+
return _assertSuccessfulRefactoring(r'''
1249+
void f() {
1250+
'a: b';
1251+
}
1252+
''');
1253+
}
1254+
1255+
Future<void> test_intoStringInterpolation2_stringInterpolation() async {
1256+
await indexTestUnit(r'''
1257+
void f() {
1258+
'a: $test';
1259+
}
1260+
1261+
const value = 0;
1262+
String get test => 'foo $value bar';
1263+
''');
1264+
_createRefactoring('test =>');
1265+
// validate change
1266+
return _assertSuccessfulRefactoring(r'''
1267+
void f() {
1268+
'a: foo $value bar';
1269+
}
1270+
1271+
const value = 0;
1272+
''');
1273+
}
1274+
11491275
Future<void> test_intoStringInterpolation_identifier() async {
11501276
await indexTestUnit(r'''
11511277
void f() {
@@ -1186,6 +1312,86 @@ void f() {
11861312
''');
11871313
}
11881314

1315+
Future<void> test_intoStringInterpolation_propertyAccess() async {
1316+
await indexTestUnit(r'''
1317+
void f(int v) {
1318+
test(v.isEven);
1319+
}
1320+
1321+
void test(bool a) {
1322+
'a: $a';
1323+
}
1324+
''');
1325+
_createRefactoring('test(bool');
1326+
// validate change
1327+
return _assertSuccessfulRefactoring(r'''
1328+
void f(int v) {
1329+
'a: ${v.isEven}';
1330+
}
1331+
''');
1332+
}
1333+
1334+
Future<void> test_intoStringInterpolation_simpleStringLiteral() async {
1335+
await indexTestUnit(r'''
1336+
void f() {
1337+
test('b');
1338+
}
1339+
1340+
void test(String a) {
1341+
'a: $a';
1342+
}
1343+
''');
1344+
_createRefactoring('test(String');
1345+
// validate change
1346+
return _assertSuccessfulRefactoring(r'''
1347+
void f() {
1348+
'a: b';
1349+
}
1350+
''');
1351+
}
1352+
1353+
Future<void> test_intoStringInterpolation_simpleStringLiteral_raw() async {
1354+
await indexTestUnit(r'''
1355+
void f() {
1356+
test(r'\n');
1357+
}
1358+
1359+
void test(String a) {
1360+
'a: $a';
1361+
}
1362+
''');
1363+
_createRefactoring('test(String');
1364+
// validate change
1365+
return _assertSuccessfulRefactoring(r'''
1366+
void f() {
1367+
'a: ${r'\n'}';
1368+
}
1369+
''');
1370+
}
1371+
1372+
Future<void> test_intoStringInterpolation_stringInterpolation() async {
1373+
await indexTestUnit(r'''
1374+
const value = 0;
1375+
1376+
void f() {
1377+
test('foo $value bar');
1378+
}
1379+
1380+
void test(String a) {
1381+
'a: $a';
1382+
}
1383+
''');
1384+
_createRefactoring('test(String');
1385+
// validate change
1386+
return _assertSuccessfulRefactoring(r'''
1387+
const value = 0;
1388+
1389+
void f() {
1390+
'a: foo $value bar';
1391+
}
1392+
''');
1393+
}
1394+
11891395
Future<void> test_method_async() async {
11901396
await indexTestUnit(r'''
11911397
import 'dart:async';

0 commit comments

Comments
 (0)