v8-to-istanbul icon indicating copy to clipboard operation
v8-to-istanbul copied to clipboard

Cannot read properties of undefined (reading 'endCol')

Open valeneiko opened this issue 2 years ago • 24 comments

I am getting Cannot read properties of undefined (reading 'endCol') when trying to generate coverage after a jest test run when using @swc/jest as a transformer.

Error

TypeError: Cannot read properties of undefined (reading 'endCol')
    at module.exports.sliceRange (/workspace/node_modules/.pnpm/[email protected]/node_modules/v8-to-istanbul/lib/range.js:30:54)
    at /workspace/node_modules/.pnpm/[email protected]/node_modules/v8-to-istanbul/lib/v8-to-istanbul.js:139:19
    at Array.forEach (<anonymous>)
    at /workspace/node_modules/.pnpm/[email protected]/node_modules/v8-to-istanbul/lib/v8-to-istanbul.js:125:20
    at Array.forEach (<anonymous>)
    at V8ToIstanbul.applyCoverage (/workspace/node_modules/.pnpm/[email protected]/node_modules/v8-to-istanbul/lib/v8-to-istanbul.js:124:12)

Details

During this call: https://github.com/istanbuljs/v8-to-istanbul/blob/b5ecd8242cf64389c9a656d78530bd0428edf195/lib/v8-to-istanbul.js#L205

_maybeRemapStartColEndCol({
  count: 0,
  startOffset: 340,
  endOffset: 348
})

It receives object with all properties null from originalPositionFor here: https://github.com/istanbuljs/v8-to-istanbul/blob/b5ecd8242cf64389c9a656d78530bd0428edf195/lib/source.js#L105

Which causes it to fail somewhere down the line.

Source File

export const noop = () => Promise.resolve();
export const noopSync = () => {
  /* noop */
};

Transformed File

If I transform it with @swc with source maps enabled, I get this:

"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
function _export(target, all) {
    for(var name in all)Object.defineProperty(target, name, {
        enumerable: true,
        get: all[name]
    });
}
_export(exports, {
    noop: ()=>noop,
    noopSync: ()=>noopSync
});
const noop = ()=>Promise.resolve();
const noopSync = ()=>{
/* noop */ };

//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi93b3Jrc3BhY2Uvc3JjL3V0aWxzL2Z1bmN0aW9uLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBjb25zdCBub29wID0gKCkgPT4gUHJvbWlzZS5yZXNvbHZlKCk7XG5leHBvcnQgY29uc3Qgbm9vcFN5bmMgPSAoKSA9PiB7XG4gIC8qIG5vb3AgKi9cbn07XG4iXSwibmFtZXMiOlsibm9vcCIsIm5vb3BTeW5jIiwiUHJvbWlzZSIsInJlc29sdmUiXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7OztJQUFhQSxJQUFJLE1BQUpBO0lBQ0FDLFFBQVEsTUFBUkE7O0FBRE4sTUFBTUQsT0FBTyxJQUFNRSxRQUFRQyxPQUFPO0FBQ2xDLE1BQU1GLFdBQVcsSUFBTTtBQUM1QixRQUFRLEdBQ1YifQ==

Source map

{
    "version": 3,
    "sources": [
        "/workspace/src/utils/function.ts"
    ],
    "sourcesContent": [
        "export const noop = () => Promise.resolve();\nexport const noopSync = () => {\n  /* noop */\n};\n"
    ],
    "names": [
        "noop",
        "noopSync",
        "Promise",
        "resolve"
    ],
    "mappings": "AAAA;;;;;;;;;;;IAAaA,IAAI,MAAJA;IACAC,QAAQ,MAARA;;AADN,MAAMD,OAAO,IAAME,QAAQC,OAAO;AAClC,MAAMF,WAAW,IAAM;AAC5B,QAAQ,GACV"
}

valeneiko avatar Oct 27 '22 14:10 valeneiko

@jridgewell can you tell if we're making wrong assumptions about source map shape here?

SimenB avatar Oct 27 '22 15:10 SimenB

I'd need a proper reproduction repo to fully debug. Or, if you could provide me with the arguments passed to the offsetToOriginalRelative call (I don't know what the value of sourceTranspiled.wrapperLength to calculate it based on the _maybeRemapStartColEndCol call).

My initial thought is that we're hitting one of the unmapped regions in the map (everything that's not highlighted). If that's the case, then we do return an all-null InvalidOriginalMapping.

jridgewell avatar Oct 27 '22 17:10 jridgewell

"wrapperLength":77

Here is the full JSON.stringify():

{
  "sourceMap": {"version":3,"file":"/workspace/src/utils/function.ts","names":["noop","noopSync","Promise","resolve"],"sources":["/workspace/src/utils/function.ts"],"sourcesContent":["export const noop = () => Promise.resolve();\nexport const noopSync = () => {\n  /* noop */\n};\n"],"resolvedSources":["/workspace/src/utils/function.ts"],"_encoded":"AAAA;;;;;;;;;;;IAAaA,IAAI,MAAJA;IACAC,QAAQ,MAARA;;AADN,MAAMD,OAAO,IAAME,QAAQC,OAAO;AAClC,MAAMF,WAAW,IAAM;AAC5B,QAAQ,GACV","_decoded":[[[0,0,0,0]],[],[],[],[],[],[],[],[],[],[],[[4,0,0,13,0],[8,0,0,17],[14,0,0,13,0]],[[4,0,1,13,1],[12,0,1,21],[18,0,1,13,1]],[],[[0,0,0,7],[6,0,0,13,0],[13,0,0,20],[17,0,0,26,2],[25,0,0,34,3],[32,0,0,41]],[[0,0,1,7],[6,0,1,13,1],[17,0,1,24],[21,0,1,30]],[[0,0,2,2],[8,0,2,10],[11,0,3,0]]],"_decodedMemo":{"lastKey":5,"lastNeedle":0,"lastIndex":-1}},
  "startCol": 263,
  "endCol": 271,
  "this": {"lines":[{"line":1,"startCol":0,"endCol":13,"count":1,"ignore":false},{"line":2,"startCol":14,"endCol":60,"count":1,"ignore":false},{"line":3,"startCol":61,"endCol":76,"count":1,"ignore":false},{"line":4,"startCol":77,"endCol":80,"count":1,"ignore":false},{"line":5,"startCol":81,"endCol":112,"count":1,"ignore":false},{"line":6,"startCol":113,"endCol":174,"count":1,"ignore":false},{"line":7,"startCol":175,"endCol":200,"count":1,"ignore":false},{"line":8,"startCol":201,"endCol":223,"count":1,"ignore":false},{"line":9,"startCol":224,"endCol":231,"count":1,"ignore":false},{"line":10,"startCol":232,"endCol":233,"count":1,"ignore":false},{"line":11,"startCol":234,"endCol":252,"count":1,"ignore":false},{"line":12,"startCol":253,"endCol":272,"count":1,"ignore":false},{"line":13,"startCol":273,"endCol":299,"count":1,"ignore":false},{"line":14,"startCol":300,"endCol":303,"count":1,"ignore":false},{"line":15,"startCol":304,"endCol":339,"count":1,"ignore":false},{"line":16,"startCol":340,"endCol":362,"count":1,"ignore":false},{"line":17,"startCol":363,"endCol":376,"count":1,"ignore":false},{"line":18,"startCol":377,"endCol":377,"count":1,"ignore":false},{"line":19,"startCol":378,"endCol":944,"count":1,"ignore":false}],"eof":944,"shebangLength":0,"wrapperLength":77}
}

valeneiko avatar Oct 27 '22 18:10 valeneiko

Sorry, I'm still missing the this.covSources field.

jridgewell avatar Oct 29 '22 23:10 jridgewell

Previously I sent this from offsetToOriginalRelative, that's way it wasn't there. Here is this from _maybeRemapStartColEndCol:

{"path":"/workspace/src/utils/function.ts","wrapperLength":77,"sources":{"originalSource":"export const noop = () => Promise.resolve();\nexport const noopSync = () => {\n  /* noop */\n};\n","source":"\"use strict\";\nObject.defineProperty(exports, \"__esModule\", {\n    value: true\n});\nfunction _export(target, all) {\n    for(var name in all)Object.defineProperty(target, name, {\n        enumerable: true,\n        get: all[name]\n    });\n}\n_export(exports, {\n    noop: ()=>noop,\n    noopSync: ()=>noopSync\n});\nconst noop = ()=>Promise.resolve();\nconst noopSync = ()=>{\n/* noop */ };\n\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi93b3Jrc3BhY2Uvc3JjL3V0aWxzL2Z1bmN0aW9uLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBjb25zdCBub29wID0gKCkgPT4gUHJvbWlzZS5yZXNvbHZlKCk7XG5leHBvcnQgY29uc3Qgbm9vcFN5bmMgPSAoKSA9PiB7XG4gIC8qIG5vb3AgKi9cbn07XG4iXSwibmFtZXMiOlsibm9vcCIsIm5vb3BTeW5jIiwiUHJvbWlzZSIsInJlc29sdmUiXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7OztJQUFhQSxJQUFJLE1BQUpBO0lBQ0FDLFFBQVEsTUFBUkE7O0FBRE4sTUFBTUQsT0FBTyxJQUFNRSxRQUFRQyxPQUFPO0FBQ2xDLE1BQU1GLFdBQVcsSUFBTTtBQUM1QixRQUFRLEdBQ1YifQ==","sourceMap":{"sourcemap":{"file":"/workspace/src/utils/function.ts","version":3,"sources":["/workspace/src/utils/function.ts"],"sourcesContent":["export const noop = () => Promise.resolve();\nexport const noopSync = () => {\n  /* noop */\n};\n"],"names":["noop","noopSync","Promise","resolve"],"mappings":"AAAA;;;;;;;;;;;IAAaA,IAAI,MAAJA;IACAC,QAAQ,MAARA;;AADN,MAAMD,OAAO,IAAME,QAAQC,OAAO;AAClC,MAAMF,WAAW,IAAM;AAC5B,QAAQ,GACV"}}},"generatedLines":[],"branches":{},"functions":{},"covSources":[{"source":{"lines":[{"line":1,"startCol":0,"endCol":44,"count":1,"ignore":false},{"line":2,"startCol":45,"endCol":76,"count":1,"ignore":false},{"line":3,"startCol":77,"endCol":89,"count":1,"ignore":false},{"line":4,"startCol":90,"endCol":92,"count":1,"ignore":false}],"eof":92,"shebangLength":0,"wrapperLength":77},"path":"/workspace/src/utils/function.ts"}],"rawSourceMap":{"sourcemap":{"file":"/workspace/src/utils/function.ts","version":3,"sources":["/workspace/src/utils/function.ts"],"sourcesContent":["export const noop = () => Promise.resolve();\nexport const noopSync = () => {\n  /* noop */\n};\n"],"names":["noop","noopSync","Promise","resolve"],"mappings":"AAAA;;;;;;;;;;;IAAaA,IAAI,MAAJA;IACAC,QAAQ,MAARA;;AADN,MAAMD,OAAO,IAAME,QAAQC,OAAO;AAClC,MAAMF,WAAW,IAAM;AAC5B,QAAQ,GACV"}},"sourceMap":{"version":3,"file":"/workspace/src/utils/function.ts","names":["noop","noopSync","Promise","resolve"],"sources":["/workspace/src/utils/function.ts"],"sourcesContent":["export const noop = () => Promise.resolve();\nexport const noopSync = () => {\n  /* noop */\n};\n"],"resolvedSources":["/workspace/src/utils/function.ts"],"_encoded":"AAAA;;;;;;;;;;;IAAaA,IAAI,MAAJA;IACAC,QAAQ,MAARA;;AADN,MAAMD,OAAO,IAAME,QAAQC,OAAO;AAClC,MAAMF,WAAW,IAAM;AAC5B,QAAQ,GACV","_decoded":[[[0,0,0,0]],[],[],[],[],[],[],[],[],[],[],[[4,0,0,13,0],[8,0,0,17],[14,0,0,13,0]],[[4,0,1,13,1],[12,0,1,21],[18,0,1,13,1]],[],[[0,0,0,7],[6,0,0,13,0],[13,0,0,20],[17,0,0,26,2],[25,0,0,34,3],[32,0,0,41]],[[0,0,1,7],[6,0,1,13,1],[17,0,1,24],[21,0,1,30]],[[0,0,2,2],[8,0,2,10],[11,0,3,0]]],"_decodedMemo":{"lastKey":5,"lastNeedle":0,"lastIndex":-1}},"sourceTranspiled":{"lines":[{"line":1,"startCol":0,"endCol":13,"count":1,"ignore":false},{"line":2,"startCol":14,"endCol":60,"count":1,"ignore":false},{"line":3,"startCol":61,"endCol":76,"count":1,"ignore":false},{"line":4,"startCol":77,"endCol":80,"count":1,"ignore":false},{"line":5,"startCol":81,"endCol":112,"count":1,"ignore":false},{"line":6,"startCol":113,"endCol":174,"count":1,"ignore":false},{"line":7,"startCol":175,"endCol":200,"count":1,"ignore":false},{"line":8,"startCol":201,"endCol":223,"count":1,"ignore":false},{"line":9,"startCol":224,"endCol":231,"count":1,"ignore":false},{"line":10,"startCol":232,"endCol":233,"count":1,"ignore":false},{"line":11,"startCol":234,"endCol":252,"count":1,"ignore":false},{"line":12,"startCol":253,"endCol":272,"count":1,"ignore":false},{"line":13,"startCol":273,"endCol":299,"count":1,"ignore":false},{"line":14,"startCol":300,"endCol":303,"count":1,"ignore":false},{"line":15,"startCol":304,"endCol":339,"count":1,"ignore":false},{"line":16,"startCol":340,"endCol":362,"count":1,"ignore":false},{"line":17,"startCol":363,"endCol":376,"count":1,"ignore":false},{"line":18,"startCol":377,"endCol":377,"count":1,"ignore":false},{"line":19,"startCol":378,"endCol":944,"count":1,"ignore":false}],"eof":944,"shebangLength":0,"wrapperLength":77},"all":false}

valeneiko avatar Oct 30 '22 10:10 valeneiko

i've run into this too, i've pinned @swc/core at 1.3.3, every patch release after that seems to cause this error

jgeschwendt avatar Nov 01 '22 19:11 jgeschwendt

@jgeschwendt I tried about 500 different upgrades/resolutions within my package.json today with no luck until I stumbled upon this comment, so thank you 🥇

zaslow avatar Nov 01 '22 21:11 zaslow

Ok, so the relevant call to offsetToOriginalRelative has the params (sourceMap, 263, 271), and if we sliceRange(lines, 263, 271, true) we get [{ "line": 12, "startCol": 253, "endCol": 272, "count": 1, "ignore": false }].

That's a single line (0-based) matching line 13: ····noopSync: ()=>noopSync,. Our start = originalPositionTryBoth(sourceMap, 12, 10) returns { source '…', line: 2, column: 13, name: '…' } (1-based) and end = originalEndPositionFor(sourceMap, 12, 18) returns the same { source '…', line: 2, column: 13, name: '…' }. This totally valid, and if we look at the sourcemap visualizer, we see this:

Screen Shot 2022-11-05 at 1 44 42 PM Screen Shot 2022-11-05 at 1 44 47 PM

This is a valid transform and sourcemap.

So we hit end = originalPositionFor(sourceMap, 12, 18, LEAST_UPPER_BOUND), and this returns the all-null { source: null, line: null, column null, name: null }. Again, correct, there's no other segments after that on this line to return with an upper bound search. We don't guard this call with a !(end && end.source) check, and we get the bug.

I'm not sure why we're performing that second end = originalPositionFor(…) call…

jridgewell avatar Nov 05 '22 17:11 jridgewell

Not sure if this will help anyone, but I encountered the same issue, the file was exporting constants only:

export const XXXXXXX = 'something'
export const YYYYYYY = 'something'

After tracking down the issue, I figured out the solution, I just added one space above the very first export, so my file became:

// Constants

export const XXXXXXX = 'something'
export const YYYYYYY = 'something'

I guessed it because in the file range.js, I found that the end variable was -1, so by adding that comment line on top of the file, the issue went away.

aladinflux avatar Nov 09 '22 06:11 aladinflux

Hi, same issue experienced here. Independently to a proper and source based fix we have an easy patch applied at @jest/reporters/v8-to-istanbul@^9.0.1

diff --git a/lib/range.js b/lib/range.js
index 4abdfe78be6c8fab30225d65970cf66cb055f941..901722bcee414924eafe8b4585d2f0522ba8681e 100644
--- a/lib/range.js
+++ b/lib/range.js
@@ -16,7 +16,7 @@ module.exports.sliceRange = (lines, startCol, endCol, inclusive = false) => {
     if (startCol >= lines[mid].endCol) {
       start = mid + 1
     } else if (endCol < lines[mid].startCol) {
-      end = mid - 1
+      end = Math.max(mid - 1, start);
     } else {
       end = mid
       while (mid >= 0 && startCol < lines[mid].endCol && endCol >= lines[mid].startCol) {

That makes end not go beneath start position. I would suggest to add that to a PR but knowing that is not a fix of the problem creator but a problem stopper.

devcorraliza avatar Nov 10 '22 09:11 devcorraliza

I lowered @swc/jest to version 2.20 and it worked!

riku0202 avatar Dec 13 '22 09:12 riku0202

Tried 0.2.20 and it doesn't work

dyatko avatar Dec 14 '22 13:12 dyatko

I have the same issue

zfben avatar Jan 04 '23 13:01 zfben

@hurstindustries curious why you closed your PR (apologies for the slow review 😄 , I'm guessing that might be the reasoning).

I'll keep an eye out for a patch for this issue.

bcoe avatar Feb 14 '23 18:02 bcoe

Having an export const in the first line of the file was the problem for me. Adding a comment in line 1 and a return in line 2 as @aladinflux did worked for me.

1   // leave this line here so the test coverage won't break
2
3   export const func = () => {...}

🤷‍♂️

mitestainer avatar Feb 17 '23 20:02 mitestainer

The simplest and safest way is to change the endCol reading on line 30 in range.js to lines[end]?.endCol. At least this allows doing not patch lib to have a working coverage :).

wolandec avatar Feb 18 '23 18:02 wolandec

As @aladinflux and @mitestainer, adding a comment in line 1 of a file that was only exporting constants solved the issue (which was previously encountered in another project, and back then strangely resolved by lowering axios from 1.2.3 to 1.1.3). Quite curious as to why it happens.

clrtrm avatar Feb 22 '23 10:02 clrtrm

    "@swc/core": "1.3.36",
    "@swc/jest": "0.2.24",

today's latest, doesn't work.

benevbright avatar Feb 26 '23 08:02 benevbright

Ran into this same issue, and pinning the @swc/jest version didn't work. @aladinflux, thank you for the idea to check for top-line exports; that ended up being the problem.

For other folks' reference, I had to:

  1. isolate the one test in our suite that was throwing this error, and run it alone
  2. comment out every import in the file, and run again. Test fails, but coverage bug does not appear.
  3. one by one, uncomment an import and run the test again. If the coverage bug still does not appear, uncomment the next import and run again. If you uncomment an import and the coverage bug reappears, you'll know the issue is somewhere in the import tree of that file. Open it.
  4. Repeat steps 2 and 3 until you reach the leaf node(s) of your problem. Pay particular attention to any files whose first line is an export--add a comment and newline to the top of the file. Keep going til it's fixed.
  5. If your import tree was as large as mine, indulge in your vice of choice

brockross avatar Mar 10 '23 20:03 brockross

@brockross it much easier to use small patch - https://github.com/istanbuljs/v8-to-istanbul/issues/198#issuecomment-1310007608. no any issues in very large (8000+ tests) repo since nov'22

koshic avatar Mar 10 '23 21:03 koshic

Hello, seems that the issue is still not fixed. As for me personally i wasnt able to find which test breaks the coverage collection process, so maybe really the easiest way is to add lines[end]**?**.endCol on line 30 in range.js ?

Edit: issue is caused by the first line export, so adding anything at the top of the file (for example comment) helps.

Nikkov17 avatar May 03 '23 23:05 Nikkov17

I ran into this today as well and really appreciate all the suggestions here but have to many tests to go one by one. In order to find the file that had export const at the top I modified ./node_modules/v8-to-istanbul/lib/v8-to-istanbul.js:146. Wrapping the call site in a try/catch and logging the path shows you which file is the issue. Hope it helps until a fix can be merged 👍

} else {
  try {
    lines = sliceRange(covSource.lines, startCol, endCol)
  } catch (e) {
    console.log(path);
    console.log(e);
  }
}

alexmarmon avatar Nov 27 '23 21:11 alexmarmon

Having an export const in the first line of the file was the problem for me. Adding a comment in line 1 and a return in line 2 as @aladinflux did worked for me.

1   // leave this line here so the test coverage won't break
2
3   export const func = () => {...}

🤷‍♂️

Bro... thanks, I've hit the same error with vitest --coverage, concluded this same thing, and found your comment here. Idk what to think of it really, but this fixed my own case.

big-kahuna-burger avatar Dec 20 '23 13:12 big-kahuna-burger

Also running into this issue when running some tests using Jest. Tried pinning @swc/core to 1.3.3, as suggested, and verified I have no exported const at the top of my test file, both to no avail.

TypeError: Cannot read properties of undefined (reading 'endCol')
    at module.exports.sliceRange (/workspace/node_modules/v8-to-istanbul/lib/range.js:30:54)
    at workspace/node_modules/v8-to-istanbul/lib/v8-to-istanbul.js:145:19
    at Array.forEach (<anonymous>)
    at workspace/node_modules/v8-to-istanbul/lib/v8-to-istanbul.js:130:20
    at Array.forEach (<anonymous>)
    at V8ToIstanbul.applyCoverage (workspace/node_modules/v8-to-istanbul/lib/v8-to-istanbul.js:129:12)
    at workspace/node_modules/@jest/reporters/build/CoverageReporter.js:531:21
    at async Promise.all (index 5)
    at async CoverageReporter._getCoverageResult (workspace/node_modules/@jest/reporters/build/CoverageReporter.js:500:35)
    at async CoverageReporter.onRunComplete (workspace/node_modules/@jest/reporters/build/CoverageReporter.js:172:34)
    at async ReporterDispatcher.onRunComplete (workspace/node_modules/@jest/core/build/ReporterDispatcher.js:71:9)
    at async TestScheduler.scheduleTests (workspace/node_modules/@jest/core/build/TestScheduler.js:306:5)
    at async runJest (workspace/node_modules/@jest/core/build/runJest.js:367:19)
    at async _run10000 (workspace/node_modules/@jest/core/build/cli/index.js:343:7)
    at async runCLI (workspace/node_modules/@jest/core/build/cli/index.js:198:3)
    at async Object.run (workspace/node_modules/jest-cli/build/run.js:130:37)
husky - pre-commit hook exited with code 1 (error)

reverbePrintemps avatar Apr 08 '24 14:04 reverbePrintemps