///|
/// A doc comment, with its Markdown and code as elm-format writes them
/// (see `format_documentation`). It must start with `{-|` and end with
/// `-}`.
fn Ctx::documentation_doc(
self : Ctx,
path : @syntax.NodePath,
text : String,
) -> @pretty.Doc raise PrintError {
guard text.length() >= 5 && text.has_prefix("{-|") && text.has_suffix("-}") else {
raise PrintError(path~, problem=InvalidDocumentation)
}
@pretty.verbatim(format_documentation(text, self.dialect, self.doc_depth))
}
///|
/// `name : type`; when it breaks, the type goes on the next lines with each
/// `->` at the start of a line (elm-format).
fn Ctx::signature_doc(
self : Ctx,
path : @syntax.NodePath,
level : Int,
s : @ast.Signature,
range? : @ast.Range? = None,
) -> @pretty.Doc raise PrintError {
// elm-format Box.hs formatTypeAnnotation: the comments between the name
// and the `:` follow the name (`formatTailCommented`). When one of them
// is not one line, the name, each comment, the `:` (indented by 4) and
// the type (indented by 4) go on lines of their own.
let colon = self.token_after(s.name.range.end)
let name_post = self.take_if(Trailing, s.name.range, c => {
is_line_comment(c) && ends_before(c, colon)
})
name_post.append(
self.take_if(Leading, s.type_annotation.range, c => ends_before(c, colon)),
)
let moved = self.after_token(s.name.range)
let name = self.with_comments(
s.name.range,
self.lower_name(path.child("name", 0), s.name.value),
)
// elm-format Parse/Helpers.hs separated: the last term of a function
// type takes the line comment at the end of its line (`withEol`). After
// another type, the comment goes after the signature. `range` is the
// range of the signature node, which holds the trailing comments.
let eol = match range {
Some(r) if s.type_annotation.value is FunctionTypeAnnotation(_, _) &&
!self.has_comment(Trailing, r, c => !is_line_comment(c)) =>
self.take(Trailing, r)
_ => []
}
let chain = self.arrow_chain(
path.child("typeAnnotation", 0),
level + 1,
s.type_annotation,
eol~,
)
// elm-format ElmStructure.definition ":" False: the type goes on the
// next line when it is multi-line; its arrows break on their own
// (`Ctx::arrow_chain`).
let chain = match self.layout {
Width(_) => chain
ElmFormat => @pretty.group(chain)
}
if !name_post.iter().all(one_line) {
return name +
@pretty.hardline() +
comment_block(name_post) +
@pretty.nest(4, @pretty.hardline() + @pretty.text(":")) +
comments_after(moved) +
@pretty.nest(4, @pretty.hardline() + chain)
}
let name = name + comments_after(name_post)
@pretty.group(
name +
self.separator(s.name.range, " :", moved) +
@pretty.nest(4, @pretty.line() + chain),
)
}
///|
/// Documentation, signature and `name args =` with the body on the next
/// line, indented by 4. Fields: `documentation`, `signature`, `declaration`.
/// A `let` uses `function_plan` (one work stack for the whole expression);
/// top-level function declarations use this entry.
fn Ctx::function_doc(
self : Ctx,
path : @syntax.NodePath,
level : Int,
f : @ast.Function,
) -> @pretty.Doc raise PrintError {
self.run(self.function_plan(path, level, f))
}
///|
/// A part of a function declaration: the documentation, the signature or
/// the implementation, or the comments between them.
priv enum FunctionPart {
Main
Between(Int)
}
///|
/// The work items of `function_doc`, in the order it checks them: the
/// documentation, the signature, the name, the arguments, the body. Elm has
/// no doc comments in a `let` (`in_let = true`), so documentation there
/// raises `LetDocumentation`.
///
/// The comments between the documentation, the signature and the
/// implementation go on their own lines. At the top level they are blocks
/// between blank lines, as elm-format makes them body comments
/// (`formatTopLevelBody`).
fn Ctx::function_plan(
self : Ctx,
path : @syntax.NodePath,
level : Int,
f : @ast.Function,
in_let? : Bool = false,
) -> Work {
let parts = []
let kinds = []
// Whether each block of comments has comments, set when its item runs.
let found = [false, false, false]
let comments = (k : Int, take : () -> Array[@ast.Node[String]]) => {
kinds.push(Between(k))
parts.push(
Leaf(() => {
let cs = take()
found[k] = !cs.is_empty()
comment_block(cs)
}),
)
}
if f.documentation is Some(doc) {
let p = path.child("documentation", 0)
kinds.push(Main)
parts.push(
Leaf(() => {
if in_let {
raise PrintError(path=p, problem=LetDocumentation)
}
self.documentation_doc(p, doc.value) + self.trailing(doc.range)
}),
)
}
if f.signature is Some(sig) {
comments(0, () => self.take(Leading, sig.range))
kinds.push(Main)
parts.push(
Leaf(() => {
self.signature_doc(
path.child("signature", 0),
level + 1,
sig.value,
range=Some(sig.range),
)
}),
)
comments(1, () => self.take(Trailing, sig.range))
}
let p = path.child("declaration", 0)
let imp = f.declaration
comments(2, () => self.take(Leading, imp.range))
let name = imp.value.name
let args = imp.value.arguments
let head_start = parts.length()
kinds.push(Main)
parts.push(
Leaf(() => {
let moved = if args.is_empty() {
self.after_token(name.range)
} else {
[]
}
self.with_comments(
name.range,
self.lower_name(p.child("name", 0), name.value),
) +
self.separator(
name.range,
if args.is_empty() {
" ="
} else {
" "
},
moved,
)
}),
)
for i, a in args {
parts.push(
Leaf(() => {
let last = i + 1 == args.length()
let moved = if last { self.after_token(a.range) } else { [] }
@pretty.nest(
4,
self.pattern_doc(p.child("arguments", i), level + 1, a, PatternArg) +
self.separator(a.range, if last { " =" } else { " " }, moved),
)
}),
)
}
parts.push(
Expr(p.child("expression", 0), level + 1, imp.value.expression, AnyExpr),
)
Plan(parts, docs => {
// The head and the body are one main part.
let mut head = @pretty.empty()
for i in head_start..<(docs.length() - 1) {
head = head + docs[i]
}
let main = head +
@pretty.nest(4, @pretty.hardline() + docs[docs.length() - 1])
let mut d = @pretty.empty()
let mut previous : FunctionPart? = None
for i, kind in kinds {
let doc = if i + 1 == kinds.length() { main } else { docs[i] }
match kind {
Between(k) if !found[k] => continue
_ => ()
}
let breaks = match (previous, kind) {
(None, _) => 0
(Some(Main), Between(_)) if !in_let => 4
(Some(Between(_)), Main) if !in_let => 3
_ => 1
}
d = d + line_breaks(breaks) + doc
previous = Some(kind)
}
d
})
}