// Portions adapted from google/go-jsonnet v0.21.0 (Apache-2.0).
// https://github.com/google/go-jsonnet/tree/67968688d9952f50662e979fab1bef889d0d3e57
// Source: internal/formatter/pretty_field_names.go
// Source: internal/formatter/no_redundant_slice_colon.go
// Source: internal/formatter/remove_plus_object.go
// Source: internal/formatter/add_plus_object.go
// Source: internal/formatter/fix_parens.go
// Copyright 2019 Google Inc. All rights reserved.
// Modifications: Adapted syntax rewrite passes to local token ranges without evaluating Jsonnet.
// License and retained attribution: LICENSE and NOTICE at the module root.
///|
fn format_name(token : Token) -> String? {
match token.value {
Some(String(name)) if is_name(name) &&
name.to_array().all(c => identifier_start(c) || digit(c)) => Some(name)
_ => None
}
}
///|
fn format_rewrite(
tokens : Array[Token],
root : Expr,
options : FormatOptions,
) -> Array[Token] {
let layout = format_layout(tokens)
let edited = tokens.copy()
let skip = Array::make(tokens.length(), false)
let insert_plus = Array::make(tokens.length(), false)
format_plus_edits(root, tokens, skip, insert_plus, options.use_implicit_plus)
for i, token in tokens {
let end = layout.close[i]
if token.text == "[" &&
end == i + 2 &&
tokens[i + 1].text == "" &&
options.pretty_field_names {
let key = tokens[i + 1]
let field = end + 1 < tokens.length() &&
(
tokens[end + 1].text.has_prefix(":") ||
tokens[end + 1].text.has_prefix("+:") ||
(tokens[end + 1].text == "(" && layout.parameter[end + 1])
)
if field || (layout.index[i] && format_name(key) is Some(_)) {
if field {
skip[i] = true
} else {
edited[i] = { ..token, text: ".", raw: ".", }
// Go replaces RightBracketFodder with the string's opening fodder,
// so comments before the removed closing bracket are discarded.
edited[end] = { ..tokens[end], leading: [], }
}
skip[end] = true
if format_name(key) is Some(name) {
edited[i + 1] = { ..key, text: name, raw: name, value: None, }
}
}
}
if token.text == "[" && layout.index[i] && end > i + 1 {
let last = tokens[end - 1]
if last.text == "::" {
edited[end - 1] = { ..last, text: ":", raw: ":", }
} else if last.text == ":" {
// Only remove the second colon of a slice; the first is required.
for j = i + 1; j < end - 1; {
if tokens[j].text == ":" {
skip[end - 1] = true
break
}
let close = layout.close[j]
continue if close > j { close + 1 } else { j + 1 }
}
}
}
if token.text == "(" &&
end > i + 2 &&
tokens[i + 1].text == "(" &&
layout.close[i + 1] == end - 1 {
let previous = if i == 0 { "" } else { tokens[i - 1].text }
let call = i > 0 &&
(
is_name(previous) ||
previous == ")" ||
previous == "]" ||
previous == "}" ||
previous == "function" ||
tokens[i - 1].value is Some(_)
)
if !call {
skip[i + 1] = true
skip[end - 1] = true
}
}
}
let result : Array[Token] = []
let pending : Array[Trivia] = []
for i, token in edited {
pending.append(token.leading)
if !skip[i] {
if insert_plus[i] {
result.push({
..token,
text: "+",
raw: "+",
value: None,
leading: pending.copy(),
})
pending.clear()
}
result.push({ ..token, leading: pending.copy(), })
pending.clear()
}
}
result
}
///|
fn format_trailing_fodder(tokens : Array[Token]) -> Array[Token] {
let layout = format_layout(tokens)
let result = tokens.copy()
for open, token in tokens {
let close = layout.close[open]
if close <= open + 1 ||
!(token.text == "{" ||
(token.text == "[" && !layout.index[open] && !layout.field_key[open])) ||
tokens[close - 1].text != "," ||
!tokens[close - 1].has_newline() {
continue
}
let comma = result[close - 1]
let closing = result[close]
let after = closing.leading.copy()
if comma.leading.last() is Some({ kind: Whitespace, raw: left, .. }) &&
left.contains("\n") &&
after.get(0) is Some({ kind: Whitespace, raw: right, .. }) &&
right.find("\n") is Some(n) {
after[0] = {
..after[0],
raw: right[:n].to_owned() + right[n + 1:].to_owned(),
}
}
result[close - 1] = { ..comma, leading: [], }
result[close] = { ..closing, leading: comma.leading + after, }
}
result
}