UNPKG

decaffeinate

Version:

Move your CoffeeScript source to modern JavaScript.

1,544 lines (1,514 loc) 359 kB
"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( 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, { PatchError: () => PatchError, convert: () => convert, modernizeJS: () => modernizeJS, run: () => run }); module.exports = __toCommonJS(src_exports); var import_coffee_lex60 = require("coffee-lex"); var import_decaffeinate_coffeescript = require("decaffeinate-coffeescript"); var import_decaffeinate_coffeescript2 = require("decaffeinate-coffeescript2"); var import_decaffeinate_parser28 = require("decaffeinate-parser"); // src/stages/add-variable-declarations/index.ts var import_add_variable_declarations = require("add-variable-declarations"); var import_magic_string = __toESM(require("magic-string")); // src/utils/debug.ts function logger(name) { if (isLoggingEnabled(name)) { return (...args) => console.log(name, ...args); } else { return () => { }; } } function isLoggingEnabled(name) { return !!process.env[`DEBUG:${name}`] || !!process.env["DEBUG:*"]; } // src/stages/add-variable-declarations/index.ts var AddVariableDeclarationsStage = class { static run(content) { const log = logger(this.name); log(content); const editor = new import_magic_string.default(content); (0, import_add_variable_declarations.addVariableDeclarations)(content, editor); return { code: editor.toString(), suggestions: [] }; } }; // src/stages/resugar/index.ts var t = __toESM(require("@babel/types")); var import_codemod_declarations_block_scope = __toESM(require("@resugar/codemod-declarations-block-scope")); var import_codemod_functions_arrow = __toESM(require("@resugar/codemod-functions-arrow")); var import_codemod_modules_commonjs = __toESM(require("@resugar/codemod-modules-commonjs")); var import_codemod_objects_concise = __toESM(require("@resugar/codemod-objects-concise")); var import_codemod_objects_destructuring = __toESM(require("@resugar/codemod-objects-destructuring")); var import_codemod_objects_shorthand = __toESM(require("@resugar/codemod-objects-shorthand")); var import_codemod_strings_template = __toESM(require("@resugar/codemod-strings-template")); var import_core = require("@codemod/core"); var ResugarStage = class { static run(content, options) { const log = logger(this.name); log(content); const code = (0, import_core.transform)(content, { plugins: Array.from(this.getPluginsForOptions(options)), configFile: false, babelrc: false }).code; return { code, suggestions: [] }; } static *getPluginsForOptions(options) { yield import_codemod_objects_shorthand.default; yield import_codemod_objects_concise.default; if (options.useJSModules) { yield [ import_codemod_modules_commonjs.default, { forceDefaultExport: !options.looseJSModules, safeFunctionIdentifiers: options.safeImportFunctionIdentifiers } ]; } yield import_codemod_functions_arrow.default; yield [ import_codemod_declarations_block_scope.default, { disableConst({ node, parent }) { if (options.preferLet) { return !t.isProgram(parent) || node.declarations.length !== 1 || !t.isIdentifier(node.declarations[0].id) || !/^[$_]?[A-Z]+$/.test(node.declarations[0].id.name); } else { return false; } } } ]; yield import_codemod_objects_destructuring.default; yield import_codemod_strings_template.default; } }; // src/stages/literate/index.ts var VALID_INDENTATIONS = [" ", " ", " ", " ", " "]; var LiterateStage = class { static run(content) { return { code: convertCodeFromLiterate(content), suggestions: [] }; } }; function convertCodeFromLiterate(code) { const lines = code.split("\n"); const resultLines = []; let commentLines = null; for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (commentLines === null) { if (lineIsEmpty(line) || lineIsIndented(line)) { resultLines.push(removeIndentation(line)); } else { commentLines = [line]; } } else { if (lineIsEmpty(line) || !lineIsIndented(line) || i > 0 && !lineIsEmpty(lines[i - 1])) { commentLines.push(line); } else { resultLines.push(...convertCommentLines(commentLines)); commentLines = null; resultLines.push(removeIndentation(line)); } } } resultLines.push(...convertCommentLines(commentLines)); return resultLines.join("\n"); } function convertCommentLines(commentLines) { if (commentLines === null) { return []; } commentLines = commentLines.slice(); while (commentLines.length > 0 && lineIsEmpty(commentLines[commentLines.length - 1])) { commentLines.pop(); } return commentLines.map((line) => `# ${line}`); } function lineIsEmpty(line) { return /^\s*$/.test(line); } function lineIsIndented(line) { return VALID_INDENTATIONS.some((indent) => line.startsWith(indent)); } function removeIndentation(line) { for (const indent of VALID_INDENTATIONS) { if (line.startsWith(indent)) { return line.slice(indent.length); } } if (lineIsEmpty(line)) { return line; } throw new Error("Unexpectedly removed indentation from an unindented line."); } // src/stages/TransformCoffeeScriptStage.ts var import_magic_string2 = __toESM(require("magic-string")); // src/utils/DecaffeinateContext.ts var import_decaffeinate_parser3 = require("decaffeinate-parser"); // src/utils/Scope.ts var import_decaffeinate_parser2 = require("decaffeinate-parser"); // src/utils/flatMap.ts function flatMap(list, map) { return list.reduce((memo, item) => memo.concat(map(item)), []); } // src/utils/isReservedWord.ts var JS_KEYWORDS = [ "true", "false", "null", "this", "new", "delete", "typeof", "in", "instanceof", "return", "throw", "break", "continue", "debugger", "yield", "if", "else", "switch", "for", "while", "do", "try", "catch", "finally", "class", "extends", "super", "import", "export", "default" ]; var COFFEE_KEYWORDS = ["undefined", "Infinity", "NaN", "then", "unless", "until", "loop", "of", "by", "when"]; var COFFEE_ALIASES = ["and", "or", "is", "isnt", "not", "yes", "no", "on", "off"]; var RESERVED = [ "case", "default", "function", "var", "void", "with", "const", "let", "enum", "export", "import", "native", "implements", "interface", "package", "private", "protected", "public", "static" ]; var STRICT_PROSCRIBED = ["arguments", "eval"]; var JS_FORBIDDEN = /* @__PURE__ */ new Set([...JS_KEYWORDS, ...RESERVED, ...STRICT_PROSCRIBED]); var RESERVED_WORDS = /* @__PURE__ */ new Set([ ...JS_KEYWORDS, ...COFFEE_KEYWORDS, ...COFFEE_ALIASES, ...RESERVED, ...STRICT_PROSCRIBED, "await" ]); function isReservedWord(name) { return RESERVED_WORDS.has(name); } function isForbiddenJsName(name) { return JS_FORBIDDEN.has(name); } // src/utils/leftHandIdentifiers.ts var import_decaffeinate_parser = require("decaffeinate-parser"); function leftHandIdentifiers(node) { if (node instanceof import_decaffeinate_parser.Identifier) { return [node]; } else if (node instanceof import_decaffeinate_parser.ArrayInitialiser) { return flatMap(node.members, leftHandIdentifiers); } else if (node instanceof import_decaffeinate_parser.ObjectInitialiser) { return flatMap(node.members, (member) => { if (member instanceof import_decaffeinate_parser.ObjectInitialiserMember) { return leftHandIdentifiers(member.expression || member.key); } else if (member instanceof import_decaffeinate_parser.Spread) { return leftHandIdentifiers(member.expression); } { return leftHandIdentifiers(member.assignee); } }); } else { return []; } } // src/utils/Scope.ts var Scope = class { constructor(containerNode, parent = null) { this.containerNode = containerNode; this.parent = parent; this.bindings = Object.create(parent ? parent.bindings : {}); this.modificationsAfterDeclaration = {}; this.innerClosureModifications = {}; } getBinding(name) { return this.bindings[this.key(name)] || null; } isBindingAvailable(name) { return !this.getBinding(name) && !isReservedWord(name); } hasBinding(name) { return this.getBinding(name) !== null; } hasModificationAfterDeclaration(name) { return this.modificationsAfterDeclaration[this.key(name)] || false; } hasInnerClosureModification(name) { return this.innerClosureModifications[this.key(name)] || false; } getOwnNames() { return Object.getOwnPropertyNames(this.bindings).map((key) => this.unkey(key)); } hasOwnBinding(name) { return Object.prototype.hasOwnProperty.call(this.bindings, this.key(name)); } declares(name, node) { const key = this.key(name); this.bindings[key] = node; } assigns(name, node) { if (!this.bindings[this.key(name)]) { this.declares(name, node); } else { this.modifies(name); } } modifies(name) { let scope = this; while (scope) { if (scope.hasOwnBinding(name)) { scope.modificationsAfterDeclaration[this.key(name)] = true; if (scope !== this) { scope.innerClosureModifications[this.key(name)] = true; } break; } scope = scope.parent; } } claimFreeBinding(node, name = null) { if (!name) { name = "ref"; } const names = Array.isArray(name) ? name : [name]; let binding = names.find((name2) => this.isBindingAvailable(name2)); if (!binding) { let counter = 0; while (!binding) { if (counter > 1e3) { throw new Error(`Unable to find free binding for names ${names.toString()}`); } counter += 1; binding = names.find((name2) => this.isBindingAvailable(`${name2}${counter}`)); } binding = `${binding}${counter}`; } this.declares(binding, node); return binding; } key(name) { return `$${name}`; } unkey(key) { return key.slice(1); } processNode(node) { if (node instanceof import_decaffeinate_parser2.AssignOp) { leftHandIdentifiers(node.assignee).forEach((identifier) => this.assigns(identifier.data, identifier)); } else if (node instanceof import_decaffeinate_parser2.CompoundAssignOp) { if (node.assignee instanceof import_decaffeinate_parser2.Identifier) { this.modifies(node.assignee.data); } } else if (node instanceof import_decaffeinate_parser2.PostDecrementOp || node instanceof import_decaffeinate_parser2.PostIncrementOp || node instanceof import_decaffeinate_parser2.PreDecrementOp || node instanceof import_decaffeinate_parser2.PreIncrementOp) { if (node.expression instanceof import_decaffeinate_parser2.Identifier) { this.modifies(node.expression.data); } } else if (node instanceof import_decaffeinate_parser2.BaseFunction) { getBindingsForNode(node).forEach((identifier) => this.declares(identifier.data, identifier)); } else if (node instanceof import_decaffeinate_parser2.For) { [node.keyAssignee, node.valAssignee].forEach((assignee) => { if (assignee) { leftHandIdentifiers(assignee).forEach((identifier) => this.assigns(identifier.data, identifier)); } }); } else if (node instanceof import_decaffeinate_parser2.Try) { if (node.catchAssignee) { leftHandIdentifiers(node.catchAssignee).forEach((identifier) => this.assigns(identifier.data, identifier)); } } else if (node instanceof import_decaffeinate_parser2.Class) { if (node.nameAssignee && node.nameAssignee instanceof import_decaffeinate_parser2.Identifier && this.parent) { this.parent.assigns(node.nameAssignee.data, node.nameAssignee); } } } toString() { const parts = this.getOwnNames(); if (this.parent) { parts.push(`parent = ${this.parent.toString()}`); } return `${this.constructor.name} {${parts.length > 0 ? ` ${parts.join(", ")} ` : ""}}`; } inspect() { return this.toString(); } }; function getBindingsForNode(node) { if (node instanceof import_decaffeinate_parser2.BaseFunction) { return flatMap(node.parameters, getBindingsForNode); } else if (node instanceof import_decaffeinate_parser2.Identifier || node instanceof import_decaffeinate_parser2.ArrayInitialiser || node instanceof import_decaffeinate_parser2.ObjectInitialiser) { return leftHandIdentifiers(node); } else if (node instanceof import_decaffeinate_parser2.DefaultParam) { return getBindingsForNode(node.param); } else if (node instanceof import_decaffeinate_parser2.Rest) { return getBindingsForNode(node.expression); } else if (node instanceof import_decaffeinate_parser2.Expansion || node instanceof import_decaffeinate_parser2.MemberAccessOp) { return []; } else { throw new Error(`unexpected parameter type: ${node.type}`); } } // src/utils/DecaffeinateContext.ts var DecaffeinateContext = class { constructor(programNode, source, sourceTokens, coffeeAST, linesAndColumns, parentMap, scopeMap) { this.programNode = programNode; this.source = source; this.sourceTokens = sourceTokens; this.coffeeAST = coffeeAST; this.linesAndColumns = linesAndColumns; this.parentMap = parentMap; this.scopeMap = scopeMap; } static create(source, useCS2) { const program = (0, import_decaffeinate_parser3.parse)(source, { useCS2 }); return new DecaffeinateContext( program, source, program.context.sourceTokens, program.context.ast, program.context.linesAndColumns, computeParentMap(program), computeScopeMap(program) ); } getParent(node) { const result = this.parentMap.get(node); if (result === void 0) { throw new Error("Unexpected parent lookup; node was not in the map."); } return result; } getScope(node) { const result = this.scopeMap.get(node); if (result === void 0) { throw new Error("Unexpected scope lookup; node was not in the map."); } return result; } }; function computeParentMap(program) { const resultMap = /* @__PURE__ */ new Map(); (0, import_decaffeinate_parser3.traverse)(program, (node, parent) => { resultMap.set(node, parent); }); return resultMap; } function computeScopeMap(program) { const scopeMap = /* @__PURE__ */ new Map(); (0, import_decaffeinate_parser3.traverse)(program, (node, parent) => { let scope; switch (node.type) { case "Program": scope = new Scope(node); break; case "Function": case "BoundFunction": case "GeneratorFunction": case "BoundGeneratorFunction": case "AsyncFunction": case "Class": { const parentScope = parent && scopeMap.get(parent); if (!parentScope) { throw new Error("Expected to find parent scope."); } scope = new Scope(node, parentScope); break; } default: { const parentScope = parent && scopeMap.get(parent); if (!parentScope) { throw new Error("Expected to find parent scope."); } scope = parentScope; break; } } scope.processNode(node); scopeMap.set(node, scope); }); return scopeMap; } // src/utils/notNull.ts function notNull(t2) { if (t2 === null || t2 === void 0) { throw new Error("Unexpected null value."); } return t2; } // src/utils/PatchError.ts var import_lines_and_columns = require("lines-and-columns"); // src/utils/printTable.ts function printTable(table, buffer = " ") { const widths = []; table.rows.forEach((row) => { row.forEach((cell, i) => { if (widths.length <= i) { widths[i] = cell.length; } else if (widths[i] < cell.length) { widths[i] = cell.length; } }); }); let output = ""; table.rows.forEach((row) => { row.forEach((cell, i) => { const column = table.columns[i]; if (column.align === "left") { output += cell; } else if (column.align === "right") { output += " ".repeat(widths[i] - cell.length) + cell; } if (i < row.length - 1) { output += buffer; } }); output += "\n"; }); return output; } // src/utils/PatchError.ts var PatchError = class extends Error { constructor(message, source, start, end) { super(message); this.message = message; this.source = source; this.start = start; this.end = end; } toString() { return this.message; } static detect(error) { return error instanceof Error && "source" in error && "start" in error && "end" in error; } static prettyPrint(error) { const { source, message } = error; let { start, end } = error; start = Math.min(Math.max(start, 0), source.length); end = Math.min(Math.max(end, start), source.length); const lineMap = new import_lines_and_columns.LinesAndColumns(source); const startLoc = lineMap.locationForIndex(start); const endLoc = lineMap.locationForIndex(end); if (!startLoc || !endLoc) { throw new Error(`unable to find locations for range: [${start}, ${end})`); } const displayStartLine = Math.max(0, startLoc.line - 2); const displayEndLine = endLoc.line + 2; const rows = []; for (let line = displayStartLine; line <= displayEndLine; line++) { const startOfLine = lineMap.indexForLocation({ line, column: 0 }); let endOfLine = lineMap.indexForLocation({ line: line + 1, column: 0 }); if (startOfLine === null) { break; } if (endOfLine === null) { endOfLine = source.length; } const lineSource = trimRight(source.slice(startOfLine, endOfLine)); if (startLoc.line !== endLoc.line) { if (line >= startLoc.line && line <= endLoc.line) { rows.push([`>`, `${line + 1} |`, lineSource]); } else { rows.push([``, `${line + 1} |`, lineSource]); } } else if (line === startLoc.line) { const highlightLength = Math.max(endLoc.column - startLoc.column, 1); rows.push( [`>`, `${line + 1} |`, lineSource], [``, `|`, " ".repeat(startLoc.column) + "^".repeat(highlightLength)] ); } else { rows.push([``, `${line + 1} |`, lineSource]); } } const columns = [ { id: "marker", align: "right" }, { id: "line", align: "right" }, { id: "source", align: "left" } ]; return `${message} ${printTable({ rows, columns })}`; } }; function trimRight(string) { return string.replace(/\s+$/, ""); } // src/stages/TransformCoffeeScriptStage.ts var TransformCoffeeScriptStage = class { constructor(ast, context, editor, options) { this.ast = ast; this.context = context; this.editor = editor; this.options = options; this.root = null; this.patchers = []; this.suggestions = []; } static run(content, options) { const log = logger(this.name); log(content); const context = DecaffeinateContext.create(content, Boolean(options.useCS2)); const editor = new import_magic_string2.default(content); const stage = new this(context.programNode, context, editor, options); const patcher = stage.build(); patcher.patch(); return { code: editor.toString(), suggestions: stage.suggestions }; } patcherConstructorForNode(_node) { return null; } build() { this.root = this.patcherForNode(this.ast); this.patchers.forEach((patcher) => patcher.initialize()); return this.root; } patcherForNode(node, parent = null, property = null) { let constructor = this._patcherConstructorForNode(node); if (parent) { const override = parent.patcherClassForChildNode(node, notNull(property)); if (override) { constructor = override; } } const children = node.getChildNames().map((name) => { const child = node[name]; if (!child) { return null; } else if (Array.isArray(child)) { return child.map((item) => item ? this.patcherForNode(item, constructor, name) : null); } else { return this.patcherForNode(child, constructor, name); } }); const patcherContext = { node, context: this.context, editor: this.editor, options: this.options, addSuggestion: (suggestion) => { this.suggestions.push(suggestion); } }; const patcher = new constructor(patcherContext, ...children); this.patchers.push(patcher); this.associateParent(patcher, children); return patcher; } associateParent(parent, child) { if (Array.isArray(child)) { child.forEach((item) => this.associateParent(parent, item)); } else if (child) { child.parent = parent; } } _patcherConstructorForNode(node) { const constructor = this.patcherConstructorForNode(node); if (constructor === null) { const props = node.getChildNames(); throw new PatchError( `no patcher available for node type: ${node.type}${props.length ? ` (props: ${props.join(", ")})` : ""}`, this.context.source, node.start, node.end ); } return constructor.patcherClassOverrideForNode(node) || constructor; } }; // src/patchers/NodePatcher.ts var import_assert = __toESM(require("assert")); var import_coffee_lex2 = require("coffee-lex"); var import_decaffeinate_parser6 = require("decaffeinate-parser"); // src/suggestions.ts var FIX_INVALID_CONSTRUCTOR = { suggestionCode: "DS002", message: "Fix invalid constructor" }; var REMOVE_ARRAY_FROM = { suggestionCode: "DS101", message: "Remove unnecessary use of Array.from" }; var CLEAN_UP_IMPLICIT_RETURNS = { suggestionCode: "DS102", message: "Remove unnecessary code created because of implicit returns" }; var REMOVE_GUARD = { suggestionCode: "DS103", message: "Rewrite code to no longer use __guard__, or convert again using --optional-chaining" }; var AVOID_INLINE_ASSIGNMENTS = { suggestionCode: "DS104", message: "Avoid inline assignments" }; var SIMPLIFY_COMPLEX_ASSIGNMENTS = { suggestionCode: "DS201", message: "Simplify complex destructure assignments" }; var SIMPLIFY_DYNAMIC_RANGE_LOOPS = { suggestionCode: "DS202", message: "Simplify dynamic range loops" }; var CLEAN_UP_FOR_OWN_LOOPS = { suggestionCode: "DS203", message: "Remove `|| {}` from converted for-own loops" }; var FIX_INCLUDES_EVALUATION_ORDER = { suggestionCode: "DS204", message: "Change includes calls to have a more natural evaluation order" }; var AVOID_IIFES = { suggestionCode: "DS205", message: "Consider reworking code to avoid use of IIFEs" }; var AVOID_INITCLASS = { suggestionCode: "DS206", message: "Consider reworking classes to avoid initClass" }; var SHORTEN_NULL_CHECKS = { suggestionCode: "DS207", message: "Consider shorter variations of null checks" }; var AVOID_TOP_LEVEL_THIS = { suggestionCode: "DS208", message: "Avoid top-level this" }; var AVOID_TOP_LEVEL_RETURN = { suggestionCode: "DS209", message: "Avoid top-level return" }; function mergeSuggestions(suggestions) { const suggestionsByCode = {}; for (const suggestion of suggestions) { suggestionsByCode[suggestion.suggestionCode] = suggestion; } return Object.keys(suggestionsByCode).sort().map((code) => suggestionsByCode[code]); } function prependSuggestionComment(code, suggestions) { if (suggestions.length === 0) { return code; } const commentLines = [ "/*", " * decaffeinate suggestions:", ...suggestions.map(({ suggestionCode, message }) => ` * ${suggestionCode}: ${message}`), " * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md", " */" ]; const codeLines = code.split("\n"); if (codeLines[0].startsWith("#!")) { return [codeLines[0], ...commentLines, ...codeLines.slice(1)].join("\n"); } else { return [...commentLines, ...codeLines].join("\n"); } } // src/utils/determineIndent.ts var import_detect_indent = __toESM(require("detect-indent")); var DEFAULT_INDENT = " "; function determineIndent(source) { const indent = (0, import_detect_indent.default)(source); if (indent.type === "space" && indent.amount % 2 === 1) { return DEFAULT_INDENT; } return indent.indent || DEFAULT_INDENT; } // src/utils/getStartOfLine.ts function getStartOfLine(source, offset) { const lfIndex = source.lastIndexOf("\n", offset - 1); if (lfIndex < 0) { return 0; } return lfIndex + 1; } // src/utils/getIndent.ts function getIndent(source, offset) { const startOfLine = getStartOfLine(source, offset); let indentOffset = startOfLine; let indentCharacter; switch (source[indentOffset]) { case " ": case " ": indentCharacter = source[indentOffset]; break; default: return ""; } while (source[indentOffset] === indentCharacter) { indentOffset++; } return source.slice(startOfLine, indentOffset); } // src/utils/adjustIndent.ts function adjustIndent(source, offset, adjustment) { let currentIndent = getIndent(source, offset); const determinedIndent = determineIndent(source); if (adjustment > 0) { while (adjustment--) { currentIndent += determinedIndent; } } else if (adjustment < 0) { currentIndent = currentIndent.slice(determinedIndent.length * -adjustment); } return currentIndent; } // src/utils/referencesArguments.ts var import_decaffeinate_parser5 = require("decaffeinate-parser"); // src/utils/containsDescendant.ts var import_decaffeinate_parser4 = require("decaffeinate-parser"); function containsDescendant(node, predicate, { shouldStopTraversal = () => false } = {}) { let found = false; (0, import_decaffeinate_parser4.traverse)(node, (childNode) => { if (found) { return false; } if (predicate(childNode)) { found = true; return false; } if (shouldStopTraversal(childNode)) { return false; } return true; }); return found; } // src/utils/types.ts var import_coffee_lex = require("coffee-lex"); function isFunction(node, allowBound = true) { return node.type === "Function" || node.type === "GeneratorFunction" || allowBound && (node.type === "BoundFunction" || node.type === "BoundGeneratorFunction"); } var NON_SEMANTIC_SOURCE_TOKEN_TYPES = [import_coffee_lex.SourceType.COMMENT, import_coffee_lex.SourceType.HERECOMMENT, import_coffee_lex.SourceType.NEWLINE]; function isSemanticToken(token) { return NON_SEMANTIC_SOURCE_TOKEN_TYPES.indexOf(token.type) < 0; } // src/utils/referencesArguments.ts function referencesArguments(node) { return containsDescendant(node, (child) => child instanceof import_decaffeinate_parser5.Identifier && child.data === "arguments", { shouldStopTraversal: (child) => child !== node && isFunction(child) }); } // src/patchers/NodePatcher.ts var NodePatcher = class { constructor({ node, context, editor, options, addSuggestion }) { this.adjustedIndentLevel = 0; this._assignee = false; this._containsYield = false; this._containsAwait = false; this._deferredSuffix = ""; this._expression = false; this._hadUnparenthesizedNegation = false; this._implicitlyReturns = false; this._repeatableOptions = null; this._repeatCode = null; this._returns = false; this.log = logger(this.constructor.name); this.node = node; this.context = context; this.editor = editor; this.options = options; this.addSuggestion = addSuggestion; this.withPrettyErrors(() => this.setupLocationInformation()); } static patcherClassForChildNode(_node, _property) { return null; } static patcherClassOverrideForNode(_node) { return null; } setupLocationInformation() { const { node, context } = this; this.contentStart = node.start; this.contentEnd = node.end; if (this.shouldTrimContentRange()) { this.trimContentRange(); } const tokens = context.sourceTokens; const firstSourceTokenIndex = tokens.indexOfTokenStartingAtSourceIndex(this.contentStart); const lastSourceTokenIndex = tokens.indexOfTokenEndingAtSourceIndex(this.contentEnd); if (!firstSourceTokenIndex || !lastSourceTokenIndex) { if (node.type === "Program") { return; } throw this.error(`cannot find first or last token in ${node.type} node`); } this.contentStartTokenIndex = firstSourceTokenIndex; this.contentEndTokenIndex = lastSourceTokenIndex; let outerStartTokenIndex = firstSourceTokenIndex; let outerEndTokenIndex = lastSourceTokenIndex; let innerStartTokenIndex = firstSourceTokenIndex; let innerEndTokenIndex = lastSourceTokenIndex; for (; ; ) { const previousSurroundingTokenIndex = tokens.lastIndexOfTokenMatchingPredicate( isSemanticToken, outerStartTokenIndex.previous() ); const nextSurroundingTokenIndex = tokens.indexOfTokenMatchingPredicate( isSemanticToken, outerEndTokenIndex.next() ); if (!previousSurroundingTokenIndex || !nextSurroundingTokenIndex) { break; } const previousSurroundingToken = tokens.tokenAtIndex(previousSurroundingTokenIndex); const nextSurroundingToken = tokens.tokenAtIndex(nextSurroundingTokenIndex); if (!previousSurroundingToken || previousSurroundingToken.type !== import_coffee_lex2.SourceType.LPAREN && previousSurroundingToken.type !== import_coffee_lex2.SourceType.CALL_START) { break; } if (!nextSurroundingToken || nextSurroundingToken.type !== import_coffee_lex2.SourceType.RPAREN && nextSurroundingToken.type !== import_coffee_lex2.SourceType.CALL_END) { break; } if (innerStartTokenIndex === firstSourceTokenIndex) { innerStartTokenIndex = previousSurroundingTokenIndex; } if (innerEndTokenIndex === lastSourceTokenIndex) { innerEndTokenIndex = nextSurroundingTokenIndex; } outerStartTokenIndex = previousSurroundingTokenIndex; outerEndTokenIndex = nextSurroundingTokenIndex; } this.innerStartTokenIndex = innerStartTokenIndex; this.innerEndTokenIndex = innerEndTokenIndex; this.outerStartTokenIndex = outerStartTokenIndex; this.outerEndTokenIndex = outerEndTokenIndex; if (innerStartTokenIndex === firstSourceTokenIndex) { this.innerStart = this.contentStart; } else { this.innerStart = notNull(tokens.tokenAtIndex(innerStartTokenIndex)).end; } if (innerEndTokenIndex === lastSourceTokenIndex) { this.innerEnd = this.contentEnd; } else { this.innerEnd = notNull(tokens.tokenAtIndex(innerEndTokenIndex)).start; } this.outerStart = notNull(tokens.tokenAtIndex(outerStartTokenIndex)).start; this.outerEnd = notNull(tokens.tokenAtIndex(outerEndTokenIndex)).end; } trimContentRange() { const context = this.context; for (; ; ) { const startChar = context.source[this.contentStart]; if (startChar === " " || startChar === " ") { this.contentStart++; } else { break; } } for (; ; ) { const lastChar = context.source[this.contentEnd - 1]; if (lastChar === " " || lastChar === " ") { this.contentEnd--; } else { break; } } } shouldTrimContentRange() { return false; } initialize() { } patch(options = {}) { this.withPrettyErrors(() => { if (this._repeatableOptions !== null) { this._repeatCode = this.patchAsRepeatableExpression(this._repeatableOptions, options); } else if (this.forcedToPatchAsExpression()) { this.patchAsForcedExpression(options); this.commitDeferredSuffix(); } else if (this.willPatchAsExpression()) { this.patchAsExpression(options); this.commitDeferredSuffix(); } else { this.patchAsStatement(options); this.commitDeferredSuffix(); } }); } patchRepeatable(repeatableOptions = {}) { this.setRequiresRepeatableExpression(repeatableOptions); this.patch(); return this.getRepeatCode(); } patchAndGetCode(options = {}) { return this.captureCodeForPatchOperation(() => this.patch(options)); } captureCodeForPatchOperation(patchFn) { let sliceStart = this.contentStart > 0 ? this.contentStart - 1 : 0; let beforeCode = null; while (beforeCode === null) { try { beforeCode = this.slice(sliceStart, this.contentStart); } catch (e) { sliceStart -= 1; if (sliceStart < 0) { throw this.error("Could not find a valid index to slice for patch operation."); } } } patchFn(); const code = this.slice(sliceStart, this.contentEnd); let startIndex = 0; while (startIndex < beforeCode.length && startIndex < code.length && beforeCode[startIndex] === code[startIndex]) { startIndex++; } return code.substr(startIndex); } withPrettyErrors(body) { try { body(); } catch (err) { (0, import_assert.default)(err instanceof Error); if (!PatchError.detect(err)) { throw this.error(err.message, this.contentStart, this.contentEnd, err); } else { throw err; } } } patchAsRepeatableExpression(repeatableOptions = {}, patchOptions = {}) { if (this.isRepeatable() && !repeatableOptions.forceRepeat) { return this.captureCodeForPatchOperation(() => { this.patchAsForcedExpression(patchOptions); this.commitDeferredSuffix(); }); } else { this.addSuggestion(AVOID_INLINE_ASSIGNMENTS); if (repeatableOptions.parens) { this.insert(this.innerStart, "("); } const ref = this.claimFreeBinding(repeatableOptions.ref); this.insert(this.innerStart, `${ref} = `); this.patchAsForcedExpression(patchOptions); this.commitDeferredSuffix(); if (repeatableOptions.parens) { this.insert(this.innerEnd, ")"); } return ref; } } patchAsExpression(_options = {}) { throw this.error(`'patchAsExpression' must be overridden in subclasses`); } patchAsStatement(options = {}) { const addParens = this.statementShouldAddParens(); if (addParens) { this.insert(this.outerStart, "("); } this.patchAsExpression(options); if (addParens) { this.insert(this.outerEnd, ")"); } } patchAsForcedExpression(options = {}) { this.patchAsExpression(options); } insert(index, content) { if (typeof index !== "number") { throw new Error(`cannot insert ${JSON.stringify(content)} at non-numeric index ${index}`); } this.log( "INSERT", index, JSON.stringify(content), "BEFORE", JSON.stringify(this.context.source.slice(index, index + 8)) ); this.adjustBoundsToInclude(index); this.editor.appendLeft(index, content); } prependLeft(index, content) { if (typeof index !== "number") { throw new Error(`cannot insert ${JSON.stringify(content)} at non-numeric index ${index}`); } this.log( "PREPEND LEFT", index, JSON.stringify(content), "BEFORE", JSON.stringify(this.context.source.slice(index, index + 8)) ); this.adjustBoundsToInclude(index); this.editor.prependLeft(index, content); } allowPatchingOuterBounds() { return false; } getEditingBounds() { let boundingPatcher = this.getBoundingPatcher(); if (boundingPatcher.parent && (this.isNodeFunctionApplication(boundingPatcher.parent.node) || boundingPatcher.parent.node.type === "ArrayInitialiser")) { boundingPatcher = boundingPatcher.parent; } if (this.allowPatchingOuterBounds()) { return [boundingPatcher.outerStart, boundingPatcher.outerEnd]; } else { return [boundingPatcher.innerStart, boundingPatcher.innerEnd]; } } isIndexEditable(index) { const [start, end] = this.getEditingBounds(); return index >= start && index <= end; } assertEditableIndex(index) { if (!this.isIndexEditable(index)) { const [start, end] = this.getEditingBounds(); throw this.error( `cannot edit index ${index} because it is not editable (i.e. outside [${start}, ${end}))`, start, end ); } } adjustBoundsToInclude(index) { this.assertEditableIndex(index); if (index < this.innerStart) { this.log("Moving `innerStart` from", this.innerStart, "to", index); this.innerStart = index; } if (index > this.innerEnd) { this.log("Moving `innerEnd` from", this.innerEnd, "to", index); this.innerEnd = index; } if (index < this.outerStart) { this.log("Moving `outerStart` from", this.outerStart, "to", index); this.outerStart = index; } if (index > this.outerEnd) { this.log("Moving `outerEnd` from", this.outerEnd, "to", index); this.outerEnd = index; } if (this.parent) { this.parent.adjustBoundsToInclude(index); } } overwrite(start, end, content) { if (typeof start !== "number" || typeof end !== "number") { throw new Error(`cannot overwrite non-numeric range [${start}, ${end}) with ${JSON.stringify(content)}`); } this.log( "OVERWRITE", `[${start}, ${end})`, JSON.stringify(this.context.source.slice(start, end)), "\u2192", JSON.stringify(content) ); this.editor.overwrite(start, end, content); } remove(start, end) { if (typeof start !== "number" || typeof end !== "number") { throw new Error(`cannot remove non-numeric range [${start}, ${end})`); } this.log("REMOVE", `[${start}, ${end})`, JSON.stringify(this.context.source.slice(start, end))); this.editor.remove(start, end); } move(start, end, index) { if (typeof start !== "number" || typeof end !== "number") { throw this.error(`cannot remove non-numeric range [${start}, ${end})`); } if (typeof index !== "number") { throw this.error(`cannot move to non-numeric index: ${index}`); } this.log( "MOVE", `[${start}, ${end}) \u2192 ${index}`, JSON.stringify(this.context.source.slice(start, end)), "BEFORE", JSON.stringify(this.context.source.slice(index, index + 8)) ); this.editor.move(start, end, index); } slice(start, end) { if (end === 0) { return ""; } return this.editor.slice(start, end); } startsWith(string) { return this.context.source.slice(this.contentStart, this.contentStart + string.length) === string; } endsWith(string) { return this.context.source.slice(this.contentEnd - string.length, this.contentEnd) === string; } setRequiresExpression() { this.setExpression(true); } setExpression(force = false) { if (force) { if (!this.canPatchAsExpression()) { throw this.error(`cannot represent ${this.node.type} as an expression`); } } else if (!this.prefersToPatchAsExpression()) { return false; } this._expression = true; return true; } prefersToPatchAsExpression() { return this.canPatchAsExpression(); } canPatchAsExpression() { return true; } willPatchAsExpression() { return this._expression; } forcedToPatchAsExpression() { return this.willPatchAsExpression() && !this.prefersToPatchAsExpression(); } setAssignee() { this._assignee = true; } isAssignee() { return this._assignee; } implicitlyReturns() { return this._implicitlyReturns || false; } setImplicitlyReturns() { this._implicitlyReturns = true; } implicitReturnPatcher() { if (this.canHandleImplicitReturn()) { return this; } else { return notNull(this.parent).implicitReturnPatcher(); } } canHandleImplicitReturn() { return false; } implicitReturnWillBreak() { return true; } patchImplicitReturnStart(patcher) { if (patcher.node.type === "Break" || patcher.node.type === "Continue") { if (patcher.isSurroundedByParentheses()) { this.remove(patcher.outerStart, patcher.innerStart); this.remove(patcher.innerEnd, patcher.outerEnd); } return; } if (isFunction(this.node) && this.isMultiline()) { this.addSuggestion(CLEAN_UP_IMPLICIT_RETURNS); } patcher.setRequiresExpression(); this.insert(patcher.outerStart, "return "); } getEmptyImplicitReturnCode() { return null; } patchImplicitReturnEnd(_patcher) { } explicitlyReturns() { return this._returns || false; } setExplicitlyReturns() { this._returns = true; if (this.parent) { this.parent.setExplicitlyReturns(); } } appendDeferredSuffix(suffix) { this._deferredSuffix += suffix; } commitDeferredSuffix() { if (this._deferredSuffix) { this.insert(this.innerEnd, this._deferredSuffix); } } statementNeedsSemicolon() { return true; } statementNeedsParens() { return false; } statementShouldAddParens() { return this.statementNeedsParens() && !this.isSurroundedByParentheses(); } getProgramSourceTokens() { return this.context.sourceTokens; } indexOfSourceTokenStartingAtSourceIndex(index) { return this.getProgramSourceTokens().indexOfTokenStartingAtSourceIndex(index); } indexOfSourceTokenBetweenPatchersMatching(left, right, predicate) { return this.indexOfSourceTokenBetweenSourceIndicesMatching(left.outerEnd, right.outerStart, predicate); } indexOfSourceTokenBetweenSourceIndicesMatching(left, right, predicate) { const tokenList = this.getProgramSourceTokens(); return tokenList.indexOfTokenMatchingPredicate( (token) => { return token.start >= left && token.start <= right && predicate(token); }, tokenList.indexOfTokenNearSourceIndex(left), tokenList.indexOfTokenNearSourceIndex(right).next() ); } sourceTokenAtIndex(index) { return this.getProgramSourceTokens().tokenAtIndex(index); } sourceOfToken(token) { return this.context.source.slice(token.start, token.end); } firstToken() { const token = this.sourceTokenAtIndex(this.contentStartTokenIndex); if (!token) { throw this.error("Expected to find a first token for node."); } return token; } lastToken() { const token = this.sourceTokenAtIndex(this.contentEndTokenIndex); if (!token) { throw this.error("Expected to find a last token for node."); } return token; } nextSemanticToken() { return this.getFirstSemanticToken(this.contentEnd, this.context.source.length); } getOriginalSource() { return this.context.source.slice(this.contentStart, this.contentEnd); } isMultiline() { return /\n/.test(this.getOriginalSource()); } getPatchedSource() { return this.slice(this.contentStart, this.contentEnd); } indexOfSourceTokenAfterSourceTokenIndex(start, type, predicate = isSemanticToken) { const index = this.getProgramSourceTokens().indexOfTokenMatchingPredicate(predicate, start.next()); if (!index) { return null; } const token = this.sourceTokenAtIndex(index); if (!token || token.type !== type) { return null; } return index; } hasSourceTokenAfter(type, predicate = isSemanticToken) { return this.indexOfSourceTokenAfterSourceTokenIndex(this.outerEndTokenIndex, type, predicate) !== null; } isSurroundedByParentheses() { if (this.contentStart === this.outerStart && this.contentEnd === this.outerEnd) { return false; } const beforeToken = this.sourceTokenAtIndex(this.outerStartTokenIndex); const afterToken = this.sourceTokenAtIndex(this.outerEndTokenIndex); if (!beforeToken || !afterToken) { return false; } let leftTokenType = import_coffee_lex2.SourceType.LPAREN; let rightTokenType = import_coffee_lex2.SourceType.RPAREN; if (beforeToken.type === import_coffee_lex2.SourceType.LPAREN && afterToken.type === import_coffee_lex2.SourceType.RPAREN) { } else if (beforeToken.type === import_coffee_lex2.SourceType.CALL_START && afterToken.type === import_coffee_lex2.SourceType.CALL_END) { leftTokenType = import_coffee_lex2.SourceType.CALL_START; rightTokenType = import_coffee_lex2.SourceType.CALL_END; } else { return false; } const parenRange = this.getProgramSourceTokens().rangeOfMatchingTokensContainingTokenIndex( leftTokenType, rightTokenType, this.outerStartTokenIndex ); if (!parenRange) { return false; } const rparenIndex = parenRange[1].previous(); const rparen = this.sourceTokenAtIndex(notNull(rparenIndex)); return rparen === afterToken; } surroundInParens() { if (!this.isSurroundedByParentheses()) { this.insert(this.outerStart, "("); this.insert(this.outerEnd, ")"); } } getBoundingPatcher() { if (this.isSurroundedByParentheses()) { return this; } else if (this.parent) { if (this.isNodeFunctionApplication(this.parent.node) && this.parent.node.arguments.some((arg) => arg === this.node)) { return this; } else if (this.parent.node.type === "ArrayInitialiser") { return this; } else if (this.parent.node.type === "ObjectInitialiser") { return this; } return this.parent.getBoundingPatcher(); } else { return this; } } isNodeFunctionApplication(node) { return node instanceof import_decaffeinate_parser6.FunctionApplication || node instanceof import_decaffeinate_parser6.SoakedFunctionApplication || node instanceof import_decaffeinate_parser6.NewOp; } canHandleNegationInternally() { return false; } negate() { this.insert(this.contentStart, "!"); this._hadUnparenthesizedNegation = true; } hadUnparenthesizedNegation() { return this._hadUnparenthesizedNegation; } getScope() { return this.context.getScope(this.node); } getIndent(offset = 0) { return adjustIndent(this.context.source, this.contentStart, this.getAdjustedIndentLevel() + offset); } setIndent(indentStr) { const currentIndent = this.getIndent(); const indentLength = this.getProgramIndentString().length; const currentIndentLevel = currentIndent.length / indentLength; const desiredIndentLevel = indentStr.length / indentLength; this.indent(desiredIndentLevel - currentIndentLevel); } getAdjustedIndentLevel() { return this.adjustedIndentLevel + (this.parent ? this.parent.getAdjustedIndentLevel() : 0); } getProgramIndentString() { return notNull(this.parent).getProgramIndentString(); } indent(offset = 1, { skipFirstLine = false } = {}) { if (offset === 0) { return; } this.adjustedIndentLevel += offset; const indentString = this.getProgramIndentString(); const indentToChange = indentString.repeat(Math.abs(offset)); let start = this.outerStart; const end = this.outerEnd; const { source } = this.context; if (skipFirstLine || !this.isFirstNodeInLine()) { while (start < end && source[start] !== "\n") { start++; } } let hasIndentedThisLine = false; for (let i = start; i < end; i++) { switch (source[i]) { case "\n": hasIndentedThisLine = false; break; case " ": case " ": break; default: if (!hasIndentedThisLine) { if (offset > 0) { this.insert(i, indentToChange); } else if (source.slice(i - indentToChange.length, i) === indentToChange) { this.remove(i - indentToChange.length, i); } else { this.log( "Warning: Ignoring an unindent operation because the line did not start with the proper indentation." ); } hasIndentedThisLine = true; } break; } } } isFirstNodeInLine(startingPoint = this.outerStart) { const { source } = this.context; for (let i = startingPoint - 1; i >= 0 && source[i] !== "\n"; i--) { if (source[i] !== " " && source[i] !== " ") { return false; } } return true; } getEndOfLine() { const { source } = this.context; for (let i = this.outerEnd - "\n".length; i < source.length; i++) { if (source[i] === "\n") { return i; } } return source.length; } appendLineAfter(content, indentOffset = 0) { const boundingPatcher = this.getBoundingPatcher(); const endOfLine = this.getEndOfLine(); const nextToken = this.nextSemanticToken(); let insertPoint = Math.min(Math.min(endOfLine, boundingPatcher.innerEnd)); if (nextToken) { insertPoint = Math.min(insertPoint, nextToken.start); } this.insert(insertPoint, ` ${this.getInden