Skip to content

Opening a definition target makes Type#getCallSignatures() diverge from the checker API #57

Description

@KazariEX

With typescript-native-bridge@6.0.3-bridge.9.tsgo.7.0.2, a TS plugin calls program.getSourceFile() for a definition target. After the editor opens that declaration file, Type#getCallSignatures() returns an empty array while checker.getSignaturesOfType() still returns the correct signature for the same Type.

The reproduction plugin returns every language service result unchanged and only records the two API results.

Stock TS 6.0.3 and TNB bridge.8 do not reproduce this. TNB bridge.9 through bridge.12 reproduce consistently.

Reproduction

package.json:

{
  "private": true,
  "devDependencies": {
    "repro-plugin": "file:./repro-plugin",
    "typescript": "npm:typescript-native-bridge@6.0.3-bridge.12.tsgo.7.0.2"
  }
}

.vscode/settings.json:

{
  "typescript.tsdk": "node_modules/typescript/lib",
  "typescript.tsserver.log": "verbose"
}

tsconfig.json:

{
  "compilerOptions": {
    "strict": true,
    "plugins": [
      { "name": "repro-plugin" }
    ]
  },
  "include": ["main.ts", "handler.ts"]
}

repro-plugin/package.json:

{
  "name": "repro-plugin",
  "version": "1.0.0",
  "main": "index.cjs"
}

repro-plugin/index.cjs:

module.exports = ({ typescript: ts }) => ({
  create(info) {
    const original = info.languageService.getDefinitionAndBoundSpan;

    info.languageService.getDefinitionAndBoundSpan = (fileName, position) => {
      const result = original(fileName, position);
      if (!result?.definitions?.length) return result;

      const program = info.languageService.getProgram();

      for (const definition of result.definitions) {
        program.getSourceFile(definition.fileName);
      }

      const sourceFile = program.getSourceFile(fileName);
      const token = ts.getTokenAtPosition(sourceFile, position);
      const parameter = ts.findAncestor(token, ts.isParameter);

      if (parameter) {
        const checker = program.getTypeChecker();
        const type = checker.getContextualType(parameter.parent);

        info.project.projectService.logger.info(`REPRO ${JSON.stringify({
          typeMethod: type?.getCallSignatures().length,
          checkerMethod: type
            ? checker.getSignaturesOfType(type, ts.SignatureKind.Call).length
            : undefined,
        })}`);
      }

      return result;
    };

    return info.languageService;
  },
});

handler.ts:

export interface TestEvent {
  value: string;
}

export interface Handler {
  (event: TestEvent): void;
}

export declare function defineHandler(handler: Handler): void;

external.d.ts:

export declare function external(): void;

main.ts:

import { external } from "./external";
import { defineHandler } from "./handler";

defineHandler((event) => {
  external();
  console.log(event.value);
});
  1. Run pnpm install.
  2. Open the project and select the workspace TypeScript version.
  3. Open main.ts and invoke Go to Definition on the callback parameter event.
  4. Invoke Go to Definition on external, opening external.d.ts.
  5. Return to main.ts and invoke Go to Definition on event again.
  6. Run TypeScript: Open TS Server log and search for REPRO.

Expected

Both queries report one call signature through both API paths:

REPRO {"typeMethod":1,"checkerMethod":1}
REPRO {"typeMethod":1,"checkerMethod":1}

Actual

After opening external.d.ts, the Type method loses the signature while the checker API remains correct:

REPRO {"typeMethod":1,"checkerMethod":1}
REPRO {"typeMethod":0,"checkerMethod":1}

This issue was created with assistance from a code agent.

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