fix(45233): allow type assertion in ExportAssignment with JSDoc type definition (#45342)

This commit is contained in:
Oleksandr T
2021-08-14 02:32:23 +03:00
committed by GitHub
parent dfd84ec0b2
commit 5b3072f687
34 changed files with 794 additions and 2 deletions

View File

@@ -9094,7 +9094,7 @@ namespace ts {
}
let type: Type;
if (declaration.kind === SyntaxKind.ExportAssignment) {
type = widenTypeForVariableLikeDeclaration(checkExpressionCached((declaration as ExportAssignment).expression), declaration);
type = widenTypeForVariableLikeDeclaration(tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionCached((declaration as ExportAssignment).expression), declaration);
}
else if (
isBinaryExpression(declaration) ||
@@ -9350,6 +9350,7 @@ namespace ts {
if (!links.type) {
const targetSymbol = resolveAlias(symbol);
const exportSymbol = symbol.declarations && getTargetOfAliasDeclaration(getDeclarationOfAliasSymbol(symbol)!, /*dontResolveAlias*/ true);
const declaredType = firstDefined(exportSymbol?.declarations, d => isExportAssignment(d) ? tryGetTypeFromEffectiveTypeNode(d) : undefined);
// It only makes sense to get the type of a value symbol. If the result of resolving
// the alias is not a value, then it has no type. To get the type associated with a
// type symbol, call getDeclaredTypeOfSymbol.
@@ -9357,6 +9358,7 @@ namespace ts {
// up recursively calling getTypeOfAlias, causing a stack overflow.
links.type = exportSymbol?.declarations && isDuplicatedCommonJSExport(exportSymbol.declarations) && symbol.declarations!.length ? getFlowTypeFromCommonJSExport(exportSymbol)
: isDuplicatedCommonJSExport(symbol.declarations) ? autoType
: declaredType ? declaredType
: targetSymbol.flags & SymbolFlags.Value ? getTypeOfSymbol(targetSymbol)
: errorType;
}
@@ -38863,11 +38865,16 @@ namespace ts {
if (!checkGrammarDecoratorsAndModifiers(node) && hasEffectiveModifiers(node)) {
grammarErrorOnFirstToken(node, Diagnostics.An_export_assignment_cannot_have_modifiers);
}
const typeAnnotationNode = getEffectiveTypeAnnotationNode(node);
if (typeAnnotationNode) {
checkTypeAssignableTo(checkExpressionCached(node.expression), getTypeFromTypeNode(typeAnnotationNode), node.expression);
}
if (node.expression.kind === SyntaxKind.Identifier) {
const id = node.expression as Identifier;
const sym = resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, node);
if (sym) {
markAliasReferenced(sym, id);
// If not a value, we're interpreting the identifier as a type export, along the lines of (`export { Id as default }`)
const target = sym.flags & SymbolFlags.Alias ? resolveAlias(sym) : sym;

View File

@@ -0,0 +1,23 @@
tests/cases/compiler/a.js(8,18): error TS2322: Type '{ c: boolean; }' is not assignable to type 'Foo'.
Object literal may only specify known properties, and 'c' does not exist in type 'Foo'.
==== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment1.js (0 errors) ====
==== tests/cases/compiler/a.js (1 errors) ====
/**
* @typedef {Object} Foo
* @property {boolean} a
* @property {boolean} b
*/
/** @type {Foo} */
export default { c: false };
~~~~~~~~
!!! error TS2322: Type '{ c: boolean; }' is not assignable to type 'Foo'.
!!! error TS2322: Object literal may only specify known properties, and 'c' does not exist in type 'Foo'.
==== tests/cases/compiler/b.js (0 errors) ====
import a from "./a";
a;

View File

@@ -0,0 +1,35 @@
//// [tests/cases/compiler/checkJsdocTypeTagOnExportAssignment1.ts] ////
//// [checkJsdocTypeTagOnExportAssignment1.js]
//// [a.js]
/**
* @typedef {Object} Foo
* @property {boolean} a
* @property {boolean} b
*/
/** @type {Foo} */
export default { c: false };
//// [b.js]
import a from "./a";
a;
//// [checkJsdocTypeTagOnExportAssignment1.js]
//// [a.js]
"use strict";
/**
* @typedef {Object} Foo
* @property {boolean} a
* @property {boolean} b
*/
exports.__esModule = true;
/** @type {Foo} */
exports["default"] = { c: false };
//// [b.js]
"use strict";
exports.__esModule = true;
var a_1 = require("./a");
a_1["default"];

View File

@@ -0,0 +1,20 @@
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment1.js ===
No type information for this code.=== tests/cases/compiler/a.js ===
/**
* @typedef {Object} Foo
* @property {boolean} a
* @property {boolean} b
*/
/** @type {Foo} */
export default { c: false };
>c : Symbol(c, Decl(a.js, 7, 16))
=== tests/cases/compiler/b.js ===
import a from "./a";
>a : Symbol(a, Decl(b.js, 0, 6))
a;
>a : Symbol(a, Decl(b.js, 0, 6))

View File

@@ -0,0 +1,22 @@
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment1.js ===
No type information for this code.=== tests/cases/compiler/a.js ===
/**
* @typedef {Object} Foo
* @property {boolean} a
* @property {boolean} b
*/
/** @type {Foo} */
export default { c: false };
>{ c: false } : { c: boolean; }
>c : boolean
>false : false
=== tests/cases/compiler/b.js ===
import a from "./a";
>a : import("tests/cases/compiler/a").Foo
a;
>a : import("tests/cases/compiler/a").Foo

View File

@@ -0,0 +1,23 @@
tests/cases/compiler/b.js(2,18): error TS2322: Type '{ c: boolean; }' is not assignable to type 'Foo'.
Object literal may only specify known properties, and 'c' does not exist in type 'Foo'.
==== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment2.js (0 errors) ====
==== tests/cases/compiler/a.ts (0 errors) ====
export interface Foo {
a: number;
b: number;
}
==== tests/cases/compiler/b.js (1 errors) ====
/** @type {import("./a").Foo} */
export default { c: false };
~~~~~~~~
!!! error TS2322: Type '{ c: boolean; }' is not assignable to type 'Foo'.
!!! error TS2322: Object literal may only specify known properties, and 'c' does not exist in type 'Foo'.
==== tests/cases/compiler/c.js (0 errors) ====
import b from "./b";
b;

View File

@@ -0,0 +1,33 @@
//// [tests/cases/compiler/checkJsdocTypeTagOnExportAssignment2.ts] ////
//// [checkJsdocTypeTagOnExportAssignment2.js]
//// [a.ts]
export interface Foo {
a: number;
b: number;
}
//// [b.js]
/** @type {import("./a").Foo} */
export default { c: false };
//// [c.js]
import b from "./b";
b;
//// [checkJsdocTypeTagOnExportAssignment2.js]
//// [a.js]
"use strict";
exports.__esModule = true;
//// [b.js]
"use strict";
exports.__esModule = true;
/** @type {import("./a").Foo} */
exports["default"] = { c: false };
//// [c.js]
"use strict";
exports.__esModule = true;
var b_1 = require("./b");
b_1["default"];

View File

@@ -0,0 +1,25 @@
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment2.js ===
No type information for this code.=== tests/cases/compiler/a.ts ===
export interface Foo {
>Foo : Symbol(Foo, Decl(a.ts, 0, 0))
a: number;
>a : Symbol(Foo.a, Decl(a.ts, 0, 22))
b: number;
>b : Symbol(Foo.b, Decl(a.ts, 1, 14))
}
=== tests/cases/compiler/b.js ===
/** @type {import("./a").Foo} */
export default { c: false };
>c : Symbol(c, Decl(b.js, 1, 16))
=== tests/cases/compiler/c.js ===
import b from "./b";
>b : Symbol(b, Decl(c.js, 0, 6))
b;
>b : Symbol(b, Decl(c.js, 0, 6))

View File

@@ -0,0 +1,25 @@
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment2.js ===
No type information for this code.=== tests/cases/compiler/a.ts ===
export interface Foo {
a: number;
>a : number
b: number;
>b : number
}
=== tests/cases/compiler/b.js ===
/** @type {import("./a").Foo} */
export default { c: false };
>{ c: false } : { c: boolean; }
>c : boolean
>false : false
=== tests/cases/compiler/c.js ===
import b from "./b";
>b : import("tests/cases/compiler/a").Foo
b;
>b : import("tests/cases/compiler/a").Foo

View File

@@ -0,0 +1,23 @@
tests/cases/compiler/a.js(10,16): error TS2739: Type '{ c: number; }' is missing the following properties from type 'Foo': a, b
==== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment3.js (0 errors) ====
==== tests/cases/compiler/a.js (1 errors) ====
/**
* @typedef {Object} Foo
* @property {boolean} a
* @property {boolean} b
*/
const bar = { c: 1 };
/** @type {Foo} */
export default bar;
~~~
!!! error TS2739: Type '{ c: number; }' is missing the following properties from type 'Foo': a, b
==== tests/cases/compiler/b.js (0 errors) ====
import a from "./a";
a;

View File

@@ -0,0 +1,38 @@
//// [tests/cases/compiler/checkJsdocTypeTagOnExportAssignment3.ts] ////
//// [checkJsdocTypeTagOnExportAssignment3.js]
//// [a.js]
/**
* @typedef {Object} Foo
* @property {boolean} a
* @property {boolean} b
*/
const bar = { c: 1 };
/** @type {Foo} */
export default bar;
//// [b.js]
import a from "./a";
a;
//// [checkJsdocTypeTagOnExportAssignment3.js]
//// [a.js]
"use strict";
/**
* @typedef {Object} Foo
* @property {boolean} a
* @property {boolean} b
*/
exports.__esModule = true;
var bar = { c: 1 };
/** @type {Foo} */
exports["default"] = bar;
//// [b.js]
"use strict";
exports.__esModule = true;
var a_1 = require("./a");
a_1["default"];

View File

@@ -0,0 +1,24 @@
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment3.js ===
No type information for this code.=== tests/cases/compiler/a.js ===
/**
* @typedef {Object} Foo
* @property {boolean} a
* @property {boolean} b
*/
const bar = { c: 1 };
>bar : Symbol(bar, Decl(a.js, 6, 5))
>c : Symbol(c, Decl(a.js, 6, 13))
/** @type {Foo} */
export default bar;
>bar : Symbol(bar, Decl(a.js, 6, 5))
=== tests/cases/compiler/b.js ===
import a from "./a";
>a : Symbol(a, Decl(b.js, 0, 6))
a;
>a : Symbol(a, Decl(b.js, 0, 6))

View File

@@ -0,0 +1,26 @@
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment3.js ===
No type information for this code.=== tests/cases/compiler/a.js ===
/**
* @typedef {Object} Foo
* @property {boolean} a
* @property {boolean} b
*/
const bar = { c: 1 };
>bar : { c: number; }
>{ c: 1 } : { c: number; }
>c : number
>1 : 1
/** @type {Foo} */
export default bar;
>bar : { c: number; }
=== tests/cases/compiler/b.js ===
import a from "./a";
>a : import("tests/cases/compiler/a").Foo
a;
>a : import("tests/cases/compiler/a").Foo

View File

@@ -0,0 +1,16 @@
tests/cases/compiler/a.js(6,16): error TS2322: Type 'string' is not assignable to type 'number'.
==== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment4.js (0 errors) ====
==== tests/cases/compiler/a.js (1 errors) ====
/**
* @typedef {number} Foo
*/
/** @type {Foo} */
export default "";
~~
!!! error TS2322: Type 'string' is not assignable to type 'number'.

View File

@@ -0,0 +1,23 @@
//// [tests/cases/compiler/checkJsdocTypeTagOnExportAssignment4.ts] ////
//// [checkJsdocTypeTagOnExportAssignment4.js]
//// [a.js]
/**
* @typedef {number} Foo
*/
/** @type {Foo} */
export default "";
//// [checkJsdocTypeTagOnExportAssignment4.js]
//// [a.js]
"use strict";
/**
* @typedef {number} Foo
*/
exports.__esModule = true;
/** @type {Foo} */
exports["default"] = "";

View File

@@ -0,0 +1,12 @@
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment4.js ===
No type information for this code.=== tests/cases/compiler/a.js ===
/**
No type information for this code. * @typedef {number} Foo
No type information for this code. */
No type information for this code.
No type information for this code./** @type {Foo} */
No type information for this code.export default "";
No type information for this code.
No type information for this code.
No type information for this code.

View File

@@ -0,0 +1,12 @@
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment4.js ===
No type information for this code.=== tests/cases/compiler/a.js ===
/**
No type information for this code. * @typedef {number} Foo
No type information for this code. */
No type information for this code.
No type information for this code./** @type {Foo} */
No type information for this code.export default "";
No type information for this code.
No type information for this code.
No type information for this code.

View File

@@ -0,0 +1,35 @@
//// [tests/cases/compiler/checkJsdocTypeTagOnExportAssignment5.ts] ////
//// [checkJsdocTypeTagOnExportAssignment5.js]
//// [a.js]
/**
* @typedef {Object} Foo
* @property {number} a
* @property {number} b
*/
/** @type {Foo} */
export default { a: 1, b: 1 };
//// [b.js]
import a from "./a";
a;
//// [checkJsdocTypeTagOnExportAssignment5.js]
//// [a.js]
"use strict";
/**
* @typedef {Object} Foo
* @property {number} a
* @property {number} b
*/
exports.__esModule = true;
/** @type {Foo} */
exports["default"] = { a: 1, b: 1 };
//// [b.js]
"use strict";
exports.__esModule = true;
var a_1 = require("./a");
a_1["default"];

View File

@@ -0,0 +1,21 @@
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment5.js ===
No type information for this code.=== tests/cases/compiler/a.js ===
/**
* @typedef {Object} Foo
* @property {number} a
* @property {number} b
*/
/** @type {Foo} */
export default { a: 1, b: 1 };
>a : Symbol(a, Decl(a.js, 7, 16))
>b : Symbol(b, Decl(a.js, 7, 22))
=== tests/cases/compiler/b.js ===
import a from "./a";
>a : Symbol(a, Decl(b.js, 0, 6))
a;
>a : Symbol(a, Decl(b.js, 0, 6))

View File

@@ -0,0 +1,24 @@
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment5.js ===
No type information for this code.=== tests/cases/compiler/a.js ===
/**
* @typedef {Object} Foo
* @property {number} a
* @property {number} b
*/
/** @type {Foo} */
export default { a: 1, b: 1 };
>{ a: 1, b: 1 } : { a: number; b: number; }
>a : number
>1 : 1
>b : number
>1 : 1
=== tests/cases/compiler/b.js ===
import a from "./a";
>a : import("tests/cases/compiler/a").Foo
a;
>a : import("tests/cases/compiler/a").Foo

View File

@@ -0,0 +1,23 @@
tests/cases/compiler/a.js(8,30): error TS2322: Type '{ a: number; b: number; c: number; }' is not assignable to type 'Foo'.
Object literal may only specify known properties, and 'c' does not exist in type 'Foo'.
==== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment6.js (0 errors) ====
==== tests/cases/compiler/a.js (1 errors) ====
/**
* @typedef {Object} Foo
* @property {number} a
* @property {number} b
*/
/** @type {Foo} */
export default { a: 1, b: 1, c: 1 };
~~~~
!!! error TS2322: Type '{ a: number; b: number; c: number; }' is not assignable to type 'Foo'.
!!! error TS2322: Object literal may only specify known properties, and 'c' does not exist in type 'Foo'.
==== tests/cases/compiler/b.js (0 errors) ====
import a from "./a";
a;

View File

@@ -0,0 +1,35 @@
//// [tests/cases/compiler/checkJsdocTypeTagOnExportAssignment6.ts] ////
//// [checkJsdocTypeTagOnExportAssignment6.js]
//// [a.js]
/**
* @typedef {Object} Foo
* @property {number} a
* @property {number} b
*/
/** @type {Foo} */
export default { a: 1, b: 1, c: 1 };
//// [b.js]
import a from "./a";
a;
//// [checkJsdocTypeTagOnExportAssignment6.js]
//// [a.js]
"use strict";
/**
* @typedef {Object} Foo
* @property {number} a
* @property {number} b
*/
exports.__esModule = true;
/** @type {Foo} */
exports["default"] = { a: 1, b: 1, c: 1 };
//// [b.js]
"use strict";
exports.__esModule = true;
var a_1 = require("./a");
a_1["default"];

View File

@@ -0,0 +1,22 @@
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment6.js ===
No type information for this code.=== tests/cases/compiler/a.js ===
/**
* @typedef {Object} Foo
* @property {number} a
* @property {number} b
*/
/** @type {Foo} */
export default { a: 1, b: 1, c: 1 };
>a : Symbol(a, Decl(a.js, 7, 16))
>b : Symbol(b, Decl(a.js, 7, 22))
>c : Symbol(c, Decl(a.js, 7, 28))
=== tests/cases/compiler/b.js ===
import a from "./a";
>a : Symbol(a, Decl(b.js, 0, 6))
a;
>a : Symbol(a, Decl(b.js, 0, 6))

View File

@@ -0,0 +1,26 @@
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment6.js ===
No type information for this code.=== tests/cases/compiler/a.js ===
/**
* @typedef {Object} Foo
* @property {number} a
* @property {number} b
*/
/** @type {Foo} */
export default { a: 1, b: 1, c: 1 };
>{ a: 1, b: 1, c: 1 } : { a: number; b: number; c: number; }
>a : number
>1 : 1
>b : number
>1 : 1
>c : number
>1 : 1
=== tests/cases/compiler/b.js ===
import a from "./a";
>a : import("tests/cases/compiler/a").Foo
a;
>a : import("tests/cases/compiler/a").Foo

View File

@@ -0,0 +1,38 @@
//// [tests/cases/compiler/checkJsdocTypeTagOnExportAssignment7.ts] ////
//// [checkJsdocTypeTagOnExportAssignment7.js]
//// [a.js]
/**
* @typedef {Object} Foo
* @property {number} a
* @property {number} b
*/
const abc = { a: 1, b: 1, c: 1 };
/** @type {Foo} */
export default abc;
//// [b.js]
import a from "./a";
a;
//// [checkJsdocTypeTagOnExportAssignment7.js]
//// [a.js]
"use strict";
/**
* @typedef {Object} Foo
* @property {number} a
* @property {number} b
*/
exports.__esModule = true;
var abc = { a: 1, b: 1, c: 1 };
/** @type {Foo} */
exports["default"] = abc;
//// [b.js]
"use strict";
exports.__esModule = true;
var a_1 = require("./a");
a_1["default"];

View File

@@ -0,0 +1,26 @@
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment7.js ===
No type information for this code.=== tests/cases/compiler/a.js ===
/**
* @typedef {Object} Foo
* @property {number} a
* @property {number} b
*/
const abc = { a: 1, b: 1, c: 1 };
>abc : Symbol(abc, Decl(a.js, 6, 5))
>a : Symbol(a, Decl(a.js, 6, 13))
>b : Symbol(b, Decl(a.js, 6, 19))
>c : Symbol(c, Decl(a.js, 6, 25))
/** @type {Foo} */
export default abc;
>abc : Symbol(abc, Decl(a.js, 6, 5))
=== tests/cases/compiler/b.js ===
import a from "./a";
>a : Symbol(a, Decl(b.js, 0, 6))
a;
>a : Symbol(a, Decl(b.js, 0, 6))

View File

@@ -0,0 +1,30 @@
=== tests/cases/compiler/checkJsdocTypeTagOnExportAssignment7.js ===
No type information for this code.=== tests/cases/compiler/a.js ===
/**
* @typedef {Object} Foo
* @property {number} a
* @property {number} b
*/
const abc = { a: 1, b: 1, c: 1 };
>abc : { a: number; b: number; c: number; }
>{ a: 1, b: 1, c: 1 } : { a: number; b: number; c: number; }
>a : number
>1 : 1
>b : number
>1 : 1
>c : number
>1 : 1
/** @type {Foo} */
export default abc;
>abc : { a: number; b: number; c: number; }
=== tests/cases/compiler/b.js ===
import a from "./a";
>a : import("tests/cases/compiler/a").Foo
a;
>a : import("tests/cases/compiler/a").Foo

View File

@@ -0,0 +1,18 @@
// @allowJs: true
// @checkJs: true
// @outDir: ./out
// @filename: checkJsdocTypeTagOnExportAssignment1.js
// @Filename: a.js
/**
* @typedef {Object} Foo
* @property {boolean} a
* @property {boolean} b
*/
/** @type {Foo} */
export default { c: false };
// @Filename: b.js
import a from "./a";
a;

View File

@@ -0,0 +1,18 @@
// @allowJs: true
// @checkJs: true
// @outDir: ./out
// @filename: checkJsdocTypeTagOnExportAssignment2.js
// @Filename: a.ts
export interface Foo {
a: number;
b: number;
}
// @Filename: b.js
/** @type {import("./a").Foo} */
export default { c: false };
// @Filename: c.js
import b from "./b";
b;

View File

@@ -0,0 +1,20 @@
// @allowJs: true
// @checkJs: true
// @outDir: ./out
// @filename: checkJsdocTypeTagOnExportAssignment3.js
// @Filename: a.js
/**
* @typedef {Object} Foo
* @property {boolean} a
* @property {boolean} b
*/
const bar = { c: 1 };
/** @type {Foo} */
export default bar;
// @Filename: b.js
import a from "./a";
a;

View File

@@ -0,0 +1,13 @@
// @allowJs: true
// @checkJs: true
// @outDir: ./out
// @filename: checkJsdocTypeTagOnExportAssignment4.js
// @Filename: a.js
/**
* @typedef {number} Foo
*/
/** @type {Foo} */
export default "";

View File

@@ -0,0 +1,18 @@
// @allowJs: true
// @checkJs: true
// @outDir: ./out
// @filename: checkJsdocTypeTagOnExportAssignment5.js
// @Filename: a.js
/**
* @typedef {Object} Foo
* @property {number} a
* @property {number} b
*/
/** @type {Foo} */
export default { a: 1, b: 1 };
// @Filename: b.js
import a from "./a";
a;

View File

@@ -0,0 +1,18 @@
// @allowJs: true
// @checkJs: true
// @outDir: ./out
// @filename: checkJsdocTypeTagOnExportAssignment6.js
// @Filename: a.js
/**
* @typedef {Object} Foo
* @property {number} a
* @property {number} b
*/
/** @type {Foo} */
export default { a: 1, b: 1, c: 1 };
// @Filename: b.js
import a from "./a";
a;

View File

@@ -0,0 +1,20 @@
// @allowJs: true
// @checkJs: true
// @outDir: ./out
// @filename: checkJsdocTypeTagOnExportAssignment7.js
// @Filename: a.js
/**
* @typedef {Object} Foo
* @property {number} a
* @property {number} b
*/
const abc = { a: 1, b: 1, c: 1 };
/** @type {Foo} */
export default abc;
// @Filename: b.js
import a from "./a";
a;