Skip to content

Prevent deduplication of imports with same from and name, but different type #506

@8ctavio

Description

@8ctavio

Describe the feature

When "resolving" imports to format them, they are first deduped with the dedupeImports function. Currently, the main criteria to decide whether to dedupe are the name and from properties of an Import. However, with TypeScript, it is possible and valid to have two exports in the same module with the same identifier if one is for the value and other for the type, which is what enables using class identifiers as both values (new SomeClass()) and types ((p: SomeClass) => void). Therefore, the following imports should not be deduped to account for this use case:

[{
  name: identifier,
  from: specifier
},{
  name: identifier,
  from: specifier,
  type: true
}]

Example

Here is a demo using generateTypeDeclarations with an imports array like the one shown above. To test run node index.js. Due to deduplication, the output depends on the order of the imports. Possible outputs are:

export {}
declare global {
  const Test: typeof import('./module').Test
}
export {}
declare global {

}
// for type re-export
declare global {
  // @ts-ignore
  export type { Test } from './module'
  import('./module')
}

If these are used to create d.ts files, the Test identifier will only be usable as either a value (first case) or a type (second case), but not both.

Workaround

Imports support a declarationType: 'class' property, which could be used to obtain the desired output. However, this property does not seem designed to solve this particular issue; using declarationType: 'class' simply exempts an import from being deduplicated, so both import entries are still required. Two additional issues with this approach are:

  • (truly) duplicate imports with declarationType: 'class' are not deduplicated.
  • it does not work well with inline presets, where a list (array) of export names is provided.

More motivation

Exported values and types with the same identifiers are not exclusive to the export class syntax. For instance, in TypeScript a class' type can be customized with the following pattern:

class TestImpl {}
interface TestConstructor {}
interface TestInstance {}

export type Test = TestInstance
export const Test: TestConstructor = TestImpl as any

Therefore, I think the most reliable solution would be for the dedupeImports function to support not deduping imports that are "complementary", i.e., same module and identifier (from and name), but different scope (type).


I am planning to submit a PR refactoring dedupeImports to address this.

Additional information

  • Would you be willing to help implement this feature?

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions