use crate::cursor::LineCursor;
use crate::cursor::indent_len;
use crate::parse::numpy::kind::NumPySectionKind;
use crate::parse::utils::build_paragraph;
use crate::parse::utils::build_text_block;
use crate::parse::utils::extend_text_block;
use crate::parse::utils::find_term_colon;
use crate::parse::utils::marker_syntax_elements;
use crate::parse::utils::missing_text_block;
use crate::parse::utils::process_reference_line;
use crate::parse::utils::scan_type_markers;
use crate::parse::utils::text_block_single;
use crate::parse::utils::try_parse_bracket_entry;
use crate::parse::utils::try_parse_directive;
use crate::syntax::Parsed;
use crate::syntax::SyntaxElement;
use crate::syntax::SyntaxKind;
use crate::syntax::SyntaxNode;
use crate::syntax::SyntaxToken;
use crate::text::TextRange;
fn is_underline(trimmed: &str) -> bool {
!trimmed.is_empty() && trimmed.bytes().all(|b| b == b'-')
}
fn try_detect_header(cursor: &LineCursor) -> Option<SectionHeaderInfo> {
let header_trimmed = cursor.current_trimmed();
if header_trimmed.is_empty() {
return None;
}
if cursor.line + 1 >= cursor.total_lines() {
return None;
}
let underline_line = cursor.line_text(cursor.line + 1);
let underline_trimmed = underline_line.trim();
if !is_underline(underline_trimmed) {
return None;
}
let header_col = cursor.current_indent();
let underline_col = indent_len(underline_line);
let normalized = header_trimmed.to_ascii_lowercase();
let kind = NumPySectionKind::from_name(&normalized);
Some(SectionHeaderInfo {
range: cursor.make_range(
cursor.line,
header_col,
cursor.line + 1,
underline_col + underline_trimmed.len(),
),
kind,
name: cursor.make_line_range(cursor.line, header_col, header_trimmed.len()),
underline: cursor.make_line_range(cursor.line + 1, underline_col, underline_trimmed.len()),
})
}
struct SectionHeaderInfo {
range: TextRange,
kind: NumPySectionKind,
name: TextRange,
underline: TextRange,
}
struct ParamHeaderParts {
names: Vec<(SyntaxKind, TextRange)>,
open_bracket: Option<TextRange>,
close_bracket: Option<TextRange>,
colon: Option<TextRange>,
param_type: Option<TextRange>,
type_commas: Vec<TextRange>,
markers: Vec<SyntaxElement>,
first_description: Option<TextRange>,
}
fn parse_name_and_type(text: &str, line_idx: usize, col_base: usize, cursor: &LineCursor) -> ParamHeaderParts {
if let Some(result) = try_parse_google_style_entry(text, line_idx, col_base, cursor) {
return result;
}
let Some(colon_pos) = find_term_colon(text) else {
let names = parse_name_list(text, line_idx, col_base, cursor);
return ParamHeaderParts {
names,
open_bracket: None,
close_bracket: None,
colon: None,
param_type: None,
type_commas: Vec::new(),
markers: Vec::new(),
first_description: None,
};
};
let name_str = text[..colon_pos].trim_end();
let colon_col = col_base + colon_pos;
let colon_span = Some(cursor.make_line_range(line_idx, colon_col, 1));
let names = parse_name_list(name_str, line_idx, col_base, cursor);
let after_colon = &text[colon_pos + 1..];
let after_trimmed = after_colon.trim();
if after_trimmed.is_empty() {
let missing_type = cursor.make_line_range(line_idx, colon_col + 1, 0);
return ParamHeaderParts {
names,
open_bracket: None,
close_bracket: None,
colon: colon_span,
param_type: Some(missing_type),
type_commas: Vec::new(),
markers: Vec::new(),
first_description: None,
};
}
let type_abs_start = cursor.substr_offset(after_trimmed);
let scanned = scan_type_markers(after_trimmed);
let param_type = if scanned.clean_type.is_empty() {
None
} else {
Some(TextRange::from_offset_len(type_abs_start, scanned.clean_type.len()))
};
let type_commas: Vec<TextRange> = scanned
.commas
.into_iter()
.map(|rel| TextRange::from_offset_len(type_abs_start + rel, 1))
.collect();
ParamHeaderParts {
names,
open_bracket: None,
close_bracket: None,
colon: colon_span,
param_type,
type_commas,
markers: marker_syntax_elements(&scanned.markers, type_abs_start),
first_description: None,
}
}
fn try_parse_google_style_entry(
text: &str,
line_idx: usize,
col_base: usize,
cursor: &LineCursor,
) -> Option<ParamHeaderParts> {
let entry = try_parse_bracket_entry(text)?;
let names = parse_name_list(entry.name, line_idx, col_base, cursor);
let open_bracket = Some(cursor.make_line_range(line_idx, col_base + entry.open_bracket, 1));
let close_bracket = Some(cursor.make_line_range(line_idx, col_base + entry.close_bracket, 1));
let param_type = if !entry.clean_type.is_empty() {
Some(cursor.make_line_range(line_idx, col_base + entry.type_offset, entry.clean_type.len()))
} else {
None
};
let type_commas = entry
.commas
.iter()
.map(|&c| cursor.make_line_range(line_idx, col_base + c, 1))
.collect();
let type_base = cursor
.make_line_range(line_idx, col_base + entry.type_offset, 0)
.start()
.raw() as usize;
let markers = marker_syntax_elements(&entry.markers, type_base);
let colon = entry.colon.map(|c| cursor.make_line_range(line_idx, col_base + c, 1));
let first_description = entry
.description_offset
.map(|d| cursor.make_line_range(line_idx, col_base + d, entry.description.unwrap().len()));
Some(ParamHeaderParts {
names,
open_bracket,
close_bracket,
colon,
param_type,
type_commas,
markers,
first_description,
})
}
fn parse_name_list(text: &str, line_idx: usize, col_base: usize, cursor: &LineCursor) -> Vec<(SyntaxKind, TextRange)> {
let mut names = Vec::new();
let mut byte_pos = 0usize;
let parts: Vec<&str> = text.split(',').collect();
for (i, part) in parts.iter().enumerate() {
let leading = part.len() - part.trim_start().len();
let trimmed = part.trim();
if !trimmed.is_empty() {
let name_col = col_base + byte_pos + leading;
names.push((
SyntaxKind::NAME,
cursor.make_line_range(line_idx, name_col, trimmed.len()),
));
}
if i + 1 < parts.len() {
let comma_col = col_base + byte_pos + part.len();
names.push((SyntaxKind::COMMA, cursor.make_line_range(line_idx, comma_col, 1)));
}
byte_pos += part.len() + 1;
}
names
}
fn build_section_header_node(info: &SectionHeaderInfo) -> SyntaxNode {
let children = vec![
SyntaxElement::Token(SyntaxToken::new(SyntaxKind::NAME, info.name)),
SyntaxElement::Token(SyntaxToken::new(SyntaxKind::UNDERLINE, info.underline)),
];
SyntaxNode::new(SyntaxKind::SECTION_HEADER, info.range, children)
}
fn build_parameter_node(parts: &ParamHeaderParts, range: TextRange) -> SyntaxNode {
let mut children = Vec::new();
for (kind, range) in &parts.names {
children.push(SyntaxElement::Token(SyntaxToken::new(*kind, *range)));
}
if let Some(ob) = parts.open_bracket {
children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::OPEN_BRACKET, ob)));
}
if let Some(colon) = parts.colon {
children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::COLON, colon)));
}
if let Some(t) = parts.param_type {
children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::TYPE, t)));
} else if parts.colon.is_some() || parts.open_bracket.is_some() {
let missing_pos = parts
.open_bracket
.map(|ob| ob.end())
.unwrap_or_else(|| parts.colon.unwrap().end());
children.push(SyntaxElement::Token(SyntaxToken::new(
SyntaxKind::TYPE,
TextRange::new(missing_pos, missing_pos),
)));
}
for comma in &parts.type_commas {
children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::COMMA, *comma)));
}
children.extend(parts.markers.iter().cloned());
if let Some(cb) = parts.close_bracket {
children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::CLOSE_BRACKET, cb)));
}
if let Some(desc) = parts.first_description {
children.push(SyntaxElement::Node(text_block_single(SyntaxKind::DESCRIPTION, desc)));
}
children.sort_by_key(|c| (c.range().start(), c.range().end()));
SyntaxNode::new(SyntaxKind::ENTRY, range, children)
}
fn build_returns_node(
name: Option<TextRange>,
colon: Option<TextRange>,
return_type: Option<TextRange>,
range: TextRange,
) -> SyntaxNode {
let mut children = Vec::new();
if let Some(n) = name {
children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::NAME, n)));
}
if let Some(c) = colon {
children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::COLON, c)));
}
if let Some(rt) = return_type {
children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::TYPE, rt)));
} else if let Some(c) = colon {
let missing_pos = c.end();
children.push(SyntaxElement::Token(SyntaxToken::new(
SyntaxKind::TYPE,
TextRange::new(missing_pos, missing_pos),
)));
}
SyntaxNode::new(SyntaxKind::ENTRY, range, children)
}
fn build_yields_node(
name: Option<TextRange>,
colon: Option<TextRange>,
return_type: Option<TextRange>,
range: TextRange,
) -> SyntaxNode {
let mut children = Vec::new();
if let Some(n) = name {
children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::NAME, n)));
}
if let Some(c) = colon {
children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::COLON, c)));
}
if let Some(rt) = return_type {
children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::TYPE, rt)));
} else if let Some(c) = colon {
let missing_pos = c.end();
children.push(SyntaxElement::Token(SyntaxToken::new(
SyntaxKind::TYPE,
TextRange::new(missing_pos, missing_pos),
)));
}
SyntaxNode::new(SyntaxKind::ENTRY, range, children)
}
fn build_exception_node(
exc_type: TextRange,
colon: Option<TextRange>,
first_desc: Option<TextRange>,
range: TextRange,
) -> SyntaxNode {
let mut children = Vec::new();
children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::TYPE, exc_type)));
if let Some(c) = colon {
children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::COLON, c)));
}
if let Some(d) = first_desc {
children.push(SyntaxElement::Node(text_block_single(SyntaxKind::DESCRIPTION, d)));
} else if let Some(c) = colon {
children.push(SyntaxElement::Node(missing_text_block(
SyntaxKind::DESCRIPTION,
c.end(),
)));
}
SyntaxNode::new(SyntaxKind::ENTRY, range, children)
}
fn build_warning_node(
warn_type: TextRange,
colon: Option<TextRange>,
first_desc: Option<TextRange>,
range: TextRange,
) -> SyntaxNode {
let mut children = Vec::new();
children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::TYPE, warn_type)));
if let Some(c) = colon {
children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::COLON, c)));
}
if let Some(d) = first_desc {
children.push(SyntaxElement::Node(text_block_single(SyntaxKind::DESCRIPTION, d)));
} else if let Some(c) = colon {
children.push(SyntaxElement::Node(missing_text_block(
SyntaxKind::DESCRIPTION,
c.end(),
)));
}
SyntaxNode::new(SyntaxKind::ENTRY, range, children)
}
fn build_see_also_node(
names_str: &str,
names_line: usize,
names_col: usize,
colon: Option<TextRange>,
description: Option<TextRange>,
range: TextRange,
cursor: &LineCursor,
) -> SyntaxNode {
let mut children = Vec::new();
let names = parse_name_list(names_str, names_line, names_col, cursor);
for (kind, range) in &names {
children.push(SyntaxElement::Token(SyntaxToken::new(*kind, *range)));
}
if let Some(c) = colon {
children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::COLON, c)));
}
if let Some(d) = description {
children.push(SyntaxElement::Node(text_block_single(SyntaxKind::DESCRIPTION, d)));
} else if let Some(c) = colon {
children.push(SyntaxElement::Node(missing_text_block(
SyntaxKind::DESCRIPTION,
c.end(),
)));
}
SyntaxNode::new(SyntaxKind::ENTRY, range, children)
}
fn build_method_node(
name: TextRange,
colon: Option<TextRange>,
first_desc: Option<TextRange>,
range: TextRange,
) -> SyntaxNode {
let mut children = Vec::new();
children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::NAME, name)));
if let Some(c) = colon {
children.push(SyntaxElement::Token(SyntaxToken::new(SyntaxKind::COLON, c)));
}
if let Some(d) = first_desc {
children.push(SyntaxElement::Node(text_block_single(SyntaxKind::DESCRIPTION, d)));
} else if let Some(c) = colon {
children.push(SyntaxElement::Node(missing_text_block(
SyntaxKind::DESCRIPTION,
c.end(),
)));
}
SyntaxNode::new(SyntaxKind::ENTRY, range, children)
}
fn extend_last_node_description(nodes: &mut [SyntaxElement], cont: TextRange) {
if let Some(SyntaxElement::Node(node)) = nodes.last_mut() {
let mut found_desc = false;
for child in node.children_mut() {
if let SyntaxElement::Node(n) = child {
if n.kind() == SyntaxKind::DESCRIPTION {
if n.range().is_empty() {
*n = text_block_single(SyntaxKind::DESCRIPTION, cont);
} else {
extend_text_block(n, cont);
}
found_desc = true;
break;
}
}
}
if !found_desc {
node.push_child(SyntaxElement::Node(text_block_single(SyntaxKind::DESCRIPTION, cont)));
}
node.extend_range_to(cont.end());
}
}
fn process_parameter_line(cursor: &LineCursor, nodes: &mut Vec<SyntaxElement>, entry_indent: &mut Option<usize>) {
let indent_cols = cursor.current_indent_columns();
if let Some(base) = *entry_indent {
if indent_cols > base {
extend_last_node_description(nodes, cursor.current_trimmed_range());
return;
}
}
if entry_indent.is_none() {
*entry_indent = Some(indent_cols);
}
let col = cursor.current_indent();
let trimmed = cursor.current_trimmed();
let parts = parse_name_and_type(trimmed, cursor.line, col, cursor);
let entry_range = cursor.current_trimmed_range();
nodes.push(SyntaxElement::Node(build_parameter_node(&parts, entry_range)));
}
fn process_returns_line(cursor: &LineCursor, nodes: &mut Vec<SyntaxElement>, entry_indent: &mut Option<usize>) {
let indent_cols = cursor.current_indent_columns();
if let Some(base) = *entry_indent {
if indent_cols > base {
extend_last_node_description(nodes, cursor.current_trimmed_range());
return;
}
}
if entry_indent.is_none() {
*entry_indent = Some(indent_cols);
}
let col = cursor.current_indent();
let trimmed = cursor.current_trimmed();
let (name, colon, return_type) = if let Some(colon_pos) = find_term_colon(trimmed) {
let n = trimmed[..colon_pos].trim_end();
let after_colon = &trimmed[colon_pos + 1..];
let t = after_colon.trim();
let ws_after = after_colon.len() - after_colon.trim_start().len();
let type_col = col + colon_pos + 1 + ws_after;
(
Some(cursor.make_line_range(cursor.line, col, n.len())),
Some(cursor.make_line_range(cursor.line, col + colon_pos, 1)),
if t.is_empty() {
None
} else {
Some(cursor.make_line_range(cursor.line, type_col, t.len()))
},
)
} else {
(None, None, Some(cursor.current_trimmed_range()))
};
let entry_range = cursor.current_trimmed_range();
nodes.push(SyntaxElement::Node(build_returns_node(
name,
colon,
return_type,
entry_range,
)));
}
fn process_yields_line(cursor: &LineCursor, nodes: &mut Vec<SyntaxElement>, entry_indent: &mut Option<usize>) {
let indent_cols = cursor.current_indent_columns();
if let Some(base) = *entry_indent {
if indent_cols > base {
extend_last_node_description(nodes, cursor.current_trimmed_range());
return;
}
}
if entry_indent.is_none() {
*entry_indent = Some(indent_cols);
}
let col = cursor.current_indent();
let trimmed = cursor.current_trimmed();
let (name, colon, return_type) = if let Some(colon_pos) = find_term_colon(trimmed) {
let n = trimmed[..colon_pos].trim_end();
let after_colon = &trimmed[colon_pos + 1..];
let t = after_colon.trim();
let ws_after = after_colon.len() - after_colon.trim_start().len();
let type_col = col + colon_pos + 1 + ws_after;
(
Some(cursor.make_line_range(cursor.line, col, n.len())),
Some(cursor.make_line_range(cursor.line, col + colon_pos, 1)),
if t.is_empty() {
None
} else {
Some(cursor.make_line_range(cursor.line, type_col, t.len()))
},
)
} else {
(None, None, Some(cursor.current_trimmed_range()))
};
let entry_range = cursor.current_trimmed_range();
nodes.push(SyntaxElement::Node(build_yields_node(
name,
colon,
return_type,
entry_range,
)));
}
fn process_raises_line(cursor: &LineCursor, nodes: &mut Vec<SyntaxElement>, entry_indent: &mut Option<usize>) {
let indent_cols = cursor.current_indent_columns();
if let Some(base) = *entry_indent {
if indent_cols > base {
extend_last_node_description(nodes, cursor.current_trimmed_range());
return;
}
}
if entry_indent.is_none() {
*entry_indent = Some(indent_cols);
}
let col = cursor.current_indent();
let trimmed = cursor.current_trimmed();
let (exc_type, colon, first_desc) = if let Some(colon_pos) = find_term_colon(trimmed) {
let type_str = trimmed[..colon_pos].trim_end();
let after_colon = &trimmed[colon_pos + 1..];
let desc_str = after_colon.trim();
let ws_after = after_colon.len() - after_colon.trim_start().len();
let desc_col = col + colon_pos + 1 + ws_after;
(
cursor.make_line_range(cursor.line, col, type_str.len()),
Some(cursor.make_line_range(cursor.line, col + colon_pos, 1)),
if desc_str.is_empty() {
None
} else {
Some(cursor.make_line_range(cursor.line, desc_col, desc_str.len()))
},
)
} else {
(cursor.current_trimmed_range(), None, None)
};
let entry_range = cursor.current_trimmed_range();
nodes.push(SyntaxElement::Node(build_exception_node(
exc_type,
colon,
first_desc,
entry_range,
)));
}
fn process_warning_line(cursor: &LineCursor, nodes: &mut Vec<SyntaxElement>, entry_indent: &mut Option<usize>) {
let indent_cols = cursor.current_indent_columns();
if let Some(base) = *entry_indent {
if indent_cols > base {
extend_last_node_description(nodes, cursor.current_trimmed_range());
return;
}
}
if entry_indent.is_none() {
*entry_indent = Some(indent_cols);
}
let col = cursor.current_indent();
let trimmed = cursor.current_trimmed();
let (warn_type, colon, first_desc) = if let Some(colon_pos) = find_term_colon(trimmed) {
let type_str = trimmed[..colon_pos].trim_end();
let after_colon = &trimmed[colon_pos + 1..];
let desc_str = after_colon.trim();
let ws_after = after_colon.len() - after_colon.trim_start().len();
let desc_col = col + colon_pos + 1 + ws_after;
(
cursor.make_line_range(cursor.line, col, type_str.len()),
Some(cursor.make_line_range(cursor.line, col + colon_pos, 1)),
if desc_str.is_empty() {
None
} else {
Some(cursor.make_line_range(cursor.line, desc_col, desc_str.len()))
},
)
} else {
(cursor.current_trimmed_range(), None, None)
};
let entry_range = cursor.current_trimmed_range();
nodes.push(SyntaxElement::Node(build_warning_node(
warn_type,
colon,
first_desc,
entry_range,
)));
}
fn process_see_also_line(cursor: &LineCursor, nodes: &mut Vec<SyntaxElement>, entry_indent: &mut Option<usize>) {
let indent_cols = cursor.current_indent_columns();
if let Some(base) = *entry_indent {
if indent_cols > base {
extend_last_node_description(nodes, cursor.current_trimmed_range());
return;
}
}
if entry_indent.is_none() {
*entry_indent = Some(indent_cols);
}
let col = cursor.current_indent();
let trimmed = cursor.current_trimmed();
let (names_str, colon, description) = if let Some(colon_pos) = find_term_colon(trimmed) {
let after_colon = &trimmed[colon_pos + 1..];
let desc_text = after_colon.trim();
let ws_after = after_colon.len() - after_colon.trim_start().len();
let desc_col = col + colon_pos + 1 + ws_after;
(
trimmed[..colon_pos].trim_end(),
Some(cursor.make_line_range(cursor.line, col + colon_pos, 1)),
if desc_text.is_empty() {
None
} else {
Some(cursor.make_line_range(cursor.line, desc_col, desc_text.len()))
},
)
} else {
(trimmed, None, None)
};
let entry_range = cursor.make_line_range(cursor.line, col, trimmed.len());
nodes.push(SyntaxElement::Node(build_see_also_node(
names_str,
cursor.line,
col,
colon,
description,
entry_range,
cursor,
)));
}
fn process_attribute_line(cursor: &LineCursor, nodes: &mut Vec<SyntaxElement>, entry_indent: &mut Option<usize>) {
let indent_cols = cursor.current_indent_columns();
if let Some(base) = *entry_indent {
if indent_cols > base {
extend_last_node_description(nodes, cursor.current_trimmed_range());
return;
}
}
if entry_indent.is_none() {
*entry_indent = Some(indent_cols);
}
let col = cursor.current_indent();
let trimmed = cursor.current_trimmed();
let parts = parse_name_and_type(trimmed, cursor.line, col, cursor);
let entry_range = cursor.current_trimmed_range();
nodes.push(SyntaxElement::Node(build_parameter_node(&parts, entry_range)));
}
fn process_method_line(cursor: &LineCursor, nodes: &mut Vec<SyntaxElement>, entry_indent: &mut Option<usize>) {
let indent_cols = cursor.current_indent_columns();
if let Some(base) = *entry_indent {
if indent_cols > base {
extend_last_node_description(nodes, cursor.current_trimmed_range());
return;
}
}
if entry_indent.is_none() {
*entry_indent = Some(indent_cols);
}
let col = cursor.current_indent();
let trimmed = cursor.current_trimmed();
let (name, colon, first_desc) = if let Some(colon_pos) = find_term_colon(trimmed) {
let n = trimmed[..colon_pos].trim_end();
let after_colon = &trimmed[colon_pos + 1..];
let desc_str = after_colon.trim();
let ws_after = after_colon.len() - after_colon.trim_start().len();
let desc_col = col + colon_pos + 1 + ws_after;
(
cursor.make_line_range(cursor.line, col, n.len()),
Some(cursor.make_line_range(cursor.line, col + colon_pos, 1)),
if desc_str.is_empty() {
None
} else {
Some(cursor.make_line_range(cursor.line, desc_col, desc_str.len()))
},
)
} else {
(cursor.current_trimmed_range(), None, None)
};
let entry_range = cursor.current_trimmed_range();
nodes.push(SyntaxElement::Node(build_method_node(
name,
colon,
first_desc,
entry_range,
)));
}
enum SectionBody {
Parameters(Vec<SyntaxElement>),
Returns(Vec<SyntaxElement>),
Yields(Vec<SyntaxElement>),
Raises(Vec<SyntaxElement>),
Warns(Vec<SyntaxElement>),
SeeAlso(Vec<SyntaxElement>),
References(Vec<SyntaxElement>),
Attributes(Vec<SyntaxElement>),
Methods(Vec<SyntaxElement>),
FreeText(Option<TextRange>),
}
impl SectionBody {
fn new(kind: NumPySectionKind) -> Self {
match kind {
NumPySectionKind::Parameters => Self::Parameters(Vec::new()),
NumPySectionKind::OtherParameters => Self::Parameters(Vec::new()),
NumPySectionKind::Receives => Self::Parameters(Vec::new()),
NumPySectionKind::KeywordParameters => Self::Parameters(Vec::new()),
NumPySectionKind::Returns => Self::Returns(Vec::new()),
NumPySectionKind::Yields => Self::Yields(Vec::new()),
NumPySectionKind::Raises => Self::Raises(Vec::new()),
NumPySectionKind::Warns => Self::Warns(Vec::new()),
NumPySectionKind::SeeAlso => Self::SeeAlso(Vec::new()),
NumPySectionKind::References => Self::References(Vec::new()),
NumPySectionKind::Attributes => Self::Attributes(Vec::new()),
NumPySectionKind::Methods => Self::Methods(Vec::new()),
_ => Self::FreeText(None),
}
}
fn process_line(&mut self, cursor: &LineCursor, entry_indent: &mut Option<usize>) {
match self {
Self::Parameters(nodes) => process_parameter_line(cursor, nodes, entry_indent),
Self::Returns(nodes) => process_returns_line(cursor, nodes, entry_indent),
Self::Yields(nodes) => process_yields_line(cursor, nodes, entry_indent),
Self::Raises(nodes) => process_raises_line(cursor, nodes, entry_indent),
Self::Warns(nodes) => process_warning_line(cursor, nodes, entry_indent),
Self::SeeAlso(nodes) => process_see_also_line(cursor, nodes, entry_indent),
Self::References(nodes) => process_reference_line(cursor, nodes, entry_indent),
Self::Attributes(nodes) => process_attribute_line(cursor, nodes, entry_indent),
Self::Methods(nodes) => process_method_line(cursor, nodes, entry_indent),
Self::FreeText(range) => {
let r = cursor.current_trimmed_range();
match range {
Some(existing) => existing.extend(r),
None => *range = Some(r),
}
}
}
}
fn into_children(self, source: &str) -> Vec<SyntaxElement> {
match self {
Self::Parameters(nodes) => nodes,
Self::Returns(nodes) => nodes,
Self::Yields(nodes) => nodes,
Self::Raises(nodes) => nodes,
Self::Warns(nodes) => nodes,
Self::SeeAlso(nodes) => nodes,
Self::References(nodes) => nodes,
Self::Attributes(nodes) => nodes,
Self::Methods(nodes) => nodes,
Self::FreeText(range) => {
if let Some(r) = range {
vec![SyntaxElement::Node(build_text_block(
SyntaxKind::DESCRIPTION,
r,
source,
))]
} else {
vec![]
}
}
}
}
}
pub fn parse_numpy(input: &str) -> Parsed {
let mut cursor = LineCursor::new(input);
let mut root_children: Vec<SyntaxElement> = Vec::new();
cursor.skip_blanks();
if cursor.is_eof() {
let mut root = SyntaxNode::new(SyntaxKind::DOCUMENT, cursor.full_range(), root_children);
crate::parse::trivia::attach_trivia(&mut root, input);
return Parsed::new(input.to_string(), root, crate::parse::Style::NumPy);
}
if try_detect_header(&cursor).is_none() {
let trimmed = cursor.current_trimmed();
if !trimmed.is_empty() {
let start_line = cursor.line;
let start_col = cursor.current_indent();
let mut last_line = start_line;
while !cursor.is_eof() {
if try_detect_header(&cursor).is_some() {
break;
}
let t = cursor.current_trimmed();
if t.is_empty() {
break;
}
last_line = cursor.line;
cursor.advance();
}
let last_text = cursor.line_text(last_line);
let last_col = indent_len(last_text) + last_text.trim().len();
let range = cursor.make_range(start_line, start_col, last_line, last_col);
if !range.is_empty() {
root_children.push(SyntaxElement::Node(build_text_block(SyntaxKind::SUMMARY, range, input)));
}
}
}
cursor.skip_blanks();
while !cursor.is_eof()
&& try_detect_header(&cursor).is_none()
&& let Some(node) = try_parse_directive(&mut cursor)
{
root_children.push(SyntaxElement::Node(node));
cursor.skip_blanks();
}
if !cursor.is_eof() && try_detect_header(&cursor).is_none() {
let start_line = cursor.line;
let mut last_non_empty_line = cursor.line;
let mut has_content = false;
while !cursor.is_eof() {
if try_detect_header(&cursor).is_some() {
break;
}
let t = cursor.current_trimmed();
if !t.is_empty() {
last_non_empty_line = cursor.line;
has_content = true;
}
cursor.advance();
}
if has_content {
let first_line = cursor.line_text(start_line);
let first_col = indent_len(first_line);
let last_line = cursor.line_text(last_non_empty_line);
let last_col = indent_len(last_line) + last_line.trim().len();
let range = cursor.make_range(start_line, first_col, last_non_empty_line, last_col);
root_children.push(SyntaxElement::Node(build_text_block(
SyntaxKind::EXTENDED_SUMMARY,
range,
input,
)));
}
}
let mut current_header: Option<SectionHeaderInfo> = None;
let mut current_body: Option<SectionBody> = None;
let mut entry_indent: Option<usize> = None;
let mut para_first: Option<usize> = None;
let mut para_last: usize = 0;
while !cursor.is_eof() {
if cursor.current_trimmed().is_empty() {
if let Some(first) = para_first.take() {
root_children.push(SyntaxElement::Node(build_paragraph(&cursor, first, para_last)));
}
cursor.advance();
continue;
}
if let Some(header_info) = try_detect_header(&cursor) {
if let Some(prev_header) = current_header.take() {
let section_node = flush_section(&cursor, prev_header, current_body.take().unwrap());
root_children.push(SyntaxElement::Node(section_node));
}
if let Some(first) = para_first.take() {
root_children.push(SyntaxElement::Node(build_paragraph(&cursor, first, para_last)));
}
current_body = Some(SectionBody::new(header_info.kind));
current_header = Some(header_info);
entry_indent = None;
cursor.line += 2; continue;
}
if let Some(ref mut body) = current_body {
body.process_line(&cursor, &mut entry_indent);
} else {
if para_first.is_none() {
para_first = Some(cursor.line);
}
para_last = cursor.line;
}
cursor.advance();
}
if let Some(header) = current_header.take() {
let section_node = flush_section(&cursor, header, current_body.take().unwrap());
root_children.push(SyntaxElement::Node(section_node));
}
if let Some(first) = para_first.take() {
root_children.push(SyntaxElement::Node(build_paragraph(&cursor, first, para_last)));
}
let mut root = SyntaxNode::new(SyntaxKind::DOCUMENT, cursor.full_range(), root_children);
crate::parse::trivia::attach_trivia(&mut root, input);
Parsed::new(input.to_string(), root, crate::parse::Style::NumPy)
}
fn flush_section(cursor: &LineCursor, header: SectionHeaderInfo, body: SectionBody) -> SyntaxNode {
let header_start = header.range.start().raw() as usize;
let section_range = cursor.span_back_from_cursor(header_start);
let mut section_children = Vec::new();
section_children.push(SyntaxElement::Node(build_section_header_node(&header)));
section_children.extend(body.into_children(cursor.source()));
SyntaxNode::new(SyntaxKind::SECTION, section_range, section_children)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_underline() {
assert!(is_underline("----------"));
assert!(is_underline("---"));
assert!(!is_underline(""));
assert!(!is_underline("not dashes"));
assert!(!is_underline("---x---"));
}
#[test]
fn test_try_detect_header() {
let c1 = LineCursor::new("Parameters\n----------");
assert!(try_detect_header(&c1).is_some());
assert_eq!(try_detect_header(&c1).unwrap().kind, NumPySectionKind::Parameters);
let c2 = LineCursor::new("just text\nmore text");
assert!(try_detect_header(&c2).is_none());
let c3 = LineCursor::new("\n----------");
assert!(try_detect_header(&c3).is_none());
let c4 = LineCursor::new("Only one line");
assert!(try_detect_header(&c4).is_none());
let mut c5 = LineCursor::new("Parameters\n----------\nx : int\nReturns\n-------");
assert!(try_detect_header(&c5).is_some());
c5.line = 3;
assert!(try_detect_header(&c5).is_some());
assert_eq!(try_detect_header(&c5).unwrap().kind, NumPySectionKind::Returns);
}
#[test]
fn test_parse_name_and_type_basic() {
let src = "x : int";
let cursor = LineCursor::new(src);
let p = parse_name_and_type(src, 0, 0, &cursor);
assert_eq!(p.names[0].1.source_text(src), "x");
assert!(p.colon.is_some());
assert_eq!(p.param_type.unwrap().source_text(src), "int");
assert!(p.markers.is_empty());
}
#[test]
fn test_parse_name_and_type_optional() {
let src = "x : int, optional";
let cursor = LineCursor::new(src);
let p = parse_name_and_type(src, 0, 0, &cursor);
assert_eq!(p.names[0].1.source_text(src), "x");
assert!(p.colon.is_some());
assert_eq!(p.param_type.unwrap().source_text(src), "int");
assert_eq!(p.markers.len(), 1);
assert_eq!(p.markers[0].kind(), SyntaxKind::OPTIONAL);
}
#[test]
fn test_parse_name_and_type_complex() {
let src = "x : Dict[str, int], optional";
let cursor = LineCursor::new(src);
let p = parse_name_and_type(src, 0, 0, &cursor);
assert!(p.colon.is_some());
assert_eq!(p.param_type.unwrap().source_text(src), "Dict[str, int]");
assert_eq!(p.markers.len(), 1);
assert_eq!(p.markers[0].kind(), SyntaxKind::OPTIONAL);
}
#[test]
fn test_basic_parse() {
let input = "Summary.\n\nParameters\n----------\nx : int\n The value.\n";
let parsed = parse_numpy(input);
let root = parsed.root();
assert_eq!(root.kind(), SyntaxKind::DOCUMENT);
let summary =
crate::parse::text_block::TextBlock::cast(&parsed, root.find_node(SyntaxKind::SUMMARY).unwrap()).unwrap();
assert_eq!(summary.text(), "Summary.");
let sections: Vec<_> = root.nodes(SyntaxKind::SECTION).collect();
assert_eq!(sections.len(), 1);
}
#[test]
fn test_google_style_entry_children_in_source_order() {
let input = "Summary.\n\nParameters\n----------\nname (str): The name.\n";
let parsed = parse_numpy(input);
let section = parsed.root().find_node(SyntaxKind::SECTION).unwrap();
let param = section.find_node(SyntaxKind::ENTRY).unwrap();
let mut last_start = None;
for child in param.children() {
assert!(
last_start.is_none_or(|prev| prev <= child.range().start()),
"children out of source order: {:?}",
param
);
last_start = Some(child.range().start());
}
let kinds: Vec<SyntaxKind> = param
.children()
.iter()
.filter(|c| !c.kind().is_trivia())
.map(|c| c.kind())
.collect();
assert_eq!(
kinds,
vec![
SyntaxKind::NAME,
SyntaxKind::OPEN_BRACKET,
SyntaxKind::TYPE,
SyntaxKind::CLOSE_BRACKET,
SyntaxKind::COLON,
SyntaxKind::DESCRIPTION,
]
);
}
#[test]
fn test_google_style_entry_missing_type_anchored_after_open_bracket() {
let input = "Summary.\n\nParameters\n----------\nname (): The name.\n";
let parsed = parse_numpy(input);
let section = parsed.root().find_node(SyntaxKind::SECTION).unwrap();
let param = section.find_node(SyntaxKind::ENTRY).unwrap();
let open = param.find_token(SyntaxKind::OPEN_BRACKET).unwrap();
let missing = param.find_missing(SyntaxKind::TYPE).unwrap();
assert_eq!(missing.range().start(), open.range().end());
}
}