fix(55994): Type-check Import Attributes in static imports (#56034)

This commit is contained in:
Oleksandr T
2024-01-10 15:35:04 -08:00
committed by GitHub
parent 9999f26483
commit 72d497352f
16 changed files with 678 additions and 4 deletions
+46
View File
@@ -325,6 +325,7 @@ import {
getModeForUsageLocation,
getModifiers,
getModuleInstanceState,
getNameFromImportAttribute,
getNameFromIndexInfo,
getNameOfDeclaration,
getNameOfExpando,
@@ -407,6 +408,7 @@ import {
IdentifierTypePredicate,
idText,
IfStatement,
ImportAttribute,
ImportAttributes,
ImportCall,
ImportClause,
@@ -553,6 +555,7 @@ import {
isIdentifierTypePredicate,
isIdentifierTypeReference,
isIfStatement,
isImportAttributes,
isImportCall,
isImportClause,
isImportDeclaration,
@@ -2179,6 +2182,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
var deferredGlobalImportMetaType: ObjectType;
var deferredGlobalImportMetaExpressionType: ObjectType;
var deferredGlobalImportCallOptionsType: ObjectType | undefined;
var deferredGlobalImportAttributesType: ObjectType | undefined;
var deferredGlobalDisposableType: ObjectType | undefined;
var deferredGlobalAsyncDisposableType: ObjectType | undefined;
var deferredGlobalExtractSymbol: Symbol | undefined;
@@ -11555,6 +11559,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return widenTypeForVariableLikeDeclaration(getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true, CheckMode.Normal), declaration, reportErrors);
}
function getTypeFromImportAttributes(node: ImportAttributes): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
const symbol = createSymbol(SymbolFlags.ObjectLiteral, InternalSymbolName.ImportAttributes);
const members = createSymbolTable();
forEach(node.elements, attr => {
const member = createSymbol(SymbolFlags.Property, getNameFromImportAttribute(attr));
member.parent = symbol;
member.links.type = checkImportAttribute(attr);
member.links.target = member;
members.set(member.escapedName, member);
});
const type = createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray);
type.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.NonInferrableType;
links.resolvedType = type;
}
return links.resolvedType;
}
function isGlobalSymbolConstructor(node: Node) {
const symbol = getSymbolOfNode(node);
const globalSymbol = getGlobalESSymbolConstructorTypeSymbol(/*reportErrors*/ false);
@@ -16417,6 +16440,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return (deferredGlobalImportCallOptionsType ||= getGlobalType("ImportCallOptions" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType;
}
function getGlobalImportAttributesType(reportErrors: boolean) {
return (deferredGlobalImportAttributesType ||= getGlobalType("ImportAttributes" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType;
}
function getGlobalESSymbolConstructorSymbol(reportErrors: boolean): Symbol | undefined {
return deferredGlobalESSymbolConstructorSymbol ||= getGlobalValueSymbol("Symbol" as __String, reportErrors);
}
@@ -30904,6 +30931,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
case SyntaxKind.JsxOpeningElement:
case SyntaxKind.JsxSelfClosingElement:
return getContextualJsxElementAttributesType(parent as JsxOpeningLikeElement, contextFlags);
case SyntaxKind.ImportAttribute:
return getContextualImportAttributeType(parent as ImportAttribute);
}
return undefined;
}
@@ -30950,6 +30979,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
function getContextualImportAttributeType(node: ImportAttribute) {
return getTypeOfPropertyOfContextualType(getGlobalImportAttributesType(/*reportErrors*/ false), getNameFromImportAttribute(node));
}
function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement, contextFlags: ContextFlags | undefined) {
if (isJsxOpeningElement(node) && contextFlags !== ContextFlags.Completions) {
const index = findContextualNode(node.parent, /*includeCaches*/ !contextFlags);
@@ -45991,6 +46024,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function checkImportAttributes(declaration: ImportDeclaration | ExportDeclaration) {
const node = declaration.attributes;
if (node) {
const importAttributesType = getGlobalImportAttributesType(/*reportErrors*/ true);
if (importAttributesType !== emptyObjectType) {
checkTypeAssignableTo(getTypeFromImportAttributes(node), getNullableType(importAttributesType, TypeFlags.Undefined), node);
}
const validForTypeAttributes = isExclusivelyTypeOnlyImportOrExport(declaration);
const override = getResolutionModeOverride(node, validForTypeAttributes ? grammarErrorOnNode : undefined);
const isImportAttributes = declaration.attributes.token === SyntaxKind.WithKeyword;
@@ -46020,6 +46058,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
function checkImportAttribute(node: ImportAttribute) {
return getRegularTypeOfLiteralType(checkExpressionCached(node.value));
}
function checkImportDeclaration(node: ImportDeclaration) {
if (checkGrammarModuleElementContext(node, isInJSFile(node) ? Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_module : Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_namespace_or_module)) {
// If we hit an import declaration in an illegal context, just bail out to avoid cascading errors.
@@ -47700,6 +47742,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return checkMetaPropertyKeyword(node.parent);
}
if (isImportAttributes(node)) {
return getGlobalImportAttributesType(/*reportErrors*/ false);
}
return errorType;
}
+1
View File
@@ -5971,6 +5971,7 @@ export const enum InternalSymbolName {
Default = "default", // Default export symbol (technically not wholly internal, but included here for usability)
This = "this",
InstantiationExpression = "__instantiationExpression", // Instantiation expressions
ImportAttributes = "__importAttributes",
}
/**
+6
View File
@@ -220,6 +220,7 @@ import {
idText,
IfStatement,
ignoredPaths,
ImportAttribute,
ImportCall,
ImportClause,
ImportDeclaration,
@@ -10656,3 +10657,8 @@ export function replaceFirstStar(s: string, replacement: string): string {
// Attempt to defeat this analysis by indirectly calling the method.
return stringReplace.call(s, "*", replacement);
}
/** @internal */
export function getNameFromImportAttribute(node: ImportAttribute) {
return isIdentifier(node.name) ? node.name.escapedText : escapeLeadingUnderscores(node.name.text);
}
+7 -1
View File
@@ -2448,7 +2448,13 @@ export class TestState {
const annotations = this.annotateContentWithTooltips(
result,
"completions",
item => item.optionalReplacementSpan,
item => {
if (item.optionalReplacementSpan) {
const { start, length } = item.optionalReplacementSpan;
return start && length === 0 ? { start, length: 1 } : item.optionalReplacementSpan;
}
return undefined;
},
item =>
item.entries?.flatMap(
entry =>
+18
View File
@@ -95,6 +95,7 @@ import {
getLineStartPositionForPosition,
getLocalSymbolForExportDefault,
getModifiers,
getNameFromImportAttribute,
getNameOfDeclaration,
getNameTable,
getNewLineCharacter,
@@ -169,6 +170,7 @@ import {
isIdentifier,
isIdentifierText,
isImportableFile,
isImportAttributes,
isImportDeclaration,
isImportEqualsDeclaration,
isImportKeyword,
@@ -3768,6 +3770,7 @@ function getCompletionData(
|| tryGetObjectLikeCompletionSymbols()
|| tryGetImportCompletionSymbols()
|| tryGetImportOrExportClauseCompletionSymbols()
|| tryGetImportAttributesCompletionSymbols()
|| tryGetLocalNamedExportCompletionSymbols()
|| tryGetConstructorCompletion()
|| tryGetClassLikeCompletionSymbols()
@@ -4455,6 +4458,21 @@ function getCompletionData(
return GlobalsSearch.Success;
}
/**
* import { x } from "foo" with { | }
*/
function tryGetImportAttributesCompletionSymbols(): GlobalsSearch {
if (contextToken === undefined) return GlobalsSearch.Continue;
const importAttributes = contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken ? tryCast(contextToken.parent, isImportAttributes) :
contextToken.kind === SyntaxKind.ColonToken ? tryCast(contextToken.parent.parent, isImportAttributes) : undefined;
if (importAttributes === undefined) return GlobalsSearch.Continue;
const existing = new Set(importAttributes.elements.map(getNameFromImportAttribute));
symbols = filter(typeChecker.getTypeAtLocation(importAttributes).getApparentProperties(), attr => !existing.has(attr.escapedName));
return GlobalsSearch.Success;
}
/**
* Adds local declarations for completions in named exports:
*