// Portions adapted from google/go-jsonnet v0.21.0 (Apache-2.0).
// https://github.com/google/go-jsonnet/tree/67968688d9952f50662e979fab1bef889d0d3e57
// Source: error_formatter.go
// Source: internal/parser/string_util.go
// Source: ast/location.go
// Copyright 2017 Google Inc. All rights reserved.
// Copyright 2016 Google Inc. All rights reserved.
// Modifications: Adapted raw-parser range rendering and malformed-Unicode byte context to local diagnostics.
// License and retained attribution: LICENSE and NOTICE at the module root.
///|
/// Render a raw-parser diagnostic in Go formatter style, without a final LF.
/// Successful lexing supplies the offending token's exclusive end position;
/// lexer failures and EOF retain a point location. This is a syntax rendering
/// helper, not an evaluator stack formatter. Source lookup is limited to 1 MiB.
/// Computed import operands are reparsed under default syntax budgets to recover
/// their complete range; budget failure falls back to the opening token.
pub fn Diagnostic::format_syntax(self : Diagnostic, source : String) -> String {
self.render_syntax_site(self.syntax_error_site(source))
}
///|
fn Diagnostic::render_syntax_site(
self : Diagnostic,
site : (Token, SourceRange)?,
) -> String {
let point = ({ begin: self.location, end: self.location, } : SourceRange).format()
(match site {
Some((_, range)) => range.format()
None =>
match self.trace.get(0) {
Some({ location: Source(range), .. }) if self.kind == Syntax ||
self.kind == Static => range.format()
_ => point
}
}) +
" " +
self.message
}
///|
fn Diagnostic::with_syntax_trace(
self : Diagnostic,
source : String,
) -> Diagnostic {
if !self.trace.is_empty() || (self.kind != Syntax && self.kind != Static) {
return self
}
let range = self
.syntax_error_site(source)
.map(site => site.1)
.unwrap_or({ begin: self.location, end: self.location, })
{ ..self, trace: [{ location: Source(range), name: "", }], }
}
///|
fn Diagnostic::syntax_error_site(
self : Diagnostic,
source : String,
) -> (Token, SourceRange)? {
if (self.kind == Syntax || self.kind == Static) &&
@utf8.encode(source).length() <= SyntaxLimits::default().source_bytes {
let tokens = lex(source, self.location.file) catch { _ => [] }
for i, token in tokens {
if token.loc == self.location && token.text != "" {
let end = if self.message == "Computed imports are not allowed" {
// The parser rejects a complete import operand, rather than just its
// opening token. Reuse its bounded expression parser to recover that
// range, including grouping parentheses, without resolving imports.
let limits = SyntaxLimits::default()
let parser : Parser = {
tokens,
pos: i,
depth: 0,
max_depth: limits.parser_depth,
expression_end: token.loc,
raw_spans: None,
}
try {
let operand = parser.expr(0)
check_ast_depth(operand, limits.tree_depth)
check_ast_nodes(operand, limits.nodes)
parser.expression_end
} catch {
_ => token.end
}
} else {
token.end
}
return Some((token, { begin: token.loc, end, }))
}
}
}
None
}
///|
/// Render exact raw-parser diagnostic bytes, without a final LF. Fixed Go
/// truncates malformed Unicode context at four bytes, potentially splitting a
/// UTF-8 character; this byte API preserves that behavior. The String helper
/// retains the normalized message exposed by Diagnostic instead.
pub fn Diagnostic::format_syntax_bytes(
self : Diagnostic,
source : String,
) -> Bytes {
let site = self.syntax_error_site(source)
let rendered = self.render_syntax_site(site)
if site is Some((token, _)) && token.value is Some(String(raw)) {
for
marker in [
"Unicode escape sequence was malformed: ", "Unicode low surrogate escape sequence was malformed: ",
] {
if rendered.find(marker) is Some(i) {
let bytes = @utf8.encode(raw)
if bytes.length() >= 4 {
let prefix = @utf8.encode(rendered[:i + marker.length()].to_owned()).to_array()
prefix.append(bytes[:4].to_array())
return Bytes::from_array(prefix)
}
}
}
}
@utf8.encode(rendered)
}