fuse-box
Version:
Fuse-Box a bundler that does it right
1,042 lines (1,040 loc) • 36.1 kB
JavaScript
"use strict";
// 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
Object.defineProperty(exports, "__esModule", { value: true });
exports.generate = exports.baseGenerator = void 0;
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 = {
'!=': 8,
'!==': 8,
'%': 12,
'&': 7,
'&&': 4,
'*': 12,
'**': 13,
'+': 11,
'-': 11,
'/': 12,
'<': 9,
'<<': 10,
'<=': 9,
'==': 8,
'===': 8,
'>': 9,
'>=': 9,
'>>': 10,
'>>>': 10,
'^': 6,
in: 9,
instanceof: 9,
'|': 5,
'||': 3,
};
// Enables parenthesis regardless of precedence
const NEEDS_PARENTHESES = 17;
const EXPRESSIONS_PRECEDENCE = {
// Definitions
ArrayExpression: 20,
// Other definitions
ArrowFunctionExpression: NEEDS_PARENTHESES,
AssignmentExpression: 3,
AwaitExpression: 2,
BinaryExpression: 14,
CallExpression: 19,
ClassExpression: NEEDS_PARENTHESES,
ConditionalExpression: 4,
FunctionExpression: NEEDS_PARENTHESES,
Identifier: 20,
Literal: 18,
LogicalExpression: 13,
// Operations
MemberExpression: 19,
NewExpression: 19,
ObjectExpression: NEEDS_PARENTHESES,
RestElement: 1,
SequenceExpression: 20,
Super: 20,
TaggedTemplateExpression: 20,
TemplateLiteral: 20,
ThisExpression: 20,
UnaryExpression: 15,
// Other operations
UpdateExpression: 16,
YieldExpression: 2,
};
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;
exports.baseGenerator = {
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,
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);
}
},
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);
},
AwaitExpression(node, state) {
state.write('await ');
if (node.argument) {
this[node.argument.type](node.argument, 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(')');
}
},
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--;
},
BreakStatement(node, state) {
state.write('break');
if (node.label != null) {
state.write(' ');
this[node.label.type](node.label, state);
}
state.write(';');
},
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']);
},
ClassBody: BlockStatement,
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);
},
ClassExpression(node, state) {
this.ClassDeclaration(node, state);
},
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);
},
ContinueStatement(node, state) {
state.write('continue');
if (node.label != null) {
state.write(' ');
this[node.label.type](node.label, state);
}
state.write(';');
},
DebuggerStatement(node, state) {
state.write('debugger;' + state.lineEnd, node);
},
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(');');
},
EmptyStatement(node, state) {
state.write(';');
},
ExportAllDeclaration(node, state) {
state.write('export * 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(';');
}
},
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(';');
},
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,
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);
},
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,
Identifier(node, state) {
state.write(node.name, node);
},
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);
}
},
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(';');
},
LabeledStatement(node, state) {
this[node.label.type](node.label, state);
state.write(': ');
this[node.body.type](node.body, state);
},
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);
}
},
LogicalExpression: BinaryExpression,
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);
},
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);
},
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']);
},
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--;
},
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('}');
},
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);
}
},
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);
}
},
RegExpLiteral(node, state) {
const { regex } = node;
state.write(`/${regex.pattern}/${regex.flags}`, node);
},
RestElement: RestElement = function (node, state) {
state.write('...');
this[node.argument.type](node.argument, state);
},
ReturnStatement(node, state) {
state.write('return');
if (node.argument) {
state.write(' ');
this[node.argument.type](node.argument, state);
}
state.write(';');
},
SequenceExpression(node, state) {
formatSequence(state, node.expressions);
},
SpreadElement: RestElement,
Super(node, state) {
state.write('super', node);
},
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 + '}');
},
TaggedTemplateExpression(node, state) {
this[node.tag.type](node.tag, state);
this[node.quasi.type](node.quasi, state);
},
TemplateLiteral(node, state) {
const { expressions, quasis } = node;
state.write('`');
const { length } = expressions;
for (let i = 0; i < length; i++) {
const expression = expressions[i];
const raw = quasis[i].value.raw;
const lines = raw.split('\n').length;
state.write(raw);
state.line = state.line + lines - 1;
state.write('${');
this[expression.type](expression, state);
state.write('}');
}
const cooked = quasis[quasis.length - 1].value.raw;
const cookedLength = cooked.split('\n').length;
const shift = cookedLength - 1;
if (shift > 0) {
state.line = state.line + shift;
}
state.write(cooked);
state.write('`');
},
ThisExpression(node, state) {
state.write('this', node);
},
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);
}
},
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);
}
},
VariableDeclaration(node, state) {
formatVariableDeclaration(state, node);
state.write(';');
},
VariableDeclarator(node, state) {
if (!node.id)
return;
this[node.id.type](node.id, state);
if (node.init != null) {
state.write(' = ');
this[node.init.type](node.init, state);
}
},
WhileStatement(node, state) {
state.write('while (');
this[node.test.type](node.test, state);
state.write(') ');
this[node.body.type](node.body, state);
},
WithStatement(node, state) {
state.write('with (');
this[node.object.type](node.object, state);
state.write(') ');
this[node.body.type](node.body, state);
},
YieldExpression(node, state) {
state.write(node.delegate ? 'yield*' : 'yield');
if (node.argument) {
state.write(' ');
this[node.argument.type](node.argument, state);
}
},
};
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 : exports.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) {
const self = this;
self.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 = {
generated: this,
name: undefined,
original: null,
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 && 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
console.log('here...');
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
// console.log(node, node.type);
state.generator[node.type](node, state);
return state.output;
}
exports.generate = generate;