mikx1's picture
Upload folder using huggingface_hub
b5ea024
raw
history blame
34.3 kB
// heavily based on https://github.com/davidbonnet/astring
// released under MIT license https://github.com/davidbonnet/astring/blob/master/LICENSE
import { re } from '../utils/id.js';
import { push_array } from '../utils/push_array.js';
/** @typedef {import('estree').ArrowFunctionExpression} ArrowFunctionExpression */
/** @typedef {import('estree').BinaryExpression} BinaryExpression */
/** @typedef {import('estree').CallExpression} CallExpression */
/** @typedef {import('estree').Comment} Comment */
/** @typedef {import('estree').ExportSpecifier} ExportSpecifier */
/** @typedef {import('estree').Expression} Expression */
/** @typedef {import('estree').FunctionDeclaration} FunctionDeclaration */
/** @typedef {import('estree').ImportDeclaration} ImportDeclaration */
/** @typedef {import('estree').ImportSpecifier} ImportSpecifier */
/** @typedef {import('estree').Literal} Literal */
/** @typedef {import('estree').LogicalExpression} LogicalExpression */
/** @typedef {import('estree').NewExpression} NewExpression */
/** @typedef {import('estree').Node} Node */
/** @typedef {import('estree').ObjectExpression} ObjectExpression */
/** @typedef {import('estree').Pattern} Pattern */
/** @typedef {import('estree').Property} Property */
/** @typedef {import('estree').PropertyDefinition} PropertyDefinition */
/** @typedef {import('estree').SequenceExpression} SequenceExpression */
/** @typedef {import('estree').SimpleCallExpression} SimpleCallExpression */
/** @typedef {import('estree').SwitchStatement} SwitchStatement */
/** @typedef {import('estree').VariableDeclaration} VariableDeclaration */
/** @typedef {import('estree').StaticBlock} StaticBlock */
/** @typedef {import('estree').PrivateIdentifier} PrivateIdenifier*/
/**
* @typedef {{
* content: string;
* loc?: {
* start: { line: number; column: number; };
* end: { line: number; column: number; };
* };
* has_newline: boolean;
* }} Chunk
*/
/**
* @typedef {(node: any, state: State) => Chunk[]} Handler
*/
/**
* @typedef {{
* indent: string;
* scope: any; // TODO import from periscopic
* scope_map: WeakMap<Node, any>;
* getName: (name: string) => string;
* deconflicted: WeakMap<Node, Map<string, string>>;
* comments: Comment[];
* }} State
*/
/**
* @param {Node} node
* @param {State} state
* @returns {Chunk[]}
*/
export function handle(node, state) {
const handler = handlers[node.type];
if (!handler) {
throw new Error(`Not implemented ${node.type}`);
}
const result = handler(node, state);
if (node.leadingComments) {
result.unshift(
c(
node.leadingComments
.map((comment) =>
comment.type === 'Block'
? `/*${comment.value}*/${
/** @type {any} */ (comment).has_trailing_newline
? `\n${state.indent}`
: ` `
}`
: `//${comment.value}${
/** @type {any} */ (comment).has_trailing_newline
? `\n${state.indent}`
: ` `
}`
)
.join(``)
)
);
}
if (node.trailingComments) {
state.comments.push(node.trailingComments[0]); // there is only ever one
}
return result;
}
/**
* @param {string} content
* @param {Node} [node]
* @returns {Chunk}
*/
function c(content, node) {
return {
content,
loc: node && node.loc,
has_newline: /\n/.test(content)
};
}
const OPERATOR_PRECEDENCE = {
'||': 2,
'&&': 3,
'??': 4,
'|': 5,
'^': 6,
'&': 7,
'==': 8,
'!=': 8,
'===': 8,
'!==': 8,
'<': 9,
'>': 9,
'<=': 9,
'>=': 9,
in: 9,
instanceof: 9,
'<<': 10,
'>>': 10,
'>>>': 10,
'+': 11,
'-': 11,
'*': 12,
'%': 12,
'/': 12,
'**': 13
};
/** @type {Record<string, number>} */
const EXPRESSIONS_PRECEDENCE = {
ArrayExpression: 20,
TaggedTemplateExpression: 20,
ThisExpression: 20,
Identifier: 20,
Literal: 18,
TemplateLiteral: 20,
Super: 20,
SequenceExpression: 20,
MemberExpression: 19,
CallExpression: 19,
NewExpression: 19,
AwaitExpression: 17,
ClassExpression: 17,
FunctionExpression: 17,
ObjectExpression: 17,
UpdateExpression: 16,
UnaryExpression: 15,
BinaryExpression: 14,
LogicalExpression: 13,
ConditionalExpression: 4,
ArrowFunctionExpression: 3,
AssignmentExpression: 3,
YieldExpression: 2,
RestElement: 1
};
/**
*
* @param {Expression} node
* @param {BinaryExpression | LogicalExpression} parent
* @param {boolean} is_right
* @returns
*/
function needs_parens(node, parent, is_right) {
// special case where logical expressions and coalesce expressions cannot be mixed,
// either of them need to be wrapped with parentheses
if (
node.type === 'LogicalExpression' &&
parent.type === 'LogicalExpression' &&
((parent.operator === '??' && node.operator !== '??') ||
(parent.operator !== '??' && node.operator === '??'))
) {
return true;
}
const precedence = EXPRESSIONS_PRECEDENCE[node.type];
const parent_precedence = EXPRESSIONS_PRECEDENCE[parent.type];
if (precedence !== parent_precedence) {
// Different node types
return (
(!is_right &&
precedence === 15 &&
parent_precedence === 14 &&
parent.operator === '**') ||
precedence < parent_precedence
);
}
if (precedence !== 13 && precedence !== 14) {
// Not a `LogicalExpression` or `BinaryExpression`
return false;
}
if (
/** @type {BinaryExpression} */ (node).operator === '**' &&
parent.operator === '**'
) {
// Exponentiation operator has right-to-left associativity
return !is_right;
}
if (is_right) {
// Parenthesis are used if both operators have the same precedence
return (
OPERATOR_PRECEDENCE[/** @type {BinaryExpression} */ (node).operator] <=
OPERATOR_PRECEDENCE[parent.operator]
);
}
return (
OPERATOR_PRECEDENCE[/** @type {BinaryExpression} */ (node).operator] <
OPERATOR_PRECEDENCE[parent.operator]
);
}
/** @param {Node} node */
function has_call_expression(node) {
while (node) {
if (node.type[0] === 'CallExpression') {
return true;
} else if (node.type === 'MemberExpression') {
node = node.object;
} else {
return false;
}
}
}
/** @param {Chunk[]} chunks */
const has_newline = (chunks) => {
for (let i = 0; i < chunks.length; i += 1) {
if (chunks[i].has_newline) return true;
}
return false;
};
/** @param {Chunk[]} chunks */
const get_length = (chunks) => {
let total = 0;
for (let i = 0; i < chunks.length; i += 1) {
total += chunks[i].content.length;
}
return total;
};
/**
* @param {number} a
* @param {number} b
*/
const sum = (a, b) => a + b;
/**
* @param {Chunk[][]} nodes
* @param {Chunk} separator
* @returns {Chunk[]}
*/
const join = (nodes, separator) => {
if (nodes.length === 0) return [];
const joined = [...nodes[0]];
for (let i = 1; i < nodes.length; i += 1) {
joined.push(separator);
push_array(joined, nodes[i]);
}
return joined;
};
/**
* @param {(node: any, state: State) => Chunk[]} fn
*/
const scoped = (fn) => {
/**
* @param {any} node
* @param {State} state
*/
const scoped_fn = (node, state) => {
return fn(node, {
...state,
scope: state.scope_map.get(node)
});
};
return scoped_fn;
};
/**
* @param {string} name
* @param {Set<string>} names
*/
const deconflict = (name, names) => {
const original = name;
let i = 1;
while (names.has(name)) {
name = `${original}$${i++}`;
}
return name;
};
/**
* @param {Node[]} nodes
* @param {State} state
*/
const handle_body = (nodes, state) => {
const chunks = [];
const body = nodes.map((statement) => {
const chunks = handle(statement, {
...state,
indent: state.indent
});
let add_newline = false;
while (state.comments.length) {
const comment = state.comments.shift();
const prefix = add_newline ? `\n${state.indent}` : ` `;
chunks.push(
c(
comment.type === 'Block'
? `${prefix}/*${comment.value}*/`
: `${prefix}//${comment.value}`
)
);
add_newline = comment.type === 'Line';
}
return chunks;
});
let needed_padding = false;
for (let i = 0; i < body.length; i += 1) {
const needs_padding = has_newline(body[i]);
if (i > 0) {
chunks.push(
c(
needs_padding || needed_padding
? `\n\n${state.indent}`
: `\n${state.indent}`
)
);
}
push_array(chunks, body[i]);
needed_padding = needs_padding;
}
return chunks;
};
/**
* @param {VariableDeclaration} node
* @param {State} state
*/
const handle_var_declaration = (node, state) => {
const chunks = [c(`${node.kind} `)];
const declarators = node.declarations.map((d) =>
handle(d, {
...state,
indent: state.indent + (node.declarations.length === 1 ? '' : '\t')
})
);
const multiple_lines =
declarators.some(has_newline) ||
declarators.map(get_length).reduce(sum, 0) +
(state.indent.length + declarators.length - 1) * 2 >
80;
const separator = c(multiple_lines ? `,\n${state.indent}\t` : ', ');
push_array(chunks, join(declarators, separator));
return chunks;
};
/** @type {Record<string, Handler>} */
const handlers = {
Program(node, state) {
return handle_body(node.body, state);
},
BlockStatement: scoped((node, state) => {
return [
c(`{\n${state.indent}\t`),
...handle_body(node.body, { ...state, indent: state.indent + '\t' }),
c(`\n${state.indent}}`)
];
}),
EmptyStatement(node, state) {
return [c(';')];
},
ParenthesizedExpression(node, state) {
return handle(node.expression, state);
},
ExpressionStatement(node, state) {
if (
node.expression.type === 'AssignmentExpression' &&
node.expression.left.type === 'ObjectPattern'
) {
// is an AssignmentExpression to an ObjectPattern
return [c('('), ...handle(node.expression, state), c(');')];
}
return [...handle(node.expression, state), c(';')];
},
IfStatement(node, state) {
const chunks = [
c('if ('),
...handle(node.test, state),
c(') '),
...handle(node.consequent, state)
];
if (node.alternate) {
chunks.push(c(' else '));
push_array(chunks, handle(node.alternate, state));
}
return chunks;
},
LabeledStatement(node, state) {
return [...handle(node.label, state), c(': '), ...handle(node.body, state)];
},
BreakStatement(node, state) {
return node.label
? [c('break '), ...handle(node.label, state), c(';')]
: [c('break;')];
},
ContinueStatement(node, state) {
return node.label
? [c('continue '), ...handle(node.label, state), c(';')]
: [c('continue;')];
},
WithStatement(node, state) {
return [
c('with ('),
...handle(node.object, state),
c(') '),
...handle(node.body, state)
];
},
SwitchStatement(/** @type {SwitchStatement} */ node, state) {
const chunks = [
c('switch ('),
...handle(node.discriminant, state),
c(') {')
];
node.cases.forEach((block) => {
if (block.test) {
chunks.push(c(`\n${state.indent}\tcase `));
push_array(
chunks,
handle(block.test, { ...state, indent: `${state.indent}\t` })
);
chunks.push(c(':'));
} else {
chunks.push(c(`\n${state.indent}\tdefault:`));
}
block.consequent.forEach((statement) => {
chunks.push(c(`\n${state.indent}\t\t`));
push_array(
chunks,
handle(statement, { ...state, indent: `${state.indent}\t\t` })
);
});
});
chunks.push(c(`\n${state.indent}}`));
return chunks;
},
ReturnStatement(node, state) {
if (node.argument) {
const contains_comment =
node.argument.leadingComments &&
node.argument.leadingComments.some(
(
/** @type import('../utils/comments.js').CommentWithLocation */ comment
) => comment.has_trailing_newline
);
return [
c(contains_comment ? 'return (' : 'return '),
...handle(node.argument, state),
c(contains_comment ? ');' : ';')
];
} else {
return [c('return;')];
}
},
ThrowStatement(node, state) {
return [c('throw '), ...handle(node.argument, state), c(';')];
},
TryStatement(node, state) {
const chunks = [c('try '), ...handle(node.block, state)];
if (node.handler) {
if (node.handler.param) {
chunks.push(c(' catch('));
push_array(chunks, handle(node.handler.param, state));
chunks.push(c(') '));
} else {
chunks.push(c(' catch '));
}
push_array(chunks, handle(node.handler.body, state));
}
if (node.finalizer) {
chunks.push(c(' finally '));
push_array(chunks, handle(node.finalizer, state));
}
return chunks;
},
WhileStatement(node, state) {
return [
c('while ('),
...handle(node.test, state),
c(') '),
...handle(node.body, state)
];
},
DoWhileStatement(node, state) {
return [
c('do '),
...handle(node.body, state),
c(' while ('),
...handle(node.test, state),
c(');')
];
},
ForStatement: scoped((node, state) => {
const chunks = [c('for (')];
if (node.init) {
if (node.init.type === 'VariableDeclaration') {
push_array(chunks, handle_var_declaration(node.init, state));
} else {
push_array(chunks, handle(node.init, state));
}
}
chunks.push(c('; '));
if (node.test) push_array(chunks, handle(node.test, state));
chunks.push(c('; '));
if (node.update) push_array(chunks, handle(node.update, state));
chunks.push(c(') '));
push_array(chunks, handle(node.body, state));
return chunks;
}),
ForInStatement: scoped((node, state) => {
const chunks = [c(`for ${node.await ? 'await ' : ''}(`)];
if (node.left.type === 'VariableDeclaration') {
push_array(chunks, handle_var_declaration(node.left, state));
} else {
push_array(chunks, handle(node.left, state));
}
chunks.push(c(node.type === 'ForInStatement' ? ` in ` : ` of `));
push_array(chunks, handle(node.right, state));
chunks.push(c(') '));
push_array(chunks, handle(node.body, state));
return chunks;
}),
DebuggerStatement(node, state) {
return [c('debugger', node), c(';')];
},
FunctionDeclaration: scoped(
(/** @type {FunctionDeclaration} */ node, state) => {
const chunks = [];
if (node.async) chunks.push(c('async '));
chunks.push(c(node.generator ? 'function* ' : 'function '));
if (node.id) push_array(chunks, handle(node.id, state));
chunks.push(c('('));
const params = node.params.map((p) =>
handle(p, {
...state,
indent: state.indent + '\t'
})
);
const multiple_lines =
params.some(has_newline) ||
params.map(get_length).reduce(sum, 0) +
(state.indent.length + params.length - 1) * 2 >
80;
const separator = c(multiple_lines ? `,\n${state.indent}` : ', ');
if (multiple_lines) {
chunks.push(c(`\n${state.indent}\t`));
push_array(chunks, join(params, separator));
chunks.push(c(`\n${state.indent}`));
} else {
push_array(chunks, join(params, separator));
}
chunks.push(c(') '));
push_array(chunks, handle(node.body, state));
return chunks;
}
),
VariableDeclaration(node, state) {
return handle_var_declaration(node, state).concat(c(';'));
},
VariableDeclarator(node, state) {
if (node.init) {
return [...handle(node.id, state), c(' = '), ...handle(node.init, state)];
} else {
return handle(node.id, state);
}
},
ClassDeclaration(node, state) {
const chunks = [c('class ')];
if (node.id) {
push_array(chunks, handle(node.id, state));
chunks.push(c(' '));
}
if (node.superClass) {
chunks.push(c('extends '));
push_array(chunks, handle(node.superClass, state));
chunks.push(c(' '));
}
push_array(chunks, handle(node.body, state));
return chunks;
},
ImportDeclaration(/** @type {ImportDeclaration} */ node, state) {
const chunks = [c('import ')];
const { length } = node.specifiers;
const source = handle(node.source, state);
if (length > 0) {
let i = 0;
while (i < length) {
if (i > 0) {
chunks.push(c(', '));
}
const specifier = node.specifiers[i];
if (specifier.type === 'ImportDefaultSpecifier') {
chunks.push(c(specifier.local.name, specifier));
i += 1;
} else if (specifier.type === 'ImportNamespaceSpecifier') {
chunks.push(c('* as ' + specifier.local.name, specifier));
i += 1;
} else {
break;
}
}
if (i < length) {
// we have named specifiers
const specifiers = node.specifiers
.slice(i)
.map((/** @type {ImportSpecifier} */ specifier) => {
const name = handle(specifier.imported, state)[0];
const as = handle(specifier.local, state)[0];
if (name.content === as.content) {
return [as];
}
return [name, c(' as '), as];
});
const width =
get_length(chunks) +
specifiers.map(get_length).reduce(sum, 0) +
2 * specifiers.length +
6 +
get_length(source);
if (width > 80) {
chunks.push(c(`{\n\t`));
push_array(chunks, join(specifiers, c(',\n\t')));
chunks.push(c('\n}'));
} else {
chunks.push(c(`{ `));
push_array(chunks, join(specifiers, c(', ')));
chunks.push(c(' }'));
}
}
chunks.push(c(' from '));
}
push_array(chunks, source);
chunks.push(c(';'));
return chunks;
},
ImportExpression(node, state) {
return [c('import('), ...handle(node.source, state), c(')')];
},
ExportDefaultDeclaration(node, state) {
const chunks = [c(`export default `), ...handle(node.declaration, state)];
if (node.declaration.type !== 'FunctionDeclaration') {
chunks.push(c(';'));
}
return chunks;
},
ExportNamedDeclaration(node, state) {
const chunks = [c('export ')];
if (node.declaration) {
push_array(chunks, handle(node.declaration, state));
} else {
const specifiers = node.specifiers.map(
(/** @type {ExportSpecifier} */ specifier) => {
const name = handle(specifier.local, state)[0];
const as = handle(specifier.exported, state)[0];
if (name.content === as.content) {
return [name];
}
return [name, c(' as '), as];
}
);
const width =
7 + specifiers.map(get_length).reduce(sum, 0) + 2 * specifiers.length;
if (width > 80) {
chunks.push(c('{\n\t'));
push_array(chunks, join(specifiers, c(',\n\t')));
chunks.push(c('\n}'));
} else {
chunks.push(c('{ '));
push_array(chunks, join(specifiers, c(', ')));
chunks.push(c(' }'));
}
if (node.source) {
chunks.push(c(' from '));
push_array(chunks, handle(node.source, state));
}
}
chunks.push(c(';'));
return chunks;
},
ExportAllDeclaration(node, state) {
return [c(`export * from `), ...handle(node.source, state), c(`;`)];
},
MethodDefinition(node, state) {
const chunks = [];
if (node.static) {
chunks.push(c('static '));
}
if (node.kind === 'get' || node.kind === 'set') {
// Getter or setter
chunks.push(c(node.kind + ' '));
}
if (node.value.async) {
chunks.push(c('async '));
}
if (node.value.generator) {
chunks.push(c('*'));
}
if (node.computed) {
chunks.push(c('['));
push_array(chunks, handle(node.key, state));
chunks.push(c(']'));
} else {
push_array(chunks, handle(node.key, state));
}
chunks.push(c('('));
const { params } = node.value;
for (let i = 0; i < params.length; i += 1) {
push_array(chunks, handle(params[i], state));
if (i < params.length - 1) chunks.push(c(', '));
}
chunks.push(c(') '));
push_array(chunks, handle(node.value.body, state));
return chunks;
},
ArrowFunctionExpression: scoped(
(/** @type {ArrowFunctionExpression} */ node, state) => {
const chunks = [];
if (node.async) chunks.push(c('async '));
if (node.params.length === 1 && node.params[0].type === 'Identifier') {
push_array(chunks, handle(node.params[0], state));
} else {
const params = node.params.map((param) =>
handle(param, {
...state,
indent: state.indent + '\t'
})
);
chunks.push(c('('));
push_array(chunks, join(params, c(', ')));
chunks.push(c(')'));
}
chunks.push(c(' => '));
if (
node.body.type === 'ObjectExpression' ||
(node.body.type === 'AssignmentExpression' &&
node.body.left.type === 'ObjectPattern')
) {
chunks.push(c('('));
push_array(chunks, handle(node.body, state));
chunks.push(c(')'));
} else {
push_array(chunks, handle(node.body, state));
}
return chunks;
}
),
ThisExpression(node, state) {
return [c('this', node)];
},
Super(node, state) {
return [c('super', node)];
},
RestElement(node, state) {
return [c('...'), ...handle(node.argument, state)];
},
YieldExpression(node, state) {
if (node.argument) {
return [
c(node.delegate ? `yield* ` : `yield `),
...handle(node.argument, state)
];
}
return [c(node.delegate ? `yield*` : `yield`)];
},
AwaitExpression(node, state) {
if (node.argument) {
const precedence = EXPRESSIONS_PRECEDENCE[node.argument.type];
if (precedence && precedence < EXPRESSIONS_PRECEDENCE.AwaitExpression) {
return [c('await ('), ...handle(node.argument, state), c(')')];
} else {
return [c('await '), ...handle(node.argument, state)];
}
}
return [c('await')];
},
TemplateLiteral(node, state) {
const chunks = [c('`')];
const { quasis, expressions } = node;
for (let i = 0; i < expressions.length; i++) {
chunks.push(c(quasis[i].value.raw), c('${'));
push_array(chunks, handle(expressions[i], state));
chunks.push(c('}'));
}
chunks.push(c(quasis[quasis.length - 1].value.raw), c('`'));
return chunks;
},
TaggedTemplateExpression(node, state) {
return handle(node.tag, state).concat(handle(node.quasi, state));
},
ArrayExpression(node, state) {
const chunks = [c('[')];
/** @type {Chunk[][]} */
const elements = [];
/** @type {Chunk[]} */
let sparse_commas = [];
for (let i = 0; i < node.elements.length; i += 1) {
// can't use map/forEach because of sparse arrays
const element = node.elements[i];
if (element) {
elements.push([
...sparse_commas,
...handle(element, {
...state,
indent: state.indent + '\t'
})
]);
sparse_commas = [];
} else {
sparse_commas.push(c(','));
}
}
const multiple_lines =
elements.some(has_newline) ||
elements.map(get_length).reduce(sum, 0) +
(state.indent.length + elements.length - 1) * 2 >
80;
if (multiple_lines) {
chunks.push(c(`\n${state.indent}\t`));
push_array(chunks, join(elements, c(`,\n${state.indent}\t`)));
chunks.push(c(`\n${state.indent}`));
push_array(chunks, sparse_commas);
} else {
push_array(chunks, join(elements, c(', ')));
push_array(chunks, sparse_commas);
}
chunks.push(c(']'));
return chunks;
},
ObjectExpression(/** @type {ObjectExpression} */ node, state) {
if (node.properties.length === 0) {
return [c('{}')];
}
let has_inline_comment = false;
/** @type {Chunk[]} */
const chunks = [];
const separator = c(', ');
node.properties.forEach((p, i) => {
push_array(
chunks,
handle(p, {
...state,
indent: state.indent + '\t'
})
);
if (state.comments.length) {
// TODO generalise this, so it works with ArrayExpressions and other things.
// At present, stuff will just get appended to the closest statement/declaration
chunks.push(c(', '));
while (state.comments.length) {
const comment = state.comments.shift();
chunks.push(
c(
comment.type === 'Block'
? `/*${comment.value}*/\n${state.indent}\t`
: `//${comment.value}\n${state.indent}\t`
)
);
if (comment.type === 'Line') {
has_inline_comment = true;
}
}
} else {
if (i < node.properties.length - 1) {
chunks.push(separator);
}
}
});
const multiple_lines =
has_inline_comment || has_newline(chunks) || get_length(chunks) > 40;
if (multiple_lines) {
separator.content = `,\n${state.indent}\t`;
}
return [
c(multiple_lines ? `{\n${state.indent}\t` : `{ `),
...chunks,
c(multiple_lines ? `\n${state.indent}}` : ` }`)
];
},
Property(node, state) {
const value = handle(node.value, state);
if (node.key === node.value) {
return value;
}
// special case
if (
!node.computed &&
node.value.type === 'AssignmentPattern' &&
node.value.left.type === 'Identifier' &&
node.value.left.name === node.key.name
) {
return value;
}
if (
!node.computed &&
node.value.type === 'Identifier' &&
((node.key.type === 'Identifier' && node.key.name === value[0].content) ||
(node.key.type === 'Literal' && node.key.value === value[0].content))
) {
return value;
}
const key = handle(node.key, state);
if (node.value.type === 'FunctionExpression' && !node.value.id) {
state = {
...state,
scope: state.scope_map.get(node.value)
};
const chunks = node.kind !== 'init' ? [c(`${node.kind} `)] : [];
if (node.value.async) {
chunks.push(c('async '));
}
if (node.value.generator) {
chunks.push(c('*'));
}
push_array(chunks, node.computed ? [c('['), ...key, c(']')] : key);
chunks.push(c('('));
push_array(
chunks,
join(
node.value.params.map((/** @type {Pattern} */ param) =>
handle(param, state)
),
c(', ')
)
);
chunks.push(c(') '));
push_array(chunks, handle(node.value.body, state));
return chunks;
}
if (node.computed) {
return [c('['), ...key, c(']: '), ...value];
}
return [...key, c(': '), ...value];
},
ObjectPattern(node, state) {
const chunks = [c('{ ')];
for (let i = 0; i < node.properties.length; i += 1) {
push_array(chunks, handle(node.properties[i], state));
if (i < node.properties.length - 1) chunks.push(c(', '));
}
chunks.push(c(' }'));
return chunks;
},
SequenceExpression(/** @type {SequenceExpression} */ node, state) {
const expressions = node.expressions.map((e) => handle(e, state));
return [c('('), ...join(expressions, c(', ')), c(')')];
},
UnaryExpression(node, state) {
const chunks = [c(node.operator)];
if (node.operator.length > 1) {
chunks.push(c(' '));
}
if (
EXPRESSIONS_PRECEDENCE[node.argument.type] <
EXPRESSIONS_PRECEDENCE.UnaryExpression
) {
chunks.push(c('('));
push_array(chunks, handle(node.argument, state));
chunks.push(c(')'));
} else {
push_array(chunks, handle(node.argument, state));
}
return chunks;
},
UpdateExpression(node, state) {
return node.prefix
? [c(node.operator), ...handle(node.argument, state)]
: [...handle(node.argument, state), c(node.operator)];
},
AssignmentExpression(node, state) {
return [
...handle(node.left, state),
c(` ${node.operator || '='} `),
...handle(node.right, state)
];
},
BinaryExpression(node, state) {
/**
* @type any[]
*/
const chunks = [];
// TODO
// const is_in = node.operator === 'in';
// if (is_in) {
// // Avoids confusion in `for` loops initializers
// chunks.push(c('('));
// }
if (needs_parens(node.left, node, false)) {
chunks.push(c('('));
push_array(chunks, handle(node.left, state));
chunks.push(c(')'));
} else {
push_array(chunks, handle(node.left, state));
}
chunks.push(c(` ${node.operator} `));
if (needs_parens(node.right, node, true)) {
chunks.push(c('('));
push_array(chunks, handle(node.right, state));
chunks.push(c(')'));
} else {
push_array(chunks, handle(node.right, state));
}
return chunks;
},
ConditionalExpression(node, state) {
/**
* @type any[]
*/
const chunks = [];
if (
EXPRESSIONS_PRECEDENCE[node.test.type] >
EXPRESSIONS_PRECEDENCE.ConditionalExpression
) {
push_array(chunks, handle(node.test, state));
} else {
chunks.push(c('('));
push_array(chunks, handle(node.test, state));
chunks.push(c(')'));
}
const child_state = { ...state, indent: state.indent + '\t' };
const consequent = handle(node.consequent, child_state);
const alternate = handle(node.alternate, child_state);
const multiple_lines =
has_newline(consequent) ||
has_newline(alternate) ||
get_length(chunks) + get_length(consequent) + get_length(alternate) > 50;
if (multiple_lines) {
chunks.push(c(`\n${state.indent}? `));
push_array(chunks, consequent);
chunks.push(c(`\n${state.indent}: `));
push_array(chunks, alternate);
} else {
chunks.push(c(` ? `));
push_array(chunks, consequent);
chunks.push(c(` : `));
push_array(chunks, alternate);
}
return chunks;
},
NewExpression(/** @type {NewExpression} */ node, state) {
const chunks = [c('new ')];
if (
EXPRESSIONS_PRECEDENCE[node.callee.type] <
EXPRESSIONS_PRECEDENCE.CallExpression ||
has_call_expression(node.callee)
) {
chunks.push(c('('));
push_array(chunks, handle(node.callee, state));
chunks.push(c(')'));
} else {
push_array(chunks, handle(node.callee, state));
}
// TODO this is copied from CallExpression — DRY it out
const args = node.arguments.map((arg) =>
handle(arg, {
...state,
indent: state.indent + '\t'
})
);
const separator = args.some(has_newline) // TODO or length exceeds 80
? c(',\n' + state.indent)
: c(', ');
chunks.push(c('('));
push_array(chunks, join(args, separator));
chunks.push(c(')'));
return chunks;
},
ChainExpression(node, state) {
return handle(node.expression, state);
},
CallExpression(/** @type {CallExpression} */ node, state) {
/**
* @type any[]
*/
const chunks = [];
if (
EXPRESSIONS_PRECEDENCE[node.callee.type] <
EXPRESSIONS_PRECEDENCE.CallExpression
) {
chunks.push(c('('));
push_array(chunks, handle(node.callee, state));
chunks.push(c(')'));
} else {
push_array(chunks, handle(node.callee, state));
}
if (/** @type {SimpleCallExpression} */ (node).optional) {
chunks.push(c('?.'));
}
let has_inline_comment = false;
let arg_chunks = [];
outer: for (const arg of node.arguments) {
const chunks = [];
while (state.comments.length) {
const comment = state.comments.shift();
if (comment.type === 'Line') {
has_inline_comment = true;
break outer;
}
chunks.push(
c(
comment.type === 'Block'
? `/*${comment.value}*/ `
: `//${comment.value}`
)
);
}
push_array(chunks, handle(arg, state));
arg_chunks.push(chunks);
}
const multiple_lines =
has_inline_comment || arg_chunks.slice(0, -1).some(has_newline); // TODO or length exceeds 80
if (multiple_lines) {
// need to handle args again. TODO find alternative approach?
const args = node.arguments.map((arg, i) => {
const chunks = handle(arg, {
...state,
indent: `${state.indent}\t`
});
if (i < node.arguments.length - 1) chunks.push(c(','));
while (state.comments.length) {
const comment = state.comments.shift();
chunks.push(
c(
comment.type === 'Block'
? ` /*${comment.value}*/ `
: ` //${comment.value}`
)
);
}
return chunks;
});
chunks.push(c(`(\n${state.indent}\t`));
push_array(chunks, join(args, c(`\n${state.indent}\t`)));
chunks.push(c(`\n${state.indent})`));
} else {
chunks.push(c('('));
push_array(chunks, join(arg_chunks, c(', ')));
chunks.push(c(')'));
}
return chunks;
},
MemberExpression(node, state) {
/**
* @type any[]
*/
const chunks = [];
if (
EXPRESSIONS_PRECEDENCE[node.object.type] <
EXPRESSIONS_PRECEDENCE.MemberExpression
) {
chunks.push(c('('));
push_array(chunks, handle(node.object, state));
chunks.push(c(')'));
} else {
push_array(chunks, handle(node.object, state));
}
if (node.computed) {
if (node.optional) {
chunks.push(c('?.'));
}
chunks.push(c('['));
push_array(chunks, handle(node.property, state));
chunks.push(c(']'));
} else {
chunks.push(c(node.optional ? '?.' : '.'));
push_array(chunks, handle(node.property, state));
}
return chunks;
},
MetaProperty(node, state) {
return [
...handle(node.meta, state),
c('.'),
...handle(node.property, state)
];
},
Identifier(node, state) {
let name = node.name;
if (name[0] === '@') {
name = state.getName(name.slice(1));
} else if (node.name[0] === '#') {
const owner = state.scope.find_owner(node.name);
if (!owner) {
throw new Error(`Could not find owner for node`);
}
if (!state.deconflicted.has(owner)) {
state.deconflicted.set(owner, new Map());
}
const deconflict_map = state.deconflicted.get(owner);
if (!deconflict_map.has(node.name)) {
deconflict_map.set(
node.name,
deconflict(node.name.slice(1), owner.references)
);
}
name = deconflict_map.get(node.name);
}
return [c(name, node)];
},
Literal(/** @type {Literal} */ node, state) {
if (typeof node.value === 'string') {
return [
// TODO do we need to handle weird unicode characters somehow?
// str.replace(/\\u(\d{4})/g, (m, n) => String.fromCharCode(+n))
c(
(node.raw || JSON.stringify(node.value)).replace(
re,
(_m, _i, at, hash, name) => {
if (at) return '@' + name;
if (hash) return '#' + name;
throw new Error(`this shouldn't happen`);
}
),
node
)
];
}
return [c(node.raw || String(node.value), node)];
},
PropertyDefinition(/** @type {PropertyDefinition} */ node, state) {
const chunks = [];
if (node.static) {
chunks.push(c('static '));
}
if (node.computed) {
chunks.push(c('['), ...handle(node.key, state), c(']'));
} else {
chunks.push(...handle(node.key, state));
}
if (node.value) {
chunks.push(c(' = '));
chunks.push(...handle(node.value, state));
}
chunks.push(c(';'));
return chunks;
},
StaticBlock(/** @type {StaticBlock} */ node, state) {
const chunks = [c('static ')];
push_array(chunks, handlers.BlockStatement(node, state));
return chunks;
},
PrivateIdentifier(/** @type {PrivateIdenifier} */ node, state) {
const chunks = [c('#')];
push_array(chunks, [c(node.name, node)]);
return chunks;
}
};
handlers.ForOfStatement = handlers.ForInStatement;
handlers.FunctionExpression = handlers.FunctionDeclaration;
handlers.ClassExpression = handlers.ClassDeclaration;
handlers.ClassBody = handlers.BlockStatement;
handlers.SpreadElement = handlers.RestElement;
handlers.ArrayPattern = handlers.ArrayExpression;
handlers.LogicalExpression = handlers.BinaryExpression;
handlers.AssignmentPattern = handlers.AssignmentExpression;