Repository: astral-sh/ruff
File: crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs (Rust)

Code before:
                .chars()
                .next()
                .is_some_and(|first_char| !first_char.is_whitespace())
            {
                if let Some(before_colon) = entry.split(':').next() {
                    let param_line = before_colon.trim_end();

                    // Split on commas to handle comma-separated parameters
                    for param_part in param_line.split(',') {
                        let param_part_trimmed = param_part.trim();
                        let param_name = param_part_trimmed.trim_start_matches('*');
                        if is_identifier(param_name) {
                            // Calculate the position of this specific parameter part within the line
                            // Safety: searching for a substring we just split above
                            let param_start_in_line = param_line.find(param_part_trimmed).unwrap();
                            let param_start = line_start
                                + indentation.text_len()
                                + TextSize::try_from(param_start_in_line).unwrap();
                            let param_end = param_start + param_part_trimmed.text_len();

                            entries.push(ParameterEntry {
                                name: param_name,
                                range: TextRange::new(
                                    content_start + param_start,
                                    content_start + param_end,
                                ),
                            });
                        }
                    }
                }
            }
        }
    }
    entries
}

/// Parse the entries in a "Raises" section of a docstring.
///
/// Attempts to parse using the specified [`SectionStyle`], falling back to the other style if no
/// entries are found.
fn parse_raises(content: &str, style: Option<SectionStyle>) -> Vec<QualifiedName<'_>> {
    match style {
        Some(SectionStyle::Google) => parse_raises_google(content),
        Some(SectionStyle::Numpy) => parse_raises_numpy(content),
        None => {
            let entries = parse_raises_google(content);
            if entries.is_empty() {
                parse_raises_numpy(content)
            } else {
                entries
            }

Review comment (suggestion):
```suggestion
                                range: TextRange::at(
                                    content_start + param_start,
                                    param_part_trimmed.text_len(),
                                ),
```

Code after:
                .chars()
                .next()
                .is_some_and(|first_char| !first_char.is_whitespace())
            {
                if let Some(before_colon) = entry.split(':').next() {
                    let param_line = before_colon.trim_end();

                    // Split on commas to handle comma-separated parameters
                    let mut current_offset = TextSize::from(0);
                    for param_part in param_line.split(',') {
                        let param_part_trimmed = param_part.trim();
                        let param_name = param_part_trimmed.trim_start_matches('*');
                        if is_identifier(param_name) {
                            // Calculate the position of this specific parameter part within the line
                            // Account for leading whitespace that gets trimmed
                            let param_start_in_line = current_offset
                                + (param_part.text_len() - param_part_trimmed.text_len());
                            let param_start =
                                line_start + indentation.text_len() + param_start_in_line;

                            entries.push(ParameterEntry {
                                name: param_name,
                                range: TextRange::at(
                                    content_start + param_start,
                                    param_part_trimmed.text_len(),
                                ),
                            });
                        }
                        // Update offset for next iteration: add the part length plus comma length
                        current_offset = current_offset + param_part.text_len() + ','.text_len();
                    }
                }
            }
        }
    }
    entries
}

/// Parse the entries in a "Raises" section of a docstring.
///
/// Attempts to parse using the specified [`SectionStyle`], falling back to the other style if no
/// entries are found.
fn parse_raises(content: &str, style: Option<SectionStyle>) -> Vec<QualifiedName<'_>> {
    match style {
        Some(SectionStyle::Google) => parse_raises_google(content),
        Some(SectionStyle::Numpy) => parse_raises_numpy(content),
        None => {
            let entries = parse_raises_google(content);
            if entries.is_empty() {
                parse_raises_numpy(content)
            } else {