Skip to content

Commit

Permalink
feat: copy ast-grep-wasm
Browse files Browse the repository at this point in the history
  • Loading branch information
LoTwT committed Apr 1, 2024
1 parent d7de968 commit 7bdc3d5
Show file tree
Hide file tree
Showing 7 changed files with 599 additions and 19 deletions.
25 changes: 21 additions & 4 deletions Cargo.toml
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
1 change: 1 addition & 0 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tab_spaces = 2
64 changes: 64 additions & 0 deletions src/dump_tree.rs
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;
}
}
}
116 changes: 110 additions & 6 deletions src/lib.rs
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())
}
97 changes: 89 additions & 8 deletions src/utils.rs
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),
}
}
}
Loading

0 comments on commit 7bdc3d5

Please sign in to comment.