All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.AstValidator Maven / Gradle / Ivy

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs.

The newest version!
/*
 * Copyright 2011 The Closure Compiler 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.
 */

package com.google.javascript.jscomp;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.isNullOrEmpty;

import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature;
import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSType.Nullability;
import java.util.Objects;
import org.jspecify.nullness.Nullable;

/** This class walks the AST and validates that the structure is correct. */
public final class AstValidator implements CompilerPass {

  // Possible enhancements:
  // * verify NAME, LABEL_NAME, GETPROP property name and unquoted
  // object-literal keys are valid JavaScript identifiers.
  // * optionally verify every node has source location information.

  /** Violation handler */
  public interface ViolationHandler {
    void handleViolation(String message, Node n);
  }

  private final AbstractCompiler compiler;
  private final ViolationHandler violationHandler;
  private Node currentScript;

  enum TypeInfoValidation {
    JSTYPE,
    COLOR,
    NONE
  }

  /** Perform type validation if this is enabled. */
  private TypeInfoValidation typeValidationMode = TypeInfoValidation.NONE;

  /** Validate that a SCRIPT's FeatureSet property includes all features if this is enabled. */
  private final boolean isScriptFeatureValidationEnabled;

  public AstValidator(
      AbstractCompiler compiler, ViolationHandler handler, boolean validateScriptFeatures) {
    this.compiler = compiler;
    this.violationHandler = handler;
    this.isScriptFeatureValidationEnabled = validateScriptFeatures;
  }

  public AstValidator(AbstractCompiler compiler) {
    this(compiler, /* validateScriptFeatures= */ false);
  }

  public AstValidator(AbstractCompiler compiler, boolean validateScriptFeatures) {
    this(
        compiler,
        new ViolationHandler() {
          @Override
          public void handleViolation(String message, Node n) {
            throw new IllegalStateException(
                message
                    + ". Reference node:\n"
                    + n.toStringTree()
                    + "\n Parent node:\n"
                    + (n.hasParent() ? n.getParent().toStringTree() : " no parent "));
          }
        },
        validateScriptFeatures);
  }

  /**
   * Enable or disable validation of type information.
   *
   * 

TODO(b/74537281): Currently only expressions are checked for type information. Do we need to * do more? */ @CanIgnoreReturnValue public AstValidator setTypeValidationMode(TypeInfoValidation mode) { typeValidationMode = mode; return this; } @Override public void process(Node externs, Node root) { if (externs != null) { validateCodeRoot(externs); } if (root != null) { validateCodeRoot(root); } } public void validateRoot(Node n) { validateNodeType(Token.ROOT, n); validateProperties(n); validateChildCount(n, 2); validateCodeRoot(n.getFirstChild()); validateCodeRoot(n.getLastChild()); } public void validateCodeRoot(Node n) { validateNodeType(Token.ROOT, n); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { validateScript(c); } } public void validateScript(Node n) { validateNodeType(Token.SCRIPT, n); validateHasSourceName(n); validateHasInputId(n); currentScript = n; if (n.hasChildren() && n.getFirstChild().isModuleBody()) { validateProperties(n); validateChildCount(n, 1); validateModuleContents(n.getFirstChild()); } else { validateStatements(n.getFirstChild()); } if (isScriptFeatureValidationEnabled) { validateScriptFeatureSet(n); } } /** * Confirm that every SCRIPT node’s FEATURE_SET <= compiler's allowable featureSet. This is * possbile because with go/accurately-maintain-script-node-featureSet, each transpiler pass * updates script features anytime it updates the compiler's allowable features. */ private void validateScriptFeatureSet(Node scriptNode) { if (!scriptNode.isScript()) { violation("Not a script node", scriptNode); // unit tests for this pass perform "Negaive Testing" (i.e pass non-script nodes to {@code // validateScript}) and expect a violation {@code expectInvalid(n, Check.SCRIPT);} // report violation and return here instead of crashing below in {@code // NodeUtil.getFeatureSetofScript} // for test to complete return; } FeatureSet scriptFeatures = NodeUtil.getFeatureSetOfScript(currentScript); FeatureSet allowableFeatures = compiler.getAllowableFeatures(); if (scriptFeatures == null || allowableFeatures == null) { return; } if (!allowableFeatures.contains(scriptFeatures)) { if (!scriptNode.isFromExterns()) { // Skip this check for externs because we don't need to complete transpilation on externs, // and currently only transpile externs so that we can typecheck ES6+ features in externs. FeatureSet differentFeatures = scriptFeatures.without(allowableFeatures); violation( "SCRIPT node contains these unallowable features:" + differentFeatures.getFeatures(), currentScript); } } } public void validateModuleContents(Node n) { validateNodeType(Token.MODULE_BODY, n); validateStatements(n.getFirstChild()); } public void validateStatements(Node n) { while (n != null) { validateStatement(n); n = n.getNext(); } } public void validateStatement(Node n) { validateStatement(n, false); } /** * Validates a statement node and its children. * * @param isAmbient whether this statement comes from TS ambient `declare [...]` */ public void validateStatement(Node n, boolean isAmbient) { switch (n.getToken()) { case LABEL: validateLabel(n); return; case BLOCK: validateBlock(n); return; case FUNCTION: if (isAmbient) { validateFunctionSignature(n); } else { validateFunctionStatement(n); } return; case WITH: validateWith(n); return; case FOR: validateFor(n); return; case FOR_IN: validateForIn(n); return; case FOR_OF: validateForOf(n); return; case FOR_AWAIT_OF: validateForAwaitOf(n); return; case WHILE: validateWhile(n); return; case DO: validateDo(n); return; case SWITCH: validateSwitch(n); return; case IF: validateIf(n); return; case CONST: case VAR: case LET: validateNameDeclarationHelper(n, n.getToken(), n); return; case EXPR_RESULT: validateExprStmt(n); return; case RETURN: validateReturn(n); return; case THROW: validateThrow(n); return; case TRY: validateTry(n); return; case BREAK: validateBreak(n); return; case CONTINUE: validateContinue(n); return; case EMPTY: case DEBUGGER: validateProperties(n); validateChildless(n); return; case CLASS: validateClassDeclaration(n, isAmbient); return; case IMPORT: validateImport(n); return; case EXPORT: validateExport(n, isAmbient); return; case INTERFACE: validateInterface(n); return; case ENUM: validateEnum(n); return; case TYPE_ALIAS: validateTypeAlias(n); return; case DECLARE: validateAmbientDeclaration(n); return; case NAMESPACE: validateNamespace(n, isAmbient); return; case MODULE_BODY: // Uncommon case where a module body is not the first child of a script. This may happen in // a specific circumstance where the {@code LateEs6ToEs3Rewriter} pass injects code above a // module body. Valid only when skipNonTranspilationPasses=true and // setWrapGoogModulesForWhitespaceOnly=false // TODO: b/294420383 Ideally the LateEs6ToEs3Rewriter pass should not inject code above the // module body node if (compiler.getOptions().skipNonTranspilationPasses) { if (!compiler.getOptions().wrapGoogModulesForWhitespaceOnly) { validateModuleContents(n); return; } } violation("Expected statement but was " + n.getToken() + ".", n); return; default: if (n.isModuleBody() && compiler.getOptions().skipNonTranspilationPasses) { checkState( !compiler.getOptions().wrapGoogModulesForWhitespaceOnly, "Modules can exist in transpiler only if setWrapGoogModulesForWhitespaceOnly is" + " false"); validateModuleContents(n); return; } violation("Expected statement but was " + n.getToken() + ".", n); } } public void validateExpression(Node n) { validateTypeInformation(n); switch (n.getToken()) { // Childless expressions case NEW_TARGET: validateFeature(Feature.NEW_TARGET, n); validateProperties(n); validateChildless(n); return; case IMPORT_META: validateFeature(Feature.IMPORT_META, n); validateProperties(n); validateChildless(n); return; case FALSE: case NULL: case THIS: case TRUE: validateProperties(n); validateChildless(n); return; // General unary ops case DELPROP: case POS: case NEG: case NOT: case TYPEOF: case VOID: case BITNOT: case CAST: validateUnaryOp(n); return; case INC: case DEC: validateIncDecOp(n); return; // Assignments case ASSIGN: validateAssignmentExpression(n); return; case ASSIGN_EXPONENT: validateFeature(Feature.EXPONENT_OP, n); validateCompoundAssignmentExpression(n); return; case ASSIGN_BITOR: case ASSIGN_BITXOR: case ASSIGN_BITAND: case ASSIGN_LSH: case ASSIGN_RSH: case ASSIGN_URSH: case ASSIGN_ADD: case ASSIGN_SUB: case ASSIGN_MUL: case ASSIGN_DIV: case ASSIGN_MOD: validateCompoundAssignmentExpression(n); return; case ASSIGN_COALESCE: validateFeature(Feature.NULL_COALESCE_OP, n); // fall-through case ASSIGN_OR: case ASSIGN_AND: validateFeature(Feature.LOGICAL_ASSIGNMENT, n); validateCompoundAssignmentExpression(n); return; case HOOK: validateTrinaryOp(n); return; // Node types that require special handling case STRINGLIT: validateStringLit(n); return; case NUMBER: validateNumber(n); return; case BIGINT: validateBigInt(n); return; case NAME: validateName(n); return; // General binary ops case EXPONENT: validateFeature(Feature.EXPONENT_OP, n); validateBinaryOp(n); return; case COALESCE: validateFeature(Feature.NULL_COALESCE_OP, n); validateBinaryOp(n); return; case COMMA: case OR: case AND: case BITOR: case BITXOR: case BITAND: case EQ: case NE: case SHEQ: case SHNE: case LT: case GT: case LE: case GE: case INSTANCEOF: case IN: case LSH: case RSH: case URSH: case SUB: case ADD: case MUL: case MOD: case DIV: validateBinaryOp(n); return; case GETELEM: validateGetElem(n); return; case OPTCHAIN_GETELEM: validateOptChainGetElem(n); return; case GETPROP: validateGetProp(n); return; case OPTCHAIN_GETPROP: validateOptChainGetProp(n); return; case ARRAYLIT: validateArrayLit(n); return; case OBJECTLIT: validateObjectLit(n); return; case REGEXP: validateRegExpLit(n); return; case CALL: validateCall(n); return; case OPTCHAIN_CALL: validateOptChainCall(n); return; case NEW: validateNew(n); return; case FUNCTION: validateFunctionExpression(n); return; case CLASS: validateClass(n); return; case TEMPLATELIT: validateTemplateLit(n); return; case TAGGED_TEMPLATELIT: validateTaggedTemplateLit(n); return; case YIELD: validateYield(n); return; case AWAIT: validateAwait(n); return; case DYNAMIC_IMPORT: validateFeature(Feature.DYNAMIC_IMPORT, n); validateUnaryOp(n); return; default: violation("Expected expression but was " + n.getToken(), n); } } /** * Validate an expression or expresison-like construct. * *

An expression-like construct (pseudoexpression) is an AST fragment that is valid in some, * but not all, of the same contexts as true expressions. For example, a VANILLA_FOR permits EMPTY * as its condition and increment expressions, even though EMPTY is not valid as an expression in * general. * *

{@code allowedPseudoexpressions} allows the caller to specify which pseudoexpressions are * valid for their context. If {@code n} is a pseudoexpression, it will be considered invalid * unless its token is in this set. */ private void validatePseudoExpression(Node n, Token... allowedPseudoexpressions) { switch (n.getToken()) { case EMPTY: validateProperties(n); validateChildless(n); break; case ITER_SPREAD: validateProperties(n); validateChildCount(n); validateFeature(Feature.SPREAD_EXPRESSIONS, n); validateExpression(n.getFirstChild()); break; default: validateExpression(n); return; } // This also implicitly validates that only known expression and pseudo-expression tokens are // permitted. ImmutableSet set = ImmutableSet.copyOf(allowedPseudoexpressions); if (!set.contains(n.getToken())) { violation("Expected expression or " + set + " but was " + n.getToken(), n); } } /** * Enforces the given node has a type if we are validating JSTypes or Colors * * @param n a Node which we expect to have a type attached (i.e. not a control-flow-only node like * a BLOCK or IF) */ private void validateTypeInformation(Node n) { if (typeValidationMode.equals(TypeInfoValidation.NONE)) { return; } if (this.typeValidationMode.equals(TypeInfoValidation.JSTYPE)) { JSType type = n.getJSType(); if (type != null && !type.isResolved()) { // null types are checked in the switch statement violation("Found unresolved type " + type, n); } } switch (n.getToken()) { case CALL: if (!n.getFirstChild().isSuper()) { // TODO(sdh): need to validate super() using validateNewType() instead, if it existed validateCallType(n); } break; default: expectSomeTypeInformation(n); } } private void validateCallType(Node callNode) { switch (this.typeValidationMode) { case JSTYPE: // TODO(b/74537281): Shouldn't CALL nodes always have a type, even if it is unknown? Node callee = callNode.getFirstChild(); JSType calleeType = checkNotNull( callee.getJSType(), "Callee of\n\n%s\nhas no type.", callNode.toStringTree()); if (calleeType.isFunctionType()) { FunctionType calleeFunctionType = calleeType.toMaybeFunctionType(); JSType returnType = calleeFunctionType.getReturnType(); // Skip this check if the call node was originally in a cast, because the cast type may be // narrower than the return type. Also skip the check if the function's return type is the // any (formerly unknown) type, since we may have inferred a better type. if (callNode.getJSTypeBeforeCast() == null && !returnType.isUnknownType()) { expectMatchingTypeInformation(callNode, returnType); } } // TODO(b/74537281): What other cases should be covered? break; case COLOR: callee = callNode.getFirstChild(); checkNotNull(callee.getColor(), "Callee of\n\n%s\nhas no color.", callNode.toStringTree()); // skip additional validation of return types, since optimization colors don't include // call signature types break; case NONE: throw new AssertionError(); } } private void expectSomeTypeInformation(Node n) { switch (this.typeValidationMode) { case JSTYPE: if (n.getJSType() == null) { violation( "Type information missing" + "\n" + compiler.toSource(NodeUtil.getEnclosingStatement(n)), n); } break; case COLOR: if (n.getColor() == null) { violation( "Color information missing" + "\n" + compiler.toSource(NodeUtil.getEnclosingStatement(n)), n); } break; case NONE: throw new AssertionError(); } } private void expectMatchingTypeInformation(Node n, JSType expectedTypeI) { JSType typeI = n.getJSType(); if (!Objects.equals(expectedTypeI, typeI)) { violation( "Expected type: " + getTypeAnnotationString(expectedTypeI) + " Actual type: " + getTypeAnnotationString(typeI), n); } } private static String getTypeAnnotationString(@Nullable JSType typeI) { if (typeI == null) { return "NO TYPE INFORMATION"; } else { return "{" + typeI.toAnnotationString(Nullability.EXPLICIT) + "}"; } } private void validateYield(Node n) { validateFeature(Feature.GENERATORS, n); validateNodeType(Token.YIELD, n); validateProperties(n); validateChildCountIn(n, 0, 1); if (n.hasChildren()) { validateExpression(n.getFirstChild()); } validateYieldWithinGeneratorFunction(n); } private void validateYieldWithinGeneratorFunction(Node n) { Node parentFunction = NodeUtil.getEnclosingFunction(n); if (parentFunction == null || !parentFunction.isGeneratorFunction()) { violation("'yield' expression is not within a generator function", n); } else if (isInParameterListOfFunction(n, parentFunction)) { violation("'yield' expression is not allowed in a parameter list", n); } } private void validateAwait(Node n) { validateFeature(Feature.ASYNC_FUNCTIONS, n); validateNodeType(Token.AWAIT, n); validateProperties(n); validateChildCount(n); validateExpression(n.getFirstChild()); validateAwaitWithinAsyncFunction(n); } private void validateAwaitWithinAsyncFunction(Node n) { Node parentFunction = NodeUtil.getEnclosingFunction(n); if (parentFunction == null || !parentFunction.isAsyncFunction()) { violation("'await' expression is not within an async function", n); } else if (isInParameterListOfFunction(n, parentFunction)) { violation("'await' expression is not allowed in a parameter list", n); } } private boolean isInParameterListOfFunction(Node child, Node functionNode) { Node paramList = checkNotNull(functionNode.getSecondChild(), functionNode); for (Node parent = child.getParent(); parent != functionNode; parent = parent.getParent()) { checkNotNull(parent, "%s not contained in function %s", child, functionNode); if (parent == paramList) { return true; } } return false; } private void validateImport(Node n) { validateFeature(Feature.MODULES, n); validateNodeType(Token.IMPORT, n); validateProperties(n); validateChildCount(n); if (n.getFirstChild().isName()) { validateName(n.getFirstChild()); } else { validateNodeType(Token.EMPTY, n.getFirstChild()); } Node secondChild = n.getSecondChild(); switch (secondChild.getToken()) { case IMPORT_SPECS: validateImportSpecifiers(secondChild); break; case IMPORT_STAR: validateNonEmptyString(secondChild); break; default: validateNodeType(Token.EMPTY, secondChild); } validateStringLit(n.getChildAtIndex(2)); } private void validateImportSpecifiers(Node n) { validateNodeType(Token.IMPORT_SPECS, n); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { validateImportSpecifier(c); } } private void validateImportSpecifier(Node n) { validateNodeType(Token.IMPORT_SPEC, n); validateProperties(n); validateChildCount(n, 2); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { validateName(c); } } private void validateExport(Node n, boolean isAmbient) { validateFeature(Feature.MODULES, n); validateNodeType(Token.EXPORT, n); if (n.getBooleanProp(Node.EXPORT_ALL_FROM)) { // export * from "mod" validateProperties(n); validateChildCount(n, 2); validateNodeType(Token.EMPTY, n.getFirstChild()); validateStringLit(n.getSecondChild()); } else if (n.getBooleanProp(Node.EXPORT_DEFAULT)) { // export default foo = 2 validateProperties(n); validateChildCount(n, 1); validateExpression(n.getFirstChild()); } else { validateProperties(n); validateChildCountIn(n, 1, 2); if (n.getFirstChild().isExportSpecs()) { validateExportSpecifiers(n.getFirstChild()); } else { validateStatement(n.getFirstChild(), isAmbient); } if (n.hasTwoChildren()) { validateStringLit(n.getSecondChild()); } } } private void validateExportSpecifiers(Node n) { validateNodeType(Token.EXPORT_SPECS, n); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { validateExportSpecifier(c); } } private void validateExportSpecifier(Node n) { validateNodeType(Token.EXPORT_SPEC, n); validateProperties(n); validateChildCount(n, 2); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { validateName(c); } } private void validateTaggedTemplateLit(Node n) { validateFeature(Feature.TEMPLATE_LITERALS, n); validateNodeType(Token.TAGGED_TEMPLATELIT, n); validateProperties(n); validateChildCount(n); validateExpression(n.getFirstChild()); validateTemplateLit(n.getLastChild()); } private void validateTemplateLit(Node n) { validateFeature(Feature.TEMPLATE_LITERALS, n); validateNodeType(Token.TEMPLATELIT, n); for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (child.isTemplateLitString()) { validateTemplateLitString(child); } else { validateTemplateLitSub(child); } } } private void validateTemplateLitString(Node n) { validateNodeType(Token.TEMPLATELIT_STRING, n); validateProperties(n); validateChildCount(n); try { // Validate that getRawString doesn't throw n.getRawString(); } catch (UnsupportedOperationException e) { violation("Invalid TEMPLATELIT_STRING node.", n); } } private void validateTemplateLitSub(Node n) { validateNodeType(Token.TEMPLATELIT_SUB, n); validateProperties(n); validateChildCount(n); validateExpression(n.getFirstChild()); } private void validateInterface(Node n) { validateNodeType(Token.INTERFACE, n); validateProperties(n); validateChildCount(n); Node name = n.getFirstChild(); validateName(name); Node superTypes = name.getNext(); if (superTypes.isEmpty()) { validateProperties(superTypes); validateChildless(superTypes); } else { validateInterfaceExtends(superTypes); } validateInterfaceMembers(n.getLastChild()); } private void validateInterfaceExtends(Node n) { validateNodeType(Token.INTERFACE_EXTENDS, n); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { validateNamedType(c); } } private void validateInterfaceMembers(Node n) { validateNodeType(Token.INTERFACE_MEMBERS, n); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { validateInterfaceMember(c); } } private void validateInterfaceMember(Node n) { switch (n.getToken()) { case MEMBER_FUNCTION_DEF: validateProperties(n); validateChildCount(n); validateFunctionSignature(n.getFirstChild()); break; case MEMBER_VARIABLE_DEF: validateProperties(n); validateChildless(n); break; case INDEX_SIGNATURE: validateProperties(n); validateChildCount(n); Node child = n.getFirstChild(); validateProperties(child); validateChildless(child); break; case CALL_SIGNATURE: validateProperties(n); validateChildCount(n); break; default: violation("Interface contained member of invalid type " + n.getToken(), n); } } private void validateEnum(Node n) { validateNodeType(Token.ENUM, n); validateName(n.getFirstChild()); validateEnumMembers(n.getLastChild()); } private void validateEnumMembers(Node n) { validateNodeType(Token.ENUM_MEMBERS, n); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { validateEnumStringKey(c); } } private void validateEnumStringKey(Node n) { validateNodeType(Token.STRING_KEY, n); validateObjectLiteralKeyName(n); validateProperties(n); validateChildCount(n, 0); } /** In a class declaration, unlike a class expression, the class name is required. */ private void validateClassDeclaration(Node n, boolean isAmbient) { validateClassHelper(n, isAmbient); validateName(n.getFirstChild()); } private void validateClass(Node n) { validateClassHelper(n, false); } private void validateClassHelper(Node n, boolean isAmbient) { validateFeature(Feature.CLASSES, n); validateNodeType(Token.CLASS, n); validateProperties(n); validateChildCount(n); Node name = n.getFirstChild(); if (name.isEmpty()) { validateProperties(name); validateChildless(name); } else { validateName(name); } Node superClass = name.getNext(); if (superClass.isEmpty()) { validateProperties(superClass); validateChildless(superClass); } else { validateFeature(Feature.CLASS_EXTENDS, n); validateExpression(superClass); } validateClassMembers(n.getLastChild(), isAmbient); } private void validateClassMembers(Node n, boolean isAmbient) { validateNodeType(Token.CLASS_MEMBERS, n); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { validateClassMember(c, isAmbient); } } private void validateClassMember(Node n, boolean isAmbient) { switch (n.getToken()) { case MEMBER_FUNCTION_DEF: validateFeature(Feature.MEMBER_DECLARATIONS, n); validateObjectLiteralKeyName(n); validateProperties(n); validateChildCount(n); validateMemberFunction(n, isAmbient); break; case GETTER_DEF: case SETTER_DEF: validateFeature(Feature.CLASS_GETTER_SETTER, n); validateObjectLiteralKeyName(n); validateObjectLitKey(n); validateProperties(n); validateChildCount(n); validateMemberFunction(n, isAmbient); break; case MEMBER_VARIABLE_DEF: validateProperties(n); validateChildless(n); break; case COMPUTED_PROP: validateComputedPropClassMethod(n); break; case MEMBER_FIELD_DEF: validateClassField(n); break; case COMPUTED_FIELD_DEF: validateComputedPropClassField(n); break; case INDEX_SIGNATURE: validateProperties(n); validateChildCount(n); Node child = n.getFirstChild(); validateProperties(child); validateChildless(child); break; case CALL_SIGNATURE: validateProperties(n); validateChildCount(n); break; case BLOCK: validateFeature(Feature.CLASS_STATIC_BLOCK, n); validateBlock(n); break; case EMPTY: // Empty is allowed too. break; default: violation("Class contained member of invalid type " + n.getToken(), n); } } private void validateMemberFunction(Node n, boolean isAmbient) { Node function = n.getFirstChild(); if (isAmbient) { validateFunctionSignature(function); } else { validateFunctionExpression(function); } } private void validateClassField(Node n) { validateFeature(Feature.PUBLIC_CLASS_FIELDS, n); validateNonEmptyString(n); if (n.hasChildren()) { validateExpression(n.getFirstChild()); } } private void validateComputedPropClassField(Node n) { validateFeature(Feature.PUBLIC_CLASS_FIELDS, n); validateExpression(n.getFirstChild()); if (n.getSecondChild() != null) { validateExpression(n.getSecondChild()); } } private void validateBlock(Node n) { validateNodeType(Token.BLOCK, n); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { validateStatement(c); } } private void validateHasSourceName(Node n) { String sourceName = n.getSourceFileName(); if (isNullOrEmpty(sourceName)) { violation("Missing 'source name' annotation.", n); } } private void validateHasInputId(Node n) { InputId inputId = n.getInputId(); if (inputId == null) { violation("Missing 'input id' annotation.", n); } } private void validateLabel(Node n) { validateNodeType(Token.LABEL, n); validateProperties(n); validateChildCount(n); validateLabelName(n.getFirstChild()); validateStatement(n.getLastChild()); } private void validateLabelName(Node n) { validateNodeType(Token.LABEL_NAME, n); validateNonEmptyString(n); validateProperties(n); validateChildCount(n); } private void validateNonEmptyString(Node n) { if (validateNonNullString(n) && n.getString().isEmpty()) { violation("Expected non-empty string.", n); } } private void validateEmptyString(Node n) { if (validateNonNullString(n) && !n.getString().isEmpty()) { violation("Expected empty string.", n); } } private boolean validateNonNullString(Node n) { try { if (n.getString() == null) { violation("Expected non-null string.", n); return false; } } catch (RuntimeException e) { violation("Expected non-null string.", n); return false; } return true; } private void validateName(Node n) { validateNodeType(Token.NAME, n); validateNonEmptyString(n); validateProperties(n); validateChildCount(n); validateTypeInformation(n); } private void validateOptionalName(Node n) { validateNodeType(Token.NAME, n); validateNonNullString(n); validateProperties(n); validateChildCount(n); boolean isEmpty = n.getString() != null && n.getString().isEmpty(); if (!isEmpty) { validateTypeInformation(n); } } private void validateEmptyName(Node n) { validateNodeType(Token.NAME, n); validateEmptyString(n); validateProperties(n); validateChildCount(n); } private void validateFunctionStatement(Node n) { validateNodeType(Token.FUNCTION, n); validateProperties(n); validateChildCount(n); validateName(n.getFirstChild()); validateParameters(n.getSecondChild()); validateFunctionBody(n.getLastChild(), false); validateFunctionFeatures(n); if (n.getParent().isBlock() && !n.getGrandparent().isFunction()) { // e.g. if (true) { function f() {} } validateFeature(Feature.BLOCK_SCOPED_FUNCTION_DECLARATION, n); } } private void validateFunctionExpression(Node n) { validateFunctionExpressionHelper(n, false); } private void validateFunctionSignature(Node n) { validateFunctionExpressionHelper(n, true); } private void validateFunctionExpressionHelper(Node n, boolean isAmbient) { validateNodeType(Token.FUNCTION, n); validateProperties(n); validateChildCount(n); validateParameters(n.getSecondChild()); Node name = n.getFirstChild(); Node body = n.getLastChild(); if (n.isArrowFunction()) { validateEmptyName(name); if (body.isBlock()) { validateBlock(body); } else { validateExpression(body); } } else { validateOptionalName(name); validateFunctionBody(body, isAmbient); } validateFunctionFeatures(n); } private void validateFunctionFeatures(Node n) { if (n.isArrowFunction()) { validateFeature(Feature.ARROW_FUNCTIONS, n); } if (n.isGeneratorFunction()) { validateFeature(Feature.GENERATORS, n); } if (n.isAsyncFunction()) { validateFeature(Feature.ASYNC_FUNCTIONS, n); } if (n.isAsyncFunction() && n.isGeneratorFunction()) { validateFeature(Feature.ASYNC_GENERATORS, n); } } private void validateFunctionBody(Node n, boolean noBlock) { if (noBlock) { validateNodeType(Token.EMPTY, n); } else { validateBlock(n); } } private void validateParameters(Node n) { validateNodeType(Token.PARAM_LIST, n); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (c.isRest()) { validateRestParameters(Token.PARAM_LIST, c); } else if (c.isDefaultValue()) { validateFeature(Feature.DEFAULT_PARAMETERS, c); validateDefaultValue(Token.PARAM_LIST, c); } else { if (c.isName()) { validateName(c); } else if (c.isArrayPattern()) { validateArrayPattern(Token.PARAM_LIST, c); } else { validateObjectPattern(Token.PARAM_LIST, c); } } } } private void validateDefaultValue(Token contextType, Node n) { validateProperties(n); validateChildCount(n); validateLHS(contextType, n.getFirstChild()); validateExpression(n.getLastChild()); } private void validateCall(Node n) { validateNodeType(Token.CALL, n); validateProperties(n); validateMinimumChildCount(n, 1); Node callee = n.getFirstChild(); if (callee.isSuper()) { validateSuper(callee); } else { validateExpression(callee); } for (Node c = callee.getNext(); c != null; c = c.getNext()) { validatePseudoExpression(c, Token.ITER_SPREAD); } } private void validateOptChainCall(Node node) { validateFeature(Feature.OPTIONAL_CHAINING, node); validateNodeType(Token.OPTCHAIN_CALL, node); validateProperties(node); validateMinimumChildCount(node, 1); Node callee = node.getFirstChild(); validateExpression(callee); for (Node argument = callee.getNext(); argument != null; argument = argument.getNext()) { validatePseudoExpression(argument, Token.ITER_SPREAD); } validateFirstNodeOfOptChain(node); } @SuppressWarnings("RhinoNodeGetGrandparent") private void validateSuper(Node superNode) { validateFeature(Feature.SUPER, superNode); validateProperties(superNode); validateChildless(superNode); validateTypeInformation(superNode); Node superParent = superNode.getParent(); Node methodNode = NodeUtil.getEnclosingNonArrowFunction(superParent); if (NodeUtil.isNormalGet(superParent) && superNode.isFirstChildOf(superParent)) { // `super.prop` or `super['prop']` if (!allowsSuperPropertyReference(superParent)) { violation( "super property references are only allowed in methods, class static blocks and class" + " fields.", superNode); } } else if (superParent.isCall() && superNode.isFirstChildOf(superParent)) { // super() constructor call if (methodNode == null || !NodeUtil.isEs6Constructor(methodNode)) { violation("super constructor call is only allowed in a constructor method", superNode); } else { Node extendsNode = methodNode .getParent() // MEMBER_FUNCTION_DEF .getParent() // CLASS_METHODS .getParent() // CLASS .getSecondChild(); // extends clause if (extendsNode.isEmpty()) { violation("super constructor call in a class that extends nothing", superNode); } } } else { violation("`super` is a syntax error here", superNode); } } // Check if a super property reference is in a method, class static block or class field. private boolean allowsSuperPropertyReference(Node n) { switch (n.getToken()) { case SCRIPT: return false; case MEMBER_FIELD_DEF: case COMPUTED_FIELD_DEF: return true; case FUNCTION: if (NodeUtil.isMethodDeclaration(n)) { return true; } else if (!n.isArrowFunction()) { return false; } break; case BLOCK: if (NodeUtil.isClassStaticBlock(n)) { return true; } break; default: break; } return allowsSuperPropertyReference(n.getParent()); } private void validateRestParameters(Token contextType, Node n) { validateFeature(Feature.REST_PARAMETERS, n); validateRest(contextType, n); } private void validateArrayPatternRest(Token contextType, Node n) { validateFeature(Feature.ARRAY_PATTERN_REST, n); validateRest(contextType, n); } private void validateObjectPatternRest(Token contextType, Node n) { validateFeature(Feature.OBJECT_PATTERN_REST, n); validateRest(contextType, n); } /** * @param contextType A {@link Token} constant value indicating that {@code n} should be validated * appropriately for a descendant of a {@link Node} of this type. */ private void validateRest(Token contextType, Node n) { switch (n.getToken()) { case ITER_REST: case OBJECT_REST: break; default: violation("Unexpected node type.", n); return; } validateProperties(n); validateChildCount(n); validateLHS(contextType, n.getFirstChild()); if (n.getNext() != null) { violation("Rest parameters must come after all other parameters.", n); } } private void validateObjectSpread(Node n) { validateProperties(n); validateChildCount(n); validateFeature(Feature.OBJECT_LITERALS_WITH_SPREAD, n); validateExpression(n.getFirstChild()); } private void validateNew(Node n) { validateNodeType(Token.NEW, n); validateProperties(n); validateMinimumChildCount(n, 1); validateExpression(n.getFirstChild()); for (Node c = n.getSecondChild(); c != null; c = c.getNext()) { validatePseudoExpression(c, Token.ITER_SPREAD); } } /** * @param statement the enclosing statement. Will not always match the declaration Token. */ private void validateNameDeclarationHelper(Node statement, Token declaration, Node n) { validateProperties(n); validateMinimumChildCount(n, 1); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { validateNameDeclarationChild(statement, declaration, c); } if (declaration.equals(Token.LET)) { validateFeature(Feature.LET_DECLARATIONS, n); } else if (declaration.equals(Token.CONST)) { validateFeature(Feature.CONST_DECLARATIONS, n); } } private void validateNameDeclarationChild(Node statement, Token declaration, Node n) { boolean inEnhancedFor = NodeUtil.isEnhancedFor(statement); boolean inForIn = statement.isForIn(); int minValues; int maxValues; if (inForIn && declaration.equals(Token.VAR)) { // ECMASCRIPT5 sloppy mode allows for-in initializers. minValues = 0; maxValues = 1; } else if (inEnhancedFor) { minValues = 0; maxValues = 0; } else if (n.isDestructuringLhs() || declaration.equals(Token.CONST)) { minValues = 1; maxValues = 1; } else { minValues = 0; maxValues = 1; } if (n.isName()) { // Don't use validateName here since this NAME node may have a child. validateNonEmptyString(n); validateProperties(n); validateChildCountIn(n, minValues, maxValues); if (n.hasChildren()) { validateExpression(n.getFirstChild()); } } else if (n.isDestructuringLhs()) { validateProperties(n); validateChildCountIn(n, 1 + minValues, 1 + maxValues); Node c = n.getFirstChild(); switch (c.getToken()) { case ARRAY_PATTERN: validateArrayPattern(declaration, c); break; case OBJECT_PATTERN: validateObjectPattern(declaration, c); break; default: violation("Invalid destructuring lhs first child for " + declaration + " node", n); } if (n.hasTwoChildren()) { validateExpression(n.getSecondChild()); } } else { violation("Invalid child for " + declaration + " node", n); } } /** * @param contextType A {@link Token} constant value indicating that {@code n} should be validated * appropriately for a descendant of a {@link Node} of this type. */ private void validateLHS(Token contextType, Node n) { switch (n.getToken()) { case NAME: validateName(n); break; case ARRAY_PATTERN: validateArrayPattern(contextType, n); break; case OBJECT_PATTERN: validateObjectPattern(contextType, n); break; case GETPROP: case GETELEM: validateGetPropGetElemInLHS(contextType, n); break; case CAST: validateLHS(contextType, n.getOnlyChild()); break; default: violation("Invalid child for " + contextType + " node", n); } } private void validateGetPropGetElemInLHS(Token contextType, Node n) { if (contextType == Token.CONST || contextType == Token.LET || contextType == Token.VAR || contextType == Token.PARAM_LIST) { violation("Invalid child for " + contextType + " node", n); return; } switch (n.getToken()) { case GETPROP: validateGetProp(n); break; case GETELEM: validateGetElem(n); break; default: throw new IllegalStateException( "Expected GETPROP or GETELEM but instead got node " + n.getToken()); } } private void validateArrayPattern(Token type, Node n) { validateFeature(Feature.ARRAY_DESTRUCTURING, n); validateNodeType(Token.ARRAY_PATTERN, n); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { switch (c.getToken()) { case DEFAULT_VALUE: validateDefaultValue(type, c); break; case ITER_REST: validateArrayPatternRest(type, c); break; case EMPTY: validateProperties(c); validateChildless(c); break; default: validateLHS(type, c); } } } private void validateObjectPattern(Token type, Node n) { validateFeature(Feature.OBJECT_DESTRUCTURING, n); validateNodeType(Token.OBJECT_PATTERN, n); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { switch (c.getToken()) { case STRING_KEY: validateObjectPatternStringKey(type, c); break; case OBJECT_REST: validateObjectPatternRest(type, c); break; case COMPUTED_PROP: validateObjectPatternComputedPropKey(type, c); break; default: violation("Invalid object pattern child for " + type + " node", n); } } } private void validateFor(Node n) { validateNodeType(Token.FOR, n); validateProperties(n); validateChildCount(n, 4); Node target = n.getFirstChild(); if (NodeUtil.isNameDeclaration(target)) { validateNameDeclarationHelper(n, target.getToken(), target); } else { validatePseudoExpression(target, Token.EMPTY); } validatePseudoExpression(n.getSecondChild(), Token.EMPTY); validatePseudoExpression(n.getChildAtIndex(2), Token.EMPTY); validateBlock(n.getLastChild()); } private void validateForIn(Node n) { validateNodeType(Token.FOR_IN, n); validateProperties(n); validateChildCount(n); validateEnhancedForVarOrAssignmentTarget(n, n.getFirstChild()); validateExpression(n.getSecondChild()); validateBlock(n.getLastChild()); } private void validateForOf(Node n) { validateFeature(Feature.FOR_OF, n); validateNodeType(Token.FOR_OF, n); validateProperties(n); validateChildCount(n); validateEnhancedForVarOrAssignmentTarget(n, n.getFirstChild()); validateExpression(n.getSecondChild()); validateBlock(n.getLastChild()); } private void validateForAwaitOf(Node n) { validateFeature(Feature.FOR_AWAIT_OF, n); validateNodeType(Token.FOR_AWAIT_OF, n); validateProperties(n); validateChildCount(n); validateEnhancedForVarOrAssignmentTarget(n, n.getFirstChild()); validateExpression(n.getSecondChild()); validateBlock(n.getLastChild()); } private void validateEnhancedForVarOrAssignmentTarget(Node forNode, Node n) { if (NodeUtil.isNameDeclaration(n)) { // Only one NAME can be declared for FOR-IN and FOR_OF expressions. validateProperties(n); validateChildCount(n, 1); validateNameDeclarationHelper(forNode, n.getToken(), n); } else { validateLHS(n.getParent().getToken(), n); } } private void validateWith(Node n) { validateNodeType(Token.WITH, n); validateProperties(n); validateChildCount(n); validateExpression(n.getFirstChild()); validateBlock(n.getLastChild()); } private void validateWhile(Node n) { validateNodeType(Token.WHILE, n); validateProperties(n); validateChildCount(n); validateExpression(n.getFirstChild()); validateBlock(n.getLastChild()); } private void validateDo(Node n) { validateNodeType(Token.DO, n); validateProperties(n); validateChildCount(n); validateBlock(n.getFirstChild()); validateExpression(n.getLastChild()); } private void validateIf(Node n) { validateNodeType(Token.IF, n); validateProperties(n); validateChildCountIn(n, 2, 3); validateExpression(n.getFirstChild()); validateBlock(n.getSecondChild()); if (n.hasXChildren(3)) { validateBlock(n.getLastChild()); } } private void validateExprStmt(Node n) { validateNodeType(Token.EXPR_RESULT, n); validateProperties(n); validateChildCount(n); validateExpression(n.getFirstChild()); } private void validateReturn(Node n) { validateNodeType(Token.RETURN, n); validateProperties(n); validateMaximumChildCount(n, 1); if (n.hasChildren()) { validateExpression(n.getFirstChild()); } } private void validateThrow(Node n) { validateNodeType(Token.THROW, n); validateProperties(n); validateChildCount(n); validateExpression(n.getFirstChild()); } private void validateBreak(Node n) { validateNodeType(Token.BREAK, n); validateProperties(n); validateMaximumChildCount(n, 1); if (n.hasChildren()) { validateLabelName(n.getFirstChild()); } } private void validateContinue(Node n) { validateNodeType(Token.CONTINUE, n); validateProperties(n); validateMaximumChildCount(n, 1); if (n.hasChildren()) { validateLabelName(n.getFirstChild()); } } private void validateTry(Node n) { validateNodeType(Token.TRY, n); validateProperties(n); validateChildCountIn(n, 2, 3); validateBlock(n.getFirstChild()); boolean seenCatchOrFinally = false; // Validate catch Node catches = n.getSecondChild(); validateNodeType(Token.BLOCK, catches); validateProperties(catches); validateMaximumChildCount(catches, 1); if (catches.hasChildren()) { validateCatch(catches.getFirstChild()); seenCatchOrFinally = true; } // Validate finally if (n.hasXChildren(3)) { validateBlock(n.getLastChild()); seenCatchOrFinally = true; } if (!seenCatchOrFinally) { violation("Missing catch or finally for try statement.", n); } } private void validateCatch(Node n) { validateNodeType(Token.CATCH, n); validateProperties(n); validateChildCount(n); Node caught = n.getFirstChild(); if (caught.isName()) { validateName(caught); } else if (caught.isArrayPattern()) { validateArrayPattern(Token.CATCH, caught); } else if (caught.isObjectPattern()) { validateObjectPattern(Token.CATCH, caught); } else if (caught.isEmpty()) { validateNoCatchBinding(caught); } else { violation("Unexpected catch binding: " + caught, n); } validateBlock(n.getLastChild()); } private void validateNoCatchBinding(Node n) { validateFeature(Feature.OPTIONAL_CATCH_BINDING, n); validateProperties(n); validateChildCount(n); } private void validateSwitch(Node n) { validateNodeType(Token.SWITCH, n); validateProperties(n); validateMinimumChildCount(n, 1); validateExpression(n.getFirstChild()); int defaults = 0; for (Node c = n.getSecondChild(); c != null; c = c.getNext()) { validateSwitchMember(c); if (c.isDefaultCase()) { defaults++; } } if (defaults > 1) { violation("Expected at most 1 'default' in switch but was " + defaults, n); } } private void validateSwitchMember(Node n) { switch (n.getToken()) { case CASE: validateCase(n); return; case DEFAULT_CASE: validateDefaultCase(n); return; default: violation("Expected switch member but was " + n.getToken(), n); } } private void validateDefaultCase(Node n) { validateNodeType(Token.DEFAULT_CASE, n); validateProperties(n); validateChildCount(n); validateBlock(n.getLastChild()); } private void validateCase(Node n) { validateNodeType(Token.CASE, n); validateProperties(n); validateChildCount(n); validateExpression(n.getFirstChild()); validateBlock(n.getLastChild()); } private void validateChildless(Node n) { validateChildCount(n, 0); } private void validateAssignmentExpression(Node n) { validateProperties(n); validateChildCount(n); validateLHS(n.getToken(), n.getFirstChild()); validateExpression(n.getLastChild()); } private void validateCompoundAssignmentExpression(Node n) { validateProperties(n); validateChildCount(n); Token contextType = n.getToken(); Node lhs = n.getFirstChild(); validateAssignmentOpTarget(lhs, contextType); validateExpression(n.getLastChild()); } /** * Validates the lhs of a compound assignment op, inc, or dec * *

This check is stricter than validateLhs. */ private void validateAssignmentOpTarget(Node lhs, Token contextType) { switch (lhs.getToken()) { case NAME: validateName(lhs); break; case GETPROP: case GETELEM: validateGetPropGetElemInLHS(contextType, lhs); break; case CAST: validateProperties(lhs); validateChildCount(lhs, 1); validateAssignmentOpTarget(lhs.getFirstChild(), contextType); break; default: violation("Invalid child for " + contextType + " node", lhs); } } private void validateGetElem(Node n) { checkArgument(n.isGetElem(), n); validateProperties(n); validateChildCount(n, 2); validatePropertyReferenceTarget(n.getFirstChild()); validateExpression(n.getLastChild()); } private void validateOptChainGetElem(Node node) { validateFeature(Feature.OPTIONAL_CHAINING, node); checkArgument(node.isOptChainGetElem(), node); validateProperties(node); validateChildCount(node, 2); validateExpression(node.getFirstChild()); validateExpression(node.getLastChild()); validateFirstNodeOfOptChain(node); } private void validateGetProp(Node n) { validateNodeType(Token.GETPROP, n); validatePropertyReferenceTarget(n.getFirstChild()); validateProperties(n); validateChildCount(n); validateNonEmptyString(n); } private void validateOptChainGetProp(Node node) { validateFeature(Feature.OPTIONAL_CHAINING, node); validateNodeType(Token.OPTCHAIN_GETPROP, node); validateExpression(node.getFirstChild()); validateFirstNodeOfOptChain(node); validateProperties(node); validateChildCount(node); validateNonEmptyString(node); } private void validatePropertyReferenceTarget(Node objectNode) { if (objectNode.isSuper()) { validateSuper(objectNode); } else { validateExpression(objectNode); } } private void validateRegExpLit(Node n) { validateNodeType(Token.REGEXP, n); validateProperties(n); validateChildCountIn(n, 1, 2); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { validateStringLit(c); } } private void validateStringLit(Node n) { validateNodeType(Token.STRINGLIT, n); validateProperties(n); validateChildCount(n); try { // Validate that getString doesn't throw n.getString(); } catch (UnsupportedOperationException e) { violation("Invalid STRING node.", n); } } private void validateNumber(Node n) { validateNodeType(Token.NUMBER, n); validateProperties(n); validateChildCount(n); try { // Validate that getDouble doesn't throw n.getDouble(); } catch (UnsupportedOperationException e) { violation("Invalid NUMBER node.", n); } } private void validateBigInt(Node n) { validateNodeType(Token.BIGINT, n); validateProperties(n); validateChildCount(n); try { // Validate that getBigInt doesn't throw n.getBigInt(); } catch (UnsupportedOperationException e) { violation("Invalid BIGINT node.", n); } } private void validateArrayLit(Node n) { validateNodeType(Token.ARRAYLIT, n); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { // Array-literals may have empty slots. validatePseudoExpression(c, Token.EMPTY, Token.ITER_SPREAD); break; } } private void validateObjectLit(Node n) { validateNodeType(Token.OBJECTLIT, n); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { validateObjectLitKey(c); } } private void validateObjectLitKey(Node n) { switch (n.getToken()) { case GETTER_DEF: validateObjectLitGetKey(n); return; case SETTER_DEF: validateObjectLitSetKey(n); return; case STRING_KEY: validateObjectLitStringKey(n); return; case MEMBER_FUNCTION_DEF: validateClassMember(n, false); if (n.isStaticMember()) { violation("Keys in an object literal should not be static.", n); } return; case COMPUTED_PROP: validateObjectLitComputedPropKey(n); return; case OBJECT_SPREAD: validateObjectSpread(n); return; default: violation("Expected object literal key expression but was " + n.getToken(), n); } } private void validateObjectLitGetKey(Node n) { validateFeature(Feature.GETTER, n); validateNodeType(Token.GETTER_DEF, n); validateProperties(n); validateChildCount(n); validateObjectLiteralKeyName(n); Node function = n.getFirstChild(); validateFunctionExpression(function); // objlit get functions must be nameless, and must have zero parameters. if (!function.getFirstChild().getString().isEmpty()) { violation("Expected unnamed function expression.", n); } Node functionParams = function.getSecondChild(); if (functionParams.hasChildren()) { violation("get methods must not have parameters.", n); } } private void validateObjectLitSetKey(Node n) { validateFeature(Feature.SETTER, n); validateNodeType(Token.SETTER_DEF, n); validateProperties(n); validateChildCount(n); validateObjectLiteralKeyName(n); Node function = n.getFirstChild(); validateFunctionExpression(function); // objlit set functions must be nameless, and must have 1 parameter. if (!function.getFirstChild().getString().isEmpty()) { violation("Expected unnamed function expression.", n); } Node functionParams = function.getSecondChild(); if (!functionParams.hasOneChild()) { violation("set methods must have exactly one parameter.", n); } } private void validateObjectLitStringKey(Node n) { validateNodeType(Token.STRING_KEY, n); validateObjectLiteralKeyName(n); validateProperties(n); validateChildCount(n, 1); validateExpression(n.getFirstChild()); if (n.getBooleanProp(Node.IS_SHORTHAND_PROPERTY)) { validateFeature(Feature.EXTENDED_OBJECT_LITERALS, n); } } private void validateObjectPatternStringKey(Token type, Node n) { validateNodeType(Token.STRING_KEY, n); validateObjectLiteralKeyName(n); validateProperties(n); validateChildCount(n, 1); Node c = n.getFirstChild(); switch (c.getToken()) { case DEFAULT_VALUE: validateDefaultValue(type, c); break; default: validateLHS(type, c); } } private void validateObjectLitComputedPropKey(Node n) { validateFeature(Feature.COMPUTED_PROPERTIES, n); validateNodeType(Token.COMPUTED_PROP, n); validateProperties(n); validateChildCount(n); validateExpression(n.getFirstChild()); validateExpression(n.getLastChild()); } private void validateObjectPatternComputedPropKey(Token type, Node n) { validateFeature(Feature.COMPUTED_PROPERTIES, n); validateNodeType(Token.COMPUTED_PROP, n); validateProperties(n); validateChildCount(n); validateExpression(n.getFirstChild()); if (n.getLastChild().isDefaultValue()) { validateDefaultValue(type, n.getLastChild()); } else { validateLHS(n.getLastChild().getToken(), n.getLastChild()); } } private void validateComputedPropClassMethod(Node n) { validateFeature(Feature.COMPUTED_PROPERTIES, n); validateNodeType(Token.COMPUTED_PROP, n); validateExpression(n.getFirstChild()); if (n.getBooleanProp(Node.COMPUTED_PROP_VARIABLE)) { validateProperties(n); validateChildCount(n, 1); } else { validateProperties(n); validateChildCount(n, 2); validateFunctionExpression(n.getLastChild()); if (n.getBooleanProp(Node.COMPUTED_PROP_GETTER)) { validateObjectLitComputedPropGetKey(n); } else if (n.getBooleanProp(Node.COMPUTED_PROP_SETTER)) { validateObjectLitComputedPropSetKey(n); } } } private void validateObjectLitComputedPropGetKey(Node n) { validateFeature(Feature.COMPUTED_PROPERTIES, n); validateNodeType(Token.COMPUTED_PROP, n); validateProperties(n); validateChildCount(n); Node function = n.getLastChild(); validateFunctionExpression(function); // objlit get functions must be nameless, and must have zero parameters. if (!function.getFirstChild().getString().isEmpty()) { violation("Expected unnamed function expression.", n); } Node functionParams = function.getSecondChild(); if (functionParams.hasChildren()) { violation("get methods must not have parameters.", n); } } private void validateObjectLitComputedPropSetKey(Node n) { validateFeature(Feature.COMPUTED_PROPERTIES, n); validateNodeType(Token.COMPUTED_PROP, n); validateProperties(n); validateChildCount(n); Node function = n.getLastChild(); validateFunctionExpression(function); // objlit set functions must be nameless, and must have 1 parameter. if (!function.getFirstChild().getString().isEmpty()) { violation("Expected unnamed function expression.", n); } Node functionParams = function.getSecondChild(); if (!functionParams.hasOneChild()) { violation("set methods must have exactly one parameter.", n); } } private void validateObjectLiteralKeyName(Node n) { if (n.isQuotedStringKey()) { try { // Validate that getString doesn't throw n.getString(); } catch (UnsupportedOperationException e) { violation("getString failed for" + n.getToken(), n); } } else { validateNonEmptyString(n); } } private void validateIncDecOp(Node n) { validateProperties(n); validateChildCount(n, 1); validateAssignmentOpTarget(n.getFirstChild(), n.getToken()); } private void validateUnaryOp(Node n) { validateProperties(n); validateChildCount(n, 1); validateExpression(n.getFirstChild()); } private void validateBinaryOp(Node n) { validateProperties(n); validateChildCount(n, 2); validateExpression(n.getFirstChild()); validateExpression(n.getLastChild()); } private void validateTrinaryOp(Node n) { validateProperties(n); validateChildCount(n, 3); Node first = n.getFirstChild(); validateExpression(first); validateExpression(first.getNext()); validateExpression(n.getLastChild()); } private void validateNamedType(Node n) { validateNodeType(Token.NAMED_TYPE, n); validateProperties(n); validateChildCount(n); validateName(n.getFirstChild()); } private void validateTypeAlias(Node n) { validateNodeType(Token.TYPE_ALIAS, n); validateProperties(n); validateChildCount(n); } private void validateAmbientDeclaration(Node n) { validateNodeType(Token.DECLARE, n); validateAmbientDeclarationHelper(n.getFirstChild()); } private void validateAmbientDeclarationHelper(Node n) { switch (n.getToken()) { case VAR: case LET: case CONST: validateNameDeclarationHelper(n.getParent(), n.getToken(), n); break; case FUNCTION: validateFunctionSignature(n); break; case CLASS: validateClassDeclaration(n, true); break; case ENUM: validateEnum(n); break; case NAMESPACE: validateNamespace(n, true); break; case TYPE_ALIAS: validateTypeAlias(n); break; case EXPORT: validateExport(n, true); break; default: break; } } private void validateNamespace(Node n, boolean isAmbient) { validateNodeType(Token.NAMESPACE, n); validateProperties(n); validateChildCount(n); validateNamespaceName(n.getFirstChild()); validateNamespaceElements(n.getLastChild(), isAmbient); } private void validateNamespaceName(Node n) { switch (n.getToken()) { case NAME: validateName(n); break; case GETPROP: validateGetProp(n); break; default: break; } } private void validateNamespaceElements(Node n, boolean isAmbient) { validateNodeType(Token.NAMESPACE_ELEMENTS, n); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (isAmbient) { validateAmbientDeclarationHelper(c); } else { validateStatement(c); } } } private void violation(String message, Node n) { violationHandler.handleViolation(message, n); } // the first node of an opt chain must be marked with Prop.START_OF_OPT_CHAIN private void validateFirstNodeOfOptChain(Node n) { if (!NodeUtil.isOptChainNode(n.getFirstChild())) { // if the first child of an opt chain node is not an opt chain node then it is the start of an // opt chain if (!n.isOptionalChainStart()) { violation( "Start of optional chain node " + n.getToken() + " is not marked as the start.", n); } } } private void validateNodeType(Token type, Node n) { if (n.getToken() != type) { violation("Expected " + type + " but was " + n.getToken(), n); } } private void validateChildCount(Node n) { int expectedArity = Token.arity(n.getToken()); if (expectedArity != -1) { validateChildCount(n, expectedArity); } } private void validateChildCount(Node n, int expected) { int count = n.getChildCount(); if (expected != count) { violation("Expected " + expected + " children, but was " + count, n); } } private void validateChildCountIn(Node n, int min, int max) { if (max == min) { validateChildCount(n, min); return; } int count = n.getChildCount(); if (count < min || count > max) { violation("Expected child count in [" + min + ", " + max + "], but was " + count, n); } } private void validateMinimumChildCount(Node n, int i) { boolean valid = false; if (i == 1) { valid = n.hasChildren(); } else if (i == 2) { valid = n.hasMoreThanOneChild(); } else { valid = n.getChildCount() >= i; } if (!valid) { violation("Expected at least " + i + " children, but was " + n.getChildCount(), n); } } private void validateMaximumChildCount(Node n, int i) { boolean valid = false; if (i == 1) { valid = !n.hasMoreThanOneChild(); } else if (i == -1) { valid = true; // Varying number of children. } else { valid = n.getChildCount() <= i; } if (!valid) { violation("Expected no more than " + i + " children, but was " + n.getChildCount(), n); } } private void validateFeature(Feature feature, Node n) { FeatureSet allowbleFeatures = compiler.getAllowableFeatures(); // Checks that feature present in the AST is recorded in the compiler's featureSet. if (!n.isFromExterns() && !allowbleFeatures.has(feature)) { // Skip this check for externs because we don't need to complete transpilation on externs, // and currently only transpile externs so that we can typecheck ES6+ features in externs. violation("AST should not contain " + feature, n); } // Note: currentScript may be null if someone called validateStatement or validateExpression if (!isScriptFeatureValidationEnabled || currentScript == null) { return; } FeatureSet scriptFeatures = NodeUtil.getFeatureSetOfScript(currentScript); // Checks that feature present in the AST is recorded in the SCRIPT node's featureSet. if (scriptFeatures == null || !NodeUtil.getFeatureSetOfScript(currentScript).has(feature)) { violation("SCRIPT node should be marked as containing feature " + feature, currentScript); } } private void validateProperties(Node n) { n.validateProperties(errorMessage -> violation(errorMessage, n)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy