microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.71k stars 12.45k forks source link

Emmited Type Declarations generate empty export #51338

Open cristobal opened 1 year ago

cristobal commented 1 year ago

Bug Report

When generating type declaration files if a type that is only used inside the typescript code however it is not exported results in an empty export at the end of the file e.g. export {};

πŸ”Ž Search Terms

πŸ•— Version & Regression Information

When i started inspecting the generated typescript declarations generated. I see the same error/behaviour when either typescript@4.8 and typescript@next

This is the behavior in every version I tried, and I reviewed the FAQ for entries about emmited code and private fields and methods.

⏯ Playground Link

πŸ’» Code

Empty Export Code

type TaskType = 'ONE' | 'TWO' | 'THREE';

// would not expected to have an empty export 
// in the emitted type declaration code here is this correct?
export interface Task {
  executeTask(type:TaskType):void;
}

No Empty Export

// no empty export since all types and interfaces exported
export type TaskType = 'ONE' | 'TWO' | 'THREE';
export interface Task {
  executeTask(type:TaskType):void;
}

πŸ™ Actual behavior

Empty export for some reason…

πŸ™‚ Expected behavior

No default empty export.

MartinJohns commented 1 year ago

This is intentional. Using export or import marks the file as a module. To keep this module semantics, the empty export statement is generated.

cristobal commented 1 year ago

This is intentional. Using export or import marks the file as a module. To keep this module semantics, the empty export statement is generated.

I see i think it would be nice to have things like this documented. Should this go in a FAQ or is there someplace one can update the docs?

MartinJohns commented 1 year ago

The first part is documented: https://www.typescriptlang.org/docs/handbook/2/modules.html#how-javascript-modules-are-defined

The second part is not explicit, but can be assumed. You wouldn't want your file semantics to change. There are many issues about this, which act as documentation as well.

I guess you could send a PR mentioning the explicit export in absence of any value imports/exports, I doubt the TypeScript team would oppose this.

cristobal commented 1 year ago

The first part is documented: https://www.typescriptlang.org/docs/handbook/2/modules.html#how-javascript-modules-are-defined

The second part is not explicit, but can be assumed. You wouldn't want your file semantics to change. There are many issues about this, which act as documentation as well.

I guess you could send a PR mentioning the explicit export in absence of any value imports/exports, I doubt the TypeScript team would oppose this.

Will do that then.

cristobal commented 1 year ago

This is intentional. Using export or import marks the file as a module. To keep this module semantics, the empty export statement is generated.

@MartinJohns This might be a stupid question, but If this is intentional and the expected behaviour is to always generate an empty export. Why is there then no empty export in the example where both the type and interface is exported? Would expect consistency here or am i misunderstanding something?

RyanCavanaugh commented 1 year ago

The file needs at least one export or import to be a module. If there already is one, an additional empty one is not needed.

I don't think the documentation needs to justify every behavior needed for correctness. Correctness is an assumed facet of operation.

cristobal commented 1 year ago

The file needs at least one export or import to be a module. If there already is one, an additional empty one is not needed.

I don't think the documentation needs to justify every behavior needed for correctness. Correctness is an assumed facet of operation.

That was my assumption that there should be not have been any empty export {} in the generated declaration file since there is already at least on export defined to make it a module.

Seems like a side effect when generating declaration fille, perhaps a bug then? After digging into some here it seems that whenever one references a type, interface or a const value that is not exported the type declaration will include an empty export {}; at the end of the file.

Not exported reference results in empty export example e.g.:

Screenshot 2022-10-28 at 20 12 32

Playground link here

No reference used that is not exported no empty export:

Screenshot 2022-10-28 at 20 16 01

Playground link here

RyanCavanaugh commented 1 year ago

Cool, thanks for clarifying -- the additional export { } indeed shouldn't be there.

kamalesh0406 commented 1 year ago

Hi @RyanCavanaugh, I would like to look into fixing this issue. Is there anyone working on it? Also, it would take me a week or so to do it since I am new to the codebase would that be fine?

Edit: Sorry, I just found out from the CONTRIBUTING.md that we should not comment about working on an issue. I will not do it next time!

weswigham commented 4 months ago

I mentioned this in the linked PR, but that export {} doesn't just ensure module-ness, it also enables private scoping of locals. Without an export declaration in the file, all locals are implciitly exported, even if they don't have an export modifier. So

type A = 1;
export interface B {}

in a declaration file actually exports A, while

type A = 1;
export interface B {}
export {}

does not. Declaration files have worked like this (and relied on this) since about the dawn of declaration files as a format, and is actually the underlying behavior that needs to change to make removing the export {} marker safe. It will, however, be a breaking change for our oldest handwritten (and pre-transform-based-declaration-emitter) declaration files.