Skip to content

Commit 1213c35

Browse files
authored
Add error when importing/exporting types in JS files (microsoft#49580)
* Add error when importing/exporting types in JS files * Ignore type-only imports, update other baselines * Clean up
1 parent 9f1983d commit 1213c35

10 files changed

+281
-117
lines changed

src/compiler/checker.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40828,6 +40828,42 @@ namespace ts {
4082840828
// otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export*
4082940829
// in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names).
4083040830
symbol = getMergedSymbol(symbol.exportSymbol || symbol);
40831+
40832+
// A type-only import/export will already have a grammar error in a JS file, so no need to issue more errors within
40833+
if (isInJSFile(node) && !(target.flags & SymbolFlags.Value) && !isTypeOnlyImportOrExportDeclaration(node)) {
40834+
const errorNode =
40835+
isImportOrExportSpecifier(node) ? node.propertyName || node.name :
40836+
isNamedDeclaration(node) ? node.name :
40837+
node;
40838+
40839+
Debug.assert(node.kind !== SyntaxKind.NamespaceExport);
40840+
if (node.kind === SyntaxKind.ExportSpecifier) {
40841+
const diag = error(errorNode, Diagnostics.Types_cannot_appear_in_export_declarations_in_JavaScript_files);
40842+
const alreadyExportedSymbol = getSourceFileOfNode(node).symbol?.exports?.get((node.propertyName || node.name).escapedText);
40843+
if (alreadyExportedSymbol === target) {
40844+
const exportingDeclaration = alreadyExportedSymbol.declarations?.find(isJSDocNode);
40845+
if (exportingDeclaration) {
40846+
addRelatedInfo(diag, createDiagnosticForNode(
40847+
exportingDeclaration,
40848+
Diagnostics._0_is_automatically_exported_here,
40849+
unescapeLeadingUnderscores(alreadyExportedSymbol.escapedName)));
40850+
}
40851+
}
40852+
}
40853+
else {
40854+
Debug.assert(node.kind !== SyntaxKind.VariableDeclaration);
40855+
const importDeclaration = findAncestor(node, or(isImportDeclaration, isImportEqualsDeclaration)) as ImportDeclaration | ImportEqualsDeclaration | undefined;
40856+
const moduleSpecifier = (importDeclaration && tryGetModuleSpecifierFromDeclaration(importDeclaration)?.text) ?? "...";
40857+
const importedIdentifier = unescapeLeadingUnderscores(isIdentifier(errorNode) ? errorNode.escapedText : symbol.escapedName);
40858+
error(
40859+
errorNode,
40860+
Diagnostics._0_is_a_type_and_cannot_be_imported_in_JavaScript_files_Use_1_in_a_JSDoc_type_annotation,
40861+
importedIdentifier,
40862+
`import("${moduleSpecifier}").${importedIdentifier}`);
40863+
}
40864+
return;
40865+
}
40866+
4083140867
const excludedMeanings =
4083240868
(symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue) ? SymbolFlags.Value : 0) |
4083340869
(symbol.flags & SymbolFlags.Type ? SymbolFlags.Type : 0) |

src/compiler/diagnosticMessages.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3498,7 +3498,7 @@
34983498
"'{0}' is an unused renaming of '{1}'. Did you intend to use it as a type annotation?": {
34993499
"category": "Error",
35003500
"code": 2842
3501-
},
3501+
},
35023502
"We can only write a type for '{0}' by adding a type for the entire parameter here.": {
35033503
"category": "Error",
35043504
"code": 2843
@@ -7405,5 +7405,17 @@
74057405
"A 'return' statement cannot be used inside a class static block.": {
74067406
"category": "Error",
74077407
"code": 18041
7408+
},
7409+
"'{0}' is a type and cannot be imported in JavaScript files. Use '{1}' in a JSDoc type annotation.": {
7410+
"category": "Error",
7411+
"code": 18042
7412+
},
7413+
"Types cannot appear in export declarations in JavaScript files.": {
7414+
"category": "Error",
7415+
"code": 18043
7416+
},
7417+
"'{0}' is automatically exported here.": {
7418+
"category": "Message",
7419+
"code": 18044
74087420
}
74097421
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/index.js(1,21): error TS18042: 'WriteFileOptions' is a type and cannot be imported in JavaScript files. Use 'import("fs").WriteFileOptions' in a JSDoc type annotation.
2+
/index.js(1,39): error TS18042: 'WriteFileOptions' is a type and cannot be imported in JavaScript files. Use 'import("fs").WriteFileOptions' in a JSDoc type annotation.
3+
/index.js(5,10): error TS18043: Types cannot appear in export declarations in JavaScript files.
4+
/index.js(6,10): error TS18043: Types cannot appear in export declarations in JavaScript files.
5+
/index.js(7,10): error TS18043: Types cannot appear in export declarations in JavaScript files.
6+
7+
8+
==== /node_modules/@types/node/index.d.ts (0 errors) ====
9+
declare module "fs" {
10+
export interface WriteFileOptions {}
11+
export function writeFile(path: string, data: any, options: WriteFileOptions, callback: (err: Error) => void): void;
12+
}
13+
14+
==== /index.js (5 errors) ====
15+
import { writeFile, WriteFileOptions, WriteFileOptions as OtherName } from "fs";
16+
~~~~~~~~~~~~~~~~
17+
!!! error TS18042: 'WriteFileOptions' is a type and cannot be imported in JavaScript files. Use 'import("fs").WriteFileOptions' in a JSDoc type annotation.
18+
~~~~~~~~~~~~~~~~
19+
!!! error TS18042: 'WriteFileOptions' is a type and cannot be imported in JavaScript files. Use 'import("fs").WriteFileOptions' in a JSDoc type annotation.
20+
21+
/** @typedef {{ x: any }} JSDocType */
22+
23+
export { JSDocType };
24+
~~~~~~~~~
25+
!!! error TS18043: Types cannot appear in export declarations in JavaScript files.
26+
!!! related TS18044 /index.js:3:5: 'JSDocType' is automatically exported here.
27+
export { JSDocType as ThisIsFine };
28+
~~~~~~~~~
29+
!!! error TS18043: Types cannot appear in export declarations in JavaScript files.
30+
!!! related TS18044 /index.js:3:5: 'JSDocType' is automatically exported here.
31+
export { WriteFileOptions };
32+
~~~~~~~~~~~~~~~~
33+
!!! error TS18043: Types cannot appear in export declarations in JavaScript files.
34+
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
=== /node_modules/@types/node/index.d.ts ===
2+
declare module "fs" {
3+
>"fs" : Symbol("fs", Decl(index.d.ts, 0, 0))
4+
5+
export interface WriteFileOptions {}
6+
>WriteFileOptions : Symbol(WriteFileOptions, Decl(index.d.ts, 0, 21))
7+
8+
export function writeFile(path: string, data: any, options: WriteFileOptions, callback: (err: Error) => void): void;
9+
>writeFile : Symbol(writeFile, Decl(index.d.ts, 1, 38))
10+
>path : Symbol(path, Decl(index.d.ts, 2, 28))
11+
>data : Symbol(data, Decl(index.d.ts, 2, 41))
12+
>options : Symbol(options, Decl(index.d.ts, 2, 52))
13+
>WriteFileOptions : Symbol(WriteFileOptions, Decl(index.d.ts, 0, 21))
14+
>callback : Symbol(callback, Decl(index.d.ts, 2, 79))
15+
>err : Symbol(err, Decl(index.d.ts, 2, 91))
16+
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
17+
}
18+
19+
=== /index.js ===
20+
import { writeFile, WriteFileOptions, WriteFileOptions as OtherName } from "fs";
21+
>writeFile : Symbol(writeFile, Decl(index.js, 0, 8))
22+
>WriteFileOptions : Symbol(WriteFileOptions, Decl(index.js, 0, 19))
23+
>WriteFileOptions : Symbol(WriteFileOptions, Decl(index.d.ts, 0, 21))
24+
>OtherName : Symbol(OtherName, Decl(index.js, 0, 37))
25+
26+
/** @typedef {{ x: any }} JSDocType */
27+
28+
export { JSDocType };
29+
>JSDocType : Symbol(JSDocType, Decl(index.js, 4, 8), Decl(index.js, 2, 4))
30+
31+
export { JSDocType as ThisIsFine };
32+
>JSDocType : Symbol(JSDocType, Decl(index.js, 4, 8), Decl(index.js, 2, 4))
33+
>ThisIsFine : Symbol(ThisIsFine, Decl(index.js, 5, 8))
34+
35+
export { WriteFileOptions };
36+
>WriteFileOptions : Symbol(WriteFileOptions, Decl(index.js, 6, 8))
37+
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
=== /node_modules/@types/node/index.d.ts ===
2+
declare module "fs" {
3+
>"fs" : typeof import("fs")
4+
5+
export interface WriteFileOptions {}
6+
export function writeFile(path: string, data: any, options: WriteFileOptions, callback: (err: Error) => void): void;
7+
>writeFile : (path: string, data: any, options: WriteFileOptions, callback: (err: Error) => void) => void
8+
>path : string
9+
>data : any
10+
>options : WriteFileOptions
11+
>callback : (err: Error) => void
12+
>err : Error
13+
}
14+
15+
=== /index.js ===
16+
import { writeFile, WriteFileOptions, WriteFileOptions as OtherName } from "fs";
17+
>writeFile : (path: string, data: any, options: WriteFileOptions, callback: (err: Error) => void) => void
18+
>WriteFileOptions : any
19+
>WriteFileOptions : any
20+
>OtherName : any
21+
22+
/** @typedef {{ x: any }} JSDocType */
23+
24+
export { JSDocType };
25+
>JSDocType : any
26+
27+
export { JSDocType as ThisIsFine };
28+
>JSDocType : any
29+
>ThisIsFine : any
30+
31+
export { WriteFileOptions };
32+
>WriteFileOptions : any
33+
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
tests/cases/conformance/jsdoc/declarations/file2.js(1,9): error TS18042: 'myTypes' is a type and cannot be imported in JavaScript files. Use 'import("./file.js").myTypes' in a JSDoc type annotation.
2+
3+
4+
==== tests/cases/conformance/jsdoc/declarations/file.js (0 errors) ====
5+
/**
6+
* @namespace myTypes
7+
* @global
8+
* @type {Object<string,*>}
9+
*/
10+
const myTypes = {
11+
// SOME PROPS HERE
12+
};
13+
14+
/** @typedef {string|RegExp|Array<string|RegExp>} myTypes.typeA */
15+
16+
/**
17+
* @typedef myTypes.typeB
18+
* @property {myTypes.typeA} prop1 - Prop 1.
19+
* @property {string} prop2 - Prop 2.
20+
*/
21+
22+
/** @typedef {myTypes.typeB|Function} myTypes.typeC */
23+
24+
export {myTypes};
25+
==== tests/cases/conformance/jsdoc/declarations/file2.js (1 errors) ====
26+
import {myTypes} from './file.js';
27+
~~~~~~~
28+
!!! error TS18042: 'myTypes' is a type and cannot be imported in JavaScript files. Use 'import("./file.js").myTypes' in a JSDoc type annotation.
29+
30+
/**
31+
* @namespace testFnTypes
32+
* @global
33+
* @type {Object<string,*>}
34+
*/
35+
const testFnTypes = {
36+
// SOME PROPS HERE
37+
};
38+
39+
/** @typedef {boolean|myTypes.typeC} testFnTypes.input */
40+
41+
/**
42+
* @function testFn
43+
* @description A test function.
44+
* @param {testFnTypes.input} input - Input.
45+
* @returns {number|null} Result.
46+
*/
47+
function testFn(input) {
48+
if (typeof input === 'number') {
49+
return 2 * input;
50+
} else {
51+
return null;
52+
}
53+
}
54+
55+
export {testFn, testFnTypes};

tests/baselines/reference/jsDeclarationsInterfaces.errors.txt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@ tests/cases/conformance/jsdoc/declarations/index.js(4,18): error TS8006: 'interf
22
tests/cases/conformance/jsdoc/declarations/index.js(6,18): error TS8006: 'interface' declarations can only be used in TypeScript files.
33
tests/cases/conformance/jsdoc/declarations/index.js(10,18): error TS8006: 'interface' declarations can only be used in TypeScript files.
44
tests/cases/conformance/jsdoc/declarations/index.js(31,11): error TS8006: 'interface' declarations can only be used in TypeScript files.
5+
tests/cases/conformance/jsdoc/declarations/index.js(33,10): error TS18043: Types cannot appear in export declarations in JavaScript files.
56
tests/cases/conformance/jsdoc/declarations/index.js(35,11): error TS8006: 'interface' declarations can only be used in TypeScript files.
7+
tests/cases/conformance/jsdoc/declarations/index.js(37,10): error TS18043: Types cannot appear in export declarations in JavaScript files.
68
tests/cases/conformance/jsdoc/declarations/index.js(39,18): error TS8006: 'interface' declarations can only be used in TypeScript files.
9+
tests/cases/conformance/jsdoc/declarations/index.js(40,10): error TS18043: Types cannot appear in export declarations in JavaScript files.
10+
tests/cases/conformance/jsdoc/declarations/index.js(42,10): error TS18043: Types cannot appear in export declarations in JavaScript files.
711
tests/cases/conformance/jsdoc/declarations/index.js(43,18): error TS8006: 'interface' declarations can only be used in TypeScript files.
812
tests/cases/conformance/jsdoc/declarations/index.js(45,18): error TS8006: 'interface' declarations can only be used in TypeScript files.
913
tests/cases/conformance/jsdoc/declarations/index.js(49,18): error TS8006: 'interface' declarations can only be used in TypeScript files.
@@ -26,7 +30,7 @@ tests/cases/conformance/jsdoc/declarations/index.js(111,18): error TS8006: 'inte
2630
tests/cases/conformance/jsdoc/declarations/index.js(115,18): error TS8006: 'interface' declarations can only be used in TypeScript files.
2731

2832

29-
==== tests/cases/conformance/jsdoc/declarations/index.js (26 errors) ====
33+
==== tests/cases/conformance/jsdoc/declarations/index.js (30 errors) ====
3034
// Pretty much all of this should be an error, (since interfaces are forbidden in js),
3135
// but we should be able to synthesize declarations from the symbols regardless
3236

@@ -68,19 +72,27 @@ tests/cases/conformance/jsdoc/declarations/index.js(115,18): error TS8006: 'inte
6872
!!! error TS8006: 'interface' declarations can only be used in TypeScript files.
6973

7074
export { G };
75+
~
76+
!!! error TS18043: Types cannot appear in export declarations in JavaScript files.
7177

7278
interface HH {}
7379
~~
7480
!!! error TS8006: 'interface' declarations can only be used in TypeScript files.
7581

7682
export { HH as H };
83+
~~
84+
!!! error TS18043: Types cannot appear in export declarations in JavaScript files.
7785

7886
export interface I {}
7987
~
8088
!!! error TS8006: 'interface' declarations can only be used in TypeScript files.
8189
export { I as II };
90+
~
91+
!!! error TS18043: Types cannot appear in export declarations in JavaScript files.
8292

8393
export { J as JJ };
94+
~
95+
!!! error TS18043: Types cannot appear in export declarations in JavaScript files.
8496
export interface J {}
8597
~
8698
!!! error TS8006: 'interface' declarations can only be used in TypeScript files.

tests/baselines/reference/jsxCheckJsxNoTypeArgumentsAllowed.errors.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
tests/cases/conformance/jsx/file.jsx(1,18): error TS18042: 'Prop' is a type and cannot be imported in JavaScript files. Use 'import("./component").Prop' in a JSDoc type annotation.
12
tests/cases/conformance/jsx/file.jsx(4,9): error TS2657: JSX expressions must have one parent element.
23
tests/cases/conformance/jsx/file.jsx(4,16): error TS1003: Identifier expected.
34
tests/cases/conformance/jsx/file.jsx(4,17): error TS2693: 'Prop' only refers to a type, but is being used as a value here.
@@ -17,8 +18,10 @@ tests/cases/conformance/jsx/file.jsx(5,1): error TS1005: '</' expected.
1718
b: string
1819
}
1920

20-
==== tests/cases/conformance/jsx/file.jsx (6 errors) ====
21+
==== tests/cases/conformance/jsx/file.jsx (7 errors) ====
2122
import { MyComp, Prop } from "./component";
23+
~~~~
24+
!!! error TS18042: 'Prop' is a type and cannot be imported in JavaScript files. Use 'import("./component").Prop' in a JSDoc type annotation.
2225
import * as React from "react";
2326

2427
let x = <MyComp<Prop> a={10} b="hi" />; // error, no type arguments in js

0 commit comments

Comments
 (0)