Skip to content

Add error when importing/exporting types in JS files #49580

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40778,6 +40778,42 @@ namespace ts {
// otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export*
// in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names).
symbol = getMergedSymbol(symbol.exportSymbol || symbol);

// A type-only import/export will already have a grammar error in a JS file, so no need to issue more errors within
if (isInJSFile(node) && !(target.flags & SymbolFlags.Value) && !isTypeOnlyImportOrExportDeclaration(node)) {
const errorNode =
isImportOrExportSpecifier(node) ? node.propertyName || node.name :
isNamedDeclaration(node) ? node.name :
node;

Debug.assert(node.kind !== SyntaxKind.NamespaceExport);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't it be?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A namespace export should always be an instantiated module (and therefore should have SymbolFlags.Value) just like a namespace import is.

if (node.kind === SyntaxKind.ExportSpecifier) {
const diag = error(errorNode, Diagnostics.Types_cannot_appear_in_export_declarations_in_JavaScript_files);
const alreadyExportedSymbol = getSourceFileOfNode(node).symbol?.exports?.get((node.propertyName || node.name).escapedText);
if (alreadyExportedSymbol === target) {
const exportingDeclaration = alreadyExportedSymbol.declarations?.find(isJSDocNode);
if (exportingDeclaration) {
addRelatedInfo(diag, createDiagnosticForNode(
exportingDeclaration,
Diagnostics._0_is_automatically_exported_here,
unescapeLeadingUnderscores(alreadyExportedSymbol.escapedName)));
}
}
}
else {
Debug.assert(node.kind !== SyntaxKind.VariableDeclaration);
const importDeclaration = findAncestor(node, or(isImportDeclaration, isImportEqualsDeclaration)) as ImportDeclaration | ImportEqualsDeclaration | undefined;
const moduleSpecifier = (importDeclaration && tryGetModuleSpecifierFromDeclaration(importDeclaration)?.text) ?? "...";
const importedIdentifier = unescapeLeadingUnderscores(isIdentifier(errorNode) ? errorNode.escapedText : symbol.escapedName);
error(
errorNode,
Diagnostics._0_is_a_type_and_cannot_be_imported_in_JavaScript_files_Use_1_in_a_JSDoc_type_annotation,
importedIdentifier,
`import("${moduleSpecifier}").${importedIdentifier}`);
}
return;
}

const excludedMeanings =
(symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue) ? SymbolFlags.Value : 0) |
(symbol.flags & SymbolFlags.Type ? SymbolFlags.Type : 0) |
Expand Down
14 changes: 13 additions & 1 deletion src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3498,7 +3498,7 @@
"'{0}' is an unused renaming of '{1}'. Did you intend to use it as a type annotation?": {
"category": "Error",
"code": 2842
},
},
"We can only write a type for '{0}' by adding a type for the entire parameter here.": {
"category": "Error",
"code": 2843
Expand Down Expand Up @@ -7396,5 +7396,17 @@
"A 'return' statement cannot be used inside a class static block.": {
"category": "Error",
"code": 18041
},
"'{0}' is a type and cannot be imported in JavaScript files. Use '{1}' in a JSDoc type annotation.": {
"category": "Error",
"code": 18042
},
"Types cannot appear in export declarations in JavaScript files.": {
"category": "Error",
"code": 18043
},
"'{0}' is automatically exported here.": {
"category": "Message",
"code": 18044
}
}
34 changes: 34 additions & 0 deletions tests/baselines/reference/importingExportingTypes.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/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.
/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.
/index.js(5,10): error TS18043: Types cannot appear in export declarations in JavaScript files.
/index.js(6,10): error TS18043: Types cannot appear in export declarations in JavaScript files.
/index.js(7,10): error TS18043: Types cannot appear in export declarations in JavaScript files.


==== /node_modules/@types/node/index.d.ts (0 errors) ====
declare module "fs" {
export interface WriteFileOptions {}
export function writeFile(path: string, data: any, options: WriteFileOptions, callback: (err: Error) => void): void;
}

==== /index.js (5 errors) ====
import { writeFile, WriteFileOptions, WriteFileOptions as OtherName } from "fs";
~~~~~~~~~~~~~~~~
!!! error TS18042: 'WriteFileOptions' is a type and cannot be imported in JavaScript files. Use 'import("fs").WriteFileOptions' in a JSDoc type annotation.
~~~~~~~~~~~~~~~~
!!! error TS18042: 'WriteFileOptions' is a type and cannot be imported in JavaScript files. Use 'import("fs").WriteFileOptions' in a JSDoc type annotation.

/** @typedef {{ x: any }} JSDocType */

export { JSDocType };
~~~~~~~~~
!!! error TS18043: Types cannot appear in export declarations in JavaScript files.
!!! related TS18044 /index.js:3:5: 'JSDocType' is automatically exported here.
export { JSDocType as ThisIsFine };
~~~~~~~~~
!!! error TS18043: Types cannot appear in export declarations in JavaScript files.
!!! related TS18044 /index.js:3:5: 'JSDocType' is automatically exported here.
export { WriteFileOptions };
~~~~~~~~~~~~~~~~
!!! error TS18043: Types cannot appear in export declarations in JavaScript files.

37 changes: 37 additions & 0 deletions tests/baselines/reference/importingExportingTypes.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
=== /node_modules/@types/node/index.d.ts ===
declare module "fs" {
>"fs" : Symbol("fs", Decl(index.d.ts, 0, 0))

export interface WriteFileOptions {}
>WriteFileOptions : Symbol(WriteFileOptions, Decl(index.d.ts, 0, 21))

export function writeFile(path: string, data: any, options: WriteFileOptions, callback: (err: Error) => void): void;
>writeFile : Symbol(writeFile, Decl(index.d.ts, 1, 38))
>path : Symbol(path, Decl(index.d.ts, 2, 28))
>data : Symbol(data, Decl(index.d.ts, 2, 41))
>options : Symbol(options, Decl(index.d.ts, 2, 52))
>WriteFileOptions : Symbol(WriteFileOptions, Decl(index.d.ts, 0, 21))
>callback : Symbol(callback, Decl(index.d.ts, 2, 79))
>err : Symbol(err, Decl(index.d.ts, 2, 91))
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
}

=== /index.js ===
import { writeFile, WriteFileOptions, WriteFileOptions as OtherName } from "fs";
>writeFile : Symbol(writeFile, Decl(index.js, 0, 8))
>WriteFileOptions : Symbol(WriteFileOptions, Decl(index.js, 0, 19))
>WriteFileOptions : Symbol(WriteFileOptions, Decl(index.d.ts, 0, 21))
>OtherName : Symbol(OtherName, Decl(index.js, 0, 37))

/** @typedef {{ x: any }} JSDocType */

export { JSDocType };
>JSDocType : Symbol(JSDocType, Decl(index.js, 4, 8), Decl(index.js, 2, 4))

export { JSDocType as ThisIsFine };
>JSDocType : Symbol(JSDocType, Decl(index.js, 4, 8), Decl(index.js, 2, 4))
>ThisIsFine : Symbol(ThisIsFine, Decl(index.js, 5, 8))

export { WriteFileOptions };
>WriteFileOptions : Symbol(WriteFileOptions, Decl(index.js, 6, 8))

33 changes: 33 additions & 0 deletions tests/baselines/reference/importingExportingTypes.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
=== /node_modules/@types/node/index.d.ts ===
declare module "fs" {
>"fs" : typeof import("fs")

export interface WriteFileOptions {}
export function writeFile(path: string, data: any, options: WriteFileOptions, callback: (err: Error) => void): void;
>writeFile : (path: string, data: any, options: WriteFileOptions, callback: (err: Error) => void) => void
>path : string
>data : any
>options : WriteFileOptions
>callback : (err: Error) => void
>err : Error
}

=== /index.js ===
import { writeFile, WriteFileOptions, WriteFileOptions as OtherName } from "fs";
>writeFile : (path: string, data: any, options: WriteFileOptions, callback: (err: Error) => void) => void
>WriteFileOptions : any
>WriteFileOptions : any
>OtherName : any

/** @typedef {{ x: any }} JSDocType */

export { JSDocType };
>JSDocType : any

export { JSDocType as ThisIsFine };
>JSDocType : any
>ThisIsFine : any

export { WriteFileOptions };
>WriteFileOptions : any

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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.


==== tests/cases/conformance/jsdoc/declarations/file.js (0 errors) ====
/**
* @namespace myTypes
* @global
* @type {Object<string,*>}
*/
const myTypes = {
// SOME PROPS HERE
};

/** @typedef {string|RegExp|Array<string|RegExp>} myTypes.typeA */

/**
* @typedef myTypes.typeB
* @property {myTypes.typeA} prop1 - Prop 1.
* @property {string} prop2 - Prop 2.
*/

/** @typedef {myTypes.typeB|Function} myTypes.typeC */

export {myTypes};
==== tests/cases/conformance/jsdoc/declarations/file2.js (1 errors) ====
import {myTypes} from './file.js';
~~~~~~~
!!! error TS18042: 'myTypes' is a type and cannot be imported in JavaScript files. Use 'import("./file.js").myTypes' in a JSDoc type annotation.

/**
* @namespace testFnTypes
* @global
* @type {Object<string,*>}
*/
const testFnTypes = {
// SOME PROPS HERE
};

/** @typedef {boolean|myTypes.typeC} testFnTypes.input */

/**
* @function testFn
* @description A test function.
* @param {testFnTypes.input} input - Input.
* @returns {number|null} Result.
*/
function testFn(input) {
if (typeof input === 'number') {
return 2 * input;
} else {
return null;
}
}

export {testFn, testFnTypes};
14 changes: 13 additions & 1 deletion tests/baselines/reference/jsDeclarationsInterfaces.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ tests/cases/conformance/jsdoc/declarations/index.js(4,18): error TS8006: 'interf
tests/cases/conformance/jsdoc/declarations/index.js(6,18): error TS8006: 'interface' declarations can only be used in TypeScript files.
tests/cases/conformance/jsdoc/declarations/index.js(10,18): error TS8006: 'interface' declarations can only be used in TypeScript files.
tests/cases/conformance/jsdoc/declarations/index.js(31,11): error TS8006: 'interface' declarations can only be used in TypeScript files.
tests/cases/conformance/jsdoc/declarations/index.js(33,10): error TS18043: Types cannot appear in export declarations in JavaScript files.
tests/cases/conformance/jsdoc/declarations/index.js(35,11): error TS8006: 'interface' declarations can only be used in TypeScript files.
tests/cases/conformance/jsdoc/declarations/index.js(37,10): error TS18043: Types cannot appear in export declarations in JavaScript files.
tests/cases/conformance/jsdoc/declarations/index.js(39,18): error TS8006: 'interface' declarations can only be used in TypeScript files.
tests/cases/conformance/jsdoc/declarations/index.js(40,10): error TS18043: Types cannot appear in export declarations in JavaScript files.
tests/cases/conformance/jsdoc/declarations/index.js(42,10): error TS18043: Types cannot appear in export declarations in JavaScript files.
tests/cases/conformance/jsdoc/declarations/index.js(43,18): error TS8006: 'interface' declarations can only be used in TypeScript files.
tests/cases/conformance/jsdoc/declarations/index.js(45,18): error TS8006: 'interface' declarations can only be used in TypeScript files.
tests/cases/conformance/jsdoc/declarations/index.js(49,18): error TS8006: 'interface' declarations can only be used in TypeScript files.
Expand All @@ -26,7 +30,7 @@ tests/cases/conformance/jsdoc/declarations/index.js(111,18): error TS8006: 'inte
tests/cases/conformance/jsdoc/declarations/index.js(115,18): error TS8006: 'interface' declarations can only be used in TypeScript files.


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

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

export { G };
~
!!! error TS18043: Types cannot appear in export declarations in JavaScript files.

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

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

export interface I {}
~
!!! error TS8006: 'interface' declarations can only be used in TypeScript files.
export { I as II };
~
!!! error TS18043: Types cannot appear in export declarations in JavaScript files.

export { J as JJ };
~
!!! error TS18043: Types cannot appear in export declarations in JavaScript files.
export interface J {}
~
!!! error TS8006: 'interface' declarations can only be used in TypeScript files.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
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.
tests/cases/conformance/jsx/file.jsx(4,9): error TS2657: JSX expressions must have one parent element.
tests/cases/conformance/jsx/file.jsx(4,16): error TS1003: Identifier expected.
tests/cases/conformance/jsx/file.jsx(4,17): error TS2693: 'Prop' only refers to a type, but is being used as a value here.
Expand All @@ -17,8 +18,10 @@ tests/cases/conformance/jsx/file.jsx(5,1): error TS1005: '</' expected.
b: string
}

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

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