Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support 'typeof class' types #41587

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12534,7 +12534,9 @@ namespace ts {
// The expression is processed as an identifier expression (section 4.3)
// or property access expression(section 4.10),
// the widened type(section 3.9) of which becomes the result.
links.resolvedType = getRegularTypeOfLiteralType(getWidenedType(checkExpression(node.exprName)));
links.resolvedType = node.exprName.kind === SyntaxKind.ClassExpression ?
checkClassExpression(node.exprName) :
getRegularTypeOfLiteralType(getWidenedType(checkExpression(node.exprName)));
}
return links.resolvedType;
}
Expand Down Expand Up @@ -39040,14 +39042,14 @@ namespace ts {
if (flags & ModifierFlags.Abstract) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "abstract");
}
if (node.kind !== SyntaxKind.ClassDeclaration) {
if (node.kind !== SyntaxKind.ClassDeclaration && node.kind !== SyntaxKind.ClassExpression) {
if (node.kind !== SyntaxKind.MethodDeclaration &&
node.kind !== SyntaxKind.PropertyDeclaration &&
node.kind !== SyntaxKind.GetAccessor &&
node.kind !== SyntaxKind.SetAccessor) {
return grammarErrorOnNode(modifier, Diagnostics.abstract_modifier_can_only_appear_on_a_class_method_or_property_declaration);
}
if (!(node.parent.kind === SyntaxKind.ClassDeclaration && hasSyntacticModifier(node.parent, ModifierFlags.Abstract))) {
if (!((node.parent.kind === SyntaxKind.ClassDeclaration || node.parent.kind === SyntaxKind.ClassExpression) && hasSyntacticModifier(node.parent, ModifierFlags.Abstract))) {
return grammarErrorOnNode(modifier, Diagnostics.Abstract_methods_can_only_appear_within_an_abstract_class);
}
if (flags & ModifierFlags.Static) {
Expand Down Expand Up @@ -39154,6 +39156,7 @@ namespace ts {
case SyntaxKind.FunctionDeclaration:
return nodeHasAnyModifiersExcept(node, SyntaxKind.AsyncKeyword);
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
return nodeHasAnyModifiersExcept(node, SyntaxKind.AbstractKeyword);
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.VariableStatement:
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1733,15 +1733,15 @@ namespace ts {
}

// @api
function createTypeQueryNode(exprName: EntityName) {
function createTypeQueryNode(exprName: EntityName | ClassExpression) {
const node = createBaseNode<TypeQueryNode>(SyntaxKind.TypeQuery);
node.exprName = exprName;
node.transformFlags = TransformFlags.ContainsTypeScript;
return node;
}

// @api
function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName) {
function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName | ClassExpression) {
return node.exprName !== exprName
? update(createTypeQueryNode(exprName), node)
: node;
Expand Down
18 changes: 16 additions & 2 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2860,10 +2860,16 @@ namespace ts {
return type;
}

function isStartOfTypeofClassExpression() {
return token() === SyntaxKind.ClassKeyword || token() === SyntaxKind.AbstractKeyword && lookAhead(nextTokenIsClassKeywordOnSameLine);
}

function parseTypeQuery(): TypeQueryNode {
const pos = getNodePos();
parseExpected(SyntaxKind.TypeOfKeyword);
return finishNode(factory.createTypeQueryNode(parseEntityName(/*allowReservedWords*/ true)), pos);
return finishNode(factory.createTypeQueryNode(isStartOfTypeofClassExpression() ?
doInsideOfContext(NodeFlags.Ambient, parseClassExpression) :
parseEntityName(/*allowReservedWords*/ true)), pos);
}

function parseTypeParameter(): TypeParameterDeclaration {
Expand Down Expand Up @@ -5315,6 +5321,11 @@ namespace ts {
}

return parseFunctionExpression();
case SyntaxKind.AbstractKeyword:
if (!lookAhead(nextTokenIsClassKeywordOnSameLine)) {
break;
}
// Fall through
case SyntaxKind.ClassKeyword:
return parseClassExpression();
case SyntaxKind.FunctionKeyword:
Expand Down Expand Up @@ -6646,7 +6657,10 @@ namespace ts {
}

function parseClassExpression(): ClassExpression {
return <ClassExpression>parseClassDeclarationOrExpression(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined, SyntaxKind.ClassExpression);
const pos = getNodePos();
const hasJSDoc = hasPrecedingJSDocComment();
const modifiers = parseModifiers();
return <ClassExpression>parseClassDeclarationOrExpression(pos, hasJSDoc, /*decorators*/ undefined, modifiers, SyntaxKind.ClassExpression);
}

function parseClassDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray<Decorator> | undefined, modifiers: NodeArray<Modifier> | undefined): ClassDeclaration {
Expand Down
6 changes: 4 additions & 2 deletions src/compiler/symbolWalker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,10 @@ namespace ts {
// query node on any of the symbol's declarations and get symbols there
if ((d as any).type && (d as any).type.kind === SyntaxKind.TypeQuery) {
const query = (d as any).type as TypeQueryNode;
const entity = getResolvedSymbol(getFirstIdentifier(query.exprName));
visitSymbol(entity);
if (query.exprName.kind !== SyntaxKind.ClassExpression) {
const entity = getResolvedSymbol(getFirstIdentifier(query.exprName));
visitSymbol(entity);
}
}
});
return false;
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/transformers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,7 @@ namespace ts {
|| isTypeAliasDeclaration(node)
|| isModuleDeclaration(node)
|| isClassDeclaration(node)
|| isClassExpression(node)
|| isInterfaceDeclaration(node)
|| isFunctionLike(node)
|| isIndexSignatureDeclaration(node)
Expand Down Expand Up @@ -849,7 +850,7 @@ namespace ts {
getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(input as DeclarationDiagnosticProducing);
}

if (isTypeQueryNode(input)) {
if (isTypeQueryNode(input) && input.exprName.kind !== SyntaxKind.ClassExpression) {
checkEntityNameVisibility(input.exprName, enclosingDeclaration);
}

Expand Down
6 changes: 3 additions & 3 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1543,7 +1543,7 @@ namespace ts {

export interface TypeQueryNode extends TypeNode {
readonly kind: SyntaxKind.TypeQuery;
readonly exprName: EntityName;
readonly exprName: EntityName | ClassExpression;
}

// A TypeLiteral is the declaration node for an anonymous symbol.
Expand Down Expand Up @@ -6804,8 +6804,8 @@ namespace ts {
updateFunctionTypeNode(node: FunctionTypeNode, typeParameters: NodeArray<TypeParameterDeclaration> | undefined, parameters: NodeArray<ParameterDeclaration>, type: TypeNode): FunctionTypeNode;
createConstructorTypeNode(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode): ConstructorTypeNode;
updateConstructorTypeNode(node: ConstructorTypeNode, typeParameters: NodeArray<TypeParameterDeclaration> | undefined, parameters: NodeArray<ParameterDeclaration>, type: TypeNode): ConstructorTypeNode;
createTypeQueryNode(exprName: EntityName): TypeQueryNode;
updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName): TypeQueryNode;
createTypeQueryNode(exprName: EntityName | ClassExpression): TypeQueryNode;
updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName | ClassExpression): TypeQueryNode;
createTypeLiteralNode(members: readonly TypeElement[] | undefined): TypeLiteralNode;
updateTypeLiteralNode(node: TypeLiteralNode, members: NodeArray<TypeElement>): TypeLiteralNode;
createArrayTypeNode(elementType: TypeNode): ArrayTypeNode;
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/utilitiesPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,10 @@ namespace ts {
|| kind === SyntaxKind.Identifier;
}

export function isEntityNameOrClassExpression(node: Node): node is EntityName {
return isEntityName(node) || node.kind === SyntaxKind.ClassExpression;
}

export function isPropertyName(node: Node): node is PropertyName {
const kind = node.kind;
return kind === SyntaxKind.Identifier
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/visitorPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ namespace ts {

case SyntaxKind.TypeQuery:
return factory.updateTypeQueryNode((<TypeQueryNode>node),
nodeVisitor((<TypeQueryNode>node).exprName, visitor, isEntityName));
nodeVisitor((<TypeQueryNode>node).exprName, visitor, isEntityNameOrClassExpression));

case SyntaxKind.TypeLiteral:
return factory.updateTypeLiteralNode((<TypeLiteralNode>node),
Expand Down
21 changes: 9 additions & 12 deletions src/services/documentHighlights.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ namespace ts {

function getNodesToSearchForModifier(declaration: Node, modifierFlag: ModifierFlags): readonly Node[] | undefined {
// Types of node whose children might have modifiers.
const container = declaration.parent as ModuleBlock | SourceFile | Block | CaseClause | DefaultClause | ConstructorDeclaration | MethodDeclaration | FunctionDeclaration | ObjectTypeDeclaration | ObjectLiteralExpression;
const container = declaration.parent;
switch (container.kind) {
case SyntaxKind.ModuleBlock:
case SyntaxKind.SourceFile:
Expand All @@ -216,22 +216,21 @@ namespace ts {
return [...declaration.members, declaration];
}
else {
return container.statements;
return (<ModuleBlock | SourceFile | Block | CaseClause | DefaultClause>container).statements;
}
case SyntaxKind.Constructor:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.FunctionDeclaration:
return [...container.parameters, ...(isClassLike(container.parent) ? container.parent.members : [])];
return [...(<ConstructorDeclaration | MethodDeclaration | FunctionDeclaration>container).parameters, ...(isClassLike(container.parent) ? container.parent.members : [])];
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.TypeLiteral:
const nodes = container.members;

const nodes = (<ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeLiteralNode>container).members;
// If we're an accessibility modifier, we're in an instance member and should search
// the constructor's parameter list for instance members as well.
if (modifierFlag & (ModifierFlags.AccessibilityModifier | ModifierFlags.Readonly)) {
const constructor = find(container.members, isConstructorDeclaration);
const constructor = find(nodes, isConstructorDeclaration);
if (constructor) {
return [...nodes, ...constructor.parameters];
}
Expand All @@ -240,13 +239,11 @@ namespace ts {
return [...nodes, container];
}
return nodes;

// Syntactically invalid positions that the parser might produce anyway
case SyntaxKind.ObjectLiteralExpression:
return undefined;

default:
Debug.assertNever(container, "Invalid container kind.");
if (modifierFlag & ModifierFlags.Abstract && isClassExpression(declaration)) {
return [...declaration.members, declaration];
}
return undefined;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/services/refactors/extractType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ namespace ts.refactor {
return true;
}
}
else if (isTypeQueryNode(node)) {
else if (isTypeQueryNode(node) && node.exprName.kind !== SyntaxKind.ClassExpression) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering out loud: it might be useful to make an isTypeQueryableNode helper or similar, since this is already used twice internally, and it'd be helpful for consumers?

if (isIdentifier(node.exprName)) {
const symbol = checker.resolveName(node.exprName.text, node.exprName, SymbolFlags.Value, /* excludeGlobals */ false);
if (symbol && rangeContainsSkipTrivia(statement, symbol.valueDeclaration, file) && !rangeContainsSkipTrivia(selection, symbol.valueDeclaration, file)) {
Expand Down
11 changes: 6 additions & 5 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -881,7 +881,7 @@ declare namespace ts {
}
export interface TypeQueryNode extends TypeNode {
readonly kind: SyntaxKind.TypeQuery;
readonly exprName: EntityName;
readonly exprName: EntityName | ClassExpression;
}
export interface TypeLiteralNode extends TypeNode, Declaration {
readonly kind: SyntaxKind.TypeLiteral;
Expand Down Expand Up @@ -3235,8 +3235,8 @@ declare namespace ts {
updateFunctionTypeNode(node: FunctionTypeNode, typeParameters: NodeArray<TypeParameterDeclaration> | undefined, parameters: NodeArray<ParameterDeclaration>, type: TypeNode): FunctionTypeNode;
createConstructorTypeNode(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode): ConstructorTypeNode;
updateConstructorTypeNode(node: ConstructorTypeNode, typeParameters: NodeArray<TypeParameterDeclaration> | undefined, parameters: NodeArray<ParameterDeclaration>, type: TypeNode): ConstructorTypeNode;
createTypeQueryNode(exprName: EntityName): TypeQueryNode;
updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName): TypeQueryNode;
createTypeQueryNode(exprName: EntityName | ClassExpression): TypeQueryNode;
updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName | ClassExpression): TypeQueryNode;
createTypeLiteralNode(members: readonly TypeElement[] | undefined): TypeLiteralNode;
updateTypeLiteralNode(node: TypeLiteralNode, members: NodeArray<TypeElement>): TypeLiteralNode;
createArrayTypeNode(elementType: TypeNode): ArrayTypeNode;
Expand Down Expand Up @@ -4215,6 +4215,7 @@ declare namespace ts {
function isStringTextContainingNode(node: Node): node is StringLiteral | TemplateLiteralToken;
function isModifier(node: Node): node is Modifier;
function isEntityName(node: Node): node is EntityName;
function isEntityNameOrClassExpression(node: Node): node is EntityName;
function isPropertyName(node: Node): node is PropertyName;
function isBindingName(node: Node): node is BindingName;
function isFunctionLike(node: Node): node is SignatureDeclaration;
Expand Down Expand Up @@ -10184,9 +10185,9 @@ declare namespace ts {
/** @deprecated Use `factory.updateConstructorTypeNode` or the factory supplied by your transformation context instead. */
const updateConstructorTypeNode: (node: ConstructorTypeNode, typeParameters: NodeArray<TypeParameterDeclaration> | undefined, parameters: NodeArray<ParameterDeclaration>, type: TypeNode) => ConstructorTypeNode;
/** @deprecated Use `factory.createTypeQueryNode` or the factory supplied by your transformation context instead. */
const createTypeQueryNode: (exprName: EntityName) => TypeQueryNode;
const createTypeQueryNode: (exprName: Identifier | ClassExpression | QualifiedName) => TypeQueryNode;
/** @deprecated Use `factory.updateTypeQueryNode` or the factory supplied by your transformation context instead. */
const updateTypeQueryNode: (node: TypeQueryNode, exprName: EntityName) => TypeQueryNode;
const updateTypeQueryNode: (node: TypeQueryNode, exprName: Identifier | ClassExpression | QualifiedName) => TypeQueryNode;
/** @deprecated Use `factory.createTypeLiteralNode` or the factory supplied by your transformation context instead. */
const createTypeLiteralNode: (members: readonly TypeElement[] | undefined) => TypeLiteralNode;
/** @deprecated Use `factory.updateTypeLiteralNode` or the factory supplied by your transformation context instead. */
Expand Down