@preventive/compare 1.2.4 → 1.2.5

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@preventive/compare",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
4
  "description": "Git-compatible file tree diffs and minified JavaScript comparison",
5
5
  "type": "module",
6
6
  "main": "./compare.js",
@@ -9,7 +9,17 @@ import { RESERVED } from './syntax.js'
9
9
  const ANCHORED_RUN = 3
10
10
 
11
11
  // Whitespace alone never tells a name apart.
12
- const changes = (a, b) => changeSet(a, b, { horizon: 0, whitespace: 'all' })
12
+ const changes = (a, b, minimal = false) => changeSet(a, b, { horizon: 0, whitespace: 'all', minimal })
13
+
14
+ // The search's cutoff can face a run with lines alike far from its own: old code
15
+ // that a block of new code now comes before, its lines faced with that block's
16
+ // braces. The shortest diff instead, where its cost, lines by lines changed, allows.
17
+ const MINIMAL_COST = 2e8
18
+ function linedUp(a, b, lines) {
19
+ const quick = changes(a, b)
20
+ const changed = quick.reduce((n, { a0, a1, b0, b1 }) => n + a1 - a0 + b1 - b0, 0)
21
+ return lines * changed > MINIMAL_COST ? quick : changes(a, b, true)
22
+ }
13
23
 
14
24
  // A text's lines with its renamable variables' names set aside (`key`), but
15
25
  // those `written` names (a pair found), each line's variables set aside in
@@ -69,7 +79,7 @@ const reading = (k, from = 0) => (x, y) => {
69
79
  // The pairs of lines a diff of `a` and `b` keeps, each told whether slack.
70
80
  function* kept(a, b, sameA, sameB) {
71
81
  const text = (lines) => lines.map((line) => `${line}\n`).join('')
72
- const blocks = changes(text(a), text(b))
82
+ const blocks = linedUp(text(a), text(b), a.length + b.length)
73
83
  const [looseA, looseB] = slack(a, b, blocks, sameA, sameB)
74
84
  let i = 0, j = 0
75
85
  for (const { a0, a1, b1 } of [...blocks, { a0: a.length, a1: a.length, b1: b.length }]) {
@@ -138,20 +148,33 @@ function count(map, from, to) {
138
148
  map.get(from).set(to, (map.get(from).get(to) ?? 0) + 1)
139
149
  }
140
150
 
151
+ // Code moved: the lines changes took out and those they put in, diffed on their
152
+ // own, a long run kept pairing as code moved out of order with the rest does.
153
+ function moves(ka, kb, blocks, alike) {
154
+ const at = (ends) => blocks.flatMap((block) => Array.from({ length: block[ends[1]] - block[ends[0]] }, (_, k) => block[ends[0]] + k))
155
+ const news = at(['b0', 'b1']), olds = at(['a0', 'a1'])
156
+ const same = (k, lines) => (x, y) => reading(k)(lines[x], lines[y])
157
+ const pairs = [...kept(plain(olds.map((i) => ka.keys[i])), plain(news.map((j) => kb.keys[j])), same(ka, olds), same(kb, news))]
158
+ for (let end = 1, start = 0; start < pairs.length; start = end++) {
159
+ while (end < pairs.length && pairs[end][0] === pairs[end - 1][0] + 1 && pairs[end][1] === pairs[end - 1][1] + 1) end++
160
+ if (end - start >= ANCHORED_RUN) for (const [x, y, loose] of pairs.slice(start, end)) if (!loose) alike(ka.lines[olds[x]], kb.lines[news[y]])
161
+ }
162
+ }
163
+
141
164
  // Pairs of lines counted, a long run of unchanged ones apart from the rest, and
142
- // apart from both the variables a change's tokens pair (`tokened`). Lines
143
- // reading variables one side has and the other not say nothing. In a change of
144
- // unlike lengths, lines alike but for a final `;` or `,` pair (a declaration
145
- // others now follow: `let t = ...;` -> `let t = ...,`).
165
+ // apart from both the variables a change's tokens pair (`tokened`) and those of
166
+ // code moved (`moved`). Lines reading variables one side has and the other not
167
+ // say nothing. In a change of unlike lengths, lines alike but for a final `;` or
168
+ // `,` pair (a declaration others now follow: `let t = ...;` -> `let t = ...,`).
146
169
  function tallied(ka, kb) {
147
- const changed = tallies(), tokened = tallies(), unchanged = tallies()
170
+ const changed = tallies(), moved = tallies(), tokened = tallies(), unchanged = tallies()
148
171
  const counted = ({ backward, forward }, variable, other) => {
149
172
  count(forward, variable, other)
150
173
  count(backward, other, variable)
151
174
  }
152
175
  const alike = (counts, olds, news) => olds.length === news.length && olds.forEach((variable, k) => counted(counts, variable, news[k]))
153
176
  const trimmed = (lines, from, to) => plain(lines.slice(from, to).map((line) => line.replace(/[;,]\s*$/u, '')))
154
- const blocks = changes(ka.key, kb.key)
177
+ const blocks = linedUp(ka.key, kb.key, ka.keys.length + kb.keys.length)
155
178
  const [looseA, looseB] = slack(plain(ka.keys), plain(kb.keys), blocks, reading(ka), reading(kb))
156
179
  let i = 0, j = 0
157
180
  for (const block of [...blocks, { a0: ka.lines.length, a1: ka.lines.length, b0: kb.lines.length, b1: kb.lines.length }]) {
@@ -169,7 +192,8 @@ function tallied(ka, kb) {
169
192
  i = block.a1
170
193
  j = block.b1
171
194
  }
172
- return { changed, tokened, unchanged }
195
+ moves(ka, kb, blocks, (olds, news) => alike(moved, olds, news))
196
+ return { changed, moved, tokened, unchanged }
173
197
  }
174
198
 
175
199
  // The most counted of those not yet paired, none where two tie.
@@ -215,8 +239,8 @@ export function pairing(before, after, a, b) {
215
239
  for (const [old, now] of passes) {
216
240
  // A pass pairs nothing where every variable of a side is paired already.
217
241
  if (!a.variables.some((variable) => variable.aside && !found.map.has(variable)) || !b.variables.some((variable) => variable.aside && !found.paired.has(variable))) break
218
- const { changed, tokened, unchanged } = tallied(keyed(before, a, old), keyed(after, b, now))
219
- for (const [counts, tier] of [[unchanged, false], [changed, true], [tokened, true]]) {
242
+ const { changed, moved, tokened, unchanged } = tallied(keyed(before, a, old), keyed(after, b, now))
243
+ for (const [counts, tier] of [[unchanged, false], [changed, true], [tokened, true], [moved, true]]) {
220
244
  for (const variable of matched(counts, found, (from, to) => !said.has(from) && !said.has(to))) if (tier) weak.add(variable)
221
245
  for (const variable of [...counts.forward.keys(), ...counts.backward.keys()]) said.add(variable)
222
246
  }