From 2e47d4a132e028fa75fec29ce695425e77b89cec Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 27 Jan 2020 16:12:43 -0800 Subject: [PATCH 01/14] Handle when files get checked in different orders --- src/compiler/checker.ts | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fc0620db8658a..b64f751fda005 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2234,7 +2234,7 @@ namespace ts { function getTargetOfImportClause(node: ImportClause, dontResolveAlias: boolean): Symbol | undefined { const moduleSymbol = resolveExternalModuleName(node, node.parent.moduleSpecifier); - markSymbolOfAliasDeclarationIfTypeOnly(node); + const isTypeOnly = markSymbolOfAliasDeclarationIfTypeOnly(node); if (moduleSymbol) { let exportDefaultSymbol: Symbol | undefined; @@ -2277,16 +2277,21 @@ namespace ts { } else if (hasSyntheticDefault) { // per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present - return resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); + const resolved = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); + if (!isTypeOnly) markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); + return resolved; } + if (!isTypeOnly) markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, exportDefaultSymbol); return exportDefaultSymbol; } } function getTargetOfNamespaceImport(node: NamespaceImport, dontResolveAlias: boolean): Symbol | undefined { const moduleSpecifier = node.parent.parent.moduleSpecifier; - markSymbolOfAliasDeclarationIfTypeOnly(node); - return resolveESModuleSymbol(resolveExternalModuleName(node, moduleSpecifier), moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false); + const isTypeOnly = markSymbolOfAliasDeclarationIfTypeOnly(node); + const resolved = resolveESModuleSymbol(resolveExternalModuleName(node, moduleSpecifier), moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false); + if (!isTypeOnly) markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); + return resolved; } function getTargetOfNamespaceExport(node: NamespaceExport, dontResolveAlias: boolean): Symbol | undefined { @@ -2433,8 +2438,10 @@ namespace ts { } function getTargetOfImportSpecifier(node: ImportSpecifier, dontResolveAlias: boolean): Symbol | undefined { - markSymbolOfAliasDeclarationIfTypeOnly(node); - return getExternalModuleMember(node.parent.parent.parent, node, dontResolveAlias); + const isTypeOnly = markSymbolOfAliasDeclarationIfTypeOnly(node); + const resolved = getExternalModuleMember(node.parent.parent.parent, node, dontResolveAlias); + if (!isTypeOnly) markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); + return resolved; } function getTargetOfNamespaceExportDeclaration(node: NamespaceExportDeclaration, dontResolveAlias: boolean): Symbol { @@ -2442,10 +2449,12 @@ namespace ts { } function getTargetOfExportSpecifier(node: ExportSpecifier, meaning: SymbolFlags, dontResolveAlias?: boolean) { - markSymbolOfAliasDeclarationIfTypeOnly(node); - return node.parent.parent.moduleSpecifier ? + const isTypeOnly = markSymbolOfAliasDeclarationIfTypeOnly(node); + const resolved = node.parent.parent.moduleSpecifier ? getExternalModuleMember(node.parent.parent, node, dontResolveAlias) : resolveEntityName(node.propertyName || node.name, meaning, /*ignoreErrors*/ false, dontResolveAlias); + if (!isTypeOnly) markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); + return resolved; } function getTargetOfExportAssignment(node: ExportAssignment | BinaryExpression, dontResolveAlias: boolean): Symbol | undefined { @@ -2557,12 +2566,14 @@ namespace ts { } } - function markSymbolOfAliasDeclarationIfTypeOnly(aliasDeclaration: TypeOnlyCompatibleAliasDeclaration) { + function markSymbolOfAliasDeclarationIfTypeOnly(aliasDeclaration: TypeOnlyCompatibleAliasDeclaration): boolean { if (isTypeOnlyImportOrExportDeclaration(aliasDeclaration)) { const symbol = getSymbolOfNode(aliasDeclaration); const links = getSymbolLinks(symbol); links.typeOnlyDeclaration = aliasDeclaration; + return true; } + return false; } /** Indicates that a symbol directly or indirectly resolves to a type-only import or export. */ From 7e1382aa1abb3cd14846152ea8249d7f645d97a9 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 28 Jan 2020 11:33:42 -0800 Subject: [PATCH 02/14] Fix interface extends clause --- src/compiler/utilities.ts | 9 +-- .../reference/extendsClause.errors.txt | 30 +++++++++ tests/baselines/reference/extendsClause.js | 65 +++++++++++++++++++ .../baselines/reference/extendsClause.symbols | 52 +++++++++++++++ tests/baselines/reference/extendsClause.types | 38 +++++++++++ .../externalModules/typeOnly/extendsClause.ts | 19 ++++++ 6 files changed, 209 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/extendsClause.errors.txt create mode 100644 tests/baselines/reference/extendsClause.js create mode 100644 tests/baselines/reference/extendsClause.symbols create mode 100644 tests/baselines/reference/extendsClause.types create mode 100644 tests/cases/conformance/externalModules/typeOnly/extendsClause.ts diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 167ee1aef2a91..863076f678399 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1792,9 +1792,10 @@ namespace ts { return containerKind === SyntaxKind.InterfaceDeclaration || containerKind === SyntaxKind.TypeLiteral; } - export function isFirstIdentifierOfImplementsClause(node: Node) { - return node.parent?.parent?.parent?.kind === SyntaxKind.HeritageClause - && (node.parent.parent.parent as HeritageClause).token === SyntaxKind.ImplementsKeyword; + export function isFirstIdentifierOfNonEmittingHeritageClause(node: Node): boolean { + // Number of parents to climb from identifier is 2 for `implements I`, 3 for `implements x.I` + const heritageClause = tryCast(node.parent.parent, isHeritageClause) ?? tryCast(node.parent.parent.parent, isHeritageClause); + return heritageClause?.token === SyntaxKind.ImplementsKeyword || heritageClause?.parent.kind === SyntaxKind.InterfaceDeclaration; } export function isExternalModuleImportEqualsDeclaration(node: Node): node is ImportEqualsDeclaration & { moduleReference: ExternalModuleReference } { @@ -6143,7 +6144,7 @@ namespace ts { export function isValidTypeOnlyAliasUseSite(useSite: Node): boolean { return !!(useSite.flags & NodeFlags.Ambient) || isPartOfTypeQuery(useSite) - || isFirstIdentifierOfImplementsClause(useSite) + || isFirstIdentifierOfNonEmittingHeritageClause(useSite) || isPartOfPossiblyValidTypeOrAbstractComputedPropertyName(useSite) || !isExpressionNode(useSite); } diff --git a/tests/baselines/reference/extendsClause.errors.txt b/tests/baselines/reference/extendsClause.errors.txt new file mode 100644 index 0000000000000..7ee2b0600c360 --- /dev/null +++ b/tests/baselines/reference/extendsClause.errors.txt @@ -0,0 +1,30 @@ +tests/cases/conformance/externalModules/typeOnly/index.ts(9,17): error TS1361: 'C' cannot be used as a value because it was imported using 'import type'. +tests/cases/conformance/externalModules/typeOnly/index.ts(10,17): error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. + + +==== tests/cases/conformance/externalModules/typeOnly/types.ts (0 errors) ==== + export interface I {} + export class C {} + +==== tests/cases/conformance/externalModules/typeOnly/ns.ts (0 errors) ==== + import type * as types from './types'; + export { types }; + +==== tests/cases/conformance/externalModules/typeOnly/index.ts (2 errors) ==== + import { types } from './ns'; + import type { C, I } from './types'; + + interface Q extends C {} + interface R extends I {} + interface S extends types.C {} + interface T extends types.I {} + + class U extends C {} // Error + ~ +!!! error TS1361: 'C' cannot be used as a value because it was imported using 'import type'. +!!! related TS1376 tests/cases/conformance/externalModules/typeOnly/index.ts:2:15: 'C' was imported here. + class V extends types.C {} // Error + ~~~~~ +!!! error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. +!!! related TS1376 tests/cases/conformance/externalModules/typeOnly/ns.ts:1:13: 'types' was imported here. + \ No newline at end of file diff --git a/tests/baselines/reference/extendsClause.js b/tests/baselines/reference/extendsClause.js new file mode 100644 index 0000000000000..d4b31c0484074 --- /dev/null +++ b/tests/baselines/reference/extendsClause.js @@ -0,0 +1,65 @@ +//// [tests/cases/conformance/externalModules/typeOnly/extendsClause.ts] //// + +//// [types.ts] +export interface I {} +export class C {} + +//// [ns.ts] +import type * as types from './types'; +export { types }; + +//// [index.ts] +import { types } from './ns'; +import type { C, I } from './types'; + +interface Q extends C {} +interface R extends I {} +interface S extends types.C {} +interface T extends types.I {} + +class U extends C {} // Error +class V extends types.C {} // Error + + +//// [types.js] +"use strict"; +exports.__esModule = true; +var C = /** @class */ (function () { + function C() { + } + return C; +}()); +exports.C = C; +//// [ns.js] +"use strict"; +exports.__esModule = true; +//// [index.js] +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +exports.__esModule = true; +var U = /** @class */ (function (_super) { + __extends(U, _super); + function U() { + return _super !== null && _super.apply(this, arguments) || this; + } + return U; +}(C)); // Error +var V = /** @class */ (function (_super) { + __extends(V, _super); + function V() { + return _super !== null && _super.apply(this, arguments) || this; + } + return V; +}(types.C)); // Error diff --git a/tests/baselines/reference/extendsClause.symbols b/tests/baselines/reference/extendsClause.symbols new file mode 100644 index 0000000000000..9a9e53d4debba --- /dev/null +++ b/tests/baselines/reference/extendsClause.symbols @@ -0,0 +1,52 @@ +=== tests/cases/conformance/externalModules/typeOnly/types.ts === +export interface I {} +>I : Symbol(I, Decl(types.ts, 0, 0)) + +export class C {} +>C : Symbol(C, Decl(types.ts, 0, 21)) + +=== tests/cases/conformance/externalModules/typeOnly/ns.ts === +import type * as types from './types'; +>types : Symbol(types, Decl(ns.ts, 0, 11)) + +export { types }; +>types : Symbol(types, Decl(ns.ts, 1, 8)) + +=== tests/cases/conformance/externalModules/typeOnly/index.ts === +import { types } from './ns'; +>types : Symbol(types, Decl(index.ts, 0, 8)) + +import type { C, I } from './types'; +>C : Symbol(C, Decl(index.ts, 1, 13)) +>I : Symbol(I, Decl(index.ts, 1, 16)) + +interface Q extends C {} +>Q : Symbol(Q, Decl(index.ts, 1, 36)) +>C : Symbol(C, Decl(index.ts, 1, 13)) + +interface R extends I {} +>R : Symbol(R, Decl(index.ts, 3, 24)) +>I : Symbol(I, Decl(index.ts, 1, 16)) + +interface S extends types.C {} +>S : Symbol(S, Decl(index.ts, 4, 24)) +>types.C : Symbol(types.C, Decl(types.ts, 0, 21)) +>types : Symbol(types, Decl(index.ts, 0, 8)) +>C : Symbol(types.C, Decl(types.ts, 0, 21)) + +interface T extends types.I {} +>T : Symbol(T, Decl(index.ts, 5, 30)) +>types.I : Symbol(types.I, Decl(types.ts, 0, 0)) +>types : Symbol(types, Decl(index.ts, 0, 8)) +>I : Symbol(types.I, Decl(types.ts, 0, 0)) + +class U extends C {} // Error +>U : Symbol(U, Decl(index.ts, 6, 30)) +>C : Symbol(C, Decl(index.ts, 1, 13)) + +class V extends types.C {} // Error +>V : Symbol(V, Decl(index.ts, 8, 20)) +>types.C : Symbol(types.C, Decl(types.ts, 0, 21)) +>types : Symbol(types, Decl(index.ts, 0, 8)) +>C : Symbol(types.C, Decl(types.ts, 0, 21)) + diff --git a/tests/baselines/reference/extendsClause.types b/tests/baselines/reference/extendsClause.types new file mode 100644 index 0000000000000..15140fd0fd6a8 --- /dev/null +++ b/tests/baselines/reference/extendsClause.types @@ -0,0 +1,38 @@ +=== tests/cases/conformance/externalModules/typeOnly/types.ts === +export interface I {} +export class C {} +>C : C + +=== tests/cases/conformance/externalModules/typeOnly/ns.ts === +import type * as types from './types'; +>types : typeof types + +export { types }; +>types : typeof types + +=== tests/cases/conformance/externalModules/typeOnly/index.ts === +import { types } from './ns'; +>types : typeof types + +import type { C, I } from './types'; +>C : types.C +>I : types.I + +interface Q extends C {} +interface R extends I {} +interface S extends types.C {} +>types : typeof types + +interface T extends types.I {} +>types : typeof types + +class U extends C {} // Error +>U : U +>C : types.C + +class V extends types.C {} // Error +>V : V +>types.C : types.C +>types : typeof types +>C : typeof types.C + diff --git a/tests/cases/conformance/externalModules/typeOnly/extendsClause.ts b/tests/cases/conformance/externalModules/typeOnly/extendsClause.ts new file mode 100644 index 0000000000000..0288d07a8f8e9 --- /dev/null +++ b/tests/cases/conformance/externalModules/typeOnly/extendsClause.ts @@ -0,0 +1,19 @@ +// @Filename: types.ts +export interface I {} +export class C {} + +// @Filename: ns.ts +import type * as types from './types'; +export { types }; + +// @Filename: index.ts +import { types } from './ns'; +import type { C, I } from './types'; + +interface Q extends C {} +interface R extends I {} +interface S extends types.C {} +interface T extends types.I {} + +class U extends C {} // Error +class V extends types.C {} // Error From 419dba1714cb23615cb6ebb93b0b6691db247584 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 28 Jan 2020 14:53:00 -0800 Subject: [PATCH 03/14] Fix import= something type only from a module --- src/compiler/checker.ts | 36 +++++++--- src/compiler/utilities.ts | 5 +- .../reference/importEquals1.errors.txt | 45 ++++++++++++ tests/baselines/reference/importEquals1.js | 69 +++++++++++++++++++ .../baselines/reference/importEquals1.symbols | 54 +++++++++++++++ tests/baselines/reference/importEquals1.types | 58 ++++++++++++++++ .../reference/importEquals2.errors.txt | 17 +++++ tests/baselines/reference/importEquals2.js | 39 +++++++++++ .../baselines/reference/importEquals2.symbols | 21 ++++++ tests/baselines/reference/importEquals2.types | 24 +++++++ .../externalModules/typeOnly/importEquals1.ts | 28 ++++++++ .../externalModules/typeOnly/importEquals2.ts | 13 ++++ 12 files changed, 397 insertions(+), 12 deletions(-) create mode 100644 tests/baselines/reference/importEquals1.errors.txt create mode 100644 tests/baselines/reference/importEquals1.js create mode 100644 tests/baselines/reference/importEquals1.symbols create mode 100644 tests/baselines/reference/importEquals1.types create mode 100644 tests/baselines/reference/importEquals2.errors.txt create mode 100644 tests/baselines/reference/importEquals2.js create mode 100644 tests/baselines/reference/importEquals2.symbols create mode 100644 tests/baselines/reference/importEquals2.types create mode 100644 tests/cases/conformance/externalModules/typeOnly/importEquals1.ts create mode 100644 tests/cases/conformance/externalModules/typeOnly/importEquals2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b64f751fda005..c50fdbff2c495 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2182,7 +2182,12 @@ namespace ts { function getTargetOfImportEqualsDeclaration(node: ImportEqualsDeclaration, dontResolveAlias: boolean): Symbol | undefined { if (node.moduleReference.kind === SyntaxKind.ExternalModuleReference) { - return resolveExternalModuleSymbol(resolveExternalModuleName(node, getExternalModuleImportEqualsDeclarationExpression(node))); + const immediate = resolveExternalModuleName(node, getExternalModuleImportEqualsDeclarationExpression(node)); + const resolved = resolveExternalModuleSymbol(immediate); + if (!markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, immediate)) { + markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); + } + return resolved; } return getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference, dontResolveAlias); } @@ -2278,7 +2283,9 @@ namespace ts { else if (hasSyntheticDefault) { // per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present const resolved = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); - if (!isTypeOnly) markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); + if (!isTypeOnly && !markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, moduleSymbol)) { + markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); + } return resolved; } if (!isTypeOnly) markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, exportDefaultSymbol); @@ -2289,8 +2296,11 @@ namespace ts { function getTargetOfNamespaceImport(node: NamespaceImport, dontResolveAlias: boolean): Symbol | undefined { const moduleSpecifier = node.parent.parent.moduleSpecifier; const isTypeOnly = markSymbolOfAliasDeclarationIfTypeOnly(node); - const resolved = resolveESModuleSymbol(resolveExternalModuleName(node, moduleSpecifier), moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false); - if (!isTypeOnly) markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); + const immediate = resolveExternalModuleName(node, moduleSpecifier); + const resolved = resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false); + if (!isTypeOnly && !markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, immediate)) { + markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); + } return resolved; } @@ -2459,7 +2469,9 @@ namespace ts { function getTargetOfExportAssignment(node: ExportAssignment | BinaryExpression, dontResolveAlias: boolean): Symbol | undefined { const expression = (isExportAssignment(node) ? node.expression : node.right) as EntityNameExpression | ClassExpression; - return getTargetOfAliasLikeExpression(expression, dontResolveAlias); + const resolved = getTargetOfAliasLikeExpression(expression, dontResolveAlias); + markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); + return resolved; } function getTargetOfAliasLikeExpression(expression: Expression, dontResolveAlias: boolean) { @@ -2556,14 +2568,18 @@ namespace ts { return links.target; } - function markSymbolOfAliasDeclarationIfResolvesToTypeOnly(aliasDeclaration: TypeOnlyCompatibleAliasDeclaration | undefined, resolvesToSymbol: Symbol | undefined) { - if (!aliasDeclaration || !resolvesToSymbol) return; + function markSymbolOfAliasDeclarationIfResolvesToTypeOnly(aliasDeclaration: Declaration | undefined, resolvesToSymbol: Symbol | undefined): boolean { + if (!aliasDeclaration || !resolvesToSymbol) return false; const sourceSymbol = getSymbolOfNode(aliasDeclaration); const links = getSymbolLinks(sourceSymbol); if (links.typeOnlyDeclaration === undefined) { + resolvesToSymbol = resolvesToSymbol.exports?.get(InternalSymbolName.ExportEquals) ?? resolvesToSymbol; const typeOnly = find(resolvesToSymbol.declarations, isTypeOnlyImportOrExportDeclaration); - links.typeOnlyDeclaration = typeOnly ?? getSymbolLinks(resolvesToSymbol).typeOnlyDeclaration ?? false; + const decl = typeOnly ?? getSymbolLinks(resolvesToSymbol).typeOnlyDeclaration ?? false; + links.typeOnlyDeclaration = decl; + return !!decl; } + return false; } function markSymbolOfAliasDeclarationIfTypeOnly(aliasDeclaration: TypeOnlyCompatibleAliasDeclaration): boolean { @@ -2711,8 +2727,8 @@ namespace ts { throw Debug.assertNever(name, "Unknown entity name kind."); } Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); - if (isIdentifier(name) && symbol.flags & SymbolFlags.Alias) { - markSymbolOfAliasDeclarationIfResolvesToTypeOnly(getTypeOnlyCompatibleAliasDeclarationFromName(name), symbol); + if (isIdentifier(name) && (symbol.flags & SymbolFlags.Alias || name.parent.kind === SyntaxKind.ExportAssignment)) { + markSymbolOfAliasDeclarationIfResolvesToTypeOnly(getAliasDeclarationFromName(name), symbol); } return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 863076f678399..830e28fc4aec5 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2766,13 +2766,14 @@ namespace ts { node.kind === SyntaxKind.PropertyAssignment && isAliasableExpression((node as PropertyAssignment).initializer); } - export function getTypeOnlyCompatibleAliasDeclarationFromName(node: Identifier): TypeOnlyCompatibleAliasDeclaration | undefined { + export function getAliasDeclarationFromName(node: Identifier): Declaration | undefined { switch (node.parent.kind) { case SyntaxKind.ImportClause: case SyntaxKind.ImportSpecifier: case SyntaxKind.NamespaceImport: case SyntaxKind.ExportSpecifier: - return node.parent as TypeOnlyCompatibleAliasDeclaration; + case SyntaxKind.ExportAssignment: + return node.parent as Declaration; } } diff --git a/tests/baselines/reference/importEquals1.errors.txt b/tests/baselines/reference/importEquals1.errors.txt new file mode 100644 index 0000000000000..8585ff46594de --- /dev/null +++ b/tests/baselines/reference/importEquals1.errors.txt @@ -0,0 +1,45 @@ +/d.ts(2,5): error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. +/e.ts(2,5): error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. +/f.ts(2,5): error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. +/g.ts(2,5): error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. + + +==== /a.ts (0 errors) ==== + export class A {} + +==== /b.ts (0 errors) ==== + import type * as types from './a'; + export = types; // Error + +==== /c.ts (0 errors) ==== + import * as types from './a'; + export = types; + +==== /d.ts (1 errors) ==== + import types from './b'; + new types.A(); // Error + ~~~~~ +!!! error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. +!!! related TS1376 /b.ts:1:13: 'types' was imported here. + +==== /e.ts (1 errors) ==== + import types = require('./b'); + new types.A(); // Error + ~~~~~ +!!! error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. +!!! related TS1376 /b.ts:1:13: 'types' was imported here. + +==== /f.ts (1 errors) ==== + import * as types from './b'; + new types.A(); // Error + ~~~~~ +!!! error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. +!!! related TS1376 /b.ts:1:13: 'types' was imported here. + +==== /g.ts (1 errors) ==== + import type types from './c' + new types.A(); // Error + ~~~~~ +!!! error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. +!!! related TS1376 /g.ts:1:8: 'types' was imported here. + \ No newline at end of file diff --git a/tests/baselines/reference/importEquals1.js b/tests/baselines/reference/importEquals1.js new file mode 100644 index 0000000000000..1789b73d58f3d --- /dev/null +++ b/tests/baselines/reference/importEquals1.js @@ -0,0 +1,69 @@ +//// [tests/cases/conformance/externalModules/typeOnly/importEquals1.ts] //// + +//// [a.ts] +export class A {} + +//// [b.ts] +import type * as types from './a'; +export = types; // Error + +//// [c.ts] +import * as types from './a'; +export = types; + +//// [d.ts] +import types from './b'; +new types.A(); // Error + +//// [e.ts] +import types = require('./b'); +new types.A(); // Error + +//// [f.ts] +import * as types from './b'; +new types.A(); // Error + +//// [g.ts] +import type types from './c' +new types.A(); // Error + + +//// [a.js] +"use strict"; +exports.__esModule = true; +var A = /** @class */ (function () { + function A() { + } + return A; +}()); +exports.A = A; +//// [b.js] +"use strict"; +module.exports = types; +//// [c.js] +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +var types = __importStar(require("./a")); +module.exports = types; +//// [d.js] +"use strict"; +exports.__esModule = true; +new types.A(); // Error +//// [e.js] +"use strict"; +exports.__esModule = true; +new types.A(); // Error +//// [f.js] +"use strict"; +exports.__esModule = true; +new types.A(); // Error +//// [g.js] +"use strict"; +exports.__esModule = true; +new types.A(); // Error diff --git a/tests/baselines/reference/importEquals1.symbols b/tests/baselines/reference/importEquals1.symbols new file mode 100644 index 0000000000000..195e8b6e371c3 --- /dev/null +++ b/tests/baselines/reference/importEquals1.symbols @@ -0,0 +1,54 @@ +=== /a.ts === +export class A {} +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== /b.ts === +import type * as types from './a'; +>types : Symbol(types, Decl(b.ts, 0, 11)) + +export = types; // Error +>types : Symbol(types, Decl(b.ts, 0, 11)) + +=== /c.ts === +import * as types from './a'; +>types : Symbol(types, Decl(c.ts, 0, 6)) + +export = types; +>types : Symbol(types, Decl(c.ts, 0, 6)) + +=== /d.ts === +import types from './b'; +>types : Symbol(types, Decl(d.ts, 0, 6)) + +new types.A(); // Error +>types.A : Symbol(types.A, Decl(a.ts, 0, 0)) +>types : Symbol(types, Decl(d.ts, 0, 6)) +>A : Symbol(types.A, Decl(a.ts, 0, 0)) + +=== /e.ts === +import types = require('./b'); +>types : Symbol(types, Decl(e.ts, 0, 0)) + +new types.A(); // Error +>types.A : Symbol(types.A, Decl(a.ts, 0, 0)) +>types : Symbol(types, Decl(e.ts, 0, 0)) +>A : Symbol(types.A, Decl(a.ts, 0, 0)) + +=== /f.ts === +import * as types from './b'; +>types : Symbol(types, Decl(f.ts, 0, 6)) + +new types.A(); // Error +>types.A : Symbol(types.A, Decl(a.ts, 0, 0)) +>types : Symbol(types, Decl(f.ts, 0, 6)) +>A : Symbol(types.A, Decl(a.ts, 0, 0)) + +=== /g.ts === +import type types from './c' +>types : Symbol(types, Decl(g.ts, 0, 6)) + +new types.A(); // Error +>types.A : Symbol(types.A, Decl(a.ts, 0, 0)) +>types : Symbol(types, Decl(g.ts, 0, 6)) +>A : Symbol(types.A, Decl(a.ts, 0, 0)) + diff --git a/tests/baselines/reference/importEquals1.types b/tests/baselines/reference/importEquals1.types new file mode 100644 index 0000000000000..f9bb4f2697ac0 --- /dev/null +++ b/tests/baselines/reference/importEquals1.types @@ -0,0 +1,58 @@ +=== /a.ts === +export class A {} +>A : A + +=== /b.ts === +import type * as types from './a'; +>types : typeof types + +export = types; // Error +>types : typeof types + +=== /c.ts === +import * as types from './a'; +>types : typeof types + +export = types; +>types : typeof types + +=== /d.ts === +import types from './b'; +>types : typeof types + +new types.A(); // Error +>new types.A() : types.A +>types.A : typeof types.A +>types : typeof types +>A : typeof types.A + +=== /e.ts === +import types = require('./b'); +>types : typeof types + +new types.A(); // Error +>new types.A() : types.A +>types.A : typeof types.A +>types : typeof types +>A : typeof types.A + +=== /f.ts === +import * as types from './b'; +>types : typeof types + +new types.A(); // Error +>new types.A() : types.A +>types.A : typeof types.A +>types : typeof types +>A : typeof types.A + +=== /g.ts === +import type types from './c' +>types : any + +new types.A(); // Error +>new types.A() : types.A +>types.A : typeof types.A +>types : typeof types +>A : typeof types.A + diff --git a/tests/baselines/reference/importEquals2.errors.txt b/tests/baselines/reference/importEquals2.errors.txt new file mode 100644 index 0000000000000..a347e9c04bbe0 --- /dev/null +++ b/tests/baselines/reference/importEquals2.errors.txt @@ -0,0 +1,17 @@ +/c.ts(2,7): error TS2339: Property 'A' does not exist on type 'typeof import("/a")'. + + +==== /c.ts (1 errors) ==== + import a = require('./b'); + new a.A(); // Error + ~ +!!! error TS2339: Property 'A' does not exist on type 'typeof import("/a")'. + +==== /a.ts (0 errors) ==== + class A {} + export type { A } + +==== /b.ts (0 errors) ==== + import * as a from './a'; + export = a; + \ No newline at end of file diff --git a/tests/baselines/reference/importEquals2.js b/tests/baselines/reference/importEquals2.js new file mode 100644 index 0000000000000..05401a6cb539e --- /dev/null +++ b/tests/baselines/reference/importEquals2.js @@ -0,0 +1,39 @@ +//// [tests/cases/conformance/externalModules/typeOnly/importEquals2.ts] //// + +//// [a.ts] +class A {} +export type { A } + +//// [b.ts] +import * as a from './a'; +export = a; + +//// [c.ts] +import a = require('./b'); +new a.A(); // Error + + +//// [a.js] +"use strict"; +exports.__esModule = true; +var A = /** @class */ (function () { + function A() { + } + return A; +}()); +//// [b.js] +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +var a = __importStar(require("./a")); +module.exports = a; +//// [c.js] +"use strict"; +exports.__esModule = true; +var a = require("./b"); +new a.A(); // Error diff --git a/tests/baselines/reference/importEquals2.symbols b/tests/baselines/reference/importEquals2.symbols new file mode 100644 index 0000000000000..84b5dbba668ac --- /dev/null +++ b/tests/baselines/reference/importEquals2.symbols @@ -0,0 +1,21 @@ +=== /c.ts === +import a = require('./b'); +>a : Symbol(a, Decl(c.ts, 0, 0)) + +new a.A(); // Error +>a : Symbol(a, Decl(c.ts, 0, 0)) + +=== /a.ts === +class A {} +>A : Symbol(A, Decl(a.ts, 0, 0)) + +export type { A } +>A : Symbol(A, Decl(a.ts, 1, 13)) + +=== /b.ts === +import * as a from './a'; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +export = a; +>a : Symbol(a, Decl(b.ts, 0, 6)) + diff --git a/tests/baselines/reference/importEquals2.types b/tests/baselines/reference/importEquals2.types new file mode 100644 index 0000000000000..e02fd8846a369 --- /dev/null +++ b/tests/baselines/reference/importEquals2.types @@ -0,0 +1,24 @@ +=== /c.ts === +import a = require('./b'); +>a : typeof a + +new a.A(); // Error +>new a.A() : any +>a.A : any +>a : typeof a +>A : any + +=== /a.ts === +class A {} +>A : A + +export type { A } +>A : A + +=== /b.ts === +import * as a from './a'; +>a : typeof a + +export = a; +>a : typeof a + diff --git a/tests/cases/conformance/externalModules/typeOnly/importEquals1.ts b/tests/cases/conformance/externalModules/typeOnly/importEquals1.ts new file mode 100644 index 0000000000000..e4ea63dcb6f17 --- /dev/null +++ b/tests/cases/conformance/externalModules/typeOnly/importEquals1.ts @@ -0,0 +1,28 @@ +// @esModuleInterop: true + +// @Filename: /a.ts +export class A {} + +// @Filename: /b.ts +import type * as types from './a'; +export = types; // Error + +// @Filename: /c.ts +import * as types from './a'; +export = types; + +// @Filename: /d.ts +import types from './b'; +new types.A(); // Error + +// @Filename: /e.ts +import types = require('./b'); +new types.A(); // Error + +// @Filename: /f.ts +import * as types from './b'; +new types.A(); // Error + +// @Filename: /g.ts +import type types from './c' +new types.A(); // Error diff --git a/tests/cases/conformance/externalModules/typeOnly/importEquals2.ts b/tests/cases/conformance/externalModules/typeOnly/importEquals2.ts new file mode 100644 index 0000000000000..42f8318c706a8 --- /dev/null +++ b/tests/cases/conformance/externalModules/typeOnly/importEquals2.ts @@ -0,0 +1,13 @@ +// @esModuleInterop: true + +// @Filename: /a.ts +class A {} +export type { A } + +// @Filename: /b.ts +import * as a from './a'; +export = a; + +// @Filename: /c.ts +import a = require('./b'); +new a.A(); // Error From 7444b0b72ea2e628ff414b675ef42723a6a7695d Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 28 Jan 2020 14:54:52 -0800 Subject: [PATCH 04/14] Revert apparently unnecessary addition --- src/compiler/checker.ts | 4 ++-- src/compiler/utilities.ts | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c50fdbff2c495..8dd4fb86fea28 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2727,8 +2727,8 @@ namespace ts { throw Debug.assertNever(name, "Unknown entity name kind."); } Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); - if (isIdentifier(name) && (symbol.flags & SymbolFlags.Alias || name.parent.kind === SyntaxKind.ExportAssignment)) { - markSymbolOfAliasDeclarationIfResolvesToTypeOnly(getAliasDeclarationFromName(name), symbol); + if (isIdentifier(name) && symbol.flags & SymbolFlags.Alias) { + markSymbolOfAliasDeclarationIfResolvesToTypeOnly(getTypeOnlyAliasDeclarationFromName(name), symbol); } return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 830e28fc4aec5..e17f60ee15385 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2766,14 +2766,13 @@ namespace ts { node.kind === SyntaxKind.PropertyAssignment && isAliasableExpression((node as PropertyAssignment).initializer); } - export function getAliasDeclarationFromName(node: Identifier): Declaration | undefined { + export function getTypeOnlyAliasDeclarationFromName(node: Identifier): TypeOnlyCompatibleAliasDeclaration | undefined { switch (node.parent.kind) { case SyntaxKind.ImportClause: case SyntaxKind.ImportSpecifier: case SyntaxKind.NamespaceImport: case SyntaxKind.ExportSpecifier: - case SyntaxKind.ExportAssignment: - return node.parent as Declaration; + return node.parent as TypeOnlyCompatibleAliasDeclaration; } } From 5e07751cc16518d3e03fea0d1e1d619aa9346eca Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 28 Jan 2020 15:31:10 -0800 Subject: [PATCH 05/14] Revert "Revert apparently unnecessary addition" This reverts commit 7444b0b72ea2e628ff414b675ef42723a6a7695d. --- src/compiler/checker.ts | 4 ++-- src/compiler/utilities.ts | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8dd4fb86fea28..c50fdbff2c495 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2727,8 +2727,8 @@ namespace ts { throw Debug.assertNever(name, "Unknown entity name kind."); } Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); - if (isIdentifier(name) && symbol.flags & SymbolFlags.Alias) { - markSymbolOfAliasDeclarationIfResolvesToTypeOnly(getTypeOnlyAliasDeclarationFromName(name), symbol); + if (isIdentifier(name) && (symbol.flags & SymbolFlags.Alias || name.parent.kind === SyntaxKind.ExportAssignment)) { + markSymbolOfAliasDeclarationIfResolvesToTypeOnly(getAliasDeclarationFromName(name), symbol); } return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index e17f60ee15385..830e28fc4aec5 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2766,13 +2766,14 @@ namespace ts { node.kind === SyntaxKind.PropertyAssignment && isAliasableExpression((node as PropertyAssignment).initializer); } - export function getTypeOnlyAliasDeclarationFromName(node: Identifier): TypeOnlyCompatibleAliasDeclaration | undefined { + export function getAliasDeclarationFromName(node: Identifier): Declaration | undefined { switch (node.parent.kind) { case SyntaxKind.ImportClause: case SyntaxKind.ImportSpecifier: case SyntaxKind.NamespaceImport: case SyntaxKind.ExportSpecifier: - return node.parent as TypeOnlyCompatibleAliasDeclaration; + case SyntaxKind.ExportAssignment: + return node.parent as Declaration; } } From e69e292fdda70a89e407ba86d5dd44ef04c453ba Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 28 Jan 2020 18:03:37 -0800 Subject: [PATCH 06/14] Disallow `import = a.b.c` on anything with type-only imports --- src/compiler/checker.ts | 46 +++++++++++----- src/compiler/diagnosticMessages.json | 8 +++ src/compiler/utilities.ts | 8 ++- .../reference/importEquals3.errors.txt | 37 +++++++++++++ tests/baselines/reference/importEquals3.js | 42 +++++++++++++++ .../baselines/reference/importEquals3.symbols | 51 ++++++++++++++++++ tests/baselines/reference/importEquals3.types | 53 +++++++++++++++++++ .../externalModules/typeOnly/importEquals3.ts | 18 +++++++ 8 files changed, 249 insertions(+), 14 deletions(-) create mode 100644 tests/baselines/reference/importEquals3.errors.txt create mode 100644 tests/baselines/reference/importEquals3.js create mode 100644 tests/baselines/reference/importEquals3.symbols create mode 100644 tests/baselines/reference/importEquals3.types create mode 100644 tests/cases/conformance/externalModules/typeOnly/importEquals3.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c50fdbff2c495..6e70414c44b81 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2189,7 +2189,23 @@ namespace ts { } return resolved; } - return getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference, dontResolveAlias); + const resolved = getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference, dontResolveAlias); + if (markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved)) { + const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfNode(node))!; + const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier + ? 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 + ? 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); + addRelatedInfo( + error(node.moduleReference, message), + createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name)); + } + return resolved; } function resolveExportByName(moduleSymbol: Symbol, name: __String, sourceNode: TypeOnlyCompatibleAliasDeclaration | undefined, dontResolveAlias: boolean) { @@ -2198,8 +2214,11 @@ namespace ts { return getPropertyOfType(getTypeOfSymbol(exportValue), name); } const exportSymbol = moduleSymbol.exports!.get(name); - markSymbolOfAliasDeclarationIfResolvesToTypeOnly(sourceNode, exportSymbol); - return resolveSymbol(exportSymbol, dontResolveAlias); + const resolved = resolveSymbol(exportSymbol, dontResolveAlias); + if (!markSymbolOfAliasDeclarationIfResolvesToTypeOnly(sourceNode, exportSymbol)) { + markSymbolOfAliasDeclarationIfResolvesToTypeOnly(sourceNode, resolved); + } + return resolved; } function isSyntacticDefault(node: Node) { @@ -2347,8 +2366,11 @@ namespace ts { if (symbol.flags & SymbolFlags.Module) { const name = (specifier.propertyName ?? specifier.name).escapedText; const exportSymbol = getExportsOfSymbol(symbol).get(name); - markSymbolOfAliasDeclarationIfResolvesToTypeOnly(specifier, exportSymbol); - return resolveSymbol(exportSymbol, dontResolveAlias); + const resolved = resolveSymbol(exportSymbol, dontResolveAlias); + if (!markSymbolOfAliasDeclarationIfResolvesToTypeOnly(specifier, exportSymbol)) { + markSymbolOfAliasDeclarationIfResolvesToTypeOnly(specifier, resolved); + } + return resolved; } } @@ -2568,18 +2590,16 @@ namespace ts { return links.target; } - function markSymbolOfAliasDeclarationIfResolvesToTypeOnly(aliasDeclaration: Declaration | undefined, resolvesToSymbol: Symbol | undefined): boolean { + function markSymbolOfAliasDeclarationIfResolvesToTypeOnly(aliasDeclaration: Declaration | undefined, resolvesToSymbol: Symbol | undefined, overwriteEmpty?: boolean): boolean { if (!aliasDeclaration || !resolvesToSymbol) return false; const sourceSymbol = getSymbolOfNode(aliasDeclaration); const links = getSymbolLinks(sourceSymbol); - if (links.typeOnlyDeclaration === undefined) { + if (links.typeOnlyDeclaration === undefined || overwriteEmpty && links.typeOnlyDeclaration === false) { resolvesToSymbol = resolvesToSymbol.exports?.get(InternalSymbolName.ExportEquals) ?? resolvesToSymbol; const typeOnly = find(resolvesToSymbol.declarations, isTypeOnlyImportOrExportDeclaration); - const decl = typeOnly ?? getSymbolLinks(resolvesToSymbol).typeOnlyDeclaration ?? false; - links.typeOnlyDeclaration = decl; - return !!decl; + links.typeOnlyDeclaration = typeOnly ?? getSymbolLinks(resolvesToSymbol).typeOnlyDeclaration ?? false; } - return false; + return !!links.typeOnlyDeclaration; } function markSymbolOfAliasDeclarationIfTypeOnly(aliasDeclaration: TypeOnlyCompatibleAliasDeclaration): boolean { @@ -2727,8 +2747,8 @@ namespace ts { throw Debug.assertNever(name, "Unknown entity name kind."); } Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); - if (isIdentifier(name) && (symbol.flags & SymbolFlags.Alias || name.parent.kind === SyntaxKind.ExportAssignment)) { - markSymbolOfAliasDeclarationIfResolvesToTypeOnly(getAliasDeclarationFromName(name), symbol); + if (isEntityName(name) && (symbol.flags & SymbolFlags.Alias || name.parent.kind === SyntaxKind.ExportAssignment)) { + markSymbolOfAliasDeclarationIfResolvesToTypeOnly(getAliasDeclarationFromName(name), symbol, /*overwriteEmpty*/ true); } return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol); } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 43077e61e6864..2dc894e71fb50 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1127,6 +1127,14 @@ "category": "Error", "code": 1378 }, + "An import alias cannot reference a declaration that was exported using 'export type'.": { + "category": "Error", + "code": 1379 + }, + "An import alias cannot reference a declaration that was imported using 'import type'.": { + "category": "Error", + "code": 1380 + }, "The types of '{0}' are incompatible between these types.": { "category": "Error", diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 830e28fc4aec5..557959dffbaf0 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2766,14 +2766,20 @@ namespace ts { node.kind === SyntaxKind.PropertyAssignment && isAliasableExpression((node as PropertyAssignment).initializer); } - export function getAliasDeclarationFromName(node: Identifier): Declaration | undefined { + export function getAliasDeclarationFromName(node: EntityName): Declaration | undefined { switch (node.parent.kind) { case SyntaxKind.ImportClause: case SyntaxKind.ImportSpecifier: case SyntaxKind.NamespaceImport: case SyntaxKind.ExportSpecifier: case SyntaxKind.ExportAssignment: + case SyntaxKind.ImportEqualsDeclaration: return node.parent as Declaration; + case SyntaxKind.QualifiedName: + do { + node = node.parent as QualifiedName; + } while (node.parent.kind === SyntaxKind.QualifiedName); + return getAliasDeclarationFromName(node); } } diff --git a/tests/baselines/reference/importEquals3.errors.txt b/tests/baselines/reference/importEquals3.errors.txt new file mode 100644 index 0000000000000..75ebc4cb19689 --- /dev/null +++ b/tests/baselines/reference/importEquals3.errors.txt @@ -0,0 +1,37 @@ +tests/cases/conformance/externalModules/typeOnly/b.ts(2,12): error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. +tests/cases/conformance/externalModules/typeOnly/b.ts(3,13): error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. +tests/cases/conformance/externalModules/typeOnly/c.ts(2,12): error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. +tests/cases/conformance/externalModules/typeOnly/c.ts(3,13): error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. + + +==== tests/cases/conformance/externalModules/typeOnly/a.ts (0 errors) ==== + export class A {} + +==== tests/cases/conformance/externalModules/typeOnly/b.ts (2 errors) ==== + import type * as a from './a'; + import A = a.A; // Error + ~~~ +!!! error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. +!!! related TS1376 tests/cases/conformance/externalModules/typeOnly/b.ts:1:13: 'a' was imported here. + import aa = a; // Error + ~ +!!! error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. +!!! related TS1376 tests/cases/conformance/externalModules/typeOnly/b.ts:1:13: 'a' was imported here. + + const x = 0; + export { a, A, x }; + +==== tests/cases/conformance/externalModules/typeOnly/c.ts (2 errors) ==== + import * as b from './b'; + import A = b.a.A; // Error + ~~~~~ +!!! error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. +!!! related TS1376 tests/cases/conformance/externalModules/typeOnly/b.ts:1:13: 'a' was imported here. + import AA = b.A; // Error + ~~~ +!!! error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. +!!! related TS1376 tests/cases/conformance/externalModules/typeOnly/b.ts:1:13: 'a' was imported here. + + import x = b.x; + console.log(x); + \ No newline at end of file diff --git a/tests/baselines/reference/importEquals3.js b/tests/baselines/reference/importEquals3.js new file mode 100644 index 0000000000000..1e76d2dded1e9 --- /dev/null +++ b/tests/baselines/reference/importEquals3.js @@ -0,0 +1,42 @@ +//// [tests/cases/conformance/externalModules/typeOnly/importEquals3.ts] //// + +//// [a.ts] +export class A {} + +//// [b.ts] +import type * as a from './a'; +import A = a.A; // Error +import aa = a; // Error + +const x = 0; +export { a, A, x }; + +//// [c.ts] +import * as b from './b'; +import A = b.a.A; // Error +import AA = b.A; // Error + +import x = b.x; +console.log(x); + + +//// [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; +var x = 0; +exports.x = x; +//// [c.js] +"use strict"; +exports.__esModule = true; +var b = require("./b"); +var x = b.x; +console.log(x); diff --git a/tests/baselines/reference/importEquals3.symbols b/tests/baselines/reference/importEquals3.symbols new file mode 100644 index 0000000000000..28f402d77d7b9 --- /dev/null +++ b/tests/baselines/reference/importEquals3.symbols @@ -0,0 +1,51 @@ +=== 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 === +import type * as a from './a'; +>a : Symbol(a, Decl(b.ts, 0, 11)) + +import A = a.A; // Error +>A : Symbol(A, Decl(b.ts, 0, 30)) +>a : Symbol(a, Decl(b.ts, 0, 11)) +>A : Symbol(a.A, Decl(a.ts, 0, 0)) + +import aa = a; // Error +>aa : Symbol(aa, Decl(b.ts, 1, 15)) +>a : Symbol(a, Decl(b.ts, 0, 11)) + +const x = 0; +>x : Symbol(x, Decl(b.ts, 4, 5)) + +export { a, A, x }; +>a : Symbol(a, Decl(b.ts, 5, 8)) +>A : Symbol(A, Decl(b.ts, 5, 11)) +>x : Symbol(x, Decl(b.ts, 5, 14)) + +=== tests/cases/conformance/externalModules/typeOnly/c.ts === +import * as b from './b'; +>b : Symbol(b, Decl(c.ts, 0, 6)) + +import A = b.a.A; // Error +>A : Symbol(A, Decl(c.ts, 0, 25)) +>b : Symbol(b, Decl(c.ts, 0, 6)) +>a : Symbol(b.a, Decl(b.ts, 5, 8)) +>A : Symbol(b.a.A, Decl(a.ts, 0, 0)) + +import AA = b.A; // Error +>AA : Symbol(AA, Decl(c.ts, 1, 17)) +>b : Symbol(b, Decl(c.ts, 0, 6)) +>A : Symbol(b.A, Decl(b.ts, 5, 11)) + +import x = b.x; +>x : Symbol(x, Decl(c.ts, 2, 16)) +>b : Symbol(b, Decl(c.ts, 0, 6)) +>x : Symbol(b.x, Decl(b.ts, 5, 14)) + +console.log(x); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>x : Symbol(x, Decl(c.ts, 2, 16)) + diff --git a/tests/baselines/reference/importEquals3.types b/tests/baselines/reference/importEquals3.types new file mode 100644 index 0000000000000..ba1eaeefe9514 --- /dev/null +++ b/tests/baselines/reference/importEquals3.types @@ -0,0 +1,53 @@ +=== tests/cases/conformance/externalModules/typeOnly/a.ts === +export class A {} +>A : A + +=== tests/cases/conformance/externalModules/typeOnly/b.ts === +import type * as a from './a'; +>a : typeof a + +import A = a.A; // Error +>A : typeof a.A +>a : typeof a +>A : a.A + +import aa = a; // Error +>aa : typeof a +>a : typeof a + +const x = 0; +>x : 0 +>0 : 0 + +export { a, A, x }; +>a : typeof a +>A : typeof a.A +>x : 0 + +=== tests/cases/conformance/externalModules/typeOnly/c.ts === +import * as b from './b'; +>b : typeof b + +import A = b.a.A; // Error +>A : typeof b.a.A +>b : typeof b +>a : typeof b.a +>A : b.a.A + +import AA = b.A; // Error +>AA : typeof b.a.A +>b : typeof b +>A : b.a.A + +import x = b.x; +>x : 0 +>b : typeof b +>x : 0 + +console.log(x); +>console.log(x) : void +>console.log : (message?: any, ...optionalParams: any[]) => void +>console : Console +>log : (message?: any, ...optionalParams: any[]) => void +>x : 0 + diff --git a/tests/cases/conformance/externalModules/typeOnly/importEquals3.ts b/tests/cases/conformance/externalModules/typeOnly/importEquals3.ts new file mode 100644 index 0000000000000..2a4613d699e19 --- /dev/null +++ b/tests/cases/conformance/externalModules/typeOnly/importEquals3.ts @@ -0,0 +1,18 @@ +// @Filename: a.ts +export class A {} + +// @Filename: b.ts +import type * as a from './a'; +import A = a.A; // Error +import aa = a; // Error + +const x = 0; +export { a, A, x }; + +// @Filename: c.ts +import * as b from './b'; +import A = b.a.A; // Error +import AA = b.A; // Error + +import x = b.x; +console.log(x); From eaaf02c765c2b81c4682d81e75773db7922b8bb9 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 29 Jan 2020 08:18:05 -0800 Subject: [PATCH 07/14] Safety first --- src/compiler/checker.ts | 2 +- src/compiler/core.ts | 2 +- src/compiler/utilities.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6e70414c44b81..ee25573f6a4bf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2596,7 +2596,7 @@ namespace ts { const links = getSymbolLinks(sourceSymbol); if (links.typeOnlyDeclaration === undefined || overwriteEmpty && links.typeOnlyDeclaration === false) { resolvesToSymbol = resolvesToSymbol.exports?.get(InternalSymbolName.ExportEquals) ?? resolvesToSymbol; - const typeOnly = find(resolvesToSymbol.declarations, isTypeOnlyImportOrExportDeclaration); + const typeOnly = resolvesToSymbol.declarations && find(resolvesToSymbol.declarations, isTypeOnlyImportOrExportDeclaration); links.typeOnlyDeclaration = typeOnly ?? getSymbolLinks(resolvesToSymbol).typeOnlyDeclaration ?? false; } return !!links.typeOnlyDeclaration; diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 8d31323a5d8c0..779d079d7d387 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2011,4 +2011,4 @@ namespace ts { } } } -} \ No newline at end of file +} diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 557959dffbaf0..2bda9e21be542 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1794,7 +1794,7 @@ namespace ts { export function isFirstIdentifierOfNonEmittingHeritageClause(node: Node): boolean { // Number of parents to climb from identifier is 2 for `implements I`, 3 for `implements x.I` - const heritageClause = tryCast(node.parent.parent, isHeritageClause) ?? tryCast(node.parent.parent.parent, isHeritageClause); + const heritageClause = tryCast(node.parent.parent, isHeritageClause) ?? tryCast(node.parent.parent?.parent, isHeritageClause); return heritageClause?.token === SyntaxKind.ImplementsKeyword || heritageClause?.parent.kind === SyntaxKind.InterfaceDeclaration; } From d2965afe68be3bab9e3477be81af79d5b0c396e7 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 29 Jan 2020 09:05:44 -0800 Subject: [PATCH 08/14] Add test for TS Server single-file open --- src/testRunner/tsconfig.json | 1 + .../tsserver/typeOnlyImportChains.ts | 104 ++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 src/testRunner/unittests/tsserver/typeOnlyImportChains.ts diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 2cadfc7ccc5a5..01491384b6378 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -185,6 +185,7 @@ "unittests/tsserver/textStorage.ts", "unittests/tsserver/telemetry.ts", "unittests/tsserver/typeAquisition.ts", + "unittests/tsserver/typeOnlyImportChains.ts", "unittests/tsserver/typeReferenceDirectives.ts", "unittests/tsserver/typingsInstaller.ts", "unittests/tsserver/untitledFiles.ts", diff --git a/src/testRunner/unittests/tsserver/typeOnlyImportChains.ts b/src/testRunner/unittests/tsserver/typeOnlyImportChains.ts new file mode 100644 index 0000000000000..7c484983f8535 --- /dev/null +++ b/src/testRunner/unittests/tsserver/typeOnlyImportChains.ts @@ -0,0 +1,104 @@ +namespace ts.projectSystem { + describe("unittests:: tsserver:: typeOnlyImportChains", () => { + it("named export -> type-only namespace import -> named export -> named import", () => { + const a = { + path: "/a.ts", + content: "export class A {}" + }; + const b = { + path: "/b.ts", + content: "import type * as a from './a'; export { a };" + }; + const c = { + path: "/c.ts", + content: "import { a } from './b'; new a.A();" + }; + + assertUsageError([a, b, c], c); + }); + + it("named export -> type-only named import -> named export -> named import", () => { + const a = { + path: "/a.ts", + content: "export class A {}" + }; + const b = { + path: "/b.ts", + content: "import type { A } from './a'; export { A };" + }; + const c = { + path: "/c.ts", + content: "import { A } from './b'; new A();" + }; + + assertUsageError([a, b, c], c); + }); + + it("named export -> type-only namespace import -> export equals -> import equals", () => { + const a = { + path: "/a.ts", + content: "export class A {}" + }; + const b = { + path: "/b.ts", + content: "import type * as a from './a'; export = a;" + }; + const c = { + path: "/c.ts", + content: "import a = require('./b'); new a.A();" + }; + + assertUsageError([a, b, c], c); + }); + + it("named export -> type-only namespace import -> export default -> import default", () => { + const a = { + path: "/a.ts", + content: "export class A {}" + }; + const b = { + path: "/b.ts", + content: "import type * as a from './a'; export default a;" + }; + const c = { + path: "/c.ts", + content: "import a from './b'; new a.A();" + }; + + assertUsageError([a, b, c], c); + }); + + it("export default -> type-only import default -> export default -> import default", () => { + const a = { + path: "/a.ts", + content: "export defai;t class A {}" + }; + const b = { + path: "/b.ts", + content: "import type A from './a'; export default A;" + }; + const c = { + path: "/c.ts", + content: "import A from './b'; new A();" + }; + + assertUsageError([a, b, c], c); + }); + }); + + function assertUsageError(files: readonly TestFSWithWatch.File[], openFile: TestFSWithWatch.File) { + const host = createServerHost(files); + const session = createSession(host); + openFilesForSession([openFile], session); + const req = makeSessionRequest( + protocol.CommandTypes.SemanticDiagnosticsSync, + { file: openFile.path } + ); + const diagnostics = session.executeCommand(req).response as protocol.Diagnostic[]; + assert.lengthOf(diagnostics, 1); + assert.oneOf(diagnostics[0].code, [ + Diagnostics._0_cannot_be_used_as_a_value_because_it_was_imported_using_import_type.code, + Diagnostics._0_cannot_be_used_as_a_value_because_it_was_exported_using_export_type.code + ]); + } +} From 455d3ae01fc38c479227d033aefa01fa9b3570ca Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 29 Jan 2020 09:32:47 -0800 Subject: [PATCH 09/14] Add big comment --- src/compiler/checker.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ee25573f6a4bf..2d0a12ba435b3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2590,6 +2590,18 @@ namespace ts { return links.target; } + /** + * If an alias symbol is not itself marked type-only, but resolves to a type-only alias + * somewhere in its resolution chain, save a reference to the type-only alias declaration + * so the alias _not_ marked type-only can be identified as _transitively_ type-only. + * @param aliasDeclaration The alias declaration not marked as type-only + * @param resolvesToSymbol A symbol somewhere in the resolution chain of the alias symbol + * @param overwriteEmpty Checks `resolvesToSymbol` for type-only declarations even if `aliasDeclaration` + * has already been marked as not resolving to a type-only alias. Used when recursively resolving qualified + * names of import aliases, e.g. `import C = a.b.C`. If namespace `a` is not found to be type-only, the + * import declaration will initially be marked as not resolving to a type-only symbol. But, namespace `b` + * must still be checked for a type-only marker, overwriting the previous negative result if found. + */ function markSymbolOfAliasDeclarationIfResolvesToTypeOnly(aliasDeclaration: Declaration | undefined, resolvesToSymbol: Symbol | undefined, overwriteEmpty?: boolean): boolean { if (!aliasDeclaration || !resolvesToSymbol) return false; const sourceSymbol = getSymbolOfNode(aliasDeclaration); From b7d35b70dad4ca164d40c4ceeecdcb5fa0f0d6f4 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 29 Jan 2020 09:40:59 -0800 Subject: [PATCH 10/14] Extract error reporting function for import aliases --- src/compiler/checker.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2d0a12ba435b3..21e65538e1d2c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2190,6 +2190,12 @@ namespace ts { return resolved; } const resolved = getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference, dontResolveAlias); + checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node, resolved); + return resolved; + + } + + function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) { if (markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved)) { const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfNode(node))!; const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier @@ -2201,11 +2207,8 @@ namespace ts { // 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); - addRelatedInfo( - error(node.moduleReference, message), - createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name)); + addRelatedInfo(error(node.moduleReference, message), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name)); } - return resolved; } function resolveExportByName(moduleSymbol: Symbol, name: __String, sourceNode: TypeOnlyCompatibleAliasDeclaration | undefined, dontResolveAlias: boolean) { From 890ba7498916c2b6151311b30236af140194d12d Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 29 Jan 2020 09:44:40 -0800 Subject: [PATCH 11/14] Delete blank line --- src/compiler/checker.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 21e65538e1d2c..5755d59d95e7b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2192,7 +2192,6 @@ namespace ts { const resolved = getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference, dontResolveAlias); checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node, resolved); return resolved; - } function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) { From a93499995e41738f7f665237e47d476b6e69cf0a Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 29 Jan 2020 11:57:41 -0800 Subject: [PATCH 12/14] Un-export, comment, and colocate some utils --- src/compiler/utilities.ts | 41 ++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index d4dc328b4cddc..c3cadfa617930 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1778,26 +1778,6 @@ namespace ts { return node.kind === SyntaxKind.TypeQuery; } - export function isPartOfPossiblyValidTypeOrAbstractComputedPropertyName(node: Node) { - while (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PropertyAccessExpression) { - node = node.parent; - } - if (node.kind !== SyntaxKind.ComputedPropertyName) { - return false; - } - if (hasModifier(node.parent, ModifierFlags.Abstract)) { - return true; - } - const containerKind = node.parent.parent.kind; - return containerKind === SyntaxKind.InterfaceDeclaration || containerKind === SyntaxKind.TypeLiteral; - } - - export function isFirstIdentifierOfNonEmittingHeritageClause(node: Node): boolean { - // Number of parents to climb from identifier is 2 for `implements I`, 3 for `implements x.I` - const heritageClause = tryCast(node.parent.parent, isHeritageClause) ?? tryCast(node.parent.parent?.parent, isHeritageClause); - return heritageClause?.token === SyntaxKind.ImplementsKeyword || heritageClause?.parent.kind === SyntaxKind.InterfaceDeclaration; - } - export function isExternalModuleImportEqualsDeclaration(node: Node): node is ImportEqualsDeclaration & { moduleReference: ExternalModuleReference } { return node.kind === SyntaxKind.ImportEqualsDeclaration && (node).moduleReference.kind === SyntaxKind.ExternalModuleReference; } @@ -6155,4 +6135,25 @@ namespace ts { || isPartOfPossiblyValidTypeOrAbstractComputedPropertyName(useSite) || !isExpressionNode(useSite); } + + function isPartOfPossiblyValidTypeOrAbstractComputedPropertyName(node: Node) { + while (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PropertyAccessExpression) { + node = node.parent; + } + if (node.kind !== SyntaxKind.ComputedPropertyName) { + return false; + } + if (hasModifier(node.parent, ModifierFlags.Abstract)) { + return true; + } + const containerKind = node.parent.parent.kind; + return containerKind === SyntaxKind.InterfaceDeclaration || containerKind === SyntaxKind.TypeLiteral; + } + + /** Returns true for the first identifier of 1) an `implements` clause, and 2) an `extends` clause of an interface. */ + function isFirstIdentifierOfNonEmittingHeritageClause(node: Node): boolean { + // Number of parents to climb from identifier is 2 for `implements I`, 3 for `implements x.I` + const heritageClause = tryCast(node.parent.parent, isHeritageClause) ?? tryCast(node.parent.parent?.parent, isHeritageClause); + return heritageClause?.token === SyntaxKind.ImplementsKeyword || heritageClause?.parent.kind === SyntaxKind.InterfaceDeclaration; + } } From 9a3f936195dc7a28a7581571bbbab4aad38c1a4a Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 29 Jan 2020 13:26:38 -0800 Subject: [PATCH 13/14] Combine 3 type-only marking function calls into one --- src/compiler/checker.ts | 90 +++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e795e3fb89e0c..10cfdb2b26178 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2222,9 +2222,7 @@ namespace ts { if (node.moduleReference.kind === SyntaxKind.ExternalModuleReference) { const immediate = resolveExternalModuleName(node, getExternalModuleImportEqualsDeclarationExpression(node)); const resolved = resolveExternalModuleSymbol(immediate); - if (!markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, immediate)) { - markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); - } + markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); return resolved; } const resolved = getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference, dontResolveAlias); @@ -2233,7 +2231,7 @@ namespace ts { } function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) { - if (markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved)) { + if (markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false)) { const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfNode(node))!; const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier ? Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_exported_using_export_type @@ -2255,9 +2253,7 @@ namespace ts { } const exportSymbol = moduleSymbol.exports!.get(name); const resolved = resolveSymbol(exportSymbol, dontResolveAlias); - if (!markSymbolOfAliasDeclarationIfResolvesToTypeOnly(sourceNode, exportSymbol)) { - markSymbolOfAliasDeclarationIfResolvesToTypeOnly(sourceNode, resolved); - } + markSymbolOfAliasDeclarationIfTypeOnly(sourceNode, exportSymbol, resolved, /*overwriteEmpty*/ false); return resolved; } @@ -2298,8 +2294,6 @@ namespace ts { function getTargetOfImportClause(node: ImportClause, dontResolveAlias: boolean): Symbol | undefined { const moduleSymbol = resolveExternalModuleName(node, node.parent.moduleSpecifier); - const isTypeOnly = markSymbolOfAliasDeclarationIfTypeOnly(node); - if (moduleSymbol) { let exportDefaultSymbol: Symbol | undefined; if (isShorthandAmbientModuleSymbol(moduleSymbol)) { @@ -2342,24 +2336,19 @@ namespace ts { else if (hasSyntheticDefault) { // per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present const resolved = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); - if (!isTypeOnly && !markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, moduleSymbol)) { - markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); - } + markSymbolOfAliasDeclarationIfTypeOnly(node, moduleSymbol, resolved, /*overwriteTypeOnly*/ false); return resolved; } - if (!isTypeOnly) markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, exportDefaultSymbol); + markSymbolOfAliasDeclarationIfTypeOnly(node, exportDefaultSymbol, /*finalTarget*/ undefined, /*overwriteTypeOnly*/ false); return exportDefaultSymbol; } } function getTargetOfNamespaceImport(node: NamespaceImport, dontResolveAlias: boolean): Symbol | undefined { const moduleSpecifier = node.parent.parent.moduleSpecifier; - const isTypeOnly = markSymbolOfAliasDeclarationIfTypeOnly(node); const immediate = resolveExternalModuleName(node, moduleSpecifier); const resolved = resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false); - if (!isTypeOnly && !markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, immediate)) { - markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); - } + markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); return resolved; } @@ -2407,9 +2396,7 @@ namespace ts { const name = (specifier.propertyName ?? specifier.name).escapedText; const exportSymbol = getExportsOfSymbol(symbol).get(name); const resolved = resolveSymbol(exportSymbol, dontResolveAlias); - if (!markSymbolOfAliasDeclarationIfResolvesToTypeOnly(specifier, exportSymbol)) { - markSymbolOfAliasDeclarationIfResolvesToTypeOnly(specifier, resolved); - } + markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved, /*overwriteEmpty*/ false); return resolved; } } @@ -2510,9 +2497,8 @@ namespace ts { } function getTargetOfImportSpecifier(node: ImportSpecifier, dontResolveAlias: boolean): Symbol | undefined { - const isTypeOnly = markSymbolOfAliasDeclarationIfTypeOnly(node); const resolved = getExternalModuleMember(node.parent.parent.parent, node, dontResolveAlias); - if (!isTypeOnly) markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); + markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); return resolved; } @@ -2521,18 +2507,17 @@ namespace ts { } function getTargetOfExportSpecifier(node: ExportSpecifier, meaning: SymbolFlags, dontResolveAlias?: boolean) { - const isTypeOnly = markSymbolOfAliasDeclarationIfTypeOnly(node); const resolved = node.parent.parent.moduleSpecifier ? getExternalModuleMember(node.parent.parent, node, dontResolveAlias) : resolveEntityName(node.propertyName || node.name, meaning, /*ignoreErrors*/ false, dontResolveAlias); - if (!isTypeOnly) markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); + markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); return resolved; } function getTargetOfExportAssignment(node: ExportAssignment | BinaryExpression, dontResolveAlias: boolean): Symbol | undefined { const expression = (isExportAssignment(node) ? node.expression : node.right) as EntityNameExpression | ClassExpression; const resolved = getTargetOfAliasLikeExpression(expression, dontResolveAlias); - markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); + markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); return resolved; } @@ -2631,37 +2616,54 @@ namespace ts { } /** - * If an alias symbol is not itself marked type-only, but resolves to a type-only alias + * Marks a symbol as type-only if its declaration is syntactically type-only. + * If it is not itself marked type-only, but resolves to a type-only alias * somewhere in its resolution chain, save a reference to the type-only alias declaration * so the alias _not_ marked type-only can be identified as _transitively_ type-only. + * + * This function is called on each alias declaration that could be type-only or resolve to + * another type-only alias during `resolveAlias`, so that later, when an alias is used in a + * JS-emitting expression, we can quickly determine if that symbol is effectively type-only + * and issue an error if so. + * * @param aliasDeclaration The alias declaration not marked as type-only - * @param resolvesToSymbol A symbol somewhere in the resolution chain of the alias symbol - * @param overwriteEmpty Checks `resolvesToSymbol` for type-only declarations even if `aliasDeclaration` * has already been marked as not resolving to a type-only alias. Used when recursively resolving qualified * names of import aliases, e.g. `import C = a.b.C`. If namespace `a` is not found to be type-only, the * import declaration will initially be marked as not resolving to a type-only symbol. But, namespace `b` * must still be checked for a type-only marker, overwriting the previous negative result if found. + * @param immediateTarget The symbol to which the alias declaration immediately resolves + * @param finalTarget The symbol to which the alias declaration ultimately resolves + * @param overwriteEmpty Checks `resolvesToSymbol` for type-only declarations even if `aliasDeclaration` */ - function markSymbolOfAliasDeclarationIfResolvesToTypeOnly(aliasDeclaration: Declaration | undefined, resolvesToSymbol: Symbol | undefined, overwriteEmpty?: boolean): boolean { - if (!aliasDeclaration || !resolvesToSymbol) return false; - const sourceSymbol = getSymbolOfNode(aliasDeclaration); - const links = getSymbolLinks(sourceSymbol); - if (links.typeOnlyDeclaration === undefined || overwriteEmpty && links.typeOnlyDeclaration === false) { - resolvesToSymbol = resolvesToSymbol.exports?.get(InternalSymbolName.ExportEquals) ?? resolvesToSymbol; - const typeOnly = resolvesToSymbol.declarations && find(resolvesToSymbol.declarations, isTypeOnlyImportOrExportDeclaration); - links.typeOnlyDeclaration = typeOnly ?? getSymbolLinks(resolvesToSymbol).typeOnlyDeclaration ?? false; - } - return !!links.typeOnlyDeclaration; - } + function markSymbolOfAliasDeclarationIfTypeOnly( + aliasDeclaration: Declaration | undefined, + immediateTarget: Symbol | undefined, + finalTarget: Symbol | undefined, + overwriteEmpty: boolean, + ): boolean { + if (!aliasDeclaration) return false; - function markSymbolOfAliasDeclarationIfTypeOnly(aliasDeclaration: TypeOnlyCompatibleAliasDeclaration): boolean { + // If the declaration itself is type-only, mark it and return. + // No need to check what it resolves to. + const sourceSymbol = getSymbolOfNode(aliasDeclaration); if (isTypeOnlyImportOrExportDeclaration(aliasDeclaration)) { - const symbol = getSymbolOfNode(aliasDeclaration); - const links = getSymbolLinks(symbol); + const links = getSymbolLinks(sourceSymbol); links.typeOnlyDeclaration = aliasDeclaration; return true; } - return false; + + const links = getSymbolLinks(sourceSymbol); + return markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, immediateTarget, overwriteEmpty) + || markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, finalTarget, overwriteEmpty); + } + + function markSymbolOfAliasDeclarationIfTypeOnlyWorker(aliasDeclarationLinks: SymbolLinks, target: Symbol | undefined, overwriteEmpty: boolean): boolean { + if (target && (aliasDeclarationLinks.typeOnlyDeclaration === undefined || overwriteEmpty && aliasDeclarationLinks.typeOnlyDeclaration === false)) { + const exportSymbol = target.exports?.get(InternalSymbolName.ExportEquals) ?? target; + const typeOnly = exportSymbol.declarations && find(exportSymbol.declarations, isTypeOnlyImportOrExportDeclaration); + aliasDeclarationLinks.typeOnlyDeclaration = typeOnly ?? getSymbolLinks(exportSymbol).typeOnlyDeclaration ?? false; + } + return !!aliasDeclarationLinks.typeOnlyDeclaration; } /** Indicates that a symbol directly or indirectly resolves to a type-only import or export. */ @@ -2800,7 +2802,7 @@ namespace ts { } Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); if (isEntityName(name) && (symbol.flags & SymbolFlags.Alias || name.parent.kind === SyntaxKind.ExportAssignment)) { - markSymbolOfAliasDeclarationIfResolvesToTypeOnly(getAliasDeclarationFromName(name), symbol, /*overwriteEmpty*/ true); + markSymbolOfAliasDeclarationIfTypeOnly(getAliasDeclarationFromName(name), symbol, /*finalTarget*/ undefined, /*overwriteEmpty*/ true); } return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol); } From 0972e5aed031fdf09980fd75914a6281c588c149 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 29 Jan 2020 13:46:54 -0800 Subject: [PATCH 14/14] Add more export default tests --- .../reference/exportDefault.errors.txt | 43 ++++++++++ tests/baselines/reference/exportDefault.js | 80 +++++++++++++++++++ .../baselines/reference/exportDefault.symbols | 50 ++++++++++++ tests/baselines/reference/exportDefault.types | 60 ++++++++++++++ .../externalModules/typeOnly/exportDefault.ts | 28 +++++++ 5 files changed, 261 insertions(+) create mode 100644 tests/baselines/reference/exportDefault.errors.txt create mode 100644 tests/baselines/reference/exportDefault.js create mode 100644 tests/baselines/reference/exportDefault.symbols create mode 100644 tests/baselines/reference/exportDefault.types create mode 100644 tests/cases/conformance/externalModules/typeOnly/exportDefault.ts diff --git a/tests/baselines/reference/exportDefault.errors.txt b/tests/baselines/reference/exportDefault.errors.txt new file mode 100644 index 0000000000000..bd39d7a43c938 --- /dev/null +++ b/tests/baselines/reference/exportDefault.errors.txt @@ -0,0 +1,43 @@ +/d.ts(2,5): error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. +/e.ts(2,11): error TS2339: Property 'A' does not exist on type 'typeof import("/b")'. +/f.ts(2,11): error TS2339: Property 'default' does not exist on type 'typeof import("/b")'. +/g.ts(2,5): error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. + + +==== /a.ts (0 errors) ==== + export class A {} + +==== /b.ts (0 errors) ==== + import type * as types from './a'; + export default types; + +==== /c.ts (0 errors) ==== + import * as types from './a'; + export default types; + +==== /d.ts (1 errors) ==== + import types from './b'; + new types.A(); // Error + ~~~~~ +!!! error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. +!!! related TS1376 /b.ts:1:13: 'types' was imported here. + +==== /e.ts (1 errors) ==== + import types = require('./b'); + new types.A(); // Error + ~ +!!! error TS2339: Property 'A' does not exist on type 'typeof import("/b")'. + +==== /f.ts (1 errors) ==== + import * as types from './b'; + new types.default.A(); // Error + ~~~~~~~ +!!! error TS2339: Property 'default' does not exist on type 'typeof import("/b")'. + +==== /g.ts (1 errors) ==== + import type types from './c' + new types.A(); // Error + ~~~~~ +!!! error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. +!!! related TS1376 /g.ts:1:8: 'types' was imported here. + \ No newline at end of file diff --git a/tests/baselines/reference/exportDefault.js b/tests/baselines/reference/exportDefault.js new file mode 100644 index 0000000000000..ed91b0f593e81 --- /dev/null +++ b/tests/baselines/reference/exportDefault.js @@ -0,0 +1,80 @@ +//// [tests/cases/conformance/externalModules/typeOnly/exportDefault.ts] //// + +//// [a.ts] +export class A {} + +//// [b.ts] +import type * as types from './a'; +export default types; + +//// [c.ts] +import * as types from './a'; +export default types; + +//// [d.ts] +import types from './b'; +new types.A(); // Error + +//// [e.ts] +import types = require('./b'); +new types.A(); // Error + +//// [f.ts] +import * as types from './b'; +new types.default.A(); // Error + +//// [g.ts] +import type types from './c' +new types.A(); // Error + + +//// [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; +exports["default"] = types; +//// [c.js] +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +exports.__esModule = true; +var types = __importStar(require("./a")); +exports["default"] = types; +//// [d.js] +"use strict"; +exports.__esModule = true; +new types.A(); // Error +//// [e.js] +"use strict"; +exports.__esModule = true; +var types = require("./b"); +new types.A(); // Error +//// [f.js] +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +exports.__esModule = true; +var types = __importStar(require("./b")); +new types["default"].A(); // Error +//// [g.js] +"use strict"; +exports.__esModule = true; +new types.A(); // Error diff --git a/tests/baselines/reference/exportDefault.symbols b/tests/baselines/reference/exportDefault.symbols new file mode 100644 index 0000000000000..6d2b5a72bd7ad --- /dev/null +++ b/tests/baselines/reference/exportDefault.symbols @@ -0,0 +1,50 @@ +=== /a.ts === +export class A {} +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== /b.ts === +import type * as types from './a'; +>types : Symbol(types, Decl(b.ts, 0, 11)) + +export default types; +>types : Symbol(types, Decl(b.ts, 0, 11)) + +=== /c.ts === +import * as types from './a'; +>types : Symbol(types, Decl(c.ts, 0, 6)) + +export default types; +>types : Symbol(types, Decl(c.ts, 0, 6)) + +=== /d.ts === +import types from './b'; +>types : Symbol(types, Decl(d.ts, 0, 6)) + +new types.A(); // Error +>types.A : Symbol(types.A, Decl(a.ts, 0, 0)) +>types : Symbol(types, Decl(d.ts, 0, 6)) +>A : Symbol(types.A, Decl(a.ts, 0, 0)) + +=== /e.ts === +import types = require('./b'); +>types : Symbol(types, Decl(e.ts, 0, 0)) + +new types.A(); // Error +>types : Symbol(types, Decl(e.ts, 0, 0)) + +=== /f.ts === +import * as types from './b'; +>types : Symbol(types, Decl(f.ts, 0, 6)) + +new types.default.A(); // Error +>types : Symbol(types, Decl(f.ts, 0, 6)) + +=== /g.ts === +import type types from './c' +>types : Symbol(types, Decl(g.ts, 0, 6)) + +new types.A(); // Error +>types.A : Symbol(types.A, Decl(a.ts, 0, 0)) +>types : Symbol(types, Decl(g.ts, 0, 6)) +>A : Symbol(types.A, Decl(a.ts, 0, 0)) + diff --git a/tests/baselines/reference/exportDefault.types b/tests/baselines/reference/exportDefault.types new file mode 100644 index 0000000000000..5fcdb6460115c --- /dev/null +++ b/tests/baselines/reference/exportDefault.types @@ -0,0 +1,60 @@ +=== /a.ts === +export class A {} +>A : A + +=== /b.ts === +import type * as types from './a'; +>types : typeof types + +export default types; +>types : typeof types + +=== /c.ts === +import * as types from './a'; +>types : typeof types + +export default types; +>types : typeof types + +=== /d.ts === +import types from './b'; +>types : typeof types + +new types.A(); // Error +>new types.A() : types.A +>types.A : typeof types.A +>types : typeof types +>A : typeof types.A + +=== /e.ts === +import types = require('./b'); +>types : typeof types + +new types.A(); // Error +>new types.A() : any +>types.A : any +>types : typeof types +>A : any + +=== /f.ts === +import * as types from './b'; +>types : typeof types + +new types.default.A(); // Error +>new types.default.A() : any +>types.default.A : any +>types.default : any +>types : typeof types +>default : any +>A : any + +=== /g.ts === +import type types from './c' +>types : any + +new types.A(); // Error +>new types.A() : types.A +>types.A : typeof types.A +>types : typeof types +>A : typeof types.A + diff --git a/tests/cases/conformance/externalModules/typeOnly/exportDefault.ts b/tests/cases/conformance/externalModules/typeOnly/exportDefault.ts new file mode 100644 index 0000000000000..9cbe305676383 --- /dev/null +++ b/tests/cases/conformance/externalModules/typeOnly/exportDefault.ts @@ -0,0 +1,28 @@ +// @esModuleInterop: true + +// @Filename: /a.ts +export class A {} + +// @Filename: /b.ts +import type * as types from './a'; +export default types; + +// @Filename: /c.ts +import * as types from './a'; +export default types; + +// @Filename: /d.ts +import types from './b'; +new types.A(); // Error + +// @Filename: /e.ts +import types = require('./b'); +new types.A(); // Error + +// @Filename: /f.ts +import * as types from './b'; +new types.default.A(); // Error + +// @Filename: /g.ts +import type types from './c' +new types.A(); // Error