Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit ec1ddcd

Browse files
Dan Rubelcommit-bot@chromium.org
authored andcommitted
improve extension message missing body error message
This improves the missing class, mixin, and extension method body error messages and lays the groundwork for improving other missing body error messages such as switch statement, finally clause, and others. Change-Id: I0a4d1338fe91eee3cfe7d61652e168df1302d212 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/103570 Reviewed-by: Brian Wilkerson <[email protected]>
1 parent 9081f28 commit ec1ddcd

14 files changed

+85
-80
lines changed

pkg/analyzer/lib/error/error.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ const List<ErrorCode> errorCodeValues = const [
423423
ParserErrorCode.EMPTY_ENUM_BODY,
424424
ParserErrorCode.ENUM_IN_CLASS,
425425
ParserErrorCode.EQUALITY_CANNOT_BE_EQUALITY_OPERAND,
426+
ParserErrorCode.EXPECTED_BODY,
426427
ParserErrorCode.EXPECTED_CASE_OR_DEFAULT,
427428
ParserErrorCode.EXPECTED_CLASS_MEMBER,
428429
ParserErrorCode.EXPECTED_ELSE_OR_COMMA,

pkg/analyzer/lib/src/dart/error/syntactic_errors.dart

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ class ParserErrorCode extends ErrorCode {
161161
static const ParserErrorCode EQUALITY_CANNOT_BE_EQUALITY_OPERAND =
162162
_EQUALITY_CANNOT_BE_EQUALITY_OPERAND;
163163

164+
static const ParserErrorCode EXPECTED_BODY = _EXPECTED_BODY;
165+
164166
static const ParserErrorCode EXPECTED_CASE_OR_DEFAULT = const ParserErrorCode(
165167
'EXPECTED_CASE_OR_DEFAULT', "Expected 'case' or 'default'.",
166168
correction: "Try placing this code inside a case clause.");
@@ -415,11 +417,7 @@ class ParserErrorCode extends ErrorCode {
415417
static const ParserErrorCode MISSING_CATCH_OR_FINALLY =
416418
_MISSING_CATCH_OR_FINALLY;
417419

418-
/// TODO(danrubel): Consider splitting this into two separate error messages.
419-
static const ParserErrorCode MISSING_CLASS_BODY = const ParserErrorCode(
420-
'MISSING_CLASS_BODY',
421-
"A class or mixin definition must have a body, even if it is empty.",
422-
correction: "Try adding a body to your class or mixin.");
420+
static const ParserErrorCode MISSING_CLASS_BODY = _EXPECTED_BODY;
423421

424422
static const ParserErrorCode MISSING_CLOSING_PARENTHESIS =
425423
const ParserErrorCode(

pkg/analyzer/lib/src/dart/error/syntactic_errors.g.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ final fastaAnalyzerErrorCodes = <ErrorCode>[
103103
_EXPERIMENT_NOT_ENABLED,
104104
_EXPECTED_ELSE_OR_COMMA,
105105
_INVALID_SUPER_IN_INITIALIZER,
106+
_EXPECTED_BODY,
106107
];
107108

108109
const ParserErrorCode _ABSTRACT_CLASS_MEMBER = const ParserErrorCode(
@@ -227,6 +228,10 @@ const ParserErrorCode _EQUALITY_CANNOT_BE_EQUALITY_OPERAND = const ParserErrorCo
227228
r"An equality expression can't be an operand of another equality expression.",
228229
correction: "Try re-writing the expression.");
229230

231+
const ParserErrorCode _EXPECTED_BODY = const ParserErrorCode(
232+
'EXPECTED_BODY', r"A #string must have a body, even if it is empty.",
233+
correction: "Try adding an empty body.");
234+
230235
const ParserErrorCode _EXPECTED_ELSE_OR_COMMA = const ParserErrorCode(
231236
'EXPECTED_ELSE_OR_COMMA', r"Expected 'else' or comma.");
232237

pkg/analyzer/lib/src/fasta/error_converter.dart

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,6 @@ class FastaErrorReporter {
205205
errorReporter?.reportErrorForOffset(
206206
StrongModeCode.INVALID_SUPER_INVOCATION, offset, length);
207207
return;
208-
case "MISSING_CLASS_BODY":
209-
errorReporter?.reportErrorForOffset(
210-
ParserErrorCode.MISSING_CLASS_BODY, offset, length);
211-
return;
212208
case "MISSING_DIGIT":
213209
errorReporter?.reportErrorForOffset(
214210
ScannerErrorCode.MISSING_DIGIT, offset, length);

pkg/analyzer/test/generated/parser_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4413,7 +4413,7 @@ class Wrong<T> {
44134413

44144414
void test_missingClassBody() {
44154415
parseCompilationUnit("class A class B {}",
4416-
errors: [expectedError(ParserErrorCode.MISSING_CLASS_BODY, 8, 5)]);
4416+
errors: [expectedError(ParserErrorCode.MISSING_CLASS_BODY, 6, 1)]);
44174417
}
44184418

44194419
void test_missingClosingParenthesis() {

pkg/analyzer/test/src/task/options_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ class ErrorCodeValuesTest {
240240
declaredNames.remove('CONST_AND_COVARIANT');
241241
declaredNames.remove('CONST_AND_VAR');
242242
declaredNames.remove('COVARIANT_AFTER_FINAL');
243+
declaredNames.remove('MISSING_CLASS_BODY');
243244
}
244245

245246
// Assert that all remaining declared names are in errorCodeValues

pkg/front_end/lib/src/fasta/fasta_codes_generated.dart

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2913,27 +2913,6 @@ Message _withArgumentsExpectedButGot(String string) {
29132913
arguments: {'string': string});
29142914
}
29152915

2916-
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
2917-
const Template<Message Function(Token token)> templateExpectedClassBodyToSkip =
2918-
const Template<Message Function(Token token)>(
2919-
messageTemplate:
2920-
r"""Expected a class or mixin body, but got '#lexeme'.""",
2921-
withArguments: _withArgumentsExpectedClassBodyToSkip);
2922-
2923-
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
2924-
const Code<Message Function(Token token)> codeExpectedClassBodyToSkip =
2925-
const Code<Message Function(Token token)>(
2926-
"ExpectedClassBodyToSkip", templateExpectedClassBodyToSkip,
2927-
analyzerCodes: <String>["MISSING_CLASS_BODY"]);
2928-
2929-
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
2930-
Message _withArgumentsExpectedClassBodyToSkip(Token token) {
2931-
String lexeme = token.lexeme;
2932-
return new Message(codeExpectedClassBodyToSkip,
2933-
message: """Expected a class or mixin body, but got '${lexeme}'.""",
2934-
arguments: {'token': token});
2935-
}
2936-
29372916
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
29382917
const Template<Message Function(Token token)> templateExpectedClassMember =
29392918
const Template<Message Function(Token token)>(
@@ -2955,24 +2934,27 @@ Message _withArgumentsExpectedClassMember(Token token) {
29552934
}
29562935

29572936
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
2958-
const Template<Message Function(Token token)> templateExpectedClassOrMixinBody =
2959-
const Template<Message Function(Token token)>(
2937+
const Template<Message Function(String string)>
2938+
templateExpectedClassOrMixinBody =
2939+
const Template<Message Function(String string)>(
29602940
messageTemplate:
2961-
r"""Expected a class or mixin body, but got '#lexeme'.""",
2941+
r"""A #string must have a body, even if it is empty.""",
2942+
tipTemplate: r"""Try adding an empty body.""",
29622943
withArguments: _withArgumentsExpectedClassOrMixinBody);
29632944

29642945
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
2965-
const Code<Message Function(Token token)> codeExpectedClassOrMixinBody =
2966-
const Code<Message Function(Token token)>(
2946+
const Code<Message Function(String string)> codeExpectedClassOrMixinBody =
2947+
const Code<Message Function(String string)>(
29672948
"ExpectedClassOrMixinBody", templateExpectedClassOrMixinBody,
2968-
analyzerCodes: <String>["MISSING_CLASS_BODY"]);
2949+
index: 96);
29692950

29702951
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
2971-
Message _withArgumentsExpectedClassOrMixinBody(Token token) {
2972-
String lexeme = token.lexeme;
2952+
Message _withArgumentsExpectedClassOrMixinBody(String string) {
2953+
if (string.isEmpty) throw 'No string provided';
29732954
return new Message(codeExpectedClassOrMixinBody,
2974-
message: """Expected a class or mixin body, but got '${lexeme}'.""",
2975-
arguments: {'token': token});
2955+
message: """A ${string} must have a body, even if it is empty.""",
2956+
tip: """Try adding an empty body.""",
2957+
arguments: {'string': string});
29762958
}
29772959

29782960
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.

pkg/front_end/lib/src/fasta/parser/parser.dart

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1654,7 +1654,7 @@ class Parser {
16541654

16551655
Token skipBlock(Token token) {
16561656
// The scanner ensures that `{` always has a closing `}`.
1657-
return ensureBlock(token, null).endGroup;
1657+
return ensureBlock(token, null, null).endGroup;
16581658
}
16591659

16601660
/// ```
@@ -1713,7 +1713,8 @@ class Parser {
17131713
}
17141714
}
17151715
} else {
1716-
leftBrace = ensureBlock(token, fasta.templateExpectedEnumBody);
1716+
// TODO(danrubel): merge this error message with missing class/mixin body
1717+
leftBrace = ensureBlock(token, fasta.templateExpectedEnumBody, null);
17171718
token = leftBrace.endGroup;
17181719
}
17191720
assert(optional('}', token));
@@ -1772,7 +1773,7 @@ class Parser {
17721773
if (!optional('{', token.next)) {
17731774
// Recovery
17741775
token = parseClassHeaderRecovery(start, begin, classKeyword);
1775-
ensureBlock(token, fasta.templateExpectedClassOrMixinBody);
1776+
ensureBlock(token, null, 'class declaration');
17761777
}
17771778
token = parseClassOrMixinBody(token);
17781779
listener.endClassDeclaration(begin, token);
@@ -1938,7 +1939,7 @@ class Parser {
19381939
if (!optional('{', token.next)) {
19391940
// Recovery
19401941
token = parseMixinHeaderRecovery(token, mixinKeyword, headerStart);
1941-
ensureBlock(token, fasta.templateExpectedClassOrMixinBody);
1942+
ensureBlock(token, null, 'mixin declaration');
19421943
}
19431944
token = parseClassOrMixinBody(token);
19441945
listener.endMixinDeclaration(mixinKeyword, token);
@@ -2078,8 +2079,7 @@ class Parser {
20782079
TypeInfo typeInfo = computeType(onKeyword, true);
20792080
token = typeInfo.ensureTypeNotVoid(onKeyword, this);
20802081
if (!optional('{', token.next)) {
2081-
// TODO(danrubel): replace this with a better error message.
2082-
ensureBlock(token, fasta.templateExpectedClassOrMixinBody);
2082+
ensureBlock(token, null, 'extension declaration');
20832083
}
20842084
// TODO(danrubel): Do not allow fields or constructors
20852085
token = parseClassOrMixinBody(token);
@@ -2701,17 +2701,28 @@ class Parser {
27012701
}
27022702

27032703
/// If the next token is an opening curly brace, return it. Otherwise, use the
2704-
/// given [template] to report an error, insert an opening and a closing curly
2705-
/// brace, and return the newly inserted opening curly brace. If the
2706-
/// [template] is `null`, use a default error message instead.
2707-
Token ensureBlock(
2708-
Token token, Template<Message Function(Token token)> template) {
2704+
/// given [template] or [blockKind] to report an error, insert an opening and
2705+
/// a closing curly brace, and return the newly inserted opening curly brace.
2706+
/// If [template] and [blockKind] are `null`, then use
2707+
/// a default error message instead.
2708+
Token ensureBlock(Token token,
2709+
Template<Message Function(Token token)> template, String blockKind) {
27092710
Token next = token.next;
27102711
if (optional('{', next)) return next;
2711-
Message message = template == null
2712-
? fasta.templateExpectedButGot.withArguments('{')
2713-
: template.withArguments(next);
2714-
reportRecoverableError(next, message);
2712+
if (template == null) {
2713+
if (blockKind == null) {
2714+
// TODO(danrubel): rename ExpectedButGot to ExpectedBefore
2715+
reportRecoverableError(
2716+
next, fasta.templateExpectedButGot.withArguments('{'));
2717+
} else {
2718+
// TODO(danrubel): rename ExpectedClassOrMixinBody
2719+
// to ExpectedDeclarationOrClauseBody
2720+
reportRecoverableError(token,
2721+
fasta.templateExpectedClassOrMixinBody.withArguments(blockKind));
2722+
}
2723+
} else {
2724+
reportRecoverableError(next, template.withArguments(next));
2725+
}
27152726
return insertBlock(token);
27162727
}
27172728

@@ -2842,7 +2853,7 @@ class Parser {
28422853

28432854
Token skipClassOrMixinBody(Token token) {
28442855
// The scanner ensures that `{` always has a closing `}`.
2845-
return ensureBlock(token, fasta.templateExpectedClassOrMixinBody);
2856+
return ensureBlock(token, null, null);
28462857
}
28472858

28482859
/// ```
@@ -3556,7 +3567,7 @@ class Parser {
35563567
begin = next = token.next;
35573568
// Fall through to parse the block.
35583569
} else {
3559-
token = ensureBlock(token, fasta.templateExpectedFunctionBody);
3570+
token = ensureBlock(token, fasta.templateExpectedFunctionBody, null);
35603571
listener.handleInvalidFunctionBody(token);
35613572
return token.endGroup;
35623573
}
@@ -3678,7 +3689,8 @@ class Parser {
36783689
}
36793690
final value = token.next.stringValue;
36803691
if (identical(value, '{')) {
3681-
return parseBlock(token);
3692+
// The scanner ensures that `{` always has a closing `}`.
3693+
return parseBlock(token, null);
36823694
} else if (identical(value, 'return')) {
36833695
return parseReturnStatement(token);
36843696
} else if (identical(value, 'var') || identical(value, 'final')) {
@@ -5631,8 +5643,8 @@ class Parser {
56315643
/// '{' statement* '}'
56325644
/// ;
56335645
/// ```
5634-
Token parseBlock(Token token) {
5635-
Token begin = token = ensureBlock(token, null);
5646+
Token parseBlock(Token token, String blockKind) {
5647+
Token begin = token = ensureBlock(token, null, blockKind);
56365648
listener.beginBlock(begin);
56375649
int statementCount = 0;
56385650
Token startToken = token.next;
@@ -5661,7 +5673,8 @@ class Parser {
56615673
// because an error has already been reported by the caller.
56625674
Listener originalListener = listener;
56635675
listener = new ForwardingListener(listener)..forwardErrors = false;
5664-
token = parseBlock(token);
5676+
// The scanner ensures that `{` always has a closing `}`.
5677+
token = parseBlock(token, null);
56655678
listener = originalListener;
56665679
listener.handleInvalidTopLevelBlock(begin);
56675680
return token;
@@ -5776,7 +5789,8 @@ class Parser {
57765789
Token tryKeyword = token.next;
57775790
assert(optional('try', tryKeyword));
57785791
listener.beginTryStatement(tryKeyword);
5779-
Token lastConsumed = parseBlock(tryKeyword);
5792+
// TODO(danrubel): pass 'try statement' for blockKind and update tests
5793+
Token lastConsumed = parseBlock(tryKeyword, null);
57805794
token = lastConsumed.next;
57815795
int catchCount = 0;
57825796

@@ -5871,7 +5885,8 @@ class Parser {
58715885
token = lastConsumed.next;
58725886
}
58735887
listener.endCatchClause(token);
5874-
lastConsumed = parseBlock(lastConsumed);
5888+
// TODO(danrubel): pass 'catch clause' for blockKind and update tests
5889+
lastConsumed = parseBlock(lastConsumed, null);
58755890
token = lastConsumed.next;
58765891
++catchCount;
58775892
listener.handleCatchBlock(onKeyword, catchKeyword, comma);
@@ -5881,7 +5896,8 @@ class Parser {
58815896
Token finallyKeyword = null;
58825897
if (optional('finally', token)) {
58835898
finallyKeyword = token;
5884-
lastConsumed = parseBlock(token);
5899+
// TODO(danrubel): pass 'finally clause' for blockKind and update tests
5900+
lastConsumed = parseBlock(token, null);
58855901
token = lastConsumed.next;
58865902
listener.handleFinallyBlock(finallyKeyword);
58875903
} else {
@@ -5919,7 +5935,8 @@ class Parser {
59195935
/// ;
59205936
/// ```
59215937
Token parseSwitchBlock(Token token) {
5922-
Token beginSwitch = token = ensureBlock(token, null);
5938+
// TODO(danrubel): pass 'switch statement' for blockKind and update tests
5939+
Token beginSwitch = token = ensureBlock(token, null, null);
59235940
listener.beginSwitchBlock(beginSwitch);
59245941
int caseCount = 0;
59255942
Token defaultKeyword = null;

pkg/front_end/messages.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -413,10 +413,10 @@ ImplementsFutureOr:
413413
class A implements FutureOr<int> {}
414414
415415
ExpectedClassOrMixinBody:
416-
template: "Expected a class or mixin body, but got '#lexeme'."
417-
analyzerCode: MISSING_CLASS_BODY
418-
419-
ExpectedClassBodyToSkip: ExpectedClassOrMixinBody
416+
index: 96
417+
template: "A #string must have a body, even if it is empty."
418+
tip: "Try adding an empty body."
419+
analyzerCode: ParserErrorCode.EXPECTED_BODY
420420

421421
ExpectedDeclaration:
422422
template: "Expected a declaration, but got '#lexeme'."

pkg/front_end/testcases/regress/issue_36647.dart.legacy.expect

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ library;
1919
//
2020
// Problems in library:
2121
//
22-
// pkg/front_end/testcases/regress/issue_36647_lib2.dart:5:11: Error: Expected a class or mixin body, but got 'xx'.
22+
// pkg/front_end/testcases/regress/issue_36647_lib2.dart:5:7: Error: A class declaration must have a body, even if it is empty.
23+
// Try adding an empty body.
2324
// class xxx xx XXX extends XXX {
24-
// ^^
25+
// ^^^
2526
//
2627
// pkg/front_end/testcases/regress/issue_36647_lib2.dart:5:14: Error: Expected ';' after this.
2728
// class xxx xx XXX extends XXX {

0 commit comments

Comments
 (0)