///|
fn section_offset_position(position : Position, offset : Position) -> Position {
Position::new(
line=position.line + offset.line,
column=if position.line == 0 {
position.column + offset.column
} else {
position.column
},
)
}
///|
fn append_decoded_section(
destination : DecodedSourceMap,
section : DecodedSourceMap,
offset : Position,
) -> Unit {
let source_base = destination.sources.length()
for source in section.sources {
destination.sources.push(source)
}
for mapping in section.mappings {
let generated = section_offset_position(mapping.generated, offset)
let original = match mapping.original {
Some(original) =>
Some(
OriginalPosition::new(
source_index=original.source_index + source_base,
line=original.line,
column=original.column,
),
)
None => None
}
destination.mappings.push({ generated, original, name: mapping.name })
}
}
///|
fn decode_document_inner(
document : SourceMapDocument,
map_url : String?,
) -> DecodedSourceMap raise SourceMapError {
match document {
Regular(map) => decode_regular_source_map(map, map_url?)
Indexed(index) => {
let diagnostics = validate(Indexed(index))
if diagnostics_have_errors(diagnostics) {
raise InvalidDocument(message=diagnostics[0].render())
}
let result = DecodedSourceMap::new(file?=index.file)
for section in index.sections {
let decoded = decode_document_inner(section.map, map_url)
append_decoded_section(result, decoded, section.offset)
}
result.mappings.sort_by((left, right) => {
left.generated.compare(right.generated)
})
result
}
}
}
///|
/// Decode either a regular or index source map for querying.
///
/// Index maps are recursively expanded. Source entries are intentionally kept
/// section-local, even when URLs repeat, so that distinct embedded contents and
/// ignore-list flags cannot be conflated.
pub fn decode_document(
document : SourceMapDocument,
map_url? : String,
) -> DecodedSourceMap raise SourceMapError {
decode_document_inner(document, map_url)
}
///|
fn unique_names(mappings : ArrayView[Mapping]) -> Array[String] {
let result : Array[String] = []
let seen : Map[String, Unit] = Map([])
for mapping in mappings {
match mapping.name {
Some(name) =>
if !seen.contains(name) {
seen[name] = ()
result.push(name)
}
None => ()
}
}
result
}
///|
fn decoded_sources(decoded : DecodedSourceMap) -> Array[String?] {
decoded.sources.map(source => source.url)
}
///|
fn decoded_contents(decoded : DecodedSourceMap) -> Array[String?]? {
let contents = decoded.sources.map(source => source.content)
let mut has_content = false
for content in contents {
if content is Some(_) {
has_content = true
break
}
}
if has_content {
Some(contents)
} else {
None
}
}
///|
fn decoded_ignore_list(decoded : DecodedSourceMap) -> Array[Int] {
let result : Array[Int] = []
for index, source in decoded.sources {
if source.ignored {
result.push(index)
}
}
result
}
///|
/// Encode a decoded source map as a normalized regular document.
pub fn encode_decoded(
decoded : DecodedSourceMap,
) -> RegularSourceMap raise SourceMapError {
let names = unique_names(decoded.mappings)
RegularSourceMap::new(
file?=decoded.file,
sources=decoded_sources(decoded),
sources_content?=decoded_contents(decoded),
names~,
mappings=encode_mappings(decoded.mappings, names~),
ignore_list=decoded_ignore_list(decoded),
)
}
///|
/// Flatten a regular or index source map into a normalized regular document.
pub fn flatten(
document : SourceMapDocument,
) -> RegularSourceMap raise SourceMapError {
encode_decoded(decode_document(document))
}