-
Notifications
You must be signed in to change notification settings - Fork 2
Crate-level API changes for tree-sitter-lint #63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: run-context
Are you sure you want to change the base?
Changes from 12 commits
b2c06c0
551e376
3d4682c
422058d
2118d3c
54b39ed
0602f3c
b05884b
d341747
1930ccf
3c2a567
5fe38b3
5a3ede1
0ad0481
c0ae26e
c73c3da
d8f21b0
3a039da
debbbc0
30a1c71
c0bf4ca
2eba2c5
e471b0d
672ac41
c5f3fa2
c371ae9
53e844f
57495f8
31959d1
2061904
6daf28a
6bb81e1
325d3e0
355ea2b
2e842eb
3b1230c
c5a8c85
d250c7c
c5b2e88
7626af8
526d110
d93d175
db220fe
7543d9d
045b98d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,7 +23,7 @@ use crate::{ | |
|
|
||
| const ALL_NODES_QUERY: &str = "(_) @node"; | ||
|
|
||
| #[derive(Parser)] | ||
| #[derive(Clone, Parser)] | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know if the need to do |
||
| #[clap(group( | ||
| ArgGroup::new("query_or_filter") | ||
| .multiple(true) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,7 +32,8 @@ mod use_printer; | |
| mod use_searcher; | ||
|
|
||
| pub use args::Args; | ||
| use language::{BySupportedLanguage, SupportedLanguage}; | ||
| use language::BySupportedLanguage; | ||
| pub use language::SupportedLanguage; | ||
| pub use plugin::PluginInitializeReturn; | ||
| use query_context::QueryContext; | ||
| use treesitter::maybe_get_query; | ||
|
|
@@ -74,6 +75,8 @@ pub enum Error { | |
| FilterPluginExpectedArgument, | ||
| #[error("plugin couldn't parse argument {filter_arg:?}")] | ||
| FilterPluginCouldntParseArgument { filter_arg: String }, | ||
| #[error("language is required when passing a slice")] | ||
| LanguageMissingForSlice, | ||
| } | ||
|
|
||
| #[derive(Clone, Debug, Error)] | ||
|
|
@@ -290,9 +293,14 @@ pub fn run_print(args: Args) -> Result<RunStatus, Error> { | |
| ) | ||
| } | ||
|
|
||
| pub struct CaptureInfo<'node> { | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Naming? |
||
| pub node: Node<'node>, | ||
| pub pattern_index: usize, | ||
| } | ||
|
|
||
| pub fn run_with_callback( | ||
| args: Args, | ||
| callback: impl Fn(Node, &[u8], &Path) + Sync, | ||
| callback: impl Fn(CaptureInfo, &[u8], &Path) + Sync, | ||
| ) -> Result<RunStatus, Error> { | ||
| run_for_context( | ||
| args, | ||
|
|
@@ -307,8 +315,8 @@ pub fn run_with_callback( | |
| .search_path_callback::<_, io::Error>( | ||
| query_context, | ||
| path, | ||
| |node: Node, file_contents: &[u8], path: &Path| { | ||
| callback(node, file_contents, path); | ||
| |capture_info: CaptureInfo, file_contents: &[u8], path: &Path| { | ||
| callback(capture_info, file_contents, path); | ||
| matched.store(true, Ordering::SeqCst); | ||
| }, | ||
| ) | ||
|
|
@@ -412,6 +420,160 @@ fn run_for_context<TContext: Sync>( | |
| }) | ||
| } | ||
|
|
||
| pub fn run_for_slice_with_callback( | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This entry point is needed for the use case where |
||
| slice: &[u8], | ||
| args: Args, | ||
| mut callback: impl FnMut(CaptureInfo) + Sync, | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Vs the things that get passed to the callback in the |
||
| ) -> Result<RunStatus, Error> { | ||
| let language = args.language.ok_or(Error::LanguageMissingForSlice)?; | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Basically I started thinking that maybe the slice itself could/should be a field on But that might be weird because then you'd have to sort of assert that "for this entry point we expect |
||
| let query_text = args.get_loaded_query_text()?; | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't know if there are great ways to DRY this up more wrt other entry points, didn't worry about that too much at the moment |
||
| let filter = args.get_loaded_filter()?; | ||
| let cached_queries: CachedQueries = Default::default(); | ||
| let capture_index = CaptureIndex::default(); | ||
| let matched = AtomicBool::new(false); | ||
| let non_fatal_errors: Arc<Mutex<Vec<NonFatalError>>> = Default::default(); | ||
|
|
||
| let query = match cached_queries.get_and_cache_query_for_language(&query_text, language) { | ||
| Some(query) => query, | ||
| None => { | ||
| return Err(cached_queries | ||
| .error_if_no_successful_query_parsing() | ||
| .unwrap_err()) | ||
| } | ||
| }; | ||
| let capture_index = capture_index.get_or_init(&query, args.capture_name.as_deref())?; | ||
|
|
||
| let query_context = QueryContext::new(query, capture_index, language.language(), filter); | ||
|
|
||
| get_searcher(&args) | ||
| .borrow_mut() | ||
| .search_slice_callback_no_path(query_context, slice, |capture_info: CaptureInfo| { | ||
| callback(capture_info); | ||
| matched.store(true, Ordering::SeqCst); | ||
| }) | ||
| .unwrap(); | ||
|
|
||
| let non_fatal_errors = non_fatal_errors.lock().unwrap().clone(); | ||
| if non_fatal_errors.is_empty() { | ||
| cached_queries.error_if_no_successful_query_parsing()?; | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This may not make any sense/be redundant since above we already checked that "the query for the definitely-specified language is parseable" |
||
| } | ||
|
|
||
| Ok(RunStatus { | ||
| matched: matched.load(Ordering::SeqCst), | ||
| non_fatal_errors, | ||
| }) | ||
| } | ||
|
|
||
| pub fn run_with_per_file_callback( | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was running into wanting callbacks in So by adding an extra layer of "callback indirection" we can accomplish that and have the "per-match" callback be an |
||
| args: Args, | ||
| per_file_callback: impl Fn(&DirEntry, Box<dyn FnMut(Box<dyn FnMut(CaptureInfo, &[u8], &Path) + '_>) + '_>) | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know if there's a way to avoid |
||
| + Sync, | ||
| ) -> Result<RunStatus, Error> { | ||
| let query_text = args.get_loaded_query_text()?; | ||
| let filter = args.get_loaded_filter()?; | ||
| let cached_queries: CachedQueries = Default::default(); | ||
| let capture_index = CaptureIndex::default(); | ||
| let matched = AtomicBool::new(false); | ||
| let searched = AtomicBool::new(false); | ||
| let non_fatal_errors: Arc<Mutex<Vec<NonFatalError>>> = Default::default(); | ||
|
|
||
| for_each_project_file( | ||
| &args, | ||
| non_fatal_errors.clone(), | ||
| |project_file_dir_entry, matched_languages| { | ||
| searched.store(true, Ordering::SeqCst); | ||
| let language = match args.language { | ||
| Some(specified_language) => { | ||
| if !matched_languages.contains(&specified_language) { | ||
| return NonFatalError::ExplicitPathArgumentNotOfSpecifiedType { | ||
| path: project_file_dir_entry.path().to_owned(), | ||
| specified_language, | ||
| } | ||
| .into(); | ||
| } | ||
| specified_language | ||
| } | ||
| None => match matched_languages.len() { | ||
| 0 => { | ||
| return NonFatalError::ExplicitPathArgumentNotOfKnownType { | ||
| path: project_file_dir_entry.path().to_owned(), | ||
| } | ||
| .into(); | ||
| } | ||
| 1 => matched_languages[0], | ||
| _ => { | ||
| let successfully_parsed_query_languages = matched_languages | ||
| .iter() | ||
| .filter_map(|&matched_language| { | ||
| cached_queries | ||
| .get_and_cache_query_for_language(&query_text, matched_language) | ||
| .map(|_| matched_language) | ||
| }) | ||
| .collect::<Vec<_>>(); | ||
| match successfully_parsed_query_languages.len() { | ||
| 0 => { | ||
| return Ok(SingleFileSearchNonFailure::QueryNotParseableForFile); | ||
| } | ||
| 1 => successfully_parsed_query_languages[0], | ||
| _ => { | ||
| return NonFatalError::AmbiguousLanguageForFile { | ||
| path: project_file_dir_entry.path().to_owned(), | ||
| languages: successfully_parsed_query_languages, | ||
| } | ||
| .into(); | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| }; | ||
| let query = match cached_queries.get_and_cache_query_for_language(&query_text, language) | ||
| { | ||
| Some(query) => query, | ||
| None => return Ok(SingleFileSearchNonFailure::QueryNotParseableForFile), | ||
| }; | ||
| let capture_index = capture_index.get_or_init(&query, args.capture_name.as_deref())?; | ||
| let path = | ||
| format_relative_path(project_file_dir_entry.path(), args.is_using_default_paths()); | ||
|
|
||
| let query_context = | ||
| QueryContext::new(query, capture_index, language.language(), filter.clone()); | ||
|
|
||
| per_file_callback( | ||
| &project_file_dir_entry, | ||
| Box::new(|mut per_match_callback| { | ||
| get_searcher(&args) | ||
| .borrow_mut() | ||
| .search_path_callback::<_, io::Error>( | ||
| query_context.clone(), | ||
| path, | ||
| |capture_info: CaptureInfo, file_contents: &[u8], path: &Path| { | ||
| per_match_callback(capture_info, file_contents, path); | ||
| matched.store(true, Ordering::SeqCst); | ||
| }, | ||
| ) | ||
| .unwrap(); | ||
| }), | ||
| ); | ||
|
|
||
| Ok(SingleFileSearchNonFailure::RanQuery) | ||
| }, | ||
| )?; | ||
|
|
||
| let mut non_fatal_errors = non_fatal_errors.lock().unwrap().clone(); | ||
| if non_fatal_errors.is_empty() { | ||
| if !searched.load(Ordering::SeqCst) { | ||
| non_fatal_errors.push(NonFatalError::NothingSearched); | ||
| } else { | ||
| cached_queries.error_if_no_successful_query_parsing()?; | ||
| } | ||
| } | ||
|
|
||
| Ok(RunStatus { | ||
| matched: matched.load(Ordering::SeqCst), | ||
| non_fatal_errors, | ||
| }) | ||
| } | ||
|
|
||
| fn for_each_project_file( | ||
| args: &Args, | ||
| non_fatal_errors: Arc<Mutex<Vec<NonFatalError>>>, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per helixbass/tree-sitter-lint#4, updating this to allow publishing that crate while this hasn't "landed"