@preventive/compare 1.2.6 → 1.2.7

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.6",
3
+ "version": "1.2.7",
4
4
  "description": "Git-compatible file tree diffs and minified JavaScript comparison",
5
5
  "type": "module",
6
6
  "main": "./compare.js",
@@ -24,12 +24,14 @@ export function checkAnalysis(text, { scopes, variables, occurrences, globals })
24
24
  for (const at of scopes) for (const [name, variable] of at.names) holds(known.has(variable) && variable.name === name, `a scope's ${name} no variable of its name`)
25
25
  }
26
26
 
27
- // Renamable variables alone paired, each new one once, the weak among them.
28
- export function checkPairing(a, b, { map, weak }) {
27
+ // Renamable variables alone paired, each new one once and by some line, the
28
+ // weak among them.
29
+ export function checkPairing(a, b, { map, support, weak }) {
29
30
  const news = new Set(b.variables), taken = new Set()
30
31
  for (const [variable, to] of map) {
31
32
  holds(variable.aside && to.aside && news.has(to), `${variable.name} paired with ${to.name}, the two not both renamable`)
32
33
  holds(!taken.has(to), `two variables paired with ${to.name}`)
34
+ holds(support.get(variable) > 0, `${variable.name} paired with ${to.name} by no line`)
33
35
  taken.add(to)
34
36
  }
35
37
  for (const variable of weak) holds(map.has(variable), `${variable.name} weak, and paired with none`)
@@ -151,13 +151,13 @@ function moves(ka, kb, blocks, alike) {
151
151
  }
152
152
  }
153
153
 
154
- // Pairs of lines counted, a long run of unchanged ones apart from the rest, and
155
- // apart from both the variables a change's tokens pair (`tokened`) and those of
156
- // code moved (`moved`). Lines reading variables one side has and the other not
157
- // say nothing. In a change of unlike lengths, lines alike but for a final `;` or
158
- // `,` pair (a declaration others now follow: `let t = ...;` -> `let t = ...,`).
154
+ // Pairs of lines counted, a long run of unchanged ones apart from the rest, as
155
+ // is code moved, and apart from both the variables a change's tokens pair
156
+ // (`tokened`). Lines reading variables one side has and the other not say
157
+ // nothing. In a change of unlike lengths, lines alike but for a final `;` or `,`
158
+ // pair (a declaration others now follow: `let t = ...;` -> `let t = ...,`).
159
159
  function tallied(ka, kb) {
160
- const changed = tallies(), moved = tallies(), tokened = tallies(), unchanged = tallies()
160
+ const changed = tallies(), tokened = tallies(), unchanged = tallies()
161
161
  const counted = ({ backward, forward }, variable, other) => {
162
162
  count(forward, variable, other)
163
163
  count(backward, other, variable)
@@ -182,8 +182,19 @@ function tallied(ka, kb) {
182
182
  i = block.a1
183
183
  j = block.b1
184
184
  }
185
- moves(ka, kb, blocks, (olds, news) => alike(moved, olds, news))
186
- return { changed, moved, tokened, unchanged }
185
+ // Code moved counts a pair once a line, so that a moved line reading a variable
186
+ // often outweighs no lines in place reading it once each.
187
+ const once = (olds, news) => {
188
+ if (olds.length !== news.length) return
189
+ const seen = new Map()
190
+ olds.forEach((variable, k) => {
191
+ const others = seen.get(variable) ?? seen.set(variable, new Set()).get(variable)
192
+ if (!others.has(news[k])) counted(unchanged, variable, news[k])
193
+ others.add(news[k])
194
+ })
195
+ }
196
+ moves(ka, kb, blocks, once)
197
+ return { changed, tokened, unchanged }
187
198
  }
188
199
 
189
200
  // The most counted of those not yet paired, none where two tie.
@@ -199,8 +210,9 @@ function best(counts, taken) {
199
210
  return found
200
211
  }
201
212
 
202
- // Pairs each the other's best, into `map`, until none more pair.
203
- function matched({ backward, forward }, { map, paired }, eligible = () => true) {
213
+ // Pairs each the other's best, into `map`, until none more pair: each pair's
214
+ // count its `support`.
215
+ function matched({ backward, forward }, { map, paired, support }, eligible = () => true) {
204
216
  const chosen = []
205
217
  for (let more = true; more;) {
206
218
  more = false
@@ -209,6 +221,7 @@ function matched({ backward, forward }, { map, paired }, eligible = () => true)
209
221
  const to = best(counts, (other) => paired.has(other))
210
222
  if (!to || best(backward.get(to), (other) => map.has(other)) !== variable || !eligible(variable, to)) continue
211
223
  map.set(variable, to)
224
+ support.set(variable, counts.get(to))
212
225
  paired.add(to)
213
226
  chosen.push(variable)
214
227
  more = true
@@ -217,23 +230,25 @@ function matched({ backward, forward }, { map, paired }, eligible = () => true)
217
230
  return chosen
218
231
  }
219
232
 
220
- // Unchanged lines pair first; changed ones then pair only what those say
221
- // nothing of, and a change's tokens only what no line says anything of: lines
222
- // reordered, a change's tokens may line up across them. Both are `weak` pairs,
223
- // the first to give way where names read wrongly. A second pass reads the
224
- // pairs found as names, so lines alike with every name set aside and diffed
225
- // against the wrong one line up by those.
233
+ // Unchanged lines pair first, and code moved counted with them: a function's
234
+ // code moved into a helper reads its variables as the helper's, the function's
235
+ // own lines as its own, and the most lines decide. Changed ones then pair only
236
+ // what those say nothing of, and a change's tokens only what no line says
237
+ // anything of: lines reordered, a change's tokens may line up across them. Both
238
+ // are `weak` pairs, the first to give way where names read wrongly. A second
239
+ // pass reads the pairs found as names, so lines alike with every name set aside
240
+ // and diffed against the wrong one line up by those.
226
241
  export function pairing(before, after, a, b) {
227
- const found = { map: new Map(), paired: new Set() }, said = new Set(), weak = new Set()
242
+ const found = { map: new Map(), paired: new Set(), support: new Map() }, said = new Set(), weak = new Set()
228
243
  const passes = [[() => null, () => null], [(variable) => found.map.get(variable)?.name ?? null, (variable) => (found.paired.has(variable) ? variable.name : null)]]
229
244
  for (const [old, now] of passes) {
230
245
  // A pass pairs nothing where every variable of a side is paired already.
231
246
  if (!a.variables.some((variable) => variable.aside && !found.map.has(variable)) || !b.variables.some((variable) => variable.aside && !found.paired.has(variable))) break
232
- const { changed, moved, tokened, unchanged } = tallied(keyed(before, a, old), keyed(after, b, now))
233
- for (const [counts, tier] of [[unchanged, false], [changed, true], [tokened, true], [moved, true]]) {
247
+ const { changed, tokened, unchanged } = tallied(keyed(before, a, old), keyed(after, b, now))
248
+ for (const [counts, tier] of [[unchanged, false], [changed, true], [tokened, true]]) {
234
249
  for (const variable of matched(counts, found, (from, to) => !said.has(from) && !said.has(to))) if (tier) weak.add(variable)
235
250
  for (const variable of [...counts.forward.keys(), ...counts.backward.keys()]) said.add(variable)
236
251
  }
237
252
  }
238
- return { map: found.map, weak }
253
+ return { map: found.map, support: found.support, weak }
239
254
  }
@@ -11,9 +11,10 @@ import { pairing } from './pairing.js'
11
11
  import { APART, Broken, holds } from './syntax.js'
12
12
 
13
13
  // Each old variable's name: its counterpart's, else its own, or one apart where
14
- // the new side has its own. Those misread yield, weak pairs first; null where
15
- // none can.
16
- function givenNames(a, b, { map, weak }) {
14
+ // the new side has its own. Those misread yield, weak pairs first, else those
15
+ // fewest lines pair: a pair that a few lines alone say gives way to one most
16
+ // do. null where none can.
17
+ function givenNames(a, b, { map, support, weak }) {
17
18
  const apart = (name) => {
18
19
  let named = name + APART
19
20
  for (let n = 2; a.names.has(named) || b.names.has(named); n++) named = `${name}${APART}${n}`
@@ -27,7 +28,9 @@ function givenNames(a, b, { map, weak }) {
27
28
  }
28
29
  const wrong = [...misread(a, given)]
29
30
  if (wrong.length === 0) return given
30
- const yielding = wrong.some((variable) => weak.has(variable) && map.has(variable)) ? wrong.filter((variable) => weak.has(variable)) : wrong
31
+ const paired = wrong.filter((variable) => map.has(variable))
32
+ const least = paired.reduce((n, variable) => Math.min(n, support.get(variable)), Infinity)
33
+ const yielding = paired.some((variable) => weak.has(variable)) ? paired.filter((variable) => weak.has(variable)) : paired.filter((variable) => support.get(variable) === least)
31
34
  if (yielding.filter((variable) => map.delete(variable)).length === 0) return null
32
35
  }
33
36
  }