decaffeinate
Version:
Move your CoffeeScript source to modern JavaScript.
1,544 lines (1,514 loc) • 359 kB
JavaScript
"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