
export default function({types: t}) { return { visitor: {} }; } visitor object whose name corresponds to the type of the abstract syntax tree (ASD) node, for example, FunctionDeclaration or StringLiteral ( full list ), to which the path (path) to the node is passed. We are interested in nodes of type Identifier . export default function({types: t}) { return { visitor: { Identifier(path, {opts: options}) { } } }; } .opts property of the second argument. Through them we will pass the variable names and paths to the modules for which the import will be created. It will look like this: { plugins: [[ "babel-plugin-auto-import", { declarations: [{name: "React", path: "react"}] } ]] } .["is" + ]() . For example, path.isIdentifier() . The path can be searched among child paths using the .find(callback) method, and among parent paths using the .findParent(callback) method. The .parentPath property stores a link to the parent path.Identifier type is widely used in various types of nodes. We need only some of them. Suppose we have this code: React.Component { type: "MemberExpression", object: { type: "Identifier", name: "React" }, property: { type: "Identifier", name: "Component" }, computed: false } .type property and some other properties specific to each type. Consider the root node - MemberExpression . He has three properties. Object is the expression to the left of the point. In this case, it is an identifier. The computed property indicates whether there is an identifier or some expression on the right, for example, x["a" + b] . Property - actually, that to the right of a point.Identifier method will be called twice: for React and Component identifiers, respectively. The plugin should handle the React ID, but skip the Component ID. To do this, the identifier path must receive the parent path and, if it is a node of type MemberExpression , check whether the identifier is a .object property. We put the check into a separate function: export default function({types: t}) { return { visitor: { Identifier(path, {opts: options}) { if (!isCorrectIdentifier(path)) return; } } }; function isCorrectIdentifier(path) { let {parentPath} = path; if (parentPath.isMemberExpression() && parentPath.get("object") == path) return true; } } function isCorrectIdentifier(path) { let {parentPath} = path; if (parentPath.isArrayExpression()) return true; else if (parentPath.isArrowFunctionExpression()) return true; else if (parentPath.isAssignmentExpression() && parentPath.get("right") == path) return true; else if (parentPath.isAwaitExpression()) return true; else if (parentPath.isBinaryExpression()) return true; else if (parentPath.bindExpression && parentPath.bindExpression()) return true; else if (parentPath.isCallExpression()) return true; else if (parentPath.isClassDeclaration() && parentPath.get("superClass") == path) return true; else if (parentPath.isClassExpression() && parentPath.get("superClass") == path) return true; else if (parentPath.isConditionalExpression()) return true; else if (parentPath.isDecorator()) return true; else if (parentPath.isDoWhileStatement()) return true; else if (parentPath.isExpressionStatement()) return true; else if (parentPath.isExportDefaultDeclaration()) return true; else if (parentPath.isForInStatement()) return true; else if (parentPath.isForStatement()) return true; else if (parentPath.isIfStatement()) return true; else if (parentPath.isLogicalExpression()) return true; else if (parentPath.isMemberExpression() && parentPath.get("object") == path) return true; else if (parentPath.isNewExpression()) return true; else if (parentPath.isObjectProperty() && parentPath.get("value") == path) return !parentPath.node.shorthand; else if (parentPath.isReturnStatement()) return true; else if (parentPath.isSpreadElement()) return true; else if (parentPath.isSwitchStatement()) return true; else if (parentPath.isTaggedTemplateExpression()) return true; else if (parentPath.isThrowStatement()) return true; else if (parentPath.isUnaryExpression()) return true; else if (parentPath.isVariableDeclarator() && parentPath.get("init") == path) return true; return false; } scope . With it, we will iterate all scopes, starting with the current. The variables for the current scope are in the .bindings property. The link to the parent scope is in the .parent property. It remains to recursively go through all the variables of all scopes and check if our identifier is there. export default function({types: t}) { return { visitor: { Identifier(path, {opts: options}) { if (!isCorrectIdentifier(path)) return; let {node: identifier, scope} = path; if (isDefined(identifier, scope)) return; } } }; // ... function isDefined(identifier, {bindings, parent}) { let variables = Object.keys(bindings); if (variables.some(has, identifier)) return true; return parent ? isDefined(identifier, parent) : false; } function has(identifier) { let {name} = this; return identifier == name; } } options from the declaration of “global” variables and process them: let {declarations} = options; declarations.some(declaration => { if (declaration.name == identifier.name) { let program = path.findParent(path => path.isProgram()); insertImport(program, declaration); return true; } }); .reduce method to get an array with paths like ImportDeclaration : function insertImport(program, { name, path }) { let programBody = program.get("body"); let currentImportDeclarations = programBody.reduce(currentPath => { if (currentPath.isImportDeclaration()) list.push(currentPath); return list; }, []); } let importDidAppend = currentImportDeclarations.some(({node: importDeclaration}) => { if (importDeclaration.source.value == path) { return importDeclaration.specifiers.some(specifier => specifier.local.name == name); } }); t . For each of the nodes has its own method. We need to create importDeclaration . We look at the documentation and see that creating import requires specifiers (that is, the names of the variables being imported) and the path to the module.export default ... ). Then create a node with a path to the module. This is a simple StringLiteral . let specifier = t.importDefaultSpecifier(t.identifier(name)); let pathToModule = t.stringLiteral(path); let importDeclaration = t.importDeclaration([specifier], pathToModule); .replaceWith(node) method, or an array of nodes using the .replaceWithMultiple([...nodes]) method .replaceWithMultiple([...nodes]) . Can be removed using the .remove() method. For insertion, the .insertBefore(node) and .insertAfter(node) methods are used to insert a node before or after the path, respectively.program node has a property .body , which contains an array of expressions representing the program. To insert nodes into such “container” arrays, paths have special methods pushContainer and unshiftContainer . We use the latter: program.unshiftContainer("body", importNode); Source: https://habr.com/ru/post/330018/
All Articles