| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- "use strict";
- /**
- * @callback MappingsSerializer
- * @param {number} generatedLine generated line
- * @param {number} generatedColumn generated column
- * @param {number} sourceIndex source index
- * @param {number} originalLine original line
- * @param {number} originalColumn generated line
- * @param {number} nameIndex generated line
- * @returns {string} result
- */
- /**
- * A push-based serializer: `add()` appends one mapping to an internal
- * byte buffer, `finish()` materialises the whole `mappings` string in a
- * single allocation. Compared to the string-returning
- * {@link MappingsSerializer} this avoids every per-mapping intermediate
- * string (each VLQ digit concatenation) plus the caller-side
- * `mappings += str` cons chain — together the dominant allocation site
- * of `map()` / `sourceAndMap()`.
- * @typedef {object} MappingsWriter
- * @property {(generatedLine: number, generatedColumn: number, sourceIndex: number, originalLine: number, originalColumn: number, nameIndex: number) => void} add append one mapping
- * @property {() => string} finish materialise the mappings string
- */
- const ALPHABET = [
- ..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
- ];
- // Char codes of ALPHABET for the buffer-writing path.
- const ALPHABET_CODES = new Uint8Array(64);
- for (let i = 0; i < 64; i++) ALPHABET_CODES[i] = ALPHABET[i].charCodeAt(0);
- const CH_SEMICOLON = 59; // ;
- const CH_COMMA = 44; // ,
- const CH_A = 65; // A
- const CONTINUATION_BIT = 0x20;
- /**
- * Append a VLQ-encoded signed integer to `str`. Hoisted to module scope so
- * that both serializers share a single function object and avoid allocating
- * a new closure on every call.
- * @param {string} str current string buffer
- * @param {number} value signed integer to encode
- * @returns {string} updated string buffer
- */
- const writeValue = (str, value) => {
- const sign = (value >>> 31) & 1;
- const mask = value >> 31;
- const absValue = (value + mask) ^ mask;
- let data = (absValue << 1) | sign;
- for (;;) {
- const sextet = data & 0x1f;
- data >>= 5;
- if (data === 0) {
- return str + ALPHABET[sextet];
- }
- str += ALPHABET[sextet | CONTINUATION_BIT];
- }
- };
- /**
- * Byte-buffer state shared by the writer variants. A plain object (not a
- * class) so the hot `add` closures capture it directly.
- * @returns {{ buf: Uint8Array, pos: number }} state
- */
- const createBufferState = () => ({ buf: new Uint8Array(1024), pos: 0 });
- /**
- * Ensure space for `n` more bytes.
- * @param {{ buf: Uint8Array, pos: number }} state state
- * @param {number} n bytes needed
- * @returns {Uint8Array} the (possibly grown) buffer
- */
- const ensure = (state, n) => {
- const { buf, pos } = state;
- if (pos + n <= buf.length) return buf;
- let nextLength = buf.length * 2;
- while (nextLength < pos + n) nextLength *= 2;
- const next = new Uint8Array(nextLength);
- next.set(buf);
- state.buf = next;
- return next;
- };
- /**
- * Append a VLQ-encoded signed integer to the byte buffer. The caller must
- * have reserved space already (a 32-bit value needs at most 7 sextets).
- * @param {{ buf: Uint8Array, pos: number }} state state
- * @param {number} value signed integer to encode
- * @returns {void}
- */
- const writeValueBytes = (state, value) => {
- const { buf } = state;
- let { pos } = state;
- const sign = (value >>> 31) & 1;
- const mask = value >> 31;
- const absValue = (value + mask) ^ mask;
- let data = (absValue << 1) | sign;
- for (;;) {
- const sextet = data & 0x1f;
- data >>= 5;
- if (data === 0) {
- buf[pos++] = ALPHABET_CODES[sextet];
- break;
- }
- buf[pos++] = ALPHABET_CODES[sextet | CONTINUATION_BIT];
- }
- state.pos = pos;
- };
- /**
- * @param {{ buf: Uint8Array, pos: number }} state state
- * @returns {string} the mappings accumulated so far
- */
- const bufferToString = (state) => {
- if (state.pos === 0) return "";
- // The mappings alphabet is pure ASCII, so a latin1 decode is exact and
- // needs no re-encoding pass.
- return Buffer.from(state.buf.buffer, 0, state.pos).toString("latin1");
- };
- /**
- * @returns {MappingsWriter} writer
- */
- const createFullMappingsWriter = () => {
- const state = createBufferState();
- let currentLine = 1;
- let currentColumn = 0;
- let currentSourceIndex = 0;
- let currentOriginalLine = 1;
- let currentOriginalColumn = 0;
- let currentNameIndex = 0;
- let activeMapping = false;
- let activeName = false;
- let initial = true;
- return {
- add(
- generatedLine,
- generatedColumn,
- sourceIndex,
- originalLine,
- originalColumn,
- nameIndex,
- ) {
- if (activeMapping && currentLine === generatedLine) {
- // A mapping is still active
- if (
- sourceIndex === currentSourceIndex &&
- originalLine === currentOriginalLine &&
- originalColumn === currentOriginalColumn &&
- !activeName &&
- nameIndex < 0
- ) {
- // avoid repeating the same original mapping
- return;
- }
- }
- // No mapping is active
- else if (sourceIndex < 0) {
- // avoid writing unneccessary generated mappings
- return;
- }
- // Reserve the worst case for one mapping in a single check:
- // line separators + 5 VLQ values of up to 7 sextets each.
- const lineDiff = generatedLine - currentLine;
- const buf = ensure(state, (lineDiff > 0 ? lineDiff : 1) + 35);
- if (lineDiff > 0) {
- // Consecutive lines (diff === 1) are the dominant case.
- buf[state.pos++] = CH_SEMICOLON;
- for (let i = 1; i < lineDiff; i++) buf[state.pos++] = CH_SEMICOLON;
- currentLine = generatedLine;
- currentColumn = 0;
- initial = false;
- } else if (initial) {
- initial = false;
- } else {
- buf[state.pos++] = CH_COMMA;
- }
- writeValueBytes(state, generatedColumn - currentColumn);
- currentColumn = generatedColumn;
- if (sourceIndex >= 0) {
- activeMapping = true;
- if (sourceIndex === currentSourceIndex) {
- buf[state.pos++] = CH_A;
- } else {
- writeValueBytes(state, sourceIndex - currentSourceIndex);
- currentSourceIndex = sourceIndex;
- }
- writeValueBytes(state, originalLine - currentOriginalLine);
- currentOriginalLine = originalLine;
- if (originalColumn === currentOriginalColumn) {
- buf[state.pos++] = CH_A;
- } else {
- writeValueBytes(state, originalColumn - currentOriginalColumn);
- currentOriginalColumn = originalColumn;
- }
- if (nameIndex >= 0) {
- writeValueBytes(state, nameIndex - currentNameIndex);
- currentNameIndex = nameIndex;
- activeName = true;
- } else {
- activeName = false;
- }
- } else {
- activeMapping = false;
- }
- },
- finish: () => bufferToString(state),
- };
- };
- const createFullMappingsSerializer = () => {
- let currentLine = 1;
- let currentColumn = 0;
- let currentSourceIndex = 0;
- let currentOriginalLine = 1;
- let currentOriginalColumn = 0;
- let currentNameIndex = 0;
- let activeMapping = false;
- let activeName = false;
- let initial = true;
- /** @type {MappingsSerializer} */
- return (
- generatedLine,
- generatedColumn,
- sourceIndex,
- originalLine,
- originalColumn,
- nameIndex,
- ) => {
- if (activeMapping && currentLine === generatedLine) {
- // A mapping is still active
- if (
- sourceIndex === currentSourceIndex &&
- originalLine === currentOriginalLine &&
- originalColumn === currentOriginalColumn &&
- !activeName &&
- nameIndex < 0
- ) {
- // avoid repeating the same original mapping
- return "";
- }
- }
- // No mapping is active
- else if (sourceIndex < 0) {
- // avoid writing unneccessary generated mappings
- return "";
- }
- let str;
- if (currentLine < generatedLine) {
- // Consecutive lines (diff === 1) are the dominant case; avoid the
- // `.repeat()` call entirely for them.
- str =
- generatedLine === currentLine + 1
- ? ";"
- : ";".repeat(generatedLine - currentLine);
- currentLine = generatedLine;
- currentColumn = 0;
- initial = false;
- } else if (initial) {
- str = "";
- initial = false;
- } else {
- str = ",";
- }
- str = writeValue(str, generatedColumn - currentColumn);
- currentColumn = generatedColumn;
- if (sourceIndex >= 0) {
- activeMapping = true;
- if (sourceIndex === currentSourceIndex) {
- str += "A";
- } else {
- str = writeValue(str, sourceIndex - currentSourceIndex);
- currentSourceIndex = sourceIndex;
- }
- str = writeValue(str, originalLine - currentOriginalLine);
- currentOriginalLine = originalLine;
- if (originalColumn === currentOriginalColumn) {
- str += "A";
- } else {
- str = writeValue(str, originalColumn - currentOriginalColumn);
- currentOriginalColumn = originalColumn;
- }
- if (nameIndex >= 0) {
- str = writeValue(str, nameIndex - currentNameIndex);
- currentNameIndex = nameIndex;
- activeName = true;
- } else {
- activeName = false;
- }
- } else {
- activeMapping = false;
- }
- return str;
- };
- };
- const createLinesOnlyMappingsSerializer = () => {
- let lastWrittenLine = 0;
- let currentLine = 1;
- let currentSourceIndex = 0;
- let currentOriginalLine = 1;
- /** @type {MappingsSerializer} */
- return (
- generatedLine,
- _generatedColumn,
- sourceIndex,
- originalLine,
- _originalColumn,
- _nameIndex,
- ) => {
- if (sourceIndex < 0) {
- // avoid writing generated mappings at all
- return "";
- }
- if (lastWrittenLine === generatedLine) {
- // avoid writing multiple original mappings per line
- return "";
- }
- let str;
- lastWrittenLine = generatedLine;
- if (generatedLine === currentLine + 1) {
- currentLine = generatedLine;
- if (sourceIndex === currentSourceIndex) {
- if (originalLine === currentOriginalLine + 1) {
- currentOriginalLine = originalLine;
- return ";AACA";
- }
- str = ";AA";
- str = writeValue(str, originalLine - currentOriginalLine);
- currentOriginalLine = originalLine;
- return `${str}A`;
- }
- str = ";A";
- str = writeValue(str, sourceIndex - currentSourceIndex);
- currentSourceIndex = sourceIndex;
- str = writeValue(str, originalLine - currentOriginalLine);
- currentOriginalLine = originalLine;
- return `${str}A`;
- }
- str = ";".repeat(generatedLine - currentLine);
- currentLine = generatedLine;
- if (sourceIndex === currentSourceIndex) {
- if (originalLine === currentOriginalLine + 1) {
- currentOriginalLine = originalLine;
- return `${str}AACA`;
- }
- str += "AA";
- str = writeValue(str, originalLine - currentOriginalLine);
- currentOriginalLine = originalLine;
- return `${str}A`;
- }
- str += "A";
- str = writeValue(str, sourceIndex - currentSourceIndex);
- currentSourceIndex = sourceIndex;
- str = writeValue(str, originalLine - currentOriginalLine);
- currentOriginalLine = originalLine;
- return `${str}A`;
- };
- };
- /**
- * Lines-only mappings emit at most one short — usually constant — segment
- * per generated line, so the classic string encoding plus cons-string
- * append is already optimal there (measurably faster than byte-writing).
- * Only the full serializer, which emits per token, benefits from the byte
- * buffer. The encoding below mirrors `createLinesOnlyMappingsSerializer`,
- * inlined so `add` costs a single call.
- * @returns {MappingsWriter} writer
- */
- const createLinesOnlyMappingsWriter = () => {
- let mappings = "";
- let lastWrittenLine = 0;
- let currentLine = 1;
- let currentSourceIndex = 0;
- let currentOriginalLine = 1;
- return {
- add(
- generatedLine,
- _generatedColumn,
- sourceIndex,
- originalLine,
- _originalColumn,
- _nameIndex,
- ) {
- if (sourceIndex < 0) {
- // avoid writing generated mappings at all
- return;
- }
- if (lastWrittenLine === generatedLine) {
- // avoid writing multiple original mappings per line
- return;
- }
- let str;
- lastWrittenLine = generatedLine;
- if (generatedLine === currentLine + 1) {
- currentLine = generatedLine;
- if (sourceIndex === currentSourceIndex) {
- if (originalLine === currentOriginalLine + 1) {
- currentOriginalLine = originalLine;
- mappings += ";AACA";
- return;
- }
- str = ";AA";
- str = writeValue(str, originalLine - currentOriginalLine);
- currentOriginalLine = originalLine;
- mappings += `${str}A`;
- return;
- }
- str = ";A";
- str = writeValue(str, sourceIndex - currentSourceIndex);
- currentSourceIndex = sourceIndex;
- str = writeValue(str, originalLine - currentOriginalLine);
- currentOriginalLine = originalLine;
- mappings += `${str}A`;
- return;
- }
- str = ";".repeat(generatedLine - currentLine);
- currentLine = generatedLine;
- if (sourceIndex === currentSourceIndex) {
- if (originalLine === currentOriginalLine + 1) {
- currentOriginalLine = originalLine;
- mappings += `${str}AACA`;
- return;
- }
- str += "AA";
- str = writeValue(str, originalLine - currentOriginalLine);
- currentOriginalLine = originalLine;
- mappings += `${str}A`;
- return;
- }
- str += "A";
- str = writeValue(str, sourceIndex - currentSourceIndex);
- currentSourceIndex = sourceIndex;
- str = writeValue(str, originalLine - currentOriginalLine);
- currentOriginalLine = originalLine;
- mappings += `${str}A`;
- },
- finish: () => mappings,
- };
- };
- /**
- * @param {{ columns?: boolean }=} options options
- * @returns {MappingsSerializer} mappings serializer
- */
- const createMappingsSerializer = (options) => {
- const linesOnly = options && options.columns === false;
- return linesOnly
- ? createLinesOnlyMappingsSerializer()
- : createFullMappingsSerializer();
- };
- /**
- * @param {{ columns?: boolean }=} options options
- * @returns {MappingsWriter} push-based mappings writer
- */
- const createMappingsWriter = (options) => {
- const linesOnly = options && options.columns === false;
- return linesOnly
- ? createLinesOnlyMappingsWriter()
- : createFullMappingsWriter();
- };
- module.exports = createMappingsSerializer;
- module.exports.createMappingsWriter = createMappingsWriter;
|