dprint-core

Rust crate to help build a code formatter.

cargo install dprint-core

Example

This reimplements the example from overview.md, but in rust.

Given the following AST nodes:

```rust enum Node { ArrayLiteralExpression(ArrayLiteralExpression), ArrayElement(ArrayElement), }

[derive(Clone)]

struct Position { /// Line number in the original source code. pub linenumber: u32, /// Column number in the original source code. pub columnnumber: u32, }

[derive(Clone)]

struct ArrayLiteralExpression { pub position: Position, pub elements: Vec, }

[derive(Clone)]

struct ArrayElement { pub position: Position, pub text: String, } ```

With the following expected outputs (when max line width configured in printer is 10):

```ts // 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:

```rust extern crate dprint_core;

use dprint_core::*;

pub fn format(expr: ArrayLiteralExpression) { let printitems = parsenode(Node::ArrayLiteralExpression(expr)); let printer = dprintcore::Printer::new(printitems, PrintOptions { indentwidth: 4, maxwidth: 10, newlinekind: "\n", usetabs: false });

printer.print()

}

// node parsing functions

fn parse_node(node: Node) -> Vec { // in a real implementation this function would deal with surrounding comments

match node {
    Node::ArrayLiteralExpression(expr) => parse_array_literal_expression(&expr),
    Node::ArrayElement(array_element) => parse_array_element(&array_element),
}

}

fn parsearrayliteralexpression(expr: &ArrayLiteralExpression) -> Vec { let mut items: Vec = Vec::new(); let startinfo = Info::new("start"); let endinfo = Info::new("end"); let ismultiplelines = createismultiplelinesresolver( expr.position.clone(), expr.elements.iter().map(|e| e.position.clone()).collect(), &startinfo, &end_info );

items.push(start_info.into());

items.push("[".into());
items.push(if_true(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(with_indent(parsed_elements.clone())),
    false_path: Some(parsed_elements),
}).into());

items.push(if_true(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 ResolveConditionContext) -> Option<bool> + Clone + 'static)
) -> Vec<PrintItem> {
    let mut items = Vec::new();

    for i in 0..elements.len() {
        items.extend_from_slice(&parse_node(Node::ArrayElement(elements[i].clone())));

        if i < elements.len() - 1 {
            items.push(",".into());
            items.push(if_true_or(
                is_multiple_lines.clone(),
                PrintItem::NewLine,
                PrintItem::SpaceOrNewLine
            ));
        }
    }

    items
}

}

fn parsearrayelement(element: &ArrayElement) -> Vec { vec![(&element.text).into()] }

// helper functions

fn createismultiplelinesresolver( parentposition: Position, childpositions: Vec, startinfo: &Info, endinfo: &Info ) -> impl Fn(&mut ResolveConditionContext) -> Option + Clone + 'static { let capturedstartinfo = startinfo.clone(); let capturedendinfo = endinfo.clone();

return move |condition_context: &mut ResolveConditionContext| {
    // no items, so format on the same line
    if child_positions.len() == 0 {
        return Some(false);
    }
    // first child is on a different line than the start of the parent
    // so format all the children as multi-line
    if parent_position.line_number < child_positions[0].line_number {
        return Some(true);
    }

    // check if it spans multiple lines, and if it does then make it multi-line
    let resolved_start_info = condition_context.get_resolved_info(&captured_start_info).unwrap();
    let optional_resolved_end_info = condition_context.get_resolved_info(&captured_end_info);

    optional_resolved_end_info.map(|resolved_end_info| {
        resolved_start_info.line_number < resolved_end_info.line_number
    })
};

}

fn with_indent(mut elements: Vec) -> Vec { elements.insert(0, PrintItem::StartIndent); elements.push(PrintItem::FinishIndent); elements }

fn iftrue( resolver: impl Fn(&mut ResolveConditionContext) -> Option + Clone + 'static, trueitem: PrintItem ) -> PrintItem { Condition::new("", ConditionProperties { truepath: Some(vec![trueitem]), false_path: Option::None, condition: Box::new(resolver.clone()), }).into() }

fn iftrueor( resolver: impl Fn(&mut ResolveConditionContext) -> Option + Clone + 'static, trueitem: PrintItem, falseitem: PrintItem ) -> PrintItem { Condition::new("", ConditionProperties { truepath: Some(vec![trueitem]), falsepath: Some(vec![falseitem]), condition: Box::new(resolver.clone()) }).into() } ```