Skip to content

Commit 3cb92cc

Browse files
authored
Merge pull request #505 from takikawa/add-spec-tests
Add source map spec tests
2 parents 43819cc + d243a04 commit 3cb92cc

File tree

4 files changed

+214
-2
lines changed

4 files changed

+214
-2
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "test/source-map-tests"]
2+
path = test/source-map-tests
3+
url = https://github.com/tc39/source-map-tests.git

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@
6565
},
6666
"license": "BSD-3-Clause",
6767
"scripts": {
68-
"lint": "eslint --fix *.js lib/ test/",
69-
"test": "node test/run-tests.js",
68+
"lint": "eslint --fix *.js lib/ test/ --ignore-pattern 'test/source-map-tests/**'",
69+
"test": "git submodule update --init --recursive; node test/run-tests.js",
7070
"coverage": "c8 --reporter=text --reporter=html npm test",
7171
"prettier": "prettier --write .",
7272
"clean": "rm -rf coverage",

test/source-map-tests

Submodule source-map-tests added at 14c8974

test/test-spec-tests.js

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
/* -*- Mode: js; js-indent-level: 2; -*- */
2+
/*
3+
* Copyright 2024 Mozilla Foundation and contributors
4+
* Licensed under the New BSD license. See LICENSE or:
5+
* http://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
8+
const fs = require("fs").promises;
9+
const SourceMapConsumer =
10+
require("../lib/source-map-consumer").SourceMapConsumer;
11+
12+
const sourceMapSpecTests = require("./source-map-tests/source-map-spec-tests.json");
13+
14+
async function readJSON(path) {
15+
const file = await fs.open(require.resolve(path));
16+
const json = JSON.parse(await file.readFile());
17+
file.close();
18+
return json;
19+
}
20+
21+
// Known failures due to intentional implementation choices or due to bugs.
22+
const skippedTests = [
23+
// Versions are explicitly checked a bit loosely.
24+
"versionNumericString",
25+
// Stricter sources array checking isn't implemented.
26+
"sourcesNotStringOrNull",
27+
"sourcesAndSourcesContentBothNull",
28+
// Stricter names array checking isn't implemented.
29+
"namesMissing",
30+
"namesNotString",
31+
// This check isn't as strict in this library.
32+
"invalidMappingNotAString1",
33+
// A mapping segment with no fields is technically invalid in the spec.
34+
"invalidMappingSegmentWithZeroFields",
35+
// These tests fail due to imprecision in the spec about the 32-bit limit.
36+
"invalidMappingSegmentWithColumnExceeding32Bits",
37+
"invalidMappingSegmentWithOriginalLineExceeding32Bits",
38+
"invalidMappingSegmentWithOriginalColumnExceeding32Bits",
39+
// A large VLQ that should parse, but currently does not.
40+
"validMappingLargeVLQ",
41+
// The library currently doesn't check the types of offset lines/columns.
42+
"indexMapOffsetLineWrongType",
43+
"indexMapOffsetColumnWrongType",
44+
// The spec is not totally clear about this case.
45+
"indexMapInvalidBaseMappings",
46+
// The spec's definition of overlap can be refined
47+
"indexMapInvalidOverlap",
48+
// The library doesn't support the new ignoreList feature yet.
49+
"ignoreListWrongType1",
50+
"ignoreListWrongType2",
51+
"ignoreListWrongType3",
52+
"ignoreListOutOfBounds",
53+
];
54+
55+
// The source-map library converts null sources to the "null" URL in its
56+
// sources list, so for equality checking we accept this as null.
57+
function nullish(nullOrString) {
58+
if (nullOrString === "null") {
59+
return null;
60+
}
61+
return nullOrString;
62+
}
63+
64+
function mapLine(line) {
65+
return line + 1;
66+
}
67+
68+
async function testMappingAction(assert, rawSourceMap, action) {
69+
return SourceMapConsumer.with(rawSourceMap, null, consumer => {
70+
let mappedPosition = consumer.originalPositionFor({
71+
line: mapLine(action.generatedLine),
72+
column: action.generatedColumn,
73+
});
74+
75+
assert.equal(
76+
mappedPosition.line,
77+
mapLine(action.originalLine),
78+
`original line didn't match, expected ${mapLine(
79+
action.originalLine
80+
)} got ${mappedPosition.line}`
81+
);
82+
assert.equal(
83+
mappedPosition.column,
84+
action.originalColumn,
85+
`original column didn't match, expected ${action.originalColumn} got ${mappedPosition.column}`
86+
);
87+
assert.equal(
88+
nullish(mappedPosition.source),
89+
action.originalSource,
90+
`original source didn't match, expected ${action.originalSource} got ${mappedPosition.source}`
91+
);
92+
if (action.mappedName) {
93+
assert.equal(
94+
mappedPosition.name,
95+
action.mappedName,
96+
`mapped name didn't match, expected ${action.mappedName} got ${mappedPosition.name}`
97+
);
98+
}
99+
100+
// When the source is null, a reverse lookup may not make sense
101+
// because there isn't a unique way to look it up.
102+
if (action.originalSource !== null) {
103+
mappedPosition = consumer.generatedPositionFor({
104+
source: action.originalSource,
105+
line: mapLine(action.originalLine),
106+
column: action.originalColumn,
107+
});
108+
109+
assert.equal(
110+
mappedPosition.line,
111+
mapLine(action.generatedLine),
112+
`generated line didn't match, expected ${mapLine(
113+
action.generatedLine
114+
)} got ${mappedPosition.line}`
115+
);
116+
assert.equal(
117+
mappedPosition.column,
118+
action.generatedColumn,
119+
`generated column didn't match, expected ${action.generatedColumn} got ${mappedPosition.column}`
120+
);
121+
}
122+
});
123+
}
124+
125+
async function testTransitiveMappingAction(assert, rawSourceMap, action) {
126+
return SourceMapConsumer.with(rawSourceMap, null, async consumer => {
127+
assert.ok(
128+
Array.isArray(action.intermediateMaps),
129+
"transitive mapping case requires intermediate maps"
130+
);
131+
132+
let mappedPosition = consumer.originalPositionFor({
133+
line: mapLine(action.generatedLine),
134+
column: action.generatedColumn,
135+
});
136+
137+
for (const intermediateMapPath of action.intermediateMaps) {
138+
const intermediateMap = await readJSON(
139+
`./source-map-tests/resources/${intermediateMapPath}`
140+
);
141+
await SourceMapConsumer.with(
142+
intermediateMap,
143+
null,
144+
consumerIntermediate => {
145+
mappedPosition = consumerIntermediate.originalPositionFor({
146+
line: mappedPosition.line,
147+
column: mappedPosition.column,
148+
});
149+
}
150+
);
151+
}
152+
153+
assert.equal(
154+
mappedPosition.line,
155+
mapLine(action.originalLine),
156+
`original line didn't match, expected ${mapLine(
157+
action.originalLine
158+
)} got ${mappedPosition.line}`
159+
);
160+
assert.equal(
161+
mappedPosition.column,
162+
action.originalColumn,
163+
`original column didn't match, expected ${action.originalColumn} got ${mappedPosition.column}`
164+
);
165+
assert.equal(
166+
mappedPosition.source,
167+
action.originalSource,
168+
`original source didn't match, expected ${action.originalSource} got ${mappedPosition.source}`
169+
);
170+
});
171+
}
172+
173+
for (const testCase of sourceMapSpecTests.tests) {
174+
if (skippedTests.includes(testCase.name)) {
175+
continue;
176+
}
177+
exports[`test from source map spec tests, name: ${testCase.name}`] =
178+
async function (assert) {
179+
const json = await readJSON(
180+
`./source-map-tests/resources/${testCase.sourceMapFile}`
181+
);
182+
try {
183+
const map = await new SourceMapConsumer(json);
184+
map.eachMapping(() => {});
185+
map.destroy();
186+
} catch (exn) {
187+
if (testCase.sourceMapIsValid) {
188+
assert.fail(
189+
"Expected valid source map but failed to load successfully: " +
190+
exn.message
191+
);
192+
}
193+
return;
194+
}
195+
if (!testCase.sourceMapIsValid) {
196+
assert.fail("Expected invalid source map but loaded successfully");
197+
}
198+
if (testCase.testActions) {
199+
for (const testAction of testCase.testActions) {
200+
if (testAction.actionType == "checkMapping") {
201+
await testMappingAction(assert, json, testAction);
202+
} else if (testAction.actionType == "checkMappingTransitive") {
203+
await testTransitiveMappingAction(assert, json, testAction);
204+
}
205+
}
206+
}
207+
};
208+
}

0 commit comments

Comments
 (0)