Skip to content

Commit 62bc80c

Browse files
bcoetargos
authored andcommitted
process: add lineLength to source-map-cache
Without the line lengths of in-memory transpiled source, it's not possible to convert from byte ofsets to line/column offsets. PR-URL: #29863 Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: David Carlier <devnexen@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 735ec1b commit 62bc80c

6 files changed

Lines changed: 118 additions & 55 deletions

File tree

‎doc/api/cli.md‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,8 +1199,9 @@ If found, Source Map data is appended to the top-level key `source-map-cache`
11991199
on the JSON coverage object.
12001200

12011201
`source-map-cache` is an object with keys representing the files source maps
1202-
were extracted from, and the values include the raw source-map URL
1203-
(in the key `url`) and the parsed Source Map V3 information (in the key `data`).
1202+
were extracted from, and values which include the raw source-map URL
1203+
(in the key `url`), the parsed Source Map V3 information (in the key `data`),
1204+
and the line lengths of the source file (in the key `lineLengths`).
12041205

12051206
```json
12061207
{
@@ -1226,7 +1227,13 @@ were extracted from, and the values include the raw source-map URL
12261227
],
12271228
"mappings": "MAAMA,IACJC,YAAaC",
12281229
"sourceRoot": "./"
1229-
}
1230+
},
1231+
"lineLengths": [
1232+
13,
1233+
62,
1234+
38,
1235+
27
1236+
]
12301237
}
12311238
}
12321239
}

‎lib/internal/bootstrap/pre_execution.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ function prepareMainThreadExecution(expandArgv1 = false) {
2525
// prepareStackTrace method, replacing the default in errors.js.
2626
if(getOptionValue('--enable-source-maps')){
2727
const{ prepareStackTrace }=
28-
require('internal/source_map/source_map_cache');
28+
require('internal/source_map/prepare_stack_trace');
2929
const{ setPrepareStackTraceCallback }=internalBinding('errors');
3030
setPrepareStackTraceCallback(prepareStackTrace);
3131
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
'use strict';
2+
3+
constdebug=require('internal/util/debuglog').debuglog('source_map');
4+
const{ findSourceMap }=require('internal/source_map/source_map_cache');
5+
const{ overrideStackTrace }=require('internal/errors');
6+
7+
// Create a prettified stacktrace, inserting context from source maps
8+
// if possible.
9+
constErrorToString=Error.prototype.toString;// Capture original toString.
10+
constprepareStackTrace=(globalThis,error,trace)=>{
11+
// API for node internals to override error stack formatting
12+
// without interfering with userland code.
13+
// TODO(bcoe): add support for source-maps to repl.
14+
if(overrideStackTrace.has(error)){
15+
constf=overrideStackTrace.get(error);
16+
overrideStackTrace.delete(error);
17+
returnf(error,trace);
18+
}
19+
20+
const{ SourceMap }=require('internal/source_map/source_map');
21+
consterrorString=ErrorToString.call(error);
22+
23+
if(trace.length===0){
24+
returnerrorString;
25+
}
26+
constpreparedTrace=trace.map((t,i)=>{
27+
letstr=i!==0 ? '\n at ' : '';
28+
str=`${str}${t}`;
29+
try{
30+
constsourceMap=findSourceMap(t.getFileName(),error);
31+
if(sourceMap&&sourceMap.data){
32+
constsm=newSourceMap(sourceMap.data);
33+
// Source Map V3 lines/columns use zero-based offsets whereas, in
34+
// stack traces, they start at 1/1.
35+
const[,,url,line,col]=
36+
sm.findEntry(t.getLineNumber()-1,t.getColumnNumber()-1);
37+
if(url&&line!==undefined&&col!==undefined){
38+
str+=
39+
`\n -> ${url.replace('file://','')}:${line+1}:${col+1}`;
40+
}
41+
}
42+
}catch(err){
43+
debug(err.stack);
44+
}
45+
returnstr;
46+
});
47+
return`${errorString}\n at ${preparedTrace.join('')}`;
48+
};
49+
50+
module.exports={
51+
prepareStackTrace,
52+
};

‎lib/internal/source_map/source_map_cache.js‎

Lines changed: 24 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ const cjsSourceMapCache = new WeakMap();
1717
// on filenames.
1818
constesmSourceMapCache=newMap();
1919
const{ fileURLToPath,URL}=require('url');
20-
const{ overrideStackTrace }=require('internal/errors');
2120

2221
letexperimentalSourceMaps;
2322
functionmaybeCacheSourceMap(filename,content,cjsModuleInstance){
@@ -38,18 +37,22 @@ function maybeCacheSourceMap(filename, content, cjsModuleInstance) {
3837

3938
constmatch=content.match(/\/[*/]#\s+sourceMappingURL=(?<sourceMappingURL>[^\s]+)/);
4039
if(match){
40+
constdata=dataFromUrl(basePath,match.groups.sourceMappingURL);
41+
consturl=data ? null : match.groups.sourceMappingURL;
4142
if(cjsModuleInstance){
4243
cjsSourceMapCache.set(cjsModuleInstance,{
4344
filename,
44-
url: match.groups.sourceMappingURL,
45-
data: dataFromUrl(basePath,match.groups.sourceMappingURL)
45+
lineLengths: lineLengths(content),
46+
data,
47+
url
4648
});
4749
}else{
4850
// If there is no cjsModuleInstance assume we are in a
4951
// "modules/esm" context.
5052
esmSourceMapCache.set(filename,{
51-
url: match.groups.sourceMappingURL,
52-
data: dataFromUrl(basePath,match.groups.sourceMappingURL)
53+
lineLengths: lineLengths(content),
54+
data,
55+
url
5356
});
5457
}
5558
}
@@ -73,6 +76,18 @@ function dataFromUrl(basePath, sourceMappingURL) {
7376
}
7477
}
7578

79+
// Cache the length of each line in the file that a source map was extracted
80+
// from. This allows translation from byte offset V8 coverage reports,
81+
// to line/column offset Source Map V3.
82+
functionlineLengths(content){
83+
// We purposefully keep \r as part of the line-length calculation, in
84+
// cases where there is a \r\n separator, so that this can be taken into
85+
// account in coverage calculations.
86+
returncontent.split(/\n|\u2028|\u2029/).map((line)=>{
87+
returnline.length;
88+
});
89+
}
90+
7691
functionsourceMapFromFile(sourceMapFile){
7792
try{
7893
constcontent=fs.readFileSync(sourceMapFile,'utf8');
@@ -161,56 +176,14 @@ function appendCJSCache(obj) {
161176
constvalue=cjsSourceMapCache.get(Module._cache[key]);
162177
if(value){
163178
obj[`file://${key}`]={
164-
url: value.url,
165-
data: value.data
179+
lineLengths: value.lineLengths,
180+
data: value.data,
181+
url: value.url
166182
};
167183
}
168184
});
169185
}
170186

171-
// Create a prettified stacktrace, inserting context from source maps
172-
// if possible.
173-
constErrorToString=Error.prototype.toString;// Capture original toString.
174-
constprepareStackTrace=(globalThis,error,trace)=>{
175-
// API for node internals to override error stack formatting
176-
// without interfering with userland code.
177-
// TODO(bcoe): add support for source-maps to repl.
178-
if(overrideStackTrace.has(error)){
179-
constf=overrideStackTrace.get(error);
180-
overrideStackTrace.delete(error);
181-
returnf(error,trace);
182-
}
183-
184-
const{ SourceMap }=require('internal/source_map/source_map');
185-
consterrorString=ErrorToString.call(error);
186-
187-
if(trace.length===0){
188-
returnerrorString;
189-
}
190-
constpreparedTrace=trace.map((t,i)=>{
191-
letstr=i!==0 ? '\n at ' : '';
192-
str=`${str}${t}`;
193-
try{
194-
constsourceMap=findSourceMap(t.getFileName(),error);
195-
if(sourceMap&&sourceMap.data){
196-
constsm=newSourceMap(sourceMap.data);
197-
// Source Map V3 lines/columns use zero-based offsets whereas, in
198-
// stack traces, they start at 1/1.
199-
const[,,url,line,col]=
200-
sm.findEntry(t.getLineNumber()-1,t.getColumnNumber()-1);
201-
if(url&&line!==undefined&&col!==undefined){
202-
str+=
203-
`\n -> ${url.replace('file://','')}:${line+1}:${col+1}`;
204-
}
205-
}
206-
}catch(err){
207-
debug(err.stack);
208-
}
209-
returnstr;
210-
});
211-
return`${errorString}\n at ${preparedTrace.join('')}`;
212-
};
213-
214187
// Attempt to lookup a source map, which is either attached to a file URI, or
215188
// keyed on an error instance.
216189
functionfindSourceMap(uri,error){
@@ -230,8 +203,8 @@ function findSourceMap(uri, error) {
230203
}
231204

232205
module.exports={
206+
findSourceMap,
233207
maybeCacheSourceMap,
234-
prepareStackTrace,
235208
rekeySourceMap,
236209
sourceMapCacheToObject,
237210
};

‎node.gyp‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@
176176
'lib/internal/repl/history.js',
177177
'lib/internal/repl/utils.js',
178178
'lib/internal/socket_list.js',
179+
'lib/internal/source_map/prepare_stack_trace.js',
179180
'lib/internal/source_map/source_map.js',
180181
'lib/internal/source_map/source_map_cache.js',
181182
'lib/internal/test/binding.js',

‎test/parallel/test-source-map.js‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,36 @@ function nextdir() {
193193
);
194194
}
195195

196+
// Does not persist url parameter if source-map has been parsed.
197+
{
198+
constcoverageDirectory=nextdir();
199+
spawnSync(process.execPath,[
200+
require.resolve('../fixtures/source-map/inline-base64.js')
201+
],{env: { ...process.env,NODE_V8_COVERAGE: coverageDirectory}});
202+
constsourceMap=getSourceMapFromCache(
203+
'inline-base64.js',
204+
coverageDirectory
205+
);
206+
assert.strictEqual(sourceMap.url,null);
207+
}
208+
209+
// Persists line lengths for in-memory representation of source file.
210+
{
211+
constcoverageDirectory=nextdir();
212+
spawnSync(process.execPath,[
213+
require.resolve('../fixtures/source-map/istanbul-throw.js')
214+
],{env: { ...process.env,NODE_V8_COVERAGE: coverageDirectory}});
215+
constsourceMap=getSourceMapFromCache(
216+
'istanbul-throw.js',
217+
coverageDirectory
218+
);
219+
if(common.isWindows){
220+
assert.deepStrictEqual(sourceMap.lineLengths,[1086,31,185,649,0]);
221+
}else{
222+
assert.deepStrictEqual(sourceMap.lineLengths,[1085,30,184,648,0]);
223+
}
224+
}
225+
196226
functiongetSourceMapFromCache(fixtureFile,coverageDirectory){
197227
constjsonFiles=fs.readdirSync(coverageDirectory);
198228
for(constjsonFileofjsonFiles){

0 commit comments

Comments
 (0)