blob: 097764a1d8399953a8f189dd3378b045bf4cd0d2 [file] [log] [blame]
// Copyright (c) 2016, 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:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/ast_factory.dart' show AstFactory;
import 'package:analyzer/dart/ast/standard_ast_factory.dart' as standard;
import 'package:analyzer/dart/ast/token.dart' show Token, TokenType;
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/fasta/error_converter.dart';
import 'package:analyzer/src/generated/java_core.dart';
import 'package:analyzer/src/generated/java_engine.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:front_end/src/fasta/parser.dart'
show
Assert,
FormalParameterKind,
IdentifierContext,
MemberKind,
optional,
Parser;
import 'package:front_end/src/fasta/scanner.dart' hide StringToken;
import 'package:front_end/src/scanner/errors.dart' show translateErrorToken;
import 'package:front_end/src/scanner/token.dart'
show
BeginToken,
StringToken,
SyntheticBeginToken,
SyntheticStringToken,
SyntheticToken;
import 'package:front_end/src/fasta/problems.dart' show unhandled;
import 'package:front_end/src/fasta/messages.dart'
show
Message,
messageConstConstructorWithBody,
messageConstMethod,
messageConstructorWithReturnType,
messageDirectiveAfterDeclaration,
messageExpectedStatement,
messageFieldInitializerOutsideConstructor,
messageIllegalAssignmentToNonAssignable,
messageInterpolationInUri,
messageMissingAssignableSelector,
messageNativeClauseShouldBeAnnotation,
messageStaticConstructor,
messageTypedefNotFunction,
templateDuplicateLabelInSwitchStatement,
templateExpectedType;
import 'package:front_end/src/fasta/quote.dart';
import 'package:front_end/src/fasta/scanner/token_constants.dart';
import 'package:front_end/src/fasta/source/stack_listener.dart'
show NullValue, StackListener;
import 'package:kernel/ast.dart' show AsyncMarker;
/// A parser listener that builds the analyzer's AST structure.
class AstBuilder extends StackListener {
final AstFactory ast = standard.astFactory;
final FastaErrorReporter errorReporter;
final Uri fileUri;
ScriptTag scriptTag;
final List<Directive> directives = <Directive>[];
final List<CompilationUnitMember> declarations = <CompilationUnitMember>[];
final localDeclarations = <int, AstNode>{};
@override
final Uri uri;
/// The parser that uses this listener, used to parse optional parts, e.g.
/// `native` support.
Parser parser;
bool parseGenericMethodComments = false;
/// The class currently being parsed, or `null` if no class is being parsed.
ClassDeclaration classDeclaration;
/// If true, this is building a full AST. Otherwise, only create method
/// bodies.
final bool isFullAst;
/// `true` if the `native` clause is allowed
/// in class, method, and function declarations.
///
/// This is being replaced by the @native(...) annotation.
//
// TODO(danrubel) Move this flag to a better location
// and should only be true if either:
// * The current library is a platform library
// * The current library has an import that uses the scheme "dart-ext".
bool allowNativeClause = false;
StringLiteral nativeName;
bool parseFunctionBodies = true;
AstBuilder(ErrorReporter errorReporter, this.fileUri, this.isFullAst,
[Uri uri])
: this.errorReporter = new FastaErrorReporter(errorReporter),
uri = uri ?? fileUri;
void beginLiteralString(Token literalString) {
assert(identical(literalString.kind, STRING_TOKEN));
debugEvent("beginLiteralString");
push(literalString);
}
void handleNamedArgument(Token colon) {
assert(optional(':', colon));
debugEvent("NamedArgument");
Expression expression = pop();
SimpleIdentifier name = pop();
push(ast.namedExpression(ast.label(name, colon), expression));
}
@override
void handleNoConstructorReferenceContinuationAfterTypeArguments(Token token) {
debugEvent("NoConstructorReferenceContinuationAfterTypeArguments");
push(NullValue.ConstructorReferenceContinuationAfterTypeArguments);
}
@override
void endConstructorReference(
Token start, Token periodBeforeName, Token endToken) {
assert(optionalOrNull('.', periodBeforeName));
debugEvent("ConstructorReference");
SimpleIdentifier constructorName = pop();
TypeArgumentList typeArguments = pop();
Identifier typeNameIdentifier = pop();
push(ast.constructorName(ast.typeName(typeNameIdentifier, typeArguments),
periodBeforeName, constructorName));
}
@override
void endConstExpression(Token constKeyword) {
assert(optional('const', constKeyword));
debugEvent("ConstExpression");
_handleInstanceCreation(constKeyword);
}
@override
void endConstLiteral(Token token) {
debugEvent("endConstLiteral");
}
void _handleInstanceCreation(Token token) {
MethodInvocation arguments = pop();
ConstructorName constructorName = pop();
push(ast.instanceCreationExpression(
token, constructorName, arguments.argumentList));
}
@override
void endImplicitCreationExpression(Token token) {
debugEvent("ImplicitCreationExpression");
_handleInstanceCreation(null);
}
@override
void endNewExpression(Token newKeyword) {
assert(optional('new', newKeyword));
debugEvent("NewExpression");
_handleInstanceCreation(newKeyword);
}
@override
void handleParenthesizedCondition(Token leftParenthesis) {
// TODO(danrubel): Implement rather than forwarding.
handleParenthesizedExpression(leftParenthesis);
}
@override
void handleParenthesizedExpression(Token leftParenthesis) {
assert(optional('(', leftParenthesis));
debugEvent("ParenthesizedExpression");
Expression expression = pop();
push(ast.parenthesizedExpression(
leftParenthesis, expression, leftParenthesis?.endGroup));
}
@override
void handleStringPart(Token literalString) {
assert(identical(literalString.kind, STRING_TOKEN));
debugEvent("StringPart");
push(literalString);
}
@override
void handleInterpolationExpression(Token leftBracket, Token rightBracket) {
Expression expression = pop();
push(ast.interpolationExpression(leftBracket, expression, rightBracket));
}
@override
void endLiteralString(int interpolationCount, Token endToken) {
debugEvent("endLiteralString");
if (interpolationCount == 0) {
Token token = pop();
String value = unescapeString(token.lexeme, token, this);
push(ast.simpleStringLiteral(token, value));
} else {
List<Object> parts = popTypedList(1 + interpolationCount * 2);
Token first = parts.first;
Token last = parts.last;
Quote quote = analyzeQuote(first.lexeme);
List<InterpolationElement> elements = <InterpolationElement>[];
elements.add(ast.interpolationString(
first, unescapeFirstStringPart(first.lexeme, quote, first, this)));
for (int i = 1; i < parts.length - 1; i++) {
var part = parts[i];
if (part is Token) {
elements.add(ast.interpolationString(part, part.lexeme));
} else if (part is InterpolationExpression) {
elements.add(part);
} else {
unhandled("${part.runtimeType}", "string interpolation",
first.charOffset, uri);
}
}
elements.add(ast.interpolationString(
last, unescapeLastStringPart(last.lexeme, quote, last, this)));
push(ast.stringInterpolation(elements));
}
}
@override
void handleNativeClause(Token nativeToken, bool hasName) {
debugEvent("NativeClause");
if (hasName) {
nativeName = pop(); // StringLiteral
} else {
nativeName = null;
}
}
void handleScript(Token token) {
assert(identical(token.type, TokenType.SCRIPT_TAG));
debugEvent("Script");
scriptTag = ast.scriptTag(token);
}
void handleStringJuxtaposition(int literalCount) {
debugEvent("StringJuxtaposition");
push(ast.adjacentStrings(popTypedList(literalCount)));
}
void endArguments(int count, Token leftParenthesis, Token rightParenthesis) {
assert(optional('(', leftParenthesis));
assert(optional(')', rightParenthesis));
debugEvent("Arguments");
List<Expression> expressions = popTypedList(count);
ArgumentList arguments =
ast.argumentList(leftParenthesis, expressions, rightParenthesis);
push(ast.methodInvocation(null, null, null, null, arguments));
}
void handleIdentifier(Token token, IdentifierContext context) {
assert(token.isKeywordOrIdentifier);
debugEvent("handleIdentifier");
if (context.inSymbol) {
push(token);
return;
}
SimpleIdentifier identifier =
ast.simpleIdentifier(token, isDeclaration: context.inDeclaration);
if (context.inLibraryOrPartOfDeclaration) {
if (!context.isContinuation) {
push([identifier]);
} else {
push(identifier);
}
} else if (context == IdentifierContext.enumValueDeclaration) {
List<Annotation> metadata = pop();
Comment comment = _parseDocumentationCommentOpt(token.precedingComments);
push(ast.enumConstantDeclaration(comment, metadata, identifier));
} else {
push(identifier);
}
}
void handleSend(Token beginToken, Token endToken) {
debugEvent("Send");
MethodInvocation arguments = pop();
TypeArgumentList typeArguments = pop();
if (arguments != null) {
doInvocation(typeArguments, arguments);
} else {
doPropertyGet();
}
}
void doInvocation(
TypeArgumentList typeArguments, MethodInvocation arguments) {
Expression receiver = pop();
if (receiver is SimpleIdentifier) {
arguments.methodName = receiver;
if (typeArguments != null) {
arguments.typeArguments = typeArguments;
}
push(arguments);
} else {
push(ast.functionExpressionInvocation(
receiver, typeArguments, arguments.argumentList));
}
}
void doPropertyGet() {}
void endExpressionStatement(Token semicolon) {
assert(optional(';', semicolon));
debugEvent("ExpressionStatement");
Expression expression = pop();
if (expression is SuperExpression) {
// This error is also reported by the body builder.
handleRecoverableError(messageMissingAssignableSelector,
expression.beginToken, expression.endToken);
}
if (expression is SimpleIdentifier &&
expression.token?.keyword?.isBuiltInOrPseudo == false) {
// This error is also reported by the body builder.
handleRecoverableError(
messageExpectedStatement, expression.beginToken, expression.endToken);
}
if (expression is AssignmentExpression) {
if (!expression.leftHandSide.isAssignable) {
// This error is also reported by the body builder.
handleRecoverableError(
messageIllegalAssignmentToNonAssignable,
expression.leftHandSide.beginToken,
expression.leftHandSide.endToken);
}
}
push(ast.expressionStatement(expression, semicolon));
}
@override
void handleNativeFunctionBody(Token nativeToken, Token semicolon) {
assert(optional('native', nativeToken));
assert(optional(';', semicolon));
debugEvent("NativeFunctionBody");
// TODO(danrubel) Change the parser to not produce these modifiers.
pop(); // star
pop(); // async
push(ast.nativeFunctionBody(nativeToken, nativeName, semicolon));
}
@override
void handleEmptyFunctionBody(Token semicolon) {
assert(optional(';', semicolon));
debugEvent("EmptyFunctionBody");
// TODO(scheglov) Change the parser to not produce these modifiers.
pop(); // star
pop(); // async
push(ast.emptyFunctionBody(semicolon));
}
@override
void handleEmptyStatement(Token semicolon) {
assert(optional(';', semicolon));
debugEvent("EmptyStatement");
push(ast.emptyStatement(semicolon));
}
void endBlockFunctionBody(int count, Token leftBracket, Token rightBracket) {
assert(optional('{', leftBracket));
assert(optional('}', rightBracket));
debugEvent("BlockFunctionBody");
List<Statement> statements = popTypedList(count);
Block block = ast.block(leftBracket, statements, rightBracket);
Token star = pop();
Token asyncKeyword = pop();
if (parseFunctionBodies) {
push(ast.blockFunctionBody(asyncKeyword, star, block));
} else {
// TODO(danrubel): Skip the block rather than parsing it.
push(ast.emptyFunctionBody(
new SyntheticToken(TokenType.SEMICOLON, leftBracket.charOffset)));
}
}
void finishFunction(
List annotations, formals, AsyncMarker asyncModifier, FunctionBody body) {
debugEvent("finishFunction");
Statement bodyStatement;
if (body is EmptyFunctionBody) {
bodyStatement = ast.emptyStatement(body.semicolon);
} else if (body is NativeFunctionBody) {
// TODO(danrubel): what do we need to do with NativeFunctionBody?
} else if (body is ExpressionFunctionBody) {
bodyStatement = ast.returnStatement(null, body.expression, null);
} else {
bodyStatement = (body as BlockFunctionBody).block;
}
// TODO(paulberry): what do we need to do with bodyStatement at this point?
bodyStatement; // Suppress "unused local variable" hint
}
void beginCascade(Token token) {
assert(optional('..', token));
debugEvent("beginCascade");
Expression expression = pop();
push(token);
if (expression is CascadeExpression) {
push(expression);
} else {
push(ast.cascadeExpression(expression, <Expression>[]));
}
push(NullValue.CascadeReceiver);
}
void endCascade() {
debugEvent("Cascade");
Expression expression = pop();
CascadeExpression receiver = pop();
pop(); // Token.
receiver.cascadeSections.add(expression);
push(receiver);
}
void handleOperator(Token operatorToken) {
assert(operatorToken.isUserDefinableOperator);
debugEvent("Operator");
push(operatorToken);
}
void handleSymbolVoid(Token voidKeyword) {
assert(optional('void', voidKeyword));
debugEvent("SymbolVoid");
push(voidKeyword);
}
@override
void endBinaryExpression(Token operatorToken) {
assert(operatorToken.isOperator ||
optional('.', operatorToken) ||
optional('?.', operatorToken) ||
optional('..', operatorToken));
debugEvent("BinaryExpression");
if (identical(".", operatorToken.stringValue) ||
identical("?.", operatorToken.stringValue) ||
identical("..", operatorToken.stringValue)) {
doDotExpression(operatorToken);
} else {
Expression right = pop();
Expression left = pop();
push(ast.binaryExpression(left, operatorToken, right));
}
}
void doDotExpression(Token dot) {
Expression identifierOrInvoke = pop();
Expression receiver = pop();
if (identifierOrInvoke is SimpleIdentifier) {
if (receiver is SimpleIdentifier && identical('.', dot.stringValue)) {
push(ast.prefixedIdentifier(receiver, dot, identifierOrInvoke));
} else {
push(ast.propertyAccess(receiver, dot, identifierOrInvoke));
}
} else if (identifierOrInvoke is MethodInvocation) {
assert(identifierOrInvoke.target == null);
identifierOrInvoke
..target = receiver
..operator = dot;
push(identifierOrInvoke);
} else {
unhandled("${identifierOrInvoke.runtimeType}", "property access",
dot.charOffset, uri);
}
}
void handleLiteralInt(Token token) {
assert(identical(token.kind, INT_TOKEN) ||
identical(token.kind, HEXADECIMAL_TOKEN));
debugEvent("LiteralInt");
push(ast.integerLiteral(token, int.tryParse(token.lexeme)));
}
void handleExpressionFunctionBody(Token arrowToken, Token semicolon) {
assert(optional('=>', arrowToken) || optional('=', arrowToken));
assert(optionalOrNull(';', semicolon));
debugEvent("ExpressionFunctionBody");
Expression expression = pop();
pop(); // star (*)
Token asyncKeyword = pop();
if (parseFunctionBodies) {
push(ast.expressionFunctionBody(
asyncKeyword, arrowToken, expression, semicolon));
} else {
push(ast.emptyFunctionBody(semicolon));
}
}
void endReturnStatement(
bool hasExpression, Token returnKeyword, Token semicolon) {
assert(optional('return', returnKeyword));
assert(optional(';', semicolon));
debugEvent("ReturnStatement");
Expression expression = hasExpression ? pop() : null;
push(ast.returnStatement(returnKeyword, expression, semicolon));
}
void endIfStatement(Token ifToken, Token elseToken) {
assert(optional('if', ifToken));
assert(optionalOrNull('else', elseToken));
Statement elsePart = popIfNotNull(elseToken);
Statement thenPart = pop();
ParenthesizedExpression condition = pop();
push(ast.ifStatement(
ifToken,
condition.leftParenthesis,
condition.expression,
condition.rightParenthesis,
thenPart,
elseToken,
elsePart));
}
void handleNoInitializers() {
debugEvent("NoInitializers");
if (!isFullAst) return;
push(NullValue.ConstructorInitializerSeparator);
push(NullValue.ConstructorInitializers);
}
void endInitializers(int count, Token colon, Token endToken) {
assert(optional(':', colon));
debugEvent("Initializers");
List<Object> initializerObjects = popTypedList(count) ?? const [];
if (!isFullAst) return;
push(colon);
var initializers = <ConstructorInitializer>[];
for (Object initializerObject in initializerObjects) {
if (initializerObject is FunctionExpressionInvocation) {
Expression function = initializerObject.function;
if (function is SuperExpression) {
initializers.add(ast.superConstructorInvocation(function.superKeyword,
null, null, initializerObject.argumentList));
} else {
initializers.add(ast.redirectingConstructorInvocation(
(function as ThisExpression).thisKeyword,
null,
null,
initializerObject.argumentList));
}
} else if (initializerObject is MethodInvocation) {
Expression target = initializerObject.target;
if (target is SuperExpression) {
initializers.add(ast.superConstructorInvocation(
target.superKeyword,
initializerObject.operator,
initializerObject.methodName,
initializerObject.argumentList));
} else {
initializers.add(ast.redirectingConstructorInvocation(
(target as ThisExpression).thisKeyword,
initializerObject.operator,
initializerObject.methodName,
initializerObject.argumentList));
}
} else if (initializerObject is AssignmentExpression) {
Token thisKeyword;
Token period;
SimpleIdentifier fieldName;
Expression left = initializerObject.leftHandSide;
if (left is PropertyAccess) {
var thisExpression = left.target as ThisExpression;
thisKeyword = thisExpression.thisKeyword;
period = left.operator;
fieldName = left.propertyName;
} else {
fieldName = left as SimpleIdentifier;
}
initializers.add(ast.constructorFieldInitializer(
thisKeyword,
period,
fieldName,
initializerObject.operator,
initializerObject.rightHandSide));
} else if (initializerObject is AssertInitializer) {
initializers.add(initializerObject);
}
}
push(initializers);
}
void endVariableInitializer(Token assignmentOperator) {
assert(optionalOrNull('=', assignmentOperator));
debugEvent("VariableInitializer");
Expression initializer = pop();
SimpleIdentifier identifier = pop();
// TODO(ahe): Don't push initializers, instead install them.
push(_makeVariableDeclaration(identifier, assignmentOperator, initializer));
}
VariableDeclaration _makeVariableDeclaration(
SimpleIdentifier name, Token equals, Expression initializer) {
var variableDeclaration =
ast.variableDeclaration(name, equals, initializer);
localDeclarations[name.offset] = variableDeclaration;
return variableDeclaration;
}
@override
void endWhileStatement(Token whileKeyword, Token endToken) {
assert(optional('while', whileKeyword));
debugEvent("WhileStatement");
Statement body = pop();
ParenthesizedExpression condition = pop();
push(ast.whileStatement(whileKeyword, condition.leftParenthesis,
condition.expression, condition.rightParenthesis, body));
}
@override
void endYieldStatement(Token yieldToken, Token starToken, Token semicolon) {
assert(optional('yield', yieldToken));
assert(optionalOrNull('*', starToken));
assert(optional(';', semicolon));
debugEvent("YieldStatement");
Expression expression = pop();
push(ast.yieldStatement(yieldToken, starToken, expression, semicolon));
}
@override
void handleNoVariableInitializer(Token token) {
debugEvent("NoVariableInitializer");
}
void endInitializedIdentifier(Token nameToken) {
debugEvent("InitializedIdentifier");
AstNode node = pop();
VariableDeclaration variable;
// TODO(paulberry): This seems kludgy. It would be preferable if we
// could respond to a "handleNoVariableInitializer" event by converting a
// SimpleIdentifier into a VariableDeclaration, and then when this code was
// reached, node would always be a VariableDeclaration.
if (node is VariableDeclaration) {
variable = node;
} else if (node is SimpleIdentifier) {
variable = _makeVariableDeclaration(node, null, null);
} else {
unhandled("${node.runtimeType}", "identifier", nameToken.charOffset, uri);
}
push(variable);
}
@override
void beginVariablesDeclaration(Token token, Token varFinalOrConst) {
debugEvent("beginVariablesDeclaration");
if (varFinalOrConst != null) {
push(new _Modifiers()..finalConstOrVarKeyword = varFinalOrConst);
} else {
push(NullValue.Modifiers);
}
}
@override
void endVariablesDeclaration(int count, Token semicolon) {
assert(optionalOrNull(';', semicolon));
debugEvent("VariablesDeclaration");
List<VariableDeclaration> variables = popTypedList(count);
_Modifiers modifiers = pop(NullValue.Modifiers);
TypeAnnotation type = pop();
Token keyword = modifiers?.finalConstOrVarKeyword;
List<Annotation> metadata = pop();
Comment comment = _findComment(metadata,
variables[0].beginToken ?? type?.beginToken ?? modifiers.beginToken);
push(ast.variableDeclarationStatement(
ast.variableDeclarationList(
comment, metadata, keyword, type, variables),
semicolon));
}
void handleAssignmentExpression(Token token) {
assert(token.type.isAssignmentOperator);
debugEvent("AssignmentExpression");
Expression rhs = pop();
Expression lhs = pop();
push(ast.assignmentExpression(lhs, token, rhs));
}
void endBlock(int count, Token leftBracket, Token rightBracket) {
assert(optional('{', leftBracket));
assert(optional('}', rightBracket));
debugEvent("Block");
List<Statement> statements = popTypedList(count) ?? <Statement>[];
push(ast.block(leftBracket, statements, rightBracket));
}
void handleInvalidTopLevelBlock(Token token) {
// TODO(danrubel): Consider improved recovery by adding this block
// as part of a synthetic top level function.
pop(); // block
}
void endForStatement(Token forKeyword, Token leftParen, Token leftSeparator,
int updateExpressionCount, Token endToken) {
assert(optional('for', forKeyword));
assert(optional('(', leftParen));
assert(optional(';', leftSeparator));
debugEvent("ForStatement");
Statement body = pop();
List<Expression> updates = popTypedList(updateExpressionCount);
Statement conditionStatement = pop();
Object initializerPart = pop();
VariableDeclarationList variableList;
Expression initializer;
if (initializerPart is VariableDeclarationStatement) {
variableList = initializerPart.variables;
} else {
initializer = initializerPart as Expression;
}
Expression condition;
Token rightSeparator;
if (conditionStatement is ExpressionStatement) {
condition = conditionStatement.expression;
rightSeparator = conditionStatement.semicolon;
} else {
rightSeparator = (conditionStatement as EmptyStatement).semicolon;
}
push(ast.forStatement(
forKeyword,
leftParen,
variableList,
initializer,
leftSeparator,
condition,
rightSeparator,
updates,
leftParen?.endGroup,
body));
}
void handleLiteralList(
int count, Token leftBracket, Token constKeyword, Token rightBracket) {
assert(optional('[', leftBracket));
assert(optionalOrNull('const', constKeyword));
assert(optional(']', rightBracket));
debugEvent("LiteralList");
List<Expression> expressions = popTypedList(count);
TypeArgumentList typeArguments = pop();
push(ast.listLiteral(
constKeyword, typeArguments, leftBracket, expressions, rightBracket));
}
void handleAsyncModifier(Token asyncToken, Token starToken) {
assert(asyncToken == null ||
optional('async', asyncToken) ||
optional('sync', asyncToken));
assert(optionalOrNull('*', starToken));
debugEvent("AsyncModifier");
push(asyncToken ?? NullValue.FunctionBodyAsyncToken);
push(starToken ?? NullValue.FunctionBodyStarToken);
}
void endAwaitExpression(Token awaitKeyword, Token endToken) {
assert(optional('await', awaitKeyword));
debugEvent("AwaitExpression");
push(ast.awaitExpression(awaitKeyword, pop()));
}
void handleLiteralBool(Token token) {
bool value = identical(token.stringValue, "true");
assert(value || identical(token.stringValue, "false"));
debugEvent("LiteralBool");
push(ast.booleanLiteral(token, value));
}
void handleLiteralDouble(Token token) {
assert(token.type == TokenType.DOUBLE);
debugEvent("LiteralDouble");
push(ast.doubleLiteral(token, double.parse(token.lexeme)));
}
void handleLiteralNull(Token token) {
assert(optional('null', token));
debugEvent("LiteralNull");
push(ast.nullLiteral(token));
}
void handleLiteralMap(
int count, Token leftBracket, Token constKeyword, Token rightBracket) {
assert(optional('{', leftBracket));
assert(optionalOrNull('const', constKeyword));
assert(optional('}', rightBracket));
debugEvent("LiteralMap");
List<MapLiteralEntry> entries = popTypedList(count) ?? <MapLiteralEntry>[];
TypeArgumentList typeArguments = pop();
push(ast.mapLiteral(
constKeyword, typeArguments, leftBracket, entries, rightBracket));
}
void endLiteralMapEntry(Token colon, Token endToken) {
assert(optional(':', colon));
debugEvent("LiteralMapEntry");
Expression value = pop();
Expression key = pop();
push(ast.mapLiteralEntry(key, colon, value));
}
void endLiteralSymbol(Token hashToken, int tokenCount) {
assert(optional('#', hashToken));
debugEvent("LiteralSymbol");
List<Token> components = popTypedList(tokenCount);
push(ast.symbolLiteral(hashToken, components));
}
@override
void handleSuperExpression(Token superKeyword, IdentifierContext context) {
assert(optional('super', superKeyword));
debugEvent("SuperExpression");
push(ast.superExpression(superKeyword));
}
@override
void handleThisExpression(Token thisKeyword, IdentifierContext context) {
assert(optional('this', thisKeyword));
debugEvent("ThisExpression");
push(ast.thisExpression(thisKeyword));
}
@override
void handleType(Token beginToken, Token endToken) {
debugEvent("Type");
TypeArgumentList arguments = pop();
Identifier name = pop();
push(ast.typeName(name, arguments));
}
@override
void endAssert(Token assertKeyword, Assert kind, Token leftParenthesis,
Token comma, Token semicolon) {
assert(optional('assert', assertKeyword));
assert(optional('(', leftParenthesis));
assert(optionalOrNull(',', comma));
assert(kind != Assert.Statement || optionalOrNull(';', semicolon));
debugEvent("Assert");
Expression message = popIfNotNull(comma);
Expression condition = pop();
switch (kind) {
case Assert.Expression:
// The parser has already reported an error indicating that assert
// cannot be used in an expression. Insert a placeholder.
List<Expression> arguments = <Expression>[condition];
if (message != null) {
arguments.add(message);
}
push(ast.functionExpressionInvocation(
ast.simpleIdentifier(assertKeyword),
null,
ast.argumentList(
leftParenthesis, arguments, leftParenthesis?.endGroup)));
break;
case Assert.Initializer:
push(ast.assertInitializer(assertKeyword, leftParenthesis, condition,
comma, message, leftParenthesis?.endGroup));
break;
case Assert.Statement:
push(ast.assertStatement(assertKeyword, leftParenthesis, condition,
comma, message, leftParenthesis?.endGroup, semicolon));
break;
}
}
void handleAsOperator(Token asOperator, Token endToken) {
assert(optional('as', asOperator));
debugEvent("AsOperator");
TypeAnnotation type = pop();
if (type is TypeName) {
Identifier name = type.name;
if (name is SimpleIdentifier) {
if (name.name == 'void') {
Token token = name.beginToken;
// TODO(danrubel): This needs to be reported during fasta resolution.
handleRecoverableError(
templateExpectedType.withArguments(token), token, token);
}
}
}
Expression expression = pop();
push(ast.asExpression(expression, asOperator, type));
}
@override
void handleBreakStatement(
bool hasTarget, Token breakKeyword, Token semicolon) {
assert(optional('break', breakKeyword));
assert(optional(';', semicolon));
debugEvent("BreakStatement");
SimpleIdentifier label = hasTarget ? pop() : null;
push(ast.breakStatement(breakKeyword, label, semicolon));
}
@override
void handleContinueStatement(
bool hasTarget, Token continueKeyword, Token semicolon) {
assert(optional('continue', continueKeyword));
assert(optional(';', semicolon));
debugEvent("ContinueStatement");
SimpleIdentifier label = hasTarget ? pop() : null;
push(ast.continueStatement(continueKeyword, label, semicolon));
}
void handleIsOperator(Token isOperator, Token not, Token endToken) {
assert(optional('is', isOperator));
assert(optionalOrNull('!', not));
debugEvent("IsOperator");
TypeAnnotation type = pop();
if (type is TypeName) {
Identifier name = type.name;
if (name is SimpleIdentifier) {
if (name.name == 'void') {
Token token = name.beginToken;
// TODO(danrubel): This needs to be reported during fasta resolution.
handleRecoverableError(
templateExpectedType.withArguments(token), token, token);
}
}
}
Expression expression = pop();
push(ast.isExpression(expression, isOperator, not, type));
}
void endConditionalExpression(Token question, Token colon) {
assert(optional('?', question));
assert(optional(':', colon));
debugEvent("ConditionalExpression");
Expression elseExpression = pop();
Expression thenExpression = pop();
Expression condition = pop();
push(ast.conditionalExpression(
condition, question, thenExpression, colon, elseExpression));
}
@override
void endRedirectingFactoryBody(Token equalToken, Token endToken) {
assert(optional('=', equalToken));
debugEvent("RedirectingFactoryBody");
ConstructorName constructorName = pop();
Token starToken = pop();
Token asyncToken = pop();
push(new _RedirectingFactoryBody(
asyncToken, starToken, equalToken, constructorName));
}
@override
void endRethrowStatement(Token rethrowToken, Token semicolon) {
assert(optional('rethrow', rethrowToken));
assert(optional(';', semicolon));
debugEvent("RethrowStatement");
RethrowExpression expression = ast.rethrowExpression(rethrowToken);
// TODO(scheglov) According to the specification, 'rethrow' is a statement.
push(ast.expressionStatement(expression, semicolon));
}
void handleThrowExpression(Token throwToken, Token endToken) {
assert(optional('throw', throwToken));
debugEvent("ThrowExpression");
push(ast.throwExpression(throwToken, pop()));
}
@override
void endOptionalFormalParameters(
int count, Token leftDelimeter, Token rightDelimeter) {
assert((optional('[', leftDelimeter) && optional(']', rightDelimeter)) ||
(optional('{', leftDelimeter) && optional('}', rightDelimeter)));
debugEvent("OptionalFormalParameters");
push(new _OptionalFormalParameters(
popTypedList(count), leftDelimeter, rightDelimeter));
}
@override
void beginFormalParameterDefaultValueExpression() {}
@override
void endFormalParameterDefaultValueExpression() {
debugEvent("FormalParameterDefaultValueExpression");
}
void handleValuedFormalParameter(Token equals, Token token) {
assert(optional('=', equals) || optional(':', equals));
debugEvent("ValuedFormalParameter");
Expression value = pop();
push(new _ParameterDefaultValue(equals, value));
}
@override
void endFunctionType(Token functionToken, Token semicolon) {
assert(optional('Function', functionToken));
debugEvent("FunctionType");
FormalParameterList parameters = pop();
TypeAnnotation returnType = pop();
TypeParameterList typeParameters = pop();
push(ast.genericFunctionType(
returnType, functionToken, typeParameters, parameters));
}
void handleFormalParameterWithoutValue(Token token) {
debugEvent("FormalParameterWithoutValue");
push(NullValue.ParameterDefaultValue);
}
@override
void endForInExpression(Token token) {
debugEvent("ForInExpression");
}
@override
void endForIn(Token awaitToken, Token forToken, Token leftParenthesis,
Token inKeyword, Token endToken) {
assert(optionalOrNull('await', awaitToken));
assert(optional('for', forToken));
assert(optional('(', leftParenthesis));
assert(optional('in', inKeyword) || optional(':', inKeyword));
debugEvent("ForInExpression");
Statement body = pop();
Expression iterator = pop();
Object variableOrDeclaration = pop();
if (variableOrDeclaration is VariableDeclarationStatement) {
VariableDeclarationList variableList = variableOrDeclaration.variables;
push(ast.forEachStatementWithDeclaration(
awaitToken,
forToken,
leftParenthesis,
ast.declaredIdentifier(
variableList.documentationComment,
variableList.metadata,
variableList.keyword,
variableList.type,
variableList.variables.first.name),
inKeyword,
iterator,
leftParenthesis?.endGroup,
body));
} else {
if (variableOrDeclaration is! SimpleIdentifier) {
// Parser has already reported the error.
if (!leftParenthesis.next.isIdentifier) {
parser.rewriter.insertTokenAfter(
leftParenthesis,
new SyntheticStringToken(
TokenType.IDENTIFIER, '', leftParenthesis.next.charOffset));
}
variableOrDeclaration = ast.simpleIdentifier(leftParenthesis.next);
}
push(ast.forEachStatementWithReference(
awaitToken,
forToken,
leftParenthesis,
variableOrDeclaration,
inKeyword,
iterator,
leftParenthesis?.endGroup,
body));
}
}
@override
void beginFormalParameter(Token token, MemberKind kind, Token covariantToken,
Token varFinalOrConst) {
push(new _Modifiers()
..covariantKeyword = covariantToken
..finalConstOrVarKeyword = varFinalOrConst);
}
@override
void endFormalParameter(Token thisKeyword, Token periodAfterThis,
Token nameToken, FormalParameterKind kind, MemberKind memberKind) {
assert(optionalOrNull('this', thisKeyword));
assert(thisKeyword == null
? periodAfterThis == null
: optional('.', periodAfterThis));
debugEvent("FormalParameter");
_ParameterDefaultValue defaultValue = pop();
SimpleIdentifier name = pop();
AstNode typeOrFunctionTypedParameter = pop();
_Modifiers modifiers = pop();
Token keyword = modifiers?.finalConstOrVarKeyword;
Token covariantKeyword = modifiers?.covariantKeyword;
List<Annotation> metadata = pop();
Comment comment = _findComment(metadata,
thisKeyword ?? typeOrFunctionTypedParameter?.beginToken ?? nameToken);
NormalFormalParameter node;
if (typeOrFunctionTypedParameter is FunctionTypedFormalParameter) {
// This is a temporary AST node that was constructed in
// [endFunctionTypedFormalParameter]. We now deconstruct it and create
// the final AST node.
if (thisKeyword == null) {
node = ast.functionTypedFormalParameter2(
identifier: name,
comment: comment,
metadata: metadata,
covariantKeyword: covariantKeyword,
returnType: typeOrFunctionTypedParameter.returnType,
typeParameters: typeOrFunctionTypedParameter.typeParameters,
parameters: typeOrFunctionTypedParameter.parameters);
} else {
node = ast.fieldFormalParameter2(
identifier: name,
comment: comment,
metadata: metadata,
covariantKeyword: covariantKeyword,
type: typeOrFunctionTypedParameter.returnType,
thisKeyword: thisKeyword,
period: periodAfterThis,
typeParameters: typeOrFunctionTypedParameter.typeParameters,
parameters: typeOrFunctionTypedParameter.parameters);
}
} else {
TypeAnnotation type = typeOrFunctionTypedParameter;
if (thisKeyword == null) {
node = ast.simpleFormalParameter2(
comment: comment,
metadata: metadata,
covariantKeyword: covariantKeyword,
keyword: keyword,
type: type,
identifier: name);
} else {
node = ast.fieldFormalParameter2(
comment: comment,
metadata: metadata,
covariantKeyword: covariantKeyword,
keyword: keyword,
type: type,
thisKeyword: thisKeyword,
period: thisKeyword.next,
identifier: name);
}
}
ParameterKind analyzerKind = _toAnalyzerParameterKind(kind);
FormalParameter parameter = node;
if (analyzerKind != ParameterKind.REQUIRED) {
parameter = ast.defaultFormalParameter(
node, analyzerKind, defaultValue?.separator, defaultValue?.value);
} else if (defaultValue != null) {
// An error is reported if a required parameter has a default value.
// Record it as named parameter for recovery.
parameter = ast.defaultFormalParameter(node, ParameterKind.NAMED,
defaultValue.separator, defaultValue.value);
}
localDeclarations[nameToken.offset] = parameter;
push(parameter);
}
@override
void endFunctionTypedFormalParameter() {
debugEvent("FunctionTypedFormalParameter");
FormalParameterList formalParameters = pop();
TypeAnnotation returnType = pop();
TypeParameterList typeParameters = pop();
// Create a temporary formal parameter that will be dissected later in
// [endFormalParameter].
push(ast.functionTypedFormalParameter2(
identifier: null,
returnType: returnType,
typeParameters: typeParameters,
parameters: formalParameters));
}
void endFormalParameters(
int count, Token leftParen, Token rightParen, MemberKind kind) {
assert(optional('(', leftParen));
assert(optional(')', rightParen));
debugEvent("FormalParameters");
List<Object> rawParameters = popTypedList(count) ?? const <Object>[];
List<FormalParameter> parameters = <FormalParameter>[];
Token leftDelimiter;
Token rightDelimiter;
for (Object raw in rawParameters) {
if (raw is _OptionalFormalParameters) {
parameters.addAll(raw.parameters ?? const <FormalParameter>[]);
leftDelimiter = raw.leftDelimiter;
rightDelimiter = raw.rightDelimiter;
} else {
parameters.add(raw as FormalParameter);
}
}
push(ast.formalParameterList(
leftParen, parameters, leftDelimiter, rightDelimiter, rightParen));
}
@override
void endSwitchBlock(int caseCount, Token leftBracket, Token rightBracket) {
assert(optional('{', leftBracket));
assert(optional('}', rightBracket));
debugEvent("SwitchBlock");
List<List<SwitchMember>> membersList = popTypedList(caseCount);
List<SwitchMember> members =
membersList?.expand((members) => members)?.toList() ?? <SwitchMember>[];
Set<String> labels = new Set<String>();
for (SwitchMember member in members) {
for (Label label in member.labels) {
if (!labels.add(label.label.name)) {
handleRecoverableError(
templateDuplicateLabelInSwitchStatement
.withArguments(label.label.name),
label.beginToken,
label.beginToken);
}
}
}
push(leftBracket);
push(members);
push(rightBracket);
}
@override
void endSwitchCase(
int labelCount,
int expressionCount,
Token defaultKeyword,
Token colonAfterDefault,
int statementCount,
Token firstToken,
Token endToken) {
assert(optionalOrNull('default', defaultKeyword));
assert(defaultKeyword == null
? colonAfterDefault == null
: optional(':', colonAfterDefault));
debugEvent("SwitchCase");
List<Statement> statements = popTypedList(statementCount);
List<SwitchMember> members = popTypedList(expressionCount) ?? [];
List<Label> labels = popTypedList(labelCount);
if (defaultKeyword != null) {
members.add(ast.switchDefault(
<Label>[], defaultKeyword, colonAfterDefault, <Statement>[]));
}
if (members.isNotEmpty) {
members.last.statements.addAll(statements);
members.first.labels.addAll(labels);
}
push(members);
}
@override
void handleCaseMatch(Token caseKeyword, Token colon) {
assert(optional('case', caseKeyword));
assert(optional(':', colon));
debugEvent("CaseMatch");
Expression expression = pop();
push(ast.switchCase(
<Label>[], caseKeyword, expression, colon, <Statement>[]));
}
@override
void endSwitchStatement(Token switchKeyword, Token endToken) {
assert(optional('switch', switchKeyword));
debugEvent("SwitchStatement");
Token rightBracket = pop();
List<SwitchMember> members = pop();
Token leftBracket = pop();
ParenthesizedExpression expression = pop();
push(ast.switchStatement(
switchKeyword,
expression.leftParenthesis,
expression.expression,
expression.rightParenthesis,
leftBracket,
members,
rightBracket));
}
void handleCatchBlock(Token onKeyword, Token catchKeyword, Token comma) {
assert(optionalOrNull('on', onKeyword));
assert(optionalOrNull('catch', catchKeyword));
assert(optionalOrNull(',', comma));
debugEvent("CatchBlock");
Block body = pop();
FormalParameterList catchParameterList = popIfNotNull(catchKeyword);
TypeAnnotation type = popIfNotNull(onKeyword);
SimpleIdentifier exception;
SimpleIdentifier stackTrace;
if (catchParameterList != null) {
List<FormalParameter> catchParameters = catchParameterList.parameters;
if (catchParameters.length > 0) {
exception = catchParameters[0].identifier;
localDeclarations[exception.offset] = exception;
}
if (catchParameters.length > 1) {
stackTrace = catchParameters[1].identifier;
localDeclarations[stackTrace.offset] = stackTrace;
}
}
push(ast.catchClause(
onKeyword,
type,
catchKeyword,
catchParameterList?.leftParenthesis,
exception,
comma,
stackTrace,
catchParameterList?.rightParenthesis,
body));
}
@override
void handleFinallyBlock(Token finallyKeyword) {
debugEvent("FinallyBlock");
// The finally block is popped in "endTryStatement".
}
void endTryStatement(int catchCount, Token tryKeyword, Token finallyKeyword) {
assert(optional('try', tryKeyword));
assert(optionalOrNull('finally', finallyKeyword));
debugEvent("TryStatement");
Block finallyBlock = popIfNotNull(finallyKeyword);
List<CatchClause> catchClauses = popTypedList(catchCount);
Block body = pop();
push(ast.tryStatement(
tryKeyword, body, catchClauses, finallyKeyword, finallyBlock));
}
@override
void handleLabel(Token colon) {
assert(optionalOrNull(':', colon));
debugEvent("Label");
SimpleIdentifier name = pop();
push(ast.label(name, colon));
}
void handleNoExpression(Token token) {
debugEvent("NoExpression");
push(NullValue.Expression);
}
void handleIndexedExpression(Token leftBracket, Token rightBracket) {
assert(optional('[', leftBracket));
assert(optional(']', rightBracket));
debugEvent("IndexedExpression");
Expression index = pop();
Expression target = pop();
if (target == null) {
CascadeExpression receiver = pop();
Token token = peek();
push(receiver);
IndexExpression expression = ast.indexExpressionForCascade(
token, leftBracket, index, rightBracket);
assert(expression.isCascaded);
push(expression);
} else {
push(ast.indexExpressionForTarget(
target, leftBracket, index, rightBracket));
}
}
@override
void handleInvalidExpression(Token token) {
debugEvent("InvalidExpression");
}
@override
void handleInvalidFunctionBody(Token leftBracket) {
assert(optional('{', leftBracket));
assert(optional('}', leftBracket.endGroup));
debugEvent("InvalidFunctionBody");
Block block = ast.block(leftBracket, [], leftBracket.endGroup);
Token star = pop();
Token asyncKeyword = pop();
push(ast.blockFunctionBody(asyncKeyword, star, block));
}
void handleUnaryPrefixExpression(Token operator) {
assert(operator.type.isUnaryPrefixOperator);
debugEvent("UnaryPrefixExpression");
push(ast.prefixExpression(operator, pop()));
}
void handleUnaryPrefixAssignmentExpression(Token operator) {
assert(operator.type.isUnaryPrefixOperator);
debugEvent("UnaryPrefixAssignmentExpression");
Expression expression = pop();
if (!expression.isAssignable) {
// This error is also reported by the body builder.
handleRecoverableError(messageMissingAssignableSelector,
expression.endToken, expression.endToken);
}
push(ast.prefixExpression(operator, expression));
}
void handleUnaryPostfixAssignmentExpression(Token operator) {
assert(operator.type.isUnaryPostfixOperator);
debugEvent("UnaryPostfixAssignmentExpression");
Expression expression = pop();
if (!expression.isAssignable) {
// This error is also reported by the body builder.
handleRecoverableError(
messageIllegalAssignmentToNonAssignable, operator, operator);
}
push(ast.postfixExpression(expression, operator));
}
void beginTopLevelMethod(Token lastConsumed, Token externalToken) {
push(new _Modifiers()..externalKeyword = externalToken);
}
void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) {
// TODO(paulberry): set up scopes properly to resolve parameters and type
// variables.
assert(getOrSet == null ||
optional('get', getOrSet) ||
optional('set', getOrSet));
debugEvent("TopLevelMethod");
FunctionBody body = pop();
FormalParameterList parameters = pop();
TypeParameterList typeParameters = pop();
SimpleIdentifier name = pop();
TypeAnnotation returnType = pop();
_Modifiers modifiers = pop();
Token externalKeyword = modifiers?.externalKeyword;
List<Annotation> metadata = pop();
Comment comment = _findComment(metadata, beginToken);
if (getOrSet != null && optional('get', getOrSet)) {
parameters = null;
}
declarations.add(ast.functionDeclaration(
comment,
metadata,
externalKeyword,
returnType,
getOrSet,
name,
ast.functionExpression(typeParameters, parameters, body)));
}
@override
void endTopLevelDeclaration(Token token) {
debugEvent("TopLevelDeclaration");
}
@override
void handleInvalidTopLevelDeclaration(Token endToken) {
debugEvent("InvalidTopLevelDeclaration");
pop(); // metadata star
// TODO(danrubel): consider creating a AST node
// representing the invalid declaration to better support code completion,
// quick fixes, etc, rather than discarding the metadata and token
}
@override
void beginCompilationUnit(Token token) {
push(token);
}
@override
void endCompilationUnit(int count, Token endToken) {
debugEvent("CompilationUnit");
Token beginToken = pop();
checkEmpty(endToken.charOffset);
push(ast.compilationUnit(
beginToken, scriptTag, directives, declarations, endToken));
}
@override
void handleImportPrefix(Token deferredKeyword, Token asKeyword) {
assert(optionalOrNull('deferred', deferredKeyword));
assert(optionalOrNull('as', asKeyword));
debugEvent("ImportPrefix");
if (asKeyword == null) {
// If asKeyword is null, then no prefix has been pushed on the stack.
// Push a placeholder indicating that there is no prefix.
push(NullValue.Prefix);
push(NullValue.As);
} else {
push(asKeyword);
}
push(deferredKeyword ?? NullValue.Deferred);
}
@override
void endImport(Token importKeyword, Token semicolon) {
assert(optional('import', importKeyword));
assert(optionalOrNull(';', semicolon));
debugEvent("Import");
List<Combinator> combinators = pop();
Token deferredKeyword = pop(NullValue.Deferred);
Token asKeyword = pop(NullValue.As);
SimpleIdentifier prefix = pop(NullValue.Prefix);
List<Configuration> configurations = pop();
StringLiteral uri = pop();
List<Annotation> metadata = pop();
Comment comment = _findComment(metadata, importKeyword);
directives.add(ast.importDirective(
comment,
metadata,
importKeyword,
uri,
configurations,
deferredKeyword,
asKeyword,
prefix,
combinators,
semicolon));
}
@override
void handleRecoverImport(Token semicolon) {
assert(optionalOrNull(';', semicolon));
debugEvent("RecoverImport");
List<Combinator> combinators = pop();
Token deferredKeyword = pop(NullValue.Deferred);
Token asKeyword = pop(NullValue.As);
SimpleIdentifier prefix = pop(NullValue.Prefix);
List<Configuration> configurations = pop();
ImportDirective directive = directives.last;
if (combinators != null) {
directive.combinators.addAll(combinators);
}
directive.deferredKeyword ??= deferredKeyword;
if (directive.asKeyword == null && asKeyword != null) {
directive.asKeyword = asKeyword;
directive.prefix = prefix;
}
if (configurations != null) {
directive.configurations.addAll(configurations);
}
directive.semicolon = semicolon;
}
void endExport(Token exportKeyword, Token semicolon) {
assert(optional('export', exportKeyword));
assert(optional(';', semicolon));
debugEvent("Export");
List<Combinator> combinators = pop();
List<Configuration> configurations = pop();
StringLiteral uri = pop();
List<Annotation> metadata = pop();
Comment comment = _findComment(metadata, exportKeyword);
directives.add(ast.exportDirective(comment, metadata, exportKeyword, uri,
configurations, combinators, semicolon));
}
@override
void handleDottedName(int count, Token firstIdentifier) {
assert(firstIdentifier.isIdentifier);
debugEvent("DottedName");
List<SimpleIdentifier> components = popTypedList(count);
push(ast.dottedName(components));
}
@override
void endDoWhileStatement(
Token doKeyword, Token whileKeyword, Token semicolon) {
assert(optional('do', doKeyword));
assert(optional('while', whileKeyword));
assert(optional(';', semicolon));
debugEvent("DoWhileStatement");
ParenthesizedExpression condition = pop();
Statement body = pop();
push(ast.doStatement(
doKeyword,
body,
whileKeyword,
condition.leftParenthesis,
condition.expression,
condition.rightParenthesis,
semicolon));
}
void endConditionalUri(Token ifKeyword, Token leftParen, Token equalSign) {
assert(optional('if', ifKeyword));
assert(optionalOrNull('(', leftParen));
assert(optionalOrNull('==', equalSign));
debugEvent("ConditionalUri");
StringLiteral libraryUri = pop();
StringLiteral value = popIfNotNull(equalSign);
if (value is StringInterpolation) {
for (var child in value.childEntities) {
if (child is InterpolationExpression) {
// This error is reported in OutlineBuilder.endLiteralString
handleRecoverableError(
messageInterpolationInUri, child.beginToken, child.endToken);
break;
}
}
}
DottedName name = pop();
push(ast.configuration(ifKeyword, leftParen, name, equalSign, value,
leftParen?.endGroup, libraryUri));
}
@override
void endConditionalUris(int count) {
debugEvent("ConditionalUris");
push(popTypedList<Configuration>(count) ?? NullValue.ConditionalUris);
}
@override
void handleIdentifierList(int count) {
debugEvent("IdentifierList");
push(popTypedList<SimpleIdentifier>(count) ?? NullValue.IdentifierList);
}
@override
void endShow(Token showKeyword) {
assert(optional('show', showKeyword));
debugEvent("Show");
List<SimpleIdentifier> shownNames = pop();
push(ast.showCombinator(showKeyword, shownNames));
}
@override
void endHide(Token hideKeyword) {
assert(optional('hide', hideKeyword));
debugEvent("Hide");
List<SimpleIdentifier> hiddenNames = pop();
push(ast.hideCombinator(hideKeyword, hiddenNames));
}
@override
void endCombinators(int count) {
debugEvent("Combinators");
push(popTypedList<Combinator>(count) ?? NullValue.Combinators);
}
@override
void endTypeList(int count) {
debugEvent("TypeList");
push(popTypedList<TypeName>(count) ?? NullValue.TypeList);
}
@override
void endClassBody(int memberCount, Token leftBracket, Token rightBracket) {
assert(optional('{', leftBracket));
assert(optional('}', rightBracket));
debugEvent("ClassBody");
classDeclaration.leftBracket = leftBracket;
classDeclaration.rightBracket = rightBracket;
}
@override
void beginClassDeclaration(Token begin, Token abstractToken, Token name) {
assert(classDeclaration == null);
push(new _Modifiers()..abstractKeyword = abstractToken);
}
@override
void handleClassExtends(Token extendsKeyword) {
assert(optionalOrNull('extends', extendsKeyword));
debugEvent("ClassExtends");
ExtendsClause extendsClause;
WithClause withClause;
var supertype = pop();
if (supertype == null) {
// No extends clause
} else if (supertype is TypeName) {
extendsClause = ast.extendsClause(extendsKeyword, supertype);
} else if (supertype is _MixinApplication) {
extendsClause = ast.extendsClause(extendsKeyword, supertype.supertype);
withClause = ast.withClause(supertype.withKeyword, supertype.mixinTypes);
} else {
unhandled("${supertype.runtimeType}", "supertype",
extendsKeyword.charOffset, uri);
}
push(extendsClause ?? NullValue.ExtendsClause);
push(withClause ?? NullValue.WithClause);
}
@override
void handleClassImplements(Token implementsKeyword, int interfacesCount) {
assert(optionalOrNull('implements', implementsKeyword));
debugEvent("ClassImplements");
if (implementsKeyword != null) {
List<TypeName> interfaces = popTypedList(interfacesCount);
push(ast.implementsClause(implementsKeyword, interfaces));
} else {
push(NullValue.IdentifierList);
}
}
@override
void handleClassHeader(Token begin, Token classKeyword, Token nativeToken) {
assert(optional('class', classKeyword));
assert(optionalOrNull('native', nativeToken));
assert(classDeclaration == null);
debugEvent("ClassHeader");
NativeClause nativeClause;
if (nativeToken != null) {
nativeClause = ast.nativeClause(nativeToken, nativeName);
}
ImplementsClause implementsClause = pop(NullValue.IdentifierList);
WithClause withClause = pop(NullValue.WithClause);
ExtendsClause extendsClause = pop(NullValue.ExtendsClause);
_Modifiers modifiers = pop();
TypeParameterList typeParameters = pop();
SimpleIdentifier name = pop();
Token abstractKeyword = modifiers?.abstractKeyword;
List<Annotation> metadata = pop();
Comment comment = _findComment(metadata, classKeyword);
// leftBracket, members, and rightBracket are set in [endClassBody].
classDeclaration = ast.classDeclaration(
comment,
metadata,
abstractKeyword,
classKeyword,
name,
typeParameters,
extendsClause,
withClause,
implementsClause,
null, // leftBracket
<ClassMember>[],
null, // rightBracket
);
classDeclaration.nativeClause = nativeClause;
declarations.add(classDeclaration);
}
@override
void handleRecoverClassHeader() {
debugEvent("RecoverClassHeader");
ImplementsClause implementsClause = pop(NullValue.IdentifierList);
WithClause withClause = pop(NullValue.WithClause);
ExtendsClause extendsClause = pop(NullValue.ExtendsClause);
ClassDeclaration declaration = declarations.last;
if (extendsClause != null && !extendsClause.extendsKeyword.isSynthetic) {
if (declaration.extendsClause?.superclass == null) {
declaration.extendsClause = extendsClause;
}
}
if (withClause != null) {
if (declaration.withClause == null) {
declaration.withClause = withClause;
} else {
declaration.withClause.mixinTypes.addAll(withClause.mixinTypes);
}
}
if (implementsClause != null) {
if (declaration.implementsClause == null) {
declaration.implementsClause = implementsClause;
} else {
declaration.implementsClause.interfaces
.addAll(implementsClause.interfaces);
}
}
}
@override
void endClassDeclaration(Token beginToken, Token endToken) {
debugEvent("ClassDeclaration");
classDeclaration = null;
}
@override
void beginNamedMixinApplication(
Token begin, Token abstractToken, Token name) {
push(new _Modifiers()..abstractKeyword = abstractToken);
}
@override
void endMixinApplication(Token withKeyword) {
assert(optionalOrNull('with', withKeyword));
debugEvent("MixinApplication");
List<TypeName> mixinTypes = pop();
TypeName supertype = pop();
push(new _MixinApplication(supertype, withKeyword, mixinTypes));
}
@override
void endNamedMixinApplication(Token beginToken, Token classKeyword,
Token equalsToken, Token implementsKeyword, Token semicolon) {
assert(optional('class', classKeyword));
assert(optionalOrNull('=', equalsToken));
assert(optionalOrNull('implements', implementsKeyword));
assert(optional(';', semicolon));
debugEvent("NamedMixinApplication");
ImplementsClause implementsClause;
if (implementsKeyword != null) {
List<TypeName> interfaces = pop();
implementsClause = ast.implementsClause(implementsKeyword, interfaces);
}
_MixinApplication mixinApplication = pop();
var superclass = mixinApplication.supertype;
var withClause = ast.withClause(
mixinApplication.withKeyword, mixinApplication.mixinTypes);
_Modifiers modifiers = pop();
TypeParameterList typeParameters = pop();
SimpleIdentifier name = pop();
Token abstractKeyword = modifiers?.abstractKeyword;
List<Annotation> metadata = pop();
Comment comment = _findComment(metadata, beginToken);
declarations.add(ast.classTypeAlias(
comment,
metadata,
classKeyword,
name,
typeParameters,
equalsToken,
abstractKeyword,
superclass,
withClause,
implementsClause,
semicolon));
}
@override
void endLabeledStatement(int labelCount) {
debugEvent("LabeledStatement");
Statement statement = pop();
List<Label> labels = popTypedList(labelCount);
push(ast.labeledStatement(labels, statement));
}
@override
void endLibraryName(Token libraryKeyword, Token semicolon) {
assert(optional('library', libraryKeyword));
assert(optional(';', semicolon));
debugEvent("LibraryName");
List<SimpleIdentifier> libraryName = pop();
var name = ast.libraryIdentifier(libraryName);
List<Annotation> metadata = pop();
Comment comment = _findComment(metadata, libraryKeyword);
directives.add(ast.libraryDirective(
comment, metadata, libraryKeyword, name, semicolon));
}
@override
void handleRecoverableError(
Message message, Token startToken, Token endToken) {
/// TODO(danrubel): Ignore this error until we deprecate `native` support.
if (message == messageNativeClauseShouldBeAnnotation && allowNativeClause) {
return;
}
debugEvent("Error: ${message.message}");
if (message.code.analyzerCode == null && startToken is ErrorToken) {
translateErrorToken(startToken, errorReporter.reportScannerError);
} else {
int offset = startToken.offset;
int length = endToken.end - offset;
addCompileTimeError(message, offset, length);
}
}
@override
void handleQualified(Token period) {
assert(optional('.', period));
SimpleIdentifier identifier = pop();
var prefix = pop();
if (prefix is List) {
// We're just accumulating components into a list.
prefix.add(identifier);
push(prefix);
} else if (prefix is SimpleIdentifier) {
// TODO(paulberry): resolve [identifier]. Note that BodyBuilder handles
// this situation using SendAccessGenerator.
push(ast.prefixedIdentifier(prefix, period, identifier));
} else {
// TODO(paulberry): implement.
logEvent('Qualified with >1 dot');
}
}
@override
void endPart(Token partKeyword, Token semicolon) {
assert(optional('part', partKeyword));
assert(optional(';', semicolon));
debugEvent("Part");
StringLiteral uri = pop();
List<Annotation> metadata = pop();
Comment comment = _findComment(metadata, partKeyword);
directives
.add(ast.partDirective(comment, metadata, partKeyword, uri, semicolon));
}
@override
void endPartOf(
Token partKeyword, Token ofKeyword, Token semicolon, bool hasName) {
assert(optional('part', partKeyword));
assert(optional('of', ofKeyword));
assert(optional(';', semicolon));
debugEvent("PartOf");
var libraryNameOrUri = pop();
LibraryIdentifier name;
StringLiteral uri;
if (libraryNameOrUri is StringLiteral) {
uri = libraryNameOrUri;
} else {
name = ast.libraryIdentifier(libraryNameOrUri as List<SimpleIdentifier>);
}
List<Annotation> metadata = pop();
Comment comment = _findComment(metadata, partKeyword);
directives.add(ast.partOfDirective(
comment, metadata, partKeyword, ofKeyword, uri, name, semicolon));
}
@override
void endFunctionExpression(Token beginToken, Token token) {
// TODO(paulberry): set up scopes properly to resolve parameters and type
// variables. Note that this is tricky due to the handling of initializers
// in constructors, so the logic should be shared with BodyBuilder as much
// as possible.
debugEvent("FunctionExpression");
FunctionBody body = pop();
FormalParameterList parameters = pop();
TypeParameterList typeParameters = pop();
push(ast.functionExpression(typeParameters, parameters, body));
}
@override
void handleNoFieldInitializer(Token token) {
debugEvent("NoFieldInitializer");
SimpleIdentifier name = pop();
push(_makeVariableDeclaration(name, null, null));
}
@override
void beginFactoryMethod(
Token lastConsumed, Token externalToken, Token constToken) {
push(new _Modifiers()
..externalKeyword = externalToken
..finalConstOrVarKeyword = constToken);
}
@override
void endFactoryMethod(
Token beginToken, Token factoryKeyword, Token endToken) {
assert(optional('factory', factoryKeyword));
assert(optional(';', endToken) || optional('}', endToken));
debugEvent("FactoryMethod");
FunctionBody body;
Token separator;
ConstructorName redirectedConstructor;
Object bodyObject = pop();
if (bodyObject is FunctionBody) {
body = bodyObject;
} else if (bodyObject is _RedirectingFactoryBody) {
separator = bodyObject.equalToken;
redirectedConstructor = bodyObject.constructorName;
body = ast.emptyFunctionBody(endToken);
} else {
unhandled("${bodyObject.runtimeType}", "bodyObject",
beginToken.charOffset, uri);
}
FormalParameterList parameters = pop();
pop(); // Type parameters
Object constructorName = pop();
_Modifiers modifiers = pop();
List<Annotation> metadata = pop();
Comment comment = _findComment(metadata, beginToken);
// Decompose the preliminary ConstructorName into the type name and
// the actual constructor name.
SimpleIdentifier returnType;
Token period;
SimpleIdentifier name;
Identifier typeName = constructorName;
if (typeName is SimpleIdentifier) {
returnType = typeName;
} else if (typeName is PrefixedIdentifier) {
returnType = typeName.prefix;
period = typeName.period;
name =
ast.simpleIdentifier(typeName.identifier.token, isDeclaration: true);
}
classDeclaration.members.add(ast.constructorDeclaration(
comment,
metadata,
modifiers?.externalKeyword,
modifiers?.finalConstOrVarKeyword,
factoryKeyword,
ast.simpleIdentifier(returnType.token),
period,
name,
parameters,
separator,
null,
redirectedConstructor,
body));
}
void endFieldInitializer(Token assignment, Token token) {
assert(optional('=', assignment));
debugEvent("FieldInitializer");
Expression initializer = pop();
SimpleIdentifier name = pop();
push(_makeVariableDeclaration(name, assignment, initializer));
}
@override
void endNamedFunctionExpression(Token endToken) {
debugEvent("NamedFunctionExpression");
FunctionBody body = pop();
if (isFullAst) {
pop(); // constructor initializers
pop(); // separator before constructor initializers
}
FormalParameterList parameters = pop();
pop(); // name
pop(); // returnType
TypeParameterList typeParameters = pop();
push(ast.functionExpression(typeParameters, parameters, body));
}
@override
void endLocalFunctionDeclaration(Token token) {
debugEvent("LocalFunctionDeclaration");
FunctionBody body = pop();
if (isFullAst) {
pop(); // constructor initializers
pop(); // separator before constructor initializers
}
FormalParameterList parameters = pop();
checkFieldFormalParameters(parameters);
SimpleIdentifier name = pop();
TypeAnnotation returnType = pop();
TypeParameterList typeParameters = pop();
List<Annotation> metadata = pop(NullValue.Metadata);
FunctionExpression functionExpression =
ast.functionExpression(typeParameters, parameters, body);
var functionDeclaration = ast.functionDeclaration(
null, metadata, null, returnType, null, name, functionExpression);
localDeclarations[name.offset] = functionDeclaration;
push(ast.functionDeclarationStatement(functionDeclaration));
}
@override
void endFunctionName(Token beginToken, Token token) {
debugEvent("FunctionName");
}
void endTopLevelFields(Token staticToken, Token covariantToken,
Token varFinalOrConst, int count, Token beginToken, Token semicolon) {
assert(optional(';', semicolon));
debugEvent("TopLevelFields");
List<VariableDeclaration> variables = popTypedList(count);
TypeAnnotation type = pop();
_Modifiers modifiers = new _Modifiers()
..staticKeyword = staticToken
..covariantKeyword = covariantToken
..finalConstOrVarKeyword = varFinalOrConst;
Token keyword = modifiers?.finalConstOrVarKeyword;
var variableList =
ast.variableDeclarationList(null, null, keyword, type, variables);
List<Annotation> metadata = pop();
Comment comment = _findComment(metadata, beginToken);
declarations.add(ast.topLevelVariableDeclaration(
comment, metadata, variableList, semicolon));
}
@override
void beginTypeVariable(Token token) {
debugEvent("beginTypeVariable");
SimpleIdentifier name = pop();
List<Annotation> metadata = pop();
Comment comment = _findComment(metadata, name.beginToken);
push(ast.typeParameter(comment, metadata, name, null, null));
}
@override
void handleTypeVariablesDefined(Token token, int count) {
debugEvent("handleTypeVariablesDefined");
assert(count > 0);
push(popTypedList(count, new List<TypeParameter>(count)));
}
@override
void endTypeVariable(Token token, int index, Token extendsOrSuper) {
debugEvent("TypeVariable");
assert(extendsOrSuper == null ||
optional('extends', extendsOrSuper) ||
optional('super', extendsOrSuper));
TypeAnnotation bound = pop();
// Peek to leave type parameters on top of stack.
List<TypeParameter> typeParameters = peek();
typeParameters[index]
..extendsKeyword = extendsOrSuper
..bound = bound;
}
@override
void endTypeVariables(Token beginToken, Token endToken) {
assert(optional('<', beginToken));
assert(optional('>', endToken));
debugEvent("TypeVariables");
List<TypeParameter> typeParameters = pop();
push(ast.typeParameterList(beginToken, typeParameters, endToken));
}
@override
void beginMethod(Token externalToken, Token staticToken, Token covariantToken,
Token varFinalOrConst, Token name) {
_Modifiers modifiers = new _Modifiers();
if (externalToken != null) {
assert(externalToken.isModifier);
modifiers.externalKeyword = externalToken;
}
if (staticToken != null) {
assert(staticToken.isModifier);
if (name?.lexeme == classDeclaration.name.name) {
// This error is also reported in OutlineBuilder.beginMethod
handleRecoverableError(
messageStaticConstructor, staticToken, staticToken);
} else {
modifiers.staticKeyword = staticToken;
}
}
if (covariantToken != null) {
assert(covariantToken.isModifier);
modifiers.covariantKeyword = covariantToken;
}
if (varFinalOrConst != null) {
assert(varFinalOrConst.isModifier);
modifiers.finalConstOrVarKeyword = varFinalOrConst;
}
push(modifiers);
}
@override
void endMethod(
Token getOrSet, Token beginToken, Token beginParam, Token endToken) {
assert(getOrSet == null ||
optional('get', getOrSet) ||
optional('set', getOrSet));
debugEvent("Method");
var bodyObject = pop();
List<ConstructorInitializer> initializers = pop() ?? const [];
Token separator = pop();
FormalParameterList parameters = pop();
TypeParameterList typeParameters = pop();
var name = pop();
TypeAnnotation returnType = pop();
_Modifiers modifiers = pop();
List<Annotation> metadata = pop();
Comment comment = _findComment(metadata, beginToken);
ConstructorName redirectedConstructor;
FunctionBody body;
if (bodyObject is FunctionBody) {
body = bodyObject;
} else if (bodyObject is _RedirectingFactoryBody) {
separator = bodyObject.equalToken;
redirectedConstructor = bodyObject.constructorName;
body = ast.emptyFunctionBody(endToken);
} else {
unhandled("${bodyObject.runtimeType}", "bodyObject",
beginToken.charOffset, uri);
}
if (parameters == null && (getOrSet == null || optional('set', getOrSet))) {
Token token = typeParameters?.endToken;
if (token == null) {
if (name is AstNode) {
token = name.endToken;
} else if (name is _OperatorName) {
token = name.name.endToken;
} else {
throw new UnimplementedError();
}
}
Token next = token.next;
int offset = next.charOffset;
BeginToken leftParen =
new SyntheticBeginToken(TokenType.OPEN_PAREN, offset);
token.setNext(leftParen);
Token rightParen =
leftParen.setNext(new SyntheticToken(TokenType.CLOSE_PAREN, offset));
leftParen.endGroup = rightParen;
rightParen.setNext(next);
parameters = ast.formalParameterList(
leftParen, <FormalParameter>[], null, null, rightParen);
}
void constructor(
SimpleIdentifier prefixOrName, Token period, SimpleIdentifier name) {
if (modifiers?.constKeyword != null &&
body != null &&
(body.length > 1 || body.beginToken?.lexeme != ';')) {
// This error is also reported in BodyBuilder.finishFunction
Token bodyToken = body.beginToken ?? modifiers.constKeyword;
handleRecoverableError(
messageConstConstructorWithBody, bodyToken, bodyToken);
}
if (returnType != null) {
// This error is also reported in OutlineBuilder.endMethod
handleRecoverableError(messageConstructorWithReturnType,
returnType.beginToken, returnType.beginToken);
}
classDeclaration.members.add(ast.constructorDeclaration(
comment,
metadata,
modifiers?.externalKeyword,
modifiers?.finalConstOrVarKeyword,
null, // TODO(paulberry): factoryKeyword
ast.simpleIdentifier(prefixOrName.token),
period,
name,
parameters,
separator,
initializers,
redirectedConstructor,
body));
}
void method(Token operatorKeyword, SimpleIdentifier name) {
if (modifiers?.constKeyword != null &&
body != null &&
(body.length > 1 || body.beginToken?.lexeme != ';')) {
// This error is also reported in OutlineBuilder.endMethod
handleRecoverableError(
messageConstMethod, modifiers.constKeyword, modifiers.constKeyword);
}
checkFieldFormalParameters(parameters);
classDeclaration.members.add(ast.methodDeclaration(
comment,
metadata,
modifiers?.externalKeyword,
modifiers?.abstractKeyword ?? modifiers?.staticKeyword,
returnType,
getOrSet,
operatorKeyword,
name,
typeParameters,
parameters,
body));
}
if (name is SimpleIdentifier) {
if (name.name == classDeclaration.name.name && getOrSet == null) {
constructor(name, null, null);
} else if (initializers.isNotEmpty) {
constructor(name, null, null);
} else {
method(null, name);
}
} else if (name is _OperatorName) {
method(name.operatorKeyword, name.name);
} else if (name is PrefixedIdentifier) {
constructor(name.prefix, name.period, name.identifier);
} else {
throw new UnimplementedError();
}
}
void checkFieldFormalParameters(FormalParameterList parameters) {
if (parameters?.parameters != null) {
parameters.parameters.forEach((FormalParameter param) {
if (param is FieldFormalParameter) {
// This error is reported in the BodyBuilder.endFormalParameter.
handleRecoverableError(messageFieldInitializerOutsideConstructor,
param.thisKeyword, param.thisKeyword);
}
});
}
}
@override
void handleInvalidMember(Token endToken) {
debugEvent("InvalidMember");
pop(); // metadata star
}
@override
void endMember() {
debugEvent("Member");
}
@override
void handleVoidKeyword(Token voidKeyword) {
assert(optional('void', voidKeyword));
debugEvent("VoidKeyword");
// TODO(paulberry): is this sufficient, or do we need to hook the "void"
// keyword up to an element?
handleIdentifier(voidKeyword, IdentifierContext.typeReference);
handleNoTypeArguments(voidKeyword);
handleType(voidKeyword, voidKeyword);
}
@override
void endFunctionTypeAlias(
Token typedefKeyword, Token equals, Token semicolon) {
assert(optional('typedef', typedefKeyword));
assert(optionalOrNull('=', equals));
assert(optional(';', semicolon));
debugEvent("FunctionTypeAlias");
if (equals == null) {
FormalParameterList parameters = pop();
TypeParameterList typeParameters = pop();
SimpleIdentifier name = pop();
TypeAnnotation returnType = pop();
List<Annotation> metadata = pop();
Comment comment = _findComment(metadata, typedefKeyword);
declarations.add(ast.functionTypeAlias(comment, metadata, typedefKeyword,
returnType, name, typeParameters, parameters, semicolon));
} else {
TypeAnnotation type = pop();
TypeParameterList templateParameters = pop();
SimpleIdentifier name = pop();
List<Annotation> metadata = pop();
Comment comment = _findComment(metadata, typedefKeyword);
if (type is! GenericFunctionType) {
// This error is also reported in the OutlineBuilder.
handleRecoverableError(messageTypedefNotFunction, equals, equals);
type = null;
}
declarations.add(ast.genericTypeAlias(
comment,
metadata,
typedefKeyword,
name,
templateParameters,
equals,
type as GenericFunctionType,
semicolon));
}
}
@override
void endEnum(Token enumKeyword, Token leftBrace, int count) {
assert(optional('enum', enumKeyword));
assert(optional('{', leftBrace));
debugEvent("Enum");
List<EnumConstantDeclaration> constants = popTypedList(count);
SimpleIdentifier name = pop();
List<Annotation> metadata = pop();
Comment comment = _findComment(metadata, enumKeyword);
declarations.add(ast.enumDeclaration(comment, metadata, enumKeyword, name,
leftBrace, constants, leftBrace?.endGroup));
}
@override
void endTypeArguments(int count, Token leftBracket, Token rightBracket) {
assert(optional('<', leftBracket));
assert(optional('>', rightBracket));
debugEvent("TypeArguments");
List<TypeAnnotation> arguments = popTypedList(count);
push(ast.typeArgumentList(leftBracket, arguments, rightBracket));
}
@override
void endFields(Token staticToken, Token covariantToken, Token varFinalOrConst,
int count, Token beginToken, Token semicolon) {
assert(optional(';', semicolon));
debugEvent("Fields");
List<VariableDeclaration> variables = popTypedList(count);
TypeAnnotation type = pop();
_Modifiers modifiers = new _Modifiers()
..staticKeyword = staticToken
..covariantKeyword = covariantToken
..finalConstOrVarKeyword = varFinalOrConst;
var variableList = ast.variableDeclarationList(
null, null, modifiers?.finalConstOrVarKeyword, type, variables);
Token covariantKeyword = modifiers?.covariantKeyword;
List<Annotation> metadata = pop();
Comment comment = _findComment(metadata, beginToken);
classDeclaration.members.add(ast.fieldDeclaration2(
comment: comment,
metadata: metadata,
covariantKeyword: covariantKeyword,
staticKeyword: modifiers?.staticKeyword,
fieldList: variableList,
semicolon: semicolon));
}
@override
AstNode finishFields() {
debugEvent("finishFields");
return classDeclaration != null
? classDeclaration.members.removeAt(classDeclaration.members.length - 1)
: declarations.removeLast();
}
@override
void handleOperatorName(Token operatorKeyword, Token token) {
assert(optional('operator', operatorKeyword));
assert(token.type.isUserDefinableOperator);
debugEvent("OperatorName");
push(new _OperatorName(
operatorKeyword, ast.simpleIdentifier(token, isDeclaration: true)));
}
@override
void handleInvalidOperatorName(Token operatorKeyword, Token token) {
assert(optional('operator', operatorKeyword));
debugEvent("InvalidOperatorName");
push(new _OperatorName(
operatorKeyword, ast.simpleIdentifier(token, isDeclaration: true)));
}
@override
void beginMetadataStar(Token token) {
debugEvent("beginMetadataStar");
}
@override
void endMetadata(Token atSign, Token periodBeforeName, Token endToken) {
assert(optional('@', atSign));
assert(optionalOrNull('.', periodBeforeName));
debugEvent("Metadata");
MethodInvocation invocation = pop();
SimpleIdentifier constructorName = periodBeforeName != null ? pop() : null;
pop(); // Type arguments, not allowed.
Identifier name = pop();
push(ast.annotation(atSign, name, periodBeforeName, constructorName,
invocation?.argumentList));
}
@override
void endMetadataStar(int count) {
debugEvent("MetadataStar");
push(popTypedList<Annotation>(count) ?? NullValue.Metadata);
}
ParameterKind _toAnalyzerParameterKind(FormalParameterKind type) {
if (type == FormalParameterKind.optionalPositional) {
return ParameterKind.POSITIONAL;
} else if (type == FormalParameterKind.optionalNamed) {
return ParameterKind.NAMED;
} else {
return ParameterKind.REQUIRED;
}
}
Comment _findComment(List<Annotation> metadata, Token tokenAfterMetadata) {
Token commentsOnNext = tokenAfterMetadata?.precedingComments;
if (commentsOnNext != null) {
Comment comment = _parseDocumentationCommentOpt(commentsOnNext);
if (comment != null) {
return comment;
}
}
if (metadata != null) {
for (Annotation annotation in metadata) {
Token commentsBeforeAnnotation =
annotation.beginToken.precedingComments;
if (commentsBeforeAnnotation != null) {
Comment comment =
_parseDocumentationCommentOpt(commentsBeforeAnnotation);
if (comment != null) {
return comment;
}
}
}
}
return null;
}
/// Search the given list of [ranges] for a range that contains the given
/// [index]. Return the range that was found, or `null` if none of the ranges
/// contain the index.
List<int> _findRange(List<List<int>> ranges, int index) {
int rangeCount = ranges.length;
for (int i = 0; i < rangeCount; i++) {
List<int> range = ranges[i];
if (range[0] <= index && index <= range[1]) {
return range;
} else if (index < range[0]) {
return null;
}
}
return null;
}
/// Return a list of the ranges of characters in the given [comment] that
/// should be treated as code blocks.
List<List<int>> _getCodeBlockRanges(String comment) {
List<List<int>> ranges = <List<int>>[];
int length = comment.length;
if (length < 3) {
return ranges;
}
int index = 0;
int firstChar = comment.codeUnitAt(0);
if (firstChar == 0x2F) {
int secondChar = comment.codeUnitAt(1);
int thirdChar = comment.codeUnitAt(2);
if ((secondChar == 0x2A && thirdChar == 0x2A) ||
(secondChar == 0x2F && thirdChar == 0x2F)) {
index = 3;
}
}
if (comment.startsWith(' ', index)) {
int end = index + 4;
while (end < length &&
comment.codeUnitAt(end) != 0xD &&
comment.codeUnitAt(end) != 0xA) {
end = end + 1;
}
ranges.add(<int>[index, end]);
index = end;
}
while (index < length) {
int currentChar = comment.codeUnitAt(index);
if (currentChar == 0xD || currentChar == 0xA) {
index = index + 1;
while (index < length &&
Character.isWhitespace(comment.codeUnitAt(index))) {
index = index + 1;
}
if (comment.startsWith(' ', index)) {
int end = index + 6;
while (end < length &&
comment.codeUnitAt(end) != 0xD &&
comment.codeUnitAt(end) != 0xA) {
end = end + 1;
}
ranges.add(<int>[index, end]);
index = end;
}
} else if (index + 1 < length &&
currentChar == 0x5B &&
comment.codeUnitAt(index + 1) == 0x3A) {
int end = comment.indexOf(':]', index + 2);
if (end < 0) {
end = length;
}
ranges.add(<int>[index, end]);
index = end + 1;
} else {
index = index + 1;
}
}
return ranges;
}
///
/// Given that we have just found bracketed text within the given [comment],
/// look to see whether that text is (a) followed by a parenthesized link
/// address, (b) followed by a colon, or (c) followed by optional whitespace
/// and another square bracket. The [rightIndex] is the index of the right
/// bracket. Return `true` if the bracketed text is followed by a link
/// address.
///
/// This method uses the syntax described by the
/// <a href="http://daringfireball.net/projects/markdown/syntax">markdown</a>
/// project.
bool _isLinkText(String comment, int rightIndex) {
int length = comment.length;
int index = rightIndex + 1;
if (index >= length) {
return false;
}
int nextChar = comment.codeUnitAt(index);
if (nextChar == 0x28 || nextChar == 0x3A) {
return true;
}
while (Character.isWhitespace(nextChar)) {
index = index + 1;
if (index >= length) {
return false;
}
nextChar = comment.codeUnitAt(index);
}
return nextChar == 0x5B;
}
/// Parse a comment reference from the source between square brackets. The
/// [referenceSource] is the source occurring between the square brackets
/// within a documentation comment. The [sourceOffset] is the offset of the
/// first character of the reference source. Return the comment reference that
/// was parsed, or `null` if no reference could be found.
/// ```
/// commentReference ::=
/// 'new'? prefixedIdentifier
/// ```
CommentReference _parseCommentReference(
String referenceSource, int sourceOffset) {
// TODO(brianwilkerson) The errors are not getting the right offset/length
// and are being duplicated.
void offsetTokens(Token token) {
while (token.type != TokenType.EOF) {
token.offset = token.offset + sourceOffset;
token = token.next;
}
}
try {
BooleanErrorListener listener = new BooleanErrorListener();
ScannerResult result = scanString(referenceSource);
Token firstToken = result.tokens;
offsetTokens(firstToken);
if (listener.errorReported) {
return null;
}
if (firstToken.type == TokenType.EOF) {
Token syntheticToken =
new SyntheticStringToken(TokenType.IDENTIFIER, "", sourceOffset);
syntheticToken.setNext(firstToken);
return ast.commentReference(null, ast.simpleIdentifier(syntheticToken));
}
Token newKeyword = null;
if (_tokenMatchesKeyword(firstToken, Keyword.NEW)) {
newKeyword = firstToken;
firstToken = firstToken.next;
}
if (firstToken.isUserDefinableOperator) {
if (firstToken.next.type != TokenType.EOF) {
return null;
}
Identifier identifier = ast.simpleIdentifier(firstToken);
return ast.commentReference(null, identifier);
} else if (_tokenMatchesKeyword(firstToken, Keyword.OPERATOR)) {
Token secondToken = firstToken.next;
if (secondToken.isUserDefinableOperator) {
if (secondToken.next.type != TokenType.EOF) {
return null;
}
Identifier identifier = ast.simpleIdentifier(secondToken);
return ast.commentReference(null, identifier);
}
return null;
} else if (_tokenMatchesIdentifier(firstToken)) {
Token secondToken = firstToken.next;
Token thirdToken = secondToken.next;
Token nextToken;
Identifier identifier;
if (_tokenMatches(secondToken, TokenType.PERIOD)) {
if (thirdToken.isUserDefinableOperator) {
identifier = ast.prefixedIdentifier(
ast.simpleIdentifier(firstToken),
secondToken,
ast.simpleIdentifier(thirdToken));
nextToken = thirdToken.next;
} else if (_tokenMatchesKeyword(thirdToken, Keyword.OPERATOR)) {
Token fourthToken = thirdToken.next;
if (fourthToken.isUserDefinableOperator) {
identifier = ast.prefixedIdentifier(
ast.simpleIdentifier(firstToken),
secondToken,
ast.simpleIdentifier(fourthToken));
nextToken = fourthToken.next;
} else {
return null;
}
} else if (_tokenMatchesIdentifier(thirdToken)) {
identifier = ast.prefixedIdentifier(
ast.simpleIdentifier(firstToken),
secondToken,
ast.simpleIdentifier(thirdToken));
nextToken = thirdToken.next;
}
} else {
identifier = ast.simpleIdentifier(firstToken);
nextToken = firstToken.next;
}
if (nextToken.type != TokenType.EOF) {
return null;
}
return ast.commentReference(newKeyword, identifier);
} else {
Keyword keyword = firstToken.keyword;
if (keyword == Keyword.THIS ||
keyword == Keyword.NULL ||
keyword == Keyword.TRUE ||
keyword == Keyword.FALSE) {
// TODO(brianwilkerson) If we want to support this we will need to
// extend the definition of CommentReference to take an expression
// rather than an identifier. For now we just ignore it to reduce the
// number of errors produced, but that's probably not a valid long
// term approach.
return null;
}
}
} catch (exception) {
// Ignored because we assume that it wasn't a real comment reference.
}
return null;
}
/// Parse all of the comment references occurring in the given array of
/// documentation comments. The [tokens] are the comment tokens representing
/// the documentation comments to be parsed. Return the comment references that
/// were parsed.
/// ```
/// commentReference ::=
/// '[' 'new'? qualified ']' libraryReference?
///
/// libraryReference ::=
/// '(' stringLiteral ')'
/// ```
List<CommentReference> _parseCommentReferences(List<Token> tokens) {
List<CommentReference> references = <CommentReference>[];
bool isInGitHubCodeBlock = false;
for (Token token in tokens) {
String comment = token.lexeme;
// Skip GitHub code blocks.
// https://help.github.com/articles/creating-and-highlighting-code-blocks/
if (tokens.length != 1) {
if (comment.indexOf('```') != -1) {
isInGitHubCodeBlock = !isInGitHubCodeBlock;
}
if (isInGitHubCodeBlock) {
continue;
}
}
// Remove GitHub include code.
comment = _removeGitHubInlineCode(comment);
// Find references.
int length = comment.length;
List<List<int>> codeBlockRanges = _getCodeBlockRanges(comment);
int leftIndex = comment.indexOf('[');
while (leftIndex >= 0 && leftIndex + 1 < length) {
List<int> range = _findRange(codeBlockRanges, leftIndex);
if (range == null) {
int nameOffset = token.offset + leftIndex + 1;
int rightIndex = comment.indexOf(']', leftIndex);
if (rightIndex >= 0) {
int firstChar = comment.codeUnitAt(leftIndex + 1);
if (firstChar != 0x27 && firstChar != 0x22) {
if (_isLinkText(comment, rightIndex)) {
// TODO(brianwilkerson) Handle the case where there's a library
// URI in the link text.
} else {
CommentReference reference = _parseCommentReference(
comment.substring(leftIndex + 1, rightIndex), nameOffset);
if (reference != null) {
references.add(reference);
}
}
}
} else {
// terminating ']' is not typed yet
int charAfterLeft = comment.codeUnitAt(leftIndex + 1);
Token nameToken;
if (Character.isLetterOrDigit(charAfterLeft)) {
int nameEnd = StringUtilities.indexOfFirstNotLetterDigit(
comment, leftIndex + 1);
String name = comment.substring(leftIndex + 1, nameEnd);
nameToken =
new StringToken(TokenType.IDENTIFIER, name, nameOffset);
} else {
nameToken = new SyntheticStringToken(
TokenType.IDENTIFIER, '', nameOffset);
}
nameToken.setNext(new Token.eof(nameToken.end));
references.add(
ast.commentReference(null, ast.simpleIdentifier(nameToken)));
// next character
rightIndex = leftIndex + 1;
}
leftIndex = comment.indexOf('[', rightIndex);
} else {
leftIndex = comment.indexOf('[', range[1]);
}
}
}
return references;
}
/// Parse a documentation comment. Return the documentation comment that was
/// parsed, or `null` if there was no comment.
Comment _parseDocumentationCommentOpt(Token commentToken) {
List<Token> tokens = <Token>[];
while (commentToken != null) {
if (commentToken.lexeme.startsWith('/**') ||
commentToken.lexeme.startsWith('///')) {
if (tokens.isNotEmpty) {
if (commentToken.type == TokenType.SINGLE_LINE_COMMENT) {
if (tokens[0].type != TokenType.SINGLE_LINE_COMMENT) {
tokens.clear();
}
} else {
tokens.clear();
}
}
tokens.add(commentToken);
}
commentToken = commentToken.next;
}
List<CommentReference> references = _parseCommentReferences(tokens);
return tokens.isEmpty ? null : ast.documentationComment(tokens, references);
}
/// Remove any substrings in the given [comment] that represent in-line code
/// in markdown.
String _removeGitHubInlineCode(String comment) {
int index = 0;
while (true) {
int beginIndex = comment.indexOf('`', index);
if (beginIndex == -1) {
break;
}
int endIndex = comment.indexOf('`', beginIndex + 1);
if (endIndex == -1) {
break;
}
comment = comment.substring(0, beginIndex + 1) +
' ' * (endIndex - beginIndex - 1) +
comment.substring(endIndex);
index = endIndex + 1;
}
return comment;
}
/// Return `true` if the given [token] has the given [type].
bool _tokenMatches(Token token, TokenType type) => token.type == type;
/// Return `true` if the given [token] is a valid identifier. Valid
/// identifiers include built-in identifiers (pseudo-keywords).
bool _tokenMatchesIdentifier(Token token) =>
_tokenMatches(token, TokenType.IDENTIFIER) ||
_tokenMatchesPseudoKeyword(token);
/// Return `true` if the given [token] matches the given [keyword].
bool _tokenMatchesKeyword(Token token, Keyword keyword) =>
token.keyword == keyword;
/// Return `true` if the given [token] matches a pseudo keyword.
bool _tokenMatchesPseudoKeyword(Token token) =>
token.keyword?.isBuiltInOrPseudo ?? false;
@override
void debugEvent(String name) {
// printEvent('AstBuilder: $name');
}
@override
void discardTypeReplacedWithCommentTypeAssign() {
pop();
}
@override
void addCompileTimeError(Message message, int offset, int length) {
if (directives.isEmpty &&
message.code.analyzerCode == 'NON_PART_OF_DIRECTIVE_IN_PART') {
message = messageDirectiveAfterDeclaration;
}
errorReporter.reportMessage(message, offset, length);