Skip to content

Conversation

@acutmore
Copy link
Collaborator

@acutmore acutmore commented Jan 27, 2025

This PR adds support for type-only/uninstantiated namespaces. i.e. namespaces that only use type and interface.

export class C {}

export namespace C {
  export type T = string;
}

Before: Error. only declare namespace ... was allowed ❌

Now: Ok. namespace declaration erased ✅


The following cases remain unchanged:

// Instantiated namespace errors
namespace A { export let x = 1 }                // error
namespace B { ; }                               // error

// Ambient namespace is erased
declare namespace C { export let x = 1 }        // ok erased
declare module    D { export let x = 1 }        // ok erased

// `module`-keyword errors
module E { export let x = 1 }                   // error
module F { export type x = number }             // error

Testing

Unit tests for both supported cases and unsupported cases (errors) have been added.

Context

This addition was motivated by --erasableSyntaxOnly and the recognition that in today's TypeScript, the only way to augment a class with types after the initial declaration is via namespaces. Whilst that use case can be solved using declare namespace that comes with a hazard. The combination of --erasableSyntaxOnly checks and transforming uninstantiated namespaces provides a safer option.

Thanks to @jakebailey for bringing this to our attention microsoft/TypeScript#61011 (comment)

Signed-off-by: Ashley Claymore <[email protected]>
@acutmore acutmore added the enhancement New feature or request label Jan 27, 2025
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
mkubilayk
mkubilayk previously approved these changes Jan 28, 2025
robpalme
robpalme previously approved these changes Jan 28, 2025
Co-authored-by: Rob Palmer <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
@acutmore acutmore dismissed stale reviews from robpalme and mkubilayk via 744aca8 January 28, 2025 19:55
@acutmore acutmore merged commit 4eeb96b into main Jan 29, 2025
2 checks passed
@acutmore acutmore deleted the namespaces branch January 29, 2025 10:30
}

namespace With.Imports {
import Types = My.Internal.Types;
Copy link

@magic-akari magic-akari Feb 1, 2025

Choose a reason for hiding this comment

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

Our one-pass design (SWC), while optimal for speed, inherently defers complex symbol resolution — a trade-off that aligns with tsc's isolatedModules for explicit import type syntax in ESM interactions.

However, this architecture faces new challenges as we now support namespaces. Unlike ESM's enforced type/value distinction, namespace imports/exports (e.g., My.Internal.Types) require non-trivial static analysis to resolve ambiguities:

  • Prior Approach: Namespaces were ignored, sidestepping the need for deep resolution.
  • Current Need: Determining if Types is a value or type within namespaces demands preliminary metadata collection, contradicting our single-scan constraint.

Adopting TypeScript syntax that mirrors ESM's explicitness — such as:

import type Types = My.Internal.Types;  // namespace type binding

would extend the same design principle to namespaces. By declaring intent upfront, the compiler could resolve symbols in a single pass without heuristic fallbacks, bridging the gap between ESM rigor and namespace flexibility.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Opened #38 to add more test cases.

acutmore added a commit that referenced this pull request Feb 12, 2025
## Context

microsoft/TypeScript#51825
nodejs/amaro#168

## Description 

This PR is a *breaking change*.
Continuing on from #32 but instead of allowing _more_
`ModuleDeclaration` AST nodes, this PR removes support for erasing
namespace declarations that use the deprecated `module` keyword.

### Still erased (no error emitted)

```ts
declare namespace N1 {}
namespace N2 {}
declare global {}
declare module "./path" {}
```

### No longer erased (triggers `onError` callback)

```ts
declare module M1 {}
module M2 {}
```

---------

Signed-off-by: Ashley Claymore <[email protected]>
Co-authored-by: Rob Palmer <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants