Skip to content

JSDoc @link not resolved across modulesΒ #43869

Description

Bug Report

While verifying microsoft/vscode#119357

πŸ”Ž Search Terms

  • link
  • export
  • modules

πŸ•— Version & Regression Information

  • I was unable to test this on prior versions because it did not previously exist

⏯ Playground Link

Workbench Repro

πŸ’» Code

// @filename: a.ts
import { Model } from './b';

/**
 * {@link Model}
 */

// @filename: b.ts
export class Model {}

πŸ™ Actual behavior

@link Model is not clickable

πŸ™‚ Expected behavior

I expected to be able to click on @link Model

Activity

  1. connor4312 commented on Apr 28, 2021

    @connor4312
    MemberAuthor

    Actually, this may be wrong -- the workbench seems like it might be slightly buggy. This works only if I import the referenced type. However, with the compiler flag disallowed unused locals, this produces a warning if I don't use the type elsewhere in the file.

    One fix for that would be to mark linked types as "Used". C# does that in their xml docs. Or just link against all exported globals, but this might direct to the wrong types if they share names.

    This is the link where I see it in a real program -- which typedoc, when generated, resolves fine https://github.com/connor4312/cosmonaut/blob/18a954f6184173fff999a4616b7cf8554addd966/src/baseModel.ts#L45

  2. orta commented on Jun 10, 2021

    @orta
    Contributor

    Cool, I agree that this should probably work, and should probably look through the whole known list of symbols in the current project (that's what I'd expect as a JS user, and I think it matches how the web version of this works).

    From a dive into this it looks like #41877 never made changes to the gotoDefinition service meaning that it will only work with identifiers in the current scope, and doesn't reach out to search the whole project for an identifier

    A failing fourslash test:

    ///<reference path="fourslash.ts" />
    
    // @Filename: foo.ts
    //// export interface [|/*def1*/Foo|] {
    ////     foo: string
    //// }
    
    // @Filename: importee.ts
    //// /** {@link /*otherFile*/[|Foo|] } not explicitly linked */
    //// const f = ""
    
    goTo.marker("otherFile");
    verify.goToDefinitionIs("def1");
  3. added
    BugA bug in TypeScript
    Effort: ModerateRequires experience with the TypeScript codebase, but feasible. Harder than "Effort: Casual".
    and removed
    Needs InvestigationThis issue needs a team member to investigate its status.
    on Jun 10, 2021
  4. orta commented on Jun 10, 2021

    @orta
    Contributor

    This had a bit of chatter about whether expanding the scope to all identifiers is the right call, so I'll wait till Nathan Shively-Sanders (@sandersn) is back to chat about that

    Otherwise, we'll make the un-used import as used if it's in the @link

  5. sandersn commented on Jun 15, 2021

    @sandersn
    Member

    After discussing this with Orta Therox (@orta), we switched our opinions. Now I think that expanding the scope to all identifiers is the right call and he doesn't. However: it would be difficult to decide on the precise semantics and difficult to implement efficiently, so I don't think it's worthwhile right now.

    What should work is import references: import('./b').Model. I don't think that @link even parses this reference right now.

    Edit: Matt Bierner (@mjbvz) beat me to it: #43950

  6. 7 remaining items

  7. nikelborm commented on Feb 12, 2026

    @nikelborm

    Facing the same problem. The only viable workaround for this problem I found is to do this:
    In files that export some logic and reference other packages inside @linkcode or @link JSDoc inline comments, I need to explicitly define types that would be referenced locally and export them

    export interface MakeAudioContextOptions extends AudioContextOptions {
      /**
       * Specifies the sink ID of the audio output device to use for the
       * {@linkcode EAudioContext}. This can take one of the following value types:
       *
       * - {@linkcode MediaBrand.AudioSinkId} retrieved for example from the
       *   {@linkcode EMediaDeviceInfo.deviceId|deviceId} of
       *   {@linkcode EMediaDeviceInfo} returned by {@linkcode enumerate}
       * - An object representing different options for a sink ID. Currently, this
       *   takes a single property, type, with a value of none. Setting this
       *   parameter causes the audio to be processed without being played through
       *   any audio output device.
       */
      readonly sinkId?: MediaBrand.AudioSinkId | AudioSinkOptions
    }

    Not working solutions

    Image

    import type { A } from 'package-a'

    If I import them like this, it doesn't work. Both these import entries get completely wiped from the file, when generating .d.ts

    import type { EMediaDeviceInfo } from 'effect-web-mediacapture-streams/EMediaDeviceInfo'
    import type { enumerate } from 'effect-web-mediacapture-streams/EMediaDevices'

    I feel like "importsNotUsedAsValues": "preserve", would have helped here, but it's deprecated 😞 . And compiler's recommendation to set "verbatimModuleSyntax": true, which is set already, doesn't help much.

    export type { A } from 'package-a'

    This syntax doesn't work as well. Although these entries get preserved in .d.ts, they don't play together with JSDoc,

    export type { EMediaDeviceInfo } from 'effect-web-mediacapture-streams/EMediaDeviceInfo';
    export type { enumerate } from 'effect-web-mediacapture-streams/EMediaDevices';

    Semi-working solutions

    Image

    export type A2 = import('package-a').A

    This syntax kinda works. A2 is the type you would actually reference in JSDoc links. Clicking on the link of the rendered JSDoc jumps to the local type declaration, and only then you can click go to definition again to jump to the package declaring the original type.

    export type EMediaDeviceInfo =
      import('effect-web-mediacapture-streams/EMediaDeviceInfo').EMediaDeviceInfo
    export type enumerate =
      typeof import('effect-web-mediacapture-streams/EMediaDevices').enumerate

    import type { A } from 'package-a'; export type _A = A

    This syntax works, but it's quite boilerplaty. In JSDoc you can reference the original name. And another problem I found with it is that clicking on the link in JSDoc jumps to .d.ts file instead of the .ts file lying near, path to which could be inferred by .d.ts.map. But this is the best option from the ones I found.

    import type { EMediaDeviceInfo } from 'effect-web-mediacapture-streams/EMediaDeviceInfo'
    import type { enumerate } from 'effect-web-mediacapture-streams/EMediaDevices'
    export type _1 = EMediaDeviceInfo
    export type _2 = typeof enumerate
  8. added a commit that references this issue on Apr 21, 2026
  9. RyanCavanaugh commented on Sep 7, 2026

    @RyanCavanaugh
    Member

    The imported {@link Model} already resolves to b.ts; the remaining problem was that noUnusedLocals reported TS6133 for the import needed by that link.

    This is fixed by PR #47822. 4.8.0-dev.20220606 still reports TS6133, while 4.8.0-dev.20220608, TypeScript 6.0.3, and current native TypeScript do not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugA bug in TypeScriptDomain: JSDocRelates to JSDoc parsing and type generationEffort: ModerateRequires experience with the TypeScript codebase, but feasible. Harder than "Effort: Casual".FixedA PR has been merged for this issueRescheduledThis issue was previously scheduled to an earlier milestone

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions