From b647be82c96302c1583c121332eb0a762288475c Mon Sep 17 00:00:00 2001 From: cjc0013 Date: Thu, 28 May 2026 00:14:39 -0400 Subject: [PATCH] Fix JS bind expando declaration emit --- .../transformers/declarations/transform.go | 224 +++++++++++++++--- .../declarationEmitJsBindExpandoFunction.js | 66 ++++++ ...clarationEmitJsBindExpandoFunction.symbols | 81 +++++++ ...declarationEmitJsBindExpandoFunction.types | 104 ++++++++ .../declarationEmitJsBindExpandoFunction.ts | 32 +++ 5 files changed, 479 insertions(+), 28 deletions(-) create mode 100644 testdata/baselines/reference/compiler/declarationEmitJsBindExpandoFunction.js create mode 100644 testdata/baselines/reference/compiler/declarationEmitJsBindExpandoFunction.symbols create mode 100644 testdata/baselines/reference/compiler/declarationEmitJsBindExpandoFunction.types create mode 100644 testdata/tests/cases/compiler/declarationEmitJsBindExpandoFunction.ts diff --git a/internal/transformers/declarations/transform.go b/internal/transformers/declarations/transform.go index 16d558fecab..8f83e58df8b 100644 --- a/internal/transformers/declarations/transform.go +++ b/internal/transformers/declarations/transform.go @@ -1785,31 +1785,58 @@ func (tx *DeclarationTransformer) transformVariableStatement(input *ast.Variable extraImports, _ = tx.Visitor().VisitSlice(imports) } - nodes, _ := tx.Visitor().VisitSlice(inputNodes) - if len(nodes) == 0 { - if len(extraImports) > 0 { - return tx.Factory().NewSyntaxList(extraImports) + var nodes []*ast.Node + var statements []*ast.Node + for _, n := range inputNodes { + if target := tx.getBoundFunctionTargetDeclaration(n); target != nil { + statements = append(statements, tx.createBoundFunctionDeclaration( + n, + tx.ensureModifiers(input.AsNode()), + n.Name(), + target, + )) + statements = append(statements, tx.transformBoundFunctionSourceProperties(n, target)...) + continue + } + + visited := tx.Visitor().Visit(n) + switch { + case visited == nil: + continue + case visited.Kind == ast.KindSyntaxList: + nodes = append(nodes, visited.AsSyntaxList().Children...) + default: + nodes = append(nodes, visited) } - return nil } - nodeList := tx.Factory().NewNodeList(nodes) - modifiers := tx.ensureModifiers(input.AsNode()) + if len(nodes) > 0 { + nodeList := tx.Factory().NewNodeList(nodes) + modifiers := tx.ensureModifiers(input.AsNode()) - var declList *ast.Node - if ast.IsVarUsing(input.DeclarationList) || ast.IsVarAwaitUsing(input.DeclarationList) { - declList = tx.Factory().NewVariableDeclarationList(nodeList, ast.NodeFlagsConst) - tx.EmitContext().SetOriginal(declList, input.DeclarationList) - tx.EmitContext().SetCommentRange(declList, input.DeclarationList.Loc) - declList.Loc = input.DeclarationList.Loc - } else { - declList = tx.Factory().UpdateVariableDeclarationList(input.DeclarationList.AsVariableDeclarationList(), nodeList, input.DeclarationList.Flags) + var declList *ast.Node + if ast.IsVarUsing(input.DeclarationList) || ast.IsVarAwaitUsing(input.DeclarationList) { + declList = tx.Factory().NewVariableDeclarationList(nodeList, ast.NodeFlagsConst) + tx.EmitContext().SetOriginal(declList, input.DeclarationList) + tx.EmitContext().SetCommentRange(declList, input.DeclarationList.Loc) + declList.Loc = input.DeclarationList.Loc + } else { + declList = tx.Factory().UpdateVariableDeclarationList(input.DeclarationList.AsVariableDeclarationList(), nodeList, input.DeclarationList.Flags) + } + statements = append(statements, tx.Factory().UpdateVariableStatement(input, modifiers, declList)) } - res := tx.Factory().UpdateVariableStatement(input, modifiers, declList) + if len(extraImports) > 0 { - return tx.Factory().NewSyntaxList(append(extraImports, res)) + statements = append(extraImports, statements...) + } + switch len(statements) { + case 0: + return nil + case 1: + return statements[0] + default: + return tx.Factory().NewSyntaxList(statements) } - return res } func (tx *DeclarationTransformer) transformEnumDeclaration(input *ast.EnumDeclaration) *ast.Node { @@ -2226,11 +2253,6 @@ func (tx *DeclarationTransformer) visitExpressionStatement(node *ast.Node) *ast. func (tx *DeclarationTransformer) transformExpandoAssignment(node *ast.BinaryExpression) *ast.Node { left := node.Left - symbol := node.Symbol - if symbol == nil || symbol.Flags&ast.SymbolFlagsAssignment == 0 { - return nil - } - ns := ast.GetLeftmostAccessExpression(left) if ns == nil || ns.Kind != ast.KindIdentifier { return nil @@ -2245,11 +2267,17 @@ func (tx *DeclarationTransformer) transformExpandoAssignment(node *ast.BinaryExp return nil } + boundFunctionTarget := tx.getBoundFunctionTargetDeclaration(declaration) + symbol := node.Symbol + if (symbol == nil || symbol.Flags&ast.SymbolFlagsAssignment == 0) && boundFunctionTarget == nil { + return nil + } + if ast.IsFunctionDeclaration(declaration) && declaration.FunctionLikeData().FullSignature != nil { return nil } - if ast.IsVariableDeclaration(declaration) && !ast.IsFunctionLike(declaration.Initializer()) { + if ast.IsVariableDeclaration(declaration) && !ast.IsFunctionLike(declaration.Initializer()) && boundFunctionTarget == nil { return nil // We're going to add a type, no need to dupe members with a namespace } @@ -2268,14 +2296,65 @@ func (tx *DeclarationTransformer) transformExpandoAssignment(node *ast.BinaryExp return nil } - tx.transformExpandoHost(name, declaration) + if boundFunctionTarget == nil { + tx.transformExpandoHost(name, declaration) + } if ast.IsFunctionDeclaration(declaration) && !shouldEmitFunctionProperties(declaration.AsFunctionDeclaration()) { return nil } + typeExpression := left + if boundFunctionTarget != nil { + typeExpression = node.Right + } + return tx.transformExpandoProperty(name, host, declaration, property, node.AsNode(), func(synthesizedNamespace *ast.Node) *ast.Node { + return tx.resolver.CreateTypeOfExpression(tx.EmitContext(), typeExpression, synthesizedNamespace, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags|nodebuilder.InternalFlagsNoSyntacticPrinter, tx.tracker) + }) +} + +func (tx *DeclarationTransformer) transformBoundFunctionSourceProperties(declaration *ast.Node, target *ast.Node) []*ast.Node { + host := declaration.Symbol() + if host == nil { + return nil + } + if ast.IsFunctionDeclaration(target) && !shouldEmitFunctionProperties(target.AsFunctionDeclaration()) { + return nil + } + + props := tx.resolver.GetPropertiesOfContainerFunction(target) + if len(props) == 0 { + return nil + } + + var results []*ast.Node + for _, prop := range props { + if prop.ValueDeclaration == nil || !ast.IsExpandoPropertyDeclaration(prop.ValueDeclaration) { + continue + } + property := prop.Name + if property == "" || !scanner.IsIdentifierText(property, core.LanguageVariantStandard) { + continue + } + if tx.hasOwnExpandoAssignment(declaration.Name().Text(), property) { + continue + } + if ns := tx.transformExpandoPropertyDeclaration(tx.Factory().NewIdentifier(declaration.Name().Text()), host, declaration, property, prop.ValueDeclaration); ns != nil { + results = append(results, ns) + } + } + return results +} + +func (tx *DeclarationTransformer) transformExpandoPropertyDeclaration(name *ast.Node, host *ast.Symbol, declaration *ast.Node, property string, valueDeclaration *ast.Node) *ast.Node { + return tx.transformExpandoProperty(name, host, declaration, property, valueDeclaration, func(synthesizedNamespace *ast.Node) *ast.Node { + return tx.resolver.CreateTypeOfDeclaration(tx.EmitContext(), valueDeclaration, synthesizedNamespace, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags|nodebuilder.InternalFlagsNoSyntacticPrinter, tx.tracker) + }) +} + +func (tx *DeclarationTransformer) transformExpandoProperty(name *ast.Node, host *ast.Symbol, declaration *ast.Node, property string, diagnosticNode *ast.Node, createType func(synthesizedNamespace *ast.Node) *ast.Node) *ast.Node { isNonContextualKeywordName := ast.IsNonContextualKeyword(scanner.StringToToken(property)) - exportName := core.IfElse(isNonContextualKeywordName, tx.Factory().NewGeneratedNameForNode(left), tx.Factory().NewIdentifier(property)) + exportName := core.IfElse(isNonContextualKeywordName, tx.Factory().NewGeneratedNameForNode(diagnosticNode), tx.Factory().NewIdentifier(property)) synthesizedNamespace := tx.Factory().NewModuleDeclaration(nil /*modifiers*/, ast.KindNamespaceKeyword, name, tx.Factory().NewModuleBlock(tx.Factory().NewNodeList([]*ast.Node{}))) synthesizedNamespace.Parent = tx.enclosingDeclaration @@ -2287,8 +2366,8 @@ func (tx *DeclarationTransformer) transformExpandoAssignment(node *ast.BinaryExp containerData.Locals = make(ast.SymbolTable, 0) saveDiag := tx.state.getSymbolAccessibilityDiagnostic - tx.state.getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(node.AsNode()) - t := tx.resolver.CreateTypeOfExpression(tx.EmitContext(), left, synthesizedNamespace, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags|nodebuilder.InternalFlagsNoSyntacticPrinter, tx.tracker) + tx.state.getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(diagnosticNode) + t := createType(synthesizedNamespace) tx.state.getSymbolAccessibilityDiagnostic = saveDiag statements := []*ast.Statement{ @@ -2326,6 +2405,89 @@ func (tx *DeclarationTransformer) transformExpandoAssignment(node *ast.BinaryExp return tx.Factory().NewModuleDeclaration(tx.Factory().NewModifierList(ast.CreateModifiersFromModifierFlags(modifierFlags, tx.Factory().NewModifier)), ast.KindNamespaceKeyword, name, tx.Factory().NewModuleBlock(tx.Factory().NewNodeList(statements))) } +func (tx *DeclarationTransformer) hasOwnExpandoAssignment(name string, property string) bool { + if tx.state.currentSourceFile == nil { + return false + } + for _, statement := range tx.state.currentSourceFile.Statements.Nodes { + if !ast.IsExpressionStatement(statement) { + continue + } + expression := statement.Expression() + if expression == nil || ast.GetAssignmentDeclarationKind(expression) != ast.JSDeclarationKindProperty || !ast.IsBinaryExpression(expression) { + continue + } + left := expression.AsBinaryExpression().Left + ns := ast.GetLeftmostAccessExpression(left) + if ns == nil || ns.Kind != ast.KindIdentifier || ns.Text() != name { + continue + } + if tx.tryGetPropertyName(left) == property { + return true + } + } + return false +} + +func (tx *DeclarationTransformer) getBoundFunctionTargetDeclaration(declaration *ast.Node) *ast.Node { + if declaration == nil || + !ast.IsVariableDeclaration(declaration) || + declaration.Type() != nil || + !ast.IsIdentifier(declaration.Name()) || + !ast.IsInJSFile(declaration) { + return nil + } + + initializer := declaration.Initializer() + if initializer == nil { + return nil + } + initializer = unwrapParenthesizedExpression(initializer) + if !ast.IsCallExpression(initializer) { + return nil + } + + call := initializer.AsCallExpression() + if call.Arguments == nil || len(call.Arguments.Nodes) != 1 { + return nil + } + + expression := unwrapParenthesizedExpression(call.Expression) + if !ast.IsPropertyAccessExpression(expression) || + expression.AsPropertyAccessExpression().QuestionDotToken != nil || + expression.Name() == nil || + expression.Name().Text() != "bind" { + return nil + } + + targetExpression := unwrapParenthesizedExpression(expression.AsPropertyAccessExpression().Expression) + target := tx.resolver.GetReferencedValueDeclaration(targetExpression) + if target == nil { + return nil + } + if ast.IsFunctionDeclaration(target) { + if target.FunctionLikeData().FullSignature != nil { + return nil + } + return target + } + if ast.IsVariableDeclaration(target) && target.Type() == nil && ast.IsFunctionExpressionOrArrowFunction(target.Initializer()) { + return target + } + return nil +} + +func (tx *DeclarationTransformer) createBoundFunctionDeclaration(declaration *ast.Node, modifiers *ast.ModifierList, name *ast.Node, target *ast.Node) *ast.Node { + signature := target + if ast.IsVariableDeclaration(target) { + signature = target.Initializer() + } + typeParameters, parameters, asteriskToken := extractExpandoHostParams(signature) + result := tx.Factory().NewFunctionDeclaration(modifiers, asteriskToken, tx.Factory().NewIdentifier(name.Text()), tx.ensureTypeParams(signature, typeParameters), tx.updateParamList(signature, parameters), tx.ensureType(signature, false), nil /*fullSignature*/, nil /*body*/) + tx.preserveJsDoc(result, signature) + return result +} + func (tx *DeclarationTransformer) transformExpandoHost(name *ast.Node, declaration *ast.Declaration) { root := core.IfElse(ast.IsVariableDeclaration(declaration), declaration.Parent.Parent, declaration) id := ast.GetNodeId(tx.EmitContext().MostOriginal(root)) @@ -2358,6 +2520,12 @@ func (tx *DeclarationTransformer) transformExpandoHost(name *ast.Node, declarati fn := declaration.Initializer() typeParameters, parameters, asteriskToken := extractExpandoHostParams(fn) replacement = append(replacement, tx.Factory().NewFunctionDeclaration(modifiers, asteriskToken, tx.Factory().NewIdentifier(name.Text()), tx.ensureTypeParams(fn, typeParameters), tx.updateParamList(fn, parameters), tx.ensureType(fn, false), nil /*fullSignature*/, nil /*body*/)) + } else if ast.IsVariableDeclaration(declaration) { + target := tx.getBoundFunctionTargetDeclaration(declaration) + if target == nil { + return + } + replacement = append(replacement, tx.createBoundFunctionDeclaration(declaration, modifiers, name, target)) } else { return } diff --git a/testdata/baselines/reference/compiler/declarationEmitJsBindExpandoFunction.js b/testdata/baselines/reference/compiler/declarationEmitJsBindExpandoFunction.js new file mode 100644 index 00000000000..0a080b5b97d --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationEmitJsBindExpandoFunction.js @@ -0,0 +1,66 @@ +//// [tests/cases/compiler/declarationEmitJsBindExpandoFunction.ts] //// + +//// [repro.js] +/** @param {{ text: string }} args */ +function Internal(args) { + return args.text; +} +Internal.args = { text: "source" }; + +export const PublicInternalBinding = Internal.bind({}); + +/** @param {{ text: string }} args */ +function Plain(args) { + return args.text; +} +export const PublicPlainBinding = Plain.bind({}); +// @ts-ignore +PublicPlainBinding.args = { text: "bound" }; + +/** @param {{ text: string }} args */ +function Mixed(args) { + return args.text; +} +Mixed.first = { text: "source" }; +Mixed.second = { count: 0 }; + +export const PublicMixedBinding = Mixed.bind({}); +// @ts-ignore +PublicMixedBinding.first = { count: 1 }; + + + + +//// [repro.d.ts] +/** @param {{ text: string }} args */ +export declare function PublicInternalBinding(args: { + text: string; +}): string; +export declare namespace PublicInternalBinding { + var args: { + text: string; + }; +} +/** @param {{ text: string }} args */ +export declare function PublicPlainBinding(args: { + text: string; +}): string; +export declare namespace PublicPlainBinding { + var args: { + text: string; + }; +} +/** @param {{ text: string }} args */ +export declare function PublicMixedBinding(args: { + text: string; +}): string; +export declare namespace PublicMixedBinding { + var second: { + count: number; + }; +} +export declare namespace PublicMixedBinding { + var first: { + count: number; + }; +} diff --git a/testdata/baselines/reference/compiler/declarationEmitJsBindExpandoFunction.symbols b/testdata/baselines/reference/compiler/declarationEmitJsBindExpandoFunction.symbols new file mode 100644 index 00000000000..287e411d3b4 --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationEmitJsBindExpandoFunction.symbols @@ -0,0 +1,81 @@ +//// [tests/cases/compiler/declarationEmitJsBindExpandoFunction.ts] //// + +=== repro.js === +/** @param {{ text: string }} args */ +function Internal(args) { +>Internal : Symbol(Internal, Decl(repro.js, 0, 0)) +>args : Symbol(args, Decl(repro.js, 1, 18)) + + return args.text; +>args.text : Symbol(text, Decl(repro.js, 0, 13)) +>args : Symbol(args, Decl(repro.js, 1, 18)) +>text : Symbol(text, Decl(repro.js, 0, 13)) +} +Internal.args = { text: "source" }; +>Internal.args : Symbol(Internal.args, Decl(repro.js, 3, 1)) +>Internal : Symbol(Internal, Decl(repro.js, 0, 0)) +>args : Symbol(Internal.args, Decl(repro.js, 3, 1)) +>text : Symbol(text, Decl(repro.js, 4, 17)) + +export const PublicInternalBinding = Internal.bind({}); +>PublicInternalBinding : Symbol(PublicInternalBinding, Decl(repro.js, 6, 12)) +>Internal.bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>Internal : Symbol(Internal, Decl(repro.js, 0, 0)) +>bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + +/** @param {{ text: string }} args */ +function Plain(args) { +>Plain : Symbol(Plain, Decl(repro.js, 6, 55)) +>args : Symbol(args, Decl(repro.js, 9, 15)) + + return args.text; +>args.text : Symbol(text, Decl(repro.js, 8, 13)) +>args : Symbol(args, Decl(repro.js, 9, 15)) +>text : Symbol(text, Decl(repro.js, 8, 13)) +} +export const PublicPlainBinding = Plain.bind({}); +>PublicPlainBinding : Symbol(PublicPlainBinding, Decl(repro.js, 12, 12)) +>Plain.bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>Plain : Symbol(Plain, Decl(repro.js, 6, 55)) +>bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + +// @ts-ignore +PublicPlainBinding.args = { text: "bound" }; +>PublicPlainBinding : Symbol(PublicPlainBinding, Decl(repro.js, 12, 12)) +>text : Symbol(text, Decl(repro.js, 14, 27)) + +/** @param {{ text: string }} args */ +function Mixed(args) { +>Mixed : Symbol(Mixed, Decl(repro.js, 14, 44)) +>args : Symbol(args, Decl(repro.js, 17, 15)) + + return args.text; +>args.text : Symbol(text, Decl(repro.js, 16, 13)) +>args : Symbol(args, Decl(repro.js, 17, 15)) +>text : Symbol(text, Decl(repro.js, 16, 13)) +} +Mixed.first = { text: "source" }; +>Mixed.first : Symbol(Mixed.first, Decl(repro.js, 19, 1)) +>Mixed : Symbol(Mixed, Decl(repro.js, 14, 44)) +>first : Symbol(Mixed.first, Decl(repro.js, 19, 1)) +>text : Symbol(text, Decl(repro.js, 20, 15)) + +Mixed.second = { count: 0 }; +>Mixed.second : Symbol(Mixed.second, Decl(repro.js, 20, 33)) +>Mixed : Symbol(Mixed, Decl(repro.js, 14, 44)) +>second : Symbol(Mixed.second, Decl(repro.js, 20, 33)) +>count : Symbol(count, Decl(repro.js, 21, 16)) + +export const PublicMixedBinding = Mixed.bind({}); +>PublicMixedBinding : Symbol(PublicMixedBinding, Decl(repro.js, 23, 12)) +>Mixed.bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>Mixed : Symbol(Mixed, Decl(repro.js, 14, 44)) +>bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + +// @ts-ignore +PublicMixedBinding.first = { count: 1 }; +>PublicMixedBinding.first : Symbol(Mixed.first, Decl(repro.js, 19, 1)) +>PublicMixedBinding : Symbol(PublicMixedBinding, Decl(repro.js, 23, 12)) +>first : Symbol(Mixed.first, Decl(repro.js, 19, 1)) +>count : Symbol(count, Decl(repro.js, 25, 28)) + diff --git a/testdata/baselines/reference/compiler/declarationEmitJsBindExpandoFunction.types b/testdata/baselines/reference/compiler/declarationEmitJsBindExpandoFunction.types new file mode 100644 index 00000000000..9cbc51694c5 --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationEmitJsBindExpandoFunction.types @@ -0,0 +1,104 @@ +//// [tests/cases/compiler/declarationEmitJsBindExpandoFunction.ts] //// + +=== repro.js === +/** @param {{ text: string }} args */ +function Internal(args) { +>Internal : { (args: { text: string; }): string; args: { text: string; }; } +>args : { text: string; } + + return args.text; +>args.text : string +>args : { text: string; } +>text : string +} +Internal.args = { text: "source" }; +>Internal.args = { text: "source" } : { text: string; } +>Internal.args : { text: string; } +>Internal : { (args: { text: string; }): string; args: { text: string; }; } +>args : { text: string; } +>{ text: "source" } : { text: string; } +>text : string +>"source" : "source" + +export const PublicInternalBinding = Internal.bind({}); +>PublicInternalBinding : { (args: { text: string; }): string; args: { text: string; }; } +>Internal.bind({}) : { (args: { text: string; }): string; args: { text: string; }; } +>Internal.bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>Internal : { (args: { text: string; }): string; args: { text: string; }; } +>bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>{} : {} + +/** @param {{ text: string }} args */ +function Plain(args) { +>Plain : (args: { text: string; }) => string +>args : { text: string; } + + return args.text; +>args.text : string +>args : { text: string; } +>text : string +} +export const PublicPlainBinding = Plain.bind({}); +>PublicPlainBinding : (args: { text: string; }) => string +>Plain.bind({}) : (args: { text: string; }) => string +>Plain.bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>Plain : (args: { text: string; }) => string +>bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>{} : {} + +// @ts-ignore +PublicPlainBinding.args = { text: "bound" }; +>PublicPlainBinding.args = { text: "bound" } : { text: string; } +>PublicPlainBinding.args : error +>PublicPlainBinding : (args: { text: string; }) => string +>args : any +>{ text: "bound" } : { text: string; } +>text : string +>"bound" : "bound" + +/** @param {{ text: string }} args */ +function Mixed(args) { +>Mixed : { (args: { text: string; }): string; first: { text: string; }; second: { count: number; }; } +>args : { text: string; } + + return args.text; +>args.text : string +>args : { text: string; } +>text : string +} +Mixed.first = { text: "source" }; +>Mixed.first = { text: "source" } : { text: string; } +>Mixed.first : { text: string; } +>Mixed : { (args: { text: string; }): string; first: { text: string; }; second: { count: number; }; } +>first : { text: string; } +>{ text: "source" } : { text: string; } +>text : string +>"source" : "source" + +Mixed.second = { count: 0 }; +>Mixed.second = { count: 0 } : { count: number; } +>Mixed.second : { count: number; } +>Mixed : { (args: { text: string; }): string; first: { text: string; }; second: { count: number; }; } +>second : { count: number; } +>{ count: 0 } : { count: number; } +>count : number +>0 : 0 + +export const PublicMixedBinding = Mixed.bind({}); +>PublicMixedBinding : { (args: { text: string; }): string; first: { text: string; }; second: { count: number; }; } +>Mixed.bind({}) : { (args: { text: string; }): string; first: { text: string; }; second: { count: number; }; } +>Mixed.bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>Mixed : { (args: { text: string; }): string; first: { text: string; }; second: { count: number; }; } +>bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>{} : {} + +// @ts-ignore +PublicMixedBinding.first = { count: 1 }; +>PublicMixedBinding.first = { count: 1 } : { count: number; } +>PublicMixedBinding.first : { text: string; } +>PublicMixedBinding : { (args: { text: string; }): string; first: { text: string; }; second: { count: number; }; } +>first : { text: string; } +>{ count: 1 } : { count: number; } +>count : number +>1 : 1 + diff --git a/testdata/tests/cases/compiler/declarationEmitJsBindExpandoFunction.ts b/testdata/tests/cases/compiler/declarationEmitJsBindExpandoFunction.ts new file mode 100644 index 00000000000..7d0aafb203e --- /dev/null +++ b/testdata/tests/cases/compiler/declarationEmitJsBindExpandoFunction.ts @@ -0,0 +1,32 @@ +// @allowJs: true +// @checkJs: true +// @declaration: true +// @emitDeclarationOnly: true + +// @filename: repro.js +/** @param {{ text: string }} args */ +function Internal(args) { + return args.text; +} +Internal.args = { text: "source" }; + +export const PublicInternalBinding = Internal.bind({}); + +/** @param {{ text: string }} args */ +function Plain(args) { + return args.text; +} +export const PublicPlainBinding = Plain.bind({}); +// @ts-ignore +PublicPlainBinding.args = { text: "bound" }; + +/** @param {{ text: string }} args */ +function Mixed(args) { + return args.text; +} +Mixed.first = { text: "source" }; +Mixed.second = { count: 0 }; + +export const PublicMixedBinding = Mixed.bind({}); +// @ts-ignore +PublicMixedBinding.first = { count: 1 };