com.shapesecurity.shift.validator.Validator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of shift Show documentation
Show all versions of shift Show documentation
Shift format ECMAScript 6 AST tooling
/*
* Copyright 2014 Shape Security, Inc.
*
* 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.shapesecurity.shift.validator;
import com.shapesecurity.functional.data.ImmutableList;
import com.shapesecurity.functional.data.Maybe;
import com.shapesecurity.shift.ast.*;
import com.shapesecurity.shift.parser.JsError;
import com.shapesecurity.shift.parser.Token;
import com.shapesecurity.shift.parser.Tokenizer;
import com.shapesecurity.shift.parser.token.EOFToken;
import com.shapesecurity.shift.parser.token.StringLiteralToken;
import com.shapesecurity.shift.utils.Utils;
import com.shapesecurity.shift.visitor.Director;
import com.shapesecurity.shift.visitor.MonoidalReducer;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Validator extends MonoidalReducer {
public Validator() {
super(ValidationContext.MONOID);
}
private static boolean checkIsLiteralRegExpPattern(String pattern) {
// copied from tokenizer for getting a regex token
int index = 0;
boolean terminated = false;
boolean classMarker = false;
while (index < pattern.length()) {
char ch = pattern.charAt(index);
if (ch == '\\') {
index++;
ch = pattern.charAt(index);
if (Utils.isLineTerminator(ch)) {
return false;
}
index++;
} else if (Utils.isLineTerminator(ch)) {
return false;
} else {
if (classMarker) {
if (ch == ']') {
classMarker = false;
}
} else {
if (ch == '/') {
terminated = true;
index++;
break;
} else if (ch == '[') {
classMarker = true;
}
}
index++;
}
}
if (!terminated) {
return false;
}
while (index < pattern.length()) {
char ch = pattern.charAt(index);
if (ch == '\\') {
return false;
}
if (!Utils.isIdentifierPart(ch)) {
break;
}
index++;
}
return true;
}
public static boolean checkIsValidIdentifierName(String name) {
return name.length() > 0 && Utils.isIdentifierStart(name.charAt(0)) && name.chars().allMatch(Utils::isIdentifierPart);
}
public static ImmutableList validate(Script script) {
List errors = Director.reduceScript(new Validator(), script).errors;
return ImmutableList.from(Director.reduceScript(new Validator(), script).errors);
}
public static ImmutableList validate(Module module) {
List errors = Director.reduceModule(new Validator(), module).errors;
return ImmutableList.from(Director.reduceModule(new Validator(), module).errors);
}
private boolean checkIsStringLiteral(String rawValue) {
Tokenizer tokenizer;
try {
tokenizer = new Tokenizer("\'" + rawValue + "\'", false);
Token token = tokenizer.lookahead;
if (!(token instanceof StringLiteralToken)) {
System.out.println("NOT STRING LITERAL");
return false;
}
token = tokenizer.collectToken();
if (!(token instanceof EOFToken)) {
return false;
}
} catch (JsError jsError) {
try {
tokenizer = new Tokenizer("\"" + rawValue + "\"", false);
Token token = tokenizer.lookahead;
if (!(token instanceof StringLiteralToken)) {
System.out.println("NOT STRING LITERAL");
return false;
}
token = tokenizer.collectToken();
if (!(token instanceof EOFToken)) {
return false;
}
} catch (JsError jsError1) {
return false;
}
}
return true;
}
private boolean isProblematicIfStatement(IfStatement node) {
if (node.alternate.isNothing()) {
return false;
}
Maybe current = Maybe.just(node.consequent);
do {
Statement currentStmt = current.just();
if (currentStmt instanceof IfStatement && ((IfStatement) currentStmt).alternate.isNothing()) {
return true;
}
current = trailingStatement(currentStmt);
} while (current.isJust());
return false;
}
@NotNull
@Override
public ValidationContext reduceBindingIdentifier(@NotNull BindingIdentifier node) {
ValidationContext s = super.reduceBindingIdentifier(node);
if (!checkIsValidIdentifierName(node.name)) {
if (node.name.equals("*default*")) {
s.addBindingIdentifierCalledDefault(node);
} else {
s.addError(new ValidationError(node, ValidationErrorMessages.VALID_BINDING_IDENTIFIER_NAME));
}
}
return s;
}
@NotNull
@Override
public ValidationContext reduceBreakStatement(@NotNull BreakStatement node) {
ValidationContext s = super.reduceBreakStatement(node);
if (node.label.isJust() && !checkIsValidIdentifierName(node.label.just())) {
s.addError(new ValidationError(node, ValidationErrorMessages.VALID_BREAK_STATEMENT_LABEL));
}
return s;
}
@NotNull
@Override
public ValidationContext reduceCatchClause(@NotNull CatchClause node, @NotNull ValidationContext binding, @NotNull ValidationContext body) {
ValidationContext s = super.reduceCatchClause(node, binding, body);
if (node.binding instanceof MemberExpression) {
s.addError(new ValidationError(node, ValidationErrorMessages.CATCH_CLAUSE_BINDING_NOT_MEMBER_EXPRESSION));
}
return s;
}
@NotNull
@Override
public ValidationContext reduceContinueStatement(@NotNull ContinueStatement node) {
ValidationContext s = super.reduceContinueStatement(node);
if (node.label.isJust() && !checkIsValidIdentifierName(node.label.just())) {
s.addError(new ValidationError(node, ValidationErrorMessages.VALID_CONTINUE_STATEMENT_LABEL));
}
return s;
}
@NotNull
@Override
public ValidationContext reduceDirective(@NotNull Directive node) {
ValidationContext s = super.reduceDirective(node);
if (!checkIsStringLiteral(node.rawValue)) {
s.addError(new ValidationError(node, ValidationErrorMessages.VALID_DIRECTIVE));
}
return s;
}
@NotNull
@Override
public ValidationContext reduceExportDefault(@NotNull ExportDefault node, @NotNull ValidationContext body) {
ValidationContext s = super.reduceExportDefault(node, body);
if (node.body instanceof FunctionDeclaration || node.body instanceof ClassDeclaration) {
s.clearBindingIdentifiersCalledDefault();
}
return s;
}
@NotNull
@Override
public ValidationContext reduceExportSpecifier(@NotNull ExportSpecifier node) {
ValidationContext s = super.reduceExportSpecifier(node);
if (node.name.isJust() && !checkIsValidIdentifierName(node.name.just())) {
s.addError(new ValidationError(node, ValidationErrorMessages.VALID_EXPORT_SPECIFIER_NAME));
}
if (!checkIsValidIdentifierName(node.exportedName)) {
s.addError(new ValidationError(node, ValidationErrorMessages.VALID_EXPORTED_NAME));
}
return s;
}
@NotNull
@Override
public ValidationContext reduceForInStatement(@NotNull ForInStatement node, @NotNull ValidationContext left, @NotNull ValidationContext right, @NotNull ValidationContext body) {
ValidationContext s = super.reduceForInStatement(node, left, right, body);
if (node.left instanceof VariableDeclaration) {
VariableDeclaration varDec = (VariableDeclaration) node.left;
if (varDec.declarators.length != 1) {
s.addError(new ValidationError(node, ValidationErrorMessages.ONE_VARIABLE_DECLARATOR_IN_FOR_IN));
}
if (varDec.declarators.maybeHead().just().init.isJust()) {
s.addError(new ValidationError(node, ValidationErrorMessages.NO_INIT_IN_VARIABLE_DECLARATOR_IN_FOR_IN));
}
}
return s;
}
@NotNull
@Override
public ValidationContext reduceForOfStatement(@NotNull ForOfStatement node, @NotNull ValidationContext left, @NotNull ValidationContext right, @NotNull ValidationContext body) {
ValidationContext s = super.reduceForOfStatement(node, left, right, body);
if (node.left instanceof VariableDeclaration) {
VariableDeclaration varDec = (VariableDeclaration) node.left;
if (varDec.declarators.length != 1) {
s.addError(new ValidationError(node, ValidationErrorMessages.ONE_VARIABLE_DECLARATOR_IN_FOR_OF));
}
if (varDec.declarators.maybeHead().just().init.isJust()) {
s.addError(new ValidationError(node, ValidationErrorMessages.NO_INIT_IN_VARIABLE_DECLARATOR_IN_FOR_OF));
}
}
return s;
}
@NotNull
@Override
public ValidationContext reduceFormalParameters(@NotNull FormalParameters node, @NotNull ImmutableList items, @NotNull Maybe rest) {
ValidationContext s = super.reduceFormalParameters(node, items, rest);
node.items.foreach(x -> {
if (x instanceof Binding) {
if (x instanceof MemberExpression) {
s.addError(new ValidationError(node, ValidationErrorMessages.FORMAL_PARAMETER_ITEMS_NOT_MEMBER_EXPRESSION));
}
} else if (x instanceof BindingWithDefault) {
if (((BindingWithDefault) x).binding instanceof MemberExpression) {
s.addError(new ValidationError(node, ValidationErrorMessages.FORMAL_PARAMETER_ITEMS_BINDING_NOT_MEMBER_EXPRESSION));
}
}
});
return s;
}
@NotNull
@Override
public ValidationContext reduceFunctionBody(@NotNull FunctionBody node, @NotNull ImmutableList directives, @NotNull ImmutableList statements
) {
ValidationContext s = super.reduceFunctionBody(node, directives, statements);
s.clearFreeReturnStatements();
return s;
}
@NotNull
@Override
public ValidationContext reduceFunctionDeclaration(@NotNull FunctionDeclaration node, @NotNull ValidationContext name, @NotNull ValidationContext params, @NotNull ValidationContext body) {
ValidationContext s = super.reduceFunctionDeclaration(node, name, params, body);
if (node.isGenerator) {
s.clearYieldExpressionsNotInGeneratorContext();
s.clearYieldGeneratorExpressionsNotInGeneratorContext();
}
return s;
}
@NotNull
@Override
public ValidationContext reduceFunctionExpression(@NotNull FunctionExpression node, @NotNull Maybe name, @NotNull ValidationContext params, @NotNull ValidationContext body) {
ValidationContext s = super.reduceFunctionExpression(node, name, params, body);
if (node.isGenerator) {
s.clearYieldExpressionsNotInGeneratorContext();
s.clearYieldGeneratorExpressionsNotInGeneratorContext();
}
return s;
}
@NotNull
@Override
public ValidationContext reduceIdentifierExpression(@NotNull IdentifierExpression node) {
ValidationContext s = super.reduceIdentifierExpression(node);
if (!checkIsValidIdentifierName(node.name)) {
s.addError(new ValidationError(node, ValidationErrorMessages.VALID_IDENTIFIER_NAME));
}
return s;
}
@NotNull
@Override
public ValidationContext reduceIfStatement(@NotNull IfStatement node, @NotNull ValidationContext test, @NotNull ValidationContext consequent, @NotNull Maybe alternate) {
ValidationContext s = super.reduceIfStatement(node, test, consequent, alternate);
if (isProblematicIfStatement(node)) {
s.addError(new ValidationError(node, ValidationErrorMessages.VALID_IF_STATEMENT));
}
return s;
}
@NotNull
@Override
public ValidationContext reduceImportSpecifier(@NotNull ImportSpecifier node, @NotNull ValidationContext binding) {
ValidationContext s = super.reduceImportSpecifier(node, binding);
if (node.name.isJust() && !checkIsValidIdentifierName(node.name.just())) {
s.addError(new ValidationError(node, ValidationErrorMessages.VALID_IMPORT_SPECIFIER_NAME));
}
return s;
}
@NotNull
@Override
public ValidationContext reduceLabeledStatement(@NotNull LabeledStatement node, @NotNull ValidationContext body) {
ValidationContext s = super.reduceLabeledStatement(node, body);
if (!checkIsValidIdentifierName(node.label)) {
s.addError(new ValidationError(node, ValidationErrorMessages.VALID_LABEL));
}
return s;
}
@NotNull
@Override
public ValidationContext reduceLiteralNumericExpression(@NotNull LiteralNumericExpression node) {
ValidationContext s = super.reduceLiteralNumericExpression(node);
if (node.value.isNaN()) {
s.addError(new ValidationError(node, ValidationErrorMessages.LITERAL_NUMERIC_VALUE_NOT_NAN));
}
if (node.value < 0) {
s.addError(new ValidationError(node, ValidationErrorMessages.LITERAL_NUMERIC_VALUE_NOT_NEGATIVE));
}
if (node.value.isInfinite()) {
s.addError(new ValidationError(node, ValidationErrorMessages.LITERAL_NUMERIC_VALUE_NOT_INFINITE));
}
return s;
}
@NotNull
@Override
public ValidationContext reduceLiteralRegExpExpression(@NotNull LiteralRegExpExpression node) {
ValidationContext s = super.reduceLiteralRegExpExpression(node);
if (!checkIsLiteralRegExpPattern(node.pattern)) {
s.addError(new ValidationError(node, ValidationErrorMessages.VALID_REG_EX_PATTERN));
}
if (node.flags.length() > 0) {
boolean hasValidFlags = node.flags.chars().allMatch(x -> x == 'g' || x == 'i' || x == 'm' || x == 'u' || x == 'y');
if (!hasValidFlags) {
s.addError(new ValidationError(node, ValidationErrorMessages.VALID_REG_EX_FLAG));
}
}
Map charMap = new HashMap<>();
node.flags.chars().forEach(x -> {
if (charMap.containsKey(x)) {
s.addError(new ValidationError(node, ValidationErrorMessages.NO_DUPLICATE_REG_EX_FLAG));
} else {
charMap.put(x, true);
}
});
return s;
}
@NotNull
@Override
public ValidationContext reduceMethod(@NotNull Method node, @NotNull ValidationContext params, @NotNull ValidationContext body, @NotNull ValidationContext name) {
ValidationContext s = super.reduceMethod(node, params, body, name);
if (node.isGenerator) {
s.clearYieldExpressionsNotInGeneratorContext();
s.clearYieldGeneratorExpressionsNotInGeneratorContext();
}
return s;
}
@NotNull
@Override
public ValidationContext reduceModule(@NotNull Module node, @NotNull ImmutableList directives, @NotNull ImmutableList items
) {
ValidationContext s = super.reduceModule(node, directives, items);
s.enforceFreeReturnStatements(returnStatement -> new ValidationError(returnStatement, ValidationErrorMessages.RETURN_STATEMENT_IN_FUNCTION_BODY));
s.enforceBindingIdentifiersCalledDefault(bindingIdentifier -> new ValidationError(bindingIdentifier, ValidationErrorMessages.BINDING_IDENTIFIERS_CALLED_DEFAULT));
s.enforceYieldExpressionsNotInGeneratorContext(yieldExpression -> new ValidationError(yieldExpression, ValidationErrorMessages.VALID_YIELD_EXPRESSION_POSITION));
s.enforceYieldGeneratorExpressionsNotInGeneratorContext(yieldGeneratorExpression -> new ValidationError(yieldGeneratorExpression, ValidationErrorMessages.VALID_YIELD_GENERATOR_EXPRESSION_POSITION));
return s;
}
@NotNull
@Override
public ValidationContext reduceReturnStatement(@NotNull ReturnStatement node, @NotNull Maybe expression
) {
ValidationContext s = super.reduceReturnStatement(node, expression);
s.addFreeReturnStatement(node);
return s;
}
@NotNull
@Override
public ValidationContext reduceScript(@NotNull Script node, @NotNull ImmutableList directives, @NotNull ImmutableList statements
) {
ValidationContext s = super.reduceScript(node, directives, statements);
s.enforceFreeReturnStatements(returnStatement -> new ValidationError(returnStatement, ValidationErrorMessages.RETURN_STATEMENT_IN_FUNCTION_BODY));
s.enforceBindingIdentifiersCalledDefault(bindingIdentifier -> new ValidationError(bindingIdentifier, ValidationErrorMessages.BINDING_IDENTIFIERS_CALLED_DEFAULT));
s.enforceYieldExpressionsNotInGeneratorContext(yieldExpression -> new ValidationError(yieldExpression, ValidationErrorMessages.VALID_YIELD_EXPRESSION_POSITION));
s.enforceYieldGeneratorExpressionsNotInGeneratorContext(yieldGeneratorExpression -> new ValidationError(yieldGeneratorExpression, ValidationErrorMessages.VALID_YIELD_GENERATOR_EXPRESSION_POSITION));
return s;
}
@NotNull
@Override
public ValidationContext reduceSetter(@NotNull Setter node, @NotNull ValidationContext name, @NotNull ValidationContext parameter, @NotNull ValidationContext body) {
ValidationContext s = super.reduceSetter(node, name, parameter, body);
if (node.param instanceof Binding) {
if (node.param instanceof MemberExpression) {
s.addError(new ValidationError(node, ValidationErrorMessages.SETTER_PARAM_NOT_MEMBER_EXPRESSION));
}
} else if (node.param instanceof BindingWithDefault) {
if (((BindingWithDefault) node.param).binding instanceof MemberExpression) {
s.addError(new ValidationError(node, ValidationErrorMessages.SETTER_PARAM_BINDING_NOT_MEMBER_EXPRESSION));
}
}
return s;
}
@NotNull
@Override
public ValidationContext reduceShorthandProperty(@NotNull ShorthandProperty node) {
ValidationContext s = super.reduceShorthandProperty(node);
if (!checkIsValidIdentifierName(node.name)) {
s.addError(new ValidationError(node, ValidationErrorMessages.VALID_SHORTHAND_PROPERTY_NAME));
}
return s;
}
@NotNull
@Override
public ValidationContext reduceStaticMemberExpression(@NotNull StaticMemberExpression node, @NotNull ValidationContext object) {
ValidationContext s = super.reduceStaticMemberExpression(node, object);
if (!checkIsValidIdentifierName(node.property)) {
s.addError(new ValidationError(node, ValidationErrorMessages.VALID_STATIC_MEMBER_EXPRESSION_PROPERTY_NAME));
}
return s;
}
@NotNull
@Override
public ValidationContext reduceTemplateElement(@NotNull TemplateElement node) {
ValidationContext s = super.reduceTemplateElement(node);
if (!checkIsStringLiteral(node.rawValue)) {
s.addError(new ValidationError(node, ValidationErrorMessages.VALID_TEMPLATE_ELEMENT_VALUE));
}
return s;
}
@NotNull
@Override
public ValidationContext reduceTemplateExpression(@NotNull TemplateExpression node, @NotNull Maybe tag, @NotNull ImmutableList elements) {
ValidationContext s = super.reduceTemplateExpression(node, tag, elements);
if (elements.length > 0) {
if (node.elements.length % 2 == 0) {
s.addError(new ValidationError(node, ValidationErrorMessages.ALTERNATING_TEMPLATE_EXPRESSION_ELEMENTS));
} else {
node.elements.mapWithIndex((i, x) -> {
if (i % 2 == 0) {
if (!(x instanceof TemplateElement)) {
s.addError(new ValidationError(node, ValidationErrorMessages.ALTERNATING_TEMPLATE_EXPRESSION_ELEMENTS));
}
} else {
if (!(x instanceof Expression)) {
s.addError(new ValidationError(node, ValidationErrorMessages.ALTERNATING_TEMPLATE_EXPRESSION_ELEMENTS));
}
}
return true;
});
}
}
return s;
}
@NotNull
@Override
public ValidationContext reduceVariableDeclaration(@NotNull VariableDeclaration node, @NotNull ImmutableList declarators) {
ValidationContext s = super.reduceVariableDeclaration(node, declarators);
if (node.declarators.length == 0) {
s.addError(new ValidationError(node, ValidationErrorMessages.NOT_EMPTY_VARIABLE_DECLARATORS_LIST));
}
return s;
}
@NotNull
@Override
public ValidationContext reduceVariableDeclarationStatement(@NotNull VariableDeclarationStatement node, @NotNull ValidationContext declaration) {
ValidationContext s = super.reduceVariableDeclarationStatement(node, declaration);
if (node.declaration.kind.equals(VariableDeclarationKind.Const)) {
node.declaration.declarators.foreach(x -> {
if (x.getInit().isNothing()) {
s.addError(new ValidationError(node, ValidationErrorMessages.CONST_VARIABLE_DECLARATION_MUST_HAVE_INIT));
}
});
}
return s;
}
@NotNull
@Override
public ValidationContext reduceVariableDeclarator(@NotNull VariableDeclarator node, @NotNull ValidationContext binding, @NotNull Maybe init) {
ValidationContext s = super.reduceVariableDeclarator(node, binding, init);
if (node.binding instanceof MemberExpression) {
s.addError(new ValidationError(node, ValidationErrorMessages.VARIABLE_DECLARATION_BINDING_NOT_MEMBER_EXPRESSION));
}
return s;
}
@NotNull
@Override
public ValidationContext reduceYieldExpression(@NotNull YieldExpression node, @NotNull Maybe expression) {
ValidationContext s = super.reduceYieldExpression(node, expression);
s.addYieldExpressionsNotInGeneratorContext(node);
return s;
}
@NotNull
@Override
public ValidationContext reduceYieldGeneratorExpression(@NotNull YieldGeneratorExpression node, @NotNull ValidationContext expression) {
ValidationContext s = super.reduceYieldGeneratorExpression(node, expression);
s.addYieldGeneratorExpressionsNotInGeneratorContext(node);
return s;
}
@NotNull
private Maybe trailingStatement(Statement node) {
if (node instanceof IfStatement) {
return Maybe.just(((IfStatement) node).alternate.orJust(((IfStatement) node).consequent));
} else if (node instanceof LabeledStatement) {
return Maybe.just(((LabeledStatement) node).body);
} else if (node instanceof IterationStatement) {
return Maybe.just(((IterationStatement) node).body);
}
return Maybe.nothing();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy