From c6b7a3dfa7e827afab55062fb751cf398b4f9a21 Mon Sep 17 00:00:00 2001 From: Matt Skelley Date: Sun, 1 Feb 2026 19:22:02 +0800 Subject: [PATCH 1/5] esm: add import trace for evaluation errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Errors thrown during ESM module evaluation often do not show how the failing module was reached via imports, making it hard to understand why it was loaded. This change appends an "Import trace" section to the formatted error stack for evaluation-time ESM errors. The trace is derived from the loader’s import graph and shows the chain of modules leading to the failure. The implementation preserves existing stack formatting and source map handling, and is limited to module evaluation only. A new test verifies that the expected import chain is included. Refs: #46992 --- lib/internal/modules/esm/loader.js | 1 + lib/internal/modules/esm/module_job.js | 74 ++++++++++++++++++- test/es-module/test-esm-import-trace.mjs | 26 +++++++ test/fixtures/es-modules/import-trace/bar.mjs | 1 + .../es-modules/import-trace/entry.mjs | 1 + test/fixtures/es-modules/import-trace/foo.mjs | 1 + 6 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 test/es-module/test-esm-import-trace.mjs create mode 100644 test/fixtures/es-modules/import-trace/bar.mjs create mode 100644 test/fixtures/es-modules/import-trace/entry.mjs create mode 100644 test/fixtures/es-modules/import-trace/foo.mjs diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index 37eb267e154cc7..64b8a2191a0685 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -195,6 +195,7 @@ class ModuleLoader { constructor(asyncLoaderHooks) { this.#setAsyncLoaderHooks(asyncLoaderHooks); + this.importParents = new Map(); } /** diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 929577c0da6d08..5b0c38ed5bf20b 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -23,6 +23,73 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { debug = fn; }); +const { + overrideStackTrace, + ErrorPrepareStackTrace, + codes, +} = require('internal/errors'); + +const { ERR_REQUIRE_ASYNC_MODULE } = codes; + +/** + * Builds a linear import trace by walking parent modules + * from the module that threw during evaluation. + */ +function buildImportTrace(importParents, startURL) { + const trace = []; + let current = startURL; + const seen = new Set([current]); + + while (true) { + const parent = importParents.get(current); + if (!parent || seen.has(parent)) break; + + trace.push({ child: current, parent }); + seen.add(current); + current = parent; + } + + return trace.length ? trace : null; +} + +/** + * Formats an import trace for inclusion in an error stack. + */ +function formatImportTrace(trace) { + return trace + .map(({ child, parent }) => ` ${child} imported by ${parent}`) + .join('\n'); +} + +/** + * Appends an ESM import trace to an error’s stack output. + * Uses a per-error stack override; no global side effects. + */ +function decorateErrorWithImportTrace(e, importParents) { + if (!importParents || typeof importParents.get !== 'function') return; + if (!e || typeof e !== 'object') return; + + overrideStackTrace.set(e, (error, trace) => { + let thrownURL; + for (const cs of trace) { + const getFileName = cs.getFileName; + if (typeof getFileName === 'function') { + const file = getFileName.call(cs); + if (typeof file === 'string' && file.startsWith('file://')) { + thrownURL = file; + break; + } + } + } + + const importTrace = thrownURL ? buildImportTrace(importParents, thrownURL) : null; + const stack = ErrorPrepareStackTrace(error, trace); + if (!importTrace) return stack; + + return `${stack}\n\nImport trace:\n${formatImportTrace(importTrace)}`; + }); +} + const { ModuleWrap, kErrored, @@ -53,9 +120,6 @@ const { } = require('internal/modules/helpers'); const { getOptionValue } = require('internal/options'); const noop = FunctionPrototype; -const { - ERR_REQUIRE_ASYNC_MODULE, -} = require('internal/errors').codes; let hasPausedEntry = false; const CJSGlobalLike = [ @@ -159,6 +223,7 @@ class ModuleJobBase { // that hooks can pre-fetch sources off-thread. const job = this.loader.getOrCreateModuleJob(this.url, request, requestType); debug(`ModuleJobBase.syncLink() ${this.url} -> ${request.specifier}`, job); + this.loader.importParents.set(job.url, this.url); assert(!isPromise(job)); assert(job.module instanceof ModuleWrap); if (request.phase === kEvaluationPhase) { @@ -430,6 +495,9 @@ class ModuleJob extends ModuleJobBase { await this.module.evaluate(timeout, breakOnSigint); } catch (e) { explainCommonJSGlobalLikeNotDefinedError(e, this.module.url, this.module.hasTopLevelAwait); + + decorateErrorWithImportTrace(e, this.loader.importParents); + throw e; } return { __proto__: null, module: this.module }; diff --git a/test/es-module/test-esm-import-trace.mjs b/test/es-module/test-esm-import-trace.mjs new file mode 100644 index 00000000000000..0a33f2e9f88c9a --- /dev/null +++ b/test/es-module/test-esm-import-trace.mjs @@ -0,0 +1,26 @@ +import { spawnSync } from 'node:child_process'; +import assert from 'node:assert'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; +import { test } from 'node:test'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const fixture = path.join( + __dirname, + '../fixtures/es-modules/import-trace/entry.mjs' +); + +test('includes import trace for evaluation-time errors', () => { + const result = spawnSync( + process.execPath, + [fixture], + { encoding: 'utf8' } + ); + + assert.notStrictEqual(result.status, 0); + assert.match(result.stderr, /Import trace:/); + assert.match(result.stderr, /bar\.mjs imported by .*foo\.mjs/); + assert.match(result.stderr, /foo\.mjs imported by .*entry\.mjs/); +}); \ No newline at end of file diff --git a/test/fixtures/es-modules/import-trace/bar.mjs b/test/fixtures/es-modules/import-trace/bar.mjs new file mode 100644 index 00000000000000..8d48f71d57cbde --- /dev/null +++ b/test/fixtures/es-modules/import-trace/bar.mjs @@ -0,0 +1 @@ +throw new Error('bar failed'); diff --git a/test/fixtures/es-modules/import-trace/entry.mjs b/test/fixtures/es-modules/import-trace/entry.mjs new file mode 100644 index 00000000000000..a63434dddb1bb6 --- /dev/null +++ b/test/fixtures/es-modules/import-trace/entry.mjs @@ -0,0 +1 @@ +import './foo.mjs'; diff --git a/test/fixtures/es-modules/import-trace/foo.mjs b/test/fixtures/es-modules/import-trace/foo.mjs new file mode 100644 index 00000000000000..118fb781654638 --- /dev/null +++ b/test/fixtures/es-modules/import-trace/foo.mjs @@ -0,0 +1 @@ +import './bar.mjs'; \ No newline at end of file From db7b084c8fde16e329536acd269cc36a0d019c81 Mon Sep 17 00:00:00 2001 From: Matt Skelley Date: Tue, 24 Feb 2026 20:49:48 +0800 Subject: [PATCH 2/5] esm: Fix linting errors and test coverage Remove importParents guard. Use primordials instead. --- lib/internal/modules/esm/loader.js | 3 ++- lib/internal/modules/esm/module_job.js | 27 ++++++++++++++++---------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index 64b8a2191a0685..53ea9916c6b903 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -10,6 +10,7 @@ const { Promise, PromisePrototypeThen, RegExpPrototypeSymbolReplace, + SafeMap, encodeURIComponent, hardenRegExp, } = primordials; @@ -195,7 +196,7 @@ class ModuleLoader { constructor(asyncLoaderHooks) { this.#setAsyncLoaderHooks(asyncLoaderHooks); - this.importParents = new Map(); + this.importParents = new SafeMap(); } /** diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 5b0c38ed5bf20b..f0e6a49c6ede5b 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -24,25 +24,28 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { }); const { - overrideStackTrace, ErrorPrepareStackTrace, - codes, + codes: { + ERR_REQUIRE_ASYNC_MODULE, + }, + overrideStackTrace, } = require('internal/errors'); -const { ERR_REQUIRE_ASYNC_MODULE } = codes; - /** * Builds a linear import trace by walking parent modules * from the module that threw during evaluation. + * @returns {Array<{child: string, parent: string}>|null} */ function buildImportTrace(importParents, startURL) { const trace = []; let current = startURL; - const seen = new Set([current]); + const seen = new SafeSet([current]); while (true) { const parent = importParents.get(current); - if (!parent || seen.has(parent)) break; + if (!parent || seen.has(parent)) { + break; + } trace.push({ child: current, parent }); seen.add(current); @@ -54,6 +57,7 @@ function buildImportTrace(importParents, startURL) { /** * Formats an import trace for inclusion in an error stack. + * @returns {string} */ function formatImportTrace(trace) { return trace @@ -62,12 +66,13 @@ function formatImportTrace(trace) { } /** - * Appends an ESM import trace to an error’s stack output. + * Appends an ESM import trace to an error's stack output. * Uses a per-error stack override; no global side effects. */ function decorateErrorWithImportTrace(e, importParents) { - if (!importParents || typeof importParents.get !== 'function') return; - if (!e || typeof e !== 'object') return; + if (!e || typeof e !== 'object') { + return; + } overrideStackTrace.set(e, (error, trace) => { let thrownURL; @@ -84,7 +89,9 @@ function decorateErrorWithImportTrace(e, importParents) { const importTrace = thrownURL ? buildImportTrace(importParents, thrownURL) : null; const stack = ErrorPrepareStackTrace(error, trace); - if (!importTrace) return stack; + if (!importTrace) { + return stack; + } return `${stack}\n\nImport trace:\n${formatImportTrace(importTrace)}`; }); From 251ade9bb5cfcbff83d9b2be08c113214a373fc5 Mon Sep 17 00:00:00 2001 From: Matt Skelley Date: Sat, 28 Feb 2026 12:58:36 +0800 Subject: [PATCH 3/5] esm: test case for multi parent imports Use import.meta.dirname for fixture path in ESM test. --- test/es-module/test-esm-import-trace.mjs | 35 ++++++++++++++++--- .../es-modules/import-trace-multi/alt.mjs | 1 + .../es-modules/import-trace-multi/bar.mjs | 1 + .../es-modules/import-trace-multi/entry.mjs | 2 ++ .../es-modules/import-trace-multi/foo.mjs | 1 + 5 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 test/fixtures/es-modules/import-trace-multi/alt.mjs create mode 100644 test/fixtures/es-modules/import-trace-multi/bar.mjs create mode 100644 test/fixtures/es-modules/import-trace-multi/entry.mjs create mode 100644 test/fixtures/es-modules/import-trace-multi/foo.mjs diff --git a/test/es-module/test-esm-import-trace.mjs b/test/es-module/test-esm-import-trace.mjs index 0a33f2e9f88c9a..c9d3ec1ac30f2e 100644 --- a/test/es-module/test-esm-import-trace.mjs +++ b/test/es-module/test-esm-import-trace.mjs @@ -1,14 +1,10 @@ import { spawnSync } from 'node:child_process'; import assert from 'node:assert'; -import { fileURLToPath } from 'node:url'; import path from 'node:path'; import { test } from 'node:test'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - const fixture = path.join( - __dirname, + import.meta.dirname, '../fixtures/es-modules/import-trace/entry.mjs' ); @@ -23,4 +19,33 @@ test('includes import trace for evaluation-time errors', () => { assert.match(result.stderr, /Import trace:/); assert.match(result.stderr, /bar\.mjs imported by .*foo\.mjs/); assert.match(result.stderr, /foo\.mjs imported by .*entry\.mjs/); +}); + +const multiFixture = path.join( + import.meta.dirname, + '../fixtures/es-modules/import-trace-multi/entry.mjs' +); + +test('import trace matches actual code path for multiple parents', () => { + const result = spawnSync( + process.execPath, + [multiFixture], + { encoding: 'utf8' } + ); + + assert.notStrictEqual(result.status, 0); + // Should show the actual import chain that led to the error + const traceFoo = /bar\.mjs imported by .*foo\.mjs/; + const traceAlt = /bar\.mjs imported by .*alt\.mjs/; + const entryFoo = /foo\.mjs imported by .*entry\.mjs/; + const entryAlt = /alt\.mjs imported by .*entry\.mjs/; + + assert( + traceFoo.test(result.stderr) || traceAlt.test(result.stderr), + 'Import trace should show either foo.mjs or alt.mjs as parent' + ); + assert( + entryFoo.test(result.stderr) || entryAlt.test(result.stderr), + 'Import trace should show either foo.mjs or alt.mjs imported by entry.mjs' + ); }); \ No newline at end of file diff --git a/test/fixtures/es-modules/import-trace-multi/alt.mjs b/test/fixtures/es-modules/import-trace-multi/alt.mjs new file mode 100644 index 00000000000000..dd325c78f2a52c --- /dev/null +++ b/test/fixtures/es-modules/import-trace-multi/alt.mjs @@ -0,0 +1 @@ +import './bar.mjs'; diff --git a/test/fixtures/es-modules/import-trace-multi/bar.mjs b/test/fixtures/es-modules/import-trace-multi/bar.mjs new file mode 100644 index 00000000000000..d6bc0faf85db40 --- /dev/null +++ b/test/fixtures/es-modules/import-trace-multi/bar.mjs @@ -0,0 +1 @@ +throw new Error('fail'); diff --git a/test/fixtures/es-modules/import-trace-multi/entry.mjs b/test/fixtures/es-modules/import-trace-multi/entry.mjs new file mode 100644 index 00000000000000..0c1d61d130340c --- /dev/null +++ b/test/fixtures/es-modules/import-trace-multi/entry.mjs @@ -0,0 +1,2 @@ +import './foo.mjs'; +import './alt.mjs'; diff --git a/test/fixtures/es-modules/import-trace-multi/foo.mjs b/test/fixtures/es-modules/import-trace-multi/foo.mjs new file mode 100644 index 00000000000000..dd325c78f2a52c --- /dev/null +++ b/test/fixtures/es-modules/import-trace-multi/foo.mjs @@ -0,0 +1 @@ +import './bar.mjs'; From c9f80940c524bf10e0e25731dbef2c3605f90197 Mon Sep 17 00:00:00 2001 From: Matt Skelley Date: Mon, 2 Mar 2026 14:01:14 +0800 Subject: [PATCH 4/5] 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: https://github.com/nodejs/node/issues/46992#issuecomment-3972265673 Fixes: https://github.com/nodejs/node/pull/61612#discussion_r2854171595 Fixes: https://github.com/nodejs/node/pull/61612#discussion_r2854192187 --- lib/internal/modules/esm/module_job.js | 41 ++++++++++++++++++------ src/env_properties.h | 2 ++ src/module_wrap.cc | 12 +++++-- test/es-module/test-esm-import-trace.mjs | 35 +++++++++++--------- 4 files changed, 63 insertions(+), 27 deletions(-) diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index f0e6a49c6ede5b..403c342e021515 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -42,14 +42,28 @@ function buildImportTrace(importParents, startURL) { const seen = new SafeSet([current]); while (true) { - const parent = importParents.get(current); - if (!parent || seen.has(parent)) { + const parentInfo = importParents.get(current); + let parentURL; + if (typeof parentInfo === 'object' && parentInfo !== null && typeof parentInfo.url === 'string') { + parentURL = parentInfo.url; + } else { + parentURL = parentInfo; + } + if (!parentInfo || seen.has(parentURL)) { break; } - - trace.push({ child: current, parent }); - seen.add(current); - current = parent; + let parentDisplay; + if (typeof parentInfo === 'object' && parentInfo !== null && typeof parentInfo.url === 'string') { + parentDisplay = parentInfo.url; + if (typeof parentInfo.line === 'number' && typeof parentInfo.column === 'number') { + parentDisplay += `:${parentInfo.line + 1}:${parentInfo.column + 1}`; + } + } else { + parentDisplay = parentInfo; + } + trace.push({ child: current, parent: parentDisplay }); + seen.add(parentURL); + current = parentURL; } return trace.length ? trace : null; @@ -61,7 +75,7 @@ function buildImportTrace(importParents, startURL) { */ function formatImportTrace(trace) { return trace - .map(({ child, parent }) => ` ${child} imported by ${parent}`) + .map(({ child, parent }) => ` ${parent}`) .join('\n'); } @@ -93,7 +107,7 @@ function decorateErrorWithImportTrace(e, importParents) { return stack; } - return `${stack}\n\nImport trace:\n${formatImportTrace(importTrace)}`; + return `${stack}\n\nImported by:\n${formatImportTrace(importTrace)}`; }); } @@ -230,7 +244,16 @@ class ModuleJobBase { // that hooks can pre-fetch sources off-thread. const job = this.loader.getOrCreateModuleJob(this.url, request, requestType); debug(`ModuleJobBase.syncLink() ${this.url} -> ${request.specifier}`, job); - this.loader.importParents.set(job.url, this.url); + + const line = request.line; + const column = request.column; + // console.log(`Import at ${request.specifier} (line: ${line}, column: ${column})`); + // Set the parent info with line/column + let parentInfo = this.url; + if (typeof line === 'number' && typeof column === 'number') { + parentInfo = { url: this.url, line, column }; + } + this.loader.importParents.set(job.url, parentInfo); assert(!isPromise(job)); assert(job.module instanceof ModuleWrap); if (request.phase === kEvaluationPhase) { diff --git a/src/env_properties.h b/src/env_properties.h index 91e2e06c3c2703..4e206d17e18840 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -100,6 +100,7 @@ "transferList") \ V(clone_untransferable_str, "Found invalid value in transferList.") \ V(code_string, "code") \ + V(column_string, "column") \ V(config_string, "config") \ V(constants_string, "constants") \ V(crypto_dh_string, "dh") \ @@ -239,6 +240,7 @@ V(length_string, "length") \ V(limits_string, "limits") \ V(library_string, "library") \ + V(line_string, "line") \ V(loop_count, "loopCount") \ V(max_buffer_string, "maxBuffer") \ V(max_concurrent_streams_string, "maxConcurrentStreams") \ diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 354b45bda9ccc7..27f88e64a99be3 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -40,6 +40,7 @@ using v8::Isolate; using v8::Just; using v8::JustVoid; using v8::Local; +using v8::Location; using v8::LocalVector; using v8::Maybe; using v8::MaybeLocal; @@ -565,7 +566,7 @@ static Local createImportAttributesContainer( } static Local createModuleRequestsContainer( - Realm* realm, Isolate* isolate, Local raw_requests) { + Realm* realm, Isolate* isolate, Local module, Local raw_requests) { EscapableHandleScope scope(isolate); Local context = realm->context(); LocalVector requests(isolate, raw_requests->Length()); @@ -584,15 +585,22 @@ static Local createModuleRequestsContainer( createImportAttributesContainer(realm, isolate, raw_attributes, 3); ModuleImportPhase phase = module_request->GetPhase(); + int source_offset = module_request->GetSourceOffset(); + Location loc = module->SourceOffsetToLocation(source_offset); + Local names[] = { realm->isolate_data()->specifier_string(), realm->isolate_data()->attributes_string(), realm->isolate_data()->phase_string(), + realm->isolate_data()->line_string(), + realm->isolate_data()->column_string(), }; Local values[] = { specifier, attributes, Integer::New(isolate, to_phase_constant(phase)), + Integer::New(isolate, loc.GetLineNumber()), + Integer::New(isolate, loc.GetColumnNumber()), }; DCHECK_EQ(arraysize(names), arraysize(values)); @@ -616,7 +624,7 @@ void ModuleWrap::GetModuleRequests(const FunctionCallbackInfo& args) { Local module = obj->module_.Get(isolate); args.GetReturnValue().Set(createModuleRequestsContainer( - realm, isolate, module->GetModuleRequests())); + realm, isolate, module, module->GetModuleRequests())); } // moduleWrap.link(moduleWraps) diff --git a/test/es-module/test-esm-import-trace.mjs b/test/es-module/test-esm-import-trace.mjs index c9d3ec1ac30f2e..5252da6cdd8e5f 100644 --- a/test/es-module/test-esm-import-trace.mjs +++ b/test/es-module/test-esm-import-trace.mjs @@ -16,9 +16,9 @@ test('includes import trace for evaluation-time errors', () => { ); assert.notStrictEqual(result.status, 0); - assert.match(result.stderr, /Import trace:/); - assert.match(result.stderr, /bar\.mjs imported by .*foo\.mjs/); - assert.match(result.stderr, /foo\.mjs imported by .*entry\.mjs/); + assert.match(result.stderr, /Imported by:/); + assert.match(result.stderr, /.*foo\.mjs:1:8/); + assert.match(result.stderr, /.*entry\.mjs:1:8/); }); const multiFixture = path.join( @@ -34,18 +34,21 @@ test('import trace matches actual code path for multiple parents', () => { ); assert.notStrictEqual(result.status, 0); - // Should show the actual import chain that led to the error - const traceFoo = /bar\.mjs imported by .*foo\.mjs/; - const traceAlt = /bar\.mjs imported by .*alt\.mjs/; - const entryFoo = /foo\.mjs imported by .*entry\.mjs/; - const entryAlt = /alt\.mjs imported by .*entry\.mjs/; + const traceFoo = /.*foo\.mjs:1:8/; + const traceAlt = /.*alt\.mjs:1:8/; + const entryFoo = /.*entry\.mjs:1:8/; + const entryAlt = /.*entry\.mjs:2:8/; - assert( - traceFoo.test(result.stderr) || traceAlt.test(result.stderr), - 'Import trace should show either foo.mjs or alt.mjs as parent' - ); - assert( - entryFoo.test(result.stderr) || entryAlt.test(result.stderr), - 'Import trace should show either foo.mjs or alt.mjs imported by entry.mjs' - ); + let parentMatched; + if (traceFoo.test(result.stderr)) { + parentMatched = 'foo'; + assert(entryFoo.test(result.stderr), + 'Import trace should show foo.mjs imported by entry.mjs with line/column'); + } else if (traceAlt.test(result.stderr)) { + parentMatched = 'alt'; + assert(entryAlt.test(result.stderr), + 'Import trace should show alt.mjs imported by entry.mjs with line/column'); + } else { + assert.fail('Import trace should show either foo.mjs or alt.mjs as parent with line/column'); + } }); \ No newline at end of file From 73785d0ad10718da5c44c5b1642fbf1cbf40c3b9 Mon Sep 17 00:00:00 2001 From: Matt Skelley Date: Mon, 2 Mar 2026 14:53:27 +0800 Subject: [PATCH 5/5] esm: code cleanup --- lib/internal/modules/esm/module_job.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 403c342e021515..5566762d7bb38f 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -247,7 +247,6 @@ class ModuleJobBase { const line = request.line; const column = request.column; - // console.log(`Import at ${request.specifier} (line: ${line}, column: ${column})`); // Set the parent info with line/column let parentInfo = this.url; if (typeof line === 'number' && typeof column === 'number') {