tsv_ts 0.8.0

TypeScript/JS formatter, type stripper and acorn-compatible parser — part of tsv
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
// Class declaration parsing

use crate::ast::{
    Accessibility, BlockStatement, ClassBody, ClassDeclaration, ClassExpression, ClassMember,
    Decorator, ExportDefaultDeclaration, ExportDefaultValue, ExportKind, ExportNamedDeclaration,
    Expression, ExpressionKind, FunctionExpression, Identifier, Literal, LiteralValue,
    MethodDefinition, MethodKind, PropertyDefinition, PropertyModifier, Statement, StatementKind,
    StaticBlock, TSIndexSignature, TSInterfaceHeritage, TSTypeParameterDeclaration,
    TSTypeParameterInstantiation,
};
use crate::lexer::{KeywordKind, TokenKind};
use crate::printer::ParenContext;
use tsv_lang::{Error, Span};

use super::super::Parser;
use super::super::expression::alloc_expr;
use super::ModuleItemContext;

/// Whose `DecoratorList` is being parsed — the axis that decides whether the run
/// opens a class's strict scope. Named at every call site rather than inferred,
/// because the two lists reach one parser from opposite sides: `parse_decorators`
/// is called both from the class parsers and from the **generic** parameter-list
/// parser, which plain functions and object-literal methods share with class
/// methods. See [`Parser::parse_decorators`] for the rule and its oracle.
#[derive(Clone, Copy)]
pub(in crate::parser) enum DecoratorListKind {
    /// A class's own list (`@dec class C {}`) or one of its members' — strict code,
    /// so the run opens the class's strictness scope. On a member the scope is
    /// already open and the wrap is a no-op; naming it `Class` still states the fact.
    Class,
    /// A parameter's list (`f(@dec x)`) — part of the enclosing function's parameter
    /// list, which parses under the outer mode, so the run opens nothing.
    Parameter,
}

/// Everything parsed off the front of a class member before its body — the
/// modifier set, the (possibly computed) key, and the optional/type-param
/// markers. `parse_class_member` builds this, then dispatches to
/// `finish_method_member` / `finish_property_member`.
#[expect(clippy::struct_excessive_bools)] // independent modifier flags, not a state machine
struct ClassMemberHeader<'arena> {
    start: usize,
    decorators: &'arena [Decorator<'arena>],
    accessibility: Option<Accessibility>,
    is_static: bool,
    is_declare: bool,
    is_override: bool,
    is_abstract: bool,
    readonly: bool,
    accessor: bool,
    is_generator: bool,
    is_async: bool,
    accessor_kind: Option<MethodKind>,
    computed: bool,
    key: Expression<'arena>,
    /// Whether the (plain identifier or string-literal) key spells `constructor`
    /// — the one bit `finish_method_member` derives from the member name to pick
    /// `MethodKind::Constructor`. Computed/private/numeric keys are never the
    /// constructor, so they record `false`. The name itself isn't carried: it is
    /// recoverable from `key`, and nothing else consumes it.
    name_is_constructor: bool,
    modifier: PropertyModifier,
    type_parameters: Option<TSTypeParameterDeclaration<'arena>>,
}

/// Where an `export` keyword sits relative to a decorated class's decorator run.
///
/// The axis acorn's shapes for an **ambient** decorated class turn on: whether the
/// decorators or the class's own modifier chain act as the declaration head, and
/// whether `declare` is a legal modifier at all. A concrete (non-`declare`) class is
/// shaped identically in all three positions.
#[derive(Clone, Copy, PartialEq, Eq)]
pub(in crate::parser) enum DecoratedClassExport {
    /// No `export` (`@dec class C {}`), or one *preceding* the decorators
    /// (`export @dec class C {}`): the decorator run is the declaration head.
    BeforeOrAbsent,
    /// `export` between the decorators and the class (`@dec export class C {}`).
    Between,
    /// `@dec export default class {}` — the class name is optional, and `declare`
    /// is not a legal modifier here (tsc raises TS1005; acorn rejects).
    BetweenDefault,
}

/// A class's head and body — everything between `class` and its closing brace —
/// as `Parser::parse_class_head_and_body` hands it back, for the declaration and
/// expression nodes to wrap. The fields are the two nodes' shared ones, by name.
struct ClassHead<'arena> {
    id: Option<Identifier<'arena>>,
    type_parameters: Option<TSTypeParameterDeclaration<'arena>>,
    super_class: Option<&'arena Expression<'arena>>,
    super_type_parameters: Option<TSTypeParameterInstantiation<'arena>>,
    implements: &'arena [TSInterfaceHeritage<'arena>],
    body: ClassBody<'arena>,
}

impl<'a, 'arena> Parser<'a, 'arena> {
    pub(super) fn parse_class_declaration(&mut self) -> Result<Statement<'arena>, Error> {
        let class = self.parse_class_declaration_inner(true, false)?;
        Ok(Statement::from_class_declaration(self.arena.alloc(class)))
    }

    /// Parse an abstract class declaration: `abstract class Foo { ... }`
    pub(super) fn parse_abstract_class(&mut self) -> Result<Statement<'arena>, Error> {
        // Capture position of 'abstract' before consuming it
        let abstract_start = self.current_pos().0;
        debug_assert_eq!(self.current_value(), "abstract");
        self.advance()?;

        if self.had_line_terminator {
            // `abstract [no LineTerminator here] class`. The statement path decides this
            // one token earlier — a break there DEMOTES `abstract` to an identifier
            // statement, so this arm is unreachable from it — but the `export` path
            // dispatches straight here, where a break has no valid reading at all: tsc
            // rejects `export abstract⏎class B {}` (TS1128, `export` left with nothing to
            // attach to) and so do acorn and prettier. Without this it welded into ONE
            // exported abstract class, a reading no oracle grants.
            return Err(self.error_msg("'class' must be on the same line as 'abstract'"));
        }

        let class =
            self.parse_class_declaration_inner_with_start(true, true, abstract_start, false)?;
        Ok(Statement::from_class_declaration(self.arena.alloc(class)))
    }

    /// Parse a decorated class: `@decorator class Foo { }`
    ///
    /// Decorators can be stacked: `@dec1 @dec2 class Foo { }`
    /// Decorator can be followed by `abstract class` or `export class`
    ///
    /// `module_item` is the `ModuleItem` list the statement sits in, `None` at a nested
    /// statement position. The decorators do not make an `export` after them anything
    /// but an export declaration, so it takes the undecorated form's two refusals: the
    /// top-level rule where there is no module item, and the goal gate where there is.
    pub(super) fn parse_decorated_class(
        &mut self,
        module_item: Option<ModuleItemContext>,
    ) -> Result<Statement<'arena>, Error> {
        let start = self.current_pos().0;

        // Parse one or more decorators
        let decorators = self.parse_decorators(DecoratorListKind::Class)?;

        // Check for `export` before class
        let is_export = *self.current_kind() == TokenKind::Keyword(KeywordKind::Export);
        if is_export {
            let Some(context) = module_item else {
                return Err(self.error_module_item_position());
            };
            self.check_module_item_goal(context, "export", self.current_pos().0)?;
        }
        let is_default = if is_export {
            self.advance()?; // consume 'export'
            if *self.current_kind() == TokenKind::Keyword(KeywordKind::Default) {
                self.advance()?; // consume 'default'
                true
            } else {
                false
            }
        } else {
            false
        };

        // Optional `declare`/`abstract`, then the `class` declaration with the
        // decorators attached (name optional for `export default`).
        let export_position = match (is_export, is_default) {
            (false, _) => DecoratedClassExport::BeforeOrAbsent,
            (true, false) => DecoratedClassExport::Between,
            (true, true) => DecoratedClassExport::BetweenDefault,
        };
        let class = self.finish_decorated_class(start, decorators, export_position)?;

        // Wrap in export if needed
        if is_export {
            if is_default {
                let end = class.span.end;
                Ok(Statement {
                    span: Span::new(start as u32, end),
                    kind: StatementKind::ExportDefaultDeclaration(self.arena.alloc(
                        ExportDefaultDeclaration {
                            declaration: ExportDefaultValue::ClassDeclaration(class),
                        },
                    )),
                })
            } else {
                let end = class.span.end;
                // An exported *ambient* declaration is a type export to acorn, exactly
                // as in the undecorated `export declare class C {}` shape.
                let export_kind = if class.declare {
                    ExportKind::Type
                } else {
                    ExportKind::Value
                };
                let class_decl = Statement::from_class_declaration(self.arena.alloc(class));
                Ok(Statement {
                    span: Span::new(start as u32, end),
                    kind: StatementKind::ExportNamedDeclaration(self.arena.alloc(
                        ExportNamedDeclaration {
                            declaration: Some(self.alloc(class_decl)),
                            specifiers: &[],
                            source: None,
                            attributes: None,
                            export_kind,
                        },
                    )),
                })
            }
        } else {
            Ok(Statement::from_class_declaration(self.arena.alloc(class)))
        }
    }

    /// With leading `decorators` (starting at `deco_start`) already parsed, consume the
    /// optional `declare` and `abstract` modifiers, expect and parse the `class`
    /// declaration, attach the decorators, and extend the class span back over them.
    /// Shared by `parse_decorated_class` (decorator-first `@dec [export] class`) and the
    /// `export @dec class` arm of `parse_export_declaration` (decorator-after-`export`).
    ///
    /// An **ambient** class is a decorator target like its concrete counterpart — tsc
    /// accepts `@dec declare class C {}` and `@dec declare abstract class C {}` outright
    /// (parser *and* checker), and so does acorn-typescript. Two limits on that:
    /// decorating any other ambient declaration (`declare namespace`, `declare
    /// function`) is TS1206, a checker-raised early error the diagnostics layer owns —
    /// so only the class forms are consumed here, and anything else after `declare`
    /// still fails the `class` check below; and `export default` takes no `declare`
    /// (tsc TS1005, acorn rejects), so the modifier is left unconsumed there and the
    /// same check rejects.
    pub(super) fn finish_decorated_class(
        &mut self,
        deco_start: usize,
        decorators: bumpalo::collections::Vec<'arena, Decorator<'arena>>,
        export: DecoratedClassExport,
    ) -> Result<ClassDeclaration<'arena>, Error> {
        // `export default class {}` is the one name-optional decorated form.
        let name_required = export != DecoratedClassExport::BetweenDefault;

        // `declare` is contextual, so it only reads as the ambient modifier when a
        // class head actually follows it *on the same line* — `class`, or the
        // `abstract` before one. The `[no LineTerminator here]` gate is the same one
        // the undecorated path applies (`peek_starts_ambient_declaration`); without it
        // `@dec⏎declare⏎class A {}` would parse as one ambient class, where tsc raises
        // TS1146 and acorn builds a `declare` expression statement *overlapped* by a
        // class node spanning back to the decorator. tsv rejects, per tsc.
        let head_start = self.current_pos().0;
        let is_declare = name_required
            && matches!(self.current_kind(), TokenKind::Identifier)
            && self.current_value() == "declare"
            && !self.peek_preceded_by_line_terminator()
            && (self.peek_kind() == TokenKind::Keyword(KeywordKind::Class)
                || (matches!(self.peek_kind(), TokenKind::Identifier)
                    && self.peek_value() == "abstract"));
        if is_declare {
            self.advance()?; // consume 'declare'
        }

        // `abstract` takes the same `[no LineTerminator here]` gate, for the same
        // reason: `@dec abstract⏎class B {}` is TS1146 to tsc, and acorn's accept is
        // the same overlapping-node shape.
        let is_abstract = matches!(self.current_kind(), TokenKind::Identifier)
            && self.current_value() == "abstract"
            && !self.peek_preceded_by_line_terminator()
            && self.peek_kind() == TokenKind::Keyword(KeywordKind::Class);
        if is_abstract {
            self.advance()?; // consume 'abstract'
        }

        if !matches!(self.current_kind(), TokenKind::Keyword(KeywordKind::Class)) {
            return Err(self.error_expected_after("'class'", "decorator"));
        }

        let class_start = self.current_pos().0;
        // Everything under `declare` parses in ambient context, exactly as the
        // undecorated path does (`parse_declare_statement`). Only set the flag when
        // this class introduces it — an unconditional `with_context_flag` would clear
        // an ambient context the enclosing `declare module` already established.
        let mut class = if is_declare {
            self.with_context_flag(
                |p| &mut p.in_ambient_context,
                true,
                |p| {
                    p.parse_class_declaration_inner_with_start(
                        name_required,
                        is_abstract,
                        class_start,
                        true,
                    )
                },
            )?
        } else {
            self.parse_class_declaration_inner_with_start(
                name_required,
                is_abstract,
                class_start,
                false,
            )?
        };
        // Always a `Some`: both callers dispatch on `TokenKind::At`, so the run is
        // non-empty. `None` and `Some(&[])` are different wire shapes — the field is
        // omitted for the first and emitted as `[]` for the second — so folding an empty
        // run to `None` here would be a claim about a case that cannot arrive; see
        // `parse_class_expression_from`, which states the same rule for its own entry.
        debug_assert!(
            !decorators.is_empty(),
            "a decorated class is only reached at a `@`"
        );
        class.decorators = Some(decorators.into_bump_slice());
        // The class span covers its decorators — except for an ambient class behind an
        // intervening `export`, where acorn anchors the declaration at its own
        // `declare` and leaves the decorators outside the span (`@dec export declare
        // class C {}` → class starts at `declare`, decorator at `@`).
        let span_start = if is_declare && export == DecoratedClassExport::Between {
            head_start
        } else {
            deco_start
        };
        class.span = Span::new(span_start as u32, class.span.end);
        Ok(class)
    }

    /// Parse a decorated class expression: `@dec class {}`.
    ///
    /// The expression-position counterpart of `parse_decorated_class` (which
    /// builds a class *declaration*): parse the decorator list, then a class
    /// expression, attaching the decorators to it. `export` and `abstract` never appear
    /// here: the `export default` value, a decorated class tsc reads as a class declaration
    /// (so the statement ends at the class) and the one position that admits `abstract`
    /// (`export default @dec abstract class {}`), reads the list itself and finishes it
    /// through [`Parser::finish_decorated_class_expression`].
    pub(in crate::parser) fn parse_decorated_class_expression(
        &mut self,
    ) -> Result<&'arena Expression<'arena>, Error> {
        let start = self.current_pos().0;
        // one wire level: the `ClassExpression` over its decorators, heritage and body
        self.nested(1, |p| {
            let decorators = p.parse_decorators(DecoratorListKind::Class)?;
            p.finish_decorated_class_expression(start, decorators, false)
        })
    }

    /// Whether the current token is an `abstract` modifier heading a class: `abstract`
    /// then `class` under the `[no LineTerminator here]` gate `finish_decorated_class`
    /// applies (`export default @dec abstract⏎class C {}` is TS1146 to tsc).
    pub(super) fn at_abstract_class_modifier(&mut self) -> bool {
        matches!(self.current_kind(), TokenKind::Identifier)
            && self.current_value() == "abstract"
            && !self.peek_preceded_by_line_terminator()
            && self.peek_kind() == TokenKind::Keyword(KeywordKind::Class)
    }

    /// Finish a decorated class expression whose decorator list, begun at `start`, is
    /// parsed: the `abstract` modifier the caller has already sighted
    /// ([`Parser::at_abstract_class_modifier`]) when `r#abstract` is set, then the class.
    /// Nowhere but the `export default` value does a decorated class take a modifier
    /// (`x = @dec abstract class {}`, which prettier rejects), so only that caller sets it.
    pub(super) fn finish_decorated_class_expression(
        &mut self,
        start: usize,
        decorators: bumpalo::collections::Vec<'arena, Decorator<'arena>>,
        r#abstract: bool,
    ) -> Result<&'arena Expression<'arena>, Error> {
        if r#abstract {
            self.advance()?; // consume 'abstract'
        }

        if *self.current_kind() != TokenKind::Keyword(KeywordKind::Class) {
            return Err(self.error_expected_after("'class'", "decorator"));
        }

        // The run is non-empty — this is only reached at a `@` — so it is always a
        // `Some`. `start` is that first `@`, so the class node's span covers it.
        self.parse_class_expression_from(start, Some(decorators.into_bump_slice()), r#abstract)
    }

    /// Parse a list of decorators: `@dec1 @dec2 ...`
    ///
    /// Whose list it is decides its **strictness**, which is why every caller names it
    /// ([`DecoratorListKind`]). A *class's* `DecoratorList` is part of the class — the
    /// decorators proposal puts it inside `ClassDeclaration` / `ClassExpression`, and
    /// every part of a class is strict mode code (ecma262 sec-strict-mode-code) — so
    /// the class's strictness scope opens here, at the first `@`, and not only at the
    /// `class` keyword: `@dec(010) class C {}` is an error in a sloppy script, as
    /// `class C extends (010) {}` is. (acorn-typescript grades the list under the
    /// enclosing mode — a cataloged divergence.)
    ///
    /// A *parameter's* list is not part of a class. It belongs to the parameter list of
    /// whatever function encloses it, and a parameter list parses under the outer mode
    /// — the same rule a parameter *default* follows — so `function f(@dec(010) x) {}`
    /// is legal in a sloppy script and `class C { m(@dec(010) x) {} }` is not, the
    /// strictness coming from the class either way and never from the `@`. acorn agrees
    /// on both. Pinned by
    /// `tests/fixtures/typescript/script_goal/param_decorator_outer_mode_prettier_divergence`.
    pub(in crate::parser) fn parse_decorators(
        &mut self,
        kind: DecoratorListKind,
    ) -> Result<bumpalo::collections::Vec<'arena, Decorator<'arena>>, Error> {
        match kind {
            DecoratorListKind::Class => self.with_strict_class_scope(Self::parse_decorator_run),
            DecoratorListKind::Parameter => self.parse_decorator_run(),
        }
    }

    /// The run itself: `@dec` until the `@`s stop. Split out so
    /// [`parse_decorators`](Self::parse_decorators) can hand it to the strict-scope
    /// combinator or call it bare, one spelling either way.
    fn parse_decorator_run(
        &mut self,
    ) -> Result<bumpalo::collections::Vec<'arena, Decorator<'arena>>, Error> {
        let mut decorators = self.bvec();

        while *self.current_kind() == TokenKind::At {
            decorators.push(self.parse_decorator()?);
        }

        Ok(decorators)
    }

    /// Parse a single decorator: `@expression`, where the expression follows the
    /// restricted ES decorators grammar — see `parse_decorator_expression`.
    fn parse_decorator(&mut self) -> Result<Decorator<'arena>, Error> {
        let start = self.current_pos().0;

        // Consume '@'
        debug_assert_eq!(*self.current_kind(), TokenKind::At);
        self.advance()?;

        // Parse the decorator expression under the restricted ES decorators
        // grammar (identifier / `.`-member chain / single call, or a
        // parenthesized full expression) — NOT a full `AssignmentExpression`, so
        // a trailing `*` or binary operator is left for the following construct
        // (e.g. a decorated generator method `@fn *a() {}`).
        // one wire level: the `Decorator` over its expression (the depth limit's count) —
        // the holder's own level is its caller's (`Parser::parse_decorated_class_expression`,
        // a member's and a parameter's list position)
        let expression = self.nested(1, Self::parse_decorator_expression)?;

        // The decorator span covers a parenthesized expression's closing `)`
        // (`@(expr)`), which the paren-stripped expression span excludes
        let end = self.prev_token_end();

        // the pair the printer writes around an expression the bare decorator grammar
        // cannot spell (`@a!(…)` prints `@(a!(…))`), a level of the output's nesting —
        // unless the author's own pair wraps the whole expression (`@(a + b)`), which
        // ends before the decorator does; a pair around a callee alone (`@(a!)(…)`) holds
        // less than the printer's would
        // (a wire-only parse keeps that pair as a node, `parse_preserve_parens`)
        if crate::printer::decorator_takes_pair(&expression)
            && expression.span().end as usize == end
            && !matches!(expression.kind, ExpressionKind::ParenthesizedExpression(_))
        {
            self.printer_pair()?;
        }

        Ok(Decorator {
            expression,
            span: Span::new(start as u32, end as u32),
        })
    }

    /// Parse an optional `extends <super_class>` heritage clause, shared by the
    /// class-declaration and class-expression parsers. The superclass is any
    /// `LeftHandSideExpression` (`parse_heritage_expression`); `extends Base<T>`
    /// splits into `Base` plus separate `super_type_parameters`. Returns
    /// `(None, None)` when there is no `extends`.
    fn parse_optional_extends_clause(
        &mut self,
    ) -> Result<
        (
            Option<&'arena Expression<'arena>>,
            Option<TSTypeParameterInstantiation<'arena>>,
        ),
        Error,
    > {
        if !matches!(
            self.current_kind(),
            TokenKind::Keyword(KeywordKind::Extends)
        ) {
            return Ok((None, None));
        }
        self.advance()?; // consume 'extends'
        let heritage_start = self.current_pos().0;
        // no level of its own: the superclass is a node of its own wherever it holds one
        // (`class extends class extends …` counts each class, `class extends f(…)` the
        // call's arguments), under a class whose level is counted already
        let expr = self.parse_heritage_expression()?;
        // the pair the printer adds around a bare heritage it parenthesizes for clarity
        // (`extends (new B())`), a level of the output's nesting
        if expr.span().start as usize == heritage_start
            && expr.span().end as usize == self.prev_token_end()
        {
            self.count_operand_pairs(Some((expr, ParenContext::SuperClass)), None)?;
        }
        let type_args = self.parse_optional_type_arguments()?;
        Ok((Some(expr), type_args))
    }

    /// Take an optional class name — a `BindingIdentifier`, so contextual type keywords
    /// (`class any {}`) are valid names and `await` is one only at Script `[~Await]` (both
    /// handled by `take_binding_identifier`).
    ///
    /// `implements` is genuinely ambiguous directly after `class`: it may be the NAME
    /// (`class implements {}` — a strict-mode-reserved word, but that bar is a deferred
    /// early error like `package` or `static`) or the keyword opening the extends-less
    /// heritage clause of an anonymous class (`export default class implements Foo {}`).
    /// One token of lookahead separates them, and the rule is tsc's own
    /// (`isImplementsClause`: `token() === ImplementsKeyword && lookAhead(nextTokenIsIdentifierOrKeyword)`):
    /// **a following identifier-or-keyword makes it the clause, anything else makes it the
    /// name.** So `class implements {}` / `class implements<T> {}` / `var v = class
    /// implements {};` name the class, while `class implements Foo {}` opens a heritage
    /// list. acorn rejects `implements` in both roles; tsc and prettier accept both.
    ///
    /// ⚠️ The lookahead deliberately does NOT try to be cleverer than tsc here. `class
    /// implements extends B {}` puts a keyword next, so `implements` reads as the clause and
    /// the declaration ends up nameless — a rejection. tsc "accepts" it only by *recovering*
    /// with an empty heritage list, and prettier rejects it outright, so matching tsc's
    /// recovery would buy a shape no formatter can print.
    ///
    /// Returns `Ok(None)` for the heritage reading and for a non-binding token; the caller
    /// decides whether a missing name is an error (declaration) or fine (expression /
    /// `export default`). Shared by both class paths.
    fn take_class_name(&mut self) -> Result<Option<Identifier<'arena>>, Error> {
        if self.current_value() == "implements" && self.peek_is_identifier_or_keyword() {
            return Ok(None);
        }
        self.take_binding_identifier()
    }

    /// Parse a class expression: `class { }` or `class Foo<T> extends Bar { }`
    ///
    /// Class expressions are similar to class declarations but:
    /// - The name is always optional
    /// - They appear in expression position
    /// - No `declare` field
    pub(in crate::parser) fn parse_class_expression(
        &mut self,
    ) -> Result<&'arena Expression<'arena>, Error> {
        let (start, _) = self.current_pos();
        // one wire level: the `ClassExpression` over its heritage and body — a declaration's
        // is its statement's
        self.nested(1, |p| p.parse_class_expression_from(start, None, false))
    }

    /// Parse a class expression whose span starts at `start` and which carries
    /// `decorators`, the two things a decorated class expression (`@dec class {}`)
    /// changes about the plain one.
    ///
    /// The decorators are a parameter rather than a post-hoc patch so the node is
    /// built once, in place: a builder that hands a bare `Expression` back for its
    /// caller to fix up makes that caller hold the 72 B value across the boxing
    /// call — see `alloc_expr`.
    ///
    /// `None` and `Some(&[])` are **different wire shapes** — the field is omitted
    /// for the first and emitted as `[]` for the second (`write_decorators_field`) —
    /// so this takes the caller's answer as given rather than folding an empty slice
    /// back to `None`. It can: the undecorated entry point passes `None`, and the
    /// decorated one is only reached at a `@`, so its run is never empty.
    fn parse_class_expression_from(
        &mut self,
        start: usize,
        decorators: Option<&'arena [Decorator<'arena>]>,
        r#abstract: bool,
    ) -> Result<&'arena Expression<'arena>, Error> {
        // Consume 'class' keyword
        debug_assert!(matches!(
            self.current_kind(),
            TokenKind::Keyword(KeywordKind::Class)
        ));
        self.advance()?;

        // The name is always optional on an expression (`implements` excluded — it
        // begins the heritage clause of an anonymous class; see `take_class_name`).
        let ClassHead {
            id,
            type_parameters,
            super_class,
            super_type_parameters,
            implements,
            body,
        } = self.parse_class_head_and_body(false)?;
        let end = body.span.end;

        Ok(alloc_expr(
            self.arena,
            Expression {
                span: Span::new(start as u32, end),
                kind: ExpressionKind::ClassExpression(self.arena.alloc(ClassExpression {
                    decorators,
                    id,
                    super_class,
                    super_type_parameters,
                    implements,
                    body,
                    r#abstract,
                    type_parameters,
                })),
            },
        ))
    }

    /// Everything a class carries between its `class` keyword and its closing
    /// brace: the head (name, type parameters, heritage) and the body. What the
    /// declaration and expression builders share; each wraps it in its own node.
    fn parse_class_head_and_body(
        &mut self,
        name_required: bool,
    ) -> Result<ClassHead<'arena>, Error> {
        // Every part of a class is strict code (ecma262 sec-strict-mode-code), and
        // the scope opens at the `class` keyword (or earlier, at a decorator list —
        // `parse_decorators`) — ahead of the heritage clause, so
        // `class C extends (010) {}` is an error even in a sloppy script. Static
        // blocks and field initializers are inside the body, so they inherit it.
        self.with_strict_class_scope(|p| {
            let id = p.take_class_name()?;
            if name_required && id.is_none() {
                return Err(p.error_expected_after("class name", "class"));
            }

            // Parse type parameters (TypeScript generics): class Foo<T>
            let type_parameters = p.parse_optional_type_parameters()?;

            // Parse optional `extends` clause
            let (super_class, super_type_parameters) = p.parse_optional_extends_clause()?;

            // Parse optional `implements` clause
            let implements: &'arena [_] = if p.eat_contextual_keyword("implements")? {
                p.parse_interface_heritage_list()?.into_bump_slice()
            } else {
                &[]
            };

            // Parse class body (ambient members share the concrete grammar; see
            // `parse_class_body`)
            // one wire level: the `ClassBody` (the depth limit's count)
            let body = p.nested(1, Self::parse_class_body)?;
            Ok(ClassHead {
                id,
                type_parameters,
                super_class,
                super_type_parameters,
                implements,
                body,
            })
        })
    }

    /// Inner function that returns the ClassDeclaration directly
    /// Used by both parse_class_declaration and export default
    ///
    /// `name_required`: If true, class name is required. If false, name is optional
    /// (for `export default class {}`)
    /// `is_abstract`: If true, this is an abstract class
    pub(super) fn parse_class_declaration_inner(
        &mut self,
        name_required: bool,
        is_abstract: bool,
    ) -> Result<ClassDeclaration<'arena>, Error> {
        let start = self.current_pos().0;
        self.parse_class_declaration_inner_with_start(name_required, is_abstract, start, false)
    }

    /// Parse a class declaration from the `class` keyword. `declare` marks an
    /// ambient (`declare class`) declaration: it sets the `declare` field. Members
    /// (bodies, decorators, static blocks) are parsed exactly like a concrete class
    /// (see `finish_method_member`); their ambient-context early-errors (a body's
    /// TS1183, a decorator's TS1206) are deferred to the diagnostics layer — the same
    /// permissive posture tsv takes on ambient field initializers (TS1039).
    pub(super) fn parse_class_declaration_inner_with_start(
        &mut self,
        name_required: bool,
        is_abstract: bool,
        start: usize,
        declare: bool,
    ) -> Result<ClassDeclaration<'arena>, Error> {
        // The `class` keyword must be here. Most callers confirm it first, but the
        // modifier-prefix paths (`export default abstract`, `declare`) reach here
        // after consuming their modifier, so a missing `class` on malformed input
        // must be a clean parse error — never a panic (debug) or a mis-consumed
        // token (release, where a bare `debug_assert!` is elided). `fuzz` gate.
        if !matches!(self.current_kind(), TokenKind::Keyword(KeywordKind::Class)) {
            return Err(self.error_expected("'class'"));
        }
        self.advance()?;

        // Class name required for declarations, optional for export default (see
        // `take_class_name`). `export default class implements Foo {}` (spec-valid, name
        // optional) parses where acorn-typescript rejects it (`implements` reserved); a
        // `name_required` declaration (`class implements Foo {}` / no name) still errors.
        let ClassHead {
            id,
            type_parameters,
            super_class,
            super_type_parameters,
            implements,
            body,
        } = self.parse_class_head_and_body(name_required)?;
        let end = body.span.end;

        Ok(ClassDeclaration {
            decorators: None,
            id,
            super_class,
            super_type_parameters,
            implements,
            body,
            declare,
            r#abstract: is_abstract,
            type_parameters,
            span: Span::new(start as u32, end),
        })
    }

    /// Parse a class body. Concrete and ambient (`declare class`) bodies share one
    /// grammar — keys, modifiers, decorators, type parameters, return types, method
    /// bodies, index/accessor signatures, static blocks. An ambient member's
    /// forbidden constructs (a method body, a decorator, an initializer) parse
    /// structurally; their early-errors are deferred to diagnostics (see
    /// `parse_class_declaration_inner_with_start`).
    pub(super) fn parse_class_body(&mut self) -> Result<ClassBody<'arena>, Error> {
        let (start, _) = self.current_pos();
        self.expect(&TokenKind::BraceOpen)?;

        // A class body is a fresh `[+In]` context: computed member names
        // (`[+In]`), field initializers (`[+In]`), method/getter/setter bodies,
        // and static blocks all permit `in` even when the class expression sits
        // in a for-header init. A nested for-header inside re-disables it.
        let body = self.with_body_frame(|p| {
            let mut body = p.bvec();
            // the members are siblings (`ListFrame`)
            let mut frame = p.list_frame();
            while !matches!(p.current_kind(), TokenKind::BraceClose | TokenKind::Eof) {
                // Stray semicolons are empty class members — acorn skips them,
                // producing no node (prettier strips them on format).
                if p.eat(TokenKind::Semicolon)? {
                    continue;
                }
                frame.element(p, body.len());
                // one wire level: the member (the depth limit's count)
                let member = p.nested(1, Self::parse_class_member)?;
                body.push(member);
            }
            frame.close(p);
            Ok(body)
        })?;

        let (_, end) = self.current_pos();
        self.expect(&TokenKind::BraceClose)?;

        Ok(ClassBody {
            body: body.into_bump_slice(),
            span: Span::new(start as u32, end as u32),
        })
    }

    /// Parse the optional (`?`) / definite (`!`) marker that follows a class
    /// member key, before any type parameters: `m?<T>()`, `prop?: T`, `prop!: T`.
    /// The two are mutually exclusive (same syntactic slot). Shared by the method
    /// and property branches of `parse_class_member` (concrete and ambient alike).
    ///
    /// ⚠️ The two markers differ on line breaks, and deliberately so. `!` carries a
    /// same-line requirement — tsc's `parsePropertyDeclaration` takes it under
    /// `!scanner.hasPrecedingLineBreak()` — so `a⏎!: T` is the bodiless field `a` (ASI)
    /// followed by a `!` that can head no member (TS1068; prettier rejects with it).
    /// tsv declines the marker there, which is the guard its **variable** sibling
    /// already spells (`let x⏎!: T`, `statement/variable.rs`). `?` carries no such rule
    /// in tsc, so `a⏎?: T` stays valid in every parser and is left alone. acorn welds
    /// both; on `!` that discards a line terminator tsc treats as fatal, so tsv follows
    /// tsc over the shape target. Per ecma262 §sec-comments a block comment holding a
    /// line terminator IS one, so `a /*⏎*/ !: T` declines on the same rule.
    pub(in crate::parser) fn parse_property_modifier(&mut self) -> Result<PropertyModifier, Error> {
        Ok(if self.eat(TokenKind::Question)? {
            PropertyModifier::Optional
        } else if !self.had_line_terminator && self.eat(TokenKind::Bang)? {
            PropertyModifier::Definite
        } else {
            PropertyModifier::None
        })
    }

    /// Consume the contextual keyword `kw` as a class-member modifier iff the
    /// current token is the identifier `kw` and the next token begins a member
    /// name or a generator `*` **on the same line** — otherwise `kw` is itself
    /// the member's name (`readonly = 1`, or `readonly⏎x = 1` where the line
    /// break makes `readonly` a bodiless field via ASI and `x` the next member).
    /// The same-line requirement mirrors acorn's `tsTokenCanFollowModifier`
    /// (`!hasPrecedingLineBreak()`). Returns whether it was consumed. Shared by
    /// the `declare`/`override`/`abstract`/`readonly` detectors; `declare` is
    /// probed in three positions (before/after accessibility, after `static`).
    #[inline]
    fn eat_modifier_keyword(&mut self, kw: &str) -> bool {
        if matches!(self.current_kind(), TokenKind::Identifier)
            && self.current_value() == kw
            && (self.peek_is_class_member_name() || self.peek_is(&TokenKind::Star))
            && !self.peek_preceded_by_line_terminator()
        {
            self.advance().ok();
            true
        } else {
            false
        }
    }

    /// Parse a single class member. Concrete and ambient (`declare class`) members
    /// share this one parser: decorators, method bodies, and static blocks all parse
    /// structurally in either context, deferring their ambient-context early-errors
    /// (a decorator's TS1206, a body's TS1183) to diagnostics — see
    /// `parse_class_declaration_inner_with_start` and `finish_method_member`.
    fn parse_class_member(&mut self) -> Result<ClassMember<'arena>, Error> {
        // A member boundary ends a `<` region as a statement's does.
        self.reset_lt_region_for_statement();
        let (start, _) = self.current_pos();

        // Parse any decorators on this member. Ambient (`declare class`) members parse
        // decorators too — acorn accepts them and prettier formats the body form; the
        // TS1206 ("Decorators are not valid here") ambient early-error is deferred to
        // the diagnostics layer.
        let decorators = self
            .parse_decorators(DecoratorListKind::Class)?
            .into_bump_slice();

        // Handle 'declare' contextual keyword - only if followed by a class member name or another modifier
        // Otherwise `declare` itself is the property name: `declare = 1;`
        // Note: declare must be parsed BEFORE accessibility because `declare public a` is valid
        let is_declare = self.eat_modifier_keyword("declare");

        // Handle accessibility modifiers (public, private, protected). Like the
        // other shared detectors, each is consumed only if followed by a class
        // member name or `*` (generator); otherwise the keyword itself is the
        // property name (`private = 1;`).
        let accessibility = if self.eat_modifier_keyword("public") {
            Some(Accessibility::Public)
        } else if self.eat_modifier_keyword("private") {
            Some(Accessibility::Private)
        } else if self.eat_modifier_keyword("protected") {
            Some(Accessibility::Protected)
        } else {
            None
        };

        // Handle 'declare' after accessibility (for non-canonical order like `public declare a`)
        let is_declare = is_declare || self.eat_modifier_keyword("declare");

        // Handle 'static' contextual keyword - only if followed by a class member name or `{` (static block)
        // Otherwise `static` itself is the property name: `static = 2;`
        // Stays inline: unlike the shared detectors, `static {` (a static block) is also a valid follow.
        // Note: `static` is deliberately NOT line-break-guarded (unlike the contextual TS
        // modifiers and `async`). ECMAScript's `ClassElement: static FieldDefinition` has no
        // `[no LineTerminator here]`, so `static⏎x = 1` is the static field `x` — matching the
        // spec, tsc, and prettier's parser. (acorn's parser quirkily ASI-splits it into two
        // fields; here tsv follows the spec/tsc over that acorn shape quirk.)
        let is_static = if matches!(self.current_kind(), TokenKind::Identifier)
            && self.current_value() == "static"
            && (self.peek_is_class_member_name()
                || self.peek_is(&TokenKind::BraceOpen)
                || self.peek_is(&TokenKind::Star))
        {
            self.advance().ok();
            true
        } else {
            false
        };

        // Handle 'declare' after static (for non-canonical order like `static declare a`)
        let is_declare = is_declare || self.eat_modifier_keyword("declare");

        // Check for static initialization block: `static { ... }` (ES2022).
        // Parsed in ambient (`declare class`) context too, as a `StaticBlock` — the
        // same defer-the-early-error posture as a `declare class` method body below
        // (tsc rejects an ambient implementation with TS1183; tsv defers it to
        // diagnostics rather than failing to parse/format).
        if is_static && matches!(self.current_kind(), TokenKind::BraceOpen) {
            // A static block takes no modifier (ecma262 `ClassStaticBlock` is bare
            // `static { … }`). tsc reports `public static {}` / `declare static {}`
            // from its checker (TS1184), but the drop-in wire has no field for the
            // modifier, so it cannot be reprinted (the representability floor) and tsv
            // rejects it; acorn rejects it too. A decorator list is a different call:
            // acorn keeps it on the block and tsc reports TS1206 from its checker, so
            // tsv keeps it too, deferring the error.
            if accessibility.is_some() || is_declare {
                return Err(self.error_msg("Static class blocks cannot have any modifier"));
            }
            // Parse the block body. A class static initialization block is a
            // `[+Await]` context (`await` is allowed inside it) but `[~Yield]`
            // (`yield` is not the enclosing generator's yield here).
            let block = self.with_fn_context(true, false, Self::parse_block_statement)?;
            let end = block.span.end;

            return Ok(ClassMember::StaticBlock(StaticBlock {
                decorators: if decorators.is_empty() {
                    None
                } else {
                    Some(decorators)
                },
                body: block.body,
                span: Span::new(start as u32, end),
            }));
        }

        // Handle 'override' contextual keyword - only if followed by a class member name or `*`
        // Otherwise `override` itself is the property name: `override = 1;`
        let is_override = self.eat_modifier_keyword("override");

        // Handle 'abstract' contextual keyword - only if followed by a class member name or `*`
        // Otherwise `abstract` itself is the property name: `abstract = 1;`
        let is_abstract = self.eat_modifier_keyword("abstract");

        // Handle 'override' after abstract. `abstract override` is tsc's CANONICAL
        // order, so probing `override` only before `abstract` rejected the valid
        // spelling; the second probe mirrors the three-position `declare` idiom above.
        // ⚠️ Deliberately widen-only: this leaves the reversed `override abstract`
        // (which tsc flags TS1029 and acorn rejects) still accepted. Flipping that is
        // a reject-flip, and the honest structure for both is a modifier LOOP — what
        // tsc and acorn-typescript each do — which is a `parse_class_member` refactor.
        let is_override = is_override || self.eat_modifier_keyword("override");

        // Handle 'readonly' contextual keyword - only if followed by a class member name
        // Otherwise `readonly` itself is the property name: `readonly = 1;`
        let readonly = self.eat_modifier_keyword("readonly");

        // Handle 'accessor' contextual keyword (ES decorator proposal)
        // Only consume as modifier if followed by a class member name on the same line.
        // Stays inline: `accessor` may not prefix a generator, so it omits the `*` follow.
        // A line break makes `accessor` a bodiless field via ASI (acorn parses it as a
        // class-accessor modifier, subject to the same `!hasPrecedingLineBreak()` rule).
        let accessor = if matches!(self.current_kind(), TokenKind::Identifier)
            && self.current_value() == "accessor"
            && self.peek_is_class_member_name()
            && !self.peek_preceded_by_line_terminator()
        {
            self.advance().ok();
            true
        } else {
            false
        };

        // Handle 'async' keyword for async methods
        // async is only a modifier if followed by: identifier, [, #, or * on the same line
        // (ECMAScript `async [no LineTerminator here] MethodDefinition` — a line break makes
        // `async` a bodiless field via ASI, `async⏎x = 1` → two members).
        // Stays inline: `async` is a reserved Keyword token, not an Identifier.
        let is_async = if matches!(self.current_kind(), TokenKind::Keyword(KeywordKind::Async))
            && (self.peek_is_class_member_name() || self.peek_is(&TokenKind::Star))
            && !self.peek_preceded_by_line_terminator()
        {
            self.advance().ok();
            true
        } else {
            false
        };

        // Handle '*' for generator methods
        let is_generator = self.eat(TokenKind::Star)?;

        // Handle 'get' and 'set' contextual keywords for getters/setters
        let accessor_kind = if matches!(self.current_kind(), TokenKind::Identifier) {
            let kind = match self.current_value() {
                "get" => Some(MethodKind::Get),
                "set" => Some(MethodKind::Set),
                _ => None,
            };
            // Peek ahead to see if next token is a class member name (identifier, bracket, or #)
            // vs `(` or `=` (property named 'get'/'set')
            if kind.is_some() && self.peek_is_class_member_name() {
                self.advance().ok();
                kind
            } else {
                None
            }
        } else {
            None
        };

        // Check for index signature: [key: Type]: ValueType
        // Index signatures look like `[ident: Type]` followed by `: ValueType`
        if self.is_index_signature_start() {
            // `readonly` and `static` are the ONLY modifiers an index signature
            // accepts. An accessibility modifier (public/private/protected — tsc
            // TS1071) or a `declare`/`abstract`/`override` modifier (tsc TS1031 /
            // TS1244-position / TS4113-position family) on an index signature is a
            // known gap against the reject-vs-defer line (tsc's checker raises it) —
            // acorn and prettier both reject it at parse, regardless of class context
            // (`abstract` is rejected even in an abstract class, `override` even with a
            // base class), and tsv rejects inline too. (These are distinct from the
            // *member*-position `abstract` / `override` early-errors, which ARE
            // context-dependent and stay deferred.)
            if accessibility.is_some() {
                return Err(
                    self.error_msg("Index signatures cannot have an accessibility modifier")
                );
            }
            if is_declare {
                return Err(self.error_msg("Index signatures cannot have the 'declare' modifier"));
            }
            if is_abstract {
                return Err(self.error_msg("Index signatures cannot have the 'abstract' modifier"));
            }
            if is_override {
                return Err(
                    self.error_msg("'override' modifier cannot appear on an index signature")
                );
            }
            return self.parse_class_index_signature(start, decorators, is_static, readonly);
        }

        // Parse member name (key)
        let (computed, key, name_is_constructor) =
            if matches!(self.current_kind(), TokenKind::BracketOpen) {
                // Computed key: [expr] — never the constructor (`['constructor']` is a
                // normal computed method, unlike the bare/string `constructor`).
                self.advance()?;
                (true, self.parse_computed_member_key()?, false)
            } else if matches!(self.current_kind(), TokenKind::Hash) {
                // Private identifier key: #name
                let (private_id, private_span) = self.parse_private_identifier()?;
                // A class element named `#constructor` is illegal in every context
                // (ecma262 Static Semantics: Early Errors for ClassElementName) —
                // acorn rejects at parse, so tsv matches for drop-in parity. This
                // shared private-key branch covers every member kind (method /
                // field / get / set / static / accessor).
                if self.private_name_is(&private_id, private_span, "constructor") {
                    return Err(
                        self.error_msg("Classes can't have an element named '#constructor'")
                    );
                }
                (
                    false,
                    Expression {
                        span: private_span,
                        kind: ExpressionKind::PrivateIdentifier(private_id),
                    },
                    false,
                )
            } else if self.current_is_identifier_or_keyword() {
                // Identifier or keyword as key - keywords are valid as class member names.
                // The constructor is matched by decoded StringValue, so an escaped
                // `constructor` is the constructor too (ecma262 ClassElementName) — acorn parity.
                let name_is_constructor = self
                    .current_decoded()
                    .unwrap_or_else(|| self.current_property_name())
                    == "constructor";
                (
                    false,
                    Expression::from_identifier(self.parse_identifier_name_node()?),
                    name_is_constructor,
                )
            } else if matches!(self.current_kind(), TokenKind::String) {
                // String literal key: `'a-b'() {}`, `'a' = 1;`. A string-keyed
                // `'constructor'` IS the constructor (kind detection reads the name),
                // unlike a computed `['constructor']`. The check goes through the
                // decoded value, so `'constructor'` is recognized too — which is why
                // this one site reads `extract_string_cooked` directly instead of
                // `Parser::parse_string_literal` like the other string-key positions:
                // it needs the cooked value in hand, and the reader's `Literal` would
                // only have to be destructured back apart to get it.
                let (key_start, key_end) = self.current_pos();
                let span = Span::new(key_start as u32, key_end as u32);
                let cooked = self.extract_string_cooked()?;
                self.advance()?;
                let name_is_constructor = self.resolve_cooked(&cooked, span) == "constructor";
                (
                    false,
                    Expression::from_literal(Literal {
                        value: LiteralValue::String(cooked),
                        span,
                    }),
                    name_is_constructor,
                )
            } else if matches!(self.current_kind(), TokenKind::Number) {
                // Numeric key: `0() {}`, `0xb_b = 1;`, `1n;` — shares the full
                // numeric decode (radix, separators, bigint)
                let literal = self.parse_number_or_bigint_literal()?;
                self.advance()?;
                (false, Expression::from_literal(literal), false)
            } else {
                return Err(self.error_expected("class member name"));
            };

        // Optional (`?`) / definite (`!`) marker, between the key and any type
        // parameters — methods read `?` as `optional`, properties keep the full modifier.
        let modifier = self.parse_property_modifier()?;

        // Parse type parameters (TypeScript generics): method<T>()
        let type_parameters = self.parse_optional_type_parameters()?;

        let header = ClassMemberHeader {
            start,
            decorators,
            accessibility,
            is_static,
            is_declare,
            is_override,
            is_abstract,
            readonly,
            accessor,
            is_generator,
            is_async,
            accessor_kind,
            computed,
            key,
            name_is_constructor,
            modifier,
            type_parameters,
        };

        // Detect if this is a method (has `(`) or property (has `=` or `;` or end of class)
        if matches!(self.current_kind(), TokenKind::ParenOpen) {
            self.finish_method_member(header)
        } else if header.accessor_kind.is_some() || header.is_async || header.is_generator {
            // A method-only modifier — `get`/`set` (accessor), `async`, or `*` (generator) —
            // was committed, but no `(params)` list follows. ecma262 gives these only a
            // `MethodDefinition`/`GeneratorMethod`/`AsyncMethod` form (always `( … )`), never a
            // `FieldDefinition`, so `get x = 1` / `async x = 1` / `*x = 1` have no valid parse.
            // acorn + prettier + tsc (TS1005 "'(' expected") all reject; there is no such
            // production — a grammar violation (invalid in every mode/goal), so tsv rejects
            // inline rather than falling into `finish_property_member`, which would silently drop
            // the modifier keyword and emit a bare field (a content-loss bug).
            Err(self.error_expected_found("'('"))
        } else {
            self.finish_property_member(header)
        }
    }

    /// Finish a method member (the `(`-led branch of `parse_class_member`):
    /// parameter list, optional return type, and body — or a bodiless signature
    /// for abstract methods and overload signatures (`;`/ASI-terminated, including
    /// ambient signatures).
    ///
    /// `inline(always)`, into its one caller: a frame of its own sits on the recursion
    /// path of every class nested in a member body, eating the depth limits' margin (the
    /// same holds for `finish_property_member`).
    #[expect(clippy::inline_always)]
    #[inline(always)]
    fn finish_method_member(
        &mut self,
        header: ClassMemberHeader<'arena>,
    ) -> Result<ClassMember<'arena>, Error> {
        let ClassMemberHeader {
            start,
            decorators,
            accessibility,
            is_static,
            is_declare,
            is_override,
            is_abstract,
            readonly,
            is_generator,
            is_async,
            accessor_kind,
            computed,
            key,
            name_is_constructor,
            modifier,
            type_parameters,
            ..
        } = header;

        // `readonly` and `declare` are field-only modifiers: a method / getter /
        // setter / constructor may not carry either (tsc TS1024 / TS1031; acorn and
        // prettier both reject at parse). A known gap against the reject-vs-defer line
        // (tsc's checker raises it): tsv rejects inline. (These sit on a class
        // *method*; the same keywords stay valid on a field — `readonly a = 1`,
        // `declare b: number` — which never reaches this method path. `abstract` /
        // `override` are deliberately NOT rejected here: on a member they are
        // context-dependent early-errors — legal in an abstract class / with a base
        // class — so tsv defers them.)
        if readonly {
            return Err(self.error_msg("Class methods cannot have the 'readonly' modifier"));
        }
        if is_declare {
            return Err(
                self.error_msg("'declare' modifier cannot appear on class elements of this kind")
            );
        }

        // A `get`/`set` accessor is neither a GeneratorMethod nor an AsyncMethod:
        // ecma262's MethodDefinition productions are disjoint, so the method-only
        // `async`/`*` modifiers cannot combine with `get`/`set`. `*get x() {}`,
        // `async get x() {}` (and the `set` forms) have no valid parse — acorn,
        // prettier, and tsc ("'async' modifier cannot be used here" / "'(' expected")
        // all reject. No such production — a grammar violation (invalid in every
        // mode/goal) → reject inline rather than emit an accessor carrying a spurious
        // generator/async flag. (`async`/`*` methods *named* `get`/`set` — `async
        // get() {}`, `*set() {}` — keep `accessor_kind = None` and stay valid.)
        if accessor_kind.is_some() && (is_generator || is_async) {
            return Err(self.error_msg("A getter or setter cannot be async or a generator"));
        }

        // A non-static, non-computed `get`/`set` accessor keyed `constructor`
        // (bare identifier or the `'constructor'` string) is rejected — acorn
        // ("Constructor can't have get/set modifier") and tsc (TS1341) both reject
        // it at parse. `name_is_constructor` is already false for computed/private/
        // numeric keys, and `!is_static` excludes the static forms, so this matches
        // acorn's boundary exactly (static and computed accessors named `constructor`
        // stay valid). Unlike the ambient-context early-errors tsv defers to the
        // diagnostics layer, acorn emits no AST here — so drop-in parity with the
        // canonical parser means rejecting too.
        if accessor_kind.is_some() && name_is_constructor && !is_static {
            return Err(self.error_msg("Constructor can't have get/set modifier"));
        }

        // Method definition - use accessor_kind if set, otherwise check for
        // constructor. A `static` method named `constructor` is NOT the class
        // constructor (that name is only reserved on instance methods), so it
        // stays `MethodKind::Method` — matching acorn.
        let kind = accessor_kind.unwrap_or(if name_is_constructor && !is_static {
            MethodKind::Constructor
        } else {
            MethodKind::Method
        });

        // A constructor is a plain method: it may not carry type parameters (tsc
        // TS1092) and — per ecma262's `SpecialMethod` rule (§15.7.1) — may not be a
        // generator or async method (the `get`/`set` accessor forms are rejected just
        // above). acorn rejects all three at parse; prettier/tsc reject them too
        // (`constructor<T>()` is TS1092, `async constructor` is "'async' modifier
        // cannot be used here", `*constructor` is TS1089-family). A known gap against
        // the reject-vs-defer line (tsc's checker raises them): tsv rejects inline. A
        // *static* method named `constructor` is an ordinary method (`kind == Method`),
        // so its type params / async / `*` stay valid.
        if matches!(kind, MethodKind::Constructor) {
            if type_parameters.is_some() {
                return Err(
                    self.error_msg("Type parameters cannot appear on a constructor declaration")
                );
            }
            if is_generator {
                return Err(self.error_msg("Constructor cannot be a generator"));
            }
            if is_async {
                return Err(self.error_msg("Constructor cannot be an async method"));
            }
        }

        // Capture paren position before parsing params (for comment detection)
        let (params_start, _) = self.current_pos();

        // Parse parameter list and block body (like a function), in the method's
        // own `[Await]`/`[Yield]` context (async/generator method → `[+Await]`/`[+Yield]`).
        let params = self
            .with_fn_context(is_async, is_generator, Self::parse_parameter_list)?
            .into_bump_slice();

        // A `get`/`set` accessor must obey the accessor arity grammar (getter: no
        // params; setter: exactly one non-rest param). acorn rejects a violation
        // at parse, so tsv does too (drop-in parity) — see
        // `check_accessor_param_arity`.
        // A class-member accessor counts a `this` param toward arity (acorn's
        // class path has no `this` exclusion), so `allow_this_param = false`.
        if let Some(k) = accessor_kind {
            self.check_accessor_param_arity(matches!(k, MethodKind::Get), params, false)?;
        }

        // Check for return type annotation: (): type or type predicate
        let return_type = self.parse_optional_return_type()?;

        // Abstract methods and overload signatures have no body - just a semicolon
        // Method overloads: `parse(x: string): object;` followed by implementation
        // Note: ASI can insert semicolon on line terminator, but NOT if next token is `{`
        // (a method with body on next line: `fn()\n{` is valid)
        // Ambient (`declare class`) members are NOT forced bodiless: a `{` body parses
        // like a concrete method. tsc rejects an ambient implementation with the
        // grammar-level TS1183 ("An implementation cannot be declared in ambient
        // contexts"), but tsv *defers* that early-error to the future diagnostics layer
        // — the same posture it takes on ambient field initializers (TS1039) — so the
        // formatter stays complete on inputs prettier also formats. Only a `;`/ASI
        // member stays a bodiless signature, exactly as in a concrete class.
        let is_overload_or_abstract = is_abstract
            || self.check(&TokenKind::Semicolon)
            || (self.can_insert_semicolon() && !self.check(&TokenKind::BraceOpen));
        // A decorator on a constructor IMPLEMENTATION: acorn rejects at parse
        // ("Decorators can't be used with a constructor"), reporting at the first
        // decorator, so tsv does too — the class itself is the decorated element. A
        // bodiless constructor signature (an overload, an ambient or `abstract` one)
        // keeps its decorators in acorn's tree, so it parses here as well, its TS1206
        // deferred like any signature's.
        if matches!(kind, MethodKind::Constructor)
            && !is_overload_or_abstract
            && !decorators.is_empty()
        {
            return Err(self.error_msg_at("Decorators can't be used with a constructor", start));
        }
        let (body_block, end) = if is_overload_or_abstract {
            // Without a return type the signature ends at the params' `)` —
            // the next token's start would overshoot past trailing comments
            // or onto the next line under ASI
            let body_end = return_type
                .as_ref()
                .map_or_else(|| self.prev_token_end() as u32, |rt| rt.span.end);
            let end = if self.eat(TokenKind::Semicolon)? {
                self.prev_token_end() as u32
            } else {
                body_end
            };
            // Create empty body for abstract methods and overload signatures
            (
                BlockStatement {
                    body: &[],
                    span: Span::new(body_end, body_end),
                },
                end,
            )
        } else {
            // one wire level beyond the body's own: the method's `FunctionExpression`
            let body_block = self.nested(1, |p| {
                p.with_fn_context(is_async, is_generator, Self::parse_function_body)
            })?;
            let end = body_block.span.end;
            (body_block, end)
        };

        // Create FunctionExpression for the method value
        // span starts at params_start (the `(`) to match acorn's FunctionExpression positioning
        let value = FunctionExpression {
            id: None,
            type_parameters,
            params,
            return_type,
            body: body_block,
            generator: is_generator,
            r#async: is_async,
            params_start: params_start as u32,
            span: Span::new(params_start as u32, end),
        };

        Ok(ClassMember::MethodDefinition(MethodDefinition {
            decorators: if decorators.is_empty() {
                None
            } else {
                Some(decorators)
            },
            key,
            value,
            kind,
            accessibility,
            is_static,
            r#override: is_override,
            r#abstract: is_abstract,
            computed,
            optional: matches!(modifier, PropertyModifier::Optional),
            span: Span::new(start as u32, end),
        }))
    }

    /// Finish a property member (the non-`(` branch of `parse_class_member`):
    /// optional type annotation, optional initializer, and trailing semicolon.
    ///
    /// `inline(always)`, into its one caller: a frame of its own sits on the recursion
    /// path of every class nested in a member body, eating the depth limits' margin (the
    /// same holds for `finish_method_member`).
    #[expect(clippy::inline_always)]
    #[inline(always)]
    fn finish_property_member(
        &mut self,
        header: ClassMemberHeader<'arena>,
    ) -> Result<ClassMember<'arena>, Error> {
        let ClassMemberHeader {
            start,
            decorators,
            accessibility,
            is_static,
            is_declare,
            is_override,
            is_abstract,
            readonly,
            accessor,
            computed,
            key,
            name_is_constructor,
            modifier,
            ..
        } = header;

        // A class FIELD (or auto-accessor) named `constructor` is illegal — ecma262
        // reserves the name for the constructor METHOD only (an instance
        // `constructor(){}` is the constructor, a static one an ordinary method;
        // both are `(`-led and reach `finish_method_member`, never here). acorn
        // rejects the field form at parse ("Classes can't have a field named
        // 'constructor'"), so tsv matches. `name_is_constructor` is already false
        // for computed / private / numeric keys, so this only fires on a bare or
        // string `constructor` key.
        if name_is_constructor {
            return Err(self.error_msg("Classes can't have a field named 'constructor'"));
        }

        // Property definition: `name: type = value;` or `name: type;` or `name = value;` or `name;`
        // The optional/definite marker was already parsed above (shared with methods).

        // Check for type annotation: `name: type`
        let type_annotation = self.parse_optional_type_annotation()?;

        // Check for value: `= value`. A class field `Initializer` is `[~Await]`
        // (it does not inherit a `[+Await]` enclosing — `await` is not an
        // await-expression there, only an identifier under Script goal), unlike
        // a computed key, which inherits.
        let value: Option<Expression<'arena>> = if self.eat(TokenKind::Equals)? {
            Some(self.with_fn_context(false, false, Self::parse_assignment_expression)?)
        } else {
            None
        };

        let end = value.as_ref().map_or_else(
            || {
                type_annotation
                    .as_ref()
                    // No type annotation/value: the last consumed token is the key
                    // (its closing `]` for a computed key) or the `?`/`!` modifier.
                    // `key.span().end` would stop inside a computed key's brackets,
                    // so read the previous token's end instead.
                    .map_or_else(|| self.prev_token_end() as u32, |ta| ta.span.end)
            },
            // prev_token_end covers a parenthesized value's closing `)`,
            // which the paren-stripped value span excludes
            |_| self.prev_token_end() as u32,
        );

        // Consume the field terminator. A class field must end with `;`, a line
        // terminator (ASI), or the class body's `}`/EOF — acorn's `semicolon()`.
        // `class C { a b }` / `class C { a foo(){} }` reject (same-line, no `;`);
        // a method with a body self-terminates and never reaches this branch. A
        // consumed `;` extends the span.
        let mut end = end;
        if self.eat(TokenKind::Semicolon)? {
            end = self.prev_token_end() as u32;
        } else if !self.can_insert_semicolon() {
            return Err(self.error_expected("';'"));
        }

        Ok(ClassMember::PropertyDefinition(PropertyDefinition {
            decorators: if decorators.is_empty() {
                None
            } else {
                Some(decorators)
            },
            key,
            type_annotation,
            value,
            accessibility,
            is_static,
            declare: is_declare,
            r#abstract: is_abstract,
            r#override: is_override,
            readonly,
            computed,
            accessor,
            modifier,
            span: Span::new(start as u32, end),
        }))
    }

    /// Parse an index signature: `[key: KeyType]: ValueType` or `readonly [key: KeyType]: ValueType`
    pub(in crate::parser) fn parse_class_index_signature(
        &mut self,
        start: usize,
        decorators: &'arena [Decorator<'arena>],
        is_static: bool,
        readonly: bool,
    ) -> Result<ClassMember<'arena>, Error> {
        // Consume `[`
        self.expect(&TokenKind::BracketOpen)?;

        // Parse parameter: `key: KeyType`
        let (id_start, id_end) = self.current_pos();
        if !matches!(self.current_kind(), TokenKind::Identifier) {
            return Err(self.error_expected("index signature parameter name"));
        }
        let param_name = self.current_ident_name();
        self.advance()?;

        // Parse type annotation on parameter: `: KeyType`
        // one wire level: the parameter's `Identifier`, above the annotation's own (the
        // depth limit's count, `Parser::nested`)
        let param_type = if self.check(&TokenKind::Colon) {
            Some(self.nested(1, Self::parse_type_annotation)?)
        } else {
            return Err(self.error_expected("type annotation for index signature parameter"));
        };

        let param_end = param_type.as_ref().map_or(id_end, |t| t.span.end as usize);
        let extra = param_type.map(|ta| self.typed_extra(ta));
        let parameter = Identifier {
            escaped_name: param_name.escaped,
            name_len: param_name.raw_len,
            name_plain_ascii: param_name.plain_ascii,
            optional: false,
            extra,
            span: Span::new(id_start as u32, param_end as u32),
        };

        // Consume `]`
        self.expect(&TokenKind::BracketClose)?;
        let bracket_end = self.prev_token_end() as u32;

        // The value type annotation is optional — a typeless index signature
        // (`[key: string]`) is valid grammar (see `parse_index_signature_body`);
        // the missing type is a deferred tsc checker error (TS1021), not a parse error.
        let value_type = if self.check(&TokenKind::Colon) {
            Some(self.parse_type_annotation()?)
        } else {
            None
        };

        // Consume semicolon, including it in the span. Like a class field (and a
        // type-member signature), an index signature must be terminated before the
        // next member: an explicit `;`, or ASI at `}` / EOF / a line break. A member
        // glued on the same line with no separator (`[a: string]: number v: number`)
        // is rejected (acorn "Unexpected token" / tsc TS1005) — the class-body analog
        // of the type-member ASI rule.
        let mut end = value_type.as_ref().map_or(bracket_end, |t| t.span.end);
        if self.eat(TokenKind::Semicolon)? {
            end = self.prev_token_end() as u32;
        } else if !self.can_insert_semicolon() {
            return Err(self.error_expected("';'"));
        }

        let mut parameters = self.bvec();
        parameters.push(parameter);
        Ok(ClassMember::IndexSignature(TSIndexSignature {
            parameters: parameters.into_bump_slice(),
            type_annotation: value_type,
            // A decorated index signature is kept: acorn keeps the list on the
            // `TSIndexSignature` and tsc reports TS1206 from its checker, which tsv defers.
            decorators: if decorators.is_empty() {
                None
            } else {
                Some(decorators)
            },
            is_static,
            readonly,
            span: Span::new(start as u32, end),
        }))
    }
}