[CMSR] Support for formal parameters selection.

Change-Id: I38beddf9eeb36615e11ae1375aac335c1a7a6d56
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/308966
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Konstantin Shcheglov 2023-06-13 20:58:39 +00:00 committed by Commit Queue
parent a0cf7286af
commit 324cef773c
2 changed files with 297 additions and 31 deletions

View file

@ -98,12 +98,16 @@ final class FormalParameterState {
/// changing types, so this field should be read-only in UI.
final String typeStr;
/// If `true`, the selection covers this formal parameter.
final bool isSelected;
FormalParameterState({
required this.id,
required this.kind,
required this.positionalIndex,
required this.name,
required this.typeStr,
required this.isSelected,
});
}
@ -209,10 +213,12 @@ final class ValidSelectionState extends SelectionState {
class _Declaration {
final ExecutableElement element;
final AstNode node;
final List<FormalParameter> selected;
_Declaration({
required this.element,
required this.node,
required this.selected,
});
}
@ -286,6 +292,7 @@ class _SelectionAnalyzer {
positionalIndex: kind.isPositional ? positionalIndex++ : null,
name: nameToken.lexeme,
typeStr: refactoringContext.utils.getNodeText(typeNode),
isSelected: declaration.selected.contains(parameterNode),
),
);
}
@ -301,28 +308,19 @@ class _SelectionAnalyzer {
final coveringNode = refactoringContext.coveringNode;
switch (coveringNode) {
case FunctionDeclaration():
if (refactoringContext.selectionIsInToken(coveringNode.name)) {
final element = coveringNode.declaredElement;
if (element != null) {
return _Declaration(
element: element,
node: coveringNode,
);
}
}
return null;
case MethodDeclaration():
if (refactoringContext.selectionIsInToken(coveringNode.name)) {
final element = coveringNode.declaredElement;
if (element != null) {
return _Declaration(
element: element,
node: coveringNode,
);
}
}
return null;
case FormalParameter():
return _declarationFormalParameter(coveringNode);
case FormalParameterList():
return _declarationFormalParameterList(coveringNode);
}
final atExecutable = _declarationExecutable(
node: coveringNode,
anyLocation: false,
selected: const [],
);
if (atExecutable != null) {
return atExecutable;
}
Element? element;
@ -348,6 +346,88 @@ class _SelectionAnalyzer {
return _Declaration(
element: element,
node: node,
selected: const [],
);
}
_Declaration? _declarationExecutable({
required AstNode? node,
required bool anyLocation,
required List<FormalParameter> selected,
}) {
bool hasGoodLocation(Token? name) {
return anyLocation || refactoringContext.selectionIsInToken(name);
}
_Declaration? buildDeclaration(Declaration node) {
final element = node.declaredElement;
if (element is ExecutableElement) {
return _Declaration(
element: element,
node: node,
selected: selected,
);
}
return null;
}
if (node is FunctionExpression) {
final functionDeclaration = node.parent;
if (functionDeclaration is FunctionDeclaration) {
node = functionDeclaration;
}
}
switch (node) {
case FunctionDeclaration():
if (hasGoodLocation(node.name)) {
return buildDeclaration(node);
}
case MethodDeclaration():
if (hasGoodLocation(node.name)) {
return buildDeclaration(node);
}
}
return null;
}
_Declaration? _declarationFormalParameter(FormalParameter node) {
final FormalParameter formalParameter;
if (node.parent case DefaultFormalParameter result) {
formalParameter = result;
} else {
formalParameter = node;
}
final formalParameterList = formalParameter.parent;
if (formalParameterList is! FormalParameterList) {
return null;
}
return _declarationExecutable(
node: formalParameterList.parent,
anyLocation: true,
selected: [formalParameter],
);
}
_Declaration? _declarationFormalParameterList(FormalParameterList node) {
final selection = refactoringContext.selection;
final selectedNodes = selection?.nodesInRange();
if (selectedNodes == null) {
return null;
}
final selected = selectedNodes.whereType<FormalParameter>().toList();
if (selected.isEmpty) {
return null;
}
return _declarationExecutable(
node: node.parent,
anyLocation: true,
selected: selected,
);
}
}

View file

@ -13,6 +13,7 @@ import 'package:analysis_server/src/services/search/search_engine_internal.dart'
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/summary2/reference.dart';
import 'package:analyzer/src/test_utilities/test_code_format.dart';
@ -69,9 +70,16 @@ class AbstractChangeMethodSignatureTest extends AbstractContextTest {
required TestCode testCode,
}) async {
// There must be exactly one position.
expect(testCode.ranges, isEmpty);
expect(testCode.positions, hasLength(1));
final position = testCode.position;
final singlePosition = testCode.positions.singleOrNull;
final singleRange = testCode.ranges.singleOrNull;
final SourceRange selectionRange;
if (singlePosition != null && singleRange == null) {
selectionRange = SourceRange(singlePosition.offset, 0);
} else if (singlePosition == null && singleRange != null) {
selectionRange = singleRange.sourceRange;
} else {
fail('Expected exactly one: $singlePosition $singleRange');
}
final analysisSession = await session;
@ -86,8 +94,8 @@ class AbstractChangeMethodSignatureTest extends AbstractContextTest {
searchEngine: SearchEngineImpl(allDrivers),
resolvedLibraryResult: resolvedLibraryResult,
resolvedUnitResult: unitResult,
selectionOffset: position.offset,
selectionLength: 0,
selectionOffset: selectionRange.offset,
selectionLength: selectionRange.length,
includeExperimental: true,
);
}
@ -153,7 +161,182 @@ formalParameters
''');
}
Future<void> test_formalParameters_optionalNamed() async {
Future<void> test_formalParameters_requiredNamed_full() async {
await _analyzeSelection(r'''
void test({
[!required int a!],
required int b,
}) {}
''');
_assertSelectionState(selectionState, r'''
element: self::@function::test
formalParameters
id: 0
kind: requiredNamed
name: a
typeStr: int
selected
id: 1
kind: requiredNamed
name: b
typeStr: int
''');
}
Future<void> test_formalParameters_requiredNamed_multiple() async {
await _analyzeSelection(r'''
void test({
required int a,
[!required int b,
required int c,!]
required int d,
}) {}
''');
_assertSelectionState(selectionState, r'''
element: self::@function::test
formalParameters
id: 0
kind: requiredNamed
name: a
typeStr: int
id: 1
kind: requiredNamed
name: b
typeStr: int
selected
id: 2
kind: requiredNamed
name: c
typeStr: int
selected
id: 3
kind: requiredNamed
name: d
typeStr: int
''');
}
Future<void> test_formalParameters_requiredNamed_name_full() async {
await _analyzeSelection(r'''
void test({
required int [!aaaa!],
required int bbbb,
}) {}
''');
_assertSelectionState(selectionState, r'''
element: self::@function::test
formalParameters
id: 0
kind: requiredNamed
name: aaaa
typeStr: int
selected
id: 1
kind: requiredNamed
name: bbbb
typeStr: int
''');
}
Future<void> test_formalParameters_requiredNamed_name_partial() async {
await _analyzeSelection(r'''
void test({
required int a[!aa!]a,
required int bbbb,
}) {}
''');
_assertSelectionState(selectionState, r'''
element: self::@function::test
formalParameters
id: 0
kind: requiredNamed
name: aaaa
typeStr: int
selected
id: 1
kind: requiredNamed
name: bbbb
typeStr: int
''');
}
Future<void> test_formalParameters_requiredNamed_name_position() async {
await _analyzeSelection(r'''
void test({
required int ^a,
required int b
}) {}
''');
_assertSelectionState(selectionState, r'''
element: self::@function::test
formalParameters
id: 0
kind: requiredNamed
name: a
typeStr: int
selected
id: 1
kind: requiredNamed
name: b
typeStr: int
''');
}
Future<void> test_formalParameters_requiredPositional_multiple() async {
await _analyzeSelection(r'''
void test(int a, [!int b, int c,!] int d) {}
''');
_assertSelectionState(selectionState, r'''
element: self::@function::test
formalParameters
id: 0
kind: requiredPositional
name: a
typeStr: int
id: 1
kind: requiredPositional
name: b
typeStr: int
selected
id: 2
kind: requiredPositional
name: c
typeStr: int
selected
id: 3
kind: requiredPositional
name: d
typeStr: int
''');
}
Future<void> test_formalParameters_requiredPositional_name() async {
await _analyzeSelection(r'''
void test(int ^a, int b) {}
''');
_assertSelectionState(selectionState, r'''
element: self::@function::test
formalParameters
id: 0
kind: requiredPositional
name: a
typeStr: int
selected
id: 1
kind: requiredPositional
name: b
typeStr: int
''');
}
Future<void> test_kind_optionalNamed() async {
await _analyzeSelection(r'''
void ^test({int? a}) {}
''');
@ -168,7 +351,7 @@ formalParameters
''');
}
Future<void> test_formalParameters_optionalPositional() async {
Future<void> test_kind_optionalPositional() async {
await _analyzeSelection(r'''
void ^test([int? a]) {}
''');
@ -183,7 +366,7 @@ formalParameters
''');
}
Future<void> test_formalParameters_requiredNamed() async {
Future<void> test_kind_requiredNamed() async {
await _analyzeSelection(r'''
void ^test({required int a}) {}
''');
@ -198,7 +381,7 @@ formalParameters
''');
}
Future<void> test_formalParameters_requiredPositional2() async {
Future<void> test_kind_requiredPositional2() async {
await _analyzeSelection(r'''
void ^test(int a, String b) {}
''');
@ -287,6 +470,9 @@ NoExecutableElementSelectionState
buffer.writeln(' kind: ${formalParameter.kind.name}');
buffer.writeln(' name: ${formalParameter.name}');
buffer.writeln(' typeStr: ${formalParameter.typeStr}');
if (formalParameter.isSelected) {
buffer.writeln(' selected');
}
}
}