415 lines
12 KiB
JavaScript
415 lines
12 KiB
JavaScript
/**
|
|
* 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.
|
|
*/
|
|
|
|
const MultiMap = require('multimap');
|
|
|
|
function addEach(thisMap, ...otherMaps) {
|
|
otherMaps.forEach(otherMap => {
|
|
otherMap.forEachEntry((v, k) => {
|
|
thisMap.set.apply(thisMap, [k].concat(v));
|
|
});
|
|
});
|
|
return thisMap;
|
|
}
|
|
|
|
let identity; // initialised below EarlyErrorState
|
|
|
|
class EarlyErrorState {
|
|
|
|
constructor() {
|
|
this.errors = [];
|
|
// errors that are only errors in strict mode code
|
|
this.strictErrors = [];
|
|
|
|
// Label values used in LabeledStatement nodes; cleared at function boundaries
|
|
this.usedLabelNames = [];
|
|
|
|
// BreakStatement nodes; cleared at iteration; switch; and function boundaries
|
|
this.freeBreakStatements = [];
|
|
// ContinueStatement nodes; cleared at
|
|
this.freeContinueStatements = [];
|
|
|
|
// labeled BreakStatement nodes; cleared at LabeledStatement with same Label and function boundaries
|
|
this.freeLabeledBreakStatements = [];
|
|
// labeled ContinueStatement nodes; cleared at labeled iteration statement with same Label and function boundaries
|
|
this.freeLabeledContinueStatements = [];
|
|
|
|
// NewTargetExpression nodes; cleared at function (besides arrow expression) boundaries
|
|
this.newTargetExpressions = [];
|
|
|
|
// BindingIdentifier nodes; cleared at containing declaration node
|
|
this.boundNames = new MultiMap;
|
|
// BindingIdentifiers that were found to be in a lexical binding position
|
|
this.lexicallyDeclaredNames = new MultiMap;
|
|
// BindingIdentifiers that were the name of a FunctionDeclaration
|
|
this.functionDeclarationNames = new MultiMap;
|
|
// BindingIdentifiers that were found to be in a variable binding position
|
|
this.varDeclaredNames = new MultiMap;
|
|
// BindingIdentifiers that were found to be in a variable binding position
|
|
this.forOfVarDeclaredNames = [];
|
|
|
|
// Names that this module exports
|
|
this.exportedNames = new MultiMap;
|
|
// Locally declared names that are referenced in export declarations
|
|
this.exportedBindings = new MultiMap;
|
|
|
|
// CallExpressions with Super callee
|
|
this.superCallExpressions = [];
|
|
// SuperCall expressions in the context of a Method named "constructor"
|
|
this.superCallExpressionsInConstructorMethod = [];
|
|
// MemberExpressions with Super object
|
|
this.superPropertyExpressions = [];
|
|
|
|
// YieldExpression and YieldGeneratorExpression nodes; cleared at function boundaries
|
|
this.yieldExpressions = [];
|
|
// AwaitExpression nodes; cleared at function boundaries
|
|
this.awaitExpressions = [];
|
|
}
|
|
|
|
|
|
addFreeBreakStatement(s) {
|
|
this.freeBreakStatements.push(s);
|
|
return this;
|
|
}
|
|
|
|
addFreeLabeledBreakStatement(s) {
|
|
this.freeLabeledBreakStatements.push(s);
|
|
return this;
|
|
}
|
|
|
|
clearFreeBreakStatements() {
|
|
this.freeBreakStatements = [];
|
|
return this;
|
|
}
|
|
|
|
addFreeContinueStatement(s) {
|
|
this.freeContinueStatements.push(s);
|
|
return this;
|
|
}
|
|
|
|
addFreeLabeledContinueStatement(s) {
|
|
this.freeLabeledContinueStatements.push(s);
|
|
return this;
|
|
}
|
|
|
|
clearFreeContinueStatements() {
|
|
this.freeContinueStatements = [];
|
|
return this;
|
|
}
|
|
|
|
enforceFreeBreakStatementErrors(createError) {
|
|
[].push.apply(this.errors, this.freeBreakStatements.map(createError));
|
|
this.freeBreakStatements = [];
|
|
return this;
|
|
}
|
|
|
|
enforceFreeLabeledBreakStatementErrors(createError) {
|
|
[].push.apply(this.errors, this.freeLabeledBreakStatements.map(createError));
|
|
this.freeLabeledBreakStatements = [];
|
|
return this;
|
|
}
|
|
|
|
enforceFreeContinueStatementErrors(createError) {
|
|
[].push.apply(this.errors, this.freeContinueStatements.map(createError));
|
|
this.freeContinueStatements = [];
|
|
return this;
|
|
}
|
|
|
|
enforceFreeLabeledContinueStatementErrors(createError) {
|
|
[].push.apply(this.errors, this.freeLabeledContinueStatements.map(createError));
|
|
this.freeLabeledContinueStatements = [];
|
|
return this;
|
|
}
|
|
|
|
|
|
observeIterationLabel(label) {
|
|
this.usedLabelNames.push(label);
|
|
this.freeLabeledBreakStatements = this.freeLabeledBreakStatements.filter(s => s.label !== label);
|
|
this.freeLabeledContinueStatements = this.freeLabeledContinueStatements.filter(s => s.label !== label);
|
|
return this;
|
|
}
|
|
|
|
observeNonIterationLabel(label) {
|
|
this.usedLabelNames.push(label);
|
|
this.freeLabeledBreakStatements = this.freeLabeledBreakStatements.filter(s => s.label !== label);
|
|
return this;
|
|
}
|
|
|
|
clearUsedLabelNames() {
|
|
this.usedLabelNames = [];
|
|
return this;
|
|
}
|
|
|
|
|
|
observeSuperCallExpression(node) {
|
|
this.superCallExpressions.push(node);
|
|
return this;
|
|
}
|
|
|
|
observeConstructorMethod() {
|
|
this.superCallExpressionsInConstructorMethod = this.superCallExpressions;
|
|
this.superCallExpressions = [];
|
|
return this;
|
|
}
|
|
|
|
clearSuperCallExpressionsInConstructorMethod() {
|
|
this.superCallExpressionsInConstructorMethod = [];
|
|
return this;
|
|
}
|
|
|
|
enforceSuperCallExpressions(createError) {
|
|
[].push.apply(this.errors, this.superCallExpressions.map(createError));
|
|
[].push.apply(this.errors, this.superCallExpressionsInConstructorMethod.map(createError));
|
|
this.superCallExpressions = [];
|
|
this.superCallExpressionsInConstructorMethod = [];
|
|
return this;
|
|
}
|
|
|
|
enforceSuperCallExpressionsInConstructorMethod(createError) {
|
|
[].push.apply(this.errors, this.superCallExpressionsInConstructorMethod.map(createError));
|
|
this.superCallExpressionsInConstructorMethod = [];
|
|
return this;
|
|
}
|
|
|
|
|
|
observeSuperPropertyExpression(node) {
|
|
this.superPropertyExpressions.push(node);
|
|
return this;
|
|
}
|
|
|
|
clearSuperPropertyExpressions() {
|
|
this.superPropertyExpressions = [];
|
|
return this;
|
|
}
|
|
|
|
enforceSuperPropertyExpressions(createError) {
|
|
[].push.apply(this.errors, this.superPropertyExpressions.map(createError));
|
|
this.superPropertyExpressions = [];
|
|
return this;
|
|
}
|
|
|
|
|
|
observeNewTargetExpression(node) {
|
|
this.newTargetExpressions.push(node);
|
|
return this;
|
|
}
|
|
|
|
clearNewTargetExpressions() {
|
|
this.newTargetExpressions = [];
|
|
return this;
|
|
}
|
|
|
|
|
|
bindName(name, node) {
|
|
this.boundNames.set(name, node);
|
|
return this;
|
|
}
|
|
|
|
clearBoundNames() {
|
|
this.boundNames = new MultiMap;
|
|
return this;
|
|
}
|
|
|
|
observeLexicalDeclaration() {
|
|
addEach(this.lexicallyDeclaredNames, this.boundNames);
|
|
this.boundNames = new MultiMap;
|
|
return this;
|
|
}
|
|
|
|
observeLexicalBoundary() {
|
|
this.previousLexicallyDeclaredNames = this.lexicallyDeclaredNames;
|
|
this.lexicallyDeclaredNames = new MultiMap;
|
|
this.functionDeclarationNames = new MultiMap;
|
|
return this;
|
|
}
|
|
|
|
enforceDuplicateLexicallyDeclaredNames(createError) {
|
|
this.lexicallyDeclaredNames.forEachEntry(nodes => {
|
|
if (nodes.length > 1) {
|
|
nodes.slice(1).forEach(dupeNode => {
|
|
this.addError(createError(dupeNode));
|
|
});
|
|
}
|
|
});
|
|
return this;
|
|
}
|
|
|
|
enforceConflictingLexicallyDeclaredNames(otherNames, createError) {
|
|
this.lexicallyDeclaredNames.forEachEntry((nodes, bindingName) => {
|
|
if (otherNames.has(bindingName)) {
|
|
nodes.forEach(conflictingNode => {
|
|
this.addError(createError(conflictingNode));
|
|
});
|
|
}
|
|
});
|
|
return this;
|
|
}
|
|
|
|
observeFunctionDeclaration() {
|
|
this.observeVarBoundary();
|
|
addEach(this.functionDeclarationNames, this.boundNames);
|
|
this.boundNames = new MultiMap;
|
|
return this;
|
|
}
|
|
|
|
functionDeclarationNamesAreLexical() {
|
|
addEach(this.lexicallyDeclaredNames, this.functionDeclarationNames);
|
|
this.functionDeclarationNames = new MultiMap;
|
|
return this;
|
|
}
|
|
|
|
observeVarDeclaration() {
|
|
addEach(this.varDeclaredNames, this.boundNames);
|
|
this.boundNames = new MultiMap;
|
|
return this;
|
|
}
|
|
|
|
recordForOfVars() {
|
|
this.varDeclaredNames.forEach(bindingIdentifier => {
|
|
this.forOfVarDeclaredNames.push(bindingIdentifier);
|
|
});
|
|
return this;
|
|
}
|
|
|
|
observeVarBoundary() {
|
|
this.lexicallyDeclaredNames = new MultiMap;
|
|
this.functionDeclarationNames = new MultiMap;
|
|
this.varDeclaredNames = new MultiMap;
|
|
this.forOfVarDeclaredNames = [];
|
|
return this;
|
|
}
|
|
|
|
|
|
exportName(name, node) {
|
|
this.exportedNames.set(name, node);
|
|
return this;
|
|
}
|
|
|
|
exportDeclaredNames() {
|
|
addEach(this.exportedNames, this.lexicallyDeclaredNames, this.varDeclaredNames);
|
|
addEach(this.exportedBindings, this.lexicallyDeclaredNames, this.varDeclaredNames);
|
|
return this;
|
|
}
|
|
|
|
exportBinding(name, node) {
|
|
this.exportedBindings.set(name, node);
|
|
return this;
|
|
}
|
|
|
|
clearExportedBindings() {
|
|
this.exportedBindings = new MultiMap;
|
|
return this;
|
|
}
|
|
|
|
|
|
observeYieldExpression(node) {
|
|
this.yieldExpressions.push(node);
|
|
return this;
|
|
}
|
|
|
|
clearYieldExpressions() {
|
|
this.yieldExpressions = [];
|
|
return this;
|
|
}
|
|
|
|
observeAwaitExpression(node) {
|
|
this.awaitExpressions.push(node);
|
|
return this;
|
|
}
|
|
|
|
clearAwaitExpressions() {
|
|
this.awaitExpressions = [];
|
|
return this;
|
|
}
|
|
|
|
|
|
addError(e) {
|
|
this.errors.push(e);
|
|
return this;
|
|
}
|
|
|
|
addStrictError(e) {
|
|
this.strictErrors.push(e);
|
|
return this;
|
|
}
|
|
|
|
enforceStrictErrors() {
|
|
[].push.apply(this.errors, this.strictErrors);
|
|
this.strictErrors = [];
|
|
return this;
|
|
}
|
|
|
|
|
|
// MONOID IMPLEMENTATION
|
|
|
|
static empty() {
|
|
return identity;
|
|
}
|
|
|
|
concat(s) {
|
|
if (this === identity) return s;
|
|
if (s === identity) return this;
|
|
[].push.apply(this.errors, s.errors);
|
|
[].push.apply(this.strictErrors, s.strictErrors);
|
|
[].push.apply(this.usedLabelNames, s.usedLabelNames);
|
|
[].push.apply(this.freeBreakStatements, s.freeBreakStatements);
|
|
[].push.apply(this.freeContinueStatements, s.freeContinueStatements);
|
|
[].push.apply(this.freeLabeledBreakStatements, s.freeLabeledBreakStatements);
|
|
[].push.apply(this.freeLabeledContinueStatements, s.freeLabeledContinueStatements);
|
|
[].push.apply(this.newTargetExpressions, s.newTargetExpressions);
|
|
addEach(this.boundNames, s.boundNames);
|
|
addEach(this.lexicallyDeclaredNames, s.lexicallyDeclaredNames);
|
|
addEach(this.functionDeclarationNames, s.functionDeclarationNames);
|
|
addEach(this.varDeclaredNames, s.varDeclaredNames);
|
|
[].push.apply(this.forOfVarDeclaredNames, s.forOfVarDeclaredNames);
|
|
addEach(this.exportedNames, s.exportedNames);
|
|
addEach(this.exportedBindings, s.exportedBindings);
|
|
[].push.apply(this.superCallExpressions, s.superCallExpressions);
|
|
[].push.apply(this.superCallExpressionsInConstructorMethod, s.superCallExpressionsInConstructorMethod);
|
|
[].push.apply(this.superPropertyExpressions, s.superPropertyExpressions);
|
|
[].push.apply(this.yieldExpressions, s.yieldExpressions);
|
|
[].push.apply(this.awaitExpressions, s.awaitExpressions);
|
|
return this;
|
|
}
|
|
|
|
}
|
|
|
|
identity = new EarlyErrorState;
|
|
Object.getOwnPropertyNames(EarlyErrorState.prototype).forEach(methodName => {
|
|
if (methodName === 'constructor') return;
|
|
Object.defineProperty(identity, methodName, {
|
|
value() {
|
|
return EarlyErrorState.prototype[methodName].apply(new EarlyErrorState, arguments);
|
|
},
|
|
enumerable: false,
|
|
writable: true,
|
|
configurable: true,
|
|
});
|
|
});
|
|
|
|
class EarlyError extends Error {
|
|
constructor(node, message) {
|
|
super(message);
|
|
this.node = node;
|
|
this.message = message;
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
EarlyErrorState,
|
|
EarlyError,
|
|
};
|