diff --git a/crates/codebook/src/queries/python.scm b/crates/codebook/src/queries/python.scm index ac9d590..4d5b125 100644 --- a/crates/codebook/src/queries/python.scm +++ b/crates/codebook/src/queries/python.scm @@ -1,10 +1,29 @@ (comment) @comment + (string_content) @string + (function_definition name: (identifier) @identifier) -(function_definition - parameters: (parameters) @identifier) + (class_definition name: (identifier) @identifier) + (assignment (identifier) @identifier) + +(parameters + (identifier) @identifier) + +; Matches typed parameters (e.g., "name: str") +; The identifier for the name is a *direct child* of typed_parameter, +; while the type identifier is nested inside a (type) node. +(typed_parameter + (identifier) @identifier) + +; Matches parameters with default values (e.g., "limit=10") +(default_parameter + (identifier) @identifier) + +; Matches typed parameters with default values (e.g., "limit: int = 10") +(typed_default_parameter + (identifier) @identifier) diff --git a/crates/codebook/tests/test_python.rs b/crates/codebook/tests/test_python.rs index a0e18d0..f721c02 100644 --- a/crates/codebook/tests/test_python.rs +++ b/crates/codebook/tests/test_python.rs @@ -5,6 +5,52 @@ use codebook::{ mod utils; +/// Strategy: +/// Use distinct misspellings to test location sensitive checking. +/// This simpler to write than asserting exact locations. +/// Granted - it doesn't test that the spell_check return correct locations, +/// but should be sufficient that some tests tests this. +/// This can be used to test language-specific grammar rules with less effort. +/// +/// `not_expected` does not have to be exhaustive. +fn assert_simple_misspellings( + processor: &codebook::Codebook, + sample_text: &str, + expected_misspellings: Vec<&str>, + not_expected: Vec<&str>, + language: LanguageType, +) { + // Check that the misspelled words used are distinct, + // otherwise the test could fail to properly test location sensitive properties + for word in expected_misspellings.iter() { + let count = sample_text.matches(word).count(); + assert_eq!( + count, 1, + "Word '{}' should occur exactly once in sample_text, but found {} occurrences", + word, count + ); + } + + let binding = processor + .spell_check(sample_text, Some(language), None) + .to_vec(); + let mut misspelled = binding + .iter() + .map(|r| r.word.as_str()) + .collect::>(); + misspelled.sort(); + println!("Misspelled words: {misspelled:?}"); + + let mut expected_misspellings_sorted = expected_misspellings.clone(); + expected_misspellings_sorted.sort(); + assert_eq!(misspelled, expected_misspellings_sorted); + + for word in not_expected { + println!("Not expecting: {word:?}"); + assert!(!misspelled.iter().any(|w| *w == word)); + } +} + #[test] fn test_python_simple() { utils::init_logging(); @@ -267,3 +313,79 @@ simple = f'check these wordz {but} {not} {the} {variables}' assert!(!misspelled.iter().any(|r| r.word == word)); } } + +#[test] +fn test_python_functions() { + utils::init_logging(); + let processor = utils::get_processor(); + + // Test simple function - function name and parameter names should be checked + let simple_function = r#" +def simple_wrngfunction_name(wrngparam, correct, wrngdefaultparam=1, correct_default=2): + pass + "#; + assert_simple_misspellings( + &processor, + simple_function, + vec!["wrngfunction", "wrngparam", "wrngdefaultparam"], + vec!["simple", "correct", "def", "name", "default"], + LanguageType::Python, + ); + + // Test typed function - function names and parameters should be checked, but not types or modules + let simple_typed_function = r#" +def simple_wrngfunction(wrngparam: str, correct: Wrngtype, other: wrngmod.Wrngmodtype, correct_default: Nons | int = 2) -> Wrngret: + pass + "#; + assert_simple_misspellings( + &processor, + simple_typed_function, + vec!["wrngfunction", "wrngparam"], + vec![ + "simple", + "correct", + "str", + "Wrngtype", + "wrngmod", + "Wrngmodtype", + "Wrngret", + "def", + "Nons", + "default", + ], + LanguageType::Python, + ); + + // Test generic function 1 - function names and parameters should be checked, but not types + let generic_function_1 = r#" +def simple_wrngfunction(wrngparam: str, correct: Wrngtype[Wrngtemplate]): + pass + "#; + assert_simple_misspellings( + &processor, + generic_function_1, + vec!["wrngfunction", "wrngparam"], + vec!["simple", "correct", "str", "Wrngtype", "Wrngtemplate"], + LanguageType::Python, + ); + + // Test generic function 2 - function names and parameters should be checked, but not type templates + let generic_function_2 = r#" +def simple_wrngfunction[Wrgtemplate](wrngparam: str, correct: Wrngtype[Wrngtemplate]): + pass + "#; + assert_simple_misspellings( + &processor, + generic_function_2, + vec!["wrngfunction", "wrngparam"], + vec![ + "simple", + "correct", + "str", + "Wrgtemplate", + "Wrngtype", + "Wrngtemplate", + ], + LanguageType::Python, + ); +}