Skip to main content
Module

x/astring/src/astring.js

🌳 Tiny and fast JavaScript code generator from an ESTree-compliant AST.
Latest
File
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189
// Astring is a tiny and fast JavaScript code generator from an ESTree-compliant AST.//// Astring was written by David Bonnet and released under an MIT license.//// The Git repository for Astring is available at:// https://github.com/davidbonnet/astring.git//// Please use the GitHub bug tracker to report issues:// https://github.com/davidbonnet/astring/issues
const { stringify } = JSON
/* c8 ignore if */if (!String.prototype.repeat) { /* c8 ignore next */ throw new Error( 'String.prototype.repeat is undefined, see https://github.com/davidbonnet/astring#installation', )}
/* c8 ignore if */if (!String.prototype.endsWith) { /* c8 ignore next */ throw new Error( 'String.prototype.endsWith is undefined, see https://github.com/davidbonnet/astring#installation', )}
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,}
// Enables parenthesis regardless of precedenceexport const NEEDS_PARENTHESES = 17
export const EXPRESSIONS_PRECEDENCE = { // Definitions ArrayExpression: 20, TaggedTemplateExpression: 20, ThisExpression: 20, Identifier: 20, PrivateIdentifier: 20, Literal: 18, TemplateLiteral: 20, Super: 20, SequenceExpression: 20, // Operations MemberExpression: 19, ChainExpression: 19, CallExpression: 19, NewExpression: 19, // Other definitions ArrowFunctionExpression: NEEDS_PARENTHESES, ClassExpression: NEEDS_PARENTHESES, FunctionExpression: NEEDS_PARENTHESES, ObjectExpression: NEEDS_PARENTHESES, // Other operations UpdateExpression: 16, UnaryExpression: 15, AwaitExpression: 15, BinaryExpression: 14, LogicalExpression: 13, ConditionalExpression: 4, AssignmentExpression: 3, YieldExpression: 2, RestElement: 1,}
function formatSequence(state, nodes) { /* Writes into `state` a sequence of `nodes`. */ const { generator } = state state.write('(') if (nodes != null && nodes.length > 0) { generator[nodes[0].type](nodes[0], state) const { length } = nodes for (let i = 1; i < length; i++) { const param = nodes[i] state.write(', ') generator[param.type](param, state) } } state.write(')')}
function expressionNeedsParenthesis(state, node, parentNode, isRightHand) { const nodePrecedence = state.expressionsPrecedence[node.type] if (nodePrecedence === NEEDS_PARENTHESES) { return true } const parentNodePrecedence = state.expressionsPrecedence[parentNode.type] if (nodePrecedence !== parentNodePrecedence) { // Different node types return ( (!isRightHand && nodePrecedence === 15 && parentNodePrecedence === 14 && parentNode.operator === '**') || nodePrecedence < parentNodePrecedence ) } if (nodePrecedence !== 13 && nodePrecedence !== 14) { // Not a `LogicalExpression` or `BinaryExpression` return false } if (node.operator === '**' && parentNode.operator === '**') { // Exponentiation operator has right-to-left associativity return !isRightHand } if ( nodePrecedence === 13 && parentNodePrecedence === 13 && (node.operator === '??' || parentNode.operator === '??') ) { // Nullish coalescing and boolean operators cannot be combined return true } if (isRightHand) { // Parenthesis are used if both operators have the same precedence return ( OPERATOR_PRECEDENCE[node.operator] <= OPERATOR_PRECEDENCE[parentNode.operator] ) } return ( OPERATOR_PRECEDENCE[node.operator] < OPERATOR_PRECEDENCE[parentNode.operator] )}
function formatExpression(state, node, parentNode, isRightHand) { /* Writes into `state` the provided `node`, adding parenthesis around if the provided `parentNode` needs it. If `node` is a right-hand argument, the provided `isRightHand` parameter should be `true`. */ const { generator } = state if (expressionNeedsParenthesis(state, node, parentNode, isRightHand)) { state.write('(') generator[node.type](node, state) state.write(')') } else { generator[node.type](node, state) }}
function reindent(state, text, indent, lineEnd) { /* Writes into `state` the `text` string reindented with the provided `indent`. */ const lines = text.split('\n') const end = lines.length - 1 state.write(lines[0].trim()) if (end > 0) { state.write(lineEnd) for (let i = 1; i < end; i++) { state.write(indent + lines[i].trim() + lineEnd) } state.write(indent + lines[end].trim()) }}
function formatComments(state, comments, indent, lineEnd) { /* Writes into `state` the provided list of `comments`, with the given `indent` and `lineEnd` strings. Line comments will end with `"\n"` regardless of the value of `lineEnd`. Expects to start on a new unindented line. */ const { length } = comments for (let i = 0; i < length; i++) { const comment = comments[i] state.write(indent) if (comment.type[0] === 'L') { // Line comment state.write('// ' + comment.value.trim() + '\n', comment) } else { // Block comment state.write('/*') reindent(state, comment.value, indent, lineEnd) state.write('*/' + lineEnd) } }}
function hasCallExpression(node) { /* Returns `true` if the provided `node` contains a call expression and `false` otherwise. */ let currentNode = node while (currentNode != null) { const { type } = currentNode if (type[0] === 'C' && type[1] === 'a') { // Is CallExpression return true } else if (type[0] === 'M' && type[1] === 'e' && type[2] === 'm') { // Is MemberExpression currentNode = currentNode.object } else { return false } }}
function formatVariableDeclaration(state, node) { /* Writes into `state` a variable declaration. */ const { generator } = state const { declarations } = node state.write(node.kind + ' ') const { length } = declarations if (length > 0) { generator.VariableDeclarator(declarations[0], state) for (let i = 1; i < length; i++) { state.write(', ') generator.VariableDeclarator(declarations[i], state) } }}
let ForInStatement, FunctionDeclaration, RestElement, BinaryExpression, ArrayExpression, BlockStatement
export const GENERATOR = { /* Default generator. */ Program(node, state) { const indent = state.indent.repeat(state.indentLevel) const { lineEnd, writeComments } = state if (writeComments && node.comments != null) { formatComments(state, node.comments, indent, lineEnd) } const statements = node.body const { length } = statements for (let i = 0; i < length; i++) { const statement = statements[i] if (writeComments && statement.comments != null) { formatComments(state, statement.comments, indent, lineEnd) } state.write(indent) this[statement.type](statement, state) state.write(lineEnd) } if (writeComments && node.trailingComments != null) { formatComments(state, node.trailingComments, indent, lineEnd) } }, BlockStatement: (BlockStatement = function (node, state) { const indent = state.indent.repeat(state.indentLevel++) const { lineEnd, writeComments } = state const statementIndent = indent + state.indent state.write('{') const statements = node.body if (statements != null && statements.length > 0) { state.write(lineEnd) if (writeComments && node.comments != null) { formatComments(state, node.comments, statementIndent, lineEnd) } const { length } = statements for (let i = 0; i < length; i++) { const statement = statements[i] if (writeComments && statement.comments != null) { formatComments(state, statement.comments, statementIndent, lineEnd) } state.write(statementIndent) this[statement.type](statement, state) state.write(lineEnd) } state.write(indent) } else { if (writeComments && node.comments != null) { state.write(lineEnd) formatComments(state, node.comments, statementIndent, lineEnd) state.write(indent) } } if (writeComments && node.trailingComments != null) { formatComments(state, node.trailingComments, statementIndent, lineEnd) } state.write('}') state.indentLevel-- }), ClassBody: BlockStatement, StaticBlock(node, state) { state.write('static ') this.BlockStatement(node, state) }, EmptyStatement(node, state) { state.write(';') }, ExpressionStatement(node, state) { const precedence = state.expressionsPrecedence[node.expression.type] if ( precedence === NEEDS_PARENTHESES || (precedence === 3 && node.expression.left.type[0] === 'O') ) { // Should always have parentheses or is an AssignmentExpression to an ObjectPattern state.write('(') this[node.expression.type](node.expression, state) state.write(')') } else { this[node.expression.type](node.expression, state) } state.write(';') }, IfStatement(node, state) { state.write('if (') this[node.test.type](node.test, state) state.write(') ') this[node.consequent.type](node.consequent, state) if (node.alternate != null) { state.write(' else ') this[node.alternate.type](node.alternate, state) } }, LabeledStatement(node, state) { this[node.label.type](node.label, state) state.write(': ') this[node.body.type](node.body, state) }, BreakStatement(node, state) { state.write('break') if (node.label != null) { state.write(' ') this[node.label.type](node.label, state) } state.write(';') }, ContinueStatement(node, state) { state.write('continue') if (node.label != null) { state.write(' ') this[node.label.type](node.label, state) } state.write(';') }, WithStatement(node, state) { state.write('with (') this[node.object.type](node.object, state) state.write(') ') this[node.body.type](node.body, state) }, SwitchStatement(node, state) { const indent = state.indent.repeat(state.indentLevel++) const { lineEnd, writeComments } = state state.indentLevel++ const caseIndent = indent + state.indent const statementIndent = caseIndent + state.indent state.write('switch (') this[node.discriminant.type](node.discriminant, state) state.write(') {' + lineEnd) const { cases: occurences } = node const { length: occurencesCount } = occurences for (let i = 0; i < occurencesCount; i++) { const occurence = occurences[i] if (writeComments && occurence.comments != null) { formatComments(state, occurence.comments, caseIndent, lineEnd) } if (occurence.test) { state.write(caseIndent + 'case ') this[occurence.test.type](occurence.test, state) state.write(':' + lineEnd) } else { state.write(caseIndent + 'default:' + lineEnd) } const { consequent } = occurence const { length: consequentCount } = consequent for (let i = 0; i < consequentCount; i++) { const statement = consequent[i] if (writeComments && statement.comments != null) { formatComments(state, statement.comments, statementIndent, lineEnd) } state.write(statementIndent) this[statement.type](statement, state) state.write(lineEnd) } } state.indentLevel -= 2 state.write(indent + '}') }, ReturnStatement(node, state) { state.write('return') if (node.argument) { state.write(' ') this[node.argument.type](node.argument, state) } state.write(';') }, ThrowStatement(node, state) { state.write('throw ') this[node.argument.type](node.argument, state) state.write(';') }, TryStatement(node, state) { state.write('try ') this[node.block.type](node.block, state) if (node.handler) { const { handler } = node if (handler.param == null) { state.write(' catch ') } else { state.write(' catch (') this[handler.param.type](handler.param, state) state.write(') ') } this[handler.body.type](handler.body, state) } if (node.finalizer) { state.write(' finally ') this[node.finalizer.type](node.finalizer, state) } }, WhileStatement(node, state) { state.write('while (') this[node.test.type](node.test, state) state.write(') ') this[node.body.type](node.body, state) }, DoWhileStatement(node, state) { state.write('do ') this[node.body.type](node.body, state) state.write(' while (') this[node.test.type](node.test, state) state.write(');') }, ForStatement(node, state) { state.write('for (') if (node.init != null) { const { init } = node if (init.type[0] === 'V') { formatVariableDeclaration(state, init) } else { this[init.type](init, state) } } state.write('; ') if (node.test) { this[node.test.type](node.test, state) } state.write('; ') if (node.update) { this[node.update.type](node.update, state) } state.write(') ') this[node.body.type](node.body, state) }, ForInStatement: (ForInStatement = function (node, state) { state.write(`for ${node.await ? 'await ' : ''}(`) const { left } = node if (left.type[0] === 'V') { formatVariableDeclaration(state, left) } else { this[left.type](left, state) } // Identifying whether node.type is `ForInStatement` or `ForOfStatement` state.write(node.type[3] === 'I' ? ' in ' : ' of ') this[node.right.type](node.right, state) state.write(') ') this[node.body.type](node.body, state) }), ForOfStatement: ForInStatement, DebuggerStatement(node, state) { state.write('debugger;', node) }, FunctionDeclaration: (FunctionDeclaration = function (node, state) { state.write( (node.async ? 'async ' : '') + (node.generator ? 'function* ' : 'function ') + (node.id ? node.id.name : ''), node, ) formatSequence(state, node.params) state.write(' ') this[node.body.type](node.body, state) }), FunctionExpression: FunctionDeclaration, VariableDeclaration(node, state) { formatVariableDeclaration(state, node) state.write(';') }, VariableDeclarator(node, state) { this[node.id.type](node.id, state) if (node.init != null) { state.write(' = ') this[node.init.type](node.init, state) } }, ClassDeclaration(node, state) { state.write('class ' + (node.id ? `${node.id.name} ` : ''), node) if (node.superClass) { state.write('extends ') const { superClass } = node const { type } = superClass const precedence = state.expressionsPrecedence[type] if ( (type[0] !== 'C' || type[1] !== 'l' || type[5] !== 'E') && (precedence === NEEDS_PARENTHESES || precedence < state.expressionsPrecedence.ClassExpression) ) { // Not a ClassExpression that needs parentheses state.write('(') this[node.superClass.type](superClass, state) state.write(')') } else { this[superClass.type](superClass, state) } state.write(' ') } this.ClassBody(node.body, state) }, ImportDeclaration(node, state) { state.write('import ') const { specifiers } = node const { length } = specifiers // TODO: Once babili is fixed, put this after condition // https://github.com/babel/babili/issues/430 let i = 0 if (length > 0) { for (; i < length; ) { if (i > 0) { state.write(', ') } const specifier = specifiers[i] const type = specifier.type[6] if (type === 'D') { // ImportDefaultSpecifier state.write(specifier.local.name, specifier) i++ } else if (type === 'N') { // ImportNamespaceSpecifier state.write('* as ' + specifier.local.name, specifier) i++ } else { // ImportSpecifier break } } if (i < length) { state.write('{') for (;;) { const specifier = specifiers[i] const { name } = specifier.imported state.write(name, specifier) if (name !== specifier.local.name) { state.write(' as ' + specifier.local.name) } if (++i < length) { state.write(', ') } else { break } } state.write('}') } state.write(' from ') } this.Literal(node.source, state) state.write(';') }, ImportExpression(node, state) { state.write('import(') this[node.source.type](node.source, state) state.write(')') }, ExportDefaultDeclaration(node, state) { state.write('export default ') this[node.declaration.type](node.declaration, state) if ( state.expressionsPrecedence[node.declaration.type] != null && node.declaration.type[0] !== 'F' ) { // All expression nodes except `FunctionExpression` state.write(';') } }, ExportNamedDeclaration(node, state) { state.write('export ') if (node.declaration) { this[node.declaration.type](node.declaration, state) } else { state.write('{') const { specifiers } = node, { length } = specifiers if (length > 0) { for (let i = 0; ; ) { const specifier = specifiers[i] const { name } = specifier.local state.write(name, specifier) if (name !== specifier.exported.name) { state.write(' as ' + specifier.exported.name) } if (++i < length) { state.write(', ') } else { break } } } state.write('}') if (node.source) { state.write(' from ') this.Literal(node.source, state) } state.write(';') } }, ExportAllDeclaration(node, state) { if (node.exported != null) { state.write('export * as ' + node.exported.name + ' from ') } else { state.write('export * from ') } this.Literal(node.source, state) state.write(';') }, MethodDefinition(node, state) { if (node.static) { state.write('static ') } const kind = node.kind[0] if (kind === 'g' || kind === 's') { // Getter or setter state.write(node.kind + ' ') } if (node.value.async) { state.write('async ') } if (node.value.generator) { state.write('*') } if (node.computed) { state.write('[') this[node.key.type](node.key, state) state.write(']') } else { this[node.key.type](node.key, state) } formatSequence(state, node.value.params) state.write(' ') this[node.value.body.type](node.value.body, state) }, ClassExpression(node, state) { this.ClassDeclaration(node, state) }, ArrowFunctionExpression(node, state) { state.write(node.async ? 'async ' : '', node) const { params } = node if (params != null) { // Omit parenthesis if only one named parameter if (params.length === 1 && params[0].type[0] === 'I') { // If params[0].type[0] starts with 'I', it can't be `ImportDeclaration` nor `IfStatement` and thus is `Identifier` state.write(params[0].name, params[0]) } else { formatSequence(state, node.params) } } state.write(' => ') if (node.body.type[0] === 'O') { // Body is an object expression state.write('(') this.ObjectExpression(node.body, state) state.write(')') } else { this[node.body.type](node.body, state) } }, ThisExpression(node, state) { state.write('this', node) }, Super(node, state) { state.write('super', node) }, RestElement: (RestElement = function (node, state) { state.write('...') this[node.argument.type](node.argument, state) }), SpreadElement: RestElement, YieldExpression(node, state) { state.write(node.delegate ? 'yield*' : 'yield') if (node.argument) { state.write(' ') this[node.argument.type](node.argument, state) } }, AwaitExpression(node, state) { state.write('await ', node) formatExpression(state, node.argument, node) }, TemplateLiteral(node, state) { const { quasis, expressions } = node state.write('`') const { length } = expressions for (let i = 0; i < length; i++) { const expression = expressions[i] const quasi = quasis[i] state.write(quasi.value.raw, quasi) state.write('${') this[expression.type](expression, state) state.write('}') } const quasi = quasis[quasis.length - 1] state.write(quasi.value.raw, quasi) state.write('`') }, TemplateElement(node, state) { state.write(node.value.raw, node) }, TaggedTemplateExpression(node, state) { formatExpression(state, node.tag, node) this[node.quasi.type](node.quasi, state) }, ArrayExpression: (ArrayExpression = function (node, state) { state.write('[') if (node.elements.length > 0) { const { elements } = node, { length } = elements for (let i = 0; ; ) { const element = elements[i] if (element != null) { this[element.type](element, state) } if (++i < length) { state.write(', ') } else { if (element == null) { state.write(', ') } break } } } state.write(']') }), ArrayPattern: ArrayExpression, ObjectExpression(node, state) { const indent = state.indent.repeat(state.indentLevel++) const { lineEnd, writeComments } = state const propertyIndent = indent + state.indent state.write('{') if (node.properties.length > 0) { state.write(lineEnd) if (writeComments && node.comments != null) { formatComments(state, node.comments, propertyIndent, lineEnd) } const comma = ',' + lineEnd const { properties } = node, { length } = properties for (let i = 0; ; ) { const property = properties[i] if (writeComments && property.comments != null) { formatComments(state, property.comments, propertyIndent, lineEnd) } state.write(propertyIndent) this[property.type](property, state) if (++i < length) { state.write(comma) } else { break } } state.write(lineEnd) if (writeComments && node.trailingComments != null) { formatComments(state, node.trailingComments, propertyIndent, lineEnd) } state.write(indent + '}') } else if (writeComments) { if (node.comments != null) { state.write(lineEnd) formatComments(state, node.comments, propertyIndent, lineEnd) if (node.trailingComments != null) { formatComments(state, node.trailingComments, propertyIndent, lineEnd) } state.write(indent + '}') } else if (node.trailingComments != null) { state.write(lineEnd) formatComments(state, node.trailingComments, propertyIndent, lineEnd) state.write(indent + '}') } else { state.write('}') } } else { state.write('}') } state.indentLevel-- }, Property(node, state) { if (node.method || node.kind[0] !== 'i') { // Either a method or of kind `set` or `get` (not `init`) this.MethodDefinition(node, state) } else { if (!node.shorthand) { if (node.computed) { state.write('[') this[node.key.type](node.key, state) state.write(']') } else { this[node.key.type](node.key, state) } state.write(': ') } this[node.value.type](node.value, state) } }, PropertyDefinition(node, state) { if (node.static) { state.write('static ') } if (node.computed) { state.write('[') } this[node.key.type](node.key, state) if (node.computed) { state.write(']') } if (node.value == null) { if (node.key.type[0] !== 'F') { state.write(';') } return } state.write(' = ') this[node.value.type](node.value, state) state.write(';') }, ObjectPattern(node, state) { state.write('{') if (node.properties.length > 0) { const { properties } = node, { length } = properties for (let i = 0; ; ) { this[properties[i].type](properties[i], state) if (++i < length) { state.write(', ') } else { break } } } state.write('}') }, SequenceExpression(node, state) { formatSequence(state, node.expressions) }, UnaryExpression(node, state) { if (node.prefix) { const { operator, argument, argument: { type }, } = node state.write(operator) const needsParentheses = expressionNeedsParenthesis(state, argument, node) if ( !needsParentheses && (operator.length > 1 || (type[0] === 'U' && (type[1] === 'n' || type[1] === 'p') && argument.prefix && argument.operator[0] === operator && (operator === '+' || operator === '-'))) ) { // Large operator or argument is UnaryExpression or UpdateExpression node state.write(' ') } if (needsParentheses) { state.write(operator.length > 1 ? ' (' : '(') this[type](argument, state) state.write(')') } else { this[type](argument, state) } } else { // FIXME: This case never occurs this[node.argument.type](node.argument, state) state.write(node.operator) } }, UpdateExpression(node, state) { // Always applied to identifiers or members, no parenthesis check needed if (node.prefix) { state.write(node.operator) this[node.argument.type](node.argument, state) } else { this[node.argument.type](node.argument, state) state.write(node.operator) } }, AssignmentExpression(node, state) { this[node.left.type](node.left, state) state.write(' ' + node.operator + ' ') this[node.right.type](node.right, state) }, AssignmentPattern(node, state) { this[node.left.type](node.left, state) state.write(' = ') this[node.right.type](node.right, state) }, BinaryExpression: (BinaryExpression = function (node, state) { const isIn = node.operator === 'in' if (isIn) { // Avoids confusion in `for` loops initializers state.write('(') } formatExpression(state, node.left, node, false) state.write(' ' + node.operator + ' ') formatExpression(state, node.right, node, true) if (isIn) { state.write(')') } }), LogicalExpression: BinaryExpression, ConditionalExpression(node, state) { const { test } = node const precedence = state.expressionsPrecedence[test.type] if ( precedence === NEEDS_PARENTHESES || precedence <= state.expressionsPrecedence.ConditionalExpression ) { state.write('(') this[test.type](test, state) state.write(')') } else { this[test.type](test, state) } state.write(' ? ') this[node.consequent.type](node.consequent, state) state.write(' : ') this[node.alternate.type](node.alternate, state) }, NewExpression(node, state) { state.write('new ') const precedence = state.expressionsPrecedence[node.callee.type] if ( precedence === NEEDS_PARENTHESES || precedence < state.expressionsPrecedence.CallExpression || hasCallExpression(node.callee) ) { state.write('(') this[node.callee.type](node.callee, state) state.write(')') } else { this[node.callee.type](node.callee, state) } formatSequence(state, node['arguments']) }, CallExpression(node, state) { const precedence = state.expressionsPrecedence[node.callee.type] if ( precedence === NEEDS_PARENTHESES || precedence < state.expressionsPrecedence.CallExpression ) { state.write('(') this[node.callee.type](node.callee, state) state.write(')') } else { this[node.callee.type](node.callee, state) } if (node.optional) { state.write('?.') } formatSequence(state, node['arguments']) }, ChainExpression(node, state) { this[node.expression.type](node.expression, state) }, MemberExpression(node, state) { const precedence = state.expressionsPrecedence[node.object.type] if ( precedence === NEEDS_PARENTHESES || precedence < state.expressionsPrecedence.MemberExpression ) { state.write('(') this[node.object.type](node.object, state) state.write(')') } else { this[node.object.type](node.object, state) } if (node.computed) { if (node.optional) { state.write('?.') } state.write('[') this[node.property.type](node.property, state) state.write(']') } else { if (node.optional) { state.write('?.') } else { state.write('.') } this[node.property.type](node.property, state) } }, MetaProperty(node, state) { state.write(node.meta.name + '.' + node.property.name, node) }, Identifier(node, state) { state.write(node.name, node) }, PrivateIdentifier(node, state) { state.write(`#${node.name}`, node) }, Literal(node, state) { if (node.raw != null) { // Non-standard property state.write(node.raw, node) } else if (node.regex != null) { this.RegExpLiteral(node, state) } else if (node.bigint != null) { state.write(node.bigint + 'n', node) } else { state.write(stringify(node.value), node) } }, RegExpLiteral(node, state) { const { regex } = node state.write(`/${regex.pattern}/${regex.flags}`, node) },}
const EMPTY_OBJECT = {}
/*DEPRECATED: Alternate export of `GENERATOR`.*/export const baseGenerator = GENERATOR
class State { constructor(options) { const setup = options == null ? EMPTY_OBJECT : options this.output = '' // Functional options if (setup.output != null) { this.output = setup.output this.write = this.writeToStream } else { this.output = '' } this.generator = setup.generator != null ? setup.generator : GENERATOR this.expressionsPrecedence = setup.expressionsPrecedence != null ? setup.expressionsPrecedence : EXPRESSIONS_PRECEDENCE // Formating setup this.indent = setup.indent != null ? setup.indent : ' ' this.lineEnd = setup.lineEnd != null ? setup.lineEnd : '\n' this.indentLevel = setup.startingIndentLevel != null ? setup.startingIndentLevel : 0 this.writeComments = setup.comments ? setup.comments : false // Source map if (setup.sourceMap != null) { this.write = setup.output == null ? this.writeAndMap : this.writeToStreamAndMap this.sourceMap = setup.sourceMap this.line = 1 this.column = 0 this.lineEndSize = this.lineEnd.split('\n').length - 1 this.mapping = { original: null, // Uses the entire state to avoid generating ephemeral objects generated: this, name: undefined, source: setup.sourceMap.file || setup.sourceMap._file, } } }
write(code) { this.output += code }
writeToStream(code) { this.output.write(code) }
writeAndMap(code, node) { this.output += code this.map(code, node) }
writeToStreamAndMap(code, node) { this.output.write(code) this.map(code, node) }
map(code, node) { if (node != null) { const { type } = node if (type[0] === 'L' && type[2] === 'n') { // LineComment this.column = 0 this.line++ return } if (node.loc != null) { const { mapping } = this mapping.original = node.loc.start mapping.name = node.name this.sourceMap.addMapping(mapping) } if ( (type[0] === 'T' && type[8] === 'E') || (type[0] === 'L' && type[1] === 'i' && typeof node.value === 'string') ) { // TemplateElement or Literal string node const { length } = code let { column, line } = this for (let i = 0; i < length; i++) { if (code[i] === '\n') { column = 0 line++ } else { column++ } } this.column = column this.line = line return } } const { length } = code const { lineEnd } = this if (length > 0) { if ( this.lineEndSize > 0 && (lineEnd.length === 1 ? code[length - 1] === lineEnd : code.endsWith(lineEnd)) ) { this.line += this.lineEndSize this.column = 0 } else { this.column += length } } }
toString() { return this.output }}
export function generate(node, options) { /* Returns a string representing the rendered code of the provided AST `node`. The `options` are:
- `indent`: string to use for indentation (defaults to `␣␣`) - `lineEnd`: string to use for line endings (defaults to `\n`) - `startingIndentLevel`: indent level to start from (defaults to `0`) - `comments`: generate comments if `true` (defaults to `false`) - `output`: output stream to write the rendered code to (defaults to `null`) - `generator`: custom code generator (defaults to `GENERATOR`) - `expressionsPrecedence`: custom map of node types and their precedence level (defaults to `EXPRESSIONS_PRECEDENCE`) */ const state = new State(options) // Travel through the AST node and generate the code state.generator[node.type](node, state) return state.output}