From 26dcd6400f88d7471e2f60e668ad3419f0b6040d Mon Sep 17 00:00:00 2001 From: Dale Bustad Date: Thu, 4 Feb 2016 19:39:19 -0800 Subject: [PATCH 1/4] When generating sourcemaps, use per-node source `filename` if present. --- packages/babel-generator/src/source-map.js | 2 +- packages/babel-generator/test/index.js | 38 ++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/babel-generator/src/source-map.js b/packages/babel-generator/src/source-map.js index 942f931b9f..6213873ad7 100644 --- a/packages/babel-generator/src/source-map.js +++ b/packages/babel-generator/src/source-map.js @@ -68,7 +68,7 @@ export default class SourceMap { } this.last = { - source: this.opts.sourceFileName, + source: loc.filename || this.opts.sourceFileName, generated: generated, original: original }; diff --git a/packages/babel-generator/test/index.js b/packages/babel-generator/test/index.js index e6cfc39827..7ee494b75d 100644 --- a/packages/babel-generator/test/index.js +++ b/packages/babel-generator/test/index.js @@ -17,6 +17,44 @@ suite("generation", function () { assert.ok(t.VISITOR_KEYS[type], type + " should not exist"); }); }); + + test("multiple sources", function () { + var sources = { + "a.js": "function hi (msg) { console.log(msg); }\n", + "b.js": "hi('hello');\n" + }; + var parsed = _.keys(sources).reduce(function (_parsed, filename) { + _parsed[filename] = parse(sources[filename], { sourceFilename: filename }); + return _parsed; + }, {}); + + var combinedAst = { + "type": "File", + "program": { + "type": "Program", + "sourceType": "module", + "body": [].concat(parsed["a.js"].program.body, parsed["b.js"].program.body) + } + }; + + var generated = generate.default(combinedAst, { sourceMaps: true }, sources); + + chai.expect(generated.map).to.deep.equal({ + version: 3, + sources: [ 'a.js', 'b.js' ], + names: [], + mappings: 'AAAA,SAAS,EAAT,CAAa,GAAb,EAAkB;AAAE,UAAQ,GAAR,CAAY,GAAZ,EAAF;CAAlB;;GCAG,OAAH', + sourcesContent: [ + 'function hi (msg) { console.log(msg); }\n', + 'hi(\'hello\');\n' + ] + }, "sourcemap was incorrectly generated"); + + chai.expect(generated.code).to.equal( + "function hi(msg) {\n console.log(msg);\n}\n\nhi('hello');", + "code was incorrectly generated" + ); + }); }); From f007b51554a9affd7cd9fd19e15391d6f63ed2aa Mon Sep 17 00:00:00 2001 From: Dale Bustad Date: Thu, 4 Feb 2016 22:58:19 -0800 Subject: [PATCH 2/4] Accept filename-to-file-content hash as code parameter of `generate`. --- packages/babel-generator/README.md | 45 +++++++++++++++++++++- packages/babel-generator/src/source-map.js | 8 +++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/packages/babel-generator/README.md b/packages/babel-generator/README.md index 0164d55c2e..ef4909834e 100644 --- a/packages/babel-generator/README.md +++ b/packages/babel-generator/README.md @@ -44,4 +44,47 @@ name | type | default | description sourceMaps | boolean | `false` | Enable generating source maps sourceMapTarget | string | | The filename of the generated code that the source map will be associated with sourceRoot | string | | A root for all relative URLs in the source map -sourceFileName | string | | The filename for the source code (i.e. the code in the `code` argument) +sourceFileName | string | | The filename for the source code (i.e. the code in the `code` argument). This will only be used if `code` is a string. + +## AST from Multiple Sources + +In most cases, Babel does a 1:1 transformation of input-file to output-file. However, +you may be dealing with AST constructed from multiple sources - JS files, templates, etc. +If this is the case, and you want the sourcemaps to reflect the correct sources, you'll need +to make some changes to your code. + +First, each node with a `loc` property (which indicates that node's original placement in the +source document) must also include a `loc.filename` property, set to the source filename. + +Second, you should pass an object to `generate` as the `code` parameter. Keys +should be the source filenames, and values should be the source content. + +Here's an example of what that might look like: + +```js +import {parse} from 'babylon'; +import traverse from "babel-traverse"; +import generate from 'babel-generator'; + +function addFilename (ast, filename) { + traverse.cheap(ast, node => { + if (node.loc) { node.loc.source = sourcePath }; + }); +} + +const a = 'var a = 1;'; +const b = 'var b = 2;'; +const astA = addFilename(parse(a), 'a.js'); +const astB = addFilename(parse(b), 'b.js'); +const ast = { + type: 'Program', + body: [].concat(astA.body, ast2.body) +}; + +const { code, map } = generate(ast, { /* options */ }, { + 'a.js': a, + 'b.js': b +}); + +// Sourcemap will point to both a.js and b.js where appropriate. +``` diff --git a/packages/babel-generator/src/source-map.js b/packages/babel-generator/src/source-map.js index 6213873ad7..beea55bddf 100644 --- a/packages/babel-generator/src/source-map.js +++ b/packages/babel-generator/src/source-map.js @@ -17,7 +17,13 @@ export default class SourceMap { sourceRoot: opts.sourceRoot }); - this.map.setSourceContent(opts.sourceFileName, code); + if (typeof code === "string") { + this.map.setSourceContent(opts.sourceFileName, code); + } else if (typeof code === "object") { + Object.keys(code).forEach((sourceFileName) => { + this.map.setSourceContent(sourceFileName, code[sourceFileName]); + }); + } } else { this.map = null; } From 288b3ba559620912047e0e4658337670f4e0f588 Mon Sep 17 00:00:00 2001 From: Dale Bustad Date: Mon, 29 Feb 2016 11:19:33 -0800 Subject: [PATCH 3/4] Attach filename property to `node.loc` when provided. --- packages/babel-generator/README.md | 10 +- packages/babylon/README.md | 2 + packages/babylon/src/options.js | 3 + packages/babylon/src/parser/index.js | 1 + packages/babylon/src/parser/node.js | 7 +- .../categorized/filename-specified/actual.js | 1 + .../filename-specified/expected.json | 109 ++++++++++++++++++ .../filename-specified/options.json | 3 + 8 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 packages/babylon/test/fixtures/core/categorized/filename-specified/actual.js create mode 100644 packages/babylon/test/fixtures/core/categorized/filename-specified/expected.json create mode 100644 packages/babylon/test/fixtures/core/categorized/filename-specified/options.json diff --git a/packages/babel-generator/README.md b/packages/babel-generator/README.md index ef4909834e..aeab598579 100644 --- a/packages/babel-generator/README.md +++ b/packages/babel-generator/README.md @@ -66,16 +66,10 @@ import {parse} from 'babylon'; import traverse from "babel-traverse"; import generate from 'babel-generator'; -function addFilename (ast, filename) { - traverse.cheap(ast, node => { - if (node.loc) { node.loc.source = sourcePath }; - }); -} - const a = 'var a = 1;'; const b = 'var b = 2;'; -const astA = addFilename(parse(a), 'a.js'); -const astB = addFilename(parse(b), 'b.js'); +const astA = parse(a, { filename: 'a.js' }); +const astB = parse(b, { filename: 'b.js' }); const ast = { type: 'Program', body: [].concat(astA.body, ast2.body) diff --git a/packages/babylon/README.md b/packages/babylon/README.md index 8c927de516..d5833e498c 100644 --- a/packages/babylon/README.md +++ b/packages/babylon/README.md @@ -37,6 +37,8 @@ Significant diversions are expected to occur in the future such as streaming, EB - **sourceType**: Indicate the mode the code should be parsed in. Can be either `"script"` or `"module"`. +- **sourceFilename**: Correlate output AST nodes with their source filename. Useful when generating code and source maps from the ASTs of multiple input files. + - **plugins**: Array containing the plugins that you want to enable. ### Example diff --git a/packages/babylon/src/options.js b/packages/babylon/src/options.js index 9da58e93b5..59bd2d35d7 100755 --- a/packages/babylon/src/options.js +++ b/packages/babylon/src/options.js @@ -3,6 +3,7 @@ export const defaultOptions: { sourceType: string, + sourceFilename: any, allowReturnOutsideFunction: boolean, allowImportExportEverywhere: boolean, allowSuperOutsideMethod: boolean, @@ -11,6 +12,8 @@ export const defaultOptions: { } = { // Source type ("script" or "module") for different semantics sourceType: "script", + // Source filename. + sourceFilename: undefined, // When enabled, a return at the top level is not considered an // error. allowReturnOutsideFunction: false, diff --git a/packages/babylon/src/parser/index.js b/packages/babylon/src/parser/index.js index 3db3c56984..8b3353bed0 100644 --- a/packages/babylon/src/parser/index.js +++ b/packages/babylon/src/parser/index.js @@ -14,6 +14,7 @@ export default class Parser extends Tokenizer { this.isReservedWord = reservedWords[6]; this.input = input; this.plugins = this.loadPlugins(this.options.plugins); + this.filename = options.sourceFilename; // If enabled, skip leading hashbang line. if (this.state.pos === 0 && this.input[0] === "#" && this.input[1] === "!") { diff --git a/packages/babylon/src/parser/node.js b/packages/babylon/src/parser/node.js index d9e07ae329..fa44f561c4 100644 --- a/packages/babylon/src/parser/node.js +++ b/packages/babylon/src/parser/node.js @@ -6,11 +6,12 @@ import { SourceLocation } from "../util/location"; const pp = Parser.prototype; class Node { - constructor(pos?: number, loc?: SourceLocation) { + constructor(pos?: number, loc?: SourceLocation, filename?: string) { this.type = ""; this.start = pos; this.end = 0; this.loc = new SourceLocation(loc); + if (filename) this.loc.filename = filename; } type: string; @@ -26,11 +27,11 @@ class Node { } pp.startNode = function () { - return new Node(this.state.start, this.state.startLoc); + return new Node(this.state.start, this.state.startLoc, this.filename); }; pp.startNodeAt = function (pos, loc) { - return new Node(pos, loc); + return new Node(pos, loc, this.filename); }; function finishNodeAt(node, type, pos, loc) { diff --git a/packages/babylon/test/fixtures/core/categorized/filename-specified/actual.js b/packages/babylon/test/fixtures/core/categorized/filename-specified/actual.js new file mode 100644 index 0000000000..76e45db756 --- /dev/null +++ b/packages/babylon/test/fixtures/core/categorized/filename-specified/actual.js @@ -0,0 +1 @@ +var node = "shouldHaveFilenameLocProp"; diff --git a/packages/babylon/test/fixtures/core/categorized/filename-specified/expected.json b/packages/babylon/test/fixtures/core/categorized/filename-specified/expected.json new file mode 100644 index 0000000000..9b5ba11fa1 --- /dev/null +++ b/packages/babylon/test/fixtures/core/categorized/filename-specified/expected.json @@ -0,0 +1,109 @@ +{ + "type": "File", + "start": 0, + "end": 39, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 39 + }, + "filename": "path/to/input-file.js" + }, + "program": { + "type": "Program", + "start": 0, + "end": 39, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 39 + }, + "filename": "path/to/input-file.js" + }, + "sourceType": "script", + "body": [ + { + "type": "VariableDeclaration", + "start": 0, + "end": 39, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 39 + }, + "filename": "path/to/input-file.js" + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 4, + "end": 38, + "loc": { + "start": { + "line": 1, + "column": 4 + }, + "end": { + "line": 1, + "column": 38 + }, + "filename": "path/to/input-file.js" + }, + "id": { + "type": "Identifier", + "start": 4, + "end": 8, + "loc": { + "start": { + "line": 1, + "column": 4 + }, + "end": { + "line": 1, + "column": 8 + }, + "filename": "path/to/input-file.js" + }, + "name": "node" + }, + "init": { + "type": "StringLiteral", + "start": 11, + "end": 38, + "loc": { + "start": { + "line": 1, + "column": 11 + }, + "end": { + "line": 1, + "column": 38 + }, + "filename": "path/to/input-file.js" + }, + "extra": { + "rawValue": "shouldHaveFilenameLocProp", + "raw": "\"shouldHaveFilenameLocProp\"" + }, + "value": "shouldHaveFilenameLocProp" + } + } + ], + "kind": "var" + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babylon/test/fixtures/core/categorized/filename-specified/options.json b/packages/babylon/test/fixtures/core/categorized/filename-specified/options.json new file mode 100644 index 0000000000..400e29986a --- /dev/null +++ b/packages/babylon/test/fixtures/core/categorized/filename-specified/options.json @@ -0,0 +1,3 @@ +{ + "sourceFilename": "path/to/input-file.js" +} \ No newline at end of file From 3c6c09d00a32a6d3e20724a9284244d15e8b4d7e Mon Sep 17 00:00:00 2001 From: Dale Bustad Date: Mon, 7 Mar 2016 02:35:31 -0800 Subject: [PATCH 4/4] Only detect indent if babel-generator is provided code string, vs multi-source object. --- packages/babel-generator/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/babel-generator/src/index.js b/packages/babel-generator/src/index.js index f65d065520..9c4e08672d 100644 --- a/packages/babel-generator/src/index.js +++ b/packages/babel-generator/src/index.js @@ -71,7 +71,7 @@ export class CodeGenerator extends Printer { static normalizeOptions(code, opts, tokens) { let style = " "; - if (code) { + if (code && typeof code === "string") { let indent = detectIndent(code).indent; if (indent && indent !== " ") style = indent; }