Files
sdk/pkg/analyzer/test/generated/parser_test_base.dart
T
Sam Rawlins ee69f45a1f analyzer: rework AnalysisErrorListener deprecation
Work towards #60635

In this change, we rework the AnalysisErrorListener deprecation to
better support users who have their own class that implements
AnalysisErrorListener. This change introduces a sealed supertype,
DiagnosticOrErrorListener, with the old implementation,
AnalysisErrorListener, and the new implementation, DiagnosticListener,
as its sole direct subclasses. Users who have implemented
AnalysisErrorListener should be able to instead implement
DiagnosticListener, and their class is an acceptable instance of
DiagnosticOrErrorListener, wherever that is needed.

In a breaking change we can drop AnalysisErrorListener and deprecate
DiagnosticOrErrorListener, and in the next breaking change, we can drop
DiagnosticOrErrorListener.

For reference, see the first API difference when deprecating AnalysisErrorListener and introducing DiagnosticListener: https://github.com/dart-lang/sdk/commit/903d77cc8229972a424941dcb7b7b79741e833eb#diff-dec15868961d7eadcd009f49d129bebcdb747aaa8dbfde5f5a08884e0cf11e32

Change-Id: I3ccf11d54b41fbca98d020d89978d250c16b4c04
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/436480
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
2025-06-25 06:54:20 -07:00

1726 lines
48 KiB
Dart

// Copyright (c) 2014, 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:_fe_analyzer_shared/src/parser/async_modifier.dart';
import 'package:_fe_analyzer_shared/src/parser/parser.dart' as fasta;
import 'package:_fe_analyzer_shared/src/scanner/error_token.dart'
show ErrorToken;
import 'package:_fe_analyzer_shared/src/scanner/scanner.dart'
show ScannerConfiguration, ScannerResult, scanString;
import 'package:_fe_analyzer_shared/src/scanner/scanner.dart' as fasta;
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/source/source.dart';
import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:analyzer/src/dart/ast/ast.dart' show CompilationUnitImpl;
import 'package:analyzer/src/dart/ast/token.dart';
import 'package:analyzer/src/dart/scanner/scanner.dart';
import 'package:analyzer/src/fasta/ast_builder.dart';
import 'package:analyzer/src/generated/parser.dart' as analyzer;
import 'package:analyzer/src/generated/source.dart' show NonExistingSource;
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:analyzer/src/string_source.dart';
import 'package:pub_semver/src/version.dart';
import 'package:test/test.dart';
import '../util/feature_sets.dart';
import 'parser_fasta_listener.dart';
import 'test_support.dart';
/// Abstract base class for parser tests, which does not make assumptions about
/// which parser is used.
abstract class AbstractParserTestCase implements ParserTestHelpers {
bool get allowNativeClause;
set allowNativeClause(bool value);
/// The error listener to which scanner and parser errors will be reported.
///
/// This field is typically initialized by invoking [createParser].
GatheringDiagnosticListener get listener;
/// Get the parser used by the test.
///
/// Caller must first invoke [createParser].
analyzer.Parser get parser;
/// Assert that the number and codes of errors occurred during parsing is the
/// same as the [expectedCodes].
void assertErrorsWithCodes(List<DiagnosticCode> expectedCodes);
/// Asserts that no errors occurred during parsing.
void assertNoErrors();
/// Prepares to parse using tokens scanned from the given [content] string.
///
/// [expectedEndOffset] is the expected offset of the next token to be parsed
/// after the parser has finished parsing,
/// or `null` (the default) if EOF is expected.
/// In general, the analyzer tests do not assert that the last token is EOF,
/// but the fasta parser adapter tests do assert this.
/// For any analyzer test where the last token is not EOF, set this value.
/// It is ignored when not using the fasta parser.
void createParser(
String content, {
int expectedEndOffset,
FeatureSet featureSet,
});
ExpectedError expectedError(DiagnosticCode code, int offset, int length);
void expectNotNullIfNoErrors(Object result);
Expression parseAdditiveExpression(String code);
Expression parseAssignableExpression(String code, bool primaryAllowed);
Expression parseAssignableSelector(
String code,
bool optional, {
bool allowConditional = true,
});
AwaitExpression parseAwaitExpression(String code);
Expression parseBitwiseAndExpression(String code);
Expression parseBitwiseOrExpression(String code);
Expression parseBitwiseXorExpression(String code);
Expression parseCascadeSection(String code);
CommentReference? parseCommentReference(
String referenceSource,
int sourceOffset,
);
CompilationUnit parseCompilationUnit(
String source, {
List<DiagnosticCode> codes,
List<ExpectedError> errors,
});
ConditionalExpression parseConditionalExpression(String code);
Expression parseConstExpression(String code);
ConstructorInitializer parseConstructorInitializer(String code);
/// Parse the given source as a compilation unit.
///
/// @param source the source to be parsed
/// @param errorCodes the error codes of the errors that are expected to be
/// found
/// @return the compilation unit that was parsed
/// @throws Exception if the source could not be parsed, if the compilation
/// errors in the source do not match those that are expected, or if
/// the result would have been `null`
CompilationUnit parseDirectives(
String source, [
List<DiagnosticCode> diagnosticCodes = const <DiagnosticCode>[],
]);
BinaryExpression parseEqualityExpression(String code);
Expression parseExpression(
String source, {
List<DiagnosticCode> codes,
List<ExpectedError> errors,
int expectedEndOffset,
});
List<Expression> parseExpressionList(String code);
Expression parseExpressionWithoutCascade(String code);
FormalParameter parseFormalParameter(
String code,
ParameterKind kind, {
List<DiagnosticCode> diagnosticCodes = const <DiagnosticCode>[],
});
FormalParameterList parseFormalParameterList(
String code, {
bool inFunctionType = false,
List<DiagnosticCode> diagnosticCodes = const <DiagnosticCode>[],
List<ExpectedError> errors,
});
/// Parses a single top level member of a compilation unit (other than a
/// directive), including any comment and/or metadata that precedes it.
CompilationUnitMember parseFullCompilationUnitMember();
/// Parses a single top level directive, including any comment and/or metadata
/// that precedes it.
Directive parseFullDirective();
FunctionExpression parseFunctionExpression(String code);
InstanceCreationExpression parseInstanceCreationExpression(
String code,
Token newToken,
);
ListLiteral parseListLiteral(
Token token,
String typeArgumentsCode,
String code,
);
TypedLiteral parseListOrMapLiteral(Token modifier, String code);
Expression parseLogicalAndExpression(String code);
Expression parseLogicalOrExpression(String code);
SetOrMapLiteral parseMapLiteral(
Token token,
String typeArgumentsCode,
String code,
);
MapLiteralEntry parseMapLiteralEntry(String code);
Expression parseMultiplicativeExpression(String code);
InstanceCreationExpression parseNewExpression(String code);
NormalFormalParameter parseNormalFormalParameter(
String code, {
bool inFunctionType = false,
List<DiagnosticCode> diagnosticCodes = const <DiagnosticCode>[],
});
Expression parsePostfixExpression(String code);
Identifier parsePrefixedIdentifier(String code);
Expression parsePrimaryExpression(
String code, {
int expectedEndOffset,
List<ExpectedError> errors,
});
Expression parseRelationalExpression(String code);
RethrowExpression parseRethrowExpression(String code);
BinaryExpression parseShiftExpression(String code);
SimpleIdentifier parseSimpleIdentifier(String code);
Statement parseStatement(String source, {int expectedEndOffset});
Expression parseStringLiteral(String code);
SymbolLiteral parseSymbolLiteral(String code);
Expression parseThrowExpression(String code);
Expression parseThrowExpressionWithoutCascade(String code);
PrefixExpression parseUnaryExpression(String code);
VariableDeclarationList parseVariableDeclarationList(String source);
}
/// This class just narrows the type of [parser] to [ParserProxy].
abstract class AbstractParserViaProxyTestCase
implements AbstractParserTestCase {
@override
ParserProxy get parser;
}
/// Implementation of [AbstractParserTestCase] specialized for testing the
/// Fasta parser.
class FastaParserTestCase
with ParserTestHelpers
implements AbstractParserTestCase {
static final List<DiagnosticCode> NO_ERROR_COMPARISON = <DiagnosticCode>[];
late ParserProxy parserProxy;
late Token _fastaTokens;
@override
bool allowNativeClause = false;
@override
GatheringDiagnosticListener get listener => parserProxy.diagnosticListener;
@override
ParserProxy get parser => parserProxy;
void assertErrors({
List<DiagnosticCode>? codes,
List<ExpectedError>? errors,
}) {
if (codes != null) {
if (!identical(codes, NO_ERROR_COMPARISON)) {
assertErrorsWithCodes(codes);
}
} else if (errors != null) {
listener.assertErrors(errors);
} else {
assertNoErrors();
}
}
@override
void assertErrorsWithCodes(List<DiagnosticCode> expectedCodes) {
parserProxy.diagnosticListener.assertErrorsWithCodes(expectedCodes);
}
@override
void assertNoErrors() {
parserProxy.diagnosticListener.assertNoErrors();
}
@override
void createParser(
String content, {
int? expectedEndOffset,
FeatureSet? featureSet,
}) {
featureSet ??= FeatureSet.latestLanguageVersion();
var languageVersion = LibraryLanguageVersion(
package: ExperimentStatus.currentVersion,
override: null,
);
var result = scanString(
content,
configuration: ScannerConfiguration.nonNullable,
includeComments: true,
languageVersionChanged: (scanner, overrideVersion) {
languageVersion = LibraryLanguageVersion(
package: ExperimentStatus.currentVersion,
override: Version(overrideVersion.major, overrideVersion.minor, 0),
);
},
);
var lineInfo = LineInfo(result.lineStarts);
_fastaTokens = result.tokens;
parserProxy = ParserProxy(
_fastaTokens,
featureSet,
languageVersion,
allowNativeClause: allowNativeClause,
expectedEndOffset: expectedEndOffset,
lineInfo: lineInfo,
);
}
@override
ExpectedError expectedError(DiagnosticCode code, int offset, int length) =>
ExpectedError(code, offset, length);
@override
void expectNotNullIfNoErrors(Object? result) {
if (!listener.hasDiagnostics) {
expect(result, isNotNull);
}
}
@override
Expression parseAdditiveExpression(String code) {
return _parseExpression(code);
}
Expression parseArgument(String source) {
createParser(source);
return parserProxy.parseArgument();
}
@override
Expression parseAssignableExpression(String code, bool primaryAllowed) {
return _parseExpression(code);
}
@override
Expression parseAssignableSelector(
String code,
bool optional, {
bool allowConditional = true,
}) {
if (optional) {
if (code.isEmpty) {
return _parseExpression('foo');
}
return _parseExpression('(foo)$code');
}
return _parseExpression('foo$code');
}
@override
AwaitExpression parseAwaitExpression(String code) {
var function = _parseExpression('() async => $code') as FunctionExpression;
return (function.body as ExpressionFunctionBody).expression
as AwaitExpression;
}
@override
Expression parseBitwiseAndExpression(String code) {
return _parseExpression(code);
}
@override
Expression parseBitwiseOrExpression(String code) {
return _parseExpression(code);
}
@override
Expression parseBitwiseXorExpression(String code) {
return _parseExpression(code);
}
@override
Expression parseCascadeSection(String code) {
var cascadeExpression = _parseExpression('null$code') as CascadeExpression;
return cascadeExpression.cascadeSections.first;
}
@override
CommentReference? parseCommentReference(
String referenceSource,
int sourceOffset,
) {
String padding = ' '.padLeft(sourceOffset - 4, 'a');
String source = '/**$padding[$referenceSource] */ class C { }';
CompilationUnit unit = parseCompilationUnit(source);
var clazz = unit.declarations[0] as ClassDeclaration;
var comment = clazz.documentationComment!;
List<CommentReference> references = comment.references;
if (references.isEmpty) {
return null;
} else {
expect(references, hasLength(1));
return references[0];
}
}
@override
CompilationUnitImpl parseCompilationUnit(
String content, {
List<DiagnosticCode>? codes,
List<ExpectedError>? errors,
FeatureSet? featureSet,
}) {
GatheringDiagnosticListener listener = GatheringDiagnosticListener();
var unit = parseCompilationUnit2(content, listener, featureSet: featureSet);
// Assert and return result
if (codes != null) {
listener.assertErrorsWithCodes(codes);
} else if (errors != null) {
listener.assertErrors(errors);
} else {
listener.assertNoErrors();
}
return unit;
}
CompilationUnitImpl parseCompilationUnit2(
String content,
GatheringDiagnosticListener listener, {
FeatureSet? featureSet,
}) {
featureSet ??= FeatureSet.latestLanguageVersion();
var source = StringSource(content, 'parser_test_StringSource.dart');
// Adjust the feature set based on language version comment.
void languageVersionChanged(
fasta.Scanner scanner,
LanguageVersionToken languageVersion,
) {
featureSet = featureSet!.restrictToVersion(
Version(languageVersion.major, languageVersion.minor, 0),
);
scanner.configuration = Scanner.buildConfig(featureSet);
}
// Scan tokens
ScannerResult result = scanString(
content,
includeComments: true,
configuration: Scanner.buildConfig(featureSet),
languageVersionChanged: languageVersionChanged,
);
_fastaTokens = result.tokens;
// Run parser
DiagnosticReporter diagnosticReporter = DiagnosticReporter(
listener,
source,
);
AstBuilder astBuilder = AstBuilder(
diagnosticReporter,
source.uri,
true,
featureSet!,
LibraryLanguageVersion(
package: ExperimentStatus.currentVersion,
override: null,
),
LineInfo.fromContent(content),
);
fasta.Parser parser = fasta.Parser(
astBuilder,
allowPatterns: featureSet!.isEnabled(Feature.patterns),
enableFeatureEnhancedParts: featureSet!.isEnabled(Feature.enhanced_parts),
);
astBuilder.parser = parser;
astBuilder.allowNativeClause = allowNativeClause;
parser.parseUnit(_fastaTokens);
var unit = astBuilder.pop() as CompilationUnitImpl;
expect(unit, isNotNull);
return unit;
}
@override
ConditionalExpression parseConditionalExpression(String code) {
return _parseExpression(code) as ConditionalExpression;
}
@override
Expression parseConstExpression(String code) {
return _parseExpression(code);
}
@override
ConstructorInitializer parseConstructorInitializer(String code) {
createParser('class __Test { __Test() : $code; }');
CompilationUnit unit = parserProxy.parseCompilationUnit2();
assertNoErrors();
var clazz = unit.declarations[0] as ClassDeclaration;
var constructor = clazz.members[0] as ConstructorDeclaration;
return constructor.initializers.single;
}
@override
CompilationUnit parseDirectives(
String source, [
List<DiagnosticCode> diagnosticCodes = const <DiagnosticCode>[],
]) {
createParser(source);
CompilationUnit unit = parserProxy.parseDirectives(
parserProxy.currentToken,
);
expect(unit, isNotNull);
expect(unit.declarations, hasLength(0));
listener.assertErrorsWithCodes(diagnosticCodes);
return unit;
}
@override
BinaryExpression parseEqualityExpression(String code) {
return _parseExpression(code) as BinaryExpression;
}
@override
Expression parseExpression(
String source, {
List<DiagnosticCode>? codes,
List<ExpectedError>? errors,
int? expectedEndOffset,
bool inAsync = false,
FeatureSet? featureSet,
}) {
createParser(
source,
expectedEndOffset: expectedEndOffset,
featureSet: featureSet,
);
if (inAsync) {
parserProxy.fastaParser.asyncState = AsyncModifier.Async;
}
Expression result = parserProxy.parseExpression2();
assertErrors(codes: codes, errors: errors);
return result;
}
@override
List<Expression> parseExpressionList(String code) {
return (_parseExpression('[$code]') as ListLiteral).elements
.toList()
.cast<Expression>();
}
@override
Expression parseExpressionWithoutCascade(String code) {
return _parseExpression(code);
}
@override
FormalParameter parseFormalParameter(
String code,
ParameterKind kind, {
List<DiagnosticCode> diagnosticCodes = const <DiagnosticCode>[],
FeatureSet? featureSet,
}) {
String parametersCode;
if (kind == ParameterKind.REQUIRED) {
parametersCode = '($code)';
} else if (kind == ParameterKind.POSITIONAL) {
parametersCode = '([$code])';
} else if (kind == ParameterKind.NAMED) {
parametersCode = '({$code})';
} else {
fail('$kind');
}
FormalParameterList list = parseFormalParameterList(
parametersCode,
diagnosticCodes: diagnosticCodes,
featureSet: featureSet,
);
return list.parameters.single;
}
@override
FormalParameterList parseFormalParameterList(
String code, {
bool inFunctionType = false,
List<DiagnosticCode> diagnosticCodes = const <DiagnosticCode>[],
List<ExpectedError>? errors,
FeatureSet? featureSet,
}) {
createParser(code, featureSet: featureSet);
FormalParameterList result = parserProxy.parseFormalParameterList(
inFunctionType: inFunctionType,
);
assertErrors(
codes: errors != null ? null : diagnosticCodes,
errors: errors,
);
return result;
}
@override
CompilationUnitMember parseFullCompilationUnitMember() {
return parserProxy.parseTopLevelDeclaration(false) as CompilationUnitMember;
}
@override
Directive parseFullDirective() {
return parserProxy.parseTopLevelDeclaration(true) as Directive;
}
@override
FunctionExpression parseFunctionExpression(String code) {
return _parseExpression(code) as FunctionExpression;
}
@override
InstanceCreationExpression parseInstanceCreationExpression(
String code,
Token newToken,
) {
return _parseExpression('$newToken $code') as InstanceCreationExpression;
}
@override
ListLiteral parseListLiteral(
Token? token,
String? typeArgumentsCode,
String code,
) {
String sc = '';
if (token != null) {
sc += '${token.lexeme} ';
}
if (typeArgumentsCode != null) {
sc += typeArgumentsCode;
}
sc += code;
return _parseExpression(sc) as ListLiteral;
}
@override
TypedLiteral parseListOrMapLiteral(Token? modifier, String code) {
String literalCode = modifier != null ? '$modifier $code' : code;
return parsePrimaryExpression(literalCode) as TypedLiteral;
}
@override
Expression parseLogicalAndExpression(String code) {
return _parseExpression(code);
}
@override
Expression parseLogicalOrExpression(String code) {
return _parseExpression(code);
}
@override
SetOrMapLiteral parseMapLiteral(
Token? token,
String? typeArgumentsCode,
String code,
) {
String sc = '';
if (token != null) {
sc += '${token.lexeme} ';
}
if (typeArgumentsCode != null) {
sc += typeArgumentsCode;
}
sc += code;
return parsePrimaryExpression(sc) as SetOrMapLiteral;
}
@override
MapLiteralEntry parseMapLiteralEntry(String code) {
var mapLiteral = parseMapLiteral(null, null, '{ $code }');
return mapLiteral.elements.single as MapLiteralEntry;
}
@override
Expression parseMultiplicativeExpression(String code) {
return _parseExpression(code);
}
@override
InstanceCreationExpression parseNewExpression(String code) {
return _parseExpression(code) as InstanceCreationExpression;
}
@override
NormalFormalParameter parseNormalFormalParameter(
String code, {
bool inFunctionType = false,
List<DiagnosticCode> diagnosticCodes = const <DiagnosticCode>[],
}) {
FormalParameterList list = parseFormalParameterList(
'($code)',
inFunctionType: inFunctionType,
diagnosticCodes: diagnosticCodes,
);
return list.parameters.single as NormalFormalParameter;
}
@override
Expression parsePostfixExpression(String code) {
return _parseExpression(code);
}
@override
Identifier parsePrefixedIdentifier(String code) {
return _parseExpression(code) as Identifier;
}
@override
Expression parsePrimaryExpression(
String code, {
int? expectedEndOffset,
List<ExpectedError>? errors,
}) {
createParser(code, expectedEndOffset: expectedEndOffset);
Expression result = parserProxy.parsePrimaryExpression();
assertErrors(errors: errors);
return result;
}
@override
Expression parseRelationalExpression(String code) {
return _parseExpression(code);
}
@override
RethrowExpression parseRethrowExpression(String code) {
return _parseExpression(code) as RethrowExpression;
}
@override
BinaryExpression parseShiftExpression(String code) {
return _parseExpression(code) as BinaryExpression;
}
@override
SimpleIdentifier parseSimpleIdentifier(String code) {
return _parseExpression(code) as SimpleIdentifier;
}
@override
Statement parseStatement(
String source, {
int? expectedEndOffset,
FeatureSet? featureSet,
bool inAsync = false,
}) {
createParser(
source,
expectedEndOffset: expectedEndOffset,
featureSet: featureSet,
);
if (inAsync) {
parserProxy.fastaParser.asyncState = AsyncModifier.Async;
}
Statement statement = parserProxy.parseStatement2();
assertErrors(codes: NO_ERROR_COMPARISON);
return statement;
}
@override
Expression parseStringLiteral(String code) {
return _parseExpression(code);
}
@override
SymbolLiteral parseSymbolLiteral(String code) {
return _parseExpression(code) as SymbolLiteral;
}
@override
Expression parseThrowExpression(String code) {
return _parseExpression(code);
}
@override
Expression parseThrowExpressionWithoutCascade(String code) {
return _parseExpression(code);
}
@override
PrefixExpression parseUnaryExpression(String code) {
return _parseExpression(code) as PrefixExpression;
}
@override
VariableDeclarationList parseVariableDeclarationList(String code) {
var statement = parseStatement('$code;') as VariableDeclarationStatement;
return statement.variables;
}
Expression _parseExpression(String code) {
var statement = parseStatement('$code;') as ExpressionStatement;
return statement.expression;
}
}
/// Proxy implementation of the analyzer parser, implemented in terms of the
/// Fasta parser.
///
/// This allows many of the analyzer parser tests to be run on Fasta, even if
/// they call into the analyzer parser class directly.
class ParserProxy extends analyzer.Parser {
/// The diagnostic listener to which scanner and parser diagnostics will be
/// reported.
final GatheringDiagnosticListener diagnosticListener;
late final ForwardingTestListener _eventListener;
final int? expectedEndOffset;
/// Creates a [ParserProxy] which is prepared to begin parsing at the given
/// Fasta token.
factory ParserProxy(
Token firstToken,
FeatureSet featureSet,
LibraryLanguageVersion languageVersion, {
bool allowNativeClause = false,
int? expectedEndOffset,
required LineInfo lineInfo,
}) {
TestSource source = TestSource();
var diagnosticListener = GatheringDiagnosticListener();
return ParserProxy._(
firstToken,
source,
diagnosticListener,
featureSet,
languageVersion,
allowNativeClause: allowNativeClause,
expectedEndOffset: expectedEndOffset,
lineInfo: lineInfo,
);
}
ParserProxy._(
Token firstToken,
Source source,
this.diagnosticListener,
FeatureSet featureSet,
LibraryLanguageVersion languageVersion, {
bool allowNativeClause = false,
this.expectedEndOffset,
required LineInfo lineInfo,
}) : super(
source,
diagnosticListener,
featureSet: featureSet,
languageVersion: languageVersion,
allowNativeClause: allowNativeClause,
lineInfo: lineInfo,
) {
_eventListener = ForwardingTestListener(astBuilder);
fastaParser.listener = _eventListener;
currentToken = firstToken;
}
@override
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
Annotation parseAnnotation() {
return _run('MetadataStar', () {
currentToken =
fastaParser
.parseMetadata(fastaParser.syntheticPreviousToken(currentToken))
.next!;
return astBuilder.pop() as Annotation;
});
}
ArgumentList parseArgumentList() {
return _run('unspecified', () {
currentToken =
fastaParser
.parseArguments(fastaParser.syntheticPreviousToken(currentToken))
.next!;
var result = astBuilder.pop();
return result is MethodInvocation
? result.argumentList
: result as ArgumentList;
});
}
ClassMember parseClassMember(String className) {
return parseClassMemberOrNull(className)!;
}
ClassMember? parseClassMemberOrNull(String className) {
return _run('ClassOrMixinBody', () {
var builder = astBuilder.createFakeClassDeclarationBuilder(className);
// TODO(danrubel): disambiguate between class and mixin
currentToken = fastaParser.parseClassMember(currentToken, className);
//currentToken = fastaParser.parseMixinMember(currentToken);
return builder.members.firstOrNull;
});
}
List<Combinator> parseCombinators() {
return _run('Import', () {
currentToken =
fastaParser
.parseCombinatorStar(
fastaParser.syntheticPreviousToken(currentToken),
)
.next!;
return astBuilder.pop() as List<Combinator>;
});
}
Comment parseComment(List<DocumentationCommentToken> tokens) {
for (int index = 0; index < tokens.length - 1; ++index) {
var next = tokens[index].next;
if (next == null) {
tokens[index].setNext(tokens[index + 1]);
} else {
expect(next, tokens[index + 1]);
}
}
expect(tokens.last.next, isNull);
var comment = astBuilder.parseDocComment(tokens.first);
if (astBuilder.stack.isNotEmpty) {
throw 'Expected empty stack, but found:'
'\n ${astBuilder.stack.values.join('\n ')}';
}
return comment;
}
@override
CompilationUnitImpl parseCompilationUnit2() {
var result = super.parseCompilationUnit2();
expect(currentToken.isEof, isTrue, reason: currentToken.lexeme);
expect(astBuilder.stack, hasLength(0));
_eventListener.expectEmpty();
return result;
}
@override
Configuration parseConfiguration() {
return _run('ConditionalUris', () => super.parseConfiguration());
}
@override
DottedName parseDottedName() {
return _run('unspecified', () => super.parseDottedName());
}
@override
Expression parseExpression2() {
return _run('unspecified', () => super.parseExpression2());
}
@override
FormalParameterList parseFormalParameterList({bool inFunctionType = false}) {
return _run(
'unspecified',
() => super.parseFormalParameterList(inFunctionType: inFunctionType),
);
}
@override
FunctionBody parseFunctionBody(
bool mayBeEmpty,
ParserErrorCode emptyErrorCode,
bool inExpression,
) {
Token? lastToken;
FunctionBody body = _run('unspecified', () {
FunctionBody body = super.parseFunctionBody(
mayBeEmpty,
emptyErrorCode,
inExpression,
);
lastToken = currentToken;
currentToken = currentToken.next!;
return body;
});
if (!inExpression) {
if (![';', '}'].contains(lastToken!.lexeme)) {
fail('Expected ";" or "}", but found: ${lastToken!.lexeme}');
}
}
return body;
}
@override
Expression parsePrimaryExpression() {
return _run('unspecified', () => super.parsePrimaryExpression());
}
@override
Statement parseStatement(Token token) {
return _run('unspecified', () => super.parseStatement(token));
}
@override
Statement parseStatement2() {
return _run('unspecified', () => super.parseStatement2());
}
@override
AnnotatedNode parseTopLevelDeclaration(bool isDirective) {
return _run(
'CompilationUnit',
() => super.parseTopLevelDeclaration(isDirective),
);
}
@override
TypeAnnotation parseTypeAnnotation(bool inExpression) {
return _run('unspecified', () => super.parseTypeAnnotation(inExpression));
}
@override
TypeArgumentList parseTypeArgumentList() {
return _run('unspecified', () => super.parseTypeArgumentList());
}
@override
NamedType parseTypeName(bool inExpression) {
return _run('unspecified', () => super.parseTypeName(inExpression));
}
@override
TypeParameter parseTypeParameter() {
return _run('unspecified', () => super.parseTypeParameter());
}
@override
TypeParameterList? parseTypeParameterList() {
return _run('unspecified', () => super.parseTypeParameterList());
}
/// Runs the specified function and returns the result. It checks the
/// enclosing listener events, that the parse consumed all of the tokens, and
/// that the result stack is empty.
T _run<T>(String enclosingEvent, T Function() f) {
_eventListener.begin(enclosingEvent);
// Simulate error handling of parseUnit by skipping error tokens
// before parsing and reporting them after parsing is complete.
Token errorToken = currentToken;
currentToken = fastaParser.skipErrorTokens(currentToken);
var result = f();
fastaParser.reportAllErrorTokens(errorToken);
_eventListener.end(enclosingEvent);
String lexeme =
currentToken is ErrorToken
? currentToken.runtimeType.toString()
: currentToken.lexeme;
if (expectedEndOffset == null) {
expect(currentToken.isEof, isTrue, reason: lexeme);
} else {
expect(currentToken.offset, expectedEndOffset, reason: lexeme);
}
expect(astBuilder.stack, hasLength(0));
expect(astBuilder.directives, hasLength(0));
expect(astBuilder.declarations, hasLength(0));
return result;
}
}
/// Implementation of [AbstractParserTestCase] specialized for testing the
/// analyzer parser.
class ParserTestCase with ParserTestHelpers implements AbstractParserTestCase {
/// A flag indicating whether parser is to parse function bodies.
static bool parseFunctionBodies = true;
@override
bool allowNativeClause = true;
/// A flag indicating whether parser is to parse async.
bool parseAsync = true;
@override
late final GatheringDiagnosticListener listener;
/// The parser used by the test.
///
/// This field is typically initialized by invoking [createParser].
@override
late final analyzer.Parser parser;
@override
void assertErrorsWithCodes(List<DiagnosticCode> expectedCodes) {
listener.assertErrorsWithCodes(expectedCodes);
}
@override
void assertNoErrors() {
listener.assertNoErrors();
}
/// Create the [parser] and [listener] used by a test. The [parser] will be
/// prepared to parse the tokens scanned from the given [content].
@override
void createParser(
String content, {
int? expectedEndOffset,
LanguageVersionToken? languageVersion,
FeatureSet? featureSet,
}) {
featureSet ??= FeatureSet.latestLanguageVersion();
var languageVersion = LibraryLanguageVersion(
package: ExperimentStatus.currentVersion,
override: null,
);
Source source = TestSource();
listener = GatheringDiagnosticListener();
fasta.ScannerResult result = fasta.scanString(
content,
includeComments: true,
languageVersionChanged: (scanner, overrideVersion) {
languageVersion = LibraryLanguageVersion(
package: ExperimentStatus.currentVersion,
override: Version(overrideVersion.major, overrideVersion.minor, 0),
);
},
);
LineInfo lineInfo = LineInfo(result.lineStarts);
listener.setLineInfo(source, lineInfo);
parser = analyzer.Parser(
source,
listener,
featureSet: featureSet,
languageVersion: languageVersion,
lineInfo: lineInfo,
);
parser.allowNativeClause = allowNativeClause;
parser.parseFunctionBodies = parseFunctionBodies;
parser.currentToken = result.tokens;
}
@override
ExpectedError expectedError(DiagnosticCode code, int offset, int length) =>
ExpectedError(code, offset, length);
@override
void expectNotNullIfNoErrors(Object result) {
if (!listener.hasDiagnostics) {
expect(result, isNotNull);
}
}
@override
Expression parseAdditiveExpression(String code) {
createParser(code);
return parser.parseAdditiveExpression();
}
@override
Expression parseAssignableExpression(String code, bool primaryAllowed) {
createParser(code);
return parser.parseAssignableExpression(primaryAllowed);
}
@override
Expression parseAssignableSelector(
String code,
bool optional, {
bool allowConditional = true,
}) {
if (optional) {
if (code.isEmpty) {
createParser('foo');
} else {
createParser('(foo)$code');
}
} else {
createParser('foo$code');
}
return parser.parseExpression2();
}
@override
AwaitExpression parseAwaitExpression(String code) {
createParser('() async => $code');
var function = parser.parseExpression2() as FunctionExpression;
return (function.body as ExpressionFunctionBody).expression
as AwaitExpression;
}
@override
Expression parseBitwiseAndExpression(String code) {
createParser(code);
return parser.parseBitwiseAndExpression();
}
@override
Expression parseBitwiseOrExpression(String code) {
createParser(code);
return parser.parseBitwiseOrExpression();
}
@override
Expression parseBitwiseXorExpression(String code) {
createParser(code);
return parser.parseBitwiseXorExpression();
}
@override
Expression parseCascadeSection(String code) {
var statement = parseStatement('null$code;') as ExpressionStatement;
var cascadeExpression = statement.expression as CascadeExpression;
return cascadeExpression.cascadeSections.first;
}
@override
CommentReference? parseCommentReference(
String referenceSource,
int sourceOffset,
) {
String padding = ' '.padLeft(sourceOffset - 4, 'a');
String source = '/**$padding[$referenceSource] */ class C { }';
CompilationUnit unit = parseCompilationUnit(source);
var clazz = unit.declarations[0] as ClassDeclaration;
var comment = clazz.documentationComment!;
List<CommentReference> references = comment.references;
if (references.isEmpty) {
return null;
} else {
expect(references, hasLength(1));
return references[0];
}
}
/// Parse the given source as a compilation unit.
///
/// @param source the source to be parsed
/// @param errorCodes the error codes of the errors that are expected to be
/// found
/// @return the compilation unit that was parsed
/// @throws Exception if the source could not be parsed, if the compilation
/// errors in the source do not match those that are expected, or if
/// the result would have been `null`
@override
CompilationUnit parseCompilationUnit(
String content, {
List<DiagnosticCode>? codes,
List<ExpectedError>? errors,
}) {
Source source = TestSource();
GatheringDiagnosticListener listener = GatheringDiagnosticListener();
var languageVersion = LibraryLanguageVersion(
package: ExperimentStatus.currentVersion,
override: null,
);
fasta.ScannerResult result = fasta.scanString(
content,
includeComments: true,
languageVersionChanged: (scanner, overrideVersion) {
languageVersion = LibraryLanguageVersion(
package: ExperimentStatus.currentVersion,
override: Version(overrideVersion.major, overrideVersion.minor, 0),
);
},
);
LineInfo lineInfo = LineInfo(result.lineStarts);
listener.setLineInfo(source, lineInfo);
analyzer.Parser parser = analyzer.Parser(
source,
listener,
featureSet: FeatureSets.latestWithExperiments,
languageVersion: languageVersion,
lineInfo: lineInfo,
);
CompilationUnit unit = parser.parseCompilationUnit(result.tokens);
expect(unit, isNotNull);
if (codes != null) {
listener.assertErrorsWithCodes(codes);
} else if (errors != null) {
listener.assertErrors(errors);
} else {
listener.assertNoErrors();
}
return unit;
}
/// Parse the given [content] as a compilation unit.
CompilationUnit parseCompilationUnit2(
String content, {
DiagnosticOrErrorListener listener = DiagnosticListener.nullListener,
}) {
Source source = NonExistingSource.unknown;
var languageVersion = LibraryLanguageVersion(
package: ExperimentStatus.currentVersion,
override: null,
);
fasta.ScannerResult result = fasta.scanString(
content,
includeComments: true,
languageVersionChanged: (scanner, overrideVersion) {
languageVersion = LibraryLanguageVersion(
package: ExperimentStatus.currentVersion,
override: Version(overrideVersion.major, overrideVersion.minor, 0),
);
},
);
LineInfo lineInfo = LineInfo(result.lineStarts);
analyzer.Parser parser = analyzer.Parser(
source,
listener,
featureSet: FeatureSet.latestLanguageVersion(),
languageVersion: languageVersion,
lineInfo: lineInfo,
);
var unit = parser.parseCompilationUnit(result.tokens);
return unit;
}
@override
ConditionalExpression parseConditionalExpression(String code) {
createParser(code);
return parser.parseConditionalExpression() as ConditionalExpression;
}
@override
Expression parseConstExpression(String code) {
createParser(code);
return parser.parseConstExpression();
}
@override
ConstructorInitializer parseConstructorInitializer(String code) {
createParser('class __Test { __Test() : $code; }');
CompilationUnit unit = parser.parseCompilationUnit2();
var clazz = unit.declarations[0] as ClassDeclaration;
var constructor = clazz.members[0] as ConstructorDeclaration;
return constructor.initializers.single;
}
@override
CompilationUnit parseDirectives(
String source, [
List<DiagnosticCode> diagnosticCodes = const <DiagnosticCode>[],
]) {
createParser(source);
CompilationUnit unit = parser.parseDirectives2();
expect(unit, isNotNull);
expect(unit.declarations, hasLength(0));
listener.assertErrorsWithCodes(diagnosticCodes);
return unit;
}
@override
BinaryExpression parseEqualityExpression(String code) {
createParser(code);
return parser.parseEqualityExpression() as BinaryExpression;
}
/// Parse the given [source] as an expression. If a list of error [codes] is
/// provided, then assert that the produced errors matches the list.
/// Otherwise, if a list of [errors] is provided, the assert that the produced
/// errors matches the list. Otherwise, assert that there are no errors.
@override
Expression parseExpression(
String source, {
List<DiagnosticCode>? codes,
List<ExpectedError>? errors,
int? expectedEndOffset,
}) {
createParser(source, expectedEndOffset: expectedEndOffset);
Expression expression = parser.parseExpression2();
expectNotNullIfNoErrors(expression);
if (codes != null) {
listener.assertErrorsWithCodes(codes);
} else if (errors != null) {
listener.assertErrors(errors);
} else {
assertNoErrors();
}
return expression;
}
@override
List<Expression> parseExpressionList(String code) {
createParser('[$code]');
return (parser.parseExpression2() as ListLiteral).elements
.toList()
.cast<Expression>();
}
@override
Expression parseExpressionWithoutCascade(String code) {
createParser(code);
return parser.parseExpressionWithoutCascade();
}
@override
FormalParameter parseFormalParameter(
String code,
ParameterKind kind, {
List<DiagnosticCode> diagnosticCodes = const <DiagnosticCode>[],
}) {
String parametersCode;
if (kind == ParameterKind.REQUIRED) {
parametersCode = '($code)';
} else if (kind == ParameterKind.POSITIONAL) {
parametersCode = '([$code])';
} else if (kind == ParameterKind.NAMED) {
parametersCode = '({$code})';
} else {
fail('$kind');
}
FormalParameterList list = parseFormalParameterList(
parametersCode,
diagnosticCodes: diagnosticCodes,
);
return list.parameters.single;
}
@override
FormalParameterList parseFormalParameterList(
String code, {
bool inFunctionType = false,
List<DiagnosticCode> diagnosticCodes = const <DiagnosticCode>[],
List<ExpectedError>? errors,
}) {
createParser(code);
FormalParameterList list = parser.parseFormalParameterList(
inFunctionType: inFunctionType,
);
if (errors != null) {
diagnosticCodes = errors.map((e) => e.code).toList();
}
assertErrorsWithCodes(diagnosticCodes);
return list;
}
/// Parses a single top level member of a compilation unit (other than a
/// directive), including any comment and/or metadata that precedes it.
@override
CompilationUnitMember parseFullCompilationUnitMember() =>
parser.parseCompilationUnit2().declarations.first;
@override
Directive parseFullDirective() {
return parser.parseTopLevelDeclaration(true) as Directive;
}
@override
FunctionExpression parseFunctionExpression(String code) {
createParser(code);
return parser.parseFunctionExpression();
}
@override
InstanceCreationExpression parseInstanceCreationExpression(
String code,
Token newToken,
) {
createParser('$newToken $code');
return parser.parseExpression2() as InstanceCreationExpression;
}
@override
ListLiteral parseListLiteral(
Token? token,
String? typeArgumentsCode,
String code,
) {
String sc = '';
if (token != null) {
sc += '${token.lexeme} ';
}
if (typeArgumentsCode != null) {
sc += typeArgumentsCode;
}
sc += code;
createParser(sc);
return parser.parseExpression2() as ListLiteral;
}
@override
TypedLiteral parseListOrMapLiteral(Token? modifier, String code) {
String literalCode = modifier != null ? '$modifier $code' : code;
createParser(literalCode);
return parser.parseExpression2() as TypedLiteral;
}
@override
Expression parseLogicalAndExpression(String code) {
createParser(code);
return parser.parseLogicalAndExpression();
}
@override
Expression parseLogicalOrExpression(String code) {
createParser(code);
return parser.parseLogicalOrExpression();
}
@override
SetOrMapLiteral parseMapLiteral(
Token? token,
String? typeArgumentsCode,
String code,
) {
String sc = '';
if (token != null) {
sc += '${token.lexeme} ';
}
if (typeArgumentsCode != null) {
sc += typeArgumentsCode;
}
sc += code;
createParser(sc);
return parser.parseExpression2() as SetOrMapLiteral;
}
@override
MapLiteralEntry parseMapLiteralEntry(String code) {
var mapLiteral = parseMapLiteral(null, null, '{ $code }');
return mapLiteral.elements.single as MapLiteralEntry;
}
@override
Expression parseMultiplicativeExpression(String code) {
createParser(code);
return parser.parseMultiplicativeExpression();
}
@override
InstanceCreationExpression parseNewExpression(String code) {
createParser(code);
return parser.parseNewExpression();
}
@override
NormalFormalParameter parseNormalFormalParameter(
String code, {
bool inFunctionType = false,
List<DiagnosticCode> diagnosticCodes = const <DiagnosticCode>[],
}) {
FormalParameterList list = parseFormalParameterList(
'($code)',
inFunctionType: inFunctionType,
diagnosticCodes: diagnosticCodes,
);
return list.parameters.single as NormalFormalParameter;
}
@override
Expression parsePostfixExpression(String code) {
createParser(code);
return parser.parsePostfixExpression();
}
@override
Identifier parsePrefixedIdentifier(String code) {
createParser(code);
return parser.parsePrefixedIdentifier();
}
@override
Expression parsePrimaryExpression(
String code, {
int? expectedEndOffset,
List<ExpectedError>? errors,
}) {
createParser(code);
var expression = parser.parsePrimaryExpression();
if (errors != null) {
listener.assertErrors(errors);
}
return expression;
}
@override
Expression parseRelationalExpression(String code) {
createParser(code);
return parser.parseRelationalExpression();
}
@override
RethrowExpression parseRethrowExpression(String code) {
createParser(code);
return parser.parseRethrowExpression() as RethrowExpression;
}
@override
BinaryExpression parseShiftExpression(String code) {
createParser(code);
return parser.parseShiftExpression() as BinaryExpression;
}
@override
SimpleIdentifier parseSimpleIdentifier(String code) {
createParser(code);
return parser.parseSimpleIdentifier();
}
/// Parses the given [content] as a statement.
@override
Statement parseStatement(String content, {int? expectedEndOffset}) {
Source source = TestSource();
listener = GatheringDiagnosticListener();
var languageVersion = LibraryLanguageVersion(
package: ExperimentStatus.currentVersion,
override: null,
);
fasta.ScannerResult result = fasta.scanString(
content,
includeComments: true,
languageVersionChanged: (scanner, overrideVersion) {
languageVersion = LibraryLanguageVersion(
package: ExperimentStatus.currentVersion,
override: Version(overrideVersion.major, overrideVersion.minor, 0),
);
},
);
LineInfo lineInfo = LineInfo(result.lineStarts);
listener.setLineInfo(source, lineInfo);
analyzer.Parser parser = analyzer.Parser(
source,
listener,
featureSet: FeatureSet.latestLanguageVersion(),
languageVersion: languageVersion,
lineInfo: lineInfo,
);
Statement statement = parser.parseStatement(result.tokens);
expect(statement, isNotNull);
return statement;
}
@override
Expression parseStringLiteral(String code) {
createParser(code);
return parser.parseStringLiteral();
}
@override
SymbolLiteral parseSymbolLiteral(String code) {
createParser(code);
return parser.parseSymbolLiteral();
}
@override
Expression parseThrowExpression(String code) {
createParser(code);
return parser.parseThrowExpression();
}
@override
Expression parseThrowExpressionWithoutCascade(String code) {
createParser(code);
return parser.parseThrowExpressionWithoutCascade();
}
@override
PrefixExpression parseUnaryExpression(String code) {
createParser(code);
return parser.parseUnaryExpression() as PrefixExpression;
}
@override
VariableDeclarationList parseVariableDeclarationList(String code) {
var statement = parseStatement('$code;') as VariableDeclarationStatement;
return statement.variables;
}
void setUp() {
parseFunctionBodies = true;
}
}
/// Helper methods that aid in parser tests.
///
/// Intended to be mixed into parser test case classes.
mixin ParserTestHelpers {
ExpectedError error(
DiagnosticCode code,
int offset,
int length, {
Pattern? correctionContains,
String? text,
List<Pattern> messageContains = const [],
List<ExpectedContextMessage> contextMessages =
const <ExpectedContextMessage>[],
}) => ExpectedError(
code,
offset,
length,
correctionContains: correctionContains,
message: text,
messageContains: messageContains,
expectedContextMessages: contextMessages,
);
void expectCommentText(Comment? comment, String expectedText) {
comment!;
expect(comment.beginToken, same(comment.endToken));
expect(comment.beginToken.lexeme, expectedText);
}
void expectDottedName(DottedName name, List<String> expectedComponents) {
int count = expectedComponents.length;
NodeList<SimpleIdentifier> components = name.components;
expect(components, hasLength(count));
for (int i = 0; i < count; i++) {
SimpleIdentifier component = components[i];
expect(component, isNotNull);
expect(component.name, expectedComponents[i]);
}
}
}