From 0a6ee7753d98eb7fcf0f588b154a222d1bda105f Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 27 Feb 2020 16:35:15 -0800 Subject: [PATCH] Grammar error on `export type *` (#37064) * Recognize `export type *` syntax, but disallow it * Add more comments to test * Revert recognizing invalid forms as type-only * Revert more --- src/compiler/checker.ts | 20 +++++++-- src/compiler/diagnosticMessages.json | 4 ++ src/compiler/utilities.ts | 4 ++ .../reference/exportNamespace4.errors.txt | 25 +++++++++++ tests/baselines/reference/exportNamespace4.js | 45 +++++++++++++++++++ .../reference/exportNamespace4.symbols | 27 +++++++++++ .../reference/exportNamespace4.types | 27 +++++++++++ .../typeOnly/exportNamespace4.ts | 16 +++++++ 8 files changed, 164 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/exportNamespace4.errors.txt create mode 100644 tests/baselines/reference/exportNamespace4.js create mode 100644 tests/baselines/reference/exportNamespace4.symbols create mode 100644 tests/baselines/reference/exportNamespace4.types create mode 100644 tests/cases/conformance/externalModules/typeOnly/exportNamespace4.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b3a63ac74eba7..1b2e6931e02c4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1936,10 +1936,11 @@ namespace ts { if (!isValidTypeOnlyAliasUseSite(useSite)) { const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(symbol); if (typeOnlyDeclaration) { - const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier + const isExport = typeOnlyDeclarationIsExport(typeOnlyDeclaration); + const message = isExport ? Diagnostics._0_cannot_be_used_as_a_value_because_it_was_exported_using_export_type : Diagnostics._0_cannot_be_used_as_a_value_because_it_was_imported_using_import_type; - const relatedMessage = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier + const relatedMessage = isExport ? Diagnostics._0_was_exported_here : Diagnostics._0_was_imported_here; const unescapedName = unescapeLeadingUnderscores(name); @@ -2286,12 +2287,14 @@ namespace ts { function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) { if (markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false)) { const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfNode(node))!; - const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier + const isExport = typeOnlyDeclarationIsExport(typeOnlyDeclaration); + const message = isExport ? Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_exported_using_export_type : Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_imported_using_import_type; - const relatedMessage = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier + const relatedMessage = isExport ? Diagnostics._0_was_exported_here : Diagnostics._0_was_imported_here; + // Non-null assertion is safe because the optionality comes from ImportClause, // but if an ImportClause was the typeOnlyDeclaration, it had to have a `name`. const name = unescapeLeadingUnderscores(typeOnlyDeclaration.name!.escapedText); @@ -33598,6 +33601,7 @@ namespace ts { checkExternalEmitHelpers(node, ExternalEmitHelpers.CreateBinding); } + checkGrammarExportDeclaration(node); if (!node.moduleSpecifier || checkExternalImportOrExportDeclaration(node)) { if (node.exportClause) { // export { x, y } @@ -33630,6 +33634,14 @@ namespace ts { } } + function checkGrammarExportDeclaration(node: ExportDeclaration): boolean { + const isTypeOnlyExportStar = node.isTypeOnly && node.exportClause?.kind !== SyntaxKind.NamedExports; + if (isTypeOnlyExportStar) { + grammarErrorOnNode(node, Diagnostics.Only_named_exports_may_use_export_type); + } + return !isTypeOnlyExportStar; + } + function checkGrammarModuleElementContext(node: Statement, errorMessage: DiagnosticMessage): boolean { const isInAppropriateContext = node.parent.kind === SyntaxKind.SourceFile || node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.ModuleDeclaration; if (!isInAppropriateContext) { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index d64e8d6ee80df..040b9a301bb47 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1143,6 +1143,10 @@ "category": "Error", "code": 1382 }, + "Only named exports may use 'export type'.": { + "category": "Error", + "code": 1383 + }, "The types of '{0}' are incompatible between these types.": { "category": "Error", diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 7fa1c520643a1..ba4cc0106b9b1 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -6220,6 +6220,10 @@ namespace ts { || !isExpressionNode(useSite); } + export function typeOnlyDeclarationIsExport(typeOnlyDeclaration: Node) { + return typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier; + } + function isPartOfPossiblyValidTypeOrAbstractComputedPropertyName(node: Node) { while (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PropertyAccessExpression) { node = node.parent; diff --git a/tests/baselines/reference/exportNamespace4.errors.txt b/tests/baselines/reference/exportNamespace4.errors.txt new file mode 100644 index 0000000000000..30b849456e67f --- /dev/null +++ b/tests/baselines/reference/exportNamespace4.errors.txt @@ -0,0 +1,25 @@ +tests/cases/conformance/externalModules/typeOnly/b.ts(1,1): error TS1383: Only named exports may use 'export type'. +tests/cases/conformance/externalModules/typeOnly/c.ts(1,1): error TS1383: Only named exports may use 'export type'. + + +==== tests/cases/conformance/externalModules/typeOnly/a.ts (0 errors) ==== + export class A {} + +==== tests/cases/conformance/externalModules/typeOnly/b.ts (1 errors) ==== + export type * from './a'; // Grammar error + ~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS1383: Only named exports may use 'export type'. + +==== tests/cases/conformance/externalModules/typeOnly/c.ts (1 errors) ==== + export type * as ns from './a'; // Grammar error + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS1383: Only named exports may use 'export type'. + +==== tests/cases/conformance/externalModules/typeOnly/d.ts (0 errors) ==== + import { A } from './b'; + A; + +==== tests/cases/conformance/externalModules/typeOnly/e.ts (0 errors) ==== + import { ns } from './c'; + ns.A; + \ No newline at end of file diff --git a/tests/baselines/reference/exportNamespace4.js b/tests/baselines/reference/exportNamespace4.js new file mode 100644 index 0000000000000..e7da4ca86d055 --- /dev/null +++ b/tests/baselines/reference/exportNamespace4.js @@ -0,0 +1,45 @@ +//// [tests/cases/conformance/externalModules/typeOnly/exportNamespace4.ts] //// + +//// [a.ts] +export class A {} + +//// [b.ts] +export type * from './a'; // Grammar error + +//// [c.ts] +export type * as ns from './a'; // Grammar error + +//// [d.ts] +import { A } from './b'; +A; + +//// [e.ts] +import { ns } from './c'; +ns.A; + + +//// [a.js] +"use strict"; +exports.__esModule = true; +var A = /** @class */ (function () { + function A() { + } + return A; +}()); +exports.A = A; +//// [b.js] +"use strict"; +exports.__esModule = true; +//// [c.js] +"use strict"; +exports.__esModule = true; +//// [d.js] +"use strict"; +exports.__esModule = true; +var b_1 = require("./b"); +b_1.A; +//// [e.js] +"use strict"; +exports.__esModule = true; +var c_1 = require("./c"); +c_1.ns.A; diff --git a/tests/baselines/reference/exportNamespace4.symbols b/tests/baselines/reference/exportNamespace4.symbols new file mode 100644 index 0000000000000..860bb80b87795 --- /dev/null +++ b/tests/baselines/reference/exportNamespace4.symbols @@ -0,0 +1,27 @@ +=== tests/cases/conformance/externalModules/typeOnly/a.ts === +export class A {} +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/conformance/externalModules/typeOnly/b.ts === +export type * from './a'; // Grammar error +No type information for this code. +No type information for this code.=== tests/cases/conformance/externalModules/typeOnly/c.ts === +export type * as ns from './a'; // Grammar error +>ns : Symbol(ns, Decl(c.ts, 0, 11)) + +=== tests/cases/conformance/externalModules/typeOnly/d.ts === +import { A } from './b'; +>A : Symbol(A, Decl(d.ts, 0, 8)) + +A; +>A : Symbol(A, Decl(d.ts, 0, 8)) + +=== tests/cases/conformance/externalModules/typeOnly/e.ts === +import { ns } from './c'; +>ns : Symbol(ns, Decl(e.ts, 0, 8)) + +ns.A; +>ns.A : Symbol(ns.A, Decl(a.ts, 0, 0)) +>ns : Symbol(ns, Decl(e.ts, 0, 8)) +>A : Symbol(ns.A, Decl(a.ts, 0, 0)) + diff --git a/tests/baselines/reference/exportNamespace4.types b/tests/baselines/reference/exportNamespace4.types new file mode 100644 index 0000000000000..18155fb4eb6e8 --- /dev/null +++ b/tests/baselines/reference/exportNamespace4.types @@ -0,0 +1,27 @@ +=== tests/cases/conformance/externalModules/typeOnly/a.ts === +export class A {} +>A : A + +=== tests/cases/conformance/externalModules/typeOnly/b.ts === +export type * from './a'; // Grammar error +No type information for this code. +No type information for this code.=== tests/cases/conformance/externalModules/typeOnly/c.ts === +export type * as ns from './a'; // Grammar error +>ns : typeof ns + +=== tests/cases/conformance/externalModules/typeOnly/d.ts === +import { A } from './b'; +>A : typeof A + +A; +>A : typeof A + +=== tests/cases/conformance/externalModules/typeOnly/e.ts === +import { ns } from './c'; +>ns : typeof ns + +ns.A; +>ns.A : typeof ns.A +>ns : typeof ns +>A : typeof ns.A + diff --git a/tests/cases/conformance/externalModules/typeOnly/exportNamespace4.ts b/tests/cases/conformance/externalModules/typeOnly/exportNamespace4.ts new file mode 100644 index 0000000000000..889552e6643fe --- /dev/null +++ b/tests/cases/conformance/externalModules/typeOnly/exportNamespace4.ts @@ -0,0 +1,16 @@ +// @Filename: a.ts +export class A {} + +// @Filename: b.ts +export type * from './a'; // Grammar error + +// @Filename: c.ts +export type * as ns from './a'; // Grammar error + +// @Filename: d.ts +import { A } from './b'; +A; + +// @Filename: e.ts +import { ns } from './c'; +ns.A;