Skip to content

fix: emit stable symbols for callable property assignments#466

Open
jordanjwong wants to merge 1 commit into
sourcegraph:mainfrom
jordanjwong:fix-callable-property-assignment-local-symbols
Open

fix: emit stable symbols for callable property assignments#466
jordanjwong wants to merge 1 commit into
sourcegraph:mainfrom
jordanjwong:fix-callable-property-assignment-local-symbols

Conversation

@jordanjwong

@jordanjwong jordanjwong commented Jul 20, 2026

Copy link
Copy Markdown

Fixes the local symbol gap where callable property assignments were emitted as local symbols.

Problem

obj.method = async () => {}
obj.method = function() {}
obj['prop'] = () => {}
obj.nested.method = async () => {}

were emitted as:

src/file.js/local 42

instead of stable property-derived symbols (e.g. obj.method(), obj.nested.method()). This caused definition coverage gaps.

Object literal cases like:

return { interfaceMethod: () => 'inferred' }

were also only references to the interface, not definitions.

Fix (src/FileIndexer.ts only, no dist)

  • Add callablePropertySymbolsInProgress: Set<Node> guard
  • visit() now handles isConstructorDeclaration and callablePropertyAssignmentName
  • visitSymbolOccurrence(node, sym?: Symbol) accepts optional symbol
  • Detect BinaryExpression where LHS is PropertyAccessExpression / ElementAccessExpression (string literal) and RHS is ArrowFunction / FunctionExpression
  • callablePropertySymbol() builds owner chain: root from declaration if found, else file-scoped termDescriptor(root), intermediate as termDescriptor, final as methodDescriptor
  • isDefinition() includes callablePropertyAssignmentName
  • Enclosing range set to RHS function node
  • SymbolInformation includes display_name + kind

Examples

1. Chained assignment

const utils = {};
utils.tokens = {};
utils.tokens.list = async () => { return 42; }
utils.tokens.list();

Before:

definition .../local 42

After:

utils.tokens.list = async () => { return 42; }
//            ^^^^ definition .../utils.tokens.list().

utils.tokens.list();
//            ^^^^ reference .../utils.tokens.list().

2. Function expression + nested + bracket

utils.tokens.get = function() { return 1; }
obj['bracket'] = async () => { return 2; }
obj.nested.deep = async () => { return 3; }

Before:

utils.tokens.get = function() { return 1; }
//           ^^^ definition .../local 5

obj['bracket'] = async () => { return 2; }
//  ^^^^^^^^^ definition .../local 6

obj.nested.deep = async () => { return 3; }
//         ^^^^ definition .../local 7

After:

utils.tokens.get = function() { return 1; }
//           ^^^ definition .../utils.tokens.get().

obj['bracket'] = async () => { return 2; }
//  ^^^^^^^^^ definition .../obj.bracket().

obj.nested.deep = async () => { return 3; }
//         ^^^^ definition .../obj.nested.deep().

3. Object literal impl (snapshot change)

return {
  methodSignature2: (param: string): string => {
    return param
  }
}

Before:

    methodSignature2: (param: string): string => {
//  ^^^^^^^^^^^^^^^^ reference .../Interface#methodSignature2.

After:

    methodSignature2: (param: string): string => {
//  ^^^^^^^^^^^^^^^^ definition .../methodSignature20:
//  relationship implementation reference .../Interface#methodSignature2.

Snapshots

  • Updated snapshots/output/syntax/src/interface.ts, inheritance.ts, object-literals-call-signatures.ts

Validation – per Development.md

  • yarn 1.22.19 (CI version)
  • yarn install, yarn build, eslint + prettier clean
  • yarn test → 31 passed

@jordanjwong
jordanjwong marked this pull request as draft July 20, 2026 22:26
Previously patterns like

  obj.method = async () => {}
  obj.method = function() {}
  obj['prop'] = () => {}
  obj.nested.method = async () => {}

were emitted as local symbols such as src/file.js/local 42 instead
of stable property-derived symbols, causing definition coverage gaps.

Changes:
- Add in-progress guard set to prevent recursive symbol resolution
- Handle constructor and callable property assignment names in visit()
- Accept optional symbol in visitSymbolOccurrence() for cases where
  checker does not provide a symbol (property assignments)
- Detect property assignments where RHS is function-like and emit
  enclosing range and symbol information with display name and kind
- Derive deterministic property path for dot and bracket string literal
  access, building owner chain with term/method descriptors
- Update isDefinition() to recognize callable property assignment names
- Add helpers for object literal callable property assignments and
  symbol information display name/kind
@jordanjwong
jordanjwong force-pushed the fix-callable-property-assignment-local-symbols branch from 24850fe to badace4 Compare July 21, 2026 14:45
@jordanjwong
jordanjwong marked this pull request as ready for review July 21, 2026 14:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant