@opensip-cli/graph 0.1.11 → 0.1.12
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/README.md +2 -2
- package/dist/__tests__/cache/engine-version.test.js +4 -4
- package/dist/__tests__/cache/engine-version.test.js.map +1 -1
- package/dist/__tests__/public-api.test.js +10 -0
- package/dist/__tests__/public-api.test.js.map +1 -1
- package/dist/__tests__/rules/registry.test.js +1 -0
- package/dist/__tests__/rules/registry.test.js.map +1 -1
- package/dist/cache/engine-version.d.ts.map +1 -1
- package/dist/cache/engine-version.js +5 -1
- package/dist/cache/engine-version.js.map +1 -1
- package/dist/cli/__tests__/equivalence-diagnostic.test.d.ts +7 -0
- package/dist/cli/__tests__/equivalence-diagnostic.test.d.ts.map +1 -0
- package/dist/cli/__tests__/equivalence-diagnostic.test.js +136 -0
- package/dist/cli/__tests__/equivalence-diagnostic.test.js.map +1 -0
- package/dist/cli/equivalence-check-command.d.ts.map +1 -1
- package/dist/cli/equivalence-check-command.js +35 -2
- package/dist/cli/equivalence-check-command.js.map +1 -1
- package/dist/cli/equivalence-diagnostic.d.ts +100 -0
- package/dist/cli/equivalence-diagnostic.d.ts.map +1 -0
- package/dist/cli/equivalence-diagnostic.js +138 -0
- package/dist/cli/equivalence-diagnostic.js.map +1 -0
- package/dist/cli/graph/graph-command-spec.d.ts.map +1 -1
- package/dist/cli/graph/graph-command-spec.js +3 -0
- package/dist/cli/graph/graph-command-spec.js.map +1 -1
- package/dist/cli/graph-config-schema.d.ts +3 -0
- package/dist/cli/graph-config-schema.d.ts.map +1 -1
- package/dist/cli/graph-config-schema.js +11 -0
- package/dist/cli/graph-config-schema.js.map +1 -1
- package/dist/cli/graph-config.d.ts.map +1 -1
- package/dist/cli/graph-config.js +5 -9
- package/dist/cli/graph-config.js.map +1 -1
- package/dist/cli/graph-envelope-view.d.ts.map +1 -1
- package/dist/cli/graph-envelope-view.js +1 -0
- package/dist/cli/graph-envelope-view.js.map +1 -1
- package/dist/cli/graph-modes.d.ts.map +1 -1
- package/dist/cli/graph-modes.js +6 -3
- package/dist/cli/graph-modes.js.map +1 -1
- package/dist/cli/orchestrate/__tests__/cross-shard-resolve.test.js +76 -0
- package/dist/cli/orchestrate/__tests__/cross-shard-resolve.test.js.map +1 -1
- package/dist/cli/orchestrate/catalog-stats.d.ts.map +1 -1
- package/dist/cli/orchestrate/catalog-stats.js +0 -1
- package/dist/cli/orchestrate/catalog-stats.js.map +1 -1
- package/dist/cli/orchestrate/cross-shard-resolve.d.ts.map +1 -1
- package/dist/cli/orchestrate/cross-shard-resolve.js +77 -2
- package/dist/cli/orchestrate/cross-shard-resolve.js.map +1 -1
- package/dist/cli/orchestrate/flat-monorepo-strategy.d.ts.map +1 -1
- package/dist/cli/orchestrate/flat-monorepo-strategy.js +0 -1
- package/dist/cli/orchestrate/flat-monorepo-strategy.js.map +1 -1
- package/dist/cli/pressure-monitor.d.ts +2 -0
- package/dist/cli/pressure-monitor.d.ts.map +1 -1
- package/dist/cli/pressure-monitor.js +15 -0
- package/dist/cli/pressure-monitor.js.map +1 -1
- package/dist/cli/report-data.d.ts.map +1 -1
- package/dist/cli/report-data.js +0 -1
- package/dist/cli/report-data.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/lang-adapter/__tests__/language-of-file.test.d.ts +2 -0
- package/dist/lang-adapter/__tests__/language-of-file.test.d.ts.map +1 -0
- package/dist/lang-adapter/__tests__/language-of-file.test.js +36 -0
- package/dist/lang-adapter/__tests__/language-of-file.test.js.map +1 -0
- package/dist/lang-adapter/__tests__/near-duplicate-signature.test.d.ts +2 -0
- package/dist/lang-adapter/__tests__/near-duplicate-signature.test.d.ts.map +1 -0
- package/dist/lang-adapter/__tests__/near-duplicate-signature.test.js +78 -0
- package/dist/lang-adapter/__tests__/near-duplicate-signature.test.js.map +1 -0
- package/dist/lang-adapter/body-digest.d.ts +9 -41
- package/dist/lang-adapter/body-digest.d.ts.map +1 -1
- package/dist/lang-adapter/body-digest.js +9 -44
- package/dist/lang-adapter/body-digest.js.map +1 -1
- package/dist/lang-adapter/language-of-file.d.ts +15 -0
- package/dist/lang-adapter/language-of-file.d.ts.map +1 -0
- package/dist/lang-adapter/language-of-file.js +43 -0
- package/dist/lang-adapter/language-of-file.js.map +1 -0
- package/dist/lang-adapter/near-duplicate-signature.d.ts +12 -0
- package/dist/lang-adapter/near-duplicate-signature.d.ts.map +1 -0
- package/dist/lang-adapter/near-duplicate-signature.js +12 -0
- package/dist/lang-adapter/near-duplicate-signature.js.map +1 -0
- package/dist/lang-adapter/read-source.d.ts.map +1 -1
- package/dist/lang-adapter/read-source.js +0 -1
- package/dist/lang-adapter/read-source.js.map +1 -1
- package/dist/lang-adapter/registry.d.ts.map +1 -1
- package/dist/lang-adapter/registry.js +0 -1
- package/dist/lang-adapter/registry.js.map +1 -1
- package/dist/persistence/catalog-repo.d.ts.map +1 -1
- package/dist/persistence/catalog-repo.js +1 -0
- package/dist/persistence/catalog-repo.js.map +1 -1
- package/dist/persistence/session-payload.d.ts.map +1 -1
- package/dist/persistence/session-payload.js +0 -1
- package/dist/persistence/session-payload.js.map +1 -1
- package/dist/persistence/session-replay.d.ts.map +1 -1
- package/dist/persistence/session-replay.js +10 -25
- package/dist/persistence/session-replay.js.map +1 -1
- package/dist/pipeline/feature-deps.d.ts.map +1 -1
- package/dist/pipeline/feature-deps.js +0 -1
- package/dist/pipeline/feature-deps.js.map +1 -1
- package/dist/pipeline/features.d.ts.map +1 -1
- package/dist/pipeline/features.js +0 -1
- package/dist/pipeline/features.js.map +1 -1
- package/dist/pipeline/indexes.d.ts.map +1 -1
- package/dist/pipeline/indexes.js +0 -1
- package/dist/pipeline/indexes.js.map +1 -1
- package/dist/render/rule-id-mapping.d.ts.map +1 -1
- package/dist/render/rule-id-mapping.js +1 -0
- package/dist/render/rule-id-mapping.js.map +1 -1
- package/dist/rules/__tests__/near-duplicate-function-body.test.d.ts +2 -0
- package/dist/rules/__tests__/near-duplicate-function-body.test.d.ts.map +1 -0
- package/dist/rules/__tests__/near-duplicate-function-body.test.js +142 -0
- package/dist/rules/__tests__/near-duplicate-function-body.test.js.map +1 -0
- package/dist/rules/_entry-points.d.ts.map +1 -1
- package/dist/rules/_entry-points.js +0 -1
- package/dist/rules/_entry-points.js.map +1 -1
- package/dist/rules/duplicated-function-body.d.ts +12 -34
- package/dist/rules/duplicated-function-body.d.ts.map +1 -1
- package/dist/rules/duplicated-function-body.js +83 -233
- package/dist/rules/duplicated-function-body.js.map +1 -1
- package/dist/rules/near-duplicate-function-body.d.ts +11 -0
- package/dist/rules/near-duplicate-function-body.d.ts.map +1 -0
- package/dist/rules/near-duplicate-function-body.js +74 -0
- package/dist/rules/near-duplicate-function-body.js.map +1 -0
- package/dist/rules/registry.d.ts.map +1 -1
- package/dist/rules/registry.js +2 -1
- package/dist/rules/registry.js.map +1 -1
- package/dist/types.d.ts +20 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +10 -9
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { makeCatalog, occ } from '../../__tests__/rules/_helpers.js';
|
|
3
|
+
import { NEAR_DUP_SIGNATURE_K, bodySignature, estimateJaccard, } from '../../lang-adapter/near-duplicate-signature.js';
|
|
4
|
+
import { buildIndexes } from '../../pipeline/indexes.js';
|
|
5
|
+
import { nearDuplicateFunctionBodyRule } from '../near-duplicate-function-body.js';
|
|
6
|
+
const PAYLOAD = 'abcdefghijklmnopqrstuvwxyz0123456789'.repeat(12);
|
|
7
|
+
const BASE_BODY = `function work(items) { const payload = "${PAYLOAD}"; let total = 0; for (const item of items) { total += item.length + payload.length; } return total; }`;
|
|
8
|
+
const NEAR_BODY = `function work(items) { const payload = "${PAYLOAD}"; let total = 0; for (const item of items) { total += item.length + payload.length + 1; } return total; }`;
|
|
9
|
+
const UNRELATED = 'export function validateConfig(cfg) { if (!cfg.apiKey) throw new Error("missing"); return cfg; }';
|
|
10
|
+
function withSignature(over, canonical) {
|
|
11
|
+
const signature = [...bodySignature(canonical)];
|
|
12
|
+
return occ({ ...over, bodySignature: signature, bodySize: canonical.length });
|
|
13
|
+
}
|
|
14
|
+
function evaluateNear(catalog, config = {}) {
|
|
15
|
+
return nearDuplicateFunctionBodyRule.evaluate(catalog, buildIndexes(catalog), config);
|
|
16
|
+
}
|
|
17
|
+
describe('near-duplicate-function-body', () => {
|
|
18
|
+
it('flags near-clone pairs above the similarity threshold', () => {
|
|
19
|
+
const sigA = bodySignature(BASE_BODY);
|
|
20
|
+
const sigB = bodySignature(NEAR_BODY);
|
|
21
|
+
expect(estimateJaccard(sigA, sigB)).toBeGreaterThanOrEqual(0.85);
|
|
22
|
+
const a = withSignature({
|
|
23
|
+
bodyHash: 'hash-a',
|
|
24
|
+
simpleName: 'processA',
|
|
25
|
+
filePath: 'src/a.ts',
|
|
26
|
+
line: 10,
|
|
27
|
+
}, BASE_BODY);
|
|
28
|
+
const b = withSignature({
|
|
29
|
+
bodyHash: 'hash-b',
|
|
30
|
+
simpleName: 'processB',
|
|
31
|
+
filePath: 'src/b.ts',
|
|
32
|
+
line: 20,
|
|
33
|
+
qualifiedName: 'src/b.processB',
|
|
34
|
+
}, NEAR_BODY);
|
|
35
|
+
const signals = evaluateNear(makeCatalog([a, b]));
|
|
36
|
+
expect(signals.length).toBe(1);
|
|
37
|
+
expect(signals[0]?.ruleId).toBe('graph:near-duplicate-function-body');
|
|
38
|
+
expect(signals[0]?.metadata?.nearMembers).toEqual(expect.arrayContaining(['src/a.processA', 'src/b.processB']));
|
|
39
|
+
});
|
|
40
|
+
it('does not flag exact-duplicate pairs (duplicated-function-body owns those)', () => {
|
|
41
|
+
const a = withSignature({ bodyHash: 'same', simpleName: 'dupA' }, BASE_BODY);
|
|
42
|
+
const b = withSignature({
|
|
43
|
+
bodyHash: 'same',
|
|
44
|
+
simpleName: 'dupB',
|
|
45
|
+
filePath: 'src/b.ts',
|
|
46
|
+
qualifiedName: 'src/b.dupB',
|
|
47
|
+
}, BASE_BODY);
|
|
48
|
+
const signals = evaluateNear(makeCatalog([a, b]));
|
|
49
|
+
expect(signals).toHaveLength(0);
|
|
50
|
+
});
|
|
51
|
+
it('skips test-file occurrences', () => {
|
|
52
|
+
const a = withSignature({ bodyHash: 'a', simpleName: 'a', inTestFile: true }, BASE_BODY);
|
|
53
|
+
const b = withSignature({
|
|
54
|
+
bodyHash: 'b',
|
|
55
|
+
simpleName: 'b',
|
|
56
|
+
filePath: 'src/b.ts',
|
|
57
|
+
qualifiedName: 'src/b.b',
|
|
58
|
+
}, NEAR_BODY);
|
|
59
|
+
const signals = evaluateNear(makeCatalog([a, b]));
|
|
60
|
+
expect(signals).toHaveLength(0);
|
|
61
|
+
});
|
|
62
|
+
it('is graceful when bodySignature is absent', () => {
|
|
63
|
+
const a = occ({ bodyHash: 'a', simpleName: 'a' });
|
|
64
|
+
const b = occ({ bodyHash: 'b', simpleName: 'b', filePath: 'src/b.ts' });
|
|
65
|
+
const signals = evaluateNear(makeCatalog([a, b]));
|
|
66
|
+
expect(signals).toHaveLength(0);
|
|
67
|
+
});
|
|
68
|
+
it('does not flag cross-language pairs (same-language gate)', () => {
|
|
69
|
+
const a = withSignature({ bodyHash: 'go-hash', simpleName: 'work', filePath: 'main.go', line: 1 }, BASE_BODY);
|
|
70
|
+
const b = withSignature({
|
|
71
|
+
bodyHash: 'ts-hash',
|
|
72
|
+
simpleName: 'work',
|
|
73
|
+
filePath: 'src/work.ts',
|
|
74
|
+
line: 1,
|
|
75
|
+
qualifiedName: 'src/work.work',
|
|
76
|
+
}, BASE_BODY);
|
|
77
|
+
const signals = evaluateNear(makeCatalog([a, b]));
|
|
78
|
+
expect(signals).toHaveLength(0);
|
|
79
|
+
});
|
|
80
|
+
it('merges transitive near pairs via union-find', () => {
|
|
81
|
+
const body1 = BASE_BODY;
|
|
82
|
+
const body2 = NEAR_BODY;
|
|
83
|
+
const body3 = body2.replace('+ 1', '+ 2');
|
|
84
|
+
const a = withSignature({ bodyHash: 'h1', simpleName: 'a', line: 1 }, body1);
|
|
85
|
+
const b = withSignature({
|
|
86
|
+
bodyHash: 'h2',
|
|
87
|
+
simpleName: 'b',
|
|
88
|
+
filePath: 'src/b.ts',
|
|
89
|
+
line: 2,
|
|
90
|
+
qualifiedName: 'src/b.b',
|
|
91
|
+
}, body2);
|
|
92
|
+
const c = withSignature({
|
|
93
|
+
bodyHash: 'h3',
|
|
94
|
+
simpleName: 'c',
|
|
95
|
+
filePath: 'src/c.ts',
|
|
96
|
+
line: 3,
|
|
97
|
+
qualifiedName: 'src/c.c',
|
|
98
|
+
}, body3);
|
|
99
|
+
expect(estimateJaccard(bodySignature(body1), bodySignature(body2))).toBeGreaterThanOrEqual(0.85);
|
|
100
|
+
expect(estimateJaccard(bodySignature(body2), bodySignature(body3))).toBeGreaterThanOrEqual(0.85);
|
|
101
|
+
const signals = evaluateNear(makeCatalog([a, b, c]), {
|
|
102
|
+
minNearDuplicateSimilarity: 0.85,
|
|
103
|
+
});
|
|
104
|
+
expect(signals.length).toBe(1);
|
|
105
|
+
expect((signals[0]?.metadata?.nearMembers).length).toBeGreaterThanOrEqual(2);
|
|
106
|
+
});
|
|
107
|
+
it('skips sub-threshold pairs', () => {
|
|
108
|
+
const a = withSignature({ bodyHash: 'a', simpleName: 'a' }, BASE_BODY);
|
|
109
|
+
const b = withSignature({
|
|
110
|
+
bodyHash: 'b',
|
|
111
|
+
simpleName: 'b',
|
|
112
|
+
filePath: 'src/b.ts',
|
|
113
|
+
qualifiedName: 'src/b.b',
|
|
114
|
+
}, UNRELATED);
|
|
115
|
+
const signals = evaluateNear(makeCatalog([a, b]), {
|
|
116
|
+
minNearDuplicateSimilarity: 0.85,
|
|
117
|
+
});
|
|
118
|
+
expect(signals).toHaveLength(0);
|
|
119
|
+
});
|
|
120
|
+
it('requires signature length === NEAR_DUP_SIGNATURE_K', () => {
|
|
121
|
+
const a = occ({ bodyHash: 'a', simpleName: 'a', bodySignature: [1, 2, 3] });
|
|
122
|
+
const b = occ({
|
|
123
|
+
bodyHash: 'b',
|
|
124
|
+
simpleName: 'b',
|
|
125
|
+
filePath: 'src/b.ts',
|
|
126
|
+
bodySignature: [1, 2, 3],
|
|
127
|
+
});
|
|
128
|
+
const signals = evaluateNear(makeCatalog([a, b]));
|
|
129
|
+
expect(signals).toHaveLength(0);
|
|
130
|
+
expect(NEAR_DUP_SIGNATURE_K).toBe(128);
|
|
131
|
+
});
|
|
132
|
+
it('emits nothing when nearDuplicateLshBands does not divide k (fractional rows)', () => {
|
|
133
|
+
// 128 / 7 is fractional → band slicing would be misaligned. The integer guard
|
|
134
|
+
// must reject it (the schema also refuses this value at config load).
|
|
135
|
+
const a = withSignature({ bodyHash: 'hash-a', simpleName: 'pA', filePath: 'src/a.ts' }, BASE_BODY);
|
|
136
|
+
const b = withSignature({ bodyHash: 'hash-b', simpleName: 'pB', filePath: 'src/b.ts', qualifiedName: 'src/b.pB' }, NEAR_BODY);
|
|
137
|
+
expect(evaluateNear(makeCatalog([a, b]), { nearDuplicateLshBands: 7 })).toHaveLength(0);
|
|
138
|
+
// ...but the same pair IS flagged with a valid divisor.
|
|
139
|
+
expect(evaluateNear(makeCatalog([a, b]), { nearDuplicateLshBands: 16 })).toHaveLength(1);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
//# sourceMappingURL=near-duplicate-function-body.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"near-duplicate-function-body.test.js","sourceRoot":"","sources":["../../../src/rules/__tests__/near-duplicate-function-body.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_entry-points.d.ts","sourceRoot":"","sources":["../../src/rules/_entry-points.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"_entry-points.d.ts","sourceRoot":"","sources":["../../src/rules/_entry-points.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAsB,OAAO,EAAE,MAAM,aAAa,CAAC;AAYxE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,YAAY,GAAG,qBAAqB,GAAG,gBAAgB,CAAC;CAC1F;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,SAAS,UAAU,EAAE,CAyB1F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_entry-points.js","sourceRoot":"","sources":["../../src/rules/_entry-points.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"_entry-points.js","sourceRoot":"","sources":["../../src/rules/_entry-points.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,40 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* graph:duplicated-function-body — group catalog entries whose body
|
|
3
|
-
*
|
|
4
|
-
* two complementary code paths under one slug:
|
|
2
|
+
* graph:duplicated-function-body — group catalog entries whose body normalizes to the
|
|
3
|
+
* same hash and report duplicate bodies. Two complementary paths under one slug:
|
|
5
4
|
*
|
|
6
|
-
* 1.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* 1. Per-instance (size-gated): N-1 signals per group of ≥2 byte-identical normalized
|
|
6
|
+
* bodies passing a line floor (default 5) AND a normalized-char floor (default 200,
|
|
7
|
+
* which drops thin `defineCheck`/pass-through wrappers).
|
|
8
|
+
* 2. Aggregate (cross-package): one signal per body hash in ≥ 3 distinct packages,
|
|
9
|
+
* suppressing that hash's per-instance signals (lighter body-size-only floor, 80).
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* `defineCheck({ ... analyze(content) { return analyzeFile(...) } })`
|
|
17
|
-
* wrapper where the source spans 8+ lines but the executable
|
|
18
|
-
* body is only ~80 characters once normalized — structurally
|
|
19
|
-
* identical to every other thin wrapper, but never an actionable
|
|
20
|
-
* refactor target.
|
|
21
|
-
*
|
|
22
|
-
* Emits N-1 signals per group (one per non-primary occurrence).
|
|
23
|
-
*
|
|
24
|
-
* 2. **Aggregate (cross-package).** For each body hash present in
|
|
25
|
-
* ≥ minCrossPackageDuplicatePackages (default 3) DISTINCT packages
|
|
26
|
-
* (via `pkgOf`), the rule emits ONE aggregate signal naming the
|
|
27
|
-
* packages and SUPPRESSES the per-instance signals for that same hash
|
|
28
|
-
* (no double-reporting). Bodies that don't reach N packages flow
|
|
29
|
-
* through path (1) unchanged.
|
|
30
|
-
*
|
|
31
|
-
* This path applies a LIGHTER, body-size-only floor
|
|
32
|
-
* (`minCrossPackageDuplicateBodySize`, default 80 chars — no line floor)
|
|
33
|
-
* rather than the per-instance floor: a *small* body copied across
|
|
34
|
-
* packages is exactly what this path exists to catch, so the floor is
|
|
35
|
-
* tuned only to drop TRIVIAL bodies (empty DI-constructor shims,
|
|
36
|
-
* one-line getters, thin delegators) that are never consolidation
|
|
37
|
-
* targets, while keeping genuinely-small shared utilities visible.
|
|
11
|
+
* The detection algorithm + curation policy now live in `@opensip-cli/clone-detection`
|
|
12
|
+
* (ADR-0064) so graph and yagni single-source them. This rule is a thin adapter: it maps
|
|
13
|
+
* the catalog to `CloneCandidate[]` (pre-resolving `bodyLines` from the feature table and
|
|
14
|
+
* `package` from `pkgOf`), calls `findDuplicateBodies`, and wraps each finding into a
|
|
15
|
+
* graph `Signal` with the unchanged message/severity/metadata — so output is byte-stable.
|
|
38
16
|
*/
|
|
39
17
|
export declare const duplicatedFunctionBodyRule: import("../types.js").Rule;
|
|
40
18
|
//# sourceMappingURL=duplicated-function-body.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duplicated-function-body.d.ts","sourceRoot":"","sources":["../../src/rules/duplicated-function-body.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"duplicated-function-body.d.ts","sourceRoot":"","sources":["../../src/rules/duplicated-function-body.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAeH,eAAO,MAAM,0BAA0B,4BAoBrC,CAAC"}
|
|
@@ -1,262 +1,112 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* graph:duplicated-function-body — group catalog entries whose body
|
|
3
|
-
*
|
|
4
|
-
* two complementary code paths under one slug:
|
|
2
|
+
* graph:duplicated-function-body — group catalog entries whose body normalizes to the
|
|
3
|
+
* same hash and report duplicate bodies. Two complementary paths under one slug:
|
|
5
4
|
*
|
|
6
|
-
* 1.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* 1. Per-instance (size-gated): N-1 signals per group of ≥2 byte-identical normalized
|
|
6
|
+
* bodies passing a line floor (default 5) AND a normalized-char floor (default 200,
|
|
7
|
+
* which drops thin `defineCheck`/pass-through wrappers).
|
|
8
|
+
* 2. Aggregate (cross-package): one signal per body hash in ≥ 3 distinct packages,
|
|
9
|
+
* suppressing that hash's per-instance signals (lighter body-size-only floor, 80).
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* `defineCheck({ ... analyze(content) { return analyzeFile(...) } })`
|
|
17
|
-
* wrapper where the source spans 8+ lines but the executable
|
|
18
|
-
* body is only ~80 characters once normalized — structurally
|
|
19
|
-
* identical to every other thin wrapper, but never an actionable
|
|
20
|
-
* refactor target.
|
|
21
|
-
*
|
|
22
|
-
* Emits N-1 signals per group (one per non-primary occurrence).
|
|
23
|
-
*
|
|
24
|
-
* 2. **Aggregate (cross-package).** For each body hash present in
|
|
25
|
-
* ≥ minCrossPackageDuplicatePackages (default 3) DISTINCT packages
|
|
26
|
-
* (via `pkgOf`), the rule emits ONE aggregate signal naming the
|
|
27
|
-
* packages and SUPPRESSES the per-instance signals for that same hash
|
|
28
|
-
* (no double-reporting). Bodies that don't reach N packages flow
|
|
29
|
-
* through path (1) unchanged.
|
|
30
|
-
*
|
|
31
|
-
* This path applies a LIGHTER, body-size-only floor
|
|
32
|
-
* (`minCrossPackageDuplicateBodySize`, default 80 chars — no line floor)
|
|
33
|
-
* rather than the per-instance floor: a *small* body copied across
|
|
34
|
-
* packages is exactly what this path exists to catch, so the floor is
|
|
35
|
-
* tuned only to drop TRIVIAL bodies (empty DI-constructor shims,
|
|
36
|
-
* one-line getters, thin delegators) that are never consolidation
|
|
37
|
-
* targets, while keeping genuinely-small shared utilities visible.
|
|
11
|
+
* The detection algorithm + curation policy now live in `@opensip-cli/clone-detection`
|
|
12
|
+
* (ADR-0064) so graph and yagni single-source them. This rule is a thin adapter: it maps
|
|
13
|
+
* the catalog to `CloneCandidate[]` (pre-resolving `bodyLines` from the feature table and
|
|
14
|
+
* `package` from `pkgOf`), calls `findDuplicateBodies`, and wraps each finding into a
|
|
15
|
+
* graph `Signal` with the unchanged message/severity/metadata — so output is byte-stable.
|
|
38
16
|
*/
|
|
17
|
+
import { findDuplicateBodies } from '@opensip-cli/clone-detection';
|
|
39
18
|
import { pkgOf } from '../resolve-callee.js';
|
|
40
19
|
import { createGraphSignal } from './create-graph-signal.js';
|
|
41
20
|
import { defineRule } from './define-rule.js';
|
|
42
|
-
const DEFAULT_MIN_LINES = 5;
|
|
43
|
-
const DEFAULT_MIN_BODY_SIZE = 200;
|
|
44
|
-
const DEFAULT_MIN_CROSS_PACKAGE_PACKAGES = 3;
|
|
45
|
-
// Lighter than DEFAULT_MIN_BODY_SIZE (the per-instance floor) and applied with
|
|
46
|
-
// NO line floor: the aggregate path is meant to catch genuinely-small shared
|
|
47
|
-
// utilities copied across packages, so its floor only drops trivial bodies
|
|
48
|
-
// (empty DI shims, one-line getters/delegators).
|
|
49
|
-
const DEFAULT_MIN_CROSS_PACKAGE_BODY_SIZE = 80;
|
|
50
21
|
const SLUG = 'graph:duplicated-function-body';
|
|
51
22
|
export const duplicatedFunctionBodyRule = defineRule({
|
|
52
23
|
slug: SLUG,
|
|
53
24
|
defaultSeverity: 'warning',
|
|
54
25
|
featureDeps: ['bodyLines'],
|
|
55
26
|
evaluate({ catalog, config, features }) {
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
continue;
|
|
70
|
-
const anchor = lowestByQualifiedName(occs);
|
|
71
|
-
// Lighter, body-size-only floor (NO line floor): keeps the aggregate
|
|
72
|
-
// path catching genuinely-small shared utilities copied across packages,
|
|
73
|
-
// while dropping trivial bodies — empty DI-constructor shims, one-line
|
|
74
|
-
// getters, thin delegators — that are not consolidation targets. A body
|
|
75
|
-
// that fails this won't surface on the per-instance path either (its
|
|
76
|
-
// 200-char floor is stricter), so there is nothing to suppress.
|
|
77
|
-
if (anchor.bodySize !== undefined && anchor.bodySize < minCrossPackageBodySize)
|
|
78
|
-
continue;
|
|
79
|
-
// This hash is owned by the aggregate path; suppress its per-instance
|
|
80
|
-
// signals so a single duplicate group never double-reports.
|
|
81
|
-
suppressedHashes.add(bodyHash);
|
|
82
|
-
signals.push(createGraphSignal(SLUG, config, {
|
|
83
|
-
severity: 'low',
|
|
84
|
-
category: 'quality',
|
|
85
|
-
message: `This body is duplicated across ${String(packages.length)} packages (${packages.join(', ')}) in ${String(occs.length)} occurrences — hoist it into a shared package.`,
|
|
86
|
-
code: { file: anchor.filePath, line: anchor.line, column: anchor.column },
|
|
87
|
-
suggestion: 'Hoist the shared body into a single shared package and have every copy import it.',
|
|
88
|
-
metadata: {
|
|
89
|
-
packages,
|
|
90
|
-
packageCount: packages.length,
|
|
91
|
-
occurrenceCount: occs.length,
|
|
92
|
-
bodyHash,
|
|
93
|
-
},
|
|
94
|
-
}));
|
|
95
|
-
}
|
|
96
|
-
// Per-instance path: size-gated groups, skipping any hash already
|
|
97
|
-
// claimed by an aggregate signal so a group never double-reports.
|
|
98
|
-
const groups = groupByHash(catalog, minLines, minBodySize, features);
|
|
99
|
-
signals.push(...emitPerInstanceSignals(groups, suppressedHashes, config));
|
|
100
|
-
return signals;
|
|
27
|
+
const candidates = toCandidates(catalog, features);
|
|
28
|
+
const { aggregates, groups } = findDuplicateBodies(candidates, {
|
|
29
|
+
minLines: config.minDuplicateBodyLines,
|
|
30
|
+
minBodySize: config.minDuplicateBodySize,
|
|
31
|
+
minCrossPackagePackages: config.minCrossPackageDuplicatePackages,
|
|
32
|
+
minCrossPackageBodySize: config.minCrossPackageDuplicateBodySize,
|
|
33
|
+
});
|
|
34
|
+
// Aggregate (cross-package) signals first, then per-instance signals — same order
|
|
35
|
+
// as the prior loops, but built as bounded in-memory projections over rule findings.
|
|
36
|
+
return [
|
|
37
|
+
...aggregates.map((agg) => aggregateSignal(agg, config)),
|
|
38
|
+
...groups.flatMap((group) => groupSignals(group, config)),
|
|
39
|
+
];
|
|
101
40
|
},
|
|
102
41
|
});
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (suppressedHashes.has(primary.bodyHash))
|
|
118
|
-
continue;
|
|
119
|
-
for (let i = 1; i < group.length; i++) {
|
|
120
|
-
const occ = group[i];
|
|
121
|
-
/* v8 ignore next */
|
|
122
|
-
if (!occ)
|
|
123
|
-
continue;
|
|
124
|
-
signals.push(createGraphSignal(SLUG, config, {
|
|
125
|
-
severity: 'low',
|
|
126
|
-
category: 'quality',
|
|
127
|
-
message: `${occ.simpleName} has the same body as ${primary.qualifiedName} (${primary.filePath}:${String(primary.line)}).`,
|
|
128
|
-
code: { file: occ.filePath, line: occ.line, column: occ.column },
|
|
129
|
-
suggestion: 'Extract the shared body to a single function and have both call sites import it.',
|
|
130
|
-
metadata: {
|
|
131
|
-
primary: primary.qualifiedName,
|
|
132
|
-
duplicate: occ.qualifiedName,
|
|
133
|
-
groupSize: group.length,
|
|
134
|
-
},
|
|
135
|
-
}));
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return signals;
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* A function's stable physical identity: where it is declared. The catalog's
|
|
142
|
-
* `functions` record is keyed by simple name, and the same physical function
|
|
143
|
-
* can surface more than once across that index (e.g. indexed under both its
|
|
144
|
-
* simple and qualified name, or a recursive function whose self-reference is
|
|
145
|
-
* walked as an extra occurrence). Two entries that share this identity are the
|
|
146
|
-
* SAME function — they must never count as a duplicate of each other. Keying on
|
|
147
|
-
* file + declaration position + simple name keeps genuinely-distinct functions
|
|
148
|
-
* (same body, different file/line) in separate identities while collapsing
|
|
149
|
-
* self-matches.
|
|
150
|
-
*/
|
|
151
|
-
function identityOf(occ) {
|
|
152
|
-
return `${occ.filePath}:${String(occ.line)}:${String(occ.column)}:${occ.simpleName}`;
|
|
42
|
+
function aggregateSignal(agg, config) {
|
|
43
|
+
return createGraphSignal(SLUG, config, {
|
|
44
|
+
severity: 'low',
|
|
45
|
+
category: 'quality',
|
|
46
|
+
message: `This body is duplicated across ${String(agg.packages.length)} packages (${agg.packages.join(', ')}) in ${String(agg.occurrenceCount)} occurrences — hoist it into a shared package.`,
|
|
47
|
+
code: { file: agg.anchor.filePath, line: agg.anchor.line, column: agg.anchor.column },
|
|
48
|
+
suggestion: 'Hoist the shared body into a single shared package and have every copy import it.',
|
|
49
|
+
metadata: {
|
|
50
|
+
packages: agg.packages,
|
|
51
|
+
packageCount: agg.packages.length,
|
|
52
|
+
occurrenceCount: agg.occurrenceCount,
|
|
53
|
+
bodyHash: agg.bodyHash,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
153
56
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
* function never appears twice in its own bucket. Without this, a function that
|
|
157
|
-
* surfaces more than once in the catalog index (self-reference, dual name
|
|
158
|
-
* indexing) would form a phantom 2-member group and self-report as a duplicate.
|
|
159
|
-
*/
|
|
160
|
-
function pushDeduped(buckets, seenByHash, occ) {
|
|
161
|
-
let bucket = buckets.get(occ.bodyHash);
|
|
162
|
-
let seen = seenByHash.get(occ.bodyHash);
|
|
163
|
-
if (!bucket) {
|
|
164
|
-
bucket = [];
|
|
165
|
-
buckets.set(occ.bodyHash, bucket);
|
|
166
|
-
seen = new Set();
|
|
167
|
-
seenByHash.set(occ.bodyHash, seen);
|
|
168
|
-
}
|
|
57
|
+
function groupSignals(group, config) {
|
|
58
|
+
const primary = group.members[0];
|
|
169
59
|
/* v8 ignore next */
|
|
170
|
-
if (!
|
|
171
|
-
return;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const occs = catalog.functions[name];
|
|
185
|
-
/* v8 ignore next */
|
|
186
|
-
if (!occs)
|
|
187
|
-
continue;
|
|
188
|
-
for (const occ of occs) {
|
|
189
|
-
if (!isInterestingForDup(occ, minLines, minBodySize, features))
|
|
190
|
-
continue;
|
|
191
|
-
pushDeduped(buckets, seenByHash, occ);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return [...buckets.values()];
|
|
60
|
+
if (!primary)
|
|
61
|
+
return [];
|
|
62
|
+
return group.members.slice(1).map((occ) => createGraphSignal(SLUG, config, {
|
|
63
|
+
severity: 'low',
|
|
64
|
+
category: 'quality',
|
|
65
|
+
message: `${occ.simpleName} has the same body as ${primary.qualifiedName} (${primary.filePath}:${String(primary.line)}).`,
|
|
66
|
+
code: { file: occ.filePath, line: occ.line, column: occ.column },
|
|
67
|
+
suggestion: 'Extract the shared body to a single function and have both call sites import it.',
|
|
68
|
+
metadata: {
|
|
69
|
+
primary: primary.qualifiedName,
|
|
70
|
+
duplicate: occ.qualifiedName,
|
|
71
|
+
groupSize: group.members.length,
|
|
72
|
+
},
|
|
73
|
+
}));
|
|
195
74
|
}
|
|
196
75
|
/**
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
76
|
+
* Map the catalog to `CloneCandidate[]` in the SAME iteration order the prior in-rule
|
|
77
|
+
* grouping used (`Object.keys(catalog.functions)` → each name's occurrences), so the
|
|
78
|
+
* substrate's bucket/primary selection and emission order are byte-identical.
|
|
79
|
+
* `bodyLines` is pre-resolved from the feature table (canonical span) with the inline
|
|
80
|
+
* `endLine − line + 1` fallback handled by the substrate; `package` is `pkgOf` (the
|
|
81
|
+
* nearest-package.json resolution the aggregate path keys on).
|
|
201
82
|
*/
|
|
202
|
-
function
|
|
203
|
-
const
|
|
204
|
-
const seenByHash = new Map();
|
|
83
|
+
function toCandidates(catalog, features) {
|
|
84
|
+
const candidates = [];
|
|
205
85
|
for (const name of Object.keys(catalog.functions)) {
|
|
206
86
|
const occs = catalog.functions[name];
|
|
207
87
|
/* v8 ignore next */
|
|
208
88
|
if (!occs)
|
|
209
89
|
continue;
|
|
210
|
-
for (const occ of occs)
|
|
211
|
-
|
|
212
|
-
continue;
|
|
213
|
-
pushDeduped(buckets, seenByHash, occ);
|
|
214
|
-
}
|
|
90
|
+
for (const occ of occs)
|
|
91
|
+
candidates.push(toCandidate(occ, features));
|
|
215
92
|
}
|
|
216
|
-
return
|
|
217
|
-
}
|
|
218
|
-
function lowestByQualifiedName(occs) {
|
|
219
|
-
return occs.reduce((lo, c) => (c.qualifiedName < lo.qualifiedName ? c : lo));
|
|
93
|
+
return candidates;
|
|
220
94
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return false;
|
|
238
|
-
return true;
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* The per-instance dup-body filter: the shared kind/test exclusions plus
|
|
242
|
-
* the size/line floor. The bodySize threshold drops trivial wrappers — a
|
|
243
|
-
* `defineCheck` or pass-through analyze that contains an `if`-guard plus a
|
|
244
|
-
* delegating call. These have identical normalized bodies across many
|
|
245
|
-
* files but are never the right refactor target. Catalogs from older runs
|
|
246
|
-
* that lack `bodySize` skip the size check (treated as "passes").
|
|
247
|
-
*/
|
|
248
|
-
function isInterestingForDup(occ, minLines, minBodySize, features) {
|
|
249
|
-
if (!isEligibleKind(occ))
|
|
250
|
-
return false;
|
|
251
|
-
// The bodyLines feature column is the canonical span (computed once in
|
|
252
|
-
// pipeline/features.ts). The inline `endLine − line + 1` here is the single
|
|
253
|
-
// sanctioned graceful-degrade fallback for features-absent calls (3/4-arg
|
|
254
|
-
// test evaluate), not a duplicate of the engine derivation.
|
|
255
|
-
const span = features?.function.get(occ.bodyHash)?.bodyLines ?? occ.endLine - occ.line + 1;
|
|
256
|
-
if (span < minLines)
|
|
257
|
-
return false;
|
|
258
|
-
if (occ.bodySize !== undefined && occ.bodySize < minBodySize)
|
|
259
|
-
return false;
|
|
260
|
-
return true;
|
|
95
|
+
function toCandidate(occ, features) {
|
|
96
|
+
const bodyLines = features?.function.get(occ.bodyHash)?.bodyLines;
|
|
97
|
+
return {
|
|
98
|
+
bodyHash: occ.bodyHash,
|
|
99
|
+
kind: occ.kind,
|
|
100
|
+
inTestFile: occ.inTestFile,
|
|
101
|
+
filePath: occ.filePath,
|
|
102
|
+
line: occ.line,
|
|
103
|
+
column: occ.column,
|
|
104
|
+
endLine: occ.endLine,
|
|
105
|
+
simpleName: occ.simpleName,
|
|
106
|
+
qualifiedName: occ.qualifiedName,
|
|
107
|
+
package: pkgOf(occ),
|
|
108
|
+
...(occ.bodySize === undefined ? {} : { bodySize: occ.bodySize }),
|
|
109
|
+
...(bodyLines === undefined ? {} : { bodyLines }),
|
|
110
|
+
};
|
|
261
111
|
}
|
|
262
112
|
//# sourceMappingURL=duplicated-function-body.js.map
|