mirror of
https://github.com/Sevichecc/Urara-Blog.git
synced 2025-05-04 11:19:30 +08:00
125 lines
3 KiB
Text
125 lines
3 KiB
Text
import { Node, walk } from 'estree-walker';
|
|
import extractAssignedNames from './extractAssignedNames';
|
|
import { AttachedScope, AttachScopes } from './pluginutils';
|
|
|
|
const blockDeclarations = {
|
|
const: true,
|
|
let: true
|
|
};
|
|
|
|
interface ScopeOptions {
|
|
parent?: AttachedScope;
|
|
block?: boolean;
|
|
params?: Array<Node>;
|
|
}
|
|
|
|
class Scope implements AttachedScope {
|
|
parent?: AttachedScope;
|
|
isBlockScope: boolean;
|
|
declarations: { [key: string]: boolean };
|
|
|
|
constructor(options: ScopeOptions = {}) {
|
|
this.parent = options.parent;
|
|
this.isBlockScope = !!options.block;
|
|
|
|
this.declarations = Object.create(null);
|
|
|
|
if (options.params) {
|
|
options.params.forEach(param => {
|
|
extractAssignedNames(param).forEach(name => {
|
|
this.declarations[name] = true;
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
addDeclaration(node: Node, isBlockDeclaration: boolean, isVar: boolean): void {
|
|
if (!isBlockDeclaration && this.isBlockScope) {
|
|
// it's a `var` or function node, and this
|
|
// is a block scope, so we need to go up
|
|
this.parent!.addDeclaration(node, isBlockDeclaration, isVar);
|
|
} else if (node.id) {
|
|
extractAssignedNames(node.id).forEach(name => {
|
|
this.declarations[name] = true;
|
|
});
|
|
}
|
|
}
|
|
|
|
contains(name: string): boolean {
|
|
return this.declarations[name] || (this.parent ? this.parent.contains(name) : false);
|
|
}
|
|
}
|
|
|
|
const attachScopes: AttachScopes = function attachScopes(ast, propertyName = 'scope') {
|
|
let scope = new Scope();
|
|
|
|
walk(ast, {
|
|
enter(node, parent) {
|
|
// function foo () {...}
|
|
// class Foo {...}
|
|
if (/(Function|Class)Declaration/.test(node.type)) {
|
|
scope.addDeclaration(node, false, false);
|
|
}
|
|
|
|
// var foo = 1
|
|
if (node.type === 'VariableDeclaration') {
|
|
const kind: keyof typeof blockDeclarations = node.kind;
|
|
const isBlockDeclaration = blockDeclarations[kind];
|
|
|
|
node.declarations.forEach((declaration: Node) => {
|
|
scope.addDeclaration(declaration, isBlockDeclaration, true);
|
|
});
|
|
}
|
|
|
|
let newScope: AttachedScope | undefined;
|
|
|
|
// create new function scope
|
|
if (/Function/.test(node.type)) {
|
|
newScope = new Scope({
|
|
parent: scope,
|
|
block: false,
|
|
params: node.params
|
|
});
|
|
|
|
// named function expressions - the name is considered
|
|
// part of the function's scope
|
|
if (node.type === 'FunctionExpression' && node.id) {
|
|
newScope.addDeclaration(node, false, false);
|
|
}
|
|
}
|
|
|
|
// create new block scope
|
|
if (node.type === 'BlockStatement' && !/Function/.test(parent!.type)) {
|
|
newScope = new Scope({
|
|
parent: scope,
|
|
block: true
|
|
});
|
|
}
|
|
|
|
// catch clause has its own block scope
|
|
if (node.type === 'CatchClause') {
|
|
newScope = new Scope({
|
|
parent: scope,
|
|
params: node.param ? [node.param] : [],
|
|
block: true
|
|
});
|
|
}
|
|
|
|
if (newScope) {
|
|
Object.defineProperty(node, propertyName, {
|
|
value: newScope,
|
|
configurable: true
|
|
});
|
|
|
|
scope = newScope;
|
|
}
|
|
},
|
|
leave(node) {
|
|
if (node[propertyName]) scope = scope.parent!;
|
|
}
|
|
});
|
|
|
|
return scope;
|
|
};
|
|
|
|
export { attachScopes as default };
|