|
|
|
|
|
|
|
import { re } from '../utils/id.js'; |
|
import { push_array } from '../utils/push_array.js'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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]); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
}; |
|
|
|
|
|
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 |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function needs_parens(node, parent, is_right) { |
|
|
|
|
|
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) { |
|
|
|
return ( |
|
(!is_right && |
|
precedence === 15 && |
|
parent_precedence === 14 && |
|
parent.operator === '**') || |
|
precedence < parent_precedence |
|
); |
|
} |
|
|
|
if (precedence !== 13 && precedence !== 14) { |
|
|
|
return false; |
|
} |
|
|
|
if ( |
|
(node).operator === '**' && |
|
parent.operator === '**' |
|
) { |
|
|
|
return !is_right; |
|
} |
|
|
|
if (is_right) { |
|
|
|
return ( |
|
OPERATOR_PRECEDENCE[ (node).operator] <= |
|
OPERATOR_PRECEDENCE[parent.operator] |
|
); |
|
} |
|
|
|
return ( |
|
OPERATOR_PRECEDENCE[ (node).operator] < |
|
OPERATOR_PRECEDENCE[parent.operator] |
|
); |
|
} |
|
|
|
|
|
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; |
|
} |
|
} |
|
} |
|
|
|
|
|
const has_newline = (chunks) => { |
|
for (let i = 0; i < chunks.length; i += 1) { |
|
if (chunks[i].has_newline) return true; |
|
} |
|
return false; |
|
}; |
|
|
|
|
|
const get_length = (chunks) => { |
|
let total = 0; |
|
for (let i = 0; i < chunks.length; i += 1) { |
|
total += chunks[i].content.length; |
|
} |
|
return total; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const sum = (a, b) => a + b; |
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
}; |
|
|
|
|
|
|
|
|
|
const scoped = (fn) => { |
|
|
|
|
|
|
|
|
|
const scoped_fn = (node, state) => { |
|
return fn(node, { |
|
...state, |
|
scope: state.scope_map.get(node) |
|
}); |
|
}; |
|
|
|
return scoped_fn; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const deconflict = (name, names) => { |
|
const original = name; |
|
let i = 1; |
|
|
|
while (names.has(name)) { |
|
name = `${original}$${i++}`; |
|
} |
|
|
|
return name; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
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; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
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; |
|
}; |
|
|
|
|
|
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' |
|
) { |
|
|
|
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( 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( |
|
( |
|
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( |
|
( 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( 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) { |
|
|
|
const specifiers = node.specifiers |
|
.slice(i) |
|
.map(( 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( |
|
( 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') { |
|
|
|
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( |
|
( 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('[')]; |
|
|
|
|
|
const elements = []; |
|
|
|
|
|
let sparse_commas = []; |
|
|
|
for (let i = 0; i < node.elements.length; i += 1) { |
|
|
|
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( node, state) { |
|
if (node.properties.length === 0) { |
|
return [c('{}')]; |
|
} |
|
|
|
let has_inline_comment = false; |
|
|
|
|
|
const chunks = []; |
|
const separator = c(', '); |
|
|
|
node.properties.forEach((p, i) => { |
|
push_array( |
|
chunks, |
|
handle(p, { |
|
...state, |
|
indent: state.indent + '\t' |
|
}) |
|
); |
|
|
|
if (state.comments.length) { |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
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(( 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( 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) { |
|
|
|
|
|
|
|
const chunks = []; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
|
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( 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)); |
|
} |
|
|
|
|
|
const args = node.arguments.map((arg) => |
|
handle(arg, { |
|
...state, |
|
indent: state.indent + '\t' |
|
}) |
|
); |
|
|
|
const separator = args.some(has_newline) |
|
? 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( node, state) { |
|
|
|
|
|
|
|
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 ( (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); |
|
if (multiple_lines) { |
|
|
|
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) { |
|
|
|
|
|
|
|
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( node, state) { |
|
if (typeof node.value === 'string') { |
|
return [ |
|
|
|
|
|
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( 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( node, state) { |
|
const chunks = [c('static ')]; |
|
|
|
push_array(chunks, handlers.BlockStatement(node, state)); |
|
|
|
return chunks; |
|
}, |
|
|
|
PrivateIdentifier( 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; |
|
|