diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..6aaf3007 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +test/fixtures/scripts/needs-compile.js eol=crlf +test/fixtures/scripts/needs-compile.compiled.js eol=crlf +test/fixtures/scripts/needs-compile.compiled.js.map eol=crlf \ No newline at end of file diff --git a/.gitignore b/.gitignore index 950be1d1..da8673f3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ coverage .nyc_output node_modules +.idea diff --git a/lib/source.js b/lib/source.js index f592f52e..60569da2 100644 --- a/lib/source.js +++ b/lib/source.js @@ -56,7 +56,7 @@ module.exports = class CovSource { const start = originalPositionTryBoth( sourceMap, lines[0].line, - startCol - lines[0].startCol + Math.max(0, startCol - lines[0].startCol) ) let end = originalEndPositionFor( sourceMap, diff --git a/test/fixtures/scripts/needs-compile.compiled.js b/test/fixtures/scripts/needs-compile.compiled.js new file mode 100644 index 00000000..680de596 --- /dev/null +++ b/test/fixtures/scripts/needs-compile.compiled.js @@ -0,0 +1,51 @@ +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +function _classPrivateFieldGet(receiver, privateMap) { var descriptor = privateMap.get(receiver); if (!descriptor) { throw new TypeError("attempted to get private field on non-instance"); } if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; } + +// compile me with babel 7+: +// > babel --plugins @babel/plugin-proposal-class-properties test/fixtures/scripts/needs-compile.js --out-file test/fixtures/scripts/needs-compile.compiled.js --source-maps +// class uses private class fields - must be compiled +// hello is called +class Foo { + constructor() { + _defineProperty(this, "a", 1); + + _b.set(this, { + writable: true, + value: 2 + }); + + this.c = this.a + _classPrivateFieldGet(this, _b); + } + + hello(i) { + console.info('hello:', this.c + i); + } + +} // class uses private class fields - must be compiled +// hello is never called + + +var _b = new WeakMap(); + +class Bar { + constructor() { + _test.set(this, { + writable: true, + value: 99 + }); + } + + hello() { + console.info(`Hello ${_classPrivateFieldGet(this, _test)}`); + } + +} + +var _test = new WeakMap(); + +const foo = new Foo(); +foo.hello(1); +const bar = new Bar(); + +//# sourceMappingURL=needs-compile.compiled.js.map \ No newline at end of file diff --git a/test/fixtures/scripts/needs-compile.compiled.js.map b/test/fixtures/scripts/needs-compile.compiled.js.map new file mode 100644 index 00000000..fd116497 --- /dev/null +++ b/test/fixtures/scripts/needs-compile.compiled.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["needs-compile.js"],"names":[],"mappings":";;;;AAAA;AACA;AACA;AACA;AACA,MAAM,GAAN,CAAU;AAGR,EAAA,WAAW,GAAI;AAAA,+BAFX,CAEW;;AAAA;AAAA;AAAA,aADV;AACU;;AACb,SAAK,CAAL,GAAS,KAAK,CAAL,yBAAS,IAAT,KAAT;AACD;;AACD,EAAA,KAAK,CAAE,CAAF,EAAK;AACR,IAAA,OAAO,CAAC,IAAR,CAAa,QAAb,EAAuB,KAAK,CAAL,GAAS,CAAhC;AACD;;AARO,C,CAWV;AACA;;;;;AACA,MAAM,GAAN,CAAU;AAER,EAAA,WAAW,GAAI;AAAA;AAAA;AAAA,aADP;AACO;AACd;;AACD,EAAA,KAAK,GAAI;AACP,IAAA,OAAO,CAAC,IAAR,CAAc,SAAD,sBAAS,IAAT,QAAoB,EAAjC;AACD;;AANO;;;;AASV,MAAM,GAAG,GAAG,IAAI,GAAJ,EAAZ;AACA,GAAG,CAAC,KAAJ,CAAU,CAAV;AACA,MAAM,GAAG,GAAG,IAAI,GAAJ,EAAZ","file":"needs-compile.compiled.js","sourcesContent":["// compile me with babel 7+:\n// > babel --plugins @babel/plugin-proposal-class-properties test/fixtures/scripts/needs-compile.js --out-file test/fixtures/scripts/needs-compile.compiled.js --source-maps\n// class uses private class fields - must be compiled\n// hello is called\nclass Foo {\n a = 1\n #b = 2\n constructor () {\n this.c = this.a + this.#b;\n }\n hello (i) {\n console.info('hello:', this.c + i)\n }\n}\n\n// class uses private class fields - must be compiled\n// hello is never called\nclass Bar {\n #test = 99\n constructor () {\n }\n hello () {\n console.info(`Hello ${this.#test}`)\n }\n}\n\nconst foo = new Foo();\nfoo.hello(1)\nconst bar = new Bar()\n\n"]} \ No newline at end of file diff --git a/test/fixtures/scripts/needs-compile.js b/test/fixtures/scripts/needs-compile.js new file mode 100644 index 00000000..149129ff --- /dev/null +++ b/test/fixtures/scripts/needs-compile.js @@ -0,0 +1,32 @@ +// compile me with babel 7+: +// > babel --plugins @babel/plugin-proposal-class-properties test/fixtures/scripts/needs-compile.js --out-file test/fixtures/scripts/needs-compile.compiled.js --source-maps +// in addition, a .gitattribute clause forces this file and its compilation assets to crlf + +// class uses private class fields - must be compiled +// hello is called +class Foo { + a = 1 + #b = 2 + constructor () { + this.c = this.a + this.#b; + } + hello (i) { + console.info('hello:', this.c + i) + } +} + +// class uses private class fields - must be compiled +// hello is never called +class Bar { + #test = 99 + constructor () { + } + hello () { + console.info(`Hello ${this.#test}`) + } +} + +const foo = new Foo(); +foo.hello(1) +const bar = new Bar() + diff --git a/test/v8-to-istanbul.js b/test/v8-to-istanbul.js index 10004967..bf17f13a 100644 --- a/test/v8-to-istanbul.js +++ b/test/v8-to-istanbul.js @@ -6,6 +6,7 @@ const V8ToIstanbul = require('../lib/v8-to-istanbul') const crypto = require('crypto') const os = require('os') const sourcemap = require('source-map') +const assert = require('assert') require('tap').mochaGlobals() require('should') @@ -88,6 +89,36 @@ ${'//'}${'#'} sourceMappingURL=data:application/json;base64,${base64Sourcemap} // that means it was bale to access the content via the provided sources object v8ToIstanbul.sourceTranspiled.should.not.be.undefined() }) + + it('should clamp line source column >= 0', async () => { + const v8ToIstanbul = new V8ToIstanbul( + `file://${require.resolve('./fixtures/scripts/needs-compile.compiled.js')}`, + 0 + ) + + // read the file and find the first end of line char + const fileBody = readFileSync(require.resolve('./fixtures/scripts/needs-compile.compiled.js')).toString() + const matchedNewLineChar = fileBody.match(/(?<=\r?\n)/u).index + + // this isn't an assertion for the test so much as it is an assertion that the + // test fixture hasn't be reverted from \r\n to \n. + assert(fileBody.substring(matchedNewLineChar - 2, matchedNewLineChar) === '\r\n', 'The test fixture is misconfigured!') + + await v8ToIstanbul.load() + // apply a fake range that starts with the matched new line + // (these ranges can occur on v8 running on windows) and verify + // coverage is applied correctly. CovLine will have a + // gap between the endCol of the previous line ending on \r and startCol of the + // next line. This would cause -1 to be sent for the source map lookup. + // This would cause source map translation to throw + v8ToIstanbul.applyCoverage([{ + functionName: 'fake', + ranges: [{ + startOffset: matchedNewLineChar - 1, + endOffset: matchedNewLineChar + 10 + }] + }]) + }) }) describe('source map format edge cases', () => { let consoleWarn