-
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 20 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 |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| [package] | ||
| name = "tree-sitter-grep" | ||
| name = "tree_sitter_lint_tree-sitter-grep" | ||
| version = "0.1.0" | ||
| edition = "2021" | ||
| license = "Unlicense OR MIT" | ||
|
|
@@ -8,6 +8,7 @@ authors = [ | |
| "Peter Stuart <peter@peterstuart.org>" | ||
| ] | ||
| description = """ | ||
| (not-yet-landed version used by tree-sitter-lint) | ||
| tree-sitter-grep is a grep-like search tool that | ||
| recursively searches the current directory for a | ||
| tree-sitter query pattern. Like ripgrep, it respects | ||
|
|
@@ -34,10 +35,13 @@ log = "0.4.5" | |
| memchr = "2.1" | ||
| memmap = { package = "memmap2", version = "0.5.3" } | ||
| once_cell = "1.18.0" | ||
| ouroboros = "0.17.2" | ||
| proc_macros = { package = "tree_sitter_grep_proc_macros", path = "proc_macros", version = "0.1.0" } | ||
| rayon = "1.7.0" | ||
| regex = "1.8.2" | ||
| ropey = "1.6.0" | ||
| serde = { version = "1.0.77", features = ["derive"] } | ||
| streaming-iterator = "0.1.9" | ||
| strum_macros = "0.25.1" | ||
| termcolor = "1.2.0" | ||
| thiserror = "1.0.43" | ||
|
|
@@ -65,6 +69,9 @@ tree-sitter-swift = "0.3.6" | |
| tree-sitter-toml = "0.20.0" | ||
| tree-sitter-typescript = "0.20.2" | ||
|
|
||
| [patch.crates-io] | ||
| tree-sitter = { git = "https://github.com/helixbass/tree-sitter", rev = "57e98fb0" } | ||
|
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. Read about how to do this "force dependencies to use this version" The thing that I guess I wasn't anticipating is that I also had to do this in any "outer" crates ( |
||
|
|
||
| [[bin]] | ||
| name = "tree-sitter-grep" | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,4 +3,5 @@ format_macro_bodies = true | |
| format_macro_matchers = true | ||
| group_imports = "StdExternalCrate" | ||
| imports_granularity = "Crate" | ||
| wrap_comments = true | ||
| edition = "2021" | ||
| # wrap_comments = true | ||
|
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. The thing of |
||
| 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 |
|---|---|---|
|
|
@@ -13,7 +13,7 @@ use ignore::DirEntry; | |
| use rayon::prelude::*; | ||
| use termcolor::{BufferWriter, ColorChoice}; | ||
| use thiserror::Error; | ||
| use tree_sitter::{Node, Query, QueryError}; | ||
| use tree_sitter::{Query, QueryError, Tree}; | ||
|
|
||
| mod args; | ||
| mod language; | ||
|
|
@@ -32,13 +32,21 @@ 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; | ||
| pub use treesitter::{ | ||
| get_captures, get_captures_for_enclosing_node, CaptureInfo, Parseable, RopeOrSlice, | ||
| }; | ||
| use use_printer::get_printer; | ||
| use use_searcher::get_searcher; | ||
|
|
||
| pub extern crate ropey; | ||
| pub extern crate streaming_iterator; | ||
| pub extern crate tree_sitter; | ||
|
|
||
| #[derive(Debug, Error)] | ||
| pub enum Error { | ||
| #[error("couldn't read query file {path_to_query_file:?}")] | ||
|
|
@@ -74,6 +82,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)] | ||
|
|
@@ -292,7 +302,7 @@ pub fn run_print(args: Args) -> Result<RunStatus, Error> { | |
|
|
||
| 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 +317,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 +422,162 @@ fn run_for_context<TContext: Sync>( | |
| }) | ||
| } | ||
|
|
||
| pub fn run_for_slice_with_callback<'a>( | ||
| slice: impl Into<RopeOrSlice<'a>>, | ||
|
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. So I think the idea here will be that the But that is blocked by running into lifetime issues at the point of calling into (although as discussed maybe we could reasonably temporarily depend on a forked version of So for now made it take the "concrete" type of (the point of this is to try and be more efficient where in |
||
| tree: Option<&Tree>, | ||
|
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. Rather than eg a separate "entry point" for when you have the pre-parsed |
||
| args: Args, | ||
| mut callback: impl FnMut(&CaptureInfo) + Sync, | ||
| ) -> Result<RunStatus, Error> { | ||
| let slice = slice.into(); | ||
| 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, tree, |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) + '_>) + '_>) | ||
| + 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"