UNPKG

traceur

Version:
1,196 lines (1,095 loc) 32.6 kB
// Copyright 2012 Traceur Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import {NewExpression} from '../syntax/trees/ParseTrees.js'; import {ParseTreeVisitor} from './ParseTreeVisitor.js'; import {TreeWriter} from '../outputgeneration/TreeWriter.js'; import { AMPERSAND, AMPERSAND_EQUAL, AND, BAR, BAR_EQUAL, CARET, CARET_EQUAL, CLOSE_ANGLE, EQUAL, EQUAL_EQUAL, EQUAL_EQUAL_EQUAL, GREATER_EQUAL, IDENTIFIER, IN, INSTANCEOF, LEFT_SHIFT, LEFT_SHIFT_EQUAL, LESS_EQUAL, MINUS, MINUS_EQUAL, NOT_EQUAL, NOT_EQUAL_EQUAL, NUMBER, OPEN_ANGLE, OR, PERCENT, PERCENT_EQUAL, PLUS, PLUS_EQUAL, RIGHT_SHIFT, RIGHT_SHIFT_EQUAL, SLASH, SLASH_EQUAL, STAR, STAR_EQUAL, STAR_STAR, STAR_STAR_EQUAL, STRING, UNSIGNED_RIGHT_SHIFT, UNSIGNED_RIGHT_SHIFT_EQUAL, YIELD } from './TokenType.js'; import { ARRAY_PATTERN, ASSIGNMENT_ELEMENT, BINDING_ELEMENT, BINDING_IDENTIFIER, BLOCK, CASE_CLAUSE, CATCH, CLASS_DECLARATION, COMPUTED_PROPERTY_NAME, DEFAULT_CLAUSE, EXPORT_DEFAULT, EXPORT_SPECIFIER, EXPORT_SPECIFIER_SET, EXPORT_STAR, FINALLY, FORMAL_PARAMETER, FORMAL_PARAMETER_LIST, FORWARD_DEFAULT_EXPORT, FUNCTION_BODY, FUNCTION_DECLARATION, GET_ACCESSOR, IDENTIFIER_EXPRESSION, IMPORTED_BINDING, IMPORT_CLAUSE_PAIR, IMPORT_SPECIFIER_SET, IMPORT_TYPE_CLAUSE, JSX_ATTRIBUTE, JSX_ELEMENT_NAME, JSX_ELEMENT, JSX_PLACEHOLDER, JSX_SPREAD_ATTRIBUTE, JSX_TEXT, LITERAL_PROPERTY_NAME, METHOD, MODULE_SPECIFIER, NAMED_EXPORT, NAME_SPACE_EXPORT, NAME_SPACE_IMPORT, OBJECT_PATTERN, OBJECT_PATTERN_FIELD, PROPERTY_NAME_ASSIGNMENT, PROPERTY_NAME_SHORTHAND, PROPERTY_VARIABLE_DECLARATION, REST_PARAMETER, SET_ACCESSOR, SPREAD_EXPRESSION, TEMPLATE_LITERAL_PORTION, TEMPLATE_SUBSTITUTION, TYPE_ALIAS_DECLARATION, TYPE_ARGUMENTS, TYPE_NAME, TYPE_PARAMETER, TYPE_PARAMETERS, VARIABLE_DECLARATION_LIST, VARIABLE_STATEMENT } from './trees/ParseTreeType.js'; import {assert} from '../util/assert.js'; /* TODO: add contextual information to the validator so we can check non-local grammar rules, such as: * operator precedence * expressions with or without "in" * return statements must be in a function * break must be enclosed in loops or switches * continue must be enclosed in loops * function declarations must have non-null names (optional for function expressions) */ /** * An error thrown when an invalid parse tree is encountered. This error is * used internally to distinguish between errors in the Validator itself vs * errors it threw to unwind the call stack. * * @param {ParseTree} tree * @param {string} message */ class ValidationError extends Error { constructor(tree, message) { super(); this.tree = tree; this.message = message; } } /** * Validates a parse tree */ export class ParseTreeValidator extends ParseTreeVisitor { /** * @param {ParseTree} tree * @param {string} message */ fail_(tree, message) { throw new ValidationError(tree, message); } /** * @param {boolean} condition * @param {ParseTree} tree * @param {string} message */ check_(condition, tree, message) { if (!condition) { this.fail_(tree, message); } } /** * @param {boolean} condition * @param {ParseTree} tree * @param {string} message */ checkVisit_(condition, tree, message) { this.check_(condition, tree, message); this.visitAny(tree); } /** * @param {ParseTreeType} type * @param {ParseTree} tree * @param {string} message */ checkType_(type, tree, message) { this.checkVisit_(tree.type === type, tree, message); } /** * @param {ArgumentList} tree */ visitArgumentList(tree) { for (let i = 0; i < tree.args.length; i++) { let argument = tree.args[i]; this.checkVisit_(argument.isAssignmentOrSpread(), argument, 'assignment or spread expected'); } } /** * @param {ArrayLiteral} tree */ visitArrayLiteral(tree) { for (let i = 0; i < tree.elements.length; i++) { let element = tree.elements[i]; this.checkVisit_(element === null || element.isAssignmentOrSpread(), element, 'assignment or spread expected'); } } /** * @param {ArrayPattern} tree */ visitArrayPattern(tree) { for (let i = 0; i < tree.elements.length; i++) { let element = tree.elements[i]; this.checkVisit_(element === null || element.type === BINDING_ELEMENT || element.type === ASSIGNMENT_ELEMENT || element.isLeftHandSideExpression() || element.isPattern() || element.isSpreadPatternElement(), element, 'null, sub pattern, left hand side expression or spread expected'); if (element && element.isSpreadPatternElement()) { this.check_(i === (tree.elements.length - 1), element, 'spread in array patterns must be the last element'); } } } /** * @param {BinaryExpression} tree */ visitBinaryExpression(tree) { switch (tree.operator.type) { // assignment case EQUAL: case STAR_EQUAL: case STAR_STAR_EQUAL: case SLASH_EQUAL: case PERCENT_EQUAL: case PLUS_EQUAL: case MINUS_EQUAL: case LEFT_SHIFT_EQUAL: case RIGHT_SHIFT_EQUAL: case UNSIGNED_RIGHT_SHIFT_EQUAL: case AMPERSAND_EQUAL: case CARET_EQUAL: case BAR_EQUAL: this.check_(tree.left.isLeftHandSideExpression() || tree.left.isPattern(), tree.left, 'left hand side expression or pattern expected'); this.check_(tree.right.isAssignmentExpression(), tree.right, 'assignment expression expected'); break; // logical case AND: case OR: case BAR: case CARET: case AMPERSAND: // equality case EQUAL_EQUAL: case NOT_EQUAL: case EQUAL_EQUAL_EQUAL: case NOT_EQUAL_EQUAL: // relational case OPEN_ANGLE: case CLOSE_ANGLE: case GREATER_EQUAL: case LESS_EQUAL: case INSTANCEOF: case IN: // shift case LEFT_SHIFT: case RIGHT_SHIFT: case UNSIGNED_RIGHT_SHIFT: // additive case PLUS: case MINUS: // multiplicative case STAR: case SLASH: case PERCENT: // exponentiation case STAR_STAR: this.check_(tree.left.isAssignmentExpression(), tree.left, 'assignment expression expected'); this.check_(tree.right.isAssignmentExpression(), tree.right, 'assignment expression expected'); break; default: this.fail_(tree, 'unexpected binary operator'); } this.visitAny(tree.left); this.visitAny(tree.right); } /** * @param {BindingElement} tree */ visitBindingElement(tree) { let binding = tree.binding; this.checkVisit_( binding.type === BINDING_IDENTIFIER || binding.type === OBJECT_PATTERN || binding.type === ARRAY_PATTERN, binding, 'expected valid binding element'); this.visitAny(tree.initializer); } /** * @param {AssignmentElement} tree */ visitAssignmentElement(tree) { let assignment = tree.assignment; this.checkVisit_( assignment.type === OBJECT_PATTERN || assignment.type === ARRAY_PATTERN || assignment.isLeftHandSideExpression(), assignment, 'expected valid assignment element'); this.visitAny(tree.initializer); } /** * @param {Block} tree */ visitBlock(tree) { for (let i = 0; i < tree.statements.length; i++) { let statement = tree.statements[i]; this.checkVisit_(statement.isStatementListItem(), statement, 'statement or function declaration expected'); } } /** * @param {CallExpression} tree */ visitCallExpression(tree) { this.check_(tree.operand.isMemberExpression(), tree.operand, 'member expression expected'); if (tree.operand instanceof NewExpression) { this.check_(tree.operand.args !== null, tree.operand, 'new args expected'); } this.visitAny(tree.operand); this.visitAny(tree.args); } /** * @param {CaseClause} tree */ visitCaseClause(tree) { this.checkVisit_(tree.expression.isExpression(), tree.expression, 'expression expected'); for (let i = 0; i < tree.statements.length; i++) { let statement = tree.statements[i]; this.checkVisit_(statement.isStatementListItem(), statement, 'statement expected'); } } /** * @param {Catch} tree */ visitCatch(tree) { this.checkVisit_(tree.binding.isPattern() || tree.binding.type === BINDING_IDENTIFIER, tree.binding, 'binding identifier expected'); this.checkVisit_(tree.catchBody.type === BLOCK, tree.catchBody, 'block expected'); } /** * @param {ClassDeclaration} tree */ visitClassDeclaration(tree) { this.visitClassShared_(tree); } /** * @param {ClassExpression} tree */ visitClassExpression(tree) { this.visitClassShared_(tree); } visitClassShared_(tree) { if (tree.typeParameters) { this.checkVisit_( tree.typeParameters.type === TYPE_PARAMETERS, tree.typeParameters, 'type parameters expected'); } for (let i = 0; i < tree.elements.length; i++) { let element = tree.elements[i]; switch (element.type) { case GET_ACCESSOR: case SET_ACCESSOR: case METHOD: case PROPERTY_VARIABLE_DECLARATION: break; default: this.fail_(element, 'class element expected'); } this.visitAny(element); } } /** * @param {CommaExpression} tree */ visitCommaExpression(tree) { for (let i = 0; i < tree.expressions.length; i++) { let expression = tree.expressions[i]; this.checkVisit_(expression.isExpression(), expression, 'expression expected'); } } /** * @param {ConditionalExpression} tree */ visitConditionalExpression(tree) { this.checkVisit_(tree.condition.isAssignmentExpression(), tree.condition, 'expression expected'); this.checkVisit_(tree.left.isAssignmentExpression(), tree.left, 'expression expected'); this.checkVisit_(tree.right.isAssignmentExpression(), tree.right, 'expression expected'); } visitCoverFormals(tree) { this.fail_(tree, 'CoverFormals should have been removed'); } visitCoverInitializedName(tree) { this.fail_(tree, 'CoverInitializedName should have been removed'); } /** * @param {DefaultClause} tree */ visitDefaultClause(tree) { for (let i = 0; i < tree.statements.length; i++) { let statement = tree.statements[i]; this.checkVisit_(statement.isStatementListItem(), statement, 'statement expected'); } } /** * @param {DoWhileStatement} tree */ visitDoWhileStatement(tree) { this.checkVisit_(tree.body.isStatement(), tree.body, 'statement expected'); this.checkVisit_(tree.condition.isExpression(), tree.condition, 'expression expected'); } /** * @param {ExportDeclaration} tree */ visitExportDeclaration(tree) { let declType = tree.declaration.type; this.checkVisit_( declType === VARIABLE_STATEMENT || declType === FUNCTION_DECLARATION || declType === CLASS_DECLARATION || declType === NAMED_EXPORT || declType === EXPORT_DEFAULT || declType === TYPE_ALIAS_DECLARATION, tree.declaration, 'expected valid export tree'); } /** * @param {NamedExport} tree */ visitNamedExport(tree) { let specifierType = tree.exportClause.type; this.checkVisit_(specifierType === EXPORT_SPECIFIER || specifierType === EXPORT_SPECIFIER_SET || specifierType === EXPORT_STAR || specifierType === FORWARD_DEFAULT_EXPORT || specifierType === NAME_SPACE_EXPORT, tree.exportClause, 'Invalid export clause'); if (tree.moduleSpecifier) { this.checkVisit_( tree.moduleSpecifier.type === MODULE_SPECIFIER, tree.moduleSpecifier, 'module expression expected'); } } /** * @param {ExportSpecifierSet} tree */ visitExportSpecifierSet(tree) { this.check_(tree.specifiers.length > 0, tree, 'expected at least one identifier'); for (let i = 0; i < tree.specifiers.length; i++) { let specifier = tree.specifiers[i]; this.checkVisit_( specifier.type === EXPORT_SPECIFIER || specifier.type === IDENTIFIER_EXPRESSION, specifier, 'expected valid export specifier'); } } /** * @param {ExpressionStatement} tree */ visitExpressionStatement(tree) { this.checkVisit_(tree.expression.isExpression(), tree.expression, 'expression expected'); } /** * @param {Finally} tree */ visitFinally(tree) { this.checkVisit_(tree.block.type === BLOCK, tree.block, 'block expected'); } /** * @param {ForOfStatement} tree */ visitForOfStatement(tree) { this.checkVisit_( tree.initializer.isPattern() || tree.initializer.type === IDENTIFIER_EXPRESSION || tree.initializer.type === VARIABLE_DECLARATION_LIST && tree.initializer.declarations.length === 1, tree.initializer, 'for-each statement may not have more than one variable declaration'); this.checkVisit_(tree.collection.isExpression(), tree.collection, 'expression expected'); this.checkVisit_(tree.body.isStatement(), tree.body, 'statement expected'); } /** * @param {ForInStatement} tree */ visitForInStatement(tree) { if (tree.initializer.type === VARIABLE_DECLARATION_LIST) { this.checkVisit_( tree.initializer.declarations.length <= 1, tree.initializer, 'for-in statement may not have more than one variable declaration'); } else { this.checkVisit_(tree.initializer.isPattern() || tree.initializer.isExpression(), tree.initializer, 'variable declaration, expression or ' + 'pattern expected'); } this.checkVisit_(tree.collection.isExpression(), tree.collection, 'expression expected'); this.checkVisit_(tree.body.isStatement(), tree.body, 'statement expected'); } /** * @param {FormalParameterList} tree */ visitFormalParameterList(tree) { for (let i = 0; i < tree.parameters.length; i++) { let parameter = tree.parameters[i]; assert(parameter.type === FORMAL_PARAMETER) parameter = parameter.parameter; switch (parameter.type) { case BINDING_ELEMENT: break; case REST_PARAMETER: this.checkVisit_( i === tree.parameters.length - 1, parameter, 'rest parameters must be the last parameter in a parameter list'); this.checkType_(BINDING_IDENTIFIER, parameter.identifier, 'binding identifier expected'); break; default: this.fail_(parameter, 'parameters must be identifiers or rest' + ` parameters. Found: ${parameter.type}`); break; } this.visitAny(parameter); } } /** * @param {ForStatement} tree */ visitForStatement(tree) { if (tree.initializer !== null) { this.checkVisit_( tree.initializer.isExpression() || tree.initializer.type === VARIABLE_DECLARATION_LIST, tree.initializer, 'variable declaration list or expression expected'); } if (tree.condition !== null) { this.checkVisit_(tree.condition.isExpression(), tree.condition, 'expression expected'); } if (tree.increment !== null) { this.checkVisit_(tree.increment.isExpression(), tree.increment, 'expression expected'); } this.checkVisit_(tree.body.isStatement(), tree.body, 'statement expected'); } /** * @param {Block} tree */ visitFunctionBody(tree) { for (let i = 0; i < tree.statements.length; i++) { let statement = tree.statements[i]; this.checkVisit_(statement.isStatementListItem(), statement, 'statement expected'); } } /** * @param {FunctionDeclaration} tree */ visitFunctionDeclaration(tree) { this.checkType_(BINDING_IDENTIFIER, tree.name, 'binding identifier expected'); this.visitFunction_(tree); } /** * @param {FunctionExpression} tree */ visitFunctionExpression(tree) { if (tree.name !== null) { this.checkType_(BINDING_IDENTIFIER, tree.name, 'binding identifier expected'); } this.visitFunction_(tree); } visitFunction_(tree) { this.checkType_(FORMAL_PARAMETER_LIST, tree.parameterList, 'formal parameters expected'); this.checkType_(FUNCTION_BODY, tree.body, 'function body expected'); } /** * @param {GetAccessor} tree */ visitGetAccessor(tree) { this.checkPropertyName_(tree.name); this.checkType_(FUNCTION_BODY, tree.body, 'function body expected'); } /** * @param {IfStatement} tree */ visitIfStatement(tree) { this.checkVisit_(tree.condition.isExpression(), tree.condition, 'expression expected'); this.checkVisit_(tree.ifClause.isStatement(), tree.ifClause, 'statement expected'); if (tree.elseClause !== null) { this.checkVisit_(tree.elseClause.isStatement(), tree.elseClause, 'statement expected'); } } visitImportDeclaration(tree) { if (tree.importClause !== null) { this.check_(tree.importClause.type === NAME_SPACE_IMPORT || tree.importClause.type === IMPORTED_BINDING || tree.importClause.type === IMPORT_SPECIFIER_SET || tree.importClause.type === IMPORT_CLAUSE_PAIR || tree.importClause.type === IMPORT_TYPE_CLAUSE, tree.importClause, 'Invalid import clause'); } this.checkType_(MODULE_SPECIFIER, tree.moduleSpecifier, 'module specifier expected'); } visitImportSpecifier(tree) { this.checkType_(IMPORTED_BINDING, tree.binding, 'ImportedBinding expected'); } visitImportedBinding(tree) { this.checkType_(BINDING_IDENTIFIER, tree.binding, 'binding identifier expected'); } visitImportClausePair(tree) { this.checkType_(IMPORTED_BINDING, tree.first, 'ImportedBinding expected'); this.check_(tree.second.type === NAME_SPACE_IMPORT || tree.second.type === IMPORT_SPECIFIER_SET, tree.second, 'Invalid import clause'); } visitJsxElement(tree) { this.checkType_(JSX_ELEMENT_NAME, tree.name, 'JSX Element Name expected'); for (let i = 0; i < tree.attributes.length; i++) { let attr = tree.attributes[i]; this.checkVisit_(attr.type === JSX_ATTRIBUTE || attr.type === JSX_SPREAD_ATTRIBUTE, attr, 'JSX Attribute expected'); } for (let i = 0; i < tree.children.length; i++) { let child = tree.children[i]; this.checkVisit_(child.type === JSX_ELEMENT || child.type === JSX_PLACEHOLDER || child.type === JSX_TEXT, child, 'JSX child expected'); } } /** * @param {LabelledStatement} tree */ visitLabelledStatement(tree) { this.checkVisit_(tree.statement.isStatement(), tree.statement, 'statement expected'); } /** * @param {MemberExpression} tree */ visitMemberExpression(tree) { this.check_(tree.operand.isMemberExpression(), tree.operand, 'member expression expected'); if (tree.operand instanceof NewExpression) { this.check_(tree.operand.args !== null, tree.operand, 'new args expected'); } this.visitAny(tree.operand); } /** * @param {MemberLookupExpression} tree */ visitMemberLookupExpression(tree) { this.check_(tree.operand.isMemberExpression(), tree.operand, 'member expression expected'); if (tree.operand instanceof NewExpression) { this.check_(tree.operand.args !== null, tree.operand, 'new args expected'); } this.visitAny(tree.operand); } /** * @param {SyntaxErrorTree} tree */ visitSyntaxErrorTree(tree) { this.fail_(tree, `parse tree contains SyntaxError: ${tree.message}`); } /** * @param {ModuleSpecifier} tree */ visitModuleSpecifier(tree) { this.check_(tree.token.type === STRING, tree, 'string or identifier expected'); } /** * @param {NewExpression} tree */ visitNewExpression(tree) { this.checkVisit_(tree.operand.isMemberExpression(), tree.operand, 'member expression expected'); this.visitAny(tree.args); } /** * @param {ObjectLiteral} tree */ visitObjectLiteral(tree) { for (let i = 0; i < tree.propertyNameAndValues.length; i++) { let propertyNameAndValue = tree.propertyNameAndValues[i]; switch (propertyNameAndValue.type) { case GET_ACCESSOR: case SET_ACCESSOR: case METHOD: this.check_(!propertyNameAndValue.isStatic, propertyNameAndValue, 'static is not allowed in object literal expression'); break; case PROPERTY_NAME_ASSIGNMENT: case PROPERTY_NAME_SHORTHAND: case SPREAD_EXPRESSION: break; default: this.fail_(propertyNameAndValue, 'accessor, property name ' + 'assignment or property method assigment expected'); } this.visitAny(propertyNameAndValue); } } /** * @param {ObjectPattern} tree */ visitObjectPattern(tree) { for (let i = 0; i < tree.fields.length; i++) { let field = tree.fields[i]; this.checkVisit_(field.type === OBJECT_PATTERN_FIELD || field.type === ASSIGNMENT_ELEMENT || field.type === BINDING_ELEMENT, field, 'object pattern field expected'); } } /** * @param {ObjectPatternField} tree */ visitObjectPatternField(tree) { this.checkPropertyName_(tree.name); this.checkVisit_(tree.element.type === ASSIGNMENT_ELEMENT || tree.element.type === BINDING_ELEMENT || tree.element.isPattern() || tree.element.isLeftHandSideExpression(), tree.element, 'binding element expected'); } /** * @param {ParenExpression} tree */ visitParenExpression(tree) { if (tree.expression.isPattern()) { this.visitAny(tree.expression); } else { this.checkVisit_(tree.expression.isExpression(), tree.expression, 'expression expected'); } } /** * @param {PostfixExpression} tree */ visitPostfixExpression(tree) { this.checkVisit_(tree.operand.isAssignmentExpression(), tree.operand, 'assignment expression expected'); } /** * @param {PredefinedType} tree */ visitPredefinedType(tree) { // TODO(peterhal): Implement. } /** * @param {Script} tree */ visitScript(tree) { for (let i = 0; i < tree.scriptItemList.length; i++) { let scriptItemList = tree.scriptItemList[i]; this.checkVisit_(scriptItemList.isScriptElement(), scriptItemList, 'global script item expected'); } } checkPropertyName_(tree) { this.checkVisit_( tree.type === LITERAL_PROPERTY_NAME || tree.type === COMPUTED_PROPERTY_NAME, tree, 'property name expected'); } /** * @param {PropertyNameAssignment} tree */ visitPropertyNameAssignment(tree) { this.checkPropertyName_(tree.name); this.checkVisit_(tree.value.isAssignmentExpression(), tree.value, 'assignment expression expected'); } /** * @param {PropertyNameShorthand} tree */ visitPropertyNameShorthand(tree) { this.check_(tree.name.type === IDENTIFIER || tree.name.type === YIELD || tree.name.isStrictKeyword(), tree, 'identifier token expected'); } /** * @param {LiteralPropertyName} tree */ visitLiteralPropertyName(tree) { let type = tree.literalToken.type; this.check_(tree.literalToken.isKeyword() || type === IDENTIFIER || type === NUMBER || type === STRING, tree, 'Unexpected token in literal property name'); } /** * @param {TemplateLiteralExpression} tree */ visitTemplateLiteralExpression(tree) { if (tree.operand) { this.checkVisit_(tree.operand.isMemberExpression(), tree.operand, 'member or call expression expected'); } // The elements are alternating between TemplateLiteralPortion and // TemplateSubstitution. for (let i = 0; i < tree.elements.length; i++) { let element = tree.elements[i]; if (i % 2) { this.checkType_(TEMPLATE_SUBSTITUTION, element, 'Template literal substitution expected'); } else { this.checkType_(TEMPLATE_LITERAL_PORTION, element, 'Template literal portion expected'); } } } /** * @param {ReturnStatement} tree */ visitReturnStatement(tree) { if (tree.expression !== null) { this.checkVisit_(tree.expression.isExpression(), tree.expression, 'expression expected'); } } /** * @param {SetAccessor} tree */ visitSetAccessor(tree) { this.checkPropertyName_(tree.name); this.checkType_(FUNCTION_BODY, tree.body, 'function body expected'); } /** * @param {SpreadExpression} tree */ visitSpreadExpression(tree) { this.checkVisit_(tree.expression.isAssignmentExpression(), tree.expression, 'assignment expression expected'); } /** * @param {StateMachine} tree */ visitStateMachine(tree) { this.fail_(tree, 'State machines are never valid outside of the ' + 'GeneratorTransformer pass.'); } /** * @param {SwitchStatement} tree */ visitSwitchStatement(tree) { this.checkVisit_(tree.expression.isExpression(), tree.expression, 'expression expected'); let defaultCount = 0; for (let i = 0; i < tree.caseClauses.length; i++) { let caseClause = tree.caseClauses[i]; if (caseClause.type === DEFAULT_CLAUSE) { ++defaultCount; this.checkVisit_(defaultCount <= 1, caseClause, 'no more than one default clause allowed'); } else { this.checkType_(CASE_CLAUSE, caseClause, 'case or default clause expected'); } } } /** * @param {ThrowStatement} tree */ visitThrowStatement(tree) { if (tree.value === null) { return; } this.checkVisit_(tree.value.isExpression(), tree.value, 'expression expected'); } /** * @param {TryStatement} tree */ visitTryStatement(tree) { this.checkType_(BLOCK, tree.body, 'block expected'); if (tree.catchBlock !== null) { this.checkType_(CATCH, tree.catchBlock, 'catch block expected'); } if (tree.finallyBlock !== null) { this.checkType_(FINALLY, tree.finallyBlock, 'finally block expected'); } if (tree.catchBlock === null && tree.finallyBlock === null) { this.fail_(tree, 'either catch or finally must be present'); } } /** * @param {TypeArguments} tree */ visitTypeArguments(tree) { let {args} = tree; for (let i = 0; i < args.length; i++) { this.checkVisit_(args[i].isType(), args[i], 'Type arguments must be type expressions'); } } /** * @param {TypeName} tree */ visitTypeName(tree) { this.checkVisit_(tree.moduleName === null || tree.moduleName.type === TYPE_NAME, tree.moduleName, 'moduleName must be null or a TypeName'); this.check_(tree.name.type === IDENTIFIER, tree, 'name must be an identifier'); } /** * @param {TypeReference} tree */ visitTypeReference(tree) { this.checkType_(TYPE_NAME, tree.typeName, 'typeName must be a TypeName'); this.checkType_(TYPE_ARGUMENTS, tree.args, 'args must be a TypeArguments'); } /** * @param {TypeParameters} tree */ visitTypeParameters(tree) { let {parameters} = tree; for (let i = 0; i < parameters.length; i++) { this.checkType_(TYPE_PARAMETER, parameters[i], 'Type parameters must all be type parameters'); } } /** * @param {TypeParameter} tree */ visitTypeParameter(tree) { this.check_(tree.identifierToken.type === IDENTIFIER, tree, 'Type parameter must be an identifier token'); if (tree.extendsType) { this.checkVisit_(tree.extendsType.isType(), tree.extendsType, 'extends type must be a type expression'); } } /** * @param {UnaryExpression} tree */ visitUnaryExpression(tree) { this.checkVisit_(tree.operand.isAssignmentExpression(), tree.operand, 'assignment expression expected'); } /** * @param {VariableDeclaration} tree */ visitVariableDeclaration(tree) { this.checkVisit_(tree.lvalue.isPattern() || tree.lvalue.type === BINDING_IDENTIFIER, tree.lvalue, 'binding identifier expected, found: ' + tree.lvalue.type); if (tree.initializer !== null) { this.checkVisit_(tree.initializer.isAssignmentExpression(), tree.initializer, 'assignment expression expected'); } } /** * @param {WhileStatement} tree */ visitWhileStatement(tree) { this.checkVisit_(tree.condition.isExpression(), tree.condition, 'expression expected'); this.checkVisit_(tree.body.isStatement(), tree.body, 'statement expected'); } /** * @param {WithStatement} tree */ visitWithStatement(tree) { this.checkVisit_(tree.expression.isExpression(), tree.expression, 'expression expected'); this.checkVisit_(tree.body.isStatement(), tree.body, 'statement expected'); } /** * @param {YieldExpression} tree */ visitYieldExpression(tree) { if (tree.expression !== null) { this.checkVisit_(tree.expression.isExpression(), tree.expression, 'expression expected'); } } } /** * Validates a parse tree. Validation failures are compiler bugs. * When a failure is found, the source file is dumped to standard * error output and a runtime exception is thrown. * * @param {ParseTree} tree */ ParseTreeValidator.validate = function(tree) { let validator = new ParseTreeValidator(); try { validator.visitAny(tree); } catch (e) { if (!(e instanceof ValidationError)) { throw e; } let location = null; if (e.tree !== null) { location = e.tree.location; } if (location === null) { location = tree.location; } let locationString = location !== null ? location.start.toString() : '(unknown)'; throw new Error( `Parse tree validation failure '${e.message}' at ${locationString}:` + `\n\n${TreeWriter.write(tree)}\n`); } };