SourceProcessor.js 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. */
  4. "use strict";
  5. // Stands in the output for text only an asynchronous caller can supply, until
  6. // {@link PrintContext.substitute} puts the answer in its place. A NUL delimits
  7. // it because both the CSS and the HTML preprocessor turn one in the input into
  8. // U+FFFD, so printed output never holds one of its own account.
  9. const DEFERRED_MARKER = "\u0000";
  10. // The infix this print marks its writes with, chosen so that `NUL infix` occurs
  11. // nowhere in the input — a NUL the source carried then spells no write of ours.
  12. let deferredInfix = "0:";
  13. /**
  14. * The text to print in place of a deferred write.
  15. * @param {number} id the write's index among this print's deferred writes
  16. * @returns {string} the marker to emit
  17. */
  18. const deferredWrite = (id) =>
  19. `${DEFERRED_MARKER}${deferredInfix}${id}${DEFERRED_MARKER}`;
  20. /**
  21. * An infix no NUL run in `input` already carries, so only this print can have
  22. * written one. An RCDATA element (`<textarea>`, `<title>`) and an attribute
  23. * value both keep the NUL they were written with, so the source can hold one.
  24. * @param {string} input the source about to be printed
  25. * @returns {string} the infix to mark this print's writes with
  26. */
  27. const chooseDeferredInfix = (input) => {
  28. let n = 0;
  29. while (input.includes(`${DEFERRED_MARKER}${n}:`)) n++;
  30. return `${n}:`;
  31. };
  32. const DEFERRED_ID_RE = /^\d+$/;
  33. /**
  34. * Read the deferred write standing at `start`, if one does.
  35. * @param {string} text the text to read
  36. * @param {number} start offset of the marker's opening NUL
  37. * @param {(id: number) => string | undefined} resolve the write's final text, or undefined where it answers for no such write
  38. * @param {string} infix what this print marked its writes with
  39. * @returns {{ end: number, replacement: string } | null} where the write ends and what stands in for it, or null where none does
  40. */
  41. const readDeferredWrite = (text, start, resolve, infix) => {
  42. const end = text.indexOf(DEFERRED_MARKER, start + 1);
  43. if (end === -1) return null;
  44. const body = text.slice(start + 1, end);
  45. if (!body.startsWith(infix)) return null;
  46. const id = body.slice(infix.length);
  47. if (!DEFERRED_ID_RE.test(id)) return null;
  48. const replacement = resolve(Number(id));
  49. return replacement === undefined ? null : { end, replacement };
  50. };
  51. /**
  52. * Stand the answers in for every write in one answer: a body offered whole
  53. * holds the markers of the bodies inside it.
  54. * @param {string} text one answer
  55. * @param {(id: number) => string | undefined} resolve the write's final text
  56. * @param {string} infix what this print marked its writes with
  57. * @returns {string} the answer with every write inside it resolved
  58. */
  59. const expandDeferredWrites = (text, resolve, infix) => {
  60. let at = text.indexOf(DEFERRED_MARKER);
  61. if (at === -1) return text;
  62. let out = "";
  63. let from = 0;
  64. while (at !== -1) {
  65. const write = readDeferredWrite(text, at, resolve, infix);
  66. if (write === null) {
  67. at = text.indexOf(DEFERRED_MARKER, at + 1);
  68. continue;
  69. }
  70. out +=
  71. text.slice(from, at) +
  72. expandDeferredWrites(write.replacement, resolve, infix);
  73. from = write.end + 1;
  74. at = text.indexOf(DEFERRED_MARKER, from);
  75. }
  76. return out + text.slice(from);
  77. };
  78. /**
  79. * Babel-style visitor map keyed by a numeric node-type discriminator; a bucket
  80. * is a function (enter-only) or `{ enter?, exit? }`.
  81. *
  82. * A visitor receives a single `path` argument (the Babel `path` shape): the
  83. * language's AST accessor with the current position on it — `path.node`,
  84. * `path.parent` (null at a root) — plus `path.skipChildren()` (enter only)
  85. * to stop the walk descending, and every field-read method (which defaults
  86. * to the current node). The path is one reused object rebound before each callback:
  87. * it is only valid during the callback, and future per-node functionality
  88. * lands on it without changing any visitor signature.
  89. * @template TPath
  90. * @typedef {(path: TPath) => void} VisitorFn
  91. */
  92. /**
  93. * @template TPath
  94. * @typedef {VisitorFn<TPath> | { enter?: VisitorFn<TPath>, exit?: VisitorFn<TPath> }} VisitorBucket
  95. */
  96. /**
  97. * @template TPath
  98. * @typedef {{ [nodeType: number]: VisitorBucket<TPath> }} VisitorMap
  99. */
  100. /**
  101. * @template TPath
  102. * @typedef {{ enter: VisitorFn<TPath>[], exit: VisitorFn<TPath>[] }} CompiledVisitorBucket
  103. */
  104. /**
  105. * @template TPath
  106. * @typedef {CompiledVisitorBucket<TPath>[]} CompiledVisitorMap a sparse array indexed by node type
  107. */
  108. /**
  109. * What every language's print options carry, read by the node printer via
  110. * `writer.options`. Only `mode` is here: what else a printer may be told is the
  111. * language's own business, and a language names it by instantiating
  112. * {@link PrintContext} with its own type — nothing CSS reads belongs in a
  113. * typedef HTML also depends on.
  114. * @typedef {{ mode: "minify" | "beautify" }} PrintOptions
  115. */
  116. /**
  117. * One write the print left a marker for, collected into the `deferEmbeddedSource`
  118. * print option by whichever grammar offered it — the other half of
  119. * {@link deferredWrite}. `source` is the text offered and `build` spells what is
  120. * printed around the answer, an untapped run's spelling where there is none. A
  121. * grammar carries whatever else describes the offer (`type`, `hostType`, `as`,
  122. * …) on the same object.
  123. * @typedef {{ source: string, build: (answer: string | undefined) => string }} DeferredWrite
  124. */
  125. /**
  126. * A version-3 source map. Written structurally (with the `3` literal) so it
  127. * satisfies both `webpack-sources` and the minimizer plugin's map types without
  128. * depending on either.
  129. * @typedef {{ version: 3, file: string, sources: string[], sourcesContent?: string[], names: string[], mappings: string }} SourceMap
  130. */
  131. /**
  132. * The `process` source-map option: turns map collection on and names the input
  133. * (`sources[0]` / optional `sourcesContent[0]`). Present => `process` returns
  134. * `{ code, map }` instead of a bare string.
  135. * @typedef {{ source: string, content?: string }} SourceMapOptions
  136. */
  137. /**
  138. * @param {SourceMapOptions} options the input's name / content
  139. * @param {string} mappings the VLQ `mappings` field
  140. * @returns {SourceMap} a version-3 source map
  141. */
  142. const _makeMap = (options, mappings) => ({
  143. version: 3,
  144. file: options.source,
  145. sources: [options.source],
  146. sourcesContent: options.content === undefined ? undefined : [options.content],
  147. names: [],
  148. mappings
  149. });
  150. // How much output accumulates before it is cut off and forced flat (see
  151. // `PrintContext._emit`). Large enough that the flattening copy is amortized away,
  152. // small enough that the un-flattened fragments behind it stay bounded.
  153. const FLATTEN_BLOCK = 64 * 1024;
  154. // Base64 VLQ, the source-map `mappings` encoding. Hand-rolled so producing a map
  155. // pulls in no dependency (`source-map` is not a webpack dep).
  156. const _B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  157. /**
  158. * @param {number} n a signed integer
  159. * @returns {string} its Base64 VLQ encoding
  160. */
  161. const _vlq = (n) => {
  162. let v = n < 0 ? (-n << 1) | 1 : n << 1;
  163. let out = "";
  164. do {
  165. let digit = v & 31;
  166. v >>>= 5;
  167. if (v > 0) digit |= 32;
  168. out += _B64[digit];
  169. } while (v > 0);
  170. return out;
  171. };
  172. /**
  173. * A language node printer, fired for one node once all its visitors have run and
  174. * its children are printed. It takes the same `path` a visitor gets plus the
  175. * print context as its `writer`; it switches on `path.type()` and **returns** the
  176. * node's serialized text, reading its children's text from `writer.get` — knowing
  177. * nothing of the walk. Returning (rather than writing to a buffer) is what lets a
  178. * parent compose / transform its text from its finished children.
  179. * @template TPath
  180. * @template TNode
  181. * @template [TPrintOptions=object]
  182. * @typedef {(path: TPath, writer: PrintContext<TPath, TNode, TPrintOptions>) => string} NodePrinter
  183. */
  184. /**
  185. * A language grammar: parse `input` once and fire the compiled visitors in
  186. * source order. When `writer` is given it is also printing — after each node's
  187. * visitors have run and its children are printed, the grammar fires the node
  188. * printer into `writer`; a single parse with no re-tokenizing.
  189. * @template TPath
  190. * @template TNode
  191. * @template TProcessOptions
  192. * @template [TPrintOptions=object]
  193. * @typedef {(input: string, visitors: CompiledVisitorMap<TPath>, writer: PrintContext<TPath, TNode, TPrintOptions> | undefined, options: TProcessOptions) => void} Grammar
  194. */
  195. /**
  196. * The per-node output store handed to a language node printer as its `writer`. It
  197. * carries the print `options` (today just `mode`) and one map: a finished node ->
  198. * its printed text. A printer *returns* its text (`printNode` stores it) and reads
  199. * a child's with `get`, so a parent composes its own text from its children's —
  200. * the map is what makes that composition (and the CSS value transforms built on
  201. * it) possible. `take` flushes a finished top-level node into the output and drops
  202. * the map, so a streaming grammar never holds more than one top-level subtree.
  203. * @template TPath
  204. * @template TNode
  205. * @template [TPrintOptions=object]
  206. */
  207. class PrintContext {
  208. /**
  209. * @param {PrintOptions & TPrintOptions} options the print options
  210. * @param {NodePrinter<TPath, TNode, TPrintOptions>} printer the node printer
  211. */
  212. constructor(options, printer) {
  213. /** @type {PrintOptions & TPrintOptions} */
  214. this.options = options;
  215. /** @type {NodePrinter<TPath, TNode, TPrintOptions>} */
  216. this._printer = printer;
  217. // Printed text by node handle, in two columns rather than one `Map`: the
  218. // handles are dense integers, and a stylesheet prints ~300k nodes through
  219. // a store that never holds more than a few dozen of them at once.
  220. /** @type {string[]} each finished node's printed text */
  221. this._storeText = [];
  222. /** @type {Int32Array} the epoch each slot was written in */
  223. this._storeGen = new Int32Array(0);
  224. /** @type {number} current epoch; a slot from an older one reads as absent */
  225. this._gen = 1;
  226. // Output accumulates into `_tail`, a plain string append, so printing that
  227. // never takes anything back costs exactly what appending to one string
  228. // costs. A piece is cut off into `_chunks` only for text that may still be
  229. // taken back ({@link emitRetractable}) — that is what lets a printer emit
  230. // before it knows a later sibling overrides it, without every other emit
  231. // paying for the ability. The output is `_chunks` joined, then `_tail`.
  232. /** @type {string[]} pieces already cut off, in output order */
  233. this._chunks = [];
  234. /** @type {string} output after the last cut piece */
  235. this._tail = "";
  236. /** @type {number} characters in `_tail`, so its length is not read off a rope */
  237. this._tailLength = 0;
  238. /** @type {number} scratch for the flattening read in {@link _cutTail} */
  239. this._flattened = 0;
  240. /** @type {[number, number, number, number][]} `[chunkIndex, offsetInChunk, srcLine, srcCol]`, output-ordered; a `srcLine` of -1 is one {@link retract} took back with its piece */
  241. this._mappings = [];
  242. /** @type {Map<number, number> | null} a retractable piece -> the mapping anchored to it, so taking the piece back takes its mapping too */
  243. this._retractableMappings = null;
  244. // A map is built only for a caller that named the input, and that is known
  245. // before printing — so a print nobody asks a map of collects none.
  246. /** @type {boolean} whether {@link sourceMap} can still be asked for one */
  247. this.mapWanted =
  248. /** @type {{ source?: string }} */ (options).source !== undefined;
  249. /** @type {[number, string][]} source-anchored literals to keep (comments), source-ordered */
  250. this._inserts = [];
  251. /** @type {number} next `_inserts` entry not yet flushed */
  252. this._insertIdx = 0;
  253. // Openers of the nodes being printed in pieces, innermost last, held back
  254. // until one of them turns out to have content: a node whose children all
  255. // print to nothing can then still be dropped whole, which is the one thing
  256. // emitting an opener eagerly would give up.
  257. /** @type {string[]} openers not yet emitted, outermost first */
  258. this._pending = [];
  259. /** @type {[number | undefined, number | undefined, number | undefined] | null} anchor for the first pending opener */
  260. this._pendingAnchor = null;
  261. }
  262. /**
  263. * Hold `text` back as the opener of a node being printed in pieces. Nothing
  264. * reaches the output until {@link flushPending}, so {@link dropPending} can
  265. * still take it back if the node turns out to be empty.
  266. * @param {string} text the node's opener
  267. * @returns {number} the opener's depth, for {@link isPending}
  268. */
  269. pushPending(text) {
  270. return this._pending.push(text) - 1;
  271. }
  272. /**
  273. * Rewrite a held-back opener — an opener whose text depends on what turns out
  274. * to follow it can only be settled once something does.
  275. * @param {number} depth the opener's depth, from {@link pushPending}
  276. * @param {string} text its opener
  277. */
  278. setPending(depth, text) {
  279. this._pending[depth] = text;
  280. }
  281. /**
  282. * Emit every held-back opener, outermost first — something inside the
  283. * innermost one has content, so all of them do.
  284. * @returns {void}
  285. */
  286. flushPending() {
  287. const pending = this._pending;
  288. if (pending.length === 0) return;
  289. const anchor = this._pendingAnchor;
  290. if (anchor !== null) {
  291. this._pendingAnchor = null;
  292. this.anchor(anchor[0], anchor[1], anchor[2]);
  293. }
  294. for (let i = 0; i < pending.length; i++) this._emit(pending[i]);
  295. pending.length = 0;
  296. }
  297. /**
  298. * Anchor the outermost held-back opener, applied when it is flushed. Openers
  299. * are flushed together, so only the outermost carries one.
  300. * @param {number=} srcOffset the node's source offset (kept-comment flush boundary)
  301. * @param {number=} srcLine 0-based source line of the node's start
  302. * @param {number=} srcCol 0-based source column of the node's start
  303. */
  304. anchorPending(srcOffset, srcLine, srcCol) {
  305. this._pendingAnchor = [srcOffset, srcLine, srcCol];
  306. }
  307. /**
  308. * @param {number} depth an opener's depth, from {@link pushPending}
  309. * @returns {boolean} whether it is still held back (nothing inside it printed)
  310. */
  311. isPending(depth) {
  312. return this._pending.length > depth;
  313. }
  314. /**
  315. * Drop the innermost held-back opener — its node printed to nothing.
  316. * @returns {void}
  317. */
  318. dropPending() {
  319. this._pending.pop();
  320. const anchor = this._pendingAnchor;
  321. if (this._pending.length !== 0 || anchor === null) return;
  322. // The node printed to nothing, but it was still a node here: {@link take}
  323. // anchors before it can know the text is empty, and the map has to read the
  324. // same either way. The kept comments before it are due for the same reason.
  325. this._pendingAnchor = null;
  326. this.anchor(anchor[0], anchor[1], anchor[2]);
  327. }
  328. /**
  329. * Close off what has been emitted so far and return the index the next piece
  330. * will take. Cutting is the point: a caller bounding a later edit by this
  331. * (see {@link dropTrailing}) must not be able to reach output from before it,
  332. * which a mark taken while the tail was still open would sit in the middle of.
  333. * @returns {number} the index the next emitted piece will get
  334. */
  335. markCut() {
  336. this._cutTail();
  337. return this._chunks.length;
  338. }
  339. /**
  340. * Append a piece of a node that is being printed in pieces. Nothing takes it
  341. * back, so it only extends the output.
  342. * @param {string} text output text
  343. */
  344. emitStreamed(text) {
  345. this._emit(text);
  346. }
  347. /**
  348. * Widen both columns to hold `need` handles, keeping what they already hold.
  349. * @param {number} need required capacity
  350. */
  351. _growStore(need) {
  352. // From empty: a context that prints one inline `style=""` must not pay for
  353. // a stylesheet's worth of columns.
  354. const size = Math.max(need, this._storeText.length * 2, 16);
  355. // Appended, which keeps the column packed for the read every composed child
  356. // makes, and carries what it already holds across in the same pass.
  357. const held = this._storeText;
  358. const text = [];
  359. for (let i = 0; i < held.length; i++) text.push(held[i]);
  360. for (let i = held.length; i < size; i++) text.push("");
  361. const gen = new Int32Array(size);
  362. gen.set(this._storeGen);
  363. this._storeText = text;
  364. this._storeGen = gen;
  365. }
  366. /**
  367. * Forget every node's printed text. A node printed in pieces does this once
  368. * each child is emitted, so the store never holds more than one of them —
  369. * which is also what keeps a recycled node id from reading as an earlier
  370. * node's text.
  371. */
  372. dropStore() {
  373. // Bumping the epoch invalidates every slot at once, so this stays O(1) —
  374. // it runs per node, and clearing a column by length would not.
  375. this._gen++;
  376. }
  377. /**
  378. * Drop trailing `charCode`s from the end of the output at or after `from` —
  379. * the separator the piece before a terminator no longer needs. Walks back over
  380. * pieces emptied by {@link retract}, so it sees what the output reads as.
  381. * @param {number} from earliest piece index this may touch
  382. * @param {number} charCode the character to drop
  383. */
  384. dropTrailing(from, charCode) {
  385. const chunks = this._chunks;
  386. for (let i = chunks.length; i >= from; i--) {
  387. let text = i === chunks.length ? this._tail : chunks[i];
  388. if (text.length === 0) continue;
  389. while (
  390. text.length !== 0 &&
  391. text.charCodeAt(text.length - 1) === charCode
  392. ) {
  393. text = text.slice(0, -1);
  394. }
  395. if (i === chunks.length) {
  396. this._tail = text;
  397. this._tailLength = text.length;
  398. } else {
  399. chunks[i] = text;
  400. }
  401. if (text.length !== 0) return;
  402. }
  403. }
  404. /**
  405. * Run the node printer for `node` and store what it returns (the grammar calls
  406. * this once the node's visitors and children are done). `path` is on `node`.
  407. * @param {TNode} node the finished node
  408. * @param {TPath} path the language accessor, positioned on `node`
  409. */
  410. printNode(node, path) {
  411. const text = this._printer(path, this);
  412. const n = /** @type {EXPECTED_ANY} */ (node);
  413. if (n >= this._storeText.length) this._growStore(n + 1);
  414. this._storeText[n] = text;
  415. this._storeGen[n] = this._gen;
  416. }
  417. /**
  418. * Print one node and hand its text straight back — for a node inside one being
  419. * printed in pieces, whose text is emitted as it is printed, so the store
  420. * would only ever be written and read once.
  421. * @param {TPath} path path positioned on the node
  422. * @returns {string} its printed text
  423. */
  424. printPiece(path) {
  425. return this._printer(path, this);
  426. }
  427. /**
  428. * @param {TNode} node a child node whose printer already ran
  429. * @returns {string} its printed text
  430. */
  431. get(node) {
  432. const n = /** @type {EXPECTED_ANY} */ (node);
  433. return /** @type {string} */ (
  434. this._storeGen[n] === this._gen ? this._storeText[n] : undefined
  435. );
  436. }
  437. /**
  438. * Append `text` to the output. Where in the output that lands is worked out
  439. * only if a source map is asked for (see {@link sourceMap}), so printing
  440. * without one never walks the text looking for newlines.
  441. * @param {string} text output text
  442. */
  443. _emit(text) {
  444. this._tail += text;
  445. this._tailLength += text.length;
  446. // A printer returns its text as a rope of its children's pieces, and
  447. // appending a rope onto a rope keeps every fragment of every node reachable
  448. // until the output is finally flattened — on a stylesheet of many top-level
  449. // rules, a second copy of the whole output. So cut the tail off once it has
  450. // grown past a block and force that block flat, which drops every fragment
  451. // behind it. Per block rather than per piece: the flattening copy is one
  452. // the result would have paid for anyway, but paying it per piece means an
  453. // allocation for each, and the pieces are small.
  454. if (this._tailLength >= FLATTEN_BLOCK) this._cutTail();
  455. }
  456. /**
  457. * Move the accumulated tail into the finished pieces, flattened.
  458. */
  459. _cutTail() {
  460. // A run of retractable pieces cuts after each; an empty piece would only pad
  461. // the output, and an anchor into an empty tail already points past it.
  462. if (this._tailLength === 0) return;
  463. // Reading a character is what forces it flat; the value is not wanted, and
  464. // it is kept only so the read cannot be optimized away.
  465. this._flattened = this._tail.charCodeAt(0);
  466. this._chunks.push(this._tail);
  467. this._tail = "";
  468. this._tailLength = 0;
  469. }
  470. /**
  471. * Append `text` as a piece of its own, so {@link retract} can still take it
  472. * back once a later sibling turns out to override it. Cuts the accumulating
  473. * tail off in front of it, so it is for the text that may actually be taken
  474. * back and not for output at large.
  475. * @param {string} text output text
  476. * @returns {number} the piece's index, for {@link retract}
  477. */
  478. emitRetractable(text) {
  479. this._cutTail();
  480. return this._chunks.push(text) - 1;
  481. }
  482. /**
  483. * Take back an already-emitted piece — the printer has since found that a
  484. * later one overrides it. Pieces after it keep their place. A piece emitted by
  485. * {@link takeRetractable} takes the mapping anchored to it back as well; any
  486. * other anchor into the piece would be left pointing at what follows it, so
  487. * this must not be used on one (see {@link take}).
  488. * @param {number} index the piece's index, from {@link emitRetractable}
  489. */
  490. retract(index) {
  491. this._chunks[index] = "";
  492. if (this._retractableMappings === null) return;
  493. const mapping = this._retractableMappings.get(index);
  494. if (mapping !== undefined) this._mappings[mapping][2] = -1;
  495. }
  496. /**
  497. * Fold `text` into an already-emitted piece, in front of the character it ends
  498. * with — a later sibling whose body belongs inside that piece, as a repeated
  499. * named `@layer` block's does. Mappings are piece-relative and this only grows
  500. * the piece past everything already anchored in it, so they keep their
  501. * positions; the folded text carries none of its own.
  502. * @param {number} index the piece's index, from {@link emitRetractable}
  503. * @param {string} text what to fold in, its own closer included
  504. */
  505. foldIntoRetractable(index, text) {
  506. const piece = this._chunks[index];
  507. this._chunks[index] = `${piece.slice(0, -1)}${text}`;
  508. }
  509. /**
  510. * Write an already-emitted piece again, shorter — a rule inside it a later
  511. * copy makes dead. Mappings anchored in the piece keep the offsets they were
  512. * given, so one inside what was cut names what now follows it.
  513. * @param {number} index the piece's index, from {@link emitRetractable}
  514. * @param {string} text what the piece says now
  515. */
  516. rewriteRetractable(index, text) {
  517. this._chunks[index] = text;
  518. }
  519. /**
  520. * {@link take} for a top-level node a later sibling may still make dead — the
  521. * unprefixed twin of a vendor-prefixed rule, which can stand anywhere after
  522. * it. The node is emitted as a piece of its own, with its mapping recorded
  523. * against that piece, so {@link retract} takes both back.
  524. * @param {TNode} node the top-level node
  525. * @param {number=} srcOffset the node's source offset (kept-comment flush boundary)
  526. * @param {number=} srcLine 0-based source line of the node's start
  527. * @param {number=} srcCol 0-based source column of the node's start
  528. * @param {string=} text what to emit instead of the node's own printed text
  529. * @returns {number} the piece's index, for {@link retract}
  530. */
  531. takeRetractable(node, srcOffset, srcLine, srcCol, text) {
  532. const before = this._mappings.length;
  533. this.anchor(srcOffset, srcLine, srcCol);
  534. const mapping = this._mappings.length > before ? before : -1;
  535. const at = this.emitRetractable(text === undefined ? this.get(node) : text);
  536. this._gen++;
  537. if (mapping !== -1) {
  538. if (this._retractableMappings === null) {
  539. this._retractableMappings = new Map();
  540. }
  541. this._retractableMappings.set(at, mapping);
  542. }
  543. return at;
  544. }
  545. /**
  546. * Emit one finished top-level node: first any kept comments that precede it,
  547. * then a source mapping anchoring its output start to `[srcLine, srcCol]`, then
  548. * its text — and drop the per-node store so the next top-level node starts
  549. * clean. `srcOffset` / `srcLine` / `srcCol` are the node's source position; a
  550. * grammar that doesn't map positions (e.g. HTML) omits them (no comments, no
  551. * mapping — the map ends up empty).
  552. * @param {TNode | undefined} node the top-level node, or undefined where `text` is given and no node is the whole of it
  553. * @param {number=} srcOffset the node's source offset (kept-comment flush boundary)
  554. * @param {number=} srcLine 0-based source line of the node's start
  555. * @param {number=} srcCol 0-based source column of the node's start
  556. * @param {string=} text what to emit instead of the node's own printed text,
  557. * for a printer that folded a later node into this one
  558. */
  559. take(node, srcOffset, srcLine, srcCol, text) {
  560. this.anchor(srcOffset, srcLine, srcCol);
  561. this._emit(
  562. text === undefined ? this.get(/** @type {TNode} */ (node)) : text
  563. );
  564. this._gen++;
  565. }
  566. /**
  567. * Tie whatever is emitted next to a source position: flush the kept comments
  568. * that precede it, then record the mapping. Split out of {@link take} for a
  569. * node printed in pieces, whose first piece is emitted well after the printer
  570. * for it began.
  571. * @param {number=} srcOffset the node's source offset (kept-comment flush boundary)
  572. * @param {number=} srcLine 0-based source line of the node's start
  573. * @param {number=} srcCol 0-based source column of the node's start
  574. */
  575. anchor(srcOffset, srcLine, srcCol) {
  576. if (srcOffset !== undefined && this._insertIdx < this._inserts.length) {
  577. this._flushBefore(srcOffset);
  578. }
  579. if (srcLine !== undefined && this.mapWanted) {
  580. // `_chunks.length` is where the tail will land once it is cut off, so
  581. // this stays right whether or not a piece is cut after it.
  582. this._mappings.push([
  583. this._chunks.length,
  584. this._tailLength,
  585. srcLine,
  586. /** @type {number} */ (srcCol)
  587. ]);
  588. }
  589. }
  590. /**
  591. * Whether a kept literal is queued to land before `pos`. A printer folding two
  592. * top-level nodes together asks first: the literal belongs between them, so
  593. * the fold would move it past what it was written above.
  594. * @param {number} pos source offset
  595. * @returns {boolean} true if a kept literal lands before `pos`
  596. */
  597. hasInsertBefore(pos) {
  598. return (
  599. this._insertIdx < this._inserts.length &&
  600. this._inserts[this._insertIdx][0] < pos
  601. );
  602. }
  603. /**
  604. * Emit the kept literals landing before `pos`. A printer holding a node back
  605. * calls this at hold time, so what was written above it is still emitted
  606. * above it — and {@link hasInsertBefore} then answers about the gap to the
  607. * next node rather than the whole span since the last node taken.
  608. * @param {number} pos source offset to flush up to
  609. * @returns {void}
  610. */
  611. flushInsertsBefore(pos) {
  612. if (this._insertIdx < this._inserts.length) this._flushBefore(pos);
  613. }
  614. /**
  615. * Queue a literal to carry through to the output at source offset `pos` — a
  616. * comment the printer chose to keep (e.g. a `/*!` license banner). Calls
  617. * arrive in source order; each lands just before the first top-level node
  618. * starting after `pos` (or at the end, via {@link result}).
  619. * @param {number} pos source offset the literal sits before
  620. * @param {string} text the literal text
  621. */
  622. insert(pos, text) {
  623. this._inserts.push([pos, text]);
  624. }
  625. /**
  626. * Take the queued inserts sitting in `[start, end)` — a printer that writes
  627. * that source range out itself places them, so the writer must not flush
  628. * them ahead of the next top-level node as well.
  629. * @param {number} start first source offset of the range
  630. * @param {number} end offset past its last
  631. * @returns {string} their text, in source order
  632. */
  633. takeInserts(start, end) {
  634. const inserts = this._inserts;
  635. // `_insertIdx` only moves on a flush, so walking from it would scan every
  636. // insert the enclosing rule queued before this range again — quadratic over
  637. // a rule's declarations. They arrive in source order, so binary search in.
  638. let low = this._insertIdx;
  639. let high = inserts.length;
  640. while (low < high) {
  641. const middle = (low + high) >> 1;
  642. if (inserts[middle][0] < start) low = middle + 1;
  643. else high = middle;
  644. }
  645. let out = "";
  646. for (let i = low; i < inserts.length && inserts[i][0] < end; i++) {
  647. out += inserts[i][1];
  648. inserts[i][1] = "";
  649. }
  650. return out;
  651. }
  652. /**
  653. * Emit every queued insert positioned before source offset `pos` (all of them
  654. * for `Infinity`), in source order, so kept comments keep their place relative
  655. * to the rules.
  656. * @param {number} pos source offset to flush up to
  657. */
  658. _flushBefore(pos) {
  659. const inserts = this._inserts;
  660. let i = this._insertIdx;
  661. while (i < inserts.length && inserts[i][0] < pos) {
  662. if (inserts[i][1] !== "") this._emit(inserts[i][1]);
  663. i++;
  664. }
  665. this._insertIdx = i;
  666. }
  667. /**
  668. * @param {SourceMapOptions} options the input's name / content
  669. * @returns {SourceMap} the input->output source map
  670. */
  671. sourceMap(options) {
  672. let out = "";
  673. let genLine = 0;
  674. let genCol = 0;
  675. let srcLine = 0;
  676. let srcCol = 0;
  677. let atLineStart = true;
  678. // Where each anchor landed, walked once alongside the mappings — both are
  679. // in output order, so this is one pass over the output, not a position
  680. // recomputed per mapping. An anchor can sit inside a piece rather than at
  681. // its start, since output accumulates into the piece it is appending to.
  682. const chunks = this._chunks;
  683. const tail = this._tail;
  684. /**
  685. * @param {number} i piece index, `chunks.length` being the tail
  686. * @returns {string} that piece
  687. */
  688. const pieceAt = (i) => (i === chunks.length ? tail : chunks[i]);
  689. let atChunk = 0;
  690. let atOffset = 0;
  691. let atLine = 0;
  692. let atCol = 0;
  693. // Offset of the next newline at or after the cursor, `-1` for none left in
  694. // this piece. Carried rather than searched for per mapping: `indexOf` has no
  695. // end bound, so asking it again for each of a piece's mappings scans again to
  696. // the end of the piece every time — and minified output, the case with no
  697. // newlines at all, is the one where that is the whole piece.
  698. let nextNewline = pieceAt(0).indexOf("\n");
  699. /**
  700. * @param {string} text the piece to walk
  701. * @param {number} to offset to walk to
  702. */
  703. const advanceOver = (text, to) => {
  704. while (nextNewline !== -1 && nextNewline < to) {
  705. atLine++;
  706. atCol = 0;
  707. atOffset = nextNewline + 1;
  708. nextNewline = text.indexOf("\n", atOffset);
  709. }
  710. atCol += to - atOffset;
  711. atOffset = to;
  712. };
  713. /**
  714. * @param {number} chunk piece index to advance to
  715. * @param {number} offset offset within it
  716. */
  717. const advanceTo = (chunk, offset) => {
  718. while (atChunk < chunk) {
  719. const text = pieceAt(atChunk);
  720. advanceOver(text, text.length);
  721. atChunk++;
  722. atOffset = 0;
  723. nextNewline = pieceAt(atChunk).indexOf("\n");
  724. }
  725. if (offset > atOffset) advanceOver(pieceAt(atChunk), offset);
  726. };
  727. for (const [chunkIndex, chunkOffset, sl, sc] of this._mappings) {
  728. // Its piece was taken back, so there is no output left to anchor.
  729. if (sl === -1) continue;
  730. advanceTo(chunkIndex, chunkOffset);
  731. const gl = atLine;
  732. const gc = atCol;
  733. while (genLine < gl) {
  734. out += ";";
  735. genLine++;
  736. genCol = 0;
  737. atLineStart = true;
  738. }
  739. if (!atLineStart) out += ",";
  740. atLineStart = false;
  741. // Single source, so the source-index delta is always 0 (`_vlq(0)`).
  742. out +=
  743. _vlq(gc - genCol) + _vlq(0) + _vlq(sl - srcLine) + _vlq(sc - srcCol);
  744. genCol = gc;
  745. srcLine = sl;
  746. srcCol = sc;
  747. }
  748. return _makeMap(options, out);
  749. }
  750. /**
  751. * Stand the text `resolve` gives back in place of every deferred write left
  752. * in the output, and move the mappings that follow one in the same piece by
  753. * what its text changed in length. Runs before {@link result} and
  754. * {@link sourceMap}, so both read the substituted output rather than
  755. * correcting for it.
  756. *
  757. * The marker is `NUL id NUL`. A NUL mostly cannot reach printed output on its
  758. * own account — both preprocessors turn one into U+FFFD — but an RCDATA
  759. * element and an attribute value keep the one they were written with, so a
  760. * pair of them is read as a write only where what stands between spells an id
  761. * this answers for. Anything else is the source's own text and is left alone.
  762. * @param {(id: number) => string | undefined} resolve the final text for a deferred write, or undefined where it answers for no such write
  763. * @param {string} infix what this print marked its writes with
  764. * @returns {void}
  765. */
  766. substitute(resolve, infix) {
  767. const chunks = this._chunks;
  768. const mappings = this._mappings;
  769. // Both pieces and mappings are output-ordered, so one cursor walks them
  770. // together rather than searching the mappings per piece.
  771. let at = 0;
  772. for (let i = 0; i <= chunks.length; i++) {
  773. const text = i === chunks.length ? this._tail : chunks[i];
  774. while (at < mappings.length && mappings[at][0] < i) at++;
  775. let start = text.indexOf(DEFERRED_MARKER);
  776. if (start === -1) continue;
  777. let out = "";
  778. let read = 0;
  779. let delta = 0;
  780. /** @type {[number, number][]} `[offset past the marker, delta so far]` */
  781. const shifts = [];
  782. while (start !== -1) {
  783. const write = readDeferredWrite(text, start, resolve, infix);
  784. // Not a write of ours — a NUL the source carried. Left where it is,
  785. // and the scan goes on past it so a real write behind it still lands.
  786. if (write === null) {
  787. start = text.indexOf(DEFERRED_MARKER, start + 1);
  788. continue;
  789. }
  790. const { end } = write;
  791. const replacement = expandDeferredWrites(
  792. write.replacement,
  793. resolve,
  794. infix
  795. );
  796. out += text.slice(read, start) + replacement;
  797. read = end + 1;
  798. delta += replacement.length - (read - start);
  799. shifts.push([read, delta]);
  800. start = text.indexOf(DEFERRED_MARKER, read);
  801. }
  802. out += text.slice(read);
  803. if (i === chunks.length) {
  804. this._tail = out;
  805. this._tailLength = out.length;
  806. } else {
  807. chunks[i] = out;
  808. }
  809. let s = 0;
  810. for (let k = at; k < mappings.length && mappings[k][0] === i; k++) {
  811. while (s < shifts.length && shifts[s][0] <= mappings[k][1]) s++;
  812. if (s !== 0) mappings[k][1] += shifts[s - 1][1];
  813. }
  814. }
  815. }
  816. /**
  817. * @returns {string} the printed output
  818. */
  819. /**
  820. * Throw away everything printed and stand `text` in its place. For the one
  821. * caller that can only tell its output is wrong once it has all of it.
  822. * @param {string} text the output to keep instead
  823. */
  824. replaceAll(text) {
  825. this._chunks.length = 0;
  826. this._tail = text;
  827. this._tailLength = text.length;
  828. this._mappings.length = 0;
  829. this._inserts.length = 0;
  830. this._insertIdx = 0;
  831. this._pending.length = 0;
  832. this._pendingAnchor = null;
  833. }
  834. result() {
  835. // Trailing kept comments (after the last top-level node) flush here.
  836. if (this._insertIdx < this._inserts.length) this._flushBefore(Infinity);
  837. // Nothing was ever cut into its own piece, so there is nothing to join.
  838. const chunks = this._chunks;
  839. return chunks.length === 0 ? this._tail : chunks.join("") + this._tail;
  840. }
  841. }
  842. /**
  843. * Visitor coordinator: owns the visitor registry and drives a language
  844. * `grammar` over the source. Language-agnostic — each syntax (CSS, HTML, …)
  845. * binds its own grammar, node-type enum and (optionally) node printer.
  846. * Babel-style usage:
  847. *
  848. * ```
  849. * processor.use({ [NodeType.X]: (path) => {}, [NodeType.Y]: { enter, exit } });
  850. * processor.process(source);
  851. * ```
  852. * @template TPath
  853. * @template TNode
  854. * @template [TProcessOptions=object]
  855. * @template [TPrintOptions=object]
  856. */
  857. class SourceProcessor {
  858. /**
  859. * @param {Grammar<TPath, TNode, TProcessOptions, TPrintOptions>} grammar the grammar to drive over the source
  860. * @param {NodePrinter<TPath, TNode, TPrintOptions>=} printer the node printer, fired per node once its visitors and children are done; the same `path` a visitor gets plus the print context as its writer. Required to print (e.g. `mode`); a future API can let a developer supply their own
  861. */
  862. constructor(grammar, printer) {
  863. /** @type {Grammar<TPath, TNode, TProcessOptions, TPrintOptions>} */
  864. this._grammar = grammar;
  865. /** @type {CompiledVisitorMap<TPath>} */
  866. this._visitors = [];
  867. /** @type {NodePrinter<TPath, TNode, TPrintOptions> | undefined} */
  868. this._printer = printer;
  869. // Set for the one print {@link processDeferred} holds open, so `process`
  870. // hands it the context instead of building the output straight away.
  871. /** @type {((ctx: PrintContext<TPath, TNode, TPrintOptions>, build: () => { code: string, map: SourceMap | undefined }) => void) | null} */
  872. this._deferred = null;
  873. }
  874. /**
  875. * Register a Babel-style visitor map; calls accumulate per node type.
  876. * A bucket is a function (= `{ enter }`) or `{ enter?, exit? }`.
  877. * @param {VisitorMap<TPath>} map visitor map keyed by node type
  878. * @returns {SourceProcessor<TPath, TNode, TProcessOptions, TPrintOptions>} `this`, for chaining
  879. */
  880. use(map) {
  881. // `map`'s keys are node-type enum members; `Object.keys` stringifies them,
  882. // so index the compiled array by the number to match the numeric `node.type`.
  883. for (const type of Object.keys(map)) {
  884. const key = Number(type);
  885. const v = map[key];
  886. let bucket = this._visitors[key];
  887. if (!bucket) {
  888. bucket = { enter: [], exit: [] };
  889. this._visitors[key] = bucket;
  890. }
  891. if (typeof v === "function") {
  892. bucket.enter.push(v);
  893. } else {
  894. if (v.enter) bucket.enter.push(v.enter);
  895. if (v.exit) bucket.exit.push(v.exit);
  896. }
  897. }
  898. return this;
  899. }
  900. /**
  901. * Parse `input` once and fire the visitors in source order. Asking for output
  902. * — `mode`, the one thing that names it — makes the same walk print, given a
  903. * printer supplied at construction: a
  904. * {@link PrintContext} is created, each node's printer fires into it as the node
  905. * finishes, and the result is returned as `{ code, map }`: the serialized output
  906. * and, for a caller that named its input with `source` / `content`, the
  907. * input->output source map — `map` is `undefined` without one. Asking for
  908. * none of it only walks and returns `undefined`. A single parse — printing
  909. * never re-parses; all configuration is per-call.
  910. * @overload
  911. * @param {string} input the source
  912. * @param {TProcessOptions & { mode: PrintOptions["mode"] } & { source: string, content?: string }} options process options, naming the input so a map is built
  913. * @returns {{ code: string, map: SourceMap }} the output and its map
  914. */
  915. /**
  916. * @overload
  917. * @param {string} input the source
  918. * @param {TProcessOptions & { mode: PrintOptions["mode"] }} options process options, asking for output but no map
  919. * @returns {{ code: string, map: undefined }} the output alone
  920. */
  921. /**
  922. * @overload
  923. * @param {string} input the source
  924. * @param {TProcessOptions=} options process options, asking for no output
  925. * @returns {undefined} nothing — the walk alone
  926. */
  927. /**
  928. * @param {string} input source text
  929. * @param {TProcessOptions=} options grammar-specific options (`skip`, …) plus `mode` and, for the map, `source` / `content`
  930. * @returns {EXPECTED_ANY} `{ code, map }` when printing, else `undefined` — see the overloads
  931. */
  932. process(input, options) {
  933. const opts = options || /** @type {TProcessOptions} */ ({});
  934. const asked = /** @type {{ mode?: PrintOptions["mode"] }} */ (opts).mode;
  935. if (this._printer === undefined || asked === undefined) {
  936. this._grammar(input, this._visitors, undefined, opts);
  937. return undefined;
  938. }
  939. // Handed over whole rather than copied entry by entry: which of them are
  940. // print options is the language's to say, and listing them here is what
  941. // made every new one an edit to this file. A grammar-only entry riding
  942. // along is inert — a printer reads only what its own type declares.
  943. const ctx = new PrintContext(
  944. /** @type {PrintOptions & TPrintOptions} */ (
  945. /** @type {unknown} */ ({ ...opts, mode: asked })
  946. ),
  947. /** @type {NodePrinter<TPath, TNode, TPrintOptions>} */ (this._printer)
  948. );
  949. this._grammar(input, this._visitors, ctx, opts);
  950. const name = /** @type {{ source?: string, content?: string }} */ (opts);
  951. const build = () => ({
  952. code: ctx.result(),
  953. // Built only for a caller that named the input: building one walks the
  954. // whole output for its line breaks, and the minifier prints an inline
  955. // `style=""` — which asks for no map — thousands of times a document.
  956. map:
  957. name.source === undefined
  958. ? undefined
  959. : ctx.sourceMap({ source: name.source, content: name.content })
  960. });
  961. if (this._deferred !== null) {
  962. const finish = this._deferred;
  963. this._deferred = null;
  964. finish(ctx, build);
  965. return undefined;
  966. }
  967. return build();
  968. }
  969. /**
  970. * {@link process}, for a caller whose renderer answers asynchronously. Code
  971. * generation is synchronous — a printer returns its node's text, it cannot
  972. * await one — so the walk leaves a marker where each answer goes, they are
  973. * asked for together, and {@link PrintContext.substitute} stands each in its
  974. * place before the output and its map are built. One parse either way, and
  975. * the async boundary stays at the top rather than on every node.
  976. *
  977. * This is the shape `process` itself takes once it is async: how the answers
  978. * are waited for is this method's business, so nothing above it changes.
  979. * @param {string} input the source
  980. * @param {Omit<TProcessOptions, "renderEmbeddedSource"> & { mode: PrintOptions["mode"] } & { source?: string, content?: string, renderEmbeddedSource?: (source: string, hole: EXPECTED_ANY) => Promise<string | undefined> | string | undefined }} options process options; a print, so `mode` names which one, and naming the input builds a map. `renderEmbeddedSource` is the grammar's own option with a wider answer — it may answer asynchronously, and is handed each {@link DeferredWrite} whole. Absent, this is `process` with a promise around it
  981. * @returns {Promise<{ code: string, map: SourceMap | undefined }>} the output and its map
  982. */
  983. async processAsync(input, options) {
  984. const render = options.renderEmbeddedSource;
  985. // The cast, twice below: `options` is the grammar's own type with only the
  986. // renderer's answer widened, which `Omit` cannot say and nothing reads.
  987. const opts =
  988. /** @type {TProcessOptions & { mode: PrintOptions["mode"] }} */
  989. (/** @type {unknown} */ (options));
  990. if (render === undefined) {
  991. return /** @type {{ code: string, map: SourceMap | undefined }} */ (
  992. this.process(input, opts)
  993. );
  994. }
  995. /** @type {DeferredWrite[]} */
  996. const deferred = [];
  997. const { finish } = this._processDeferred(input, {
  998. ...opts,
  999. // The grammar collects into this rather than asking, and it takes
  1000. // precedence over a synchronous renderer — which is not passed on.
  1001. renderEmbeddedSource: undefined,
  1002. deferEmbeddedSource: deferred
  1003. });
  1004. // Asked together rather than one after another: they are independent, and
  1005. // a renderer that goes out to a worker pool would otherwise serialize.
  1006. const answers = await Promise.all(
  1007. deferred.map((hole) => render(hole.source, hole))
  1008. );
  1009. return finish((id) => {
  1010. const hole = deferred[id];
  1011. return hole === undefined ? undefined : hole.build(answers[id]);
  1012. });
  1013. }
  1014. /**
  1015. * Print `input` holding the deferred writes open: `finish` puts the answers
  1016. * in their place and only then builds the output and its map, so neither has
  1017. * to be corrected for what an answer changed in length. The one mechanism
  1018. * {@link processAsync} is built on, and private for that reason: a caller
  1019. * asks for an asynchronous print, not for the way one is arranged.
  1020. * @param {string} input the source
  1021. * @param {TProcessOptions & { mode: PrintOptions["mode"] } & { source?: string, content?: string }} options process options; a print, so `mode` names which one, and naming the input builds a map
  1022. * @returns {{ finish: (resolve: (id: number) => string | undefined) => { code: string, map: SourceMap | undefined } }} the deferred print
  1023. */
  1024. _processDeferred(input, options) {
  1025. /** @type {PrintContext<TPath, TNode, TPrintOptions> | undefined} */
  1026. let held;
  1027. /** @type {(() => { code: string, map: SourceMap | undefined }) | undefined} */
  1028. let heldBuild;
  1029. /** @type {NonNullable<typeof this._deferred>} */
  1030. const deferred = (ctx, build) => {
  1031. held = ctx;
  1032. heldBuild = build;
  1033. };
  1034. this._deferred = deferred;
  1035. const infix = chooseDeferredInfix(input);
  1036. // Restored rather than cleared: a nested print runs inside this one's
  1037. // `finish`, and the writes it left are still marked with this infix.
  1038. const outer = deferredInfix;
  1039. deferredInfix = infix;
  1040. try {
  1041. this.process(input, options);
  1042. } finally {
  1043. deferredInfix = outer;
  1044. // A throw never reaches where `process` clears this, and the next print
  1045. // on this processor would take it for its own. Only ours: a nested print
  1046. // during the walk may have left a newer one.
  1047. if (this._deferred === deferred) this._deferred = null;
  1048. }
  1049. return {
  1050. finish: (resolve) => {
  1051. /** @type {PrintContext<TPath, TNode, TPrintOptions>} */
  1052. (held).substitute(resolve, infix);
  1053. return /** @type {() => { code: string, map: SourceMap | undefined }} */ (
  1054. heldBuild
  1055. )();
  1056. }
  1057. };
  1058. }
  1059. }
  1060. SourceProcessor.PrintContext = PrintContext;
  1061. module.exports = SourceProcessor;
  1062. module.exports.deferredWrite = deferredWrite;