Add verbatimModuleSyntax, deprecate importsNotUsedAsValues and preserveValueImports (#52203)

This commit is contained in:
Andrew Branch
2023-01-19 16:56:30 -08:00
committed by GitHub
parent ebbc56d0b3
commit cfe1842adf
108 changed files with 2882 additions and 377 deletions
+140 -59
View File
@@ -7,6 +7,7 @@ import {
addRange,
addRelatedInfo,
addSyntheticLeadingComment,
AliasDeclarationNode,
AllAccessorDeclarations,
AmbientModuleDeclaration,
and,
@@ -283,6 +284,7 @@ import {
getInitializerOfBinaryExpression,
getInterfaceBaseTypeNodes,
getInvokedExpression,
getIsolatedModules,
getJSDocClassTag,
getJSDocDeprecatedTag,
getJSDocEnumTag,
@@ -441,6 +443,7 @@ import {
isBindableStaticElementAccessExpression,
isBindableStaticNameExpression,
isBindingElement,
isBindingElementOfBareOrAccessedRequire,
isBindingPattern,
isBlock,
isBlockOrCatchScoped,
@@ -1451,6 +1454,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments" as __String);
const requireSymbol = createSymbol(SymbolFlags.Property, "require" as __String);
const isolatedModulesLikeFlagName = compilerOptions.verbatimModuleSyntax ? "verbatimModuleSyntax" : "isolatedModules";
/** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */
let apparentArgumentCount: number | undefined;
@@ -2310,7 +2314,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
addErrorOrSuggestion(isError, "message" in message ? createFileDiagnostic(file, 0, 0, message, arg0, arg1, arg2, arg3) : createDiagnosticForFileFromMessageChain(file, message)); // eslint-disable-line local/no-in-operator
return;
}
addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) : createDiagnosticForNodeFromMessageChain(location, message)); // eslint-disable-line local/no-in-operator
addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) : createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(location), location, message)); // eslint-disable-line local/no-in-operator
}
function errorAndMaybeSuggestAwait(
@@ -3091,6 +3095,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
break;
case SyntaxKind.EnumDeclaration:
if (result = lookup(getSymbolOfDeclaration(location as EnumDeclaration)?.exports || emptySymbols, name, meaning & SymbolFlags.EnumMember)) {
if (nameNotFoundMessage && getIsolatedModules(compilerOptions) && !(location.flags & NodeFlags.Ambient) && getSourceFileOfNode(location) !== getSourceFileOfNode(result.valueDeclaration)) {
error(
errorLocation,
Diagnostics.Cannot_access_0_from_another_file_without_qualification_when_1_is_enabled_Use_2_instead,
unescapeLeadingUnderscores(name),
isolatedModulesLikeFlagName,
`${unescapeLeadingUnderscores(getSymbolOfNode(location)!.escapedName)}.${unescapeLeadingUnderscores(name)}`);
}
break loop;
}
break;
@@ -4474,6 +4486,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function markExportAsReferenced(node: ImportEqualsDeclaration | ExportSpecifier) {
if (compilerOptions.verbatimModuleSyntax) {
return;
}
const symbol = getSymbolOfDeclaration(node);
const target = resolveAlias(symbol);
if (target) {
@@ -4490,6 +4505,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// we reach a non-alias or an exported entity (which is always considered referenced). We do this by checking the target of
// the alias as an expression (which recursively takes us back here if the target references another alias).
function markAliasSymbolAsReferenced(symbol: Symbol) {
Debug.assert(!compilerOptions.verbatimModuleSyntax);
const links = getSymbolLinks(symbol);
if (!links.referenced) {
links.referenced = true;
@@ -4862,7 +4878,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
}
diagnostics.add(createDiagnosticForNodeFromMessageChain(errorNode, chainDiagnosticMessages(
diagnostics.add(createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(errorNode), errorNode, chainDiagnosticMessages(
diagnosticDetails,
Diagnostics.The_current_file_is_a_CommonJS_module_whose_imports_will_produce_require_calls_however_the_referenced_file_is_an_ECMAScript_module_and_cannot_be_imported_with_require_Consider_writing_a_dynamic_import_0_call_instead,
moduleReference)));
@@ -11651,7 +11667,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!isValidBaseType(reducedBaseType)) {
const elaboration = elaborateNeverIntersection(/*errorInfo*/ undefined, baseType);
const diagnostic = chainDiagnosticMessages(elaboration, Diagnostics.Base_constructor_return_type_0_is_not_an_object_type_or_intersection_of_object_types_with_statically_known_members, typeToString(reducedBaseType));
diagnostics.add(createDiagnosticForNodeFromMessageChain(baseTypeNode.expression, diagnostic));
diagnostics.add(createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(baseTypeNode.expression), baseTypeNode.expression, diagnostic));
return type.resolvedBaseTypes = emptyArray;
}
if (type === reducedBaseType || hasBaseType(reducedBaseType, type)) {
@@ -17110,7 +17126,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
errorInfo,
Diagnostics.Element_implicitly_has_an_any_type_because_expression_of_type_0_can_t_be_used_to_index_type_1, typeToString(fullIndexType), typeToString(objectType)
);
diagnostics.add(createDiagnosticForNodeFromMessageChain(accessExpression, errorInfo));
diagnostics.add(createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(accessExpression), accessExpression, errorInfo));
}
}
}
@@ -19954,7 +19970,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
}
const diag = createDiagnosticForNodeFromMessageChain(errorNode!, errorInfo, relatedInformation);
const diag = createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(errorNode!), errorNode!, errorInfo, relatedInformation);
if (relatedInfo) {
addRelatedInfo(diag, ...relatedInfo);
}
@@ -27279,13 +27295,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function markAliasReferenced(symbol: Symbol, location: Node) {
if (compilerOptions.verbatimModuleSyntax) {
return;
}
if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location) && !getTypeOnlyAliasDeclaration(symbol, SymbolFlags.Value)) {
const target = resolveAlias(symbol);
if (getAllSymbolFlags(target) & (SymbolFlags.Value | SymbolFlags.ExportValue)) {
// An alias resolving to a const enum cannot be elided if (1) 'isolatedModules' is enabled
// (because the const enum value will not be inlined), or if (2) the alias is an export
// of a const enum declaration that will be preserved.
if (compilerOptions.isolatedModules ||
if (getIsolatedModules(compilerOptions) ||
shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location) ||
!isConstEnumOrConstEnumOnlyModule(getExportSymbolOfValueSymbolIfExported(target))
) {
@@ -30366,7 +30385,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
jsxFactorySym.isReferenced = SymbolFlags.All;
// If react/jsxFactory symbol is alias, mark it as refereced
if (jsxFactorySym.flags & SymbolFlags.Alias && !getTypeOnlyAliasDeclaration(jsxFactorySym)) {
if (!compilerOptions.verbatimModuleSyntax && jsxFactorySym.flags & SymbolFlags.Alias && !getTypeOnlyAliasDeclaration(jsxFactorySym)) {
markAliasSymbolAsReferenced(jsxFactorySym);
}
}
@@ -30937,7 +30956,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// 1. if 'isolatedModules' is enabled, because the const enum value will not be inlined, and
// 2. if 'preserveConstEnums' is enabled and the expression is itself an export, e.g. `export = Foo.Bar.Baz`.
if (isIdentifier(left) && parentSymbol && (
compilerOptions.isolatedModules ||
getIsolatedModules(compilerOptions) ||
!(prop && (isConstEnumOrConstEnumOnlyModule(prop) || prop.flags & SymbolFlags.EnumMember && node.parent.kind === SyntaxKind.EnumMember)) ||
shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(node)
)) {
@@ -31202,7 +31221,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
}
const resultDiagnostic = createDiagnosticForNodeFromMessageChain(propNode, errorInfo);
const resultDiagnostic = createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(propNode), propNode, errorInfo);
if (relatedInfo) {
addRelatedInfo(resultDiagnostic, relatedInfo);
}
@@ -32337,7 +32356,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if ("message" in message) { // eslint-disable-line local/no-in-operator
return createDiagnosticForNode(node, message, arg0, arg1, arg2, arg3);
}
return createDiagnosticForNodeFromMessageChain(node, message);
return createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(node), node, message);
}
}
@@ -32667,7 +32686,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
diag = { file, start, length, code: chain.code, category: chain.category, messageText: chain, relatedInformation: related };
}
else {
diag = createDiagnosticForNodeFromMessageChain(node, chain, related);
diag = createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(node), node, chain, related);
}
addImplementationSuccessElaboration(candidatesForArgumentError[0], diag);
diagnostics.add(diag);
@@ -33296,7 +33315,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function invocationError(errorTarget: Node, apparentType: Type, kind: SignatureKind, relatedInformation?: DiagnosticRelatedInformation) {
const { messageChain, relatedMessage: relatedInfo } = invocationErrorDetails(errorTarget, apparentType, kind);
const diagnostic = createDiagnosticForNodeFromMessageChain(errorTarget, messageChain);
const diagnostic = createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(errorTarget), errorTarget, messageChain);
if (relatedInfo) {
addRelatedInfo(diagnostic, createDiagnosticForNode(errorTarget, relatedInfo));
}
@@ -33407,7 +33426,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!callSignatures.length) {
const errorDetails = invocationErrorDetails(node.expression, apparentType, SignatureKind.Call);
const messageChain = chainDiagnosticMessages(errorDetails.messageChain, headMessage);
const diag = createDiagnosticForNodeFromMessageChain(node.expression, messageChain);
const diag = createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(node.expression), node.expression, messageChain);
if (errorDetails.relatedMessage) {
addRelatedInfo(diag, createDiagnosticForNode(node.expression, errorDetails.relatedMessage));
}
@@ -37151,11 +37170,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
error(node, Diagnostics.const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment_or_type_query);
}
if (compilerOptions.isolatedModules) {
if (getIsolatedModules(compilerOptions)) {
Debug.assert(!!(type.symbol.flags & SymbolFlags.ConstEnum));
const constEnumDeclaration = type.symbol.valueDeclaration as EnumDeclaration;
if (constEnumDeclaration.flags & NodeFlags.Ambient) {
error(node, Diagnostics.Cannot_access_ambient_const_enums_when_the_isolatedModules_flag_is_provided);
error(node, Diagnostics.Cannot_access_ambient_const_enums_when_0_is_enabled, isolatedModulesLikeFlagName);
}
}
}
@@ -39031,7 +39050,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
chain = chainDiagnosticMessages(chain, Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1, typeToString(type), typeToString(thisTypeForErrorOut.value));
}
chain = chainDiagnosticMessages(chain, diagnosticMessage, arg0);
diagnostics.add(createDiagnosticForNodeFromMessageChain(errorNode, chain));
diagnostics.add(createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(errorNode), errorNode, chain));
}
return undefined;
}
@@ -39243,13 +39262,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const meaning = (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias;
const rootSymbol = resolveName(rootName, rootName.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isReference*/ true);
if (rootSymbol && rootSymbol.flags & SymbolFlags.Alias) {
if (symbolIsValue(rootSymbol)
if (!compilerOptions.verbatimModuleSyntax
&& symbolIsValue(rootSymbol)
&& !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol))
&& !getTypeOnlyAliasDeclaration(rootSymbol)) {
markAliasSymbolAsReferenced(rootSymbol);
}
else if (forDecoratorMetadata
&& compilerOptions.isolatedModules
&& getIsolatedModules(compilerOptions)
&& getEmitModuleKind(compilerOptions) >= ModuleKind.ES2015
&& !symbolIsValue(rootSymbol)
&& !some(rootSymbol.declarations, isTypeOnlyImportOrExportDeclaration)) {
@@ -40354,8 +40374,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
// For a commonjs `const x = require`, validate the alias and exit
const symbol = getSymbolOfDeclaration(node);
if (symbol.flags & SymbolFlags.Alias && isVariableDeclarationInitializedToBareOrAccessedRequire(node.kind === SyntaxKind.BindingElement ? node.parent.parent : node)) {
checkAliasSymbol(node as BindingElement | VariableDeclaration);
if (symbol.flags & SymbolFlags.Alias && (isVariableDeclarationInitializedToBareOrAccessedRequire(node) || isBindingElementOfBareOrAccessedRequire(node))) {
checkAliasSymbol(node);
return;
}
@@ -42761,7 +42781,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
let errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Named_property_0_of_types_1_and_2_are_not_identical, symbolToString(prop), typeName1, typeName2);
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Interface_0_cannot_simultaneously_extend_types_1_and_2, typeToString(type), typeName1, typeName2);
diagnostics.add(createDiagnosticForNodeFromMessageChain(typeNode, errorInfo));
diagnostics.add(createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(typeNode), typeNode, errorInfo));
}
}
}
@@ -43202,25 +43222,38 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// The following checks only apply on a non-ambient instantiated module declaration.
if (symbol.flags & SymbolFlags.ValueModule
&& !inAmbientContext
&& symbol.declarations
&& symbol.declarations.length > 1
&& isInstantiatedModule(node, shouldPreserveConstEnums(compilerOptions))) {
const firstNonAmbientClassOrFunc = getFirstNonAmbientClassOrFunctionDeclaration(symbol);
if (firstNonAmbientClassOrFunc) {
if (getSourceFileOfNode(node) !== getSourceFileOfNode(firstNonAmbientClassOrFunc)) {
error(node.name, Diagnostics.A_namespace_declaration_cannot_be_in_a_different_file_from_a_class_or_function_with_which_it_is_merged);
&& isInstantiatedModule(node, shouldPreserveConstEnums(compilerOptions))
) {
if (getIsolatedModules(compilerOptions) && !getSourceFileOfNode(node).externalModuleIndicator) {
// This could be loosened a little if needed. The only problem we are trying to avoid is unqualified
// references to namespace members declared in other files. But use of namespaces is discouraged anyway,
// so for now we will just not allow them in scripts, which is the only place they can merge cross-file.
error(node.name, Diagnostics.Namespaces_are_not_allowed_in_global_script_files_when_0_is_enabled_If_this_file_is_not_intended_to_be_a_global_script_set_moduleDetection_to_force_or_add_an_empty_export_statement, isolatedModulesLikeFlagName);
}
if (symbol.declarations?.length! > 1) {
const firstNonAmbientClassOrFunc = getFirstNonAmbientClassOrFunctionDeclaration(symbol);
if (firstNonAmbientClassOrFunc) {
if (getSourceFileOfNode(node) !== getSourceFileOfNode(firstNonAmbientClassOrFunc)) {
error(node.name, Diagnostics.A_namespace_declaration_cannot_be_in_a_different_file_from_a_class_or_function_with_which_it_is_merged);
}
else if (node.pos < firstNonAmbientClassOrFunc.pos) {
error(node.name, Diagnostics.A_namespace_declaration_cannot_be_located_prior_to_a_class_or_function_with_which_it_is_merged);
}
}
else if (node.pos < firstNonAmbientClassOrFunc.pos) {
error(node.name, Diagnostics.A_namespace_declaration_cannot_be_located_prior_to_a_class_or_function_with_which_it_is_merged);
// if the module merges with a class declaration in the same lexical scope,
// we need to track this to ensure the correct emit.
const mergedClass = getDeclarationOfKind(symbol, SyntaxKind.ClassDeclaration);
if (mergedClass &&
inSameLexicalScope(node, mergedClass)) {
getNodeLinks(node).flags |= NodeCheckFlags.LexicalModuleMergesWithClass;
}
}
// if the module merges with a class declaration in the same lexical scope,
// we need to track this to ensure the correct emit.
const mergedClass = getDeclarationOfKind(symbol, SyntaxKind.ClassDeclaration);
if (mergedClass &&
inSameLexicalScope(node, mergedClass)) {
getNodeLinks(node).flags |= NodeCheckFlags.LexicalModuleMergesWithClass;
if (compilerOptions.verbatimModuleSyntax && node.parent.kind === SyntaxKind.SourceFile) {
const exportModifier = node.modifiers?.find(m => m.kind === SyntaxKind.ExportKeyword);
if (exportModifier) {
error(exportModifier, Diagnostics.A_top_level_export_modifier_cannot_be_used_on_value_declarations_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled);
}
}
}
@@ -43362,7 +43395,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return true;
}
function checkAliasSymbol(node: ImportEqualsDeclaration | VariableDeclaration | ImportClause | NamespaceImport | ImportSpecifier | ExportSpecifier | NamespaceExport | BindingElement) {
function checkAliasSymbol(node: AliasDeclarationNode) {
let symbol = getSymbolOfDeclaration(node);
const target = resolveAlias(symbol);
@@ -43422,7 +43455,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
error(node, message, symbolToString(symbol));
}
if (compilerOptions.isolatedModules
if (getIsolatedModules(compilerOptions)
&& !isTypeOnlyImportOrExportDeclaration(node)
&& !(node.flags & NodeFlags.Ambient)) {
const typeOnlyAlias = getTypeOnlyAliasDeclaration(symbol);
@@ -43432,11 +43465,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
case SyntaxKind.ImportClause:
case SyntaxKind.ImportSpecifier:
case SyntaxKind.ImportEqualsDeclaration: {
if (compilerOptions.preserveValueImports) {
if (compilerOptions.preserveValueImports || compilerOptions.verbatimModuleSyntax) {
Debug.assertIsDefined(node.name, "An ImportClause with a symbol should have a name");
const message = isType
? Diagnostics._0_is_a_type_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled
: Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled;
const message = compilerOptions.verbatimModuleSyntax && isInternalModuleImportEqualsDeclaration(node)
? Diagnostics.An_import_alias_cannot_resolve_to_a_type_or_type_only_declaration_when_verbatimModuleSyntax_is_enabled
: isType
? compilerOptions.verbatimModuleSyntax
? Diagnostics._0_is_a_type_and_must_be_imported_using_a_type_only_import_when_verbatimModuleSyntax_is_enabled
: Diagnostics._0_is_a_type_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled
: compilerOptions.verbatimModuleSyntax
? Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_imported_using_a_type_only_import_when_verbatimModuleSyntax_is_enabled
: Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled;
const name = idText(node.kind === SyntaxKind.ImportSpecifier ? node.propertyName || node.name : node.name);
addTypeOnlyDeclarationRelatedInfo(
error(node, message, name),
@@ -43445,7 +43484,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
);
}
if (isType && node.kind === SyntaxKind.ImportEqualsDeclaration && hasEffectiveModifier(node, ModifierFlags.Export)) {
error(node, Diagnostics.Cannot_use_export_import_on_a_type_or_type_only_namespace_when_the_isolatedModules_flag_is_provided);
error(node, Diagnostics.Cannot_use_export_import_on_a_type_or_type_only_namespace_when_0_is_enabled, isolatedModulesLikeFlagName);
}
break;
}
@@ -43453,21 +43492,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// Don't allow re-exporting an export that will be elided when `--isolatedModules` is set.
// The exception is that `import type { A } from './a'; export { A }` is allowed
// because single-file analysis can determine that the export should be dropped.
if (getSourceFileOfNode(typeOnlyAlias) !== getSourceFileOfNode(node)) {
const message = isType
? Diagnostics.Re_exporting_a_type_when_the_isolatedModules_flag_is_provided_requires_using_export_type
: Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_re_exported_using_a_type_only_re_export_when_isolatedModules_is_enabled;
if (compilerOptions.verbatimModuleSyntax || getSourceFileOfNode(typeOnlyAlias) !== getSourceFileOfNode(node)) {
const name = idText(node.propertyName || node.name);
addTypeOnlyDeclarationRelatedInfo(
error(node, message, name),
isType ? undefined : typeOnlyAlias,
name
);
return;
const diagnostic = isType
? error(node, Diagnostics.Re_exporting_a_type_when_0_is_enabled_requires_using_export_type, isolatedModulesLikeFlagName)
: error(node, Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_re_exported_using_a_type_only_re_export_when_1_is_enabled, name, isolatedModulesLikeFlagName);
addTypeOnlyDeclarationRelatedInfo(diagnostic, isType ? undefined : typeOnlyAlias, name);
break;
}
}
}
}
if (compilerOptions.verbatimModuleSyntax &&
node.kind !== SyntaxKind.ImportEqualsDeclaration &&
!isInJSFile(node) &&
(moduleKind === ModuleKind.CommonJS || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)
) {
error(node, Diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled);
}
}
if (isImportSpecifier(node)) {
@@ -43811,30 +43854,51 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
checkTypeAssignableTo(checkExpressionCached(node.expression), getTypeFromTypeNode(typeAnnotationNode), node.expression);
}
const isIllegalExportDefaultInCJS = !node.isExportEquals &&
compilerOptions.verbatimModuleSyntax &&
(moduleKind === ModuleKind.CommonJS || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS);
if (node.expression.kind === SyntaxKind.Identifier) {
const id = node.expression as Identifier;
const sym = resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, node);
if (sym) {
markAliasReferenced(sym, id);
// If not a value, we're interpreting the identifier as a type export, along the lines of (`export { Id as default }`)
const target = sym.flags & SymbolFlags.Alias ? resolveAlias(sym) : sym;
if (getAllSymbolFlags(target) & SymbolFlags.Value) {
if (getAllSymbolFlags(sym) & SymbolFlags.Value) {
// However if it is a value, we need to check it's being used correctly
checkExpressionCached(node.expression);
checkExpressionCached(id);
if (!isIllegalExportDefaultInCJS && compilerOptions.verbatimModuleSyntax && getTypeOnlyAliasDeclaration(sym, SymbolFlags.Value)) {
error(id,
node.isExportEquals
? Diagnostics.An_export_declaration_must_reference_a_real_value_when_verbatimModuleSyntax_is_enabled_but_0_resolves_to_a_type_only_declaration
: Diagnostics.An_export_default_must_reference_a_real_value_when_verbatimModuleSyntax_is_enabled_but_0_resolves_to_a_type_only_declaration,
idText(id));
}
}
else if (!isIllegalExportDefaultInCJS && compilerOptions.verbatimModuleSyntax) {
error(id,
node.isExportEquals
? Diagnostics.An_export_declaration_must_reference_a_value_when_verbatimModuleSyntax_is_enabled_but_0_only_refers_to_a_type
: Diagnostics.An_export_default_must_reference_a_value_when_verbatimModuleSyntax_is_enabled_but_0_only_refers_to_a_type,
idText(id));
}
}
else {
checkExpressionCached(node.expression); // doesn't resolve, check as expression to mark as error
checkExpressionCached(id); // doesn't resolve, check as expression to mark as error
}
if (getEmitDeclarations(compilerOptions)) {
collectLinkedAliases(node.expression as Identifier, /*setVisibility*/ true);
collectLinkedAliases(id, /*setVisibility*/ true);
}
}
else {
checkExpressionCached(node.expression);
}
if (isIllegalExportDefaultInCJS) {
error(node, Diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled);
}
checkExternalModuleExports(container);
if ((node.flags & NodeFlags.Ambient) && !isEntityNameExpression(node.expression)) {
@@ -45423,6 +45487,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function isValueAliasDeclaration(node: Node): boolean {
Debug.assert(!compilerOptions.verbatimModuleSyntax);
switch (node.kind) {
case SyntaxKind.ImportEqualsDeclaration:
return isAliasResolvedToValue(getSymbolOfDeclaration(node as ImportEqualsDeclaration));
@@ -45476,6 +45541,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean {
Debug.assert(!compilerOptions.verbatimModuleSyntax);
if (isAliasSymbolDeclaration(node)) {
const symbol = getSymbolOfDeclaration(node as Declaration);
const links = symbol && getSymbolLinks(symbol);
@@ -46461,6 +46527,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
break;
case SyntaxKind.ExportKeyword:
if (compilerOptions.verbatimModuleSyntax &&
!(node.flags & NodeFlags.Ambient) &&
node.kind !== SyntaxKind.TypeAliasDeclaration &&
node.kind !== SyntaxKind.InterfaceDeclaration &&
// ModuleDeclaration needs to be checked that it is uninstantiated later
node.kind !== SyntaxKind.ModuleDeclaration &&
node.parent.kind === SyntaxKind.SourceFile &&
(moduleKind === ModuleKind.CommonJS || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)
) {
return grammarErrorOnNode(modifier, Diagnostics.A_top_level_export_modifier_cannot_be_used_on_value_declarations_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled);
}
if (flags & ModifierFlags.Export) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "export");
}
@@ -47983,6 +48060,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function checkGrammarImportCallExpression(node: ImportCall): boolean {
if (compilerOptions.verbatimModuleSyntax && moduleKind === ModuleKind.CommonJS) {
return grammarErrorOnNode(node, Diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled);
}
if (moduleKind === ModuleKind.ES2015) {
return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_are_only_supported_when_the_module_flag_is_set_to_es2020_es2022_esnext_commonjs_amd_system_umd_node16_or_nodenext);
}
+7
View File
@@ -789,6 +789,13 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
transpileOptionValue: true,
defaultValueDescription: false,
},
{
name: "verbatimModuleSyntax",
type: "boolean",
category: Diagnostics.Interop_Constraints,
description: Diagnostics.Do_not_transform_or_elide_any_imports_or_exports_not_marked_as_type_only_ensuring_they_are_written_in_the_output_file_s_format_based_on_the_module_setting,
defaultValueDescription: false,
},
// Strict Type Checks
{
+65 -9
View File
@@ -635,7 +635,7 @@
"category": "Error",
"code": 1203
},
"Re-exporting a type when the '--isolatedModules' flag is provided requires using 'export type'.": {
"Re-exporting a type when '{0}' is enabled requires using 'export type'.": {
"category": "Error",
"code": 1205
},
@@ -647,10 +647,6 @@
"category": "Error",
"code": 1207
},
"'{0}' cannot be compiled under '--isolatedModules' because it is considered a global script file. Add an import, export, or an empty 'export {}' statement to make it a module.": {
"category": "Error",
"code": 1208
},
"Invalid optional chain from new expression. Did you mean to call '{0}()'?": {
"category": "Error",
"code": 1209
@@ -871,7 +867,7 @@
"category": "Error",
"code": 1268
},
"Cannot use 'export import' on a type or type-only namespace when the '--isolatedModules' flag is provided.": {
"Cannot use 'export import' on a type or type-only namespace when '{0}' is enabled.": {
"category": "Error",
"code": 1269
},
@@ -915,6 +911,42 @@
"category": "Error",
"code": 1279
},
"Namespaces are not allowed in global script files when '{0}' is enabled. If this file is not intended to be a global script, set 'moduleDetection' to 'force' or add an empty 'export {}' statement.": {
"category": "Error",
"code": 1280
},
"Cannot access '{0}' from another file without qualification when '{1}' is enabled. Use '{2}' instead.": {
"category": "Error",
"code": 1281
},
"An 'export =' declaration must reference a value when 'verbatimModuleSyntax' is enabled, but '{0}' only refers to a type.": {
"category": "Error",
"code": 1282
},
"An 'export =' declaration must reference a real value when 'verbatimModuleSyntax' is enabled, but '{0}' resolves to a type-only declaration.": {
"category": "Error",
"code": 1283
},
"An 'export default' must reference a value when 'verbatimModuleSyntax' is enabled, but '{0}' only refers to a type.": {
"category": "Error",
"code": 1284
},
"An 'export default' must reference a real value when 'verbatimModuleSyntax' is enabled, but '{0}' resolves to a type-only declaration.": {
"category": "Error",
"code": 1285
},
"ESM syntax is not allowed in a CommonJS module when 'verbatimModuleSyntax' is enabled.": {
"category": "Error",
"code": 1286
},
"A top-level 'export' modifier cannot be used on value declarations in a CommonJS module when 'verbatimModuleSyntax' is enabled.": {
"category": "Error",
"code": 1287
},
"An import alias cannot resolve to a type or type-only declaration when 'verbatimModuleSyntax' is enabled.": {
"category": "Error",
"code": 1288
},
"'with' statements are not allowed in an async function block.": {
"category": "Error",
@@ -1444,7 +1476,7 @@
"category": "Error",
"code": 1446
},
"'{0}' resolves to a type-only declaration and must be re-exported using a type-only re-export when 'isolatedModules' is enabled.": {
"'{0}' resolves to a type-only declaration and must be re-exported using a type-only re-export when '{1}' is enabled.": {
"category": "Error",
"code": 1448
},
@@ -1557,6 +1589,14 @@
"category": "Message",
"code": 1483
},
"'{0}' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.": {
"category": "Error",
"code": 1484
},
"'{0}' resolves to a type-only declaration and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.": {
"category": "Error",
"code": 1485
},
"The types of '{0}' are incompatible between these types.": {
"category": "Error",
@@ -3223,7 +3263,7 @@
"category": "Error",
"code": 2747
},
"Cannot access ambient const enums when the '--isolatedModules' flag is provided.": {
"Cannot access ambient const enums when '{0}' is enabled.": {
"category": "Error",
"code": 2748
},
@@ -4217,7 +4257,7 @@
"category": "Error",
"code": 5090
},
"Option 'preserveConstEnums' cannot be disabled when 'isolatedModules' is enabled.": {
"Option 'preserveConstEnums' cannot be disabled when '{0}' is enabled.": {
"category": "Error",
"code": 5091
},
@@ -4269,6 +4309,18 @@
"category": "Error",
"code": 5103
},
"Option '{0}' is redundant and cannot be specified with option '{1}'.": {
"category": "Error",
"code": 5104
},
"Option 'verbatimModuleSyntax' cannot be used when 'module' is set to 'UMD', 'AMD', or 'System'.": {
"category": "Error",
"code": 5105
},
"Use '{0}' instead.": {
"category": "Message",
"code": 5106
},
"Generates a sourcemap for each corresponding '.d.ts' file.": {
"category": "Message",
@@ -6003,6 +6055,10 @@
"category": "Message",
"code": 6803
},
"Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting.": {
"category": "Message",
"code": 6804
},
"one of:": {
"category": "Message",
+86 -20
View File
@@ -35,6 +35,7 @@ import {
createCompilerDiagnostic,
createCompilerDiagnosticFromMessageChain,
createDiagnosticCollection,
createDiagnosticForNodeFromMessageChain,
createDiagnosticForNodeInSourceFile,
createDiagnosticForRange,
createFileDiagnostic,
@@ -4117,21 +4118,13 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
const languageVersion = getEmitScriptTarget(options);
const firstNonAmbientExternalModuleSourceFile = find(files, f => isExternalModule(f) && !f.isDeclarationFile);
if (options.isolatedModules) {
if (options.module === ModuleKind.None && languageVersion < ScriptTarget.ES2015) {
if (options.isolatedModules || options.verbatimModuleSyntax) {
if (options.module === ModuleKind.None && languageVersion < ScriptTarget.ES2015 && options.isolatedModules) {
createDiagnosticForOptionName(Diagnostics.Option_isolatedModules_can_only_be_used_when_either_option_module_is_provided_or_option_target_is_ES2015_or_higher, "isolatedModules", "target");
}
if (options.preserveConstEnums === false) {
createDiagnosticForOptionName(Diagnostics.Option_preserveConstEnums_cannot_be_disabled_when_isolatedModules_is_enabled, "preserveConstEnums", "isolatedModules");
}
for (const file of files) {
if (!isExternalModule(file) && !isSourceFileJS(file) && !file.isDeclarationFile && file.scriptKind !== ScriptKind.JSON) {
const span = getErrorSpanForNode(file, file);
programDiagnostics.add(createFileDiagnostic(file, span.start, span.length,
Diagnostics._0_cannot_be_compiled_under_isolatedModules_because_it_is_considered_a_global_script_file_Add_an_import_export_or_an_empty_export_statement_to_make_it_a_module, getBaseFileName(file.fileName)));
}
createDiagnosticForOptionName(Diagnostics.Option_preserveConstEnums_cannot_be_disabled_when_0_is_enabled, options.verbatimModuleSyntax ? "verbatimModuleSyntax" : "isolatedModules", "preserveConstEnums");
}
}
else if (firstNonAmbientExternalModuleSourceFile && languageVersion < ScriptTarget.ES2015 && options.module === ModuleKind.None) {
@@ -4240,7 +4233,23 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
}
if (options.preserveValueImports && getEmitModuleKind(options) < ModuleKind.ES2015) {
createOptionValueDiagnostic("importsNotUsedAsValues", Diagnostics.Option_preserveValueImports_can_only_be_used_when_module_is_set_to_es2015_or_later);
createDiagnosticForOptionName(Diagnostics.Option_preserveValueImports_can_only_be_used_when_module_is_set_to_es2015_or_later, "preserveValueImports");
}
if (options.verbatimModuleSyntax) {
const moduleKind = getEmitModuleKind(options);
if (moduleKind === ModuleKind.AMD || moduleKind === ModuleKind.UMD || moduleKind === ModuleKind.System) {
createDiagnosticForOptionName(Diagnostics.Option_verbatimModuleSyntax_cannot_be_used_when_module_is_set_to_UMD_AMD_or_System, "verbatimModuleSyntax");
}
if (options.isolatedModules) {
createRedundantOptionDiagnostic("isolatedModules", "verbatimModuleSyntax");
}
if (options.preserveValueImports) {
createRedundantOptionDiagnostic("preserveValueImports", "verbatimModuleSyntax");
}
if (options.importsNotUsedAsValues) {
createRedundantOptionDiagnostic("importsNotUsedAsValues", "verbatimModuleSyntax");
}
}
if (options.allowImportingTsExtensions && !(options.noEmit || options.emitDeclarationOnly)) {
@@ -4333,15 +4342,35 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
if (options.out) {
createDeprecatedDiagnosticForOption(version, "out");
}
if (options.importsNotUsedAsValues) {
createDeprecatedDiagnosticForOption(version, "importsNotUsedAsValues", /*value*/ undefined, "verbatimModuleSyntax");
}
if (options.preserveValueImports) {
createDeprecatedDiagnosticForOption(version, "preserveValueImports", /*value*/ undefined, "verbatimModuleSyntax");
}
}
function createDeprecatedDiagnosticForOption(version: string, name: string, value?: string) {
function createDeprecatedDiagnosticForOption(version: string, name: string, value?: string, useInstead?: string) {
if (version === DeprecationVersion.v6_0) {
createDiagnosticForOption(/*onKey*/ !value, name, /*option2*/ undefined, Diagnostics.Flag_0_is_deprecated_Please_remove_it_from_your_configuration, value || name);
if (useInstead) {
const details = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Use_0_instead, useInstead);
const chain = chainDiagnosticMessages(details, Diagnostics.Flag_0_is_deprecated_Please_remove_it_from_your_configuration, value || name);
createDiagnosticForOption(/*onKey*/ !value, name, /*option2*/ undefined, chain);
}
else {
createDiagnosticForOption(/*onKey*/ !value, name, /*option2*/ undefined, Diagnostics.Flag_0_is_deprecated_Please_remove_it_from_your_configuration, value || name);
}
}
else {
createDiagnosticForOption(/*onKey*/ !value, name, /*option2*/ undefined,
Diagnostics.Flag_0_is_deprecated_and_will_stop_functioning_in_TypeScript_1_Specify_ignoreDeprecations_Colon_2_to_silence_this_error, value || name, DeprecationVersion.v5_5, DeprecationVersion.v5_0);
if (useInstead) {
const details = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Use_0_instead, useInstead);
const chain = chainDiagnosticMessages(details, Diagnostics.Flag_0_is_deprecated_and_will_stop_functioning_in_TypeScript_1_Specify_ignoreDeprecations_Colon_2_to_silence_this_error, value || name, DeprecationVersion.v5_5, DeprecationVersion.v5_0);
createDiagnosticForOption(/*onKey*/ !value, name, /*option2*/ undefined, chain);
}
else {
createDiagnosticForOption(/*onKey*/ !value, name, /*option2*/ undefined,
Diagnostics.Flag_0_is_deprecated_and_will_stop_functioning_in_TypeScript_1_Specify_ignoreDeprecations_Colon_2_to_silence_this_error, value || name, DeprecationVersion.v5_5, DeprecationVersion.v5_0);
}
}
}
@@ -4590,13 +4619,21 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
}
}
function createDiagnosticForOption(onKey: boolean, option1: string, option2: string | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number) {
function createDiagnosticForOption(onKey: boolean, option1: string, option2: string | undefined, message: DiagnosticMessageChain): void;
function createDiagnosticForOption(onKey: boolean, option1: string, option2: string | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): void;
function createDiagnosticForOption(onKey: boolean, option1: string, option2: string | undefined, message: DiagnosticMessage | DiagnosticMessageChain, arg0?: string | number, arg1?: string | number, arg2?: string | number): void {
const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax();
const needCompilerDiagnostic = !compilerOptionsObjectLiteralSyntax ||
!createOptionDiagnosticInObjectLiteralSyntax(compilerOptionsObjectLiteralSyntax, onKey, option1, option2, message, arg0, arg1, arg2);
if (needCompilerDiagnostic) {
programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1, arg2));
// eslint-disable-next-line local/no-in-operator
if ("messageText" in message) {
programDiagnostics.add(createCompilerDiagnosticFromMessageChain(message));
}
else {
programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1, arg2));
}
}
}
@@ -4616,14 +4653,43 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
return _compilerOptionsObjectLiteralSyntax || undefined;
}
function createOptionDiagnosticInObjectLiteralSyntax(objectLiteral: ObjectLiteralExpression, onKey: boolean, key1: string, key2: string | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): boolean {
function createOptionDiagnosticInObjectLiteralSyntax(objectLiteral: ObjectLiteralExpression, onKey: boolean, key1: string, key2: string | undefined, messageChain: DiagnosticMessageChain): boolean;
function createOptionDiagnosticInObjectLiteralSyntax(objectLiteral: ObjectLiteralExpression, onKey: boolean, key1: string, key2: string | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): boolean;
function createOptionDiagnosticInObjectLiteralSyntax(objectLiteral: ObjectLiteralExpression, onKey: boolean, key1: string, key2: string | undefined, message: DiagnosticMessage | DiagnosticMessageChain, arg0?: string | number, arg1?: string | number, arg2?: string | number): boolean;
function createOptionDiagnosticInObjectLiteralSyntax(objectLiteral: ObjectLiteralExpression, onKey: boolean, key1: string, key2: string | undefined, message: DiagnosticMessage | DiagnosticMessageChain, arg0?: string | number, arg1?: string | number, arg2?: string | number): boolean {
const props = getPropertyAssignment(objectLiteral, key1, key2);
for (const prop of props) {
programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, onKey ? prop.name : prop.initializer, message, arg0, arg1, arg2));
// eslint-disable-next-line local/no-in-operator
if ("messageText" in message) {
programDiagnostics.add(createDiagnosticForNodeFromMessageChain(options.configFile!, onKey ? prop.name : prop.initializer, message));
}
else {
programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, onKey ? prop.name : prop.initializer, message, arg0, arg1, arg2));
}
}
return !!props.length;
}
/**
* Only creates a diagnostic on the option key specified by `errorOnOption`.
* If both options are specified in the program in separate config files via `extends`,
* a diagnostic is only created if `errorOnOption` is specified in the leaf config file.
* Useful if `redundantWithOption` represents a superset of the functionality of `errorOnOption`:
* if a user inherits `errorOnOption` from a base config file, it's still valid and useful to
* override it in the leaf config file.
*/
function createRedundantOptionDiagnostic(errorOnOption: string, redundantWithOption: string) {
const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax();
if (compilerOptionsObjectLiteralSyntax) {
// This is a no-op if `errorOnOption` isn't present in the leaf config file.
createOptionDiagnosticInObjectLiteralSyntax(compilerOptionsObjectLiteralSyntax, /*onKey*/ true, errorOnOption, /*key2*/ undefined, Diagnostics.Option_0_is_redundant_and_cannot_be_specified_with_option_1, errorOnOption, redundantWithOption);
}
else {
// There was no config file, so both options were specified on the command line.
createDiagnosticForOptionName(Diagnostics.Option_0_is_redundant_and_cannot_be_specified_with_option_1, errorOnOption, redundantWithOption);
}
}
function blockEmittingOfFile(emitFileName: string, diag: Diagnostic) {
hasEmitBlockingDiagnostics.set(toPath(emitFileName), true);
programDiagnostics.add(diag);
+5 -5
View File
@@ -2264,7 +2264,7 @@ export function transformTypeScript(context: TransformationContext) {
}
else {
// Elide named imports if all of its import specifiers are elided and settings allow.
const allowEmpty = compilerOptions.preserveValueImports && (
const allowEmpty = compilerOptions.verbatimModuleSyntax || compilerOptions.preserveValueImports && (
compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Preserve ||
compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error);
const elements = visitNodes(node.elements, visitImportSpecifier, isImportSpecifier);
@@ -2289,7 +2289,7 @@ export function transformTypeScript(context: TransformationContext) {
*/
function visitExportAssignment(node: ExportAssignment): VisitResult<Statement | undefined> {
// Elide the export assignment if it does not reference a value.
return resolver.isValueAliasDeclaration(node)
return compilerOptions.verbatimModuleSyntax || resolver.isValueAliasDeclaration(node)
? visitEachChild(node, visitor, context)
: undefined;
}
@@ -2312,7 +2312,7 @@ export function transformTypeScript(context: TransformationContext) {
}
// Elide the export declaration if all of its named exports are elided.
const allowEmpty = !!node.moduleSpecifier && (
const allowEmpty = compilerOptions.verbatimModuleSyntax || !!node.moduleSpecifier && (
compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Preserve ||
compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error);
const exportClause = visitNode(
@@ -2358,7 +2358,7 @@ export function transformTypeScript(context: TransformationContext) {
*/
function visitExportSpecifier(node: ExportSpecifier): VisitResult<ExportSpecifier> | undefined {
// Elide an export specifier if it does not reference a value.
return !node.isTypeOnly && resolver.isValueAliasDeclaration(node) ? node : undefined;
return !node.isTypeOnly && (compilerOptions.verbatimModuleSyntax || resolver.isValueAliasDeclaration(node)) ? node : undefined;
}
/**
@@ -2714,7 +2714,7 @@ export function transformTypeScript(context: TransformationContext) {
}
function shouldEmitAliasDeclaration(node: Node): boolean {
return isInJSFile(node) ||
return compilerOptions.verbatimModuleSyntax || isInJSFile(node) ||
(compilerOptions.preserveValueImports
? resolver.isValueAliasDeclaration(node)
: resolver.isReferencedAliasDeclaration(node));
+16
View File
@@ -5453,6 +5453,21 @@ export type AnyImportOrRequire = AnyImportSyntax | VariableDeclarationInitialize
/** @internal */
export type AnyImportOrBareOrAccessedRequire = AnyImportSyntax | VariableDeclarationInitializedTo<RequireOrImportCall | AccessExpression>;
/** @internal */
export type AliasDeclarationNode =
| ImportEqualsDeclaration
| VariableDeclarationInitializedTo<RequireOrImportCall
| AccessExpression>
| ImportClause
| NamespaceImport
| ImportSpecifier
| ExportSpecifier
| NamespaceExport
| BindingElementOfBareOrAccessedRequire;
/** @internal */
export type BindingElementOfBareOrAccessedRequire = BindingElement & { parent: { parent: VariableDeclarationInitializedTo<RequireOrImportCall | AccessExpression> } };
/** @internal */
export type AnyImportOrRequireStatement = AnyImportSyntax | RequireVariableStatement;
@@ -7087,6 +7102,7 @@ export interface CompilerOptions {
types?: string[];
/** Paths used to compute primary types search locations */
typeRoots?: string[];
verbatimModuleSyntax?: boolean;
/** @internal */ version?: boolean;
/** @internal */ watch?: boolean;
esModuleInterop?: boolean;
+30 -4
View File
@@ -5,6 +5,7 @@ import {
addRange,
affectsDeclarationPathOptionDeclarations,
affectsEmitOptionDeclarations,
AliasDeclarationNode,
AllAccessorDeclarations,
AmbientModuleDeclaration,
AmpersandAmpersandEqualsToken,
@@ -31,6 +32,7 @@ import {
BindableStaticElementAccessExpression,
BindableStaticNameExpression,
BindingElement,
BindingElementOfBareOrAccessedRequire,
Block,
BundleFileSection,
BundleFileSectionKind,
@@ -197,8 +199,8 @@ import {
HasExpressionInitializer,
hasExtension,
HasFlowNode,
HasInitializer,
hasInitializer,
HasInitializer,
HasJSDoc,
hasJSDocNodes,
HasModifiers,
@@ -234,6 +236,7 @@ import {
isArrowFunction,
isBigIntLiteral,
isBinaryExpression,
isBindingElement,
isBindingPattern,
isCallExpression,
isClassDeclaration,
@@ -1731,8 +1734,7 @@ export function createDiagnosticForNodeInSourceFile(sourceFile: SourceFile, node
}
/** @internal */
export function createDiagnosticForNodeFromMessageChain(node: Node, messageChain: DiagnosticMessageChain, relatedInformation?: DiagnosticRelatedInformation[]): DiagnosticWithLocation {
const sourceFile = getSourceFileOfNode(node);
export function createDiagnosticForNodeFromMessageChain(sourceFile: SourceFile, node: Node, messageChain: DiagnosticMessageChain, relatedInformation?: DiagnosticRelatedInformation[]): DiagnosticWithLocation {
const span = getErrorSpanForNode(sourceFile, node);
return createFileDiagnosticFromMessageChain(sourceFile, span.start, span.length, messageChain, relatedInformation);
}
@@ -3116,6 +3118,11 @@ export function isVariableDeclarationInitializedToBareOrAccessedRequire(node: No
return isVariableDeclarationInitializedWithRequireHelper(node, /*allowAccessedRequire*/ true);
}
/** @internal */
export function isBindingElementOfBareOrAccessedRequire(node: Node): node is BindingElementOfBareOrAccessedRequire {
return isBindingElement(node) && isVariableDeclarationInitializedToBareOrAccessedRequire(node.parent.parent);
}
function isVariableDeclarationInitializedWithRequireHelper(node: Node, allowAccessedRequire: boolean) {
return isVariableDeclaration(node) &&
!!node.initializer &&
@@ -3542,14 +3549,23 @@ export function isFunctionSymbol(symbol: Symbol | undefined) {
}
/** @internal */
export function tryGetModuleSpecifierFromDeclaration(node: AnyImportOrBareOrAccessedRequire): StringLiteralLike | undefined {
export function tryGetModuleSpecifierFromDeclaration(node: AnyImportOrBareOrAccessedRequire | AliasDeclarationNode): StringLiteralLike | undefined {
switch (node.kind) {
case SyntaxKind.VariableDeclaration:
case SyntaxKind.BindingElement:
return findAncestor(node.initializer, (node): node is RequireOrImportCall => isRequireCall(node, /*requireStringLiteralLikeArgument*/ true))?.arguments[0];
case SyntaxKind.ImportDeclaration:
return tryCast(node.moduleSpecifier, isStringLiteralLike);
case SyntaxKind.ImportEqualsDeclaration:
return tryCast(tryCast(node.moduleReference, isExternalModuleReference)?.expression, isStringLiteralLike);
case SyntaxKind.ImportClause:
case SyntaxKind.NamespaceExport:
return tryCast(node.parent.moduleSpecifier, isStringLiteralLike);
case SyntaxKind.NamespaceImport:
case SyntaxKind.ExportSpecifier:
return tryCast(node.parent.parent.moduleSpecifier, isStringLiteralLike);
case SyntaxKind.ImportSpecifier:
return tryCast(node.parent.parent.parent.moduleSpecifier, isStringLiteralLike);
default:
Debug.assertNever(node);
}
@@ -8009,6 +8025,16 @@ export function hasJsonModuleEmitEnabled(options: CompilerOptions) {
}
}
/** @internal */
export function getIsolatedModules(options: CompilerOptions) {
return !!(options.isolatedModules || options.verbatimModuleSyntax);
}
/** @internal */
export function importNameElisionDisabled(options: CompilerOptions) {
return options.verbatimModuleSyntax || options.isolatedModules && options.preserveValueImports;
}
/** @internal */
export function unreachableCodeIsError(options: CompilerOptions): boolean {
return options.allowUnreachableCode === false;
+55 -9
View File
@@ -2985,8 +2985,10 @@ export class TestState {
this.applyChanges(fixes[index].changes);
}
public applyCodeActionFromCompletion(markerName: string, options: FourSlashInterface.VerifyCompletionActionOptions) {
this.goToMarker(markerName);
public applyCodeActionFromCompletion(markerName: string | undefined, options: FourSlashInterface.VerifyCompletionActionOptions) {
if (markerName !== undefined) {
this.goToMarker(markerName);
}
const details = this.getCompletionEntryDetails(options.name, options.source, options.data, options.preferences);
if (!details) {
@@ -3213,9 +3215,9 @@ export class TestState {
// Undo changes to perform next fix
const span = change.textChanges[0].span;
const deletedText = originalContent.substr(span.start, change.textChanges[0].span.length);
const insertedText = change.textChanges[0].newText;
this.editScriptAndUpdateMarkers(fileName, span.start, span.start + insertedText.length, deletedText);
const deletedText = originalContent.substr(span.start, change.textChanges[0].span.length);
const insertedText = change.textChanges[0].newText;
this.editScriptAndUpdateMarkers(fileName, span.start, span.start + insertedText.length, deletedText);
}
if (expectedTextArray.length !== actualTextArray.length) {
this.raiseError(`Expected ${expectedTextArray.length} import fixes, got ${actualTextArray.length}:\n\n${actualTextArray.join("\n\n" + "-".repeat(20) + "\n\n")}`);
@@ -3301,7 +3303,7 @@ export class TestState {
}
}
public baselineAutoImports(markerName: string, preferences?: ts.UserPreferences) {
public baselineAutoImports(markerName: string, fullNamesForCodeFix?: string[], preferences?: ts.UserPreferences) {
const marker = this.getMarkerByName(markerName);
const baselineFile = this.getBaselineFileNameForContainingTestFile(`.baseline.md`);
const completionPreferences = {
@@ -3312,10 +3314,12 @@ export class TestState {
...preferences
};
const ext = ts.getAnyExtensionFromPath(this.activeFile.fileName).slice(1);
this.goToMarker(marker);
this.configure(completionPreferences);
const fileName = this.activeFile.fileName;
const ext = ts.getAnyExtensionFromPath(fileName).slice(1);
const lang = ["mts", "cts"].includes(ext) ? "ts" : ext;
let baselineText = codeFence(this.renderMarkers([{ text: "|", fileName: marker.fileName, position: marker.position }], /*useTerminalBoldSequence*/ false), lang) + "\n\n";
this.goToMarker(marker);
const completions = this.getCompletionListAtCaret(completionPreferences)!;
@@ -3339,7 +3343,36 @@ export class TestState {
});
}
// TODO: do codefixes too
if (fullNamesForCodeFix) {
const scriptInfo = this.languageServiceAdapterHost.getScriptInfo(fileName)!;
const originalContent = scriptInfo.content;
const range = this.getRangesInFile()[0]
|| getRangeOfIdentifierTouchingPosition(this.activeFile.content, marker.position)
|| { pos: marker.position, end: marker.position };
baselineText += `## From codefixes\n\n`;
for (const fullNameForCodeFix of fullNamesForCodeFix) {
this.applyEdits(fileName, [{ span: { start: 0, length: this.getFileContent(fileName).length }, newText: originalContent }]);
this.applyEdits(fileName, [{ span: ts.createTextSpanFromRange(range), newText: fullNameForCodeFix }]);
baselineText += `### When marker text is \`${fullNameForCodeFix}\`\n\n`;
const codeFixes = this.getCodeFixes(fileName, /*errorCode*/ undefined, completionPreferences)
.filter(f => f.fixName === ts.codefix.importFixName);
for (const fix of codeFixes) {
baselineText += fix.description + "\n";
if (fix.fixAllDescription) {
baselineText += `Fix all available: ${fix.fixAllDescription}\n`;
}
ts.Debug.assert(fix.changes.length === 1);
const change = ts.first(fix.changes);
ts.Debug.assert(change.fileName === fileName);
this.applyEdits(change.fileName, change.textChanges);
const text = this.getFileContent(fileName);
baselineText += "\n" + codeFence(text, lang) + "\n\n";
}
}
}
Harness.Baseline.runBaseline(baselineFile, baselineText);
}
@@ -4791,3 +4824,16 @@ function highlightDifferenceBetweenStrings(source: string, target: string) {
function codeFence(code: string, lang?: string) {
return `\`\`\`${lang || ""}\n${code}\n\`\`\``;
}
function getRangeOfIdentifierTouchingPosition(content: string, position: number): ts.TextRange | undefined {
const scanner = ts.createScanner(ts.ScriptTarget.Latest, /*skipTrivia*/ true, ts.LanguageVariant.Standard, content);
while (scanner.scan() !== ts.SyntaxKind.EndOfFileToken) {
const tokenStart = scanner.getStartPos();
if (scanner.getToken() === ts.SyntaxKind.Identifier && tokenStart <= position && scanner.getTextPos() >= position) {
return { pos: tokenStart, end: scanner.getTextPos() };
}
if (tokenStart > position) {
break;
}
}
}
+3 -3
View File
@@ -480,7 +480,7 @@ export class Verify extends VerifyNegatable {
this.state.getAndApplyCodeActions(errorCode, index);
}
public applyCodeActionFromCompletion(markerName: string, options: VerifyCompletionActionOptions): void {
public applyCodeActionFromCompletion(markerName: string | undefined, options: VerifyCompletionActionOptions): void {
this.state.applyCodeActionFromCompletion(markerName, options);
}
@@ -492,8 +492,8 @@ export class Verify extends VerifyNegatable {
this.state.verifyImportFixModuleSpecifiers(marker, moduleSpecifiers, preferences);
}
public baselineAutoImports(marker: string, preferences?: ts.UserPreferences) {
this.state.baselineAutoImports(marker, preferences);
public baselineAutoImports(marker: string, fullNamesForCodeFix?: string[], options?: ts.UserPreferences) {
this.state.baselineAutoImports(marker, fullNamesForCodeFix, options);
}
public navigationBar(json: any, options?: { checkSpans?: boolean }) {
@@ -24,7 +24,7 @@ import {
registerCodeFix,
} from "../_namespaces/ts.codefix";
const errorCodes = [Diagnostics.Re_exporting_a_type_when_the_isolatedModules_flag_is_provided_requires_using_export_type.code];
const errorCodes = [Diagnostics.Re_exporting_a_type_when_0_is_enabled_requires_using_export_type.code];
const fixId = "convertToTypeOnlyExport";
registerCodeFix({
errorCodes,
+85 -64
View File
@@ -22,7 +22,6 @@ import {
Diagnostics,
DiagnosticWithLocation,
emptyArray,
escapeLeadingUnderscores,
every,
ExportKind,
factory,
@@ -34,6 +33,7 @@ import {
formatting,
getAllowSyntheticDefaultImports,
getBaseFileName,
getDefaultExportInfoWorker,
getDefaultLikeExportInfo,
getDirectoryPath,
getEmitModuleKind,
@@ -232,7 +232,7 @@ function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAu
const symbol = checker.getMergedSymbol(skipAlias(exportedSymbol, checker));
const exportInfo = getAllExportInfoForSymbol(sourceFile, symbol, symbolName, /*isJsxTagName*/ false, program, host, preferences, cancellationToken);
const useRequire = shouldUseRequire(sourceFile, program);
const fix = getImportFixForSymbol(sourceFile, Debug.checkDefined(exportInfo), moduleSymbol, program, /*useNamespaceInfo*/ undefined, !!isValidTypeOnlyUseSite, useRequire, host, preferences);
const fix = getImportFixForSymbol(sourceFile, Debug.checkDefined(exportInfo), moduleSymbol, program, /*position*/ undefined, !!isValidTypeOnlyUseSite, useRequire, host, preferences);
if (fix) {
addImport({ fix, symbolName, errorIdentifierText: undefined });
}
@@ -392,7 +392,6 @@ function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAu
export interface ImportSpecifierResolver {
getModuleSpecifierForBestExportInfo(
exportInfo: readonly SymbolExportInfo[],
symbolName: string,
position: number,
isValidTypeOnlyUseSite: boolean,
fromCacheOnly?: boolean
@@ -407,14 +406,13 @@ export function createImportSpecifierResolver(importingFile: SourceFile, program
function getModuleSpecifierForBestExportInfo(
exportInfo: readonly SymbolExportInfo[],
symbolName: string,
position: number,
isValidTypeOnlyUseSite: boolean,
fromCacheOnly?: boolean,
): { exportInfo?: SymbolExportInfo, moduleSpecifier: string, computedWithoutCacheCount: number } | undefined {
const { fixes, computedWithoutCacheCount } = getImportFixes(
exportInfo,
{ symbolName, position },
position,
isValidTypeOnlyUseSite,
/*useRequire*/ false,
program,
@@ -448,14 +446,16 @@ interface ImportFixBase {
readonly exportInfo?: SymbolExportInfo;
readonly moduleSpecifier: string;
}
interface FixUseNamespaceImport extends ImportFixBase {
readonly kind: ImportFixKind.UseNamespace;
interface Qualification {
readonly usagePosition: number;
readonly namespacePrefix: string;
readonly position: number;
}
interface FixUseNamespaceImport extends ImportFixBase, Qualification {
readonly kind: ImportFixKind.UseNamespace;
}
interface FixAddJsdocTypeImport extends ImportFixBase {
readonly kind: ImportFixKind.JsdocTypeImport;
readonly position: number;
readonly usagePosition: number;
readonly isReExport: boolean;
readonly exportInfo: SymbolExportInfo;
}
@@ -470,6 +470,7 @@ interface FixAddNewImport extends ImportFixBase {
readonly importKind: ImportKind;
readonly addAsTypeOnly: AddAsTypeOnly;
readonly useRequire: boolean;
readonly qualification?: Qualification;
}
interface FixPromoteTypeOnlyImport {
readonly kind: ImportFixKind.PromoteTypeOnly;
@@ -508,7 +509,7 @@ export function getImportCompletionAction(
Debug.assertIsDefined(exportInfos);
const useRequire = shouldUseRequire(sourceFile, program);
const isValidTypeOnlyUseSite = isValidTypeOnlyAliasUseSite(getTokenAtPosition(sourceFile, position));
const fix = Debug.checkDefined(getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, program, { symbolName, position }, isValidTypeOnlyUseSite, useRequire, host, preferences));
const fix = Debug.checkDefined(getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, program, position, isValidTypeOnlyUseSite, useRequire, host, preferences));
return {
moduleSpecifier: fix.moduleSpecifier,
codeAction: codeFixActionToCodeAction(codeActionForFix(
@@ -531,10 +532,10 @@ export function getPromoteTypeOnlyCompletionAction(sourceFile: SourceFile, symbo
return fix && codeFixActionToCodeAction(codeActionForFix({ host, formatContext, preferences }, sourceFile, symbolName, fix, includeSymbolNameInDescription, compilerOptions, preferences));
}
function getImportFixForSymbol(sourceFile: SourceFile, exportInfos: readonly SymbolExportInfo[], moduleSymbol: Symbol, program: Program, useNamespaceInfo: { position: number, symbolName: string } | undefined, isValidTypeOnlyUseSite: boolean, useRequire: boolean, host: LanguageServiceHost, preferences: UserPreferences) {
function getImportFixForSymbol(sourceFile: SourceFile, exportInfos: readonly SymbolExportInfo[], moduleSymbol: Symbol, 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");
const packageJsonImportFilter = createPackageJsonImportFilter(sourceFile, preferences, host);
return getBestFix(getImportFixes(exportInfos, useNamespaceInfo, isValidTypeOnlyUseSite, useRequire, program, sourceFile, host, preferences).fixes, sourceFile, program, packageJsonImportFilter, host);
return getBestFix(getImportFixes(exportInfos, position, isValidTypeOnlyUseSite, useRequire, program, sourceFile, host, preferences).fixes, sourceFile, program, packageJsonImportFilter, host);
}
function codeFixActionToCodeAction({ description, changes, commands }: CodeFixAction): CodeAction {
@@ -574,11 +575,7 @@ function getSingleExportInfoForSymbol(symbol: Symbol, moduleSymbol: Symbol, prog
function getImportFixes(
exportInfos: readonly SymbolExportInfo[],
useNamespaceInfo: {
symbolName: string,
position: number,
} | undefined,
/** undefined only for missing JSX namespace */
usagePosition: number | undefined,
isValidTypeOnlyUseSite: boolean,
useRequire: boolean,
program: Program,
@@ -590,7 +587,7 @@ function getImportFixes(
): { computedWithoutCacheCount: number, fixes: readonly ImportFixWithModuleSpecifier[] } {
const checker = program.getTypeChecker();
const existingImports = flatMap(exportInfos, importMap.getImportsForExportInfo);
const useNamespace = useNamespaceInfo && tryUseExistingNamespaceImport(existingImports, useNamespaceInfo.symbolName, useNamespaceInfo.position, checker);
const useNamespace = usagePosition !== undefined && tryUseExistingNamespaceImport(existingImports, usagePosition);
const addToExisting = tryAddToExistingImport(existingImports, isValidTypeOnlyUseSite, checker, program.getCompilerOptions());
if (addToExisting) {
// Don't bother providing an action to add a new import if we can add to an existing one.
@@ -605,7 +602,7 @@ function getImportFixes(
existingImports,
program,
sourceFile,
useNamespaceInfo?.position,
usagePosition,
isValidTypeOnlyUseSite,
useRequire,
host,
@@ -617,7 +614,7 @@ function getImportFixes(
};
}
function tryUseExistingNamespaceImport(existingImports: readonly FixAddToExistingImportInfo[], symbolName: string, position: number, checker: TypeChecker): FixUseNamespaceImport | undefined {
function tryUseExistingNamespaceImport(existingImports: readonly FixAddToExistingImportInfo[], position: number): FixUseNamespaceImport | undefined {
// It is possible that multiple import statements with the same specifier exist in the file.
// e.g.
//
@@ -630,32 +627,16 @@ function tryUseExistingNamespaceImport(existingImports: readonly FixAddToExistin
// 1. change "member3" to "ns.member3"
// 2. add "member3" to the second import statement's import list
// and it is up to the user to decide which one fits best.
return firstDefined(existingImports, ({ declaration }): FixUseNamespaceImport | undefined => {
return firstDefined(existingImports, ({ declaration, importKind }): FixUseNamespaceImport | undefined => {
if (importKind !== ImportKind.Named) return undefined;
const namespacePrefix = getNamespaceLikeImportText(declaration);
const moduleSpecifier = tryGetModuleSpecifierFromDeclaration(declaration)?.text;
if (namespacePrefix && moduleSpecifier) {
const moduleSymbol = getTargetModuleFromNamespaceLikeImport(declaration, checker);
if (moduleSymbol && moduleSymbol.exports!.has(escapeLeadingUnderscores(symbolName))) {
return { kind: ImportFixKind.UseNamespace, namespacePrefix, position, moduleSpecifier };
}
const moduleSpecifier = namespacePrefix && tryGetModuleSpecifierFromDeclaration(declaration)?.text;
if (moduleSpecifier) {
return { kind: ImportFixKind.UseNamespace, namespacePrefix, usagePosition: position, moduleSpecifier };
}
});
}
function getTargetModuleFromNamespaceLikeImport(declaration: AnyImportOrRequire, checker: TypeChecker) {
switch (declaration.kind) {
case SyntaxKind.VariableDeclaration:
return checker.resolveExternalModuleName(declaration.initializer.arguments[0]);
case SyntaxKind.ImportEqualsDeclaration:
return checker.getAliasedSymbol(declaration.symbol);
case SyntaxKind.ImportDeclaration:
const namespaceImport = tryCast(declaration.importClause?.namedBindings, isNamespaceImport);
return namespaceImport && checker.getAliasedSymbol(namespaceImport.symbol);
default:
return Debug.assertNever(declaration);
}
}
function getNamespaceLikeImportText(declaration: AnyImportOrRequire) {
switch (declaration.kind) {
case SyntaxKind.VariableDeclaration:
@@ -685,7 +666,7 @@ function getAddAsTypeOnly(
// 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 &&
if ((compilerOptions.isolatedModules && compilerOptions.preserveValueImports || compilerOptions.verbatimModuleSyntax) &&
(!(targetFlags & SymbolFlags.Value) || !!checker.getTypeOnlyAliasDeclaration(symbol))
) {
// A type-only import is required for this symbol if under these settings if the symbol will
@@ -804,7 +785,7 @@ function createGetChecker(program: Program, host: LanguageServiceHost) {
function getNewImportFixes(
program: Program,
sourceFile: SourceFile,
position: number | undefined,
usagePosition: number | undefined,
isValidTypeOnlyUseSite: boolean,
useRequire: boolean,
exportInfo: readonly SymbolExportInfo[],
@@ -829,20 +810,45 @@ function getNewImportFixes(
const importedSymbolHasValueMeaning = !!(exportInfo.targetFlags & SymbolFlags.Value);
const addAsTypeOnly = getAddAsTypeOnly(isValidTypeOnlyUseSite, /*isForNewImportDeclaration*/ true, exportInfo.symbol, exportInfo.targetFlags, checker, compilerOptions);
computedWithoutCacheCount += computedWithoutCache ? 1 : 0;
return mapDefined(moduleSpecifiers, (moduleSpecifier): FixAddNewImport | FixAddJsdocTypeImport | undefined =>
rejectNodeModulesRelativePaths && pathContainsNodeModules(moduleSpecifier) ? undefined :
// `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types.
!importedSymbolHasValueMeaning && isJs && position !== undefined ? { kind: ImportFixKind.JsdocTypeImport, moduleSpecifier, position, exportInfo, isReExport: i > 0 } :
{
return mapDefined(moduleSpecifiers, (moduleSpecifier): FixAddNewImport | FixAddJsdocTypeImport | undefined => {
if (rejectNodeModulesRelativePaths && pathContainsNodeModules(moduleSpecifier)) {
return undefined;
}
if (!importedSymbolHasValueMeaning && isJs && usagePosition !== undefined) {
// `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types.
return { kind: ImportFixKind.JsdocTypeImport, moduleSpecifier, usagePosition, exportInfo, isReExport: i > 0 };
}
const importKind = getImportKind(sourceFile, exportInfo.exportKind, compilerOptions);
let qualification: Qualification | undefined;
if (usagePosition !== undefined && importKind === ImportKind.CommonJS && exportInfo.exportKind === ExportKind.Named) {
// Compiler options are restricting our import options to a require, but we need to access
// a named export or property of the exporting module. We need to import the entire module
// and insert a property access, e.g. `writeFile` becomes
//
// import fs = require("fs"); // or const in JS
// fs.writeFile
const exportEquals = checker.resolveExternalModuleSymbol(exportInfo.moduleSymbol);
let namespacePrefix;
if (exportEquals !== exportInfo.moduleSymbol) {
namespacePrefix = getDefaultExportInfoWorker(exportEquals, checker, compilerOptions)?.name;
}
namespacePrefix ||= moduleSymbolToValidIdentifier(
exportInfo.moduleSymbol,
getEmitScriptTarget(compilerOptions),
/*forceCapitalize*/ false);
qualification = { namespacePrefix, usagePosition };
}
return {
kind: ImportFixKind.AddNew,
moduleSpecifier,
importKind: getImportKind(sourceFile, exportInfo.exportKind, compilerOptions),
importKind,
useRequire,
addAsTypeOnly,
exportInfo,
isReExport: i > 0,
}
);
qualification,
};
});
});
return { computedWithoutCacheCount, fixes };
@@ -853,7 +859,7 @@ function getFixesForAddImport(
existingImports: readonly FixAddToExistingImportInfo[],
program: Program,
sourceFile: SourceFile,
position: number | undefined,
usagePosition: number | undefined,
isValidTypeOnlyUseSite: boolean,
useRequire: boolean,
host: LanguageServiceHost,
@@ -861,7 +867,7 @@ function getFixesForAddImport(
fromCacheOnly?: boolean,
): { computedWithoutCacheCount?: number, fixes: readonly (FixAddNewImport | FixAddJsdocTypeImport)[] } {
const existingDeclaration = firstDefined(existingImports, info => newImportInfoFromExistingSpecifier(info, isValidTypeOnlyUseSite, useRequire, program.getTypeChecker(), program.getCompilerOptions()));
return existingDeclaration ? { fixes: [existingDeclaration] } : getNewImportFixes(program, sourceFile, position, isValidTypeOnlyUseSite, useRequire, exportInfos, host, preferences, fromCacheOnly);
return existingDeclaration ? { fixes: [existingDeclaration] } : getNewImportFixes(program, sourceFile, usagePosition, isValidTypeOnlyUseSite, useRequire, exportInfos, host, preferences, fromCacheOnly);
}
function newImportInfoFromExistingSpecifier(
@@ -991,8 +997,13 @@ function getFixesInfoForUMDImport({ sourceFile, program, host, preferences }: Co
const symbolName = umdSymbol.name;
const exportInfo: readonly SymbolExportInfo[] = [{ symbol: umdSymbol, moduleSymbol: symbol, moduleFileName: undefined, exportKind: ExportKind.UMD, targetFlags: symbol.flags, isFromPackageJson: false }];
const useRequire = shouldUseRequire(sourceFile, program);
const position = isIdentifier(token) ? token.getStart(sourceFile) : undefined;
const fixes = getImportFixes(exportInfo, position ? { position, symbolName } : undefined, /*isValidTypeOnlyUseSite*/ false, useRequire, program, sourceFile, host, preferences).fixes;
// `usagePosition` is undefined because `token` may not actually be a usage of the symbol we're importing.
// For example, we might need to import `React` in order to use an arbitrary JSX tag. We could send a position
// for other UMD imports, but `usagePosition` is currently only used to insert a namespace qualification
// before a named import, like converting `writeFile` to `fs.writeFile` (whether `fs` is already imported or
// not), and this function will only be called for UMD symbols, which are necessarily an `export =`, not a
// named export.
const fixes = getImportFixes(exportInfo, /*usagePosition*/ undefined, /*isValidTypeOnlyUseSite*/ false, useRequire, program, sourceFile, host, preferences).fixes;
return fixes.map(fix => ({ fix, symbolName, errorIdentifierText: tryCast(token, isIdentifier)?.text }));
}
function getUmdSymbol(token: Node, checker: TypeChecker): Symbol | undefined {
@@ -1018,6 +1029,10 @@ function getUmdSymbol(token: Node, checker: TypeChecker): Symbol | undefined {
* @internal
*/
export function getImportKind(importingFile: SourceFile, exportKind: ExportKind, compilerOptions: CompilerOptions, forceImportKeyword?: boolean): ImportKind {
if (compilerOptions.verbatimModuleSyntax && (getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS || importingFile.impliedNodeFormat === ModuleKind.CommonJS)) {
// TODO: if the exporting file is ESM under nodenext, or `forceImport` is given in a JS file, this is impossible
return ImportKind.CommonJS;
}
switch (exportKind) {
case ExportKind.Named: return ImportKind.Named;
case ExportKind.Default: return ImportKind.Default;
@@ -1072,7 +1087,7 @@ function getFixesInfoForNonUMDImport({ sourceFile, program, cancellationToken, h
const exportInfo = getExportInfos(symbolName, isJSXTagName(symbolToken), getMeaningFromLocation(symbolToken), cancellationToken, sourceFile, program, useAutoImportProvider, host, preferences);
return arrayFrom(
flatMapIterator(exportInfo.values(), exportInfos =>
getImportFixes(exportInfos, { symbolName, position: symbolToken.getStart(sourceFile) }, isValidTypeOnlyUseSite, useRequire, program, sourceFile, host, preferences).fixes),
getImportFixes(exportInfos, symbolToken.getStart(sourceFile), isValidTypeOnlyUseSite, useRequire, program, sourceFile, host, preferences).fixes),
fix => ({ fix, symbolName, errorIdentifierText: symbolToken.text, isJsxNamespaceFix: symbolName !== symbolToken.text })
);
});
@@ -1142,7 +1157,7 @@ function getExportInfos(
const compilerOptions = program.getCompilerOptions();
const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions);
if (defaultInfo && (defaultInfo.name === symbolName || moduleSymbolToValidIdentifier(moduleSymbol, getEmitScriptTarget(compilerOptions), isJsxTagName) === symbolName) && symbolHasMeaning(defaultInfo.symbolForMeaning, currentTokenMeaning)) {
if (defaultInfo && (defaultInfo.name === symbolName || moduleSymbolToValidIdentifier(moduleSymbol, getEmitScriptTarget(compilerOptions), isJsxTagName) === symbolName) && symbolHasMeaning(defaultInfo.resolvedSymbol, currentTokenMeaning)) {
addSymbol(moduleSymbol, sourceFile, defaultInfo.symbol, defaultInfo.exportKind, program, isFromPackageJson);
}
@@ -1216,12 +1231,17 @@ function codeActionForFixWorker(changes: textChanges.ChangeTracker, sourceFile:
: [Diagnostics.Update_import_from_0, moduleSpecifierWithoutQuotes];
}
case ImportFixKind.AddNew: {
const { importKind, moduleSpecifier, addAsTypeOnly, useRequire } = fix;
const { importKind, moduleSpecifier, addAsTypeOnly, useRequire, qualification } = fix;
const getDeclarations = useRequire ? getNewRequires : getNewImports;
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;
const namespaceLikeImport = importKind === ImportKind.Namespace || importKind === ImportKind.CommonJS
? { importKind, name: qualification?.namespacePrefix || symbolName, addAsTypeOnly }
: undefined;
insertImports(changes, sourceFile, getDeclarations(moduleSpecifier, quotePreference, defaultImport, namedImports, namespaceLikeImport), /*blankLineBetween*/ true, preferences);
if (qualification) {
addNamespaceQualifier(changes, sourceFile, qualification);
}
return includeSymbolNameInDescription
? [Diagnostics.Import_0_from_1, symbolName, moduleSpecifier]
: [Diagnostics.Add_import_from_0, moduleSpecifier];
@@ -1246,7 +1266,7 @@ function getModuleSpecifierText(promotedDeclaration: ImportClause | ImportEquals
function promoteFromTypeOnly(changes: textChanges.ChangeTracker, aliasDeclaration: TypeOnlyAliasDeclaration, compilerOptions: CompilerOptions, sourceFile: SourceFile, preferences: UserPreferences) {
// See comment in `doAddExistingFix` on constant with the same name.
const convertExistingToTypeOnly = compilerOptions.preserveValueImports && compilerOptions.isolatedModules;
const convertExistingToTypeOnly = compilerOptions.preserveValueImports && compilerOptions.isolatedModules || compilerOptions.verbatimModuleSyntax;
switch (aliasDeclaration.kind) {
case SyntaxKind.ImportSpecifier:
if (aliasDeclaration.isTypeOnly) {
@@ -1332,7 +1352,8 @@ function doAddExistingFix(
// 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;
const convertExistingToTypeOnly = promoteFromTypeOnly
&& (compilerOptions.preserveValueImports && compilerOptions.isolatedModules || compilerOptions.verbatimModuleSyntax);
if (defaultImport) {
Debug.assert(!clause.name, "Cannot add a default import to an import clause that already has one");
@@ -1421,11 +1442,11 @@ function doAddExistingFix(
}
}
function addNamespaceQualifier(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { namespacePrefix, position }: FixUseNamespaceImport): void {
changes.insertText(sourceFile, position, namespacePrefix + ".");
function addNamespaceQualifier(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { namespacePrefix, usagePosition }: Qualification): void {
changes.insertText(sourceFile, usagePosition, namespacePrefix + ".");
}
function addImportType(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { moduleSpecifier, position }: FixAddJsdocTypeImport, quotePreference: QuotePreference): void {
function addImportType(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { moduleSpecifier, usagePosition: position }: FixAddJsdocTypeImport, quotePreference: QuotePreference): void {
changes.insertText(sourceFile, position, getImportTypePrefix(moduleSpecifier, quotePreference));
}
+8 -9
View File
@@ -87,7 +87,6 @@ import {
getLineAndCharacterOfPosition,
getLineStartPositionForPosition,
getLocalSymbolForExportDefault,
getNameForExportedSymbol,
getNameOfDeclaration,
getNameTable,
getNewLineCharacter,
@@ -558,7 +557,7 @@ const enum KeywordCompletionFilters {
const enum GlobalsSearch { Continue, Success, Fail }
interface ModuleSpecifierResolutionContext {
tryResolve: (exportInfo: readonly SymbolExportInfo[], symbolName: string, isFromAmbientModule: boolean) => ModuleSpecifierResolutionResult;
tryResolve: (exportInfo: readonly SymbolExportInfo[], isFromAmbientModule: boolean) => ModuleSpecifierResolutionResult;
resolvedAny: () => boolean;
skippedAny: () => boolean;
resolvedBeyondLimit: () => boolean;
@@ -606,9 +605,9 @@ function resolvingModuleSpecifiers<TReturn>(
host.log?.(`${logPrefix}: ${timestamp() - start}`);
return result;
function tryResolve(exportInfo: readonly SymbolExportInfo[], symbolName: string, isFromAmbientModule: boolean): ModuleSpecifierResolutionResult {
function tryResolve(exportInfo: readonly SymbolExportInfo[], isFromAmbientModule: boolean): ModuleSpecifierResolutionResult {
if (isFromAmbientModule) {
const result = resolver.getModuleSpecifierForBestExportInfo(exportInfo, symbolName, position, isValidTypeOnlyUseSite);
const result = resolver.getModuleSpecifierForBestExportInfo(exportInfo, position, isValidTypeOnlyUseSite);
if (result) {
ambientCount++;
}
@@ -617,7 +616,7 @@ function resolvingModuleSpecifiers<TReturn>(
const shouldResolveModuleSpecifier = needsFullResolution || preferences.allowIncompleteCompletions && resolvedCount < moduleSpecifierResolutionLimit;
const shouldGetModuleSpecifierFromCache = !shouldResolveModuleSpecifier && preferences.allowIncompleteCompletions && cacheAttemptCount < moduleSpecifierResolutionCacheAttemptLimit;
const result = (shouldResolveModuleSpecifier || shouldGetModuleSpecifierFromCache)
? resolver.getModuleSpecifierForBestExportInfo(exportInfo, symbolName, position, isValidTypeOnlyUseSite, shouldGetModuleSpecifierFromCache)
? resolver.getModuleSpecifierForBestExportInfo(exportInfo, position, isValidTypeOnlyUseSite, shouldGetModuleSpecifierFromCache)
: undefined;
if (!shouldResolveModuleSpecifier && !shouldGetModuleSpecifierFromCache || shouldGetModuleSpecifierFromCache && !result) {
@@ -779,7 +778,7 @@ function continuePreviousIncompleteResponse(
const { origin } = Debug.checkDefined(getAutoImportSymbolFromCompletionEntryData(entry.name, entry.data, program, host));
const info = exportMap.get(file.path, entry.data.exportMapKey);
const result = info && context.tryResolve(info, entry.name, !isExternalModuleNameRelative(stripQuotes(origin.moduleSymbol.name)));
const result = info && context.tryResolve(info, !isExternalModuleNameRelative(stripQuotes(origin.moduleSymbol.name)));
if (result === "skipped") return entry;
if (!result || result === "failed") {
host.log?.(`Unexpected failure resolving auto import for '${entry.name}' from '${entry.source}'`);
@@ -2525,7 +2524,7 @@ function getCompletionEntryCodeActionsAndSourceDisplay(
targetSymbol,
moduleSymbol,
sourceFile,
getNameForExportedSymbol(symbol, getEmitScriptTarget(compilerOptions), isJsxOpeningTagName),
name,
isJsxOpeningTagName,
host,
program,
@@ -3196,7 +3195,7 @@ function getCompletionData(
moduleSymbol,
symbol: firstAccessibleSymbol,
targetFlags: skipAlias(firstAccessibleSymbol, typeChecker).flags,
}], firstAccessibleSymbol.name, position, isValidTypeOnlyAliasUseSite(location)) || {};
}], position, isValidTypeOnlyAliasUseSite(location)) || {};
if (moduleSpecifier) {
const origin: SymbolOriginInfoResolvedExport = {
@@ -3518,7 +3517,7 @@ function getCompletionData(
// N.B. in this resolution mode we always try to resolve module specifiers here,
// because we have to know now if it's going to fail so we can omit the completion
// from the list.
const result = context.tryResolve(info, symbolName, isFromAmbientModule) || {};
const result = context.tryResolve(info, isFromAmbientModule) || {};
if (result === "failed") return;
// If we skipped resolving module specifiers, our selection of which ExportInfo
+9 -8
View File
@@ -428,14 +428,14 @@ export function forEachExternalModuleToImportFrom(
}
function forEachExternalModule(checker: TypeChecker, allSourceFiles: readonly SourceFile[], excludePatterns: readonly RegExp[] | undefined, cb: (module: Symbol, sourceFile: SourceFile | undefined) => void) {
const isExcluded = (fileName: string) => excludePatterns?.some(p => p.test(fileName));
const isExcluded = excludePatterns && ((fileName: string) => excludePatterns.some(p => p.test(fileName)));
for (const ambient of checker.getAmbientModules()) {
if (!stringContains(ambient.name, "*") && !(excludePatterns && ambient.declarations?.every(d => isExcluded(d.getSourceFile().fileName)))) {
if (!stringContains(ambient.name, "*") && !(excludePatterns && ambient.declarations?.every(d => isExcluded!(d.getSourceFile().fileName)))) {
cb(ambient, /*sourceFile*/ undefined);
}
}
for (const sourceFile of allSourceFiles) {
if (isExternalOrCommonJsModule(sourceFile) && !isExcluded(sourceFile.fileName)) {
if (isExternalOrCommonJsModule(sourceFile) && !isExcluded?.(sourceFile.fileName)) {
cb(checker.getMergedSymbol(sourceFile.symbol), sourceFile);
}
}
@@ -526,12 +526,13 @@ function getDefaultLikeExportWorker(moduleSymbol: Symbol, checker: TypeChecker):
if (defaultExport) return { symbol: defaultExport, exportKind: ExportKind.Default };
}
function getDefaultExportInfoWorker(defaultExport: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions): { readonly symbolForMeaning: Symbol, readonly name: string } | undefined {
/** @internal */
export function getDefaultExportInfoWorker(defaultExport: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions): { readonly resolvedSymbol: Symbol, readonly name: string } | undefined {
const localSymbol = getLocalSymbolForExportDefault(defaultExport);
if (localSymbol) return { symbolForMeaning: localSymbol, name: localSymbol.name };
if (localSymbol) return { resolvedSymbol: localSymbol, name: localSymbol.name };
const name = getNameForExportDefault(defaultExport);
if (name !== undefined) return { symbolForMeaning: defaultExport, name };
if (name !== undefined) return { resolvedSymbol: defaultExport, name };
if (defaultExport.flags & SymbolFlags.Alias) {
const aliased = checker.getImmediateAliasedSymbol(defaultExport);
@@ -546,9 +547,9 @@ function getDefaultExportInfoWorker(defaultExport: Symbol, checker: TypeChecker,
if (defaultExport.escapedName !== InternalSymbolName.Default &&
defaultExport.escapedName !== InternalSymbolName.ExportEquals) {
return { symbolForMeaning: defaultExport, name: defaultExport.getName() };
return { resolvedSymbol: defaultExport, name: defaultExport.getName() };
}
return { symbolForMeaning: defaultExport, name: getNameForExportedSymbol(defaultExport, compilerOptions.target) };
return { resolvedSymbol: defaultExport, name: getNameForExportedSymbol(defaultExport, compilerOptions.target) };
}
function getNameForExportDefault(symbol: Symbol): string | undefined {