284 lines
5.6 KiB
JavaScript
284 lines
5.6 KiB
JavaScript
|
|
import reduceUsing, { CloneReducer } from 'shift-reducer';
|
|
import analyzeScope, { ScopeLookup } from 'shift-scope';
|
|
|
|
import { check as isReservedWord } from 'reserved-words';
|
|
|
|
class RegexReducer extends CloneReducer {
|
|
reduceLiteralRegExpExpression(node) {
|
|
return {
|
|
...node,
|
|
unicode: false,
|
|
};
|
|
}
|
|
}
|
|
|
|
class NoReservedKeywordsReducer extends CloneReducer {
|
|
isSafeName(name) {
|
|
return !isReservedWord(name, 3)
|
|
&& !isReservedWord(name, 6, true)
|
|
&& name !== 'arguments'
|
|
&& name !== 'eval';
|
|
}
|
|
|
|
reduceLabeledStatement(node, rest) {
|
|
if (this.isSafeName(node.label)) {
|
|
return super.reduceLabeledStatement(node, rest);
|
|
}
|
|
|
|
return {
|
|
...node,
|
|
...rest,
|
|
label: node.label + '_avoid_reserved_word',
|
|
};
|
|
}
|
|
|
|
reduceBindingIdentifier(node) {
|
|
if (this.isSafeName(node.name)) {
|
|
return node;
|
|
}
|
|
|
|
return {
|
|
...node,
|
|
name: node.name + '_avoid_reserved_word',
|
|
};
|
|
}
|
|
|
|
reduceAssignmentTargetIdentifier(...args) {
|
|
return this.reduceBindingIdentifier(...args);
|
|
}
|
|
|
|
reduceIdentifierExpression(...args) {
|
|
return this.reduceBindingIdentifier(...args);
|
|
}
|
|
}
|
|
|
|
class NoIdentifierDuplicatingAnImportReducer extends CloneReducer {
|
|
reduceImportNamespace(node, { namespaceBinding, ...rest }) {
|
|
return {
|
|
...node,
|
|
...rest,
|
|
namespaceBinding: namespaceBinding && {
|
|
...namespaceBinding,
|
|
name: namespaceBinding.name + '_avoid_capture',
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
class ScopeLookupCloneReducer extends CloneReducer {
|
|
constructor(scopeLookup) {
|
|
super();
|
|
this.scopeLookup = scopeLookup;
|
|
}
|
|
}
|
|
|
|
class NoUndeclaredExportReducer extends ScopeLookupCloneReducer {
|
|
reduceExportLocals(node, { namedExports, ...rest }) {
|
|
return {
|
|
...node,
|
|
...rest,
|
|
namedExports: namedExports.filter(export_ => !export_._isUndeclaredVariable),
|
|
};
|
|
}
|
|
|
|
reduceExportLocalSpecifier(node, { name: { _isUndeclaredVariable, ...name }, ...rest }) {
|
|
return {
|
|
...node,
|
|
...rest,
|
|
name,
|
|
_isUndeclaredVariable,
|
|
};
|
|
}
|
|
|
|
reduceIdentifierExpression(node) {
|
|
const [ variable ] = this.scopeLookup.lookup(node);
|
|
return {
|
|
...node,
|
|
_isUndeclaredVariable: variable && variable.declarations.length === 0,
|
|
};
|
|
}
|
|
}
|
|
|
|
const removingHOR = Reducer => class extends Reducer {
|
|
constructor(...args) {
|
|
super(...args);
|
|
this.nodesToRemove = new Set();
|
|
this.shouldKeep = statement => !this.nodesToRemove.has(statement);
|
|
}
|
|
|
|
markForRemoval(node) {
|
|
this.nodesToRemove.add(node);
|
|
}
|
|
|
|
propagateRemoval(from, to) {
|
|
if (this.nodesToRemove.has(from)) {
|
|
this.nodesToRemove.add(to);
|
|
}
|
|
}
|
|
|
|
reduceSwitchCase(node, { consequent, ...rest }) {
|
|
return {
|
|
...node,
|
|
...rest,
|
|
consequent: consequent.filter(this.shouldKeep),
|
|
};
|
|
}
|
|
|
|
reduceSwitchDefault(node, { consequent, ...rest }) {
|
|
return {
|
|
...node,
|
|
...rest,
|
|
consequent: consequent.filter(this.shouldKeep),
|
|
};
|
|
}
|
|
|
|
reduceScript(node, { statements, ...rest }) {
|
|
return {
|
|
...node,
|
|
...rest,
|
|
statements: statements.filter(this.shouldKeep),
|
|
};
|
|
}
|
|
|
|
reduceFunctionBody(node, { statements, ...rest }) {
|
|
return {
|
|
...node,
|
|
...rest,
|
|
statements: statements.filter(this.shouldKeep),
|
|
};
|
|
}
|
|
|
|
reduceBlock(node, { statements, ...rest }) {
|
|
return {
|
|
...node,
|
|
...rest,
|
|
statements: statements.filter(this.shouldKeep),
|
|
};
|
|
}
|
|
|
|
reduceVariableDeclaration(node, { declarators, ...rest }) {
|
|
declarators = declarators.filter(this.shouldKeep);
|
|
|
|
node = {
|
|
...node,
|
|
...rest,
|
|
declarators,
|
|
};
|
|
|
|
if (declarators.length === 0) {
|
|
this.markForRemoval(node);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
reduceVariableDeclarationStatement(node, { declaration }) {
|
|
this.propagateRemoval(declaration, node);
|
|
return node;
|
|
}
|
|
|
|
reduceForStatement(node, { body }) {
|
|
this.propagateRemoval(body, node);
|
|
return node;
|
|
}
|
|
|
|
reduceWhileStatement(node, { body }) {
|
|
this.propagateRemoval(body, node);
|
|
return node;
|
|
}
|
|
|
|
reduceLabeledStatement(node, { body }) {
|
|
this.propagateRemoval(body, node);
|
|
return node;
|
|
}
|
|
|
|
reduceForInStatement(node, { body }) {
|
|
this.propagateRemoval(body, node);
|
|
return node;
|
|
}
|
|
|
|
reduceIfStatement(node, { consequent, alternate }) {
|
|
this.propagateRemoval(consequent, node);
|
|
this.propagateRemoval(alternate, node);
|
|
return node;
|
|
}
|
|
|
|
reduceDoWhileStatement(node, { body }) {
|
|
this.propagateRemoval(body, node);
|
|
return node;
|
|
}
|
|
};
|
|
|
|
class NoNonStrictFeaturesReducer extends removingHOR(CloneReducer) {
|
|
reduceWithStatement(node) {
|
|
this.markForRemoval(node);
|
|
return node;
|
|
}
|
|
|
|
reduceUnaryExpression(node) {
|
|
if (node.operator === 'delete' && node.operand.type === 'IdentifierExpression') {
|
|
return node.operand;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
}
|
|
|
|
class NoLabeledFunctionDeclarationReducer extends CloneReducer {
|
|
reduceLabeledStatement(node, { body }) {
|
|
if (body.type === 'FunctionDeclaration') {
|
|
return body;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
}
|
|
|
|
class NoLabeledBreakContinueReducer extends CloneReducer {
|
|
reduceBreakStatement(node) {
|
|
return {
|
|
...node,
|
|
label: null,
|
|
};
|
|
}
|
|
}
|
|
|
|
class UniqueBindingIdentifiersReducer extends CloneReducer {
|
|
constructor() {
|
|
super();
|
|
this.boundNames = new Set();
|
|
}
|
|
|
|
reduceBindingIdentifier(node) {
|
|
if (!this.boundNames.has(node.name)) {
|
|
this.boundNames.add(node.name);
|
|
return node;
|
|
}
|
|
|
|
return {
|
|
...node,
|
|
name: node.name + Math.random().toString(16).slice(2),
|
|
};
|
|
}
|
|
}
|
|
|
|
export default shiftAST => {
|
|
[
|
|
NoNonStrictFeaturesReducer,
|
|
RegexReducer,
|
|
NoReservedKeywordsReducer,
|
|
NoIdentifierDuplicatingAnImportReducer,
|
|
NoUndeclaredExportReducer,
|
|
NoLabeledFunctionDeclarationReducer,
|
|
UniqueBindingIdentifiersReducer,
|
|
NoLabeledBreakContinueReducer,
|
|
].forEach(Reducer => {
|
|
const scope = analyzeScope(shiftAST);
|
|
const scopeLookup = new ScopeLookup(scope);
|
|
shiftAST = reduceUsing(new Reducer(scopeLookup), shiftAST);
|
|
});
|
|
|
|
return shiftAST;
|
|
};
|