Skip to content

should applySourceMap remove unknown mapping? #409

@3cp

Description

@3cp

I noticed this issue when using gulp-typescript and gulp-terser together.
When compiling ts.ts to ts.js and compress it, the source map ends up with sources: [ 'ts.js', 'ts.ts' ], sourcesContent: [ null, '...' ]. But it should be only one source ts.ts.

The null ts.js content is due to the 2nd source map (by terser) has some mapping cannot be found in the first source map (by typescript), because typescript compiler injected few lines of header in compiled code.

This issue can be demonstrated by following code snippet.

var {SourceMapGenerator, SourceMapConsumer} = require('source-map');

async function applySourceMap(first, second) {
  var generator = SourceMapGenerator.fromSourceMap(await new SourceMapConsumer(second));
  generator.applySourceMap(await new SourceMapConsumer(first));
  return JSON.parse(generator.toString());
};


var first = {
  version: 3,
  sources: [ 'ts.ts' ],
  names: [],
  mappings: ';;AAAA', // [ [ [ 0, 0, 0, 0 ] ] ]
  file: 'ts.js',
  sourcesContent: ['export const 1;']
};

var second = {
  version: 3,
  sources: [ 'ts.js' ],
  names: [],
  mappings: 'AAAA,AAEA', // [ [ [ 0, 0, 0, 0 ], [ 0, 0, 2, 0 ] ] ]
  file: 'ts.js'
};

applySourceMap(first, second).then(merged => {
  console.log(merged);
});

/* prints following merged map:

{
  version: 3,
  sources: [ 'ts.js', 'ts.ts' ],
  names: [],
  mappings: 'AAAA,ACAA', // [ [ [ 0, 0, 0, 0 ], [ 0, 1, 0, 0 ] ] ]
  file: 'ts.js',
  sourcesContent: [ null, 'export const 1;' ]
}

*/

The resulting source map retained a mapping [0, 0, 0, 0] which points to ts.js.

The retained mapping is due to implementation here. When original mapping could not be found, the new mapping is untouched.

if (mapping.source === sourceFile && mapping.originalLine != null) {
// Check if it can be mapped by the source map, then update the mapping.
const original = aSourceMapConsumer.originalPositionFor({
line: mapping.originalLine,
column: mapping.originalColumn
});
if (original.source != null) {
// Copy mapping
mapping.source = original.source;
if (aSourceMapPath != null) {
mapping.source = util.join(aSourceMapPath, mapping.source);
}
if (sourceRoot != null) {
mapping.source = util.relative(sourceRoot, mapping.source);
}
mapping.originalLine = original.line;
mapping.originalColumn = original.column;
if (original.name != null) {
mapping.name = original.name;
}
}
}

This is probably wrong.

The whole block is behind condition if (mapping.source === sourceFile ..., that means the current mapping is referencing a chained source from previous source map, not referencing an additional source.

I think the proper logic should be:

      if (mapping.source === sourceFile && mapping.originalLine != null) {
        ...
        if (original.source != null) {
          ...
        } else {
          // Add else branch here
          remove_this_mapping_from_merged_result
        }
      }

Let me know whether this makes sense.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions