Skip to main content

tsz_parser/parser/parse_rules/
utils.rs

1//! Shared parsing helpers used by parser modules.
2//! Keep this module focused on reusable token lookahead and token classification
3//! logic without pulling in parser state behavior.
4
5use tsz_scanner::SyntaxKind;
6use tsz_scanner::scanner_impl::ScannerState;
7
8/// Check if a token is an identifier or any keyword token.
9pub fn is_identifier_or_keyword(token: SyntaxKind) -> bool {
10    token == SyntaxKind::Identifier || tsz_scanner::token_is_keyword(token)
11}
12
13/// Look ahead to check if current token is followed by a token matching `check`.
14pub fn look_ahead_is<F>(scanner: &mut ScannerState, _current_token: SyntaxKind, check: F) -> bool
15where
16    F: FnOnce(SyntaxKind) -> bool,
17{
18    let snapshot = scanner.save_state();
19    let next = scanner.scan();
20
21    let result = check(next);
22
23    scanner.restore_state(snapshot);
24    result
25}
26
27/// Look ahead to check if current token is followed on the **same line** by a token matching `check`.
28/// Returns false if the next token has a preceding line break (ASI would apply).
29pub fn look_ahead_is_on_same_line<F>(
30    scanner: &mut ScannerState,
31    _current_token: SyntaxKind,
32    check: F,
33) -> bool
34where
35    F: FnOnce(SyntaxKind) -> bool,
36{
37    let snapshot = scanner.save_state();
38    let next = scanner.scan();
39    let result = !scanner.has_preceding_line_break() && check(next);
40    scanner.restore_state(snapshot);
41    result
42}
43
44/// Look ahead to check if "async" is followed by a declaration keyword.
45pub fn look_ahead_is_async_declaration(
46    scanner: &mut ScannerState,
47    current_token: SyntaxKind,
48) -> bool {
49    look_ahead_is(scanner, current_token, |token| {
50        matches!(
51            token,
52            SyntaxKind::ClassKeyword
53                | SyntaxKind::FunctionKeyword
54                | SyntaxKind::InterfaceKeyword
55                | SyntaxKind::EnumKeyword
56                | SyntaxKind::NamespaceKeyword
57                | SyntaxKind::ModuleKeyword
58        )
59    })
60}
61
62/// Look ahead to check if "abstract" is followed by a declaration keyword.
63pub fn look_ahead_is_abstract_declaration(
64    scanner: &mut ScannerState,
65    current_token: SyntaxKind,
66) -> bool {
67    look_ahead_is(scanner, current_token, |token| {
68        matches!(
69            token,
70            SyntaxKind::ClassKeyword
71                | SyntaxKind::InterfaceKeyword
72                | SyntaxKind::EnumKeyword
73                | SyntaxKind::NamespaceKeyword
74                | SyntaxKind::ModuleKeyword
75        )
76    })
77}
78
79/// Look ahead to check if `namespace`/`module` is followed by a declaration name on the same line.
80/// ASI prevents treating `namespace\nfoo` as a namespace declaration.
81pub fn look_ahead_is_module_declaration(
82    scanner: &mut ScannerState,
83    current_token: SyntaxKind,
84) -> bool {
85    look_ahead_is_on_same_line(scanner, current_token, |token| {
86        matches!(
87            token,
88            SyntaxKind::StringLiteral | SyntaxKind::OpenBraceToken
89        ) || (token == SyntaxKind::Identifier
90            || (tsz_scanner::token_is_keyword(token)
91                && !tsz_scanner::token_is_reserved_word(token)))
92    })
93}
94
95/// Look ahead to check if `type` begins a type alias declaration.
96/// ASI prevents treating `type\nFoo = ...` as a type alias declaration.
97pub fn look_ahead_is_type_alias_declaration(
98    scanner: &mut ScannerState,
99    current_token: SyntaxKind,
100) -> bool {
101    look_ahead_is_on_same_line(scanner, current_token, is_identifier_or_keyword)
102}
103
104/// Look ahead to check if we have `const enum`.
105pub fn look_ahead_is_const_enum(scanner: &mut ScannerState, current_token: SyntaxKind) -> bool {
106    look_ahead_is(scanner, current_token, |token| {
107        token == SyntaxKind::EnumKeyword
108    })
109}
110
111/// Look ahead to check if current token begins an import equals declaration.
112pub fn look_ahead_is_import_equals(
113    scanner: &mut ScannerState,
114    _current_token: SyntaxKind,
115    is_identifier_fn: impl Fn(SyntaxKind) -> bool,
116) -> bool {
117    let snapshot = scanner.save_state();
118
119    let next1 = scanner.scan();
120    if !is_identifier_fn(next1) {
121        scanner.restore_state(snapshot);
122        return false;
123    }
124
125    let next2 = scanner.scan();
126    if next2 == SyntaxKind::EqualsToken {
127        scanner.restore_state(snapshot);
128        return true;
129    }
130
131    if next1 == SyntaxKind::TypeKeyword && is_identifier_fn(next2) {
132        let next3 = scanner.scan();
133        if next3 == SyntaxKind::EqualsToken {
134            scanner.restore_state(snapshot);
135            return true;
136        }
137    }
138
139    scanner.restore_state(snapshot);
140    false
141}
142
143/// Look ahead to check if we have `import (`/`import.` (dynamic import forms).
144pub fn look_ahead_is_import_call(scanner: &mut ScannerState, current_token: SyntaxKind) -> bool {
145    look_ahead_is(scanner, current_token, |token| {
146        matches!(token, SyntaxKind::OpenParenToken | SyntaxKind::DotToken)
147    })
148}