mirror of
https://github.com/astral-sh/ruff
synced 2026-01-22 22:10:48 -05:00
This PR implements template strings (t-strings) in the parser and formatter for Ruff. Minimal changes necessary to compile were made in other parts of the code (e.g. ty, the linter, etc.). These will be covered properly in follow-up PRs.
343 lines
7.7 KiB
Rust
343 lines
7.7 KiB
Rust
use std::fmt::{Debug, Write};
|
|
|
|
use insta::assert_snapshot;
|
|
|
|
use ruff_python_ast::visitor::{
|
|
Visitor, walk_alias, walk_bytes_literal, walk_comprehension, walk_except_handler, walk_expr,
|
|
walk_f_string, walk_interpolated_string_element, walk_keyword, walk_match_case, walk_parameter,
|
|
walk_parameters, walk_pattern, walk_stmt, walk_string_literal, walk_t_string, walk_type_param,
|
|
walk_with_item,
|
|
};
|
|
use ruff_python_ast::{
|
|
self as ast, Alias, AnyNodeRef, BoolOp, BytesLiteral, CmpOp, Comprehension, ExceptHandler,
|
|
Expr, FString, InterpolatedStringElement, Keyword, MatchCase, Operator, Parameter, Parameters,
|
|
Pattern, Stmt, StringLiteral, TString, TypeParam, UnaryOp, WithItem,
|
|
};
|
|
use ruff_python_parser::{Mode, ParseOptions, parse};
|
|
|
|
#[test]
|
|
fn function_arguments() {
|
|
let source = r"def a(b, c,/, d, e = 20, *args, named=5, other=20, **kwargs): pass";
|
|
|
|
let trace = trace_visitation(source);
|
|
|
|
assert_snapshot!(trace);
|
|
}
|
|
|
|
#[test]
|
|
fn function_positional_only_with_default() {
|
|
let source = r"def a(b, c = 34,/, e = 20, *args): pass";
|
|
|
|
let trace = trace_visitation(source);
|
|
|
|
assert_snapshot!(trace);
|
|
}
|
|
|
|
#[test]
|
|
fn compare() {
|
|
let source = r"4 < x < 5";
|
|
|
|
let trace = trace_visitation(source);
|
|
|
|
assert_snapshot!(trace);
|
|
}
|
|
|
|
#[test]
|
|
fn list_comprehension() {
|
|
let source = "[x for x in numbers]";
|
|
|
|
let trace = trace_visitation(source);
|
|
|
|
assert_snapshot!(trace);
|
|
}
|
|
|
|
#[test]
|
|
fn dict_comprehension() {
|
|
let source = "{x: x**2 for x in numbers}";
|
|
|
|
let trace = trace_visitation(source);
|
|
|
|
assert_snapshot!(trace);
|
|
}
|
|
|
|
#[test]
|
|
fn set_comprehension() {
|
|
let source = "{x for x in numbers}";
|
|
|
|
let trace = trace_visitation(source);
|
|
|
|
assert_snapshot!(trace);
|
|
}
|
|
|
|
#[test]
|
|
fn match_class_pattern() {
|
|
let source = r"
|
|
match x:
|
|
case Point2D(0, 0):
|
|
...
|
|
case Point3D(x=0, y=0, z=0):
|
|
...
|
|
";
|
|
|
|
let trace = trace_visitation(source);
|
|
|
|
assert_snapshot!(trace);
|
|
}
|
|
|
|
#[test]
|
|
fn decorators() {
|
|
let source = r"
|
|
@decorator
|
|
def a():
|
|
pass
|
|
|
|
@test
|
|
class A:
|
|
pass
|
|
";
|
|
|
|
let trace = trace_visitation(source);
|
|
|
|
assert_snapshot!(trace);
|
|
}
|
|
|
|
#[test]
|
|
fn type_aliases() {
|
|
let source = r"type X[T: str, U, *Ts, **P] = list[T]";
|
|
|
|
let trace = trace_visitation(source);
|
|
|
|
assert_snapshot!(trace);
|
|
}
|
|
|
|
#[test]
|
|
fn class_type_parameters() {
|
|
let source = r"class X[T: str, U, *Ts, **P]: ...";
|
|
|
|
let trace = trace_visitation(source);
|
|
|
|
assert_snapshot!(trace);
|
|
}
|
|
|
|
#[test]
|
|
fn function_type_parameters() {
|
|
let source = r"def X[T: str, U, *Ts, **P](): ...";
|
|
|
|
let trace = trace_visitation(source);
|
|
|
|
assert_snapshot!(trace);
|
|
}
|
|
|
|
#[test]
|
|
fn string_literals() {
|
|
let source = r"'a' 'b' 'c'";
|
|
|
|
let trace = trace_visitation(source);
|
|
|
|
assert_snapshot!(trace);
|
|
}
|
|
|
|
#[test]
|
|
fn bytes_literals() {
|
|
let source = r"b'a' b'b' b'c'";
|
|
|
|
let trace = trace_visitation(source);
|
|
|
|
assert_snapshot!(trace);
|
|
}
|
|
|
|
#[test]
|
|
fn f_strings() {
|
|
let source = r"'pre' f'foo {bar:.{x}f} baz'";
|
|
|
|
let trace = trace_visitation(source);
|
|
|
|
assert_snapshot!(trace);
|
|
}
|
|
|
|
#[test]
|
|
fn t_strings() {
|
|
let source = r"'pre' t'foo {bar:.{x}f} baz'";
|
|
|
|
let trace = trace_visitation(source);
|
|
|
|
assert_snapshot!(trace);
|
|
}
|
|
|
|
fn trace_visitation(source: &str) -> String {
|
|
let parsed = parse(source, ParseOptions::from(Mode::Module)).unwrap();
|
|
|
|
let mut visitor = RecordVisitor::default();
|
|
walk_module(&mut visitor, parsed.syntax());
|
|
|
|
visitor.output
|
|
}
|
|
|
|
fn walk_module<'a, V>(visitor: &mut V, module: &'a ast::Mod)
|
|
where
|
|
V: Visitor<'a> + ?Sized,
|
|
{
|
|
match module {
|
|
ast::Mod::Module(ast::ModModule { body, range: _ }) => {
|
|
visitor.visit_body(body);
|
|
}
|
|
ast::Mod::Expression(ast::ModExpression { body, range: _ }) => visitor.visit_expr(body),
|
|
}
|
|
}
|
|
|
|
/// Emits a `tree` with a node for every visited AST node (labelled by the AST node's kind)
|
|
/// and leaves for attributes.
|
|
#[derive(Default)]
|
|
struct RecordVisitor {
|
|
depth: usize,
|
|
output: String,
|
|
}
|
|
|
|
impl RecordVisitor {
|
|
fn enter_node<'a, T>(&mut self, node: T)
|
|
where
|
|
T: Into<AnyNodeRef<'a>>,
|
|
{
|
|
self.emit(&node.into().kind());
|
|
self.depth += 1;
|
|
}
|
|
|
|
fn exit_node(&mut self) {
|
|
self.depth -= 1;
|
|
}
|
|
|
|
fn emit(&mut self, text: &dyn Debug) {
|
|
for _ in 0..self.depth {
|
|
self.output.push_str(" ");
|
|
}
|
|
|
|
writeln!(self.output, "- {text:?}").unwrap();
|
|
}
|
|
}
|
|
|
|
impl Visitor<'_> for RecordVisitor {
|
|
fn visit_stmt(&mut self, stmt: &Stmt) {
|
|
self.enter_node(stmt);
|
|
walk_stmt(self, stmt);
|
|
self.exit_node();
|
|
}
|
|
|
|
fn visit_annotation(&mut self, expr: &Expr) {
|
|
self.enter_node(expr);
|
|
walk_expr(self, expr);
|
|
self.exit_node();
|
|
}
|
|
|
|
fn visit_expr(&mut self, expr: &Expr) {
|
|
self.enter_node(expr);
|
|
walk_expr(self, expr);
|
|
self.exit_node();
|
|
}
|
|
|
|
fn visit_bool_op(&mut self, bool_op: &BoolOp) {
|
|
self.emit(&bool_op);
|
|
}
|
|
|
|
fn visit_operator(&mut self, operator: &Operator) {
|
|
self.emit(&operator);
|
|
}
|
|
|
|
fn visit_unary_op(&mut self, unary_op: &UnaryOp) {
|
|
self.emit(&unary_op);
|
|
}
|
|
|
|
fn visit_cmp_op(&mut self, cmp_op: &CmpOp) {
|
|
self.emit(&cmp_op);
|
|
}
|
|
|
|
fn visit_comprehension(&mut self, comprehension: &Comprehension) {
|
|
self.enter_node(comprehension);
|
|
walk_comprehension(self, comprehension);
|
|
self.exit_node();
|
|
}
|
|
|
|
fn visit_except_handler(&mut self, except_handler: &ExceptHandler) {
|
|
self.enter_node(except_handler);
|
|
walk_except_handler(self, except_handler);
|
|
self.exit_node();
|
|
}
|
|
|
|
fn visit_parameters(&mut self, parameters: &Parameters) {
|
|
self.enter_node(parameters);
|
|
walk_parameters(self, parameters);
|
|
self.exit_node();
|
|
}
|
|
|
|
fn visit_parameter(&mut self, parameter: &Parameter) {
|
|
self.enter_node(parameter);
|
|
walk_parameter(self, parameter);
|
|
self.exit_node();
|
|
}
|
|
|
|
fn visit_keyword(&mut self, keyword: &Keyword) {
|
|
self.enter_node(keyword);
|
|
walk_keyword(self, keyword);
|
|
self.exit_node();
|
|
}
|
|
|
|
fn visit_alias(&mut self, alias: &Alias) {
|
|
self.enter_node(alias);
|
|
walk_alias(self, alias);
|
|
self.exit_node();
|
|
}
|
|
|
|
fn visit_with_item(&mut self, with_item: &WithItem) {
|
|
self.enter_node(with_item);
|
|
walk_with_item(self, with_item);
|
|
self.exit_node();
|
|
}
|
|
|
|
fn visit_match_case(&mut self, match_case: &MatchCase) {
|
|
self.enter_node(match_case);
|
|
walk_match_case(self, match_case);
|
|
self.exit_node();
|
|
}
|
|
|
|
fn visit_pattern(&mut self, pattern: &Pattern) {
|
|
self.enter_node(pattern);
|
|
walk_pattern(self, pattern);
|
|
self.exit_node();
|
|
}
|
|
|
|
fn visit_type_param(&mut self, type_param: &TypeParam) {
|
|
self.enter_node(type_param);
|
|
walk_type_param(self, type_param);
|
|
self.exit_node();
|
|
}
|
|
|
|
fn visit_string_literal(&mut self, string_literal: &StringLiteral) {
|
|
self.enter_node(string_literal);
|
|
walk_string_literal(self, string_literal);
|
|
self.exit_node();
|
|
}
|
|
|
|
fn visit_bytes_literal(&mut self, bytes_literal: &BytesLiteral) {
|
|
self.enter_node(bytes_literal);
|
|
walk_bytes_literal(self, bytes_literal);
|
|
self.exit_node();
|
|
}
|
|
|
|
fn visit_f_string(&mut self, f_string: &FString) {
|
|
self.enter_node(f_string);
|
|
walk_f_string(self, f_string);
|
|
self.exit_node();
|
|
}
|
|
|
|
fn visit_interpolated_string_element(&mut self, f_string_element: &InterpolatedStringElement) {
|
|
self.enter_node(f_string_element);
|
|
walk_interpolated_string_element(self, f_string_element);
|
|
self.exit_node();
|
|
}
|
|
|
|
fn visit_t_string(&mut self, t_string: &TString) {
|
|
self.enter_node(t_string);
|
|
walk_t_string(self, t_string);
|
|
self.exit_node();
|
|
}
|
|
}
|