fix(coverage): better handling of multi-byte characters (#15159)

This commit is contained in:
David Sherret 2022-07-11 19:02:11 -04:00 committed by GitHub
parent b68115db3a
commit 82431062fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 129 additions and 113 deletions

14
Cargo.lock generated
View file

@ -838,7 +838,7 @@ dependencies = [
"tempfile",
"test_util",
"text-size",
"text_lines",
"text_lines 0.6.0",
"tokio",
"tokio-util",
"tower-lsp",
@ -881,7 +881,7 @@ dependencies = [
"swc_ecma_transforms_typescript",
"swc_ecma_utils",
"swc_ecma_visit",
"text_lines",
"text_lines 0.4.1",
"url 2.2.2",
]
@ -1370,7 +1370,7 @@ dependencies = [
"dprint-core",
"jsonc-parser",
"serde",
"text_lines",
"text_lines 0.4.1",
]
[[package]]
@ -1412,7 +1412,7 @@ dependencies = [
"swc_common",
"swc_ecma_ast",
"swc_ecma_parser",
"text_lines",
"text_lines 0.4.1",
]
[[package]]
@ -4580,6 +4580,12 @@ dependencies = [
"serde",
]
[[package]]
name = "text_lines"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fd5828de7deaa782e1dd713006ae96b3bee32d3279b79eb67ecf8072c059bcf"
[[package]]
name = "textwrap"
version = "0.15.0"

View file

@ -94,7 +94,7 @@ serde = { version = "=1.0.139", features = ["derive"] }
serde_repr = "=0.1.8"
shell-escape = "=0.1.5"
text-size = "=1.1.0"
text_lines = "=0.4.1"
text_lines = "=0.6.0"
tokio = { version = "=1.19", features = ["full"] }
tokio-util = "=0.7.2"
tower-lsp = "=0.17.0"

View file

@ -46,9 +46,9 @@ DA:64,0
DA:65,0
DA:66,0
DA:67,0
DA:68,1
DA:68,0
DA:71,0
DA:74,1
LH:23
LH:22
LF:38
end_of_record

View file

@ -1,4 +1,4 @@
cover [WILDCARD]/coverage/complex.ts ... 60.526% (23/38)
cover [WILDCARD]/coverage/complex.ts ... 57.895% (22/38)
46 | export function unused(
47 | foo: string,
48 | bar: string,
@ -15,5 +15,6 @@ cover [WILDCARD]/coverage/complex.ts ... 60.526% (23/38)
65 | return (
66 | 0
67 | );
68 | }
-----|-----
71 | console.log("%s", () => 1);

View file

@ -6,10 +6,12 @@ use serde::Serialize;
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CoverageRange {
/// Start byte index.
pub start_offset: usize,
/// End byte index.
pub end_offset: usize,
/// Start character index.
#[serde(rename = "startOffset")]
pub start_char_offset: usize,
/// End character index.
#[serde(rename = "endOffset")]
pub end_char_offset: usize,
pub count: i64,
}

View file

@ -54,15 +54,15 @@ pub fn merge_scripts(
let first: &ScriptCoverage = &scripts[0];
(first.script_id.clone(), first.url.clone())
};
let mut range_to_funcs: BTreeMap<Range, Vec<FunctionCoverage>> =
let mut range_to_funcs: BTreeMap<CharRange, Vec<FunctionCoverage>> =
BTreeMap::new();
for script_cov in scripts {
for func_cov in script_cov.functions {
let root_range = {
let root_range_cov: &CoverageRange = &func_cov.ranges[0];
Range {
start: root_range_cov.start_offset,
end: root_range_cov.end_offset,
CharRange {
start: root_range_cov.start_char_offset,
end: root_range_cov.end_char_offset,
}
};
range_to_funcs
@ -85,12 +85,12 @@ pub fn merge_scripts(
}
#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)]
struct Range {
struct CharRange {
start: usize,
end: usize,
}
impl Ord for Range {
impl Ord for CharRange {
fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
if self.start != other.start {
self.start.cmp(&other.start)
@ -100,7 +100,7 @@ impl Ord for Range {
}
}
impl PartialOrd for Range {
impl PartialOrd for CharRange {
fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> {
if self.start != other.start {
self.start.partial_cmp(&other.start)
@ -249,7 +249,7 @@ fn merge_range_tree_children<'a>(
Vec::with_capacity(parent_trees.len());
let mut wrapped_children: Vec<Vec<&'a mut RangeTree<'a>>> =
Vec::with_capacity(parent_trees.len());
let mut open_range: Option<Range> = None;
let mut open_range: Option<CharRange> = None;
for _parent_tree in parent_trees.iter() {
flat_children.push(Vec::new());
@ -318,7 +318,7 @@ fn merge_range_tree_children<'a>(
.push(tree);
}
start_event_queue.set_pending_offset(open_range_end);
open_range = Some(Range {
open_range = Some(CharRange {
start: event.offset,
end: open_range_end,
});
@ -452,8 +452,8 @@ mod tests {
function_name: String::from("lib"),
is_block_coverage: true,
ranges: vec![CoverageRange {
start_offset: 0,
end_offset: 9,
start_char_offset: 0,
end_char_offset: 9,
count: 1,
}],
}],
@ -467,8 +467,8 @@ mod tests {
function_name: String::from("lib"),
is_block_coverage: true,
ranges: vec![CoverageRange {
start_offset: 0,
end_offset: 9,
start_char_offset: 0,
end_char_offset: 9,
count: 2,
}],
}],
@ -483,8 +483,8 @@ mod tests {
function_name: String::from("lib"),
is_block_coverage: true,
ranges: vec![CoverageRange {
start_offset: 0,
end_offset: 9,
start_char_offset: 0,
end_char_offset: 9,
count: 3,
}],
}],
@ -506,13 +506,13 @@ mod tests {
is_block_coverage: true,
ranges: vec![
CoverageRange {
start_offset: 0,
end_offset: 9,
start_char_offset: 0,
end_char_offset: 9,
count: 10,
},
CoverageRange {
start_offset: 3,
end_offset: 6,
start_char_offset: 3,
end_char_offset: 6,
count: 1,
},
],
@ -528,13 +528,13 @@ mod tests {
is_block_coverage: true,
ranges: vec![
CoverageRange {
start_offset: 0,
end_offset: 9,
start_char_offset: 0,
end_char_offset: 9,
count: 20,
},
CoverageRange {
start_offset: 3,
end_offset: 6,
start_char_offset: 3,
end_char_offset: 6,
count: 2,
},
],
@ -551,13 +551,13 @@ mod tests {
is_block_coverage: true,
ranges: vec![
CoverageRange {
start_offset: 0,
end_offset: 9,
start_char_offset: 0,
end_char_offset: 9,
count: 30,
},
CoverageRange {
start_offset: 3,
end_offset: 6,
start_char_offset: 3,
end_char_offset: 6,
count: 3,
},
],
@ -580,13 +580,13 @@ mod tests {
is_block_coverage: true,
ranges: vec![
CoverageRange {
start_offset: 0,
end_offset: 9,
start_char_offset: 0,
end_char_offset: 9,
count: 10,
},
CoverageRange {
start_offset: 2,
end_offset: 5,
start_char_offset: 2,
end_char_offset: 5,
count: 1,
},
],
@ -602,13 +602,13 @@ mod tests {
is_block_coverage: true,
ranges: vec![
CoverageRange {
start_offset: 0,
end_offset: 9,
start_char_offset: 0,
end_char_offset: 9,
count: 20,
},
CoverageRange {
start_offset: 4,
end_offset: 7,
start_char_offset: 4,
end_char_offset: 7,
count: 2,
},
],
@ -625,23 +625,23 @@ mod tests {
is_block_coverage: true,
ranges: vec![
CoverageRange {
start_offset: 0,
end_offset: 9,
start_char_offset: 0,
end_char_offset: 9,
count: 30,
},
CoverageRange {
start_offset: 2,
end_offset: 5,
start_char_offset: 2,
end_char_offset: 5,
count: 21,
},
CoverageRange {
start_offset: 4,
end_offset: 5,
start_char_offset: 4,
end_char_offset: 5,
count: 3,
},
CoverageRange {
start_offset: 5,
end_offset: 7,
start_char_offset: 5,
end_char_offset: 7,
count: 12,
},
],
@ -664,23 +664,23 @@ mod tests {
is_block_coverage: true,
ranges: vec![
CoverageRange {
start_offset: 0,
end_offset: 9,
start_char_offset: 0,
end_char_offset: 9,
count: 1,
},
CoverageRange {
start_offset: 1,
end_offset: 8,
start_char_offset: 1,
end_char_offset: 8,
count: 6,
},
CoverageRange {
start_offset: 1,
end_offset: 5,
start_char_offset: 1,
end_char_offset: 5,
count: 5,
},
CoverageRange {
start_offset: 5,
end_offset: 8,
start_char_offset: 5,
end_char_offset: 8,
count: 7,
},
],
@ -696,23 +696,23 @@ mod tests {
is_block_coverage: true,
ranges: vec![
CoverageRange {
start_offset: 0,
end_offset: 9,
start_char_offset: 0,
end_char_offset: 9,
count: 4,
},
CoverageRange {
start_offset: 1,
end_offset: 8,
start_char_offset: 1,
end_char_offset: 8,
count: 8,
},
CoverageRange {
start_offset: 1,
end_offset: 5,
start_char_offset: 1,
end_char_offset: 5,
count: 9,
},
CoverageRange {
start_offset: 5,
end_offset: 8,
start_char_offset: 5,
end_char_offset: 8,
count: 7,
},
],
@ -729,13 +729,13 @@ mod tests {
is_block_coverage: true,
ranges: vec![
CoverageRange {
start_offset: 0,
end_offset: 9,
start_char_offset: 0,
end_char_offset: 9,
count: 5,
},
CoverageRange {
start_offset: 1,
end_offset: 8,
start_char_offset: 1,
end_char_offset: 8,
count: 14,
},
],
@ -758,13 +758,13 @@ mod tests {
is_block_coverage: true,
ranges: vec![
CoverageRange {
start_offset: 0,
end_offset: 7,
start_char_offset: 0,
end_char_offset: 7,
count: 10,
},
CoverageRange {
start_offset: 0,
end_offset: 4,
start_char_offset: 0,
end_char_offset: 4,
count: 1,
},
],
@ -780,18 +780,18 @@ mod tests {
is_block_coverage: true,
ranges: vec![
CoverageRange {
start_offset: 0,
end_offset: 7,
start_char_offset: 0,
end_char_offset: 7,
count: 20,
},
CoverageRange {
start_offset: 1,
end_offset: 6,
start_char_offset: 1,
end_char_offset: 6,
count: 11,
},
CoverageRange {
start_offset: 2,
end_offset: 5,
start_char_offset: 2,
end_char_offset: 5,
count: 2,
},
],
@ -808,23 +808,23 @@ mod tests {
is_block_coverage: true,
ranges: vec![
CoverageRange {
start_offset: 0,
end_offset: 7,
start_char_offset: 0,
end_char_offset: 7,
count: 30,
},
CoverageRange {
start_offset: 0,
end_offset: 6,
start_char_offset: 0,
end_char_offset: 6,
count: 21,
},
CoverageRange {
start_offset: 1,
end_offset: 5,
start_char_offset: 1,
end_char_offset: 5,
count: 12,
},
CoverageRange {
start_offset: 2,
end_offset: 4,
start_char_offset: 2,
end_char_offset: 4,
count: 3,
},
],

View file

@ -202,16 +202,18 @@ fn generate_coverage_report(
continue;
}
let source_line_index =
text_lines.line_index(function.ranges[0].start_offset);
let dest_line_index = text_lines.line_index(
text_lines
.byte_index_from_char_index(function.ranges[0].start_char_offset),
);
let line_index = if let Some(source_map) = maybe_source_map.as_ref() {
source_map
.tokens()
.find(|token| token.get_dst_line() as usize == source_line_index)
.find(|token| token.get_dst_line() as usize == dest_line_index)
.map(|token| token.get_src_line() as usize)
.unwrap_or(0)
} else {
source_line_index
dest_line_index
};
coverage_report.named_functions.push(FunctionCoverageItem {
@ -224,7 +226,9 @@ fn generate_coverage_report(
for (block_number, function) in script_coverage.functions.iter().enumerate() {
let block_hits = function.ranges[0].count;
for (branch_number, range) in function.ranges[1..].iter().enumerate() {
let source_line_index = text_lines.line_index(range.start_offset);
let source_line_index = text_lines.line_index(
text_lines.byte_index_from_char_index(range.start_char_offset),
);
let line_index = if let Some(source_map) = maybe_source_map.as_ref() {
source_map
.tokens()
@ -264,11 +268,14 @@ fn generate_coverage_report(
// parts of a line in color (word diff style) instead of the entire line.
let mut line_counts = Vec::with_capacity(text_lines.lines_count());
for line_index in 0..text_lines.lines_count() {
let line_start_offset = text_lines.line_start(line_index);
let line_end_offset = text_lines.line_end(line_index);
let line_start_byte_offset = text_lines.line_start(line_index);
let line_start_char_offset = text_lines.char_index(line_start_byte_offset);
let line_end_byte_offset = text_lines.line_end(line_index);
let line_end_char_offset = text_lines.char_index(line_end_byte_offset);
let ignore = comment_ranges.iter().any(|range| {
range.start <= line_start_offset && range.end >= line_end_offset
}) || script_source[line_start_offset..line_end_offset]
range.start <= line_start_byte_offset && range.end >= line_end_byte_offset
}) || script_source
[line_start_byte_offset..line_end_byte_offset]
.trim()
.is_empty();
let mut count = 0;
@ -280,8 +287,8 @@ fn generate_coverage_report(
// as long as the code has been evaluated.
for function in &script_coverage.functions {
for range in &function.ranges {
if range.start_offset <= line_start_offset
&& range.end_offset >= line_end_offset
if range.start_char_offset <= line_start_char_offset
&& range.end_char_offset >= line_end_char_offset
{
count += range.count;
}
@ -295,8 +302,8 @@ fn generate_coverage_report(
continue;
}
let overlaps = range.start_offset < line_end_offset
&& range.end_offset > line_start_offset;
let overlaps = range.start_char_offset < line_end_char_offset
&& range.end_char_offset > line_start_char_offset;
if overlaps {
count = 0;
}

View file

@ -134,8 +134,8 @@ impl<'rt> RangeTree<'rt> {
while let Some((cur, parent_count)) = stack.pop() {
let count: i64 = parent_count + cur.delta;
ranges.push(CoverageRange {
start_offset: cur.start,
end_offset: cur.end,
start_char_offset: cur.start,
end_char_offset: cur.end,
count,
});
for child in cur.children.iter().rev() {
@ -165,14 +165,14 @@ impl<'rt> RangeTree<'rt> {
) -> Option<&'a mut RangeTree<'a>> {
let has_range: bool = match ranges.peek() {
None => false,
Some(range) => range.start_offset < parent_end,
Some(range) => range.start_char_offset < parent_end,
};
if !has_range {
return None;
}
let range = ranges.next().unwrap();
let start: usize = range.start_offset;
let end: usize = range.end_offset;
let start: usize = range.start_char_offset;
let end: usize = range.end_char_offset;
let count: i64 = range.count;
let delta: i64 = count - parent_count;
let mut children: Vec<&mut RangeTree> = Vec::new();
@ -193,8 +193,8 @@ mod tests {
fn from_sorted_ranges_empty() {
let rta = RangeTreeArena::new();
let inputs: Vec<CoverageRange> = vec![CoverageRange {
start_offset: 0,
end_offset: 9,
start_char_offset: 0,
end_char_offset: 9,
count: 1,
}];
let actual: Option<&mut RangeTree> =