@preventive/compare 1.2.5 → 1.2.6
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/names.d.ts +7 -4
- package/package.json +1 -1
- package/src/names/analyze.js +11 -1
- package/src/names/diff.js +4 -4
- package/src/names/pairing.js +4 -14
- package/src/names/rename.js +5 -4
- package/src/text.js +11 -0
package/names.d.ts
CHANGED
|
@@ -21,12 +21,15 @@ export type RenameReason = 'unparsed' | 'dynamic' | 'jsx' | 'block-function' | '
|
|
|
21
21
|
// The diff of two JavaScript files minified on both sides with renamed names
|
|
22
22
|
// set aside. The old side is given the new side's names wherever a minifier
|
|
23
23
|
// renamed a variable alike throughout (a declared variable's short ASCII name,
|
|
24
|
-
// not a script's top level -- but where the file
|
|
25
|
-
// as a bundle's chunk is once it exports,
|
|
26
|
-
//
|
|
24
|
+
// not a script's top level -- but where either version of the file is a module,
|
|
25
|
+
// as a bundle's chunk is once it exports, or declares 16 names or more there,
|
|
26
|
+
// every one short, as a minifier renames a script's top level only told that no
|
|
27
|
+
// other script reads it: the same file loaded alike -- a name exported or
|
|
28
|
+
// imported as itself, or a shorthand property's), checked to bind
|
|
27
29
|
// every name as before by this package's scope analysis and again by oxc's
|
|
28
30
|
// minifier; formatted again where that changes its layout alone and still binds
|
|
29
|
-
// alike; diffed as diff -U0 has it
|
|
31
|
+
// alike; diffed as diff -U0 has it, and as --minimal does where that is cheap
|
|
32
|
+
// (lines by lines changed up to 2e8); and the blocks checked to take `before` to
|
|
30
33
|
// the new side (thrown should they not). `reason` says why no names were given;
|
|
31
34
|
// `reads` the properties either text reads names through (`name`, `toString`,
|
|
32
35
|
// `stack`, `callee`, `caller`), sorted, as code reading its names may act
|
package/package.json
CHANGED
package/src/names/analyze.js
CHANGED
|
@@ -248,8 +248,18 @@ function resolved(program, s) {
|
|
|
248
248
|
return { scopes: s.scopes, variables: s.variables, occurrences: deduplicated, globals, names, reads: s.reads, module: program.sourceType === 'module', commonjs: program.sourceType === 'commonjs', own }
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
const minified = (name) => name.length <= RENAMED_MAX_LENGTH && MINIFIED.test(name)
|
|
251
252
|
// A short minified name, declared, and kept by no export, import or shorthand.
|
|
252
|
-
const renamable = (variable) =>
|
|
253
|
+
const renamable = (variable) => minified(variable.name) && !variable.kept
|
|
254
|
+
|
|
255
|
+
// A script whose top level a minifier renamed: many names there, every one short.
|
|
256
|
+
// A minifier renames a script's top level only told that no other script reads
|
|
257
|
+
// it, as a bundle's chunk loaded as a module is; globals others read are few.
|
|
258
|
+
const MANY_TOP_LEVEL = 16
|
|
259
|
+
export function minifiedTopLevel(analysis) {
|
|
260
|
+
const top = analysis.variables.filter((variable) => variable.scope.parent === null)
|
|
261
|
+
return top.length >= MANY_TOP_LEVEL && top.every((variable) => minified(variable.name))
|
|
262
|
+
}
|
|
253
263
|
|
|
254
264
|
// A script's analysis with its top level its own, as a module's: renamable there
|
|
255
265
|
// too. Still read as a script, `this` and all, but no global of it set aside.
|
package/src/names/diff.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// A diff of a file minified on both sides with renamed names set aside: the
|
|
2
2
|
// old side renamed, formatted again where that changes its layout alone and
|
|
3
|
-
// still binds alike, diffed
|
|
3
|
+
// still binds alike, diffed at its shortest where affordable, and the diff
|
|
4
|
+
// checked to hide no change.
|
|
4
5
|
|
|
5
|
-
import { changeSet } from '@preventive/diff'
|
|
6
6
|
import { blocksHold } from '../blocks.js'
|
|
7
7
|
import { markedBlocks } from '../marked.js'
|
|
8
8
|
import { format } from '../pretty.js'
|
|
9
|
-
import { checkLineOptions } from '../text.js'
|
|
9
|
+
import { checkLineOptions, shortestChangeSet } from '../text.js'
|
|
10
10
|
import { bindsAlike } from './binds.js'
|
|
11
11
|
import { renameMap } from './rename.js'
|
|
12
12
|
|
|
@@ -30,7 +30,7 @@ export async function renamedDiffs(before, after, { extension = 'js', otherExten
|
|
|
30
30
|
}
|
|
31
31
|
let unmarked = false
|
|
32
32
|
const diffs = settings.map((options) => {
|
|
33
|
-
const blocks =
|
|
33
|
+
const blocks = shortestChangeSet(renamed, after, options).map(ranges)
|
|
34
34
|
if (!blocksHold(renamed, after, blocks, options)) throw new Error('change blocks that do not take the old side to the new')
|
|
35
35
|
// Read off the renamed text before formatting, line for line the old side's.
|
|
36
36
|
const marks = mapped.reason ? [] : markedBlocks(before, mapped.text, after, options)
|
package/src/names/pairing.js
CHANGED
|
@@ -2,24 +2,14 @@
|
|
|
2
2
|
// their keys (names set aside) pairs them, counts its variables as standing for
|
|
3
3
|
// each other, and two each the other's most counted pair, a tie pairing neither.
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { shortestChangeSet } from '../text.js'
|
|
6
6
|
import { RESERVED } from './syntax.js'
|
|
7
7
|
|
|
8
8
|
// Unchanged lines between changes that pair as unchanged ones, not as changed.
|
|
9
9
|
const ANCHORED_RUN = 3
|
|
10
10
|
|
|
11
11
|
// Whitespace alone never tells a name apart.
|
|
12
|
-
const
|
|
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
|
-
}
|
|
12
|
+
const KEYS = { horizon: 0, whitespace: 'all' }
|
|
23
13
|
|
|
24
14
|
// A text's lines with its renamable variables' names set aside (`key`), but
|
|
25
15
|
// those `written` names (a pair found), each line's variables set aside in
|
|
@@ -79,7 +69,7 @@ const reading = (k, from = 0) => (x, y) => {
|
|
|
79
69
|
// The pairs of lines a diff of `a` and `b` keeps, each told whether slack.
|
|
80
70
|
function* kept(a, b, sameA, sameB) {
|
|
81
71
|
const text = (lines) => lines.map((line) => `${line}\n`).join('')
|
|
82
|
-
const blocks =
|
|
72
|
+
const blocks = shortestChangeSet(text(a), text(b), KEYS, a.length + b.length)
|
|
83
73
|
const [looseA, looseB] = slack(a, b, blocks, sameA, sameB)
|
|
84
74
|
let i = 0, j = 0
|
|
85
75
|
for (const { a0, a1, b1 } of [...blocks, { a0: a.length, a1: a.length, b1: b.length }]) {
|
|
@@ -174,7 +164,7 @@ function tallied(ka, kb) {
|
|
|
174
164
|
}
|
|
175
165
|
const alike = (counts, olds, news) => olds.length === news.length && olds.forEach((variable, k) => counted(counts, variable, news[k]))
|
|
176
166
|
const trimmed = (lines, from, to) => plain(lines.slice(from, to).map((line) => line.replace(/[;,]\s*$/u, '')))
|
|
177
|
-
const blocks =
|
|
167
|
+
const blocks = shortestChangeSet(ka.key, kb.key, KEYS, ka.keys.length + kb.keys.length)
|
|
178
168
|
const [looseA, looseB] = slack(plain(ka.keys), plain(kb.keys), blocks, reading(ka), reading(kb))
|
|
179
169
|
let i = 0, j = 0
|
|
180
170
|
for (const block of [...blocks, { a0: ka.lines.length, a1: ka.lines.length, b0: kb.lines.length, b1: kb.lines.length }]) {
|
package/src/names/rename.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// to read wrongly, and the result checked again by oxc (bindsAlike).
|
|
4
4
|
|
|
5
5
|
import { getOxc } from '#oxc'
|
|
6
|
-
import { analyze, ownTopLevel } from './analyze.js'
|
|
6
|
+
import { analyze, minifiedTopLevel, ownTopLevel } from './analyze.js'
|
|
7
7
|
import { bindsAlike } from './binds.js'
|
|
8
8
|
import { checkAnalysis, checkGiven, checkPairing, checkRenamed } from './check.js'
|
|
9
9
|
import { misread } from './misread.js'
|
|
@@ -36,9 +36,10 @@ function renamedNames(before, after, { extension = 'js', otherExtension = extens
|
|
|
36
36
|
let a = analyze(before, extension), b = analyze(after, otherExtension)
|
|
37
37
|
if (a.reason || b.reason) return { reason: a.reason ?? b.reason }
|
|
38
38
|
// A file is a module by how it is loaded, which its versions share, more than
|
|
39
|
-
// by its syntax: a bundle's chunk that one version exports from, the other not
|
|
40
|
-
//
|
|
41
|
-
|
|
39
|
+
// by its syntax: a bundle's chunk that one version exports from, the other not,
|
|
40
|
+
// or that neither does, its top level renamed by a minifier as a module's is.
|
|
41
|
+
// Where either version is one, a script's top level is its own too.
|
|
42
|
+
if ([a, b].some((side) => side.module || minifiedTopLevel(side))) [a, b] = [ownTopLevel(a), ownTopLevel(b)]
|
|
42
43
|
checkAnalysis(before, a)
|
|
43
44
|
checkAnalysis(after, b)
|
|
44
45
|
const paired = pairing(before, after, a, b)
|
package/src/text.js
CHANGED
|
@@ -31,6 +31,17 @@ export function checkLineOptions({ horizon = Infinity, whitespace = 'none', igno
|
|
|
31
31
|
return { horizon, whitespace, ignoreCase }
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// The shortest diff where its cost, lines by lines changed, allows, as --minimal
|
|
35
|
+
// has it; past that the search's cutoff, which can face a run with lines alike
|
|
36
|
+
// far from its own: old code that new code now comes before, faced with its braces.
|
|
37
|
+
const SHORTEST_COST = 2e8
|
|
38
|
+
const lineCount = (text) => text.split('\n').length
|
|
39
|
+
export function shortestChangeSet(a, b, options, lines = lineCount(a) + lineCount(b)) {
|
|
40
|
+
const quick = changeSet(a, b, options)
|
|
41
|
+
const changed = quick.reduce((n, { a0, a1, b0, b1 }) => n + a1 - a0 + b1 - b0, 0)
|
|
42
|
+
return lines * changed > SHORTEST_COST ? quick : changeSet(a, b, { ...options, minimal: true })
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
// Binary content has no blocks and no counts, as --numstat prints `-` for it.
|
|
35
46
|
export function contentOf(old, fresh, options) {
|
|
36
47
|
const a = textOf(old), b = textOf(fresh)
|