Version 2.17.0-9.0.dev
Merge commit '6e30e5a7f62188986b0658b9fc8d5f2e85554670' into 'dev'
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/extract_local_variable.dart b/pkg/analysis_server/lib/src/services/correction/dart/extract_local_variable.dart
new file mode 100644
index 0000000..bdb9fc7
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/dart/extract_local_variable.dart
@@ -0,0 +1,209 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
+import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analysis_server/src/services/correction/util.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/source/source_range.dart';
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:analyzer_plugin/utilities/range_factory.dart';
+
+class ExtractLocalVariable extends CorrectionProducer {
+ @override
+ FixKind get fixKind => DartFixKind.EXTRACT_LOCAL_VARIABLE;
+
+ @override
+ Future<void> compute(ChangeBuilder builder) async {
+ final node = this.node;
+ if (node is! SimpleIdentifier) {
+ return;
+ }
+
+ var parent = node.parent;
+
+ if (parent is MethodInvocation && parent.methodName == node) {
+ await _rewrite(
+ builder: builder,
+ target: parent.target,
+ );
+ }
+
+ if (parent is PrefixedIdentifier && parent.identifier == node) {
+ await _rewrite(
+ builder: builder,
+ target: parent.prefix,
+ );
+ }
+
+ if (parent is PropertyAccess && parent.propertyName == node) {
+ await _rewrite(
+ builder: builder,
+ target: parent.target,
+ );
+ }
+ }
+
+ Future<void> _rewrite({
+ required ChangeBuilder builder,
+ required Expression? target,
+ }) async {
+ if (target is PrefixedIdentifier) {
+ await _rewriteProperty(
+ builder: builder,
+ target: target,
+ targetProperty: target.staticElement,
+ );
+ }
+
+ if (target is PropertyAccess) {
+ await _rewriteProperty(
+ builder: builder,
+ target: target,
+ targetProperty: target.propertyName.staticElement,
+ );
+ }
+
+ if (target is SimpleIdentifier) {
+ await _rewriteProperty(
+ builder: builder,
+ target: target,
+ targetProperty: target.staticElement,
+ );
+ }
+ }
+
+ Future<void> _rewriteProperty({
+ required ChangeBuilder builder,
+ required Expression target,
+ required Element? targetProperty,
+ }) async {
+ if (targetProperty is PropertyAccessorElement &&
+ targetProperty.isGetter &&
+ typeSystem.isPotentiallyNullable(targetProperty.returnType)) {
+ AstNode? enclosingNode = target;
+ while (true) {
+ if (enclosingNode == null || enclosingNode is FunctionBody) {
+ return;
+ }
+ if (enclosingNode is IfStatement) {
+ var condition = enclosingNode.condition;
+ if (condition is BinaryExpression &&
+ condition.rightOperand is NullLiteral &&
+ condition.operator.type == TokenType.BANG_EQ) {
+ var encoder = _ExpressionEncoder();
+ var leftCode = encoder.encode(condition.leftOperand);
+ var targetCode = encoder.encode(target);
+ if (leftCode == targetCode) {
+ var occurrences = <SourceRange>[];
+ enclosingNode.accept(
+ _OccurrencesVisitor(encoder, occurrences, leftCode),
+ );
+
+ var ifOffset = enclosingNode.offset;
+ var ifLineOffset = utils.getLineContentStart(ifOffset);
+ var prefix = utils.getLinePrefix(ifOffset);
+
+ var initializerCode = utils.getNodeText(target);
+ if (target is SimpleIdentifier) {
+ initializerCode = 'this.$initializerCode';
+ }
+
+ await builder.addDartFileEdit(file, (builder) {
+ var propertyName = targetProperty.name;
+ builder.addInsertion(ifLineOffset, (builder) {
+ builder.write(prefix);
+ builder.writeln('final $propertyName = $initializerCode;');
+ });
+ for (var occurrence in occurrences) {
+ builder.addSimpleReplacement(occurrence, propertyName);
+ }
+ });
+ return;
+ }
+ }
+ break;
+ }
+ enclosingNode = enclosingNode.parent;
+ }
+ }
+ }
+
+ /// Return an instance of this class. Used as a tear-off in `FixProcessor`.
+ static ExtractLocalVariable newInstance() => ExtractLocalVariable();
+}
+
+class _ExpressionEncoder {
+ final Map<Element, int> _elementIds = {};
+
+ String encode(Expression node) {
+ var tokens = TokenUtils.getNodeTokens(node);
+
+ var tokenToElementMap = Map<Token, Element>.identity();
+ node.accept(
+ _FunctionAstVisitor(
+ simpleIdentifier: (node) {
+ var element = node.staticElement;
+ if (element != null) {
+ tokenToElementMap[node.token] = element;
+ }
+ },
+ ),
+ );
+
+ var tokensWithId = tokens.map((token) {
+ var tokenString = token.lexeme;
+ var element = tokenToElementMap[token];
+ if (element != null) {
+ var elementId = _elementIds.putIfAbsent(
+ element,
+ () => _elementIds.length,
+ );
+ tokenString += '#$elementId';
+ }
+ return tokenString;
+ });
+
+ const separator = '\uFFFF';
+ return tokensWithId.join(separator) + separator;
+ }
+}
+
+/// [RecursiveAstVisitor] that delegates visit methods to functions.
+class _FunctionAstVisitor extends RecursiveAstVisitor<void> {
+ final void Function(SimpleIdentifier)? simpleIdentifier;
+
+ _FunctionAstVisitor({
+ this.simpleIdentifier,
+ });
+
+ @override
+ void visitSimpleIdentifier(SimpleIdentifier node) {
+ if (simpleIdentifier != null) {
+ simpleIdentifier!(node);
+ }
+ super.visitSimpleIdentifier(node);
+ }
+}
+
+class _OccurrencesVisitor extends GeneralizingAstVisitor<void> {
+ final _ExpressionEncoder encoder;
+ final List<SourceRange> occurrences;
+ final String searchCode;
+
+ _OccurrencesVisitor(this.encoder, this.occurrences, this.searchCode);
+
+ @override
+ void visitExpression(Expression node) {
+ var nodeCode = encoder.encode(node);
+ if (nodeCode == searchCode) {
+ occurrences.add(range.node(node));
+ }
+ super.visitExpression(node);
+ }
+}
diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart
index 666ea13..d1778fb 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix.dart
@@ -635,6 +635,11 @@
DartFixKindPriority.DEFAULT,
"Extend the class '{0}'",
);
+ static const EXTRACT_LOCAL_VARIABLE = FixKind(
+ 'dart.fix.extractLocalVariable',
+ DartFixKindPriority.DEFAULT,
+ 'Extract local variable',
+ );
static const IGNORE_ERROR_LINE = FixKind(
'dart.fix.ignore.line',
DartFixKindPriority.IGNORE,
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index 34e4921..0a0a125 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -80,6 +80,7 @@
import 'package:analysis_server/src/services/correction/dart/create_setter.dart';
import 'package:analysis_server/src/services/correction/dart/data_driven.dart';
import 'package:analysis_server/src/services/correction/dart/extend_class_for_mixin.dart';
+import 'package:analysis_server/src/services/correction/dart/extract_local_variable.dart';
import 'package:analysis_server/src/services/correction/dart/flutter_remove_widget.dart';
import 'package:analysis_server/src/services/correction/dart/ignore_diagnostic.dart';
import 'package:analysis_server/src/services/correction/dart/import_library.dart';
@@ -990,6 +991,7 @@
],
CompileTimeErrorCode.UNCHECKED_METHOD_INVOCATION_OF_NULLABLE_VALUE: [
AddNullCheck.newInstance,
+ ExtractLocalVariable.newInstance,
ReplaceWithNullAware.single,
],
CompileTimeErrorCode.UNCHECKED_OPERATOR_INVOCATION_OF_NULLABLE_VALUE: [
@@ -997,6 +999,7 @@
],
CompileTimeErrorCode.UNCHECKED_PROPERTY_ACCESS_OF_NULLABLE_VALUE: [
AddNullCheck.newInstance,
+ ExtractLocalVariable.newInstance,
ReplaceWithNullAware.single,
],
CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE_AS_CONDITION: [
diff --git a/pkg/analysis_server/test/src/services/correction/fix/extract_local_variable_test.dart b/pkg/analysis_server/test/src/services/correction/fix/extract_local_variable_test.dart
new file mode 100644
index 0000000..3fdb4db
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/extract_local_variable_test.dart
@@ -0,0 +1,183 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'fix_processor.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(ExtractLocalVariableTest);
+ });
+}
+
+@reflectiveTest
+class ExtractLocalVariableTest extends FixProcessorTest {
+ @override
+ FixKind get kind => DartFixKind.EXTRACT_LOCAL_VARIABLE;
+
+ Future<void> test_ifCondition_notBangEq() async {
+ await resolveTestCode('''
+abstract class A {
+ int? get foo;
+
+ void bar() {
+ if (foo == 0) {
+ foo.isEven;
+ }
+ }
+}
+''');
+ await assertNoFix();
+ }
+
+ Future<void> test_ifCondition_notBinaryExpression() async {
+ await resolveTestCode('''
+abstract class A {
+ int? get foo;
+
+ void bar() {
+ if (!(1 == 0)) {
+ foo.isEven;
+ }
+ }
+}
+''');
+ await assertNoFix();
+ }
+
+ Future<void> test_ifCondition_notNull() async {
+ await resolveTestCode('''
+abstract class A {
+ int? get foo;
+
+ void bar() {
+ if (foo != 0) {
+ foo.isEven;
+ }
+ }
+}
+''');
+ await assertNoFix();
+ }
+
+ Future<void> test_noEnclosingIf() async {
+ await resolveTestCode('''
+abstract class A {
+ int? get foo;
+
+ void bar() {
+ foo.isEven;
+ }
+}
+''');
+ await assertNoFix();
+ }
+
+ Future<void> test_prefixedIdentifier() async {
+ await resolveTestCode('''
+abstract class A {
+ int? get foo;
+
+ void bar() {
+ if (foo != null) {
+ foo.isEven;
+ }
+ }
+}
+''');
+ await assertHasFix('''
+abstract class A {
+ int? get foo;
+
+ void bar() {
+ final foo = this.foo;
+ if (foo != null) {
+ foo.isEven;
+ }
+ }
+}
+''');
+ }
+
+ Future<void> test_prefixedIdentifier_methodInvocation() async {
+ await resolveTestCode('''
+abstract class A {
+ int? get foo;
+}
+void f(A a) {
+ if (a.foo != null) {
+ a.foo.abs();
+ }
+}
+''');
+ await assertHasFix('''
+abstract class A {
+ int? get foo;
+}
+void f(A a) {
+ final foo = a.foo;
+ if (foo != null) {
+ foo.abs();
+ }
+}
+''');
+ }
+
+ Future<void> test_prefixedIdentifier_propertyAccess() async {
+ await resolveTestCode('''
+abstract class A {
+ int? get foo;
+}
+void f(A a) {
+ if (a.foo != null) {
+ a.foo.isEven;
+ }
+}
+''');
+ await assertHasFix('''
+abstract class A {
+ int? get foo;
+}
+void f(A a) {
+ final foo = a.foo;
+ if (foo != null) {
+ foo.isEven;
+ }
+}
+''');
+ }
+
+ Future<void> test_propertyAccess_methodInvocation() async {
+ await resolveTestCode('''
+abstract class A {
+ int? get foo;
+}
+abstract class B {
+ A get a;
+}
+void f(B b) {
+ if (b.a.foo != null) {
+ b.a.foo.abs();
+ }
+}
+''');
+ await assertHasFix('''
+abstract class A {
+ int? get foo;
+}
+abstract class B {
+ A get a;
+}
+void f(B b) {
+ final foo = b.a.foo;
+ if (foo != null) {
+ foo.abs();
+ }
+}
+''');
+ }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
index 3174635..d202946 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
@@ -96,6 +96,7 @@
import 'create_setter_test.dart' as create_setter;
import 'data_driven/test_all.dart' as data_driven;
import 'extend_class_for_mixin_test.dart' as extend_class_for_mixin;
+import 'extract_local_variable_test.dart' as extract_local_variable;
import 'fix_in_file_test.dart' as fix_in_file;
import 'fix_processor_map_test.dart' as fix_processor_map;
import 'fix_test.dart' as fix;
@@ -296,6 +297,7 @@
data_driven.main();
extend_class_for_mixin.main();
fix.main();
+ extract_local_variable.main();
fix_in_file.main();
fix_processor_map.main();
ignore_error.main();
diff --git a/pkg/analyzer/lib/src/dart/element/scope.dart b/pkg/analyzer/lib/src/dart/element/scope.dart
index 4df2190..7287b59 100644
--- a/pkg/analyzer/lib/src/dart/element/scope.dart
+++ b/pkg/analyzer/lib/src/dart/element/scope.dart
@@ -83,7 +83,8 @@
List<ParameterElement> elements,
) : super(parent) {
for (var parameter in elements) {
- if (parameter is! FieldFormalParameterElement) {
+ if (parameter is! FieldFormalParameterElement &&
+ parameter is! SuperFormalParameterElement) {
_addGetter(parameter);
}
}
diff --git a/pkg/analyzer/test/src/dart/resolution/super_formal_parameter_test.dart b/pkg/analyzer/test/src/dart/resolution/super_formal_parameter_test.dart
index 83f2777..42db1a5 100644
--- a/pkg/analyzer/test/src/dart/resolution/super_formal_parameter_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/super_formal_parameter_test.dart
@@ -130,4 +130,42 @@
findElement.unnamedConstructor('B').superFormalParameter('a'),
);
}
+
+ test_scoping_inBody() async {
+ await assertNoErrorsInCode(r'''
+class A {
+ final int a;
+ A(this.a);
+}
+
+class B extends A {
+ B(super.a) {
+ a; // ref
+ }
+}
+''');
+
+ assertElement(
+ findNode.simple('a; // ref'),
+ findElement.getter('a', of: 'A'),
+ );
+ }
+
+ test_scoping_inInitializer() async {
+ await assertNoErrorsInCode(r'''
+class A {
+ A(int a);
+}
+
+class B extends A {
+ var f;
+ B(super.a) : f = ((){ a; });
+}
+''');
+
+ assertElement(
+ findNode.simple('a; }'),
+ findElement.unnamedConstructor('B').superFormalParameter('a'),
+ );
+ }
}
diff --git a/tools/VERSION b/tools/VERSION
index eafe4ae..a83e30d 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 17
PATCH 0
-PRERELEASE 8
+PRERELEASE 9
PRERELEASE_PATCH 0
\ No newline at end of file