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..deac8887 --- /dev/null +++ b/snapshots/input/prototype-members/src/connection.mjs @@ -0,0 +1,113 @@ +export function Connection() {} + +Connection.prototype = { + getSchemaVersion() { + return 0 + }, + getVersion: function () { + return 1 + }, + version: 1, + 'schema-version': 2, + ['computed-version']: 3, +} + +// Different spellings of the same property must share one symbol. +Connection.prototype['schema-version'] = 4 +Object.defineProperty(Connection.prototype, 'schema-version', { + value: 5, + writable: true, +}) +Connection.prototype['computed-version'] = 6 + +export function DirectConnection() {} + +DirectConnection.prototype.getVersion = function () { + return 2 +} +DirectConnection.prototype.version = 2 + +function getSchemaVersion() { + return 3 +} +DirectConnection.prototype['getSchemaVersion'] = getSchemaVersion +DirectConnection.prototype['schema-version'] = 3 + +// Reads and compound writes are references, not member declarations. +DirectConnection.prototype['version'] += 1 +DirectConnection.prototype['getSchemaVersion']() + +export function BracketConnection() {} + +BracketConnection['prototype'] = { + getSchemaVersion() { + return 4 + }, + getVersion: function () { + return 4 + }, + version: 4, +} + +export function MixedConnection() {} + +MixedConnection['prototype'].getVersion = getSchemaVersion +MixedConnection['prototype']['version'] = 5 + +export function DefinedConnection() {} + +Object.defineProperty(DefinedConnection.prototype, 'getVersion', { + value: getSchemaVersion, +}) +Object.defineProperty(DefinedConnection['prototype'], 'version', { + value: 14, + writable: true, +}) +Object.defineProperty(DefinedConnection.prototype, 'schema-version', { + get() { + return 15 + }, +}) + +let currentVersion = 16 +Object.defineProperty(DefinedConnection.prototype, 'currentVersion', { + get() { + return currentVersion + }, + set(value) { + currentVersion = value + }, +}) + +// A shadowed Object must not introduce a prototype member definition. +/** @param {{ defineProperty: Function }} Object */ +export function shadowObject(Object) { + function ShadowedConnection() {} + Object.defineProperty(ShadowedConnection.prototype, 'notAMember', { + value: 17, + }) +} + +// Ordinary writes must remain references, not new prototype definitions. +export function updateOrdinary(ordinary = { version: 6 }) { + ordinary.version = 7 + ordinary['version'] = 8 + Object.defineProperty(ordinary, 'version', { value: 9 }) +} + +// Members of local constructors must keep valid local symbols. +export function localVersion() { + function LocalConnection() {} + LocalConnection.prototype['version'] = 11 + + function LocalReplacement() {} + LocalReplacement['prototype'] = { version: 12 } + + function LocalDefined() {} + Object.defineProperty(LocalDefined.prototype, 'version', { value: 18 }) + return [ + new LocalConnection().version, + new LocalReplacement().version, + new LocalDefined().version, + ] +} diff --git a/snapshots/input/prototype-members/src/use.mjs b/snapshots/input/prototype-members/src/use.mjs new file mode 100644 index 00000000..3b1d7917 --- /dev/null +++ b/snapshots/input/prototype-members/src/use.mjs @@ -0,0 +1,59 @@ +/** @import { Connection, DirectConnection, BracketConnection, MixedConnection, DefinedConnection } from './connection.mjs' */ + +/** @param {Connection} connection */ +export function schemaVersion(connection) { + return [ + connection.getSchemaVersion(), + connection.getVersion(), + connection.version, + connection['schema-version'], + connection['computed-version'], + ] +} + +/** @param {DirectConnection} connection */ +export function directVersion(connection) { + return [ + connection.getVersion(), + connection['getVersion'](), + connection.version, + connection['version'], + connection.getSchemaVersion(), + connection['getSchemaVersion'](), + connection['schema-version'], + ] +} + +/** @param {BracketConnection} connection */ +export function bracketVersion(connection) { + return [ + connection.getSchemaVersion(), + connection.getVersion(), + connection.version, + ] +} + +/** @param {MixedConnection} connection */ +export function mixedVersion(connection) { + return [connection.getVersion(), connection.version] +} + +/** @param {DefinedConnection} connection */ +export function definedVersion(connection) { + connection.currentVersion = 19 + return [ + connection.getVersion(), + connection['getVersion'](), + connection.version, + connection['schema-version'], + connection.currentVersion, + ] +} + +/** @param {DirectConnection} connection */ +export function updateVersion(connection) { + connection.version = 9 + connection['version'] = 10 + connection.version += 1 + return 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..7ed9c00b --- /dev/null +++ b/snapshots/output/prototype-members/src/connection.mjs @@ -0,0 +1,255 @@ +// 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. + 'schema-version': 2, +//^^^^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/Connection().schema-version. + ['computed-version']: 3, +// ^^^^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/Connection().computed-version. +} + +// Different spellings of the same property must share one symbol. +Connection.prototype['schema-version'] = 4 +//^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/Connection(). +// ^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/Connection(). +// ^^^^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/Connection().schema-version. +Object.defineProperty(Connection.prototype, 'schema-version', { +//^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Object# +//^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Object. +// ^^^^^^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/ObjectConstructor#defineProperty(). +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/Connection(). +// ^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/Connection(). +// ^^^^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/Connection().schema-version. + value: 5, +//^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/PropertyDescriptor#value. + writable: true, +//^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/PropertyDescriptor#writable. +}) +Connection.prototype['computed-version'] = 6 +//^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/Connection(). +// ^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/Connection(). +// ^^^^^^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/Connection().computed-version. + +export function DirectConnection() {} +// ^^^^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/DirectConnection(). + +DirectConnection.prototype.getVersion = function () { +//^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DirectConnection(). +// ^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Function#prototype. +// ^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/DirectConnection().getVersion. + return 2 +} +DirectConnection.prototype.version = 2 +//^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DirectConnection(). +// ^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Function#prototype. +// ^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/DirectConnection().version. + +function getSchemaVersion() { +// ^^^^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/getSchemaVersion(). + return 3 +} +DirectConnection.prototype['getSchemaVersion'] = getSchemaVersion +//^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DirectConnection(). +// ^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Function#prototype. +// ^^^^^^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/DirectConnection().getSchemaVersion. +// ^^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/getSchemaVersion(). +DirectConnection.prototype['schema-version'] = 3 +//^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DirectConnection(). +// ^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Function#prototype. +// ^^^^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/DirectConnection().schema-version. + +// Reads and compound writes are references, not member declarations. +DirectConnection.prototype['version'] += 1 +//^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DirectConnection(). +// ^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Function#prototype. +// ^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DirectConnection().version. +DirectConnection.prototype['getSchemaVersion']() +//^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DirectConnection(). +// ^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Function#prototype. +// ^^^^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DirectConnection().getSchemaVersion. + +export function BracketConnection() {} +// ^^^^^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/BracketConnection(). + +BracketConnection['prototype'] = { +//^^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/BracketConnection(). +// ^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/BracketConnection(). + getSchemaVersion() { +//^^^^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/BracketConnection().getSchemaVersion(). + return 4 + }, + getVersion: function () { +//^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/BracketConnection().getVersion. + return 4 + }, + version: 4, +//^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/BracketConnection().version. +} + +export function MixedConnection() {} +// ^^^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/MixedConnection(). + +MixedConnection['prototype'].getVersion = getSchemaVersion +//^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/MixedConnection(). +// ^^^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Function#prototype. +// ^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/MixedConnection().getVersion. +// ^^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/getSchemaVersion(). +MixedConnection['prototype']['version'] = 5 +//^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/MixedConnection(). +// ^^^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Function#prototype. +// ^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/MixedConnection().version. + +export function DefinedConnection() {} +// ^^^^^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/DefinedConnection(). + +Object.defineProperty(DefinedConnection.prototype, 'getVersion', { +//^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Object# +//^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Object. +// ^^^^^^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/ObjectConstructor#defineProperty(). +// ^^^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DefinedConnection(). +// ^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Function#prototype. +// ^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/DefinedConnection().getVersion. + value: getSchemaVersion, +//^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/PropertyDescriptor#value. +// ^^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/getSchemaVersion(). +}) +Object.defineProperty(DefinedConnection['prototype'], 'version', { +//^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Object# +//^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Object. +// ^^^^^^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/ObjectConstructor#defineProperty(). +// ^^^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DefinedConnection(). +// ^^^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Function#prototype. +// ^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/DefinedConnection().version. + value: 14, +//^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/PropertyDescriptor#value. + writable: true, +//^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/PropertyDescriptor#writable. +}) +Object.defineProperty(DefinedConnection.prototype, 'schema-version', { +//^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Object# +//^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Object. +// ^^^^^^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/ObjectConstructor#defineProperty(). +// ^^^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DefinedConnection(). +// ^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Function#prototype. +// ^^^^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/DefinedConnection().schema-version. + get() { +//^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/PropertyDescriptor#get(). + return 15 + }, +}) + +let currentVersion = 16 +// ^^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/currentVersion. +Object.defineProperty(DefinedConnection.prototype, 'currentVersion', { +//^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Object# +//^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Object. +// ^^^^^^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/ObjectConstructor#defineProperty(). +// ^^^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DefinedConnection(). +// ^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Function#prototype. +// ^^^^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/DefinedConnection().currentVersion. + get() { +//^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/PropertyDescriptor#get(). + return currentVersion +// ^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/currentVersion. + }, + set(value) { +//^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/PropertyDescriptor#set(). +// ^^^^^ definition local 2 + currentVersion = value +// ^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/currentVersion. +// ^^^^^ reference local 2 + }, +}) + +// A shadowed Object must not introduce a prototype member definition. +/** @param {{ defineProperty: Function }} Object */ +export function shadowObject(Object) { +// ^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/shadowObject(). +// ^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/shadowObject().(Object) + function ShadowedConnection() {} +// ^^^^^^^^^^^^^^^^^^ definition local 3 + Object.defineProperty(ShadowedConnection.prototype, 'notAMember', { +//^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/shadowObject().(Object) +// ^^^^^^^^^^^^^^ reference local 8 +// ^^^^^^^^^^^^^^^^^^ reference local 3 +// ^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Function#prototype. +// ^^^^^^^^^^^^ reference local 10 + value: 17, +// ^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/value0: + }) +} + +// Ordinary writes must remain references, not new prototype definitions. +export function updateOrdinary(ordinary = { version: 6 }) { +// ^^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/updateOrdinary(). +// ^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/updateOrdinary().(ordinary) +// ^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/version0: + ordinary.version = 7 +//^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/updateOrdinary().(ordinary) +// ^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/version0: + ordinary['version'] = 8 +//^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/updateOrdinary().(ordinary) +// ^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/version0: + Object.defineProperty(ordinary, 'version', { value: 9 }) +//^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Object# +//^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Object. +// ^^^^^^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/ObjectConstructor#defineProperty(). +// ^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/updateOrdinary().(ordinary) +// ^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/PropertyDescriptor#value. +} + +// Members of local constructors must keep valid local symbols. +export function localVersion() { +// ^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`connection.mjs`/localVersion(). + function LocalConnection() {} +// ^^^^^^^^^^^^^^^ definition local 11 + LocalConnection.prototype['version'] = 11 +//^^^^^^^^^^^^^^^ reference local 11 +// ^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Function#prototype. +// ^^^^^^^^^ definition local 12 + + function LocalReplacement() {} +// ^^^^^^^^^^^^^^^^ definition local 13 + LocalReplacement['prototype'] = { version: 12 } +//^^^^^^^^^^^^^^^^ reference local 13 +// ^^^^^^^^^^^ reference local 13 +// ^^^^^^^ definition local 14 + + function LocalDefined() {} +// ^^^^^^^^^^^^ definition local 15 + Object.defineProperty(LocalDefined.prototype, 'version', { value: 18 }) +//^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Object# +//^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Object. +// ^^^^^^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/ObjectConstructor#defineProperty(). +// ^^^^^^^^^^^^ reference local 15 +// ^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Function#prototype. +// ^^^^^^^^^ definition local 16 +// ^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/PropertyDescriptor#value. + return [ + new LocalConnection().version, +// ^^^^^^^^^^^^^^^ reference local 11 +// ^^^^^^^ reference local 12 + new LocalReplacement().version, +// ^^^^^^^^^^^^^^^^ reference local 13 +// ^^^^^^^ reference local 14 + new LocalDefined().version, +// ^^^^^^^^^^^^ reference local 15 +// ^^^^^^^ reference local 16 + ] +} + diff --git a/snapshots/output/prototype-members/src/use.mjs b/snapshots/output/prototype-members/src/use.mjs new file mode 100644 index 00000000..f1db297f --- /dev/null +++ b/snapshots/output/prototype-members/src/use.mjs @@ -0,0 +1,129 @@ +// language JavaScript +// < definition prototype-members 1.0.0 src/`use.mjs`/ + +/** @import { Connection, DirectConnection, BracketConnection, MixedConnection, DefinedConnection } 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. + connection['schema-version'], +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/schemaVersion().(connection) +// ^^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/Connection().schema-version. + connection['computed-version'], +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/schemaVersion().(connection) +// ^^^^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/Connection().computed-version. + ] +} + +/** @param {DirectConnection} connection */ +export function directVersion(connection) { +// ^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`use.mjs`/directVersion(). +// ^^^^^^^^^^ definition prototype-members 1.0.0 src/`use.mjs`/directVersion().(connection) + return [ + connection.getVersion(), +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/directVersion().(connection) +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DirectConnection().getVersion. + connection['getVersion'](), +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/directVersion().(connection) +// ^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DirectConnection().getVersion. + connection.version, +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/directVersion().(connection) +// ^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DirectConnection().version. + connection['version'], +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/directVersion().(connection) +// ^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DirectConnection().version. + connection.getSchemaVersion(), +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/directVersion().(connection) +// ^^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DirectConnection().getSchemaVersion. + connection['getSchemaVersion'](), +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/directVersion().(connection) +// ^^^^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DirectConnection().getSchemaVersion. + connection['schema-version'], +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/directVersion().(connection) +// ^^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DirectConnection().schema-version. + ] +} + +/** @param {BracketConnection} connection */ +export function bracketVersion(connection) { +// ^^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`use.mjs`/bracketVersion(). +// ^^^^^^^^^^ definition prototype-members 1.0.0 src/`use.mjs`/bracketVersion().(connection) + return [ + connection.getSchemaVersion(), +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/bracketVersion().(connection) +// ^^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/BracketConnection().getSchemaVersion(). + connection.getVersion(), +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/bracketVersion().(connection) +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/BracketConnection().getVersion. + connection.version, +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/bracketVersion().(connection) +// ^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/BracketConnection().version. + ] +} + +/** @param {MixedConnection} connection */ +export function mixedVersion(connection) { +// ^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`use.mjs`/mixedVersion(). +// ^^^^^^^^^^ definition prototype-members 1.0.0 src/`use.mjs`/mixedVersion().(connection) + return [connection.getVersion(), connection.version] +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/mixedVersion().(connection) +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/MixedConnection().getVersion. +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/mixedVersion().(connection) +// ^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/MixedConnection().version. +} + +/** @param {DefinedConnection} connection */ +export function definedVersion(connection) { +// ^^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`use.mjs`/definedVersion(). +// ^^^^^^^^^^ definition prototype-members 1.0.0 src/`use.mjs`/definedVersion().(connection) + connection.currentVersion = 19 +//^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/definedVersion().(connection) +// ^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DefinedConnection().currentVersion. + return [ + connection.getVersion(), +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/definedVersion().(connection) +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DefinedConnection().getVersion. + connection['getVersion'](), +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/definedVersion().(connection) +// ^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DefinedConnection().getVersion. + connection.version, +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/definedVersion().(connection) +// ^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DefinedConnection().version. + connection['schema-version'], +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/definedVersion().(connection) +// ^^^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DefinedConnection().schema-version. + connection.currentVersion, +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/definedVersion().(connection) +// ^^^^^^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DefinedConnection().currentVersion. + ] +} + +/** @param {DirectConnection} connection */ +export function updateVersion(connection) { +// ^^^^^^^^^^^^^ definition prototype-members 1.0.0 src/`use.mjs`/updateVersion(). +// ^^^^^^^^^^ definition prototype-members 1.0.0 src/`use.mjs`/updateVersion().(connection) + connection.version = 9 +//^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/updateVersion().(connection) +// ^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DirectConnection().version. + connection['version'] = 10 +//^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/updateVersion().(connection) +// ^^^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DirectConnection().version. + connection.version += 1 +//^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/updateVersion().(connection) +// ^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DirectConnection().version. + return connection.version +// ^^^^^^^^^^ reference prototype-members 1.0.0 src/`use.mjs`/updateVersion().(connection) +// ^^^^^^^ reference prototype-members 1.0.0 src/`connection.mjs`/DirectConnection().version. +} + diff --git a/src/FileIndexer.test.ts b/src/FileIndexer.test.ts new file mode 100644 index 00000000..1e44e609 --- /dev/null +++ b/src/FileIndexer.test.ts @@ -0,0 +1,65 @@ +import * as path from 'path' + +import * as ts from 'typescript' +import { test } from 'uvu' +import * as assert from 'uvu/assert' + +import { FileIndexer } from './FileIndexer' +import { Input } from './Input' +import { Packages } from './Packages' +import { scip } from './scip' +import { ScipSymbol } from './ScipSymbol' + +test('local prototype owners stay out of the global symbol table', () => { + const cwd = path.resolve('snapshots/input/prototype-members') + const fileName = path.join(cwd, 'src/connection.mjs') + const program = ts.createProgram([fileName], { + allowJs: true, + noEmit: true, + target: ts.ScriptTarget.ES2022, + }) + const sourceFile = program.getSourceFile(fileName)! + const globalSymbols = new Map() + const document = new scip.Document() + new FileIndexer( + program.getTypeChecker(), + { + cwd, + projectRoot: cwd, + projectDisplayName: 'prototype-members', + output: '', + inferTsconfig: false, + progressBar: false, + yarnWorkspaces: false, + yarnBerryWorkspaces: false, + pnpmWorkspaces: false, + globalCaches: true, + indexedProjects: new Set(), + writeIndex: () => {}, + }, + Input.fromFile(fileName), + document, + globalSymbols, + new Map(), + new Packages(cwd), + sourceFile + ).index() + + assert.ok( + document.occurrences.some( + occurrence => + occurrence.symbol.startsWith('local ') && + (occurrence.symbol_roles & scip.SymbolRole.Definition) !== 0 + ), + 'the fixture must exercise local definitions' + ) + assert.ok(globalSymbols.size > 0) + assert.equal( + [...globalSymbols] + .filter(([, symbol]) => symbol.isLocal()) + .map(([node]) => node.getText()), + [] + ) +}) + +test.run() diff --git a/src/FileIndexer.ts b/src/FileIndexer.ts index bcae42ee..a763e58e 100644 --- a/src/FileIndexer.ts +++ b/src/FileIndexer.ts @@ -110,7 +110,21 @@ export class FileIndexer { const rangeNode: ts.Node = ts.isConstructorDeclaration(node) ? (node.getFirstToken() ?? node) : node - const symbol = this.checker.getSymbolAtLocation(rangeNode) + let symbol = this.checker.getSymbolAtLocation(rangeNode) + + // For C.prototype['member'] assignments the checker exposes the member on + // C's instance type, but not at the string literal itself. + if ( + !symbol && + ts.isElementAccessExpression(node.parent) && + node.parent.argumentExpression === node && + ts.isStringLiteralLike(node) + ) { + const owner = this.prototypeOwner(node.parent.expression) + symbol = owner + ? this.checker.getDeclaredTypeOfSymbol(owner).getProperty(node.text) + : undefined + } // If this is an alias, and the request came at the declaration location // get the aliased symbol instead. This allows for goto def on an import e.g. @@ -177,7 +191,16 @@ export class FileIndexer { let role = 0 let declarations: ts.Node[] = this.getDeclarationsForPropertyAssignment(node) ?? [] - const isDefinitionNode = declarations.length === 0 && isDefinition(node) + const isPrototypeMemberDefinition = + (accessName(node.parent) !== undefined || + (ts.isCallExpression(node.parent) && + node.parent.arguments[1] === node)) && + sym.declarations?.some(declaration => declaration === node.parent) === + true && + this.prototypeAssignment(node.parent)?.memberName !== undefined + const isDefinitionNode = + declarations.length === 0 && + (isDefinition(node) || isPrototypeMemberDefinition) if (isDefinitionNode) { role |= scip.scip.SymbolRole.Definition } @@ -424,6 +447,122 @@ export class FileIndexer { } return relationships } + + private prototypeOwner(node: ts.Node): ts.Symbol | undefined { + if ( + !( + ts.isPropertyAccessExpression(node) || + ts.isElementAccessExpression(node) + ) || + accessName(node) !== 'prototype' + ) { + return + } + const symbol = this.checker.getSymbolAtLocation(node.expression) + return symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0 + ? this.checker.getAliasedSymbol(symbol) + : symbol + } + + private prototypeAssignment( + node: ts.Node + ): { declaration: ts.Declaration; memberName?: string } | undefined { + if (ts.isCallExpression(node)) { + const [target, name] = node.arguments + if ( + node.arguments.length !== 3 || + !ts.isStringLiteralLike(name) || + !ts.isPropertyAccessExpression(node.expression) || + node.expression.name.text !== 'defineProperty' || + !ts.isIdentifier(node.expression.expression) || + node.expression.expression.text !== 'Object' + ) { + return + } + // The checker also treats a shadowed Object.defineProperty as a member + // declaration. Only the global Object has the semantics we rely on. + const object = this.checker.getSymbolAtLocation( + node.expression.expression + ) + if ( + !object || + object !== + this.checker.resolveName( + 'Object', + undefined, + ts.SymbolFlags.Value, + false + ) + ) { + return + } + const symbol = this.prototypeOwner(target) + const declaration = symbol?.valueDeclaration ?? symbol?.declarations?.[0] + return declaration ? { declaration, memberName: name.text } : undefined + } + + const assignment = ts.isBinaryExpression(node) + ? node + : ts.isObjectLiteralExpression(node) + ? node.parent + : ts.isPropertyAccessExpression(node) || + ts.isElementAccessExpression(node) + ? node.parent + : (ts.isIdentifier(node) || ts.isStringLiteralLike(node)) && + (ts.isPropertyAccessExpression(node.parent) || + ts.isElementAccessExpression(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.isPropertyAccessExpression(assignment.left) || + ts.isElementAccessExpression(assignment.left) + ) + ) { + return + } + + let prototype: ts.Node = assignment.left + let memberName: string | undefined + if ( + node === assignment.left && + accessName(assignment.left.expression) === 'prototype' + ) { + prototype = assignment.left.expression + memberName = accessName(node) + if (memberName === undefined) { + return + } + } else { + if (!ts.isObjectLiteralExpression(assignment.right)) { + return + } + if ( + ts.isPropertyAssignment(node) || + ts.isShorthandPropertyAssignment(node) + ) { + const name = ts.isComputedPropertyName(node.name) + ? node.name.expression + : node.name + memberName = + ts.isStringLiteralLike(name) || ts.isNumericLiteral(name) + ? name.text + : node.name.getText() + } + } + + const symbol = this.prototypeOwner(prototype) + const declaration = symbol?.valueDeclaration ?? symbol?.declarations?.[0] + return declaration ? { declaration, memberName } : undefined + } + private scipSymbol(node: ts.Node): ScipSymbol { const fromCache: ScipSymbol | undefined = this.globalSymbolTable.get(node) || this.localSymbolTable.get(node) @@ -440,6 +579,28 @@ export class FileIndexer { } return this.cached(node, package_) } + + const prototypeAssignment = this.prototypeAssignment(node) + if (prototypeAssignment) { + // Declarations attached to prototype writes and their object literals belong + // to the constructor. Members need their own stable descriptor instead + // of the local or counter-based object-property fallback. + const owner = this.scipSymbol(prototypeAssignment.declaration) + if (owner.isEmpty() || owner.isLocal()) { + return prototypeAssignment.memberName !== undefined + ? this.newLocalSymbol(node) + : owner + } + const symbol = + prototypeAssignment.memberName !== undefined + ? ScipSymbol.global( + owner, + termDescriptor(prototypeAssignment.memberName) + ) + : owner + return this.cached(node, symbol) + } + if ( ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node) @@ -765,6 +926,19 @@ export class FileIndexer { } } +function accessName(node: ts.Node): string | undefined { + if (ts.isPropertyAccessExpression(node)) { + return node.name.text + } + if ( + ts.isElementAccessExpression(node) && + ts.isStringLiteralLike(node.argumentExpression) + ) { + return node.argumentExpression.text + } + return undefined +} + function isAnonymousContainerOfSymbols(node: ts.Node): boolean { return ( ts.isModuleBlock(node) ||