MCPcopy Create free account
hub / github.com/astral-sh/ruff / parse_cells_unchecked

Function parse_cells_unchecked

crates/ruff_python_parser/src/lib.rs:313–383  ·  view source on GitHub ↗

Parses each `range` of `source` as an independent module and concatenates the results into a single [`Parsed `] whose nodes keep their offsets into `source`. This validates sources such as Jupyter notebooks, where each cell must be syntactically valid on its own while later cells can still reference earlier definitions. The `ranges` must be ordered and non-overlapping. Consecutive rang

(
    source: &str,
    ranges: impl IntoIterator<Item = TextRange>,
    options: &ParseOptions,
)

Source from the content-addressed store, hash-verified

311/// anchored at a cell's trailing offset then lands on that separator, the cell's own last line, so
312/// it is attributed to that cell rather than to the following one.
313pub fn parse_cells_unchecked(
314 source: &str,
315 ranges: impl IntoIterator<Item = TextRange>,
316 options: &ParseOptions,
317) -> Parsed<ModModule> {
318 let mut ranges = ranges.into_iter().peekable();
319 let mut body = Suite::new();
320 let mut tokens = Vec::new();
321 let mut errors = Vec::new();
322 let mut unsupported_syntax_errors = Vec::new();
323 let mut module_range: Option<TextRange> = None;
324
325 while let Some(range) = ranges.next() {
326 if let Some(previous) = module_range {
327 assert!(previous.end() <= range.start());
328 }
329
330 // The cell is lexed from `range.start()`, so the slice must keep the leading text to
331 // preserve absolute offsets into the concatenated source.
332 let cell_source = &source[TextRange::up_to(range.end())];
333 let Parsed {
334 syntax,
335 tokens: cell_tokens,
336 errors: cell_errors,
337 unsupported_syntax_errors: cell_unsupported_syntax_errors,
338 } = Parser::new_starts_at(cell_source, range.start(), options.clone())
339 .parse()
340 .try_into_module()
341 .expect("module options should parse into a module");
342
343 body.extend(syntax.body);
344 tokens.extend(cell_tokens);
345 errors.extend(cell_errors);
346 unsupported_syntax_errors.extend(cell_unsupported_syntax_errors);
347
348 // Each range excludes its trailing `\n` separator (see the doc comment above), leaving a
349 // one-byte gap in the token stream. Cover it with a `NonLogicalNewline` so token-based
350 // checks don't treat the separator as another logical line terminator. The final cell's
351 // separator is the file-final newline and is deliberately left uncovered.
352 if let Some(next) = ranges.peek() {
353 let separator = TextRange::new(range.end(), next.start());
354 assert_eq!(&source[separator], "\n");
355 tokens.push(Token::new(
356 TokenKind::NonLogicalNewline,
357 separator,
358 TokenFlags::empty(),
359 ));
360 }
361
362 module_range = Some(match module_range {
363 Some(previous) => TextRange::new(previous.start(), range.end()),
364 None => range,
365 });
366 }
367
368 body.shrink_to_fit();
369 tokens.shrink_to_fit();
370 errors.shrink_to_fit();

Callers 2

parsed_module_implFunction · 0.85
parse_unchecked_sourceFunction · 0.85

Calls 13

expectMethod · 0.80
try_into_moduleMethod · 0.80
emptyFunction · 0.50
into_iterMethod · 0.45
nextMethod · 0.45
endMethod · 0.45
parseMethod · 0.45
startMethod · 0.45
cloneMethod · 0.45
extendMethod · 0.45
peekMethod · 0.45
pushMethod · 0.45

Tested by

no test coverage detected