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
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/native-preview/src/ast/ast.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1265,6 +1265,7 @@ export interface ModuleDeclaration extends DeclarationBase, StatementBase, Modif
readonly kind: SyntaxKind.ModuleDeclaration;
readonly keyword: SyntaxKind.ModuleKeyword | SyntaxKind.NamespaceKeyword;
readonly name: ModuleName;
readonly attributes?: ImportAttributes;
readonly body?: ModuleBody;
}
export interface ImportEqualsDeclaration extends DeclarationBase, StatementBase, ModifiersBase {
Expand Down
10 changes: 6 additions & 4 deletions _packages/native-preview/src/ast/factory.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1053,7 +1053,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 @@ -1583,6 +1583,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 @@ -2933,11 +2934,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 @@ -3704,8 +3706,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/native-preview/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 _scripts/ast.json
Original file line number Diff line number Diff line change
Expand Up @@ -4800,6 +4800,11 @@
"type": "ModuleName",
"private": true
},
{
"name": "Attributes",
"type": "ImportAttributes",
"optional": true
},
{
"name": "Body",
"inherited": true,
Expand Down
5 changes: 3 additions & 2 deletions 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 internal/api/encoder/encoder_generated.go

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

11 changes: 11 additions & 0 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -2378,6 +2378,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 @@ -2519,6 +2529,7 @@ type SourceFile struct {
SymbolCount int
ClassifiableNames collections.Set[string]
PatternAmbientModules []*PatternAmbientModule
AttributedAmbientModules []*AttributedAmbientModule
GlobalExports SymbolTable

// Fields set by ECMALineMap
Expand Down
23 changes: 14 additions & 9 deletions 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 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 @@ -1932,6 +1933,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()
}
Comment thread
bartlomieju marked this conversation as resolved.
}
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 @@ -3506,6 +3539,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 internal/binder/binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,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 @@ -790,6 +796,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})
} else if pattern.StarIndex >= 0 {
b.file.PatternAmbientModules = append(b.file.PatternAmbientModules, &ast.PatternAmbientModule{Pattern: pattern, Symbol: symbol})
}
Expand Down
Loading