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:
parent
26aef89a72
commit
e160bc8c0d
|
@ -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);
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -3441,6 +3441,10 @@ namespace ts {
|
|||
}
|
||||
|
||||
function emitImportOrExportSpecifier(node: ImportOrExportSpecifier) {
|
||||
if (node.isTypeOnly) {
|
||||
writeKeyword("type");
|
||||
writeSpace();
|
||||
}
|
||||
if (node.propertyName) {
|
||||
emit(node.propertyName);
|
||||
writeSpace();
|
||||
|
|
|
@ -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)
|
||||
])
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}))
|
||||
));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
||||
//
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -27,6 +27,7 @@ namespace ts.Completions.StringCompletions {
|
|||
completion.symbols,
|
||||
entries,
|
||||
contextToken,
|
||||
contextToken,
|
||||
sourceFile,
|
||||
sourceFile,
|
||||
checker,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -335,4 +335,4 @@ namespace Harness {
|
|||
return { unitName: unitName || this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";`,
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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. */
|
||||
|
|
18
tests/baselines/reference/api/typescript.d.ts
vendored
18
tests/baselines/reference/api/typescript.d.ts
vendored
|
@ -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. */
|
||||
|
|
40
tests/baselines/reference/exportSpecifiers.errors.txt
Normal file
40
tests/baselines/reference/exportSpecifiers.errors.txt
Normal 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.
|
||||
|
48
tests/baselines/reference/exportSpecifiers.js
Normal file
48
tests/baselines/reference/exportSpecifiers.js
Normal 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 {};
|
54
tests/baselines/reference/exportSpecifiers.symbols
Normal file
54
tests/baselines/reference/exportSpecifiers.symbols
Normal 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))
|
||||
|
57
tests/baselines/reference/exportSpecifiers.types
Normal file
57
tests/baselines/reference/exportSpecifiers.types
Normal 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
|
||||
|
108
tests/baselines/reference/importSpecifiers1.errors.txt
Normal file
108
tests/baselines/reference/importSpecifiers1.errors.txt
Normal 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.
|
||||
|
97
tests/baselines/reference/importSpecifiers1.js
Normal file
97
tests/baselines/reference/importSpecifiers1.js
Normal 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 {};
|
93
tests/baselines/reference/importSpecifiers1.symbols
Normal file
93
tests/baselines/reference/importSpecifiers1.symbols
Normal 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))
|
||||
|
104
tests/baselines/reference/importSpecifiers1.types
Normal file
104
tests/baselines/reference/importSpecifiers1.types
Normal 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
|
||||
|
|
@ -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";
|
|
@ -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))
|
||||
|
|
@ -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
|
||||
|
|
@ -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";
|
||||
|
|
@ -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";
|
|
@ -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))
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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";
|
|
@ -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";
|
|
@ -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 }
|
||||
] },
|
||||
);
|
||||
|
|
|
@ -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 }] });
|
||||
|
|
|
@ -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 }] });
|
||||
|
|
|
@ -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 },
|
||||
);
|
||||
|
|
|
@ -8,4 +8,4 @@
|
|||
//// export { /**/ } from "M1"
|
||||
////}
|
||||
|
||||
verify.completions({ marker: "", exact: "V" });
|
||||
verify.completions({ marker: "", exact: ["V", { name: "type", sortText: completion.SortText.GlobalsOrKeywords }] });
|
||||
|
|
|
@ -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 }] });
|
||||
|
|
|
@ -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'
|
||||
);
|
||||
|
|
|
@ -8,4 +8,4 @@
|
|||
//// import { /**/ } from "M1"
|
||||
////}
|
||||
|
||||
verify.completions({ marker: "", exact: "V" });
|
||||
verify.completions({ marker: "", exact: ["V", { name: "type", sortText: completion.SortText.GlobalsOrKeywords }] });
|
||||
|
|
|
@ -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 }] });
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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,
|
||||
]
|
||||
});
|
||||
|
|
|
@ -8,5 +8,5 @@
|
|||
|
||||
verify.completions({
|
||||
marker: "",
|
||||
exact: ["topLevel"]
|
||||
exact: ["topLevel", { name: "type", sortText: completion.SortText.GlobalsOrKeywords }]
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
]
|
||||
});
|
||||
|
|
28
tests/cases/fourslash/importNameCodeFix_importType1.ts
Normal file
28
tests/cases/fourslash/importNameCodeFix_importType1.ts
Normal 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 {}`]);
|
51
tests/cases/fourslash/importNameCodeFix_importType2.ts
Normal file
51
tests/cases/fourslash/importNameCodeFix_importType2.ts
Normal 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`]);
|
18
tests/cases/fourslash/importNameCodeFix_importType3.ts
Normal file
18
tests/cases/fourslash/importNameCodeFix_importType3.ts
Normal 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)`]);
|
18
tests/cases/fourslash/importNameCodeFix_importType4.ts
Normal file
18
tests/cases/fourslash/importNameCodeFix_importType4.ts
Normal 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`]);
|
|
@ -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,
|
||||
|
|
|
@ -19,6 +19,9 @@ verify.completions({
|
|||
isSnippet: true,
|
||||
replacementSpan: test.ranges()[0],
|
||||
sourceDisplay: "./mod",
|
||||
}, {
|
||||
name: "type",
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
}],
|
||||
preferences: {
|
||||
includeCompletionsForImportStatements: true,
|
||||
|
|
|
@ -19,6 +19,9 @@ verify.completions({
|
|||
isSnippet: true,
|
||||
replacementSpan: test.ranges()[0],
|
||||
sourceDisplay: "./mod",
|
||||
}, {
|
||||
name: "type",
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
}],
|
||||
preferences: {
|
||||
includeCompletionsForImportStatements: true,
|
||||
|
|
|
@ -12,7 +12,10 @@
|
|||
verify.completions({
|
||||
isNewIdentifierLocation: true,
|
||||
marker: "",
|
||||
exact: [],
|
||||
exact: [{
|
||||
name: "type",
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
}],
|
||||
preferences: {
|
||||
includeCompletionsForImportStatements: true,
|
||||
includeInsertTextCompletions: true,
|
||||
|
|
|
@ -16,6 +16,9 @@ verify.completions({
|
|||
isSnippet: undefined, // <-- undefined
|
||||
replacementSpan: test.ranges()[0],
|
||||
sourceDisplay: "./mod",
|
||||
}, {
|
||||
name: "type",
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
}],
|
||||
preferences: {
|
||||
includeCompletionsForImportStatements: true,
|
||||
|
|
|
@ -17,6 +17,9 @@ verify.completions({
|
|||
isSnippet: true,
|
||||
replacementSpan: test.ranges()[0],
|
||||
sourceDisplay: "./mod",
|
||||
}, {
|
||||
name: "type",
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
}],
|
||||
preferences: {
|
||||
includeCompletionsForImportStatements: true,
|
||||
|
|
|
@ -17,6 +17,9 @@ verify.completions({
|
|||
isSnippet: true,
|
||||
replacementSpan: test.ranges()[0],
|
||||
sourceDisplay: "./mod",
|
||||
}, {
|
||||
name: "type",
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
}],
|
||||
preferences: {
|
||||
includeCompletionsForImportStatements: true,
|
||||
|
|
|
@ -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: {
|
||||
|
|
26
tests/cases/fourslash/importTypeCompletions8.ts
Normal file
26
tests/cases/fourslash/importTypeCompletions8.ts
Normal 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
|
||||
}
|
||||
});
|
26
tests/cases/fourslash/importTypeCompletions9.ts
Normal file
26
tests/cases/fourslash/importTypeCompletions9.ts
Normal 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
|
||||
}
|
||||
});
|
|
@ -22,6 +22,9 @@ verify.completions({
|
|||
isSnippet: true,
|
||||
replacementSpan: test.ranges()[0],
|
||||
sourceDisplay: "react",
|
||||
}, {
|
||||
name: "type",
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
}],
|
||||
preferences: {
|
||||
includeCompletionsForImportStatements: true,
|
||||
|
|
|
@ -20,7 +20,7 @@ goTo.marker("");
|
|||
verify.completions({
|
||||
isNewIdentifierLocation: true,
|
||||
marker: "",
|
||||
exact: [],
|
||||
exact: [{ name: "type", sortText: completion.SortText.GlobalsOrKeywords }],
|
||||
preferences: {
|
||||
includeCompletionsForImportStatements: true,
|
||||
includeCompletionsWithInsertText: true,
|
||||
|
|
Loading…
Reference in a new issue