Include an empty body when suggesting a name for a top-level declaration that does not have a body

Change-Id: I92875fdb80b0541eaa5505b507e822915181ee52
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/370980
Reviewed-by: Keerti Parthasarathy <keertip@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Brian Wilkerson 2024-06-12 00:26:59 +00:00 committed by Commit Queue
parent 73cd7c6439
commit 2c97c6949e
9 changed files with 163 additions and 44 deletions

View file

@ -258,12 +258,25 @@ final class IdentifierSuggestion extends CandidateSuggestion {
/// The identifier to be inserted.
final String identifier;
/// Whether an empty body should be included in the completion string.
final bool includeBody;
/// Initialize a newly created candidate suggestion to suggest the
/// [identifier].
IdentifierSuggestion({required this.identifier, required super.matcherScore});
///
/// If [includeBody] is `true`, then empty curly braces will be included in
/// the suggestion.
IdentifierSuggestion(
{required this.identifier,
required this.includeBody,
required super.matcherScore});
@override
String get completion => identifier;
String get completion => identifier + (includeBody ? ' {}' : '');
/// The offset, from the beginning of the inserted text, where the cursor
/// should be positioned.
int get selectionOffset => identifier.length + (includeBody ? 2 : 0);
}
/// The information about a candidate suggestion based on a declaration that can
@ -843,7 +856,10 @@ extension SuggestionBuilderExtension on SuggestionBuilder {
case FunctionCall():
suggestFunctionCall();
case IdentifierSuggestion():
suggestName(suggestion.identifier);
suggestName(
suggestion.completion,
selectionOffset: suggestion.selectionOffset,
);
case ImportPrefixSuggestion():
suggestPrefix(
suggestion.libraryElement,

View file

@ -51,7 +51,7 @@ class IdentifierHelper {
/// Adds any suggestions for the name of a top-level declaration (class, enum,
/// mixin, function, etc.).
void addTopLevelName() {
void addTopLevelName({required bool includeBody}) {
var context = state.request.analysisSession.resourceProvider.pathContext;
var path = state.request.path;
var candidateName = context.basenameWithoutExtension(path).toUpperCamelCase;
@ -69,7 +69,10 @@ class IdentifierHelper {
var matcherScore = state.matcher.score(candidateName);
if (matcherScore != -1) {
collector.addSuggestion(IdentifierSuggestion(
identifier: candidateName, matcherScore: matcherScore));
identifier: candidateName,
includeBody: includeBody,
matcherScore: matcherScore,
));
}
}
@ -85,8 +88,11 @@ class IdentifierHelper {
if (name.isNotEmpty) {
var matcherScore = state.matcher.score(name);
if (matcherScore != -1) {
collector.addSuggestion(
IdentifierSuggestion(identifier: name, matcherScore: matcherScore));
collector.addSuggestion(IdentifierSuggestion(
identifier: name,
includeBody: false,
matcherScore: matcherScore,
));
}
}
}

View file

@ -618,7 +618,10 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
} else if (offset <= node.classKeyword.end) {
keywordHelper.addKeyword(Keyword.CLASS);
} else if (offset <= node.name.end) {
identifierHelper(includePrivateIdentifiers: false).addTopLevelName();
var hasSyntheticBody =
node.leftBracket.isSynthetic && node.rightBracket.isSynthetic;
identifierHelper(includePrivateIdentifiers: false)
.addTopLevelName(includeBody: hasSyntheticBody);
} else if (offset <= node.leftBracket.offset) {
keywordHelper.addClassDeclarationKeywords(node);
} else if (offset >= node.leftBracket.end &&
@ -944,7 +947,10 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
return;
}
if (offset <= node.name.end) {
identifierHelper(includePrivateIdentifiers: false).addTopLevelName();
var hasSyntheticBody =
node.leftBracket.isSynthetic && node.rightBracket.isSynthetic;
identifierHelper(includePrivateIdentifiers: false)
.addTopLevelName(includeBody: hasSyntheticBody);
return;
}
if (offset <= node.leftBracket.offset) {
@ -1103,10 +1109,16 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
if (featureSet.isEnabled(Feature.inline_class)) {
keywordHelper.addText('type');
}
identifierHelper(includePrivateIdentifiers: false).addTopLevelName();
// TODO(brianwilkerson): Consider adding both an `on` keyword and a body
// when none of them already exist.
identifierHelper(includePrivateIdentifiers: false)
.addTopLevelName(includeBody: false);
return;
} else {
identifierHelper(includePrivateIdentifiers: false).addTopLevelName();
// TODO(brianwilkerson): Consider adding both an `on` keyword and a body
// when none of them already exist.
identifierHelper(includePrivateIdentifiers: false)
.addTopLevelName(includeBody: false);
}
if (offset <= node.leftBracket.offset) {
collector.completionLocation = 'ExtensionDeclaration_onClause';
@ -1144,7 +1156,10 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
if (offset == node.offset) {
_forCompilationUnitMemberBefore(node);
} else if (offset <= node.name.end) {
identifierHelper(includePrivateIdentifiers: false).addTopLevelName();
var hasSyntheticBody =
node.leftBracket.isSynthetic && node.rightBracket.isSynthetic;
identifierHelper(includePrivateIdentifiers: false)
.addTopLevelName(includeBody: hasSyntheticBody);
} else if (offset >= node.representation.end &&
(offset <= node.leftBracket.offset || node.leftBracket.isSynthetic)) {
keywordHelper.addKeyword(Keyword.IMPLEMENTS);
@ -1838,7 +1853,10 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
return;
}
if (offset <= node.name.end) {
identifierHelper(includePrivateIdentifiers: false).addTopLevelName();
var hasSyntheticBody =
node.leftBracket.isSynthetic && node.rightBracket.isSynthetic;
identifierHelper(includePrivateIdentifiers: false)
.addTopLevelName(includeBody: hasSyntheticBody);
return;
}
if (offset <= node.leftBracket.offset) {

View file

@ -834,11 +834,11 @@ class SuggestionBuilder {
}
/// Add a suggestion to use the [name] at a declaration site.
void suggestName(String name) {
void suggestName(String name, {int? selectionOffset}) {
// TODO(brianwilkerson): Explore whether there are any features of the name
// that can be used to provide better relevance scores.
_addSuggestion(CompletionSuggestion(CompletionSuggestionKind.IDENTIFIER,
500, name, name.length, 0, false, false));
500, name, selectionOffset ?? name.length, 0, false, false));
}
/// Add a suggestion to add a named argument corresponding to the [parameter].

View file

@ -125,10 +125,11 @@ suggestions
''');
}
Future<void> test_name() async {
allowedIdentifiers = {'Test'};
Future<void> test_name_withBody() async {
allowedIdentifiers = {'Test', 'Test {}'};
printerConfiguration.withSelection = true;
await computeSuggestions('''
class ^
class ^ {}
''');
assertResponse(r'''
suggestions
@ -137,6 +138,20 @@ suggestions
''');
}
Future<void> test_name_withoutBody() async {
allowedIdentifiers = {'Test', 'Test {}'};
printerConfiguration.withSelection = true;
await computeSuggestions('''
class ^
''');
assertResponse(r'''
suggestions
Test {}
kind: identifier
selection: 6
''');
}
Future<void> test_noBody() async {
await computeSuggestions('''
class A ^

View file

@ -353,15 +353,30 @@ suggestions
''');
}
Future<void> test_name() async {
allowedIdentifiers = {'Test'};
Future<void> test_name_withBody() async {
allowedIdentifiers = {'Test', 'Test {}'};
printerConfiguration.withSelection = true;
await computeSuggestions('''
enum ^
enum ^ {}
''');
assertResponse(r'''
suggestions
Test
kind: identifier
''');
}
Future<void> test_name_withoutBody() async {
allowedIdentifiers = {'Test', 'Test {}'};
printerConfiguration.withSelection = true;
await computeSuggestions('''
enum ^
''');
assertResponse(r'''
suggestions
Test {}
kind: identifier
selection: 6
''');
}
}

View file

@ -54,24 +54,9 @@ suggestions
''');
}
Future<void> test_name() async {
allowedIdentifiers = {'Test'};
await computeSuggestions('''
extension ^
''');
assertResponse(r'''
suggestions
Test
kind: identifier
on
kind: keyword
type
kind: keyword
''');
}
Future<void> test_name_partial() async {
allowedIdentifiers = {'Test'};
allowedIdentifiers = {'Test', 'Test {}'};
printerConfiguration.withSelection = true;
await computeSuggestions('''
extension T^
''');
@ -83,6 +68,40 @@ suggestions
kind: identifier
type
kind: keyword
''');
}
Future<void> test_name_withBody() async {
allowedIdentifiers = {'Test', 'Test {}'};
printerConfiguration.withSelection = true;
await computeSuggestions('''
extension ^ {}
''');
assertResponse(r'''
suggestions
Test
kind: identifier
on
kind: keyword
type
kind: keyword
''');
}
Future<void> test_name_withoutBody() async {
allowedIdentifiers = {'Test', 'Test {}'};
printerConfiguration.withSelection = true;
await computeSuggestions('''
extension ^
''');
assertResponse(r'''
suggestions
Test
kind: identifier
on
kind: keyword
type
kind: keyword
''');
}
}

View file

@ -122,10 +122,11 @@ suggestions
''');
}
Future<void> test_name() async {
allowedIdentifiers = {'Test'};
Future<void> test_name_withBody() async {
allowedIdentifiers = {'Test', 'Test {}'};
printerConfiguration.withSelection = true;
await computeSuggestions('''
extension type ^
extension type ^ {}
''');
assertResponse(r'''
suggestions
@ -134,6 +135,20 @@ suggestions
''');
}
Future<void> test_name_withoutBody() async {
allowedIdentifiers = {'Test', 'Test {}'};
printerConfiguration.withSelection = true;
await computeSuggestions('''
extension type ^
''');
assertResponse(r'''
suggestions
Test {}
kind: identifier
selection: 6
''');
}
Future<void> test_representationField_annotation() async {
await computeSuggestions('''
extension type E(@^)

View file

@ -43,15 +43,30 @@ suggestions
''');
}
Future<void> test_name() async {
allowedIdentifiers = {'Test'};
Future<void> test_name_withBody() async {
allowedIdentifiers = {'Test', 'Test {}'};
printerConfiguration.withSelection = true;
await computeSuggestions('''
mixin ^
mixin ^ {}
''');
assertResponse(r'''
suggestions
Test
kind: identifier
''');
}
Future<void> test_name_withoutBody() async {
allowedIdentifiers = {'Test', 'Test {}'};
printerConfiguration.withSelection = true;
await computeSuggestions('''
mixin ^
''');
assertResponse(r'''
suggestions
Test {}
kind: identifier
selection: 6
''');
}
}