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

Code before:
/// a : int
///     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
                    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,
                                ),
                            });
                        }
                    }
                }
            }

Review comment (suggestion):
I suspect that the `trim_end` call here now is unnecessary, given that you also trim each parameter part.

Code after:
/// a : int
///     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();
                    }