diff --git a/src/command.rs b/src/command.rs index 19a0f4c172..b0fd29bca0 100644 --- a/src/command.rs +++ b/src/command.rs @@ -33,7 +33,7 @@ pub(crate) fn gather_commandline_args() -> (Vec, String, Vec) { let flag_value = match arg.as_ref() { "--commands" | "-c" | "--table-mode" | "-m" | "-e" | "--execute" | "--config" - | "--env-config" | "-I" => args.next().map(|a| escape_quote_string(&a)), + | "--env-config" | "-I" | "ide-ast" => args.next().map(|a| escape_quote_string(&a)), #[cfg(feature = "plugin")] "--plugin-config" => args.next().map(|a| escape_quote_string(&a)), "--log-level" | "--log-target" | "--testbin" | "--threads" | "-t" @@ -104,16 +104,18 @@ pub(crate) fn parse_commandline_args( let log_level: Option = call.get_flag_expr("log-level"); let log_target: Option = call.get_flag_expr("log-target"); let execute: Option = call.get_flag_expr("execute"); - let include_path: Option = call.get_flag_expr("include-path"); let table_mode: Option = call.get_flag(engine_state, &mut stack, "table-mode")?; + // ide flags + let include_path: Option = call.get_flag_expr("include-path"); let ide_goto_def: Option = call.get_flag(engine_state, &mut stack, "ide-goto-def")?; let ide_hover: Option = call.get_flag(engine_state, &mut stack, "ide-hover")?; let ide_complete: Option = call.get_flag(engine_state, &mut stack, "ide-complete")?; let ide_check: Option = call.get_flag(engine_state, &mut stack, "ide-check")?; + let ide_ast: Option> = call.get_named_arg("ide-ast"); fn extract_contents( expression: Option, @@ -192,6 +194,7 @@ pub(crate) fn parse_commandline_args( ide_hover, ide_complete, ide_check, + ide_ast, table_mode, }); } @@ -230,6 +233,7 @@ pub(crate) struct NushellCliArgs { pub(crate) ide_hover: Option, pub(crate) ide_complete: Option, pub(crate) ide_check: Option, + pub(crate) ide_ast: Option>, } #[derive(Clone)] @@ -317,7 +321,8 @@ impl Command for Nu { SyntaxShape::Int, "run a diagnostic check on the given source", None, - ); + ) + .switch("ide-ast", "generate the ast on the given source", None); #[cfg(feature = "plugin")] { diff --git a/src/ide.rs b/src/ide.rs index f78f933808..937757e7e2 100644 --- a/src/ide.rs +++ b/src/ide.rs @@ -7,7 +7,7 @@ use nu_protocol::{ DeclId, ShellError, Span, Value, VarId, }; use reedline::Completer; -use serde_json::json; +use serde_json::{json, Value as JsonValue}; use std::sync::Arc; #[derive(Debug)] @@ -602,3 +602,59 @@ pub fn complete(engine_reference: Arc, file_path: &String, location println!("]}}"); } } + +pub fn ast(engine_state: &mut EngineState, file_path: &String) { + let cwd = std::env::current_dir().expect("Could not get current working directory."); + engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy())); + + let mut working_set = StateWorkingSet::new(engine_state); + let file = std::fs::read(file_path); + + if let Ok(contents) = file { + let offset = working_set.next_span_start(); + let parsed_block = parse(&mut working_set, Some(file_path), &contents, false); + + let flat = flatten_block(&working_set, &parsed_block); + let mut json_val: JsonValue = json!([]); + for (span, shape) in flat { + let content = String::from_utf8_lossy(working_set.get_span_contents(span)).to_string(); + + let json = json!( + { + "type": "ast", + "span": { + "start": span.start - offset, + "end": span.end - offset, + }, + "shape": shape.to_string(), + "content": content // may not be necessary, but helpful for debugging + } + ); + json_merge(&mut json_val, &json); + } + if let Ok(json_str) = serde_json::to_string(&json_val) { + println!("{json_str}"); + } else { + println!("{{}}"); + }; + } +} + +fn json_merge(a: &mut JsonValue, b: &JsonValue) { + match (a, b) { + (JsonValue::Object(ref mut a), JsonValue::Object(b)) => { + for (k, v) in b { + json_merge(a.entry(k).or_insert(JsonValue::Null), v); + } + } + (JsonValue::Array(ref mut a), JsonValue::Array(b)) => { + a.extend(b.clone()); + } + (JsonValue::Array(ref mut a), JsonValue::Object(b)) => { + a.extend([JsonValue::Object(b.clone())]); + } + (a, b) => { + *a = b.clone(); + } + } +} diff --git a/src/main.rs b/src/main.rs index 4305994834..5a42c39bac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -170,6 +170,10 @@ fn main() -> Result<()> { } else if let Some(max_errors) = parsed_nu_cli_args.ide_check { ide::check(&mut engine_state, &script_name, &max_errors); + return Ok(()); + } else if parsed_nu_cli_args.ide_ast.is_some() { + ide::ast(&mut engine_state, &script_name); + return Ok(()); }