diff --git a/src/treesitter-stamp/download-treesitter.cmake b/src/treesitter-stamp/download-treesitter.cmake index dea47137a..385a9916d 100644 --- a/src/treesitter-stamp/download-treesitter.cmake +++ b/src/treesitter-stamp/download-treesitter.cmake @@ -22,16 +22,16 @@ function(check_file_hash has_hash hash_is_good) set("${has_hash}" TRUE PARENT_SCOPE) message(VERBOSE "verifying file... - file='/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.3.tar.gz'") + file='/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.4.tar.gz'") - file("SHA256" "/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.3.tar.gz" actual_value) + file("SHA256" "/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.4.tar.gz" actual_value) - if(NOT "${actual_value}" STREQUAL "0a8d0cf8e09caba22ed0d8439f7fa1e3d8453800038e43ccad1f34ef29537da1") + if(NOT "${actual_value}" STREQUAL "d704832a6bfaac8b3cbca3b5d773cad613183ba8c04166638af2c6e5dfb9e2d2") set("${hash_is_good}" FALSE PARENT_SCOPE) message(VERBOSE "SHA256 hash of - /home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.3.tar.gz + /home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.4.tar.gz does not match expected value - expected: '0a8d0cf8e09caba22ed0d8439f7fa1e3d8453800038e43ccad1f34ef29537da1' + expected: 'd704832a6bfaac8b3cbca3b5d773cad613183ba8c04166638af2c6e5dfb9e2d2' actual: '${actual_value}'") else() set("${hash_is_good}" TRUE PARENT_SCOPE) @@ -71,32 +71,32 @@ function(sleep_before_download attempt) execute_process(COMMAND "${CMAKE_COMMAND}" -E sleep "${sleep_seconds}") endfunction() -if(EXISTS "/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.3.tar.gz") +if(EXISTS "/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.4.tar.gz") check_file_hash(has_hash hash_is_good) if(has_hash) if(hash_is_good) message(VERBOSE "File already exists and hash match (skip download): - file='/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.3.tar.gz' - SHA256='0a8d0cf8e09caba22ed0d8439f7fa1e3d8453800038e43ccad1f34ef29537da1'" + file='/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.4.tar.gz' + SHA256='d704832a6bfaac8b3cbca3b5d773cad613183ba8c04166638af2c6e5dfb9e2d2'" ) return() else() message(VERBOSE "File already exists but hash mismatch. Removing...") - file(REMOVE "/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.3.tar.gz") + file(REMOVE "/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.4.tar.gz") endif() else() message(VERBOSE "File already exists but no hash specified (use URL_HASH): - file='/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.3.tar.gz' + file='/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.4.tar.gz' Old file will be removed and new file downloaded from URL." ) - file(REMOVE "/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.3.tar.gz") + file(REMOVE "/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.4.tar.gz") endif() endif() set(retry_number 5) message(VERBOSE "Downloading... - dst='/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.3.tar.gz' + dst='/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.4.tar.gz' timeout='none' inactivity timeout='none'" ) @@ -107,7 +107,7 @@ foreach(i RANGE ${retry_number}) if(status_code IN_LIST download_retry_codes) sleep_before_download(${i}) endif() - foreach(url IN ITEMS [====[https://github.com/tree-sitter/tree-sitter/archive/v0.24.3.tar.gz]====]) + foreach(url IN ITEMS [====[https://github.com/tree-sitter/tree-sitter/archive/v0.24.4.tar.gz]====]) if(NOT url IN_LIST skip_url_list) message(VERBOSE "Using src='${url}'") @@ -119,7 +119,7 @@ foreach(i RANGE ${retry_number}) file( DOWNLOAD - "${url}" "/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.3.tar.gz" + "${url}" "/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.4.tar.gz" # no TIMEOUT # no INACTIVITY_TIMEOUT @@ -136,7 +136,7 @@ foreach(i RANGE ${retry_number}) check_file_hash(has_hash hash_is_good) if(has_hash AND NOT hash_is_good) message(VERBOSE "Hash mismatch, removing...") - file(REMOVE "/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.3.tar.gz") + file(REMOVE "/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.4.tar.gz") else() message(VERBOSE "Downloading... done") return() diff --git a/src/treesitter-stamp/extract-treesitter.cmake b/src/treesitter-stamp/extract-treesitter.cmake index 8a9621950..98e06067e 100644 --- a/src/treesitter-stamp/extract-treesitter.cmake +++ b/src/treesitter-stamp/extract-treesitter.cmake @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.5) # Make file names absolute: # -get_filename_component(filename "/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.3.tar.gz" ABSOLUTE) +get_filename_component(filename "/home/runner/work/deps/deps/neovim/deps/build/downloads/treesitter/v0.24.4.tar.gz" ABSOLUTE) get_filename_component(directory "/home/runner/work/deps/deps/neovim/deps/build/src/treesitter" ABSOLUTE) message(VERBOSE "extracting... diff --git a/src/treesitter-stamp/treesitter-urlinfo.txt b/src/treesitter-stamp/treesitter-urlinfo.txt index cfb442c2e..2f3e6bc0a 100644 --- a/src/treesitter-stamp/treesitter-urlinfo.txt +++ b/src/treesitter-stamp/treesitter-urlinfo.txt @@ -6,7 +6,7 @@ method=url command=/usr/local/bin/cmake;-DCMAKE_MESSAGE_LOG_LEVEL=VERBOSE;-P;/home/runner/work/deps/deps/neovim/deps/build/src/treesitter-stamp/download-treesitter.cmake;COMMAND;/usr/local/bin/cmake;-DCMAKE_MESSAGE_LOG_LEVEL=VERBOSE;-P;/home/runner/work/deps/deps/neovim/deps/build/src/treesitter-stamp/verify-treesitter.cmake;COMMAND;/usr/local/bin/cmake;-DCMAKE_MESSAGE_LOG_LEVEL=VERBOSE;-P;/home/runner/work/deps/deps/neovim/deps/build/src/treesitter-stamp/extract-treesitter.cmake source_dir=/home/runner/work/deps/deps/neovim/deps/build/src/treesitter work_dir=/home/runner/work/deps/deps/neovim/deps/build/src -url(s)=https://github.com/tree-sitter/tree-sitter/archive/v0.24.3.tar.gz -hash=SHA256=0a8d0cf8e09caba22ed0d8439f7fa1e3d8453800038e43ccad1f34ef29537da1 +url(s)=https://github.com/tree-sitter/tree-sitter/archive/v0.24.4.tar.gz +hash=SHA256=d704832a6bfaac8b3cbca3b5d773cad613183ba8c04166638af2c6e5dfb9e2d2 no_extract= diff --git a/src/treesitter/Cargo.lock b/src/treesitter/Cargo.lock index 0b8fd339f..9c280e66d 100644 --- a/src/treesitter/Cargo.lock +++ b/src/treesitter/Cargo.lock @@ -1538,7 +1538,7 @@ dependencies = [ [[package]] name = "tree-sitter" -version = "0.24.3" +version = "0.24.4" dependencies = [ "bindgen", "cc", @@ -1551,7 +1551,7 @@ dependencies = [ [[package]] name = "tree-sitter-cli" -version = "0.24.3" +version = "0.24.4" dependencies = [ "anstyle", "anyhow", @@ -1601,7 +1601,7 @@ dependencies = [ [[package]] name = "tree-sitter-config" -version = "0.24.3" +version = "0.24.4" dependencies = [ "anyhow", "dirs", @@ -1611,7 +1611,7 @@ dependencies = [ [[package]] name = "tree-sitter-generate" -version = "0.24.3" +version = "0.24.4" dependencies = [ "anyhow", "heck 0.5.0", @@ -1632,7 +1632,7 @@ dependencies = [ [[package]] name = "tree-sitter-highlight" -version = "0.24.3" +version = "0.24.4" dependencies = [ "lazy_static", "regex", @@ -1647,13 +1647,14 @@ version = "0.1.2" [[package]] name = "tree-sitter-loader" -version = "0.24.3" +version = "0.24.4" dependencies = [ "anyhow", "cc", "dirs", "fs4", "indoc", + "lazy_static", "libloading", "once_cell", "path-slash", @@ -1670,7 +1671,7 @@ dependencies = [ [[package]] name = "tree-sitter-tags" -version = "0.24.3" +version = "0.24.4" dependencies = [ "memchr", "regex", diff --git a/src/treesitter/Cargo.toml b/src/treesitter/Cargo.toml index e9140f2a7..82f99e991 100644 --- a/src/treesitter/Cargo.toml +++ b/src/treesitter/Cargo.toml @@ -13,7 +13,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.24.3" +version = "0.24.4" authors = ["Max Brunsfeld "] edition = "2021" rust-version = "1.74.1" @@ -96,9 +96,9 @@ walkdir = "2.5.0" wasmparser = "0.217.0" webbrowser = "1.0.2" -tree-sitter = { version = "0.24.3", path = "./lib" } -tree-sitter-generate = { version = "0.24.3", path = "./cli/generate" } -tree-sitter-loader = { version = "0.24.3", path = "./cli/loader" } -tree-sitter-config = { version = "0.24.3", path = "./cli/config" } -tree-sitter-highlight = { version = "0.24.3", path = "./highlight" } -tree-sitter-tags = { version = "0.24.3", path = "./tags" } +tree-sitter = { version = "0.24.4", path = "./lib" } +tree-sitter-generate = { version = "0.24.4", path = "./cli/generate" } +tree-sitter-loader = { version = "0.24.4", path = "./cli/loader" } +tree-sitter-config = { version = "0.24.4", path = "./cli/config" } +tree-sitter-highlight = { version = "0.24.4", path = "./highlight" } +tree-sitter-tags = { version = "0.24.4", path = "./tags" } diff --git a/src/treesitter/Makefile b/src/treesitter/Makefile index 92cd83953..0de508d7e 100644 --- a/src/treesitter/Makefile +++ b/src/treesitter/Makefile @@ -2,7 +2,7 @@ ifeq ($(OS),Windows_NT) $(error Windows is not supported) endif -VERSION := 0.24.3 +VERSION := 0.24.4 DESCRIPTION := An incremental parsing system for programming tools HOMEPAGE_URL := https://tree-sitter.github.io/tree-sitter/ diff --git a/src/treesitter/build.zig.zon b/src/treesitter/build.zig.zon index f9f9798c2..f1c2f7f42 100644 --- a/src/treesitter/build.zig.zon +++ b/src/treesitter/build.zig.zon @@ -1,6 +1,6 @@ .{ .name = "tree-sitter", - .version = "0.24.3", + .version = "0.24.4", .paths = .{ "build.zig", "build.zig.zon", diff --git a/src/treesitter/cli/generate/src/build_tables/minimize_parse_table.rs b/src/treesitter/cli/generate/src/build_tables/minimize_parse_table.rs index 74f708699..70950dffa 100644 --- a/src/treesitter/cli/generate/src/build_tables/minimize_parse_table.rs +++ b/src/treesitter/cli/generate/src/build_tables/minimize_parse_table.rs @@ -70,18 +70,17 @@ impl<'a> Minimizer<'a> { production_id: 0, symbol, .. - } => { - if !self.simple_aliases.contains_key(symbol) - && !self.syntax_grammar.supertype_symbols.contains(symbol) - && !aliased_symbols.contains(symbol) - && self.syntax_grammar.variables[symbol.index].kind - != VariableType::Named - && (unit_reduction_symbol.is_none() - || unit_reduction_symbol == Some(symbol)) - { - unit_reduction_symbol = Some(symbol); - continue; - } + } if !self.simple_aliases.contains_key(symbol) + && !self.syntax_grammar.supertype_symbols.contains(symbol) + && !self.syntax_grammar.extra_symbols.contains(symbol) + && !aliased_symbols.contains(symbol) + && self.syntax_grammar.variables[symbol.index].kind + != VariableType::Named + && (unit_reduction_symbol.is_none() + || unit_reduction_symbol == Some(symbol)) => + { + unit_reduction_symbol = Some(symbol); + continue; } _ => {} } diff --git a/src/treesitter/cli/generate/src/prepare_grammar/intern_symbols.rs b/src/treesitter/cli/generate/src/prepare_grammar/intern_symbols.rs index 0941676d7..02845f35d 100644 --- a/src/treesitter/cli/generate/src/prepare_grammar/intern_symbols.rs +++ b/src/treesitter/cli/generate/src/prepare_grammar/intern_symbols.rs @@ -149,7 +149,7 @@ impl<'a> Interner<'a> { fn check_single(&self, elements: &[Rule], name: Option<&str>) { if elements.len() == 1 && matches!(elements[0], Rule::String(_) | Rule::Pattern(_, _)) { eprintln!( - "Warning: rule {} is just a `seq` or `choice` rule with a single element. This is unnecessary.", + "Warning: rule {} contains a `seq` or `choice` rule with a single element. This is unnecessary.", name.unwrap_or_default() ); } diff --git a/src/treesitter/cli/loader/Cargo.toml b/src/treesitter/cli/loader/Cargo.toml index ec2b35d14..65025127a 100644 --- a/src/treesitter/cli/loader/Cargo.toml +++ b/src/treesitter/cli/loader/Cargo.toml @@ -24,6 +24,7 @@ cc.workspace = true dirs.workspace = true fs4.workspace = true indoc.workspace = true +lazy_static.workspace = true libloading.workspace = true once_cell.workspace = true path-slash.workspace = true diff --git a/src/treesitter/cli/loader/src/lib.rs b/src/treesitter/cli/loader/src/lib.rs index 1b8083c08..13b4fabf3 100644 --- a/src/treesitter/cli/loader/src/lib.rs +++ b/src/treesitter/cli/loader/src/lib.rs @@ -21,6 +21,7 @@ use anyhow::Error; use anyhow::{anyhow, Context, Result}; use fs4::fs_std::FileExt; use indoc::indoc; +use lazy_static::lazy_static; use libloading::{Library, Symbol}; use once_cell::unsync::OnceCell; use path_slash::PathBufExt as _; @@ -38,6 +39,10 @@ use tree_sitter_highlight::HighlightConfiguration; use tree_sitter_tags::{Error as TagsError, TagsConfiguration}; use url::Url; +lazy_static! { + static ref GRAMMAR_NAME_REGEX: Regex = Regex::new(r#""name":\s*"(.*?)""#).unwrap(); +} + pub const EMSCRIPTEN_TAG: &str = concat!("docker.io/emscripten/emsdk:", env!("EMSCRIPTEN_VERSION")); #[derive(Default, Deserialize, Serialize)] @@ -141,12 +146,10 @@ pub struct TreeSitterJSON { } impl TreeSitterJSON { - pub fn from_file(path: &Path) -> Option { - if let Ok(file) = fs::File::open(path.join("tree-sitter.json")) { - Some(serde_json::from_reader(file).ok()?) - } else { - None - } + pub fn from_file(path: &Path) -> Result { + Ok(serde_json::from_str(&fs::read_to_string( + path.join("tree-sitter.json"), + )?)?) } pub fn has_multiple_language_configs(&self) -> bool { @@ -161,7 +164,8 @@ pub struct Grammar { #[serde(skip_serializing_if = "Option::is_none")] pub camelcase: Option, pub scope: String, - pub path: PathBuf, + #[serde(skip_serializing_if = "Option::is_none")] + pub path: Option, #[serde(default, skip_serializing_if = "PathsJSON::is_empty")] pub external_files: PathsJSON, pub file_types: Option>, @@ -192,7 +196,6 @@ pub struct Metadata { pub authors: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub links: Option, - // #[serde(skip_serializing_if = "Option::is_none")] #[serde(skip)] pub namespace: Option, } @@ -600,6 +603,13 @@ impl Loader { } } + pub fn language_for_configuration( + &self, + configuration: &LanguageConfiguration, + ) -> Result { + self.language_for_id(configuration.language_id) + } + fn language_for_id(&self, id: usize) -> Result { let (path, language, externals) = &self.languages_by_id[id]; language @@ -628,27 +638,7 @@ impl Loader { pub fn load_language_at_path(&self, mut config: CompileConfig) -> Result { let grammar_path = config.src_path.join("grammar.json"); - - #[derive(Deserialize)] - struct GrammarJSON { - name: String, - } - let mut grammar_file = fs::File::open(&grammar_path).with_context(|| { - format!( - "Failed to read grammar.json file at the following path:\n{:?}", - &grammar_path - ) - })?; - let grammar_json: GrammarJSON = serde_json::from_reader(BufReader::new(&mut grammar_file)) - .with_context(|| { - format!( - "Failed to parse grammar.json file at the following path:\n{:?}", - &grammar_path - ) - })?; - - config.name = grammar_json.name; - + config.name = Self::grammar_json_name(&grammar_path)?; self.load_language_at_path_with_name(config) } @@ -1125,27 +1115,16 @@ impl Loader { parser_path: &Path, set_current_path_config: bool, ) -> Result<&[LanguageConfiguration]> { - #[derive(Deserialize)] - struct GrammarJSON { - name: String, - } - let initial_language_configuration_count = self.language_configurations.len(); - if let Some(config) = TreeSitterJSON::from_file(parser_path) { + let ts_json = TreeSitterJSON::from_file(parser_path); + if let Ok(config) = ts_json { let language_count = self.languages_by_id.len(); for grammar in config.grammars { // Determine the path to the parser directory. This can be specified in - // the package.json, but defaults to the directory containing the - // package.json. - let language_path = parser_path.join(grammar.path); - - let grammar_path = language_path.join("src").join("grammar.json"); - let mut grammar_file = - fs::File::open(grammar_path).with_context(|| "Failed to read grammar.json")?; - let grammar_json: GrammarJSON = - serde_json::from_reader(BufReader::new(&mut grammar_file)) - .with_context(|| "Failed to parse grammar.json")?; + // the tree-sitter.json, but defaults to the directory containing the + // tree-sitter.json. + let language_path = parser_path.join(grammar.path.unwrap_or(PathBuf::from("."))); // Determine if a previous language configuration in this package.json file // already uses the same language. @@ -1184,7 +1163,7 @@ impl Loader { let configuration = LanguageConfiguration { root_path: parser_path.to_path_buf(), - language_name: grammar_json.name, + language_name: grammar.name, scope: Some(grammar.scope), language_id, file_types: grammar.file_types.unwrap_or_default(), @@ -1230,20 +1209,30 @@ impl Loader { Some(self.language_configurations.len() - 1); } } + } else if let Err(e) = ts_json { + match e.downcast_ref::() { + // This is noisy, and not really an issue. + Some(e) if e.kind() == std::io::ErrorKind::NotFound => {} + _ => { + eprintln!( + "Warning: Failed to parse {} -- {e}", + parser_path.join("tree-sitter.json").display() + ); + } + } } + // If we didn't find any language configurations in the tree-sitter.json file, + // but there is a grammar.json file, then use the grammar file to form a simple + // language configuration. if self.language_configurations.len() == initial_language_configuration_count && parser_path.join("src").join("grammar.json").exists() { let grammar_path = parser_path.join("src").join("grammar.json"); - let mut grammar_file = - fs::File::open(grammar_path).with_context(|| "Failed to read grammar.json")?; - let grammar_json: GrammarJSON = - serde_json::from_reader(BufReader::new(&mut grammar_file)) - .with_context(|| "Failed to parse grammar.json")?; + let language_name = Self::grammar_json_name(&grammar_path)?; let configuration = LanguageConfiguration { root_path: parser_path.to_owned(), - language_name: grammar_json.name, + language_name, language_id: self.languages_by_id.len(), file_types: Vec::new(), scope: None, @@ -1279,6 +1268,36 @@ impl Loader { pattern.and_then(|r| RegexBuilder::new(r).multi_line(true).build().ok()) } + fn grammar_json_name(grammar_path: &Path) -> Result { + let file = fs::File::open(grammar_path).with_context(|| { + format!("Failed to open grammar.json at {}", grammar_path.display()) + })?; + + let first_three_lines = BufReader::new(file) + .lines() + .take(3) + .collect::, _>>() + .with_context(|| { + format!( + "Failed to read the first three lines of grammar.json at {}", + grammar_path.display() + ) + })? + .join("\n"); + + let name = GRAMMAR_NAME_REGEX + .captures(&first_three_lines) + .and_then(|c| c.get(1)) + .ok_or_else(|| { + anyhow!( + "Failed to parse the language name from grammar.json at {}", + grammar_path.display() + ) + })?; + + Ok(name.as_str().to_string()) + } + pub fn select_language( &mut self, path: &Path, diff --git a/src/treesitter/cli/npm/package.json b/src/treesitter/cli/npm/package.json index 8e7c600bb..a884529f8 100644 --- a/src/treesitter/cli/npm/package.json +++ b/src/treesitter/cli/npm/package.json @@ -1,6 +1,6 @@ { "name": "tree-sitter-cli", - "version": "0.24.3", + "version": "0.24.4", "author": "Max Brunsfeld", "license": "MIT", "repository": { diff --git a/src/treesitter/cli/src/init.rs b/src/treesitter/cli/src/init.rs index fc661aabd..d9d0fb3e3 100644 --- a/src/treesitter/cli/src/init.rs +++ b/src/treesitter/cli/src/init.rs @@ -1,6 +1,5 @@ use std::{ - fs::{self, File}, - io::BufReader, + fs, path::{Path, PathBuf}, str::{self, FromStr}, }; @@ -76,7 +75,7 @@ const BINDING_GYP_TEMPLATE: &str = include_str!("./templates/binding.gyp"); const BINDING_TEST_JS_TEMPLATE: &str = include_str!("./templates/binding_test.js"); const MAKEFILE_TEMPLATE: &str = include_str!("./templates/makefile"); -const CMAKELISTS_TXT_TEMPLATE: &str = include_str!("./templates/cmakelists.txt"); +const CMAKELISTS_TXT_TEMPLATE: &str = include_str!("./templates/cmakelists.cmake"); const PARSER_NAME_H_TEMPLATE: &str = include_str!("./templates/PARSER_NAME.h"); const PARSER_NAME_PC_IN_TEMPLATE: &str = include_str!("./templates/PARSER_NAME.pc.in"); @@ -136,9 +135,9 @@ impl JsonConfigOpts { name: self.name.clone(), camelcase: Some(self.camelcase), scope: self.scope, - path: PathBuf::from("."), + path: None, external_files: PathsJSON::Empty, - file_types: None, + file_types: Some(self.file_types), highlights: PathsJSON::Empty, injections: PathsJSON::Empty, locals: PathsJSON::Empty, @@ -154,7 +153,7 @@ impl JsonConfigOpts { authors: Some(vec![Author { name: self.author, email: self.email, - url: None, + url: self.url.map(|url| url.to_string()), }]), links: Some(Links { repository: self.repository.unwrap_or_else(|| { @@ -199,6 +198,7 @@ struct GenerateOpts<'a> { description: Option<&'a str>, repository: Option<&'a str>, version: &'a Version, + camel_parser_name: &'a str, } // TODO: remove in 0.25 @@ -211,9 +211,9 @@ pub fn migrate_package_json(repo_path: &Path) -> Result { root_path.join("tree-sitter.json"), ); - let old_config = serde_json::from_reader::<_, PackageJSON>( - File::open(&package_json_path) - .with_context(|| format!("Failed to open package.json in {}", root_path.display()))?, + let old_config = serde_json::from_str::( + &fs::read_to_string(&package_json_path) + .with_context(|| format!("Failed to read package.json in {}", root_path.display()))?, )?; if old_config.tree_sitter.is_none() { @@ -232,7 +232,7 @@ pub fn migrate_package_json(repo_path: &Path) -> Result { name: name.clone(), camelcase: Some(name.to_upper_camel_case()), scope: l.scope.unwrap_or_else(|| format!("source.{name}")), - path: l.path, + path: Some(l.path), external_files: l.external_files, file_types: l.file_types, highlights: l.highlights, @@ -335,19 +335,19 @@ pub fn migrate_package_json(repo_path: &Path) -> Result { write_file( &tree_sitter_json_path, - serde_json::to_string_pretty(&new_config)?, + serde_json::to_string_pretty(&new_config)? + "\n", )?; // Remove the `tree-sitter` field in-place - let mut package_json = serde_json::from_reader::<_, Map>( - File::open(&package_json_path) - .with_context(|| format!("Failed to open package.json in {}", root_path.display()))?, + let mut package_json = serde_json::from_str::>( + &fs::read_to_string(&package_json_path) + .with_context(|| format!("Failed to read package.json in {}", root_path.display()))?, ) .unwrap(); package_json.remove("tree-sitter"); write_file( &root_path.join("package.json"), - serde_json::to_string_pretty(&package_json)?, + serde_json::to_string_pretty(&package_json)? + "\n", )?; println!("Warning: your package.json's `tree-sitter` field has been automatically migrated to the new `tree-sitter.json` config file"); @@ -361,7 +361,7 @@ pub fn migrate_package_json(repo_path: &Path) -> Result { pub fn generate_grammar_files( repo_path: &Path, language_name: &str, - _allow_update: bool, + allow_update: bool, opts: Option, ) -> Result<()> { let dashed_language_name = language_name.to_kebab_case(); @@ -388,12 +388,16 @@ pub fn generate_grammar_files( }, )?; - let tree_sitter_config = serde_json::from_reader::<_, TreeSitterJSON>( - File::open(tree_sitter_config.as_path()) - .with_context(|| "Failed to open tree-sitter.json")?, + let tree_sitter_config = serde_json::from_str::( + &fs::read_to_string(tree_sitter_config.as_path()) + .with_context(|| "Failed to read tree-sitter.json")?, )?; let authors = tree_sitter_config.metadata.authors.as_ref(); + let camel_name = tree_sitter_config.grammars[0] + .camelcase + .clone() + .unwrap_or_else(|| language_name.to_upper_camel_case()); let generate_opts = GenerateOpts { author_name: authors @@ -413,6 +417,7 @@ pub fn generate_grammar_files( .as_ref() .map(|l| l.repository.as_str()), version: &tree_sitter_config.metadata.version, + camel_parser_name: &camel_name, }; // Create package.json @@ -476,9 +481,18 @@ pub fn generate_grammar_files( // Generate Node bindings if tree_sitter_config.bindings.node { missing_path(bindings_dir.join("node"), create_dir)?.apply(|path| { - missing_path(path.join("index.js"), |path| { - generate_file(path, INDEX_JS_TEMPLATE, language_name, &generate_opts) - })?; + missing_path_else( + path.join("index.js"), + allow_update, + |path| generate_file(path, INDEX_JS_TEMPLATE, language_name, &generate_opts), + |path| { + let contents = fs::read_to_string(path)?; + if !contents.contains("bun") { + generate_file(path, INDEX_JS_TEMPLATE, language_name, &generate_opts)?; + } + Ok(()) + }, + )?; missing_path(path.join("index.d.ts"), |path| { generate_file(path, INDEX_D_TS_TEMPLATE, language_name, &generate_opts) @@ -529,9 +543,20 @@ pub fn generate_grammar_files( generate_file(path, MAKEFILE_TEMPLATE, language_name, &generate_opts) })?; - missing_path(repo_path.join("CMakeLists.txt"), |path| { - generate_file(path, CMAKELISTS_TXT_TEMPLATE, language_name, &generate_opts) - })?; + missing_path_else( + repo_path.join("CMakeLists.txt"), + allow_update, + |path| generate_file(path, CMAKELISTS_TXT_TEMPLATE, language_name, &generate_opts), + |path| { + let contents = fs::read_to_string(path)?; + let old = "add_custom_target(test"; + if contents.contains(old) { + write_file(path, contents.replace(old, "add_custom_target(ts-test")) + } else { + Ok(()) + } + }, + )?; Ok(()) })?; @@ -615,7 +640,7 @@ pub fn generate_grammar_files( // Generate Swift bindings if tree_sitter_config.bindings.swift { missing_path(bindings_dir.join("swift"), create_dir)?.apply(|path| { - let lang_path = path.join(format!("TreeSitter{}", language_name.to_upper_camel_case())); + let lang_path = path.join(format!("TreeSitter{camel_name}",)); missing_path(&lang_path, create_dir)?; missing_path(lang_path.join(format!("{language_name}.h")), |path| { @@ -623,18 +648,12 @@ pub fn generate_grammar_files( })?; missing_path( - path.join(format!( - "TreeSitter{}Tests", - language_name.to_upper_camel_case() - )), + path.join(format!("TreeSitter{camel_name}Tests",)), create_dir, )? .apply(|path| { missing_path( - path.join(format!( - "TreeSitter{}Tests.swift", - language_name.to_upper_camel_case() - )), + path.join(format!("TreeSitter{camel_name}Tests.swift")), |path| generate_file(path, TESTS_SWIFT_TEMPLATE, language_name, &generate_opts), )?; @@ -660,15 +679,14 @@ pub fn get_root_path(path: &Path) -> Result { let json = pathbuf .exists() .then(|| { - let file = File::open(pathbuf.as_path()) - .with_context(|| format!("Failed to open {filename}"))?; - let reader = BufReader::new(file); + let contents = fs::read_to_string(pathbuf.as_path()) + .with_context(|| format!("Failed to read {filename}"))?; if is_package_json { - serde_json::from_reader::<_, Map>(reader) + serde_json::from_str::>(&contents) .context(format!("Failed to parse {filename}")) .map(|v| v.contains_key("tree-sitter")) } else { - serde_json::from_reader::<_, TreeSitterJSON>(reader) + serde_json::from_str::(&contents) .context(format!("Failed to parse {filename}")) .map(|_| true) } @@ -702,7 +720,7 @@ fn generate_file( let mut replacement = template .replace( CAMEL_PARSER_NAME_PLACEHOLDER, - &language_name.to_upper_camel_case(), + generate_opts.camel_parser_name, ) .replace( UPPER_PARSER_NAME_PLACEHOLDER, @@ -845,7 +863,7 @@ fn generate_file( PARSER_DESCRIPTION_PLACEHOLDER, &format!( "{} grammar for tree-sitter", - language_name.to_upper_camel_case() + generate_opts.camel_parser_name, ), ) } diff --git a/src/treesitter/cli/src/main.rs b/src/treesitter/cli/src/main.rs index 1758fada8..4926f2566 100644 --- a/src/treesitter/cli/src/main.rs +++ b/src/treesitter/cli/src/main.rs @@ -653,11 +653,11 @@ impl Init { (opts.name.clone(), Some(opts)) } else { - let json = serde_json::from_reader::<_, TreeSitterJSON>( - fs::File::open(current_dir.join("tree-sitter.json")) - .with_context(|| "Failed to open tree-sitter.json")?, + let mut json = serde_json::from_str::( + &fs::read_to_string(current_dir.join("tree-sitter.json")) + .with_context(|| "Failed to read tree-sitter.json")?, )?; - (json.grammars[0].name.clone(), None) + (json.grammars.swap_remove(0).name, None) }; generate_grammar_files(current_dir, &language_name, self.update, json_config_opts)?; diff --git a/src/treesitter/cli/src/templates/.editorconfig b/src/treesitter/cli/src/templates/.editorconfig index 7756ee951..65330c40c 100644 --- a/src/treesitter/cli/src/templates/.editorconfig +++ b/src/treesitter/cli/src/templates/.editorconfig @@ -41,3 +41,6 @@ indent_size = 8 [parser.c] indent_size = 2 + +[{alloc,array,parser}.h] +indent_size = 2 diff --git a/src/treesitter/cli/src/templates/PARSER_NAME.pc.in b/src/treesitter/cli/src/templates/PARSER_NAME.pc.in index 8567e9e21..e92fc8034 100644 --- a/src/treesitter/cli/src/templates/PARSER_NAME.pc.in +++ b/src/treesitter/cli/src/templates/PARSER_NAME.pc.in @@ -6,6 +6,5 @@ Name: tree-sitter-PARSER_NAME Description: @PROJECT_DESCRIPTION@ URL: @PROJECT_HOMEPAGE_URL@ Version: @PROJECT_VERSION@ -Requires: @TS_REQUIRES@ Libs: -L${libdir} -ltree-sitter-PARSER_NAME Cflags: -I${includedir} diff --git a/src/treesitter/cli/src/templates/_cargo.toml b/src/treesitter/cli/src/templates/_cargo.toml index 04540d345..4a1b64099 100644 --- a/src/treesitter/cli/src/templates/_cargo.toml +++ b/src/treesitter/cli/src/templates/_cargo.toml @@ -12,7 +12,7 @@ edition = "2021" autoexamples = false build = "bindings/rust/build.rs" -include = ["bindings/rust/*", "grammar.js", "queries/*", "src/*"] +include = ["bindings/rust/*", "grammar.js", "queries/*", "src/*", "tree-sitter.json"] [lib] path = "bindings/rust/lib.rs" diff --git a/src/treesitter/cli/src/templates/binding_test.js b/src/treesitter/cli/src/templates/binding_test.js index afede30a7..55becacfb 100644 --- a/src/treesitter/cli/src/templates/binding_test.js +++ b/src/treesitter/cli/src/templates/binding_test.js @@ -1,9 +1,9 @@ -/// - const assert = require("node:assert"); const { test } = require("node:test"); +const Parser = require("tree-sitter"); + test("can load grammar", () => { - const parser = new (require("tree-sitter"))(); + const parser = new Parser(); assert.doesNotThrow(() => parser.setLanguage(require("."))); }); diff --git a/src/treesitter/cli/src/templates/cmakelists.txt b/src/treesitter/cli/src/templates/cmakelists.cmake similarity index 95% rename from src/treesitter/cli/src/templates/cmakelists.txt rename to src/treesitter/cli/src/templates/cmakelists.cmake index 24f2507d1..3ce702399 100644 --- a/src/treesitter/cli/src/templates/cmakelists.txt +++ b/src/treesitter/cli/src/templates/cmakelists.cmake @@ -25,7 +25,7 @@ add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/parser.c" COMMENT "Generating parser.c") add_library(tree-sitter-PARSER_NAME src/parser.c) -if(EXISTS src/scanner.c) +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/src/scanner.c) target_sources(tree-sitter-PARSER_NAME PRIVATE src/scanner.c) endif() target_include_directories(tree-sitter-PARSER_NAME PRIVATE src) @@ -53,8 +53,6 @@ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tree-sitter-PARSER_NAME.pc" install(TARGETS tree-sitter-PARSER_NAME LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}") -add_custom_target(test "${TREE_SITTER_CLI}" test +add_custom_target(ts-test "${TREE_SITTER_CLI}" test WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" COMMENT "tree-sitter test") - -# vim:ft=cmake: diff --git a/src/treesitter/cli/src/templates/gitattributes b/src/treesitter/cli/src/templates/gitattributes index 9d5c5d499..7e2cae0c5 100644 --- a/src/treesitter/cli/src/templates/gitattributes +++ b/src/treesitter/cli/src/templates/gitattributes @@ -1,13 +1,37 @@ * text=auto eol=lf +# Generated source files src/*.json linguist-generated src/parser.c linguist-generated src/tree_sitter/* linguist-generated -bindings/** linguist-generated +# C bindings +bindings/c/* linguist-generated +CMakeLists.txt linguist-generated +Makefile linguist-generated + +# Rust bindings +bindings/rust/* linguist-generated +Cargo.toml linguist-generated +Cargo.lock linguist-generated + +# Node.js bindings +bindings/node/* linguist-generated binding.gyp linguist-generated +package.json linguist-generated +package-lock.json linguist-generated + +# Python bindings +bindings/python/** linguist-generated setup.py linguist-generated -Makefile linguist-generated -CMakeLists.txt linguist-generated -Package.swift linguist-generated +pyproject.toml linguist-generated + +# Go bindings +bindings/go/* linguist-generated go.mod linguist-generated +go.sum linguist-generated + +# Swift bindings +bindings/swift/** linguist-generated +Package.swift linguist-generated +Package.resolved linguist-generated diff --git a/src/treesitter/cli/src/templates/go.mod b/src/treesitter/cli/src/templates/go.mod index f5887715b..93436c824 100644 --- a/src/treesitter/cli/src/templates/go.mod +++ b/src/treesitter/cli/src/templates/go.mod @@ -2,4 +2,4 @@ module PARSER_URL_STRIPPED go 1.22 -require github.com/tree-sitter/go-tree-sitter v0.23.1 +require github.com/tree-sitter/go-tree-sitter v0.24.0 diff --git a/src/treesitter/cli/src/templates/index.js b/src/treesitter/cli/src/templates/index.js index 6657bcf42..884374956 100644 --- a/src/treesitter/cli/src/templates/index.js +++ b/src/treesitter/cli/src/templates/index.js @@ -1,6 +1,10 @@ const root = require("path").join(__dirname, "..", ".."); -module.exports = require("node-gyp-build")(root); +module.exports = + typeof process.versions.bun === "string" + // Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time + ? require(`../../prebuilds/${process.platform}-${process.arch}/tree-sitter-PARSER_NAME.node`) + : require("node-gyp-build")(root); try { module.exports.nodeTypeInfo = require("../../src/node-types.json"); diff --git a/src/treesitter/cli/src/templates/package.json b/src/treesitter/cli/src/templates/package.json index 212ffc3bb..1db11b2dd 100644 --- a/src/treesitter/cli/src/templates/package.json +++ b/src/treesitter/cli/src/templates/package.json @@ -2,7 +2,7 @@ "name": "tree-sitter-PARSER_NAME", "version": "PARSER_VERSION", "description": "PARSER_DESCRIPTION", - "repository": "github:tree-sitter/tree-sitter-PARSER_NAME", + "repository": "PARSER_URL", "license": "PARSER_LICENSE", "author": { "name": "PARSER_AUTHOR_NAME", @@ -19,6 +19,7 @@ ], "files": [ "grammar.js", + "tree-sitter.json", "binding.gyp", "prebuilds/**", "bindings/node/*", @@ -27,7 +28,7 @@ "*.wasm" ], "dependencies": { - "node-addon-api": "^8.1.0", + "node-addon-api": "^8.2.1", "node-gyp-build": "^4.8.2" }, "devDependencies": { @@ -47,11 +48,5 @@ "prestart": "tree-sitter build --wasm", "start": "tree-sitter playground", "test": "node --test bindings/node/*_test.js" - }, - "tree-sitter": [ - { - "scope": "source.LOWER_PARSER_NAME", - "injection-regex": "^LOWER_PARSER_NAME$" - } - ] + } } diff --git a/src/treesitter/cli/src/templates/pyproject.toml b/src/treesitter/cli/src/templates/pyproject.toml index 4df75f74d..e0db4e21a 100644 --- a/src/treesitter/cli/src/templates/pyproject.toml +++ b/src/treesitter/cli/src/templates/pyproject.toml @@ -9,7 +9,6 @@ version = "PARSER_VERSION" keywords = ["incremental", "parsing", "tree-sitter", "PARSER_NAME"] classifiers = [ "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", "Topic :: Software Development :: Compilers", "Topic :: Text Processing :: Linguistic", "Typing :: Typed", diff --git a/src/treesitter/cli/src/tests/detect_language.rs b/src/treesitter/cli/src/tests/detect_language.rs index 5bad36d37..aed4ae184 100644 --- a/src/treesitter/cli/src/tests/detect_language.rs +++ b/src/treesitter/cli/src/tests/detect_language.rs @@ -32,7 +32,7 @@ fn detect_language_by_first_line_regex() { .find_language_configurations_at_path(strace_dir.path(), false) .unwrap(); - // this is just to validate that we can read the package.json correctly + // this is just to validate that we can read the tree-sitter.json correctly assert_eq!(config[0].scope.as_ref().unwrap(), "source.strace"); let file_name = strace_dir.path().join("strace.log"); diff --git a/src/treesitter/cli/src/tests/node_test.rs b/src/treesitter/cli/src/tests/node_test.rs index fdabc499d..20686a4ae 100644 --- a/src/treesitter/cli/src/tests/node_test.rs +++ b/src/treesitter/cli/src/tests/node_test.rs @@ -1026,6 +1026,31 @@ fn test_node_numeric_symbols_respect_simple_aliases() { assert_eq!(unary_minus_node.kind_id(), binary_minus_node.kind_id()); } +#[test] +fn test_hidden_zero_width_node_with_visible_child() { + let code = r" +class Foo { + std:: +private: + std::string s; +}; +"; + + let mut parser = Parser::new(); + parser.set_language(&get_language("cpp")).unwrap(); + let tree = parser.parse(code, None).unwrap(); + let root = tree.root_node(); + + let class_specifier = root.child(0).unwrap(); + let field_decl_list = class_specifier.child_by_field_name("body").unwrap(); + let field_decl = field_decl_list.named_child(0).unwrap(); + let field_ident = field_decl.child_by_field_name("declarator").unwrap(); + assert_eq!( + field_decl.child_with_descendant(field_ident).unwrap(), + field_ident + ); +} + fn get_all_nodes(tree: &Tree) -> Vec { let mut result = Vec::new(); let mut visited_children = false; diff --git a/src/treesitter/lib/CMakeLists.txt b/src/treesitter/lib/CMakeLists.txt index c27db119c..d6e758b01 100644 --- a/src/treesitter/lib/CMakeLists.txt +++ b/src/treesitter/lib/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.13) project(tree-sitter - VERSION "0.24.3" + VERSION "0.24.4" DESCRIPTION "An incremental parsing system for programming tools" HOMEPAGE_URL "https://tree-sitter.github.io/tree-sitter/" LANGUAGES C) diff --git a/src/treesitter/lib/binding_rust/lib.rs b/src/treesitter/lib/binding_rust/lib.rs index bba21bea9..bb752f357 100644 --- a/src/treesitter/lib/binding_rust/lib.rs +++ b/src/treesitter/lib/binding_rust/lib.rs @@ -8,7 +8,6 @@ extern crate alloc; #[cfg(not(feature = "std"))] use alloc::{boxed::Box, format, string::String, string::ToString, vec::Vec}; use core::{ - char, ffi::{c_char, c_void, CStr}, fmt::{self, Write}, hash, iter, @@ -489,9 +488,9 @@ impl Parser { /// Get the parser's current language. #[doc(alias = "ts_parser_language")] #[must_use] - pub fn language(&self) -> Option { + pub fn language(&self) -> Option> { let ptr = unsafe { ffi::ts_parser_language(self.0.as_ptr()) }; - (!ptr.is_null()).then(|| Language(ptr)) + (!ptr.is_null()).then_some(LanguageRef(ptr, PhantomData)) } /// Get the parser's current logger. @@ -1854,9 +1853,28 @@ impl Query { // Error types that report names ffi::TSQueryErrorNodeType | ffi::TSQueryErrorField | ffi::TSQueryErrorCapture => { let suffix = source.split_at(offset).1; - let end_offset = suffix - .find(|c| !char::is_alphanumeric(c) && c != '_' && c != '-') - .unwrap_or(suffix.len()); + let in_quotes = source.as_bytes()[offset - 1] == b'"'; + let mut end_offset = suffix.len(); + if let Some(pos) = suffix + .char_indices() + .take_while(|(_, c)| *c != '\n') + .find_map(|(i, c)| match c { + '"' if in_quotes + && i > 0 + && suffix.chars().nth(i - 1) != Some('\\') => + { + Some(i) + } + c if !in_quotes + && (c.is_whitespace() || c == '(' || c == ')' || c == ':') => + { + Some(i) + } + _ => None, + }) + { + end_offset = pos; + } message = suffix.split_at(end_offset).0.to_string(); kind = match error_type { ffi::TSQueryErrorNodeType => QueryErrorKind::NodeType, diff --git a/src/treesitter/lib/binding_web/package.json b/src/treesitter/lib/binding_web/package.json index fa9b5e7e3..faa4dbe45 100644 --- a/src/treesitter/lib/binding_web/package.json +++ b/src/treesitter/lib/binding_web/package.json @@ -1,6 +1,6 @@ { "name": "web-tree-sitter", - "version": "0.24.3", + "version": "0.24.4", "description": "Tree-sitter bindings for the web", "main": "tree-sitter.js", "types": "tree-sitter-web.d.ts", diff --git a/src/treesitter/lib/src/node.c b/src/treesitter/lib/src/node.c index 818735a10..40d6024ff 100644 --- a/src/treesitter/lib/src/node.c +++ b/src/treesitter/lib/src/node.c @@ -103,21 +103,6 @@ static inline bool ts_node_child_iterator_next( return true; } -// This will return true if the next sibling is a zero-width token that is adjacent to the current node and is relevant -static inline bool ts_node_child_iterator_next_sibling_is_empty_adjacent(NodeChildIterator *self, TSNode previous) { - if (!self->parent.ptr || ts_node_child_iterator_done(self)) return false; - if (self->child_index == 0) return false; - const Subtree *child = &ts_subtree_children(self->parent)[self->child_index]; - TSSymbol alias = 0; - if (!ts_subtree_extra(*child)) { - if (self->alias_sequence) { - alias = self->alias_sequence[self->structural_child_index]; - } - } - TSNode next = ts_node_new(self->tree, child, self->position, alias); - return ts_node_end_byte(previous) == ts_node_end_byte(next) && ts_node__is_relevant(next, true); -} - // TSNode - private static inline bool ts_node__is_relevant(TSNode self, bool include_anonymous) { @@ -549,9 +534,9 @@ TSNode ts_node_parent(TSNode self) { if (node.id == self.id) return ts_node__null(); while (true) { - TSNode next_node = ts_node_child_containing_descendant(node, self); - if (ts_node_is_null(next_node)) break; - node = next_node; + TSNode next_node = ts_node_child_containing_descendant(node, self); + if (ts_node_is_null(next_node)) break; + node = next_node; } return node; @@ -560,6 +545,7 @@ TSNode ts_node_parent(TSNode self) { TSNode ts_node_child_containing_descendant(TSNode self, TSNode descendant) { uint32_t start_byte = ts_node_start_byte(descendant); uint32_t end_byte = ts_node_end_byte(descendant); + bool is_empty = start_byte == end_byte; do { NodeChildIterator iter = ts_node_iterate_children(&self); @@ -572,24 +558,16 @@ TSNode ts_node_child_containing_descendant(TSNode self, TSNode descendant) { return ts_node__null(); } - // Here we check the current self node and *all* of its zero-width token siblings that follow. - // If any of these nodes contain the target subnode, we return that node. Otherwise, we restore the node we started at - // for the loop condition, and that will continue with the next *non-zero-width* sibling. - TSNode old = self; - // While the next sibling is a zero-width token - while (ts_node_child_iterator_next_sibling_is_empty_adjacent(&iter, self)) { - TSNode current_node = ts_node_child_containing_descendant(self, descendant); - // If the target child is in self, return it - if (!ts_node_is_null(current_node)) { - return current_node; - } - ts_node_child_iterator_next(&iter, &self); - if (self.id == descendant.id) { - return ts_node__null(); + // If the descendant is empty, and the end byte is within `self`, + // we check whether `self` contains it or not. + if (is_empty && iter.position.bytes >= end_byte && ts_node_child_count(self) > 0) { + TSNode child = ts_node_child_with_descendant(self, descendant); + // If the child is not null, return self if it's relevant, else return the child + if (!ts_node_is_null(child)) { + return ts_node__is_relevant(self, true) ? self : child; } } - self = old; - } while (iter.position.bytes < end_byte || ts_node_child_count(self) == 0); + } while ((is_empty ? iter.position.bytes <= end_byte : iter.position.bytes < end_byte) || ts_node_child_count(self) == 0); } while (!ts_node__is_relevant(self, true)); return self; @@ -598,6 +576,7 @@ TSNode ts_node_child_containing_descendant(TSNode self, TSNode descendant) { TSNode ts_node_child_with_descendant(TSNode self, TSNode descendant) { uint32_t start_byte = ts_node_start_byte(descendant); uint32_t end_byte = ts_node_end_byte(descendant); + bool is_empty = start_byte == end_byte; do { NodeChildIterator iter = ts_node_iterate_children(&self); @@ -612,24 +591,16 @@ TSNode ts_node_child_with_descendant(TSNode self, TSNode descendant) { return self; } - // Here we check the current self node and *all* of its zero-width token siblings that follow. - // If any of these nodes contain the target subnode, we return that node. Otherwise, we restore the node we started at - // for the loop condition, and that will continue with the next *non-zero-width* sibling. - TSNode old = self; - // While the next sibling is a zero-width token - while (ts_node_child_iterator_next_sibling_is_empty_adjacent(&iter, self)) { - TSNode current_node = ts_node_child_with_descendant(self, descendant); - // If the target child is in self, return it - if (!ts_node_is_null(current_node)) { - return current_node; - } - ts_node_child_iterator_next(&iter, &self); - if (self.id == descendant.id) { - return self; + // If the descendant is empty, and the end byte is within `self`, + // we check whether `self` contains it or not. + if (is_empty && iter.position.bytes >= end_byte && ts_node_child_count(self) > 0) { + TSNode child = ts_node_child_with_descendant(self, descendant); + // If the child is not null, return self if it's relevant, else return the child + if (!ts_node_is_null(child)) { + return ts_node__is_relevant(self, true) ? self : child; } } - self = old; - } while (iter.position.bytes < end_byte || ts_node_child_count(self) == 0); + } while ((is_empty ? iter.position.bytes <= end_byte : iter.position.bytes < end_byte) || ts_node_child_count(self) == 0); } while (!ts_node__is_relevant(self, true)); return self;