diff --git a/snapshots/input/prototype-members/package.json b/snapshots/input/prototype-members/package.json new file mode 100644 index 00000000..f5a01517 --- /dev/null +++ b/snapshots/input/prototype-members/package.json @@ -0,0 +1,6 @@ +{ + "name": "prototype-members", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/snapshots/input/prototype-members/src/connection.mjs b/snapshots/input/prototype-members/src/connection.mjs new file mode 100644 index 00000000..14352345 --- /dev/null +++ b/snapshots/input/prototype-members/src/connection.mjs @@ -0,0 +1,11 @@ +export function Connection() {} + +Connection.prototype = { + getSchemaVersion() { + return 0 + }, + getVersion: function () { + return 1 + }, + version: 1, +} diff --git a/snapshots/input/prototype-members/src/use.mjs b/snapshots/input/prototype-members/src/use.mjs new file mode 100644 index 00000000..b7781e42 --- /dev/null +++ b/snapshots/input/prototype-members/src/use.mjs @@ -0,0 +1,10 @@ +/** @import { Connection } from './connection.mjs' */ + +/** @param {Connection} connection */ +export function schemaVersion(connection) { + return [ + connection.getSchemaVersion(), + connection.getVersion(), + connection.version, + ] +} diff --git a/snapshots/input/prototype-members/tsconfig.json b/snapshots/input/prototype-members/tsconfig.json new file mode 100644 index 00000000..8a630ef1 --- /dev/null +++ b/snapshots/input/prototype-members/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + "target": "ES2022" + }, + "include": ["src/**/*.mjs"] +} diff --git a/snapshots/output/prototype-members/src/connection.mjs b/snapshots/output/prototype-members/src/connection.mjs new file mode 100644 index 00000000..131f6eee --- /dev/null +++ b/snapshots/output/prototype-members/src/connection.mjs @@ -0,0 +1,21 @@ +// language JavaScript +// < definition prototype-members 1.0.0 src/`connection.mjs`/ + +export function Connection() {} +// ^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/Connection(). + +Connection.prototype = { +//^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/Connection(). +// ^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/Connection(). + getSchemaVersion() { +//^^^^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/Connection().getSchemaVersion(). + return 0 + }, + getVersion: function () { +//^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/Connection().getVersion. + return 1 + }, + version: 1, +//^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/Connection().version. +} + diff --git a/snapshots/output/prototype-members/src/use.mjs b/snapshots/output/prototype-members/src/use.mjs new file mode 100644 index 00000000..b5dc1c4a --- /dev/null +++ b/snapshots/output/prototype-members/src/use.mjs @@ -0,0 +1,22 @@ +// language JavaScript +// < definition prototype-members 1.0.0 src/`use.mjs`/ + +/** @import { Connection } from './connection.mjs' */ + +/** @param {Connection} connection */ +export function schemaVersion(connection) { +// ^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`use.mjs`/schemaVersion(). +// ^^^^^^^^^^ definition prototype-members 1.0.0 src/`use.mjs`/schemaVersion().(connection) + return [ + connection.getSchemaVersion(), +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/schemaVersion().(connection) +// ^^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/Connection().getSchemaVersion(). + connection.getVersion(), +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/schemaVersion().(connection) +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/Connection().getVersion. + connection.version, +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/schemaVersion().(connection) +// ^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/Connection().version. + ] +} + diff --git a/src/FileIndexer.ts b/src/FileIndexer.ts index bcae42ee..9b9f200c 100644 --- a/src/FileIndexer.ts +++ b/src/FileIndexer.ts @@ -424,6 +424,38 @@ export class FileIndexer { } return relationships } + + private prototypeAssignmentOwner(node: ts.Node): ts.Declaration | undefined { + const assignment = ts.isBinaryExpression(node) + ? node + : ts.isObjectLiteralExpression(node) + ? node.parent + : ts.isPropertyAccessExpression(node) + ? node.parent + : ts.isIdentifier(node) && ts.isPropertyAccessExpression(node.parent) + ? node.parent.parent + : (ts.isPropertyAssignment(node) || + ts.isShorthandPropertyAssignment(node)) && + ts.isObjectLiteralExpression(node.parent) + ? node.parent.parent + : undefined + if ( + !assignment || + !ts.isBinaryExpression(assignment) || + assignment.operatorToken.kind !== ts.SyntaxKind.EqualsToken || + !ts.isObjectLiteralExpression(assignment.right) || + !ts.isPropertyAccessExpression(assignment.left) || + assignment.left.name.text !== 'prototype' + ) { + return + } + let symbol = this.checker.getSymbolAtLocation(assignment.left.expression) + if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0) { + symbol = this.checker.getAliasedSymbol(symbol) + } + return symbol?.valueDeclaration ?? symbol?.declarations?.[0] + } + private scipSymbol(node: ts.Node): ScipSymbol { const fromCache: ScipSymbol | undefined = this.globalSymbolTable.get(node) || this.localSymbolTable.get(node) @@ -440,6 +472,20 @@ export class FileIndexer { } return this.cached(node, package_) } + + const prototypeOwner = this.prototypeAssignmentOwner(node) + if (prototypeOwner) { + // Declarations attached to the assignment and its object literal belong + // to the constructor. Property assignments need their own stable member + // descriptor instead of the counter-based object-property fallback. + const owner = this.scipSymbol(prototypeOwner) + const symbol = + ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node) + ? ScipSymbol.global(owner, termDescriptor(node.name.getText())) + : owner + return this.cached(node, symbol) + } + if ( ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node)