Skip to content

JavaScript: Ctor.prototype.m = function () {} methods get no node; this.m() inside them links to unrelated namesakes #2966

Open

Description

@inth3shadows

Methods defined the classic JavaScript way, Ctor.prototype.m = function () {…}, get no node. The calls inside them are attributed to the file, and this.m() between them resolves by bare name to whatever else in the project is called m: an unrelated function in another file, or an interface method in a .d.ts.

// lib/hooks.js
'use strict'
function Hooks () { this.list = [] }
Hooks.prototype.validate = function (hook, fn) { return typeof fn === 'function' }
Hooks.prototype.add = function (hook, fn) {
  this.validate(hook, fn)                       // line 5
  this.list.push(fn)
}
module.exports = Hooks

// lib/validation.js
function validate (schema, data) { return true }
module.exports = { validate }

// lib/reply.js
function Reply () {}
Reply.prototype.getHeader = function (k) { return k }
Reply.prototype.send = function () { return this.getHeader('content-type') }   // line 3
module.exports = Reply

// lib/types.d.ts
export interface FastifyReply { getHeader(key: string): string | undefined; }
on main expected
nodes for Hooks.prototype.validate, .add, Reply.prototype.getHeader, .send none a method node each (on Hooks / Reply)
this.validate(hook, fn) (line 5) hooks.js (file) → validate in lib/validation.js (exact-match) ✗ add → Hooks.prototype.validate
this.getHeader(…) (line 3) reply.js (file) → FastifyReply.getHeader in types.d.ts (exact-match) ✗ send → Reply.prototype.getHeader

Reproduced on main at 72f398a, with CODEGRAPH_KERNEL=0 and with the native kernel. Linux/WSL2, Node 22.23.2.

Where it shows up. fastify (5df959f) defines its request/reply/hooks API this way: 35 X.prototype.m = function methods in lib/, for example Reply.prototype.header, .getHeader, .send and .compileSerializationSchema. None of them are in the graph as methods. Their internal this.m() calls show up as calls from the file to the type declarations (types/reply.d.ts) or to same-named helpers elsewhere (lib/hooks.js:69 this.validate(…) → lib/validation.js). Older Node libraries and much of npm's CommonJS code still define classes like this.

Same root, two more shapes (both reproduced on 72f398a with CODEGRAPH_KERNEL=0):

Static methods assigned to the constructor. The function expressions are indexed as plain functions, not as members of Model, so Model.x() doesn't reach them:

// lib/model.js
function Model () {}
Model.insertMany = async function insertMany (arr) { return arr }
Model.init = function init () { return Model.insertMany([]) }   // → Cast.insertMany in lib/other.js ✗
module.exports = Model
// lib/other.js
class Cast { init () { return 1 } insertMany () { return 2 } }
// test/model.test.js
const Model = require('../lib/model')
async function t () { await Model.init(); await Model.insertMany([1]) }   // → links the `Model` constructor only

mongoose (f59e13a) defines its model API this way (Model.insertMany, Model.init, Model.validate, Model.discriminator, … in lib/model.js). Those calls land on types/models.d.ts declarations or on other classes' same-named methods.

Methods of an object literal assigned to module.exports. They get no node, and this.m() inside links to another file's m:

// lib/context.js
module.exports = {
  inspect () { return this.toJSON() },   // → Application.toJSON in lib/application.js ✗
  toJSON () { return {} }
}

koa (b786e7b) writes context, request and response like this. lib/context.js this.toJSON() links to lib/application.js's toJSON.

Expected. Index X.prototype.m = function () {} (and = async function, = () => {}) as a method m of X when X is a function or class in the file. Attribute calls inside it to that method, and resolve this.m() inside it against X's members first, never by bare name across files.

Related: #1771 (exports.x = function indexed as a function), #2954 / #2958 (other this./self. calls that fall through to an unrelated same-named function).

Edited 2026-11-04: re-checked on main at f72c9d2 (with CODEGRAPH_KERNEL=0 and with the native kernel, Node 22.22.2): still reproduces. Corrected two slips: the this.getHeader call is on line 3 of the snippet, not line 4, and the Model test links the Model constructor (not init or insertMany) rather than nothing. Related: #3171 (stops one lone-method guess, doesn't index these members) and #2300 (object-literal members get no node).

Activity

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