-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
599 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,44 @@ | ||
[package] | ||
name = "ag-wasm" | ||
version = "0.1.0" | ||
version = "0.0.1" | ||
authors = ["Lo <[email protected]>"] | ||
edition = "2018" | ||
keywords = ["ast", "pattern", "codemod", "search", "rewrite"] | ||
license = "MIT" | ||
|
||
[lib] | ||
crate-type = ["cdylib", "rlib"] | ||
|
||
[features] | ||
default = ["console_error_panic_hook"] | ||
default = [] | ||
|
||
[dependencies] | ||
wasm-bindgen = "0.2.84" | ||
wasm-bindgen = { version = "0.2.92", features = ["serde-serialize"] } | ||
wasm-bindgen-futures = "0.4.42" | ||
serde = { version = "1.0", features = ["derive"] } | ||
|
||
# The `console_error_panic_hook` crate provides better debugging of panics by | ||
# logging them with `console.error`. This is great for development, but requires | ||
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for | ||
# code size when deploying. | ||
console_error_panic_hook = { version = "0.1.7", optional = true } | ||
|
||
once_cell = "1.19.0" | ||
wee_alloc = { version = "0.4.5" } | ||
ast-grep-core = { version = "0.19.4" } | ||
ast-grep-config = { version = "0.19.4" } | ||
ast-grep-language = { version = "0.19.4", default-features = false } | ||
web-tree-sitter-sg = "1.3.4" | ||
tree-sitter = { version = "0.9.2", package = "tree-sitter-facade-sg" } | ||
serde-wasm-bindgen = "0.6" | ||
serde_json = "1.0.114" | ||
|
||
[dev-dependencies] | ||
wasm-bindgen-test = "0.3.34" | ||
wasm-bindgen-test = "0.3.42" | ||
tree-sitter-rust = "0.20.4" | ||
|
||
[profile.release] | ||
panic = "abort" | ||
# Tell `rustc` to optimize for small code size. | ||
opt-level = "s" | ||
lto = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
tab_spaces = 2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
use serde::{Deserialize, Serialize}; | ||
use tree_sitter as ts; | ||
|
||
#[derive(Deserialize, Serialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct DumpNode { | ||
field: Option<String>, | ||
kind: String, | ||
start: Pos, | ||
end: Pos, | ||
is_named: bool, | ||
children: Vec<DumpNode>, | ||
} | ||
|
||
#[derive(Deserialize, Serialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct Pos { | ||
row: u32, | ||
column: u32, | ||
} | ||
|
||
impl From<ts::Point> for Pos { | ||
#[inline] | ||
fn from(pt: ts::Point) -> Self { | ||
Pos { | ||
row: pt.row(), | ||
column: pt.column(), | ||
} | ||
} | ||
} | ||
|
||
pub fn dump_one_node(cursor: &mut ts::TreeCursor, target: &mut Vec<DumpNode>) { | ||
let node = cursor.node(); | ||
let kind = if node.is_missing() { | ||
format!("MISSING {}", node.kind()) | ||
} else { | ||
node.kind().to_string() | ||
}; | ||
let start = node.start_position().into(); | ||
let end = node.end_position().into(); | ||
let field = cursor.field_name().map(|c| c.to_string()); | ||
let mut children = vec![]; | ||
if cursor.goto_first_child() { | ||
dump_nodes(cursor, &mut children); | ||
cursor.goto_parent(); | ||
} | ||
target.push(DumpNode { | ||
field, | ||
kind, | ||
start, | ||
end, | ||
children, | ||
is_named: node.is_named(), | ||
}) | ||
} | ||
|
||
fn dump_nodes(cursor: &mut ts::TreeCursor, target: &mut Vec<DumpNode>) { | ||
loop { | ||
dump_one_node(cursor, target); | ||
if !cursor.goto_next_sibling() { | ||
break; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,117 @@ | ||
mod dump_tree; | ||
mod utils; | ||
mod wasm_lang; | ||
|
||
use dump_tree::{dump_one_node, DumpNode}; | ||
use utils::WasmMatch; | ||
use wasm_lang::{WasmDoc, WasmLang}; | ||
|
||
use ast_grep_config::{CombinedScan, RuleConfig, SerializableRuleConfig}; | ||
use ast_grep_core::language::Language; | ||
use ast_grep_core::{AstGrep, Node as SgNode}; | ||
use serde_wasm_bindgen::from_value as from_js_val; | ||
use std::collections::HashMap; | ||
use tree_sitter as ts; | ||
use wasm_bindgen::prelude::*; | ||
|
||
#[wasm_bindgen] | ||
extern "C" { | ||
fn alert(s: &str); | ||
type Node<'a> = SgNode<'a, WasmDoc>; | ||
|
||
#[global_allocator] | ||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; | ||
|
||
#[wasm_bindgen(js_name = initializeTreeSitter)] | ||
pub async fn initialize_tree_sitter() -> Result<(), JsError> { | ||
ts::TreeSitter::init().await | ||
} | ||
|
||
#[wasm_bindgen(js_name = setupParser)] | ||
pub async fn setup_parser(lang_name: String, parser_path: String) -> Result<(), JsError> { | ||
WasmLang::set_current(&lang_name, &parser_path).await | ||
} | ||
|
||
#[wasm_bindgen(js_name = findNodes)] | ||
pub fn find_nodes(src: String, configs: Vec<JsValue>) -> Result<JsValue, JsError> { | ||
let lang = WasmLang::get_current(); | ||
let mut rules = vec![]; | ||
for config in configs { | ||
let config: SerializableRuleConfig<WasmLang> = from_js_val(config)?; | ||
let finder = RuleConfig::try_from(config, &Default::default())?; | ||
rules.push(finder); | ||
} | ||
let combined = CombinedScan::new(rules.iter().collect()); | ||
let root = lang.ast_grep(src); | ||
let sets = combined.find(&root); | ||
let ret: HashMap<_, _> = combined | ||
.scan(&root, sets, false) | ||
.into_iter() | ||
.map(|(id, matches)| { | ||
let rule = combined.get_rule(id).id.clone(); | ||
let matches: Vec<_> = matches.into_iter().map(WasmMatch::from).collect(); | ||
(rule, matches) | ||
}) | ||
.collect(); | ||
let ret = serde_wasm_bindgen::to_value(&ret)?; | ||
Ok(ret) | ||
} | ||
|
||
#[wasm_bindgen(js_name = fixErrors)] | ||
pub fn fix_errors(src: String, configs: Vec<JsValue>) -> Result<String, JsError> { | ||
let lang = WasmLang::get_current(); | ||
let mut rules = vec![]; | ||
for config in configs { | ||
let config: SerializableRuleConfig<WasmLang> = from_js_val(config)?; | ||
let finder = RuleConfig::try_from(config, &Default::default())?; | ||
rules.push(finder); | ||
} | ||
let combined = CombinedScan::new(rules.iter().collect()); | ||
let doc = WasmDoc::new(src.clone(), lang); | ||
let root = AstGrep::doc(doc); | ||
let sets = combined.find(&root); | ||
let diffs = combined.diffs(&root, sets); | ||
if diffs.is_empty() { | ||
return Ok(src); | ||
} | ||
let mut start = 0; | ||
let src: Vec<_> = src.chars().collect(); | ||
let mut new_content = Vec::<char>::new(); | ||
for (nm, idx) in diffs { | ||
let range = nm.range(); | ||
if start > range.start { | ||
continue; | ||
} | ||
let rule = combined.get_rule(idx); | ||
let fixer = rule | ||
.get_fixer()? | ||
.expect("rule returned by diff must have fixer"); | ||
let edit = nm.make_edit(&rule.matcher, &fixer); | ||
new_content.extend(&src[start..edit.position]); | ||
new_content.extend(&edit.inserted_text); | ||
start = edit.position + edit.deleted_length; | ||
} | ||
// add trailing statements | ||
new_content.extend(&src[start..]); | ||
Ok(new_content.into_iter().collect()) | ||
} | ||
|
||
fn convert_to_debug_node(n: Node) -> DumpNode { | ||
let mut cursor = n.get_ts_node().walk(); | ||
let mut target = vec![]; | ||
dump_one_node(&mut cursor, &mut target); | ||
target.pop().expect_throw("found empty node") | ||
} | ||
|
||
#[wasm_bindgen(js_name = dumpASTNodes)] | ||
pub fn dump_ast_nodes(src: String) -> Result<JsValue, JsError> { | ||
let lang = WasmLang::get_current(); | ||
let doc = WasmDoc::new(src, lang); | ||
let root = AstGrep::doc(doc); | ||
let debug_node = convert_to_debug_node(root.root()); | ||
let ret = serde_wasm_bindgen::to_value(&debug_node)?; | ||
Ok(ret) | ||
} | ||
|
||
#[wasm_bindgen] | ||
pub fn greet() -> String { | ||
String::from("Greet from wasm!") | ||
#[wasm_bindgen(js_name = preProcessPattern)] | ||
pub fn pre_process_pattern(query: String) -> Result<String, JsError> { | ||
let lang = WasmLang::get_current(); | ||
Ok(lang.pre_process_pattern(&query).into()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,91 @@ | ||
use crate::wasm_lang::WasmLang; | ||
use ast_grep_core::{ | ||
meta_var::{MetaVarEnv, MetaVariable}, | ||
Node as SgNode, NodeMatch as SgNodeMatch, StrDoc, | ||
}; | ||
use serde::{Deserialize, Serialize}; | ||
use std::collections::BTreeMap; | ||
|
||
#[cfg(feature = "console_error_panic_hook")] | ||
pub fn set_panic_hook() { | ||
// When the `console_error_panic_hook` feature is enabled, we can call the | ||
// `set_panic_hook` function at least once during initialization, and then | ||
// we will get better error messages if our code ever panics. | ||
// | ||
// For more details see | ||
// https://github.com/rustwasm/console_error_panic_hook#readme | ||
#[cfg(feature = "console_error_panic_hook")] | ||
console_error_panic_hook::set_once(); | ||
// When the `console_error_panic_hook` feature is enabled, we can call the | ||
// `set_panic_hook` function at least once during initialization, and then | ||
// we will get better error messages if our code ever panics. | ||
// | ||
// For more details see | ||
// https://github.com/rustwasm/console_error_panic_hook#readme | ||
console_error_panic_hook::set_once(); | ||
} | ||
|
||
type Node<'a> = SgNode<'a, StrDoc<WasmLang>>; | ||
type NodeMatch<'a> = SgNodeMatch<'a, StrDoc<WasmLang>>; | ||
|
||
#[derive(Serialize, Deserialize)] | ||
pub struct WasmNode { | ||
pub text: String, | ||
pub range: (usize, usize, usize, usize), | ||
} | ||
|
||
#[derive(Serialize, Deserialize)] | ||
pub struct WasmMatch { | ||
pub node: WasmNode, | ||
pub env: BTreeMap<String, WasmNode>, | ||
} | ||
|
||
impl From<NodeMatch<'_>> for WasmMatch { | ||
fn from(nm: NodeMatch) -> Self { | ||
let node = nm.get_node().clone(); | ||
let node = WasmNode::from(node); | ||
let env = nm.get_env().clone(); | ||
let env = env_to_map(env); | ||
Self { node, env } | ||
} | ||
} | ||
|
||
fn env_to_map(env: MetaVarEnv<'_, StrDoc<WasmLang>>) -> BTreeMap<String, WasmNode> { | ||
let mut map = BTreeMap::new(); | ||
for id in env.get_matched_variables() { | ||
match id { | ||
MetaVariable::Capture(name, _) => { | ||
if let Some(node) = env.get_match(&name) { | ||
map.insert(name, WasmNode::from(node.clone())); | ||
} else if let Some(bytes) = env.get_transformed(&name) { | ||
let node = WasmNode { | ||
text: String::from_utf8_lossy(bytes).to_string(), | ||
range: (0, 0, 0, 0), | ||
}; | ||
map.insert(name, WasmNode::from(node)); | ||
} | ||
} | ||
MetaVariable::MultiCapture(name) => { | ||
let nodes = env.get_multiple_matches(&name); | ||
let (Some(first), Some(last)) = (nodes.first(), nodes.last()) else { | ||
continue; | ||
}; | ||
let start = first.start_pos(); | ||
let end = last.end_pos(); | ||
|
||
let text = nodes.iter().map(|n| n.text()).collect(); | ||
let node = WasmNode { | ||
text, | ||
range: (start.0, start.1, end.0, end.1), | ||
}; | ||
map.insert(name, node); | ||
} | ||
// ignore anonymous | ||
_ => continue, | ||
} | ||
} | ||
map | ||
} | ||
|
||
impl From<Node<'_>> for WasmNode { | ||
fn from(nm: Node) -> Self { | ||
let start = nm.start_pos(); | ||
let end = nm.end_pos(); | ||
Self { | ||
text: nm.text().to_string(), | ||
range: (start.0, start.1, end.0, end.1), | ||
} | ||
} | ||
} |
Oops, something went wrong.