Build a `TextEdit` that adds a `@phpstan-ignore` comment to a line. If the line already contains `@phpstan-ignore`, the identifier is appended to the existing comma-separated list. Otherwise, a `// @phpstan-ignore ` comment is inserted at the end of the line.
(content: &str, line: u32, line_text: &str, identifier: &str)
| 221 | /// appended to the existing comma-separated list. Otherwise, a |
| 222 | /// `// @phpstan-ignore <id>` comment is inserted at the end of the line. |
| 223 | fn build_add_ignore_edit(content: &str, line: u32, line_text: &str, identifier: &str) -> TextEdit { |
| 224 | // Check if line already has a @phpstan-ignore comment. |
| 225 | if let Some(ignore_pos) = line_text.find("@phpstan-ignore") { |
| 226 | let after_tag = &line_text[ignore_pos + "@phpstan-ignore".len()..]; |
| 227 | |
| 228 | // If it's `@phpstan-ignore-line` or `@phpstan-ignore-next-line`, |
| 229 | // we don't touch it — add a new comment instead. |
| 230 | if after_tag.starts_with("-line") || after_tag.starts_with("-next-line") { |
| 231 | return build_eol_comment(content, line, line_text, identifier); |
| 232 | } |
| 233 | |
| 234 | // Find the end of the existing identifier list. |
| 235 | // The identifiers are everything after `@phpstan-ignore ` up to |
| 236 | // a closing comment delimiter, parenthesis, or end of line. |
| 237 | let ids_start = ignore_pos + "@phpstan-ignore".len(); |
| 238 | let ids_text = &line_text[ids_start..]; |
| 239 | |
| 240 | // Trim leading whitespace after the tag. |
| 241 | let ids_trimmed = ids_text.trim_start(); |
| 242 | let ids_offset = ids_text.len() - ids_trimmed.len(); |
| 243 | |
| 244 | // Find where the identifier list ends: at `*/`, `)`, or EOL. |
| 245 | let ids_end = ids_trimmed |
| 246 | .find("*/") |
| 247 | .or_else(|| { |
| 248 | // For `// @phpstan-ignore id (reason)`, stop at the |
| 249 | // opening paren of the reason. But only if the paren |
| 250 | // is preceded by whitespace (not part of an identifier). |
| 251 | ids_trimmed.find(" (") |
| 252 | }) |
| 253 | .unwrap_or(ids_trimmed.len()); |
| 254 | |
| 255 | let existing_ids = ids_trimmed[..ids_end].trim(); |
| 256 | |
| 257 | // Check if identifier is already present. |
| 258 | if existing_ids.split(',').any(|id| id.trim() == identifier) { |
| 259 | // Already ignored — return a no-op edit. |
| 260 | return TextEdit { |
| 261 | range: Range { |
| 262 | start: Position { line, character: 0 }, |
| 263 | end: Position { line, character: 0 }, |
| 264 | }, |
| 265 | new_text: String::new(), |
| 266 | }; |
| 267 | } |
| 268 | |
| 269 | // Insert the new identifier after the existing ones. |
| 270 | let insert_col = (ids_start + ids_offset + ids_end) as u32; |
| 271 | |
| 272 | // Check if we need a comma separator. |
| 273 | let separator = if existing_ids.is_empty() { "" } else { ", " }; |
| 274 | |
| 275 | return TextEdit { |
| 276 | range: Range { |
| 277 | start: Position { |
| 278 | line, |
| 279 | character: insert_col, |
| 280 | }, |