Son CV dans un terminal web en Javascript!
https://terminal-cv.gregandev.fr
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1151 lines
33 KiB
1151 lines
33 KiB
2 years ago
|
// 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 = {
|
||
|
'||': 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
|
||
|
export const NEEDS_PARENTHESES = 17
|
||
|
|
||
|
export const EXPRESSIONS_PRECEDENCE = {
|
||
|
// Definitions
|
||
|
ArrayExpression: 20,
|
||
|
TaggedTemplateExpression: 20,
|
||
|
ThisExpression: 20,
|
||
|
Identifier: 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 (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,
|
||
|
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) {
|
||
|
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) {
|
||
|
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)
|
||
|
},
|
||
|
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
|
||
|
}
|