Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Non-exported classes in type declaration files leaking value names #32182

Open
ericdrobinson opened this issue Jun 29, 2019 · 3 comments
Open
Labels
Docs The issue relates to how you learn TypeScript

Comments

@ericdrobinson
Copy link

ericdrobinson commented Jun 29, 2019

TypeScript Version: 3.5.2

Search Terms: ambient module declaration export declare class type name

Code

In a file called classes.d.ts:

declare class A {}
export declare class B extends A {}

In a file called test.js:

let a = require('classes').A;

In a file called test.ts:

import { A } from './types/classes';

[NEW] Assumptions:
The following is a list of assumptions I had when originally opening this issue:

  1. Type Declaration Files work like modules: once you use import or export [on a top-level declaration], only explicitly exported declarations are visible externally.
  2. Type names declared in a Declaration File are always accessible via import types (at least those that are used by exported types.
    • E.g. export class B extends A exports the type names A and B, even if A was not directly exported.
  3. Value names declared in a module-style Declaration File (see #1 above) are only accessible if explicitly exported.

These assumptions are the result of reading the documentation and working with declaration files. Note that the documentation does not mention:

  1. All declarations in a declaration file are implicitly exported.
  2. Special [and undocumented?] export {}; syntax causes only explicitly exported declarations to be available by consumers of the declaration file.

I list them here to provide context for the Expected Behavior section.

Expected behavior:
In both cases , an Error that name 'A' could be found in module 'classes'.

I expect in this case that TypeScript is capable of resolving the following from the declaration file:

  1. The Value "B".
  2. The Types "A" and "B".

In other words, I should be able to use import types to resolve class A, but attempts to use them should fail.

Actual behavior:
No compiler error in either case. TypeScript-powered IDEs (e.g. VSCode) happily show that the full non-exported class A is available.

In short, a non-exported, declared class should resolve in the same way as an interface.

As things stand today, TypeScript erroneously resolves the Value "A".

Playground Link: NA

Related Issues: NA

@ericdrobinson
Copy link
Author

Without support for this it is extremely difficult to model a module that has a private base class with many public subclasses.

The following is a snippet of JavaScript:

//                           ↓ OK!
/** @type {import('classes').A} */
let x;

//                                      ↓ ERROR!
let y = x instanceof require("classes").A;

The import type currently works as expected. 👍
The instanceof line beneath it does not report the expected error. 👎

@ajafff
Copy link
Contributor

ajafff commented Jun 30, 2019

In a declaration file everything is implicitly exported. You can prevent that by adding export{};.
With that change you can also no longer use A as type. To fix that you need to export it as interface.

@ericdrobinson
Copy link
Author

In a declaration file everything is implicitly exported.

Is that documented anywhere? That should really be documented.

You can prevent that by adding export{};.

This should also be documented. Does this syntax have a name?

With that change you can also no longer use A as type. To fix that you need to export it as interface.

@ajafff What do you mean that you can "export it as interface"?

I see three options:

  1. Convert the base class into an interface.
  2. Rename the A class to AClass and export a type: export type A = AClass;.
  3. Rename the A class to AClass and export an interface: export interface A extends AClass {}.

Here are the issues with those options:

  1. Converting to a class is undesirable as it requires adding all properties to classes that implement the type. For a base class with 24 documented properties, this is untenable.
  2. While using a type alias results in IntelliSense autocomplete [in VSCode] correctly showing the type as a class, the type shown on hover reads as the resolved AClass. Which is not the desirable information to convey.
  3. IntelliSense autocomplete [in VSCode] shows the type as an interface rather than a class.

In my opinion, the third option is the best of the bunch.


Is there really no way to [meaning-full-y] export the type of a class rather than both its type and value?

@ericdrobinson ericdrobinson changed the title Non-exported classes in type declaration files leaking names Non-exported classes in type declaration files leaking value names Jul 1, 2019
@RyanCavanaugh RyanCavanaugh added the Docs The issue relates to how you learn TypeScript label Jul 2, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Docs The issue relates to how you learn TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants