tenko
Version:
A "pixel perfect" 100% spec compliant ES2021 JavaScript parser written in JS.
889 lines (863 loc) • 36.8 kB
JavaScript
// Serialize an AST into source code
// The resulting string may be different than the original input but should parse into an equivalent AST
// The output of this printer is in particular overly saturated with parentheses.
import {
$$A_61, $$A_UC_41,
$$B_62, $$B_UC_42, $$C_UC_43,
$$D_64, $$D_UC_44,
$$E_65, $$E_UC_45, $$G_67, $$H_68,
$$I_69,
$$I_UC_49,
$$L_6C, $$L_UC_4C,
$$M_6D, $$N_UC_4E,
$$O_6F, $$O_UC_4F,
$$P_70,
$$R_72,
$$S_73, $$S_UC_53, $$T_74,
$$U_75, $$W_77, $$X_78
} from "../charcodes.mjs";
function assert(a, b) {
// This is an assert that can be dropped for a build... It confirms hashing assumptions
// (Will also be an invaluable tool when adding a new node type ;)
if (a !== b) throw new Error('Expected `' + b + '`, got `' + a + '`');
}
function ArrayExpression(node) {
assert(node.type, 'ArrayExpression');
return '[' + node.elements.map((n, i) => n === null ? ',' : ($(n) + ((n.type === 'RestElement' || i === node.elements.length - 1) ? '' : ','))).join(' ') + ']';
}
function ArrayPattern(node) {
assert(node.type, 'ArrayPattern');
return '[' + node.elements.map((n, i) => n === null ? ',' : ($(n) + ((n.type === 'RestElement' || i === node.elements.length - 1) ? '' : ','))).join(' ') + ']';
}
function ArrowFunctionExpression(node) {
assert(node.type, 'ArrowFunctionExpression');
// Babel has no .expressions: https://github.com/babel/babel/issues/6772#issuecomment-342935685
let body = $(node.body);
if (node.expression || (node.expression === undefined && node.body.type !== 'BlockStatement') && (
node.expression.type !== 'Identifier' // x=>x
&& node.expression.type !== 'Import' // x=>import()
&& node.expression.type !== 'Super' // x=>super
&& node.expression.type !== 'MemberExpression' // x=>x.y
&& node.expression.type !== 'Literal' // x=>5
&& node.expression.type !== 'CallExpression' // x=>x()
&& node.expression.type !== 'ArrayExpression' // x=>[x]
// && node.expression.type !== 'ObjectExpression' // {x} Can not because x=>{} is a block, not objlit
&& node.expression.type !== 'MetaProperty' // x=>new.target
&& node.expression.type !== 'TaggedTemplateExpression' // x=>foo``
&& node.expression.type !== 'TemplateLiteral' // x=>`foo`
&& node.expression.type !== 'ThisExpression' // x=>this
)) {
body = '(' + body + ')';
}
if (
node.params.length === 1 &&
node.params[0].type !== 'AssignmentPattern' &&
node.params[0].type !== 'ArrayPattern' &&
node.params[0].type !== 'ObjectPattern' &&
node.params[0].type !== 'RestElement'
) {
return (node.async ? 'async ' : '') + $(node.params[0]) + ' => ' + body;
}
return (node.async ? 'async ' : '') + '(' + node.params.map($).join(', ') + ') => ' + body;
}
function AssignmentExpression(node) {
assert(node.type, 'AssignmentExpression');
if (node.left.type === 'ObjectPattern') {
return '(' + $(node.left) + ' ' + node.operator + ' ' + $(node.right) + ')';
}
return $(node.left) + ' ' + node.operator + ' ' + $(node.right);
}
function AssignmentPattern(node) {
assert(node.type, 'AssignmentPattern');
return $(node.left) + ' = ' + $(node.right);
}
function AwaitExpression(node) {
assert(node.type, 'AwaitExpression');
return 'await (' + $(node.argument) + ')';
}
function BigIntLiteral(node) {
assert(node.type, 'BigIntLiteral');
return node.bigint + 'n';
}
function BinaryExpression(node) {
assert(node.type, 'BinaryExpression');
let left = $(node.left);
switch (node.left.type) {
case 'Literal':
case 'MemberExpression':
case 'CallExpression':
case 'ArrayExpression':
case 'MetaProperty':
case 'TaggedTemplateExpression':
case 'TemplateLiteral':
case 'ThisExpression':
break;
case 'Identifier':
// Prevent toplevel `let in x` cases, which should be `(let) in x`
if (left === 'let' && (node.operator === 'in' || node.operator === 'instanceof')) {
left = '(' + left + ')';
}
break;
default:
left = '(' + left + ')';
}
let right = $(node.right);
switch (node.right.type) {
case 'Identifier':
case 'Literal':
case 'MemberExpression':
case 'CallExpression':
case 'ArrayExpression':
case 'ObjectExpression':
case 'MetaProperty':
case 'TaggedTemplateExpression':
case 'TemplateLiteral':
case 'ThisExpression':
break;
default:
right = '(' + right + ')';
}
return left + ' ' + node.operator + ' ' + right;
}
function BlockStatement(node) {
assert(node.type, 'BlockStatement');
return '{' + node.body.map($).join('\n') + '}';
}
function BooleanLiteral(node) {
assert(node.type, 'BooleanLiteral');
return node.value;
}
function BreakStatement(node) {
assert(node.type, 'BreakStatement');
return 'break' + (node.label ? ' ' + $(node.label) : '') + ';';
}
function CallExpression(node) {
assert(node.type, 'CallExpression');
if (
node.callee.type === 'Identifier' // prevents `x()` becoming `(x)()`, but also `async({__proto__: 1, __proto__: 2})`
|| node.callee.type === 'Import' // import()
|| node.callee.type === 'Super' // super()
|| node.callee.type === 'MemberExpression' // x.y()
|| node.callee.type === 'Literal' // 5() Probably an error
|| node.callee.type === 'CallExpression' // x()()
|| node.callee.type === 'ArrayExpression' // [x]() Probably an error
// || node.callee.type === 'ObjectExpression' // {x}() Probably an error, but unsafe for block anyways
|| node.callee.type === 'MetaProperty' // new.target()
|| node.callee.type === 'TaggedTemplateExpression' // foo``() Probably an error
|| node.callee.type === 'TemplateLiteral' // `foo`() Probably an error
|| node.callee.type === 'ThisExpression' // this() Could technically work
) {
return $(node.callee) + (node.optional ? '?.' : '') + '(' + node.arguments.map($).join(', ') + ')';
}
return $w(node.callee) + (node.optional ? '?.' : '') + '(' + node.arguments.map($).join(', ') + ')';
}
function CatchClause(node) {
assert(node.type, 'CatchClause');
return 'catch ' + (node.param ? $w(node.param) + ' ' : '') + $(node.body);
}
function ClassBody(node) {
assert(node.type, 'ClassBody');
return '{' + node.body.map($).join('\n') + '}';
}
function ClassDeclaration(node) {
assert(node.type, 'ClassDeclaration');
return 'class' + (node.id ? ' ' + $(node.id) : '') + (node.superClass ? ' extends (' + $(node.superClass) + ') ' : '') + $(node.body);
}
function ClassExpression(node) {
assert(node.type, 'ClassExpression');
return 'class' + (node.id ? ' ' + $(node.id) : '') + (node.superClass ? ' extends (' + $(node.superClass) + ') ' : '') + $(node.body);
}
function ClassMethod(node) {
assert(node.type, 'ClassMethod');
assert('value' in node, false);
// The `ClassMethod` type is only used for babelCompat
// Babel does not have .value and merges the method node with the function node, different from the estree spec
return (
(node.static ? 'static ' : '') +
(node.kind === 'get' ? 'get ' : '') +
(node.kind === 'set' ? 'set ' : '') +
(node.async ? 'async ' : '') +
(node.generator ? '* ' : '') +
(node.computed ? '[' + $(node.key) + ']' : $(node.key)) +
'(' + node.params.map($).join(', ') + ')' +
$(node.body) +
';'
);
}
function ChainExpression(node) {
assert(node.type, 'ChainExpression');
// Wrap because the AST for `a?.b?.c` is different from `(a?.b)?.c`
return $w(node.expression);
}
function CommentBlock(node) {
assert(node.type, 'CommentBlock');
return '/*' + node.value + '*/';
}
function CommentLine(node) {
assert(node.type, 'CommentLine');
return '//' + node.value + '\n';
}
function ConditionalExpression(node) {
assert(node.type, 'ConditionalExpression');
let a = $(node.test);
if (
node.test.type !== 'Super' // super ? b : c (Error since super is always call or member)
&& node.test.type !== 'Import' // import() ? b : c
&& node.test.type !== 'Identifier' // y ? b : c
&& node.test.type !== 'Literal' // 5 ? b : c
&& node.test.type !== 'MemberExpression' // x.y ? b : c
&& node.test.type !== 'CallExpression' // x()() ? b : c
&& node.test.type !== 'ArrayExpression' // [] ? b : c
// && node.test.type !== 'ObjectExpression' // {} ? b : c Unsafe if exprstmt
&& node.test.type !== 'MetaProperty' // new.target ? b : c
&& node.test.type !== 'TaggedTemplateExpression' // foo``() ? b : c
&& node.test.type !== 'TemplateLiteral' // `foo` ? b : c
&& node.test.type !== 'ThisExpression' // this ? b : c
// && node.test.type !== 'BinaryExpression' // a ? x + y : c TODO: verify whether this is safe
// && node.test.type !== 'AssignmentExpression' // x = y ? b : c -> x = (y ? b : c) Unsafe
) {
a = '(' + a + ')';
}
let b = $(node.consequent);
if (
node.consequent.type !== 'Super' // a ? super : c (Error since super is always call or member)
&& node.consequent.type !== 'Import' // a ? import() : c
&& node.consequent.type !== 'Identifier' // a ? y : c
&& node.consequent.type !== 'Literal' // a ? 5 : c
&& node.consequent.type !== 'MemberExpression' // a ? x.y : c
&& node.consequent.type !== 'CallExpression' // a ? x()() : c
&& node.consequent.type !== 'ArrayExpression' // a ? [] : c
&& node.consequent.type !== 'ObjectExpression' // a ? {} : c
&& node.consequent.type !== 'MetaProperty' // a ? new.target : c
&& node.consequent.type !== 'TaggedTemplateExpression' // a ? foo``() : c
&& node.consequent.type !== 'TemplateLiteral' // a ? `foo` : c
&& node.consequent.type !== 'ThisExpression' // a ? this : c
&& node.consequent.type !== 'BinaryExpression' // a ? x + y : c
&& node.consequent.type !== 'AssignmentExpression' // a ? x = y : c
) {
b = '(' + b + ')';
}
let c = $(node.alternate);
if (
node.alternate.type !== 'Super' // a ? b : super (Error since super is always call or member)
&& node.alternate.type !== 'Import' // a ? b : import()
&& node.alternate.type !== 'Identifier' // a ? b : y
&& node.alternate.type !== 'Literal' // a ? b : 5
&& node.alternate.type !== 'MemberExpression' // a ? b : x.y
&& node.alternate.type !== 'CallExpression' // a ? b : x()()
&& node.alternate.type !== 'ArrayExpression' // a ? b : []
&& node.alternate.type !== 'ObjectExpression' // a ? b : {}
&& node.alternate.type !== 'MetaProperty' // a ? b : new.target
&& node.alternate.type !== 'TaggedTemplateExpression' // a ? b : foo``()
&& node.alternate.type !== 'TemplateLiteral' // a ? b : `foo`
&& node.alternate.type !== 'ThisExpression' // a ? b : this
// && node.alternate.type !== 'BinaryExpression' // a ? x + y : c TODO: verify whether this is safe
// && node.alternate.type !== 'AssignmentExpression' // a ? b : (x = y) Unsafe
) {
c = '(' + c + ')';
}
return '(' + a + '? ' + b + ' : ' + c + ')';
}
function ContinueStatement(node) {
assert(node.type, 'ContinueStatement');
return 'continue' + (node.label ? ' ' + $(node.label) : '') + ';';
}
function DebuggerStatement(node) {
assert(node.type, 'DebuggerStatement');
return 'debugger;';
}
function Directive(node) {
assert(node.type, 'Directive');
return $(node.value);
}
function DirectiveLiteral(node) {
assert(node.type, 'DirectiveLiteral');
return "'" + node.value + "';";
}
function DoWhileStatement(node) {
assert(node.type, 'DoWhileStatement');
return 'do ' + $(node.body) + ' while ' + $w(node.test) + ';';
}
function EmptyStatement(node) {
assert(node.type, 'EmptyStatement');
return ';';
}
function ExportAllDeclaration(node) {
assert(node.type, 'ExportAllDeclaration');
return 'export * from ' + $(node.source) + ';';
}
function ExportDefaultDeclaration(node) {
assert(node.type, 'ExportDefaultDeclaration');
return 'export default ' + $(node.declaration) + (node.declaration.type === 'ClassDeclaration' || node.declaration.type === 'FunctionDeclaration' ? '' : ';');
}
function ExportNamespaceSpecifier(node) {
assert(node.type, 'ExportNamespaceSpecifier');
return '* as ' + $(node.exported)
}
function ExportNamedDeclaration(node) {
assert(node.type, 'ExportNamedDeclaration');
if (node.specifiers.length === 1 && node.specifiers[0].type === 'ExportNamespaceSpecifier') {
// This is specifically `export * as foo from 'bar'` syntax
assert(!!node.source, true, 'spec dictates this syntax requires the source');
return 'export ' + $(node.specifiers[0]) + ' from ' + $(node.source) + ';';
}
assert(node.specifiers.length !== 1 || (node.specifiers.length > 0 && node.specifiers[0].type !== 'ExportNamespaceSpecifier'), true, 'the ExportNamespaceSpecifier node has restrictions');
return 'export ' + (node.declaration ? $(node.declaration) : ('{' + node.specifiers.map($).join(', ') + '}')) + (node.source ? ' from ' + $(node.source) : '');
}
function ExportSpecifier(node) {
assert(node.type, 'ExportSpecifier');
return (node.local.name !== node.exported.name ? $(node.local) + ' as ' : '') + $(node.exported);
}
function ExpressionStatement(node) {
assert(node.type, 'ExpressionStatement');
if (node.directive === undefined && ( // Protect directives from demotion
node.expression.type === 'ObjectExpression' || // {} would be a block if not paren wrapped
// node.expression.type === 'ArrayExpression' || // [{__proto__: 1, __proto__: 2}] Should be fine with semis
// node.expression.type === 'SequenceExpression' || // a,b
node.expression.type === 'FunctionExpression' || // function(){} -> (function(){}) Else it's a decl or illegal
node.expression.type === 'ClassExpression' || // class{} -> (class{}) Else it's a decl or illegal
// node.expression.type === 'BinaryExpression' || // a+b
// node.expression.type === 'MemberExpression' || // a.b
(node.expression.type === 'Identifier' && node.expression.name === 'let') || // `let \n in x` vs `(let) in x`
// node.expression.type === 'UnaryExpression' || // ~foo
// node.expression.type === 'CallExpression' || // foo()
// node.expression.type === 'AssignmentExpression' // a=b
// node.expression.type === 'MetaExpression' // new.target
// TaggedTemplate
// TemplateLiteral
// ThisExpression
(!node.directive && node.expression.type === 'Literal' && typeof node.expression.value === 'string') // Prevent grouped strings of being promoted to directives
)) {
// :'(
return $w(node.expression) + ';';
}
return $(node.expression) + ';';
}
function ForInStatement(node) {
assert(node.type, 'ForInStatement');
return 'for (' + (node.left.type === 'VariableDeclaration' || node.left.type === 'ObjectPattern' || node.left.type === 'ArrayPattern' ? $(node.left, undefined, undefined, true) : $w(node.left)) + ' in ' + $(node.right) + ') ' + $(node.body);
}
function ForOfStatement(node) {
assert(node.type, 'ForOfStatement');
return 'for ' + (node.await ? 'await ' : '') + '(' + (node.left.type === 'VariableDeclaration' || node.left.type === 'ObjectPattern' || node.left.type === 'ArrayPattern' ? $(node.left, undefined, undefined, true) : $w(node.left)) + ' of ' + $(node.right) + ') ' + $(node.body);
}
function ForStatement(node) {
assert(node.type, 'ForStatement');
return 'for (' + (node.init ? (node.init.type === 'VariableDeclaration' ? $(node.init, undefined, undefined, true) : $w(node.init)) : '') + ';' + (node.test ? $(node.test) : '') + ';' + (node.update ? $(node.update) : '') + ') ' + $(node.body);
}
function FunctionDeclaration(node) {
assert(node.type, 'FunctionDeclaration');
return (node.async ? 'async ' : '') + 'function' + (node.generator ? '*' : '') + (node.id ? ' ' + $(node.id) : '') + '(' + node.params.map($).join(', ') + ') {' + node.body.body.map($).join('\n') + '}';
}
function FunctionExpression(node) {
assert(node.type, 'FunctionExpression');
return (node.async ? 'async ' : '') + 'function' + (node.generator ? '*' : '') + (node.id ? ' ' + $(node.id) : '') + '(' + node.params.map($).join(', ') + ') {' + node.body.body.map($).join('\n') + '}';
}
function Identifier(node) {
assert(node.type, 'Identifier');
return node.name;
}
function IfStatement(node) {
assert(node.type, 'IfStatement');
return 'if ' + $w(node.test) + ' ' + $(node.consequent) + (node.alternate ? ' else ' + $(node.alternate) : '');
}
function Import(node) {
assert(node.type, 'Import');
return 'import';
}
function ImportDeclaration(node) {
assert(node.type, 'ImportDeclaration');
let importSpecifiers = node.specifiers.filter(s => s.type === 'ImportSpecifier');
let otherSpecifiers = node.specifiers.filter(s => s.type !== 'ImportSpecifier');
if (!importSpecifiers.length && !otherSpecifiers.length) {
return 'import {}' + (node.source ? ' from ' + $(node.source) : '') + ';';
}
return 'import ' + (otherSpecifiers.length ? otherSpecifiers.map($).join(', ') : '') + (importSpecifiers.length && otherSpecifiers.length ? ', ' : '') + (importSpecifiers.length ? '{' + importSpecifiers.map($).join(', ') + '}' : '') + (node.source ? ' from ' + $(node.source) : '') + ';';
}
function ImportDefaultSpecifier(node) {
assert(node.type, 'ImportDefaultSpecifier');
return $(node.local);
}
function ImportExpression(node) {
assert(node.type, 'ImportExpression');
return 'import(' + node.arguments.map($).join(', ') + ')';
}
function ImportNamespaceSpecifier(node) {
assert(node.type, 'ImportNamespaceSpecifier');
return '* as ' + $(node.local);
}
function ImportSpecifier(node) {
assert(node.type, 'ImportSpecifier');
return $(node.imported) + (node.local ? ' as ' + $(node.local) : '');
}
function LabeledStatement(node) {
assert(node.type, 'LabeledStatement');
return $(node.label) + ': ' + $(node.body);
}
function Literal(node) {
assert(node.type, 'Literal');
switch (typeof node.value) {
case 'boolean':
return node.raw;
case 'number':
return node.raw;
case 'string':
return node.raw;
case 'object': // regex
return node.raw;
}
throw new Error('fixme; literal type');
}
function LogicalExpression(node) {
assert(node.type, 'LogicalExpression');
let left = $(node.left);
if (
node.left.type !== 'Super' // super && x (Error since super is always call or member)
&& node.left.type !== 'Import' // import() && x
&& node.left.type !== 'Identifier' // x && y
&& node.left.type !== 'Literal' // 5 && x
&& node.left.type !== 'MemberExpression' // x.y && z
&& node.left.type !== 'CallExpression' // x()() && x
&& node.left.type !== 'ArrayExpression' // [] && x
// && node.left.type !== 'ObjectExpression' // {} && x Block if statement expression so, meh
&& node.left.type !== 'MetaProperty' // new.target && x
&& node.left.type !== 'TaggedTemplateExpression' // foo``() && x
&& node.left.type !== 'TemplateLiteral' // `foo` && x
&& node.left.type !== 'ThisExpression' // this && x
) {
left = '(' + left + ')';
}
let right = $(node.right);
if (
node.right.type !== 'Super' // x && super (Error since super is always call or member)
&& node.right.type !== 'Import' // x && import()
&& node.right.type !== 'Identifier' // x && y
&& node.right.type !== 'Literal' // x && 5
&& node.right.type !== 'MemberExpression' // x && x.y
&& node.right.type !== 'CallExpression' // x && x()()
&& node.right.type !== 'ArrayExpression' // x && []
&& node.right.type !== 'ObjectExpression' // x && {}
&& node.right.type !== 'MetaProperty' // x && new.target
&& node.right.type !== 'TaggedTemplateExpression' // x && foo``()
&& node.right.type !== 'TemplateLiteral' // x && `foo`
&& node.right.type !== 'ThisExpression' // x && this
) {
right = '(' + right + ')';
}
return '(' + left + ' ' + node.operator + ' ' + right + ')';
}
function MemberExpression(node) {
assert(node.type, 'MemberExpression');
if (
node.object.type === 'ObjectExpression' || // {}.c -> ({}).c
node.object.type === 'SequenceExpression' || // a,b.c -> (a,b).c
node.object.type === 'FunctionExpression' || // function(){}.c -> (function(){}).c
node.object.type === 'ClassExpression' || // class x{}.b -> (class x{}).b
node.object.type === 'BinaryExpression' || // a+b.c -> (a+b).c
// node.object.type === 'MemberExpression' || // a.b.c -> (a.b).c
// node.object.type === 'Identifier' || // a.b -> (a).b
// node.object.type === 'CallExpression' || // -> a().b -> (a()).b
node.object.type === 'UnaryExpression' || // `(!t).y`
node.object.type === 'ArrowFunctionExpression' || // ()=>x.y -> (()=>x).y
node.object.type === 'UpdateExpression' || // `(++x)[x]`
(node.object.type === 'Literal' && typeof node.object.value === 'number') || // `4 .p`
(node.object.type === 'Identifier' && node.object.name === 'let') || // `(let)[x]`
node.object.type === 'AssignmentExpression' // a=b.c -> (a=b).c
) {
// Need to wrap object
if (node.optional) {
return $w(node.object) + (node.computed ? '?.[' + $(node.property) + ']' : ('?.' + $(node.property)));
}
return $w(node.object) + (node.computed ? '[' + $(node.property) + ']' : ('.' + $(node.property)));
}
if (node.optional) {
return $(node.object) + (node.computed ? '?.[' + $(node.property) + ']' : ('?.' + $(node.property)));
}
return $(node.object) + (node.computed ? '[' + $(node.property) + ']' : ('.' + $(node.property)));
}
function MetaProperty(node) {
assert(node.type, 'MetaProperty');
return $(node.meta) + '.' + $(node.property);
}
function MethodDefinition(node) {
assert(node.type, 'MethodDefinition');
return (
(node.static ? 'static ' : '') +
(node.kind === 'get' ? 'get ' : '') +
(node.kind === 'set' ? 'set ' : '') +
(node.value.async ? 'async ' : '') +
(node.value.generator ? '* ' : '') +
(node.computed ? '[' + $(node.key) + ']' : $(node.key)) +
'(' + node.value.params.map($).join(', ') + ')' +
$(node.value.body) +
';'
);
}
function NewExpression(node) {
assert(node.type, 'NewExpression');
if (
node.callee.type === 'Super'
|| node.callee.type === 'Import'
|| node.callee.type === 'Identifier'
|| node.callee.type === 'Literal'
|| node.callee.type === 'MemberExpression'
// || node.callee.type === 'CallExpression' // new x()() -> new (x())()
|| node.callee.type === 'ArrayExpression' // new [] Runtime error...?
|| node.callee.type === 'ObjectExpression' // new {} Runtime error...?
|| node.callee.type === 'MetaProperty' // new new.target
// || node.callee.type === 'TaggedTemplateExpression' // new foo``() -> new (foo``)
|| node.callee.type === 'TemplateLiteral' // new `foo` Runtime error?
|| node.callee.type === 'ThisExpression' // new this (Could be made to work)
) {
return 'new ' + $(node.callee) + '(' + node.arguments.map($).join(', ') + ')';
}
return 'new ' + $w(node.callee) + '(' + node.arguments.map($).join(', ') + ')';
}
function NullLiteral(node) {
assert(node.type, 'NullLiteral');
return 'null';
}
function NumericLiteral(node) {
assert(node.type, 'NumericLiteral');
return node.raw;
}
function ObjectExpression(node) {
assert(node.type, 'ObjectExpression');
return '{' + node.properties.map($).join(', ') + '}';
}
function ObjectMethod(node) {
assert(node.type, 'ObjectMethod');
assert('value' in node, false);
// Note: 'ObjectMethod' is only used for babelCompat
// Babel does not have .value and merges the method node with the function node, different from the estree spec
return (
(node.static ? 'static ' : '') +
(node.kind === 'get' ? 'get ' : '') +
(node.kind === 'set' ? 'set ' : '') +
(node.async ? 'async ' : '') +
(node.generator ? '* ' : '') +
(node.computed ? '[' + $(node.key) + ']' : $(node.key)) +
'(' + node.params.map($).join(', ') + ')' +
$(node.body)
);
}
function ObjectPattern(node) {
assert(node.type, 'ObjectPattern');
return '{' + node.properties.map($).join(', ') + '}';
}
function ObjectProperty(node) {
assert(node.type, 'ObjectProperty');
if (node.body) return node.body.map($).join('\n');
// Babel
return Property(node);
}
function Program(node) {
assert(node.type, 'Program');
return node.body.map($).join('\n');
}
function Property(node) {
assert(node.type === 'Property' || node.type === 'ObjectProperty', true);
return (
(node.kind === 'get' || node.kind === 'set' || node.method) ?
(
(node.static ? 'static ' : '') +
(node.kind === 'get' ? 'get ' : '') +
(node.kind === 'set' ? 'set ' : '') +
(node.value.async ? 'async ' : '') +
(node.value.generator ? '* ' : '') +
(node.computed ? '[' : '') + $(node.key) + (node.computed ? ']' : '') +
'(' + node.value.params.map($).join(', ') + ')' +
$(node.value.body) +
''
) : (
(node.shorthand ? '' : ((node.computed ? '[' : '') + $(node.key) + (node.computed ? ']' : '') + ':')) + $(node.value)
)
);
}
function RegExpLiteral(node) {
assert(node.type, 'RegExpLiteral');
return node.extra.raw;
}
function RestElement(node) {
assert(node.type, 'RestElement');
return '...' + $(node.argument);
}
function ReturnStatement(node) {
assert(node.type, 'ReturnStatement');
return 'return' + (node.argument ? ' ' + $(node.argument) : '') + ';';
}
function SequenceExpression(node) {
assert(node.type, 'SequenceExpression');
return '(' + node.expressions.map($).join(', ') + ')';
}
function SpreadElement(node) {
assert(node.type, 'SpreadElement');
return '...' + $(node.argument);
}
function StringLiteral(node) {
assert(node.type, 'StringLiteral');
return node.extra.raw;
}
function Super(node) {
assert(node.type, 'Super');
return 'super';
}
function SwitchCase(node) {
assert(node.type, 'SwitchCase');
return (node.test ? 'case ' + $(node.test) : 'default') + ':\n' + node.consequent.map($).join('\n');
}
function SwitchStatement(node) {
assert(node.type, 'SwitchStatement');
return 'switch ' + $w(node.discriminant) + ' {' + node.cases.map($).join('\n') + '}';
}
function TaggedTemplateExpression(node) {
assert(node.type, 'TaggedTemplateExpression');
return $w(node.tag) + $(node.quasi);
}
function TemplateElement(node) {
assert(node.type, 'TemplateElement');
return node.value.raw;
}
function TemplateLiteral(node) {
assert(node.type, 'TemplateLiteral');
return '`' + $(node.quasis[0]) + (node.expressions.length ? '${' : '') + node.expressions.map((e, i) => $(e) + '}' + $(node.quasis[i+1])).join('${') + '`';
}
function ThisExpression(node) {
assert(node.type, 'ThisExpression');
return 'this';
}
function ThrowStatement(node) {
assert(node.type, 'ThrowStatement');
return 'throw ' + $(node.argument) + ';';
}
function TryStatement(node) {
assert(node.type, 'TryStatement');
return 'try ' + $(node.block) + (node.handler ? ' ' + $(node.handler) : '') + (node.finalizer ? ' finally ' + $(node.finalizer) : '');
}
function UnaryExpression(node) {
assert(node.type, 'UnaryExpression');
if (
node.argument.type === 'Identifier' // !foo
|| node.argument.type === 'Import' // !import()
|| node.argument.type === 'Super' // !super
|| node.argument.type === 'MemberExpression' // !x.y
|| node.argument.type === 'Literal' // !5
|| node.argument.type === 'CallExpression' // !x()
|| node.argument.type === 'ArrayExpression' // ![x]
|| node.argument.type === 'ObjectExpression' // !{x}
|| node.argument.type === 'MetaProperty' // !new.target
|| node.argument.type === 'TaggedTemplateExpression' // ! foo`x`
|| node.argument.type === 'TemplateLiteral' // !`foo`
|| node.argument.type === 'ThisExpression' // !this
) {
return node.operator + ('!+-~'.includes(node.operator)?'':' ') + $(node.argument);
}
return node.operator + ('!+-~'.includes(node.operator)?'':' ') + $w(node.argument);
}
function UpdateExpression(node) {
assert(node.type, 'UpdateExpression');
return (node.prefix ? node.operator : '') + $(node.argument) + (node.prefix ? '' : node.operator);
}
function VariableDeclaration(node, fromFor) {
assert(node.type, 'VariableDeclaration');
return node.kind + ' ' + node.declarations.map($).join(', ') + (fromFor ? '' : ';'); // no semi inside `for`
}
function VariableDeclarator(node) {
assert(node.type, 'VariableDeclarator');
return $(node.id) + (node.init ? ' = ' + $(node.init) : '');
}
function WhileStatement(node) {
assert(node.type, 'WhileStatement');
return 'while ' + $w(node.test) + ' ' + $(node.body);
}
function WithStatement(node) {
assert(node.type, 'WithStatement');
return 'with ' + $w(node.object) + ' ' + $(node.body);
}
function YieldExpression(node) {
assert(node.type, 'YieldExpression');
return '(yield' + (node.delegate ? ' *' : '') + (node.argument ? ' ' + $w(node.argument) : '') + ')';
}
function $w(node) {
return '(' + $(node) + ')';
}
let jumpTable = [
(node, fromFor, type, c) => {
if (c === $$I_69) return Directive(node);
if (c === $$X_78) {
c = type.charCodeAt(6);
if (c === $$D_UC_44) return ExportDefaultDeclaration(node);
return ExportNamespaceSpecifier(node);
}
return UpdateExpression(node);
},
(node, fromFor, type, c) => {
c = type.charCodeAt(3);
if (c === $$L_6C) return BooleanLiteral(node);
if (c === $$I_UC_49) return ForInStatement(node);
if (c === $$O_UC_4F) return ForOfStatement(node);
return UnaryExpression(node);
},
(node, fromFor, type, c) => {
c = type.charCodeAt(0);
if (c === $$A_UC_41) return AssignmentPattern(node);
if (c === $$B_UC_42) return BlockStatement(node);
return ImportSpecifier(node);
},
(node, fromFor, type, c) => {
c = type.charCodeAt(2);
if (c === $$A_61) return ClassExpression(node);
if (c === $$M_6D) return CommentBlock(node);
if (c === $$P_70) return EmptyStatement(node);
return ForStatement(node);
},
(node, fromFor, type, c) => {
c = type.charCodeAt(2);
if (c === $$G_67) return BigIntLiteral(node);
if (c === $$M_6D) return CommentLine(node);
return WithStatement(node);
},
(node, fromFor, type, c) => {
if (c === $$R_72) return ArrowFunctionExpression(node);
return ClassBody(node);
},
(node, fromFor, type, c) => {
c = type.charCodeAt(4);
if (c === $$T_74) return FunctionDeclaration(node);
if (c === $$E_UC_45) return ThisExpression(node);
if (c === $$W_77) return ThrowStatement(node);
if (c === $$E_65) return WhileStatement(node);
return YieldExpression(node);
},
(node, fromFor, type, c) => {
if (c === $$S_73) return AssignmentExpression(node);
if (c === $$L_6C) return ClassMethod(node);
if (c === $$H_68) return ChainExpression(node);
return FunctionExpression(node);
},
(node, fromFor, type, c) => {
c = type.charCodeAt(0);
if (c === $$N_UC_4E) return NewExpression(node);
return RegExpLiteral(node);
},
(node, fromFor, type, c) => {
return MetaProperty(node);
},
(node, fromFor, type, c) => {
c = type.charCodeAt(8);
if (c === $$U_75) return CatchClause(node);
if (c === $$A_61) return ReturnStatement(node);
if (c === $$E_UC_45) return TemplateElement(node);
return TemplateLiteral(node);
},
(node, fromFor, type, c) => {
if (c === $$X_78) return ExpressionStatement(node);
return Import(node);
},
(node, fromFor, type, c) => {
if (c === $$X_78) return ExportAllDeclaration(node);
return ObjectProperty(node);
},
(node, fromFor, type, c) => {
return IfStatement(node);
},
(node, fromFor, type, c) => {
if (c === $$X_78) return ExportNamedDeclaration(node);
if (c === $$D_64) return Identifier(node);
if (c === $$I_69) return Literal(node);
if (c === $$B_62) return ObjectMethod(node);
return RestElement(node);
},
(node, fromFor, type, c) => {
if (c === $$A_61) return CallExpression(node);
return ObjectPattern(node);
},
(node, fromFor, type, c) => {
return Super(node);
},
(node, fromFor, type, c) => {
return LabeledStatement(node);
},
(node, fromFor, type, c) => {
if (c === $$B_62) return ObjectExpression(node);
return VariableDeclaration(node, fromFor);
},
(node, fromFor, type, c) => {
return VariableDeclarator(node);
},
(node, fromFor, type, c) => {
return DebuggerStatement(node);
},
(node, fromFor, type, c) => {
c = type.charCodeAt(2);
if (c === $$P_70) return ImportNamespaceSpecifier(node);
if (c === $$M_6D) return MemberExpression(node);
if (c === $$T_74) return MethodDefinition(node);
return Program(node);
},
(node, fromFor, type, c) => {
// (nothing)
},
(node, fromFor, type, c) => {
if (c === $$X_78) return ExportSpecifier(node);
return SequenceExpression(node);
},
(node, fromFor, type, c) => {
c = type.charCodeAt(0);
if (c === $$A_UC_41) return AwaitExpression(node);
return SwitchStatement(node);
},
(node, fromFor, type, c) => {
c = type.charCodeAt(0);
if (c === $$B_UC_42) return BinaryExpression(node);
if (c === $$D_UC_44) return DirectiveLiteral(node);
if (c === $$S_UC_53) return StringLiteral(node);
return TaggedTemplateExpression(node);
},
(node, fromFor, type, c) => {
if (c === $$O_6F) return ConditionalExpression(node);
return Property(node);
},
(node, fromFor, type, c) => {
if (c === $$M_6D) return ImportDefaultSpecifier(node);
return NumericLiteral(node);
},
(node, fromFor, type, c) => {
if (c === $$R_72) return BreakStatement(node);
if (c === $$L_6C) return ClassDeclaration(node);
return ImportDeclaration(node);
},
(node, fromFor, type, c) => {
if (c === $$R_72) return ArrayExpression(node);
if (c === $$M_6D) return ImportExpression(node);
if (c === $$P_70) return SpreadElement(node);
return SwitchCase(node);
},
(node, fromFor, type, c) => {
c = type.charCodeAt(0);
if (c === $$A_UC_41) return ArrayPattern(node);
if (c === $$C_UC_43) return ContinueStatement(node);
if (c === $$L_UC_4C) return LogicalExpression(node);
if (c === $$N_UC_4E) return NullLiteral(node);
return TryStatement(node);
},
(node, fromFor, type, c) => {
return DoWhileStatement(node);
},
];
function $(node, _, __, fromFor) {
// This is a walker that was (manually) built using a simple hash, as follows:
// Input file is a text file with every node name, one per line
// Get distribution
// var x=1; require('fs').readFileSync('nodes.txt', 'utf8').split('\n').filter(Boolean).reduce((map, s) => { let p = s.length^s.charCodeAt(x)-96; if (!map[p]) map[p] = 0; ++map[p]; return map }, {})
// Get node names per bucket
// var x=6; var obj=require('fs').readFileSync('nodes.txt', 'utf8').split('\n').filter(Boolean).reduce((map, s) => { let p = s.length^s.charCodeAt(s.length-x)-96; if (!map[p]) map[p] = []; map[p].push(s); return map }, {});
// This breaks down the 83 node names into an almost perfect 31 case hash, within 5bit, where each bucket has at most 5 elements
let type = node.type;
let c = type.charCodeAt(1); // We can use the second character as a second hash in some of these :)
let hash = type.length ^ c - 96;
return jumpTable[hash](node, fromFor, type, c);
}
export {
$ as printer,
};