@@ -16,31 +16,52 @@ use stack_graphs::stitching::Database;
1616use std:: collections:: HashMap ;
1717use std:: path:: Path ;
1818use std:: path:: PathBuf ;
19+ use std:: sync:: Arc ;
20+ use std:: time:: Duration ;
1921use tree_sitter_graph:: Variables ;
2022use walkdir:: WalkDir ;
2123
24+ use crate :: cli:: util:: duration_from_seconds_str;
2225use crate :: cli:: util:: map_parse_errors;
2326use crate :: cli:: util:: path_exists;
2427use crate :: loader:: Loader ;
28+ use crate :: CancelAfterDuration ;
29+ use crate :: CancellationFlag ;
2530use crate :: LoadError ;
2631use crate :: NoCancellation ;
2732
2833/// Analyze sources
2934#[ derive( Args ) ]
3035pub struct AnalyzeArgs {
3136 /// 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) ]
37+ #[ clap(
38+ value_name = "SOURCE_PATH" ,
39+ required = true ,
40+ value_hint = ValueHint :: AnyPath ,
41+ parse( from_os_str) ,
42+ validator_os = path_exists,
43+ ) ]
3344 pub source_paths : Vec < PathBuf > ,
3445
35- #[ clap( short = 'v' ) ]
46+ #[ clap( long , short = 'v' ) ]
3647 pub verbose : bool ,
48+
49+ /// Maximum runtime per file in seconds.
50+ #[ clap(
51+ long,
52+ value_name = "SECONDS" ,
53+ parse( try_from_str = duration_from_seconds_str) ,
54+ require_equals = true ,
55+ ) ]
56+ pub max_file_time : Option < Duration > ,
3757}
3858
3959impl AnalyzeArgs {
4060 pub fn new ( source_paths : Vec < PathBuf > ) -> Self {
4161 Self {
4262 source_paths,
4363 verbose : false ,
64+ max_file_time : None ,
4465 }
4566 }
4667
@@ -76,36 +97,41 @@ impl AnalyzeArgs {
7697 . with_context ( || format ! ( "Error analyzing file {}" , source_path. display( ) ) )
7798 }
7899
79- /// Run test file.
80100 fn analyze_file (
81101 & self ,
82102 source_root : & Path ,
83103 source_path : & Path ,
84104 loader : & mut Loader ,
85105 ) -> anyhow:: Result < ( ) > {
86106 if self . verbose {
87- eprint ! ( "{} " , source_path. display( ) ) ;
107+ eprint ! ( "{}: " , source_path. display( ) ) ;
108+ }
109+
110+ let mut cancellation_flag: Arc < dyn CancellationFlag > = Arc :: new ( NoCancellation ) ;
111+ if let Some ( max_file_time) = self . max_file_time {
112+ cancellation_flag = CancelAfterDuration :: new ( max_file_time) ;
88113 }
89114
90115 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( ) ) ;
116+ let lc = match loader. load_for_file ( source_path, Some ( & source) , cancellation_flag. as_ref ( ) )
117+ {
118+ Ok ( Some ( sgl) ) => sgl,
119+ Ok ( None ) => return Ok ( ( ) ) ,
120+ Err ( crate :: loader:: LoadError :: Cancelled ( _) ) => {
121+ if !self . verbose {
122+ eprint ! ( "{}: " , source_path. display( ) ) ;
96123 }
124+ eprintln ! ( "{}" , "language loading timed out" . yellow( ) ) ;
97125 return Ok ( ( ) ) ;
98126 }
127+ Err ( e) => return Err ( e. into ( ) ) ,
99128 } ;
100129
101130 let mut graph = StackGraph :: new ( ) ;
102131 let file = graph
103132 . add_file ( & source_path. to_string_lossy ( ) )
104133 . map_err ( |_| anyhow ! ( "Duplicate file {}" , source_path. display( ) ) ) ?;
105134
106- if self . verbose {
107- eprint ! ( "{} " , "🕸" . yellow( ) ) ;
108- }
109135 let relative_source_path = source_path. strip_prefix ( source_root) . unwrap ( ) ;
110136 let result = if let Some ( fa) = source_path
111137 . file_name ( )
@@ -118,45 +144,63 @@ impl AnalyzeArgs {
118144 & source,
119145 & mut std:: iter:: empty ( ) ,
120146 & HashMap :: new ( ) ,
121- & NoCancellation ,
147+ cancellation_flag . as_ref ( ) ,
122148 )
123149 } else {
124150 let globals = Variables :: new ( ) ;
125- lc. sgl
126- . build_stack_graph_into ( & mut graph, file, & source, & globals, & NoCancellation )
151+ lc. sgl . build_stack_graph_into (
152+ & mut graph,
153+ file,
154+ & source,
155+ & globals,
156+ cancellation_flag. as_ref ( ) ,
157+ )
127158 } ;
128159 match result {
129160 Err ( LoadError :: ParseErrors ( parse_errors) ) => {
130- let parse_error = map_parse_errors ( source_path, & parse_errors, & source) ;
161+ let parse_error = map_parse_errors ( source_path, & parse_errors, & source, "" ) ;
131162 if !self . verbose {
132- eprint ! ( "{}" , source_path. display( ) ) ;
163+ eprint ! ( "{}: " , source_path. display( ) ) ;
133164 }
134- eprintln ! ( "{}" , "✗ " . red( ) ) ;
165+ eprintln ! ( "{}" , "parsing failed " . red( ) ) ;
135166 eprintln ! ( "{}" , parse_error) ;
136167 return Ok ( ( ) ) ;
137168 }
169+ Err ( LoadError :: Cancelled ( _) ) => {
170+ if !self . verbose {
171+ eprint ! ( "{}: " , source_path. display( ) ) ;
172+ }
173+ eprintln ! ( "{}" , "parsing timed out" . yellow( ) ) ;
174+ return Ok ( ( ) ) ;
175+ }
138176 Err ( e) => return Err ( e. into ( ) ) ,
139177 Ok ( _) => { }
140178 } ;
141179
142- if self . verbose {
143- eprint ! ( "{} " , "🦶" . yellow( ) ) ;
144- }
145180 let mut partials = PartialPaths :: new ( ) ;
146181 let mut db = Database :: new ( ) ;
147- partials. find_all_partial_paths_in_file (
182+ match partials. find_all_partial_paths_in_file (
148183 & graph,
149184 file,
150- & stack_graphs :: NoCancellation ,
185+ & cancellation_flag . as_ref ( ) ,
151186 |g, ps, p| {
152187 if p. is_complete_as_possible ( g) {
153188 db. add_partial_path ( g, ps, p) ;
154189 }
155190 } ,
156- ) ?;
191+ ) {
192+ Ok ( _) => { }
193+ Err ( _) => {
194+ if !self . verbose {
195+ eprint ! ( "{}: " , source_path. display( ) ) ;
196+ }
197+ eprintln ! ( "{}" , "path computation timed out" . yellow( ) ) ;
198+ return Ok ( ( ) ) ;
199+ }
200+ }
157201
158202 if self . verbose {
159- eprintln ! ( "{}" , "✓ " . green( ) ) ;
203+ eprintln ! ( "{}" , "success " . green( ) ) ;
160204 }
161205 Ok ( ( ) )
162206 }
0 commit comments