Skip to content

Commit 5b3072f

Browse files
authored
fix(45233): allow type assertion in ExportAssignment with JSDoc type definition (microsoft#45342)
1 parent dfd84ec commit 5b3072f

File tree

34 files changed

+794
-2
lines changed

34 files changed

+794
-2
lines changed

src/compiler/checker.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9094,7 +9094,7 @@ namespace ts {
90949094
}
90959095
let type: Type;
90969096
if (declaration.kind === SyntaxKind.ExportAssignment) {
9097-
type = widenTypeForVariableLikeDeclaration(checkExpressionCached((declaration as ExportAssignment).expression), declaration);
9097+
type = widenTypeForVariableLikeDeclaration(tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionCached((declaration as ExportAssignment).expression), declaration);
90989098
}
90999099
else if (
91009100
isBinaryExpression(declaration) ||
@@ -9350,13 +9350,15 @@ namespace ts {
93509350
if (!links.type) {
93519351
const targetSymbol = resolveAlias(symbol);
93529352
const exportSymbol = symbol.declarations && getTargetOfAliasDeclaration(getDeclarationOfAliasSymbol(symbol)!, /*dontResolveAlias*/ true);
9353+
const declaredType = firstDefined(exportSymbol?.declarations, d => isExportAssignment(d) ? tryGetTypeFromEffectiveTypeNode(d) : undefined);
93539354
// It only makes sense to get the type of a value symbol. If the result of resolving
93549355
// the alias is not a value, then it has no type. To get the type associated with a
93559356
// type symbol, call getDeclaredTypeOfSymbol.
93569357
// This check is important because without it, a call to getTypeOfSymbol could end
93579358
// up recursively calling getTypeOfAlias, causing a stack overflow.
93589359
links.type = exportSymbol?.declarations && isDuplicatedCommonJSExport(exportSymbol.declarations) && symbol.declarations!.length ? getFlowTypeFromCommonJSExport(exportSymbol)
93599360
: isDuplicatedCommonJSExport(symbol.declarations) ? autoType
9361+
: declaredType ? declaredType
93609362
: targetSymbol.flags & SymbolFlags.Value ? getTypeOfSymbol(targetSymbol)
93619363
: errorType;
93629364
}
@@ -38863,11 +38865,16 @@ namespace ts {
3886338865
if (!checkGrammarDecoratorsAndModifiers(node) && hasEffectiveModifiers(node)) {
3886438866
grammarErrorOnFirstToken(node, Diagnostics.An_export_assignment_cannot_have_modifiers);
3886538867
}
38868+
38869+
const typeAnnotationNode = getEffectiveTypeAnnotationNode(node);
38870+
if (typeAnnotationNode) {
38871+
checkTypeAssignableTo(checkExpressionCached(node.expression), getTypeFromTypeNode(typeAnnotationNode), node.expression);
38872+
}
38873+
3886638874
if (node.expression.kind === SyntaxKind.Identifier) {
3886738875
const id = node.expression as Identifier;
3886838876
const sym = resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, node);
3886938877
if (sym) {
38870-
3887138878
markAliasReferenced(sym, id);
3887238879
// If not a value, we're interpreting the identifier as a type export, along the lines of (`export { Id as default }`)
3887338880
const target = sym.flags & SymbolFlags.Alias ? resolveAlias(sym) : sym;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
tests/cases/compiler/a.js(8,18): error TS2322: Type '{ c: boolean; }' is not assignable to type 'Foo'.
2+
Object literal may only specify known properties, and 'c' does not exist in type 'Foo'.
3+
4+
5+
==== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment1.js (0 errors) ====
6+
7+
==== tests/cases/compiler/a.js (1 errors) ====
8+
/**
9+
* @typedef {Object} Foo
10+
* @property {boolean} a
11+
* @property {boolean} b
12+
*/
13+
14+
/** @type {Foo} */
15+
export default { c: false };
16+
~~~~~~~~
17+
!!! error TS2322: Type '{ c: boolean; }' is not assignable to type 'Foo'.
18+
!!! error TS2322: Object literal may only specify known properties, and 'c' does not exist in type 'Foo'.
19+
20+
==== tests/cases/compiler/b.js (0 errors) ====
21+
import a from "./a";
22+
a;
23+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//// [tests/cases/compiler/checkJsdocTypeTagOnExportAssignment1.ts] ////
2+
3+
//// [checkJsdocTypeTagOnExportAssignment1.js]
4+
5+
//// [a.js]
6+
/**
7+
* @typedef {Object} Foo
8+
* @property {boolean} a
9+
* @property {boolean} b
10+
*/
11+
12+
/** @type {Foo} */
13+
export default { c: false };
14+
15+
//// [b.js]
16+
import a from "./a";
17+
a;
18+
19+
20+
//// [checkJsdocTypeTagOnExportAssignment1.js]
21+
//// [a.js]
22+
"use strict";
23+
/**
24+
* @typedef {Object} Foo
25+
* @property {boolean} a
26+
* @property {boolean} b
27+
*/
28+
exports.__esModule = true;
29+
/** @type {Foo} */
30+
exports["default"] = { c: false };
31+
//// [b.js]
32+
"use strict";
33+
exports.__esModule = true;
34+
var a_1 = require("./a");
35+
a_1["default"];
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment1.js ===
2+
3+
No type information for this code.=== tests/cases/compiler/a.js ===
4+
/**
5+
* @typedef {Object} Foo
6+
* @property {boolean} a
7+
* @property {boolean} b
8+
*/
9+
10+
/** @type {Foo} */
11+
export default { c: false };
12+
>c : Symbol(c, Decl(a.js, 7, 16))
13+
14+
=== tests/cases/compiler/b.js ===
15+
import a from "./a";
16+
>a : Symbol(a, Decl(b.js, 0, 6))
17+
18+
a;
19+
>a : Symbol(a, Decl(b.js, 0, 6))
20+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment1.js ===
2+
3+
No type information for this code.=== tests/cases/compiler/a.js ===
4+
/**
5+
* @typedef {Object} Foo
6+
* @property {boolean} a
7+
* @property {boolean} b
8+
*/
9+
10+
/** @type {Foo} */
11+
export default { c: false };
12+
>{ c: false } : { c: boolean; }
13+
>c : boolean
14+
>false : false
15+
16+
=== tests/cases/compiler/b.js ===
17+
import a from "./a";
18+
>a : import("tests/cases/compiler/a").Foo
19+
20+
a;
21+
>a : import("tests/cases/compiler/a").Foo
22+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
tests/cases/compiler/b.js(2,18): error TS2322: Type '{ c: boolean; }' is not assignable to type 'Foo'.
2+
Object literal may only specify known properties, and 'c' does not exist in type 'Foo'.
3+
4+
5+
==== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment2.js (0 errors) ====
6+
7+
==== tests/cases/compiler/a.ts (0 errors) ====
8+
export interface Foo {
9+
a: number;
10+
b: number;
11+
}
12+
13+
==== tests/cases/compiler/b.js (1 errors) ====
14+
/** @type {import("./a").Foo} */
15+
export default { c: false };
16+
~~~~~~~~
17+
!!! error TS2322: Type '{ c: boolean; }' is not assignable to type 'Foo'.
18+
!!! error TS2322: Object literal may only specify known properties, and 'c' does not exist in type 'Foo'.
19+
20+
==== tests/cases/compiler/c.js (0 errors) ====
21+
import b from "./b";
22+
b;
23+
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//// [tests/cases/compiler/checkJsdocTypeTagOnExportAssignment2.ts] ////
2+
3+
//// [checkJsdocTypeTagOnExportAssignment2.js]
4+
5+
//// [a.ts]
6+
export interface Foo {
7+
a: number;
8+
b: number;
9+
}
10+
11+
//// [b.js]
12+
/** @type {import("./a").Foo} */
13+
export default { c: false };
14+
15+
//// [c.js]
16+
import b from "./b";
17+
b;
18+
19+
20+
//// [checkJsdocTypeTagOnExportAssignment2.js]
21+
//// [a.js]
22+
"use strict";
23+
exports.__esModule = true;
24+
//// [b.js]
25+
"use strict";
26+
exports.__esModule = true;
27+
/** @type {import("./a").Foo} */
28+
exports["default"] = { c: false };
29+
//// [c.js]
30+
"use strict";
31+
exports.__esModule = true;
32+
var b_1 = require("./b");
33+
b_1["default"];
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment2.js ===
2+
3+
No type information for this code.=== tests/cases/compiler/a.ts ===
4+
export interface Foo {
5+
>Foo : Symbol(Foo, Decl(a.ts, 0, 0))
6+
7+
a: number;
8+
>a : Symbol(Foo.a, Decl(a.ts, 0, 22))
9+
10+
b: number;
11+
>b : Symbol(Foo.b, Decl(a.ts, 1, 14))
12+
}
13+
14+
=== tests/cases/compiler/b.js ===
15+
/** @type {import("./a").Foo} */
16+
export default { c: false };
17+
>c : Symbol(c, Decl(b.js, 1, 16))
18+
19+
=== tests/cases/compiler/c.js ===
20+
import b from "./b";
21+
>b : Symbol(b, Decl(c.js, 0, 6))
22+
23+
b;
24+
>b : Symbol(b, Decl(c.js, 0, 6))
25+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment2.js ===
2+
3+
No type information for this code.=== tests/cases/compiler/a.ts ===
4+
export interface Foo {
5+
a: number;
6+
>a : number
7+
8+
b: number;
9+
>b : number
10+
}
11+
12+
=== tests/cases/compiler/b.js ===
13+
/** @type {import("./a").Foo} */
14+
export default { c: false };
15+
>{ c: false } : { c: boolean; }
16+
>c : boolean
17+
>false : false
18+
19+
=== tests/cases/compiler/c.js ===
20+
import b from "./b";
21+
>b : import("tests/cases/compiler/a").Foo
22+
23+
b;
24+
>b : import("tests/cases/compiler/a").Foo
25+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
tests/cases/compiler/a.js(10,16): error TS2739: Type '{ c: number; }' is missing the following properties from type 'Foo': a, b
2+
3+
4+
==== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment3.js (0 errors) ====
5+
6+
==== tests/cases/compiler/a.js (1 errors) ====
7+
/**
8+
* @typedef {Object} Foo
9+
* @property {boolean} a
10+
* @property {boolean} b
11+
*/
12+
13+
const bar = { c: 1 };
14+
15+
/** @type {Foo} */
16+
export default bar;
17+
~~~
18+
!!! error TS2739: Type '{ c: number; }' is missing the following properties from type 'Foo': a, b
19+
20+
==== tests/cases/compiler/b.js (0 errors) ====
21+
import a from "./a";
22+
a;
23+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//// [tests/cases/compiler/checkJsdocTypeTagOnExportAssignment3.ts] ////
2+
3+
//// [checkJsdocTypeTagOnExportAssignment3.js]
4+
5+
//// [a.js]
6+
/**
7+
* @typedef {Object} Foo
8+
* @property {boolean} a
9+
* @property {boolean} b
10+
*/
11+
12+
const bar = { c: 1 };
13+
14+
/** @type {Foo} */
15+
export default bar;
16+
17+
//// [b.js]
18+
import a from "./a";
19+
a;
20+
21+
22+
//// [checkJsdocTypeTagOnExportAssignment3.js]
23+
//// [a.js]
24+
"use strict";
25+
/**
26+
* @typedef {Object} Foo
27+
* @property {boolean} a
28+
* @property {boolean} b
29+
*/
30+
exports.__esModule = true;
31+
var bar = { c: 1 };
32+
/** @type {Foo} */
33+
exports["default"] = bar;
34+
//// [b.js]
35+
"use strict";
36+
exports.__esModule = true;
37+
var a_1 = require("./a");
38+
a_1["default"];
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment3.js ===
2+
3+
No type information for this code.=== tests/cases/compiler/a.js ===
4+
/**
5+
* @typedef {Object} Foo
6+
* @property {boolean} a
7+
* @property {boolean} b
8+
*/
9+
10+
const bar = { c: 1 };
11+
>bar : Symbol(bar, Decl(a.js, 6, 5))
12+
>c : Symbol(c, Decl(a.js, 6, 13))
13+
14+
/** @type {Foo} */
15+
export default bar;
16+
>bar : Symbol(bar, Decl(a.js, 6, 5))
17+
18+
=== tests/cases/compiler/b.js ===
19+
import a from "./a";
20+
>a : Symbol(a, Decl(b.js, 0, 6))
21+
22+
a;
23+
>a : Symbol(a, Decl(b.js, 0, 6))
24+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment3.js ===
2+
3+
No type information for this code.=== tests/cases/compiler/a.js ===
4+
/**
5+
* @typedef {Object} Foo
6+
* @property {boolean} a
7+
* @property {boolean} b
8+
*/
9+
10+
const bar = { c: 1 };
11+
>bar : { c: number; }
12+
>{ c: 1 } : { c: number; }
13+
>c : number
14+
>1 : 1
15+
16+
/** @type {Foo} */
17+
export default bar;
18+
>bar : { c: number; }
19+
20+
=== tests/cases/compiler/b.js ===
21+
import a from "./a";
22+
>a : import("tests/cases/compiler/a").Foo
23+
24+
a;
25+
>a : import("tests/cases/compiler/a").Foo
26+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
tests/cases/compiler/a.js(6,16): error TS2322: Type 'string' is not assignable to type 'number'.
2+
3+
4+
==== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment4.js (0 errors) ====
5+
6+
==== tests/cases/compiler/a.js (1 errors) ====
7+
/**
8+
* @typedef {number} Foo
9+
*/
10+
11+
/** @type {Foo} */
12+
export default "";
13+
~~
14+
!!! error TS2322: Type 'string' is not assignable to type 'number'.
15+
16+

0 commit comments

Comments
 (0)