1#![doc = include_str!("../examples/semantic.rs")]
6use std::ops::RangeBounds;
9
10use oxc_ast::{
11 AstKind, Comment, CommentsRange, ast::IdentifierReference, comments_range,
12 has_comments_between, is_inside_comment,
13};
14#[cfg(feature = "cfg")]
15use oxc_cfg::ControlFlowGraph;
16use oxc_span::{GetSpan, SourceType, Span};
17pub use oxc_syntax::{
19 node::{NodeFlags, NodeId},
20 reference::{Reference, ReferenceFlags, ReferenceId},
21 scope::{ScopeFlags, ScopeId},
22 symbol::{SymbolFlags, SymbolId},
23};
24
25#[cfg(feature = "cfg")]
26pub mod dot;
27
28#[cfg(feature = "linter")]
29mod ast_types_bitset;
30mod binder;
31mod builder;
32mod checker;
33mod class;
34mod diagnostics;
35mod is_global_reference;
36#[cfg(feature = "linter")]
37mod jsdoc;
38mod label;
39mod node;
40mod scoping;
41mod stats;
42mod unresolved_stack;
43
44#[cfg(feature = "linter")]
45pub use ast_types_bitset::AstTypesBitset;
46pub use builder::{SemanticBuilder, SemanticBuilderReturn};
47pub use is_global_reference::IsGlobalReference;
48#[cfg(feature = "linter")]
49pub use jsdoc::{JSDoc, JSDocFinder, JSDocTag};
50pub use node::{AstNode, AstNodes};
51pub use scoping::Scoping;
52pub use stats::Stats;
53
54use class::ClassTable;
55
56#[derive(Default)]
67pub struct Semantic<'a> {
68 source_text: &'a str,
70
71 source_type: SourceType,
73
74 nodes: AstNodes<'a>,
76
77 scoping: Scoping,
78
79 classes: ClassTable<'a>,
80
81 comments: &'a [Comment],
83 irregular_whitespaces: Box<[Span]>,
84
85 #[cfg(feature = "linter")]
87 jsdoc: JSDocFinder<'a>,
88
89 unused_labels: Vec<NodeId>,
90
91 #[cfg(feature = "cfg")]
94 cfg: Option<ControlFlowGraph>,
95 #[cfg(not(feature = "cfg"))]
96 #[allow(unused)]
97 cfg: (),
98}
99
100impl<'a> Semantic<'a> {
101 pub fn into_scoping(self) -> Scoping {
103 self.scoping
104 }
105
106 pub fn into_scoping_and_nodes(self) -> (Scoping, AstNodes<'a>) {
108 (self.scoping, self.nodes)
109 }
110
111 pub fn source_text(&self) -> &'a str {
113 self.source_text
114 }
115
116 pub fn source_type(&self) -> &SourceType {
118 &self.source_type
119 }
120
121 pub fn nodes(&self) -> &AstNodes<'a> {
123 &self.nodes
124 }
125
126 pub fn scoping(&self) -> &Scoping {
127 &self.scoping
128 }
129
130 pub fn scoping_mut(&mut self) -> &mut Scoping {
131 &mut self.scoping
132 }
133
134 pub fn scoping_mut_and_nodes(&mut self) -> (&mut Scoping, &AstNodes<'a>) {
135 (&mut self.scoping, &self.nodes)
136 }
137
138 pub fn classes(&self) -> &ClassTable<'_> {
139 &self.classes
140 }
141
142 pub fn set_irregular_whitespaces(&mut self, irregular_whitespaces: Box<[Span]>) {
143 self.irregular_whitespaces = irregular_whitespaces;
144 }
145
146 pub fn comments(&self) -> &[Comment] {
148 self.comments
149 }
150
151 pub fn comments_range<R>(&self, range: R) -> CommentsRange<'_>
152 where
153 R: RangeBounds<u32>,
154 {
155 comments_range(self.comments, range)
156 }
157
158 pub fn has_comments_between(&self, span: Span) -> bool {
159 has_comments_between(self.comments, span)
160 }
161
162 pub fn is_inside_comment(&self, pos: u32) -> bool {
163 is_inside_comment(self.comments, pos)
164 }
165
166 pub fn irregular_whitespaces(&self) -> &[Span] {
167 &self.irregular_whitespaces
168 }
169
170 #[cfg(feature = "linter")]
174 pub fn jsdoc(&self) -> &JSDocFinder<'a> {
175 &self.jsdoc
176 }
177
178 pub fn unused_labels(&self) -> &Vec<NodeId> {
179 &self.unused_labels
180 }
181
182 #[cfg(feature = "cfg")]
187 pub fn cfg(&self) -> Option<&ControlFlowGraph> {
188 self.cfg.as_ref()
189 }
190
191 #[cfg(not(feature = "cfg"))]
192 pub fn cfg(&self) -> Option<&()> {
193 None
194 }
195
196 pub fn stats(&self) -> Stats {
198 #[expect(clippy::cast_possible_truncation)]
199 Stats::new(
200 self.nodes.len() as u32,
201 self.scoping.scopes_len() as u32,
202 self.scoping.symbols_len() as u32,
203 self.scoping.references.len() as u32,
204 )
205 }
206
207 pub fn is_unresolved_reference(&self, node_id: NodeId) -> bool {
208 let reference_node = self.nodes.get_node(node_id);
209 let AstKind::IdentifierReference(id) = reference_node.kind() else {
210 return false;
211 };
212 self.scoping.root_unresolved_references().contains_key(id.name.as_str())
213 }
214
215 pub fn symbol_scope(&self, symbol_id: SymbolId) -> ScopeId {
217 self.scoping.symbol_scope_id(symbol_id)
218 }
219
220 pub fn symbol_references(
222 &self,
223 symbol_id: SymbolId,
224 ) -> impl Iterator<Item = &Reference> + '_ + use<'_> {
225 self.scoping.get_resolved_references(symbol_id)
226 }
227
228 pub fn symbol_declaration(&self, symbol_id: SymbolId) -> &AstNode<'a> {
229 self.nodes.get_node(self.scoping.symbol_declaration(symbol_id))
230 }
231
232 pub fn is_reference_to_global_variable(&self, ident: &IdentifierReference) -> bool {
233 self.scoping.root_unresolved_references().contains_key(ident.name.as_str())
234 }
235
236 pub fn reference_name(&self, reference: &Reference) -> &str {
237 let node = self.nodes.get_node(reference.node_id());
238 match node.kind() {
239 AstKind::IdentifierReference(id) => id.name.as_str(),
240 _ => unreachable!(),
241 }
242 }
243
244 pub fn reference_span(&self, reference: &Reference) -> Span {
245 let node = self.nodes.get_node(reference.node_id());
246 node.kind().span()
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use oxc_allocator::Allocator;
253 use oxc_ast::{AstKind, ast::VariableDeclarationKind};
254 use oxc_span::{Atom, SourceType};
255
256 use super::*;
257
258 fn get_semantic<'s, 'a: 's>(
260 allocator: &'a Allocator,
261 source: &'s str,
262 source_type: SourceType,
263 ) -> Semantic<'s> {
264 let parse = oxc_parser::Parser::new(allocator, source, source_type).parse();
265 assert!(parse.errors.is_empty());
266 let semantic = SemanticBuilder::new().build(allocator.alloc(parse.program));
267 assert!(semantic.errors.is_empty(), "Parse error: {}", semantic.errors[0]);
268 semantic.semantic
269 }
270
271 #[test]
272 fn test_symbols() {
273 let source = "
274 let a;
275 function foo(a) {
276 return a + 1;
277 }
278 let b = a + foo(1);";
279 let allocator = Allocator::default();
280 let semantic = get_semantic(&allocator, source, SourceType::default());
281
282 let top_level_a =
283 semantic.scoping().get_binding(semantic.scoping().root_scope_id(), "a").unwrap();
284
285 let decl = semantic.symbol_declaration(top_level_a);
286 match decl.kind() {
287 AstKind::VariableDeclarator(decl) => {
288 assert_eq!(decl.kind, VariableDeclarationKind::Let);
289 }
290 kind => panic!("Expected VariableDeclarator for 'let', got {kind:?}"),
291 }
292
293 let references = semantic.symbol_references(top_level_a);
294 assert_eq!(references.count(), 1);
295 }
296
297 #[test]
298 fn test_top_level_symbols() {
299 let source = "function Fn() {}";
300 let allocator = Allocator::default();
301 let semantic = get_semantic(&allocator, source, SourceType::default());
302 let scopes = semantic.scoping();
303
304 assert!(scopes.get_binding(scopes.root_scope_id(), "Fn").is_some());
305 }
306
307 #[test]
308 fn test_is_global() {
309 let source = "
310 var a = 0;
311 function foo() {
312 a += 1;
313 }
314
315 var b = a + 2;
316 ";
317 let allocator = Allocator::default();
318 let semantic = get_semantic(&allocator, source, SourceType::default());
319 for node in semantic.nodes() {
320 if let AstKind::IdentifierReference(id) = node.kind() {
321 assert!(!semantic.is_reference_to_global_variable(id));
322 }
323 }
324 }
325
326 #[test]
327 fn type_alias_gets_reference() {
328 let source = "type A = 1; type B = A";
329 let allocator = Allocator::default();
330 let source_type: SourceType = SourceType::default().with_typescript(true);
331 let semantic = get_semantic(&allocator, source, source_type);
332 assert_eq!(semantic.scoping().references.len(), 1);
333 }
334
335 #[test]
336 fn test_reference_resolutions_simple_read_write() {
337 let alloc = Allocator::default();
338 let target_symbol_name = Atom::from("a");
339 let typescript = SourceType::ts();
340 let sources = [
341 (SourceType::default(), "let a = 1; a = 2", ReferenceFlags::write()),
343 (SourceType::default(), "let a = 1, b; b = a", ReferenceFlags::read()),
344 (SourceType::default(), "let a = 1, b; b[a]", ReferenceFlags::read()),
345 (SourceType::default(), "let a = 1, b = 1, c; c = a + b", ReferenceFlags::read()),
346 (SourceType::default(), "function a() { return }; a()", ReferenceFlags::read()),
347 (SourceType::default(), "class a {}; new a()", ReferenceFlags::read()),
348 (SourceType::default(), "let a; function foo() { return a }", ReferenceFlags::read()),
349 (SourceType::default(), "let a = 1, b; b = { a }", ReferenceFlags::read()),
351 (SourceType::default(), "let a, b; ({ b } = { a })", ReferenceFlags::read()),
352 (SourceType::default(), "let a, b; ({ a } = { b })", ReferenceFlags::write()),
353 (SourceType::default(), "let a, b; ([ b ] = [ a ])", ReferenceFlags::read()),
354 (SourceType::default(), "let a, b; ([ a ] = [ b ])", ReferenceFlags::write()),
355 (SourceType::default(), "let a = { b: 1 }; a.b = 2", ReferenceFlags::read()),
357 (SourceType::default(), "let a = { b: 1 }; a.b += 2", ReferenceFlags::read()),
358 (SourceType::default(), "let a = 1, b; b = (a)", ReferenceFlags::read()),
360 (SourceType::default(), "let a = 1, b; b = ++(a)", ReferenceFlags::read_write()),
361 (SourceType::default(), "let a = 1, b; b = ++((((a))))", ReferenceFlags::read_write()),
362 (SourceType::default(), "let a = 1, b; b = ((++((a))))", ReferenceFlags::read_write()),
363 (SourceType::default(), "let a, b; a + b", ReferenceFlags::read()),
365 (SourceType::default(), "let a, b; b(a)", ReferenceFlags::read()),
366 (SourceType::default(), "let a, b; a = 5", ReferenceFlags::write()),
367 (SourceType::default(), "let a = 1, b; b = ++a", ReferenceFlags::read_write()),
369 (SourceType::default(), "let a = 1, b; b = --a", ReferenceFlags::read_write()),
370 (SourceType::default(), "let a = 1, b; b = a++", ReferenceFlags::read_write()),
371 (SourceType::default(), "let a = 1, b; b = a--", ReferenceFlags::read_write()),
372 (SourceType::default(), "let a = 1, b; b = a += 5", ReferenceFlags::read_write()),
374 (SourceType::default(), "let a = 1; a += 5", ReferenceFlags::read_write()),
375 (SourceType::default(), "let a, b; b = a = 1", ReferenceFlags::write()),
376 (SourceType::default(), "let a, b; b = (a = 1)", ReferenceFlags::write()),
377 (SourceType::default(), "let a, b, c; b = c = a", ReferenceFlags::read()),
378 (SourceType::default(), "let a, b; b = (0, a++)", ReferenceFlags::read_write()),
380 (
382 SourceType::default(),
383 "var a, arr = [1, 2, 3]; for(a in arr) { break }",
384 ReferenceFlags::write(),
385 ),
386 (
387 SourceType::default(),
388 "var a, obj = { }; for(a of obj) { break }",
389 ReferenceFlags::write(),
390 ),
391 (SourceType::default(), "var a; for(; false; a++) { }", ReferenceFlags::read_write()),
392 (SourceType::default(), "var a = 1; while(a < 5) { break }", ReferenceFlags::read()),
393 (
395 SourceType::default(),
396 "let a; if (a) { true } else { false }",
397 ReferenceFlags::read(),
398 ),
399 (
400 SourceType::default(),
401 "let a, b; if (a == b) { true } else { false }",
402 ReferenceFlags::read(),
403 ),
404 (
405 SourceType::default(),
406 "let a, b; if (b == a) { true } else { false }",
407 ReferenceFlags::read(),
408 ),
409 (SourceType::default(), "let a, b; b = (a, 0)", ReferenceFlags::read()),
412 (SourceType::default(), "let a, b; b = (--a, 0)", ReferenceFlags::read_write()),
413 (
414 SourceType::default(),
415 "let a; function foo(a) { return a }; foo(a = 1)",
416 ReferenceFlags::write(),
418 ),
419 (SourceType::default(), "let a; a.b = 1", ReferenceFlags::read()),
421 (SourceType::default(), "let a; let b; b[a += 1] = 1", ReferenceFlags::read_write()),
422 (
423 SourceType::default(),
424 "let a; let b; let c; b[c[a = c['a']] = 'c'] = 'b'",
425 ReferenceFlags::write(),
427 ),
428 (
429 SourceType::default(),
430 "let a; let b; let c; a[c[b = c['a']] = 'c'] = 'b'",
431 ReferenceFlags::read(),
432 ),
433 (SourceType::default(), "console.log;let a=0;a++", ReferenceFlags::read_write()),
434 (typescript, "let a: number = 1; (a as any) = true", ReferenceFlags::write()),
437 (typescript, "let a: number = 1; a = true as any", ReferenceFlags::write()),
438 (typescript, "let a: number = 1; a = 2 as const", ReferenceFlags::write()),
439 (typescript, "let a: number = 1; a = 2 satisfies number", ReferenceFlags::write()),
440 (typescript, "let a: number; (a as any) = 1;", ReferenceFlags::write()),
441 ];
442
443 for (source_type, source, flags) in sources {
444 let semantic = get_semantic(&alloc, source, source_type);
445 let a_id =
446 semantic.scoping().get_root_binding(&target_symbol_name).unwrap_or_else(|| {
447 panic!("no references for '{target_symbol_name}' found");
448 });
449 let a_refs: Vec<_> = semantic.symbol_references(a_id).collect();
450 let num_refs = a_refs.len();
451
452 assert!(
453 num_refs == 1,
454 "expected to find 1 reference to '{target_symbol_name}' but {num_refs} were found\n\nsource:\n{source}"
455 );
456 let ref_type = a_refs[0];
457 if flags.is_write() {
458 assert!(
459 ref_type.is_write(),
460 "expected reference to '{target_symbol_name}' to be write\n\nsource:\n{source}"
461 );
462 } else {
463 assert!(
464 !ref_type.is_write(),
465 "expected reference to '{target_symbol_name}' not to have been written to, but it is\n\nsource:\n{source}"
466 );
467 }
468 if flags.is_read() {
469 assert!(
470 ref_type.is_read(),
471 "expected reference to '{target_symbol_name}' to be read\n\nsource:\n{source}"
472 );
473 } else {
474 assert!(
475 !ref_type.is_read(),
476 "expected reference to '{target_symbol_name}' not to be read, but it is\n\nsource:\n{source}"
477 );
478 }
479 }
480 }
481}