///|
/// Why `format` gives no text.
///
/// ```mbt check
/// test {
/// let failed = try @printer.format("module A exposing (a)\n\na = (\n") catch {
/// ParseFailed(diagnostics) => diagnostics.length() > 0
/// Unprintable(_) => false
/// } noraise {
/// _ => false
/// }
/// inspect(failed, content="true")
/// }
/// ```
pub suberror FormatError {
/// The source has a diagnostic with severity `Error` (the diagnostics
/// with that severity). Render them with `@report.render_plain` or
/// `@report.render_terminal`.
ParseFailed(Array[@scanner.Diagnostic])
/// The parsed file cannot print: a comment that the printer cannot place
/// (`UnplacedComment`), or a problem that `print_file` also raises.
Unprintable(PrintError)
} derive(Debug)
///|
/// Formats Elm source as elm-format 0.8.7 does (`layout=ElmFormat`, the
/// default), or with the line-width layout of `print_file`
/// (`layout=Width(n)`).
///
/// - The output is the source of `normalize_file(ast)`: exposed items and
/// imports in elm-format's order, elm-format's parentheses and literal
/// forms, and doc comments with their Markdown and Elm code formatted.
/// - Every regular comment prints once, where elm-format puts it: a
/// comment leads the node after it, trails the node before it on its
/// line, or goes before the closing token of its container. A comment
/// moves with an exposed item or an import that elm-format reorders. A
/// block comment gets elm-format's form (`{-a-}` gives `{- a -}`).
/// No comment is dropped: one that the printer cannot place raises
/// `Unprintable` with `UnplacedComment`.
/// - Raises `ParseFailed` when the source has a syntax error in
/// `dialect`.
///
/// `format(format(s)) == format(s)`, except for the doc comments on which
/// elm-format 0.8.7 is not idempotent (see `normalize_file`).
///
/// ```mbt check
/// test {
/// let source = "module A exposing (a)\n\na = f x -- why\n y\n"
/// inspect(
/// @printer.format(source),
/// content=(
/// #|module A exposing (a)
/// #|
/// #|
/// #|a =
/// #| f x
/// #| -- why
/// #| y
/// #|
/// ),
/// )
/// }
/// ```
pub fn format(
source : String,
layout? : Layout = ElmFormat,
dialect? : @dialect.Dialect = @dialect.Dialect::elm_0_19_1(),
) -> String raise FormatError {
format_source(source, layout, dialect, 0)
}
///|
/// `format` for code inside `doc_depth` doc comments.
fn format_source(
source : String,
layout : Layout,
dialect : @dialect.Dialect,
doc_depth : Int,
) -> String raise FormatError {
let result = @parser.parse_module(
@scanner.SourceText::new(source),
@scanner.DefaultScanner::new(dialect~),
dialect~,
)
format_parsed_at(result, layout, dialect, doc_depth)
}
///|
/// Formats a parse result, as `format` formats source. Give the dialect
/// that parsed it: the printer uses its operator table. Raises
/// `ParseFailed` when the result has a diagnostic with severity `Error`.
/// Use it when the parse result is already there, for example after a
/// check of its diagnostics.
///
/// ```mbt check
/// test {
/// let result = @parser.parse_module(
/// @scanner.SourceText::new("module A exposing (a)\n\na = [1,2]\n"),
/// @scanner.DefaultScanner::new(),
/// )
/// inspect(
/// @printer.format_parsed(result, layout=Width(40)),
/// content=(
/// #|module A exposing (a)
/// #|
/// #|
/// #|a =
/// #| [ 1, 2 ]
/// #|
/// ),
/// )
/// }
/// ```
pub fn format_parsed(
result : @parser.ParseResult,
layout? : Layout = ElmFormat,
dialect? : @dialect.Dialect = @dialect.Dialect::elm_0_19_1(),
) -> String raise FormatError {
format_parsed_at(result, layout, dialect, 0)
}
///|
/// `format_parsed` for code inside `doc_depth` doc comments.
fn format_parsed_at(
result : @parser.ParseResult,
layout : Layout,
dialect : @dialect.Dialect,
doc_depth : Int,
) -> String raise FormatError {
let errors = result.diagnostics.filter(d => d.severity is Error)
// With no error, the parser gives an AST and a CST. `errors` is empty
// only for a result built by hand without them: then `ParseFailed([])`.
guard errors.is_empty() && result.ast is Some(file) && result.cst is Some(cst) else {
raise ParseFailed(errors)
}
let comments = place_comments(file)
let ctx = Ctx::new(
dialect,
layout~,
source=Some(SourceFacts::from_tokens(cst.tokens)),
comments=Some(comments),
doc_depth~,
)
let doc = ctx.file_doc(file) catch { e => raise Unprintable(e) }
if comments.unprinted() is Some(r) {
raise Unprintable(
PrintError(path=@syntax.NodePath::root(), problem=UnplacedComment(r)),
)
}
@pretty.render(doc, width=layout.render_width())
}