Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to @jridgewell/gen-mapping for sourcemap generation #14497

Merged
merged 6 commits into from Apr 28, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reviewers: this._sourceFileName is already normalized in the constructor.

original:
line == null
? undefined
Expand Down