Skip to content

Commit 5399169

Browse files
kallentuCommit Queue
authored andcommitted
[analysis_server] Dot shorthands: Extract expression tests.
Adding logic to add a type when extracting locals or methods, otherwise we're refactoring shorthands into a statement with no context. Tests for mixins, extension types, enums and classes. Bug: #59836 Change-Id: I8ee1407ba82118831e3bbb092e11a44b6ea5384c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/431950 Commit-Queue: Kallen Tu <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]> Reviewed-by: Paul Berry <[email protected]>
1 parent 22d6cd6 commit 5399169

File tree

4 files changed

+452
-29
lines changed

4 files changed

+452
-29
lines changed

pkg/analysis_server/lib/src/services/refactoring/legacy/extract_local.dart

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import 'package:analyzer/dart/element/element.dart';
2424
import 'package:analyzer/dart/element/type.dart';
2525
import 'package:analyzer/source/source_range.dart';
2626
import 'package:analyzer/src/generated/java_core.dart';
27+
import 'package:analyzer/src/utilities/dot_shorthands.dart';
2728
import 'package:analyzer_plugin/utilities/range_factory.dart';
2829

2930
const String _TOKEN_SEPARATOR = '\uFFFF';
@@ -81,21 +82,32 @@ class ExtractLocalRefactoringImpl extends RefactoringImpl
8182
CompilationUnit get unit => resolveResult.unit;
8283

8384
String get _declarationKeywordAndType {
84-
var useConst =
85-
stringLiteralPart == null &&
86-
_isPartOfConstantExpression(singleExpression);
87-
var useFinal = codeStyleOptions.makeLocalsFinal;
85+
bool useConst;
86+
bool dotShorthandRequiresType;
87+
var expression = singleExpression;
88+
if (expression != null) {
89+
useConst = stringLiteralPart == null && expression.inConstantContext;
90+
// If the expression that we're extracting is a dot shorthand or we have a
91+
// dependent dot shorthand, we need to retain the type in the new
92+
// declaration.
93+
dotShorthandRequiresType =
94+
isDotShorthand(expression) || hasDependentDotShorthand(expression);
95+
} else {
96+
dotShorthandRequiresType = false;
97+
useConst = false;
98+
}
8899

89100
String? typeString;
90-
if (codeStyleOptions.specifyTypes) {
101+
if (codeStyleOptions.specifyTypes || dotShorthandRequiresType) {
91102
typeString =
92-
singleExpression != null
93-
? singleExpression?.staticType?.getDisplayString()
103+
expression != null
104+
? expression.staticType?.getDisplayString()
94105
: stringLiteralPart != null
95106
? 'String'
96107
: null;
97108
}
98109

110+
var useFinal = codeStyleOptions.makeLocalsFinal;
99111
if (useConst) {
100112
return typeString != null ? 'const $typeString' : 'const';
101113
} else if (useFinal) {
@@ -499,28 +511,6 @@ class ExtractLocalRefactoringImpl extends RefactoringImpl
499511
return null;
500512
}
501513

502-
bool _isPartOfConstantExpression(AstNode? node) {
503-
if (node == null) {
504-
return false;
505-
}
506-
if (node is TypedLiteral) {
507-
return node.isConst;
508-
}
509-
if (node is InstanceCreationExpression) {
510-
return node.isConst;
511-
}
512-
if (node is ArgumentList ||
513-
node is ConditionalExpression ||
514-
node is BinaryExpression ||
515-
node is ParenthesizedExpression ||
516-
node is PrefixExpression ||
517-
node is Literal ||
518-
node is MapLiteralEntry) {
519-
return _isPartOfConstantExpression(node.parent);
520-
}
521-
return false;
522-
}
523-
524514
void _prepareNames() {
525515
names.clear();
526516
var stringLiteralPart = this.stringLiteralPart;

pkg/analysis_server/test/services/refactoring/legacy/extract_local_test.dart

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,24 @@ void f() {
344344
''');
345345
}
346346

347+
Future<void> test_const_enum() async {
348+
await indexTestUnit('''
349+
enum A { a }
350+
void f() {
351+
const A vvv = A.a;
352+
}
353+
''');
354+
_createRefactoringForString('A.a');
355+
// apply refactoring
356+
return _assertSuccessfulRefactoring('''
357+
enum A { a }
358+
void f() {
359+
const res = A.a;
360+
const A vvv = res;
361+
}
362+
''');
363+
}
364+
347365
Future<void> test_const_inList() async {
348366
await indexTestUnit('''
349367
void f() {
@@ -608,6 +626,94 @@ int foo({int ppp = 0}) => ppp + 1;
608626
expect(subExpressions, ['foo(ppp: 42)']);
609627
}
610628

629+
Future<void> test_dotShorthand() async {
630+
await indexTestUnit('''
631+
enum A { a }
632+
void f() {
633+
A vvv = .a;
634+
}
635+
''');
636+
_createRefactoringForString('.a');
637+
// apply refactoring
638+
return _assertSuccessfulRefactoring('''
639+
enum A { a }
640+
void f() {
641+
A res = .a;
642+
A vvv = res;
643+
}
644+
''');
645+
}
646+
647+
Future<void> test_dotShorthand_const() async {
648+
await indexTestUnit('''
649+
enum A { a }
650+
void f() {
651+
const A vvv = .a;
652+
}
653+
''');
654+
_createRefactoringForString('.a');
655+
// apply refactoring
656+
return _assertSuccessfulRefactoring('''
657+
enum A { a }
658+
void f() {
659+
const A res = .a;
660+
const A vvv = res;
661+
}
662+
''');
663+
}
664+
665+
Future<void> test_dotShorthand_inferred() async {
666+
await indexTestUnit('''
667+
enum A { a }
668+
T f<T>(T arg) => arg;
669+
670+
void g() {
671+
A a = f(.a);
672+
}
673+
''');
674+
_createRefactoringForString('.a');
675+
// apply refactoring
676+
return _assertSuccessfulRefactoring('''
677+
enum A { a }
678+
T f<T>(T arg) => arg;
679+
680+
void g() {
681+
A res = .a;
682+
A a = f(res);
683+
}
684+
''');
685+
}
686+
687+
Future<void> test_dotShorthand_inferred_chain() async {
688+
await indexTestUnit('''
689+
class A {
690+
A.named();
691+
A fn() => A.named();
692+
}
693+
694+
T f<T>(T arg) => arg;
695+
696+
void g() {
697+
A a = f(.named().fn());
698+
}
699+
''');
700+
_createRefactoringForString('.named().fn()');
701+
// apply refactoring
702+
return _assertSuccessfulRefactoring('''
703+
class A {
704+
A.named();
705+
A fn() => A.named();
706+
}
707+
708+
T f<T>(T arg) => arg;
709+
710+
void g() {
711+
A res = .named().fn();
712+
A a = f(res);
713+
}
714+
''');
715+
}
716+
611717
Future<void> test_fragmentExpression() async {
612718
await indexTestUnit('''
613719
void f() {
@@ -830,6 +936,32 @@ void f() {
830936
''');
831937
}
832938

939+
// Enabling the lint should not impact the result -- which is keeping the type
940+
// in the extracted local.
941+
Future<void> test_lint_alwaysSpecifyTypes_dotShorthand() async {
942+
createAnalysisOptionsFile(
943+
experiments: experiments,
944+
lints: [LintNames.always_specify_types],
945+
);
946+
await indexTestUnit('''
947+
enum A { a }
948+
void fn(A a) => print(a);
949+
void f() {
950+
fn(.a);
951+
}
952+
''');
953+
_createRefactoringForString('.a');
954+
// apply refactoring
955+
return _assertSuccessfulRefactoring('''
956+
enum A { a }
957+
void fn(A a) => print(a);
958+
void f() {
959+
A res = .a;
960+
fn(res);
961+
}
962+
''');
963+
}
964+
833965
Future<void> test_lint_alwaysSpecifyTypes_final() async {
834966
createAnalysisOptionsFile(
835967
lints: [LintNames.always_specify_types, LintNames.prefer_final_locals],
@@ -916,6 +1048,74 @@ void f() {
9161048
''');
9171049
}
9181050

1051+
Future<void> test_lint_preferFinalLocals_const() async {
1052+
createAnalysisOptionsFile(
1053+
experiments: experiments,
1054+
lints: [LintNames.prefer_final_locals],
1055+
);
1056+
await indexTestUnit('''
1057+
enum A { a }
1058+
void f() {
1059+
const a = A.a;
1060+
}
1061+
''');
1062+
_createRefactoringForString('.a');
1063+
// apply refactoring
1064+
return _assertSuccessfulRefactoring('''
1065+
enum A { a }
1066+
void f() {
1067+
const res = A.a;
1068+
const a = res;
1069+
}
1070+
''');
1071+
}
1072+
1073+
Future<void> test_lint_preferFinalLocals_dotShorthand() async {
1074+
createAnalysisOptionsFile(
1075+
experiments: experiments,
1076+
lints: [LintNames.prefer_final_locals],
1077+
);
1078+
await indexTestUnit('''
1079+
enum A { a }
1080+
void fn(A a) => print(a);
1081+
void f() {
1082+
fn(.a);
1083+
}
1084+
''');
1085+
_createRefactoringForString('.a');
1086+
// apply refactoring
1087+
return _assertSuccessfulRefactoring('''
1088+
enum A { a }
1089+
void fn(A a) => print(a);
1090+
void f() {
1091+
final A res = .a;
1092+
fn(res);
1093+
}
1094+
''');
1095+
}
1096+
1097+
Future<void> test_lint_preferFinalLocals_dotShorthand_const() async {
1098+
createAnalysisOptionsFile(
1099+
experiments: experiments,
1100+
lints: [LintNames.prefer_final_locals],
1101+
);
1102+
await indexTestUnit('''
1103+
enum A { a }
1104+
void f() {
1105+
const A a = .a;
1106+
}
1107+
''');
1108+
_createRefactoringForString('.a');
1109+
// apply refactoring
1110+
return _assertSuccessfulRefactoring('''
1111+
enum A { a }
1112+
void f() {
1113+
const A res = .a;
1114+
const A a = res;
1115+
}
1116+
''');
1117+
}
1118+
9191119
Future<void> test_occurrences_differentName_samePrefix() async {
9201120
await indexTestUnit('''
9211121
void f(A a) {

0 commit comments

Comments
 (0)