///|
/// One symbolized frame with optional embedded source context.
pub(all) struct ContextualSymbolizedFrame {
result : SymbolizedFrame
context : SourceContext?
} derive(Eq, Debug)
///|
/// Aggregate single-map symbolization outcome.
pub(all) struct ContextualSymbolizationReport {
frames : Array[ContextualSymbolizedFrame]
matched : Int
unmatched : Int
with_context : Int
} derive(Eq, Debug)
///|
/// Symbolize one frame and attach embedded source context when available.
pub fn symbolize_frame_with_context(
map : DecodedSourceMap,
frame : StackFrame,
bias? : LookupBias = GreatestLowerBound,
context_radius? : Int = 1,
) -> ContextualSymbolizedFrame {
let result = symbolize_frame(map, frame, bias~)
let context = if result.matched && context_radius >= 0 {
let mapping = original_position_for(
map,
generated=Position::new(
line=result.generated.line,
column=result.generated.column,
),
bias~,
)
match mapping {
Some(mapping) =>
source_context_for_mapping(map, mapping, radius=context_radius)
None => None
}
} else {
None
}
{ result, context }
}
///|
/// Parse and symbolize newline-separated frames with source context.
pub fn symbolize_with_context(
map : DecodedSourceMap,
frames : String,
bias? : LookupBias = GreatestLowerBound,
context_radius? : Int = 1,
) -> ContextualSymbolizationReport raise SourceMapError {
if context_radius < 0 {
raise InvalidField(
path="$context",
message="context radius must be non-negative",
)
}
let results : Array[ContextualSymbolizedFrame] = []
let mut matched = 0
let mut unmatched = 0
let mut with_context = 0
for line in frames.split("\n") {
let text = line.trim()
if text.is_empty() {
continue
}
let contextual = symbolize_frame_with_context(
map,
parse_stack_frame(text.to_owned()),
bias~,
context_radius~,
)
if contextual.result.matched {
matched = matched + 1
} else {
unmatched = unmatched + 1
}
if contextual.context is Some(_) {
with_context = with_context + 1
}
results.push(contextual)
}
{ frames: results, matched, unmatched, with_context }
}
///|
/// Convert one contextual result to JSON.
pub fn ContextualSymbolizedFrame::to_json(
self : ContextualSymbolizedFrame,
) -> Json {
Json::object({
"result": self.result.to_json(),
"context": match self.context {
Some(context) => context.to_json()
None => Json::null()
},
})
}
///|
/// Render one contextual result.
pub fn ContextualSymbolizedFrame::render(
self : ContextualSymbolizedFrame,
) -> String {
let output = StringBuilder()
output.write_string(self.result.render())
match self.context {
Some(context) => {
output.write_char('\n')
output.write_string(context.render())
}
None => ()
}
output.to_string()
}
///|
/// Convert a contextual report to stable JSON.
pub fn ContextualSymbolizationReport::to_json(
self : ContextualSymbolizationReport,
) -> Json {
Json::object({
"matched": Json::number(self.matched.to_double()),
"unmatched": Json::number(self.unmatched.to_double()),
"with_context": Json::number(self.with_context.to_double()),
"frames": Json::array(self.frames.map(frame => frame.to_json())),
})
}
///|
/// Render every contextual result.
pub fn ContextualSymbolizationReport::render(
self : ContextualSymbolizationReport,
) -> String {
self.frames.map(frame => frame.render()).join("\n")
}
///|
/// Return true when all frames were mapped.
pub fn ContextualSymbolizationReport::is_complete(
self : ContextualSymbolizationReport,
) -> Bool {
self.frames.length() == self.matched
}
///|
/// Return true when every mapped frame has embedded source context.
pub fn ContextualSymbolizationReport::has_complete_context(
self : ContextualSymbolizationReport,
) -> Bool {
self.matched == self.with_context
}
///|
/// Return only unmatched generated frames.
pub fn ContextualSymbolizationReport::unmatched_frames(
self : ContextualSymbolizationReport,
) -> Array[StackFrame] {
let result : Array[StackFrame] = []
for frame in self.frames {
if !frame.result.matched {
result.push(frame.result.generated)
}
}
result
}