From 45a7985524baa17684590c26d37c2c31035d3bb4 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Wed, 3 Jan 2024 16:10:42 +0800 Subject: [PATCH] feat(playground): visualize scope (#1882) Partial support https://github.com/oxc-project/oxc/issues/1048 --- crates/oxc_semantic/src/scope.rs | 4 +++ crates/oxc_wasm/src/lib.rs | 58 ++++++++++++++++++++++++++++++-- crates/oxc_wasm/src/options.rs | 11 ++++++ website/playground/index.html | 1 + website/playground/index.js | 10 ++++++ 5 files changed, 81 insertions(+), 3 deletions(-) diff --git a/crates/oxc_semantic/src/scope.rs b/crates/oxc_semantic/src/scope.rs index ea1fc8a35..403043e14 100644 --- a/crates/oxc_semantic/src/scope.rs +++ b/crates/oxc_semantic/src/scope.rs @@ -67,6 +67,10 @@ impl ScopeTree { list.into_iter() } + pub fn get_child_ids(&self, scope_id: ScopeId) -> Option<&Vec> { + self.child_ids.get(&scope_id) + } + pub fn descendants_from_root(&self) -> impl Iterator + '_ { self.parent_ids.iter_enumerated().map(|(scope_id, _)| scope_id) } diff --git a/crates/oxc_wasm/src/lib.rs b/crates/oxc_wasm/src/lib.rs index 1ec42539a..3e65b2521 100644 --- a/crates/oxc_wasm/src/lib.rs +++ b/crates/oxc_wasm/src/lib.rs @@ -9,7 +9,7 @@ use oxc::{ formatter::{Formatter, FormatterOptions}, minifier::{CompressOptions, Minifier, MinifierOptions}, parser::{Parser, ParserReturn}, - semantic::{SemanticBuilder, SemanticBuilderReturn}, + semantic::{ScopeId, Semantic, SemanticBuilder, SemanticBuilderReturn}, span::SourceType, transformer::{TransformOptions, TransformTarget, Transformer}, }; @@ -45,6 +45,7 @@ pub struct Oxc { ast: JsValue, ir: JsValue, + scope_text: String, codegen_text: String, formatted_text: String, prettier_formatted_text: String, @@ -114,6 +115,11 @@ impl Oxc { self.codegen_text.clone() } + #[wasm_bindgen(getter = scopeText)] + pub fn scope_text(&self) -> String { + self.scope_text.clone() + } + /// Returns Array of String /// # Errors /// # Panics @@ -191,14 +197,16 @@ impl Oxc { let program = allocator.alloc(ret.program); + let semantic_builder = SemanticBuilder::new(source_text, source_type); + if run_options.syntax() && !run_options.lint() { - let semantic_ret = SemanticBuilder::new(source_text, source_type) + let semantic_ret = semantic_builder .with_trivias(ret.trivias) .with_check_syntax_error(true) .build(program); self.save_diagnostics(semantic_ret.errors); } else if run_options.lint() { - let semantic_ret = SemanticBuilder::new(source_text, source_type) + let semantic_ret = semantic_builder .with_trivias(ret.trivias) .with_check_syntax_error(true) .build(program); @@ -266,6 +274,11 @@ impl Oxc { } } + if run_options.scope() { + let semantic = SemanticBuilder::new(source_text, source_type).build(program).semantic; + self.scope_text = Self::get_scope_text(&semantic); + } + let program = allocator.alloc(program); if minifier_options.compress() || minifier_options.mangle() { @@ -289,6 +302,45 @@ impl Oxc { Ok(()) } + fn get_scope_text(semantic: &Semantic) -> String { + fn write_scope_text( + semantic: &Semantic, + scope_text: &mut String, + depth: usize, + scope_ids: &Vec, + ) { + let space = " ".repeat(depth * 2); + + for scope_id in scope_ids { + let flag = semantic.scopes().get_flags(*scope_id); + let next_scope_ids = semantic.scopes().get_child_ids(*scope_id); + + scope_text.push_str(&format!("{space}Scope{:?} ({flag:?}) {{\n", *scope_id + 1)); + let bindings = semantic.scopes().get_bindings(*scope_id); + let binding_space = " ".repeat((depth + 1) * 2); + if !bindings.is_empty() { + scope_text.push_str(&format!("{binding_space}Bindings: {{")); + } + bindings.iter().for_each(|(name, symbol_id)| { + let symbol_flag = semantic.symbols().get_flag(*symbol_id); + scope_text.push_str(&format!("\n{binding_space} {name} ({symbol_flag:?})",)); + }); + if !bindings.is_empty() { + scope_text.push_str(&format!("\n{binding_space}}}\n")); + } + + if let Some(next_scope_ids) = next_scope_ids { + write_scope_text(semantic, scope_text, depth + 1, next_scope_ids); + } + scope_text.push_str(&format!("{space}}}\n")); + } + } + + let mut scope_text = String::default(); + write_scope_text(semantic, &mut scope_text, 0, &vec![semantic.scopes().root_scope_id()]); + scope_text + } + fn save_diagnostics(&self, diagnostics: Vec) { self.diagnostics.borrow_mut().extend(diagnostics); } diff --git a/crates/oxc_wasm/src/options.rs b/crates/oxc_wasm/src/options.rs index 1c7e31d03..a2ed074aa 100644 --- a/crates/oxc_wasm/src/options.rs +++ b/crates/oxc_wasm/src/options.rs @@ -10,6 +10,7 @@ pub struct OxcRunOptions { prettier_ir: bool, transform: bool, type_check: bool, + scope: bool, } #[wasm_bindgen] @@ -88,6 +89,16 @@ impl OxcRunOptions { pub fn set_type_check(&mut self, yes: bool) { self.type_check = yes; } + + #[wasm_bindgen(getter)] + pub fn scope(self) -> bool { + self.scope + } + + #[wasm_bindgen(setter)] + pub fn set_scope(&mut self, yes: bool) { + self.scope = yes; + } } #[wasm_bindgen] diff --git a/website/playground/index.html b/website/playground/index.html index 14ee2a1f1..7da9d1ef7 100644 --- a/website/playground/index.html +++ b/website/playground/index.html @@ -65,6 +65,7 @@ + diff --git a/website/playground/index.js b/website/playground/index.js index 459a9726a..1a2a99d2a 100644 --- a/website/playground/index.js +++ b/website/playground/index.js @@ -519,6 +519,12 @@ class Playground { this.run(); text = JSON.stringify(this.oxc.ast, null, 2); break; + case "scope": + this.runOptions.scope = true; + this.run(); + this.runOptions.scope = false + text = this.oxc.scopeText; + break; case "codegen": this.run(); text = this.oxc.codegenText; @@ -793,6 +799,10 @@ async function main() { playground.updateView("ast"); }; + document.getElementById("scope").onclick = () => { + playground.updateView("scope"); + }; + document.getElementById("codegen").onclick = () => { playground.updateView("codegen"); };