Skip to content

Commit 6e30e5a

Browse files
scheglovCommit Bot
authored and
Commit Bot
committed
Add 'Extract local variable' fix for UNCHECKED_PROPERTY_ACCESS_OF_NULLABLE_VALUE.
Bug: #47588 Change-Id: Ice823f239b3b152d9583cecb143ea78a62361539 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/227900 Reviewed-by: Samuel Rawlins <[email protected]> Reviewed-by: Phil Quitslund <[email protected]> Commit-Queue: Konstantin Shcheglov <[email protected]>
1 parent d0bd50c commit 6e30e5a

File tree

5 files changed

+402
-0
lines changed

5 files changed

+402
-0
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
// Copyright (c) 2022, 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:analysis_server/src/services/correction/util.dart';
8+
import 'package:analyzer/dart/ast/ast.dart';
9+
import 'package:analyzer/dart/ast/token.dart';
10+
import 'package:analyzer/dart/ast/visitor.dart';
11+
import 'package:analyzer/dart/element/element.dart';
12+
import 'package:analyzer/source/source_range.dart';
13+
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
14+
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
15+
import 'package:analyzer_plugin/utilities/range_factory.dart';
16+
17+
class ExtractLocalVariable extends CorrectionProducer {
18+
@override
19+
FixKind get fixKind => DartFixKind.EXTRACT_LOCAL_VARIABLE;
20+
21+
@override
22+
Future<void> compute(ChangeBuilder builder) async {
23+
final node = this.node;
24+
if (node is! SimpleIdentifier) {
25+
return;
26+
}
27+
28+
var parent = node.parent;
29+
30+
if (parent is MethodInvocation && parent.methodName == node) {
31+
await _rewrite(
32+
builder: builder,
33+
target: parent.target,
34+
);
35+
}
36+
37+
if (parent is PrefixedIdentifier && parent.identifier == node) {
38+
await _rewrite(
39+
builder: builder,
40+
target: parent.prefix,
41+
);
42+
}
43+
44+
if (parent is PropertyAccess && parent.propertyName == node) {
45+
await _rewrite(
46+
builder: builder,
47+
target: parent.target,
48+
);
49+
}
50+
}
51+
52+
Future<void> _rewrite({
53+
required ChangeBuilder builder,
54+
required Expression? target,
55+
}) async {
56+
if (target is PrefixedIdentifier) {
57+
await _rewriteProperty(
58+
builder: builder,
59+
target: target,
60+
targetProperty: target.staticElement,
61+
);
62+
}
63+
64+
if (target is PropertyAccess) {
65+
await _rewriteProperty(
66+
builder: builder,
67+
target: target,
68+
targetProperty: target.propertyName.staticElement,
69+
);
70+
}
71+
72+
if (target is SimpleIdentifier) {
73+
await _rewriteProperty(
74+
builder: builder,
75+
target: target,
76+
targetProperty: target.staticElement,
77+
);
78+
}
79+
}
80+
81+
Future<void> _rewriteProperty({
82+
required ChangeBuilder builder,
83+
required Expression target,
84+
required Element? targetProperty,
85+
}) async {
86+
if (targetProperty is PropertyAccessorElement &&
87+
targetProperty.isGetter &&
88+
typeSystem.isPotentiallyNullable(targetProperty.returnType)) {
89+
AstNode? enclosingNode = target;
90+
while (true) {
91+
if (enclosingNode == null || enclosingNode is FunctionBody) {
92+
return;
93+
}
94+
if (enclosingNode is IfStatement) {
95+
var condition = enclosingNode.condition;
96+
if (condition is BinaryExpression &&
97+
condition.rightOperand is NullLiteral &&
98+
condition.operator.type == TokenType.BANG_EQ) {
99+
var encoder = _ExpressionEncoder();
100+
var leftCode = encoder.encode(condition.leftOperand);
101+
var targetCode = encoder.encode(target);
102+
if (leftCode == targetCode) {
103+
var occurrences = <SourceRange>[];
104+
enclosingNode.accept(
105+
_OccurrencesVisitor(encoder, occurrences, leftCode),
106+
);
107+
108+
var ifOffset = enclosingNode.offset;
109+
var ifLineOffset = utils.getLineContentStart(ifOffset);
110+
var prefix = utils.getLinePrefix(ifOffset);
111+
112+
var initializerCode = utils.getNodeText(target);
113+
if (target is SimpleIdentifier) {
114+
initializerCode = 'this.$initializerCode';
115+
}
116+
117+
await builder.addDartFileEdit(file, (builder) {
118+
var propertyName = targetProperty.name;
119+
builder.addInsertion(ifLineOffset, (builder) {
120+
builder.write(prefix);
121+
builder.writeln('final $propertyName = $initializerCode;');
122+
});
123+
for (var occurrence in occurrences) {
124+
builder.addSimpleReplacement(occurrence, propertyName);
125+
}
126+
});
127+
return;
128+
}
129+
}
130+
break;
131+
}
132+
enclosingNode = enclosingNode.parent;
133+
}
134+
}
135+
}
136+
137+
/// Return an instance of this class. Used as a tear-off in `FixProcessor`.
138+
static ExtractLocalVariable newInstance() => ExtractLocalVariable();
139+
}
140+
141+
class _ExpressionEncoder {
142+
final Map<Element, int> _elementIds = {};
143+
144+
String encode(Expression node) {
145+
var tokens = TokenUtils.getNodeTokens(node);
146+
147+
var tokenToElementMap = Map<Token, Element>.identity();
148+
node.accept(
149+
_FunctionAstVisitor(
150+
simpleIdentifier: (node) {
151+
var element = node.staticElement;
152+
if (element != null) {
153+
tokenToElementMap[node.token] = element;
154+
}
155+
},
156+
),
157+
);
158+
159+
var tokensWithId = tokens.map((token) {
160+
var tokenString = token.lexeme;
161+
var element = tokenToElementMap[token];
162+
if (element != null) {
163+
var elementId = _elementIds.putIfAbsent(
164+
element,
165+
() => _elementIds.length,
166+
);
167+
tokenString += '#$elementId';
168+
}
169+
return tokenString;
170+
});
171+
172+
const separator = '\uFFFF';
173+
return tokensWithId.join(separator) + separator;
174+
}
175+
}
176+
177+
/// [RecursiveAstVisitor] that delegates visit methods to functions.
178+
class _FunctionAstVisitor extends RecursiveAstVisitor<void> {
179+
final void Function(SimpleIdentifier)? simpleIdentifier;
180+
181+
_FunctionAstVisitor({
182+
this.simpleIdentifier,
183+
});
184+
185+
@override
186+
void visitSimpleIdentifier(SimpleIdentifier node) {
187+
if (simpleIdentifier != null) {
188+
simpleIdentifier!(node);
189+
}
190+
super.visitSimpleIdentifier(node);
191+
}
192+
}
193+
194+
class _OccurrencesVisitor extends GeneralizingAstVisitor<void> {
195+
final _ExpressionEncoder encoder;
196+
final List<SourceRange> occurrences;
197+
final String searchCode;
198+
199+
_OccurrencesVisitor(this.encoder, this.occurrences, this.searchCode);
200+
201+
@override
202+
void visitExpression(Expression node) {
203+
var nodeCode = encoder.encode(node);
204+
if (nodeCode == searchCode) {
205+
occurrences.add(range.node(node));
206+
}
207+
super.visitExpression(node);
208+
}
209+
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,11 @@ class DartFixKind {
635635
DartFixKindPriority.DEFAULT,
636636
"Extend the class '{0}'",
637637
);
638+
static const EXTRACT_LOCAL_VARIABLE = FixKind(
639+
'dart.fix.extractLocalVariable',
640+
DartFixKindPriority.DEFAULT,
641+
'Extract local variable',
642+
);
638643
static const IGNORE_ERROR_LINE = FixKind(
639644
'dart.fix.ignore.line',
640645
DartFixKindPriority.IGNORE,

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ import 'package:analysis_server/src/services/correction/dart/create_no_such_meth
8080
import 'package:analysis_server/src/services/correction/dart/create_setter.dart';
8181
import 'package:analysis_server/src/services/correction/dart/data_driven.dart';
8282
import 'package:analysis_server/src/services/correction/dart/extend_class_for_mixin.dart';
83+
import 'package:analysis_server/src/services/correction/dart/extract_local_variable.dart';
8384
import 'package:analysis_server/src/services/correction/dart/flutter_remove_widget.dart';
8485
import 'package:analysis_server/src/services/correction/dart/ignore_diagnostic.dart';
8586
import 'package:analysis_server/src/services/correction/dart/import_library.dart';
@@ -990,13 +991,15 @@ class FixProcessor extends BaseProcessor {
990991
],
991992
CompileTimeErrorCode.UNCHECKED_METHOD_INVOCATION_OF_NULLABLE_VALUE: [
992993
AddNullCheck.newInstance,
994+
ExtractLocalVariable.newInstance,
993995
ReplaceWithNullAware.single,
994996
],
995997
CompileTimeErrorCode.UNCHECKED_OPERATOR_INVOCATION_OF_NULLABLE_VALUE: [
996998
AddNullCheck.newInstance,
997999
],
9981000
CompileTimeErrorCode.UNCHECKED_PROPERTY_ACCESS_OF_NULLABLE_VALUE: [
9991001
AddNullCheck.newInstance,
1002+
ExtractLocalVariable.newInstance,
10001003
ReplaceWithNullAware.single,
10011004
],
10021005
CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE_AS_CONDITION: [

0 commit comments

Comments
 (0)