import fs from 'fs';
import {ArgumentParser} from '../js_code_coverage/node_modules/argparse/argparse.js';
import {SourceMapConsumer, SourceMapGenerator} from '../js_code_coverage/node_modules/source-map/source-map.js';
const INLINE_SOURCE_MAP_REGEX =
/\/\/# sourceMappingURL=data:application\/json;base64,(.+)/;
function addMapping(
map, sourceFileName, originalLine, originalColumn, generatedLine,
generatedColumn) {
const mapping = {
source: sourceFileName,
original: {
line: originalLine,
column: originalColumn,
},
generated: {
line: generatedLine,
column: generatedColumn,
},
};
map.addMapping(mapping);
}
/**
* Processes a generated js file to correct the inline sourcemap for changes
* made by rewrite_imports.py
* e.g. 'lit' is rewritten in imports to '//resources/mwc/lit/index.js'
* before: import {css, CSSResult, CSSResultGroup, html, LitElement,
* PropertyValues} from 'lit';
* after: import {css, CSSResult, CSSResultGroup,
* html, LitElement, PropertyValues} from '//resources/mwc/lit/index.js';
* @param {string} originalDirectory Path to the compiled js
* @param {string} file File name of generated file with inline sourcemap
* @param {string} outputDirectory Path to the rewritten js
* @param {JSON} changedMappings A JSON list of lines and their changes
*/
async function processOneFile(
originalDirectory, file, outputDirectory, changedMappings) {
// Retrieve inline sourcemap from last line of generated file.
const inputFile = fs.readFileSync(originalDirectory + '/' + file, 'utf8');
const inputLines = inputFile.split('\n');
const lastLine = inputLines[inputLines.length - 1];
// Parse raw sourcemap out of line.
const lastLineMatcher = lastLine.match(INLINE_SOURCE_MAP_REGEX);
if (!lastLineMatcher) {
return;
}
const rawSourceMap = Buffer.from(lastLineMatcher[1], 'base64').toString();
const mapConsumer = await new SourceMapConsumer(rawSourceMap, null);
const mapGenerator = SourceMapGenerator.fromSourceMap(mapConsumer);
mapConsumer.destroy();
// Create a new 1:1 sourcemap of tsc generated js to itself.
const jsToJsMapGenerator = new SourceMapGenerator(
{ file: mapGenerator._file, sourceRoot: originalDirectory });
mapGenerator._mappings.unsortedForEach(mapping => {
addMapping(jsToJsMapGenerator, file, mapping.generatedLine, mapping.generatedColumn, mapping.generatedLine, mapping.generatedColumn);
});
// `changedMappings` contains a list of lines and their changes. Convert this
// to a map of changedLine to change information for easier lookup.
const changedLinesMap = new Map();
const mappingsJSON = JSON.parse(changedMappings);
for (const mapping of mappingsJSON) {
changedLinesMap.set(mapping.lineNum, mapping);
}
// We rewrite the sourcemap by iterating the in-place 1:1 js sourcemap and
// changing affected mappings.
const pathToGeneratedJs = `${process.cwd()}/${originalDirectory}/`
const rewriteImportsMapGenerator = new SourceMapGenerator(
{ file: mapGenerator._file, sourceRoot: pathToGeneratedJs });
// We iterate over the in-place 1:1 js sourcemap mapping, if the mapping is
// affected (it is in an affected line after the column change by
// rewrite_imports.py), we create a new mapping with the correct delta.
// Otherwise, the original mapping persists.
jsToJsMapGenerator._mappings.unsortedForEach(mapping => {
// Check if mapping is in a changed line after the rewritten column.
const changedLineMapping = changedLinesMap.get(mapping.generatedLine);
if (changedLineMapping &&
changedLineMapping.generatedColumn <= mapping.generatedColumn) {
const delta = changedLineMapping.rewrittenColumn -
changedLineMapping.generatedColumn;
// Keep in mind the entire build chain is:
// ts [original] -> js [generated] -> js [rewritten]
// We are creating a new sourcemap for:
// ts [original] -> js [rewritten].
// Nomenclature is confusing since sourcemaps only have an original file
// and a generated file to map between, since we are creating a sourcemap
// straight from the original to the rewritten step, we are treating the
// rewritten file as the (new) generated file.
addMapping(
rewriteImportsMapGenerator, file, mapping.originalLine,
mapping.originalColumn, mapping.generatedLine,
mapping.generatedColumn + delta)
} else {
addMapping(
rewriteImportsMapGenerator, file, mapping.originalLine,
mapping.originalColumn, mapping.generatedLine,
mapping.generatedColumn);
}
});
// Rewrite the inline sourcemap in the last line of the rewritten output file.
const newSourceMap64 =
Buffer.from(rewriteImportsMapGenerator.toString()).toString('base64');
const outputFile = fs.readFileSync(outputDirectory + '/' + file, 'utf8');
const outputLines = outputFile.split('\n');
// Kill the previous sourcemap for an overwrite.
outputLines.pop();
const outputFileContents = outputLines.join('\n') +
'\n//# sourceMappingURL=data:application/json;base64,' + newSourceMap64;
fs.writeFileSync(outputDirectory + '/' + file, outputFileContents);
}
function main() {
const parser = new ArgumentParser({
description:
'Creates source maps for files preprocessed by rewrite_imports',
});
parser.addArgument(
'originalTsDirectory',
{help: 'Directory of original .ts file', action: 'store'});
parser.addArgument('fileName', {
help: 'name of compiled .js file with inline sourcemap',
action: 'store'
});
parser.addArgument(
'rewrittenJsDirectory',
{help: 'Directory of the rewritten .js', action: 'store'});
parser.addArgument(
'changedMappings',
{help: 'A JSON list of JSON mappings of line changes', action: 'store'});
const argv = parser.parseArgs();
processOneFile(
argv.originalTsDirectory, argv.fileName, argv.rewrittenJsDirectory,
argv.changedMappings);
}
main();