<commit_before>/*
 * Copyright 2014 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.parsing;

import com.google.common.base.Preconditions;
import com.google.common.math.DoubleMath;
import com.google.javascript.jscomp.parsing.Config.LanguageMode;
import com.google.javascript.jscomp.parsing.ParserRunner.ParseResult;
import com.google.javascript.rhino.ErrorReporter;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.SimpleErrorReporter;
import com.google.javascript.rhino.StaticSourceFile;

import java.util.HashSet;

/**
 * A parser for the type transformation expressions (TTL-Exp) as in
 * @template T := TTL-Exp =:
 *
 */
public final class TypeTransformationParser {

  private String typeTransformationString;
  private Node typeTransformationAst;
  private StaticSourceFile sourceFile;
  private ErrorReporter errorReporter;
  private int templateLineno, templateCharno;

  private static final int VAR_ARGS = Integer.MAX_VALUE;

  /** The classification of the keywords */
  public static enum OperationKind {
    TYPE_CONSTRUCTOR,
    OPERATION,
    STRING_PREDICATE,
    TYPE_PREDICATE,
    TYPEVAR_PREDICATE
  }

  /** Keywords of the type transformation language */
  public static enum Keywords {
    ALL("all", 0, 0, OperationKind.TYPE_CONSTRUCTOR),
    COND("cond", 3, 3, OperationKind.OPERATION),
    EQ("eq", 2, 2, OperationKind.TYPE_PREDICATE),
    ISCTOR("isCtor", 1, 1, OperationKind.TYPE_PREDICATE),
    ISDEFINED("isDefined", 1, 1, OperationKind.TYPEVAR_PREDICATE),
    ISRECORD("isRecord", 1, 1, OperationKind.TYPE_PREDICATE),
    ISTEMPLATIZED("isTemplatized", 1, 1, OperationKind.TYPE_PREDICATE),
    ISUNKNOWN("isUnknown", 1, 1, OperationKind.TYPE_PREDICATE),
    INSTANCEOF("instanceOf", 1, 1, OperationKind.OPERATION),
    MAPUNION("mapunion", 2, 2, OperationKind.OPERATION),
    MAPRECORD("maprecord", 2, 2, OperationKind.OPERATION),
    NONE("none", 0, 0, OperationKind.TYPE_CONSTRUCTOR),
    PRINTTYPE("printType", 2, 2, OperationKind.OPERATION),
    PROPTYPE("propType", 2, 2, OperationKind.OPERATION),
    RAWTYPEOF("rawTypeOf", 1, 1, OperationKind.TYPE_CONSTRUCTOR),
    SUB("sub", 2, 2, OperationKind.TYPE_PREDICATE),
    STREQ("streq", 2, 2, OperationKind.STRING_PREDICATE),
    RECORD("record", 1, VAR_ARGS, OperationKind.TYPE_CONSTRUCTOR),
    TEMPLATETYPEOF("templateTypeOf", 2, 2, OperationKind.TYPE_CONSTRUCTOR),
    TYPE("type", 2, VAR_ARGS, OperationKind.TYPE_CONSTRUCTOR),
    TYPEEXPR("typeExpr", 1, 1, OperationKind.TYPE_CONSTRUCTOR),
    TYPEOFVAR("typeOfVar", 1, 1, OperationKind.OPERATION),
    UNION("union", 2, VAR_ARGS, OperationKind.TYPE_CONSTRUCTOR),
    UNKNOWN("unknown", 0, 0, OperationKind.TYPE_CONSTRUCTOR);

    public final String name;
    public final int minParamCount, maxParamCount;
    public final OperationKind kind;

    Keywords(String name, int minParamCount, int maxParamCount,
        OperationKind kind) {
      this.name = name;
      this.minParamCount = minParamCount;
      this.maxParamCount = maxParamCount;
      this.kind = kind;
    }
  }

  public TypeTransformationParser(String typeTransformationString,
      StaticSourceFile sourceFile, ErrorReporter errorReporter,
      int templateLineno, int templateCharno) {
    this.typeTransformationString = typeTransformationString;
    this.sourceFile = sourceFile;
    this.errorReporter = errorReporter;
    this.templateLineno = templateLineno;
    this.templateCharno = templateCharno;
  }

  public Node getTypeTransformationAst() {
    return typeTransformationAst;
  }

  private void addNewWarning(String messageId, String messageArg, Node nodeWarning) {
    // TODO(lpino): Use the exact lineno and charno, it is currently using
    // the lineno and charno of the parent @template
    // TODO(lpino): Use only constants as parameters of this method
    errorReporter.warning(
        "Bad type annotation. "
            + SimpleErrorReporter.getMessage1(messageId, messageArg),
            sourceFile.getName(),
            templateLineno,
            templateCharno);
  }

  private Keywords nameToKeyword(String s) {
    return Keywords.valueOf(s.toUpperCase());
  }

  private boolean isValidKeyword(String name) {
    for (Keywords k : Keywords.values()) {
      if (k.name.equals(name)) {
        return true;
      }
    }
    return false;
  }

  private boolean isOperationKind(String name, OperationKind kind) {
    return isValidKeyword(name) && nameToKeyword(name).kind == kind;
  }

  private boolean isValidStringPredicate(String name) {
    return isOperationKind(name, OperationKind.STRING_PREDICATE);
  }

  private boolean isValidTypePredicate(String name) {
    return isOperationKind(name, OperationKind.TYPE_PREDICATE);
  }

  private boolean isValidTypevarPredicate(String name) {
    return isOperationKind(name, OperationKind.TYPEVAR_PREDICATE);
  }

  private boolean isBooleanOperation(Node n) {
    return n.isAnd() || n.isOr() || n.isNot();
  }

  private boolean isValidPredicate(String name) {
    return isValidStringPredicate(name)
        || isValidTypePredicate(name)
        || isValidTypevarPredicate(name);
  }

  private int getFunctionParamCount(Node n) {
    Preconditions.checkArgument(n.isFunction(),
        "Expected a function node, found %s", n);
    return n.getChildAtIndex(1).getChildCount();
  }

  private Node getFunctionBody(Node n) {
    Preconditions.checkArgument(n.isFunction(),
        "Expected a function node, found %s", n);
    return n.getChildAtIndex(2);
  }

  private String getCallName(Node n) {
    Preconditions.checkArgument(n.isCall(),
        "Expected a call node, found %s", n);
    return n.getFirstChild().getString();
  }

  private Node getCallArgument(Node n, int i) {
    Preconditions.checkArgument(n.isCall(),
        "Expected a call node, found %s", n);
    return n.getChildAtIndex(i + 1);
  }

  private int getCallParamCount(Node n) {
    Preconditions.checkArgument(n.isCall(),
        "Expected a call node, found %s", n);
    return n.getChildCount() - 1;
  }

  private boolean isTypeVar(Node n) {
    return n.isName();
  }

  private boolean isTypeName(Node n) {
    return n.isString();
  }

  private boolean isOperation(Node n) {
    return n.isCall();
  }

  /**
   * A valid expression is either:
   * - NAME for a type variable
   * - STRING for a type name
   * - CALL for the other expressions
   */
  private boolean isValidExpression(Node e) {
    return isTypeVar(e) || isTypeName(e) || isOperation(e);
  }

  private void warnInvalid(String msg, Node e) {
    addNewWarning("msg.jsdoc.typetransformation.invalid", msg, e);
  }

  private void warnInvalidExpression(String msg, Node e) {
    addNewWarning("msg.jsdoc.typetransformation.invalid.expression", msg, e);
  }

  private void warnMissingParam(String msg, Node e) {
    addNewWarning("msg.jsdoc.typetransformation.missing.param", msg, e);
  }

  private void warnExtraParam(String msg, Node e) {
    addNewWarning("msg.jsdoc.typetransformation.extra.param", msg, e);
  }

  private void warnInvalidInside(String msg, Node e) {
    addNewWarning("msg.jsdoc.typetransformation.invalid.inside", msg, e);
  }

  private boolean checkParameterCount(Node expr, Keywords keyword) {
    int paramCount = getCallParamCount(expr);
    if (paramCount < keyword.minParamCount) {
      warnMissingParam(keyword.name, expr);
      return false;
    }
    if (paramCount > keyword.maxParamCount) {
      warnExtraParam(keyword.name, expr);
      return false;
    }
    return true;
  }

  /**
   * Takes a type transformation expression, transforms it to an AST using
   * the ParserRunner of the JSCompiler and then verifies that it is a valid
   * AST.
   * @return true if the parsing was successful otherwise it returns false and
   * at least one warning is reported
   */
  public boolean parseTypeTransformation() {
    Config config = new Config(new HashSet<String>(),
        new HashSet<String>(), true, LanguageMode.ECMASCRIPT6);
    // TODO(lpino): ParserRunner reports errors if the expression is not
    // ES6 valid. We need to abort the validation of the type transformation
    // whenever an error is reported.
    ParseResult result = ParserRunner.parse(
        sourceFile, typeTransformationString, config, errorReporter);
    Node ast = result.ast;
    // Check that the expression is a script with an expression result
    if (!ast.isScript() || !ast.getFirstChild().isExprResult()) {
      warnInvalidExpression("type transformation", ast);
      return false;
    }

    Node expr = ast.getFirstChild().getFirstChild();
    // The AST of the type transformation must correspond to a valid expression
    if (!validTypeTransformationExpression(expr)) {
      // No need to add a new warning because the validation does it
      return false;
    }
    // Store the result if the AST is valid
    typeTransformationAst = expr;
    return true;
  }

  /**
   * A template type expression must be of the form type(typename, TTLExp,...)
   * or type(typevar, TTLExp...)
   */
  private boolean validTemplateTypeExpression(Node expr) {
    // The expression must have at least three children the type keyword,
    // a type name (or type variable) and a type expression
    if (!checkParameterCount(expr, Keywords.TYPE)) {
      return false;
    }
    int paramCount = getCallParamCount(expr);
    // The first parameter must be a type variable or a type name
    Node firstParam = getCallArgument(expr, 0);
    if (!isTypeVar(firstParam) && !isTypeName(firstParam)) {
      warnInvalid("type name or type variable", expr);
      warnInvalidInside("template type operation", expr);
      return false;
    }
    // The rest of the parameters must be valid type expressions
    for (int i = 1; i < paramCount; i++) {
      if (!validTypeTransformationExpression(getCallArgument(expr, i))) {
        warnInvalidInside("template type operation", expr);
        return false;
      }
    }
    return true;
  }

  /**
   * A Union type expression must be a valid type variable or
   * a union(TTLExp, TTLExp, ...)
   */
  private boolean validUnionTypeExpression(Node expr) {
    // The expression must have at least three children: The union keyword and
    // two type expressions
    if (!checkParameterCount(expr, Keywords.UNION)) {
      return false;
    }
    int paramCount = getCallParamCount(expr);
    // Check if each of the members of the union is a valid type expression
    for (int i = 0; i < paramCount; i++) {
      if (!validTypeTransformationExpression(getCallArgument(expr, i))) {
        warnInvalidInside("union type", expr);
        return false;
      }
    }
    return true;
  }

  /**
   * A none type expression must be of the form: none()
   */
  private boolean validNoneTypeExpression(Node expr) {
    // The expression must have no children
    return checkParameterCount(expr, Keywords.NONE);
  }

  /**
   * An all type expression must be of the form: all()
   */
  private boolean validAllTypeExpression(Node expr) {
    // The expression must have no children
    return checkParameterCount(expr, Keywords.ALL);
  }

  /**
   * An unknown type expression must be of the form: unknown()
   */
  private boolean validUnknownTypeExpression(Node expr) {
    // The expression must have no children
    return checkParameterCount(expr, Keywords.UNKNOWN);
  }

  /**
   * A raw type expression must be of the form rawTypeOf(TTLExp)
   */
  private boolean validRawTypeOfTypeExpression(Node expr) {
    // The expression must have two children. The rawTypeOf keyword and the
    // parameter
    if (!checkParameterCount(expr, Keywords.RAWTYPEOF)) {
      return false;
    }
    // The parameter must be a valid type expression
    if (!validTypeTransformationExpression(getCallArgument(expr, 0))) {
      warnInvalidInside(Keywords.RAWTYPEOF.name, expr);
      return false;
    }
    return true;
  }

  /**
   * A template type of expression must be of the form
   * templateTypeOf(TTLExp, index)
   */
  private boolean validTemplateTypeOfExpression(Node expr) {
    // The expression must have three children. The templateTypeOf keyword, a
    // templatized type and an index
    if (!checkParameterCount(expr, Keywords.TEMPLATETYPEOF)) {
      return false;
    }
    // The parameter must be a valid type expression
    if (!validTypeTransformationExpression(getCallArgument(expr, 0))) {
      warnInvalidInside(Keywords.TEMPLATETYPEOF.name, expr);
      return false;
    }
    if (!getCallArgument(expr, 1).isNumber()) {
      warnInvalid("index", expr);
      warnInvalidInside(Keywords.TEMPLATETYPEOF.name, expr);
      return false;
    }
    double index = getCallArgument(expr, 1).getDouble();
    if (!DoubleMath.isMathematicalInteger(index) || index < 0) {
      warnInvalid("index", expr);
      warnInvalidInside(Keywords.TEMPLATETYPEOF.name, expr);
      return false;
    }
    return true;
  }

  /**
   * A record must be a valid type transformation expression or a node of the form:
   * {prop:TTLExp, prop:TTLExp, ...}
   * Notice that the values are mandatory and they must be valid type
   * transformation expressions
   */
  private boolean validRecordParam(Node expr) {
    if (expr.isObjectLit()) {
      // Each value of a property must be a valid expression
      for (Node prop : expr.children()) {
        if (!prop.hasChildren()) {
          warnInvalid("property, missing type", prop);
          return false;
        } else if (!validTypeTransformationExpression(prop.getFirstChild())) {
          return false;
        }
      }
    } else if (!validTypeTransformationExpression(expr)) {
      return false;
    }
    return true;
  }<commit_msg>Remove dependency on Guava DoubleMath in TypeTransformationParser<commit_after>/*
 * Copyright 2014 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.parsing;

import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.parsing.Config.LanguageMode;
import com.google.javascript.jscomp.parsing.ParserRunner.ParseResult;
import com.google.javascript.rhino.ErrorReporter;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.SimpleErrorReporter;
import com.google.javascript.rhino.StaticSourceFile;

import java.util.HashSet;

/**
 * A parser for the type transformation expressions (TTL-Exp) as in
 * @template T := TTL-Exp =:
 *
 */
public final class TypeTransformationParser {

  private String typeTransformationString;
  private Node typeTransformationAst;
  private StaticSourceFile sourceFile;
  private ErrorReporter errorReporter;
  private int templateLineno, templateCharno;

  private static final int VAR_ARGS = Integer.MAX_VALUE;

  /** The classification of the keywords */
  public static enum OperationKind {
    TYPE_CONSTRUCTOR,
    OPERATION,
    STRING_PREDICATE,
    TYPE_PREDICATE,
    TYPEVAR_PREDICATE
  }

  /** Keywords of the type transformation language */
  public static enum Keywords {
    ALL("all", 0, 0, OperationKind.TYPE_CONSTRUCTOR),
    COND("cond", 3, 3, OperationKind.OPERATION),
    EQ("eq", 2, 2, OperationKind.TYPE_PREDICATE),
    ISCTOR("isCtor", 1, 1, OperationKind.TYPE_PREDICATE),
    ISDEFINED("isDefined", 1, 1, OperationKind.TYPEVAR_PREDICATE),
    ISRECORD("isRecord", 1, 1, OperationKind.TYPE_PREDICATE),
    ISTEMPLATIZED("isTemplatized", 1, 1, OperationKind.TYPE_PREDICATE),
    ISUNKNOWN("isUnknown", 1, 1, OperationKind.TYPE_PREDICATE),
    INSTANCEOF("instanceOf", 1, 1, OperationKind.OPERATION),
    MAPUNION("mapunion", 2, 2, OperationKind.OPERATION),
    MAPRECORD("maprecord", 2, 2, OperationKind.OPERATION),
    NONE("none", 0, 0, OperationKind.TYPE_CONSTRUCTOR),
    PRINTTYPE("printType", 2, 2, OperationKind.OPERATION),
    PROPTYPE("propType", 2, 2, OperationKind.OPERATION),
    RAWTYPEOF("rawTypeOf", 1, 1, OperationKind.TYPE_CONSTRUCTOR),
    SUB("sub", 2, 2, OperationKind.TYPE_PREDICATE),
    STREQ("streq", 2, 2, OperationKind.STRING_PREDICATE),
    RECORD("record", 1, VAR_ARGS, OperationKind.TYPE_CONSTRUCTOR),
    TEMPLATETYPEOF("templateTypeOf", 2, 2, OperationKind.TYPE_CONSTRUCTOR),
    TYPE("type", 2, VAR_ARGS, OperationKind.TYPE_CONSTRUCTOR),
    TYPEEXPR("typeExpr", 1, 1, OperationKind.TYPE_CONSTRUCTOR),
    TYPEOFVAR("typeOfVar", 1, 1, OperationKind.OPERATION),
    UNION("union", 2, VAR_ARGS, OperationKind.TYPE_CONSTRUCTOR),
    UNKNOWN("unknown", 0, 0, OperationKind.TYPE_CONSTRUCTOR);

    public final String name;
    public final int minParamCount, maxParamCount;
    public final OperationKind kind;

    Keywords(String name, int minParamCount, int maxParamCount,
        OperationKind kind) {
      this.name = name;
      this.minParamCount = minParamCount;
      this.maxParamCount = maxParamCount;
      this.kind = kind;
    }
  }

  public TypeTransformationParser(String typeTransformationString,
      StaticSourceFile sourceFile, ErrorReporter errorReporter,
      int templateLineno, int templateCharno) {
    this.typeTransformationString = typeTransformationString;
    this.sourceFile = sourceFile;
    this.errorReporter = errorReporter;
    this.templateLineno = templateLineno;
    this.templateCharno = templateCharno;
  }

  public Node getTypeTransformationAst() {
    return typeTransformationAst;
  }

  private void addNewWarning(String messageId, String messageArg, Node nodeWarning) {
    // TODO(lpino): Use the exact lineno and charno, it is currently using
    // the lineno and charno of the parent @template
    // TODO(lpino): Use only constants as parameters of this method
    errorReporter.warning(
        "Bad type annotation. "
            + SimpleErrorReporter.getMessage1(messageId, messageArg),
            sourceFile.getName(),
            templateLineno,
            templateCharno);
  }

  private Keywords nameToKeyword(String s) {
    return Keywords.valueOf(s.toUpperCase());
  }

  private boolean isValidKeyword(String name) {
    for (Keywords k : Keywords.values()) {
      if (k.name.equals(name)) {
        return true;
      }
    }
    return false;
  }

  private boolean isOperationKind(String name, OperationKind kind) {
    return isValidKeyword(name) && nameToKeyword(name).kind == kind;
  }

  private boolean isValidStringPredicate(String name) {
    return isOperationKind(name, OperationKind.STRING_PREDICATE);
  }

  private boolean isValidTypePredicate(String name) {
    return isOperationKind(name, OperationKind.TYPE_PREDICATE);
  }

  private boolean isValidTypevarPredicate(String name) {
    return isOperationKind(name, OperationKind.TYPEVAR_PREDICATE);
  }

  private boolean isBooleanOperation(Node n) {
    return n.isAnd() || n.isOr() || n.isNot();
  }

  private boolean isValidPredicate(String name) {
    return isValidStringPredicate(name)
        || isValidTypePredicate(name)
        || isValidTypevarPredicate(name);
  }

  private int getFunctionParamCount(Node n) {
    Preconditions.checkArgument(n.isFunction(),
        "Expected a function node, found %s", n);
    return n.getChildAtIndex(1).getChildCount();
  }

  private Node getFunctionBody(Node n) {
    Preconditions.checkArgument(n.isFunction(),
        "Expected a function node, found %s", n);
    return n.getChildAtIndex(2);
  }

  private String getCallName(Node n) {
    Preconditions.checkArgument(n.isCall(),
        "Expected a call node, found %s", n);
    return n.getFirstChild().getString();
  }

  private Node getCallArgument(Node n, int i) {
    Preconditions.checkArgument(n.isCall(),
        "Expected a call node, found %s", n);
    return n.getChildAtIndex(i + 1);
  }

  private int getCallParamCount(Node n) {
    Preconditions.checkArgument(n.isCall(),
        "Expected a call node, found %s", n);
    return n.getChildCount() - 1;
  }

  private boolean isTypeVar(Node n) {
    return n.isName();
  }

  private boolean isTypeName(Node n) {
    return n.isString();
  }

  private boolean isOperation(Node n) {
    return n.isCall();
  }

  /**
   * A valid expression is either:
   * - NAME for a type variable
   * - STRING for a type name
   * - CALL for the other expressions
   */
  private boolean isValidExpression(Node e) {
    return isTypeVar(e) || isTypeName(e) || isOperation(e);
  }

  private void warnInvalid(String msg, Node e) {
    addNewWarning("msg.jsdoc.typetransformation.invalid", msg, e);
  }

  private void warnInvalidExpression(String msg, Node e) {
    addNewWarning("msg.jsdoc.typetransformation.invalid.expression", msg, e);
  }

  private void warnMissingParam(String msg, Node e) {
    addNewWarning("msg.jsdoc.typetransformation.missing.param", msg, e);
  }

  private void warnExtraParam(String msg, Node e) {
    addNewWarning("msg.jsdoc.typetransformation.extra.param", msg, e);
  }

  private void warnInvalidInside(String msg, Node e) {
    addNewWarning("msg.jsdoc.typetransformation.invalid.inside", msg, e);
  }

  private boolean checkParameterCount(Node expr, Keywords keyword) {
    int paramCount = getCallParamCount(expr);
    if (paramCount < keyword.minParamCount) {
      warnMissingParam(keyword.name, expr);
      return false;
    }
    if (paramCount > keyword.maxParamCount) {
      warnExtraParam(keyword.name, expr);
      return false;
    }
    return true;
  }

  /**
   * Takes a type transformation expression, transforms it to an AST using
   * the ParserRunner of the JSCompiler and then verifies that it is a valid
   * AST.
   * @return true if the parsing was successful otherwise it returns false and
   * at least one warning is reported
   */
  public boolean parseTypeTransformation() {
    Config config = new Config(new HashSet<String>(),
        new HashSet<String>(), true, LanguageMode.ECMASCRIPT6);
    // TODO(lpino): ParserRunner reports errors if the expression is not
    // ES6 valid. We need to abort the validation of the type transformation
    // whenever an error is reported.
    ParseResult result = ParserRunner.parse(
        sourceFile, typeTransformationString, config, errorReporter);
    Node ast = result.ast;
    // Check that the expression is a script with an expression result
    if (!ast.isScript() || !ast.getFirstChild().isExprResult()) {
      warnInvalidExpression("type transformation", ast);
      return false;
    }

    Node expr = ast.getFirstChild().getFirstChild();
    // The AST of the type transformation must correspond to a valid expression
    if (!validTypeTransformationExpression(expr)) {
      // No need to add a new warning because the validation does it
      return false;
    }
    // Store the result if the AST is valid
    typeTransformationAst = expr;
    return true;
  }

  /**
   * A template type expression must be of the form type(typename, TTLExp,...)
   * or type(typevar, TTLExp...)
   */
  private boolean validTemplateTypeExpression(Node expr) {
    // The expression must have at least three children the type keyword,
    // a type name (or type variable) and a type expression
    if (!checkParameterCount(expr, Keywords.TYPE)) {
      return false;
    }
    int paramCount = getCallParamCount(expr);
    // The first parameter must be a type variable or a type name
    Node firstParam = getCallArgument(expr, 0);
    if (!isTypeVar(firstParam) && !isTypeName(firstParam)) {
      warnInvalid("type name or type variable", expr);
      warnInvalidInside("template type operation", expr);
      return false;
    }
    // The rest of the parameters must be valid type expressions
    for (int i = 1; i < paramCount; i++) {
      if (!validTypeTransformationExpression(getCallArgument(expr, i))) {
        warnInvalidInside("template type operation", expr);
        return false;
      }
    }
    return true;
  }

  /**
   * A Union type expression must be a valid type variable or
   * a union(TTLExp, TTLExp, ...)
   */
  private boolean validUnionTypeExpression(Node expr) {
    // The expression must have at least three children: The union keyword and
    // two type expressions
    if (!checkParameterCount(expr, Keywords.UNION)) {
      return false;
    }
    int paramCount = getCallParamCount(expr);
    // Check if each of the members of the union is a valid type expression
    for (int i = 0; i < paramCount; i++) {
      if (!validTypeTransformationExpression(getCallArgument(expr, i))) {
        warnInvalidInside("union type", expr);
        return false;
      }
    }
    return true;
  }

  /**
   * A none type expression must be of the form: none()
   */
  private boolean validNoneTypeExpression(Node expr) {
    // The expression must have no children
    return checkParameterCount(expr, Keywords.NONE);
  }

  /**
   * An all type expression must be of the form: all()
   */
  private boolean validAllTypeExpression(Node expr) {
    // The expression must have no children
    return checkParameterCount(expr, Keywords.ALL);
  }

  /**
   * An unknown type expression must be of the form: unknown()
   */
  private boolean validUnknownTypeExpression(Node expr) {
    // The expression must have no children
    return checkParameterCount(expr, Keywords.UNKNOWN);
  }

  /**
   * A raw type expression must be of the form rawTypeOf(TTLExp)
   */
  private boolean validRawTypeOfTypeExpression(Node expr) {
    // The expression must have two children. The rawTypeOf keyword and the
    // parameter
    if (!checkParameterCount(expr, Keywords.RAWTYPEOF)) {
      return false;
    }
    // The parameter must be a valid type expression
    if (!validTypeTransformationExpression(getCallArgument(expr, 0))) {
      warnInvalidInside(Keywords.RAWTYPEOF.name, expr);
      return false;
    }
    return true;
  }

  /**
   * A template type of expression must be of the form
   * templateTypeOf(TTLExp, index)
   */
  private boolean validTemplateTypeOfExpression(Node expr) {
    // The expression must have three children. The templateTypeOf keyword, a
    // templatized type and an index
    if (!checkParameterCount(expr, Keywords.TEMPLATETYPEOF)) {
      return false;
    }
    // The parameter must be a valid type expression
    if (!validTypeTransformationExpression(getCallArgument(expr, 0))) {
      warnInvalidInside(Keywords.TEMPLATETYPEOF.name, expr);
      return false;
    }
    if (!getCallArgument(expr, 1).isNumber()) {
      warnInvalid("index", expr);
      warnInvalidInside(Keywords.TEMPLATETYPEOF.name, expr);
      return false;
    }
    double index = getCallArgument(expr, 1).getDouble();
    if (index < 0 || index % 1 != 0) {
      warnInvalid("index", expr);
      warnInvalidInside(Keywords.TEMPLATETYPEOF.name, expr);
      return false;
    }
    return true;
  }

  /**
   * A record must be a valid type transformation expression or a node of the form:
   * {prop:TTLExp, prop:TTLExp, ...}
   * Notice that the values are mandatory and they must be valid type
   * transformation expressions
   */
  private boolean validRecordParam(Node expr) {
    if (expr.isObjectLit()) {
      // Each value of a property must be a valid expression
      for (Node prop : expr.children()) {
        if (!prop.hasChildren()) {
          warnInvalid("property, missing type", prop);
          return false;
        } else if (!validTypeTransformationExpression(prop.getFirstChild())) {
          return false;
        }
      }
    } else if (!validTypeTransformationExpression(expr)) {
      return false;
    }
    return true;
  }
{'fraction_non_alphanumeric': 0.09843211699360191, 'fraction_numerical': 0.004851297194684666, 'mean_word_length': 3.4615746549560855, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4671313', 'n_tokens_mistral': 8445, 'n_tokens_neox': 8146, 'n_words': 2438}