esmodule-builder
Version:
ECMAScript-Module (ES Module) builder.
676 lines (601 loc) • 19 kB
JavaScript
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
const util = require("./util");
const SourceMapGenerator = require("../lib/source-map-generator")
.SourceMapGenerator;
const SourceMapConsumer = require("../lib/source-map-consumer")
.SourceMapConsumer;
const SourceNode = require("../lib/source-node").SourceNode;
function forEachNewline(fn) {
return async function(assert) {
await fn(assert, "\n");
await fn(assert, "\r\n");
};
}
exports["test .add()"] = function(assert) {
const node = new SourceNode(null, null, null);
// Adding a string works.
node.add("function noop() {}");
// Adding another source node works.
node.add(new SourceNode(null, null, null));
// Adding an array works.
node.add([
"function foo() {",
new SourceNode(null, null, null, "return 10;"),
"}"
]);
// Adding other stuff doesn't.
assert.throws(function() {
node.add({});
}, /TypeError: Expected a SourceNode, string, or an array of SourceNodes and strings/);
assert.throws(function() {
node.add(function() {});
}, /TypeError: Expected a SourceNode, string, or an array of SourceNodes and strings/);
};
exports["test .prepend()"] = function(assert) {
const node = new SourceNode(null, null, null);
// Prepending a string works.
node.prepend("function noop() {}");
assert.equal(node.children[0], "function noop() {}");
assert.equal(node.children.length, 1);
// Prepending another source node works.
node.prepend(new SourceNode(null, null, null));
assert.equal(node.children[0], "");
assert.equal(node.children[1], "function noop() {}");
assert.equal(node.children.length, 2);
// Prepending an array works.
node.prepend([
"function foo() {",
new SourceNode(null, null, null, "return 10;"),
"}"
]);
assert.equal(node.children[0], "function foo() {");
assert.equal(node.children[1], "return 10;");
assert.equal(node.children[2], "}");
assert.equal(node.children[3], "");
assert.equal(node.children[4], "function noop() {}");
assert.equal(node.children.length, 5);
// Prepending other stuff doesn't.
assert.throws(function() {
node.prepend({});
}, /TypeError: Expected a SourceNode, string, or an array of SourceNodes and strings/);
assert.throws(function() {
node.prepend(function() {});
}, /TypeError: Expected a SourceNode, string, or an array of SourceNodes and strings/);
};
exports["test .toString()"] = function(assert) {
assert.equal(
new SourceNode(null, null, null, [
"function foo() {",
new SourceNode(null, null, null, "return 10;"),
"}"
]).toString(),
"function foo() {return 10;}"
);
};
exports["test .join()"] = function(assert) {
assert.equal(
new SourceNode(null, null, null, ["a", "b", "c", "d"])
.join(", ")
.toString(),
"a, b, c, d"
);
};
exports["test .walk()"] = function(assert) {
const node = new SourceNode(null, null, null, [
"(function () {\n",
" ",
new SourceNode(1, 0, "a.js", ["someCall()"]),
";\n",
" ",
new SourceNode(2, 0, "b.js", ["if (foo) bar()"]),
";\n",
"}());"
]);
const expected = [
{ str: "(function () {\n", source: null, line: null, column: null },
{ str: " ", source: null, line: null, column: null },
{ str: "someCall()", source: "a.js", line: 1, column: 0 },
{ str: ";\n", source: null, line: null, column: null },
{ str: " ", source: null, line: null, column: null },
{ str: "if (foo) bar()", source: "b.js", line: 2, column: 0 },
{ str: ";\n", source: null, line: null, column: null },
{ str: "}());", source: null, line: null, column: null }
];
let i = 0;
node.walk(function(chunk, loc) {
assert.equal(expected[i].str, chunk);
assert.equal(expected[i].source, loc.source);
assert.equal(expected[i].line, loc.line);
assert.equal(expected[i].column, loc.column);
i++;
});
};
exports["test .replaceRight"] = function(assert) {
let node;
// Not nested
node = new SourceNode(null, null, null, "hello world");
node.replaceRight(/world/, "universe");
assert.equal(node.toString(), "hello universe");
// Nested
node = new SourceNode(null, null, null, [
new SourceNode(null, null, null, "hey sexy mama, "),
new SourceNode(null, null, null, "want to kill all humans?")
]);
node.replaceRight(/kill all humans/, "watch Futurama");
assert.equal(node.toString(), "hey sexy mama, want to watch Futurama?");
};
exports["test .toStringWithSourceMap()"] = forEachNewline(async function(
assert,
nl
) {
const node = new SourceNode(null, null, null, [
"(function () {" + nl,
" ",
new SourceNode(1, 0, "a.js", "someCall", "originalCall"),
new SourceNode(1, 8, "a.js", "()"),
";" + nl,
" ",
new SourceNode(2, 0, "b.js", ["if (foo) bar()"]),
";" + nl,
"}());"
]);
const result = node.toStringWithSourceMap({
file: "foo.js"
});
assert.equal(
result.code,
["(function () {", " someCall();", " if (foo) bar();", "}());"].join(nl)
);
let map = result.map;
const mapWithoutOptions = node.toStringWithSourceMap().map;
assert.ok(
map instanceof SourceMapGenerator,
"map instanceof SourceMapGenerator"
);
assert.ok(
mapWithoutOptions instanceof SourceMapGenerator,
"mapWithoutOptions instanceof SourceMapGenerator"
);
assert.ok(!("file" in mapWithoutOptions));
mapWithoutOptions._file = "foo.js";
util.assertEqualMaps(assert, map.toJSON(), mapWithoutOptions.toJSON());
map = await new SourceMapConsumer(map.toString());
let actual;
actual = map.originalPositionFor({
line: 1,
column: 4
});
assert.equal(actual.source, null);
assert.equal(actual.line, null);
assert.equal(actual.column, null);
actual = map.originalPositionFor({
line: 2,
column: 2
});
assert.equal(actual.source, "a.js");
assert.equal(actual.line, 1);
assert.equal(actual.column, 0);
assert.equal(actual.name, "originalCall");
actual = map.originalPositionFor({
line: 3,
column: 2
});
assert.equal(actual.source, "b.js");
assert.equal(actual.line, 2);
assert.equal(actual.column, 0);
actual = map.originalPositionFor({
line: 3,
column: 16
});
assert.equal(actual.source, null);
assert.equal(actual.line, null);
assert.equal(actual.column, null);
actual = map.originalPositionFor({
line: 4,
column: 2
});
assert.equal(actual.source, null);
assert.equal(actual.line, null);
assert.equal(actual.column, null);
map.destroy();
});
exports["test .fromStringWithSourceMap()"] = forEachNewline(async function(
assert,
nl
) {
const testCode = util.testGeneratedCode.replace(/\n/g, nl);
let map = await new SourceMapConsumer(util.testMap);
const node = SourceNode.fromStringWithSourceMap(testCode, map);
map.destroy();
const result = node.toStringWithSourceMap({
file: "min.js"
});
map = result.map;
const code = result.code;
assert.equal(code, testCode);
assert.ok(
map instanceof SourceMapGenerator,
"map instanceof SourceMapGenerator"
);
map = map.toJSON();
assert.equal(map.version, util.testMap.version);
assert.equal(map.file, util.testMap.file);
assert.equal(map.mappings, util.testMap.mappings);
});
exports["test .fromStringWithSourceMap() empty map"] = forEachNewline(
async function(assert, nl) {
let map = await new SourceMapConsumer(util.emptyMap);
const node = SourceNode.fromStringWithSourceMap(
util.testGeneratedCode.replace(/\n/g, nl),
map
);
map.destroy();
const result = node.toStringWithSourceMap({
file: "min.js"
});
map = result.map;
const code = result.code;
assert.equal(code, util.testGeneratedCode.replace(/\n/g, nl));
assert.ok(
map instanceof SourceMapGenerator,
"map instanceof SourceMapGenerator"
);
map = map.toJSON();
assert.equal(map.version, util.emptyMap.version);
assert.equal(map.file, util.emptyMap.file);
assert.equal(map.mappings.length, util.emptyMap.mappings.length);
assert.equal(map.mappings, util.emptyMap.mappings);
}
);
exports["test .fromStringWithSourceMap() complex version"] = forEachNewline(
async function(assert, nl) {
let input = new SourceNode(null, null, null, [
"(function() {" + nl,
" var Test = {};" + nl,
" ",
new SourceNode(1, 0, "a.js", "Test.A = { value: 1234 };" + nl),
" ",
new SourceNode(2, 0, "a.js", "Test.A.x = 'xyz';"),
nl,
"}());" + nl,
"/* Generated Source */"
]);
input = input.toStringWithSourceMap({
file: "foo.js"
});
let map = await new SourceMapConsumer(input.map.toString());
const node = SourceNode.fromStringWithSourceMap(input.code, map);
map.destroy();
const result = node.toStringWithSourceMap({
file: "foo.js"
});
map = result.map;
const code = result.code;
assert.equal(code, input.code);
assert.ok(
map instanceof SourceMapGenerator,
"map instanceof SourceMapGenerator"
);
map = map.toJSON();
const inputMap = input.map.toJSON();
util.assertEqualMaps(assert, map, inputMap);
}
);
exports["test .fromStringWithSourceMap() third argument"] = async function(
assert
) {
// Assume the following directory structure:
//
// http://foo.org/
// app/
// coffee/
// foo.coffee
// coffeeBundle.js # Made from {foo,bar,baz}.coffee
// maps/
// coffeeBundle.js.map
// js/
// foo.js
// public/
// app.js # Made from {foo,coffeeBundle}.js
// app.js.map
let coffeeBundle = new SourceNode(1, 0, "foo.coffee", "foo(coffee);\n");
coffeeBundle.setSourceContent("foo.coffee", "foo coffee");
coffeeBundle = coffeeBundle.toStringWithSourceMap({
file: "foo.js",
sourceRoot: ".."
});
const foo = new SourceNode(1, 0, "foo.js", "foo(js);");
const test = async function(relativePath, expectedSources) {
const app = new SourceNode();
const map = await new SourceMapConsumer(coffeeBundle.map.toString());
app.add(
SourceNode.fromStringWithSourceMap(coffeeBundle.code, map, relativePath)
);
map.destroy();
app.add(foo);
let i = 0;
app.walk(function(chunk, loc) {
assert.equal(loc.source, expectedSources[i]);
i++;
});
app.walkSourceContents(function(sourceFile, sourceContent) {
assert.equal(sourceFile, expectedSources[0]);
assert.equal(sourceContent, "foo coffee");
});
};
await test("../coffee/maps", ["../coffee/foo.coffee", "foo.js"]);
// If the third parameter is omitted or set to the current working
// directory we get incorrect source paths:
await test(undefined, ["../foo.coffee", "foo.js"]);
await test("", ["../foo.coffee", "foo.js"]);
await test(".", ["../foo.coffee", "foo.js"]);
await test("./", ["../foo.coffee", "foo.js"]);
};
exports[
"test .toStringWithSourceMap() merging duplicate mappings"
] = forEachNewline(function(assert, nl) {
let input = new SourceNode(null, null, null, [
new SourceNode(1, 0, "a.js", "(function"),
new SourceNode(1, 0, "a.js", "() {" + nl),
" ",
new SourceNode(1, 0, "a.js", "var Test = "),
new SourceNode(1, 0, "b.js", "{};" + nl),
new SourceNode(2, 0, "b.js", "Test"),
new SourceNode(2, 0, "b.js", ".A", "A"),
new SourceNode(2, 20, "b.js", " = { value: ", "A"),
"1234",
new SourceNode(2, 40, "b.js", " };" + nl, "A"),
"}());" + nl,
"/* Generated Source */"
]);
input = input.toStringWithSourceMap({
file: "foo.js"
});
assert.equal(
input.code,
[
"(function() {",
" var Test = {};",
"Test.A = { value: 1234 };",
"}());",
"/* Generated Source */"
].join(nl)
);
let correctMap = new SourceMapGenerator({
file: "foo.js"
});
correctMap.addMapping({
generated: { line: 1, column: 0 },
source: "a.js",
original: { line: 1, column: 0 }
});
// Here is no need for a empty mapping,
// because mappings ends at eol
correctMap.addMapping({
generated: { line: 2, column: 2 },
source: "a.js",
original: { line: 1, column: 0 }
});
correctMap.addMapping({
generated: { line: 2, column: 13 },
source: "b.js",
original: { line: 1, column: 0 }
});
correctMap.addMapping({
generated: { line: 3, column: 0 },
source: "b.js",
original: { line: 2, column: 0 }
});
correctMap.addMapping({
generated: { line: 3, column: 4 },
source: "b.js",
name: "A",
original: { line: 2, column: 0 }
});
correctMap.addMapping({
generated: { line: 3, column: 6 },
source: "b.js",
name: "A",
original: { line: 2, column: 20 }
});
// This empty mapping is required,
// because there is a hole in the middle of the line
correctMap.addMapping({
generated: { line: 3, column: 18 }
});
correctMap.addMapping({
generated: { line: 3, column: 22 },
source: "b.js",
name: "A",
original: { line: 2, column: 40 }
});
// Here is no need for a empty mapping,
// because mappings ends at eol
const inputMap = input.map.toJSON();
correctMap = correctMap.toJSON();
util.assertEqualMaps(assert, inputMap, correctMap);
});
exports[
"test .toStringWithSourceMap() multi-line SourceNodes"
] = forEachNewline(function(assert, nl) {
let input = new SourceNode(null, null, null, [
new SourceNode(
1,
0,
"a.js",
"(function() {" + nl + "var nextLine = 1;" + nl + "anotherLine();" + nl
),
new SourceNode(2, 2, "b.js", "Test.call(this, 123);" + nl),
new SourceNode(2, 2, "b.js", "this['stuff'] = 'v';" + nl),
new SourceNode(2, 2, "b.js", "anotherLine();" + nl),
"/*" + nl + "Generated" + nl + "Source" + nl + "*/" + nl,
new SourceNode(3, 4, "c.js", "anotherLine();" + nl),
"/*" + nl + "Generated" + nl + "Source" + nl + "*/"
]);
input = input.toStringWithSourceMap({
file: "foo.js"
});
assert.equal(
input.code,
[
"(function() {",
"var nextLine = 1;",
"anotherLine();",
"Test.call(this, 123);",
"this['stuff'] = 'v';",
"anotherLine();",
"/*",
"Generated",
"Source",
"*/",
"anotherLine();",
"/*",
"Generated",
"Source",
"*/"
].join(nl)
);
let correctMap = new SourceMapGenerator({
file: "foo.js"
});
correctMap.addMapping({
generated: { line: 1, column: 0 },
source: "a.js",
original: { line: 1, column: 0 }
});
correctMap.addMapping({
generated: { line: 2, column: 0 },
source: "a.js",
original: { line: 1, column: 0 }
});
correctMap.addMapping({
generated: { line: 3, column: 0 },
source: "a.js",
original: { line: 1, column: 0 }
});
correctMap.addMapping({
generated: { line: 4, column: 0 },
source: "b.js",
original: { line: 2, column: 2 }
});
correctMap.addMapping({
generated: { line: 5, column: 0 },
source: "b.js",
original: { line: 2, column: 2 }
});
correctMap.addMapping({
generated: { line: 6, column: 0 },
source: "b.js",
original: { line: 2, column: 2 }
});
correctMap.addMapping({
generated: { line: 11, column: 0 },
source: "c.js",
original: { line: 3, column: 4 }
});
const inputMap = input.map.toJSON();
correctMap = correctMap.toJSON();
util.assertEqualMaps(assert, inputMap, correctMap);
});
exports["test .toStringWithSourceMap() with empty string"] = function(assert) {
const node = new SourceNode(1, 0, "empty.js", "");
const result = node.toStringWithSourceMap();
assert.equal(result.code, "");
};
exports[
"test .toStringWithSourceMap() with consecutive newlines"
] = forEachNewline(function(assert, nl) {
let input = new SourceNode(null, null, null, [
"/***/" + nl + nl,
new SourceNode(1, 0, "a.js", "'use strict';" + nl),
new SourceNode(2, 0, "a.js", "a();")
]);
input = input.toStringWithSourceMap({
file: "foo.js"
});
assert.equal(input.code, ["/***/", "", "'use strict';", "a();"].join(nl));
let correctMap = new SourceMapGenerator({
file: "foo.js"
});
correctMap.addMapping({
generated: { line: 3, column: 0 },
source: "a.js",
original: { line: 1, column: 0 }
});
correctMap.addMapping({
generated: { line: 4, column: 0 },
source: "a.js",
original: { line: 2, column: 0 }
});
const inputMap = input.map.toJSON();
correctMap = correctMap.toJSON();
util.assertEqualMaps(assert, inputMap, correctMap);
});
exports["test setSourceContent with toStringWithSourceMap"] = async function(
assert
) {
const aNode = new SourceNode(1, 1, "a.js", "a");
aNode.setSourceContent("a.js", "someContent");
const node = new SourceNode(null, null, null, [
"(function () {\n",
" ",
aNode,
" ",
new SourceNode(1, 1, "b.js", "b"),
"}());"
]);
node.setSourceContent("b.js", "otherContent");
let map = node.toStringWithSourceMap({
file: "foo.js"
}).map;
assert.ok(
map instanceof SourceMapGenerator,
"map instanceof SourceMapGenerator"
);
map = await new SourceMapConsumer(map.toString());
assert.equal(map.sources.length, 2);
assert.equal(map.sources[0], "a.js");
assert.equal(map.sources[1], "b.js");
assert.equal(map.sourcesContent.length, 2);
assert.equal(map.sourcesContent[0], "someContent");
assert.equal(map.sourcesContent[1], "otherContent");
map.destroy();
};
exports["test walkSourceContents"] = function(assert) {
const aNode = new SourceNode(1, 1, "a.js", "a");
aNode.setSourceContent("a.js", "someContent");
const node = new SourceNode(null, null, null, [
"(function () {\n",
" ",
aNode,
" ",
new SourceNode(1, 1, "b.js", "b"),
"}());"
]);
node.setSourceContent("b.js", "otherContent");
const results = [];
node.walkSourceContents(function(sourceFile, sourceContent) {
results.push([sourceFile, sourceContent]);
});
assert.equal(results.length, 2);
assert.equal(results[0][0], "a.js");
assert.equal(results[0][1], "someContent");
assert.equal(results[1][0], "b.js");
assert.equal(results[1][1], "otherContent");
};
exports["test from issue 258"] = async function(assert) {
const node = new SourceNode();
const reactCode =
";require(0);\n//# sourceMappingURL=/index.ios.map?platform=ios&dev=false&minify=true";
const reactMap =
// eslint-disable-next-line
'{"version":3,"file":"/index.ios.bundle?platform=ios&dev=false&minify=true","sections":[{"offset":{"line":0,"column":0},"map":{"version":3,"sources":["require-0.js"],"names":[],"mappings":"AAAA;","file":"require-0.js","sourcesContent":[";require(0);"]}}]}';
const map = await new SourceMapConsumer(reactMap);
node.add(SourceNode.fromStringWithSourceMap(reactCode, map));
map.destroy();
};