dprint-core
Rust crate to help build a code formatter.
Api
Use:
let print_items = ...; let result = dprint_core::print(print_items, PrintOptions {
indent_width: 4,
max_width: 10,
is_testing: false,
use_tabs: false,
newline_kind: "\n",
});
Or do the steps individually:
let write_items = dprint_core::get_write_items(print_items, GetWriteItemsOptions {
indent_width: 4,
max_width: 10,
is_testing: false,
});
let result = dprint_core::print_write_items(write_items, PrintWriteItemsOptions {
use_tabs: false,
newline_kind: "\n",
indent_width: 4,
})
It is useful to only do the first step of getting the "write items" when using this library in WebAssembly. That allows for avoiding to copy/encode over the strings from JavaScript to Rust and back—all the strings can remain in JS. An example of this can be seen in the @dprint/rust-printer package.
Example
This reimplements the example from overview.md, but in rust.
Given the following AST nodes:
enum Node<'a> {
ArrayLiteralExpression(&'a ArrayLiteralExpression),
ArrayElement(&'a ArrayElement),
}
#[derive(Clone)]
struct Position {
pub line_number: u32,
pub column_number: u32,
}
#[derive(Clone)]
struct ArrayLiteralExpression {
pub position: Position,
pub elements: Vec<ArrayElement>,
}
#[derive(Clone)]
struct ArrayElement {
pub position: Position,
pub text: String,
}
With the following expected outputs (when max line width configured in printer is 10):
// input
[a , b
, c
]
// output
[a, b, c]
// input
[four, four, four]
// output (since it exceeds the line width of 10)
[
four,
four,
four
]
// input
[
four]
// output (since first element was placed on a different line)
[
four
]
Here's some example IR generation:
extern crate dprint_core;
use dprint_core::*;
pub fn format(expr: &ArrayLiteralExpression) -> String {
let print_items = parse_node(Node::ArrayLiteralExpression(expr));
dprint_core::print(print_items, GetWriteItemsOptions {
indent_width: 4,
max_width: 10,
is_testing: false,
use_tabs: false,
newline_kind: "\n",
});
}
fn parse_node(node: Node) -> Vec<PrintItem> {
match node {
Node::ArrayLiteralExpression(expr) => parse_array_literal_expression(&expr),
Node::ArrayElement(array_element) => parse_array_element(&array_element),
}
}
fn parse_array_literal_expression(expr: &ArrayLiteralExpression) -> Vec<PrintItem> {
let mut items: Vec<PrintItem> = Vec::new();
let start_info = Info::new("start");
let end_info = Info::new("end");
let is_multiple_lines = create_is_multiple_lines_resolver(
expr.position.clone(),
expr.elements.iter().map(|e| e.position.clone()).collect(),
start_info.clone(),
end_info.clone()
);
items.push(start_info.into());
items.push("[".into());
items.push(parser_helpers::if_true(
"arrayStartNewLine",
is_multiple_lines.clone(),
PrintItem::NewLine
));
let parsed_elements = parse_elements(&expr.elements, &is_multiple_lines);
items.push(Condition::new("indentIfMultipleLines", ConditionProperties {
condition: Box::new(is_multiple_lines.clone()),
true_path: Some(parser_helpers::with_indent(parsed_elements.clone())),
false_path: Some(parsed_elements),
}).into());
items.push(parser_helpers::if_true(
"arrayEndNewLine",
is_multiple_lines,
PrintItem::NewLine
));
items.push("]".into());
items.push(end_info.into());
return items;
fn parse_elements(
elements: &Vec<ArrayElement>,
is_multiple_lines: &(impl Fn(&mut ConditionResolverContext) -> Option<bool> + Clone + 'static)
) -> Vec<PrintItem> {
let mut items = Vec::new();
let elements_len = elements.len();
for (i, elem) in elements.iter().enumerate() {
items.extend(parse_node(Node::ArrayElement(elem)));
if i < elements_len - 1 {
items.push(",".into());
items.push(parser_helpers::if_true_or(
"afterCommaSeparator",
is_multiple_lines.clone(),
PrintItem::NewLine,
PrintItem::SpaceOrNewLine
));
}
}
items
}
}
fn parse_array_element(element: &ArrayElement) -> Vec<PrintItem> {
vec![element.text.clone().into()]
}
fn create_is_multiple_lines_resolver(
parent_position: Position,
child_positions: Vec<Position>,
start_info: Info,
end_info: Info
) -> impl Fn(&mut ConditionResolverContext) -> Option<bool> + Clone + 'static {
return move |condition_context: &mut ConditionResolverContext| {
if child_positions.len() == 0 {
return Some(false);
}
if parent_position.line_number < child_positions[0].line_number {
return Some(true);
}
condition_resolvers::is_multiple_lines(condition_context, &start_info, &end_info)
};
}