@@ -92,6 +92,11 @@ func (s *Service) CheckBatch(texts []string) []Result {
9292 go func (i int , text string ) {
9393 defer wg .Done ()
9494 defer func () { <- sem }()
95+ defer func () {
96+ if rec := recover (); rec != nil {
97+ results [i ] = Result {}
98+ }
99+ }()
95100
96101 r , err := s .Check (text )
97102 if err != nil {
@@ -106,14 +111,24 @@ func (s *Service) CheckBatch(texts []string) []Result {
106111 return results
107112}
108113
114+ // isValidSpan reports whether m describes a non-empty, non-overflowing span
115+ // within a slice of runeCount runes.
116+ func isValidSpan (m ltMatch , runeCount int ) bool {
117+ return len (m .Replacements ) > 0 &&
118+ m .Offset >= 0 &&
119+ m .Length >= 0 &&
120+ m .Offset < runeCount &&
121+ m .Length <= runeCount - m .Offset
122+ }
123+
109124// buildResult converts LanguageTool matches into our Result type.
110125// Replacements are applied in reverse order to keep offsets valid.
111126func buildResult (text string , matches []ltMatch ) Result {
112127 runes := []rune (text )
113128 corrections := make ([]Correction , 0 , len (matches ))
114129
115130 for _ , m := range matches {
116- if len ( m . Replacements ) == 0 || m . Offset < 0 || m . Length < 0 || m . Offset + m . Length > len (runes ) {
131+ if ! isValidSpan ( m , len (runes ) ) {
117132 continue
118133 }
119134 // Collect up to 3 suggestions so callers can present a picker.
@@ -137,7 +152,7 @@ func buildResult(text string, matches []ltMatch) Result {
137152 copy (corrected , runes )
138153 for i := len (matches ) - 1 ; i >= 0 ; i -- {
139154 m := matches [i ]
140- if len ( m . Replacements ) == 0 || m . Offset < 0 || m . Length < 0 || m . Offset + m . Length > len (corrected ) {
155+ if ! isValidSpan ( m , len (corrected ) ) {
141156 continue
142157 }
143158 repl := []rune (m .Replacements [0 ].Value )
0 commit comments