replace-and-update-source-map.js 3.09 KB
"use strict";

/*
  Copyright 2019 Google LLC

  Use of this source code is governed by an MIT-style
  license that can be found in the LICENSE file or at
  https://opensource.org/licenses/MIT.
*/
const {
  SourceMapConsumer,
  SourceMapGenerator
} = require('source-map');
/**
 * Adapted from https://github.com/nsams/sourcemap-aware-replace, with modern
 * JavaScript updates, along with additional properties copied from originalMap.
 *
 * @param {Object} options
 * @param {string} options.jsFilename The name for the file whose contents
 * correspond to originalSource.
 * @param {Object} options.originalMap The sourcemap for originalSource,
 * prior to any replacements.
 * @param {string} options.originalSource The source code, prior to any
 * replacements.
 * @param {string} options.replaceString A string to swap in for searchString.
 * @param {string} options.searchString A string in originalSource to replace.
 * Only the first occurrence will be replaced.
 * @return {{source: string, map: string}} An object containing both
 * originalSource with the replacement applied, and the modified originalMap.
 *
 * @private
 */


async function replaceAndUpdateSourceMap({
  jsFilename,
  originalMap,
  originalSource,
  replaceString,
  searchString
}) {
  const generator = new SourceMapGenerator({
    file: jsFilename
  });
  const consumer = await new SourceMapConsumer(originalMap);
  let pos;
  let src = originalSource;
  const replacements = [];
  let lineNum = 0;
  let filePos = 0;
  const lines = src.split('\n');

  for (let line of lines) {
    lineNum++;
    let searchPos = 0;

    while ((pos = line.indexOf(searchString, searchPos)) !== -1) {
      src = src.substring(0, filePos + pos) + replaceString + src.substring(filePos + pos + searchString.length);
      line = line.substring(0, pos) + replaceString + line.substring(pos + searchString.length);
      replacements.push({
        line: lineNum,
        column: pos
      });
      searchPos = pos + replaceString.length;
    }

    filePos += line.length + 1;
  }

  replacements.reverse();
  consumer.eachMapping(mapping => {
    for (const replacement of replacements) {
      if (replacement.line == mapping.generatedLine && mapping.generatedColumn > replacement.column) {
        const offset = searchString.length - replaceString.length;
        mapping.generatedColumn -= offset;
      }
    }

    if (mapping.source) {
      const newMapping = {
        generated: {
          line: mapping.generatedLine,
          column: mapping.generatedColumn
        },
        original: {
          line: mapping.originalLine,
          column: mapping.originalColumn
        },
        source: mapping.source
      };
      return generator.addMapping(newMapping);
    }

    return mapping;
  });
  consumer.destroy();
  const updatedSourceMap = Object.assign(JSON.parse(generator.toString()), {
    names: originalMap.names,
    sourceRoot: originalMap.sourceRoot,
    sources: originalMap.sources,
    sourcesContent: originalMap.sourcesContent
  });
  return {
    map: JSON.stringify(updatedSourceMap),
    source: src
  };
}

module.exports = replaceAndUpdateSourceMap;