Skip to content
This repository was archived by the owner on Sep 9, 2025. It is now read-only.

Commit 714af5a

Browse files
author
Hendrik van Antwerpen
committed
Add rudimentary analyze command
1 parent 3286f81 commit 714af5a

File tree

2 files changed

+203
-2
lines changed

2 files changed

+203
-2
lines changed

tree-sitter-stack-graphs/src/cli.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
5656
pub(self) const MAX_PARSE_ERRORS: usize = 5;
5757

58+
pub mod analyze;
5859
pub mod init;
5960
pub mod load;
6061
pub mod parse;
@@ -64,13 +65,15 @@ mod util;
6465
pub mod path_loading {
6566
use clap::Subcommand;
6667

68+
use crate::cli::analyze::AnalyzeArgs;
6769
use crate::cli::init::InitArgs;
6870
use crate::cli::load::PathLoaderArgs;
6971
use crate::cli::parse::ParseArgs;
7072
use crate::cli::test::TestArgs;
7173

7274
#[derive(Subcommand)]
7375
pub enum Subcommands {
76+
Analyze(Analyze),
7477
Init(Init),
7578
Parse(Parse),
7679
Test(Test),
@@ -79,13 +82,30 @@ pub mod path_loading {
7982
impl Subcommands {
8083
pub fn run(&self) -> anyhow::Result<()> {
8184
match self {
85+
Self::Analyze(cmd) => cmd.run(),
8286
Self::Init(cmd) => cmd.run(),
8387
Self::Parse(cmd) => cmd.run(),
8488
Self::Test(cmd) => cmd.run(),
8589
}
8690
}
8791
}
8892

93+
/// Analyze command
94+
#[derive(clap::Parser)]
95+
pub struct Analyze {
96+
#[clap(flatten)]
97+
load_args: PathLoaderArgs,
98+
#[clap(flatten)]
99+
analyze_args: AnalyzeArgs,
100+
}
101+
102+
impl Analyze {
103+
pub fn run(&self) -> anyhow::Result<()> {
104+
let mut loader = self.load_args.get()?;
105+
self.analyze_args.run(&mut loader)
106+
}
107+
}
108+
89109
/// Init command
90110
#[derive(clap::Parser)]
91111
pub struct Init {
@@ -135,27 +155,45 @@ pub mod path_loading {
135155
pub mod provided_languages {
136156
use clap::Subcommand;
137157

158+
use crate::cli::analyze::AnalyzeArgs;
159+
use crate::cli::load::LanguageConfigurationsLoaderArgs;
138160
use crate::cli::parse::ParseArgs;
139161
use crate::cli::test::TestArgs;
140162
use crate::loader::LanguageConfiguration;
141163

142-
use super::load::LanguageConfigurationsLoaderArgs;
143-
144164
#[derive(Subcommand)]
145165
pub enum Subcommands {
166+
Analyze(Analyze),
146167
Parse(Parse),
147168
Test(Test),
148169
}
149170

150171
impl Subcommands {
151172
pub fn run(&self, configurations: Vec<LanguageConfiguration>) -> anyhow::Result<()> {
152173
match self {
174+
Self::Analyze(cmd) => cmd.run(configurations),
153175
Self::Parse(cmd) => cmd.run(configurations),
154176
Self::Test(cmd) => cmd.run(configurations),
155177
}
156178
}
157179
}
158180

181+
/// Analyze command
182+
#[derive(clap::Parser)]
183+
pub struct Analyze {
184+
#[clap(flatten)]
185+
load_args: LanguageConfigurationsLoaderArgs,
186+
#[clap(flatten)]
187+
analyze_args: AnalyzeArgs,
188+
}
189+
190+
impl Analyze {
191+
pub fn run(&self, configurations: Vec<LanguageConfiguration>) -> anyhow::Result<()> {
192+
let mut loader = self.load_args.get(configurations)?;
193+
self.analyze_args.run(&mut loader)
194+
}
195+
}
196+
159197
/// Parse command
160198
#[derive(clap::Parser)]
161199
pub struct Parse {
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// -*- coding: utf-8 -*-
2+
// ------------------------------------------------------------------------------------------------
3+
// Copyright © 2021, stack-graphs authors.
4+
// Licensed under either of Apache License, Version 2.0, or MIT license, at your option.
5+
// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details.
6+
// ------------------------------------------------------------------------------------------------
7+
8+
use anyhow::anyhow;
9+
use anyhow::Context as _;
10+
use clap::Args;
11+
use clap::ValueHint;
12+
use colored::Colorize as _;
13+
use stack_graphs::graph::StackGraph;
14+
use stack_graphs::partial::PartialPaths;
15+
use stack_graphs::stitching::Database;
16+
use std::collections::HashMap;
17+
use std::path::Path;
18+
use std::path::PathBuf;
19+
use tree_sitter_graph::Variables;
20+
use walkdir::WalkDir;
21+
22+
use crate::cli::util::map_parse_errors;
23+
use crate::cli::util::path_exists;
24+
use crate::loader::Loader;
25+
use crate::LoadError;
26+
use crate::NoCancellation;
27+
28+
/// Analyze sources
29+
#[derive(Args)]
30+
pub struct AnalyzeArgs {
31+
/// Source file or directory paths.
32+
#[clap(value_name = "SOURCE_PATH", required = true, value_hint = ValueHint::AnyPath, parse(from_os_str), validator_os = path_exists)]
33+
pub source_paths: Vec<PathBuf>,
34+
35+
#[clap(short = 'v')]
36+
pub verbose: bool,
37+
}
38+
39+
impl AnalyzeArgs {
40+
pub fn new(source_paths: Vec<PathBuf>) -> Self {
41+
Self {
42+
source_paths,
43+
verbose: false,
44+
}
45+
}
46+
47+
pub fn run(&self, loader: &mut Loader) -> anyhow::Result<()> {
48+
for source_path in &self.source_paths {
49+
if source_path.is_dir() {
50+
let source_root = source_path;
51+
for source_entry in WalkDir::new(source_root)
52+
.follow_links(true)
53+
.into_iter()
54+
.filter_map(|e| e.ok())
55+
.filter(|e| e.file_type().is_file())
56+
{
57+
let source_path = source_entry.path();
58+
self.run_with_context(source_root, source_path, loader)?;
59+
}
60+
} else {
61+
let source_root = source_path.parent().unwrap();
62+
self.run_with_context(source_root, source_path, loader)?;
63+
}
64+
}
65+
Ok(())
66+
}
67+
68+
/// Run test file and add error context to any failures that are returned.
69+
fn run_with_context(
70+
&self,
71+
source_root: &Path,
72+
source_path: &Path,
73+
loader: &mut Loader,
74+
) -> anyhow::Result<()> {
75+
self.analyze_file(source_root, source_path, loader)
76+
.with_context(|| format!("Error analyzing file {}", source_path.display()))
77+
}
78+
79+
/// Run test file.
80+
fn analyze_file(
81+
&self,
82+
source_root: &Path,
83+
source_path: &Path,
84+
loader: &mut Loader,
85+
) -> anyhow::Result<()> {
86+
if self.verbose {
87+
eprint!("{} ", source_path.display());
88+
}
89+
90+
let source = std::fs::read_to_string(source_path)?;
91+
let lc = match loader.load_for_file(source_path, Some(&source), &NoCancellation)? {
92+
Some(sgl) => sgl,
93+
None => {
94+
if self.verbose {
95+
eprintln!("{}", "⦵".dimmed());
96+
}
97+
return Ok(());
98+
}
99+
};
100+
101+
let mut graph = StackGraph::new();
102+
let file = graph
103+
.add_file(&source_path.to_string_lossy())
104+
.map_err(|_| anyhow!("Duplicate file {}", source_path.display()))?;
105+
106+
if self.verbose {
107+
eprint!("{} ", "🕸".yellow());
108+
}
109+
let relative_source_path = source_path.strip_prefix(source_root).unwrap();
110+
let result = if let Some(fa) = source_path
111+
.file_name()
112+
.and_then(|f| lc.special_files.get(&f.to_string_lossy()))
113+
{
114+
fa.build_stack_graph_into(
115+
&mut graph,
116+
file,
117+
&relative_source_path,
118+
&source,
119+
&mut std::iter::empty(),
120+
&HashMap::new(),
121+
&NoCancellation,
122+
)
123+
} else {
124+
let globals = Variables::new();
125+
lc.sgl
126+
.build_stack_graph_into(&mut graph, file, &source, &globals, &NoCancellation)
127+
};
128+
match result {
129+
Err(LoadError::ParseErrors(parse_errors)) => {
130+
let parse_error = map_parse_errors(source_path, &parse_errors, &source);
131+
if !self.verbose {
132+
eprint!("{}", source_path.display());
133+
}
134+
eprintln!("{}", "✗".red());
135+
eprintln!("{}", parse_error);
136+
return Ok(());
137+
}
138+
Err(e) => return Err(e.into()),
139+
Ok(_) => {}
140+
};
141+
142+
if self.verbose {
143+
eprint!("{} ", "🦶".yellow());
144+
}
145+
let mut partials = PartialPaths::new();
146+
let mut db = Database::new();
147+
partials.find_all_partial_paths_in_file(
148+
&graph,
149+
file,
150+
&stack_graphs::NoCancellation,
151+
|g, ps, p| {
152+
if p.is_complete_as_possible(g) {
153+
db.add_partial_path(g, ps, p);
154+
}
155+
},
156+
)?;
157+
158+
if self.verbose {
159+
eprintln!("{}", "✓".green());
160+
}
161+
Ok(())
162+
}
163+
}

0 commit comments

Comments
 (0)