Skip to content

Commit 51966da

Browse files
authored
Merge pull request #21782 from jaroslawroszyk/feat-parse-json
feat: add --json flag to rust-analyzer parse
2 parents 9603dab + 6ff82f9 commit 51966da

2 files changed

Lines changed: 89 additions & 3 deletions

File tree

crates/rust-analyzer/src/cli/flags.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ xflags::xflags! {
4040
cmd parse {
4141
/// Suppress printing.
4242
optional --no-dump
43+
/// Output as JSON.
44+
optional --json
4345
}
4446

4547
/// Parse stdin and print the list of symbols.
@@ -233,6 +235,7 @@ pub struct LspServer {
233235
#[derive(Debug)]
234236
pub struct Parse {
235237
pub no_dump: bool,
238+
pub json: bool,
236239
}
237240

238241
#[derive(Debug)]
@@ -257,8 +260,8 @@ pub struct AnalysisStats {
257260
pub disable_build_scripts: bool,
258261
pub disable_proc_macros: bool,
259262
pub proc_macro_srv: Option<PathBuf>,
260-
pub skip_lowering: bool,
261263
pub skip_lang_items: bool,
264+
pub skip_lowering: bool,
262265
pub skip_inference: bool,
263266
pub skip_mir_stats: bool,
264267
pub skip_data_layout: bool,
Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,101 @@
11
//! Read Rust code on stdin, print syntax tree on stdout.
22
use ide::Edition;
3-
use syntax::{AstNode, SourceFile};
3+
use ide_db::line_index::LineIndex;
4+
use serde::Serialize;
5+
use syntax::{AstNode, NodeOrToken, SourceFile, SyntaxNode, SyntaxToken};
46

57
use crate::cli::{flags, read_stdin};
68

9+
#[derive(Serialize)]
10+
struct JsonNode {
11+
kind: String,
12+
#[serde(rename = "type")]
13+
node_type: &'static str,
14+
start: [u32; 3],
15+
end: [u32; 3],
16+
#[serde(skip_serializing_if = "Option::is_none")]
17+
text: Option<String>,
18+
#[serde(skip_serializing_if = "Option::is_none")]
19+
children: Option<Vec<JsonNode>>,
20+
}
21+
22+
fn pos(line_index: &LineIndex, offset: syntax::TextSize) -> [u32; 3] {
23+
let offset_u32 = u32::from(offset);
24+
let line_col = line_index.line_col(offset);
25+
[offset_u32, line_col.line, line_col.col]
26+
}
27+
728
impl flags::Parse {
829
pub fn run(self) -> anyhow::Result<()> {
930
let _p = tracing::info_span!("flags::Parse::run").entered();
1031
let text = read_stdin()?;
32+
let line_index = LineIndex::new(&text);
1133
let file = SourceFile::parse(&text, Edition::CURRENT).tree();
34+
1235
if !self.no_dump {
13-
println!("{:#?}", file.syntax());
36+
if self.json {
37+
let json_tree = node_to_json(NodeOrToken::Node(file.syntax().clone()), &line_index);
38+
println!("{}", serde_json::to_string(&json_tree)?);
39+
} else {
40+
println!("{:#?}", file.syntax());
41+
}
1442
}
43+
1544
std::mem::forget(file);
1645
Ok(())
1746
}
1847
}
48+
49+
fn node_to_json(node: NodeOrToken<SyntaxNode, SyntaxToken>, line_index: &LineIndex) -> JsonNode {
50+
let range = node.text_range();
51+
let kind = format!("{:?}", node.kind());
52+
53+
match node {
54+
NodeOrToken::Node(n) => {
55+
let children: Vec<_> =
56+
n.children_with_tokens().map(|it| node_to_json(it, line_index)).collect();
57+
JsonNode {
58+
kind,
59+
node_type: "Node",
60+
start: pos(line_index, range.start()),
61+
end: pos(line_index, range.end()),
62+
text: None,
63+
children: Some(children),
64+
}
65+
}
66+
NodeOrToken::Token(t) => JsonNode {
67+
kind,
68+
node_type: "Token",
69+
start: pos(line_index, range.start()),
70+
end: pos(line_index, range.end()),
71+
text: Some(t.text().to_owned()),
72+
children: None,
73+
},
74+
}
75+
}
76+
77+
#[cfg(test)]
78+
mod tests {
79+
use super::*;
80+
use crate::cli::flags;
81+
82+
#[test]
83+
fn test_parse_json_output() {
84+
let text = "fn main() {}".to_owned();
85+
let flags = flags::Parse { json: true, no_dump: false };
86+
let line_index = LineIndex::new(&text);
87+
88+
let file = SourceFile::parse(&text, Edition::CURRENT).tree();
89+
90+
let output = if flags.json {
91+
let json_tree = node_to_json(NodeOrToken::Node(file.syntax().clone()), &line_index);
92+
serde_json::to_string(&json_tree).unwrap()
93+
} else {
94+
format!("{:#?}", file.syntax())
95+
};
96+
97+
assert!(output.contains(r#""kind":"SOURCE_FILE""#));
98+
assert!(output.contains(r#""text":"main""#));
99+
assert!(output.contains(r#""start":[0,0,0]"#));
100+
}
101+
}

0 commit comments

Comments
 (0)