diff --git a/.xo-config.json b/.xo-config.json index 326e4e016..56282e412 100644 --- a/.xo-config.json +++ b/.xo-config.json @@ -2,6 +2,7 @@ "ignores": [ "media/**", "test/config/fixtures/config-errors/test.js", + "test/line-numbers/fixtures/line-numbers.js", "test-tap/fixture/snapshots/test-sourcemaps/build/**", "test-tap/fixture/report/edgecases/ast-syntax-error.cjs", "examples/typescript-*/**/*.ts" diff --git a/lib/worker/line-numbers.js b/lib/worker/line-numbers.js index a7248936c..c7865f85f 100644 --- a/lib/worker/line-numbers.js +++ b/lib/worker/line-numbers.js @@ -1,5 +1,5 @@ import * as fs from 'node:fs'; -import {createRequire} from 'node:module'; +import {createRequire, findSourceMap} from 'node:module'; import {pathToFileURL} from 'node:url'; import callsites from 'callsites'; @@ -60,15 +60,45 @@ function findTest(locations, declaration) { const range = (start, end) => Array.from({length: end - start + 1}).fill(start).map((element, index) => element + index); +const translate = (sourceMap, pos) => { + if (sourceMap === undefined) { + return pos; + } + + const entry = sourceMap.findEntry(pos.line - 1, pos.column); // Source maps are 0-based + return { + line: entry.originalLine + 1, // Readjust for Acorn. + column: entry.originalColumn, + }; +}; + export default function lineNumberSelection({file, lineNumbers = []}) { if (lineNumbers.length === 0) { return undefined; } - const locations = parse(file); const selected = new Set(lineNumbers); + let locations = parse(file); + let lookedForSourceMap = false; + let sourceMap; + return () => { + if (!lookedForSourceMap) { + lookedForSourceMap = true; + + // The returned function is called *after* the file has been loaded. + // Source maps are not available before then. + sourceMap = findSourceMap(file); + + if (sourceMap !== undefined) { + locations = locations.map(({start, end}) => ({ + start: translate(sourceMap, start), + end: translate(sourceMap, end), + })); + } + } + // Assume this is called from a test declaration, which is located in the file. // If not… don't select the test! const callSite = callsites().find(callSite => { @@ -83,10 +113,10 @@ export default function lineNumberSelection({file, lineNumbers = []}) { return false; } - const start = { - line: callSite.getLineNumber(), - column: callSite.getColumnNumber() - 1, // Use 0-indexed columns. - }; + const start = translate(sourceMap, { + line: callSite.getLineNumber(), // 1-based + column: callSite.getColumnNumber() - 1, // Comes out as 1-based, Acorn wants 0-based + }); const test = findTest(locations, start); if (!test) { diff --git a/test/line-numbers/fixtures/README.md b/test/line-numbers/fixtures/README.md new file mode 100644 index 000000000..c35659a08 --- /dev/null +++ b/test/line-numbers/fixtures/README.md @@ -0,0 +1,5 @@ +Compile using: + +```console +npx tsc line-numbers.ts --outDir . --sourceMap --module es2020 --moduleResolution node +``` diff --git a/test/line-numbers/fixtures/line-numbers.js b/test/line-numbers/fixtures/line-numbers.js index 1ba516de4..1fb8144df 100644 --- a/test/line-numbers/fixtures/line-numbers.js +++ b/test/line-numbers/fixtures/line-numbers.js @@ -1,27 +1,23 @@ import test from 'ava'; - -test('unicorn', t => { - t.pass(); +test('unicorn', function (t) { + t.pass(); }); - -test('rainbow', t => { - t.pass(); +test('rainbow', function (t) { + t.pass(); }); - -test.serial('cat', t => { - t.pass(); +test.serial('cat', function (t) { + t.pass(); }); - test.todo('dog'); // eslint-disable-line ava/no-todo-test - /* eslint-disable max-statements-per-line, ava/no-inline-assertions */ -test('sun', t => t.pass()); test('moon', t => { - t.pass(); +test('sun', function (t) { return t.pass(); }); +test('moon', function (t) { + t.pass(); }); /* eslint-enable max-statements-per-line, ava/no-inline-assertions */ - -(() => { - test('nested call', t => { - t.pass(); - }); +(function () { + test('nested call', function (t) { + t.pass(); + }); })(); +//# sourceMappingURL=line-numbers.js.map \ No newline at end of file diff --git a/test/line-numbers/fixtures/line-numbers.js.map b/test/line-numbers/fixtures/line-numbers.js.map new file mode 100644 index 000000000..fed77ae4d --- /dev/null +++ b/test/line-numbers/fixtures/line-numbers.js.map @@ -0,0 +1 @@ +{"version":3,"file":"line-numbers.js","sourceRoot":"","sources":["line-numbers.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,KAAK,CAAC;AAEvB,IAAI,CAAC,SAAS,EAAE,UAAA,CAAC;IAChB,CAAC,CAAC,IAAI,EAAE,CAAC;AACV,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,SAAS,EAAE,UAAA,CAAC;IAChB,CAAC,CAAC,IAAI,EAAE,CAAC;AACV,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,UAAA,CAAC;IACnB,CAAC,CAAC,IAAI,EAAE,CAAC;AACV,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,uCAAuC;AAEzD,sEAAsE;AACtE,IAAI,CAAC,KAAK,EAAE,UAAA,CAAC,IAAI,OAAA,CAAC,CAAC,IAAI,EAAE,EAAR,CAAQ,CAAC,CAAC;AAAC,IAAI,CAAC,MAAM,EAAE,UAAA,CAAC;IACzC,CAAC,CAAC,IAAI,EAAE,CAAC;AACV,CAAC,CAAC,CAAC;AACH,qEAAqE;AAErE,CAAC;IACA,IAAI,CAAC,aAAa,EAAE,UAAA,CAAC;QACpB,CAAC,CAAC,IAAI,EAAE,CAAC;IACV,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,EAAE,CAAC"} \ No newline at end of file diff --git a/test/line-numbers/fixtures/line-numbers.ts b/test/line-numbers/fixtures/line-numbers.ts new file mode 100644 index 000000000..068887813 --- /dev/null +++ b/test/line-numbers/fixtures/line-numbers.ts @@ -0,0 +1,27 @@ +import test from 'ava'; // eslint-disable-line ava/no-ignored-test-files + +test('unicorn', t => { + t.pass(); +}); + +test('rainbow', t => { + t.pass(); +}); + +test.serial('cat', t => { + t.pass(); +}); + +test.todo('dog'); // eslint-disable-line ava/no-todo-test + +/* eslint-disable max-statements-per-line, ava/no-inline-assertions */ +test('sun', t => t.pass()); test('moon', t => { + t.pass(); +}); +/* eslint-enable max-statements-per-line, ava/no-inline-assertions */ + +(() => { + test('nested call', t => { + t.pass(); + }); +})();