Type-only import specifiers (#45998)

* Parse type-only import specifiers

* Add type-only export specifiers

* Update transform and emit

* Update checking

* Fix elision when combined with importsNotUsedAsValues=preserve

* Accept baselines

* Add test

* WIP auto imports updates

* First auto-imports test working

* More auto-import tests

* Fix auto imports of type-only exports

* Add test for promoting type-only import

* Sort import/export specifiers by type-onlyness

* Update completions for `import { type |`

* Update other completions tests

* Respect organize imports sorting when promoting type-only to regular while adding a specifier

* Fix comment mistakes

* Update src/services/codefixes/importFixes.ts

Co-authored-by: Daniel Rosenwasser <DanielRosenwasser@users.noreply.github.com>

* Rearrange some order of assignments in parser

* Split huge if statement

* Remove redundant check

* Update new transformer

* Fix import statement completions

* Fix type keyword completions good grief

* Fix last tests

Co-authored-by: Daniel Rosenwasser <DanielRosenwasser@users.noreply.github.com>
This commit is contained in:
Andrew Branch 2021-09-27 12:38:30 -07:00 committed by GitHub
parent 26aef89a72
commit e160bc8c0d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
80 changed files with 1842 additions and 349 deletions

View file

@ -721,6 +721,7 @@ namespace ts {
getLocalTypeParametersOfClassOrInterfaceOrTypeAlias,
isDeclarationVisible,
isPropertyAccessible,
getTypeOnlyAliasDeclaration,
};
function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined {
@ -2203,8 +2204,7 @@ namespace ts {
if (!isValidTypeOnlyAliasUseSite(useSite)) {
const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(symbol);
if (typeOnlyDeclaration) {
const isExport = typeOnlyDeclarationIsExport(typeOnlyDeclaration);
const message = isExport
const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier
? Diagnostics._0_cannot_be_used_as_a_value_because_it_was_exported_using_export_type
: Diagnostics._0_cannot_be_used_as_a_value_because_it_was_imported_using_import_type;
const unescapedName = unescapeLeadingUnderscores(name);
@ -2222,7 +2222,7 @@ namespace ts {
diagnostic,
createDiagnosticForNode(
typeOnlyDeclaration,
typeOnlyDeclarationIsExport(typeOnlyDeclaration) ? Diagnostics._0_was_exported_here : Diagnostics._0_was_imported_here,
typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier ? Diagnostics._0_was_exported_here : Diagnostics._0_was_imported_here,
unescapedName));
}
@ -2590,7 +2590,7 @@ namespace ts {
function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) {
if (markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false) && !node.isTypeOnly) {
const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfNode(node))!;
const isExport = typeOnlyDeclarationIsExport(typeOnlyDeclaration);
const isExport = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier;
const message = isExport
? Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_exported_using_export_type
: Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_imported_using_import_type;
@ -2598,9 +2598,7 @@ namespace ts {
? 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);
const name = unescapeLeadingUnderscores(typeOnlyDeclaration.name.escapedText);
addRelatedInfo(error(node.moduleReference, message), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name));
}
}
@ -3054,13 +3052,13 @@ namespace ts {
* and issue an error if so.
*
* @param aliasDeclaration The alias declaration not marked as type-only
* @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`
* 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 markSymbolOfAliasDeclarationIfTypeOnly(
aliasDeclaration: Declaration | undefined,
@ -3094,7 +3092,7 @@ namespace ts {
}
/** Indicates that a symbol directly or indirectly resolves to a type-only import or export. */
function getTypeOnlyAliasDeclaration(symbol: Symbol): TypeOnlyCompatibleAliasDeclaration | undefined {
function getTypeOnlyAliasDeclaration(symbol: Symbol): TypeOnlyAliasDeclaration | undefined {
if (!(symbol.flags & SymbolFlags.Alias)) {
return undefined;
}
@ -6536,7 +6534,7 @@ namespace ts {
/*decorators*/ undefined,
/*modifiers*/ undefined,
/*isTypeOnly*/ false,
factory.createNamedExports(map(flatMap(excessExports, e => getNamesOfDeclaration(e)), id => factory.createExportSpecifier(/*alias*/ undefined, id))),
factory.createNamedExports(map(flatMap(excessExports, e => getNamesOfDeclaration(e)), id => factory.createExportSpecifier(/*isTypeOnly*/ false, /*alias*/ undefined, id))),
/*moduleSpecifier*/ undefined
)])
)
@ -6794,7 +6792,7 @@ namespace ts {
/*decorators*/ undefined,
/*modifiers*/ undefined,
/*isTypeOnly*/ false,
factory.createNamedExports([factory.createExportSpecifier(alias, localName)])
factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, alias, localName)])
),
ModifierFlags.None
);
@ -6832,7 +6830,7 @@ namespace ts {
/*decorators*/ undefined,
/*modifiers*/ undefined,
/*isTypeOnly*/ false,
factory.createNamedExports([factory.createExportSpecifier(name, localName)])
factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, name, localName)])
),
ModifierFlags.None
);
@ -6892,7 +6890,7 @@ namespace ts {
/*decorators*/ undefined,
/*modifiers*/ undefined,
/*isTypeOnly*/ false,
factory.createNamedExports([factory.createExportSpecifier(getInternalSymbolName(symbol, symbolName), symbolName)])
factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, getInternalSymbolName(symbol, symbolName), symbolName)])
), ModifierFlags.None);
}
}
@ -7031,7 +7029,7 @@ namespace ts {
const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true);
includePrivateSymbol(target || s);
const targetName = target ? getInternalSymbolName(target, unescapeLeadingUnderscores(target.escapedName)) : localName;
return factory.createExportSpecifier(name === targetName ? undefined : targetName, name);
return factory.createExportSpecifier(/*isTypeOnly*/ false, name === targetName ? undefined : targetName, name);
}))
)]);
addResult(factory.createModuleDeclaration(
@ -7137,7 +7135,7 @@ namespace ts {
/*decorators*/ undefined,
/*modifiers*/ undefined,
/*isTypeOnly*/ false,
factory.createNamedExports([factory.createExportSpecifier(d.expression, factory.createIdentifier(InternalSymbolName.Default))])
factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, d.expression, factory.createIdentifier(InternalSymbolName.Default))])
) : d);
const exportModifierStripped = every(defaultReplaced, d => hasSyntacticModifier(d, ModifierFlags.Export)) ? map(defaultReplaced, removeExportModifier) : defaultReplaced;
fakespace = factory.updateModuleDeclaration(
@ -7313,6 +7311,7 @@ namespace ts {
/*decorators*/ undefined,
/*modifiers*/ undefined,
factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamedImports([factory.createImportSpecifier(
/*isTypeOnly*/ false,
propertyName && isIdentifier(propertyName) ? factory.createIdentifier(idText(propertyName)) : undefined,
factory.createIdentifier(localName)
)])),
@ -7424,6 +7423,7 @@ namespace ts {
/*importClause*/ undefined,
factory.createNamedImports([
factory.createImportSpecifier(
/*isTypeOnly*/ false,
localName !== verbatimTargetName ? factory.createIdentifier(verbatimTargetName) : undefined,
factory.createIdentifier(localName)
)
@ -7470,7 +7470,7 @@ namespace ts {
/*decorators*/ undefined,
/*modifiers*/ undefined,
/*isTypeOnly*/ false,
factory.createNamedExports([factory.createExportSpecifier(localName !== targetName ? targetName : undefined, localName)]),
factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, localName !== targetName ? targetName : undefined, localName)]),
specifier
), ModifierFlags.None);
}
@ -39415,11 +39415,15 @@ namespace ts {
}
function checkGrammarExportDeclaration(node: ExportDeclaration): boolean {
const isTypeOnlyExportStar = node.isTypeOnly && node.exportClause?.kind !== SyntaxKind.NamedExports;
if (isTypeOnlyExportStar) {
grammarErrorOnNode(node, Diagnostics.Only_named_exports_may_use_export_type);
if (node.isTypeOnly) {
if (node.exportClause?.kind === SyntaxKind.NamedExports) {
return checkGrammarNamedImportsOrExports(node.exportClause);
}
else {
return grammarErrorOnNode(node, Diagnostics.Only_named_exports_may_use_export_type);
}
}
return !isTypeOnlyExportStar;
return false;
}
function checkGrammarModuleElementContext(node: Statement, errorMessage: DiagnosticMessage): boolean {
@ -43445,9 +43449,24 @@ namespace ts {
if (node.isTypeOnly && node.name && node.namedBindings) {
return grammarErrorOnNode(node, Diagnostics.A_type_only_import_can_specify_a_default_import_or_named_bindings_but_not_both);
}
if (node.isTypeOnly && node.namedBindings?.kind === SyntaxKind.NamedImports) {
return checkGrammarNamedImportsOrExports(node.namedBindings);
}
return false;
}
function checkGrammarNamedImportsOrExports(namedBindings: NamedImportsOrExports): boolean {
return !!forEach<ImportSpecifier | ExportSpecifier, boolean>(namedBindings.elements, specifier => {
if (specifier.isTypeOnly) {
return grammarErrorOnFirstToken(
specifier,
specifier.kind === SyntaxKind.ImportSpecifier
? Diagnostics.The_type_modifier_cannot_be_used_on_a_named_import_when_import_type_is_used_on_its_import_statement
: Diagnostics.The_type_modifier_cannot_be_used_on_a_named_export_when_export_type_is_used_on_its_export_statement);
}
});
}
function checkGrammarImportCallExpression(node: ImportCall): boolean {
if (moduleKind === ModuleKind.ES2015) {
return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_are_only_supported_when_the_module_flag_is_set_to_es2020_esnext_commonjs_amd_system_umd_node12_or_nodenext);

View file

@ -1434,6 +1434,14 @@
"code": 2205,
"elidedInCompatabilityPyramid": true
},
"The 'type' modifier cannot be used on a named import when 'import type' is used on its import statement.": {
"category": "Error",
"code": 2206
},
"The 'type' modifier cannot be used on a named export when 'export type' is used on its export statement.": {
"category": "Error",
"code": 2207
},
"Duplicate identifier '{0}'.": {
"category": "Error",

View file

@ -3441,6 +3441,10 @@ namespace ts {
}
function emitImportOrExportSpecifier(node: ImportOrExportSpecifier) {
if (node.isTypeOnly) {
writeKeyword("type");
writeSpace();
}
if (node.propertyName) {
emit(node.propertyName);
writeSpace();

View file

@ -4089,8 +4089,9 @@ namespace ts {
}
// @api
function createImportSpecifier(propertyName: Identifier | undefined, name: Identifier) {
function createImportSpecifier(isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) {
const node = createBaseNode<ImportSpecifier>(SyntaxKind.ImportSpecifier);
node.isTypeOnly = isTypeOnly;
node.propertyName = propertyName;
node.name = name;
node.transformFlags |=
@ -4101,10 +4102,11 @@ namespace ts {
}
// @api
function updateImportSpecifier(node: ImportSpecifier, propertyName: Identifier | undefined, name: Identifier) {
return node.propertyName !== propertyName
function updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) {
return node.isTypeOnly !== isTypeOnly
|| node.propertyName !== propertyName
|| node.name !== name
? update(createImportSpecifier(propertyName, name), node)
? update(createImportSpecifier(isTypeOnly, propertyName, name), node)
: node;
}
@ -4205,8 +4207,9 @@ namespace ts {
}
// @api
function createExportSpecifier(propertyName: string | Identifier | undefined, name: string | Identifier) {
function createExportSpecifier(isTypeOnly: boolean, propertyName: string | Identifier | undefined, name: string | Identifier) {
const node = createBaseNode<ExportSpecifier>(SyntaxKind.ExportSpecifier);
node.isTypeOnly = isTypeOnly;
node.propertyName = asName(propertyName);
node.name = asName(name);
node.transformFlags |=
@ -4217,10 +4220,11 @@ namespace ts {
}
// @api
function updateExportSpecifier(node: ExportSpecifier, propertyName: Identifier | undefined, name: Identifier) {
return node.propertyName !== propertyName
function updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) {
return node.isTypeOnly !== isTypeOnly
|| node.propertyName !== propertyName
|| node.name !== name
? update(createExportSpecifier(propertyName, name), node)
? update(createExportSpecifier(isTypeOnly, propertyName, name), node)
: node;
}
@ -5488,7 +5492,7 @@ namespace ts {
/*modifiers*/ undefined,
/*isTypeOnly*/ false,
createNamedExports([
createExportSpecifier(/*propertyName*/ undefined, exportName)
createExportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, exportName)
])
);
}

View file

@ -500,8 +500,8 @@ namespace ts {
// NOTE: We don't need to care about global import collisions as this is a module.
namedBindings = nodeFactory.createNamedImports(
map(helperNames, name => isFileLevelUniqueName(sourceFile, name)
? nodeFactory.createImportSpecifier(/*propertyName*/ undefined, nodeFactory.createIdentifier(name))
: nodeFactory.createImportSpecifier(nodeFactory.createIdentifier(name), helperFactory.getUnscopedHelperName(name))
? nodeFactory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, nodeFactory.createIdentifier(name))
: nodeFactory.createImportSpecifier(/*isTypeOnly*/ false, nodeFactory.createIdentifier(name), helperFactory.getUnscopedHelperName(name))
)
);
const parseNode = getOriginalNode(sourceFile, isSourceFile);

View file

@ -7404,27 +7404,76 @@ namespace ts {
let checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier();
let checkIdentifierStart = scanner.getTokenPos();
let checkIdentifierEnd = scanner.getTextPos();
const identifierName = parseIdentifierName();
let isTypeOnly = false;
let propertyName: Identifier | undefined;
let name: Identifier;
if (token() === SyntaxKind.AsKeyword) {
propertyName = identifierName;
parseExpected(SyntaxKind.AsKeyword);
checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier();
checkIdentifierStart = scanner.getTokenPos();
checkIdentifierEnd = scanner.getTextPos();
name = parseIdentifierName();
let canParseAsKeyword = true;
let name = parseIdentifierName();
if (name.escapedText === "type") {
// If the first token of an import specifier is 'type', there are a lot of possibilities,
// especially if we see 'as' afterwards:
//
// import { type } from "mod"; - isTypeOnly: false, name: type
// import { type as } from "mod"; - isTypeOnly: true, name: as
// import { type as as } from "mod"; - isTypeOnly: false, name: as, propertyName: type
// import { type as as as } from "mod"; - isTypeOnly: true, name: as, propertyName: as
if (token() === SyntaxKind.AsKeyword) {
// { type as ...? }
const firstAs = parseIdentifierName();
if (token() === SyntaxKind.AsKeyword) {
// { type as as ...? }
const secondAs = parseIdentifierName();
if (tokenIsIdentifierOrKeyword(token())) {
// { type as as something }
isTypeOnly = true;
propertyName = firstAs;
name = parseNameWithKeywordCheck();
canParseAsKeyword = false;
}
else {
// { type as as }
propertyName = name;
name = secondAs;
canParseAsKeyword = false;
}
}
else if (tokenIsIdentifierOrKeyword(token())) {
// { type as something }
propertyName = name;
canParseAsKeyword = false;
name = parseNameWithKeywordCheck();
}
else {
// { type as }
isTypeOnly = true;
name = firstAs;
}
}
else if (tokenIsIdentifierOrKeyword(token())) {
// { type something ...? }
isTypeOnly = true;
name = parseNameWithKeywordCheck();
}
}
else {
name = identifierName;
if (canParseAsKeyword && token() === SyntaxKind.AsKeyword) {
propertyName = name;
parseExpected(SyntaxKind.AsKeyword);
name = parseNameWithKeywordCheck();
}
if (kind === SyntaxKind.ImportSpecifier && checkIdentifierIsKeyword) {
parseErrorAt(checkIdentifierStart, checkIdentifierEnd, Diagnostics.Identifier_expected);
}
const node = kind === SyntaxKind.ImportSpecifier
? factory.createImportSpecifier(propertyName, name)
: factory.createExportSpecifier(propertyName, name);
? factory.createImportSpecifier(isTypeOnly, propertyName, name)
: factory.createExportSpecifier(isTypeOnly, propertyName, name);
return finishNode(node, pos);
function parseNameWithKeywordCheck() {
checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier();
checkIdentifierStart = scanner.getTokenPos();
checkIdentifierEnd = scanner.getTextPos();
return parseIdentifierName();
}
}
function parseNamespaceExport(pos: number): NamespaceExport {

View file

@ -1256,7 +1256,7 @@ namespace ts {
/*modifiers*/ undefined,
/*isTypeOnly*/ false,
factory.createNamedExports(map(exportMappings, ([gen, exp]) => {
return factory.createExportSpecifier(gen, exp);
return factory.createExportSpecifier(/*isTypeOnly*/ false, gen, exp);
}))
));
}

View file

@ -56,7 +56,7 @@ namespace ts {
currentFileState.utilizedImplicitRuntimeImports.set(importSource, specifierSourceImports);
}
const generatedName = factory.createUniqueName(`_${name}`, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel | GeneratedIdentifierFlags.AllowNameSubstitution);
const specifier = factory.createImportSpecifier(factory.createIdentifier(name), generatedName);
const specifier = factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier(name), generatedName);
generatedName.generatedImportReference = specifier;
specifierSourceImports.set(name, specifier);
return generatedName;

View file

@ -104,7 +104,7 @@ namespace ts {
/*isTypeOnly*/ false,
/*name*/ undefined,
factory.createNamedImports([
factory.createImportSpecifier(factory.createIdentifier("createRequire"), createRequireName)
factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("createRequire"), createRequireName)
])
),
factory.createStringLiteral("module")
@ -177,7 +177,7 @@ namespace ts {
/*decorators*/ undefined,
/*modifiers*/ undefined,
node.isTypeOnly,
factory.createNamedExports([factory.createExportSpecifier(/*propertyName*/ undefined, idText(node.name))])
factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, idText(node.name))])
));
}
return statements;
@ -220,7 +220,7 @@ namespace ts {
/*decorators*/ undefined,
/*modifiers*/ undefined,
/*isTypeOnly*/ false,
factory.createNamedExports([factory.createExportSpecifier(synthName, oldIdentifier)]),
factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, synthName, oldIdentifier)]),
);
setOriginalNode(exportDecl, node);

View file

@ -2845,9 +2845,12 @@ namespace ts {
return shouldEmitAliasDeclaration(node) ? node : undefined;
}
else {
// Elide named imports if all of its import specifiers are elided.
// Elide named imports if all of its import specifiers are elided and settings allow.
const allowEmpty = compilerOptions.preserveValueImports && (
compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Preserve ||
compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error);
const elements = visitNodes(node.elements, visitImportSpecifier, isImportSpecifier);
return some(elements) ? factory.updateNamedImports(node, elements) : undefined;
return allowEmpty || some(elements) ? factory.updateNamedImports(node, elements) : undefined;
}
}
@ -2857,7 +2860,7 @@ namespace ts {
* @param node The import specifier node.
*/
function visitImportSpecifier(node: ImportSpecifier): VisitResult<ImportSpecifier> {
return shouldEmitAliasDeclaration(node) ? node : undefined;
return !node.isTypeOnly && shouldEmitAliasDeclaration(node) ? node : undefined;
}
/**
@ -2890,13 +2893,15 @@ namespace ts {
return node;
}
if (!resolver.isValueAliasDeclaration(node)) {
// Elide the export declaration if it does not export a value.
return undefined;
}
// Elide the export declaration if all of its named exports are elided.
const exportClause = visitNode(node.exportClause, visitNamedExportBindings, isNamedExportBindings);
const allowEmpty = !!node.moduleSpecifier && (
compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Preserve ||
compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error);
const exportClause = visitNode(
node.exportClause,
(bindings: NamedExportBindings) => visitNamedExportBindings(bindings, allowEmpty),
isNamedExportBindings);
return exportClause
? factory.updateExportDeclaration(
node,
@ -2915,18 +2920,18 @@ namespace ts {
*
* @param node The named exports node.
*/
function visitNamedExports(node: NamedExports): VisitResult<NamedExports> {
function visitNamedExports(node: NamedExports, allowEmpty: boolean): VisitResult<NamedExports> {
// Elide the named exports if all of its export specifiers were elided.
const elements = visitNodes(node.elements, visitExportSpecifier, isExportSpecifier);
return some(elements) ? factory.updateNamedExports(node, elements) : undefined;
return allowEmpty || some(elements) ? factory.updateNamedExports(node, elements) : undefined;
}
function visitNamespaceExports(node: NamespaceExport): VisitResult<NamespaceExport> {
return factory.updateNamespaceExport(node, visitNode(node.name, visitor, isIdentifier));
}
function visitNamedExportBindings(node: NamedExportBindings): VisitResult<NamedExportBindings> {
return isNamespaceExport(node) ? visitNamespaceExports(node) : visitNamedExports(node);
function visitNamedExportBindings(node: NamedExportBindings, allowEmpty: boolean): VisitResult<NamedExportBindings> {
return isNamespaceExport(node) ? visitNamespaceExports(node) : visitNamedExports(node, allowEmpty);
}
/**
@ -2936,7 +2941,7 @@ namespace ts {
*/
function visitExportSpecifier(node: ExportSpecifier): VisitResult<ExportSpecifier> {
// Elide an export specifier if it does not reference a value.
return resolver.isValueAliasDeclaration(node) ? node : undefined;
return !node.isTypeOnly && resolver.isValueAliasDeclaration(node) ? node : undefined;
}
/**

View file

@ -3112,11 +3112,13 @@ namespace ts {
readonly parent: NamedImports;
readonly propertyName?: Identifier; // Name preceding "as" keyword (or undefined when "as" is absent)
readonly name: Identifier; // Declared name
readonly isTypeOnly: boolean;
}
export interface ExportSpecifier extends NamedDeclaration {
readonly kind: SyntaxKind.ExportSpecifier;
readonly parent: NamedExports;
readonly isTypeOnly: boolean;
readonly propertyName?: Identifier; // Name preceding "as" keyword (or undefined when "as" is absent)
readonly name: Identifier; // Declared name
}
@ -4409,6 +4411,7 @@ namespace ts {
/* @internal */ getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): readonly TypeParameter[] | undefined;
/* @internal */ isDeclarationVisible(node: Declaration | AnyImportSyntax): boolean;
/* @internal */ isPropertyAccessible(node: Node, isSuper: boolean, isWrite: boolean, containingType: Type, property: Symbol): boolean;
/* @internal */ getTypeOnlyAliasDeclaration(symbol: Symbol): TypeOnlyAliasDeclaration | undefined;
}
/* @internal */
@ -4938,7 +4941,7 @@ namespace ts {
deferralConstituents?: Type[]; // Calculated list of constituents for a deferred type
deferralParent?: Type; // Source union/intersection of a deferred type
cjsExportMerged?: Symbol; // Version of the symbol with all non export= exports merged with the export= target
typeOnlyDeclaration?: TypeOnlyCompatibleAliasDeclaration | false; // First resolved alias declaration that makes the symbol only usable in type constructs
typeOnlyDeclaration?: TypeOnlyAliasDeclaration | false; // First resolved alias declaration that makes the symbol only usable in type constructs
isConstructorDeclaredProperty?: boolean; // Property declared through 'this.x = ...' assignment in constructor
tupleLabelDeclaration?: NamedTupleMember | ParameterDeclaration; // Declaration associated with the tuple's label
accessibleChainCache?: ESMap<string, Symbol[] | undefined>;
@ -7392,16 +7395,16 @@ namespace ts {
updateNamespaceExport(node: NamespaceExport, name: Identifier): NamespaceExport;
createNamedImports(elements: readonly ImportSpecifier[]): NamedImports;
updateNamedImports(node: NamedImports, elements: readonly ImportSpecifier[]): NamedImports;
createImportSpecifier(propertyName: Identifier | undefined, name: Identifier): ImportSpecifier;
updateImportSpecifier(node: ImportSpecifier, propertyName: Identifier | undefined, name: Identifier): ImportSpecifier;
createImportSpecifier(isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier): ImportSpecifier;
updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier): ImportSpecifier;
createExportAssignment(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isExportEquals: boolean | undefined, expression: Expression): ExportAssignment;
updateExportAssignment(node: ExportAssignment, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, expression: Expression): ExportAssignment;
createExportDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isTypeOnly: boolean, exportClause: NamedExportBindings | undefined, moduleSpecifier?: Expression, assertClause?: AssertClause): ExportDeclaration;
updateExportDeclaration(node: ExportDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isTypeOnly: boolean, exportClause: NamedExportBindings | undefined, moduleSpecifier: Expression | undefined, assertClause: AssertClause | undefined): ExportDeclaration;
createNamedExports(elements: readonly ExportSpecifier[]): NamedExports;
updateNamedExports(node: NamedExports, elements: readonly ExportSpecifier[]): NamedExports;
createExportSpecifier(propertyName: string | Identifier | undefined, name: string | Identifier): ExportSpecifier;
updateExportSpecifier(node: ExportSpecifier, propertyName: Identifier | undefined, name: Identifier): ExportSpecifier;
createExportSpecifier(isTypeOnly: boolean, propertyName: string | Identifier | undefined, name: string | Identifier): ExportSpecifier;
updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier): ExportSpecifier;
/* @internal*/ createMissingDeclaration(): MissingDeclaration;
//

View file

@ -7095,10 +7095,6 @@ namespace ts {
|| !(isExpressionNode(useSite) || isShorthandPropertyNameUseSite(useSite));
}
export function typeOnlyDeclarationIsExport(typeOnlyDeclaration: Node) {
return typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier;
}
function isShorthandPropertyNameUseSite(useSite: Node) {
return isIdentifier(useSite) && isShorthandPropertyAssignment(useSite.parent) && useSite.parent.name === useSite;
}

View file

@ -1130,7 +1130,7 @@ namespace ts {
switch (node.kind) {
case SyntaxKind.ImportSpecifier:
case SyntaxKind.ExportSpecifier:
return (node as ImportOrExportSpecifier).parent.parent.isTypeOnly;
return (node as ImportOrExportSpecifier).isTypeOnly || (node as ImportOrExportSpecifier).parent.parent.isTypeOnly;
case SyntaxKind.NamespaceImport:
return (node as NamespaceImport).parent.isTypeOnly;
case SyntaxKind.ImportClause:

View file

@ -1116,6 +1116,7 @@ namespace ts {
case SyntaxKind.ImportSpecifier:
Debug.type<ImportSpecifier>(node);
return factory.updateImportSpecifier(node,
node.isTypeOnly,
nodeVisitor(node.propertyName, visitor, isIdentifier),
nodeVisitor(node.name, visitor, isIdentifier));
@ -1144,6 +1145,7 @@ namespace ts {
case SyntaxKind.ExportSpecifier:
Debug.type<ExportSpecifier>(node);
return factory.updateExportSpecifier(node,
node.isTypeOnly,
nodeVisitor(node.propertyName, visitor, isIdentifier),
nodeVisitor(node.name, visitor, isIdentifier));

View file

@ -294,7 +294,7 @@ namespace ts.codefix {
*/
const newNodes = [
makeConst(/*modifiers*/ undefined, rename, assignment.right),
makeExportDeclaration([factory.createExportSpecifier(rename, text)]),
makeExportDeclaration([factory.createExportSpecifier(/*isTypeOnly*/ false, rename, text)]),
];
changes.replaceNodeWithNodes(sourceFile, assignment.parent, newNodes);
}
@ -317,7 +317,7 @@ namespace ts.codefix {
return makeExportDeclaration(/*exportClause*/ undefined, moduleSpecifier);
}
function reExportDefault(moduleSpecifier: string): ExportDeclaration {
return makeExportDeclaration([factory.createExportSpecifier(/*propertyName*/ undefined, "default")], moduleSpecifier);
return makeExportDeclaration([factory.createExportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, "default")], moduleSpecifier);
}
function convertExportsPropertyAssignment({ left, right, parent }: BinaryExpression & { left: PropertyAccessExpression }, sourceFile: SourceFile, changes: textChanges.ChangeTracker): void {
@ -480,7 +480,7 @@ namespace ts.codefix {
}
const namedBindings = namedBindingsNames.size === 0 ? undefined : arrayFrom(mapIterator(namedBindingsNames.entries(), ([propertyName, idName]) =>
factory.createImportSpecifier(propertyName === idName ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(idName))));
factory.createImportSpecifier(/*isTypeOnly*/ false, propertyName === idName ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(idName))));
if (!namedBindings) {
// If it was unused, ensure that we at least import *something*.
needDefaultImport = true;
@ -573,7 +573,7 @@ namespace ts.codefix {
}
function makeImportSpecifier(propertyName: string | undefined, name: string): ImportSpecifier {
return factory.createImportSpecifier(propertyName !== undefined && propertyName !== name ? factory.createIdentifier(propertyName) : undefined, factory.createIdentifier(name));
return factory.createImportSpecifier(/*isTypeOnly*/ false, propertyName !== undefined && propertyName !== name ? factory.createIdentifier(propertyName) : undefined, factory.createIdentifier(name));
}
function makeConst(modifiers: readonly Modifier[] | undefined, name: string | BindingName, init: Expression): VariableStatement {

View file

@ -532,6 +532,6 @@ namespace ts.codefix {
}
export function importSymbols(importAdder: ImportAdder, symbols: readonly Symbol[]) {
symbols.forEach(s => importAdder.addImportFromExportedSymbol(s, /*usageIsTypeOnly*/ true));
symbols.forEach(s => importAdder.addImportFromExportedSymbol(s, /*isValidTypeOnlyUseSite*/ true));
}
}

View file

@ -16,12 +16,12 @@ namespace ts.codefix {
registerCodeFix({
errorCodes,
getCodeActions(context) {
const { errorCode, preferences, sourceFile, span } = context;
const { errorCode, preferences, sourceFile, span, program } = context;
const info = getFixesInfo(context, errorCode, span.start, /*useAutoImportProvider*/ true);
if (!info) return undefined;
const { fixes, symbolName } = info;
const quotePreference = getQuotePreference(sourceFile, preferences);
return fixes.map(fix => codeActionForFix(context, sourceFile, symbolName, fix, quotePreference));
return fixes.map(fix => codeActionForFix(context, sourceFile, symbolName, fix, quotePreference, program.getCompilerOptions()));
},
fixIds: [importFixId],
getAllCodeActions: context => {
@ -34,7 +34,7 @@ namespace ts.codefix {
export interface ImportAdder {
addImportFromDiagnostic: (diagnostic: DiagnosticWithLocation, context: CodeFixContextBase) => void;
addImportFromExportedSymbol: (exportedSymbol: Symbol, usageIsTypeOnly?: boolean) => void;
addImportFromExportedSymbol: (exportedSymbol: Symbol, isValidTypeOnlyUseSite?: boolean) => void;
writeFixes: (changeTracker: textChanges.ChangeTracker) => void;
}
@ -42,14 +42,23 @@ namespace ts.codefix {
return createImportAdderWorker(sourceFile, program, /*useAutoImportProvider*/ false, preferences, host);
}
interface AddToExistingState {
readonly importClauseOrBindingPattern: ImportClause | ObjectBindingPattern;
defaultImport: Import | undefined;
readonly namedImports: ESMap<string, AddAsTypeOnly>;
}
function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAutoImportProvider: boolean, preferences: UserPreferences, host: LanguageServiceHost): ImportAdder {
const compilerOptions = program.getCompilerOptions();
// Namespace fixes don't conflict, so just build a list.
const addToNamespace: FixUseNamespaceImport[] = [];
const importType: FixUseImportType[] = [];
// Keys are import clause node IDs.
const addToExisting = new Map<string, { readonly importClauseOrBindingPattern: ImportClause | ObjectBindingPattern, defaultImport: string | undefined; readonly namedImports: string[], canUseTypeOnlyImport: boolean }>();
const newImports = new Map<string, Mutable<ImportsCollection & { useRequire: boolean }>>();
const importType: FixAddJsdocTypeImport[] = [];
/** Keys are import clause node IDs. */
const addToExisting = new Map<string, AddToExistingState>();
type NewImportsKey = `${0 | 1}|${string}`;
/** Use `getNewImportEntry` for access */
const newImports = new Map<NewImportsKey, Mutable<ImportsCollection & { useRequire: boolean }>>();
return { addImportFromDiagnostic, addImportFromExportedSymbol, writeFixes };
function addImportFromDiagnostic(diagnostic: DiagnosticWithLocation, context: CodeFixContextBase) {
@ -58,15 +67,14 @@ namespace ts.codefix {
addImport(info);
}
function addImportFromExportedSymbol(exportedSymbol: Symbol, usageIsTypeOnly?: boolean) {
function addImportFromExportedSymbol(exportedSymbol: Symbol, isValidTypeOnlyUseSite?: boolean) {
const moduleSymbol = Debug.checkDefined(exportedSymbol.parent);
const symbolName = getNameForExportedSymbol(exportedSymbol, getEmitScriptTarget(compilerOptions));
const checker = program.getTypeChecker();
const symbol = checker.getMergedSymbol(skipAlias(exportedSymbol, checker));
const exportInfos = getAllReExportingModules(sourceFile, symbol, moduleSymbol, symbolName, host, program, preferences, useAutoImportProvider);
const preferTypeOnlyImport = !!usageIsTypeOnly && compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error;
const useRequire = shouldUseRequire(sourceFile, program);
const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, /*position*/ undefined, preferTypeOnlyImport, useRequire, host, preferences);
const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, /*position*/ undefined, !!isValidTypeOnlyUseSite, useRequire, host, preferences);
if (fix) {
addImport({ fixes: [fix], symbolName });
}
@ -79,47 +87,47 @@ namespace ts.codefix {
case ImportFixKind.UseNamespace:
addToNamespace.push(fix);
break;
case ImportFixKind.ImportType:
case ImportFixKind.JsdocTypeImport:
importType.push(fix);
break;
case ImportFixKind.AddToExisting: {
const { importClauseOrBindingPattern, importKind, canUseTypeOnlyImport } = fix;
const { importClauseOrBindingPattern, importKind, addAsTypeOnly } = fix;
const key = String(getNodeId(importClauseOrBindingPattern));
let entry = addToExisting.get(key);
if (!entry) {
addToExisting.set(key, entry = { importClauseOrBindingPattern, defaultImport: undefined, namedImports: [], canUseTypeOnlyImport });
addToExisting.set(key, entry = { importClauseOrBindingPattern, defaultImport: undefined, namedImports: new Map() });
}
if (importKind === ImportKind.Named) {
pushIfUnique(entry.namedImports, symbolName);
const prevValue = entry?.namedImports.get(symbolName);
entry.namedImports.set(symbolName, reduceAddAsTypeOnlyValues(prevValue, addAsTypeOnly));
}
else {
Debug.assert(entry.defaultImport === undefined || entry.defaultImport === symbolName, "(Add to Existing) Default import should be missing or match symbolName");
entry.defaultImport = symbolName;
Debug.assert(entry.defaultImport === undefined || entry.defaultImport.name === symbolName, "(Add to Existing) Default import should be missing or match symbolName");
entry.defaultImport = {
name: symbolName,
addAsTypeOnly: reduceAddAsTypeOnlyValues(entry.defaultImport?.addAsTypeOnly, addAsTypeOnly),
};
}
break;
}
case ImportFixKind.AddNew: {
const { moduleSpecifier, importKind, useRequire, typeOnly } = fix;
let entry = newImports.get(moduleSpecifier);
if (!entry) {
newImports.set(moduleSpecifier, entry = { namedImports: [], namespaceLikeImport: undefined, typeOnly, useRequire });
}
else {
// An import clause can only be type-only if every import fix contributing to it can be type-only.
entry.typeOnly = entry.typeOnly && typeOnly;
}
const { moduleSpecifier, importKind, useRequire, addAsTypeOnly } = fix;
const entry = getNewImportEntry(moduleSpecifier, importKind, useRequire, addAsTypeOnly);
Debug.assert(entry.useRequire === useRequire, "(Add new) Tried to add an `import` and a `require` for the same module");
switch (importKind) {
case ImportKind.Default:
Debug.assert(entry.defaultImport === undefined || entry.defaultImport === symbolName, "(Add new) Default import should be missing or match symbolName");
entry.defaultImport = symbolName;
Debug.assert(entry.defaultImport === undefined || entry.defaultImport.name === symbolName, "(Add new) Default import should be missing or match symbolName");
entry.defaultImport = { name: symbolName, addAsTypeOnly: reduceAddAsTypeOnlyValues(entry.defaultImport?.addAsTypeOnly, addAsTypeOnly) };
break;
case ImportKind.Named:
pushIfUnique(entry.namedImports || (entry.namedImports = []), symbolName);
const prevValue = (entry.namedImports ||= new Map()).get(symbolName);
entry.namedImports.set(symbolName, reduceAddAsTypeOnlyValues(prevValue, addAsTypeOnly));
break;
case ImportKind.CommonJS:
case ImportKind.Namespace:
Debug.assert(entry.namespaceLikeImport === undefined || entry.namespaceLikeImport.name === symbolName, "Namespacelike import shoudl be missing or match symbolName");
entry.namespaceLikeImport = { importKind, name: symbolName };
entry.namespaceLikeImport = { importKind, name: symbolName, addAsTypeOnly };
break;
}
break;
@ -127,6 +135,52 @@ namespace ts.codefix {
default:
Debug.assertNever(fix, `fix wasn't never - got kind ${(fix as ImportFix).kind}`);
}
function reduceAddAsTypeOnlyValues(prevValue: AddAsTypeOnly | undefined, newValue: AddAsTypeOnly): AddAsTypeOnly {
// `NotAllowed` overrides `Required` because one addition of a new import might be required to be type-only
// because of `--importsNotUsedAsValues=error`, but if a second addition of the same import is `NotAllowed`
// to be type-only, the reason the first one was `Required` - the unused runtime dependency - is now moot.
// Alternatively, if one addition is `Required` because it has no value meaning under `--preserveValueImports`
// and `--isolatedModules`, it should be impossible for another addition to be `NotAllowed` since that would
// mean a type is being referenced in a value location.
return Math.max(prevValue ?? 0, newValue);
}
function getNewImportEntry(moduleSpecifier: string, importKind: ImportKind, useRequire: boolean, addAsTypeOnly: AddAsTypeOnly): Mutable<ImportsCollection & { useRequire: boolean }> {
// A default import that requires type-only makes the whole import type-only.
// (We could add `default` as a named import, but that style seems undesirable.)
// Under `--preserveValueImports` and `--importsNotUsedAsValues=error`, if a
// module default-exports a type but named-exports some values (weird), you would
// have to use a type-only default import and non-type-only named imports. These
// require two separate import declarations, so we build this into the map key.
const typeOnlyKey = newImportsKey(moduleSpecifier, /*topLevelTypeOnly*/ true);
const nonTypeOnlyKey = newImportsKey(moduleSpecifier, /*topLevelTypeOnly*/ false);
const typeOnlyEntry = newImports.get(typeOnlyKey);
const nonTypeOnlyEntry = newImports.get(nonTypeOnlyKey);
const newEntry: ImportsCollection & { useRequire: boolean } = {
defaultImport: undefined,
namedImports: undefined,
namespaceLikeImport: undefined,
useRequire
};
if (importKind === ImportKind.Default && addAsTypeOnly === AddAsTypeOnly.Required) {
if (typeOnlyEntry) return typeOnlyEntry;
newImports.set(typeOnlyKey, newEntry);
return newEntry;
}
if (addAsTypeOnly === AddAsTypeOnly.Allowed && (typeOnlyEntry || nonTypeOnlyEntry)) {
return (typeOnlyEntry || nonTypeOnlyEntry)!;
}
if (nonTypeOnlyEntry) {
return nonTypeOnlyEntry;
}
newImports.set(nonTypeOnlyKey, newEntry);
return newEntry;
}
function newImportsKey(moduleSpecifier: string, topLevelTypeOnly: boolean): NewImportsKey {
return `${topLevelTypeOnly ? 1 : 0}|${moduleSpecifier}`;
}
}
function writeFixes(changeTracker: textChanges.ChangeTracker) {
@ -137,14 +191,27 @@ namespace ts.codefix {
for (const fix of importType) {
addImportType(changeTracker, sourceFile, fix, quotePreference);
}
addToExisting.forEach(({ importClauseOrBindingPattern, defaultImport, namedImports, canUseTypeOnlyImport }) => {
doAddExistingFix(changeTracker, sourceFile, importClauseOrBindingPattern, defaultImport, namedImports, canUseTypeOnlyImport);
addToExisting.forEach(({ importClauseOrBindingPattern, defaultImport, namedImports }) => {
doAddExistingFix(
changeTracker,
sourceFile,
importClauseOrBindingPattern,
defaultImport,
arrayFrom(namedImports.entries(), ([name, addAsTypeOnly]) => ({ addAsTypeOnly, name })),
compilerOptions);
});
let newDeclarations: AnyImportOrRequireStatement | readonly AnyImportOrRequireStatement[] | undefined;
newImports.forEach(({ useRequire, ...imports }, moduleSpecifier) => {
newImports.forEach(({ useRequire, defaultImport, namedImports, namespaceLikeImport }, key) => {
const moduleSpecifier = key.slice(2); // From `${0 | 1}|${moduleSpecifier}` format
const getDeclarations = useRequire ? getNewRequires : getNewImports;
newDeclarations = combine(newDeclarations, getDeclarations(moduleSpecifier, quotePreference, imports));
const declarations = getDeclarations(
moduleSpecifier,
quotePreference,
defaultImport,
namedImports && arrayFrom(namedImports.entries(), ([name, addAsTypeOnly]) => ({ addAsTypeOnly, name })),
namespaceLikeImport);
newDeclarations = combine(newDeclarations, declarations);
});
if (newDeclarations) {
insertImports(changeTracker, sourceFile, newDeclarations, /*blankLineBetween*/ true);
@ -153,16 +220,25 @@ namespace ts.codefix {
}
// Sorted with the preferred fix coming first.
const enum ImportFixKind { UseNamespace, ImportType, AddToExisting, AddNew }
type ImportFix = FixUseNamespaceImport | FixUseImportType | FixAddToExistingImport | FixAddNewImport;
const enum ImportFixKind { UseNamespace, JsdocTypeImport, AddToExisting, AddNew }
// These should not be combined as bitflags, but are given powers of 2 values to
// easily detect conflicts between `NotAllowed` and `Required` by giving them a unique sum.
// They're also ordered in terms of increasing priority for a fix-all scenario (see
// `reduceAddAsTypeOnlyValues`).
const enum AddAsTypeOnly {
Allowed = 1 << 0,
Required = 1 << 1,
NotAllowed = 1 << 2,
}
type ImportFix = FixUseNamespaceImport | FixAddJsdocTypeImport | FixAddToExistingImport | FixAddNewImport;
interface FixUseNamespaceImport {
readonly kind: ImportFixKind.UseNamespace;
readonly namespacePrefix: string;
readonly position: number;
readonly moduleSpecifier: string;
}
interface FixUseImportType {
readonly kind: ImportFixKind.ImportType;
interface FixAddJsdocTypeImport {
readonly kind: ImportFixKind.JsdocTypeImport;
readonly moduleSpecifier: string;
readonly position: number;
readonly exportInfo: SymbolExportInfo;
@ -172,13 +248,13 @@ namespace ts.codefix {
readonly importClauseOrBindingPattern: ImportClause | ObjectBindingPattern;
readonly moduleSpecifier: string;
readonly importKind: ImportKind.Default | ImportKind.Named;
readonly canUseTypeOnlyImport: boolean;
readonly addAsTypeOnly: AddAsTypeOnly;
}
interface FixAddNewImport {
readonly kind: ImportFixKind.AddNew;
readonly moduleSpecifier: string;
readonly importKind: ImportKind;
readonly typeOnly: boolean;
readonly addAsTypeOnly: AddAsTypeOnly;
readonly useRequire: boolean;
readonly exportInfo?: SymbolExportInfo;
}
@ -187,6 +263,8 @@ namespace ts.codefix {
interface FixAddToExistingImportInfo {
readonly declaration: AnyImportOrRequire;
readonly importKind: ImportKind;
readonly targetFlags: SymbolFlags;
readonly symbol: Symbol;
}
export function getImportCompletionAction(
@ -205,14 +283,22 @@ namespace ts.codefix {
? [getSymbolExportInfoForSymbol(exportedSymbol, moduleSymbol, program, host)]
: getAllReExportingModules(sourceFile, exportedSymbol, moduleSymbol, symbolName, host, program, preferences, /*useAutoImportProvider*/ true);
const useRequire = shouldUseRequire(sourceFile, program);
const preferTypeOnlyImport = compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && !isSourceFileJS(sourceFile) && isValidTypeOnlyAliasUseSite(getTokenAtPosition(sourceFile, position));
const fix = Debug.checkDefined(getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, position, preferTypeOnlyImport, useRequire, host, preferences));
return { moduleSpecifier: fix.moduleSpecifier, codeAction: codeFixActionToCodeAction(codeActionForFix({ host, formatContext, preferences }, sourceFile, symbolName, fix, getQuotePreference(sourceFile, preferences))) };
const isValidTypeOnlyUseSite = isValidTypeOnlyAliasUseSite(getTokenAtPosition(sourceFile, position));
const fix = Debug.checkDefined(getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, position, isValidTypeOnlyUseSite, useRequire, host, preferences));
return {
moduleSpecifier: fix.moduleSpecifier,
codeAction: codeFixActionToCodeAction(codeActionForFix(
{ host, formatContext, preferences },
sourceFile,
symbolName,
fix,
getQuotePreference(sourceFile, preferences), compilerOptions))
};
}
function getImportFixForSymbol(sourceFile: SourceFile, exportInfos: readonly SymbolExportInfo[], moduleSymbol: Symbol, symbolName: string, program: Program, position: number | undefined, preferTypeOnlyImport: boolean, useRequire: boolean, host: LanguageServiceHost, preferences: UserPreferences) {
function getImportFixForSymbol(sourceFile: SourceFile, exportInfos: readonly SymbolExportInfo[], moduleSymbol: Symbol, symbolName: string, program: Program, position: number | undefined, isValidTypeOnlyUseSite: boolean, useRequire: boolean, host: LanguageServiceHost, preferences: UserPreferences) {
Debug.assert(exportInfos.some(info => info.moduleSymbol === moduleSymbol || info.symbol.parent === moduleSymbol), "Some exportInfo should match the specified moduleSymbol");
return getBestFix(getImportFixes(exportInfos, symbolName, position, preferTypeOnlyImport, useRequire, program, sourceFile, host, preferences), sourceFile, program, host, preferences);
return getBestFix(getImportFixes(exportInfos, symbolName, position, isValidTypeOnlyUseSite, useRequire, program, sourceFile, host, preferences), sourceFile, program, host, preferences);
}
function codeFixActionToCodeAction({ description, changes, commands }: CodeFixAction): CodeAction {
@ -284,7 +370,7 @@ namespace ts.codefix {
program,
importingFile,
/*position*/ undefined,
/*preferTypeOnlyImport*/ false,
/*isValidTypeOnlyUseSite*/ false,
/*useRequire*/ false,
exportInfo,
host,
@ -294,16 +380,12 @@ namespace ts.codefix {
return result && { ...result, computedWithoutCacheCount };
}
function isTypeOnlyPosition(sourceFile: SourceFile, position: number) {
return isValidTypeOnlyAliasUseSite(getTokenAtPosition(sourceFile, position));
}
function getImportFixes(
exportInfos: readonly SymbolExportInfo[],
symbolName: string,
/** undefined only for missing JSX namespace */
position: number | undefined,
preferTypeOnlyImport: boolean,
isValidTypeOnlyUseSite: boolean,
useRequire: boolean,
program: Program,
sourceFile: SourceFile,
@ -313,9 +395,9 @@ namespace ts.codefix {
const checker = program.getTypeChecker();
const existingImports = flatMap(exportInfos, info => getExistingImportDeclarations(info, checker, sourceFile, program.getCompilerOptions()));
const useNamespace = position === undefined ? undefined : tryUseExistingNamespaceImport(existingImports, symbolName, position, checker);
const addToExisting = tryAddToExistingImport(existingImports, position !== undefined && isTypeOnlyPosition(sourceFile, position));
const addToExisting = tryAddToExistingImport(existingImports, isValidTypeOnlyUseSite, checker, program.getCompilerOptions());
// Don't bother providing an action to add a new import if we can add to an existing one.
const addImport = addToExisting ? [addToExisting] : getFixesForAddImport(exportInfos, existingImports, program, sourceFile, position, preferTypeOnlyImport, useRequire, host, preferences);
const addImport = addToExisting ? [addToExisting] : getFixesForAddImport(exportInfos, existingImports, program, sourceFile, position, isValidTypeOnlyUseSite, useRequire, host, preferences);
return [...(useNamespace ? [useNamespace] : emptyArray), ...addImport];
}
@ -371,37 +453,88 @@ namespace ts.codefix {
}
}
function tryAddToExistingImport(existingImports: readonly FixAddToExistingImportInfo[], canUseTypeOnlyImport: boolean): FixAddToExistingImport | undefined {
return firstDefined(existingImports, ({ declaration, importKind }): FixAddToExistingImport | undefined => {
if (declaration.kind === SyntaxKind.ImportEqualsDeclaration) return undefined;
function getAddAsTypeOnly(
isValidTypeOnlyUseSite: boolean,
isForNewImportDeclaration: boolean,
symbol: Symbol,
targetFlags: SymbolFlags,
checker: TypeChecker,
compilerOptions: CompilerOptions
) {
if (!isValidTypeOnlyUseSite) {
// Can't use a type-only import if the usage is an emitting position
return AddAsTypeOnly.NotAllowed;
}
if (isForNewImportDeclaration && compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error) {
// Not writing a (top-level) type-only import here would create an error because the runtime dependency is unnecessary
return AddAsTypeOnly.Required;
}
if (compilerOptions.isolatedModules && compilerOptions.preserveValueImports &&
(!(targetFlags & SymbolFlags.Value) || !!checker.getTypeOnlyAliasDeclaration(symbol))
) {
// A type-only import is required for this symbol if under these settings if the symbol will
// be erased, which will happen if the target symbol is purely a type or if it was exported/imported
// as type-only already somewhere between this import and the target.
return AddAsTypeOnly.Required;
}
return AddAsTypeOnly.Allowed;
}
function tryAddToExistingImport(existingImports: readonly FixAddToExistingImportInfo[], isValidTypeOnlyUseSite: boolean, checker: TypeChecker, compilerOptions: CompilerOptions): FixAddToExistingImport | undefined {
return firstDefined(existingImports, ({ declaration, importKind, symbol, targetFlags }): FixAddToExistingImport | undefined => {
if (importKind === ImportKind.CommonJS || importKind === ImportKind.Namespace || declaration.kind === SyntaxKind.ImportEqualsDeclaration) {
// These kinds of imports are not combinable with anything
return undefined;
}
if (declaration.kind === SyntaxKind.VariableDeclaration) {
return (importKind === ImportKind.Named || importKind === ImportKind.Default) && declaration.name.kind === SyntaxKind.ObjectBindingPattern
? { kind: ImportFixKind.AddToExisting, importClauseOrBindingPattern: declaration.name, importKind, moduleSpecifier: declaration.initializer.arguments[0].text, canUseTypeOnlyImport: false }
? { kind: ImportFixKind.AddToExisting, importClauseOrBindingPattern: declaration.name, importKind, moduleSpecifier: declaration.initializer.arguments[0].text, addAsTypeOnly: AddAsTypeOnly.NotAllowed }
: undefined;
}
const { importClause } = declaration;
if (!importClause || !isStringLiteralLike(declaration.moduleSpecifier)) return undefined;
const { name, namedBindings } = importClause;
// A type-only import may not have both a default and named imports, so the only way a name can
// be added to an existing type-only import is adding a named import to existing named bindings.
if (importClause.isTypeOnly && !(importKind === ImportKind.Named && namedBindings)) return undefined;
return importKind === ImportKind.Default && !name || importKind === ImportKind.Named && (!namedBindings || namedBindings.kind === SyntaxKind.NamedImports)
? { kind: ImportFixKind.AddToExisting, importClauseOrBindingPattern: importClause, importKind, moduleSpecifier: declaration.moduleSpecifier.text, canUseTypeOnlyImport }
: undefined;
// N.B. we don't have to figure out whether to use the main program checker
// or the AutoImportProvider checker because we're adding to an existing import; the existence of
// the import guarantees the symbol came from the main program.
const addAsTypeOnly = getAddAsTypeOnly(isValidTypeOnlyUseSite, /*isForNewImportDeclaration*/ false, symbol, targetFlags, checker, compilerOptions);
if (importKind === ImportKind.Default && (
name || // Cannot add a default import to a declaration that already has one
addAsTypeOnly === AddAsTypeOnly.Required && namedBindings // Cannot add a default import as type-only if the import already has named bindings
)) return undefined;
if (
importKind === ImportKind.Named &&
namedBindings?.kind === SyntaxKind.NamespaceImport // Cannot add a named import to a declaration that has a namespace import
) return undefined;
return {
kind: ImportFixKind.AddToExisting,
importClauseOrBindingPattern: importClause,
importKind,
moduleSpecifier: declaration.moduleSpecifier.text,
addAsTypeOnly,
};
});
}
function getExistingImportDeclarations({ moduleSymbol, exportKind, targetFlags }: SymbolExportInfo, checker: TypeChecker, importingFile: SourceFile, compilerOptions: CompilerOptions): readonly FixAddToExistingImportInfo[] {
function getExistingImportDeclarations({ moduleSymbol, exportKind, targetFlags, symbol }: SymbolExportInfo, checker: TypeChecker, importingFile: SourceFile, compilerOptions: CompilerOptions): readonly FixAddToExistingImportInfo[] {
// Can't use an es6 import for a type in JS.
if (!(targetFlags & SymbolFlags.Value) && isSourceFileJS(importingFile)) return emptyArray;
const importKind = getImportKind(importingFile, exportKind, compilerOptions);
return mapDefined(importingFile.imports, (moduleSpecifier): FixAddToExistingImportInfo | undefined => {
const i = importFromModuleSpecifier(moduleSpecifier);
if (isRequireVariableDeclaration(i.parent)) {
return checker.resolveExternalModuleName(moduleSpecifier) === moduleSymbol ? { declaration: i.parent, importKind } : undefined;
return checker.resolveExternalModuleName(moduleSpecifier) === moduleSymbol ? { declaration: i.parent, importKind, symbol, targetFlags } : undefined;
}
if (i.kind === SyntaxKind.ImportDeclaration || i.kind === SyntaxKind.ImportEqualsDeclaration) {
return checker.getSymbolAtLocation(moduleSpecifier) === moduleSymbol ? { declaration: i, importKind } : undefined;
return checker.getSymbolAtLocation(moduleSpecifier) === moduleSymbol ? { declaration: i, importKind, symbol, targetFlags } : undefined;
}
});
}
@ -437,35 +570,38 @@ namespace ts.codefix {
program: Program,
sourceFile: SourceFile,
position: number | undefined,
preferTypeOnlyImport: boolean,
isValidTypeOnlyUseSite: boolean,
useRequire: boolean,
moduleSymbols: readonly SymbolExportInfo[],
host: LanguageServiceHost,
preferences: UserPreferences,
fromCacheOnly?: boolean,
): { computedWithoutCacheCount: number, fixes: readonly (FixAddNewImport | FixUseImportType)[] } {
): { computedWithoutCacheCount: number, fixes: readonly (FixAddNewImport | FixAddJsdocTypeImport)[] } {
const isJs = isSourceFileJS(sourceFile);
const compilerOptions = program.getCompilerOptions();
const moduleSpecifierResolutionHost = createModuleSpecifierResolutionHost(program, host);
const checker = program.getTypeChecker();
const getChecker = memoizeOne((isFromPackageJson: boolean) => isFromPackageJson ? host.getPackageJsonAutoImportProvider!()!.getTypeChecker() : program.getTypeChecker());
const getModuleSpecifiers = fromCacheOnly
? (moduleSymbol: Symbol) => ({ moduleSpecifiers: moduleSpecifiers.tryGetModuleSpecifiersFromCache(moduleSymbol, sourceFile, moduleSpecifierResolutionHost, preferences), computedWithoutCache: false })
: (moduleSymbol: Symbol) => moduleSpecifiers.getModuleSpecifiersWithCacheInfo(moduleSymbol, checker, compilerOptions, sourceFile, moduleSpecifierResolutionHost, preferences);
: (moduleSymbol: Symbol, checker: TypeChecker) => moduleSpecifiers.getModuleSpecifiersWithCacheInfo(moduleSymbol, checker, compilerOptions, sourceFile, moduleSpecifierResolutionHost, preferences);
let computedWithoutCacheCount = 0;
const fixes = flatMap(moduleSymbols, exportInfo => {
const { computedWithoutCache, moduleSpecifiers } = getModuleSpecifiers(exportInfo.moduleSymbol);
const checker = getChecker(exportInfo.isFromPackageJson);
const { computedWithoutCache, moduleSpecifiers } = getModuleSpecifiers(exportInfo.moduleSymbol, checker);
const importedSymbolHasValueMeaning = !!(exportInfo.targetFlags & SymbolFlags.Value);
const addAsTypeOnly = getAddAsTypeOnly(isValidTypeOnlyUseSite, /*isForNewImportDeclaration*/ true, exportInfo.symbol, exportInfo.targetFlags, checker, compilerOptions);
computedWithoutCacheCount += computedWithoutCache ? 1 : 0;
return moduleSpecifiers?.map((moduleSpecifier): FixAddNewImport | FixUseImportType =>
return moduleSpecifiers?.map((moduleSpecifier): FixAddNewImport | FixAddJsdocTypeImport =>
// `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types.
!(exportInfo.targetFlags & SymbolFlags.Value) && isJs && position !== undefined
? { kind: ImportFixKind.ImportType, moduleSpecifier, position, exportInfo }
!importedSymbolHasValueMeaning && isJs && position !== undefined
? { kind: ImportFixKind.JsdocTypeImport, moduleSpecifier, position, exportInfo }
: {
kind: ImportFixKind.AddNew,
moduleSpecifier,
importKind: getImportKind(sourceFile, exportInfo.exportKind, compilerOptions),
useRequire,
typeOnly: preferTypeOnlyImport,
addAsTypeOnly,
exportInfo,
}
);
@ -480,20 +616,29 @@ namespace ts.codefix {
program: Program,
sourceFile: SourceFile,
position: number | undefined,
preferTypeOnlyImport: boolean,
isValidTypeOnlyUseSite: boolean,
useRequire: boolean,
host: LanguageServiceHost,
preferences: UserPreferences,
): readonly (FixAddNewImport | FixUseImportType)[] {
const existingDeclaration = firstDefined(existingImports, info => newImportInfoFromExistingSpecifier(info, preferTypeOnlyImport, useRequire));
return existingDeclaration ? [existingDeclaration] : getNewImportFixes(program, sourceFile, position, preferTypeOnlyImport, useRequire, exportInfos, host, preferences).fixes;
): readonly (FixAddNewImport | FixAddJsdocTypeImport)[] {
const existingDeclaration = firstDefined(existingImports, info => newImportInfoFromExistingSpecifier(info, isValidTypeOnlyUseSite, useRequire, program.getTypeChecker(), program.getCompilerOptions()));
return existingDeclaration ? [existingDeclaration] : getNewImportFixes(program, sourceFile, position, isValidTypeOnlyUseSite, useRequire, exportInfos, host, preferences).fixes;
}
function newImportInfoFromExistingSpecifier({ declaration, importKind }: FixAddToExistingImportInfo, preferTypeOnlyImport: boolean, useRequire: boolean): FixAddNewImport | undefined {
function newImportInfoFromExistingSpecifier(
{ declaration, importKind, symbol, targetFlags }: FixAddToExistingImportInfo,
isValidTypeOnlyUseSite: boolean,
useRequire: boolean,
checker: TypeChecker,
compilerOptions: CompilerOptions
): FixAddNewImport | undefined {
const moduleSpecifier = tryGetModuleSpecifierFromDeclaration(declaration);
return moduleSpecifier
? { kind: ImportFixKind.AddNew, moduleSpecifier, importKind, typeOnly: preferTypeOnlyImport, useRequire }
: undefined;
if (moduleSpecifier) {
const addAsTypeOnly = useRequire
? AddAsTypeOnly.NotAllowed
: getAddAsTypeOnly(isValidTypeOnlyUseSite, /*isForNewImportDeclaration*/ true, symbol, targetFlags, checker, compilerOptions);
return { kind: ImportFixKind.AddNew, moduleSpecifier, importKind, addAsTypeOnly, useRequire };
}
}
interface FixesInfo { readonly fixes: readonly ImportFix[]; readonly symbolName: string; }
@ -545,7 +690,7 @@ namespace ts.codefix {
const symbolName = umdSymbol.name;
const exportInfos: readonly SymbolExportInfo[] = [{ symbol: umdSymbol, moduleSymbol: symbol, moduleFileName: undefined, exportKind: ExportKind.UMD, targetFlags: symbol.flags, isFromPackageJson: false }];
const useRequire = shouldUseRequire(sourceFile, program);
const fixes = getImportFixes(exportInfos, symbolName, isIdentifier(token) ? token.getStart(sourceFile) : undefined, /*preferTypeOnlyImport*/ false, useRequire, program, sourceFile, host, preferences);
const fixes = getImportFixes(exportInfos, symbolName, isIdentifier(token) ? token.getStart(sourceFile) : undefined, /*isValidTypeOnlyUseSite*/ false, useRequire, program, sourceFile, host, preferences);
return { fixes, symbolName };
}
function getUmdSymbol(token: Node, checker: TypeChecker): Symbol | undefined {
@ -612,11 +757,11 @@ namespace ts.codefix {
// "default" is a keyword and not a legal identifier for the import, so we don't expect it here
Debug.assert(symbolName !== InternalSymbolName.Default, "'default' isn't a legal identifier and couldn't occur here");
const preferTypeOnlyImport = compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && isValidTypeOnlyAliasUseSite(symbolToken);
const isValidTypeOnlyUseSite = isValidTypeOnlyAliasUseSite(symbolToken);
const useRequire = shouldUseRequire(sourceFile, program);
const exportInfos = getExportInfos(symbolName, getMeaningFromLocation(symbolToken), cancellationToken, sourceFile, program, useAutoImportProvider, host, preferences);
const fixes = arrayFrom(flatMapIterator(exportInfos.entries(), ([_, exportInfos]) =>
getImportFixes(exportInfos, symbolName, symbolToken.getStart(sourceFile), preferTypeOnlyImport, useRequire, program, sourceFile, host, preferences)));
getImportFixes(exportInfos, symbolName, symbolToken.getStart(sourceFile), isValidTypeOnlyUseSite, useRequire, program, sourceFile, host, preferences)));
return { fixes, symbolName };
}
@ -707,34 +852,44 @@ namespace ts.codefix {
return allowSyntheticDefaults ? ImportKind.Default : ImportKind.CommonJS;
}
function codeActionForFix(context: textChanges.TextChangesContext, sourceFile: SourceFile, symbolName: string, fix: ImportFix, quotePreference: QuotePreference): CodeFixAction {
function codeActionForFix(context: textChanges.TextChangesContext, sourceFile: SourceFile, symbolName: string, fix: ImportFix, quotePreference: QuotePreference, compilerOptions: CompilerOptions): CodeFixAction {
let diag!: DiagnosticAndArguments;
const changes = textChanges.ChangeTracker.with(context, tracker => {
diag = codeActionForFixWorker(tracker, sourceFile, symbolName, fix, quotePreference);
diag = codeActionForFixWorker(tracker, sourceFile, symbolName, fix, quotePreference, compilerOptions);
});
return createCodeFixAction(importFixName, changes, diag, importFixId, Diagnostics.Add_all_missing_imports);
}
function codeActionForFixWorker(changes: textChanges.ChangeTracker, sourceFile: SourceFile, symbolName: string, fix: ImportFix, quotePreference: QuotePreference): DiagnosticAndArguments {
function codeActionForFixWorker(changes: textChanges.ChangeTracker, sourceFile: SourceFile, symbolName: string, fix: ImportFix, quotePreference: QuotePreference, compilerOptions: CompilerOptions): DiagnosticAndArguments {
switch (fix.kind) {
case ImportFixKind.UseNamespace:
addNamespaceQualifier(changes, sourceFile, fix);
return [Diagnostics.Change_0_to_1, symbolName, `${fix.namespacePrefix}.${symbolName}`];
case ImportFixKind.ImportType:
case ImportFixKind.JsdocTypeImport:
addImportType(changes, sourceFile, fix, quotePreference);
return [Diagnostics.Change_0_to_1, symbolName, getImportTypePrefix(fix.moduleSpecifier, quotePreference) + symbolName];
case ImportFixKind.AddToExisting: {
const { importClauseOrBindingPattern, importKind, canUseTypeOnlyImport, moduleSpecifier } = fix;
doAddExistingFix(changes, sourceFile, importClauseOrBindingPattern, importKind === ImportKind.Default ? symbolName : undefined, importKind === ImportKind.Named ? [symbolName] : emptyArray, canUseTypeOnlyImport);
const { importClauseOrBindingPattern, importKind, addAsTypeOnly, moduleSpecifier } = fix;
doAddExistingFix(
changes,
sourceFile,
importClauseOrBindingPattern,
importKind === ImportKind.Default ? { name: symbolName, addAsTypeOnly } : undefined,
importKind === ImportKind.Named ? [{ name: symbolName, addAsTypeOnly }] : emptyArray,
compilerOptions);
const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier);
return [importKind === ImportKind.Default ? Diagnostics.Add_default_import_0_to_existing_import_declaration_from_1 : Diagnostics.Add_0_to_existing_import_declaration_from_1, symbolName, moduleSpecifierWithoutQuotes]; // you too!
return [
importKind === ImportKind.Default ? Diagnostics.Add_default_import_0_to_existing_import_declaration_from_1 : Diagnostics.Add_0_to_existing_import_declaration_from_1,
symbolName,
moduleSpecifierWithoutQuotes
]; // you too!
}
case ImportFixKind.AddNew: {
const { importKind, moduleSpecifier, typeOnly, useRequire } = fix;
const { importKind, moduleSpecifier, addAsTypeOnly, useRequire } = fix;
const getDeclarations = useRequire ? getNewRequires : getNewImports;
const importsCollection = importKind === ImportKind.Default ? { defaultImport: symbolName, typeOnly } :
importKind === ImportKind.Named ? { namedImports: [symbolName], typeOnly } :
{ namespaceLikeImport: { importKind, name: symbolName }, typeOnly };
insertImports(changes, sourceFile, getDeclarations(moduleSpecifier, quotePreference, importsCollection), /*blankLineBetween*/ true);
const defaultImport: Import | undefined = importKind === ImportKind.Default ? { name: symbolName, addAsTypeOnly } : undefined;
const namedImports: Import[] | undefined = importKind === ImportKind.Named ? [{ name: symbolName, addAsTypeOnly }] : undefined;
const namespaceLikeImport = importKind === ImportKind.Namespace || importKind === ImportKind.CommonJS ? { importKind, name: symbolName, addAsTypeOnly } : undefined;
insertImports(changes, sourceFile, getDeclarations(moduleSpecifier, quotePreference, defaultImport, namedImports, namespaceLikeImport), /*blankLineBetween*/ true);
return [importKind === ImportKind.Default ? Diagnostics.Import_default_0_from_module_1 : Diagnostics.Import_0_from_module_1, symbolName, moduleSpecifier];
}
default:
@ -742,32 +897,55 @@ namespace ts.codefix {
}
}
function doAddExistingFix(changes: textChanges.ChangeTracker, sourceFile: SourceFile, clause: ImportClause | ObjectBindingPattern, defaultImport: string | undefined, namedImports: readonly string[], canUseTypeOnlyImport: boolean): void {
function doAddExistingFix(
changes: textChanges.ChangeTracker,
sourceFile: SourceFile,
clause: ImportClause | ObjectBindingPattern,
defaultImport: Import | undefined,
namedImports: readonly Import[],
compilerOptions: CompilerOptions,
): void {
if (clause.kind === SyntaxKind.ObjectBindingPattern) {
if (defaultImport) {
addElementToBindingPattern(clause, defaultImport, "default");
addElementToBindingPattern(clause, defaultImport.name, "default");
}
for (const specifier of namedImports) {
addElementToBindingPattern(clause, specifier, /*propertyName*/ undefined);
addElementToBindingPattern(clause, specifier.name, /*propertyName*/ undefined);
}
return;
}
const convertTypeOnlyToRegular = !canUseTypeOnlyImport && clause.isTypeOnly;
const promoteFromTypeOnly = clause.isTypeOnly && some([defaultImport, ...namedImports], i => i?.addAsTypeOnly === AddAsTypeOnly.NotAllowed);
const existingSpecifiers = clause.namedBindings && tryCast(clause.namedBindings, isNamedImports)?.elements;
// If we are promoting from a type-only import and `--isolatedModules` and `--preserveValueImports`
// are enabled, we need to make every existing import specifier type-only. It may be possible that
// some of them don't strictly need to be marked type-only (if they have a value meaning and are
// never used in an emitting position). These are allowed to be imported without being type-only,
// but the user has clearly already signified that they don't need them to be present at runtime
// by placing them in a type-only import. So, just mark each specifier as type-only.
const convertExistingToTypeOnly = promoteFromTypeOnly && compilerOptions.preserveValueImports && compilerOptions.isolatedModules;
if (defaultImport) {
Debug.assert(!clause.name, "Cannot add a default import to an import clause that already has one");
changes.insertNodeAt(sourceFile, clause.getStart(sourceFile), factory.createIdentifier(defaultImport), { suffix: ", " });
changes.insertNodeAt(sourceFile, clause.getStart(sourceFile), factory.createIdentifier(defaultImport.name), { suffix: ", " });
}
if (namedImports.length) {
const existingSpecifiers = clause.namedBindings && cast(clause.namedBindings, isNamedImports).elements;
const newSpecifiers = stableSort(
namedImports.map(name => factory.createImportSpecifier(/*propertyName*/ undefined, factory.createIdentifier(name))),
namedImports.map(namedImport => factory.createImportSpecifier(
(!clause.isTypeOnly || promoteFromTypeOnly) && needsTypeOnly(namedImport),
/*propertyName*/ undefined,
factory.createIdentifier(namedImport.name))),
OrganizeImports.compareImportOrExportSpecifiers);
if (existingSpecifiers?.length && OrganizeImports.importSpecifiersAreSorted(existingSpecifiers)) {
for (const spec of newSpecifiers) {
const insertionIndex = OrganizeImports.getImportSpecifierInsertionIndex(existingSpecifiers, spec);
// Organize imports puts type-only import specifiers last, so if we're
// adding a non-type-only specifier and converting all the other ones to
// type-only, there's no need to ask for the insertion index - it's 0.
const insertionIndex = convertExistingToTypeOnly && !spec.isTypeOnly
? 0
: OrganizeImports.getImportSpecifierInsertionIndex(existingSpecifiers, spec);
const prevSpecifier = (clause.namedBindings as NamedImports).elements[insertionIndex - 1];
if (prevSpecifier) {
changes.insertNodeInListAfter(sourceFile, prevSpecifier, spec);
@ -799,8 +977,13 @@ namespace ts.codefix {
}
}
if (convertTypeOnlyToRegular) {
if (promoteFromTypeOnly) {
changes.delete(sourceFile, getTypeKeywordOfTypeOnlyImport(clause, sourceFile));
if (convertExistingToTypeOnly && existingSpecifiers) {
for (const specifier of existingSpecifiers) {
changes.insertModifierBefore(sourceFile, SyntaxKind.TypeKeyword, specifier);
}
}
}
function addElementToBindingPattern(bindingPattern: ObjectBindingPattern, name: string, propertyName: string | undefined) {
@ -818,7 +1001,7 @@ namespace ts.codefix {
changes.insertText(sourceFile, position, namespacePrefix + ".");
}
function addImportType(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { moduleSpecifier, position }: FixUseImportType, quotePreference: QuotePreference): void {
function addImportType(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { moduleSpecifier, position }: FixAddJsdocTypeImport, quotePreference: QuotePreference): void {
changes.insertText(sourceFile, position, getImportTypePrefix(moduleSpecifier, quotePreference));
}
@ -827,37 +1010,60 @@ namespace ts.codefix {
return `import(${quote}${moduleSpecifier}${quote}).`;
}
interface Import {
readonly name: string;
readonly addAsTypeOnly: AddAsTypeOnly;
}
interface ImportsCollection {
readonly typeOnly: boolean;
readonly defaultImport?: string;
readonly namedImports?: string[];
readonly defaultImport?: Import;
readonly namedImports?: ESMap<string, AddAsTypeOnly>;
readonly namespaceLikeImport?: {
readonly importKind: ImportKind.CommonJS | ImportKind.Namespace;
readonly name: string;
readonly addAsTypeOnly: AddAsTypeOnly;
};
}
function getNewImports(moduleSpecifier: string, quotePreference: QuotePreference, imports: ImportsCollection): AnyImportSyntax | readonly AnyImportSyntax[] {
function needsTypeOnly({ addAsTypeOnly }: { addAsTypeOnly: AddAsTypeOnly }): boolean {
return addAsTypeOnly === AddAsTypeOnly.Required;
}
function getNewImports(
moduleSpecifier: string,
quotePreference: QuotePreference,
defaultImport: Import | undefined,
namedImports: readonly Import[] | undefined,
namespaceLikeImport: Import & { importKind: ImportKind.CommonJS | ImportKind.Namespace } | undefined
): AnyImportSyntax | readonly AnyImportSyntax[] {
const quotedModuleSpecifier = makeStringLiteral(moduleSpecifier, quotePreference);
let statements: AnyImportSyntax | readonly AnyImportSyntax[] | undefined;
if (imports.defaultImport !== undefined || imports.namedImports?.length) {
if (defaultImport !== undefined || namedImports?.length) {
const topLevelTypeOnly = (!defaultImport || needsTypeOnly(defaultImport)) && every(namedImports, needsTypeOnly);
statements = combine(statements, makeImport(
imports.defaultImport === undefined ? undefined : factory.createIdentifier(imports.defaultImport),
imports.namedImports?.map(n => factory.createImportSpecifier(/*propertyName*/ undefined, factory.createIdentifier(n))), moduleSpecifier, quotePreference, imports.typeOnly));
defaultImport && factory.createIdentifier(defaultImport.name),
namedImports?.map(({ addAsTypeOnly, name }) => factory.createImportSpecifier(
!topLevelTypeOnly && addAsTypeOnly === AddAsTypeOnly.Required,
/*propertyName*/ undefined,
factory.createIdentifier(name))),
moduleSpecifier,
quotePreference,
topLevelTypeOnly));
}
const { namespaceLikeImport, typeOnly } = imports;
if (namespaceLikeImport) {
const declaration = namespaceLikeImport.importKind === ImportKind.CommonJS
? factory.createImportEqualsDeclaration(
/*decorators*/ undefined,
/*modifiers*/ undefined,
typeOnly,
needsTypeOnly(namespaceLikeImport),
factory.createIdentifier(namespaceLikeImport.name),
factory.createExternalModuleReference(quotedModuleSpecifier))
: factory.createImportDeclaration(
/*decorators*/ undefined,
/*modifiers*/ undefined,
factory.createImportClause(
typeOnly,
needsTypeOnly(namespaceLikeImport),
/*name*/ undefined,
factory.createNamespaceImport(factory.createIdentifier(namespaceLikeImport.name))),
quotedModuleSpecifier,
@ -867,21 +1073,21 @@ namespace ts.codefix {
return Debug.checkDefined(statements);
}
function getNewRequires(moduleSpecifier: string, quotePreference: QuotePreference, imports: ImportsCollection): RequireVariableStatement | readonly RequireVariableStatement[] {
function getNewRequires(moduleSpecifier: string, quotePreference: QuotePreference, defaultImport: Import | undefined, namedImports: readonly Import[] | undefined, namespaceLikeImport: Import | undefined): RequireVariableStatement | readonly RequireVariableStatement[] {
const quotedModuleSpecifier = makeStringLiteral(moduleSpecifier, quotePreference);
let statements: RequireVariableStatement | readonly RequireVariableStatement[] | undefined;
// const { default: foo, bar, etc } = require('./mod');
if (imports.defaultImport || imports.namedImports?.length) {
const bindingElements = imports.namedImports?.map(name => factory.createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, name)) || [];
if (imports.defaultImport) {
bindingElements.unshift(factory.createBindingElement(/*dotDotDotToken*/ undefined, "default", imports.defaultImport));
if (defaultImport || namedImports?.length) {
const bindingElements = namedImports?.map(({ name }) => factory.createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, name)) || [];
if (defaultImport) {
bindingElements.unshift(factory.createBindingElement(/*dotDotDotToken*/ undefined, "default", defaultImport.name));
}
const declaration = createConstEqualsRequireDeclaration(factory.createObjectBindingPattern(bindingElements), quotedModuleSpecifier);
statements = combine(statements, declaration);
}
// const foo = require('./mod');
if (imports.namespaceLikeImport) {
const declaration = createConstEqualsRequireDeclaration(imports.namespaceLikeImport.name, quotedModuleSpecifier);
if (namespaceLikeImport) {
const declaration = createConstEqualsRequireDeclaration(namespaceLikeImport.name, quotedModuleSpecifier);
statements = combine(statements, declaration);
}
return Debug.checkDefined(statements);

View file

@ -62,7 +62,7 @@ namespace ts.codefix {
if (!isIdentifier(element.name) || element.initializer) {
return undefined;
}
importSpecifiers.push(factory.createImportSpecifier(tryCast(element.propertyName, isIdentifier), element.name));
importSpecifiers.push(factory.createImportSpecifier(/*isTypeOnly*/ false, tryCast(element.propertyName, isIdentifier), element.name));
}
if (importSpecifiers.length) {

View file

@ -149,6 +149,7 @@ namespace ts.Completions {
FunctionLikeBodyKeywords, // Keywords at function like body
TypeAssertionKeywords,
TypeKeywords,
TypeKeyword, // Literally just `type`
Last = TypeKeywords
}
@ -287,7 +288,7 @@ namespace ts.Completions {
case CompletionDataKind.JsDocParameterName:
return jsdocCompletionInfo(JsDoc.getJSDocParameterNameCompletions(completionData.tag));
case CompletionDataKind.Keywords:
return specificKeywordCompletionInfo(completionData.keywords);
return specificKeywordCompletionInfo(completionData.keywordCompletions, completionData.isNewIdentifierLocation);
default:
return Debug.assertNever(completionData);
}
@ -368,20 +369,39 @@ namespace ts.Completions {
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries };
}
function specificKeywordCompletionInfo(keywords: readonly SyntaxKind[]): CompletionInfo {
function keywordToCompletionEntry(keyword: TokenSyntaxKind) {
return {
name: tokenToString(keyword)!,
kind: ScriptElementKind.keyword,
kindModifiers: ScriptElementKindModifier.none,
sortText: SortText.GlobalsOrKeywords,
};
}
function specificKeywordCompletionInfo(entries: readonly CompletionEntry[], isNewIdentifierLocation: boolean): CompletionInfo {
return {
isGlobalCompletion: false,
isMemberCompletion: false,
isNewIdentifierLocation: false,
entries: keywords.map(k => ({
name: tokenToString(k)!,
kind: ScriptElementKind.keyword,
kindModifiers: ScriptElementKindModifier.none,
sortText: SortText.GlobalsOrKeywords,
})),
isNewIdentifierLocation,
entries: entries.slice(),
};
}
function keywordCompletionData(keywordFilters: KeywordCompletionFilters, filterOutTsOnlyKeywords: boolean, isNewIdentifierLocation: boolean): Request {
return {
kind: CompletionDataKind.Keywords,
keywordCompletions: getKeywordCompletions(keywordFilters, filterOutTsOnlyKeywords),
isNewIdentifierLocation,
};
}
function keywordFiltersFromSyntaxKind(keywordCompletion: TokenSyntaxKind): KeywordCompletionFilters {
switch (keywordCompletion) {
case SyntaxKind.TypeKeyword: return KeywordCompletionFilters.TypeKeyword;
default: Debug.fail("Unknown mapping from SyntaxKind to KeywordCompletionFilters");
}
}
function getOptionalReplacementSpan(location: Node | undefined) {
// StringLiteralLike locations are handled separately in stringCompletions.ts
return location?.kind === SyntaxKind.Identifier ? createTextSpanFromNode(location) : undefined;
@ -390,6 +410,7 @@ namespace ts.Completions {
function completionInfoFromData(sourceFile: SourceFile, typeChecker: TypeChecker, compilerOptions: CompilerOptions, log: Log, completionData: CompletionData, preferences: UserPreferences): CompletionInfo | undefined {
const {
symbols,
contextToken,
completionKind,
isInSnippetScope,
isNewIdentifierLocation,
@ -422,7 +443,8 @@ namespace ts.Completions {
const uniqueNames = getCompletionEntriesFromSymbols(
symbols,
entries,
/* contextToken */ undefined,
/*replacementToken*/ undefined,
contextToken,
location,
sourceFile,
typeChecker,
@ -450,7 +472,8 @@ namespace ts.Completions {
getCompletionEntriesFromSymbols(
symbols,
entries,
/* contextToken */ undefined,
/*replacementToken*/ undefined,
contextToken,
location,
sourceFile,
typeChecker,
@ -591,6 +614,7 @@ namespace ts.Completions {
function createCompletionEntry(
symbol: Symbol,
sortText: SortText,
replacementToken: Node | undefined,
contextToken: Node | undefined,
location: Node,
sourceFile: SourceFile,
@ -602,13 +626,12 @@ namespace ts.Completions {
propertyAccessToConvert: PropertyAccessExpression | undefined,
isJsxInitializer: IsJsxInitializer | undefined,
importCompletionNode: Node | undefined,
isTypeOnlyImport: boolean,
useSemicolons: boolean,
options: CompilerOptions,
preferences: UserPreferences,
): CompletionEntry | undefined {
let insertText: string | undefined;
let replacementSpan = getReplacementSpanForContextToken(contextToken);
let replacementSpan = getReplacementSpanForContextToken(replacementToken);
let data: CompletionEntryData | undefined;
let isSnippet: true | undefined;
let sourceDisplay;
@ -662,7 +685,7 @@ namespace ts.Completions {
if (originIsResolvedExport(origin)) {
sourceDisplay = [textPart(origin.moduleSpecifier)];
if (importCompletionNode) {
({ insertText, replacementSpan } = getInsertTextAndReplacementSpanForImportCompletion(name, importCompletionNode, isTypeOnlyImport, origin, useSemicolons, options, preferences));
({ insertText, replacementSpan } = getInsertTextAndReplacementSpanForImportCompletion(name, importCompletionNode, contextToken, origin, useSemicolons, options, preferences));
isSnippet = preferences.includeCompletionsWithSnippetText ? true : undefined;
}
}
@ -747,9 +770,9 @@ namespace ts.Completions {
};
}
function getInsertTextAndReplacementSpanForImportCompletion(name: string, importCompletionNode: Node, isTypeOnly: boolean | undefined, origin: SymbolOriginInfoResolvedExport, useSemicolons: boolean, options: CompilerOptions, preferences: UserPreferences) {
function getInsertTextAndReplacementSpanForImportCompletion(name: string, importCompletionNode: Node, contextToken: Node | undefined, origin: SymbolOriginInfoResolvedExport, useSemicolons: boolean, options: CompilerOptions, preferences: UserPreferences) {
const sourceFile = importCompletionNode.getSourceFile();
const replacementSpan = createTextSpanFromNode(importCompletionNode, sourceFile);
const replacementSpan = createTextSpanFromNode(findAncestor(importCompletionNode, or(isImportDeclaration, isImportEqualsDeclaration)) || importCompletionNode, sourceFile);
const quotedModuleSpecifier = quote(sourceFile, preferences, origin.moduleSpecifier);
const exportKind =
origin.isDefaultExport ? ExportKind.Default :
@ -757,13 +780,16 @@ namespace ts.Completions {
ExportKind.Named;
const tabStop = preferences.includeCompletionsWithSnippetText ? "$1" : "";
const importKind = codefix.getImportKind(sourceFile, exportKind, options, /*forceImportKeyword*/ true);
const typeOnlyPrefix = isTypeOnly ? ` ${tokenToString(SyntaxKind.TypeKeyword)} ` : " ";
const isTopLevelTypeOnly = tryCast(importCompletionNode, isImportDeclaration)?.importClause?.isTypeOnly || tryCast(importCompletionNode, isImportEqualsDeclaration)?.isTypeOnly;
const isImportSpecifierTypeOnly = couldBeTypeOnlyImportSpecifier(importCompletionNode, contextToken);
const topLevelTypeOnlyText = isTopLevelTypeOnly ? ` ${tokenToString(SyntaxKind.TypeKeyword)} ` : " ";
const importSpecifierTypeOnlyText = isImportSpecifierTypeOnly ? `${tokenToString(SyntaxKind.TypeKeyword)} ` : "";
const suffix = useSemicolons ? ";" : "";
switch (importKind) {
case ImportKind.CommonJS: return { replacementSpan, insertText: `import${typeOnlyPrefix}${escapeSnippetText(name)}${tabStop} = require(${quotedModuleSpecifier})${suffix}` };
case ImportKind.Default: return { replacementSpan, insertText: `import${typeOnlyPrefix}${escapeSnippetText(name)}${tabStop} from ${quotedModuleSpecifier}${suffix}` };
case ImportKind.Namespace: return { replacementSpan, insertText: `import${typeOnlyPrefix}* as ${escapeSnippetText(name)} from ${quotedModuleSpecifier}${suffix}` };
case ImportKind.Named: return { replacementSpan, insertText: `import${typeOnlyPrefix}{ ${escapeSnippetText(name)}${tabStop} } from ${quotedModuleSpecifier}${suffix}` };
case ImportKind.CommonJS: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}${escapeSnippetText(name)}${tabStop} = require(${quotedModuleSpecifier})${suffix}` };
case ImportKind.Default: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}${escapeSnippetText(name)}${tabStop} from ${quotedModuleSpecifier}${suffix}` };
case ImportKind.Namespace: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}* as ${escapeSnippetText(name)} from ${quotedModuleSpecifier}${suffix}` };
case ImportKind.Named: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}{ ${importSpecifierTypeOnlyText}${escapeSnippetText(name)}${tabStop} } from ${quotedModuleSpecifier}${suffix}` };
}
}
@ -795,6 +821,7 @@ namespace ts.Completions {
export function getCompletionEntriesFromSymbols(
symbols: readonly Symbol[],
entries: Push<CompletionEntry>,
replacementToken: Node | undefined,
contextToken: Node | undefined,
location: Node,
sourceFile: SourceFile,
@ -816,7 +843,6 @@ namespace ts.Completions {
const start = timestamp();
const variableDeclaration = getVariableDeclaration(location);
const useSemicolons = probablyUsesSemicolons(sourceFile);
const isTypeOnlyImport = !!importCompletionNode && isTypeOnlyImportOrExportDeclaration(location.parent);
// Tracks unique names.
// Value is set to false for global variables or completions from external module exports, because we can have multiple of those;
// true otherwise. Based on the order we add things we will always see locals first, then globals, then module exports.
@ -836,6 +862,7 @@ namespace ts.Completions {
const entry = createCompletionEntry(
symbol,
sortText,
replacementToken,
contextToken,
location,
sourceFile,
@ -847,7 +874,6 @@ namespace ts.Completions {
propertyAccessToConvert,
isJsxInitializer,
importCompletionNode,
isTypeOnlyImport,
useSemicolons,
compilerOptions,
preferences
@ -1050,7 +1076,7 @@ namespace ts.Completions {
case CompletionDataKind.JsDocParameterName:
return JsDoc.getJSDocParameterNameCompletionDetails(name);
case CompletionDataKind.Keywords:
return request.keywords.indexOf(stringToToken(name)!) > -1 ? createSimpleDetails(name, ScriptElementKind.keyword, SymbolDisplayPartKind.keyword) : undefined;
return some(request.keywordCompletions, c => c.name === name) ? createSimpleDetails(name, ScriptElementKind.keyword, SymbolDisplayPartKind.keyword) : undefined;
default:
return Debug.assertNever(request);
}
@ -1107,7 +1133,7 @@ namespace ts.Completions {
): CodeActionsAndSourceDisplay {
if (data?.moduleSpecifier) {
const { contextToken, previousToken } = getRelevantTokens(position, sourceFile);
if (previousToken && getImportCompletionNode(contextToken || previousToken)) {
if (previousToken && getImportStatementCompletionInfo(contextToken || previousToken).replacementNode) {
// Import statement completion: 'import c|'
return { codeActions: undefined, sourceDisplay: [textPart(data.moduleSpecifier)] };
}
@ -1164,6 +1190,7 @@ namespace ts.Completions {
readonly symbolToOriginInfoMap: SymbolOriginInfoMap;
readonly recommendedCompletion: Symbol | undefined;
readonly previousToken: Node | undefined;
readonly contextToken: Node | undefined;
readonly isJsxInitializer: IsJsxInitializer;
readonly insideJsDocTagTypeExpression: boolean;
readonly symbolToSortTextIdMap: SymbolSortTextIdMap;
@ -1176,7 +1203,7 @@ namespace ts.Completions {
type Request =
| { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag }
| { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag }
| { readonly kind: CompletionDataKind.Keywords, keywords: readonly SyntaxKind[] };
| { readonly kind: CompletionDataKind.Keywords, keywordCompletions: readonly CompletionEntry[], isNewIdentifierLocation: boolean };
export const enum CompletionKind {
ObjectPropertyDeclaration,
@ -1332,6 +1359,7 @@ namespace ts.Completions {
start = timestamp();
// The decision to provide completion depends on the contextToken, which is determined through the previousToken.
// Note: 'previousToken' (and thus 'contextToken') can be undefined if we are the beginning of the file
const isJsOnlyLocation = !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile);
const tokens = getRelevantTokens(position, sourceFile);
const previousToken = tokens.previousToken!;
let contextToken = tokens.contextToken!;
@ -1350,23 +1378,35 @@ namespace ts.Completions {
let isJsxIdentifierExpected = false;
let importCompletionNode: Node | undefined;
let location = getTouchingPropertyName(sourceFile, position);
let keywordFilters = KeywordCompletionFilters.None;
let isNewIdentifierLocation = false;
if (contextToken) {
const importCompletionCandidate = getImportCompletionNode(contextToken);
if (importCompletionCandidate === SyntaxKind.FromKeyword) {
return { kind: CompletionDataKind.Keywords, keywords: [SyntaxKind.FromKeyword] };
const importStatementCompletion = getImportStatementCompletionInfo(contextToken);
isNewIdentifierLocation = importStatementCompletion.isNewIdentifierLocation;
if (importStatementCompletion.keywordCompletion) {
if (importStatementCompletion.isKeywordOnlyCompletion) {
return {
kind: CompletionDataKind.Keywords,
keywordCompletions: [keywordToCompletionEntry(importStatementCompletion.keywordCompletion)],
isNewIdentifierLocation,
};
}
keywordFilters = keywordFiltersFromSyntaxKind(importStatementCompletion.keywordCompletion);
}
// Import statement completions use `insertText`, and also require the `data` property of `CompletionEntryIdentifier`
// added in TypeScript 4.3 to be sent back from the client during `getCompletionEntryDetails`. Since this feature
// is not backward compatible with older clients, the language service defaults to disabling it, allowing newer clients
// to opt in with the `includeCompletionsForImportStatements` user preference.
if (importCompletionCandidate && preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText) {
importCompletionNode = importCompletionCandidate;
if (importStatementCompletion.replacementNode && preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText) {
// Import statement completions use `insertText`, and also require the `data` property of `CompletionEntryIdentifier`
// added in TypeScript 4.3 to be sent back from the client during `getCompletionEntryDetails`. Since this feature
// is not backward compatible with older clients, the language service defaults to disabling it, allowing newer clients
// to opt in with the `includeCompletionsForImportStatements` user preference.
importCompletionNode = importStatementCompletion.replacementNode;
}
// Bail out if this is a known invalid completion location
if (!importCompletionNode && isCompletionListBlocker(contextToken)) {
log("Returning an empty list because completion was requested in an invalid position.");
return undefined;
return keywordFilters
? keywordCompletionData(keywordFilters, isJsOnlyLocation, isNewIdentifierDefinitionLocation())
: undefined;
}
let parent = contextToken.parent;
@ -1495,10 +1535,8 @@ namespace ts.Completions {
const semanticStart = timestamp();
let completionKind = CompletionKind.None;
let isNewIdentifierLocation = false;
let isNonContextualObjectLiteral = false;
let hasUnresolvedAutoImports = false;
let keywordFilters = KeywordCompletionFilters.None;
// This also gets mutated in nested-functions after the return
let symbols: Symbol[] = [];
const symbolToOriginInfoMap: SymbolOriginInfoMap = [];
@ -1533,7 +1571,9 @@ namespace ts.Completions {
// global symbols in scope. These results should be valid for either language as
// the set of symbols that can be referenced from this location.
if (!tryGetGlobalSymbols()) {
return undefined;
return keywordFilters
? keywordCompletionData(keywordFilters, isJsOnlyLocation, isNewIdentifierLocation)
: undefined;
}
}
@ -1558,6 +1598,7 @@ namespace ts.Completions {
symbolToOriginInfoMap,
recommendedCompletion,
previousToken,
contextToken,
isJsxInitializer,
insideJsDocTagTypeExpression,
symbolToSortTextIdMap,
@ -1971,10 +2012,18 @@ namespace ts.Completions {
return;
}
const moduleSpecifierCache = host.getModuleSpecifierCache?.();
const lowerCaseTokenText = previousToken && isIdentifier(previousToken) ? previousToken.text.toLowerCase() : "";
const exportInfo = getExportInfoMap(sourceFile, host, program, cancellationToken);
// import { type | -> token text should be blank
const isAfterTypeOnlyImportSpecifierModifier = previousToken === contextToken
&& importCompletionNode
&& couldBeTypeOnlyImportSpecifier(importCompletionNode, contextToken);
const lowerCaseTokenText =
isAfterTypeOnlyImportSpecifierModifier ? "" :
previousToken && isIdentifier(previousToken) ? previousToken.text.toLowerCase() :
"";
const moduleSpecifierCache = host.getModuleSpecifierCache?.();
const exportInfo = getExportInfoMap(sourceFile, host, program, cancellationToken);
const packageJsonAutoImportProvider = host.getPackageJsonAutoImportProvider?.();
const packageJsonFilter = detailsEntryId ? undefined : createPackageJsonImportFilter(sourceFile, preferences, host);
resolvingModuleSpecifiers(
@ -2130,6 +2179,7 @@ namespace ts.Completions {
case SyntaxKind.ModuleKeyword: // module |
case SyntaxKind.NamespaceKeyword: // namespace |
case SyntaxKind.ImportKeyword: // import |
return true;
case SyntaxKind.DotToken:
@ -2292,22 +2342,42 @@ namespace ts.Completions {
* Relevant symbols are stored in the captured 'symbols' variable.
*/
function tryGetImportOrExportClauseCompletionSymbols(): GlobalsSearch {
// `import { |` or `import { a as 0, | }`
const namedImportsOrExports = contextToken && (contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken)
? tryCast(contextToken.parent, isNamedImportsOrExports) : undefined;
if (!contextToken) return GlobalsSearch.Continue;
// `import { |` or `import { a as 0, | }` or `import { type | }`
const namedImportsOrExports =
contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken ? tryCast(contextToken.parent, isNamedImportsOrExports) :
isTypeKeywordTokenOrIdentifier(contextToken) ? tryCast(contextToken.parent.parent, isNamedImportsOrExports) : undefined;
if (!namedImportsOrExports) return GlobalsSearch.Continue;
// We can at least offer `type` at `import { |`
if (!isTypeKeywordTokenOrIdentifier(contextToken)) {
keywordFilters = KeywordCompletionFilters.TypeKeyword;
}
// try to show exported member for imported/re-exported module
const { moduleSpecifier } = namedImportsOrExports.kind === SyntaxKind.NamedImports ? namedImportsOrExports.parent.parent : namedImportsOrExports.parent;
if (!moduleSpecifier) return namedImportsOrExports.kind === SyntaxKind.NamedImports ? GlobalsSearch.Fail : GlobalsSearch.Continue;
if (!moduleSpecifier) {
isNewIdentifierLocation = true;
return namedImportsOrExports.kind === SyntaxKind.NamedImports ? GlobalsSearch.Fail : GlobalsSearch.Continue;
}
const moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(moduleSpecifier); // TODO: GH#18217
if (!moduleSpecifierSymbol) return GlobalsSearch.Fail;
if (!moduleSpecifierSymbol) {
isNewIdentifierLocation = true;
return GlobalsSearch.Fail;
}
completionKind = CompletionKind.MemberLike;
isNewIdentifierLocation = false;
const exports = typeChecker.getExportsAndPropertiesOfModule(moduleSpecifierSymbol);
const existing = new Set((namedImportsOrExports.elements as NodeArray<ImportOrExportSpecifier>).filter(n => !isCurrentlyEditingNode(n)).map(n => (n.propertyName || n.name).escapedText));
symbols = concatenate(symbols, exports.filter(e => e.escapedName !== InternalSymbolName.Default && !existing.has(e.escapedName)));
const uniques = exports.filter(e => e.escapedName !== InternalSymbolName.Default && !existing.has(e.escapedName));
symbols = concatenate(symbols, uniques);
if (!uniques.length) {
// If there's nothing else to import, don't offer `type` either
keywordFilters = KeywordCompletionFilters.None;
}
return GlobalsSearch.Success;
}
@ -2597,6 +2667,16 @@ namespace ts.Completions {
case SyntaxKind.SetKeyword:
return !isFromObjectTypeDeclaration(contextToken);
case SyntaxKind.Identifier:
if (containingNodeKind === SyntaxKind.ImportSpecifier &&
contextToken === (parent as ImportSpecifier).name &&
(contextToken as Identifier).text === "type"
) {
// import { type | }
return false;
}
break;
case SyntaxKind.ClassKeyword:
case SyntaxKind.EnumKeyword:
case SyntaxKind.InterfaceKeyword:
@ -2606,9 +2686,12 @@ namespace ts.Completions {
case SyntaxKind.LetKeyword:
case SyntaxKind.ConstKeyword:
case SyntaxKind.InferKeyword:
case SyntaxKind.TypeKeyword: // type htm|
return true;
case SyntaxKind.TypeKeyword:
// import { type foo| }
return containingNodeKind !== SyntaxKind.ImportSpecifier;
case SyntaxKind.AsteriskToken:
return isFunctionLike(contextToken.parent) && !isMethodDeclaration(contextToken.parent);
}
@ -3014,6 +3097,8 @@ namespace ts.Completions {
return isTypeKeyword(kind) || kind === SyntaxKind.ConstKeyword;
case KeywordCompletionFilters.TypeKeywords:
return isTypeKeyword(kind);
case KeywordCompletionFilters.TypeKeyword:
return kind === SyntaxKind.TypeKeyword;
default:
return Debug.assertNever(keywordFilter);
}
@ -3311,36 +3396,81 @@ namespace ts.Completions {
return undefined;
}
function getImportCompletionNode(contextToken: Node) {
interface ImportStatementCompletionInfo {
isKeywordOnlyCompletion: boolean;
keywordCompletion: TokenSyntaxKind | undefined;
isNewIdentifierLocation: boolean;
replacementNode: ImportEqualsDeclaration | ImportDeclaration | ImportSpecifier | Token<SyntaxKind.ImportKeyword> | undefined;
}
function getImportStatementCompletionInfo(contextToken: Node): ImportStatementCompletionInfo {
let keywordCompletion: TokenSyntaxKind | undefined;
let isKeywordOnlyCompletion = false;
const candidate = getCandidate();
return candidate === SyntaxKind.FromKeyword || candidate && rangeIsOnSingleLine(candidate, candidate.getSourceFile()) ? candidate : undefined;
return {
isKeywordOnlyCompletion,
keywordCompletion,
isNewIdentifierLocation: !!(candidate || keywordCompletion === SyntaxKind.TypeKeyword),
replacementNode: candidate && rangeIsOnSingleLine(candidate, candidate.getSourceFile())
? candidate
: undefined
};
function getCandidate() {
const parent = contextToken.parent;
if (isImportEqualsDeclaration(parent)) {
keywordCompletion = contextToken.kind === SyntaxKind.TypeKeyword ? undefined : SyntaxKind.TypeKeyword;
return isModuleSpecifierMissingOrEmpty(parent.moduleReference) ? parent : undefined;
}
if (couldBeTypeOnlyImportSpecifier(parent, contextToken) && canCompleteFromNamedBindings(parent.parent)) {
return parent;
}
if (isNamedImports(parent) || isNamespaceImport(parent)) {
if (isModuleSpecifierMissingOrEmpty(parent.parent.parent.moduleSpecifier) && (isNamespaceImport(parent) || parent.elements.length < 2) && !parent.parent.name) {
if (!parent.parent.isTypeOnly && (
contextToken.kind === SyntaxKind.OpenBraceToken ||
contextToken.kind === SyntaxKind.ImportKeyword ||
contextToken.kind === SyntaxKind.CommaToken
)) {
keywordCompletion = SyntaxKind.TypeKeyword;
}
if (canCompleteFromNamedBindings(parent)) {
// At `import { ... } |` or `import * as Foo |`, the only possible completion is `from`
return contextToken.kind === SyntaxKind.CloseBraceToken || contextToken.kind === SyntaxKind.Identifier
? SyntaxKind.FromKeyword
: parent.parent.parent;
if (contextToken.kind === SyntaxKind.CloseBraceToken || contextToken.kind === SyntaxKind.Identifier) {
isKeywordOnlyCompletion = true;
keywordCompletion = SyntaxKind.FromKeyword;
}
else {
return parent.parent.parent;
}
}
return undefined;
}
if (isImportKeyword(contextToken) && isSourceFile(parent)) {
// A lone import keyword with nothing following it does not parse as a statement at all
keywordCompletion = SyntaxKind.TypeKeyword;
return contextToken as Token<SyntaxKind.ImportKeyword>;
}
if (isImportKeyword(contextToken) && isImportDeclaration(parent)) {
// `import s| from`
keywordCompletion = SyntaxKind.TypeKeyword;
return isModuleSpecifierMissingOrEmpty(parent.moduleSpecifier) ? parent : undefined;
}
return undefined;
}
}
function couldBeTypeOnlyImportSpecifier(importSpecifier: Node, contextToken: Node | undefined): importSpecifier is ImportSpecifier {
return isImportSpecifier(importSpecifier)
&& (importSpecifier.isTypeOnly || contextToken === importSpecifier.name && isTypeKeywordTokenOrIdentifier(contextToken));
}
function canCompleteFromNamedBindings(namedBindings: NamedImportBindings) {
return isModuleSpecifierMissingOrEmpty(namedBindings.parent.parent.moduleSpecifier)
&& (isNamespaceImport(namedBindings) || namedBindings.elements.length < 2)
&& !namedBindings.parent.name;
}
function isModuleSpecifierMissingOrEmpty(specifier: ModuleReference | Expression) {
if (nodeIsMissing(specifier)) return true;
return !tryCast(isExternalModuleReference(specifier) ? specifier.expression : specifier, isStringLiteralLike)?.text;
@ -3426,3 +3556,4 @@ namespace ts.Completions {
return charCode;
}
}

View file

@ -232,7 +232,7 @@ namespace ts.OrganizeImports {
else {
for (const defaultImport of defaultImports) {
newImportSpecifiers.push(
factory.createImportSpecifier(factory.createIdentifier("default"), defaultImport.importClause!.name!)); // TODO: GH#18217
factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("default"), defaultImport.importClause!.name!)); // TODO: GH#18217
}
}
@ -416,7 +416,8 @@ namespace ts.OrganizeImports {
}
export function compareImportOrExportSpecifiers<T extends ImportOrExportSpecifier>(s1: T, s2: T) {
return compareIdentifiers(s1.propertyName || s1.name, s2.propertyName || s2.name)
return compareBooleans(s1.isTypeOnly, s2.isTypeOnly)
|| compareIdentifiers(s1.propertyName || s1.name, s2.propertyName || s2.name)
|| compareIdentifiers(s1.name, s2.name);
}

View file

@ -252,10 +252,10 @@ namespace ts.refactor {
}
function makeImportSpecifier(propertyName: string, name: string): ImportSpecifier {
return factory.createImportSpecifier(propertyName === name ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name));
return factory.createImportSpecifier(/*isTypeOnly*/ false, propertyName === name ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name));
}
function makeExportSpecifier(propertyName: string, name: string): ExportSpecifier {
return factory.createExportSpecifier(propertyName === name ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name));
return factory.createExportSpecifier(/*isTypeOnly*/ false, propertyName === name ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name));
}
}

View file

@ -116,7 +116,7 @@ namespace ts.refactor {
const importSpecifiers: ImportSpecifier[] = [];
exportNameToImportName.forEach((name, propertyName) => {
importSpecifiers.push(factory.createImportSpecifier(name === propertyName ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name)));
importSpecifiers.push(factory.createImportSpecifier(/*isTypeOnly*/ false, name === propertyName ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name)));
});
const importDecl = toConvert.parent.parent;
@ -191,7 +191,7 @@ namespace ts.refactor {
changes.replaceNode(sourceFile, toConvert, factory.createNamespaceImport(factory.createIdentifier(namespaceImportName)));
if (neededNamedImports.size) {
const newNamedImports: ImportSpecifier[] = arrayFrom(neededNamedImports.values()).map(element =>
factory.createImportSpecifier(element.propertyName && factory.createIdentifier(element.propertyName.text), factory.createIdentifier(element.name.text)));
factory.createImportSpecifier(element.isTypeOnly, element.propertyName && factory.createIdentifier(element.propertyName.text), factory.createIdentifier(element.name.text)));
changes.insertNodeAfter(sourceFile, toConvert.parent.parent, updateImport(importDecl, /*defaultImportName*/ undefined, newNamedImports));
}
}

View file

@ -319,7 +319,7 @@ namespace ts.refactor {
function makeImportOrRequire(defaultImport: Identifier | undefined, imports: readonly string[], path: string, useEs6Imports: boolean, quotePreference: QuotePreference): AnyImportOrRequireStatement | undefined {
path = ensurePathIsNonModuleName(path);
if (useEs6Imports) {
const specifiers = imports.map(i => factory.createImportSpecifier(/*propertyName*/ undefined, factory.createIdentifier(i)));
const specifiers = imports.map(i => factory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, factory.createIdentifier(i)));
return makeImportIfNecessary(defaultImport, specifiers, path, quotePreference);
}
else {

View file

@ -27,6 +27,7 @@ namespace ts.Completions.StringCompletions {
completion.symbols,
entries,
contextToken,
contextToken,
sourceFile,
sourceFile,
checker,

View file

@ -1795,6 +1795,10 @@ namespace ts {
return node.kind === SyntaxKind.TypeKeyword;
}
export function isTypeKeywordTokenOrIdentifier(node: Node) {
return isTypeKeywordToken(node) || isIdentifier(node) && node.text === "type";
}
/** True if the symbol is for an external module, as opposed to a namespace. */
export function isExternalModuleSymbol(moduleSymbol: Symbol): boolean {
return !!(moduleSymbol.flags & SymbolFlags.Module) && moduleSymbol.name.charCodeAt(0) === CharacterCodes.doubleQuote;

View file

@ -335,4 +335,4 @@ namespace Harness {
return { unitName: unitName || this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions };
}
}
}
}

View file

@ -206,6 +206,13 @@ namespace ts {
assertListEqual(actualCoalescedExports, expectedCoalescedExports);
});
it("Sort specifiers - type-only", () => {
const sortedImports = parseImports(`import { type z, y, type x, c, type b, a } from "lib";`);
const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
const expectedCoalescedImports = parseImports(`import { a, c, y, type b, type x, type z } from "lib";`);
assertListEqual(actualCoalescedImports, expectedCoalescedImports);
});
it("Combine namespace re-exports", () => {
const sortedExports = parseExports(
`export * from "lib";`,

View file

@ -535,7 +535,7 @@ import {
x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter10", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(factory.createIdentifier("b"), factory.createIdentifier("a")));
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("b"), factory.createIdentifier("a")));
});
}
{
@ -544,7 +544,7 @@ import {
x // this is x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter11", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(factory.createIdentifier("b"), factory.createIdentifier("a")));
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("b"), factory.createIdentifier("a")));
});
}
{
@ -554,7 +554,7 @@ import {
} from "bar"`;
runSingleFileTest("insertNodeInListAfter12", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
// eslint-disable-next-line boolean-trivia
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(undefined, factory.createIdentifier("a")));
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a")));
});
}
{
@ -564,7 +564,7 @@ import {
} from "bar"`;
runSingleFileTest("insertNodeInListAfter13", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
// eslint-disable-next-line boolean-trivia
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(undefined, factory.createIdentifier("a")));
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a")));
});
}
{
@ -574,7 +574,7 @@ import {
x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter14", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(factory.createIdentifier("b"), factory.createIdentifier("a")));
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("b"), factory.createIdentifier("a")));
});
}
{
@ -584,7 +584,7 @@ import {
x // this is x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter15", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(factory.createIdentifier("b"), factory.createIdentifier("a")));
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("b"), factory.createIdentifier("a")));
});
}
{
@ -595,7 +595,7 @@ import {
} from "bar"`;
runSingleFileTest("insertNodeInListAfter16", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
// eslint-disable-next-line boolean-trivia
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(undefined, factory.createIdentifier("a")));
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a")));
});
}
{
@ -606,7 +606,7 @@ import {
} from "bar"`;
runSingleFileTest("insertNodeInListAfter17", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
// eslint-disable-next-line boolean-trivia
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(undefined, factory.createIdentifier("a")));
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a")));
});
}
{
@ -616,14 +616,14 @@ import {
} from "bar"`;
runSingleFileTest("insertNodeInListAfter18", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
// eslint-disable-next-line boolean-trivia
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(undefined, factory.createIdentifier("a")));
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a")));
});
}
{
const runTest = (name: string, text: string) => runSingleFileTest(name, /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
for (const specifier of ["x3", "x4", "x5"]) {
// eslint-disable-next-line boolean-trivia
changeTracker.insertNodeInListAfter(sourceFile, findChild("x2", sourceFile), factory.createImportSpecifier(undefined, factory.createIdentifier(specifier)));
changeTracker.insertNodeInListAfter(sourceFile, findChild("x2", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier(specifier)));
}
});

View file

@ -275,7 +275,7 @@ namespace ts {
if (node.kind === SyntaxKind.ExportDeclaration) {
const ed = node as Node as ExportDeclaration;
const exports = [{ name: "x" }];
const exportSpecifiers = exports.map(e => factory.createExportSpecifier(e.name, e.name));
const exportSpecifiers = exports.map(e => factory.createExportSpecifier(/*isTypeOnly*/ false, e.name, e.name));
const exportClause = factory.createNamedExports(exportSpecifiers);
const newEd = factory.updateExportDeclaration(ed, ed.decorators, ed.modifiers, ed.isTypeOnly, exportClause, ed.moduleSpecifier, ed.assertClause);

View file

@ -1698,10 +1698,12 @@ declare namespace ts {
readonly parent: NamedImports;
readonly propertyName?: Identifier;
readonly name: Identifier;
readonly isTypeOnly: boolean;
}
export interface ExportSpecifier extends NamedDeclaration {
readonly kind: SyntaxKind.ExportSpecifier;
readonly parent: NamedExports;
readonly isTypeOnly: boolean;
readonly propertyName?: Identifier;
readonly name: Identifier;
}
@ -3578,16 +3580,16 @@ declare namespace ts {
updateNamespaceExport(node: NamespaceExport, name: Identifier): NamespaceExport;
createNamedImports(elements: readonly ImportSpecifier[]): NamedImports;
updateNamedImports(node: NamedImports, elements: readonly ImportSpecifier[]): NamedImports;
createImportSpecifier(propertyName: Identifier | undefined, name: Identifier): ImportSpecifier;
updateImportSpecifier(node: ImportSpecifier, propertyName: Identifier | undefined, name: Identifier): ImportSpecifier;
createImportSpecifier(isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier): ImportSpecifier;
updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier): ImportSpecifier;
createExportAssignment(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isExportEquals: boolean | undefined, expression: Expression): ExportAssignment;
updateExportAssignment(node: ExportAssignment, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, expression: Expression): ExportAssignment;
createExportDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isTypeOnly: boolean, exportClause: NamedExportBindings | undefined, moduleSpecifier?: Expression, assertClause?: AssertClause): ExportDeclaration;
updateExportDeclaration(node: ExportDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isTypeOnly: boolean, exportClause: NamedExportBindings | undefined, moduleSpecifier: Expression | undefined, assertClause: AssertClause | undefined): ExportDeclaration;
createNamedExports(elements: readonly ExportSpecifier[]): NamedExports;
updateNamedExports(node: NamedExports, elements: readonly ExportSpecifier[]): NamedExports;
createExportSpecifier(propertyName: string | Identifier | undefined, name: string | Identifier): ExportSpecifier;
updateExportSpecifier(node: ExportSpecifier, propertyName: Identifier | undefined, name: Identifier): ExportSpecifier;
createExportSpecifier(isTypeOnly: boolean, propertyName: string | Identifier | undefined, name: string | Identifier): ExportSpecifier;
updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier): ExportSpecifier;
createExternalModuleReference(expression: Expression): ExternalModuleReference;
updateExternalModuleReference(node: ExternalModuleReference, expression: Expression): ExternalModuleReference;
createJSDocAllType(): JSDocAllType;
@ -11053,9 +11055,9 @@ declare namespace ts {
/** @deprecated Use `factory.updateNamedImports` or the factory supplied by your transformation context instead. */
const updateNamedImports: (node: NamedImports, elements: readonly ImportSpecifier[]) => NamedImports;
/** @deprecated Use `factory.createImportSpecifier` or the factory supplied by your transformation context instead. */
const createImportSpecifier: (propertyName: Identifier | undefined, name: Identifier) => ImportSpecifier;
const createImportSpecifier: (isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) => ImportSpecifier;
/** @deprecated Use `factory.updateImportSpecifier` or the factory supplied by your transformation context instead. */
const updateImportSpecifier: (node: ImportSpecifier, propertyName: Identifier | undefined, name: Identifier) => ImportSpecifier;
const updateImportSpecifier: (node: ImportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) => ImportSpecifier;
/** @deprecated Use `factory.createExportAssignment` or the factory supplied by your transformation context instead. */
const createExportAssignment: (decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isExportEquals: boolean | undefined, expression: Expression) => ExportAssignment;
/** @deprecated Use `factory.updateExportAssignment` or the factory supplied by your transformation context instead. */
@ -11065,9 +11067,9 @@ declare namespace ts {
/** @deprecated Use `factory.updateNamedExports` or the factory supplied by your transformation context instead. */
const updateNamedExports: (node: NamedExports, elements: readonly ExportSpecifier[]) => NamedExports;
/** @deprecated Use `factory.createExportSpecifier` or the factory supplied by your transformation context instead. */
const createExportSpecifier: (propertyName: string | Identifier | undefined, name: string | Identifier) => ExportSpecifier;
const createExportSpecifier: (isTypeOnly: boolean, propertyName: string | Identifier | undefined, name: string | Identifier) => ExportSpecifier;
/** @deprecated Use `factory.updateExportSpecifier` or the factory supplied by your transformation context instead. */
const updateExportSpecifier: (node: ExportSpecifier, propertyName: Identifier | undefined, name: Identifier) => ExportSpecifier;
const updateExportSpecifier: (node: ExportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) => ExportSpecifier;
/** @deprecated Use `factory.createExternalModuleReference` or the factory supplied by your transformation context instead. */
const createExternalModuleReference: (expression: Expression) => ExternalModuleReference;
/** @deprecated Use `factory.updateExternalModuleReference` or the factory supplied by your transformation context instead. */

View file

@ -1698,10 +1698,12 @@ declare namespace ts {
readonly parent: NamedImports;
readonly propertyName?: Identifier;
readonly name: Identifier;
readonly isTypeOnly: boolean;
}
export interface ExportSpecifier extends NamedDeclaration {
readonly kind: SyntaxKind.ExportSpecifier;
readonly parent: NamedExports;
readonly isTypeOnly: boolean;
readonly propertyName?: Identifier;
readonly name: Identifier;
}
@ -3578,16 +3580,16 @@ declare namespace ts {
updateNamespaceExport(node: NamespaceExport, name: Identifier): NamespaceExport;
createNamedImports(elements: readonly ImportSpecifier[]): NamedImports;
updateNamedImports(node: NamedImports, elements: readonly ImportSpecifier[]): NamedImports;
createImportSpecifier(propertyName: Identifier | undefined, name: Identifier): ImportSpecifier;
updateImportSpecifier(node: ImportSpecifier, propertyName: Identifier | undefined, name: Identifier): ImportSpecifier;
createImportSpecifier(isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier): ImportSpecifier;
updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier): ImportSpecifier;
createExportAssignment(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isExportEquals: boolean | undefined, expression: Expression): ExportAssignment;
updateExportAssignment(node: ExportAssignment, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, expression: Expression): ExportAssignment;
createExportDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isTypeOnly: boolean, exportClause: NamedExportBindings | undefined, moduleSpecifier?: Expression, assertClause?: AssertClause): ExportDeclaration;
updateExportDeclaration(node: ExportDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isTypeOnly: boolean, exportClause: NamedExportBindings | undefined, moduleSpecifier: Expression | undefined, assertClause: AssertClause | undefined): ExportDeclaration;
createNamedExports(elements: readonly ExportSpecifier[]): NamedExports;
updateNamedExports(node: NamedExports, elements: readonly ExportSpecifier[]): NamedExports;
createExportSpecifier(propertyName: string | Identifier | undefined, name: string | Identifier): ExportSpecifier;
updateExportSpecifier(node: ExportSpecifier, propertyName: Identifier | undefined, name: Identifier): ExportSpecifier;
createExportSpecifier(isTypeOnly: boolean, propertyName: string | Identifier | undefined, name: string | Identifier): ExportSpecifier;
updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier): ExportSpecifier;
createExternalModuleReference(expression: Expression): ExternalModuleReference;
updateExternalModuleReference(node: ExternalModuleReference, expression: Expression): ExternalModuleReference;
createJSDocAllType(): JSDocAllType;
@ -7252,9 +7254,9 @@ declare namespace ts {
/** @deprecated Use `factory.updateNamedImports` or the factory supplied by your transformation context instead. */
const updateNamedImports: (node: NamedImports, elements: readonly ImportSpecifier[]) => NamedImports;
/** @deprecated Use `factory.createImportSpecifier` or the factory supplied by your transformation context instead. */
const createImportSpecifier: (propertyName: Identifier | undefined, name: Identifier) => ImportSpecifier;
const createImportSpecifier: (isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) => ImportSpecifier;
/** @deprecated Use `factory.updateImportSpecifier` or the factory supplied by your transformation context instead. */
const updateImportSpecifier: (node: ImportSpecifier, propertyName: Identifier | undefined, name: Identifier) => ImportSpecifier;
const updateImportSpecifier: (node: ImportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) => ImportSpecifier;
/** @deprecated Use `factory.createExportAssignment` or the factory supplied by your transformation context instead. */
const createExportAssignment: (decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isExportEquals: boolean | undefined, expression: Expression) => ExportAssignment;
/** @deprecated Use `factory.updateExportAssignment` or the factory supplied by your transformation context instead. */
@ -7264,9 +7266,9 @@ declare namespace ts {
/** @deprecated Use `factory.updateNamedExports` or the factory supplied by your transformation context instead. */
const updateNamedExports: (node: NamedExports, elements: readonly ExportSpecifier[]) => NamedExports;
/** @deprecated Use `factory.createExportSpecifier` or the factory supplied by your transformation context instead. */
const createExportSpecifier: (propertyName: string | Identifier | undefined, name: string | Identifier) => ExportSpecifier;
const createExportSpecifier: (isTypeOnly: boolean, propertyName: string | Identifier | undefined, name: string | Identifier) => ExportSpecifier;
/** @deprecated Use `factory.updateExportSpecifier` or the factory supplied by your transformation context instead. */
const updateExportSpecifier: (node: ExportSpecifier, propertyName: Identifier | undefined, name: Identifier) => ExportSpecifier;
const updateExportSpecifier: (node: ExportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) => ExportSpecifier;
/** @deprecated Use `factory.createExternalModuleReference` or the factory supplied by your transformation context instead. */
const createExternalModuleReference: (expression: Expression) => ExternalModuleReference;
/** @deprecated Use `factory.updateExternalModuleReference` or the factory supplied by your transformation context instead. */

View file

@ -0,0 +1,40 @@
/exports.ts(9,15): error TS2207: The 'type' modifier cannot be used on a named export when 'export type' is used on its export statement.
/imports.ts(3,1): error TS1362: 'as' cannot be used as a value because it was exported using 'export type'.
/imports.ts(4,1): error TS1362: 'something' cannot be used as a value because it was exported using 'export type'.
/imports.ts(5,1): error TS1362: 'foo' cannot be used as a value because it was exported using 'export type'.
/imports.ts(6,1): error TS1362: 'bar' cannot be used as a value because it was exported using 'export type'.
==== /imports.ts (4 errors) ====
import { type, as, something, foo, bar } from "./exports.js";
type;
as; // Error (used in emitting position)
~~
!!! error TS1362: 'as' cannot be used as a value because it was exported using 'export type'.
!!! related TS1377 /exports.ts:5:10: 'as' was exported here.
something; // Error (used in emitting position)
~~~~~~~~~
!!! error TS1362: 'something' cannot be used as a value because it was exported using 'export type'.
!!! related TS1377 /exports.ts:6:10: 'something' was exported here.
foo; // Error (used in emitting position)
~~~
!!! error TS1362: 'foo' cannot be used as a value because it was exported using 'export type'.
!!! related TS1377 /exports.ts:7:10: 'foo' was exported here.
bar; // Error (used in emitting position)
~~~
!!! error TS1362: 'bar' cannot be used as a value because it was exported using 'export type'.
!!! related TS1377 /exports.ts:8:10: 'bar' was exported here.
==== /exports.ts (1 errors) ====
const type = 0;
const as = 0;
const something = 0;
export { type };
export { type as };
export { type something };
export { type type as foo };
export { type as as bar };
export type { type something as whatever }; // Error
~~~~
!!! error TS2207: The 'type' modifier cannot be used on a named export when 'export type' is used on its export statement.

View file

@ -0,0 +1,48 @@
//// [tests/cases/conformance/externalModules/typeOnly/exportSpecifiers.ts] ////
//// [imports.ts]
import { type, as, something, foo, bar } from "./exports.js";
type;
as; // Error (used in emitting position)
something; // Error (used in emitting position)
foo; // Error (used in emitting position)
bar; // Error (used in emitting position)
//// [exports.ts]
const type = 0;
const as = 0;
const something = 0;
export { type };
export { type as };
export { type something };
export { type type as foo };
export { type as as bar };
export type { type something as whatever }; // Error
//// [exports.js]
var type = 0;
var as = 0;
var something = 0;
export { type };
//// [imports.js]
import { type } from "./exports.js";
type;
as; // Error (used in emitting position)
something; // Error (used in emitting position)
foo; // Error (used in emitting position)
bar; // Error (used in emitting position)
//// [exports.d.ts]
declare const type = 0;
declare const as = 0;
declare const something = 0;
export { type };
export { type as };
export { type something };
export { type type as foo };
export { type as as bar };
export type { type something as whatever };
//// [imports.d.ts]
export {};

View file

@ -0,0 +1,54 @@
=== /imports.ts ===
import { type, as, something, foo, bar } from "./exports.js";
>type : Symbol(type, Decl(imports.ts, 0, 8))
>as : Symbol(as, Decl(imports.ts, 0, 14))
>something : Symbol(something, Decl(imports.ts, 0, 18))
>foo : Symbol(foo, Decl(imports.ts, 0, 29))
>bar : Symbol(bar, Decl(imports.ts, 0, 34))
type;
>type : Symbol(type, Decl(imports.ts, 0, 8))
as; // Error (used in emitting position)
>as : Symbol(as, Decl(imports.ts, 0, 14))
something; // Error (used in emitting position)
>something : Symbol(something, Decl(imports.ts, 0, 18))
foo; // Error (used in emitting position)
>foo : Symbol(foo, Decl(imports.ts, 0, 29))
bar; // Error (used in emitting position)
>bar : Symbol(bar, Decl(imports.ts, 0, 34))
=== /exports.ts ===
const type = 0;
>type : Symbol(type, Decl(exports.ts, 0, 5))
const as = 0;
>as : Symbol(as, Decl(exports.ts, 1, 5))
const something = 0;
>something : Symbol(something, Decl(exports.ts, 2, 5))
export { type };
>type : Symbol(type, Decl(exports.ts, 3, 8))
export { type as };
>as : Symbol(as, Decl(exports.ts, 4, 8))
export { type something };
>something : Symbol(something, Decl(exports.ts, 5, 8))
export { type type as foo };
>type : Symbol(type, Decl(exports.ts, 0, 5))
>foo : Symbol(foo, Decl(exports.ts, 6, 8))
export { type as as bar };
>as : Symbol(as, Decl(exports.ts, 1, 5))
>bar : Symbol(bar, Decl(exports.ts, 7, 8))
export type { type something as whatever }; // Error
>something : Symbol(something, Decl(exports.ts, 2, 5))
>whatever : Symbol(whatever, Decl(exports.ts, 8, 13))

View file

@ -0,0 +1,57 @@
=== /imports.ts ===
import { type, as, something, foo, bar } from "./exports.js";
>type : 0
>as : 0
>something : 0
>foo : 0
>bar : 0
type;
>type : 0
as; // Error (used in emitting position)
>as : 0
something; // Error (used in emitting position)
>something : 0
foo; // Error (used in emitting position)
>foo : 0
bar; // Error (used in emitting position)
>bar : 0
=== /exports.ts ===
const type = 0;
>type : 0
>0 : 0
const as = 0;
>as : 0
>0 : 0
const something = 0;
>something : 0
>0 : 0
export { type };
>type : 0
export { type as };
>as : 0
export { type something };
>something : 0
export { type type as foo };
>type : 0
>foo : 0
export { type as as bar };
>as : 0
>bar : 0
export type { type something as whatever }; // Error
>something : 0
>whatever : any

View file

@ -0,0 +1,108 @@
/a.ts(4,1): error TS1361: 'as' cannot be used as a value because it was imported using 'import type'.
/b.ts(2,1): error TS2304: Cannot find name 'type'.
/c.ts(2,1): error TS2304: Cannot find name 'type'.
/c.ts(3,1): error TS1361: 'as' cannot be used as a value because it was imported using 'import type'.
/d.ts(1,21): error TS2300: Duplicate identifier 'as'.
/d.ts(1,24): error TS1005: ',' expected.
/d.ts(1,24): error TS2300: Duplicate identifier 'as'.
/e.ts(4,1): error TS1361: 'as' cannot be used as a value because it was imported using 'import type'.
/f.ts(1,15): error TS1003: Identifier expected.
/f.ts(1,15): error TS2305: Module '"./mod.js"' has no exported member 'import'.
/f.ts(2,18): error TS1003: Identifier expected.
/f.ts(2,18): error TS2300: Duplicate identifier 'export'.
/f.ts(3,21): error TS1003: Identifier expected.
/f.ts(3,21): error TS2300: Duplicate identifier 'export'.
/f.ts(6,1): error TS2304: Cannot find name 'type'.
/f.ts(7,1): error TS2304: Cannot find name 'as'.
/f.ts(8,1): error TS1361: 'something' cannot be used as a value because it was imported using 'import type'.
/f.ts(9,1): error TS1361: 's' cannot be used as a value because it was imported using 'import type'.
/g.ts(1,15): error TS2206: The 'type' modifier cannot be used on a named import when 'import type' is used on its import statement.
==== /mod.ts (0 errors) ====
export const as = 0;
export const type = 0;
export const something = 0;
==== /a.ts (1 errors) ====
import { type } from "./mod.js";
import { type as } from "./mod.js";
type;
as; // Error (used in emitting position)
~~
!!! error TS1361: 'as' cannot be used as a value because it was imported using 'import type'.
!!! related TS1376 /a.ts:2:10: 'as' was imported here.
==== /b.ts (1 errors) ====
import { type as as } from "./mod.js";
type; // Error (cannot resolve name)
~~~~
!!! error TS2304: Cannot find name 'type'.
as;
==== /c.ts (2 errors) ====
import { type as as as } from "./mod.js";
type; // Error (cannot resolve name)
~~~~
!!! error TS2304: Cannot find name 'type'.
as; // Error (used in emitting position)
~~
!!! error TS1361: 'as' cannot be used as a value because it was imported using 'import type'.
!!! related TS1376 /c.ts:1:10: 'as' was imported here.
==== /d.ts (3 errors) ====
import { type as as as as } from "./mod.js"; // Error
~~
!!! error TS2300: Duplicate identifier 'as'.
~~
!!! error TS1005: ',' expected.
~~
!!! error TS2300: Duplicate identifier 'as'.
==== /e.ts (1 errors) ====
import { type type as as } from "./mod.js";
import { type as type } from "./mod.js";
type;
as; // Error (used in emitting position)
~~
!!! error TS1361: 'as' cannot be used as a value because it was imported using 'import type'.
!!! related TS1376 /e.ts:1:10: 'as' was imported here.
==== /f.ts (10 errors) ====
import { type import } from "./mod.js"; // Error
~~~~~~
!!! error TS1003: Identifier expected.
~~~~~~
!!! error TS2305: Module '"./mod.js"' has no exported member 'import'.
import { type as export } from "./mod.js"; // Error
~~~~~~
!!! error TS1003: Identifier expected.
~~~~~~
!!! error TS2300: Duplicate identifier 'export'.
import { type as as export } from "./mod.js"; // Error
~~~~~~
!!! error TS1003: Identifier expected.
~~~~~~
!!! error TS2300: Duplicate identifier 'export'.
import { type something } from "./mod.js";
import { type something as s } from "./mod.js";
type; // Error (cannot resolve name)
~~~~
!!! error TS2304: Cannot find name 'type'.
as; // Error (cannot resolve name)
~~
!!! error TS2304: Cannot find name 'as'.
something; // Error (used in emitting position)
~~~~~~~~~
!!! error TS1361: 'something' cannot be used as a value because it was imported using 'import type'.
!!! related TS1376 /f.ts:4:10: 'something' was imported here.
s; // Error (used in emitting position)
~
!!! error TS1361: 's' cannot be used as a value because it was imported using 'import type'.
!!! related TS1376 /f.ts:5:10: 's' was imported here.
==== /g.ts (1 errors) ====
import type { type something } from "./mod.js"; // Error
~~~~
!!! error TS2206: The 'type' modifier cannot be used on a named import when 'import type' is used on its import statement.

View file

@ -0,0 +1,97 @@
//// [tests/cases/conformance/externalModules/typeOnly/importSpecifiers1.ts] ////
//// [mod.ts]
export const as = 0;
export const type = 0;
export const something = 0;
//// [a.ts]
import { type } from "./mod.js";
import { type as } from "./mod.js";
type;
as; // Error (used in emitting position)
//// [b.ts]
import { type as as } from "./mod.js";
type; // Error (cannot resolve name)
as;
//// [c.ts]
import { type as as as } from "./mod.js";
type; // Error (cannot resolve name)
as; // Error (used in emitting position)
//// [d.ts]
import { type as as as as } from "./mod.js"; // Error
//// [e.ts]
import { type type as as } from "./mod.js";
import { type as type } from "./mod.js";
type;
as; // Error (used in emitting position)
//// [f.ts]
import { type import } from "./mod.js"; // Error
import { type as export } from "./mod.js"; // Error
import { type as as export } from "./mod.js"; // Error
import { type something } from "./mod.js";
import { type something as s } from "./mod.js";
type; // Error (cannot resolve name)
as; // Error (cannot resolve name)
something; // Error (used in emitting position)
s; // Error (used in emitting position)
//// [g.ts]
import type { type something } from "./mod.js"; // Error
//// [mod.js]
export var as = 0;
export var type = 0;
export var something = 0;
//// [a.js]
import { type } from "./mod.js";
type;
as; // Error (used in emitting position)
//// [b.js]
import { type as as } from "./mod.js";
type; // Error (cannot resolve name)
as;
//// [c.js]
type; // Error (cannot resolve name)
as; // Error (used in emitting position)
export {};
//// [d.js]
export {};
//// [e.js]
import { type as type } from "./mod.js";
type;
as; // Error (used in emitting position)
//// [f.js]
type; // Error (cannot resolve name)
as; // Error (cannot resolve name)
something; // Error (used in emitting position)
s; // Error (used in emitting position)
export {};
//// [g.js]
export {};
//// [mod.d.ts]
export declare const as = 0;
export declare const type = 0;
export declare const something = 0;
//// [a.d.ts]
export {};
//// [b.d.ts]
export {};
//// [c.d.ts]
export {};
//// [d.d.ts]
export {};
//// [e.d.ts]
export {};
//// [f.d.ts]
export {};
//// [g.d.ts]
export {};

View file

@ -0,0 +1,93 @@
=== /mod.ts ===
export const as = 0;
>as : Symbol(as, Decl(mod.ts, 0, 12))
export const type = 0;
>type : Symbol(type, Decl(mod.ts, 1, 12))
export const something = 0;
>something : Symbol(something, Decl(mod.ts, 2, 12))
=== /a.ts ===
import { type } from "./mod.js";
>type : Symbol(type, Decl(a.ts, 0, 8))
import { type as } from "./mod.js";
>as : Symbol(as, Decl(a.ts, 1, 8))
type;
>type : Symbol(type, Decl(a.ts, 0, 8))
as; // Error (used in emitting position)
>as : Symbol(as, Decl(a.ts, 1, 8))
=== /b.ts ===
import { type as as } from "./mod.js";
>type : Symbol(as, Decl(mod.ts, 1, 12))
>as : Symbol(as, Decl(b.ts, 0, 8))
type; // Error (cannot resolve name)
as;
>as : Symbol(as, Decl(b.ts, 0, 8))
=== /c.ts ===
import { type as as as } from "./mod.js";
>as : Symbol(as, Decl(mod.ts, 0, 12))
>as : Symbol(as, Decl(c.ts, 0, 8))
type; // Error (cannot resolve name)
as; // Error (used in emitting position)
>as : Symbol(as, Decl(c.ts, 0, 8))
=== /d.ts ===
import { type as as as as } from "./mod.js"; // Error
>as : Symbol(as, Decl(mod.ts, 0, 12))
>as : Symbol(as, Decl(d.ts, 0, 8))
>as : Symbol(as, Decl(d.ts, 0, 22))
=== /e.ts ===
import { type type as as } from "./mod.js";
>type : Symbol(as, Decl(mod.ts, 1, 12))
>as : Symbol(as, Decl(e.ts, 0, 8))
import { type as type } from "./mod.js";
>type : Symbol(as, Decl(mod.ts, 1, 12))
>type : Symbol(type, Decl(e.ts, 1, 8))
type;
>type : Symbol(type, Decl(e.ts, 1, 8))
as; // Error (used in emitting position)
>as : Symbol(as, Decl(e.ts, 0, 8))
=== /f.ts ===
import { type import } from "./mod.js"; // Error
>import : Symbol(import, Decl(f.ts, 0, 8))
import { type as export } from "./mod.js"; // Error
>type : Symbol(export, Decl(mod.ts, 1, 12))
>export : Symbol(export, Decl(f.ts, 1, 8))
import { type as as export } from "./mod.js"; // Error
>as : Symbol(as, Decl(mod.ts, 0, 12))
>export : Symbol(export, Decl(f.ts, 2, 8))
import { type something } from "./mod.js";
>something : Symbol(something, Decl(f.ts, 3, 8))
import { type something as s } from "./mod.js";
>something : Symbol(something, Decl(mod.ts, 2, 12))
>s : Symbol(s, Decl(f.ts, 4, 8))
type; // Error (cannot resolve name)
as; // Error (cannot resolve name)
something; // Error (used in emitting position)
>something : Symbol(something, Decl(f.ts, 3, 8))
s; // Error (used in emitting position)
>s : Symbol(s, Decl(f.ts, 4, 8))
=== /g.ts ===
import type { type something } from "./mod.js"; // Error
>something : Symbol(something, Decl(g.ts, 0, 13))

View file

@ -0,0 +1,104 @@
=== /mod.ts ===
export const as = 0;
>as : 0
>0 : 0
export const type = 0;
>type : 0
>0 : 0
export const something = 0;
>something : 0
>0 : 0
=== /a.ts ===
import { type } from "./mod.js";
>type : 0
import { type as } from "./mod.js";
>as : 0
type;
>type : 0
as; // Error (used in emitting position)
>as : 0
=== /b.ts ===
import { type as as } from "./mod.js";
>type : 0
>as : 0
type; // Error (cannot resolve name)
>type : any
as;
>as : 0
=== /c.ts ===
import { type as as as } from "./mod.js";
>as : 0
>as : 0
type; // Error (cannot resolve name)
>type : any
as; // Error (used in emitting position)
>as : 0
=== /d.ts ===
import { type as as as as } from "./mod.js"; // Error
>as : 0
>as : 0
>as : 0
=== /e.ts ===
import { type type as as } from "./mod.js";
>type : 0
>as : 0
import { type as type } from "./mod.js";
>type : 0
>type : 0
type;
>type : 0
as; // Error (used in emitting position)
>as : 0
=== /f.ts ===
import { type import } from "./mod.js"; // Error
>import : any
import { type as export } from "./mod.js"; // Error
>type : 0
>export : 0
import { type as as export } from "./mod.js"; // Error
>as : 0
>export : 0
import { type something } from "./mod.js";
>something : 0
import { type something as s } from "./mod.js";
>something : 0
>s : 0
type; // Error (cannot resolve name)
>type : any
as; // Error (cannot resolve name)
>as : any
something; // Error (used in emitting position)
>something : 0
s; // Error (used in emitting position)
>s : 0
=== /g.ts ===
import type { type something } from "./mod.js"; // Error
>something : any

View file

@ -0,0 +1,20 @@
//// [tests/cases/conformance/externalModules/typeOnly/preserveValueImports_importsNotUsedAsValues.ts] ////
//// [mod.ts]
export type A = unknown;
export type B = never;
export type C = any;
//// [index.ts]
import { type A, type B, type C } from "./mod.js";
//// [reexport.ts]
export { type A, type B, type C } from "./mod.js";
//// [mod.js]
export {};
//// [index.js]
import {} from "./mod.js";
//// [reexport.js]
export {} from "./mod.js";

View file

@ -0,0 +1,22 @@
=== /mod.ts ===
export type A = unknown;
>A : Symbol(A, Decl(mod.ts, 0, 0))
export type B = never;
>B : Symbol(B, Decl(mod.ts, 0, 24))
export type C = any;
>C : Symbol(C, Decl(mod.ts, 1, 22))
=== /index.ts ===
import { type A, type B, type C } from "./mod.js";
>A : Symbol(A, Decl(index.ts, 0, 8))
>B : Symbol(B, Decl(index.ts, 0, 16))
>C : Symbol(C, Decl(index.ts, 0, 24))
=== /reexport.ts ===
export { type A, type B, type C } from "./mod.js";
>A : Symbol(A, Decl(reexport.ts, 0, 8))
>B : Symbol(B, Decl(reexport.ts, 0, 16))
>C : Symbol(C, Decl(reexport.ts, 0, 24))

View file

@ -0,0 +1,22 @@
=== /mod.ts ===
export type A = unknown;
>A : unknown
export type B = never;
>B : never
export type C = any;
>C : any
=== /index.ts ===
import { type A, type B, type C } from "./mod.js";
>A : any
>B : any
>C : any
=== /reexport.ts ===
export { type A, type B, type C } from "./mod.js";
>A : any
>B : any
>C : any

View file

@ -0,0 +1,15 @@
/index.ts(1,21): error TS1444: 'ComponentProps' is a type and must be imported using a type-only import when 'preserveValueImports' and 'isolatedModules' are both enabled.
==== /exports.ts (0 errors) ====
export function Component() {}
export interface ComponentProps {}
==== /index.ts (1 errors) ====
import { Component, ComponentProps } from "./exports.js";
~~~~~~~~~~~~~~
!!! error TS1444: 'ComponentProps' is a type and must be imported using a type-only import when 'preserveValueImports' and 'isolatedModules' are both enabled.
==== /index.fixed.ts (0 errors) ====
import { Component, type ComponentProps } from "./exports.js";

View file

@ -0,0 +1,19 @@
//// [tests/cases/conformance/externalModules/typeOnly/preserveValueImports_mixedImports.ts] ////
//// [exports.ts]
export function Component() {}
export interface ComponentProps {}
//// [index.ts]
import { Component, ComponentProps } from "./exports.js";
//// [index.fixed.ts]
import { Component, type ComponentProps } from "./exports.js";
//// [exports.js]
export function Component() { }
//// [index.js]
import { Component } from "./exports.js";
//// [index.fixed.js]
import { Component } from "./exports.js";

View file

@ -0,0 +1,17 @@
=== /exports.ts ===
export function Component() {}
>Component : Symbol(Component, Decl(exports.ts, 0, 0))
export interface ComponentProps {}
>ComponentProps : Symbol(ComponentProps, Decl(exports.ts, 0, 30))
=== /index.ts ===
import { Component, ComponentProps } from "./exports.js";
>Component : Symbol(Component, Decl(index.ts, 0, 8))
>ComponentProps : Symbol(ComponentProps, Decl(index.ts, 0, 19))
=== /index.fixed.ts ===
import { Component, type ComponentProps } from "./exports.js";
>Component : Symbol(Component, Decl(index.fixed.ts, 0, 8))
>ComponentProps : Symbol(ComponentProps, Decl(index.fixed.ts, 0, 19))

View file

@ -0,0 +1,16 @@
=== /exports.ts ===
export function Component() {}
>Component : () => void
export interface ComponentProps {}
=== /index.ts ===
import { Component, ComponentProps } from "./exports.js";
>Component : () => void
>ComponentProps : any
=== /index.fixed.ts ===
import { Component, type ComponentProps } from "./exports.js";
>Component : () => void
>ComponentProps : any

View file

@ -0,0 +1,21 @@
// @module: esnext
// @declaration: true
// @Filename: /imports.ts
import { type, as, something, foo, bar } from "./exports.js";
type;
as; // Error (used in emitting position)
something; // Error (used in emitting position)
foo; // Error (used in emitting position)
bar; // Error (used in emitting position)
// @Filename: /exports.ts
const type = 0;
const as = 0;
const something = 0;
export { type };
export { type as };
export { type something };
export { type type as foo };
export { type as as bar };
export type { type something as whatever }; // Error

View file

@ -0,0 +1,46 @@
// @module: esnext
// @declaration: true
// @Filename: /mod.ts
export const as = 0;
export const type = 0;
export const something = 0;
// @Filename: /a.ts
import { type } from "./mod.js";
import { type as } from "./mod.js";
type;
as; // Error (used in emitting position)
// @Filename: /b.ts
import { type as as } from "./mod.js";
type; // Error (cannot resolve name)
as;
// @Filename: /c.ts
import { type as as as } from "./mod.js";
type; // Error (cannot resolve name)
as; // Error (used in emitting position)
// @Filename: /d.ts
import { type as as as as } from "./mod.js"; // Error
// @Filename: /e.ts
import { type type as as } from "./mod.js";
import { type as type } from "./mod.js";
type;
as; // Error (used in emitting position)
// @Filename: /f.ts
import { type import } from "./mod.js"; // Error
import { type as export } from "./mod.js"; // Error
import { type as as export } from "./mod.js"; // Error
import { type something } from "./mod.js";
import { type something as s } from "./mod.js";
type; // Error (cannot resolve name)
as; // Error (cannot resolve name)
something; // Error (used in emitting position)
s; // Error (used in emitting position)
// @Filename: /g.ts
import type { type something } from "./mod.js"; // Error

View file

@ -0,0 +1,14 @@
// @preserveValueImports: true
// @importsNotUsedAsValues: preserve
// @module: esnext
// @Filename: /mod.ts
export type A = unknown;
export type B = never;
export type C = any;
// @Filename: /index.ts
import { type A, type B, type C } from "./mod.js";
// @Filename: /reexport.ts
export { type A, type B, type C } from "./mod.js";

View file

@ -0,0 +1,13 @@
// @preserveValueImports: true
// @isolatedModules: true
// @module: es2015
// @Filename: /exports.ts
export function Component() {}
export interface ComponentProps {}
// @Filename: /index.ts
import { Component, ComponentProps } from "./exports.js";
// @Filename: /index.fixed.ts
import { Component, type ComponentProps } from "./exports.js";

View file

@ -13,6 +13,18 @@
goTo.file("a.ts");
verify.completions(
{ marker: "1", exact: [{ name: "x", text: "var x: number" }, { name: "y", text: "var y: number" }] },
{ marker: "2", exact: [{ name: "y", text: "var y: number" }] },
{
marker: "1",
exact: [
{ name: "x", text: "var x: number" },
{ name: "y", text: "var y: number" },
{ name: "type", sortText: completion.SortText.GlobalsOrKeywords }
]
},
{
marker: "2",
exact: [
{ name: "y", text: "var y: number" },
{ name: "type", sortText: completion.SortText.GlobalsOrKeywords }
] },
);

View file

@ -13,4 +13,4 @@
// @Filename: /a.ts
////import { /**/ } from "foo";
verify.completions({ marker: "", exact: ["Static", "foo"] });
verify.completions({ marker: "", exact: ["Static", "foo", { name: "type", sortText: completion.SortText.GlobalsOrKeywords }] });

View file

@ -11,4 +11,4 @@
// @Filename: /a.ts
////import { /**/ } from "foo";
verify.completions({ marker: "", exact: "Static" });
verify.completions({ marker: "", exact: ["Static", { name: "type", sortText: completion.SortText.GlobalsOrKeywords }] });

View file

@ -12,10 +12,12 @@
////export {bar as /*5*/, /*6*/ from "./m1"
////export {foo, bar, baz as b,/*7*/} from "./m1"
const type = { name: "type", sortText: completion.SortText.GlobalsOrKeywords };
verify.completions(
{ marker: ["1", "2", "3"], exact: ["bar", "baz", "foo"] },
{ marker: "4", exact: ["bar", "baz"] },
{ marker: ["1", "2", "3"], exact: ["bar", "baz", "foo", type] },
{ marker: "4", exact: ["bar", "baz", type] },
{ marker: "5", exact: undefined, isNewIdentifierLocation: true },
{ marker: "6", exact: ["baz", "foo"] },
{ marker: "6", exact: ["baz", "foo", type] },
{ marker: "7", exact: undefined },
);

View file

@ -8,4 +8,4 @@
//// export { /**/ } from "M1"
////}
verify.completions({ marker: "", exact: "V" });
verify.completions({ marker: "", exact: ["V", { name: "type", sortText: completion.SortText.GlobalsOrKeywords }] });

View file

@ -10,4 +10,4 @@
////}
// Ensure we don't filter out the current item.
verify.completions({ marker: "", exact: ["abc", "def"] });
verify.completions({ marker: "", exact: ["abc", "def", { name: "type", sortText: completion.SortText.GlobalsOrKeywords }] });

View file

@ -1,4 +1,5 @@
/// <reference path='fourslash.ts'/>
// @ModuleResolution: classic
// @Filename: m1.ts
@ -12,11 +13,16 @@
////import {foo,/*4*/ from "m1"
////import {bar as /*5*/, /*6*/ from "m1"
////import {foo, bar, baz as b,/*7*/} from "m1"
////import { type /*8*/ } from "m1";
////import { type b/*9*/ } from "m1";
const type = { name: "type", sortText: completion.SortText.GlobalsOrKeywords };
verify.completions(
{ marker: ["1", "2", "3"], exact: ["bar", "baz", "foo"] },
{ marker: "4", exact: ["bar", "baz"] },
{ marker: "5", exact: undefined, isNewIdentifierLocation: true },
{ marker: "6", exact: ["baz", "foo"] },
{ marker: "7", exact: undefined },
// { marker: ["1", "2", "3"], exact: ["bar", "baz", "foo", type] },
// { marker: "4", exact: ["bar", "baz", type] },
// { marker: "5", exact: undefined, isNewIdentifierLocation: true },
// { marker: "6", exact: ["baz", "foo", type] },
// { marker: "7", exact: undefined },
{ marker: ["8", "9"], exact: ["bar", "baz", "foo"] }, // No 'type'
);

View file

@ -8,4 +8,4 @@
//// import { /**/ } from "M1"
////}
verify.completions({ marker: "", exact: "V" });
verify.completions({ marker: "", exact: ["V", { name: "type", sortText: completion.SortText.GlobalsOrKeywords }] });

View file

@ -10,4 +10,4 @@
////}
// Ensure we don't filter out the current item.
verify.completions({ marker: "", exact: ["abc", "def"] });
verify.completions({ marker: "", exact: ["abc", "def", { name: "type", sortText: completion.SortText.GlobalsOrKeywords }] });

View file

@ -11,7 +11,7 @@
// @Filename: app.ts
////import {/*1*/} from './foo';
verify.completions({ marker: "1", exact: ["prototype", "prop1", "prop2"] });
verify.completions({ marker: "1", exact: ["prototype", "prop1", "prop2", { name: "type", sortText: completion.SortText.GlobalsOrKeywords }] });
verify.noErrors();
goTo.marker('2');
verify.noErrors();

View file

@ -7,7 +7,7 @@
////}
// @Filename: a.ts
////import { /*1*/ } from "./file.ts"; // no globals in imports - export found
////import { /*1*/ } from "./file.ts"; // no globals in imports - export not found
//@Filename: file.tsx
/////// <reference path="/*2*/..\services\services.ts" /> // no globals in reference paths
@ -38,7 +38,8 @@
const x = ["test", "A", "B", "C", "y", "z", "x", "user"];
const globals: ReadonlyArray<FourSlashInterface.ExpectedCompletionEntry> = [...x, ...completion.globals]
verify.completions(
{ marker: ["1", "3", "6", "8", "12", "14"], exact: undefined, isGlobalCompletion: false },
{ marker: ["1", "3"], exact: [{ name: "type", sortText: completion.SortText.GlobalsOrKeywords }], isNewIdentifierLocation: true, isGlobalCompletion: false },
{ marker: ["6", "8", "12", "14"], exact: undefined, isGlobalCompletion: false },
{ marker: "2", exact: ["a.ts", "file.ts"], isGlobalCompletion: false, isNewIdentifierLocation: true },
{ marker: ["4", "19"], exact: [], isGlobalCompletion: false },
{ marker: ["5", "11"], exact: globals, isGlobalCompletion: true },

View file

@ -4,16 +4,18 @@
////type T = number;
////export { /**/ };
const type = { name: "type", sortText: completion.SortText.GlobalsOrKeywords }
verify.completions({
marker: "",
exact: ["a", "T"]
exact: ["a", "T", type]
});
// Deprioritize 'a' since it has been exported already.
// (Keep it in the list because you can still do 'a as b'.)
edit.insert("a, ");
verify.completions({
exact: [{ name: "a", sortText: completion.SortText.OptionalMember }, "T"]
exact: [{ name: "a", sortText: completion.SortText.OptionalMember }, "T", type]
});
// No completions for new name
@ -25,7 +27,7 @@ verify.completions({
// 'T' still hasn't been exported by name
edit.insert("U, ");
verify.completions({
exact: [{ name: "a", sortText: completion.SortText.OptionalMember }, "T"]
exact: [{ name: "a", sortText: completion.SortText.OptionalMember }, "T", type]
});
// 'a' and 'T' are back to the same priority
@ -33,6 +35,7 @@ edit.insert("T, ");
verify.completions({
exact: [
{ name: "a", sortText: completion.SortText.OptionalMember },
{ name: "T", sortText: completion.SortText.OptionalMember }
{ name: "T", sortText: completion.SortText.OptionalMember },
type,
]
});

View file

@ -8,5 +8,5 @@
verify.completions({
marker: "",
exact: ["topLevel"]
exact: ["topLevel", { name: "type", sortText: completion.SortText.GlobalsOrKeywords }]
});

View file

@ -8,16 +8,18 @@
//// export { /**/ };
////}
const type = { name: "type", sortText: completion.SortText.GlobalsOrKeywords };
verify.completions({
marker: "",
exact: ["a", "T"]
exact: ["a", "T", type]
});
// Deprioritize 'a' since it has been exported already.
// (Keep it in the list because you can still do 'a as b'.)
edit.insert("a, ");
verify.completions({
exact: [{ name: "a", sortText: completion.SortText.OptionalMember }, "T"]
exact: [{ name: "a", sortText: completion.SortText.OptionalMember }, "T", type]
});
// No completions for new name
@ -29,7 +31,7 @@ verify.completions({
// 'T' still hasn't been exported by name
edit.insert("U, ");
verify.completions({
exact: [{ name: "a", sortText: completion.SortText.OptionalMember }, "T"]
exact: [{ name: "a", sortText: completion.SortText.OptionalMember }, "T", type]
});
// 'a' and 'T' are back to the same priority
@ -37,6 +39,7 @@ edit.insert("T, ");
verify.completions({
exact: [
{ name: "a", sortText: completion.SortText.OptionalMember },
{ name: "T", sortText: completion.SortText.OptionalMember }
{ name: "T", sortText: completion.SortText.OptionalMember },
type,
]
});

View file

@ -0,0 +1,28 @@
/// <reference path="fourslash.ts" />
// @preserveValueImports: true
// @isolatedModules: true
// @module: es2015
// @Filename: /exports.ts
//// export default someValue = 0;
//// export function Component() {}
//// export interface ComponentProps {}
// @Filename: /a.ts
//// import { Component } from "./exports.js";
//// interface MoreProps extends /*a*/ComponentProps {}
// @Filename: /b.ts
//// import someValue from "./exports.js";
//// interface MoreProps extends /*b*/ComponentProps {}
goTo.marker("a");
verify.importFixAtPosition([
`import { Component, type ComponentProps } from "./exports.js";
interface MoreProps extends ComponentProps {}`]);
goTo.marker("b");
verify.importFixAtPosition([
`import someValue, { type ComponentProps } from "./exports.js";
interface MoreProps extends ComponentProps {}`]);

View file

@ -0,0 +1,51 @@
/// <reference path="fourslash.ts" />
// @importsNotUsedAsValues: error
// @preserveValueImports: true
// @isolatedModules: true
// @module: es2015
// @Filename: /exports1.ts
//// export default interface SomeType {}
//// export interface OtherType {}
//// export interface OtherOtherType {}
//// export const someValue = 0;
// @Filename: /a.ts
//// import type SomeType from "./exports1.js";
//// someValue/*a*/
// @Filename: /b.ts
//// import { someValue } from "./exports1.js";
//// const b: SomeType/*b*/ = someValue;
// @Filename: /c.ts
//// import type SomeType from "./exports1.js";
//// const x: OtherType/*c*/
// @Filename: /d.ts
//// import type { OtherType } from "./exports1.js";
//// const x: OtherOtherType/*d*/
goTo.marker("a");
verify.importFixAtPosition([
`import type SomeType from "./exports1.js";
import { someValue } from "./exports1.js";
someValue`]);
goTo.marker("b");
verify.importFixAtPosition([
`import type SomeType from "./exports1.js";
import { someValue } from "./exports1.js";
const b: SomeType = someValue;`]);
goTo.marker("c");
verify.importFixAtPosition([
`import type { OtherType } from "./exports1.js";
import type SomeType from "./exports1.js";
const x: OtherType`]);
goTo.marker("d");
verify.importFixAtPosition([
`import type { OtherOtherType, OtherType } from "./exports1.js";
const x: OtherOtherType`]);

View file

@ -0,0 +1,18 @@
/// <reference path="fourslash.ts" />
// @preserveValueImports: true
// @isolatedModules: true
// @module: es2015
// @Filename: /exports.ts
//// class SomeClass {}
//// export type { SomeClass };
// @Filename: /a.ts
//// import {} from "./exports.js";
//// function takeSomeClass(c: SomeClass/**/)
goTo.marker("");
verify.importFixAtPosition([
`import { type SomeClass } from "./exports.js";
function takeSomeClass(c: SomeClass)`]);

View file

@ -0,0 +1,18 @@
/// <reference path="fourslash.ts" />
// @preserveValueImports: true
// @isolatedModules: true
// @module: es2015
// @Filename: /exports.ts
//// export interface SomeInterface {}
//// export class SomePig {}
// @Filename: /a.ts
//// import type { SomeInterface } from "./exports.js";
//// new SomePig/**/
goTo.marker("");
verify.importFixAtPosition([
`import { SomePig, type SomeInterface } from "./exports.js";
new SomePig`]);

View file

@ -22,32 +22,37 @@
// @Filename: /index5.ts
//// import f/*5*/ from "";
[0, 1, 2, 3, 4, 5].forEach(marker => {
verify.completions({
isNewIdentifierLocation: true,
marker: "" + marker,
exact: [{
name: "foo",
source: "./mod",
insertText: `import { foo$1 } from "./mod";`,
isSnippet: true,
replacementSpan: test.ranges()[marker],
sourceDisplay: "./mod",
}, {
name: "Foo",
source: "./mod",
insertText: `import { Foo$1 } from "./mod";`,
isSnippet: true,
replacementSpan: test.ranges()[marker],
sourceDisplay: "./mod",
}],
preferences: {
includeCompletionsForImportStatements: true,
includeInsertTextCompletions: true,
includeCompletionsWithSnippetText: true,
}
});
});
// ([[0, true], [1, true], [2, false], [3, true], [4, true], [5, true]] as const).forEach(([marker, typeKeywordValid]) => {
// verify.completions({
// isNewIdentifierLocation: true,
// marker: "" + marker,
// exact: [{
// name: "foo",
// source: "./mod",
// insertText: `import { foo$1 } from "./mod";`,
// isSnippet: true,
// replacementSpan: test.ranges()[marker],
// sourceDisplay: "./mod",
// },
// {
// name: "Foo",
// source: "./mod",
// insertText: `import { Foo$1 } from "./mod";`,
// isSnippet: true,
// replacementSpan: test.ranges()[marker],
// sourceDisplay: "./mod",
// },
// ...typeKeywordValid ? [{
// name: "type",
// sortText: completion.SortText.GlobalsOrKeywords,
// }] : []],
// preferences: {
// includeCompletionsForImportStatements: true,
// includeInsertTextCompletions: true,
// includeCompletionsWithSnippetText: true,
// }
// });
// });
// @Filename: /index6.ts
//// import f/*6*/ from "nope";
@ -76,7 +81,7 @@
verify.completions({
isNewIdentifierLocation: true,
marker: "" + marker,
exact: [],
exact: [{ name: "type", sortText: completion.SortText.GlobalsOrKeywords }],
preferences: {
includeCompletionsForImportStatements: true,
includeInsertTextCompletions: true,

View file

@ -19,6 +19,9 @@ verify.completions({
isSnippet: true,
replacementSpan: test.ranges()[0],
sourceDisplay: "./mod",
}, {
name: "type",
sortText: completion.SortText.GlobalsOrKeywords,
}],
preferences: {
includeCompletionsForImportStatements: true,

View file

@ -19,6 +19,9 @@ verify.completions({
isSnippet: true,
replacementSpan: test.ranges()[0],
sourceDisplay: "./mod",
}, {
name: "type",
sortText: completion.SortText.GlobalsOrKeywords,
}],
preferences: {
includeCompletionsForImportStatements: true,

View file

@ -12,7 +12,10 @@
verify.completions({
isNewIdentifierLocation: true,
marker: "",
exact: [],
exact: [{
name: "type",
sortText: completion.SortText.GlobalsOrKeywords,
}],
preferences: {
includeCompletionsForImportStatements: true,
includeInsertTextCompletions: true,

View file

@ -16,6 +16,9 @@ verify.completions({
isSnippet: undefined, // <-- undefined
replacementSpan: test.ranges()[0],
sourceDisplay: "./mod",
}, {
name: "type",
sortText: completion.SortText.GlobalsOrKeywords,
}],
preferences: {
includeCompletionsForImportStatements: true,

View file

@ -17,6 +17,9 @@ verify.completions({
isSnippet: true,
replacementSpan: test.ranges()[0],
sourceDisplay: "./mod",
}, {
name: "type",
sortText: completion.SortText.GlobalsOrKeywords,
}],
preferences: {
includeCompletionsForImportStatements: true,

View file

@ -17,6 +17,9 @@ verify.completions({
isSnippet: true,
replacementSpan: test.ranges()[0],
sourceDisplay: "./mod",
}, {
name: "type",
sortText: completion.SortText.GlobalsOrKeywords,
}],
preferences: {
includeCompletionsForImportStatements: true,

View file

@ -19,6 +19,9 @@ verify.completions({
source: "./foo",
insertText: "import * as Foo from \"./foo\";",
replacementSpan: test.ranges()[0]
}, {
name: "type",
sortText: completion.SortText.GlobalsOrKeywords,
}],
isNewIdentifierLocation: true,
preferences: {

View file

@ -0,0 +1,26 @@
/// <reference path="fourslash.ts" />
// @target: esnext
// @filename: /foo.ts
////export interface Foo {}
// @filename: /bar.ts
////[|import { type F/**/ }|]
goTo.file("/bar.ts")
verify.completions({
marker: "",
exact: [{
name: "Foo",
sourceDisplay: "./foo",
source: "./foo",
insertText: "import { type Foo } from \"./foo\";",
replacementSpan: test.ranges()[0]
}],
isNewIdentifierLocation: true,
preferences: {
includeCompletionsForModuleExports: true,
includeCompletionsForImportStatements: true,
includeCompletionsWithInsertText: true
}
});

View file

@ -0,0 +1,26 @@
/// <reference path="fourslash.ts" />
// @target: esnext
// @filename: /foo.ts
////export interface Foo {}
// @filename: /bar.ts
////[|import { type /**/ }|]
goTo.file("/bar.ts")
verify.completions({
marker: "",
exact: [{
name: "Foo",
sourceDisplay: "./foo",
source: "./foo",
insertText: "import { type Foo } from \"./foo\";",
replacementSpan: test.ranges()[0]
}],
isNewIdentifierLocation: true,
preferences: {
includeCompletionsForModuleExports: true,
includeCompletionsForImportStatements: true,
includeCompletionsWithInsertText: true
}
});

View file

@ -22,6 +22,9 @@ verify.completions({
isSnippet: true,
replacementSpan: test.ranges()[0],
sourceDisplay: "react",
}, {
name: "type",
sortText: completion.SortText.GlobalsOrKeywords,
}],
preferences: {
includeCompletionsForImportStatements: true,

View file

@ -20,7 +20,7 @@ goTo.marker("");
verify.completions({
isNewIdentifierLocation: true,
marker: "",
exact: [],
exact: [{ name: "type", sortText: completion.SortText.GlobalsOrKeywords }],
preferences: {
includeCompletionsForImportStatements: true,
includeCompletionsWithInsertText: true,