Skip to content

Conversation

fasttime
Copy link
Member

@fasttime fasttime commented Oct 7, 2025

Prerequisites checklist

What is the purpose of this pull request?

This pull request, together with eslint/eslint#20201 in the main repo, fixes issue #283 by replacing import() syntax inside JSDoc @typedef tags with local module aliases in the bundled .js output.

Before:

/** @typedef {import("@eslint/core").ConfigObject} Config */
/** @typedef {import("@eslint/core").LegacyConfigObject} LegacyConfig */

After

/** @import * as _eslintcore from "@eslint/core"; */
/** @typedef {_eslintcore.ConfigObject} Config */
/** @typedef {_eslintcore.LegacyConfigObject} LegacyConfig */

This has the effect of creating a type import (import type *) in the generated .d.ts files:

Before:

export type Config = import("@eslint/core").ConfigObject;
export type LegacyConfig = import("@eslint/core").LegacyConfigObject;

After

export type Config = _eslintcore.ConfigObject;
export type LegacyConfig = _eslintcore.LegacyConfigObject;
import type * as _eslintcore from "@eslint/core";

The type import doesn't change the exported types, but it overcomes a limitation of TypeScript when resolving types across symlinked directories. See also microsoft/TypeScript#62558.

The current change only affects packages built using the dedupe-types tools:

  • config-array
  • config-helpers
  • plugin-kit

Anyway, these are not the only packages impacted by the TypeScript issue. compat is widely used and it is also impacted:

StackBlitz demo

This could be fixed in a follow-up pull request if necessary.

What changes did you make? (Give an overview)

  • Added tests for pnpm type support.
  • Updated dedupe-types and build-cts tools to match the new format.
  • Updated new package template.

Related Issues

fixes #283

Is there anything you'd like reviewers to focus on?

@github-project-automation github-project-automation bot moved this to Needs Triage in Triage Oct 7, 2025
@fasttime fasttime changed the title test: add pnpm type support test fix: improve type support for isolated dependencies in pnpm Oct 8, 2025
@eslint-github-bot eslint-github-bot bot added the bug Something isn't working label Oct 8, 2025
"extends": "./tsconfig.json",
"files": ["dist/esm/index.js"],
"compilerOptions": {
"allowImportingTsExtensions": true,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allowImportingTsExtensions is required for TypeScript to accept import statements from "./types.ts" in the generated dist/esm/index.js file:

/** @import * as _typests from "./types.ts"; */

Another possibility to avoid allowImportingTsExtensions is importing from "./types.js" instead. This will work if a dummy file dist/esm/types.js is added to the package, similarly to what is done in eslint/markdown:

https://app.unpkg.com/@eslint/markdown@7.4.0/files/dist/esm/types.js

@nzakas
Copy link
Member

nzakas commented Oct 9, 2025

Is there a reason we can't do this?

/** @import { ConfigObject as Config, LegacyConfigObject as LegacyConfig ) from "@eslint/core" */

@nzakas nzakas moved this from Needs Triage to Implementing in Triage Oct 9, 2025
@fasttime
Copy link
Member Author

Is there a reason we can't do this?

/** @import { ConfigObject as Config, LegacyConfigObject as LegacyConfig ) from "@eslint/core" */

The problem is that @import tags only generate import statements, not the corresponding exports. That is why @typedef is needed.

For example, this JSDoc comment:

/** @import { ConfigObject as Config, LegacyConfigObject as LegacyConfig, Plugin, RuleConfig } from "@eslint/core"; */

Compiles into:

import type { ConfigObject as Config } from "@eslint/core";

In this case, TypeScript even recognizes that LegacyConfig, Plugin and RuleConfig are unused and prunes them from the generated types.

const newSourceText = oldSourceText.replaceAll(
'import("./types.ts")',
'import("./types.cts")',
' from "./types.ts";\n',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just double-checking -- are we sure there are no files that use import()?

'import("./types.ts")',
'import("./types.cts")',
' from "./types.ts";\n',
' from "./types.cts";\n',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just double-checking: Are we sure there aren't any files that are still using import()?

// we haven't seen this typedef before, so process it
typedefs.add(line);
const { start, importSource, end } = match.groups;
const importName = `_${importSource.replace(/\W/gu, "")}`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of using _ as a signifier for this purpose. In most languages, this indicates that an identifier is not used, which is not the case here. Could we use $ instead?

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

Labels

bug Something isn't working chore

Projects

Status: Implementing

2 participants