From 76f9a55f01a56d4555755656f7d12eae1a5e581c Mon Sep 17 00:00:00 2001 From: Martin Ball Date: Sun, 1 Nov 2026 22:21:14 +0000 Subject: [PATCH] =?UTF-8?q?CP6.13:=20it=20grammar=20=E2=80=94=20a=20senten?= =?UTF-8?q?ce=20after=20the=20first=20starts=20with=20a=20capital:=20the?= =?UTF-8?q?=20rule=20reads=20the=20gap=20before=20the=20next=20word,=20not?= =?UTF-8?q?=20the=20rest=20of=20the=20paragraph=20(CodeRabbit=20on=20craft?= =?UTF-8?q?-libs#484/#489)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2 --- crates/proof/src/it/grammar.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/proof/src/it/grammar.rs b/crates/proof/src/it/grammar.rs index 46302936..63e56eff 100644 --- a/crates/proof/src/it/grammar.rs +++ b/crates/proof/src/it/grammar.rs @@ -210,13 +210,15 @@ fn spaces(text: &str, v: &mut Vec) { fn sentence_capitals(text: &str, ws: &[(usize, usize)], capital_first: bool, v: &mut Vec) { let mut after_end = capital_first; - for (a, b) in ws { + for (i, (a, b)) in ws.iter().enumerate() { let Some(w) = text.get(*a..*b) else { continue }; if after_end && w.chars().next().is_some_and(char::is_lowercase) && w.chars().all(char::is_alphabetic) { let fixed: String = w.chars().next().map(|f| f.to_uppercase().chain(w.chars().skip(1)).collect()).unwrap_or_default(); v.push(issue(*a, *b, "Maiuscola a inizio frase".into(), vec![fixed])); } - after_end = ends_sentence(text.get(*b..).unwrap_or(""), w); + // Only the gap before the next word: the rest of the paragraph never ends a sentence. + let next = ws.get(i + 1).map_or(text.len(), |n| n.0); + after_end = ends_sentence(text.get(*b..next).unwrap_or(""), w); } } @@ -384,6 +386,15 @@ mod tests { (a.to_string(), b.to_string()) } + /// CodeRabbit on craft-libs#484: a sentence after the first one starts with a capital too. + #[test] + fn later_sentences_start_with_a_capital() { + assert!(fixes("Vado a Roma. il cane dorme.").contains(&pair("il", "Il"))); + assert!(fixes("Vado a Roma? sì.").contains(&pair("sì", "Sì"))); + assert!(!fixes("Vado a Roma. Il cane dorme.").iter().any(|(_, b)| b == "Il")); + assert!(!fixes("Il sig. rossi arriva.").iter().any(|(a, _)| a == "rossi"), "after an abbreviation"); + } + #[test] fn po_takes_an_apostrophe() { assert_eq!(fixes("Dammi un pò di pane"), [pair("pò", "po'")]);