feat(semantic): report TS1228 for invalid type predicates (#23174)

- Emit TS1228 for invalid type predicate positions from semantic syntax checks.
- Treat valid predicates as only those that are the immediate return type of a function, method, function type, call signature, or non-accessor method signature.

## Why semantic
TypeScript parses type predicates as part of the normal type grammar, then validates their placement later in checker logic. This is not a parser recovery decision: the syntax can be represented in the AST, but whether it is legal depends on the predicate node's parent chain.

Keeping this check in semantic avoids parser mode plumbing and matches that model. The semantic checker can inspect ancestry to reject predicates in property annotations, constructor types, accessors, construct signatures, and nested or parenthesized type positions while reporting the span on the predicate node itself.
This commit is contained in:
camc314 committed 2026-06-11 19:50:06 +00:00
1 parent ff94d4afa5
commit 47991bd7b5
4 files changed
+192 -9

No files matched your search

+1
View File
@@ -104,6 +104,7 @@ pub fn check<'a>(kind: AstKind<'a>, ctx: &SemanticBuilder<'a>) {
js::check_variable_declarator_redeclaration(decl, ctx);
}
AstKind::TSTypeAnnotation(annot) => ts::check_ts_type_annotation(annot, ctx),
AstKind::TSTypePredicate(predicate) => ts::check_ts_type_predicate(predicate, ctx),
AstKind::TSTypeParameter(param) => ts::check_ts_type_parameter(param, ctx),
AstKind::TSModuleDeclaration(decl) => ts::check_ts_module_declaration(decl, ctx),
AstKind::TSGlobalDeclaration(decl) => ts::check_ts_global_declaration(decl, ctx),
@@ -64,6 +64,37 @@ pub fn check_ts_type_annotation(annotation: &TSTypeAnnotation<'_>, ctx: &Semanti
));
}
pub fn check_ts_type_predicate(predicate: &TSTypePredicate<'_>, ctx: &SemanticBuilder<'_>) {
if !is_allowed_type_predicate_position(ctx) {
ctx.error(diagnostics::type_predicate_only_in_return_type(predicate.span));
}
}
fn is_allowed_type_predicate_position(ctx: &SemanticBuilder<'_>) -> bool {
let mut ancestors = ctx.ancestry().ancestor_kinds();
let Some(AstKind::TSTypeAnnotation(_)) = ancestors.next() else {
return false;
};
match ancestors.next() {
Some(AstKind::Function(_)) => match ancestors.next() {
Some(AstKind::MethodDefinition(method)) => method.kind.is_method(),
Some(AstKind::ObjectProperty(property)) => !property.kind.is_accessor(),
_ => true,
},
Some(
AstKind::ArrowFunctionExpression(_)
| AstKind::TSFunctionType(_)
| AstKind::TSCallSignatureDeclaration(_),
) => true,
Some(AstKind::TSMethodSignature(signature)) => {
signature.kind == TSMethodSignatureKind::Method
}
_ => false,
}
}
pub fn check_ts_infer_type<'a>(infer_type: &TSInferType<'a>, ctx: &SemanticBuilder<'a>) {
let is_in_conditional_extends_clause = ctx.ancestry().ancestor_kinds().any(|kind| {
kind.as_ts_conditional_type().is_some_and(|conditional| {
+9
View File
@@ -241,6 +241,15 @@ pub fn require_class_name(span: Span) -> OxcDiagnostic {
OxcDiagnostic::error("A class name is required.").with_label(span)
}
#[cold]
pub fn type_predicate_only_in_return_type(span: Span) -> OxcDiagnostic {
ts_error(
"1228",
"A type predicate is only allowed in return type position for functions and methods.",
)
.with_label(span)
}
#[cold]
pub fn super_without_derived_class(span: Span, span1: Span) -> OxcDiagnostic {
OxcDiagnostic::error("'super' can only be referenced in a derived class.")
+151 -9
View File
@@ -3,7 +3,7 @@ commit: 7539c04d
parser_typescript Summary:
AST Parsed : 9779/9779 (100.00%)
Positive Passed: 9779/9779 (100.00%)
Negative Passed: 1638/2640 (62.05%)
Negative Passed: 1642/2640 (62.20%)
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/FunctionDeclaration3.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/FunctionDeclaration4.ts
@@ -1074,8 +1074,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/constEnum
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/constEnums/importElisionConstEnumMerge1.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/controlFlow/assertionTypePredicates1.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/controlFlow/controlFlowAliasing.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/controlFlow/controlFlowDeleteOperator.ts
@@ -1394,10 +1392,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/expressio
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormInstanceOf.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMember.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMemberErrors.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/expressions/typeGuards/typeGuardRedundancy.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/expressions/typeGuards/typeGuardsInIfStatement.ts
@@ -1412,8 +1406,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/expressio
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfBySymbolHasInstance.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/expressions/typeGuards/typePredicateOnVariableDeclaration01.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/expressions/typeSatisfaction/typeSatisfaction_optionalMemberConformance.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/expressions/typeSatisfaction/typeSatisfaction_propNameConstraining.ts
@@ -17839,6 +17831,62 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
╰────
help: Only 'readonly' modifier is allowed here.
× TS(1228): A type predicate is only allowed in return type position for functions and methods.
╭─[typescript/tests/cases/conformance/controlFlow/assertionTypePredicates1.ts:157:37]
156 │
157 │ declare let Q1: new (x: unknown) => x is string;
· ───────────
158 │ declare let Q2: new (x: boolean) => asserts x;
╰────
× TS(1228): A type predicate is only allowed in return type position for functions and methods.
╭─[typescript/tests/cases/conformance/controlFlow/assertionTypePredicates1.ts:158:37]
157 │ declare let Q1: new (x: unknown) => x is string;
158 │ declare let Q2: new (x: boolean) => asserts x;
· ─────────
159 │ declare let Q3: new (x: unknown) => asserts x is string;
╰────
× TS(1228): A type predicate is only allowed in return type position for functions and methods.
╭─[typescript/tests/cases/conformance/controlFlow/assertionTypePredicates1.ts:159:37]
158 │ declare let Q2: new (x: boolean) => asserts x;
159 │ declare let Q3: new (x: unknown) => asserts x is string;
· ───────────────────
160 │
╰────
× TS(1228): A type predicate is only allowed in return type position for functions and methods.
╭─[typescript/tests/cases/conformance/controlFlow/assertionTypePredicates1.ts:162:15]
161 │ declare class Wat {
162 │ get p1(): this is string;
· ──────────────
163 │ set p1(x: this is string);
╰────
× TS(1228): A type predicate is only allowed in return type position for functions and methods.
╭─[typescript/tests/cases/conformance/controlFlow/assertionTypePredicates1.ts:163:15]
162 │ get p1(): this is string;
163 │ set p1(x: this is string);
· ──────────────
164 │ get p2(): asserts this is string;
╰────
× TS(1228): A type predicate is only allowed in return type position for functions and methods.
╭─[typescript/tests/cases/conformance/controlFlow/assertionTypePredicates1.ts:164:15]
163 │ set p1(x: this is string);
164 │ get p2(): asserts this is string;
· ──────────────────────
165 │ set p2(x: asserts this is string);
╰────
× TS(1228): A type predicate is only allowed in return type position for functions and methods.
╭─[typescript/tests/cases/conformance/controlFlow/assertionTypePredicates1.ts:165:15]
164 │ get p2(): asserts this is string;
165 │ set p2(x: asserts this is string);
· ──────────────────────
166 │ }
╰────
× TS(1263): Declarations with initializers cannot also have definite assignment assertions.
╭─[typescript/tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts:20:6]
19 │ class C3 {
@@ -22466,6 +22514,100 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
╰────
help: Try inserting a semicolon here
× TS(1228): A type predicate is only allowed in return type position for functions and methods.
╭─[typescript/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMember.ts:4:10]
3 │ export class FileSystemObject {
4 │ isFSO: this is FileSystemObject;
· ────────────────────────
5 │ get isFile(): this is File {
╰────
× TS(1228): A type predicate is only allowed in return type position for functions and methods.
╭─[typescript/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMember.ts:5:17]
4 │ isFSO: this is FileSystemObject;
5 │ get isFile(): this is File {
· ────────────
6 │ return this instanceof File;
╰────
× TS(1228): A type predicate is only allowed in return type position for functions and methods.
╭─[typescript/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMember.ts:11:22]
10 │ }
11 │ get isDirectory(): this is Directory {
· ─────────────────
12 │ return this instanceof Directory;
╰────
× TS(1228): A type predicate is only allowed in return type position for functions and methods.
╭─[typescript/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMember.ts:14:16]
13 │ }
14 │ isNetworked: this is (Networked & this);
· ──────────────────────────
15 │ constructor(public path: string) {}
╰────
× TS(1228): A type predicate is only allowed in return type position for functions and methods.
╭─[typescript/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMember.ts:57:13]
56 │ target: T;
57 │ isLeader: this is (GenericLeadGuard<T>);
· ─────────────────────────────
58 │ isFollower: this is GenericFollowerGuard<T>;
╰────
× TS(1228): A type predicate is only allowed in return type position for functions and methods.
╭─[typescript/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMember.ts:58:15]
57 │ isLeader: this is (GenericLeadGuard<T>);
58 │ isFollower: this is GenericFollowerGuard<T>;
· ───────────────────────────────
59 │ }
╰────
× TS(1228): A type predicate is only allowed in return type position for functions and methods.
╭─[typescript/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMember.ts:70:19]
69 │ interface SpecificGuard {
70 │ isMoreSpecific: this is MoreSpecificGuard;
· ─────────────────────────
71 │ }
╰────
× TS(1228): A type predicate is only allowed in return type position for functions and methods.
╭─[typescript/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMemberErrors.ts:4:10]
3 │ export class FileSystemObject {
4 │ isFSO: this is FileSystemObject;
· ────────────────────────
5 │ get isFile(): this is File {
╰────
× TS(1228): A type predicate is only allowed in return type position for functions and methods.
╭─[typescript/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMemberErrors.ts:5:17]
4 │ isFSO: this is FileSystemObject;
5 │ get isFile(): this is File {
· ────────────
6 │ return this instanceof File;
╰────
× TS(1228): A type predicate is only allowed in return type position for functions and methods.
╭─[typescript/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMemberErrors.ts:11:22]
10 │ }
11 │ get isDirectory(): this is Directory {
· ─────────────────
12 │ return this instanceof Directory;
╰────
× TS(1228): A type predicate is only allowed in return type position for functions and methods.
╭─[typescript/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMemberErrors.ts:14:16]
13 │ }
14 │ isNetworked: this is (Networked & this);
· ──────────────────────────
15 │ constructor(public path: string) {}
╰────
× TS(1228): A type predicate is only allowed in return type position for functions and methods.
╭─[typescript/tests/cases/conformance/expressions/typeGuards/typePredicateOnVariableDeclaration01.ts:1:8]
1 │ var x: this is string;
· ──────────────
╰────
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[typescript/tests/cases/conformance/expressions/typeGuards/typePredicateOnVariableDeclaration02.ts:1:9]
1 │ var y: z is number;