Source: lib/compiler/sourcemap.js

import path from 'node:path';

const VLQ_BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

/**
 * Encodes an integer value to a VLQ Base64 string.
 * @param {number} value
 * @returns {string}
 */
export function encodeVLQ(value) {
  let vlq = value < 0 ? (-value << 1) | 1 : value << 1;
  let encoded = '';
  do {
    let digit = vlq & 31;
    vlq >>>= 5;
    if (vlq > 0) {
      digit |= 32;
    }
    encoded += VLQ_BASE64_CHARS[digit];
  } while (vlq > 0);
  return encoded;
}

/**
 * Generates a Source Map v3 object mapping compiled JavaScript lines to original template source lines.
 * @param {string} filePath - File path of the source template (.html, .avx, .component.js, .page.js).
 * @param {string} originalCode - Raw template source code.
 * @param {string} compiledCode - Compiled and wrapped ES module JavaScript code.
 * @param {object} [options]
 * @param {boolean} [options.sourcesContent]
 * @returns {object} Source Map v3 compliant object.
 */
export function generateTemplateSourceMap(filePath, originalCode, compiledCode, options = {}) {
  const includeContent = options.sourcesContent !== false;
  const origLines = (originalCode || '').split(/\r?\n/);
  const genLines = (compiledCode || '').split(/\r?\n/);

  let stateLine = 0;
  const actionLines = new Map();
  const computedLines = new Map();
  const resourceLines = new Map();

  origLines.forEach((line, idx) => {
    const trimmed = line.trim();
    if (/<state\b/i.test(trimmed) && stateLine === 0) {
      stateLine = idx;
    }

    const actionMatch = line.match(/<action\s+[^>]*name=["']([\w-]+)["']/i);
    if (actionMatch) {
      actionLines.set(actionMatch[1], idx);
    }

    const computedMatch = line.match(/<computed\s+[^>]*name=["']([\w-]+)["']/i);
    if (computedMatch) {
      computedLines.set(computedMatch[1], idx);
    }

    const resourceMatch = line.match(/<resource\s+[^>]*name=["']([\w-]+)["']/i);
    if (resourceMatch) {
      resourceLines.set(resourceMatch[1], idx);
    }
  });

  const lineMappings = new Array(genLines.length).fill(null);

  genLines.forEach((genLine, gIdx) => {
    const trimmedGen = genLine.trim();

    if (trimmedGen.startsWith('constructor(') || trimmedGen.startsWith('super(')) {
      lineMappings[gIdx] = stateLine;
      return;
    }

    for (const [actionName, startLine] of actionLines.entries()) {
      if (genLine.includes(`${actionName}:`) || genLine.includes(`"${actionName}":`) || genLine.includes(`'${actionName}':`)) {
        lineMappings[gIdx] = startLine;
        return;
      }
    }

    for (const [compName, startLine] of computedLines.entries()) {
      if (genLine.includes(`${compName}:`) || genLine.includes(`"${compName}":`) || genLine.includes(`'${compName}':`)) {
        lineMappings[gIdx] = startLine;
        return;
      }
    }

    for (const [resName, startLine] of resourceLines.entries()) {
      if (genLine.includes(`${resName}:`) || genLine.includes(`"${resName}":`) || genLine.includes(`'${resName}':`)) {
        lineMappings[gIdx] = startLine;
        return;
      }
    }

    if (trimmedGen.length > 0) {
      let matchedOrigLine = -1;
      const cleanGen = trimmedGen.replace(/^`|`\s*,?$|^`|`$/g, '').trim();

      if (cleanGen.length > 0) {
        for (let oIdx = 0; oIdx < origLines.length; oIdx++) {
          const origTrimmed = origLines[oIdx].trim();
          if (
            origTrimmed.length > 0 &&
            (origTrimmed === cleanGen || origTrimmed.includes(cleanGen) || cleanGen.includes(origTrimmed))
          ) {
            matchedOrigLine = oIdx;
            break;
          }
        }
      }

      if (matchedOrigLine !== -1) {
        lineMappings[gIdx] = matchedOrigLine;
        return;
      }
    }

    if (gIdx > 0 && gIdx < genLines.length - 1) {
      lineMappings[gIdx] = stateLine;
    } else {
      lineMappings[gIdx] = 0;
    }
  });

  let mappings = '';
  let prevSourceLine = 0;
  let prevSourceCol = 0;
  let prevSourceIdx = 0;

  for (let g = 0; g < genLines.length; g++) {
    if (g > 0) {
      mappings += ';';
    }

    const origLine0 = lineMappings[g];
    if (origLine0 !== null && origLine0 !== undefined) {
      const genCol = 0;
      const sourceIdx = 0;
      const origCol0 = 0;

      const dGenCol = genCol;
      const dSourceIdx = sourceIdx - prevSourceIdx;
      const dSourceLine = origLine0 - prevSourceLine;
      const dSourceCol = origCol0 - prevSourceCol;

      mappings += encodeVLQ(dGenCol) + encodeVLQ(dSourceIdx) + encodeVLQ(dSourceLine) + encodeVLQ(dSourceCol);

      prevSourceIdx = sourceIdx;
      prevSourceLine = origLine0;
      prevSourceCol = origCol0;
    }
  }

  const fileName = path.basename(filePath);

  const mapResult = {
    version: 3,
    file: fileName,
    sources: [filePath],
    names: [],
    mappings: mappings,
  };

  if (includeContent) {
    mapResult.sourcesContent = [originalCode];
  }

  return mapResult;
}

/**
 * Builds a bundle-level v3 source map given file sections and their position in the final bundle.
 * @param {string} bundleFileName - e.g. 'bundle.js'
 * @param {number} totalBundleLines - Total lines in bundle.js
 * @param {Array<{ filePath: string, originalCode: string, startLine: number, lineCount: number }>} sections
 * @param {object} [options]
 * @param {boolean} [options.sourcesContent]
 * @returns {object} Source Map v3 object.
 */
export function composeBundleSourceMap(bundleFileName, totalBundleLines, sections = [], options = {}) {
  const includeContent = options.sourcesContent !== false;
  const sources = [];
  const sourcesContent = [];
  const sourceIndexMap = new Map();

  sections.forEach((section) => {
    if (!sourceIndexMap.has(section.filePath)) {
      sourceIndexMap.set(section.filePath, sources.length);
      sources.push(section.filePath);
      if (includeContent) {
        sourcesContent.push(section.originalCode);
      }
    }
  });

  let mappings = '';
  let prevSourceLine = 0;
  let prevSourceCol = 0;
  let prevSourceIdx = 0;

  // Build lookup for each bundle line
  const lineToSection = new Map();
  sections.forEach((sec) => {
    for (let i = 0; i < sec.lineCount; i++) {
      lineToSection.set(sec.startLine + i, {
        sourceIdx: sourceIndexMap.get(sec.filePath),
        origLine: i,
      });
    }
  });

  for (let g = 0; g < totalBundleLines; g++) {
    if (g > 0) {
      mappings += ';';
    }

    const mapping = lineToSection.get(g);
    if (mapping) {
      const dGenCol = 0;
      const dSourceIdx = mapping.sourceIdx - prevSourceIdx;
      const dSourceLine = mapping.origLine - prevSourceLine;
      const dSourceCol = 0 - prevSourceCol;

      mappings += encodeVLQ(dGenCol) + encodeVLQ(dSourceIdx) + encodeVLQ(dSourceLine) + encodeVLQ(dSourceCol);

      prevSourceIdx = mapping.sourceIdx;
      prevSourceLine = mapping.origLine;
      prevSourceCol = 0;
    }
  }

  const bundleMap = {
    version: 3,
    file: bundleFileName,
    sources,
    names: [],
    mappings,
  };

  if (includeContent) {
    bundleMap.sourcesContent = sourcesContent;
  }

  return bundleMap;
}