code-gauge 10.1.0 → 10.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +37 -15
  2. package/THIRD-PARTY-NOTICES.txt +8 -8
  3. package/dist/checkCommand.cjs +1 -1
  4. package/dist/checkCommand.cjs.map +1 -1
  5. package/dist/checkCommand.js +1 -1
  6. package/dist/checkCommand.js.map +1 -1
  7. package/dist/crossFileDuplication.cjs +1 -1
  8. package/dist/crossFileDuplication.cjs.map +1 -1
  9. package/dist/crossFileDuplication.d.ts +3 -2
  10. package/dist/crossFileDuplication.js +1 -1
  11. package/dist/crossFileDuplication.js.map +1 -1
  12. package/dist/crossFileNearMiss.cjs +1 -1
  13. package/dist/crossFileNearMiss.cjs.map +1 -1
  14. package/dist/crossFileNearMiss.d.ts +9 -4
  15. package/dist/crossFileNearMiss.js +1 -1
  16. package/dist/crossFileNearMiss.js.map +1 -1
  17. package/dist/duplication.cjs +1 -1
  18. package/dist/duplication.cjs.map +1 -1
  19. package/dist/duplication.d.ts +28 -7
  20. package/dist/duplication.js +1 -1
  21. package/dist/duplication.js.map +1 -1
  22. package/dist/thresholdCheck.cjs +1 -1
  23. package/dist/thresholdCheck.cjs.map +1 -1
  24. package/dist/thresholdCheck.d.ts +2 -0
  25. package/dist/thresholdCheck.js +1 -1
  26. package/dist/thresholdCheck.js.map +1 -1
  27. package/dist/thresholds.cjs.map +1 -1
  28. package/dist/thresholds.d.ts +2 -3
  29. package/dist/thresholds.js.map +1 -1
  30. package/dist/types.d.ts +20 -8
  31. package/native/Cargo.lock +16 -16
  32. package/native/Cargo.toml +8 -8
  33. package/native/code-gauge.wasm +0 -0
  34. package/native/src/complexity.rs +10 -1
  35. package/native/src/container.rs +158 -7
  36. package/native/src/dep_degree.rs +13 -14
  37. package/native/src/duplication.rs +302 -137
  38. package/native/src/functions.rs +112 -24
  39. package/native/src/languages.rs +5 -0
  40. package/native/src/measure.rs +9 -4
  41. package/native/src/near_miss.rs +303 -52
  42. package/package.json +8 -8
@@ -1,4 +1,5 @@
1
1
  use rustc_hash::{FxHashMap, FxHashSet};
2
+ use std::borrow::Cow;
2
3
 
3
4
  /// N-gram size for the candidate index and local-match anchors (NIL's default); shared with
4
5
  /// crossFileNearMiss.ts.
@@ -40,6 +41,11 @@ pub(crate) struct Block {
40
41
  is_content: Vec<bool>,
41
42
  /// Identifiers anonymized by first occurrence within the block.
42
43
  sequence: Vec<i32>,
44
+ /// The content of the block as written: per token its content key, or the number of its
45
+ /// identifier. Equal for equal content wherever it lies, which symbols, numbered by first
46
+ /// occurrence in the file, are not; ordered like `writtenContent` in crossFileNearMiss.ts, so
47
+ /// that both detectors take the sides of a pair in the same order.
48
+ written_content: Vec<i64>,
43
49
  pub ngrams: FxHashSet<i32>,
44
50
  /// The n-grams occurring exactly once in the block with their offsets, sorted by hash so two
45
51
  /// blocks' local-match anchors intersect by merging.
@@ -48,15 +54,18 @@ pub(crate) struct Block {
48
54
  /// Matcher::new turns the counts into information-weighted counts.
49
55
  content: Vec<(i32, u64)>,
50
56
  content_total: u64,
51
- /// The sequence with its top-level statements in canonical order, when it has enough of them
52
- /// for statement-order-insensitive comparison.
53
- canonical_sequence: Option<Vec<i32>>,
57
+ /// The sequence with its top-level statements in canonical order and the block offset each of
58
+ /// its tokens comes from, when the block has enough statements for statement-order-insensitive
59
+ /// comparison.
60
+ canonical_sequence: Option<(Vec<i32>, Vec<usize>)>,
54
61
  }
55
62
 
56
63
  impl Block {
64
+ /// `token_keys` holds the content key of each non-identifier token of the file.
57
65
  pub fn new(
58
66
  symbols: &[i32],
59
67
  is_content: &[bool],
68
+ token_keys: &[i64],
60
69
  start: usize,
61
70
  end: usize,
62
71
  statements: Vec<(usize, usize)>,
@@ -64,16 +73,25 @@ impl Block {
64
73
  let symbols = symbols[start..end].to_vec();
65
74
  let is_content = is_content[start..end].to_vec();
66
75
  // Identifier-blind, so a block copied into different surroundings (renumbering its
67
- // identifiers) or with reordered statements still shares its n-grams.
68
- let ngram_hashes: Vec<i32> = symbols
76
+ // identifiers) or with reordered statements still shares its n-grams. Hashed from the
77
+ // content of the tokens: with symbols, numbered by first occurrence in the file, which
78
+ // n-grams collide, and so which anchor a match, would depend on where other code stands.
79
+ let hashed: Vec<i32> = symbols
80
+ .iter()
81
+ .zip(&token_keys[start..end])
82
+ .map(|(&symbol, &token_key)| {
83
+ if symbol < 0 {
84
+ BLIND_IDENTIFIER
85
+ } else {
86
+ (token_key ^ (token_key >> 32)) as i32
87
+ }
88
+ })
89
+ .collect();
90
+ let ngram_hashes: Vec<i32> = hashed
69
91
  .windows(NGRAM_SIZE)
70
92
  .map(|window| {
71
- window.iter().fold(5381i32, |hash, &symbol| {
72
- hash.wrapping_mul(31).wrapping_add(if symbol < 0 {
73
- BLIND_IDENTIFIER
74
- } else {
75
- symbol
76
- })
93
+ window.iter().fold(5381i32, |hash, &value| {
94
+ hash.wrapping_mul(31).wrapping_add(value)
77
95
  })
78
96
  })
79
97
  .collect();
@@ -91,14 +109,21 @@ impl Block {
91
109
  let canonical_sequence = (statements.len() >= MIN_REORDER_STATEMENT_COUNT).then(|| {
92
110
  canonical_sequence(
93
111
  &symbols,
112
+ &token_keys[start..end],
94
113
  statements.iter().map(|&(statement_start, statement_end)| {
95
114
  (statement_start - start, statement_end - start)
96
115
  }),
97
116
  )
98
117
  });
118
+ let sequence = anonymize(&symbols);
99
119
  Block {
100
120
  start,
101
- sequence: anonymize(&symbols),
121
+ written_content: sequence
122
+ .iter()
123
+ .zip(&token_keys[start..end])
124
+ .map(|(&symbol, &token_key)| content_value(symbol, token_key))
125
+ .collect(),
126
+ sequence,
102
127
  ngrams: occurrence_counts.into_keys().collect(),
103
128
  unique_ngrams,
104
129
  content: count_content(&symbols, &is_content),
@@ -112,16 +137,65 @@ impl Block {
112
137
  pub fn len(&self) -> usize {
113
138
  self.symbols.len()
114
139
  }
140
+
141
+ /// The content of the block or of a core of it, as written (see `written_content`), with the
142
+ /// identifiers of a core numbered within it.
143
+ fn content(&self, core: Option<(usize, usize)>, token_keys: &[i64]) -> Cow<'_, [i64]> {
144
+ if core.is_none() {
145
+ return Cow::Borrowed(&self.written_content);
146
+ }
147
+ let (sequence, positions) = self.compared(core, false);
148
+ sequence
149
+ .iter()
150
+ .zip(&positions)
151
+ .map(|(&symbol, &position)| content_value(symbol, token_keys[position]))
152
+ .collect()
153
+ }
154
+
155
+ /// The sequence a match compared, with the absolute token index of each of its symbols: a core,
156
+ /// the block with its statements in canonical order, or the block as written.
157
+ fn compared(
158
+ &self,
159
+ core: Option<(usize, usize)>,
160
+ reordered: bool,
161
+ ) -> (Cow<'_, [i32]>, Vec<usize>) {
162
+ match (core, &self.canonical_sequence) {
163
+ (Some((start, end)), _) => (
164
+ Cow::Owned(anonymize(
165
+ &self.symbols[start - self.start..end - self.start],
166
+ )),
167
+ (start..end).collect(),
168
+ ),
169
+ (None, Some((sequence, offsets))) if reordered => (
170
+ Cow::Borrowed(sequence),
171
+ offsets.iter().map(|offset| self.start + offset).collect(),
172
+ ),
173
+ _ => (
174
+ Cow::Borrowed(&self.sequence),
175
+ (self.start..self.start + self.len()).collect(),
176
+ ),
177
+ }
178
+ }
115
179
  }
116
180
 
181
+ /// Runs of tokens, as half-open absolute token ranges in position order.
182
+ pub(crate) type TokenRuns = Vec<(usize, usize)>;
183
+
117
184
  /// A verified core in each block of a pair, as absolute token ranges.
118
185
  pub(crate) type CorePair = ((usize, usize), (usize, usize));
119
186
 
120
187
  /// How a verified pair matched: whole blocks, or every anchored core pair (one per gap-split
121
188
  /// chain segment) where the blocks share a copy embedded in different code.
122
189
  pub(crate) enum PairMatch {
123
- Whole,
124
- Local(Vec<CorePair>),
190
+ Whole(Alignment),
191
+ Local(Vec<(CorePair, Alignment)>),
192
+ }
193
+
194
+ /// What Matcher::align needs to recover the tokens a verified match pairs.
195
+ #[derive(Clone, Copy)]
196
+ pub(crate) struct Alignment {
197
+ /// Whether the blocks matched only with their statements in canonical order.
198
+ reordered: bool,
125
199
  }
126
200
 
127
201
  /// Verifies near-miss block pairs: token-level LCS against the larger side (NiCad's per-fragment
@@ -182,21 +256,69 @@ impl Matcher {
182
256
  &right.content,
183
257
  right.content_total,
184
258
  )
185
- && (lcs_length(&left.sequence, &right.sequence) * 100 >= required
186
- || self.matches_reordered(left, right, required))
187
259
  {
188
- return Some(PairMatch::Whole);
260
+ let in_order = lcs_length(&left.sequence, &right.sequence);
261
+ if in_order * 100 >= required {
262
+ return Some(PairMatch::Whole(Alignment { reordered: false }));
263
+ }
264
+ // With their top-level statements (each anonymized on its own) in a canonical order,
265
+ // a copy whose independent statements were swapped still matches.
266
+ if let (Some((left, _)), Some((right, _))) =
267
+ (&left.canonical_sequence, &right.canonical_sequence)
268
+ {
269
+ let reordered = lcs_length(left, right);
270
+ if reordered * 100 >= required {
271
+ return Some(PairMatch::Whole(Alignment { reordered: true }));
272
+ }
273
+ }
274
+ }
275
+ // Of the equally long chains of anchors, the one kept depends on which block comes first,
276
+ // so the blocks are taken in the order of their content rather than of their positions.
277
+ if right.written_content < left.written_content {
278
+ return self
279
+ .match_locally(right, left)
280
+ .map(|matched| match matched {
281
+ PairMatch::Local(cores) => PairMatch::Local(
282
+ cores
283
+ .into_iter()
284
+ .map(|((right_core, left_core), alignment)| {
285
+ ((left_core, right_core), alignment)
286
+ })
287
+ .collect(),
288
+ ),
289
+ whole => whole,
290
+ });
189
291
  }
190
292
  self.match_locally(left, right)
191
293
  }
192
294
 
193
- /// Compares the blocks with their top-level statements (each anonymized on its own) in a
194
- /// canonical order, so a copy whose independent statements were swapped still matches.
195
- fn matches_reordered(&self, left: &Block, right: &Block, required: usize) -> bool {
196
- match (&left.canonical_sequence, &right.canonical_sequence) {
197
- (Some(left), Some(right)) => lcs_length(left, right) * 100 >= required,
198
- _ => false,
199
- }
295
+ /// The token runs a verified match pairs, per side as absolute token ranges: those of a longest
296
+ /// common subsequence of what the match compared, `cores` or else the whole blocks. Of the
297
+ /// equally long subsequences, which one is marked depends on which sequence comes first, so
298
+ /// the sides are taken in the order of their content rather than of their positions.
299
+ /// `token_keys` holds the content key of each non-identifier token of the file.
300
+ pub fn align(
301
+ &self,
302
+ left: &Block,
303
+ right: &Block,
304
+ cores: Option<CorePair>,
305
+ alignment: Alignment,
306
+ token_keys: &[i64],
307
+ ) -> (TokenRuns, TokenRuns) {
308
+ let (left_core, right_core) = cores.unzip();
309
+ let (left_sequence, left_positions) = left.compared(left_core, alignment.reordered);
310
+ let (right_sequence, right_positions) = right.compared(right_core, alignment.reordered);
311
+ let (left_matched, right_matched) =
312
+ if left.content(left_core, token_keys) <= right.content(right_core, token_keys) {
313
+ mark_lcs(&left_sequence, &right_sequence)
314
+ } else {
315
+ let (right_matched, left_matched) = mark_lcs(&right_sequence, &left_sequence);
316
+ (left_matched, right_matched)
317
+ };
318
+ (
319
+ to_runs(left_positions, left_matched),
320
+ to_runs(right_positions, right_matched),
321
+ )
200
322
  }
201
323
 
202
324
  /// Matches the cores two blocks share inside different surroundings (a copy wrapped in added
@@ -242,7 +364,7 @@ impl Matcher {
242
364
  .map(|index| anchors[index])
243
365
  .collect();
244
366
  let chain = longest_increasing_chain(&run_anchors);
245
- let cores: Vec<CorePair> = chain_segments(&chain)
367
+ let cores: Vec<(CorePair, Alignment)> = chain_segments(&chain)
246
368
  .filter_map(|segment| {
247
369
  let (first, last) = (segment[0], segment[segment.len() - 1]);
248
370
  let (left_start, left_end) = (first.0, last.0 + NGRAM_SIZE);
@@ -250,23 +372,28 @@ impl Matcher {
250
372
  let (left_length, right_length) = (left_end - left_start, right_end - right_start);
251
373
  let shorter = left_length.min(right_length);
252
374
  let required = self.min_similarity_percent * left_length.max(right_length);
253
- let verified = shorter >= self.min_tokens
254
- && shorter * 100 >= required
255
- && anchored_token_count(segment) * 100 >= MIN_ANCHOR_COVERAGE_PERCENT * shorter
256
- && self.spans_share_content(
375
+ if shorter < self.min_tokens
376
+ || shorter * 100 < required
377
+ || anchored_token_count(segment) * 100 < MIN_ANCHOR_COVERAGE_PERCENT * shorter
378
+ || !self.spans_share_content(
257
379
  left,
258
380
  (left_start, left_end),
259
381
  right,
260
382
  (right_start, right_end),
261
383
  )
262
- && lcs_length(
263
- &anonymize(&left.symbols[left_start..left_end]),
264
- &anonymize(&right.symbols[right_start..right_end]),
265
- ) * 100
266
- >= required;
267
- verified.then_some((
268
- (left.start + left_start, left.start + left_end),
269
- (right.start + right_start, right.start + right_end),
384
+ {
385
+ return None;
386
+ }
387
+ let lcs_length = lcs_length(
388
+ &anonymize(&left.symbols[left_start..left_end]),
389
+ &anonymize(&right.symbols[right_start..right_end]),
390
+ );
391
+ (lcs_length * 100 >= required).then_some((
392
+ (
393
+ (left.start + left_start, left.start + left_end),
394
+ (right.start + right_start, right.start + right_end),
395
+ ),
396
+ Alignment { reordered: false },
270
397
  ))
271
398
  })
272
399
  .collect();
@@ -361,25 +488,43 @@ fn anonymize(symbols: &[i32]) -> Vec<i32> {
361
488
  }
362
489
 
363
490
  /// The block's units (its top-level statements, as block-relative offsets, and the token runs
364
- /// between them), each anonymized on its own and sorted, concatenated.
491
+ /// between them), each anonymized on its own and sorted, concatenated, with the block offset of
492
+ /// every token. The units are ordered by the content of their tokens (`token_keys`, per token of
493
+ /// the block): symbols, numbered by first occurrence in the file, would order them by where
494
+ /// other code stands.
365
495
  fn canonical_sequence(
366
496
  symbols: &[i32],
497
+ token_keys: &[i64],
367
498
  statements: impl Iterator<Item = (usize, usize)>,
368
- ) -> Vec<i32> {
369
- let mut units: Vec<Vec<i32>> = Vec::new();
499
+ ) -> (Vec<i32>, Vec<usize>) {
500
+ let mut units: Vec<(Vec<i64>, usize, Vec<i32>)> = Vec::new();
501
+ let mut push_unit = |start: usize, end: usize| {
502
+ let unit = anonymize(&symbols[start..end]);
503
+ let content = unit
504
+ .iter()
505
+ .zip(&token_keys[start..end])
506
+ .map(|(&symbol, &token_key)| content_value(symbol, token_key))
507
+ .collect();
508
+ units.push((content, start, unit));
509
+ };
370
510
  let mut cursor = 0;
371
511
  for (start, end) in statements {
372
512
  if cursor < start {
373
- units.push(anonymize(&symbols[cursor..start]));
513
+ push_unit(cursor, start);
374
514
  }
375
- units.push(anonymize(&symbols[start..end]));
515
+ push_unit(start, end);
376
516
  cursor = end;
377
517
  }
378
518
  if cursor < symbols.len() {
379
- units.push(anonymize(&symbols[cursor..]));
519
+ push_unit(cursor, symbols.len());
380
520
  }
381
521
  units.sort_unstable();
382
- units.concat()
522
+ let offsets = units
523
+ .iter()
524
+ .flat_map(|(_, start, unit)| *start..*start + unit.len())
525
+ .collect();
526
+ let sequence = units.into_iter().flat_map(|(_, _, unit)| unit).collect();
527
+ (sequence, offsets)
383
528
  }
384
529
 
385
530
  /// The longest chain of anchors increasing in both blocks (anchors arrive sorted by left offset),
@@ -419,13 +564,20 @@ fn chain_segments(chain: &[(usize, usize)]) -> impl Iterator<Item = &[(usize, us
419
564
  })
420
565
  }
421
566
 
422
- /// Longest-common-subsequence LENGTH via the Allison–Dix bit-parallel recurrence. Only the length
423
- /// is needed and LCS length is algorithm-independent, so u64 words are safe even though the
424
- /// TypeScript port in src/duplication.ts uses 32-bit words.
567
+ /// Longest-common-subsequence LENGTH. Only the length is needed and LCS length is
568
+ /// algorithm-independent, so u64 words are safe even though the TypeScript port in
569
+ /// src/duplication.ts uses 32-bit words.
425
570
  fn lcs_length(a: &[i32], b: &[i32]) -> usize {
426
- if a.is_empty() || b.is_empty() {
427
- return 0;
428
- }
571
+ lcs_growth_bits(a, b.iter())
572
+ .iter()
573
+ .map(|word| word.count_ones() as usize)
574
+ .sum()
575
+ }
576
+
577
+ /// One bit per position of `a`, set where the longest common subsequence of `symbols` with the
578
+ /// prefix of `a` ending there is longer than with the prefix before it, so the set bits up to a
579
+ /// position count that length; via the Allison–Dix bit-parallel recurrence.
580
+ fn lcs_growth_bits<'a>(a: &[i32], symbols: impl Iterator<Item = &'a i32>) -> Vec<u64> {
429
581
  let word_count = a.len().div_ceil(64);
430
582
  let mut position_masks: FxHashMap<i32, Vec<u64>> = FxHashMap::default();
431
583
  for (index, &symbol) in a.iter().enumerate() {
@@ -435,7 +587,7 @@ fn lcs_length(a: &[i32], b: &[i32]) -> usize {
435
587
  }
436
588
 
437
589
  let mut v = vec![0u64; word_count];
438
- for symbol in b {
590
+ for symbol in symbols {
439
591
  let match_mask = position_masks.get(symbol);
440
592
  // `(v << 1) | 1` shifts a carry bit across words; subtraction borrows across words.
441
593
  let mut shift_carry = 1u64;
@@ -451,5 +603,104 @@ fn lcs_length(a: &[i32], b: &[i32]) -> usize {
451
603
  *slot = x & !difference;
452
604
  }
453
605
  }
454
- v.iter().map(|word| word.count_ones() as usize).sum()
606
+ v
607
+ }
608
+
609
+ /// Which tokens of each sequence a longest common subsequence pairs, by Hirschberg's divide and
610
+ /// conquer over the bit-parallel recurrence, which alone yields lengths but no pairing.
611
+ fn mark_lcs(a: &[i32], b: &[i32]) -> (Vec<bool>, Vec<bool>) {
612
+ let mut a_matched = vec![false; a.len()];
613
+ let mut b_matched = vec![false; b.len()];
614
+ mark_lcs_into(a, b, &mut a_matched, &mut b_matched);
615
+ (a_matched, b_matched)
616
+ }
617
+
618
+ fn mark_lcs_into(a: &[i32], b: &[i32], a_matched: &mut [bool], b_matched: &mut [bool]) {
619
+ // A common prefix or suffix belongs to some longest common subsequence.
620
+ let prefix = a
621
+ .iter()
622
+ .zip(b)
623
+ .take_while(|(left, right)| left == right)
624
+ .count();
625
+ let suffix = a[prefix..]
626
+ .iter()
627
+ .rev()
628
+ .zip(b[prefix..].iter().rev())
629
+ .take_while(|(left, right)| left == right)
630
+ .count();
631
+ let (a_end, b_end) = (a.len() - suffix, b.len() - suffix);
632
+ a_matched[..prefix].fill(true);
633
+ b_matched[..prefix].fill(true);
634
+ a_matched[a_end..].fill(true);
635
+ b_matched[b_end..].fill(true);
636
+ let (a, b) = (&a[prefix..a_end], &b[prefix..b_end]);
637
+ let (a_matched, b_matched) = (&mut a_matched[prefix..a_end], &mut b_matched[prefix..b_end]);
638
+ if a.is_empty() || b.is_empty() {
639
+ return;
640
+ }
641
+ if a.len() == 1 {
642
+ if let Some(position) = b.iter().position(|symbol| *symbol == a[0]) {
643
+ a_matched[0] = true;
644
+ b_matched[position] = true;
645
+ }
646
+ return;
647
+ }
648
+ let middle = a.len() / 2;
649
+ let reversed_b: Vec<i32> = b.iter().rev().copied().collect();
650
+ let forward = lcs_prefix_lengths(a[..middle].iter(), b);
651
+ let backward = lcs_prefix_lengths(a[middle..].iter().rev(), &reversed_b);
652
+ let mut split = 0;
653
+ for candidate in 1..=b.len() {
654
+ if forward[candidate] + backward[b.len() - candidate]
655
+ > forward[split] + backward[b.len() - split]
656
+ {
657
+ split = candidate;
658
+ }
659
+ }
660
+ let (a_head, a_tail) = a_matched.split_at_mut(middle);
661
+ let (b_head, b_tail) = b_matched.split_at_mut(split);
662
+ mark_lcs_into(&a[..middle], &b[..split], a_head, b_head);
663
+ mark_lcs_into(&a[middle..], &b[split..], a_tail, b_tail);
664
+ }
665
+
666
+ /// The longest-common-subsequence length of `a` with every prefix of `b`.
667
+ fn lcs_prefix_lengths<'a>(a: impl Iterator<Item = &'a i32>, b: &[i32]) -> Vec<u32> {
668
+ let bits = lcs_growth_bits(b, a);
669
+ let mut lengths = Vec::with_capacity(b.len() + 1);
670
+ let mut length = 0;
671
+ lengths.push(length);
672
+ for index in 0..b.len() {
673
+ length += (bits[index / 64] >> (index % 64)) as u32 & 1;
674
+ lengths.push(length);
675
+ }
676
+ lengths
677
+ }
678
+
679
+ /// The maximal runs of the matched positions, as half-open ranges.
680
+ fn to_runs(positions: Vec<usize>, matched: Vec<bool>) -> TokenRuns {
681
+ let mut matched_positions: Vec<usize> = positions
682
+ .into_iter()
683
+ .zip(matched)
684
+ .filter(|&(_, matched)| matched)
685
+ .map(|(position, _)| position)
686
+ .collect();
687
+ matched_positions.sort_unstable();
688
+ let mut runs = TokenRuns::new();
689
+ for position in matched_positions {
690
+ match runs.last_mut() {
691
+ Some(last) if last.1 == position => last.1 = position + 1,
692
+ _ => runs.push((position, position + 1)),
693
+ }
694
+ }
695
+ runs
696
+ }
697
+
698
+ /// What a token contributes to the content of a range: its content key, or the range-local number
699
+ /// of its identifier.
700
+ fn content_value(symbol: i32, token_key: i64) -> i64 {
701
+ if symbol < 0 {
702
+ i64::from(symbol)
703
+ } else {
704
+ token_key
705
+ }
455
706
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-gauge",
3
- "version": "10.1.0",
3
+ "version": "10.2.1",
4
4
  "description": "Measure code metrics with tree-sitter.",
5
5
  "keywords": [
6
6
  "cli",
@@ -93,12 +93,12 @@
93
93
  "registry": "https://registry.npmjs.org/"
94
94
  },
95
95
  "optionalDependencies": {
96
- "code-gauge-linux-x64-gnu": "10.1.0",
97
- "code-gauge-linux-arm64-gnu": "10.1.0",
98
- "code-gauge-linux-x64-musl": "10.1.0",
99
- "code-gauge-linux-arm64-musl": "10.1.0",
100
- "code-gauge-darwin-x64": "10.1.0",
101
- "code-gauge-darwin-arm64": "10.1.0",
102
- "code-gauge-win32-x64-msvc": "10.1.0"
96
+ "code-gauge-linux-x64-gnu": "10.2.1",
97
+ "code-gauge-linux-arm64-gnu": "10.2.1",
98
+ "code-gauge-linux-x64-musl": "10.2.1",
99
+ "code-gauge-linux-arm64-musl": "10.2.1",
100
+ "code-gauge-darwin-x64": "10.2.1",
101
+ "code-gauge-darwin-arm64": "10.2.1",
102
+ "code-gauge-win32-x64-msvc": "10.2.1"
103
103
  }
104
104
  }