// Portions adapted from google/go-jsonnet v0.21.0 (Apache-2.0).
// https://github.com/google/go-jsonnet/tree/67968688d9952f50662e979fab1bef889d0d3e57
// Source: internal/formatter/fix_newlines.go
// Source: internal/formatter/fix_trailing_commas.go
// Source: internal/formatter/enforce_max_blank_lines.go
// Copyright 2019 Google Inc. All rights reserved.
// Modifications: Adapted layout and trailing-comma passes to local token/trivia containers.
// License and retained attribution: LICENSE and NOTICE at the module root.
///|
priv struct FormatLayout {
close : Array[Int]
index : Array[Bool]
field_key : Array[Bool]
parameter : Array[Bool]
expanded : Array[Bool]
newline : Array[Bool]
skip : Array[Bool]
trailing_comma : Array[Bool]
}
///|
fn Token::has_newline(self : Token) -> Bool {
self.leading.any(t => t.raw.contains("\n"))
}
///|
fn format_layout(tokens : Array[Token]) -> FormatLayout {
let n = tokens.length()
let layout : FormatLayout = {
close: Array::make(n, -1),
index: Array::make(n, false),
field_key: Array::make(n, false),
parameter: Array::make(n, false),
expanded: Array::make(n, false),
newline: Array::make(n, false),
skip: Array::make(n, false),
trailing_comma: Array::make(n, false),
}
let stack : Array[Int] = []
let mut ends_expr = false
for i, token in tokens {
let t = token.text
if t == "(" || t == "[" || t == "{" {
layout.index[i] = t == "[" && ends_expr
layout.parameter[i] = t == "(" &&
i > 0 &&
tokens[i - 1].text == "function"
// At an object member boundary, brackets enclose a computed key,
// not an array. A multiline key must never receive a trailing comma.
layout.field_key[i] = t == "[" &&
stack.last() is Some(parent) &&
tokens[parent].text == "{" &&
i > 0 &&
(tokens[i - 1].text == "{" || tokens[i - 1].text == ",")
stack.push(i)
ends_expr = false
} else if t == ")" || t == "]" || t == "}" {
let start = stack.pop().unwrap()
layout.close[start] = i
layout.close[i] = start
ends_expr = !layout.parameter[start]
} else {
ends_expr = token.value is Some(_) ||
t == "" ||
is_name(t) ||
t == "true" ||
t == "false" ||
t == "null" ||
t == "self" ||
t == "super" ||
t == "$"
}
}
for start, token in tokens {
let end = layout.close[start]
if end <= start {
continue
}
if token.text == "(" &&
end + 1 < n &&
(
tokens[end + 1].text == "=" ||
tokens[end + 1].text == ":" ||
tokens[end + 1].text == "::" ||
tokens[end + 1].text == ":::"
) {
layout.parameter[start] = true
}
let boundaries : Array[Int] = []
let mut comprehension = false
let mut local_depth = 0
for i = start + 1; i < end; {
let t = tokens[i].text
if t == "local" && token.text != "{" {
local_depth += 1
}
if t == ";" && local_depth > 0 {
local_depth -= 1
}
if t == "," && local_depth == 0 && i + 1 < end {
boundaries.push(i + 1)
}
if token.text == "(" && t == "," && tokens[i].has_newline() {
layout.newline[i] = true
}
if t == "for" || t == "if" {
// A comprehension has a top-level 'for'; its later clauses are
// added below only after that marker is found.
if t == "for" {
comprehension = true
if i > start + 1 && tokens[i - 1].text == "," {
layout.skip[i - 1] = true
}
}
if comprehension {
boundaries.push(i)
}
}
let close = layout.close[i]
continue if close > i { close + 1 } else { i + 1 }
}
let outer_newline = tokens[start + 1].has_newline() ||
tokens[end].has_newline()
let inner_newline = boundaries.any(i => tokens[i].has_newline())
let container = token.text == "{" ||
(token.text == "[" && !layout.index[start] && !layout.field_key[start])
if container && (outer_newline || inner_newline) {
layout.expanded[start] = true
layout.newline[start + 1] = true
layout.newline[end] = true
for i in boundaries {
layout.newline[i] = true
}
} else if token.text == "(" {
if outer_newline {
layout.expanded[start] = true
layout.newline[start + 1] = true
layout.newline[end] = true
}
if inner_newline {
for i in boundaries {
layout.newline[i] = true
}
}
}
if container && end > start + 1 && !comprehension {
if layout.expanded[start] {
layout.trailing_comma[end] = tokens[end - 1].text != ","
} else if tokens[end - 1].text == "," {
layout.skip[end - 1] = true
}
}
}
layout
}