1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#![doc = include_str!("../README.md")]

pub mod config;
mod ctx;
mod doc_gen;
mod error;
mod helpers;
mod line_bounds;
mod state;

use crate::{config::FormatOptions, ctx::Ctx, doc_gen::DocGen};
pub use crate::{error::Error, line_bounds::LineBounds};
pub use raffia::Syntax;
use raffia::{ast::Stylesheet, token::Comment, ParserBuilder, ParserOptions};
use std::path::Path;

/// Format the given source code.
pub fn format_text(input: &str, syntax: Syntax, options: &FormatOptions) -> Result<String, Error> {
    let line_bounds = LineBounds::new(input);
    let mut comments = vec![];
    let mut parser = ParserBuilder::new(input)
        .syntax(syntax)
        .comments(&mut comments)
        .options(ParserOptions {
            try_parsing_value_in_custom_property: true,
            tolerate_semicolon_in_sass: true,
        })
        .build();
    let stylesheet = match parser.parse::<Stylesheet>() {
        Ok(stylesheet) => stylesheet,
        Err(error) => return Err(error.into()),
    };

    Ok(print_stylesheet(
        &stylesheet,
        &comments,
        Some(input),
        line_bounds,
        syntax,
        options,
    ))
}

/// Print the given stylesheet AST.
/// You may use this when you already have the parsed AST.
pub fn print_stylesheet<'a, 's>(
    stylesheet: &'a Stylesheet<'s>,
    comments: &'a [Comment<'s>],
    source: Option<&'s str>,
    line_bounds: LineBounds,
    syntax: Syntax,
    options: &'a FormatOptions,
) -> String {
    use tiny_pretty::{IndentKind, PrintOptions};

    let ctx = Ctx {
        source,
        syntax,
        options: &options.language,
        comments,
        indent_width: options.layout.indent_width,
        line_bounds,
        state: Default::default(),
    };
    let doc = stylesheet.doc(&ctx);
    tiny_pretty::print(
        &doc,
        &PrintOptions {
            indent_kind: if options.layout.use_tabs {
                IndentKind::Tab
            } else {
                IndentKind::Space
            },
            line_break: options.layout.line_break.clone().into(),
            width: options.layout.print_width,
            tab_size: options.layout.indent_width,
        },
    )
}

/// Detect syntax from file extension.
pub fn detect_syntax(path: impl AsRef<Path>) -> Option<Syntax> {
    match path.as_ref().extension().and_then(std::ffi::OsStr::to_str) {
        Some(ext) if ext.eq_ignore_ascii_case("css") => Some(Syntax::Css),
        Some(ext) if ext.eq_ignore_ascii_case("scss") => Some(Syntax::Scss),
        Some(ext) if ext.eq_ignore_ascii_case("sass") => Some(Syntax::Sass),
        Some(ext) if ext.eq_ignore_ascii_case("less") => Some(Syntax::Less),
        _ => None,
    }
}