From 231af1e646643d9aca00368f0e3749b0a09dc0ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Fri, 14 Nov 2025 17:39:43 +0100 Subject: [PATCH 1/2] Fix parsing of python f-strings Double-quoted f-strings mostly worked, but single-quoted did not: a = f'what' Reported that `f'what` was mispelled. I think this is due to: 1. the `(strings) @string` query give back in the whole node string node - including `f'` 2. the word-extraction in parser.rs (`processor.extract_words()`) does not consider `'` a word separator (rightly so) (2) explains why double-quote f-strings works - the word splitting split on `"`, resulting in "f" and "what", and I assume single letter words is not checked. (1) can be confirmed in the tree-sitter playgound [1], where we also see that there is an `string_content` node type more suitable. Using the `string_content` node means that code inside interpolation units wont be spell-checked, but this aligns with the current behavior that only definitions, comments, and strings are checked. [1] https://tree-sitter.github.io/tree-sitter/7-playground.html --- crates/codebook/src/queries/python.scm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/codebook/src/queries/python.scm b/crates/codebook/src/queries/python.scm index 42f820a..ac9d590 100644 --- a/crates/codebook/src/queries/python.scm +++ b/crates/codebook/src/queries/python.scm @@ -1,5 +1,5 @@ (comment) @comment -(string) @string +(string_content) @string (function_definition name: (identifier) @identifier) (function_definition From d5d94ff6327c33549d821b364956dcf5c78f8ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Fri, 14 Nov 2025 21:08:56 +0100 Subject: [PATCH 2/2] Unit test for python f-string spell checking --- crates/codebook/tests/test_python.rs | 79 ++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/crates/codebook/tests/test_python.rs b/crates/codebook/tests/test_python.rs index f67e950..308642e 100644 --- a/crates/codebook/tests/test_python.rs +++ b/crates/codebook/tests/test_python.rs @@ -188,3 +188,82 @@ mesage = "Helllo Wolrd!" assert_eq!(miss.locations, e.locations); } } + +#[test] +fn test_python_f_strings() { + utils::init_logging(); + let processor = utils::get_processor(); + let sample_text = r#" +name = "John" +age = 25 +message = f'Hello, my naem is {name} and I am {age} years oldd' +another = f"This is antoher examle with {name} varibles" +simple = f'check these wordz {but} {not} {the} {variables}' + "#; + + let expected = vec![ + WordLocation::new( + "naem".to_string(), + vec![TextRange { + start_byte: 46, + end_byte: 50, + }], + ), + WordLocation::new( + "oldd".to_string(), + vec![TextRange { + start_byte: 82, + end_byte: 86, + }], + ), + WordLocation::new( + "antoher".to_string(), + vec![TextRange { + start_byte: 108, + end_byte: 115, + }], + ), + WordLocation::new( + "examle".to_string(), + vec![TextRange { + start_byte: 116, + end_byte: 122, + }], + ), + WordLocation::new( + "varibles".to_string(), + vec![TextRange { + start_byte: 135, + end_byte: 143, + }], + ), + WordLocation::new( + "wordz".to_string(), + vec![TextRange { + start_byte: 168, + end_byte: 173, + }], + ), + ]; + + let not_expected = vec!["name", "age", "but", "not", "the", "variables"]; + + let misspelled = processor + .spell_check(sample_text, Some(LanguageType::Python), None) + .to_vec(); + println!("Misspelled words: {misspelled:?}"); + + for e in &expected { + let miss = misspelled + .iter() + .find(|r| r.word == e.word) + .unwrap_or_else(|| panic!("Word '{}' not found in misspelled list", e.word)); + println!("Expecting: {e:?}"); + assert_eq!(miss.locations, e.locations); + } + + for word in not_expected { + println!("Not expecting: {word:?}"); + assert!(!misspelled.iter().any(|r| r.word == word)); + } +}