Repository: microsoft/typescript-go (go)

Issue:
Panic compiling webpack
```
panic: Unhandled case in Node.Text: *ast.ElementAccessExpression

goroutine 1915 [running]:
github.com/microsoft/typescript-go/internal/ast.(*Node).Text(0x4002ee0de0?)
        D:/typescript-go/internal/ast/ast.go:293 +0x2ec
github.com/microsoft/typescript-go/internal/ast.GetElementOrPropertyAccessName(0x400c949938?)
        D:/typescript-go/internal/ast/utilities.go:1314 +0x24
github.com/microsoft/typescript-go/internal/checker.isModuleExportsAccessExpression(0x4002ee0de0?)
        D:/typescript-go/internal/checker/utilities.go:1636 +0xa4
github.com/microsoft/typescript-go/internal/checker.(*Checker).checkTestingKnownTruthyType(0x400c4fb008, 0x4002ee0de0, 0x400cace000, 0x4002edfcc0)
        D:/typescript-go/internal/checker/checker.go:3578 +0x90
github.com/microsoft/typescript-go/internal/checker.(*Checker).checkTestingKnownTruthyTypes(0x400c4fb008, 0x4002ee0de0?, 0x400cace000, 0x4002edfcc0)
        D:/typescript-go/internal/checker/checker.go:3566 +0x4c
github.com/microsoft/typescript-go/internal/checker.(*Checker).checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(...)
        D:/typescript-go/internal/checker/checker.go:3561
github.com/microsoft/typescript-go/internal/checker.(*Checker).checkConditionalExpression(0x400c4fb008, 0x7ff72be079a0?, 0x0)
        D:/typescript-go/internal/checker/checker.go:10325 +0x6c
github.com/microsoft/typescript-go/internal/checker.(*Checker).checkExpressionWorker(0x400c4fb008, 0x7ff72b922f28?, 0xc4fb008?)
        D:/typescript-go/internal/checker/checker.go:7320 +0x49c
github.com/microsoft/typescript-go/internal/checker.(*Checker).checkExpressionEx(0x400c4fb008, 0x4002ee0e40, 0x0)
        D:/typescript-go/internal/checker/checker.go:7073 +0x50
github.com/microsoft/typescript-go/internal/checker.(*Checker).checkParenthesizedExpression(0x400c4fb008, 0x7ff72b9ed72c?, 0x0)
        D:/typescript-go/internal/checker/checker.go:9609 +0x38
github.com/microsoft/typescript-go/internal/checker.(*Checker).checkExpressionWorker(0x400c4fb008, 0x7ff72b915b8c?, 0x2bb86680?)
        D:/typescript-go/internal/checker/checker.go:7290 +0x378
github.com/microsoft/typescript-go/internal/checker.(*Checker).checkExpressionEx(0x400c4fb008, 0x4002b95900, 0x0)
        D:/typescript-go/internal/checker/checker.go:7073 +0x50
github.com/microsoft/typescript-go/internal/checker.(*Checker).checkExpressionCachedEx(0x400c4fb008, 0x4002b95900, 0xc949c38?)
        D:/typescript-go/internal/checker/checker.go:7042 +0xb0
github.com/microsoft/typescript-go/internal/checker.(*Checker).getReturnTypeFromBody(0x400c4fb008, 0x4002e8f9a0, 0x0)
        D:/typescript-go/internal/checker/checker.go:18622 +0x2a0
github.com/microsoft/typescript-go/internal/checker.(*Checker).getReturnTypeOfSignature(0x400c4fb008, 0x4012033580)
        D:/typescript-go/internal/checker/checker.go:18517 +0x148
github.com/microsoft/typescript-go/internal/checker.(*Checker).checkFunctionExpressionOrObjectLiteralMethodDeferred(0x400c4fb008, 0x4002e8f9a0)
        D:/typescript-go/internal/checker/checker.go:9722 +0x9c
github.com/microsoft/typescript-go/internal/checker.(*Checker).checkDeferredNode(0x400c4fb008, 0x7ff72be06da0?)
        D:/typescript-go/internal/checker/checker.go:2283 +0xcc
github.com/microsoft/typescript-go/internal/checker.(*Checker).checkDeferredNodes-range1(...)
        D:/typescript-go/internal/checker/checker.go:2268
github.com/microsoft/typescript-go/internal/checker.(*Checker).checkDeferredNodes.(*OrderedSet[...]).Keys.func1(...)
        D:/typescript-go/internal/collections/ordered_map.go:117
github.com/microsoft/typescript-go/internal/checker.(*Checker).checkDeferredNodes(0x400c4fb008, 0x4003183208?)
        D:/typescript-go/internal/checker/checker.go:2264 +0xc0
github.com/microsoft/typescript-go/internal/checker.(*Checker).checkSourceFile(0x400c4fb008, {0x7ff72be013f8, 0x7ff72c2e36e0}, 0x4002297608)
        D:/typescript-go/internal/checker/checker.go:2071 +0x100
github.com/microsoft/typescript-go/internal/checker.(*Checker).CheckSourceFile(0x400c4fb008, {0x7ff72be013f8, 0x7ff72c2e36e0}, 0x4002297608)
        D:/typescript-go/internal/checker/checker.go:2059 +0x58
github.com/microsoft/typescript-go/internal/compiler.(*Program).CheckSourceFiles.func1-range1(0x400b823f38?)
        D:/typescript-go/internal/compiler/program.go:297 +0x44
github.com/microsoft/typescript-go/internal/compiler.(*checkerPool).Files.func1(0x400d10a450)
        D:/typescript-go/internal/compiler/checkerpool.go:82 +0x7c
github.com/microsoft/typescript-go/internal/compiler.(*Program).CheckSourceFiles.func1()
        D:/typescript-go/internal/compiler/program.go:296 +0xc4
github.com/microsoft/typescript-go/internal/core.(*parallelWorkGroup).Queue.func1()
        D:/typescript-go/internal/core/workgroup.go:39 +0x50
created by github.com/microsoft/typescript-go/internal/core.(*parallelWorkGroup).Queue in goroutine 1
        D:/typescript-go/internal/core/workgroup.go:37 +0x7c
```

Pull request description:
Clean up GetElementOrPropertyAccessName
Fixes #890

There were two related functions but only one caller needed the .Text() followup. The new code is stricter and therefore less likely to panic downstream.

There was also a duplicate implementation of IsModuleExports in checker. I removed that and cleaned it up as well.

This is also the inauguaral test in cases/conformance/salsa.

Patch:
diff --git a/cmd/tsgo/lsp.go b/cmd/tsgo/lsp.go
index cdc8d6e9e..a47b4a00c 100644
--- a/cmd/tsgo/lsp.go
+++ b/cmd/tsgo/lsp.go
@@ -1,10 +1,8 @@
 package main
 
 import (
-	"errors"
 	"flag"
 	"fmt"
-	"io"
 	"os"
 
 	"github.com/microsoft/typescript-go/internal/bundled"
@@ -49,7 +47,7 @@ func runLSP(args []string) int {
 		DefaultLibraryPath: defaultLibraryPath,
 	})
 
-	if err := s.Run(); err != nil && !errors.Is(err, io.EOF) {
+	if err := s.Run(); err != nil {
 		return 1
 	}
 	return 0
diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go
index 9c9d774c6..767a03c8f 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 87168d9b0..fb08fd759 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) {
@@ -27686,7 +27686,10 @@ func (c *Checker) getContextualTypeForBinaryOperand(node *ast.Node, contextFlags
 	case ast.KindEqualsToken, ast.KindAmpersandAmpersandEqualsToken, ast.KindBarBarEqualsToken, ast.KindQuestionQuestionEqualsToken:
 		// In an assignment expression, the right operand is contextually typed by the type of the left operand
 		// unless it's an assignment declaration.
-		if node == binary.Right && !c.isReferenceToModuleExports(binary.Left) && (binary.Symbol == nil || c.canGetContextualTypeForAssignmentDeclaration(node.Parent)) {
+		if node == binary.Right && !c.isReferenceToModuleExports(binary.Left) {
+			if binary.Symbol != nil {
+				return c.getContextualTypeForAssignmentDeclaration(node.Parent)
+			}
 			return c.getTypeOfExpression(binary.Left)
 		}
 	case ast.KindBarBarToken, ast.KindQuestionQuestionToken:
@@ -27708,14 +27711,15 @@ func (c *Checker) getContextualTypeForBinaryOperand(node *ast.Node, contextFlags
 	return nil
 }
 
-func (c *Checker) canGetContextualTypeForAssignmentDeclaration(node *ast.Node) bool {
+func (c *Checker) getContextualTypeForAssignmentDeclaration(node *ast.Node) *Type {
 	// Node is the left operand of an assignment declaration (a binary expression with a symbol assigned by the
 	// binder) of the form 'F.id = expr' or 'F[xxx] = expr'. If 'F' is declared as a variable with a type annotation,
 	// we can obtain a contextual type from the annotated type without triggering a circularity. Otherwise, the
 	// assignment declaration has no contextual type.
 	left := node.AsBinaryExpression().Left
 	expr := left.Expression()
-	if ast.IsAccessExpression(left) && expr.Kind == ast.KindThisKeyword {
+	switch expr.Kind {
+	case ast.KindThisKeyword:
 		var symbol *ast.Symbol
 		if ast.IsPropertyAccessExpression(left) {
 			name := left.Name()
@@ -27734,27 +27738,39 @@ func (c *Checker) canGetContextualTypeForAssignmentDeclaration(node *ast.Node) b
 		if symbol != nil {
 			d := symbol.ValueDeclaration
 			if d != nil && (ast.IsPropertyDeclaration(d) || ast.IsPropertySignatureDeclaration(d)) && d.Type() == nil && d.Initializer() == nil {
-				return false
+				return nil
 			}
 		}
 		symbol = node.Symbol()
 		if symbol != nil && symbol.ValueDeclaration != nil && symbol.ValueDeclaration.Type() == nil {
 			if !ast.IsObjectLiteralMethod(c.getThisContainer(expr, false, false)) {
-				return false
+				return nil
 			}
 			// and now for one single case of object literal methods
-			name := ast.GetElementOrPropertyAccessArgumentExpressionOrName(left)
+			name := ast.GetElementOrPropertyAccessName(left)
 			if name == nil {
-				return false
+				return nil
 			} else {
 				// !!! contextual typing for `this` in object literals
-				return false
+				return nil
+			}
+		}
+		return c.getTypeOfExpression(left)
+	case ast.KindIdentifier:
+		symbol := c.getExportSymbolOfValueSymbolIfExported(c.getResolvedSymbol(expr))
+		if symbol.ValueDeclaration != nil && ast.IsVariableDeclaration(symbol.ValueDeclaration) {
+			if typeNode := symbol.ValueDeclaration.Type(); typeNode != nil {
+				if ast.IsPropertyAccessExpression(left) {
+					return c.getTypeOfPropertyOfContextualType(c.getTypeFromTypeNode(typeNode), left.Name().Text())
+				}
+				nameType := c.checkExpressionCached(left.AsElementAccessExpression().ArgumentExpression)
+				if isTypeUsableAsPropertyName(nameType) {
+					return c.getTypeOfPropertyOfContextualTypeEx(c.getTypeFromTypeNode(typeNode), getPropertyNameFromType(nameType), nameType)
+				}
 			}
 		}
-		return true
 	}
-	symbol := c.getExportSymbolOfValueSymbolIfExported(c.getResolvedSymbol(expr))
-	return symbol.ValueDeclaration != nil && ast.IsVariableDeclaration(symbol.ValueDeclaration) && symbol.ValueDeclaration.Type() != nil
+	return nil
 }
 
 func (c *Checker) isReferenceToModuleExports(node *ast.Node) bool {
diff --git a/internal/checker/utilities.go b/internal/checker/utilities.go
index ca8704fc6..37ef80203 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/lsp/server.go b/internal/lsp/server.go
index b9d935329..77d482424 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -187,16 +187,17 @@ func (s *Server) Run() error {
 	g.Go(func() error { return s.dispatchLoop(ctx) })
 	g.Go(func() error { return s.writeLoop(ctx) })
 	g.Go(func() error { return s.readLoop(ctx) })
-	return g.Wait()
+
+	if err := g.Wait(); err != nil && !errors.Is(err, io.EOF) {
+		return err
+	}
+	return nil
 }
 
 func (s *Server) readLoop(ctx context.Context) error {
 	for {
 		msg, err := s.read()
 		if err != nil {
-			if errors.Is(err, io.EOF) {
-				return nil
-			}
 			if errors.Is(err, lsproto.ErrInvalidRequest) {
 				s.sendError(nil, err)
 				continue
diff --git a/internal/parser/reparser.go b/internal/parser/reparser.go
index 041fd45f7..242609e2a 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


Test patch:
diff --git a/internal/testutil/harnessutil/harnessutil.go b/internal/testutil/harnessutil/harnessutil.go
index 3f4474789..9c02222fb 100644
--- a/internal/testutil/harnessutil/harnessutil.go
+++ b/internal/testutil/harnessutil/harnessutil.go
@@ -146,6 +146,10 @@ func CompileFilesEx(
 		}
 	}
 
+	if includeLibDir {
+		repo.SkipIfNoTypeScriptSubmodule(t)
+	}
+
 	// !!!
 	// ts.assign(options, ts.convertToOptionsWithAbsolutePaths(options, path => ts.getNormalizedAbsolutePath(path, currentDirectory)));
 	if compilerOptions.OutDir != "" {
diff --git a/testdata/baselines/reference/compiler/expandoContextualTypes.symbols b/testdata/baselines/reference/compiler/expandoContextualTypes.symbols
new file mode 100644
index 000000000..42700f77e
--- /dev/null
+++ b/testdata/baselines/reference/compiler/expandoContextualTypes.symbols
@@ -0,0 +1,52 @@
+//// [tests/cases/compiler/expandoContextualTypes.tsx] ////
+
+=== expandoContextualTypes.tsx ===
+// https://github.com/microsoft/typescript-go/issues/921
+
+/// <reference path="react16.d.ts" />
+
+import type { ComponentType } from "react";
+>ComponentType : Symbol(ComponentType, Decl(expandoContextualTypes.tsx, 4, 13))
+
+export type Page<P = NonNullable<unknown>> = ComponentType<P> & {
+>Page : Symbol(Page, Decl(expandoContextualTypes.tsx, 4, 43))
+>P : Symbol(P, Decl(expandoContextualTypes.tsx, 6, 17))
+>NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --))
+>ComponentType : Symbol(ComponentType, Decl(expandoContextualTypes.tsx, 4, 13))
+>P : Symbol(P, Decl(expandoContextualTypes.tsx, 6, 17))
+
+    getLayout?: (component: JSX.Element) => JSX.Element;
+>getLayout : Symbol(getLayout, Decl(expandoContextualTypes.tsx, 6, 65))
+>component : Symbol(component, Decl(expandoContextualTypes.tsx, 7, 17))
+>JSX : Symbol(JSX, Decl(react16.d.ts, 2493, 12))
+>Element : Symbol(Element, Decl(react16.d.ts, 2494, 23))
+>JSX : Symbol(JSX, Decl(react16.d.ts, 2493, 12))
+>Element : Symbol(Element, Decl(react16.d.ts, 2494, 23))
+}
+
+export const FooPage: Page = () => {
+>FooPage : Symbol(FooPage, Decl(expandoContextualTypes.tsx, 10, 12))
+>Page : Symbol(Page, Decl(expandoContextualTypes.tsx, 4, 43))
+
+    return (
+        <div>
+>div : Symbol(div, Decl(react16.d.ts, 2546, 114))
+
+            <p>Foo</p>
+>p : Symbol(p, Decl(react16.d.ts, 2593, 102))
+>p : Symbol(p, Decl(react16.d.ts, 2593, 102))
+
+        </div>
+>div : Symbol(div, Decl(react16.d.ts, 2546, 114))
+
+    )
+};
+
+FooPage.getLayout = () => {
+>FooPage.getLayout : Symbol(getLayout, Decl(expandoContextualTypes.tsx, 6, 65))
+>FooPage : Symbol(FooPage, Decl(expandoContextualTypes.tsx, 10, 12))
+>getLayout : Symbol(getLayout, Decl(expandoContextualTypes.tsx, 6, 65))
+
+    return <></>
+};
+
diff --git a/testdata/baselines/reference/compiler/expandoContextualTypes.types b/testdata/baselines/reference/compiler/expandoContextualTypes.types
new file mode 100644
index 000000000..bbf6d9844
--- /dev/null
+++ b/testdata/baselines/reference/compiler/expandoContextualTypes.types
@@ -0,0 +1,54 @@
+//// [tests/cases/compiler/expandoContextualTypes.tsx] ////
+
+=== expandoContextualTypes.tsx ===
+// https://github.com/microsoft/typescript-go/issues/921
+
+/// <reference path="react16.d.ts" />
+
+import type { ComponentType } from "react";
+>ComponentType : ComponentType<P>
+
+export type Page<P = NonNullable<unknown>> = ComponentType<P> & {
+>Page : Page<P>
+
+    getLayout?: (component: JSX.Element) => JSX.Element;
+>getLayout : ((component: Element) => Element) | undefined
+>component : Element
+>JSX : any
+>JSX : any
+}
+
+export const FooPage: Page = () => {
+>FooPage : Page<{}>
+>() => {    return (        <div>            <p>Foo</p>        </div>    )} : { (): Element; getLayout: () => Element; }
+
+    return (
+>(        <div>            <p>Foo</p>        </div>    ) : Element
+
+        <div>
+><div>            <p>Foo</p>        </div> : Element
+>div : any
+
+            <p>Foo</p>
+><p>Foo</p> : Element
+>p : any
+>p : any
+
+        </div>
+>div : any
+
+    )
+};
+
+FooPage.getLayout = () => {
+>FooPage.getLayout = () => {    return <></>} : () => Element
+>FooPage.getLayout : ((component: Element) => Element) | undefined
+>FooPage : StatelessComponent<{}> & { getLayout?: ((component: Element) => Element) | undefined; }
+>getLayout : ((component: Element) => Element) | undefined
+>() => {    return <></>} : () => Element
+
+    return <></>
+><></> : Element
+
+};
+
diff --git a/testdata/baselines/reference/conformance/deepElementAccessExpressionInJS.errors.txt b/testdata/baselines/reference/conformance/deepElementAccessExpressionInJS.errors.txt
new file mode 100644
index 000000000..7fb2b6b9c
--- /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 000000000..600d53ac6
--- /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 000000000..c7328994a
--- /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/compiler/expandoContextualTypes.tsx b/testdata/tests/cases/compiler/expandoContextualTypes.tsx
new file mode 100644
index 000000000..ef6e65dff
--- /dev/null
+++ b/testdata/tests/cases/compiler/expandoContextualTypes.tsx
@@ -0,0 +1,26 @@
+// @strict: true
+// @target: esnext
+// @noEmit: true
+// @jsx: preserve
+
+// https://github.com/microsoft/typescript-go/issues/921
+
+/// <reference path="/.lib/react16.d.ts" />
+
+import type { ComponentType } from "react";
+
+export type Page<P = NonNullable<unknown>> = ComponentType<P> & {
+    getLayout?: (component: JSX.Element) => JSX.Element;
+}
+
+export const FooPage: Page = () => {
+    return (
+        <div>
+            <p>Foo</p>
+        </div>
+    )
+};
+
+FooPage.getLayout = () => {
+    return <></>
+};
diff --git a/testdata/tests/cases/conformance/salsa/deepElementAccessExpressionInJS.ts b/testdata/tests/cases/conformance/salsa/deepElementAccessExpressionInJS.ts
new file mode 100644
index 000000000..e51a36f7d
--- /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}`;
+}