Skip to content

JavaScript: every mod.x() through a CommonJS require links to the module's one anonymous exports.y = function #2952

Closed

Description

@inth3shadows

In a CommonJS module that assigns functions to exports.x, an anonymous function expression is indexed as exported but a named one isn't. A caller that does const mod = require('./mod'); mod.anything() then links every member call to the one anonymous export, through the "first exported function" default fallback. Calls to the named exports get no edge.

// lib/utils.js
function createETagGenerator(options) { return function generateETag(body) { return body; }; }
exports.etag = createETagGenerator({ weak: false });
exports.normalizeType = function (type) { return type; };            // line 4, anonymous
exports.compileTrust = function compileTrust(val) { return val; };   // line 5, named

// test/utils.js
var utils = require('../lib/utils');
function t() { utils.etag('x'); utils.compileTrust(1); utils.missing(); }
module.exports = t;
call edge on main expected
utils.etag('x') normalizeType (resolvedBy: import) ✗ none (a value, not a declared function)
utils.compileTrust(1) normalizeType ✗ compileTrust
utils.missing() normalizeType ✗ none

The indexed nodes show why:

function normalizeType  lib/utils.js:4  isExported=1
function compileTrust   lib/utils.js:5  isExported=0

With only the named form (exports.a = function a() {} for every export) there are no wrong edges, but utils.a() doesn't link to a either. const vs var makes no difference.

Reproduced on main at 2577601 with CODEGRAPH_KERNEL=0. Linux/WSL2, Node 22.23.2. Not fixed by #2754 or #2800 (I ran this fixture on both heads).

Where it shows up. express (1c6f1b0) writes lib/utils.js this way: exports.normalizeType = function(type){…} and exports.compileTrust = function(val){…} are anonymous, and exports.etag = createETagGenerator(…) is a value. In test/utils.js (var utils = require('../lib/utils')), utils.etag(…) links to normalizeType.

Two parts (inferred, not traced):

  1. Export status differs between an anonymous and a named function expression on the right of exports.x =. The named one should count as exported too. Export status is answered in three places, two of them hand-mirrored regexes #1721 / fix: unify export-status detection and fix ambient export handling (#1721) #2896 consolidate this check.
  2. Separately, the default fallback (the first exported function when the module has no export default) shouldn't answer a member call through a require binding: mod.x() reads property x, not the default. Fixing 1 alone makes this worse, because the fallback then has more functions to pick from. fix: unify export-status detection and fix ambient export handling (#1721) #2896 shows the same thing for module.exports = { … } modules.

Related: #2189 (calls through CommonJS require bindings), #2432 (ES default imports no longer take the first exported function), #1721, #2896.

Edited 2026-11-04: the call edges no longer reproduce on main at f72c9d2 (with CODEGRAPH_KERNEL=0 and with the native kernel, Node 22.22.2): utils.etag() and utils.missing() get no edge and utils.compileTrust() links to compileTrust. Most likely fixed by #3162. One leftover: a named function expression (exports.x = function x() {}) still isn't marked exported, unlike the anonymous form, but I found no user-visible effect, so I'm not refiling it. Closing.

Activity

  1. inth3shadows commented on Nov 4, 2026

    @inth3shadows
    ContributorAuthor

    The call edges no longer reproduce on main at f72c9d2 (with CODEGRAPH_KERNEL=0 and with the native kernel). Most likely fixed by #3162. A named function expression on exports.x still isn't marked exported, but I found no user-visible effect, so I'm not refiling it. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions