Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 9fcac03

Browse files
bwilkersoncommit-bot@chromium.org
authored andcommitted
Convert more quick assists to be correction producers
Change-Id: I6cc0343cc9af390923a7d4a66a95e1c15aeb47f8 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/151887 Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Brian Wilkerson <[email protected]>
1 parent 6cc9439 commit 9fcac03

14 files changed

+861
-682
lines changed

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

Lines changed: 22 additions & 679 deletions
Large diffs are not rendered by default.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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 'dart:math';
6+
7+
import 'package:_fe_analyzer_shared/src/scanner/token.dart';
8+
import 'package:analysis_server/src/services/correction/assist.dart';
9+
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
10+
import 'package:analyzer/dart/ast/ast.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 AddNotNullAssert extends CorrectionProducer {
15+
@override
16+
AssistKind get assistKind => DartAssistKind.ADD_NOT_NULL_ASSERT;
17+
18+
@override
19+
Future<void> compute(DartChangeBuilder builder) async {
20+
final identifier = node;
21+
if (identifier is SimpleIdentifier) {
22+
if (identifier.parent is FormalParameter) {
23+
final exp = identifier.parent.thisOrAncestorMatching(
24+
(node) => node is FunctionExpression || node is MethodDeclaration);
25+
var body;
26+
if (exp is FunctionExpression) {
27+
body = exp.body;
28+
} else if (exp is MethodDeclaration) {
29+
body = exp.body;
30+
}
31+
if (body is BlockFunctionBody) {
32+
var blockBody = body;
33+
// Check for an obvious pre-existing assertion.
34+
for (var statement in blockBody.block.statements) {
35+
if (statement is AssertStatement) {
36+
final condition = statement.condition;
37+
if (condition is BinaryExpression) {
38+
final leftOperand = condition.leftOperand;
39+
if (leftOperand is SimpleIdentifier) {
40+
if (leftOperand.staticElement == identifier.staticElement &&
41+
condition.operator.type == TokenType.BANG_EQ &&
42+
condition.rightOperand is NullLiteral) {
43+
return;
44+
}
45+
}
46+
}
47+
}
48+
}
49+
50+
await builder.addFileEdit(file, (DartFileEditBuilder builder) {
51+
final id = identifier.name;
52+
final prefix = utils.getNodePrefix(exp);
53+
final indent = utils.getIndent(1);
54+
// todo (pq): follow-ups:
55+
// 1. if the end token is on the same line as the body
56+
// we should add an `eol` before the assert as well.
57+
// 2. also, consider asking the block for the list of statements and
58+
// adding the statement to the beginning of the list, special casing
59+
// when there are no statements (or when there's a single statement
60+
// and the whole block is on the same line).
61+
var offset = min(utils.getLineNext(blockBody.beginToken.offset),
62+
blockBody.endToken.offset);
63+
builder.addSimpleInsertion(
64+
offset, '$prefix${indent}assert($id != null);$eol');
65+
});
66+
}
67+
}
68+
}
69+
}
70+
71+
/// Return an instance of this class. Used as a tear-off in `AssistProcessor`.
72+
static AddNotNullAssert newInstance() => AddNotNullAssert();
73+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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/element/element.dart';
9+
import 'package:analyzer/dart/element/type.dart';
10+
import 'package:analyzer_plugin/utilities/assist/assist.dart';
11+
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
12+
import 'package:analyzer_plugin/utilities/range_factory.dart';
13+
14+
class ConvertIntoForIndex extends CorrectionProducer {
15+
@override
16+
AssistKind get assistKind => DartAssistKind.CONVERT_INTO_FOR_INDEX;
17+
18+
@override
19+
Future<void> compute(DartChangeBuilder builder) async {
20+
// find enclosing ForEachStatement
21+
var forEachStatement = node.thisOrAncestorMatching(
22+
(node) => node is ForStatement && node.forLoopParts is ForEachParts)
23+
as ForStatement;
24+
if (forEachStatement == null) {
25+
return;
26+
}
27+
ForEachParts forEachParts = forEachStatement.forLoopParts;
28+
if (selectionOffset < forEachStatement.offset ||
29+
forEachStatement.rightParenthesis.end < selectionOffset) {
30+
return;
31+
}
32+
// loop should declare variable
33+
var loopVariable = forEachParts is ForEachPartsWithDeclaration
34+
? forEachParts.loopVariable
35+
: null;
36+
if (loopVariable == null) {
37+
return;
38+
}
39+
// iterable should be VariableElement
40+
String listName;
41+
var iterable = forEachParts.iterable;
42+
if (iterable is SimpleIdentifier &&
43+
iterable.staticElement is VariableElement) {
44+
listName = iterable.name;
45+
} else {
46+
return;
47+
}
48+
// iterable should be List
49+
{
50+
var iterableType = iterable.staticType;
51+
if (iterableType is! InterfaceType ||
52+
iterableType.element != typeProvider.listElement) {
53+
return;
54+
}
55+
}
56+
// body should be Block
57+
if (forEachStatement.body is! Block) {
58+
return;
59+
}
60+
Block body = forEachStatement.body;
61+
// prepare a name for the index variable
62+
String indexName;
63+
{
64+
var conflicts =
65+
utils.findPossibleLocalVariableConflicts(forEachStatement.offset);
66+
if (!conflicts.contains('i')) {
67+
indexName = 'i';
68+
} else if (!conflicts.contains('j')) {
69+
indexName = 'j';
70+
} else if (!conflicts.contains('k')) {
71+
indexName = 'k';
72+
} else {
73+
return;
74+
}
75+
}
76+
// prepare environment
77+
var prefix = utils.getNodePrefix(forEachStatement);
78+
var indent = utils.getIndent(1);
79+
var firstBlockLine = utils.getLineContentEnd(body.leftBracket.end);
80+
// add change
81+
await builder.addFileEdit(file, (DartFileEditBuilder builder) {
82+
// TODO(brianwilkerson) Create linked positions for the loop variable.
83+
builder.addSimpleReplacement(
84+
range.startEnd(forEachStatement, forEachStatement.rightParenthesis),
85+
'for (int $indexName = 0; $indexName < $listName.length; $indexName++)');
86+
builder.addSimpleInsertion(firstBlockLine,
87+
'$prefix$indent$loopVariable = $listName[$indexName];$eol');
88+
});
89+
}
90+
91+
/// Return an instance of this class. Used as a tear-off in `AssistProcessor`.
92+
static ConvertIntoForIndex newInstance() => ConvertIntoForIndex();
93+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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:analysis_server/src/services/correction/util.dart';
8+
import 'package:analyzer/dart/ast/ast.dart';
9+
import 'package:analyzer/dart/ast/precedence.dart';
10+
import 'package:analyzer/dart/ast/token.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 ConvertIntoIsNot extends CorrectionProducer {
16+
@override
17+
AssistKind get assistKind => DartAssistKind.CONVERT_INTO_IS_NOT;
18+
19+
@override
20+
Future<void> compute(DartChangeBuilder builder) async {
21+
// Find the is expression
22+
var isExpression = node.thisOrAncestorOfType<IsExpression>();
23+
if (isExpression == null) {
24+
var node = this.node;
25+
if (node is PrefixExpression) {
26+
var operand = node.operand;
27+
if (operand is ParenthesizedExpression &&
28+
operand.expression is IsExpression) {
29+
isExpression = operand.expression as IsExpression;
30+
}
31+
} else if (node is ParenthesizedExpression &&
32+
node.expression is IsExpression) {
33+
isExpression = node.expression as IsExpression;
34+
}
35+
}
36+
if (isExpression == null) {
37+
return;
38+
}
39+
if (isExpression.notOperator != null) {
40+
return;
41+
}
42+
// prepare enclosing ()
43+
var parent = isExpression.parent;
44+
if (parent is! ParenthesizedExpression) {
45+
return;
46+
}
47+
var parExpression = parent as ParenthesizedExpression;
48+
// prepare enclosing !()
49+
var parent2 = parent.parent;
50+
if (parent2 is! PrefixExpression) {
51+
return;
52+
}
53+
var prefExpression = parent2 as PrefixExpression;
54+
if (prefExpression.operator.type != TokenType.BANG) {
55+
return;
56+
}
57+
58+
await builder.addFileEdit(file, (DartFileEditBuilder builder) {
59+
if (getExpressionParentPrecedence(prefExpression) >=
60+
Precedence.relational) {
61+
builder.addDeletion(range.token(prefExpression.operator));
62+
} else {
63+
builder.addDeletion(
64+
range.startEnd(prefExpression, parExpression.leftParenthesis));
65+
builder.addDeletion(
66+
range.startEnd(parExpression.rightParenthesis, prefExpression));
67+
}
68+
builder.addSimpleInsertion(isExpression.isOperator.end, '!');
69+
});
70+
}
71+
72+
/// Return an instance of this class. Used as a tear-off in `AssistProcessor`.
73+
static ConvertIntoIsNot newInstance() => ConvertIntoIsNot();
74+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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:analysis_server/src/services/search/hierarchy.dart';
9+
import 'package:analyzer/dart/ast/ast.dart';
10+
import 'package:analyzer_plugin/utilities/assist/assist.dart';
11+
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
12+
import 'package:analyzer_plugin/utilities/range_factory.dart';
13+
14+
class ConvertIntoIsNotEmpty extends CorrectionProducer {
15+
@override
16+
AssistKind get assistKind => DartAssistKind.CONVERT_INTO_IS_NOT_EMPTY;
17+
18+
@override
19+
Future<void> compute(DartChangeBuilder builder) async {
20+
// prepare "expr.isEmpty"
21+
AstNode isEmptyAccess;
22+
SimpleIdentifier isEmptyIdentifier;
23+
if (node is SimpleIdentifier) {
24+
var identifier = node as SimpleIdentifier;
25+
var parent = identifier.parent;
26+
// normal case (but rare)
27+
if (parent is PropertyAccess) {
28+
isEmptyIdentifier = parent.propertyName;
29+
isEmptyAccess = parent;
30+
}
31+
// usual case
32+
if (parent is PrefixedIdentifier) {
33+
isEmptyIdentifier = parent.identifier;
34+
isEmptyAccess = parent;
35+
}
36+
}
37+
if (isEmptyIdentifier == null) {
38+
return;
39+
}
40+
// should be "isEmpty"
41+
var propertyElement = isEmptyIdentifier.staticElement;
42+
if (propertyElement == null || 'isEmpty' != propertyElement.name) {
43+
return;
44+
}
45+
// should have "isNotEmpty"
46+
var propertyTarget = propertyElement.enclosingElement;
47+
if (propertyTarget == null ||
48+
getChildren(propertyTarget, 'isNotEmpty').isEmpty) {
49+
return;
50+
}
51+
// should be in PrefixExpression
52+
if (isEmptyAccess.parent is! PrefixExpression) {
53+
return;
54+
}
55+
var prefixExpression = isEmptyAccess.parent as PrefixExpression;
56+
// should be !
57+
if (prefixExpression.operator.type != TokenType.BANG) {
58+
return;
59+
}
60+
61+
await builder.addFileEdit(file, (DartFileEditBuilder builder) {
62+
builder.addDeletion(
63+
range.startStart(prefixExpression, prefixExpression.operand));
64+
builder.addSimpleReplacement(range.node(isEmptyIdentifier), 'isNotEmpty');
65+
});
66+
}
67+
68+
/// Return an instance of this class. Used as a tear-off in `AssistProcessor`.
69+
static ConvertIntoIsNotEmpty newInstance() => ConvertIntoIsNotEmpty();
70+
}

0 commit comments

Comments
 (0)