Skip to content

Commit

Permalink
Switch to @jridgewell/gen-mapping for sourcemap generation (#14497)
Browse files Browse the repository at this point in the history
  • Loading branch information
jridgewell committed Apr 28, 2022
1 parent f05b877 commit b6d8434
Show file tree
Hide file tree
Showing 14 changed files with 141 additions and 135 deletions.
35 changes: 0 additions & 35 deletions lib/third-party-libs.js.flow
Expand Up @@ -51,41 +51,6 @@ declare module "semver" {
}
}

declare module "source-map" {
declare export type SourceMap = {
version: 3,
file: ?string,
sourceRoot: ?string,
sources: [?string],
sourcesContent: [?string],
names: [?string],
mappings: string,
};

declare module.exports: {
SourceMapGenerator: typeof SourceMapGenerator,
}

declare class SourceMapGenerator {
constructor(?{
file?: string | null,
sourceRoot?: string | null,
skipValidation?: boolean | null,
}): this;

addMapping({
generated: {
line: number,
column: number,
}
}): void;

setSourceContent(string, string): void;

toJSON(): SourceMap;
}
}

declare module "convert-source-map" {
import type { SourceMap, SourceMapGenerator } from "source-map";

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Expand Up @@ -3,6 +3,6 @@
arr.map(function (x) {
return x * x;
});
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0ZGluIl0sIm5hbWVzIjpbImFyciIsIm1hcCIsIngiXSwibWFwcGluZ3MiOiI7O0FBQUFBLEdBQUcsQ0FBQ0MsR0FBSixDQUFRLFVBQUFDLENBQUM7QUFBQSxTQUFJQSxDQUFDLEdBQUdBLENBQVI7QUFBQSxDQUFUIiwic291cmNlc0NvbnRlbnQiOlsiYXJyLm1hcCh4ID0+IHggKiB4KTsiXX0=
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJhcnIiLCJtYXAiLCJ4Il0sInNvdXJjZXMiOlsic3RkaW4iXSwic291cmNlc0NvbnRlbnQiOlsiYXJyLm1hcCh4ID0+IHggKiB4KTsiXSwibWFwcGluZ3MiOiI7O0FBQUFBLEdBQUcsQ0FBQ0MsR0FBSixDQUFRLFVBQUFDLENBQUM7QUFBQSxTQUFJQSxDQUFDLEdBQUdBLENBQVI7QUFBQSxDQUFUIn0=

//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3Rkb3V0IiwibmFtZXMiOltdLCJzb3VyY2VzIjpbXSwic291cmNlc0NvbnRlbnQiOltdLCJtYXBwaW5ncyI6IiJ9
10 changes: 8 additions & 2 deletions packages/babel-cli/test/index.js
Expand Up @@ -121,8 +121,13 @@ const assertTest = function (stdout, stderr, opts, cwd) {
expect(actual).toBe(expected || "");
}
} catch (e) {
e.message += "\n at " + filename;
throw e;
if (!process.env.OVERWRITE) {
e.message += "\n at " + filename;
throw e;
}
const expectedLoc = path.join(opts.testLoc, "out-files", filename);
console.log(`Updated test file: ${expectedLoc}`);
fs.writeFileSync(expectedLoc, actualFiles[filename]);
}
});

Expand Down Expand Up @@ -267,6 +272,7 @@ fs.readdirSync(fixtureLoc).forEach(function (binName) {
}
});

opts.testLoc = testLoc;
opts.outFiles = readDir(path.join(testLoc, "out-files"), fileFilter);
opts.inFiles = readDir(path.join(testLoc, "in-files"), fileFilter);

Expand Down
24 changes: 17 additions & 7 deletions packages/babel-core/src/transformation/file/generate.ts
Expand Up @@ -46,14 +46,24 @@ export default function generateCode(
throw new Error("More than one plugin attempted to override codegen.");
}

let { code: outputCode, map: outputMap } = result;
// Decoded maps are faster to merge, so we attempt to get use the decodedMap
// first. But to preserve backwards compat with older Generator, we'll fall
// back to the encoded map.
let { code: outputCode, decodedMap: outputMap = result.map } = result;

if (outputMap && inputMap) {
outputMap = mergeSourceMap(
inputMap.toObject(),
outputMap,
generatorOpts.sourceFileName,
);
if (outputMap) {
if (inputMap) {
// mergeSourceMap returns an encoded map
outputMap = mergeSourceMap(
inputMap.toObject(),
outputMap,
generatorOpts.sourceFileName,
);
} else {
// We cannot output a decoded map, so retrieve the encoded form. Because
// the decoded form is free, it's fine to prioritize decoded first.
outputMap = result.map;
}
}

if (opts.sourceMaps === "inline" || opts.sourceMaps === "both") {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions packages/babel-generator/package.json
Expand Up @@ -20,15 +20,14 @@
],
"dependencies": {
"@babel/types": "workspace:^",
"jsesc": "condition: BABEL_8_BREAKING ? ^3.0.2 : ^2.5.1",
"source-map": "^0.5.0"
"@jridgewell/gen-mapping": "^0.1.0",
"jsesc": "condition: BABEL_8_BREAKING ? ^3.0.2 : ^2.5.1"
},
"devDependencies": {
"@babel/helper-fixtures": "workspace:^",
"@babel/parser": "workspace:^",
"@jridgewell/trace-mapping": "^0.3.8",
"@types/jsesc": "^2.5.0",
"@types/source-map": "^0.5.0",
"charcodes": "^0.2.0"
},
"engines": {
Expand Down
37 changes: 18 additions & 19 deletions packages/babel-generator/src/buffer.ts
Expand Up @@ -46,25 +46,25 @@ export default class Buffer {
// Whatever trim is used here should not execute a regex against the
// source string since it may be arbitrarily large after all transformations
code: this._buf.trimRight(),
map: null,
rawMappings: map?.getRawMappings(),
// Decoded sourcemap is free to generate.
decodedMap: map?.getDecoded(),

// Encoding the sourcemap is moderately CPU expensive.
get map() {
return (result.map = map ? map.get() : null);
},
set map(value) {
Object.defineProperty(result, "map", { value, writable: true });
},
// Retrieving the raw mappings is very memory intensive.
get rawMappings() {
return (result.rawMappings = map?.getRawMappings());
},
set rawMappings(value) {
Object.defineProperty(result, "rawMappings", { value, writable: true });
},
};

if (map) {
// The `.map` property is lazy to allow callers to use the raw mappings
// without any overhead
Object.defineProperty(result, "map", {
configurable: true,
enumerable: true,
get() {
return (this.map = map.get());
},
set(value) {
Object.defineProperty(this, "map", { value, writable: true });
},
});
}

return result;
}

Expand Down Expand Up @@ -158,8 +158,7 @@ export default class Buffer {
force?: boolean,
): void {
this._map?.mark(
this._position.line,
this._position.column,
this._position,
line,
column,
identifierName,
Expand Down
13 changes: 12 additions & 1 deletion packages/babel-generator/src/index.ts
Expand Up @@ -3,14 +3,23 @@ import Printer from "./printer";
import type * as t from "@babel/types";

import type { Format } from "./printer";
import type { DecodedSourceMap, Mapping } from "@jridgewell/gen-mapping";

/**
* Babel's code generator, turns an ast into code, maintaining sourcemaps,
* user preferences, and valid output.
*/

class Generator extends Printer {
constructor(ast: t.Node, opts: { sourceMaps?: boolean } = {}, code) {
constructor(
ast: t.Node,
opts: {
sourceFileName?: string;
sourceMaps?: boolean;
sourceRoot?: string;
} = {},
code,
) {
const format = normalizeOptions(code, opts);
const map = opts.sourceMaps ? new SourceMap(opts, code) : null;
super(format, map);
Expand Down Expand Up @@ -217,6 +226,8 @@ export interface GeneratorResult {
mappings: string;
file: string;
} | null;
decodedMap: DecodedSourceMap | undefined;
rawMappings: Mapping[] | undefined;
}

/**
Expand Down
113 changes: 60 additions & 53 deletions packages/babel-generator/src/source-map.ts
@@ -1,57 +1,69 @@
import sourceMap from "source-map";
import {
GenMapping,
addMapping,
setSourceContent,
allMappings,
encodedMap,
decodedMap,
} from "@jridgewell/gen-mapping";

import type {
EncodedSourceMap,
DecodedSourceMap,
Mapping,
} from "@jridgewell/gen-mapping";

/**
* Build a sourcemap.
*/

export default class SourceMap {
private _cachedMap: sourceMap.SourceMapGenerator | null;
private _code: any;
private _opts: any;
private _rawMappings: any[];
private _lastGenLine: number;
private _lastSourceLine: number;
private _lastSourceColumn: number;
constructor(opts, code) {
this._cachedMap = null;
this._code = code;
this._opts = opts;
this._rawMappings = [];
private _map: GenMapping;
private _rawMappings: Mapping[] | undefined;
private _sourceFileName: string | undefined;

// Any real line is > 0, so init to 0 is fine.
private _lastGenLine = 0;
private _lastSourceLine = 0;

// Source columns can be 0, but we ony check in unison with sourceLine, which
// inits to an impossible value. So init to 0 is fine.
private _lastSourceColumn = 0;

constructor(
opts: { sourceFileName?: string; sourceRoot?: string },
code: string | { [sourceFileName: string]: string },
) {
const map = (this._map = new GenMapping({ sourceRoot: opts.sourceRoot }));
this._sourceFileName = opts.sourceFileName?.replace(/\\/g, "/");
this._rawMappings = undefined;

if (typeof code === "string") {
setSourceContent(map, this._sourceFileName, code);
} else if (typeof code === "object") {
Object.keys(code).forEach(sourceFileName => {
setSourceContent(
map,
sourceFileName.replace(/\\/g, "/"),
code[sourceFileName],
);
});
}
}

/**
* Get the sourcemap.
*/
get(): EncodedSourceMap {
return encodedMap(this._map);
}

get() {
if (!this._cachedMap) {
const map = (this._cachedMap = new sourceMap.SourceMapGenerator({
sourceRoot: this._opts.sourceRoot,
}));

const code = this._code;
if (typeof code === "string") {
map.setSourceContent(
this._opts.sourceFileName.replace(/\\/g, "/"),
code,
);
} else if (typeof code === "object") {
Object.keys(code).forEach(sourceFileName => {
map.setSourceContent(
sourceFileName.replace(/\\/g, "/"),
code[sourceFileName],
);
});
}

this._rawMappings.forEach(mapping => map.addMapping(mapping), map);
}

return this._cachedMap.toJSON();
getDecoded(): DecodedSourceMap {
return decodedMap(this._map);
}

getRawMappings() {
return this._rawMappings.slice();
getRawMappings(): Mapping[] {
return (this._rawMappings ||= allMappings(this._map));
}

/**
Expand All @@ -60,14 +72,15 @@ export default class SourceMap {
*/

mark(
generatedLine: number,
generatedColumn: number,
generated: { line: number; column: number },
line: number,
column: number,
identifierName?: string | null,
filename?: string | null,
force?: boolean,
) {
const generatedLine = generated.line;

// Adding an empty mapping at the start of a generated line just clutters the map.
if (this._lastGenLine !== generatedLine && line === null) return;

Expand All @@ -82,24 +95,18 @@ export default class SourceMap {
return;
}

this._cachedMap = null;
this._rawMappings = undefined;
this._lastGenLine = generatedLine;
this._lastSourceLine = line;
this._lastSourceColumn = column;

// We are deliberately not using the `source-map` library here to allow
// callers to use these mappings without any overhead
this._rawMappings.push({
// undefined to allow for more compact json serialization
name: identifierName || undefined,
generated: {
line: generatedLine,
column: generatedColumn,
},
addMapping(this._map, {
name: identifierName,
generated,
source:
line == null
? undefined
: (filename || this._opts.sourceFileName).replace(/\\/g, "/"),
: filename?.replace(/\\/g, "/") || this._sourceFileName,
original:
line == null
? undefined
Expand Down

0 comments on commit b6d8434

Please sign in to comment.