Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Type-only imports and exports #35200

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 50 commits into from
Jan 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
426685a
Add type-only support for export declarations
andrewbranch Nov 12, 2019
856fe0f
Use a synthetic type alias instead of binding type-only exports as a …
andrewbranch Nov 13, 2019
9c659f6
Works for re-exports!
andrewbranch Nov 13, 2019
a3d16f6
isolatedModules works fine
andrewbranch Nov 13, 2019
edfc798
Diagnostic for type-only exporting a value
andrewbranch Nov 13, 2019
84238f9
Start isolated modules codefix
andrewbranch Nov 14, 2019
5d2bc5d
Update for LKG control flow changes
andrewbranch Nov 15, 2019
e736045
Type-only import clause parsing
andrewbranch Nov 18, 2019
558dd49
Type-only default import checking
andrewbranch Nov 18, 2019
151a397
Type-only named imports
andrewbranch Nov 18, 2019
85390d7
Fix isolated modules error
andrewbranch Nov 18, 2019
e130453
Filter namespaces down to type-only
andrewbranch Nov 18, 2019
1db2773
Fix class references
andrewbranch Nov 19, 2019
bd73f27
Test nested namespaces
andrewbranch Nov 19, 2019
fa72cf5
Test circular type-only imports/exports
andrewbranch Nov 19, 2019
6de5150
Fix getTypeAtLocation for type-only import/export specifiers
andrewbranch Nov 20, 2019
9bf28fe
Fix type-only generic imports
andrewbranch Nov 20, 2019
ed5dcf4
Update public APIs
andrewbranch Nov 20, 2019
3b45e98
Remove unused WIP comment
andrewbranch Nov 20, 2019
48de63b
Type-only namespace imports
andrewbranch Nov 20, 2019
113823d
Fix factory update calls
andrewbranch Nov 20, 2019
d69eed0
Add grammar errors for JS usage and mixing default and named bindings
andrewbranch Nov 21, 2019
c048c9a
Update updateExportDeclaration API baseline
andrewbranch Nov 21, 2019
2227f4e
Fix grammar checking import clauses
andrewbranch Nov 21, 2019
421d333
Enums, sort of
andrewbranch Nov 21, 2019
26704c0
Dedicated error for type-only enum
andrewbranch Nov 21, 2019
e8d4c08
Skip past type-only alias symbols in quick info
andrewbranch Nov 22, 2019
1c10aa1
Update error code in baseline
andrewbranch Nov 22, 2019
16c2534
WIP: convertToTypeOnlyExport
andrewbranch Nov 22, 2019
debc8cb
isolatedModules codefix (single export declaration)
andrewbranch Nov 22, 2019
ae00f75
isolatedModules code fix (all)
andrewbranch Nov 22, 2019
f823bf5
Stop eliding non-type-only imports by default, add compiler flag
andrewbranch Nov 25, 2019
12bd78d
Update to match updated diagnostic messages
andrewbranch Nov 25, 2019
3724f88
Update more baselines
andrewbranch Nov 25, 2019
42fc34a
Update more tests
andrewbranch Nov 25, 2019
73cd8ab
Auto-import as type-only
andrewbranch Nov 25, 2019
f846ff1
Add codefix for splitting type-only import with default and named bin…
andrewbranch Nov 26, 2019
b64effa
Add more services tests
andrewbranch Nov 26, 2019
010273a
Add targeted error message for "export type T;" when T exists
andrewbranch Nov 26, 2019
4992945
Add targeted error for "import type T = require(...)"
andrewbranch Nov 26, 2019
9490df5
Merge branch 'master' into feature/type-only
andrewbranch Dec 2, 2019
a2bdecf
Merge branch 'master' into feature/type-only
andrewbranch Dec 20, 2019
f7e889b
Flip emit flag
andrewbranch Dec 20, 2019
0c2df8e
Add test for preserveUnusedImports option
andrewbranch Dec 20, 2019
9b94bf9
Fix flag flip on import =
andrewbranch Dec 20, 2019
ae78865
Make compiler option string-valued
andrewbranch Dec 31, 2019
6b548bb
Merge branch 'master' into feature/type-only
andrewbranch Dec 31, 2019
8b279a9
Fix merge conflicts
andrewbranch Dec 31, 2019
f8333d0
Add --importsNotUsedAsValue=error
andrewbranch Dec 31, 2019
c71d87b
Phrasing of messages.
DanielRosenwasser Jan 3, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,8 +517,13 @@ namespace ts {
}
}

const declarationName = getNameOfDeclaration(node) || node;
const relatedInformation: DiagnosticRelatedInformation[] = [];
if (isTypeAliasDeclaration(node) && nodeIsMissing(node.type) && hasModifier(node, ModifierFlags.Export) && symbol.flags & (SymbolFlags.Alias | SymbolFlags.Type | SymbolFlags.Namespace)) {
// export type T; - may have meant export type { T }?
relatedInformation.push(createDiagnosticForNode(node, Diagnostics.Did_you_mean_0, `export type { ${unescapeLeadingUnderscores(node.name.escapedText)} }`));
}

const declarationName = getNameOfDeclaration(node) || node;
forEach(symbol.declarations, (declaration, index) => {
const decl = getNameOfDeclaration(declaration) || declaration;
const diag = createDiagnosticForNode(decl, message, messageNeedsName ? getDisplayName(declaration) : undefined);
Expand All @@ -531,7 +536,7 @@ namespace ts {
});

const diag = createDiagnosticForNode(declarationName, message, messageNeedsName ? getDisplayName(node) : undefined);
file.bindDiagnostics.push(multipleDefaultExports ? addRelatedInfo(diag, ...relatedInformation) : diag);
file.bindDiagnostics.push(addRelatedInfo(diag, ...relatedInformation));

symbol = createSymbol(SymbolFlags.None, name);
}
Expand Down
200 changes: 178 additions & 22 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,17 @@ namespace ts {
category: Diagnostics.Basic_Options,
description: Diagnostics.Import_emit_helpers_from_tslib
},
{
name: "importsNotUsedAsValue",
type: createMapFromTemplate({
remove: ImportsNotUsedAsValue.Remove,
preserve: ImportsNotUsedAsValue.Preserve,
error: ImportsNotUsedAsValue.Error
}),
affectsEmit: true,
category: Diagnostics.Advanced_Options,
description: Diagnostics.Specify_emit_Slashchecking_behavior_for_imports_that_are_only_used_for_types
},
{
name: "downlevelIteration",
type: "boolean",
Expand Down
13 changes: 10 additions & 3 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1509,6 +1509,13 @@ namespace ts {
return compareComparableValues(a, b);
}

/**
* Compare two TextSpans, first by `start`, then by `length`.
*/
export function compareTextSpans(a: Partial<TextSpan> | undefined, b: Partial<TextSpan> | undefined): Comparison {
return compareValues(a?.start, b?.start) || compareValues(a?.length, b?.length);
}

export function min<T>(a: T, b: T, compare: Comparer<T>): T {
return compare(a, b) === Comparison.LessThan ? a : b;
}
Expand Down Expand Up @@ -1914,10 +1921,10 @@ namespace ts {
return (arg: T) => f(arg) && g(arg);
}

export function or<T extends unknown>(...fs: ((arg: T) => boolean)[]): (arg: T) => boolean {
return arg => {
export function or<T extends unknown[]>(...fs: ((...args: T) => boolean)[]): (...args: T) => boolean {
return (...args) => {
for (const f of fs) {
if (f(arg)) {
if (f(...args)) {
return true;
}
}
Expand Down
60 changes: 58 additions & 2 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@
"category": "Error",
"code": 1203
},
"Cannot re-export a type when the '--isolatedModules' flag is provided.": {
"Re-exporting a type when the '--isolatedModules' flag is provided requires using 'export type'.": {
"category": "Error",
"code": 1205
},
Expand Down Expand Up @@ -1059,10 +1059,66 @@
"category": "Error",
"code": 1360
},
"'await' outside of an async function is only allowed at the top level of a module when '--module' is 'esnext' or 'system' and '--target' is 'es2017' or higher.": {
"Type-only {0} must reference a type, but '{1}' is a value.": {
"category": "Error",
"code": 1361
},
"Enum '{0}' cannot be used as a value because only its type has been imported.": {
"category": "Error",
"code": 1362
},
"A type-only import can specify a default import or named bindings, but not both.": {
"category": "Error",
"code": 1363
},
"Convert to type-only export": {
"category": "Message",
"code": 1364
},
"Convert all re-exported types to type-only exports": {
"category": "Message",
"code": 1365
},
"Split into two separate import declarations": {
"category": "Message",
"code": 1366
},
"Split all invalid type-only imports": {
"category": "Message",
"code": 1377
},
"Specify emit/checking behavior for imports that are only used for types": {
"category": "Message",
"code": 1368
},
"Did you mean '{0}'?": {
"category": "Message",
"code": 1369
},
"Only ECMAScript imports may use 'import type'.": {
"category": "Error",
"code": 1370
},
"This import is never used as a value and must use 'import type' because the 'importsNotUsedAsValue' is set to 'error'.": {
"category": "Error",
"code": 1371
},
"This import may be converted to a type-only import.": {
"category": "Suggestion",
"code": 1372
},
"Convert to type-only import": {
"category": "Message",
"code": 1373
},
"Convert all imports not used as a value to type-only imports": {
"category": "Message",
"code": 1374
},
"'await' outside of an async function is only allowed at the top level of a module when '--module' is 'esnext' or 'system' and '--target' is 'es2017' or higher.": {
"category": "Error",
"code": 1375
},

"The types of '{0}' are incompatible between these types.": {
"category": "Error",
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3048,6 +3048,10 @@ namespace ts {
}

function emitImportClause(node: ImportClause) {
if (node.isTypeOnly) {
emitTokenWithComment(SyntaxKind.TypeKeyword, node.pos, writeKeyword, node);
writeSpace();
}
emit(node.name);
if (node.name && node.namedBindings) {
emitTokenWithComment(SyntaxKind.CommaToken, node.name.end, writePunctuation, node);
Expand Down Expand Up @@ -3089,6 +3093,10 @@ namespace ts {
function emitExportDeclaration(node: ExportDeclaration) {
let nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node);
writeSpace();
if (node.isTypeOnly) {
nextPos = emitTokenWithComment(SyntaxKind.TypeKeyword, nextPos, writeKeyword, node);
writeSpace();
}
if (node.exportClause) {
emit(node.exportClause);
}
Expand Down
17 changes: 11 additions & 6 deletions src/compiler/factoryPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2267,17 +2267,19 @@ namespace ts {
: node;
}

export function createImportClause(name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause {
export function createImportClause(name: Identifier | undefined, namedBindings: NamedImportBindings | undefined, isTypeOnly = false): ImportClause {
const node = <ImportClause>createSynthesizedNode(SyntaxKind.ImportClause);
node.name = name;
node.namedBindings = namedBindings;
node.isTypeOnly = isTypeOnly;
return node;
}

export function updateImportClause(node: ImportClause, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined) {
export function updateImportClause(node: ImportClause, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined, isTypeOnly: boolean) {
return node.name !== name
|| node.namedBindings !== namedBindings
? updateNode(createImportClause(name, namedBindings), node)
|| node.isTypeOnly !== isTypeOnly
? updateNode(createImportClause(name, namedBindings, isTypeOnly), node)
: node;
}

Expand Down Expand Up @@ -2348,10 +2350,11 @@ namespace ts {
: node;
}

export function createExportDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, exportClause: NamedExportBindings | undefined, moduleSpecifier?: Expression) {
export function createExportDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, exportClause: NamedExportBindings | undefined, moduleSpecifier?: Expression, isTypeOnly = false) {
const node = <ExportDeclaration>createSynthesizedNode(SyntaxKind.ExportDeclaration);
node.decorators = asNodeArray(decorators);
node.modifiers = asNodeArray(modifiers);
node.isTypeOnly = isTypeOnly;
node.exportClause = exportClause;
node.moduleSpecifier = moduleSpecifier;
return node;
Expand All @@ -2362,12 +2365,14 @@ namespace ts {
decorators: readonly Decorator[] | undefined,
modifiers: readonly Modifier[] | undefined,
exportClause: NamedExportBindings | undefined,
moduleSpecifier: Expression | undefined) {
moduleSpecifier: Expression | undefined,
isTypeOnly: boolean) {
return node.decorators !== decorators
|| node.modifiers !== modifiers
|| node.isTypeOnly !== isTypeOnly
|| node.exportClause !== exportClause
|| node.moduleSpecifier !== moduleSpecifier
? updateNode(createExportDeclaration(decorators, modifiers, exportClause, moduleSpecifier), node)
? updateNode(createExportDeclaration(decorators, modifiers, exportClause, moduleSpecifier, isTypeOnly), node)
: node;
}

Expand Down
74 changes: 60 additions & 14 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1516,7 +1516,10 @@ namespace ts {
if (token() === SyntaxKind.DefaultKeyword) {
return lookAhead(nextTokenCanFollowDefaultKeyword);
}
return token() !== SyntaxKind.AsteriskToken && token() !== SyntaxKind.AsKeyword && token() !== SyntaxKind.OpenBraceToken && canFollowModifier();
if (token() === SyntaxKind.TypeKeyword) {
return lookAhead(nextTokenCanFollowExportModifier);
}
return canFollowExportModifier();
case SyntaxKind.DefaultKeyword:
return nextTokenCanFollowDefaultKeyword();
case SyntaxKind.StaticKeyword:
Expand All @@ -1529,6 +1532,18 @@ namespace ts {
}
}

function canFollowExportModifier(): boolean {
return token() !== SyntaxKind.AsteriskToken
&& token() !== SyntaxKind.AsKeyword
&& token() !== SyntaxKind.OpenBraceToken
&& canFollowModifier();
}

function nextTokenCanFollowExportModifier(): boolean {
nextToken();
return canFollowExportModifier();
}

function parseAnyContextualModifier(): boolean {
return isModifierKind(token()) && tryParse(nextTokenCanFollowModifier);
}
Expand Down Expand Up @@ -5470,10 +5485,13 @@ namespace ts {
return token() === SyntaxKind.StringLiteral || token() === SyntaxKind.AsteriskToken ||
token() === SyntaxKind.OpenBraceToken || tokenIsIdentifierOrKeyword(token());
case SyntaxKind.ExportKeyword:
nextToken();
if (token() === SyntaxKind.EqualsToken || token() === SyntaxKind.AsteriskToken ||
token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.DefaultKeyword ||
token() === SyntaxKind.AsKeyword) {
let currentToken = nextToken();
if (currentToken === SyntaxKind.TypeKeyword) {
currentToken = lookAhead(nextToken);
}
if (currentToken === SyntaxKind.EqualsToken || currentToken === SyntaxKind.AsteriskToken ||
currentToken === SyntaxKind.OpenBraceToken || currentToken === SyntaxKind.DefaultKeyword ||
currentToken === SyntaxKind.AsKeyword) {
return true;
}
continue;
Expand Down Expand Up @@ -6355,9 +6373,19 @@ namespace ts {
let identifier: Identifier | undefined;
if (isIdentifier()) {
identifier = parseIdentifier();
if (token() !== SyntaxKind.CommaToken && token() !== SyntaxKind.FromKeyword) {
return parseImportEqualsDeclaration(<ImportEqualsDeclaration>node, identifier);
}
}

let isTypeOnly = false;
if (token() !== SyntaxKind.FromKeyword &&
identifier?.escapedText === "type" &&
(isIdentifier() || tokenAfterImportDefinitelyProducesImportDeclaration())
) {
isTypeOnly = true;
identifier = isIdentifier() ? parseIdentifier() : undefined;
}

if (identifier && !tokenAfterImportedIdentifierDefinitelyProducesImportDeclaration()) {
return parseImportEqualsDeclaration(<ImportEqualsDeclaration>node, identifier, isTypeOnly);
}

// Import statement
Expand All @@ -6366,9 +6394,10 @@ namespace ts {
// import ImportClause from ModuleSpecifier ;
// import ModuleSpecifier;
if (identifier || // import id
token() === SyntaxKind.AsteriskToken || // import *
token() === SyntaxKind.OpenBraceToken) { // import {
(<ImportDeclaration>node).importClause = parseImportClause(identifier, afterImportPos);
token() === SyntaxKind.AsteriskToken || // import *
token() === SyntaxKind.OpenBraceToken // import {
) {
(<ImportDeclaration>node).importClause = parseImportClause(identifier, afterImportPos, isTypeOnly);
parseExpected(SyntaxKind.FromKeyword);
}

Expand All @@ -6377,16 +6406,30 @@ namespace ts {
return finishNode(node);
}

function parseImportEqualsDeclaration(node: ImportEqualsDeclaration, identifier: Identifier): ImportEqualsDeclaration {
function tokenAfterImportDefinitelyProducesImportDeclaration() {
return token() === SyntaxKind.AsteriskToken || token() === SyntaxKind.OpenBraceToken;
}

function tokenAfterImportedIdentifierDefinitelyProducesImportDeclaration() {
// In `import id ___`, the current token decides whether to produce
// an ImportDeclaration or ImportEqualsDeclaration.
return token() === SyntaxKind.CommaToken || token() === SyntaxKind.FromKeyword;
}

function parseImportEqualsDeclaration(node: ImportEqualsDeclaration, identifier: Identifier, isTypeOnly: boolean): ImportEqualsDeclaration {
node.kind = SyntaxKind.ImportEqualsDeclaration;
node.name = identifier;
parseExpected(SyntaxKind.EqualsToken);
node.moduleReference = parseModuleReference();
parseSemicolon();
return finishNode(node);
const finished = finishNode(node);
if (isTypeOnly) {
parseErrorAtRange(finished, Diagnostics.Only_ECMAScript_imports_may_use_import_type);
}
return finished;
}

function parseImportClause(identifier: Identifier | undefined, fullStart: number) {
function parseImportClause(identifier: Identifier | undefined, fullStart: number, isTypeOnly: boolean) {
// ImportClause:
// ImportedDefaultBinding
// NameSpaceImport
Expand All @@ -6395,6 +6438,8 @@ namespace ts {
// ImportedDefaultBinding, NamedImports

const importClause = <ImportClause>createNode(SyntaxKind.ImportClause, fullStart);
importClause.isTypeOnly = isTypeOnly;

if (identifier) {
// ImportedDefaultBinding:
// ImportedBinding
Expand Down Expand Up @@ -6514,6 +6559,7 @@ namespace ts {

function parseExportDeclaration(node: ExportDeclaration): ExportDeclaration {
node.kind = SyntaxKind.ExportDeclaration;
node.isTypeOnly = parseOptional(SyntaxKind.TypeKeyword);
if (parseOptional(SyntaxKind.AsteriskToken)) {
if (parseOptional(SyntaxKind.AsKeyword)) {
node.exportClause = parseNamespaceExport();
Expand Down
12 changes: 12 additions & 0 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1820,6 +1820,18 @@ namespace ts {
}

switch (node.kind) {
case SyntaxKind.ImportClause:
if ((node as ImportClause).isTypeOnly) {
diagnostics.push(createDiagnosticForNode(node.parent, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, "import type"));
return;
}
break;
case SyntaxKind.ExportDeclaration:
if ((node as ExportDeclaration).isTypeOnly) {
diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, "export type"));
return;
}
break;
case SyntaxKind.ImportEqualsDeclaration:
diagnostics.push(createDiagnosticForNode(node, Diagnostics.import_can_only_be_used_in_TypeScript_files));
return;
Expand Down
Loading