Skip to content

Commit 0d3e77d

Browse files
bwilkersoncommit-bot@chromium.org
authored andcommitted
Start converting assists to CorrectionProducer
Change-Id: Ie2d12ce8c15a75e8db71434d790c32a76a06b13e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/151324 Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Brian Wilkerson <[email protected]>
1 parent 7f6b0d8 commit 0d3e77d

10 files changed

+541
-416
lines changed

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

Lines changed: 17 additions & 416 deletions
Large diffs are not rendered by default.

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,8 @@ abstract class _AbstractCorrectionProducer {
356356
ResourceProvider get resourceProvider =>
357357
resolvedResult.session.resourceProvider;
358358

359+
int get selectionEnd => _context.selectionEnd;
360+
359361
int get selectionLength => _context.selectionLength;
360362

361363
int get selectionOffset => _context.selectionOffset;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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/protocol_server.dart';
6+
import 'package:analysis_server/src/services/correction/assist.dart';
7+
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
8+
import 'package:analysis_server/src/services/correction/name_suggestion.dart';
9+
import 'package:analyzer/dart/ast/ast.dart';
10+
import 'package:analyzer/src/dart/ast/utilities.dart';
11+
import 'package:analyzer_plugin/utilities/assist/assist.dart';
12+
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
13+
14+
class AssignToLocalVariable extends CorrectionProducer {
15+
@override
16+
AssistKind get assistKind => DartAssistKind.ASSIGN_TO_LOCAL_VARIABLE;
17+
18+
@override
19+
Future<void> compute(DartChangeBuilder builder) async {
20+
// prepare enclosing ExpressionStatement
21+
ExpressionStatement expressionStatement;
22+
// ignore: unnecessary_this
23+
for (var node = this.node; node != null; node = node.parent) {
24+
if (node is ExpressionStatement) {
25+
expressionStatement = node;
26+
break;
27+
}
28+
if (node is ArgumentList ||
29+
node is AssignmentExpression ||
30+
node is Statement ||
31+
node is ThrowExpression) {
32+
return;
33+
}
34+
}
35+
if (expressionStatement == null) {
36+
return;
37+
}
38+
// prepare expression
39+
var expression = expressionStatement.expression;
40+
var offset = expression.offset;
41+
// prepare expression type
42+
var type = expression.staticType;
43+
if (type.isVoid) {
44+
return;
45+
}
46+
// prepare excluded names
47+
var excluded = <String>{};
48+
var scopedNameFinder = ScopedNameFinder(offset);
49+
expression.accept(scopedNameFinder);
50+
excluded.addAll(scopedNameFinder.locals.keys.toSet());
51+
var suggestions =
52+
getVariableNameSuggestionsForExpression(type, expression, excluded);
53+
54+
if (suggestions.isNotEmpty) {
55+
await builder.addFileEdit(file, (builder) {
56+
builder.addInsertion(offset, (builder) {
57+
builder.write('var ');
58+
builder.addSimpleLinkedEdit('NAME', suggestions[0],
59+
kind: LinkedEditSuggestionKind.VARIABLE,
60+
suggestions: suggestions);
61+
builder.write(' = ');
62+
});
63+
});
64+
}
65+
}
66+
67+
/// Return an instance of this class. Used as a tear-off in `AssistProcessor`.
68+
static AssignToLocalVariable newInstance() => AssignToLocalVariable();
69+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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/correction/assist.dart';
6+
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
7+
import 'package:analyzer/dart/ast/ast.dart';
8+
import 'package:analyzer/dart/ast/visitor.dart';
9+
import 'package:analyzer/dart/element/element.dart';
10+
import 'package:analyzer/dart/element/type.dart';
11+
import 'package:analyzer_plugin/utilities/assist/assist.dart';
12+
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
13+
import 'package:analyzer_plugin/utilities/range_factory.dart';
14+
15+
class ConvertClassToMixin extends CorrectionProducer {
16+
@override
17+
AssistKind get assistKind => DartAssistKind.CONVERT_CLASS_TO_MIXIN;
18+
19+
@override
20+
Future<void> compute(DartChangeBuilder builder) async {
21+
var classDeclaration = node.thisOrAncestorOfType<ClassDeclaration>();
22+
if (classDeclaration == null) {
23+
return;
24+
}
25+
if (selectionOffset > classDeclaration.name.end ||
26+
selectionEnd < classDeclaration.classKeyword.offset) {
27+
return;
28+
}
29+
if (classDeclaration.members
30+
.any((member) => member is ConstructorDeclaration)) {
31+
return;
32+
}
33+
var finder = _SuperclassReferenceFinder();
34+
classDeclaration.accept(finder);
35+
var referencedClasses = finder.referencedClasses;
36+
var superclassConstraints = <InterfaceType>[];
37+
var interfaces = <InterfaceType>[];
38+
39+
var classElement = classDeclaration.declaredElement;
40+
for (var type in classElement.mixins) {
41+
if (referencedClasses.contains(type.element)) {
42+
superclassConstraints.add(type);
43+
} else {
44+
interfaces.add(type);
45+
}
46+
}
47+
var extendsClause = classDeclaration.extendsClause;
48+
if (extendsClause != null) {
49+
if (referencedClasses.length > superclassConstraints.length) {
50+
superclassConstraints.insert(0, classElement.supertype);
51+
} else {
52+
interfaces.insert(0, classElement.supertype);
53+
}
54+
}
55+
interfaces.addAll(classElement.interfaces);
56+
57+
await builder.addFileEdit(file, (builder) {
58+
builder.addReplacement(
59+
range.startStart(
60+
classDeclaration.abstractKeyword ?? classDeclaration.classKeyword,
61+
classDeclaration.leftBracket), (builder) {
62+
builder.write('mixin ');
63+
builder.write(classDeclaration.name.name);
64+
builder.writeTypeParameters(
65+
classDeclaration.declaredElement.typeParameters);
66+
builder.writeTypes(superclassConstraints, prefix: ' on ');
67+
builder.writeTypes(interfaces, prefix: ' implements ');
68+
builder.write(' ');
69+
});
70+
});
71+
}
72+
73+
/// Return an instance of this class. Used as a tear-off in `AssistProcessor`.
74+
static ConvertClassToMixin newInstance() => ConvertClassToMixin();
75+
}
76+
77+
/// A visitor used to find all of the classes that define members referenced via
78+
/// `super`.
79+
class _SuperclassReferenceFinder extends RecursiveAstVisitor<void> {
80+
final List<ClassElement> referencedClasses = <ClassElement>[];
81+
82+
_SuperclassReferenceFinder();
83+
84+
@override
85+
void visitSuperExpression(SuperExpression node) {
86+
var parent = node.parent;
87+
if (parent is BinaryExpression) {
88+
_addElement(parent.staticElement);
89+
} else if (parent is IndexExpression) {
90+
_addElement(parent.staticElement);
91+
} else if (parent is MethodInvocation) {
92+
_addIdentifier(parent.methodName);
93+
} else if (parent is PrefixedIdentifier) {
94+
_addIdentifier(parent.identifier);
95+
} else if (parent is PropertyAccess) {
96+
_addIdentifier(parent.propertyName);
97+
}
98+
return super.visitSuperExpression(node);
99+
}
100+
101+
void _addElement(Element element) {
102+
if (element is ExecutableElement) {
103+
var enclosingElement = element.enclosingElement;
104+
if (enclosingElement is ClassElement) {
105+
referencedClasses.add(enclosingElement);
106+
}
107+
}
108+
}
109+
110+
void _addIdentifier(SimpleIdentifier identifier) {
111+
_addElement(identifier.staticElement);
112+
}
113+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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:_fe_analyzer_shared/src/scanner/token.dart';
6+
import 'package:analysis_server/src/services/correction/assist.dart';
7+
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
8+
import 'package:analyzer/dart/ast/ast.dart';
9+
import 'package:analyzer_plugin/utilities/assist/assist.dart';
10+
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
11+
import 'package:analyzer_plugin/utilities/range_factory.dart';
12+
13+
class ConvertDocumentationIntoBlock extends CorrectionProducer {
14+
@override
15+
AssistKind get assistKind => DartAssistKind.CONVERT_DOCUMENTATION_INTO_BLOCK;
16+
17+
@override
18+
Future<void> compute(DartChangeBuilder builder) async {
19+
var comment = node.thisOrAncestorOfType<Comment>();
20+
if (comment == null || !comment.isDocumentation) {
21+
return;
22+
}
23+
var tokens = comment.tokens;
24+
if (tokens.isEmpty ||
25+
tokens.any((Token token) =>
26+
token is! DocumentationCommentToken ||
27+
token.type != TokenType.SINGLE_LINE_COMMENT)) {
28+
return;
29+
}
30+
var prefix = utils.getNodePrefix(comment);
31+
32+
await builder.addFileEdit(file, (DartFileEditBuilder builder) {
33+
builder.addReplacement(range.node(comment), (DartEditBuilder builder) {
34+
builder.writeln('/**');
35+
for (var token in comment.tokens) {
36+
builder.write(prefix);
37+
builder.write(' *');
38+
builder.writeln(token.lexeme.substring('///'.length));
39+
}
40+
builder.write(prefix);
41+
builder.write(' */');
42+
});
43+
});
44+
}
45+
46+
/// Return an instance of this class. Used as a tear-off in `AssistProcessor`.
47+
static ConvertDocumentationIntoBlock newInstance() =>
48+
ConvertDocumentationIntoBlock();
49+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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/correction/assist.dart';
6+
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
7+
import 'package:analyzer/dart/ast/ast.dart';
8+
import 'package:analyzer_plugin/utilities/assist/assist.dart';
9+
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
10+
11+
class ConvertIntoAsyncBody extends CorrectionProducer {
12+
@override
13+
AssistKind get assistKind => DartAssistKind.CONVERT_INTO_ASYNC_BODY;
14+
15+
@override
16+
Future<void> compute(DartChangeBuilder builder) async {
17+
var body = getEnclosingFunctionBody();
18+
if (body == null ||
19+
body is EmptyFunctionBody ||
20+
body.isAsynchronous ||
21+
body.isGenerator) {
22+
return;
23+
}
24+
25+
// Function bodies can be quite large, e.g. Flutter build() methods.
26+
// It is surprising to see this Quick Assist deep in a function body.
27+
if (body is BlockFunctionBody &&
28+
selectionOffset > body.block.beginToken.end) {
29+
return;
30+
}
31+
if (body is ExpressionFunctionBody &&
32+
selectionOffset > body.beginToken.end) {
33+
return;
34+
}
35+
36+
var parent = body.parent;
37+
if (parent is ConstructorDeclaration) {
38+
return;
39+
}
40+
41+
await builder.addFileEdit(file, (DartFileEditBuilder builder) {
42+
builder.convertFunctionFromSyncToAsync(body, typeProvider);
43+
});
44+
}
45+
46+
/// Return an instance of this class. Used as a tear-off in `AssistProcessor`.
47+
static ConvertIntoAsyncBody newInstance() => ConvertIntoAsyncBody();
48+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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/correction/assist.dart';
6+
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
7+
import 'package:analyzer/dart/ast/ast.dart';
8+
import 'package:analyzer_plugin/utilities/assist/assist.dart';
9+
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
10+
import 'package:analyzer_plugin/utilities/range_factory.dart';
11+
12+
class ConvertIntoBlockBody extends CorrectionProducer {
13+
@override
14+
AssistKind get assistKind => DartAssistKind.CONVERT_INTO_BLOCK_BODY;
15+
16+
@override
17+
Future<void> compute(DartChangeBuilder builder) async {
18+
var body = getEnclosingFunctionBody();
19+
// prepare expression body
20+
if (body is! ExpressionFunctionBody || body.isGenerator) {
21+
return;
22+
}
23+
24+
var returnValue = (body as ExpressionFunctionBody).expression;
25+
26+
// Return expressions can be quite large, e.g. Flutter build() methods.
27+
// It is surprising to see this Quick Assist deep in the function body.
28+
if (selectionOffset >= returnValue.offset) {
29+
return;
30+
}
31+
32+
var returnValueType = returnValue.staticType;
33+
var returnValueCode = utils.getNodeText(returnValue);
34+
// prepare prefix
35+
var prefix = utils.getNodePrefix(body.parent);
36+
var indent = utils.getIndent(1);
37+
38+
await builder.addFileEdit(file, (DartFileEditBuilder builder) {
39+
builder.addReplacement(range.node(body), (DartEditBuilder builder) {
40+
if (body.isAsynchronous) {
41+
builder.write('async ');
42+
}
43+
builder.write('{$eol$prefix$indent');
44+
if (!returnValueType.isVoid && !returnValueType.isBottom) {
45+
builder.write('return ');
46+
}
47+
builder.write(returnValueCode);
48+
builder.write(';');
49+
builder.selectHere();
50+
builder.write('$eol$prefix}');
51+
});
52+
});
53+
}
54+
55+
/// Return an instance of this class. Used as a tear-off in `AssistProcessor`.
56+
static ConvertIntoBlockBody newInstance() => ConvertIntoBlockBody();
57+
}

0 commit comments

Comments
 (0)