Skip to content

Commit c9f8094

Browse files
committed
esm: add line and column
Refactor import trace output. Add line and column information. Remove unnecessary duplicates. Update the tests. Add line and column info. Add test for module with multiple parents. Refs: #46992 (comment) Fixes: #61612 (comment) Fixes: #61612 (comment)
1 parent 251ade9 commit c9f8094

4 files changed

Lines changed: 63 additions & 27 deletions

File tree

lib/internal/modules/esm/module_job.js

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,28 @@ function buildImportTrace(importParents, startURL) {
4242
const seen = new SafeSet([current]);
4343

4444
while (true) {
45-
const parent = importParents.get(current);
46-
if (!parent || seen.has(parent)) {
45+
const parentInfo = importParents.get(current);
46+
let parentURL;
47+
if (typeof parentInfo === 'object' && parentInfo !== null && typeof parentInfo.url === 'string') {
48+
parentURL = parentInfo.url;
49+
} else {
50+
parentURL = parentInfo;
51+
}
52+
if (!parentInfo || seen.has(parentURL)) {
4753
break;
4854
}
49-
50-
trace.push({ child: current, parent });
51-
seen.add(current);
52-
current = parent;
55+
let parentDisplay;
56+
if (typeof parentInfo === 'object' && parentInfo !== null && typeof parentInfo.url === 'string') {
57+
parentDisplay = parentInfo.url;
58+
if (typeof parentInfo.line === 'number' && typeof parentInfo.column === 'number') {
59+
parentDisplay += `:${parentInfo.line + 1}:${parentInfo.column + 1}`;
60+
}
61+
} else {
62+
parentDisplay = parentInfo;
63+
}
64+
trace.push({ child: current, parent: parentDisplay });
65+
seen.add(parentURL);
66+
current = parentURL;
5367
}
5468

5569
return trace.length ? trace : null;
@@ -61,7 +75,7 @@ function buildImportTrace(importParents, startURL) {
6175
*/
6276
function formatImportTrace(trace) {
6377
return trace
64-
.map(({ child, parent }) => ` ${child} imported by ${parent}`)
78+
.map(({ child, parent }) => ` ${parent}`)
6579
.join('\n');
6680
}
6781

@@ -93,7 +107,7 @@ function decorateErrorWithImportTrace(e, importParents) {
93107
return stack;
94108
}
95109

96-
return `${stack}\n\nImport trace:\n${formatImportTrace(importTrace)}`;
110+
return `${stack}\n\nImported by:\n${formatImportTrace(importTrace)}`;
97111
});
98112
}
99113

@@ -230,7 +244,16 @@ class ModuleJobBase {
230244
// that hooks can pre-fetch sources off-thread.
231245
const job = this.loader.getOrCreateModuleJob(this.url, request, requestType);
232246
debug(`ModuleJobBase.syncLink() ${this.url} -> ${request.specifier}`, job);
233-
this.loader.importParents.set(job.url, this.url);
247+
248+
const line = request.line;
249+
const column = request.column;
250+
// console.log(`Import at ${request.specifier} (line: ${line}, column: ${column})`);
251+
// Set the parent info with line/column
252+
let parentInfo = this.url;
253+
if (typeof line === 'number' && typeof column === 'number') {
254+
parentInfo = { url: this.url, line, column };
255+
}
256+
this.loader.importParents.set(job.url, parentInfo);
234257
assert(!isPromise(job));
235258
assert(job.module instanceof ModuleWrap);
236259
if (request.phase === kEvaluationPhase) {

src/env_properties.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
"transferList") \
101101
V(clone_untransferable_str, "Found invalid value in transferList.") \
102102
V(code_string, "code") \
103+
V(column_string, "column") \
103104
V(config_string, "config") \
104105
V(constants_string, "constants") \
105106
V(crypto_dh_string, "dh") \
@@ -239,6 +240,7 @@
239240
V(length_string, "length") \
240241
V(limits_string, "limits") \
241242
V(library_string, "library") \
243+
V(line_string, "line") \
242244
V(loop_count, "loopCount") \
243245
V(max_buffer_string, "maxBuffer") \
244246
V(max_concurrent_streams_string, "maxConcurrentStreams") \

src/module_wrap.cc

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ using v8::Isolate;
4040
using v8::Just;
4141
using v8::JustVoid;
4242
using v8::Local;
43+
using v8::Location;
4344
using v8::LocalVector;
4445
using v8::Maybe;
4546
using v8::MaybeLocal;
@@ -565,7 +566,7 @@ static Local<Object> createImportAttributesContainer(
565566
}
566567

567568
static Local<Array> createModuleRequestsContainer(
568-
Realm* realm, Isolate* isolate, Local<FixedArray> raw_requests) {
569+
Realm* realm, Isolate* isolate, Local<Module> module, Local<FixedArray> raw_requests) {
569570
EscapableHandleScope scope(isolate);
570571
Local<Context> context = realm->context();
571572
LocalVector<Value> requests(isolate, raw_requests->Length());
@@ -584,15 +585,22 @@ static Local<Array> createModuleRequestsContainer(
584585
createImportAttributesContainer(realm, isolate, raw_attributes, 3);
585586
ModuleImportPhase phase = module_request->GetPhase();
586587

588+
int source_offset = module_request->GetSourceOffset();
589+
Location loc = module->SourceOffsetToLocation(source_offset);
590+
587591
Local<Name> names[] = {
588592
realm->isolate_data()->specifier_string(),
589593
realm->isolate_data()->attributes_string(),
590594
realm->isolate_data()->phase_string(),
595+
realm->isolate_data()->line_string(),
596+
realm->isolate_data()->column_string(),
591597
};
592598
Local<Value> values[] = {
593599
specifier,
594600
attributes,
595601
Integer::New(isolate, to_phase_constant(phase)),
602+
Integer::New(isolate, loc.GetLineNumber()),
603+
Integer::New(isolate, loc.GetColumnNumber()),
596604
};
597605
DCHECK_EQ(arraysize(names), arraysize(values));
598606

@@ -616,7 +624,7 @@ void ModuleWrap::GetModuleRequests(const FunctionCallbackInfo<Value>& args) {
616624

617625
Local<Module> module = obj->module_.Get(isolate);
618626
args.GetReturnValue().Set(createModuleRequestsContainer(
619-
realm, isolate, module->GetModuleRequests()));
627+
realm, isolate, module, module->GetModuleRequests()));
620628
}
621629

622630
// moduleWrap.link(moduleWraps)

test/es-module/test-esm-import-trace.mjs

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ test('includes import trace for evaluation-time errors', () => {
1616
);
1717

1818
assert.notStrictEqual(result.status, 0);
19-
assert.match(result.stderr, /Import trace:/);
20-
assert.match(result.stderr, /bar\.mjs imported by .*foo\.mjs/);
21-
assert.match(result.stderr, /foo\.mjs imported by .*entry\.mjs/);
19+
assert.match(result.stderr, /Imported by:/);
20+
assert.match(result.stderr, /.*foo\.mjs:1:8/);
21+
assert.match(result.stderr, /.*entry\.mjs:1:8/);
2222
});
2323

2424
const multiFixture = path.join(
@@ -34,18 +34,21 @@ test('import trace matches actual code path for multiple parents', () => {
3434
);
3535

3636
assert.notStrictEqual(result.status, 0);
37-
// Should show the actual import chain that led to the error
38-
const traceFoo = /bar\.mjs imported by .*foo\.mjs/;
39-
const traceAlt = /bar\.mjs imported by .*alt\.mjs/;
40-
const entryFoo = /foo\.mjs imported by .*entry\.mjs/;
41-
const entryAlt = /alt\.mjs imported by .*entry\.mjs/;
37+
const traceFoo = /.*foo\.mjs:1:8/;
38+
const traceAlt = /.*alt\.mjs:1:8/;
39+
const entryFoo = /.*entry\.mjs:1:8/;
40+
const entryAlt = /.*entry\.mjs:2:8/;
4241

43-
assert(
44-
traceFoo.test(result.stderr) || traceAlt.test(result.stderr),
45-
'Import trace should show either foo.mjs or alt.mjs as parent'
46-
);
47-
assert(
48-
entryFoo.test(result.stderr) || entryAlt.test(result.stderr),
49-
'Import trace should show either foo.mjs or alt.mjs imported by entry.mjs'
50-
);
42+
let parentMatched;
43+
if (traceFoo.test(result.stderr)) {
44+
parentMatched = 'foo';
45+
assert(entryFoo.test(result.stderr),
46+
'Import trace should show foo.mjs imported by entry.mjs with line/column');
47+
} else if (traceAlt.test(result.stderr)) {
48+
parentMatched = 'alt';
49+
assert(entryAlt.test(result.stderr),
50+
'Import trace should show alt.mjs imported by entry.mjs with line/column');
51+
} else {
52+
assert.fail('Import trace should show either foo.mjs or alt.mjs as parent with line/column');
53+
}
5154
});

0 commit comments

Comments
 (0)