Repository: nodejs/node
Pull request: module: trim off internal stack frames for require(esm) warnings

Trim off irrelevant internal stack frames for require(esm) warnings so it's easier to locate where the call comes from when --trace-warnings is used.

Previously, running `npm install` in `tools/doc` with `--trace-warning` leads to the following stack trace - none of the top 10 frames are useful in finding where the `require()` call comes from:

```
    at emitExperimentalWarning (node:internal/util:273:11)
    at loadESMFromCJS (node:internal/modules/cjs/loader:1388:5)
    at Module._compile (node:internal/modules/cjs/loader:1525:5)
    at Object..js (node:internal/modules/cjs/loader:1680:16)
    at Module.load (node:internal/modules/cjs/loader:1328:32)
    at Function._load (node:internal/modules/cjs/loader:1138:12)
    at TracingChannel.traceSync (node:diagnostics_channel:315:14)
    at wrapModuleLoad (node:internal/modules/cjs/loader:218:24)
    at Module.require (node:internal/modules/cjs/loader:1350:12)
    at require (node:internal/modules/helpers:138:16)
```

After this change it's in the second frame (the first frame is still preserved, to show the function name `require`, otherwise the top frame only has the dummy name `Object.<anonymous>` inferred from CommonJS wrappers which looks a bit off).

```
    at require (node:internal/modules/helpers:139:16)
    at Object.<anonymous> (/Users/joyee/projects/node/deps/npm/node_modules/debug/src/node.js:32:24)
    at Module._compile (node:internal/modules/cjs/loader:1575:14)
    at Object..js (node:internal/modules/cjs/loader:1712:10)
    at Module.load (node:internal/modules/cjs/loader:1315:32)
    at Function._load (node:internal/modules/cjs/loader:1125:12)
    at TracingChannel.traceSync (node:diagnostics_channel:322:14)
    at wrapModuleLoad (node:internal/modules/cjs/loader:216:24)
    at Module.require (node:internal/modules/cjs/loader:1337:12)
    at require (node:internal/modules/helpers:139:16)
```

<!--
Before submitting a pull request, please read:

- the CONTRIBUTING guide at https://github.com/nodejs/node/blob/HEAD/CONTRIBUTING.md
- the commit message formatting guidelines at
  https://github.com/nodejs/node/blob/HEAD/doc/contributing/pull-requests.md#commit-message-guidelines

For code changes:
1. Include tests for any bug fixes or new features.
2. Update documentation if relevant.
3. Ensure that `make -j4 test` (UNIX), or `vcbuild test` (Windows) passes.

If you believe this PR should be highlighted in the Node.js CHANGELOG
please add the `notable-change` label.

Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.
-->


Changed files:
filename: lib/internal/modules/cjs/loader.js
patch: @@ -1389,7 +1389,10 @@ function loadESMFromCJS(mod, filename) {
         messagePrefix = `${from} is loading ES Module ${to} using require().\n`;
       }
     }
-    emitExperimentalWarning('Support for loading ES Module in require()', messagePrefix);
+    emitExperimentalWarning('Support for loading ES Module in require()',
+                            messagePrefix,
+                            undefined,
+                            parent?.require);
     const {
       wrap,
       namespace,

filename: lib/internal/util.js
patch: @@ -266,14 +266,20 @@ function slowCases(enc) {
   }
 }
 
-function emitExperimentalWarning(feature, messagePrefix) {
+/**
+ * @param {string} feature Feature name used in the warning message
+ * @param {string} messagePrefix Prefix of the warning message
+ * @param {string} code See documentation of process.emitWarning
+ * @param {string} ctor See documentation of process.emitWarning
+ */
+function emitExperimentalWarning(feature, messagePrefix, code, ctor) {
   if (experimentalWarnings.has(feature)) return;
   experimentalWarnings.add(feature);
   let msg = `${feature} is an experimental feature and might change at any time`;
   if (messagePrefix) {
     msg = messagePrefix + msg;
   }
-  process.emitWarning(msg, 'ExperimentalWarning');
+  process.emitWarning(msg, 'ExperimentalWarning', code, ctor);
 }
 
 function filterDuplicateStrings(items, low) {

filename: test/es-module/test-require-module-warning.js
patch: @@ -0,0 +1,35 @@
+'use strict';
+
+// This checks the warning and the stack trace emitted by the require(esm)
+// experimental warning. It can get removed when `require(esm)` becomes stable.
+
+require('../common');
+const { spawnSyncAndAssert } = require('../common/child_process');
+const fixtures = require('../common/fixtures');
+const assert = require('assert');
+
+spawnSyncAndAssert(process.execPath, [
+  '--trace-warnings',
+  fixtures.path('es-modules', 'require-module.js'),
+], {
+  trim: true,
+  stderr(output) {
+    const lines = output.split('\n');
+    assert.match(
+      lines[0],
+      /ExperimentalWarning: CommonJS module .*require-module\.js is loading ES Module .*message\.mjs/
+    );
+    assert.strictEqual(
+      lines[1],
+      'Support for loading ES Module in require() is an experimental feature and might change at any time'
+    );
+    assert.match(
+      lines[2],
+      /at require \(.*modules\/helpers:\d+:\d+\)/
+    );
+    assert.match(
+      lines[3],
+      /at Object\.<anonymous> \(.*require-module\.js:1:1\)/
+    );
+  }
+});

filename: test/fixtures/es-modules/require-module.js
patch: @@ -0,0 +1 @@
+require('./message.mjs');