Skip to content

Commit 86c7493

Browse files
author
Dart CI
committed
Version 2.14.0-90.0.dev
Merge commit '484c999f129aacedc6c568d865450c98e4967fbd' into 'dev'
2 parents 4832da6 + 484c999 commit 86c7493

File tree

15 files changed

+476
-3
lines changed

15 files changed

+476
-3
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
6+
import 'package:analysis_server/src/services/correction/fix.dart';
7+
import 'package:analyzer/dart/ast/ast.dart';
8+
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
9+
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
10+
import 'package:analyzer_plugin/utilities/range_factory.dart';
11+
12+
class ConvertForEachToForLoop extends CorrectionProducer {
13+
@override
14+
FixKind get fixKind => DartFixKind.CONVERT_FOR_EACH_TO_FOR_LOOP;
15+
16+
@override
17+
FixKind get multiFixKind => DartFixKind.CONVERT_FOR_EACH_TO_FOR_LOOP_MULTI;
18+
19+
@override
20+
Future<void> compute(ChangeBuilder builder) async {
21+
var invocation = node.parent;
22+
if (invocation is! MethodInvocation) {
23+
return;
24+
}
25+
var statement = invocation.parent;
26+
if (statement is! ExpressionStatement) {
27+
return;
28+
}
29+
var argument = invocation.argumentList.arguments[0];
30+
if (argument is! FunctionExpression) {
31+
return;
32+
}
33+
var parameters = argument.parameters?.parameters;
34+
if (parameters == null || parameters.length != 1) {
35+
return;
36+
}
37+
var parameter = parameters[0];
38+
if (parameter is! NormalFormalParameter) {
39+
return;
40+
}
41+
var loopVariableName = parameter.identifier?.name;
42+
if (loopVariableName == null) {
43+
return;
44+
}
45+
var target = utils.getNodeText(invocation.target!);
46+
var body = argument.body;
47+
if (body is BlockFunctionBody) {
48+
await builder.addDartFileEdit(file, (builder) {
49+
builder.addReplacement(range.startStart(invocation, body), (builder) {
50+
builder.write('for (var ');
51+
builder.write(loopVariableName);
52+
builder.write(' in ');
53+
builder.write(target);
54+
builder.write(') ');
55+
});
56+
builder.addDeletion(range.endEnd(body, statement));
57+
});
58+
} else if (body is ExpressionFunctionBody) {
59+
await builder.addDartFileEdit(file, (builder) {
60+
var expression = body.expression;
61+
var prefix = utils.getPrefix(statement.offset);
62+
builder.addReplacement(range.startStart(invocation, expression),
63+
(builder) {
64+
builder.write('for (var ');
65+
builder.write(loopVariableName);
66+
builder.write(' in ');
67+
builder.write(target);
68+
builder.writeln(') {');
69+
builder.write(prefix);
70+
builder.write(' ');
71+
});
72+
builder.addReplacement(range.endEnd(expression, statement), (builder) {
73+
builder.writeln(';');
74+
builder.write(prefix);
75+
builder.write('}');
76+
});
77+
});
78+
}
79+
}
80+
81+
/// Return an instance of this class. Used as a tear-off in `FixProcessor`.
82+
static ConvertForEachToForLoop newInstance() => ConvertForEachToForLoop();
83+
}

pkg/analysis_server/lib/src/services/correction/fix.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,14 @@ class DartFixKind {
257257
'dart.fix.flutter.convert.childrenToChild',
258258
DartFixKindPriority.DEFAULT,
259259
'Convert to child:');
260+
static const CONVERT_FOR_EACH_TO_FOR_LOOP = FixKind(
261+
'dart.fix.convert.toForLoop',
262+
DartFixKindPriority.DEFAULT,
263+
"Convert 'forEach' to a 'for' loop");
264+
static const CONVERT_FOR_EACH_TO_FOR_LOOP_MULTI = FixKind(
265+
'dart.fix.convert.toForLoop.multi',
266+
DartFixKindPriority.IN_FILE,
267+
"Convert 'forEach' to a 'for' loop everywhere in file");
260268
static const CONVERT_INTO_EXPRESSION_BODY = FixKind(
261269
'dart.fix.convert.toExpressionBody',
262270
DartFixKindPriority.DEFAULT,

pkg/analysis_server/lib/src/services/correction/fix_internal.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import 'package:analysis_server/src/services/correction/dart/convert_map_from_it
4343
import 'package:analysis_server/src/services/correction/dart/convert_quotes.dart';
4444
import 'package:analysis_server/src/services/correction/dart/convert_to_contains.dart';
4545
import 'package:analysis_server/src/services/correction/dart/convert_to_expression_function_body.dart';
46+
import 'package:analysis_server/src/services/correction/dart/convert_to_for_loop.dart';
4647
import 'package:analysis_server/src/services/correction/dart/convert_to_generic_function_syntax.dart';
4748
import 'package:analysis_server/src/services/correction/dart/convert_to_if_null.dart';
4849
import 'package:analysis_server/src/services/correction/dart/convert_to_int_literal.dart';
@@ -452,6 +453,15 @@ class FixProcessor extends BaseProcessor {
452453
],
453454
)
454455
],
456+
LintNames.avoid_function_literals_in_foreach_calls: [
457+
FixInfo(
458+
canBeAppliedToFile: true,
459+
canBeBulkApplied: true,
460+
generators: [
461+
ConvertForEachToForLoop.newInstance,
462+
],
463+
)
464+
],
455465
LintNames.avoid_init_to_null: [
456466
FixInfo(
457467
canBeAppliedToFile: true,

pkg/analysis_server/lib/src/services/linter/lint_names.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ class LintNames {
1313
static const String avoid_annotating_with_dynamic =
1414
'avoid_annotating_with_dynamic';
1515
static const String avoid_empty_else = 'avoid_empty_else';
16+
static const String avoid_function_literals_in_foreach_calls =
17+
'avoid_function_literals_in_foreach_calls';
1618
static const String avoid_init_to_null = 'avoid_init_to_null';
1719
static const String avoid_private_typedef_functions =
1820
'avoid_private_typedef_functions';
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analysis_server/src/services/linter/lint_names.dart';
6+
import 'package:test_reflective_loader/test_reflective_loader.dart';
7+
8+
import 'bulk_fix_processor.dart';
9+
10+
void main() {
11+
defineReflectiveSuite(() {
12+
defineReflectiveTests(ConvertForEachToForLoop);
13+
});
14+
}
15+
16+
@reflectiveTest
17+
class ConvertForEachToForLoop extends BulkFixProcessorTest {
18+
@override
19+
String get lintCode => LintNames.avoid_function_literals_in_foreach_calls;
20+
21+
Future<void> test_blockBody_blockBody() async {
22+
await resolveTestCode(r'''
23+
void f(List<String> a, List<String> b) {
24+
var result = <String>[];
25+
a.forEach((ea) {
26+
b.forEach((eb) {
27+
result.add('$ea $eb');
28+
});
29+
});
30+
}
31+
''');
32+
await assertHasFix(r'''
33+
void f(List<String> a, List<String> b) {
34+
var result = <String>[];
35+
for (var ea in a) {
36+
for (var eb in b) {
37+
result.add('$ea $eb');
38+
}
39+
}
40+
}
41+
''');
42+
}
43+
44+
Future<void> test_blockBody_expressionBody() async {
45+
await resolveTestCode(r'''
46+
void f(List<String> a, List<String> b) {
47+
var result = <String>[];
48+
a.forEach((ea) {
49+
b.forEach((eb) => result.add('$ea $eb'));
50+
});
51+
}
52+
''');
53+
await assertHasFix(r'''
54+
void f(List<String> a, List<String> b) {
55+
var result = <String>[];
56+
for (var ea in a) {
57+
for (var eb in b) {
58+
result.add('$ea $eb');
59+
}
60+
}
61+
}
62+
''');
63+
}
64+
65+
Future<void> test_expressionBody_blockBody() async {
66+
await resolveTestCode(r'''
67+
void f(List<String> a, List<String> b) {
68+
var result = <String>[];
69+
a.forEach((ea) => b.forEach((eb) {
70+
result.add('$ea $eb');
71+
}));
72+
}
73+
''');
74+
await assertHasFix(r'''
75+
void f(List<String> a, List<String> b) {
76+
var result = <String>[];
77+
for (var ea in a) {
78+
b.forEach((eb) {
79+
result.add('$ea $eb');
80+
});
81+
}
82+
}
83+
''');
84+
}
85+
86+
Future<void> test_expressionBody_expressionBody() async {
87+
await resolveTestCode(r'''
88+
void f(List<String> a, List<String> b) {
89+
var result = <String>[];
90+
a.forEach((ea) => b.forEach((eb) => result.add('$ea $eb')));
91+
}
92+
''');
93+
await assertHasFix(r'''
94+
void f(List<String> a, List<String> b) {
95+
var result = <String>[];
96+
for (var ea in a) {
97+
b.forEach((eb) => result.add('$ea $eb'));
98+
}
99+
}
100+
''');
101+
}
102+
}

pkg/analysis_server/test/src/services/correction/fix/bulk/test_all.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'add_required_test.dart' as add_required;
1313
import 'bulk_fix_processor_test.dart' as bulk_fix_processor;
1414
import 'convert_documentation_into_line_test.dart'
1515
as convert_documentation_into_line;
16+
import 'convert_for_each_to_for_loop_test.dart' as convert_for_each_to_for_loop;
1617
import 'convert_map_from_iterable_to_for_literal_test.dart'
1718
as convert_map_from_iterable_to_for_literal;
1819
import 'convert_to_contains_test.dart' as convert_to_contains;
@@ -82,6 +83,7 @@ void main() {
8283
add_required.main();
8384
bulk_fix_processor.main();
8485
convert_documentation_into_line.main();
86+
convert_for_each_to_for_loop.main();
8587
convert_map_from_iterable_to_for_literal.main();
8688
convert_to_contains.main();
8789
convert_to_generic_function_syntax.main();
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analysis_server/src/services/correction/fix.dart';
6+
import 'package:analysis_server/src/services/linter/lint_names.dart';
7+
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
8+
import 'package:test_reflective_loader/test_reflective_loader.dart';
9+
10+
import 'fix_processor.dart';
11+
12+
void main() {
13+
defineReflectiveSuite(() {
14+
defineReflectiveTests(ConvertForEachToForLoopTest);
15+
});
16+
}
17+
18+
@reflectiveTest
19+
class ConvertForEachToForLoopTest extends FixProcessorLintTest {
20+
@override
21+
FixKind get kind => DartFixKind.CONVERT_FOR_EACH_TO_FOR_LOOP;
22+
23+
@override
24+
String get lintCode => LintNames.avoid_function_literals_in_foreach_calls;
25+
26+
Future<void> test_blockBody() async {
27+
await resolveTestCode('''
28+
void f(List<String> list) {
29+
list.forEach((e) {
30+
e.length / 2;
31+
});
32+
}
33+
''');
34+
await assertHasFix('''
35+
void f(List<String> list) {
36+
for (var e in list) {
37+
e.length / 2;
38+
}
39+
}
40+
''');
41+
}
42+
43+
Future<void> test_expressionBody() async {
44+
await resolveTestCode('''
45+
void f(List<String> list) {
46+
list.forEach((e) => e.substring(3, 7));
47+
}
48+
''');
49+
await assertHasFix('''
50+
void f(List<String> list) {
51+
for (var e in list) {
52+
e.substring(3, 7);
53+
}
54+
}
55+
''');
56+
}
57+
}

pkg/analysis_server/test/src/services/correction/fix/test_all.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import 'convert_documentation_into_line_test.dart'
4343
as convert_documentation_into_line;
4444
import 'convert_flutter_child_test.dart' as convert_flutter_child;
4545
import 'convert_flutter_children_test.dart' as convert_flutter_children;
46+
import 'convert_for_each_to_for_loop_test.dart' as convert_for_each_to_for_loop;
4647
import 'convert_into_expression_body_test.dart' as convert_into_expression_body;
4748
import 'convert_to_contains_test.dart' as convert_to_contains;
4849
import 'convert_to_for_element_test.dart' as convert_to_for_element;
@@ -211,6 +212,7 @@ void main() {
211212
convert_documentation_into_line.main();
212213
convert_flutter_child.main();
213214
convert_flutter_children.main();
215+
convert_for_each_to_for_loop.main();
214216
convert_into_expression_body.main();
215217
convert_to_contains.main();
216218
convert_to_for_element.main();

pkg/dev_compiler/lib/src/kernel/compiler.dart

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2642,14 +2642,37 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
26422642
}
26432643

26442644
var nameExpr = _emitTopLevelName(p);
2645+
var jsName = _safeFunctionNameForSafari(p.name.text, fn);
26452646
body.add(js.statement('# = #',
2646-
[nameExpr, js_ast.NamedFunction(_emitTemporaryId(p.name.text), fn)]));
2647+
[nameExpr, js_ast.NamedFunction(_emitTemporaryId(jsName), fn)]));
26472648

26482649
_currentUri = savedUri;
26492650
_staticTypeContext.leaveMember(p);
26502651
return js_ast.Statement.from(body);
26512652
}
26522653

2654+
/// Choose a safe name for [fn].
2655+
///
2656+
/// Most of the time we use [candidateName], except if the name collides
2657+
/// with a parameter name and the function contains default parameter values.
2658+
///
2659+
/// In ES6, functions containing default parameter values, which DDC
2660+
/// generates when Dart uses positional optional parameters, cannot have
2661+
/// two parameters with the same name. Because we have a similar restriction
2662+
/// in Dart, this is not normally an issue we need to pay attention to.
2663+
/// However, a bug in Safari makes it a syntax error to have the function
2664+
/// name overlap with the parameter names as well. This rename works around
2665+
/// such bug (dartbug.com/43520).
2666+
static String _safeFunctionNameForSafari(
2667+
String candidateName, js_ast.Fun fn) {
2668+
if (fn.params.any((p) => p is js_ast.DestructuredVariable)) {
2669+
while (fn.params.any((a) => a.parameterName == candidateName)) {
2670+
candidateName = '$candidateName\$';
2671+
}
2672+
}
2673+
return candidateName;
2674+
}
2675+
26532676
js_ast.Expression _emitFunctionTagged(js_ast.Expression fn, FunctionType type,
26542677
{bool topLevel = false}) {
26552678
var lazy = topLevel && !_canEmitTypeAtTopLevel(type);

0 commit comments

Comments
 (0)