Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/typescript/src/api/node/protocol.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export const childProperties: Readonly<Partial<Record<SyntaxKind, readonly (stri
[SyntaxKind.JSDocSignature]: ["typeParameters", "parameters", "type"],
[SyntaxKind.JSDocNameReference]: ["name"],
[SyntaxKind.SourceFile]: ["statements", "endOfFileToken"],
[SyntaxKind.ModuleDeclaration]: ["modifiers", "name", "body"],
[SyntaxKind.ModuleDeclaration]: ["modifiers", "name", "attributes", "body"],
[SyntaxKind.ImportEqualsDeclaration]: ["modifiers", "name", "moduleReference"],
[SyntaxKind.ExportDeclaration]: ["modifiers", "exportClause", "moduleSpecifier", "attributes"],
[SyntaxKind.ImportType]: ["argument", "attributes", "qualifier", "typeArguments"],
Expand Down
1 change: 1 addition & 0 deletions packages/typescript/src/ast/ast.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1268,6 +1268,7 @@ export interface ModuleDeclaration extends StatementBase, DeclarationBase, Modif
readonly kind: SyntaxKind.ModuleDeclaration;
readonly keyword: SyntaxKind.ModuleKeyword | SyntaxKind.NamespaceKeyword;
readonly name: ModuleName;
readonly attributes?: ImportAttributes;
readonly body?: ModuleBody;
}
export interface ImportEqualsDeclaration extends StatementBase, DeclarationBase, ModifiersBase {
Expand Down
10 changes: 6 additions & 4 deletions packages/typescript/src/ast/factory.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1075,7 +1075,7 @@ function cloneNodeData(node: Node): any {
case SyntaxKind.JSDocNameReference:
return { name: n.name };
case SyntaxKind.ModuleDeclaration:
return { modifiers: n.modifiers, keyword: n.keyword, name: n.name, body: n.body };
return { modifiers: n.modifiers, keyword: n.keyword, name: n.name, attributes: n.attributes, body: n.body };
case SyntaxKind.ImportEqualsDeclaration:
return { modifiers: n.modifiers, isTypeOnly: n.isTypeOnly, name: n.name, moduleReference: n.moduleReference };
case SyntaxKind.ExportDeclaration:
Expand Down Expand Up @@ -1605,6 +1605,7 @@ const forEachChildTable: Record<number, ForEachChildFunction> = {
[SyntaxKind.ModuleDeclaration]: (data, cbNode, cbNodes) =>
visitNodes(cbNode, cbNodes, data.modifiers) ||
visitNode(cbNode, data.name) ||
visitNode(cbNode, data.attributes) ||
visitNode(cbNode, data.body),
[SyntaxKind.ImportEqualsDeclaration]: (data, cbNode, cbNodes) =>
visitNodes(cbNode, cbNodes, data.modifiers) ||
Expand Down Expand Up @@ -2955,11 +2956,12 @@ export function createJSDocNameReference(name: EntityName): JSDocNameReference {
}) as unknown as JSDocNameReference;
}

export function createModuleDeclaration(modifiers: readonly ModifierLike[] | undefined, keyword: SyntaxKind.ModuleKeyword | SyntaxKind.NamespaceKeyword, name: ModuleName, body?: ModuleBody): ModuleDeclaration {
export function createModuleDeclaration(modifiers: readonly ModifierLike[] | undefined, keyword: SyntaxKind.ModuleKeyword | SyntaxKind.NamespaceKeyword, name: ModuleName, attributes?: ImportAttributes, body?: ModuleBody): ModuleDeclaration {
return new NodeObject(SyntaxKind.ModuleDeclaration, {
modifiers: modifiers ? createNodeArray(modifiers) : undefined,
keyword,
name,
attributes,
body,
}) as unknown as ModuleDeclaration;
}
Expand Down Expand Up @@ -3726,8 +3728,8 @@ export function updateJSDocNameReference(node: JSDocNameReference, name: EntityN
return node.name !== name ? createJSDocNameReference(name) : node;
}

export function updateModuleDeclaration(node: ModuleDeclaration, modifiers: readonly ModifierLike[] | undefined, name: ModuleName, body?: ModuleBody): ModuleDeclaration {
return node.modifiers !== modifiers || node.name !== name || node.body !== body ? createModuleDeclaration(modifiers, node.keyword, name, body) : node;
export function updateModuleDeclaration(node: ModuleDeclaration, modifiers: readonly ModifierLike[] | undefined, name: ModuleName, attributes?: ImportAttributes, body?: ModuleBody): ModuleDeclaration {
return node.modifiers !== modifiers || node.name !== name || node.attributes !== attributes || node.body !== body ? createModuleDeclaration(modifiers, node.keyword, name, attributes, body) : node;
}

export function updateImportEqualsDeclaration(node: ImportEqualsDeclaration, modifiers: readonly ModifierLike[] | undefined, name: Identifier, moduleReference: ModuleReference): ImportEqualsDeclaration {
Expand Down
3 changes: 2 additions & 1 deletion packages/typescript/src/ast/visitor.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1342,8 +1342,9 @@ const visitEachChildTable: Record<number, VisitEachChildFunction> = {
[SyntaxKind.ModuleDeclaration]: (node: ModuleDeclaration, visitor: Visitor): ModuleDeclaration => {
const _modifiers = visitNodes(node.modifiers, visitor);
const _name = visitNode(node.name, visitor, isModuleName);
const _attributes = visitNode(node.attributes, visitor, isImportAttributes);
const _body = visitNode(node.body, visitor, isModuleBody);
return updateModuleDeclaration(node, _modifiers, _name, _body);
return updateModuleDeclaration(node, _modifiers, _name, _attributes, _body);
},
[SyntaxKind.ImportEqualsDeclaration]: (node: ImportEqualsDeclaration, visitor: Visitor): ImportEqualsDeclaration => {
const _modifiers = visitNodes(node.modifiers, visitor);
Expand Down
5 changes: 5 additions & 0 deletions tools/scripts/tsc/ast.json
Original file line number Diff line number Diff line change
Expand Up @@ -4807,6 +4807,11 @@
"type": "ModuleName",
"private": true
},
{
"name": "Attributes",
"type": "ImportAttributes",
"optional": true
},
{
"name": "Body",
"inherited": true,
Expand Down
5 changes: 3 additions & 2 deletions tsc/internal/api/encoder/decoder_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tsc/internal/api/encoder/encoder_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 17 additions & 6 deletions tsc/internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -2373,6 +2373,16 @@ type PatternAmbientModule struct {
Symbol *Symbol
}

// AttributedAmbientModule records an ambient module declaration keyed on import
// attributes, e.g. `declare module "*" with { type: "text" } { ... }`
// (proposal: microsoft/TypeScript#46135). Unlike a plain ambient/pattern module,
// it is only consulted when an import specifies matching attributes.
type AttributedAmbientModule struct {
Pattern core.Pattern
Attributes *Node // ImportAttributes node from the `with { ... }` clause
Symbol *Symbol
}

type CommentDirectiveKind int32

const (
Expand Down Expand Up @@ -2507,12 +2517,13 @@ type SourceFile struct {

// Fields set by binder

isBound atomic.Bool
bindOnce sync.Once
bindDiagnostics []*Diagnostic
SymbolCount int
PatternAmbientModules []*PatternAmbientModule
GlobalExports SymbolTable
isBound atomic.Bool
bindOnce sync.Once
bindDiagnostics []*Diagnostic
SymbolCount int
PatternAmbientModules []*PatternAmbientModule
AttributedAmbientModules []*AttributedAmbientModule
GlobalExports SymbolTable

// Fields set by ECMALineMap

Expand Down
23 changes: 14 additions & 9 deletions tsc/internal/ast/ast_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions tsc/internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ast
import (
"fmt"
"slices"
"strconv"
"strings"
"sync"
"sync/atomic"
Expand Down Expand Up @@ -1951,6 +1952,38 @@ func GetImportAttributes(node *Node) *Node {
panic("Unhandled case in getImportAttributes")
}

// ImportAttributesToMap collects the string-valued attributes of an
// `ImportAttributes` node (the contents of a `with { ... }` clause, or the
// deprecated `assert { ... }` form) into a name->value map. Non-string values
// are skipped.
func ImportAttributesToMap(attributes *Node) map[string]string {
nodes := attributes.AsImportAttributes().Attributes.Nodes
result := make(map[string]string, len(nodes))
for _, attr := range nodes {
value := attr.AsImportAttribute().Value
if IsStringLiteralLike(value) {
result[attr.Name().Text()] = value.Text()
}
}
return result
}

// ImportAttributesKey returns a stable, order-independent string key for the
// string-valued attributes of an `ImportAttributes` node. Used to give
// attribute-keyed ambient modules distinct symbol names so they neither merge
// with nor shadow the plain ambient module of the same name. Names and values are
// quoted so that distinct attribute sets cannot collide into one key (e.g.
// `{ a: "b,c=d" }` vs `{ a: "b", c: "d" }`).
func ImportAttributesKey(attributes *Node) string {
m := ImportAttributesToMap(attributes)
pairs := make([]string, 0, len(m))
for name, value := range m {
pairs = append(pairs, strconv.Quote(name)+":"+strconv.Quote(value))
}
slices.Sort(pairs)
return "{" + strings.Join(pairs, ",") + "}"
}

func getImportTypeNodeLiteral(node *Node) *Node {
if IsImportTypeNode(node) {
importTypeNode := node.AsImportTypeNode()
Expand Down Expand Up @@ -3521,6 +3554,7 @@ func ReplaceModifiers(factory *NodeFactory, node *Node, modifierArray *ModifierL
modifierArray,
node.AsModuleDeclaration().Keyword,
node.Name(),
node.AsModuleDeclaration().Attributes,
node.Body(),
)
case KindImportEqualsDeclaration:
Expand Down
12 changes: 12 additions & 0 deletions tsc/internal/binder/binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,12 @@ func (b *Binder) getDeclarationName(node *ast.Node) string {
if ast.IsGlobalScopeAugmentation(node) {
return ast.InternalSymbolNameGlobal
}
// An ambient module keyed on import attributes (microsoft/TypeScript#46135)
// gets a distinct name so it does not merge with, or shadow, the plain
// ambient module of the same specifier (e.g. `declare module "*"`).
if attributes := node.AsModuleDeclaration().Attributes; attributes != nil {
return "\"" + moduleName + "\" with " + ast.ImportAttributesKey(attributes)
}
return "\"" + moduleName + "\""
}
if ast.IsPrivateIdentifier(name) {
Expand Down Expand Up @@ -785,6 +791,12 @@ func (b *Binder) bindModuleDeclaration(node *ast.Node) {
if !pattern.IsValid() {
// An invalid pattern - must have multiple wildcards.
b.errorOnFirstToken(name, diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, name.Text())
} else if attributes := node.AsModuleDeclaration().Attributes; attributes != nil {
// An ambient module keyed on import attributes (microsoft/TypeScript#46135).
// It is recorded separately and only consulted when an import specifies
// matching attributes, so it neither participates in normal pattern/ambient
// resolution nor shadows a plain `declare module` of the same specifier.
b.file.AttributedAmbientModules = append(b.file.AttributedAmbientModules, &ast.AttributedAmbientModule{Pattern: pattern, Attributes: attributes, Symbol: symbol})
Comment on lines +794 to +799
} else if pattern.StarIndex >= 0 {
b.file.PatternAmbientModules = append(b.file.PatternAmbientModules, &ast.PatternAmbientModule{Pattern: pattern, Symbol: symbol})
}
Expand Down
Loading