astring.js 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060
  1. // Astring is a tiny and fast JavaScript code generator from an ESTree-compliant AST.
  2. //
  3. // Astring was written by David Bonnet and released under an MIT license.
  4. //
  5. // The Git repository for Astring is available at:
  6. // https://github.com/davidbonnet/astring.git
  7. //
  8. // Please use the GitHub bug tracker to report issues:
  9. // https://github.com/davidbonnet/astring/issues
  10. const { stringify } = JSON
  11. /* istanbul ignore if */
  12. if (!String.prototype.repeat) {
  13. /* istanbul ignore next */
  14. throw new Error(
  15. 'String.prototype.repeat is undefined, see https://github.com/davidbonnet/astring#installation',
  16. )
  17. }
  18. /* istanbul ignore if */
  19. if (!String.prototype.endsWith) {
  20. /* istanbul ignore next */
  21. throw new Error(
  22. 'String.prototype.endsWith is undefined, see https://github.com/davidbonnet/astring#installation',
  23. )
  24. }
  25. const OPERATOR_PRECEDENCE = {
  26. '||': 3,
  27. '&&': 4,
  28. '|': 5,
  29. '^': 6,
  30. '&': 7,
  31. '==': 8,
  32. '!=': 8,
  33. '===': 8,
  34. '!==': 8,
  35. '<': 9,
  36. '>': 9,
  37. '<=': 9,
  38. '>=': 9,
  39. in: 9,
  40. instanceof: 9,
  41. '<<': 10,
  42. '>>': 10,
  43. '>>>': 10,
  44. '+': 11,
  45. '-': 11,
  46. '*': 12,
  47. '%': 12,
  48. '/': 12,
  49. '**': 13,
  50. }
  51. // Enables parenthesis regardless of precedence
  52. const NEEDS_PARENTHESES = 17
  53. const EXPRESSIONS_PRECEDENCE = {
  54. // Definitions
  55. ArrayExpression: 20,
  56. TaggedTemplateExpression: 20,
  57. ThisExpression: 20,
  58. Identifier: 20,
  59. Literal: 18,
  60. TemplateLiteral: 20,
  61. Super: 20,
  62. SequenceExpression: 20,
  63. // Operations
  64. MemberExpression: 19,
  65. CallExpression: 19,
  66. NewExpression: 19,
  67. // Other definitions
  68. ArrowFunctionExpression: NEEDS_PARENTHESES,
  69. ClassExpression: NEEDS_PARENTHESES,
  70. FunctionExpression: NEEDS_PARENTHESES,
  71. ObjectExpression: NEEDS_PARENTHESES,
  72. // Other operations
  73. UpdateExpression: 16,
  74. UnaryExpression: 15,
  75. BinaryExpression: 14,
  76. LogicalExpression: 13,
  77. ConditionalExpression: 4,
  78. AssignmentExpression: 3,
  79. AwaitExpression: 2,
  80. YieldExpression: 2,
  81. RestElement: 1,
  82. }
  83. function formatSequence(state, nodes) {
  84. /*
  85. Writes into `state` a sequence of `nodes`.
  86. */
  87. const { generator } = state
  88. state.write('(')
  89. if (nodes != null && nodes.length > 0) {
  90. generator[nodes[0].type](nodes[0], state)
  91. const { length } = nodes
  92. for (let i = 1; i < length; i++) {
  93. const param = nodes[i]
  94. state.write(', ')
  95. generator[param.type](param, state)
  96. }
  97. }
  98. state.write(')')
  99. }
  100. function expressionNeedsParenthesis(node, parentNode, isRightHand) {
  101. const nodePrecedence = EXPRESSIONS_PRECEDENCE[node.type]
  102. if (nodePrecedence === NEEDS_PARENTHESES) {
  103. return true
  104. }
  105. const parentNodePrecedence = EXPRESSIONS_PRECEDENCE[parentNode.type]
  106. if (nodePrecedence !== parentNodePrecedence) {
  107. // Different node types
  108. return (
  109. (!isRightHand &&
  110. nodePrecedence === 15 &&
  111. parentNodePrecedence === 14 &&
  112. parentNode.operator === '**') ||
  113. nodePrecedence < parentNodePrecedence
  114. )
  115. }
  116. if (nodePrecedence !== 13 && nodePrecedence !== 14) {
  117. // Not a `LogicalExpression` or `BinaryExpression`
  118. return false
  119. }
  120. if (node.operator === '**' && parentNode.operator === '**') {
  121. // Exponentiation operator has right-to-left associativity
  122. return !isRightHand
  123. }
  124. if (isRightHand) {
  125. // Parenthesis are used if both operators have the same precedence
  126. return (
  127. OPERATOR_PRECEDENCE[node.operator] <=
  128. OPERATOR_PRECEDENCE[parentNode.operator]
  129. )
  130. }
  131. return (
  132. OPERATOR_PRECEDENCE[node.operator] <
  133. OPERATOR_PRECEDENCE[parentNode.operator]
  134. )
  135. }
  136. function formatBinaryExpressionPart(state, node, parentNode, isRightHand) {
  137. /*
  138. Writes into `state` a left-hand or right-hand expression `node`
  139. from a binary expression applying the provided `operator`.
  140. The `isRightHand` parameter should be `true` if the `node` is a right-hand argument.
  141. */
  142. const { generator } = state
  143. if (expressionNeedsParenthesis(node, parentNode, isRightHand)) {
  144. state.write('(')
  145. generator[node.type](node, state)
  146. state.write(')')
  147. } else {
  148. generator[node.type](node, state)
  149. }
  150. }
  151. function reindent(state, text, indent, lineEnd) {
  152. /*
  153. Writes into `state` the `text` string reindented with the provided `indent`.
  154. */
  155. const lines = text.split('\n')
  156. const end = lines.length - 1
  157. state.write(lines[0].trim())
  158. if (end > 0) {
  159. state.write(lineEnd)
  160. for (let i = 1; i < end; i++) {
  161. state.write(indent + lines[i].trim() + lineEnd)
  162. }
  163. state.write(indent + lines[end].trim())
  164. }
  165. }
  166. function formatComments(state, comments, indent, lineEnd) {
  167. /*
  168. Writes into `state` the provided list of `comments`, with the given `indent` and `lineEnd` strings.
  169. Line comments will end with `"\n"` regardless of the value of `lineEnd`.
  170. Expects to start on a new unindented line.
  171. */
  172. const { length } = comments
  173. for (let i = 0; i < length; i++) {
  174. const comment = comments[i]
  175. state.write(indent)
  176. if (comment.type[0] === 'L') {
  177. // Line comment
  178. state.write('// ' + comment.value.trim() + '\n')
  179. } else {
  180. // Block comment
  181. state.write('/*')
  182. reindent(state, comment.value, indent, lineEnd)
  183. state.write('*/' + lineEnd)
  184. }
  185. }
  186. }
  187. function hasCallExpression(node) {
  188. /*
  189. Returns `true` if the provided `node` contains a call expression and `false` otherwise.
  190. */
  191. let currentNode = node
  192. while (currentNode != null) {
  193. const { type } = currentNode
  194. if (type[0] === 'C' && type[1] === 'a') {
  195. // Is CallExpression
  196. return true
  197. } else if (type[0] === 'M' && type[1] === 'e' && type[2] === 'm') {
  198. // Is MemberExpression
  199. currentNode = currentNode.object
  200. } else {
  201. return false
  202. }
  203. }
  204. }
  205. function formatVariableDeclaration(state, node) {
  206. /*
  207. Writes into `state` a variable declaration.
  208. */
  209. const { generator } = state
  210. const { declarations } = node
  211. state.write(node.kind + ' ')
  212. const { length } = declarations
  213. if (length > 0) {
  214. generator.VariableDeclarator(declarations[0], state)
  215. for (let i = 1; i < length; i++) {
  216. state.write(', ')
  217. generator.VariableDeclarator(declarations[i], state)
  218. }
  219. }
  220. }
  221. let ForInStatement,
  222. FunctionDeclaration,
  223. RestElement,
  224. BinaryExpression,
  225. ArrayExpression,
  226. BlockStatement
  227. const baseGenerator = {
  228. Program(node, state) {
  229. const indent = state.indent.repeat(state.indentLevel)
  230. const { lineEnd, writeComments } = state
  231. if (writeComments && node.comments != null) {
  232. formatComments(state, node.comments, indent, lineEnd)
  233. }
  234. const statements = node.body
  235. const { length } = statements
  236. for (let i = 0; i < length; i++) {
  237. const statement = statements[i]
  238. if (writeComments && statement.comments != null) {
  239. formatComments(state, statement.comments, indent, lineEnd)
  240. }
  241. state.write(indent)
  242. this[statement.type](statement, state)
  243. state.write(lineEnd)
  244. }
  245. if (writeComments && node.trailingComments != null) {
  246. formatComments(state, node.trailingComments, indent, lineEnd)
  247. }
  248. },
  249. BlockStatement: (BlockStatement = function(node, state) {
  250. const indent = state.indent.repeat(state.indentLevel++)
  251. const { lineEnd, writeComments } = state
  252. const statementIndent = indent + state.indent
  253. state.write('{')
  254. const statements = node.body
  255. if (statements != null && statements.length > 0) {
  256. state.write(lineEnd)
  257. if (writeComments && node.comments != null) {
  258. formatComments(state, node.comments, statementIndent, lineEnd)
  259. }
  260. const { length } = statements
  261. for (let i = 0; i < length; i++) {
  262. const statement = statements[i]
  263. if (writeComments && statement.comments != null) {
  264. formatComments(state, statement.comments, statementIndent, lineEnd)
  265. }
  266. state.write(statementIndent)
  267. this[statement.type](statement, state)
  268. state.write(lineEnd)
  269. }
  270. state.write(indent)
  271. } else {
  272. if (writeComments && node.comments != null) {
  273. state.write(lineEnd)
  274. formatComments(state, node.comments, statementIndent, lineEnd)
  275. state.write(indent)
  276. }
  277. }
  278. if (writeComments && node.trailingComments != null) {
  279. formatComments(state, node.trailingComments, statementIndent, lineEnd)
  280. }
  281. state.write('}')
  282. state.indentLevel--
  283. }),
  284. ClassBody: BlockStatement,
  285. EmptyStatement(node, state) {
  286. state.write(';')
  287. },
  288. ExpressionStatement(node, state) {
  289. const precedence = EXPRESSIONS_PRECEDENCE[node.expression.type]
  290. if (
  291. precedence === NEEDS_PARENTHESES ||
  292. (precedence === 3 && node.expression.left.type[0] === 'O')
  293. ) {
  294. // Should always have parentheses or is an AssignmentExpression to an ObjectPattern
  295. state.write('(')
  296. this[node.expression.type](node.expression, state)
  297. state.write(')')
  298. } else {
  299. this[node.expression.type](node.expression, state)
  300. }
  301. state.write(';')
  302. },
  303. IfStatement(node, state) {
  304. state.write('if (')
  305. this[node.test.type](node.test, state)
  306. state.write(') ')
  307. this[node.consequent.type](node.consequent, state)
  308. if (node.alternate != null) {
  309. state.write(' else ')
  310. this[node.alternate.type](node.alternate, state)
  311. }
  312. },
  313. LabeledStatement(node, state) {
  314. this[node.label.type](node.label, state)
  315. state.write(': ')
  316. this[node.body.type](node.body, state)
  317. },
  318. BreakStatement(node, state) {
  319. state.write('break')
  320. if (node.label != null) {
  321. state.write(' ')
  322. this[node.label.type](node.label, state)
  323. }
  324. state.write(';')
  325. },
  326. ContinueStatement(node, state) {
  327. state.write('continue')
  328. if (node.label != null) {
  329. state.write(' ')
  330. this[node.label.type](node.label, state)
  331. }
  332. state.write(';')
  333. },
  334. WithStatement(node, state) {
  335. state.write('with (')
  336. this[node.object.type](node.object, state)
  337. state.write(') ')
  338. this[node.body.type](node.body, state)
  339. },
  340. SwitchStatement(node, state) {
  341. const indent = state.indent.repeat(state.indentLevel++)
  342. const { lineEnd, writeComments } = state
  343. state.indentLevel++
  344. const caseIndent = indent + state.indent
  345. const statementIndent = caseIndent + state.indent
  346. state.write('switch (')
  347. this[node.discriminant.type](node.discriminant, state)
  348. state.write(') {' + lineEnd)
  349. const { cases: occurences } = node
  350. const { length: occurencesCount } = occurences
  351. for (let i = 0; i < occurencesCount; i++) {
  352. const occurence = occurences[i]
  353. if (writeComments && occurence.comments != null) {
  354. formatComments(state, occurence.comments, caseIndent, lineEnd)
  355. }
  356. if (occurence.test) {
  357. state.write(caseIndent + 'case ')
  358. this[occurence.test.type](occurence.test, state)
  359. state.write(':' + lineEnd)
  360. } else {
  361. state.write(caseIndent + 'default:' + lineEnd)
  362. }
  363. const { consequent } = occurence
  364. const { length: consequentCount } = consequent
  365. for (let i = 0; i < consequentCount; i++) {
  366. const statement = consequent[i]
  367. if (writeComments && statement.comments != null) {
  368. formatComments(state, statement.comments, statementIndent, lineEnd)
  369. }
  370. state.write(statementIndent)
  371. this[statement.type](statement, state)
  372. state.write(lineEnd)
  373. }
  374. }
  375. state.indentLevel -= 2
  376. state.write(indent + '}')
  377. },
  378. ReturnStatement(node, state) {
  379. state.write('return')
  380. if (node.argument) {
  381. state.write(' ')
  382. this[node.argument.type](node.argument, state)
  383. }
  384. state.write(';')
  385. },
  386. ThrowStatement(node, state) {
  387. state.write('throw ')
  388. this[node.argument.type](node.argument, state)
  389. state.write(';')
  390. },
  391. TryStatement(node, state) {
  392. state.write('try ')
  393. this[node.block.type](node.block, state)
  394. if (node.handler) {
  395. const { handler } = node
  396. if (handler.param == null) {
  397. state.write(' catch ')
  398. } else {
  399. state.write(' catch (')
  400. this[handler.param.type](handler.param, state)
  401. state.write(') ')
  402. }
  403. this[handler.body.type](handler.body, state)
  404. }
  405. if (node.finalizer) {
  406. state.write(' finally ')
  407. this[node.finalizer.type](node.finalizer, state)
  408. }
  409. },
  410. WhileStatement(node, state) {
  411. state.write('while (')
  412. this[node.test.type](node.test, state)
  413. state.write(') ')
  414. this[node.body.type](node.body, state)
  415. },
  416. DoWhileStatement(node, state) {
  417. state.write('do ')
  418. this[node.body.type](node.body, state)
  419. state.write(' while (')
  420. this[node.test.type](node.test, state)
  421. state.write(');')
  422. },
  423. ForStatement(node, state) {
  424. state.write('for (')
  425. if (node.init != null) {
  426. const { init } = node
  427. if (init.type[0] === 'V') {
  428. formatVariableDeclaration(state, init)
  429. } else {
  430. this[init.type](init, state)
  431. }
  432. }
  433. state.write('; ')
  434. if (node.test) {
  435. this[node.test.type](node.test, state)
  436. }
  437. state.write('; ')
  438. if (node.update) {
  439. this[node.update.type](node.update, state)
  440. }
  441. state.write(') ')
  442. this[node.body.type](node.body, state)
  443. },
  444. ForInStatement: (ForInStatement = function(node, state) {
  445. state.write(`for ${node.await ? 'await ' : ''}(`)
  446. const { left } = node
  447. if (left.type[0] === 'V') {
  448. formatVariableDeclaration(state, left)
  449. } else {
  450. this[left.type](left, state)
  451. }
  452. // Identifying whether node.type is `ForInStatement` or `ForOfStatement`
  453. state.write(node.type[3] === 'I' ? ' in ' : ' of ')
  454. this[node.right.type](node.right, state)
  455. state.write(') ')
  456. this[node.body.type](node.body, state)
  457. }),
  458. ForOfStatement: ForInStatement,
  459. DebuggerStatement(node, state) {
  460. state.write('debugger;' + state.lineEnd)
  461. },
  462. FunctionDeclaration: (FunctionDeclaration = function(node, state) {
  463. state.write(
  464. (node.async ? 'async ' : '') +
  465. (node.generator ? 'function* ' : 'function ') +
  466. (node.id ? node.id.name : ''),
  467. node,
  468. )
  469. formatSequence(state, node.params)
  470. state.write(' ')
  471. this[node.body.type](node.body, state)
  472. }),
  473. FunctionExpression: FunctionDeclaration,
  474. VariableDeclaration(node, state) {
  475. formatVariableDeclaration(state, node)
  476. state.write(';')
  477. },
  478. VariableDeclarator(node, state) {
  479. this[node.id.type](node.id, state)
  480. if (node.init != null) {
  481. state.write(' = ')
  482. this[node.init.type](node.init, state)
  483. }
  484. },
  485. ClassDeclaration(node, state) {
  486. state.write('class ' + (node.id ? `${node.id.name} ` : ''), node)
  487. if (node.superClass) {
  488. state.write('extends ')
  489. this[node.superClass.type](node.superClass, state)
  490. state.write(' ')
  491. }
  492. this.ClassBody(node.body, state)
  493. },
  494. ImportDeclaration(node, state) {
  495. state.write('import ')
  496. const { specifiers } = node
  497. const { length } = specifiers
  498. // NOTE: Once babili is fixed, put this after condition
  499. // https://github.com/babel/babili/issues/430
  500. let i = 0
  501. if (length > 0) {
  502. for (; i < length; ) {
  503. if (i > 0) {
  504. state.write(', ')
  505. }
  506. const specifier = specifiers[i]
  507. const type = specifier.type[6]
  508. if (type === 'D') {
  509. // ImportDefaultSpecifier
  510. state.write(specifier.local.name, specifier)
  511. i++
  512. } else if (type === 'N') {
  513. // ImportNamespaceSpecifier
  514. state.write('* as ' + specifier.local.name, specifier)
  515. i++
  516. } else {
  517. // ImportSpecifier
  518. break
  519. }
  520. }
  521. if (i < length) {
  522. state.write('{')
  523. for (;;) {
  524. const specifier = specifiers[i]
  525. const { name } = specifier.imported
  526. state.write(name, specifier)
  527. if (name !== specifier.local.name) {
  528. state.write(' as ' + specifier.local.name)
  529. }
  530. if (++i < length) {
  531. state.write(', ')
  532. } else {
  533. break
  534. }
  535. }
  536. state.write('}')
  537. }
  538. state.write(' from ')
  539. }
  540. this.Literal(node.source, state)
  541. state.write(';')
  542. },
  543. ExportDefaultDeclaration(node, state) {
  544. state.write('export default ')
  545. this[node.declaration.type](node.declaration, state)
  546. if (
  547. EXPRESSIONS_PRECEDENCE[node.declaration.type] &&
  548. node.declaration.type[0] !== 'F'
  549. ) {
  550. // All expression nodes except `FunctionExpression`
  551. state.write(';')
  552. }
  553. },
  554. ExportNamedDeclaration(node, state) {
  555. state.write('export ')
  556. if (node.declaration) {
  557. this[node.declaration.type](node.declaration, state)
  558. } else {
  559. state.write('{')
  560. const { specifiers } = node,
  561. { length } = specifiers
  562. if (length > 0) {
  563. for (let i = 0; ; ) {
  564. const specifier = specifiers[i]
  565. const { name } = specifier.local
  566. state.write(name, specifier)
  567. if (name !== specifier.exported.name) {
  568. state.write(' as ' + specifier.exported.name)
  569. }
  570. if (++i < length) {
  571. state.write(', ')
  572. } else {
  573. break
  574. }
  575. }
  576. }
  577. state.write('}')
  578. if (node.source) {
  579. state.write(' from ')
  580. this.Literal(node.source, state)
  581. }
  582. state.write(';')
  583. }
  584. },
  585. ExportAllDeclaration(node, state) {
  586. state.write('export * from ')
  587. this.Literal(node.source, state)
  588. state.write(';')
  589. },
  590. MethodDefinition(node, state) {
  591. if (node.static) {
  592. state.write('static ')
  593. }
  594. const kind = node.kind[0]
  595. if (kind === 'g' || kind === 's') {
  596. // Getter or setter
  597. state.write(node.kind + ' ')
  598. }
  599. if (node.value.async) {
  600. state.write('async ')
  601. }
  602. if (node.value.generator) {
  603. state.write('*')
  604. }
  605. if (node.computed) {
  606. state.write('[')
  607. this[node.key.type](node.key, state)
  608. state.write(']')
  609. } else {
  610. this[node.key.type](node.key, state)
  611. }
  612. formatSequence(state, node.value.params)
  613. state.write(' ')
  614. this[node.value.body.type](node.value.body, state)
  615. },
  616. ClassExpression(node, state) {
  617. this.ClassDeclaration(node, state)
  618. },
  619. ArrowFunctionExpression(node, state) {
  620. state.write(node.async ? 'async ' : '', node)
  621. const { params } = node
  622. if (params != null) {
  623. // Omit parenthesis if only one named parameter
  624. if (params.length === 1 && params[0].type[0] === 'I') {
  625. // If params[0].type[0] starts with 'I', it can't be `ImportDeclaration` nor `IfStatement` and thus is `Identifier`
  626. state.write(params[0].name, params[0])
  627. } else {
  628. formatSequence(state, node.params)
  629. }
  630. }
  631. state.write(' => ')
  632. if (node.body.type[0] === 'O') {
  633. // Body is an object expression
  634. state.write('(')
  635. this.ObjectExpression(node.body, state)
  636. state.write(')')
  637. } else {
  638. this[node.body.type](node.body, state)
  639. }
  640. },
  641. ThisExpression(node, state) {
  642. state.write('this', node)
  643. },
  644. Super(node, state) {
  645. state.write('super', node)
  646. },
  647. RestElement: (RestElement = function(node, state) {
  648. state.write('...')
  649. this[node.argument.type](node.argument, state)
  650. }),
  651. SpreadElement: RestElement,
  652. YieldExpression(node, state) {
  653. state.write(node.delegate ? 'yield*' : 'yield')
  654. if (node.argument) {
  655. state.write(' ')
  656. this[node.argument.type](node.argument, state)
  657. }
  658. },
  659. AwaitExpression(node, state) {
  660. state.write('await ')
  661. if (node.argument) {
  662. this[node.argument.type](node.argument, state)
  663. }
  664. },
  665. TemplateLiteral(node, state) {
  666. const { quasis, expressions } = node
  667. state.write('`')
  668. const { length } = expressions
  669. for (let i = 0; i < length; i++) {
  670. const expression = expressions[i]
  671. this.TemplateElement(quasis[i], state)
  672. state.write('${')
  673. this[expression.type](expression, state)
  674. state.write('}')
  675. }
  676. state.write(quasis[quasis.length - 1].value.raw)
  677. state.write('`')
  678. },
  679. TemplateElement(node, state) {
  680. state.write(node.value.raw)
  681. },
  682. TaggedTemplateExpression(node, state) {
  683. this[node.tag.type](node.tag, state)
  684. this[node.quasi.type](node.quasi, state)
  685. },
  686. ArrayExpression: (ArrayExpression = function(node, state) {
  687. state.write('[')
  688. if (node.elements.length > 0) {
  689. const { elements } = node,
  690. { length } = elements
  691. for (let i = 0; ; ) {
  692. const element = elements[i]
  693. if (element != null) {
  694. this[element.type](element, state)
  695. }
  696. if (++i < length) {
  697. state.write(', ')
  698. } else {
  699. if (element == null) {
  700. state.write(', ')
  701. }
  702. break
  703. }
  704. }
  705. }
  706. state.write(']')
  707. }),
  708. ArrayPattern: ArrayExpression,
  709. ObjectExpression(node, state) {
  710. const indent = state.indent.repeat(state.indentLevel++)
  711. const { lineEnd, writeComments } = state
  712. const propertyIndent = indent + state.indent
  713. state.write('{')
  714. if (node.properties.length > 0) {
  715. state.write(lineEnd)
  716. if (writeComments && node.comments != null) {
  717. formatComments(state, node.comments, propertyIndent, lineEnd)
  718. }
  719. const comma = ',' + lineEnd
  720. const { properties } = node,
  721. { length } = properties
  722. for (let i = 0; ; ) {
  723. const property = properties[i]
  724. if (writeComments && property.comments != null) {
  725. formatComments(state, property.comments, propertyIndent, lineEnd)
  726. }
  727. state.write(propertyIndent)
  728. this[property.type](property, state)
  729. if (++i < length) {
  730. state.write(comma)
  731. } else {
  732. break
  733. }
  734. }
  735. state.write(lineEnd)
  736. if (writeComments && node.trailingComments != null) {
  737. formatComments(state, node.trailingComments, propertyIndent, lineEnd)
  738. }
  739. state.write(indent + '}')
  740. } else if (writeComments) {
  741. if (node.comments != null) {
  742. state.write(lineEnd)
  743. formatComments(state, node.comments, propertyIndent, lineEnd)
  744. if (node.trailingComments != null) {
  745. formatComments(state, node.trailingComments, propertyIndent, lineEnd)
  746. }
  747. state.write(indent + '}')
  748. } else if (node.trailingComments != null) {
  749. state.write(lineEnd)
  750. formatComments(state, node.trailingComments, propertyIndent, lineEnd)
  751. state.write(indent + '}')
  752. } else {
  753. state.write('}')
  754. }
  755. } else {
  756. state.write('}')
  757. }
  758. state.indentLevel--
  759. },
  760. Property(node, state) {
  761. if (node.method || node.kind[0] !== 'i') {
  762. // Either a method or of kind `set` or `get` (not `init`)
  763. this.MethodDefinition(node, state)
  764. } else {
  765. if (!node.shorthand) {
  766. if (node.computed) {
  767. state.write('[')
  768. this[node.key.type](node.key, state)
  769. state.write(']')
  770. } else {
  771. this[node.key.type](node.key, state)
  772. }
  773. state.write(': ')
  774. }
  775. this[node.value.type](node.value, state)
  776. }
  777. },
  778. ObjectPattern(node, state) {
  779. state.write('{')
  780. if (node.properties.length > 0) {
  781. const { properties } = node,
  782. { length } = properties
  783. for (let i = 0; ; ) {
  784. this[properties[i].type](properties[i], state)
  785. if (++i < length) {
  786. state.write(', ')
  787. } else {
  788. break
  789. }
  790. }
  791. }
  792. state.write('}')
  793. },
  794. SequenceExpression(node, state) {
  795. formatSequence(state, node.expressions)
  796. },
  797. UnaryExpression(node, state) {
  798. if (node.prefix) {
  799. state.write(node.operator)
  800. if (node.operator.length > 1) {
  801. state.write(' ')
  802. }
  803. if (
  804. EXPRESSIONS_PRECEDENCE[node.argument.type] <
  805. EXPRESSIONS_PRECEDENCE.UnaryExpression
  806. ) {
  807. state.write('(')
  808. this[node.argument.type](node.argument, state)
  809. state.write(')')
  810. } else {
  811. this[node.argument.type](node.argument, state)
  812. }
  813. } else {
  814. // FIXME: This case never occurs
  815. this[node.argument.type](node.argument, state)
  816. state.write(node.operator)
  817. }
  818. },
  819. UpdateExpression(node, state) {
  820. // Always applied to identifiers or members, no parenthesis check needed
  821. if (node.prefix) {
  822. state.write(node.operator)
  823. this[node.argument.type](node.argument, state)
  824. } else {
  825. this[node.argument.type](node.argument, state)
  826. state.write(node.operator)
  827. }
  828. },
  829. AssignmentExpression(node, state) {
  830. this[node.left.type](node.left, state)
  831. state.write(' ' + node.operator + ' ')
  832. this[node.right.type](node.right, state)
  833. },
  834. AssignmentPattern(node, state) {
  835. this[node.left.type](node.left, state)
  836. state.write(' = ')
  837. this[node.right.type](node.right, state)
  838. },
  839. BinaryExpression: (BinaryExpression = function(node, state) {
  840. const isIn = node.operator === 'in'
  841. if (isIn) {
  842. // Avoids confusion in `for` loops initializers
  843. state.write('(')
  844. }
  845. formatBinaryExpressionPart(state, node.left, node, false)
  846. state.write(' ' + node.operator + ' ')
  847. formatBinaryExpressionPart(state, node.right, node, true)
  848. if (isIn) {
  849. state.write(')')
  850. }
  851. }),
  852. LogicalExpression: BinaryExpression,
  853. ConditionalExpression(node, state) {
  854. if (
  855. EXPRESSIONS_PRECEDENCE[node.test.type] >
  856. EXPRESSIONS_PRECEDENCE.ConditionalExpression
  857. ) {
  858. this[node.test.type](node.test, state)
  859. } else {
  860. state.write('(')
  861. this[node.test.type](node.test, state)
  862. state.write(')')
  863. }
  864. state.write(' ? ')
  865. this[node.consequent.type](node.consequent, state)
  866. state.write(' : ')
  867. this[node.alternate.type](node.alternate, state)
  868. },
  869. NewExpression(node, state) {
  870. state.write('new ')
  871. if (
  872. EXPRESSIONS_PRECEDENCE[node.callee.type] <
  873. EXPRESSIONS_PRECEDENCE.CallExpression ||
  874. hasCallExpression(node.callee)
  875. ) {
  876. state.write('(')
  877. this[node.callee.type](node.callee, state)
  878. state.write(')')
  879. } else {
  880. this[node.callee.type](node.callee, state)
  881. }
  882. formatSequence(state, node['arguments'])
  883. },
  884. CallExpression(node, state) {
  885. if (
  886. EXPRESSIONS_PRECEDENCE[node.callee.type] <
  887. EXPRESSIONS_PRECEDENCE.CallExpression
  888. ) {
  889. state.write('(')
  890. this[node.callee.type](node.callee, state)
  891. state.write(')')
  892. } else {
  893. this[node.callee.type](node.callee, state)
  894. }
  895. formatSequence(state, node['arguments'])
  896. },
  897. MemberExpression(node, state) {
  898. if (
  899. EXPRESSIONS_PRECEDENCE[node.object.type] <
  900. EXPRESSIONS_PRECEDENCE.MemberExpression
  901. ) {
  902. state.write('(')
  903. this[node.object.type](node.object, state)
  904. state.write(')')
  905. } else {
  906. this[node.object.type](node.object, state)
  907. }
  908. if (node.computed) {
  909. state.write('[')
  910. this[node.property.type](node.property, state)
  911. state.write(']')
  912. } else {
  913. state.write('.')
  914. this[node.property.type](node.property, state)
  915. }
  916. },
  917. MetaProperty(node, state) {
  918. state.write(node.meta.name + '.' + node.property.name, node)
  919. },
  920. Identifier(node, state) {
  921. state.write(node.name, node)
  922. },
  923. Literal(node, state) {
  924. if (node.raw != null) {
  925. state.write(node.raw, node)
  926. } else if (node.regex != null) {
  927. this.RegExpLiteral(node, state)
  928. } else {
  929. state.write(stringify(node.value), node)
  930. }
  931. },
  932. RegExpLiteral(node, state) {
  933. const { regex } = node
  934. state.write(`/${regex.pattern}/${regex.flags}`, node)
  935. },
  936. }
  937. const EMPTY_OBJECT = {}
  938. class State {
  939. constructor(options) {
  940. const setup = options == null ? EMPTY_OBJECT : options
  941. this.output = ''
  942. // Functional options
  943. if (setup.output != null) {
  944. this.output = setup.output
  945. this.write = this.writeToStream
  946. } else {
  947. this.output = ''
  948. }
  949. this.generator = setup.generator != null ? setup.generator : baseGenerator
  950. // Formating setup
  951. this.indent = setup.indent != null ? setup.indent : ' '
  952. this.lineEnd = setup.lineEnd != null ? setup.lineEnd : '\n'
  953. this.indentLevel =
  954. setup.startingIndentLevel != null ? setup.startingIndentLevel : 0
  955. this.writeComments = setup.comments ? setup.comments : false
  956. // Source map
  957. if (setup.sourceMap != null) {
  958. this.write =
  959. setup.output == null ? this.writeAndMap : this.writeToStreamAndMap
  960. this.sourceMap = setup.sourceMap
  961. this.line = 1
  962. this.column = 0
  963. this.lineEndSize = this.lineEnd.split('\n').length - 1
  964. this.mapping = {
  965. original: null,
  966. generated: this,
  967. name: undefined,
  968. source: setup.sourceMap.file || setup.sourceMap._file,
  969. }
  970. }
  971. }
  972. write(code) {
  973. this.output += code
  974. }
  975. writeToStream(code) {
  976. this.output.write(code)
  977. }
  978. writeAndMap(code, node) {
  979. this.output += code
  980. this.map(code, node)
  981. }
  982. writeToStreamAndMap(code, node) {
  983. this.output.write(code)
  984. this.map(code, node)
  985. }
  986. map(code, node) {
  987. if (node != null && node.loc != null) {
  988. const { mapping } = this
  989. mapping.original = node.loc.start
  990. mapping.name = node.name
  991. this.sourceMap.addMapping(mapping)
  992. }
  993. if (code.length > 0) {
  994. if (this.lineEndSize > 0) {
  995. if (code.endsWith(this.lineEnd)) {
  996. this.line += this.lineEndSize
  997. this.column = 0
  998. } else if (code[code.length - 1] === '\n') {
  999. // Case of inline comment
  1000. this.line++
  1001. this.column = 0
  1002. } else {
  1003. this.column += code.length
  1004. }
  1005. } else {
  1006. if (code[code.length - 1] === '\n') {
  1007. // Case of inline comment
  1008. this.line++
  1009. this.column = 0
  1010. } else {
  1011. this.column += code.length
  1012. }
  1013. }
  1014. }
  1015. }
  1016. toString() {
  1017. return this.output
  1018. }
  1019. }
  1020. function generate(node, options) {
  1021. /*
  1022. Returns a string representing the rendered code of the provided AST `node`.
  1023. The `options` are:
  1024. - `indent`: string to use for indentation (defaults to `␣␣`)
  1025. - `lineEnd`: string to use for line endings (defaults to `\n`)
  1026. - `startingIndentLevel`: indent level to start from (defaults to `0`)
  1027. - `comments`: generate comments if `true` (defaults to `false`)
  1028. - `output`: output stream to write the rendered code to (defaults to `null`)
  1029. - `generator`: custom code generator (defaults to `baseGenerator`)
  1030. */
  1031. const state = new State(options)
  1032. // Travel through the AST node and generate the code
  1033. state.generator[node.type](node, state)
  1034. return state.output
  1035. }