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

Code before:
        return entries;
    };
    let indentation = &dashes[..dashes.len() - dashes.trim_start().len()];

    let mut current_pos = content.full_line_end(dashes.text_len());
    for potential in lines {
        let line_start = current_pos;
        current_pos = content.full_line_end(line_start);

        if let Some(entry) = potential.strip_prefix(indentation) {
            if entry
                .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
                            let param_start_in_line =
                                param_line.find(param_part_trimmed).unwrap_or(0);
                            let param_start = line_start
                                + indentation.text_len()
                                + TextSize::from(u32::try_from(param_start_in_line).unwrap_or(0));
                            let param_end = param_start
                                + TextSize::from(
                                    u32::try_from(param_part_trimmed.len()).unwrap_or(0),
                                );

                            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.

Review comment (suggestion):
Let's just make this an `unwrap` too. We're searching for a substring in a line we just split, so it should be okay. And if we end up unwrapping to zero  somehow, the rendered range could look really bad. It might be worth a short safety comment to that effect, e.g.:

> Safety: searching for a substring we just split above

Ideally I feel like we'd just update the `current_pos` or a similar offset value as we go instead of `find`ing after the fact, but I think that's hard to do with `split`, so this seems okay.

Code after:
///     The first number to add.
/// b : int
///     The second number to add.
/// ```
fn parse_parameters_numpy(content: &str, content_start: TextSize) -> Vec<ParameterEntry<'_>> {
    let mut entries: Vec<ParameterEntry> = Vec::new();
    let mut lines = content.lines();
    let Some(dashes) = lines.next() else {
        return entries;
    };
    let indentation = &dashes[..dashes.len() - dashes.trim_start().len()];

    let mut current_pos = content.full_line_end(dashes.text_len());
    for potential in lines {
        let line_start = current_pos;
        current_pos = content.full_line_end(line_start);

        if let Some(entry) = potential.strip_prefix(indentation) {
            if entry
                .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();
                    }
                }