1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060 |
- // 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
- /* istanbul ignore if */
- if (!String.prototype.repeat) {
- /* istanbul ignore next */
- throw new Error(
- 'String.prototype.repeat is undefined, see https://github.com/davidbonnet/astring#installation',
- )
- }
- /* istanbul ignore if */
- if (!String.prototype.endsWith) {
- /* istanbul ignore next */
- throw new Error(
- 'String.prototype.endsWith is undefined, see https://github.com/davidbonnet/astring#installation',
- )
- }
- const OPERATOR_PRECEDENCE = {
- '||': 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 precedence
- const NEEDS_PARENTHESES = 17
- const EXPRESSIONS_PRECEDENCE = {
- // Definitions
- ArrayExpression: 20,
- TaggedTemplateExpression: 20,
- ThisExpression: 20,
- Identifier: 20,
- Literal: 18,
- TemplateLiteral: 20,
- Super: 20,
- SequenceExpression: 20,
- // Operations
- MemberExpression: 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,
- BinaryExpression: 14,
- LogicalExpression: 13,
- ConditionalExpression: 4,
- AssignmentExpression: 3,
- AwaitExpression: 2,
- 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(node, parentNode, isRightHand) {
- const nodePrecedence = EXPRESSIONS_PRECEDENCE[node.type]
- if (nodePrecedence === NEEDS_PARENTHESES) {
- return true
- }
- const parentNodePrecedence = EXPRESSIONS_PRECEDENCE[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 (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 formatBinaryExpressionPart(state, node, parentNode, isRightHand) {
- /*
- Writes into `state` a left-hand or right-hand expression `node`
- from a binary expression applying the provided `operator`.
- The `isRightHand` parameter should be `true` if the `node` is a right-hand argument.
- */
- const { generator } = state
- if (expressionNeedsParenthesis(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')
- } 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
- const baseGenerator = {
- 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,
- EmptyStatement(node, state) {
- state.write(';')
- },
- ExpressionStatement(node, state) {
- const precedence = EXPRESSIONS_PRECEDENCE[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;' + state.lineEnd)
- },
- 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 ')
- this[node.superClass.type](node.superClass, state)
- state.write(' ')
- }
- this.ClassBody(node.body, state)
- },
- ImportDeclaration(node, state) {
- state.write('import ')
- const { specifiers } = node
- const { length } = specifiers
- // NOTE: 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(';')
- },
- ExportDefaultDeclaration(node, state) {
- state.write('export default ')
- this[node.declaration.type](node.declaration, state)
- if (
- EXPRESSIONS_PRECEDENCE[node.declaration.type] &&
- 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) {
- 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 ')
- if (node.argument) {
- this[node.argument.type](node.argument, state)
- }
- },
- TemplateLiteral(node, state) {
- const { quasis, expressions } = node
- state.write('`')
- const { length } = expressions
- for (let i = 0; i < length; i++) {
- const expression = expressions[i]
- this.TemplateElement(quasis[i], state)
- state.write('${')
- this[expression.type](expression, state)
- state.write('}')
- }
- state.write(quasis[quasis.length - 1].value.raw)
- state.write('`')
- },
- TemplateElement(node, state) {
- state.write(node.value.raw)
- },
- TaggedTemplateExpression(node, state) {
- this[node.tag.type](node.tag, state)
- 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)
- }
- },
- 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) {
- state.write(node.operator)
- if (node.operator.length > 1) {
- state.write(' ')
- }
- if (
- EXPRESSIONS_PRECEDENCE[node.argument.type] <
- EXPRESSIONS_PRECEDENCE.UnaryExpression
- ) {
- state.write('(')
- this[node.argument.type](node.argument, state)
- state.write(')')
- } else {
- this[node.argument.type](node.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('(')
- }
- formatBinaryExpressionPart(state, node.left, node, false)
- state.write(' ' + node.operator + ' ')
- formatBinaryExpressionPart(state, node.right, node, true)
- if (isIn) {
- state.write(')')
- }
- }),
- LogicalExpression: BinaryExpression,
- ConditionalExpression(node, state) {
- if (
- EXPRESSIONS_PRECEDENCE[node.test.type] >
- EXPRESSIONS_PRECEDENCE.ConditionalExpression
- ) {
- this[node.test.type](node.test, state)
- } else {
- state.write('(')
- this[node.test.type](node.test, state)
- state.write(')')
- }
- state.write(' ? ')
- this[node.consequent.type](node.consequent, state)
- state.write(' : ')
- this[node.alternate.type](node.alternate, state)
- },
- NewExpression(node, state) {
- state.write('new ')
- if (
- EXPRESSIONS_PRECEDENCE[node.callee.type] <
- EXPRESSIONS_PRECEDENCE.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) {
- if (
- EXPRESSIONS_PRECEDENCE[node.callee.type] <
- EXPRESSIONS_PRECEDENCE.CallExpression
- ) {
- state.write('(')
- this[node.callee.type](node.callee, state)
- state.write(')')
- } else {
- this[node.callee.type](node.callee, state)
- }
- formatSequence(state, node['arguments'])
- },
- MemberExpression(node, state) {
- if (
- EXPRESSIONS_PRECEDENCE[node.object.type] <
- EXPRESSIONS_PRECEDENCE.MemberExpression
- ) {
- state.write('(')
- this[node.object.type](node.object, state)
- state.write(')')
- } else {
- this[node.object.type](node.object, state)
- }
- if (node.computed) {
- state.write('[')
- this[node.property.type](node.property, state)
- 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)
- },
- Literal(node, state) {
- if (node.raw != null) {
- state.write(node.raw, node)
- } else if (node.regex != null) {
- this.RegExpLiteral(node, state)
- } else {
- state.write(stringify(node.value), node)
- }
- },
- RegExpLiteral(node, state) {
- const { regex } = node
- state.write(`/${regex.pattern}/${regex.flags}`, node)
- },
- }
- const EMPTY_OBJECT = {}
- 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 : baseGenerator
- // 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,
- 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 && node.loc != null) {
- const { mapping } = this
- mapping.original = node.loc.start
- mapping.name = node.name
- this.sourceMap.addMapping(mapping)
- }
- if (code.length > 0) {
- if (this.lineEndSize > 0) {
- if (code.endsWith(this.lineEnd)) {
- this.line += this.lineEndSize
- this.column = 0
- } else if (code[code.length - 1] === '\n') {
- // Case of inline comment
- this.line++
- this.column = 0
- } else {
- this.column += code.length
- }
- } else {
- if (code[code.length - 1] === '\n') {
- // Case of inline comment
- this.line++
- this.column = 0
- } else {
- this.column += code.length
- }
- }
- }
- }
- toString() {
- return this.output
- }
- }
- 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 `baseGenerator`)
- */
- const state = new State(options)
- // Travel through the AST node and generate the code
- state.generator[node.type](node, state)
- return state.output
- }
|