diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index 9c9d774c61..767a03c8fe 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -1292,26 +1292,20 @@ func IsThisParameter(node *Node) bool { // Does not handle signed numeric names like `a[+0]` - handling those would require handling prefix unary expressions // throughout late binding handling as well, which is awkward (but ultimately probably doable if there is demand) -func GetElementOrPropertyAccessArgumentExpressionOrName(node *Node) *Node { +func GetElementOrPropertyAccessName(node *Node) *Node { switch node.Kind { case KindPropertyAccessExpression: - return node.Name() + if IsIdentifier(node.Name()) { + return node.Name() + } + return nil case KindElementAccessExpression: - arg := SkipParentheses(node.AsElementAccessExpression().ArgumentExpression) - if IsStringOrNumericLiteralLike(arg) { + if arg := SkipParentheses(node.AsElementAccessExpression().ArgumentExpression); IsStringOrNumericLiteralLike(arg) { return arg } - return node - } - panic("Unhandled case in GetElementOrPropertyAccessArgumentExpressionOrName") -} - -func GetElementOrPropertyAccessName(node *Node) string { - name := GetElementOrPropertyAccessArgumentExpressionOrName(node) - if name == nil { - return "" + return nil } - return name.Text() + panic("Unhandled case in GetElementOrPropertyAccessName") } func IsExpressionWithTypeArgumentsInClassExtendsClause(node *Node) bool { @@ -1356,7 +1350,11 @@ func GetNonAssignedNameOfDeclaration(declaration *Node) *Node { bin := declaration.AsBinaryExpression() kind := GetAssignmentDeclarationKind(bin) if kind == JSDeclarationKindProperty || kind == JSDeclarationKindThisProperty { - return GetElementOrPropertyAccessArgumentExpressionOrName(bin.Left) + if name := GetElementOrPropertyAccessName(bin.Left); name != nil { + return name + } else { + return bin.Left + } } return nil case KindExportAssignment, KindJSExportAssignment: @@ -1426,24 +1424,19 @@ func GetAssignmentDeclarationKind(bin *BinaryExpression) JSDeclarationKind { if IsModuleExportsAccessExpression(bin.Left) { return JSDeclarationKindModuleExports } else if (IsModuleExportsAccessExpression(bin.Left.Expression()) || IsExportsIdentifier(bin.Left.Expression())) && - hasJSBindableName(bin.Left) { + GetElementOrPropertyAccessName(bin.Left) != nil { return JSDeclarationKindExportsProperty } if bin.Left.Expression().Kind == KindThisKeyword { return JSDeclarationKindThisProperty } - if bin.Left.Kind == KindPropertyAccessExpression && IsIdentifier(bin.Left.Expression()) && hasJSBindableName(bin.Left) || + if bin.Left.Kind == KindPropertyAccessExpression && IsIdentifier(bin.Left.Expression()) && IsIdentifier(bin.Left.Name()) || bin.Left.Kind == KindElementAccessExpression && IsIdentifier(bin.Left.Expression()) { return JSDeclarationKindProperty } return JSDeclarationKindNone } -func hasJSBindableName(node *Node) bool { - name := GetElementOrPropertyAccessArgumentExpressionOrName(node) - return IsIdentifier(name) || IsStringLiteralLike(name) -} - /** * A declaration has a dynamic name if all of the following are true: * 1. The declaration has a computed property name. @@ -2679,9 +2672,12 @@ func IsVariableDeclarationInitializedToRequire(node *Node) bool { } func IsModuleExportsAccessExpression(node *Node) bool { - return (IsPropertyAccessExpression(node) || isLiteralLikeElementAccess(node)) && - IsModuleIdentifier(node.Expression()) && - GetElementOrPropertyAccessName(node) == "exports" + if IsAccessExpression(node) && IsModuleIdentifier(node.Expression()) { + if name := GetElementOrPropertyAccessName(node); name != nil { + return name.Text() == "exports" + } + } + return false } func isLiteralLikeElementAccess(node *Node) bool { diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 87168d9b0b..d472563b1d 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -3575,7 +3575,7 @@ func (c *Checker) checkTestingKnownTruthyType(condExpr *ast.Node, condType *Type if ast.IsLogicalOrCoalescingBinaryExpression(condExpr) { location = ast.SkipParentheses(condExpr.AsBinaryExpression().Right) } - if isModuleExportsAccessExpression(location) { + if ast.IsModuleExportsAccessExpression(location) { return } if ast.IsLogicalOrCoalescingBinaryExpression(location) { @@ -27743,7 +27743,7 @@ func (c *Checker) canGetContextualTypeForAssignmentDeclaration(node *ast.Node) b return false } // and now for one single case of object literal methods - name := ast.GetElementOrPropertyAccessArgumentExpressionOrName(left) + name := ast.GetElementOrPropertyAccessName(left) if name == nil { return false } else { diff --git a/internal/checker/utilities.go b/internal/checker/utilities.go index ca8704fc60..37ef802033 100644 --- a/internal/checker/utilities.go +++ b/internal/checker/utilities.go @@ -1632,10 +1632,6 @@ func minAndMax[T any](slice []T, getValue func(value T) int) (int, int) { return minValue, maxValue } -func isModuleExportsAccessExpression(node *ast.Node) bool { - return ast.IsAccessExpression(node) && ast.IsModuleIdentifier(node.Expression()) && ast.GetElementOrPropertyAccessName(node) == "exports" -} - func getNonModifierTokenRangeOfNode(node *ast.Node) core.TextRange { pos := node.Pos() if node.Modifiers() != nil { diff --git a/internal/parser/reparser.go b/internal/parser/reparser.go index 041fd45f76..242609e2a3 100644 --- a/internal/parser/reparser.go +++ b/internal/parser/reparser.go @@ -24,7 +24,7 @@ func (p *Parser) reparseCommonJS(node *ast.Node) { nodes[0].Flags = ast.NodeFlagsReparsed nodes[0].Loc = bin.Loc // TODO: Name can sometimes be a string literal, so downstream code needs to handle this - export = p.factory.NewCommonJSExport(p.newModifierList(bin.Loc, nodes), ast.GetElementOrPropertyAccessArgumentExpressionOrName(bin.Left), bin.Right) + export = p.factory.NewCommonJSExport(p.newModifierList(bin.Loc, nodes), ast.GetElementOrPropertyAccessName(bin.Left), bin.Right) } if export != nil { export.Flags = ast.NodeFlagsReparsed diff --git a/testdata/baselines/reference/conformance/deepElementAccessExpressionInJS.errors.txt b/testdata/baselines/reference/conformance/deepElementAccessExpressionInJS.errors.txt new file mode 100644 index 0000000000..7fb2b6b9c5 --- /dev/null +++ b/testdata/baselines/reference/conformance/deepElementAccessExpressionInJS.errors.txt @@ -0,0 +1,24 @@ +elementAccessExpressionInJS.js(1,5): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ exports: { [key: string]: any; }; }'. + No index signature with a parameter of type 'string' was found on type '{ exports: { [key: string]: any; }; }'. +elementAccessExpressionInJS.js(3,32): error TS7006: Parameter 'index' implicitly has an 'any' type. + + +==== declarations.d.ts (0 errors) ==== + declare var module: { + exports: { + [key: string]: any; + }; + } +==== elementAccessExpressionInJS.js (2 errors) ==== + if (module[calculatePropertyName(1)]) { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ exports: { [key: string]: any; }; }'. +!!! error TS7053: No index signature with a parameter of type 'string' was found on type '{ exports: { [key: string]: any; }; }'. + } + function calculatePropertyName(index) { + ~~~~~ +!!! error TS7006: Parameter 'index' implicitly has an 'any' type. + // this would be some webpack index in real life + return `property${index}`; + } + \ No newline at end of file diff --git a/testdata/baselines/reference/conformance/deepElementAccessExpressionInJS.symbols b/testdata/baselines/reference/conformance/deepElementAccessExpressionInJS.symbols new file mode 100644 index 0000000000..600d53ac62 --- /dev/null +++ b/testdata/baselines/reference/conformance/deepElementAccessExpressionInJS.symbols @@ -0,0 +1,28 @@ +//// [tests/cases/conformance/salsa/deepElementAccessExpressionInJS.ts] //// + +=== declarations.d.ts === +declare var module: { +>module : Symbol(module, Decl(declarations.d.ts, 0, 11)) + + exports: { +>exports : Symbol(exports, Decl(declarations.d.ts, 0, 21)) + + [key: string]: any; +>key : Symbol(key, Decl(declarations.d.ts, 2, 9)) + + }; +} +=== elementAccessExpressionInJS.js === +if (module[calculatePropertyName(1)]) { +>module : Symbol(module, Decl(declarations.d.ts, 0, 11)) +>calculatePropertyName : Symbol(calculatePropertyName, Decl(elementAccessExpressionInJS.js, 1, 1)) +} +function calculatePropertyName(index) { +>calculatePropertyName : Symbol(calculatePropertyName, Decl(elementAccessExpressionInJS.js, 1, 1)) +>index : Symbol(index, Decl(elementAccessExpressionInJS.js, 2, 31)) + + // this would be some webpack index in real life + return `property${index}`; +>index : Symbol(index, Decl(elementAccessExpressionInJS.js, 2, 31)) +} + diff --git a/testdata/baselines/reference/conformance/deepElementAccessExpressionInJS.types b/testdata/baselines/reference/conformance/deepElementAccessExpressionInJS.types new file mode 100644 index 0000000000..c7328994a4 --- /dev/null +++ b/testdata/baselines/reference/conformance/deepElementAccessExpressionInJS.types @@ -0,0 +1,32 @@ +//// [tests/cases/conformance/salsa/deepElementAccessExpressionInJS.ts] //// + +=== declarations.d.ts === +declare var module: { +>module : { exports: { [key: string]: any; }; } + + exports: { +>exports : { [key: string]: any; } + + [key: string]: any; +>key : string + + }; +} +=== elementAccessExpressionInJS.js === +if (module[calculatePropertyName(1)]) { +>module[calculatePropertyName(1)] : any +>module : { exports: { [key: string]: any; }; } +>calculatePropertyName(1) : string +>calculatePropertyName : (index: any) => string +>1 : 1 +} +function calculatePropertyName(index) { +>calculatePropertyName : (index: any) => string +>index : any + + // this would be some webpack index in real life + return `property${index}`; +>`property${index}` : string +>index : any +} + diff --git a/testdata/tests/cases/conformance/salsa/deepElementAccessExpressionInJS.ts b/testdata/tests/cases/conformance/salsa/deepElementAccessExpressionInJS.ts new file mode 100644 index 0000000000..e51a36f7da --- /dev/null +++ b/testdata/tests/cases/conformance/salsa/deepElementAccessExpressionInJS.ts @@ -0,0 +1,18 @@ +// @allowJs: true +// @checkJs: true +// @strict: true +// @target: es6 +// @outDir: ./out +// @filename: declarations.d.ts +declare var module: { + exports: { + [key: string]: any; + }; +} +// @filename: elementAccessExpressionInJS.js +if (module[calculatePropertyName(1)]) { +} +function calculatePropertyName(index) { + // this would be some webpack index in real life + return `property${index}`; +}