fixclosure
Version:
JavaScript dependency checker/fixer for Closure Library based on ECMAScript AST
110 lines (109 loc) • 3.61 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.leave = void 0;
/**
* Visitor for estraverse.
*/
function leave(node, uses) {
switch (node.type) {
case "MemberExpression":
case "JSXMemberExpression":
if (hasComputedProperty_(node)) {
return;
}
if (isIdentifierType_(node.object) &&
!isLocalVar_(node.object, this.parents())) {
const parents = this.parents().concat(node).reverse();
const path = nonNullable(this.path()).concat("object").reverse();
const use = registerIdentifier_(node.object, parents, path);
if (use) {
uses.push(use);
}
}
break;
default:
break;
}
}
exports.leave = leave;
function nonNullable(value) {
/* istanbul ignore if */
if (value == null) {
throw new TypeError(`The value must be non-nullable, but actually ${value}`);
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return value;
}
/**
* @return True if the property is computed like `foo["bar"]` not `foo.bar`.
*/
function hasComputedProperty_(node) {
return node.type === "MemberExpression" && node.computed;
}
/**
* @return True if the type is Identifier or JSXIdentifier.
*/
function isIdentifierType_(node) {
return node.type === "Identifier" || node.type === "JSXIdentifier";
}
/**
* @return True if the object is a local variable, not a global object.
* TODO: use escope to support complicated patterns like destructuring.
*/
function isLocalVar_(object, parents) {
const nodeName = object.name;
let node;
parents = parents.slice();
while ((node = parents.pop())) {
switch (node.type) {
case "FunctionExpression":
case "FunctionDeclaration":
if (node.params &&
node.params.some((param) => param.type === "Identifier" && param.name === nodeName)) {
return true;
}
break;
case "BlockStatement":
if (node.body &&
node.body.some((bodyPart) => bodyPart.type === "VariableDeclaration" &&
bodyPart.declarations.some((declaration) => declaration.type === "VariableDeclarator" &&
declaration.id.type === "Identifier" &&
declaration.id.name === nodeName))) {
return true;
}
break;
default:
break;
}
}
return false;
}
function registerIdentifier_(node, parents, path) {
const namespace = [node.name];
for (let i = 0; i < parents.length; i++) {
const current = parents[i];
const parentKey = path[i];
switch (current.type) {
case "MemberExpression":
case "JSXMemberExpression":
if (!hasComputedProperty_(current) &&
isIdentifierType_(current.property)) {
namespace.push(current.property.name);
}
else {
return createMemberObject_(namespace, current, parentKey);
}
break;
default:
return createMemberObject_(namespace, current, parentKey);
}
}
return null;
}
function createMemberObject_(namespace, node, parentKey) {
return {
name: namespace,
node,
key: parentKey,
};
}