Skip to content

Commit 4097d30

Browse files
srawlinscommit-bot@chromium.org
authored andcommitted
Migrator: Insert required keyword in correct position
Previously, migration might change: m({@required @deprecated x}) {} to m({required @deprecated x}) {} Which is illegal syntax. Change-Id: Ib8397e9e22dbb951758a04c085c40905860a7cb3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/170026 Commit-Queue: Samuel Rawlins <srawlins@google.com> Reviewed-by: Paul Berry <paulberry@google.com>
1 parent f25c50f commit 4097d30

File tree

6 files changed

+159
-53
lines changed

6 files changed

+159
-53
lines changed

pkg/nnbd_migration/lib/src/edit_plan.dart

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -495,11 +495,11 @@ class EditPlanner {
495495
}
496496
}
497497

498-
/// Creates a new edit plan that removes [node] from the AST.
498+
/// Creates a new edit plan that removes [sourceNode] from the AST.
499499
///
500-
/// [node] must be one element of a variable length sequence maintained by
501-
/// [node]'s parent (for example, a statement in a block, an element in a
502-
/// list, a declaration in a class, etc.). If it is not, an exception is
500+
/// [sourceNode] must be one element of a variable length sequence maintained
501+
/// by [sourceNode]'s parent (for example, a statement in a block, an element
502+
/// in a list, a declaration in a class, etc.). If it is not, an exception is
503503
/// thrown.
504504
///
505505
/// Optional argument [info] contains information about why the change was
@@ -674,9 +674,10 @@ class EditPlanner {
674674
///
675675
/// Optional argument [info] contains information about why the change was
676676
/// made.
677-
EditPlan tryRemoveNode(AstNode sourceNode, {AtomicEditInfo info}) {
677+
EditPlan tryRemoveNode(AstNode sourceNode,
678+
{List<AstNode> sequenceNodes, AtomicEditInfo info}) {
678679
var parent = sourceNode.parent;
679-
var sequenceNodes = _computeSequenceNodes(parent);
680+
sequenceNodes ??= _computeSequenceNodes(parent);
680681
if (sequenceNodes == null) {
681682
return null;
682683
}
@@ -809,6 +810,8 @@ class EditPlanner {
809810
return node.elements;
810811
} else if (node is ArgumentList) {
811812
return node.arguments;
813+
} else if (node is FormalParameter) {
814+
return node.metadata;
812815
} else if (node is FormalParameterList) {
813816
return node.parameters;
814817
} else if (node is VariableDeclarationList) {
@@ -1525,7 +1528,8 @@ class _PassThroughBuilderImpl implements PassThroughBuilder {
15251528
AstNode parent, List<AstNode> childNodes) {
15261529
if (parent is Block ||
15271530
parent is ClassDeclaration ||
1528-
parent is CompilationUnit) {
1531+
parent is CompilationUnit ||
1532+
parent is FormalParameter) {
15291533
// These parent types don't use separators.
15301534
return null;
15311535
} else {

pkg/nnbd_migration/lib/src/fix_aggregator.dart

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,14 @@ class NodeChangeForDefaultFormalParameter
666666
/// contained in the edit.
667667
AtomicEditInfo addRequiredKeywordInfo;
668668

669+
/// If non-null, indicates a `@required` annotation which should be removed
670+
/// from this node.
671+
Annotation annotationToRemove;
672+
673+
/// If [annotationToRemove] is non-null, the information that should be
674+
/// contained in the edit.
675+
AtomicEditInfo removeAnnotationInfo;
676+
669677
NodeChangeForDefaultFormalParameter() : super._();
670678

671679
@override
@@ -676,8 +684,17 @@ class NodeChangeForDefaultFormalParameter
676684
EditPlan _apply(DefaultFormalParameter node, FixAggregator aggregator) {
677685
var innerPlan = aggregator.innerPlanForNode(node);
678686
if (!addRequiredKeyword) return innerPlan;
679-
return aggregator.planner.surround(innerPlan,
680-
prefix: [AtomicEdit.insert('required ', info: addRequiredKeywordInfo)]);
687+
688+
var offset = node.firstTokenAfterCommentAndMetadata.offset;
689+
return aggregator.planner.passThrough(node, innerPlans: [
690+
aggregator.planner.insertText(node, offset, [
691+
AtomicEdit.insert('required ', info: addRequiredKeywordInfo),
692+
]),
693+
if (annotationToRemove != null)
694+
aggregator.planner
695+
.removeNode(annotationToRemove, info: removeAnnotationInfo),
696+
...aggregator.innerPlansForNode(node),
697+
]);
681698
}
682699
}
683700

pkg/nnbd_migration/lib/src/fix_builder.dart

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,20 +1145,33 @@ class _FixBuilderPreVisitor extends GeneralizingAstVisitor<void>
11451145
cls.name, method.name, element.name),
11461146
{FixReasonTarget.root: node});
11471147
var metadata = parameter.metadata;
1148-
for (var annotation in metadata) {
1149-
if (annotation.elementAnnotation.isRequired) {
1150-
// TODO(paulberry): what if `@required` isn't the first annotation?
1151-
// Will we produce something that isn't grammatical?
1152-
(_fixBuilder._getChange(annotation) as NodeChangeForAnnotation)
1148+
if (metadata != null && metadata.isNotEmpty) {
1149+
// Only the last annotation can be changed into a `required` keyword;
1150+
// changing an earlier annotation into a keyword would be illegal.
1151+
var lastAnnotation = metadata.last;
1152+
if (lastAnnotation.elementAnnotation.isRequired) {
1153+
(_fixBuilder._getChange(lastAnnotation) as NodeChangeForAnnotation)
11531154
..changeToRequiredKeyword = true
11541155
..changeToRequiredKeywordInfo = info;
11551156
return;
11561157
}
11571158
}
11581159
// Otherwise create a new `required` keyword.
1159-
(_fixBuilder._getChange(parameter) as NodeChangeForDefaultFormalParameter)
1160+
var nodeChange = (_fixBuilder._getChange(parameter)
1161+
as NodeChangeForDefaultFormalParameter)
11601162
..addRequiredKeyword = true
11611163
..addRequiredKeywordInfo = info;
1164+
var requiredAnnotation = metadata?.firstWhere(
1165+
(annotation) => annotation.elementAnnotation.isRequired,
1166+
orElse: () => null);
1167+
if (requiredAnnotation != null) {
1168+
// If the parameter was annotated with `@required`, but it was not the
1169+
// last annotation, we remove the annotation in addition to adding the
1170+
// `required` keyword.
1171+
nodeChange
1172+
..annotationToRemove = requiredAnnotation
1173+
..removeAnnotationInfo = info;
1174+
}
11621175
}
11631176

11641177
void _makeTypeNameNullable(TypeAnnotation node, DecoratedType decoratedType) {

pkg/nnbd_migration/lib/src/node_builder.dart

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'package:_fe_analyzer_shared/src/scanner/token.dart';
65
import 'package:analyzer/dart/ast/ast.dart';
76
import 'package:analyzer/dart/ast/visitor.dart';
87
import 'package:analyzer/dart/element/element.dart';
@@ -894,40 +893,3 @@ class NodeBuilder extends GeneralizingAstVisitor<DecoratedType>
894893
throw UnimplementedError(buffer.toString());
895894
}
896895
}
897-
898-
extension on FormalParameter {
899-
// TODO(srawlins): Add this to FormalParameter interface.
900-
Token get firstTokenAfterCommentAndMetadata {
901-
var parameter = this is DefaultFormalParameter
902-
? (this as DefaultFormalParameter).parameter
903-
: this as NormalFormalParameter;
904-
if (parameter is FieldFormalParameter) {
905-
if (parameter.keyword != null) {
906-
return parameter.keyword;
907-
} else if (parameter.type != null) {
908-
return parameter.type.beginToken;
909-
} else {
910-
return parameter.thisKeyword;
911-
}
912-
} else if (parameter is FunctionTypedFormalParameter) {
913-
if (parameter.covariantKeyword != null) {
914-
return parameter.covariantKeyword;
915-
} else if (parameter.returnType != null) {
916-
return parameter.returnType.beginToken;
917-
} else {
918-
return parameter.identifier.token;
919-
}
920-
} else if (parameter is SimpleFormalParameter) {
921-
if (parameter.covariantKeyword != null) {
922-
return parameter.covariantKeyword;
923-
} else if (parameter.keyword != null) {
924-
return parameter.keyword;
925-
} else if (parameter.type != null) {
926-
return parameter.type.beginToken;
927-
} else {
928-
return parameter.identifier.token;
929-
}
930-
}
931-
return null;
932-
}
933-
}

pkg/nnbd_migration/lib/src/utilities/hint_utils.dart

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'package:_fe_analyzer_shared/src/scanner/token.dart';
6+
import 'package:analyzer/dart/ast/ast.dart';
67
import 'package:nnbd_migration/src/edit_plan.dart';
78

89
/// Determines if the given [token] is followed by a nullability hint, and if
@@ -226,3 +227,40 @@ enum HintCommentKind {
226227
/// required.
227228
required,
228229
}
230+
231+
extension FormalParameterExtensions on FormalParameter {
232+
// TODO(srawlins): Add this to FormalParameter interface.
233+
Token get firstTokenAfterCommentAndMetadata {
234+
var parameter = this is DefaultFormalParameter
235+
? (this as DefaultFormalParameter).parameter
236+
: this as NormalFormalParameter;
237+
if (parameter is FieldFormalParameter) {
238+
if (parameter.keyword != null) {
239+
return parameter.keyword;
240+
} else if (parameter.type != null) {
241+
return parameter.type.beginToken;
242+
} else {
243+
return parameter.thisKeyword;
244+
}
245+
} else if (parameter is FunctionTypedFormalParameter) {
246+
if (parameter.covariantKeyword != null) {
247+
return parameter.covariantKeyword;
248+
} else if (parameter.returnType != null) {
249+
return parameter.returnType.beginToken;
250+
} else {
251+
return parameter.identifier.token;
252+
}
253+
} else if (parameter is SimpleFormalParameter) {
254+
if (parameter.covariantKeyword != null) {
255+
return parameter.covariantKeyword;
256+
} else if (parameter.keyword != null) {
257+
return parameter.keyword;
258+
} else if (parameter.type != null) {
259+
return parameter.type.beginToken;
260+
} else {
261+
return parameter.identifier.token;
262+
}
263+
}
264+
return null;
265+
}
266+
}

pkg/nnbd_migration/test/fix_aggregator_test.dart

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,60 @@ main() {}
244244
expect(previewInfo.applyTo(code), 'f({required int x}) => 0;');
245245
}
246246

247+
Future<void> test_addRequired_afterMetadata() async {
248+
await analyze('f({@deprecated int x}) => 0;');
249+
var previewInfo = run({
250+
findNode.defaultParameter('int x'): NodeChangeForDefaultFormalParameter()
251+
..addRequiredKeyword = true
252+
});
253+
expect(previewInfo.applyTo(code), 'f({@deprecated required int x}) => 0;');
254+
}
255+
256+
Future<void> test_addRequired_afterMetadata_andRequiredAnnotation() async {
257+
addMetaPackage();
258+
var content = '''
259+
import 'package:meta/meta.dart';
260+
f({@required @deprecated int x}) {}
261+
''';
262+
await analyze(content);
263+
var annotation = findNode.annotation('required');
264+
var previewInfo = run({
265+
findNode.defaultParameter('int x'): NodeChangeForDefaultFormalParameter()
266+
..addRequiredKeyword = true
267+
..annotationToRemove = annotation
268+
});
269+
expect(previewInfo.applyTo(code), '''
270+
import 'package:meta/meta.dart';
271+
f({@deprecated required int x}) {}
272+
''');
273+
expect(previewInfo.values, hasLength(2));
274+
275+
expect(previewInfo[content.indexOf('int ')], hasLength(1));
276+
expect(previewInfo[content.indexOf('int ')].single.isInsertion, true);
277+
expect(previewInfo[content.indexOf('@required')], isNotNull);
278+
expect(previewInfo[content.indexOf('@required')].single.isDeletion, true);
279+
}
280+
281+
Future<void> test_addRequired_afterMetadata_beforeFinal() async {
282+
await analyze('f({@deprecated final int x}) => 0;');
283+
var previewInfo = run({
284+
findNode.defaultParameter('int x'): NodeChangeForDefaultFormalParameter()
285+
..addRequiredKeyword = true
286+
});
287+
expect(previewInfo.applyTo(code),
288+
'f({@deprecated required final int x}) => 0;');
289+
}
290+
291+
Future<void> test_addRequired_afterMetadata_beforeFunctionTyped() async {
292+
await analyze('f({@deprecated int x()}) => 0;');
293+
var previewInfo = run({
294+
findNode.defaultParameter('int x'): NodeChangeForDefaultFormalParameter()
295+
..addRequiredKeyword = true
296+
});
297+
expect(
298+
previewInfo.applyTo(code), 'f({@deprecated required int x()}) => 0;');
299+
}
300+
247301
Future<void> test_addShownName_atEnd_multiple() async {
248302
await analyze("import 'dart:math' show cos;");
249303
var previewInfo = run({
@@ -1728,6 +1782,24 @@ int f() => null;
17281782
expect(previewInfo.applyTo(code), 'f(x) => x.y;');
17291783
}
17301784

1785+
Future<void>
1786+
test_requiredAnnotationToRequiredKeyword_leadingAnnotations() async {
1787+
addMetaPackage();
1788+
await analyze('''
1789+
import 'package:meta/meta.dart';
1790+
f({@deprecated @required int x}) {}
1791+
''');
1792+
var annotation = findNode.annotation('required');
1793+
var previewInfo = run({
1794+
annotation: NodeChangeForAnnotation()..changeToRequiredKeyword = true
1795+
});
1796+
expect(previewInfo.applyTo(code), '''
1797+
import 'package:meta/meta.dart';
1798+
f({@deprecated required int x}) {}
1799+
''');
1800+
expect(previewInfo.values.single.single.isDeletion, true);
1801+
}
1802+
17311803
Future<void> test_requiredAnnotationToRequiredKeyword_prefixed() async {
17321804
addMetaPackage();
17331805
await analyze('''

0 commit comments

Comments
 (0)