diff --git a/external-declarations/fixed-tests.jsonc b/external-declarations/fixed-tests.jsonc index 7626f305afda5..8f1b3fc01d16e 100644 --- a/external-declarations/fixed-tests.jsonc +++ b/external-declarations/fixed-tests.jsonc @@ -5,6 +5,7 @@ 9008, // Declaration_emit_for_this_file_requires_adding_a_type_reference_directive_Add_a_type_reference_directive_to_0_to_unblock_declaration_emit 9009, // Assigning_properties_to_functions_without_declaring_them_is_not_supported_with_isolatedDeclarations_Add_an_explicit_declaration_for_the_properties_assigned_to_this_function 9010, // Reference directives are not supported in isolated declaration mode. + 9011, // Heritage_clause_for_Class_Expressions_is_not_allowed_with_isolatedDeclarations ], "with-unreliable-errors": [ 2784, // 'get' and 'set' accessors cannot declare 'this' parameters. diff --git a/external-declarations/src/code-mod/fixer/isolated-declarations-errors.ts b/external-declarations/src/code-mod/fixer/isolated-declarations-errors.ts index 20f2edfb725f2..e933c29440287 100644 --- a/external-declarations/src/code-mod/fixer/isolated-declarations-errors.ts +++ b/external-declarations/src/code-mod/fixer/isolated-declarations-errors.ts @@ -3,4 +3,5 @@ export const isolatedDeclarationsErrors = new Set([ 9008, 9009, 9010, + 9011, ]); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 38287315fb7b1..ace4101d34ce4 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -6710,6 +6710,10 @@ "category": "Error", "code": 9010 }, + "Heritage clauses in class expressions are not allowed with --isolatedDeclarations": { + "category": "Error", + "code": 9011 + }, "JSX attributes must only be assigned a non-empty 'expression'.": { "category": "Error", "code": 17000 diff --git a/src/compiler/transformers/declarations/localInferenceResolver.ts b/src/compiler/transformers/declarations/localInferenceResolver.ts index 33490514274c4..c8e723ae27b8b 100644 --- a/src/compiler/transformers/declarations/localInferenceResolver.ts +++ b/src/compiler/transformers/declarations/localInferenceResolver.ts @@ -10,9 +10,11 @@ import { } from "../../diagnosticInformationMap.generated"; import { isComputedPropertyName, + isConstructorDeclaration, isExportAssignment, isGetAccessorDeclaration, isIdentifier, + isIndexSignatureDeclaration, isInterfaceDeclaration, isLiteralTypeNode, isMethodDeclaration, @@ -49,6 +51,7 @@ import { ArrayLiteralExpression, ArrowFunction, AsExpression, + ClassExpression, EntityNameOrEntityNameExpression, ExportAssignment, Expression, @@ -62,6 +65,7 @@ import { MethodDeclaration, MethodSignature, Modifier, + ModifierLike, Node, NodeArray, NodeFlags, @@ -85,6 +89,7 @@ import { } from "../../types"; import { createDiagnosticForNode, + createDiagnosticForRange, isEntityNameExpression, } from "../../utilities"; import { @@ -110,6 +115,7 @@ enum LocalTypeInfoFlags { None = 0, Invalid = 1 << 1, } +const propertyLikeModifiers = new Set([SyntaxKind.ReadonlyKeyword, SyntaxKind.PublicKeyword]); interface LocalInferenceResolver { makeInvalidType(): Node; @@ -128,7 +134,7 @@ export function createLocalInferenceResolver({ ensureParameter(p: ParameterDeclaration): ParameterDeclaration; context: TransformationContext; }): { resolver: LocalInferenceResolver, isolatedDeclarations: true } | { resolver: undefined, isolatedDeclarations: false } { - let currentSourceFile: SourceFile | undefined; + let currentSourceFile: SourceFile; const options = context.getCompilerOptions(); const resolver = context.getEmitResolver(); if (!options.isolatedDeclarations) { @@ -187,10 +193,10 @@ export function createLocalInferenceResolver({ const getAccessor = knownIsGetAccessor ? knownAccessor : otherAccessor && isGetAccessorDeclaration(otherAccessor) ? otherAccessor : - undefined; + undefined; const setAccessor = !knownIsGetAccessor ? knownAccessor : otherAccessor && isSetAccessorDeclaration(otherAccessor) ? otherAccessor : - undefined; + undefined; return { otherAccessorIndex, @@ -338,13 +344,15 @@ export function createLocalInferenceResolver({ ); tupleType.emitNode = { flags: 1, autoGenerate: undefined, internalFlags: 0 }; return regular(factory.createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleType), node, inheritedArrayTypeFlags); - case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ObjectLiteralExpression: return getTypeForObjectLiteralExpression(node as ObjectLiteralExpression, inferenceFlags); + case SyntaxKind.ClassExpression: + return getClassExpressionTypeNode(node as ClassExpression); } return invalid(node); } - function invalid(sourceNode: Node): LocalTypeInfo { + function invalid(sourceNode: Node): LocalTypeInfo { reportIsolatedDeclarationError(sourceNode); return { typeNode: makeInvalidType(), flags: LocalTypeInfoFlags.Invalid, sourceNode }; } @@ -496,6 +504,144 @@ export function createLocalInferenceResolver({ const typeNode: TypeNode = replaceWithInvalid ? makeInvalidType() : factory.createTypeLiteralNode(properties); return regular(typeNode, objectLiteral, inheritedObjectTypeFlags); } + + function getClassExpressionTypeNode(node: ClassExpression): LocalTypeInfo { + let invalid = false; + let hasGetSetAccessor = false; + const staticMembers: TypeElement[] = []; + const nonStaticMembers: TypeElement[] = []; + const constructorParameters: ParameterDeclaration[] = []; + + if (node.heritageClauses && node.heritageClauses.length > 0) { + context.addDiagnostic({ + ...createDiagnosticForNode(node, Diagnostics.Declaration_emit_for_this_file_requires_type_resolution_An_explicit_type_annotation_may_unblock_declaration_emit), + relatedInformation: [ + createDiagnosticForRange( + currentSourceFile, + { + pos: node.heritageClauses[0].pos, + end: node.heritageClauses[node.heritageClauses.length - 1].end + }, + Diagnostics.Heritage_clause_for_Class_Expressions_is_not_allowed_with_isolatedDeclarations) + ], + }); + invalid = true; + } + + for (const member of node.members) { + if (isConstructorDeclaration(member)) { + for (const parameter of member.parameters) { + const type = localInferenceFromInitializer(parameter, parameter.type); + if (!type) { + invalid = true; + } + // TODO: See what happens on private modifiers. + if (parameter.modifiers?.some((modifier) => propertyLikeModifiers.has(modifier.kind))) { + nonStaticMembers.push(factory.createPropertySignature( + keepReadonlyKeyword(parameter.modifiers), + parameter.name as Identifier, + parameter.questionToken, + type, + )); + } + constructorParameters.push(factory.createParameterDeclaration( + /*modifiers*/ undefined, + parameter.dotDotDotToken, + parameter.name, + parameter.questionToken, + type, + parameter.initializer, + )); + } + } else if (isMethodDeclaration(member)) { + const type = localInferenceFromInitializer(member, member.type); + if (!type) { + invalid = true; + } + const methodSignature = factory.createMethodSignature( + /*modifiers*/ undefined, + member.name, + member.questionToken, + member.typeParameters, + member.parameters, + type, + ); + if (member.modifiers?.some((modifier) => modifier.kind === SyntaxKind.StaticKeyword)) { + staticMembers.push(methodSignature); + } else { + nonStaticMembers.push(methodSignature); + } + } else if (isGetAccessorDeclaration(member) || isSetAccessorDeclaration(member)) { + if (!hasGetSetAccessor) { + hasGetSetAccessor = true; + let type; + if (isGetAccessorDeclaration(member)) { + type = localInferenceFromInitializer(member, member.type); + } else { + type = localInferenceFromInitializer(member.parameters[0], member.parameters[0].type) + } + if (!type) { + invalid = true; + } + nonStaticMembers.push( + factory.createPropertySignature( + [], + member.name, + /*questionToken*/ undefined, + type, + ) + ); + } + } else if (isIndexSignatureDeclaration(member)) { + nonStaticMembers.push(member); + } else if (isPropertyDeclaration(member)) { + const name = isPrivateIdentifier(member.name) ? + // imitating the behavior from utilities.ts : getSymbolNameForPrivateIdentifier, but as we don't have + // a Symbol & SymbolId in hand, we use NodeId of the declaration instead as an approximiation and to provide uniqueness. + // TODO: This seems to have a high collision possibilitiy than the vanilla implementation as we have much less + // ids for nodes in DTE. + factory.createStringLiteral(`__#${node.parent.id}@${member.name.escapedText}`) : + member.name; + const type = localInferenceFromInitializer(member, member.type); + if (!type) { + invalid = true; + } + const propertySignature = factory.createPropertySignature( + keepReadonlyKeyword(member.modifiers), + name, + member.questionToken, + type, + ) + if (member.modifiers?.some((modifier) => modifier.kind === SyntaxKind.StaticKeyword)) { + staticMembers.push(propertySignature); + } else { + nonStaticMembers.push(propertySignature); + } + } + } + + if (invalid) { + return { typeNode: makeInvalidType(), flags: LocalTypeInfoFlags.Invalid, sourceNode: node }; + } + else { + const constructorSignature = factory.createConstructSignature( + node.typeParameters, + constructorParameters, + factory.createTypeLiteralNode(nonStaticMembers) + ); + const typeNode = factory.createTypeLiteralNode([constructorSignature, ...staticMembers]); + return { typeNode, flags: LocalTypeInfoFlags.None, sourceNode: node }; + } + } + + function keepReadonlyKeyword(modifiers?: NodeArray): Modifier[] { + if (modifiers?.some((modifier) => modifier.kind === SyntaxKind.ReadonlyKeyword)) { + return [factory.createModifier(SyntaxKind.ReadonlyKeyword)]; + } else { + return []; + } + } + function normalizeLiteralValue(literal: LiteralExpression) { switch (literal.kind) { case SyntaxKind.BigIntLiteral: