From 6dcc1edff2222c44617299227514ba5f17ca39f0 Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Thu, 20 Nov 2025 12:37:37 +0900 Subject: [PATCH] test: add parser and node-types.json compatibility tests for multiple grammars --- crates/cli/src/tests/node_test.rs | 112 +++++++++++++++++++++++++++++- crates/generate/src/generate.rs | 2 + crates/generate/src/node_types.rs | 40 +++++------ 3 files changed, 133 insertions(+), 21 deletions(-) diff --git a/crates/cli/src/tests/node_test.rs b/crates/cli/src/tests/node_test.rs index 614bfdb9..1be57d55 100644 --- a/crates/cli/src/tests/node_test.rs +++ b/crates/cli/src/tests/node_test.rs @@ -1,5 +1,7 @@ +use std::{collections::HashMap, fs}; + use tree_sitter::{InputEdit, Node, Parser, Point, Tree}; -use tree_sitter_generate::load_grammar_file; +use tree_sitter_generate::{load_grammar_file, NodeInfoJSON}; use super::{ get_random_edit, @@ -1243,3 +1245,111 @@ fn parse_json_example() -> Tree { parser.set_language(&get_language("json")).unwrap(); parser.parse(JSON_EXAMPLE, None).unwrap() } + +fn test_parser_and_node_types_compatibility(grammar_name: &str) { + let language = get_language(grammar_name); + let node_types: Vec = { + let node_types_path = fixtures_dir() + .join("grammars") + .join(grammar_name) + .join("src") + .join("node-types.json"); + + let node_types_content = fs::read_to_string(&node_types_path) + .unwrap_or_else(|_| panic!("Failed to read node-types.json at {:?}", node_types_path)); + + serde_json::from_str(&node_types_content) + .unwrap_or_else(|e| panic!("Failed to parse node-types.json: {}", e)) + }; + + let symbol_ids_by_kind_from_node_types: HashMap<(String, bool), Option<&Vec>> = node_types + .iter() + .map(|node_type| { + ( + (node_type.kind.clone(), node_type.named), + node_type.symbol_ids.as_ref(), // .unwrap_or(Vec::new()), + ) + }) + .collect(); + let kind_count = language.node_kind_count(); + + let mut symbol_ids_by_kind_from_language: HashMap<(String, bool), Vec> = HashMap::new(); + for i in 0..kind_count as u16 { + let kind = language.node_kind_for_id(i).unwrap().to_string(); + let id = language.node_kind_is_named(i); + + symbol_ids_by_kind_from_language + .entry((kind, id)) + .or_insert_with(Vec::new) + .push(i); + } + + for (key, symbol_ids) in symbol_ids_by_kind_from_node_types { + assert_eq!(symbol_ids_by_kind_from_language.get(&key), symbol_ids); + } +} + +#[test] +fn test_bash_parser_and_node_types_compatibility() { + test_parser_and_node_types_compatibility("bash"); +} + +#[test] +fn test_c_parser_and_node_types_compatibility() { + test_parser_and_node_types_compatibility("c"); +} + +#[test] +fn test_cpp_parser_and_node_types_compatibility() { + test_parser_and_node_types_compatibility("cpp"); +} + +#[test] +fn test_embedded_template_parser_and_node_types_compatibility() { + test_parser_and_node_types_compatibility("embedded_template"); +} + +#[test] +fn test_go_parser_and_node_types_compatibility() { + test_parser_and_node_types_compatibility("go"); +} + +#[test] +fn test_html_parser_and_node_types_compatibility() { + test_parser_and_node_types_compatibility("html"); +} + +#[test] +fn test_java_parser_and_node_types_compatibility() { + test_parser_and_node_types_compatibility("java"); +} + +#[test] +fn test_javascript_parser_and_node_types_compatibility() { + test_parser_and_node_types_compatibility("javascript"); +} + +#[test] +fn test_jsdoc_parser_and_node_types_compatibility() { + test_parser_and_node_types_compatibility("jsdoc"); +} + +#[test] +fn test_json_parser_and_node_types_compatibility() { + test_parser_and_node_types_compatibility("json"); +} + +#[test] +fn test_python_parser_and_node_types_compatibility() { + test_parser_and_node_types_compatibility("python"); +} + +#[test] +fn test_ruby_parser_and_node_types_compatibility() { + test_parser_and_node_types_compatibility("ruby"); +} + +#[test] +fn test_rust_parser_and_node_types_compatibility() { + test_parser_and_node_types_compatibility("rust"); +} diff --git a/crates/generate/src/generate.rs b/crates/generate/src/generate.rs index 1926889c..f203648a 100644 --- a/crates/generate/src/generate.rs +++ b/crates/generate/src/generate.rs @@ -34,6 +34,8 @@ mod tables; pub use build_tables::ParseTableBuilderError; use introspect_grammar::{introspect_grammar, GrammarIntrospection}; pub use node_types::{SuperTypeCycleError, VariableInfoError}; +#[cfg(feature = "load")] +pub use node_types::{FieldInfoJSON, NodeInfoJSON, NodeTypeJSON}; use parse_grammar::parse_grammar; pub use parse_grammar::ParseGrammarError; pub use prepare_grammar::PrepareGrammarError; diff --git a/crates/generate/src/node_types.rs b/crates/generate/src/node_types.rs index e724f6f2..db2ecb0a 100644 --- a/crates/generate/src/node_types.rs +++ b/crates/generate/src/node_types.rs @@ -1,6 +1,6 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use thiserror::Error; use super::{ @@ -28,40 +28,40 @@ pub struct VariableInfo { pub has_multi_step_production: bool, } -#[derive(Debug, Serialize, PartialEq, Eq, Default, PartialOrd, Ord)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default, PartialOrd, Ord)] #[cfg(feature = "load")] pub struct NodeInfoJSON { #[serde(rename = "type")] - kind: String, - named: bool, - #[serde(skip_serializing_if = "std::ops::Not::not")] - root: bool, - #[serde(skip_serializing_if = "std::ops::Not::not")] - extra: bool, + pub kind: String, + pub named: bool, + #[serde(skip_serializing_if = "std::ops::Not::not", default)] + pub root: bool, + #[serde(skip_serializing_if = "std::ops::Not::not", default)] + pub extra: bool, #[serde(skip_serializing_if = "Option::is_none")] - fields: Option>, + pub fields: Option>, #[serde(skip_serializing_if = "Option::is_none")] - children: Option, + pub children: Option, #[serde(skip_serializing_if = "Option::is_none")] - subtypes: Option>, + pub subtypes: Option>, #[serde(skip_serializing_if = "Option::is_none")] - symbol_ids: Option>, + pub symbol_ids: Option>, } -#[derive(Clone, Debug, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg(feature = "load")] pub struct NodeTypeJSON { #[serde(rename = "type")] - kind: String, - named: bool, + pub kind: String, + pub named: bool, } -#[derive(Debug, Serialize, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[cfg(feature = "load")] pub struct FieldInfoJSON { - multiple: bool, - required: bool, - types: Vec, + pub multiple: bool, + pub required: bool, + pub types: Vec, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -1551,7 +1551,7 @@ mod tests { subtypes: None, children: None, fields: None, - symbol_ids: None, + symbol_ids: Some(vec![7]), }) ); }