mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-12-11 09:24:19 -06:00
1331 lines
68 KiB
TypeScript
1331 lines
68 KiB
TypeScript
import {
|
|
AccessorDeclaration,
|
|
AllAccessorDeclarations,
|
|
ArrayLiteralExpression,
|
|
ArrowFunction,
|
|
AsExpression,
|
|
BinaryExpression,
|
|
ClassExpression,
|
|
CompilerOptions,
|
|
ConditionalTypeNode,
|
|
countWhere,
|
|
Debug,
|
|
Declaration,
|
|
ElementAccessExpression,
|
|
EmitFlags,
|
|
Expression,
|
|
factory,
|
|
findAncestor,
|
|
forEachReturnStatement,
|
|
FunctionExpression,
|
|
FunctionFlags,
|
|
FunctionLikeDeclaration,
|
|
GetAccessorDeclaration,
|
|
getEffectiveReturnTypeNode,
|
|
getEffectiveSetAccessorTypeAnnotationNode,
|
|
getEffectiveTypeAnnotationNode,
|
|
getEmitFlags,
|
|
getEmitScriptTarget,
|
|
getFunctionFlags,
|
|
getJSDocType,
|
|
getJSDocTypeAssertionType,
|
|
getSourceFileOfNode,
|
|
getStrictOptionValue,
|
|
hasDynamicName,
|
|
HasInferredType,
|
|
Identifier,
|
|
ImportTypeNode,
|
|
IndexedAccessTypeNode,
|
|
IntersectionTypeNode,
|
|
IntroducesNewScopeNode,
|
|
isAsExpression,
|
|
isBlock,
|
|
isCallExpression,
|
|
isComputedPropertyName,
|
|
isConditionalTypeNode,
|
|
isConstTypeReference,
|
|
isDeclaration,
|
|
isDeclarationReadonly,
|
|
isEntityName,
|
|
isEntityNameExpression,
|
|
isExpressionWithTypeArguments,
|
|
isFunctionLike,
|
|
isFunctionLikeDeclaration,
|
|
isGetAccessor,
|
|
isIdentifier,
|
|
isIdentifierText,
|
|
isImportAttributes,
|
|
isImportTypeNode,
|
|
isIndexedAccessTypeNode,
|
|
isInJSFile,
|
|
isJSDocAllType,
|
|
isJSDocConstructSignature,
|
|
isJSDocFunctionType,
|
|
isJSDocIndexSignature,
|
|
isJSDocNonNullableType,
|
|
isJSDocNullableType,
|
|
isJSDocOptionalType,
|
|
isJSDocTypeAssertion,
|
|
isJSDocTypeExpression,
|
|
isJSDocTypeLiteral,
|
|
isJSDocUnknownType,
|
|
isJSDocVariadicType,
|
|
isJsxElement,
|
|
isJsxExpression,
|
|
isKeyword,
|
|
isLiteralImportTypeNode,
|
|
isLiteralTypeNode,
|
|
isMappedTypeNode,
|
|
isModifier,
|
|
isNamedDeclaration,
|
|
isNewScopeNode,
|
|
isOptionalDeclaration,
|
|
isParameter,
|
|
isPrimitiveLiteralValue,
|
|
isPropertyDeclaration,
|
|
isPropertySignature,
|
|
isShorthandPropertyAssignment,
|
|
isSpreadAssignment,
|
|
isStringLiteral,
|
|
isThisTypeNode,
|
|
isTupleTypeNode,
|
|
isTypeAssertionExpression,
|
|
isTypeLiteralNode,
|
|
isTypeNode,
|
|
isTypeOperatorNode,
|
|
isTypeParameterDeclaration,
|
|
isTypePredicateNode,
|
|
isTypeQueryNode,
|
|
isTypeReferenceNode,
|
|
isUnionTypeNode,
|
|
isValueSignatureDeclaration,
|
|
isVarConstLike,
|
|
isVariableDeclaration,
|
|
JSDocParameterTag,
|
|
JSDocPropertyTag,
|
|
JSDocSignature,
|
|
JsxAttributeValue,
|
|
KeywordTypeSyntaxKind,
|
|
map,
|
|
mapDefined,
|
|
MethodDeclaration,
|
|
Mutable,
|
|
Node,
|
|
NodeArray,
|
|
NodeBuilderFlags,
|
|
NodeFlags,
|
|
nodeIsMissing,
|
|
NoSubstitutionTemplateLiteral,
|
|
ObjectLiteralExpression,
|
|
ParameterDeclaration,
|
|
ParenthesizedExpression,
|
|
ParenthesizedTypeNode,
|
|
PrefixUnaryExpression,
|
|
PrimitiveLiteral,
|
|
PropertyAccessExpression,
|
|
PropertyAssignment,
|
|
PropertyDeclaration,
|
|
PropertyName,
|
|
PropertySignature,
|
|
SetAccessorDeclaration,
|
|
setCommentRange,
|
|
setEmitFlags,
|
|
setOriginalNode,
|
|
setTextRangePosEnd,
|
|
ShorthandPropertyAssignment,
|
|
SignatureDeclaration,
|
|
skipTypeParentheses,
|
|
StringLiteral,
|
|
Symbol,
|
|
SyntacticNodeBuilder,
|
|
SyntacticTypeNodeBuilderContext,
|
|
SyntacticTypeNodeBuilderResolver,
|
|
SyntaxKind,
|
|
TypeAssertion,
|
|
TypeElement,
|
|
TypeNode,
|
|
TypeOperatorNode,
|
|
TypeParameterDeclaration,
|
|
TypeQueryNode,
|
|
TypeReferenceNode,
|
|
UnionTypeNode,
|
|
VariableDeclaration,
|
|
visitEachChild as visitEachChildWorker,
|
|
visitNode,
|
|
visitNodes,
|
|
Visitor,
|
|
walkUpParenthesizedExpressions,
|
|
} from "./_namespaces/ts.js";
|
|
|
|
type SyntacticResult =
|
|
| { type: TypeNode; reportFallback: undefined; }
|
|
| { type: undefined; reportFallback: true; }
|
|
| { type: undefined; reportFallback: false; };
|
|
function syntacticResult(type: TypeNode | undefined): SyntacticResult;
|
|
function syntacticResult(type: undefined, reportFallback: boolean): SyntacticResult;
|
|
function syntacticResult(type: TypeNode | undefined, reportFallback: boolean = true) {
|
|
return { type, reportFallback } as SyntacticResult;
|
|
}
|
|
const notImplemented: SyntacticResult = syntacticResult(/*type*/ undefined, /*reportFallback*/ false);
|
|
const alreadyReported: SyntacticResult = syntacticResult(/*type*/ undefined, /*reportFallback*/ false);
|
|
const failed: SyntacticResult = syntacticResult(/*type*/ undefined, /*reportFallback*/ true);
|
|
|
|
/** @internal */
|
|
export function createSyntacticTypeNodeBuilder(
|
|
options: CompilerOptions,
|
|
resolver: SyntacticTypeNodeBuilderResolver,
|
|
): SyntacticNodeBuilder {
|
|
const strictNullChecks = getStrictOptionValue(options, "strictNullChecks");
|
|
|
|
return {
|
|
serializeTypeOfDeclaration,
|
|
serializeReturnTypeForSignature,
|
|
serializeTypeOfExpression,
|
|
serializeTypeOfAccessor,
|
|
tryReuseExistingTypeNode(context: SyntacticTypeNodeBuilderContext, existing: TypeNode): TypeNode | undefined {
|
|
if (!resolver.canReuseTypeNode(context, existing)) {
|
|
return undefined;
|
|
}
|
|
return tryReuseExistingTypeNode(context, existing);
|
|
},
|
|
};
|
|
|
|
function reuseNode<T extends Node>(context: SyntacticTypeNodeBuilderContext, node: T, range?: Node): T;
|
|
function reuseNode<T extends Node>(context: SyntacticTypeNodeBuilderContext, node: T | undefined, range?: Node): T | undefined;
|
|
function reuseNode<T extends Node>(context: SyntacticTypeNodeBuilderContext, node: T | undefined, range: Node | undefined = node) {
|
|
return node === undefined ? undefined : resolver.markNodeReuse(context, node.flags & NodeFlags.Synthesized ? node : factory.cloneNode(node), range ?? node);
|
|
}
|
|
function tryReuseExistingTypeNode(context: SyntacticTypeNodeBuilderContext, existing: TypeNode): TypeNode | undefined {
|
|
const { finalizeBoundary, startRecoveryScope, hadError, markError } = resolver.createRecoveryBoundary(context);
|
|
const transformed = visitNode(existing, visitExistingNodeTreeSymbols, isTypeNode);
|
|
if (!finalizeBoundary()) {
|
|
return undefined;
|
|
}
|
|
context.approximateLength += existing.end - existing.pos;
|
|
return transformed;
|
|
|
|
function visitExistingNodeTreeSymbols(node: Node): Node | undefined {
|
|
// If there was an error in a sibling node bail early, the result will be discarded anyway
|
|
if (hadError()) return node;
|
|
const recover = startRecoveryScope();
|
|
|
|
const onExitNewScope = isNewScopeNode(node) ? resolver.enterNewScope(context, node) : undefined;
|
|
const result = visitExistingNodeTreeSymbolsWorker(node);
|
|
onExitNewScope?.();
|
|
|
|
// If there was an error, maybe we can recover by serializing the actual type of the node
|
|
if (hadError()) {
|
|
if (isTypeNode(node) && !isTypePredicateNode(node)) {
|
|
recover();
|
|
return resolver.serializeExistingTypeNode(context, node);
|
|
}
|
|
return node;
|
|
}
|
|
// We want to clone the subtree, so when we mark it up with __pos and __end in quickfixes,
|
|
// we don't get odd behavior because of reused nodes. We also need to clone to _remove_
|
|
// the position information if the node comes from a different file than the one the node builder
|
|
// is set to build for (even though we are reusing the node structure, the position information
|
|
// would make the printer print invalid spans for literals and identifiers, and the formatter would
|
|
// choke on the mismatched positonal spans between a parent and an injected child from another file).
|
|
return result ? resolver.markNodeReuse(context, result, node) : undefined;
|
|
}
|
|
|
|
function tryVisitSimpleTypeNode(node: TypeNode): TypeNode | undefined {
|
|
const innerNode = skipTypeParentheses(node);
|
|
switch (innerNode.kind) {
|
|
case SyntaxKind.TypeReference:
|
|
return tryVisitTypeReference(innerNode as TypeReferenceNode);
|
|
case SyntaxKind.TypeQuery:
|
|
return tryVisitTypeQuery(innerNode as TypeQueryNode);
|
|
case SyntaxKind.IndexedAccessType:
|
|
return tryVisitIndexedAccess(innerNode as IndexedAccessTypeNode);
|
|
case SyntaxKind.TypeOperator:
|
|
const typeOperatorNode = innerNode as TypeOperatorNode;
|
|
if (typeOperatorNode.operator === SyntaxKind.KeyOfKeyword) {
|
|
return tryVisitKeyOf(typeOperatorNode);
|
|
}
|
|
}
|
|
return visitNode(node, visitExistingNodeTreeSymbols, isTypeNode);
|
|
}
|
|
|
|
function tryVisitIndexedAccess(node: IndexedAccessTypeNode): TypeNode | undefined {
|
|
const resultObjectType = tryVisitSimpleTypeNode(node.objectType);
|
|
if (resultObjectType === undefined) {
|
|
return undefined;
|
|
}
|
|
return factory.updateIndexedAccessTypeNode(node, resultObjectType, visitNode(node.indexType, visitExistingNodeTreeSymbols, isTypeNode)!);
|
|
}
|
|
|
|
function tryVisitKeyOf(node: TypeOperatorNode): TypeNode | undefined {
|
|
Debug.assertEqual(node.operator, SyntaxKind.KeyOfKeyword);
|
|
const type = tryVisitSimpleTypeNode(node.type);
|
|
if (type === undefined) {
|
|
return undefined;
|
|
}
|
|
return factory.updateTypeOperatorNode(node, type);
|
|
}
|
|
|
|
function tryVisitTypeQuery(node: TypeQueryNode): TypeNode | undefined {
|
|
const { introducesError, node: exprName } = resolver.trackExistingEntityName(context, node.exprName);
|
|
if (!introducesError) {
|
|
return factory.updateTypeQueryNode(
|
|
node,
|
|
exprName,
|
|
visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode),
|
|
);
|
|
}
|
|
|
|
const serializedName = resolver.serializeTypeName(context, node.exprName, /*isTypeOf*/ true);
|
|
if (serializedName) {
|
|
return resolver.markNodeReuse(context, serializedName, node.exprName);
|
|
}
|
|
}
|
|
|
|
function tryVisitTypeReference(node: TypeReferenceNode): TypeNode | undefined {
|
|
if (resolver.canReuseTypeNode(context, node)) {
|
|
const { introducesError, node: newName } = resolver.trackExistingEntityName(context, node.typeName);
|
|
const typeArguments = visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode);
|
|
|
|
if (!introducesError) {
|
|
const updated = factory.updateTypeReferenceNode(
|
|
node,
|
|
newName,
|
|
typeArguments,
|
|
);
|
|
return resolver.markNodeReuse(context, updated, node);
|
|
}
|
|
else {
|
|
const serializedName = resolver.serializeTypeName(context, node.typeName, /*isTypeOf*/ false, typeArguments);
|
|
if (serializedName) {
|
|
return resolver.markNodeReuse(context, serializedName, node.typeName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function visitExistingNodeTreeSymbolsWorker(node: Node): Node | undefined {
|
|
if (isJSDocTypeExpression(node)) {
|
|
// Unwrap JSDocTypeExpressions
|
|
return visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode);
|
|
}
|
|
// We don't _actually_ support jsdoc namepath types, emit `any` instead
|
|
if (isJSDocAllType(node) || node.kind === SyntaxKind.JSDocNamepathType) {
|
|
return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
|
}
|
|
if (isJSDocUnknownType(node)) {
|
|
return factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword);
|
|
}
|
|
if (isJSDocNullableType(node)) {
|
|
return factory.createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode)!, factory.createLiteralTypeNode(factory.createNull())]);
|
|
}
|
|
if (isJSDocOptionalType(node)) {
|
|
return factory.createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode)!, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]);
|
|
}
|
|
if (isJSDocNonNullableType(node)) {
|
|
return visitNode(node.type, visitExistingNodeTreeSymbols);
|
|
}
|
|
if (isJSDocVariadicType(node)) {
|
|
return factory.createArrayTypeNode(visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode)!);
|
|
}
|
|
if (isJSDocTypeLiteral(node)) {
|
|
return factory.createTypeLiteralNode(map(node.jsDocPropertyTags, t => {
|
|
const name = visitNode(isIdentifier(t.name) ? t.name : t.name.right, visitExistingNodeTreeSymbols, isIdentifier)!;
|
|
const overrideTypeNode = resolver.getJsDocPropertyOverride(context, node, t);
|
|
|
|
return factory.createPropertySignature(
|
|
/*modifiers*/ undefined,
|
|
name,
|
|
t.isBracketed || t.typeExpression && isJSDocOptionalType(t.typeExpression.type) ? factory.createToken(SyntaxKind.QuestionToken) : undefined,
|
|
overrideTypeNode || (t.typeExpression && visitNode(t.typeExpression.type, visitExistingNodeTreeSymbols, isTypeNode)) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword),
|
|
);
|
|
}));
|
|
}
|
|
if (isTypeReferenceNode(node) && isIdentifier(node.typeName) && node.typeName.escapedText === "") {
|
|
return setOriginalNode(factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), node);
|
|
}
|
|
if ((isExpressionWithTypeArguments(node) || isTypeReferenceNode(node)) && isJSDocIndexSignature(node)) {
|
|
return factory.createTypeLiteralNode([factory.createIndexSignature(
|
|
/*modifiers*/ undefined,
|
|
[factory.createParameterDeclaration(
|
|
/*modifiers*/ undefined,
|
|
/*dotDotDotToken*/ undefined,
|
|
"x",
|
|
/*questionToken*/ undefined,
|
|
visitNode(node.typeArguments![0], visitExistingNodeTreeSymbols, isTypeNode),
|
|
)],
|
|
visitNode(node.typeArguments![1], visitExistingNodeTreeSymbols, isTypeNode),
|
|
)]);
|
|
}
|
|
if (isJSDocFunctionType(node)) {
|
|
if (isJSDocConstructSignature(node)) {
|
|
let newTypeNode: TypeNode | undefined;
|
|
return factory.createConstructorTypeNode(
|
|
/*modifiers*/ undefined,
|
|
visitNodes(node.typeParameters, visitExistingNodeTreeSymbols, isTypeParameterDeclaration),
|
|
mapDefined(node.parameters, (p, i) =>
|
|
p.name && isIdentifier(p.name) && p.name.escapedText === "new" ? (newTypeNode = p.type, undefined) : factory.createParameterDeclaration(
|
|
/*modifiers*/ undefined,
|
|
getEffectiveDotDotDotForParameter(p),
|
|
resolver.markNodeReuse(context, factory.createIdentifier(getNameForJSDocFunctionParameter(p, i)), p),
|
|
factory.cloneNode(p.questionToken),
|
|
visitNode(p.type, visitExistingNodeTreeSymbols, isTypeNode),
|
|
/*initializer*/ undefined,
|
|
)),
|
|
visitNode(newTypeNode || node.type, visitExistingNodeTreeSymbols, isTypeNode) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword),
|
|
);
|
|
}
|
|
else {
|
|
return factory.createFunctionTypeNode(
|
|
visitNodes(node.typeParameters, visitExistingNodeTreeSymbols, isTypeParameterDeclaration),
|
|
map(node.parameters, (p, i) =>
|
|
factory.createParameterDeclaration(
|
|
/*modifiers*/ undefined,
|
|
getEffectiveDotDotDotForParameter(p),
|
|
resolver.markNodeReuse(context, factory.createIdentifier(getNameForJSDocFunctionParameter(p, i)), p),
|
|
factory.cloneNode(p.questionToken),
|
|
visitNode(p.type, visitExistingNodeTreeSymbols, isTypeNode),
|
|
/*initializer*/ undefined,
|
|
)),
|
|
visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword),
|
|
);
|
|
}
|
|
}
|
|
if (isThisTypeNode(node)) {
|
|
if (resolver.canReuseTypeNode(context, node)) {
|
|
return node;
|
|
}
|
|
markError();
|
|
return node;
|
|
}
|
|
if (isTypeParameterDeclaration(node)) {
|
|
const { node: newName } = resolver.trackExistingEntityName(context, node.name);
|
|
return factory.updateTypeParameterDeclaration(
|
|
node,
|
|
visitNodes(node.modifiers, visitExistingNodeTreeSymbols, isModifier),
|
|
// resolver.markNodeReuse(context, typeParameterToName(getDeclaredTypeOfSymbol(getSymbolOfDeclaration(node)), context), node),
|
|
newName,
|
|
visitNode(node.constraint, visitExistingNodeTreeSymbols, isTypeNode),
|
|
visitNode(node.default, visitExistingNodeTreeSymbols, isTypeNode),
|
|
);
|
|
}
|
|
if (isIndexedAccessTypeNode(node)) {
|
|
const result = tryVisitIndexedAccess(node);
|
|
if (!result) {
|
|
markError();
|
|
return node;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
if (isTypeReferenceNode(node)) {
|
|
const result = tryVisitTypeReference(node);
|
|
if (result) {
|
|
return result;
|
|
}
|
|
markError();
|
|
return node;
|
|
}
|
|
if (isLiteralImportTypeNode(node)) {
|
|
// assert keyword in imported attributes is deprecated, so we don't reuse types that contain it
|
|
// Ex: import("pkg", { assert: {} }
|
|
if (node.attributes?.token === SyntaxKind.AssertKeyword) {
|
|
markError();
|
|
return node;
|
|
}
|
|
if (!resolver.canReuseTypeNode(context, node)) {
|
|
return resolver.serializeExistingTypeNode(context, node);
|
|
}
|
|
const specifier = rewriteModuleSpecifier(node, node.argument.literal);
|
|
const literal = specifier === node.argument.literal ? reuseNode(context, node.argument.literal) : specifier;
|
|
return factory.updateImportTypeNode(
|
|
node,
|
|
literal === node.argument.literal ? reuseNode(context, node.argument) : factory.createLiteralTypeNode(literal),
|
|
visitNode(node.attributes, visitExistingNodeTreeSymbols, isImportAttributes),
|
|
visitNode(node.qualifier, visitExistingNodeTreeSymbols, isEntityName),
|
|
visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode),
|
|
node.isTypeOf,
|
|
);
|
|
}
|
|
if (isNamedDeclaration(node) && node.name.kind === SyntaxKind.ComputedPropertyName && !resolver.hasLateBindableName(node)) {
|
|
if (!hasDynamicName(node)) {
|
|
return visitEachChild(node, visitExistingNodeTreeSymbols);
|
|
}
|
|
if (resolver.shouldRemoveDeclaration(context, node)) {
|
|
return undefined;
|
|
}
|
|
}
|
|
if (
|
|
(isFunctionLike(node) && !node.type)
|
|
|| (isPropertyDeclaration(node) && !node.type && !node.initializer)
|
|
|| (isPropertySignature(node) && !node.type && !node.initializer)
|
|
|| (isParameter(node) && !node.type && !node.initializer)
|
|
) {
|
|
let visited = visitEachChild(node, visitExistingNodeTreeSymbols);
|
|
if (visited === node) {
|
|
visited = resolver.markNodeReuse(context, factory.cloneNode(node), node);
|
|
}
|
|
(visited as Mutable<typeof visited>).type = factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
|
if (isParameter(node)) {
|
|
(visited as Mutable<ParameterDeclaration>).modifiers = undefined;
|
|
}
|
|
return visited;
|
|
}
|
|
if (isTypeQueryNode(node)) {
|
|
const result = tryVisitTypeQuery(node);
|
|
if (!result) {
|
|
markError();
|
|
return node;
|
|
}
|
|
return result;
|
|
}
|
|
if (isComputedPropertyName(node) && isEntityNameExpression(node.expression)) {
|
|
const { node: result, introducesError } = resolver.trackExistingEntityName(context, node.expression);
|
|
if (!introducesError) {
|
|
return factory.updateComputedPropertyName(node, result);
|
|
}
|
|
else {
|
|
const computedPropertyNameType = resolver.serializeTypeOfExpression(context, node.expression);
|
|
let literal;
|
|
if (isLiteralTypeNode(computedPropertyNameType)) {
|
|
literal = computedPropertyNameType.literal;
|
|
}
|
|
else {
|
|
const evaluated = resolver.evaluateEntityNameExpression(node.expression);
|
|
const literalNode = typeof evaluated.value === "string" ? factory.createStringLiteral(evaluated.value, /*isSingleQuote*/ undefined) :
|
|
typeof evaluated.value === "number" ? factory.createNumericLiteral(evaluated.value, /*numericLiteralFlags*/ 0) :
|
|
undefined;
|
|
if (!literalNode) {
|
|
if (isImportTypeNode(computedPropertyNameType)) {
|
|
resolver.trackComputedName(context, node.expression);
|
|
}
|
|
return node;
|
|
}
|
|
literal = literalNode;
|
|
}
|
|
if (literal.kind === SyntaxKind.StringLiteral && isIdentifierText(literal.text, getEmitScriptTarget(options))) {
|
|
return factory.createIdentifier(literal.text);
|
|
}
|
|
if (literal.kind === SyntaxKind.NumericLiteral && !literal.text.startsWith("-")) {
|
|
return literal;
|
|
}
|
|
return factory.updateComputedPropertyName(node, literal);
|
|
}
|
|
}
|
|
if (isTypePredicateNode(node)) {
|
|
let parameterName;
|
|
if (isIdentifier(node.parameterName)) {
|
|
const { node: result, introducesError } = resolver.trackExistingEntityName(context, node.parameterName);
|
|
// Should not usually happen the only case is when a type predicate comes from a JSDoc type annotation with it's own parameter symbol definition.
|
|
// /** @type {(v: unknown) => v is undefined} */
|
|
// const isUndef = v => v === undefined;
|
|
if (introducesError) markError();
|
|
parameterName = result;
|
|
}
|
|
else {
|
|
parameterName = factory.cloneNode(node.parameterName);
|
|
}
|
|
return factory.updateTypePredicateNode(node, factory.cloneNode(node.assertsModifier), parameterName, visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode));
|
|
}
|
|
|
|
if (isTupleTypeNode(node) || isTypeLiteralNode(node) || isMappedTypeNode(node)) {
|
|
const visited = visitEachChild(node, visitExistingNodeTreeSymbols);
|
|
const clone = resolver.markNodeReuse(context, visited === node ? factory.cloneNode(node) : visited, node);
|
|
const flags = getEmitFlags(clone);
|
|
setEmitFlags(clone, flags | (context.flags & NodeBuilderFlags.MultilineObjectLiterals && isTypeLiteralNode(node) ? 0 : EmitFlags.SingleLine));
|
|
return clone;
|
|
}
|
|
if (isStringLiteral(node) && !!(context.flags & NodeBuilderFlags.UseSingleQuotesForStringLiteralType) && !node.singleQuote) {
|
|
const clone = factory.cloneNode(node);
|
|
(clone as Mutable<typeof clone>).singleQuote = true;
|
|
return clone;
|
|
}
|
|
if (isConditionalTypeNode(node)) {
|
|
const checkType = visitNode(node.checkType, visitExistingNodeTreeSymbols, isTypeNode)!;
|
|
|
|
const disposeScope = resolver.enterNewScope(context, node);
|
|
const extendType = visitNode(node.extendsType, visitExistingNodeTreeSymbols, isTypeNode)!;
|
|
const trueType = visitNode(node.trueType, visitExistingNodeTreeSymbols, isTypeNode)!;
|
|
disposeScope();
|
|
const falseType = visitNode(node.falseType, visitExistingNodeTreeSymbols, isTypeNode)!;
|
|
return factory.updateConditionalTypeNode(
|
|
node,
|
|
checkType,
|
|
extendType,
|
|
trueType,
|
|
falseType,
|
|
);
|
|
}
|
|
|
|
if (isTypeOperatorNode(node)) {
|
|
if (node.operator === SyntaxKind.UniqueKeyword && node.type.kind === SyntaxKind.SymbolKeyword) {
|
|
if (!resolver.canReuseTypeNode(context, node)) {
|
|
markError();
|
|
return node;
|
|
}
|
|
}
|
|
else if (node.operator === SyntaxKind.KeyOfKeyword) {
|
|
const result = tryVisitKeyOf(node);
|
|
if (!result) {
|
|
markError();
|
|
return node;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return visitEachChild(node, visitExistingNodeTreeSymbols);
|
|
|
|
function visitEachChild<T extends Node>(node: T, visitor: Visitor): T;
|
|
function visitEachChild<T extends Node>(node: T | undefined, visitor: Visitor): T | undefined;
|
|
function visitEachChild<T extends Node>(node: T | undefined, visitor: Visitor): T | undefined {
|
|
const nonlocalNode = !context.enclosingFile || context.enclosingFile !== getSourceFileOfNode(node);
|
|
return visitEachChildWorker(node, visitor, /*context*/ undefined, nonlocalNode ? visitNodesWithoutCopyingPositions : undefined);
|
|
}
|
|
|
|
function visitNodesWithoutCopyingPositions(
|
|
nodes: NodeArray<Node> | undefined,
|
|
visitor: Visitor,
|
|
test?: (node: Node) => boolean,
|
|
start?: number,
|
|
count?: number,
|
|
): NodeArray<Node> | undefined {
|
|
let result = visitNodes(nodes, visitor, test, start, count);
|
|
if (result) {
|
|
if (result.pos !== -1 || result.end !== -1) {
|
|
if (result === nodes) {
|
|
result = factory.createNodeArray(nodes.slice(), nodes.hasTrailingComma);
|
|
}
|
|
setTextRangePosEnd(result, -1, -1);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function getEffectiveDotDotDotForParameter(p: ParameterDeclaration) {
|
|
return p.dotDotDotToken || (p.type && isJSDocVariadicType(p.type) ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined);
|
|
}
|
|
|
|
/** Note that `new:T` parameters are not handled, but should be before calling this function. */
|
|
function getNameForJSDocFunctionParameter(p: ParameterDeclaration, index: number) {
|
|
return p.name && isIdentifier(p.name) && p.name.escapedText === "this" ? "this"
|
|
: getEffectiveDotDotDotForParameter(p) ? `args`
|
|
: `arg${index}`;
|
|
}
|
|
|
|
function rewriteModuleSpecifier(parent: ImportTypeNode, lit: StringLiteral) {
|
|
const newName = resolver.getModuleSpecifierOverride(context, parent, lit);
|
|
return newName ? setOriginalNode(factory.createStringLiteral(newName), lit) : lit;
|
|
}
|
|
}
|
|
}
|
|
|
|
function serializeExistingTypeNode(typeNode: TypeNode, context: SyntacticTypeNodeBuilderContext, addUndefined?: boolean): TypeNode;
|
|
function serializeExistingTypeNode(typeNode: TypeNode | undefined, context: SyntacticTypeNodeBuilderContext, addUndefined?: boolean): TypeNode | undefined;
|
|
function serializeExistingTypeNode(typeNode: TypeNode | undefined, context: SyntacticTypeNodeBuilderContext, addUndefined?: boolean): TypeNode | undefined {
|
|
if (!typeNode) return undefined;
|
|
let result;
|
|
if (
|
|
(!addUndefined || canAddUndefined(typeNode)) && resolver.canReuseTypeNode(context, typeNode)
|
|
) {
|
|
result = tryReuseExistingTypeNode(context, typeNode);
|
|
if (result !== undefined) {
|
|
result = addUndefinedIfNeeded(result, addUndefined, /*owner*/ undefined, context);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
function serializeTypeAnnotationOfDeclaration(declaredType: TypeNode | undefined, context: SyntacticTypeNodeBuilderContext, node: Declaration, symbol: Symbol | undefined, requiresAddingUndefined?: boolean, useFallback = requiresAddingUndefined !== undefined) {
|
|
if (!declaredType) return undefined;
|
|
if (!resolver.canReuseTypeNodeAnnotation(context, node, declaredType, symbol, requiresAddingUndefined)) {
|
|
// If we need to add undefined, can add undefined, and the resolver says we can reuse the type, we reuse the type
|
|
// If we don't know syntactically that we can add the undefined, we will report the fallback below.
|
|
if (!requiresAddingUndefined || !resolver.canReuseTypeNodeAnnotation(context, node, declaredType, symbol, /*requiresAddingUndefined*/ false)) {
|
|
return undefined;
|
|
}
|
|
}
|
|
let result;
|
|
if (!requiresAddingUndefined || canAddUndefined(declaredType)) {
|
|
result = serializeExistingTypeNode(declaredType, context, requiresAddingUndefined);
|
|
}
|
|
if (result !== undefined || !useFallback) {
|
|
return result;
|
|
}
|
|
context.tracker.reportInferenceFallback(node);
|
|
return resolver.serializeExistingTypeNode(context, declaredType, requiresAddingUndefined) ?? factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
|
}
|
|
function serializeExistingTypeNodeWithFallback(typeNode: TypeNode | undefined, context: SyntacticTypeNodeBuilderContext, addUndefined?: boolean, targetNode?: Node) {
|
|
if (!typeNode) return undefined;
|
|
const result = serializeExistingTypeNode(typeNode, context, addUndefined);
|
|
if (result !== undefined) {
|
|
return result;
|
|
}
|
|
context.tracker.reportInferenceFallback(targetNode ?? typeNode);
|
|
return resolver.serializeExistingTypeNode(context, typeNode, addUndefined) ?? factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
|
}
|
|
function serializeTypeOfAccessor(accessor: AccessorDeclaration, symbol: Symbol | undefined, context: SyntacticTypeNodeBuilderContext) {
|
|
return typeFromAccessor(accessor, symbol, context) ?? inferAccessorType(accessor, resolver.getAllAccessorDeclarations(accessor), context, symbol);
|
|
}
|
|
|
|
function serializeTypeOfExpression(expr: Expression, context: SyntacticTypeNodeBuilderContext, addUndefined?: boolean, preserveLiterals?: boolean) {
|
|
const result = typeFromExpression(expr, context, /*isConstContext*/ false, addUndefined, preserveLiterals);
|
|
return result.type !== undefined ? result.type : inferExpressionType(expr, context, result.reportFallback);
|
|
}
|
|
function serializeTypeOfDeclaration(node: HasInferredType, symbol: Symbol, context: SyntacticTypeNodeBuilderContext) {
|
|
switch (node.kind) {
|
|
case SyntaxKind.Parameter:
|
|
case SyntaxKind.JSDocParameterTag:
|
|
return typeFromParameter(node, symbol, context);
|
|
case SyntaxKind.VariableDeclaration:
|
|
return typeFromVariable(node, symbol, context);
|
|
case SyntaxKind.PropertySignature:
|
|
case SyntaxKind.JSDocPropertyTag:
|
|
case SyntaxKind.PropertyDeclaration:
|
|
return typeFromProperty(node, symbol, context);
|
|
case SyntaxKind.BindingElement:
|
|
return inferTypeOfDeclaration(node, symbol, context);
|
|
case SyntaxKind.ExportAssignment:
|
|
return serializeTypeOfExpression(node.expression, context, /*addUndefined*/ undefined, /*preserveLiterals*/ true);
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
case SyntaxKind.ElementAccessExpression:
|
|
case SyntaxKind.BinaryExpression:
|
|
return typeFromExpandoProperty(node, symbol, context);
|
|
case SyntaxKind.PropertyAssignment:
|
|
case SyntaxKind.ShorthandPropertyAssignment:
|
|
return typeFromPropertyAssignment(node, symbol, context);
|
|
default:
|
|
Debug.assertNever(node, `Node needs to be an inferrable node, found ${Debug.formatSyntaxKind((node as Node).kind)}`);
|
|
}
|
|
}
|
|
|
|
function typeFromPropertyAssignment(node: PropertyAssignment | ShorthandPropertyAssignment, symbol: Symbol, context: SyntacticTypeNodeBuilderContext) {
|
|
const typeAnnotation = getEffectiveTypeAnnotationNode(node);
|
|
let result;
|
|
if (typeAnnotation && resolver.canReuseTypeNodeAnnotation(context, node, typeAnnotation, symbol)) {
|
|
result = serializeExistingTypeNode(typeAnnotation, context);
|
|
}
|
|
if (!result && node.kind === SyntaxKind.PropertyAssignment) {
|
|
const initializer = node.initializer;
|
|
const assertionNode = isJSDocTypeAssertion(initializer) ? getJSDocTypeAssertionType(initializer) :
|
|
initializer.kind === SyntaxKind.AsExpression || initializer.kind === SyntaxKind.TypeAssertionExpression ? (initializer as AsExpression | TypeAssertion).type :
|
|
undefined;
|
|
|
|
if (assertionNode && !isConstTypeReference(assertionNode) && resolver.canReuseTypeNodeAnnotation(context, node, assertionNode, symbol)) {
|
|
result = serializeExistingTypeNode(assertionNode, context);
|
|
}
|
|
}
|
|
return result ?? inferTypeOfDeclaration(node, symbol, context, /*reportFallback*/ false);
|
|
}
|
|
function serializeReturnTypeForSignature(node: SignatureDeclaration | JSDocSignature, symbol: Symbol, context: SyntacticTypeNodeBuilderContext) {
|
|
switch (node.kind) {
|
|
case SyntaxKind.GetAccessor:
|
|
return serializeTypeOfAccessor(node, symbol, context);
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.ConstructSignature:
|
|
case SyntaxKind.MethodSignature:
|
|
case SyntaxKind.CallSignature:
|
|
case SyntaxKind.Constructor:
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.IndexSignature:
|
|
case SyntaxKind.FunctionType:
|
|
case SyntaxKind.ConstructorType:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ArrowFunction:
|
|
case SyntaxKind.JSDocFunctionType:
|
|
case SyntaxKind.JSDocSignature:
|
|
return createReturnFromSignature(node, symbol, context);
|
|
default:
|
|
Debug.assertNever(node, `Node needs to be an inferrable node, found ${Debug.formatSyntaxKind((node as Node).kind)}`);
|
|
}
|
|
}
|
|
function getTypeAnnotationFromAccessor(accessor: AccessorDeclaration): TypeNode | undefined {
|
|
if (accessor) {
|
|
return accessor.kind === SyntaxKind.GetAccessor
|
|
? (isInJSFile(accessor) && getJSDocType(accessor)) || getEffectiveReturnTypeNode(accessor)
|
|
: getEffectiveSetAccessorTypeAnnotationNode(accessor);
|
|
}
|
|
}
|
|
function getTypeAnnotationFromAllAccessorDeclarations(node: AccessorDeclaration, accessors: AllAccessorDeclarations) {
|
|
let accessorType = getTypeAnnotationFromAccessor(node);
|
|
if (!accessorType && node !== accessors.firstAccessor) {
|
|
accessorType = getTypeAnnotationFromAccessor(accessors.firstAccessor);
|
|
}
|
|
if (!accessorType && accessors.secondAccessor && node !== accessors.secondAccessor) {
|
|
accessorType = getTypeAnnotationFromAccessor(accessors.secondAccessor);
|
|
}
|
|
return accessorType;
|
|
}
|
|
|
|
function typeFromAccessor(node: AccessorDeclaration, symbol: Symbol | undefined, context: SyntacticTypeNodeBuilderContext): TypeNode | undefined {
|
|
const accessorDeclarations = resolver.getAllAccessorDeclarations(node);
|
|
const accessorType = getTypeAnnotationFromAllAccessorDeclarations(node, accessorDeclarations);
|
|
if (accessorType && !isTypePredicateNode(accessorType)) {
|
|
return withNewScope(context, node, () => serializeTypeAnnotationOfDeclaration(accessorType, context, node, symbol) ?? inferTypeOfDeclaration(node, symbol, context));
|
|
}
|
|
if (accessorDeclarations.getAccessor) {
|
|
return withNewScope(context, accessorDeclarations.getAccessor, () => createReturnFromSignature(accessorDeclarations.getAccessor!, symbol, context));
|
|
}
|
|
return undefined;
|
|
}
|
|
function typeFromVariable(node: VariableDeclaration, symbol: Symbol, context: SyntacticTypeNodeBuilderContext): TypeNode | undefined {
|
|
const declaredType = getEffectiveTypeAnnotationNode(node);
|
|
let resultType = failed;
|
|
if (declaredType) {
|
|
resultType = syntacticResult(serializeTypeAnnotationOfDeclaration(declaredType, context, node, symbol));
|
|
}
|
|
else if (node.initializer && (symbol.declarations?.length === 1 || countWhere(symbol.declarations, isVariableDeclaration) === 1)) {
|
|
if (!resolver.isExpandoFunctionDeclaration(node) && !isContextuallyTyped(node)) {
|
|
resultType = typeFromExpression(node.initializer, context, /*isConstContext*/ undefined, /*requiresAddingUndefined*/ undefined, isVarConstLike(node));
|
|
}
|
|
}
|
|
return resultType.type !== undefined ? resultType.type : inferTypeOfDeclaration(node, symbol, context, resultType.reportFallback);
|
|
}
|
|
function typeFromParameter(node: ParameterDeclaration | JSDocParameterTag, symbol: Symbol | undefined, context: SyntacticTypeNodeBuilderContext): TypeNode | undefined {
|
|
const parent = node.parent;
|
|
if (parent.kind === SyntaxKind.SetAccessor) {
|
|
return serializeTypeOfAccessor(parent, /*symbol*/ undefined, context);
|
|
}
|
|
const declaredType = getEffectiveTypeAnnotationNode(node);
|
|
const addUndefined = resolver.requiresAddingImplicitUndefined(node, symbol, context.enclosingDeclaration);
|
|
let resultType = failed;
|
|
if (declaredType) {
|
|
resultType = syntacticResult(serializeTypeAnnotationOfDeclaration(declaredType, context, node, symbol, addUndefined));
|
|
}
|
|
else if (isParameter(node) && node.initializer && isIdentifier(node.name) && !isContextuallyTyped(node)) {
|
|
resultType = typeFromExpression(node.initializer, context, /*isConstContext*/ undefined, addUndefined);
|
|
}
|
|
return resultType.type !== undefined ? resultType.type : inferTypeOfDeclaration(node, symbol, context, resultType.reportFallback);
|
|
}
|
|
/**
|
|
* While expando poperies are errors in TSC, in JS we try to extract the type from the binary expression;
|
|
*/
|
|
function typeFromExpandoProperty(node: PropertyAccessExpression | BinaryExpression | ElementAccessExpression, symbol: Symbol, context: SyntacticTypeNodeBuilderContext) {
|
|
const declaredType = getEffectiveTypeAnnotationNode(node);
|
|
let result;
|
|
if (declaredType) {
|
|
result = serializeTypeAnnotationOfDeclaration(declaredType, context, node, symbol);
|
|
}
|
|
const oldSuppressReportInferenceFallback = context.suppressReportInferenceFallback;
|
|
context.suppressReportInferenceFallback = true;
|
|
const resultType = result ?? inferTypeOfDeclaration(node, symbol, context, /*reportFallback*/ false);
|
|
context.suppressReportInferenceFallback = oldSuppressReportInferenceFallback;
|
|
return resultType;
|
|
}
|
|
function typeFromProperty(node: PropertyDeclaration | PropertySignature | JSDocPropertyTag, symbol: Symbol, context: SyntacticTypeNodeBuilderContext) {
|
|
const declaredType = getEffectiveTypeAnnotationNode(node);
|
|
const requiresAddingUndefined = resolver.requiresAddingImplicitUndefined(node, symbol, context.enclosingDeclaration);
|
|
let resultType = failed;
|
|
if (declaredType) {
|
|
resultType = syntacticResult(serializeTypeAnnotationOfDeclaration(declaredType, context, node, symbol, requiresAddingUndefined));
|
|
}
|
|
else {
|
|
const initializer = isPropertyDeclaration(node) ? node.initializer : undefined;
|
|
if (initializer && !isContextuallyTyped(node)) {
|
|
const isReadonly = isDeclarationReadonly(node);
|
|
resultType = typeFromExpression(initializer, context, /*isConstContext*/ undefined, requiresAddingUndefined, isReadonly);
|
|
}
|
|
}
|
|
return resultType.type !== undefined ? resultType.type : inferTypeOfDeclaration(node, symbol, context, resultType.reportFallback);
|
|
}
|
|
|
|
function inferTypeOfDeclaration(
|
|
node: HasInferredType | GetAccessorDeclaration | SetAccessorDeclaration,
|
|
symbol: Symbol | undefined,
|
|
context: SyntacticTypeNodeBuilderContext,
|
|
reportFallback = true,
|
|
) {
|
|
if (reportFallback) {
|
|
context.tracker.reportInferenceFallback(node);
|
|
}
|
|
if (context.noInferenceFallback === true) {
|
|
return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
|
}
|
|
return resolver.serializeTypeOfDeclaration(context, node, symbol);
|
|
}
|
|
|
|
function inferExpressionType(node: Expression, context: SyntacticTypeNodeBuilderContext, reportFallback = true, requiresAddingUndefined?: boolean) {
|
|
Debug.assert(!requiresAddingUndefined);
|
|
if (reportFallback) {
|
|
context.tracker.reportInferenceFallback(node);
|
|
}
|
|
if (context.noInferenceFallback === true) {
|
|
return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
|
}
|
|
return resolver.serializeTypeOfExpression(context, node) ?? factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
|
}
|
|
|
|
function inferReturnTypeOfSignatureSignature(node: SignatureDeclaration | JSDocSignature, context: SyntacticTypeNodeBuilderContext, symbol: Symbol | undefined, reportFallback: boolean) {
|
|
if (reportFallback) {
|
|
context.tracker.reportInferenceFallback(node);
|
|
}
|
|
if (context.noInferenceFallback === true) {
|
|
return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
|
}
|
|
return resolver.serializeReturnTypeForSignature(context, node, symbol) ?? factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
|
}
|
|
|
|
function inferAccessorType(node: GetAccessorDeclaration | SetAccessorDeclaration, allAccessors: AllAccessorDeclarations, context: SyntacticTypeNodeBuilderContext, symbol: Symbol | undefined, reportFallback: boolean = true): TypeNode | undefined {
|
|
if (node.kind === SyntaxKind.GetAccessor) {
|
|
return createReturnFromSignature(node, symbol, context, reportFallback);
|
|
}
|
|
else {
|
|
if (reportFallback) {
|
|
context.tracker.reportInferenceFallback(node);
|
|
}
|
|
const result = allAccessors.getAccessor && createReturnFromSignature(allAccessors.getAccessor, symbol, context, reportFallback);
|
|
return result ?? resolver.serializeTypeOfDeclaration(context, node, symbol) ?? factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
|
}
|
|
}
|
|
|
|
function withNewScope<R>(context: SyntacticTypeNodeBuilderContext, node: IntroducesNewScopeNode | ConditionalTypeNode, fn: () => R) {
|
|
const cleanup = resolver.enterNewScope(context, node);
|
|
const result = fn();
|
|
cleanup();
|
|
return result;
|
|
}
|
|
function typeFromTypeAssertion(expression: Expression, type: TypeNode, context: SyntacticTypeNodeBuilderContext, requiresAddingUndefined: boolean): SyntacticResult {
|
|
if (isConstTypeReference(type)) {
|
|
return typeFromExpression(expression, context, /*isConstContext*/ true, requiresAddingUndefined);
|
|
}
|
|
return syntacticResult(serializeExistingTypeNodeWithFallback(type, context, requiresAddingUndefined));
|
|
}
|
|
function typeFromExpression(node: Expression | JsxAttributeValue, context: SyntacticTypeNodeBuilderContext, isConstContext = false, requiresAddingUndefined = false, preserveLiterals = false): SyntacticResult {
|
|
switch (node.kind) {
|
|
case SyntaxKind.ParenthesizedExpression:
|
|
if (isJSDocTypeAssertion(node)) {
|
|
return typeFromTypeAssertion(node.expression, getJSDocTypeAssertionType(node), context, requiresAddingUndefined);
|
|
}
|
|
return typeFromExpression((node as ParenthesizedExpression).expression, context, isConstContext, requiresAddingUndefined);
|
|
case SyntaxKind.Identifier:
|
|
if (resolver.isUndefinedIdentifierExpression(node as Identifier)) {
|
|
return syntacticResult(createUndefinedTypeNode());
|
|
}
|
|
break;
|
|
case SyntaxKind.NullKeyword:
|
|
if (strictNullChecks) {
|
|
return syntacticResult(addUndefinedIfNeeded(factory.createLiteralTypeNode(factory.createNull()), requiresAddingUndefined, node, context));
|
|
}
|
|
else {
|
|
return syntacticResult(factory.createKeywordTypeNode(SyntaxKind.AnyKeyword));
|
|
}
|
|
case SyntaxKind.ArrowFunction:
|
|
case SyntaxKind.FunctionExpression:
|
|
Debug.type<ArrowFunction | FunctionExpression>(node);
|
|
return withNewScope(context, node, () => typeFromFunctionLikeExpression(node, context));
|
|
case SyntaxKind.TypeAssertionExpression:
|
|
case SyntaxKind.AsExpression:
|
|
const asExpression = node as AsExpression | TypeAssertion;
|
|
return typeFromTypeAssertion(asExpression.expression, asExpression.type, context, requiresAddingUndefined);
|
|
case SyntaxKind.PrefixUnaryExpression:
|
|
const unaryExpression = node as PrefixUnaryExpression;
|
|
if (isPrimitiveLiteralValue(unaryExpression)) {
|
|
return typeFromPrimitiveLiteral(
|
|
unaryExpression.operator === SyntaxKind.PlusToken ? unaryExpression.operand : unaryExpression,
|
|
unaryExpression.operand.kind === SyntaxKind.BigIntLiteral ? SyntaxKind.BigIntKeyword : SyntaxKind.NumberKeyword,
|
|
context,
|
|
isConstContext || preserveLiterals,
|
|
requiresAddingUndefined,
|
|
);
|
|
}
|
|
break;
|
|
case SyntaxKind.ArrayLiteralExpression:
|
|
return typeFromArrayLiteral(node as ArrayLiteralExpression, context, isConstContext, requiresAddingUndefined);
|
|
case SyntaxKind.ObjectLiteralExpression:
|
|
return typeFromObjectLiteral(node as ObjectLiteralExpression, context, isConstContext, requiresAddingUndefined);
|
|
case SyntaxKind.ClassExpression:
|
|
return syntacticResult(inferExpressionType(node as ClassExpression, context, /*reportFallback*/ true, requiresAddingUndefined));
|
|
case SyntaxKind.TemplateExpression:
|
|
if (!isConstContext && !preserveLiterals) {
|
|
return syntacticResult(factory.createKeywordTypeNode(SyntaxKind.StringKeyword));
|
|
}
|
|
break;
|
|
default:
|
|
let typeKind: KeywordTypeSyntaxKind | undefined;
|
|
let primitiveNode = node as PrimitiveLiteral;
|
|
switch (node.kind) {
|
|
case SyntaxKind.NumericLiteral:
|
|
typeKind = SyntaxKind.NumberKeyword;
|
|
break;
|
|
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
primitiveNode = factory.createStringLiteral((node as NoSubstitutionTemplateLiteral).text);
|
|
typeKind = SyntaxKind.StringKeyword;
|
|
break;
|
|
case SyntaxKind.StringLiteral:
|
|
typeKind = SyntaxKind.StringKeyword;
|
|
break;
|
|
case SyntaxKind.BigIntLiteral:
|
|
typeKind = SyntaxKind.BigIntKeyword;
|
|
break;
|
|
case SyntaxKind.TrueKeyword:
|
|
case SyntaxKind.FalseKeyword:
|
|
typeKind = SyntaxKind.BooleanKeyword;
|
|
break;
|
|
}
|
|
if (typeKind) {
|
|
return typeFromPrimitiveLiteral(primitiveNode, typeKind, context, isConstContext || preserveLiterals, requiresAddingUndefined);
|
|
}
|
|
}
|
|
return failed;
|
|
}
|
|
function typeFromFunctionLikeExpression(fnNode: FunctionExpression | ArrowFunction, context: SyntacticTypeNodeBuilderContext) {
|
|
const returnType = createReturnFromSignature(fnNode, /*symbol*/ undefined, context);
|
|
const typeParameters = reuseTypeParameters(fnNode.typeParameters, context);
|
|
const parameters = fnNode.parameters.map(p => ensureParameter(p, context));
|
|
return syntacticResult(
|
|
factory.createFunctionTypeNode(
|
|
typeParameters,
|
|
parameters,
|
|
returnType,
|
|
),
|
|
);
|
|
}
|
|
function canGetTypeFromArrayLiteral(arrayLiteral: ArrayLiteralExpression, context: SyntacticTypeNodeBuilderContext, isConstContext: boolean) {
|
|
if (!isConstContext) {
|
|
context.tracker.reportInferenceFallback(arrayLiteral);
|
|
return false;
|
|
}
|
|
for (const element of arrayLiteral.elements) {
|
|
if (element.kind === SyntaxKind.SpreadElement) {
|
|
context.tracker.reportInferenceFallback(element);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
function typeFromArrayLiteral(arrayLiteral: ArrayLiteralExpression, context: SyntacticTypeNodeBuilderContext, isConstContext: boolean, requiresAddingUndefined: boolean): SyntacticResult {
|
|
if (!canGetTypeFromArrayLiteral(arrayLiteral, context, isConstContext)) {
|
|
if (requiresAddingUndefined || isDeclaration(walkUpParenthesizedExpressions(arrayLiteral).parent)) {
|
|
return alreadyReported;
|
|
}
|
|
return syntacticResult(inferExpressionType(arrayLiteral, context, /*reportFallback*/ false, requiresAddingUndefined));
|
|
}
|
|
// Disable any inference fallback since we won't actually use the resulting type and we don't want to generate errors
|
|
const oldNoInferenceFallback = context.noInferenceFallback;
|
|
context.noInferenceFallback = true;
|
|
const elementTypesInfo: TypeNode[] = [];
|
|
for (const element of arrayLiteral.elements) {
|
|
Debug.assert(element.kind !== SyntaxKind.SpreadElement);
|
|
if (element.kind === SyntaxKind.OmittedExpression) {
|
|
elementTypesInfo.push(
|
|
createUndefinedTypeNode(),
|
|
);
|
|
}
|
|
else {
|
|
const expressionType = typeFromExpression(element, context, isConstContext);
|
|
const elementType = expressionType.type !== undefined ? expressionType.type : inferExpressionType(element, context, expressionType.reportFallback);
|
|
elementTypesInfo.push(elementType);
|
|
}
|
|
}
|
|
const tupleType = factory.createTupleTypeNode(elementTypesInfo);
|
|
tupleType.emitNode = { flags: 1, autoGenerate: undefined, internalFlags: 0 };
|
|
context.noInferenceFallback = oldNoInferenceFallback;
|
|
return notImplemented;
|
|
}
|
|
function canGetTypeFromObjectLiteral(objectLiteral: ObjectLiteralExpression, context: SyntacticTypeNodeBuilderContext) {
|
|
let result = true;
|
|
for (const prop of objectLiteral.properties) {
|
|
if (prop.flags & NodeFlags.ThisNodeHasError) {
|
|
result = false;
|
|
break; // Bail if parse errors
|
|
}
|
|
if (prop.kind === SyntaxKind.ShorthandPropertyAssignment || prop.kind === SyntaxKind.SpreadAssignment) {
|
|
context.tracker.reportInferenceFallback(prop);
|
|
result = false;
|
|
}
|
|
else if (prop.name.flags & NodeFlags.ThisNodeHasError) {
|
|
result = false;
|
|
break; // Bail if parse errors
|
|
}
|
|
else if (prop.name.kind === SyntaxKind.PrivateIdentifier) {
|
|
// Not valid in object literals but the compiler will complain about this, we just ignore it here.
|
|
result = false;
|
|
}
|
|
else if (prop.name.kind === SyntaxKind.ComputedPropertyName) {
|
|
const expression = prop.name.expression;
|
|
if (!isPrimitiveLiteralValue(expression, /*includeBigInt*/ false) && !resolver.isDefinitelyReferenceToGlobalSymbolObject(expression)) {
|
|
context.tracker.reportInferenceFallback(prop.name);
|
|
result = false;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
function typeFromObjectLiteral(objectLiteral: ObjectLiteralExpression, context: SyntacticTypeNodeBuilderContext, isConstContext: boolean, requiresAddingUndefined: boolean) {
|
|
if (!canGetTypeFromObjectLiteral(objectLiteral, context)) {
|
|
if (requiresAddingUndefined || isDeclaration(walkUpParenthesizedExpressions(objectLiteral).parent)) {
|
|
return alreadyReported;
|
|
}
|
|
return syntacticResult(inferExpressionType(objectLiteral, context, /*reportFallback*/ false, requiresAddingUndefined));
|
|
}
|
|
// Disable any inference fallback since we won't actually use the resulting type and we don't want to generate errors
|
|
const oldNoInferenceFallback = context.noInferenceFallback;
|
|
context.noInferenceFallback = true;
|
|
const properties: TypeElement[] = [];
|
|
const oldFlags = context.flags;
|
|
context.flags |= NodeBuilderFlags.InObjectTypeLiteral;
|
|
for (const prop of objectLiteral.properties) {
|
|
Debug.assert(!isShorthandPropertyAssignment(prop) && !isSpreadAssignment(prop));
|
|
|
|
const name = prop.name;
|
|
let newProp;
|
|
switch (prop.kind) {
|
|
case SyntaxKind.MethodDeclaration:
|
|
newProp = withNewScope(context, prop, () => typeFromObjectLiteralMethod(prop, name, context, isConstContext));
|
|
break;
|
|
case SyntaxKind.PropertyAssignment:
|
|
newProp = typeFromObjectLiteralPropertyAssignment(prop, name, context, isConstContext);
|
|
break;
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.GetAccessor:
|
|
newProp = typeFromObjectLiteralAccessor(prop, name, context);
|
|
break;
|
|
}
|
|
if (newProp) {
|
|
setCommentRange(newProp, prop);
|
|
properties.push(newProp);
|
|
}
|
|
}
|
|
|
|
context.flags = oldFlags;
|
|
const typeNode = factory.createTypeLiteralNode(properties);
|
|
if (!(context.flags & NodeBuilderFlags.MultilineObjectLiterals)) {
|
|
setEmitFlags(typeNode, EmitFlags.SingleLine);
|
|
}
|
|
context.noInferenceFallback = oldNoInferenceFallback;
|
|
return notImplemented;
|
|
}
|
|
|
|
function typeFromObjectLiteralPropertyAssignment(prop: PropertyAssignment, name: PropertyName, context: SyntacticTypeNodeBuilderContext, isConstContext: boolean) {
|
|
const modifiers = isConstContext ?
|
|
[factory.createModifier(SyntaxKind.ReadonlyKeyword)] :
|
|
[];
|
|
const expressionResult = typeFromExpression(prop.initializer, context, isConstContext);
|
|
const typeNode = expressionResult.type !== undefined ? expressionResult.type : inferTypeOfDeclaration(prop, /*symbol*/ undefined, context, expressionResult.reportFallback);
|
|
|
|
return factory.createPropertySignature(
|
|
modifiers,
|
|
reuseNode(context, name),
|
|
/*questionToken*/ undefined,
|
|
typeNode,
|
|
);
|
|
}
|
|
|
|
function ensureParameter(p: ParameterDeclaration, context: SyntacticTypeNodeBuilderContext) {
|
|
return factory.updateParameterDeclaration(
|
|
p,
|
|
/*modifiers*/ undefined,
|
|
reuseNode(context, p.dotDotDotToken),
|
|
resolver.serializeNameOfParameter(context, p),
|
|
resolver.isOptionalParameter(p) ? factory.createToken(SyntaxKind.QuestionToken) : undefined,
|
|
typeFromParameter(p, /*symbol*/ undefined, context), // Ignore private param props, since this type is going straight back into a param
|
|
/*initializer*/ undefined,
|
|
);
|
|
}
|
|
function reuseTypeParameters(typeParameters: NodeArray<TypeParameterDeclaration> | undefined, context: SyntacticTypeNodeBuilderContext) {
|
|
return typeParameters?.map(tp => {
|
|
const { node: tpName } = resolver.trackExistingEntityName(context, tp.name);
|
|
return factory.updateTypeParameterDeclaration(
|
|
tp,
|
|
tp.modifiers?.map(m => reuseNode(context, m)),
|
|
tpName,
|
|
serializeExistingTypeNodeWithFallback(tp.constraint, context),
|
|
serializeExistingTypeNodeWithFallback(tp.default, context),
|
|
);
|
|
});
|
|
}
|
|
|
|
function typeFromObjectLiteralMethod(method: MethodDeclaration, name: PropertyName, context: SyntacticTypeNodeBuilderContext, isConstContext: boolean) {
|
|
const returnType = createReturnFromSignature(method, /*symbol*/ undefined, context);
|
|
const typeParameters = reuseTypeParameters(method.typeParameters, context);
|
|
const parameters = method.parameters.map(p => ensureParameter(p, context));
|
|
if (isConstContext) {
|
|
return factory.createPropertySignature(
|
|
[factory.createModifier(SyntaxKind.ReadonlyKeyword)],
|
|
reuseNode(context, name),
|
|
reuseNode(context, method.questionToken),
|
|
factory.createFunctionTypeNode(
|
|
typeParameters,
|
|
parameters,
|
|
returnType,
|
|
),
|
|
);
|
|
}
|
|
else {
|
|
if (isIdentifier(name) && name.escapedText === "new") {
|
|
name = factory.createStringLiteral("new");
|
|
}
|
|
return factory.createMethodSignature(
|
|
[],
|
|
reuseNode(context, name),
|
|
reuseNode(context, method.questionToken),
|
|
typeParameters,
|
|
parameters,
|
|
returnType,
|
|
);
|
|
}
|
|
}
|
|
function typeFromObjectLiteralAccessor(accessor: GetAccessorDeclaration | SetAccessorDeclaration, name: PropertyName, context: SyntacticTypeNodeBuilderContext) {
|
|
const allAccessors = resolver.getAllAccessorDeclarations(accessor);
|
|
const getAccessorType = allAccessors.getAccessor && getTypeAnnotationFromAccessor(allAccessors.getAccessor);
|
|
const setAccessorType = allAccessors.setAccessor && getTypeAnnotationFromAccessor(allAccessors.setAccessor);
|
|
// We have types for both accessors, we can't know if they are the same type so we keep both accessors
|
|
if (getAccessorType !== undefined && setAccessorType !== undefined) {
|
|
return withNewScope(context, accessor, () => {
|
|
const parameters = accessor.parameters.map(p => ensureParameter(p, context));
|
|
|
|
if (isGetAccessor(accessor)) {
|
|
return factory.updateGetAccessorDeclaration(
|
|
accessor,
|
|
[],
|
|
reuseNode(context, name),
|
|
parameters,
|
|
serializeExistingTypeNodeWithFallback(getAccessorType, context),
|
|
/*body*/ undefined,
|
|
);
|
|
}
|
|
else {
|
|
return factory.updateSetAccessorDeclaration(
|
|
accessor,
|
|
[],
|
|
reuseNode(context, name),
|
|
parameters,
|
|
/*body*/ undefined,
|
|
);
|
|
}
|
|
});
|
|
}
|
|
else if (allAccessors.firstAccessor === accessor) {
|
|
const foundType = getAccessorType ? withNewScope(context, allAccessors.getAccessor!, () => serializeExistingTypeNodeWithFallback(getAccessorType, context)) :
|
|
setAccessorType ? withNewScope(context, allAccessors.setAccessor!, () => serializeExistingTypeNodeWithFallback(setAccessorType, context)) :
|
|
undefined;
|
|
const propertyType = foundType ?? inferAccessorType(accessor, allAccessors, context, /*symbol*/ undefined);
|
|
|
|
const propertySignature = factory.createPropertySignature(
|
|
allAccessors.setAccessor === undefined ? [factory.createModifier(SyntaxKind.ReadonlyKeyword)] : [],
|
|
reuseNode(context, name),
|
|
/*questionToken*/ undefined,
|
|
propertyType,
|
|
);
|
|
return propertySignature;
|
|
}
|
|
}
|
|
function createUndefinedTypeNode() {
|
|
if (strictNullChecks) {
|
|
return factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword);
|
|
}
|
|
else {
|
|
return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
|
}
|
|
}
|
|
function typeFromPrimitiveLiteral(node: PrimitiveLiteral, baseType: KeywordTypeSyntaxKind, context: SyntacticTypeNodeBuilderContext, preserveLiterals: boolean, requiresAddingUndefined: boolean) {
|
|
let result;
|
|
if (preserveLiterals) {
|
|
if (node.kind === SyntaxKind.PrefixUnaryExpression && node.operator === SyntaxKind.PlusToken) {
|
|
result = factory.createLiteralTypeNode(reuseNode(context, node.operand));
|
|
}
|
|
result = factory.createLiteralTypeNode(reuseNode(context, node));
|
|
}
|
|
else {
|
|
result = factory.createKeywordTypeNode(baseType);
|
|
}
|
|
return syntacticResult(addUndefinedIfNeeded(result, requiresAddingUndefined, node, context));
|
|
}
|
|
|
|
function addUndefinedIfNeeded(node: TypeNode, addUndefined: boolean | undefined, owner: Node | undefined, context: SyntacticTypeNodeBuilderContext) {
|
|
const parentDeclaration = owner && walkUpParenthesizedExpressions(owner).parent;
|
|
const optionalDeclaration = parentDeclaration && isDeclaration(parentDeclaration) && isOptionalDeclaration(parentDeclaration);
|
|
if (!strictNullChecks || !(addUndefined || optionalDeclaration)) return node;
|
|
if (!canAddUndefined(node)) {
|
|
context.tracker.reportInferenceFallback(node);
|
|
}
|
|
if (isUnionTypeNode(node)) {
|
|
return factory.createUnionTypeNode([...node.types, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]);
|
|
}
|
|
return factory.createUnionTypeNode([node, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]);
|
|
}
|
|
function canAddUndefined(node: TypeNode): boolean {
|
|
if (!strictNullChecks) return true;
|
|
if (
|
|
isKeyword(node.kind)
|
|
|| node.kind === SyntaxKind.LiteralType
|
|
|| node.kind === SyntaxKind.FunctionType
|
|
|| node.kind === SyntaxKind.ConstructorType
|
|
|| node.kind === SyntaxKind.ArrayType
|
|
|| node.kind === SyntaxKind.TupleType
|
|
|| node.kind === SyntaxKind.TypeLiteral
|
|
|| node.kind === SyntaxKind.TemplateLiteralType
|
|
|| node.kind === SyntaxKind.ThisType
|
|
) {
|
|
return true;
|
|
}
|
|
if (node.kind === SyntaxKind.ParenthesizedType) {
|
|
return canAddUndefined((node as ParenthesizedTypeNode).type);
|
|
}
|
|
if (node.kind === SyntaxKind.UnionType || node.kind === SyntaxKind.IntersectionType) {
|
|
return (node as UnionTypeNode | IntersectionTypeNode).types.every(canAddUndefined);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function createReturnFromSignature(fn: SignatureDeclaration | JSDocSignature, symbol: Symbol | undefined, context: SyntacticTypeNodeBuilderContext, reportFallback: boolean = true): TypeNode {
|
|
let returnType = failed;
|
|
const returnTypeNode = isJSDocConstructSignature(fn) ? getEffectiveTypeAnnotationNode(fn.parameters[0]) : getEffectiveReturnTypeNode(fn);
|
|
if (returnTypeNode) {
|
|
returnType = syntacticResult(serializeTypeAnnotationOfDeclaration(returnTypeNode, context, fn, symbol));
|
|
}
|
|
else if (isValueSignatureDeclaration(fn)) {
|
|
returnType = typeFromSingleReturnExpression(fn, context);
|
|
}
|
|
return returnType.type !== undefined ? returnType.type : inferReturnTypeOfSignatureSignature(fn, context, symbol, reportFallback && returnType.reportFallback && !returnTypeNode);
|
|
}
|
|
|
|
function typeFromSingleReturnExpression(declaration: FunctionLikeDeclaration | undefined, context: SyntacticTypeNodeBuilderContext): SyntacticResult {
|
|
let candidateExpr: Expression | undefined;
|
|
if (declaration && !nodeIsMissing(declaration.body)) {
|
|
const flags = getFunctionFlags(declaration);
|
|
if (flags & FunctionFlags.AsyncGenerator) return failed;
|
|
|
|
const body = declaration.body;
|
|
if (body && isBlock(body)) {
|
|
forEachReturnStatement(body, s => {
|
|
if (s.parent !== body) {
|
|
candidateExpr = undefined;
|
|
return true;
|
|
}
|
|
if (!candidateExpr) {
|
|
candidateExpr = s.expression;
|
|
}
|
|
else {
|
|
candidateExpr = undefined;
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
candidateExpr = body;
|
|
}
|
|
}
|
|
if (candidateExpr) {
|
|
if (isContextuallyTyped(candidateExpr)) {
|
|
const type = isJSDocTypeAssertion(candidateExpr) ? getJSDocTypeAssertionType(candidateExpr) :
|
|
isAsExpression(candidateExpr) || isTypeAssertionExpression(candidateExpr) ? candidateExpr.type :
|
|
undefined;
|
|
if (type && !isConstTypeReference(type)) {
|
|
return syntacticResult(serializeExistingTypeNode(type, context));
|
|
}
|
|
}
|
|
else {
|
|
return typeFromExpression(candidateExpr, context);
|
|
}
|
|
}
|
|
return failed;
|
|
}
|
|
|
|
function isContextuallyTyped(node: Node) {
|
|
return findAncestor(node.parent, n => {
|
|
// Functions calls or parent type annotations (but not the return type of a function expression) may impact the inferred type and local inference is unreliable
|
|
return isCallExpression(n) || (!isFunctionLikeDeclaration(n) && !!getEffectiveTypeAnnotationNode(n)) || isJsxElement(n) || isJsxExpression(n);
|
|
});
|
|
}
|
|
}
|