diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts
index bab6ca7b360..9c7756d0d30 100644
--- a/src/compiler/binder.ts
+++ b/src/compiler/binder.ts
@@ -2080,8 +2080,8 @@ namespace ts {
if (isInJavaScriptFile(node) &&
file.commonJsModuleIndicator &&
isModuleExportsPropertyAccessExpression(node as PropertyAccessExpression) &&
- !lookupSymbolForNameWorker(container, "module" as __String)) {
- declareSymbol(container.locals!, /*parent*/ undefined, (node as PropertyAccessExpression).expression as Identifier,
+ !lookupSymbolForNameWorker(blockScopeContainer, "module" as __String)) {
+ declareSymbol(file.locals!, /*parent*/ undefined, (node as PropertyAccessExpression).expression as Identifier,
SymbolFlags.FunctionScopedVariable | SymbolFlags.ModuleExports, SymbolFlags.FunctionScopedVariableExcludes);
}
break;
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 57de68ab02b..29720ff8532 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -16122,7 +16122,14 @@ namespace ts {
const { left, operatorToken, right } = binaryExpression;
switch (operatorToken.kind) {
case SyntaxKind.EqualsToken:
- return node === right && isContextSensitiveAssignment(binaryExpression) ? getTypeOfExpression(left) : undefined;
+ if (node !== right) {
+ return undefined;
+ }
+ const contextSensitive = getIsContextSensitiveAssignmentOrContextType(binaryExpression);
+ if (!contextSensitive) {
+ return undefined;
+ }
+ return contextSensitive === true ? getTypeOfExpression(left) : contextSensitive;
case SyntaxKind.BarBarToken:
// When an || expression has a contextual type, the operands are contextually typed by that type. When an ||
// expression has no contextual type, the right operand is contextually typed by the type of the left operand,
@@ -16140,7 +16147,7 @@ namespace ts {
// In an assignment expression, the right operand is contextually typed by the type of the left operand.
// Don't do this for special property assignments unless there is a type tag on the assignment, to avoid circularity from checking the right operand.
- function isContextSensitiveAssignment(binaryExpression: BinaryExpression): boolean {
+ function getIsContextSensitiveAssignmentOrContextType(binaryExpression: BinaryExpression): boolean | Type {
const kind = getSpecialPropertyAssignmentKind(binaryExpression);
switch (kind) {
case SpecialPropertyAssignmentKind.None:
@@ -16159,29 +16166,44 @@ namespace ts {
if (!decl) {
return false;
}
- if (isInJavaScriptFile(decl)) {
- return !!getJSDocTypeTag(decl);
+ const lhs = binaryExpression.left as PropertyAccessExpression;
+ const overallAnnotation = getEffectiveTypeAnnotationNode(decl);
+ if (overallAnnotation) {
+ return getTypeFromTypeNode(overallAnnotation);
}
- else if (isIdentifier((binaryExpression.left as PropertyAccessExpression).expression)) {
- const id = (binaryExpression.left as PropertyAccessExpression).expression as Identifier;
+ else if (isIdentifier(lhs.expression)) {
+ const id = lhs.expression;
const parentSymbol = resolveName(id, id.escapedText, SymbolFlags.Value, undefined, id.escapedText, /*isUse*/ true);
- return !isFunctionSymbol(parentSymbol);
+ if (parentSymbol && isFunctionSymbol(parentSymbol)) {
+ const annotated = getEffectiveTypeAnnotationNode(parentSymbol.valueDeclaration);
+ if (annotated) {
+ const type = getTypeOfPropertyOfContextualType(getTypeFromTypeNode(annotated), lhs.name.escapedText);
+ return type || false;
+ }
+ return false;
+ }
}
- return true;
+ return !isInJavaScriptFile(decl);
}
+ case SpecialPropertyAssignmentKind.ModuleExports:
case SpecialPropertyAssignmentKind.ThisProperty:
- if (!binaryExpression.symbol ||
- binaryExpression.symbol.valueDeclaration && !!getJSDocTypeTag(binaryExpression.symbol.valueDeclaration)) {
- return true;
+ if (!binaryExpression.symbol) return true;
+ if (binaryExpression.symbol.valueDeclaration) {
+ const annotated = getEffectiveTypeAnnotationNode(binaryExpression.symbol.valueDeclaration);
+ if (annotated) {
+ const type = getTypeFromTypeNode(annotated);
+ if (type) {
+ return type;
+ }
+ }
}
+ if (kind === SpecialPropertyAssignmentKind.ModuleExports) return false;
const thisAccess = binaryExpression.left as PropertyAccessExpression;
if (!isObjectLiteralMethod(getThisContainer(thisAccess.expression, /*includeArrowFunctions*/ false))) {
return false;
}
const thisType = checkThisExpression(thisAccess.expression);
- return thisType && !!getPropertyOfType(thisType, thisAccess.name.escapedText);
- case SpecialPropertyAssignmentKind.ModuleExports:
- return !binaryExpression.symbol || binaryExpression.symbol.valueDeclaration && !!getJSDocTypeTag(binaryExpression.symbol.valueDeclaration);
+ return thisType && getTypeOfPropertyOfContextualType(thisType, thisAccess.name.escapedText) || false;
default:
return Debug.assertNever(kind);
}
diff --git a/tests/baselines/reference/expandoFunctionContextualTypes.js b/tests/baselines/reference/expandoFunctionContextualTypes.js
new file mode 100644
index 00000000000..541adf963e4
--- /dev/null
+++ b/tests/baselines/reference/expandoFunctionContextualTypes.js
@@ -0,0 +1,22 @@
+//// [expandoFunctionContextualTypes.ts]
+interface MyComponentProps {
+ color: "red" | "blue"
+}
+
+interface StatelessComponent
{
+ (): any;
+ defaultProps?: Partial
;
+}
+
+const MyComponent: StatelessComponent = () => null as any;
+
+MyComponent.defaultProps = {
+ color: "red"
+};
+
+
+//// [expandoFunctionContextualTypes.js]
+var MyComponent = function () { return null; };
+MyComponent.defaultProps = {
+ color: "red"
+};
diff --git a/tests/baselines/reference/expandoFunctionContextualTypes.symbols b/tests/baselines/reference/expandoFunctionContextualTypes.symbols
new file mode 100644
index 00000000000..cea0647c762
--- /dev/null
+++ b/tests/baselines/reference/expandoFunctionContextualTypes.symbols
@@ -0,0 +1,34 @@
+=== tests/cases/compiler/expandoFunctionContextualTypes.ts ===
+interface MyComponentProps {
+>MyComponentProps : Symbol(MyComponentProps, Decl(expandoFunctionContextualTypes.ts, 0, 0))
+
+ color: "red" | "blue"
+>color : Symbol(MyComponentProps.color, Decl(expandoFunctionContextualTypes.ts, 0, 28))
+}
+
+interface StatelessComponent {
+>StatelessComponent : Symbol(StatelessComponent, Decl(expandoFunctionContextualTypes.ts, 2, 1))
+>P : Symbol(P, Decl(expandoFunctionContextualTypes.ts, 4, 29))
+
+ (): any;
+ defaultProps?: Partial
;
+>defaultProps : Symbol(StatelessComponent.defaultProps, Decl(expandoFunctionContextualTypes.ts, 5, 12))
+>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
+>P : Symbol(P, Decl(expandoFunctionContextualTypes.ts, 4, 29))
+}
+
+const MyComponent: StatelessComponent = () => null as any;
+>MyComponent : Symbol(MyComponent, Decl(expandoFunctionContextualTypes.ts, 9, 5))
+>StatelessComponent : Symbol(StatelessComponent, Decl(expandoFunctionContextualTypes.ts, 2, 1))
+>MyComponentProps : Symbol(MyComponentProps, Decl(expandoFunctionContextualTypes.ts, 0, 0))
+
+MyComponent.defaultProps = {
+>MyComponent.defaultProps : Symbol(StatelessComponent.defaultProps, Decl(expandoFunctionContextualTypes.ts, 5, 12))
+>MyComponent : Symbol(MyComponent, Decl(expandoFunctionContextualTypes.ts, 9, 5))
+>defaultProps : Symbol(StatelessComponent.defaultProps, Decl(expandoFunctionContextualTypes.ts, 5, 12))
+
+ color: "red"
+>color : Symbol(color, Decl(expandoFunctionContextualTypes.ts, 11, 28))
+
+};
+
diff --git a/tests/baselines/reference/expandoFunctionContextualTypes.types b/tests/baselines/reference/expandoFunctionContextualTypes.types
new file mode 100644
index 00000000000..e1eca92659c
--- /dev/null
+++ b/tests/baselines/reference/expandoFunctionContextualTypes.types
@@ -0,0 +1,31 @@
+=== tests/cases/compiler/expandoFunctionContextualTypes.ts ===
+interface MyComponentProps {
+ color: "red" | "blue"
+>color : "red" | "blue"
+}
+
+interface StatelessComponent {
+ (): any;
+ defaultProps?: Partial
;
+>defaultProps : Partial
+}
+
+const MyComponent: StatelessComponent = () => null as any;
+>MyComponent : StatelessComponent
+>() => null as any : { (): any; defaultProps: { color: "red"; }; }
+>null as any : any
+>null : null
+
+MyComponent.defaultProps = {
+>MyComponent.defaultProps = { color: "red"} : { color: "red"; }
+>MyComponent.defaultProps : Partial
+>MyComponent : StatelessComponent
+>defaultProps : Partial
+>{ color: "red"} : { color: "red"; }
+
+ color: "red"
+>color : "red"
+>"red" : "red"
+
+};
+
diff --git a/tests/baselines/reference/expandoFunctionContextualTypesJs.symbols b/tests/baselines/reference/expandoFunctionContextualTypesJs.symbols
new file mode 100644
index 00000000000..5ef7acd5366
--- /dev/null
+++ b/tests/baselines/reference/expandoFunctionContextualTypesJs.symbols
@@ -0,0 +1,86 @@
+=== tests/cases/compiler/input.js ===
+/** @typedef {{ color: "red" | "blue" }} MyComponentProps */
+
+/**
+ * @template P
+ * @typedef {{ (): any; defaultProps?: Partial }} StatelessComponent */
+
+ /**
+ * @type {StatelessComponent}
+ */
+const MyComponent = () => /* @type {any} */(null);
+>MyComponent : Symbol(MyComponent, Decl(input.js, 9, 5))
+
+MyComponent.defaultProps = {
+>MyComponent.defaultProps : Symbol(defaultProps, Decl(input.js, 4, 23))
+>MyComponent : Symbol(MyComponent, Decl(input.js, 9, 5))
+>defaultProps : Symbol(defaultProps, Decl(input.js, 4, 23))
+
+ color: "red"
+>color : Symbol(color, Decl(input.js, 11, 28))
+
+};
+
+const MyComponent2 = () => null;
+>MyComponent2 : Symbol(MyComponent2, Decl(input.js, 15, 5))
+
+/**
+ * @type {MyComponentProps}
+ */
+MyComponent2.defaultProps = {
+>MyComponent2.defaultProps : Symbol(MyComponent2.defaultProps, Decl(input.js, 15, 32))
+>MyComponent2 : Symbol(MyComponent2, Decl(input.js, 15, 5))
+>defaultProps : Symbol(MyComponent2.defaultProps, Decl(input.js, 15, 32))
+
+ color: "red"
+>color : Symbol(color, Decl(input.js, 20, 29))
+}
+
+/**
+ * @type {StatelessComponent}
+ */
+const check = MyComponent2;
+>check : Symbol(check, Decl(input.js, 27, 5))
+>MyComponent2 : Symbol(MyComponent2, Decl(input.js, 15, 5))
+
+/**
+ *
+ * @param {{ props: MyComponentProps }} p
+ */
+function expectLiteral(p) {}
+>expectLiteral : Symbol(expectLiteral, Decl(input.js, 27, 27))
+>p : Symbol(p, Decl(input.js, 33, 23))
+
+function foo() {
+>foo : Symbol(foo, Decl(input.js, 33, 28))
+
+ /**
+ * @type {MyComponentProps}
+ */
+ this.props = { color: "red" };
+>props : Symbol(foo.props, Decl(input.js, 35, 16))
+>color : Symbol(color, Decl(input.js, 39, 18))
+
+ expectLiteral(this);
+>expectLiteral : Symbol(expectLiteral, Decl(input.js, 27, 27))
+}
+
+/**
+ * @type {MyComponentProps}
+ */
+module.exports = {
+>module.exports : Symbol("tests/cases/compiler/input", Decl(input.js, 0, 0))
+>module : Symbol(export=, Decl(input.js, 42, 1))
+>exports : Symbol(export=, Decl(input.js, 42, 1))
+
+ color: "red"
+>color : Symbol(color, Decl(input.js, 47, 18))
+}
+
+expectLiteral({ props: module.exports });
+>expectLiteral : Symbol(expectLiteral, Decl(input.js, 27, 27))
+>props : Symbol(props, Decl(input.js, 51, 15))
+>module.exports : Symbol("tests/cases/compiler/input", Decl(input.js, 0, 0))
+>module : Symbol(module, Decl(input.js, 42, 1), Decl(input.js, 51, 22))
+>exports : Symbol("tests/cases/compiler/input", Decl(input.js, 0, 0))
+
diff --git a/tests/baselines/reference/expandoFunctionContextualTypesJs.types b/tests/baselines/reference/expandoFunctionContextualTypesJs.types
new file mode 100644
index 00000000000..081c57c0dac
--- /dev/null
+++ b/tests/baselines/reference/expandoFunctionContextualTypesJs.types
@@ -0,0 +1,109 @@
+=== tests/cases/compiler/input.js ===
+/** @typedef {{ color: "red" | "blue" }} MyComponentProps */
+
+/**
+ * @template P
+ * @typedef {{ (): any; defaultProps?: Partial }} StatelessComponent */
+
+ /**
+ * @type {StatelessComponent}
+ */
+const MyComponent = () => /* @type {any} */(null);
+>MyComponent : { (): any; defaultProps?: Partial<{ color: "red" | "blue"; }>; }
+>() => /* @type {any} */(null) : { (): any; defaultProps: { color: "red"; }; }
+>(null) : null
+>null : null
+
+MyComponent.defaultProps = {
+>MyComponent.defaultProps = { color: "red"} : { color: "red"; }
+>MyComponent.defaultProps : Partial<{ color: "red" | "blue"; }>
+>MyComponent : { (): any; defaultProps?: Partial<{ color: "red" | "blue"; }>; }
+>defaultProps : Partial<{ color: "red" | "blue"; }>
+>{ color: "red"} : { color: "red"; }
+
+ color: "red"
+>color : "red"
+>"red" : "red"
+
+};
+
+const MyComponent2 = () => null;
+>MyComponent2 : { (): any; defaultProps: { color: "red" | "blue"; }; }
+>() => null : { (): any; defaultProps: { color: "red" | "blue"; }; }
+>null : null
+
+/**
+ * @type {MyComponentProps}
+ */
+MyComponent2.defaultProps = {
+>MyComponent2.defaultProps = { color: "red"} : { color: "red"; }
+>MyComponent2.defaultProps : { color: "red" | "blue"; }
+>MyComponent2 : { (): any; defaultProps: { color: "red" | "blue"; }; }
+>defaultProps : { color: "red" | "blue"; }
+>{ color: "red"} : { color: "red"; }
+
+ color: "red"
+>color : "red"
+>"red" : "red"
+}
+
+/**
+ * @type {StatelessComponent}
+ */
+const check = MyComponent2;
+>check : { (): any; defaultProps?: Partial<{ color: "red" | "blue"; }>; }
+>MyComponent2 : { (): any; defaultProps: { color: "red" | "blue"; }; }
+
+/**
+ *
+ * @param {{ props: MyComponentProps }} p
+ */
+function expectLiteral(p) {}
+>expectLiteral : (p: { props: { color: "red" | "blue"; }; }) => void
+>p : { props: { color: "red" | "blue"; }; }
+
+function foo() {
+>foo : typeof foo
+
+ /**
+ * @type {MyComponentProps}
+ */
+ this.props = { color: "red" };
+>this.props = { color: "red" } : { color: "red"; }
+>this.props : any
+>this : any
+>props : any
+>{ color: "red" } : { color: "red"; }
+>color : "red"
+>"red" : "red"
+
+ expectLiteral(this);
+>expectLiteral(this) : void
+>expectLiteral : (p: { props: { color: "red" | "blue"; }; }) => void
+>this : any
+}
+
+/**
+ * @type {MyComponentProps}
+ */
+module.exports = {
+>module.exports = { color: "red"} : { color: "red" | "blue"; }
+>module.exports : { color: "red" | "blue"; }
+>module : { "tests/cases/compiler/input": { color: "red" | "blue"; }; }
+>exports : { color: "red" | "blue"; }
+>{ color: "red"} : { color: "red"; }
+
+ color: "red"
+>color : "red"
+>"red" : "red"
+}
+
+expectLiteral({ props: module.exports });
+>expectLiteral({ props: module.exports }) : void
+>expectLiteral : (p: { props: { color: "red" | "blue"; }; }) => void
+>{ props: module.exports } : { props: { color: "red" | "blue"; }; }
+>props : { color: "red" | "blue"; }
+>module.exports : { color: "red" | "blue"; }
+>module : { "tests/cases/compiler/input": { color: "red" | "blue"; }; }
+>exports : { color: "red" | "blue"; }
+
diff --git a/tests/baselines/reference/moduleExportAssignment2.symbols b/tests/baselines/reference/moduleExportAssignment2.symbols
index 25e80647d89..eaafdbefb1f 100644
--- a/tests/baselines/reference/moduleExportAssignment2.symbols
+++ b/tests/baselines/reference/moduleExportAssignment2.symbols
@@ -9,7 +9,7 @@ var npm = module.exports = function (tree) {
module.exports.asReadInstalled = function (tree) {
>module.exports.asReadInstalled : Symbol(asReadInstalled, Decl(npm.js, 1, 1))
>module.exports : Symbol(asReadInstalled, Decl(npm.js, 1, 1))
->module : Symbol(module, Decl(npm.js, 0, 9))
+>module : Symbol(module, Decl(npm.js, 0, 9), Decl(npm.js, 3, 13))
>exports : Symbol("tests/cases/conformance/salsa/npm", Decl(npm.js, 0, 0))
>asReadInstalled : Symbol(asReadInstalled, Decl(npm.js, 1, 1))
>tree : Symbol(tree, Decl(npm.js, 2, 43))
@@ -20,7 +20,7 @@ module.exports.asReadInstalled = function (tree) {
module.exports(tree)
>module.exports : Symbol("tests/cases/conformance/salsa/npm", Decl(npm.js, 0, 0))
->module : Symbol(module, Decl(npm.js, 3, 13))
+>module : Symbol(module, Decl(npm.js, 0, 9), Decl(npm.js, 3, 13))
>exports : Symbol("tests/cases/conformance/salsa/npm", Decl(npm.js, 0, 0))
>tree : Symbol(tree, Decl(npm.js, 2, 43))
}
diff --git a/tests/cases/compiler/expandoFunctionContextualTypes.ts b/tests/cases/compiler/expandoFunctionContextualTypes.ts
new file mode 100644
index 00000000000..ae624549252
--- /dev/null
+++ b/tests/cases/compiler/expandoFunctionContextualTypes.ts
@@ -0,0 +1,14 @@
+interface MyComponentProps {
+ color: "red" | "blue"
+}
+
+interface StatelessComponent {
+ (): any;
+ defaultProps?: Partial
;
+}
+
+const MyComponent: StatelessComponent = () => null as any;
+
+MyComponent.defaultProps = {
+ color: "red"
+};
diff --git a/tests/cases/compiler/expandoFunctionContextualTypesJs.ts b/tests/cases/compiler/expandoFunctionContextualTypesJs.ts
new file mode 100644
index 00000000000..e69f3dc79c4
--- /dev/null
+++ b/tests/cases/compiler/expandoFunctionContextualTypesJs.ts
@@ -0,0 +1,57 @@
+// @allowJs: true
+// @checkJs: true
+// @noEmit: true
+// @filename: input.js
+
+/** @typedef {{ color: "red" | "blue" }} MyComponentProps */
+
+/**
+ * @template P
+ * @typedef {{ (): any; defaultProps?: Partial }} StatelessComponent */
+
+ /**
+ * @type {StatelessComponent}
+ */
+const MyComponent = () => /* @type {any} */(null);
+
+MyComponent.defaultProps = {
+ color: "red"
+};
+
+const MyComponent2 = () => null;
+
+/**
+ * @type {MyComponentProps}
+ */
+MyComponent2.defaultProps = {
+ color: "red"
+}
+
+/**
+ * @type {StatelessComponent}
+ */
+const check = MyComponent2;
+
+/**
+ *
+ * @param {{ props: MyComponentProps }} p
+ */
+function expectLiteral(p) {}
+
+function foo() {
+ /**
+ * @type {MyComponentProps}
+ */
+ this.props = { color: "red" };
+
+ expectLiteral(this);
+}
+
+/**
+ * @type {MyComponentProps}
+ */
+module.exports = {
+ color: "red"
+}
+
+expectLiteral({ props: module.exports });