mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(oxc_semantic): Improve sample visualization (#2251)
1. add a `test.js` file to the project root:
```js
class A extends B {
constructor() {
try {
super();
} finally {
this.a;
}
}
}
```
2. run:
```bash
$ cargo run -p oxc_semantic --example simple
Compiling oxc_semantic v0.5.0 (/home/tzvipm/src/github.com/tzvipm/oxc/crates/oxc_semantic)
Finished dev [unoptimized + debuginfo] target(s) in 32.07s
Running `target/debug/examples/simple`
Wrote AST to: test.ast.txt
Wrote CFG blocks to: test.cfg.txt
Wrote CFG dot diagram to: test.dot
```
3. resulting graph from .dot file:

This commit is contained in:
parent
73ccf8a4da
commit
27681951e1
3 changed files with 117 additions and 1 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -18,5 +18,8 @@ npm/cli-*
|
|||
/*.ts
|
||||
/*.tsx
|
||||
/tasks/transform_conformance/fixtures/*
|
||||
/*.ast.txt
|
||||
/*.cfg.txt
|
||||
/*.dot
|
||||
|
||||
.oxc/plugins/**.*
|
||||
114
crates/oxc_semantic/examples/cfg.rs
Normal file
114
crates/oxc_semantic/examples/cfg.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
use std::{collections::HashMap, env, path::Path, sync::Arc};
|
||||
|
||||
use itertools::Itertools;
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_parser::Parser;
|
||||
use oxc_semantic::{print_basic_block, SemanticBuilder};
|
||||
use oxc_span::SourceType;
|
||||
use petgraph::dot::{Config, Dot};
|
||||
|
||||
// Instruction:
|
||||
// 1. create a `test.js`,
|
||||
// 2. run `cargo run -p oxc_semantic --example cfg`
|
||||
// or `just watch "run -p oxc_semantic --example cfg"`
|
||||
// 3. observe visualizations of:
|
||||
// - AST (test.ast.txt)
|
||||
// - CFG blocks (test.cfg.txt)
|
||||
// - CFG graph (test.dot)
|
||||
|
||||
fn main() -> Result<(), std::io::Error> {
|
||||
let test_file_name = env::args().nth(1).unwrap_or_else(|| "test.js".to_string());
|
||||
let ast_file_name = env::args().nth(1).unwrap_or_else(|| "test.ast.txt".to_string());
|
||||
let cfg_file_name = env::args().nth(1).unwrap_or_else(|| "test.cfg.txt".to_string());
|
||||
let dot_file_name = env::args().nth(1).unwrap_or_else(|| "test.dot".to_string());
|
||||
|
||||
let test_file_path = Path::new(&test_file_name);
|
||||
let ast_file_path = Path::new(&ast_file_name);
|
||||
let cfg_file_path = Path::new(&cfg_file_name);
|
||||
let dot_file_path = Path::new(&dot_file_name);
|
||||
|
||||
let source_text = Arc::new(
|
||||
std::fs::read_to_string(test_file_path)
|
||||
.unwrap_or_else(|_| panic!("{test_file_name} not found")),
|
||||
);
|
||||
let allocator = Allocator::default();
|
||||
let source_type = SourceType::from_path(test_file_path).unwrap();
|
||||
let ret = Parser::new(&allocator, &source_text, source_type).parse();
|
||||
|
||||
let program = allocator.alloc(ret.program);
|
||||
std::fs::write(ast_file_path, format!("{:#?}", &program))?;
|
||||
println!("Wrote AST to: {}", &ast_file_name);
|
||||
|
||||
let semantic = SemanticBuilder::new(&source_text, source_type)
|
||||
.with_check_syntax_error(true)
|
||||
.with_trivias(ret.trivias)
|
||||
.build(program);
|
||||
|
||||
if !semantic.errors.is_empty() {
|
||||
let error_message: String = semantic
|
||||
.errors
|
||||
.into_iter()
|
||||
.map(|error| error.with_source_code(Arc::clone(&source_text)).to_string())
|
||||
.join("\n\n");
|
||||
|
||||
panic!("Semantic analysis failed:\n\n{error_message}",);
|
||||
}
|
||||
|
||||
let mut ast_nodes_by_block = HashMap::<_, Vec<_>>::new();
|
||||
for node in semantic.semantic.nodes().iter() {
|
||||
let block = node.cfg_ix();
|
||||
let block_ix = semantic.semantic.cfg().graph.node_weight(block).unwrap();
|
||||
ast_nodes_by_block.entry(*block_ix).or_default().push(node);
|
||||
}
|
||||
|
||||
let basic_blocks_printed = semantic
|
||||
.semantic
|
||||
.cfg()
|
||||
.basic_blocks
|
||||
.iter()
|
||||
.map(print_basic_block)
|
||||
.enumerate()
|
||||
.map(|(i, it)| {
|
||||
format!(
|
||||
"bb{i}: {{\n{}\n---\n{}\n}}",
|
||||
it.lines().map(|x| format!("\t{}", x.trim())).join("\n"),
|
||||
ast_nodes_by_block
|
||||
.get(&i)
|
||||
.map(|nodes| {
|
||||
nodes.iter().map(|node| format!("{}", node.kind().debug_name())).join("\n")
|
||||
})
|
||||
.unwrap_or_default()
|
||||
)
|
||||
})
|
||||
.join("\n\n");
|
||||
|
||||
std::fs::write(cfg_file_path, basic_blocks_printed)?;
|
||||
println!("Wrote CFG blocks to: {}", &cfg_file_name);
|
||||
|
||||
let cfg_dot_diagram = format!(
|
||||
"{:?}",
|
||||
Dot::with_attr_getters(
|
||||
&semantic.semantic.cfg().graph,
|
||||
&[Config::EdgeNoLabel, Config::NodeNoLabel],
|
||||
&|_graph, edge| format!("label = {:?}", edge.weight()),
|
||||
&|_graph, node| format!(
|
||||
"xlabel = {:?}, label = {:?}",
|
||||
format!(
|
||||
"bb{} [{}]",
|
||||
node.1,
|
||||
print_basic_block(&semantic.semantic.cfg().basic_blocks[*node.1],).trim()
|
||||
),
|
||||
ast_nodes_by_block
|
||||
.get(node.1)
|
||||
.map(|nodes| {
|
||||
nodes.iter().map(|node| format!("{}", node.kind().debug_name())).join("\n")
|
||||
})
|
||||
.unwrap_or_default()
|
||||
)
|
||||
)
|
||||
);
|
||||
std::fs::write(dot_file_path, cfg_dot_diagram)?;
|
||||
println!("Wrote CFG dot diagram to: {}", &dot_file_name);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -21,7 +21,6 @@ fn main() {
|
|||
let ret = Parser::new(&allocator, &source_text, source_type).parse();
|
||||
|
||||
let program = allocator.alloc(ret.program);
|
||||
println!("{:#?}", &program);
|
||||
|
||||
let semantic = SemanticBuilder::new(&source_text, source_type)
|
||||
.with_check_syntax_error(true)
|
||||
|
|
|
|||
Loading…
Reference in a new issue