"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { addVariableDeclarations: () => addVariableDeclarations }); module.exports = __toCommonJS(src_exports); var import_traverse = __toESM(require("@babel/traverse")); var t4 = __toESM(require("@babel/types")); var import_parser = require("@codemod/parser"); var import_magic_string = __toESM(require("magic-string")); // src/utils/getBindingIdentifiersFromLHS.ts var t = __toESM(require("@babel/types")); function getBindingIdentifiersFromLHS(node) { if (t.isIdentifier(node)) { return [node]; } if (t.isObjectPattern(node)) { return node.properties.flatMap( (property) => getBindingIdentifiersFromLHS( t.isRestElement(property) ? property.argument : property.value ) ); } if (t.isArrayPattern(node)) { return node.elements.flatMap( (element) => element ? getBindingIdentifiersFromLHS(element) : [] ); } if (t.isRestElement(node)) { return getBindingIdentifiersFromLHS(node.argument); } if (t.isAssignmentPattern(node)) { return getBindingIdentifiersFromLHS(node.left); } return []; } // src/utils/lhsHasNonIdentifierAssignment.ts var t2 = __toESM(require("@babel/types")); function lhsHasNonIdentifierAssignment(node) { if (t2.isIdentifier(node)) { return false; } if (t2.isObjectPattern(node)) { return node.properties.some( (property) => lhsHasNonIdentifierAssignment( t2.isRestElement(property) ? property.argument : property.value ) ); } if (t2.isArrayPattern(node)) { return node.elements.some( (element) => element && lhsHasNonIdentifierAssignment(element) ); } if (t2.isRestElement(node)) { return !t2.isIdentifier(node.argument); } if (t2.isAssignmentPattern(node)) { return lhsHasNonIdentifierAssignment(node.left); } return true; } // src/utils/TraverseState.ts var import_types = require("@babel/types"); // src/utils/getIndentForLineContainingOffset.ts function getIndentForLineContainingOffset(source, offset) { let lastNewlineIndex = source.lastIndexOf("\n", offset); for (let i = lastNewlineIndex + 1; i < source.length; i++) { switch (source[i]) { case " ": case " ": break; default: return source.slice(lastNewlineIndex + 1, i); } } return source.slice(lastNewlineIndex + 1); } // src/utils/buildDeclarationForNames.ts function buildDeclarationForNames(names, source, offset) { let nameList = names.sort((a, b) => a.localeCompare(b)).join(", "); let indent = getIndentForLineContainingOffset(source, offset); return `var ${nameList}; ${indent}`; } // src/utils/getFirstStatementInBlock.ts var t3 = __toESM(require("@babel/types")); function getFirstStatementInBlock(node) { if (t3.isBlockStatement(node) || t3.isProgram(node)) { return node.body[0]; } if (t3.isFunction(node)) { return getFirstStatementInBlock(node.body); } return null; } // src/utils/getParenthesesRanges.ts function getParenthesesRanges(node, tokens) { let leftParenTokens = []; let rightParenTokens = []; for (let i = 0; i < tokens.length; i++) { let token = tokens[i]; if (token.start === node.start) { for (let j = i - 1; j >= 0; j--) { if (tokens[j].type.label === "(") { leftParenTokens.unshift(tokens[j]); } else { break; } } } else if (token.end === node.end) { for (let j = i + 1; j < tokens.length; j++) { if (tokens[j].type.label === ")") { rightParenTokens.push(tokens[j]); } else { break; } } break; } } if (leftParenTokens.length === 0 || leftParenTokens.length !== rightParenTokens.length) { return []; } return [ { start: leftParenTokens[0].start, end: leftParenTokens[leftParenTokens.length - 1].end }, { start: rightParenTokens[0].start, end: rightParenTokens[rightParenTokens.length - 1].end } ]; } // src/utils/TraverseState.ts var BindingState = class { constructor(name, scope) { this.isInOriginalPosition = true; this.name = name; this.mostSpecificScope = scope; } handleSeenScope(scope) { let newScopes = /* @__PURE__ */ new Set(); for (let newScope = scope; newScope; newScope = newScope.parent) { newScopes.add(newScope); } while (!newScopes.has(this.mostSpecificScope)) { this.mostSpecificScope = this.mostSpecificScope.parent; this.isInOriginalPosition = false; } } }; var TraverseState = class { constructor(scope, parentState = null) { this.ownedBindings = /* @__PURE__ */ new Map(); this.ownedInlineBindings = []; this.scope = scope; this.parentState = parentState; } /** * Declare that there is an assignment to a variable with this name in this * scope. */ addBinding(name) { let searchResult = this.resolveName(name); if (searchResult === "NOT_FOUND") { this.createBinding(name, this.scope); } else if (searchResult !== "ALREADY_DECLARED") { searchResult.handleSeenScope(this.scope); } } /** * Note that this identifier appears in this scope. This won't create * bindings, but might update the most specific scope for existing bindings. */ handleSeenIdentifier(name) { let searchResult = this.resolveName(name); if (searchResult !== "NOT_FOUND" && searchResult !== "ALREADY_DECLARED") { searchResult.handleSeenScope(this.scope); } } /** * Declare that, if possible, the given names should all have declarations * added by inserting `var` at the start of the specified node. If any of them * end up changing scopes due to later information, or if any of them are * already declared, we'll just add the names to the most specific scope. * * To make calling code simpler, this method allows an empty array of names * (in which case it's a no-op) and allows names that are already defined * (in which case we immediately know that we won't be able to do an inline * binding). */ addInlineBinding(node, names, { shouldRemoveParens }) { if (names.length === 0) { return; } if (names.every((name) => this.resolveName(name) === "NOT_FOUND") && new Set(names).size === names.length) { let newBindings = names.map( (name) => this.createBinding(name, this.scope) ); let bindingOwner = this.getEnclosingBindingOwner(); bindingOwner.ownedInlineBindings.push({ node, bindings: newBindings, shouldRemoveParens }); } else { for (let name of names) { this.addBinding(name); } } } createBinding(name, scope) { let bindingOwner = this.getEnclosingBindingOwner(); if (bindingOwner.ownedBindings.has(name)) { throw new Error( "Tried to create a binding for a name that is already taken." ); } let newState = new BindingState(name, scope); bindingOwner.ownedBindings.set(name, newState); return newState; } getEnclosingBindingOwner() { if (this.canOwnBindings()) { return this; } else if (!this.parentState) { throw new Error(`expected parent state but none was found!`); } else { return this.parentState.getEnclosingBindingOwner(); } } canOwnBindings() { if (!this.parentState) { return true; } return (0, import_types.isFunction)(this.scope.block); } resolveName(name) { if (this.scope.getBinding(name)) { return "ALREADY_DECLARED"; } let binding = this.ownedBindings.get(name); if (binding) { return binding; } if (this.parentState) { return this.parentState.resolveName(name); } else { return "NOT_FOUND"; } } /** * When we finish processing a function, we know that we have all information * we need for variables scoped to this function, so we can insert the `var` * declarations at the right places. */ commitDeclarations(editor, source, tokens) { let usedNames = /* @__PURE__ */ new Set(); let varInsertionPoints = []; for (let inlineBinding of this.ownedInlineBindings) { if (inlineBinding.bindings.every((binding) => binding.isInOriginalPosition)) { let { node, shouldRemoveParens } = inlineBinding; if (shouldRemoveParens) { for (let { start, end } of getParenthesesRanges(node, tokens)) { editor.remove(start, end); } } varInsertionPoints.push(node.start); for (let binding of inlineBinding.bindings) { usedNames.add(binding.name); } } } for (let [scope, names] of this.getBindingNamesByScope( usedNames ).entries()) { let firstStatement = this.getFirstStatementForScope(scope); if (firstStatement) { editor.appendLeft( firstStatement.start, buildDeclarationForNames(names, source, firstStatement.start) ); } } for (let insertionPoint of varInsertionPoints) { editor.appendLeft(insertionPoint, "var "); } } /** * Get all names that we still need to declare (ones not in usedNames), sorted * and grouped by scope. */ getBindingNamesByScope(usedNames) { let bindingNamesByScope = /* @__PURE__ */ new Map(); for (let { name, mostSpecificScope } of this.ownedBindings.values()) { if (usedNames.has(name)) { continue; } let names = bindingNamesByScope.get(mostSpecificScope); if (names) { names.push(name); } else { bindingNamesByScope.set(mostSpecificScope, [name]); } } for (let names of bindingNamesByScope.values()) { names.sort(); } return bindingNamesByScope; } getFirstStatementForScope(scope) { let firstStatement; let insertionScope = scope; do { firstStatement = getFirstStatementInBlock(insertionScope.block); insertionScope = insertionScope.parent; } while (!firstStatement); return firstStatement; } }; // src/index.ts function addVariableDeclarations(source, editor = new import_magic_string.default(source), ast = (0, import_parser.parse)(source, { tokens: true })) { let state = null; let savedStates = []; let seen = /* @__PURE__ */ new Set(); function visitForStatement(path) { let state2 = getState(); let { node } = path; let names = getBindingIdentifiersFromLHS(node.left).map((id) => id.name); if (lhsHasNonIdentifierAssignment(node.left)) { for (let name of names) { state2.addBinding(name); } } else { state2.addInlineBinding(node.left, names, { shouldRemoveParens: false }); } } (0, import_traverse.default)(ast, { /** * Adds `var` for assignments, either in place or at the top of the scope. * * a = 1; // can add `var` inline * b(c = 2); // needs standalone `var` at the top of scope */ AssignmentExpression(path) { let { node } = path; if (node.operator !== "=") { return; } if (seen.has(node)) { return; } let state2 = getState(); let names = getBindingIdentifiersFromLHS(node.left).map((id) => id.name); let canInsertVar = !lhsHasNonIdentifierAssignment(node.left) && (t4.isExpressionStatement(path.parent) || t4.isForStatement(path.parent) && node === path.parent.init); if (canInsertVar) { state2.addInlineBinding(node, names, { shouldRemoveParens: true }); } else { for (let name of names) { state2.addBinding(name); } } }, /** * We want to declare each variable at its most specific scope across all * assignments and usages, so note each usage, since it might affect that * scope. */ Identifier(path) { let state2 = getState(); state2.handleSeenIdentifier(path.node.name); }, /** * Adds `var` to `for-in` and `for-of` loops, e.g. * * for (key in object) { * … * } * * for (item of list) { * … * } */ ForInStatement: visitForStatement, ForOfStatement: visitForStatement, /** * Optimizes for the case where there are multiple assignments in one * sequence of expressions, e.g. * * for (i = 0, length = list.length; i < length; i++) { * … * } */ SequenceExpression(path) { let state2 = getState(); let { node } = path; let names = []; if (!t4.isExpressionStatement(path.parent) && !(t4.isForStatement(path.parent) && node === path.parent.init)) { return; } for (let expression of node.expressions) { if (!t4.isAssignmentExpression(expression)) { return; } let identifiers = getBindingIdentifiersFromLHS(expression.left); if (identifiers.length === 0) { return; } if (lhsHasNonIdentifierAssignment(expression.left)) { return; } names.push(...identifiers.map((identifier) => identifier.name)); } state2.addInlineBinding(node, names, { shouldRemoveParens: true }); node.expressions.forEach((expression) => seen.add(expression)); }, enter(path) { if (t4.isScopable(path.node)) { state = new TraverseState(path.scope, state); } if (t4.isObjectMethod(path.parent) && path.key === "key" || t4.isClassMethod(path.parent) && path.key === "key") { savedStates.push(getState()); state = getState().parentState; } }, exit(path) { var _a, _b; if (t4.isScopable(path.node)) { if (state) { state.commitDeclarations( editor, source, (_b = (_a = ast.tokens) == null ? void 0 : _a.filter(Boolean)) != null ? _b : [] ); state = state.parentState; } } if (t4.isObjectMethod(path.parent) && path.key === "key" || t4.isClassMethod(path.parent) && path.key === "key") { state = savedStates.pop() || null; } } }); function getState() { if (!state) { throw new Error("BUG: state is not set"); } else { return state; } } return { code: editor.toString(), map: editor.generateMap() }; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { addVariableDeclarations });