@kernlang/codemod 3.3.4
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/LICENSE +678 -0
- package/dist/adapter-registry.d.ts +12 -0
- package/dist/adapter-registry.js +21 -0
- package/dist/adapter-registry.js.map +1 -0
- package/dist/adapters/index.d.ts +2 -0
- package/dist/adapters/index.js +5 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/zustand-store.d.ts +14 -0
- package/dist/adapters/zustand-store.js +261 -0
- package/dist/adapters/zustand-store.js.map +1 -0
- package/dist/apply-files.d.ts +19 -0
- package/dist/apply-files.js +60 -0
- package/dist/apply-files.js.map +1 -0
- package/dist/apply.d.ts +37 -0
- package/dist/apply.js +275 -0
- package/dist/apply.js.map +1 -0
- package/dist/audit.d.ts +10 -0
- package/dist/audit.js +26 -0
- package/dist/audit.js.map +1 -0
- package/dist/diagnostics.d.ts +36 -0
- package/dist/diagnostics.js +117 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/format.d.ts +13 -0
- package/dist/format.js +30 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/project.d.ts +16 -0
- package/dist/project.js +47 -0
- package/dist/project.js.map +1 -0
- package/dist/template-loader.d.ts +15 -0
- package/dist/template-loader.js +55 -0
- package/dist/template-loader.js.map +1 -0
- package/dist/types.d.ts +86 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/package.json +39 -0
package/dist/apply.js
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apply orchestrator — turn a TemplateMatch into a transformed source file.
|
|
3
|
+
*
|
|
4
|
+
* Pipeline:
|
|
5
|
+
* 1. Adapter resolves the exact rewrite region (re-derived from AST; detector
|
|
6
|
+
* reports endLine=EOF which we ignore).
|
|
7
|
+
* 2. Adapter extracts the user interior as CHILDREN lines.
|
|
8
|
+
* 3. Parse match.suggestedKern into an IR node and register the named
|
|
9
|
+
* template if it isn't already.
|
|
10
|
+
* 4. Attach a `handler` child carrying the extracted CHILDREN.
|
|
11
|
+
* 5. expandTemplateNode → generateCoreNode-style line array.
|
|
12
|
+
* 6. Strip template-prepended imports (source file already imports them).
|
|
13
|
+
* 7. Splice new text into source at [region.start, region.end] (no comment
|
|
14
|
+
* duplication — `before` already contains any leading trivia).
|
|
15
|
+
* 8. Mutate the original SourceFile IN-PLACE with replaceWithText so that
|
|
16
|
+
* downstream consumers see the transformed module when diagnostics run,
|
|
17
|
+
* then revert if any gate fails.
|
|
18
|
+
* 9. Reparse check (syntax), re-detect gate (template still recognized),
|
|
19
|
+
* affected-set diagnostics gate, whole-program gate (--write only).
|
|
20
|
+
* 10. Write audit entry and return ApplyResult.
|
|
21
|
+
*
|
|
22
|
+
* The shared ts-morph Project is the caller's responsibility (see project.ts)
|
|
23
|
+
* so affected-set diagnostics amortize across --interactive sessions.
|
|
24
|
+
*/
|
|
25
|
+
import { expandTemplateNode, parse } from '@kernlang/core';
|
|
26
|
+
import { detectTemplates } from '@kernlang/review';
|
|
27
|
+
import { getAdapter } from './adapter-registry.js';
|
|
28
|
+
import { runAffectedSetDiagnostics, runWholeProgramDiagnostics, snapshotWholeProgram } from './diagnostics.js';
|
|
29
|
+
import { ensureTemplate } from './template-loader.js';
|
|
30
|
+
// ── Helpers ────────────────────────────────────────────────────────────
|
|
31
|
+
/**
|
|
32
|
+
* Strip leading `import ... from '...';` lines plus a single following blank line.
|
|
33
|
+
* The source file already imports what the template requires; re-injecting the
|
|
34
|
+
* template's import block would duplicate it.
|
|
35
|
+
*/
|
|
36
|
+
function stripTemplateImports(lines) {
|
|
37
|
+
const out = [...lines];
|
|
38
|
+
while (out.length > 0 && /^\s*import\s/.test(out[0])) {
|
|
39
|
+
out.shift();
|
|
40
|
+
}
|
|
41
|
+
if (out.length > 0 && out[0].trim() === '') {
|
|
42
|
+
out.shift();
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
/** Return the column (0-indexed) of region.start in its containing line. */
|
|
47
|
+
function columnAt(sourceText, offset) {
|
|
48
|
+
const lineStart = sourceText.lastIndexOf('\n', offset - 1) + 1;
|
|
49
|
+
return offset - lineStart;
|
|
50
|
+
}
|
|
51
|
+
/** Indent every line (except the first) by `col` spaces. */
|
|
52
|
+
function indentBlock(text, col) {
|
|
53
|
+
if (col <= 0)
|
|
54
|
+
return text;
|
|
55
|
+
const pad = ' '.repeat(col);
|
|
56
|
+
return text
|
|
57
|
+
.split('\n')
|
|
58
|
+
.map((line, i) => (i === 0 || line.length === 0 ? line : pad + line))
|
|
59
|
+
.join('\n');
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Produce a minimal unified-style diff for audit/dry-run display.
|
|
63
|
+
* Not a full diff engine — just enough to visualize what will change.
|
|
64
|
+
*/
|
|
65
|
+
function minimalDiff(oldText, newText, filePath) {
|
|
66
|
+
if (oldText === newText)
|
|
67
|
+
return '';
|
|
68
|
+
const oldLines = oldText.split('\n');
|
|
69
|
+
const newLines = newText.split('\n');
|
|
70
|
+
const header = `--- a/${filePath}\n+++ b/${filePath}\n`;
|
|
71
|
+
return `${header}${oldLines.map((l) => `-${l}`).join('\n')}\n${newLines.map((l) => `+${l}`).join('\n')}\n`;
|
|
72
|
+
}
|
|
73
|
+
export function applyMatch(input, options = {}) {
|
|
74
|
+
const { project, filePath, match } = input;
|
|
75
|
+
const timestamp = new Date().toISOString();
|
|
76
|
+
const confidencePct = match.confidencePct;
|
|
77
|
+
const base = {
|
|
78
|
+
filePath,
|
|
79
|
+
templateName: match.templateName,
|
|
80
|
+
confidencePct,
|
|
81
|
+
decision: 'skipped',
|
|
82
|
+
timestamp,
|
|
83
|
+
tsTokens: match.tsTokens,
|
|
84
|
+
kernTokens: match.kernTokens,
|
|
85
|
+
};
|
|
86
|
+
// Confidence gate first — cheapest check.
|
|
87
|
+
const minConf = options.minConfidence ?? 80;
|
|
88
|
+
if (confidencePct < minConf) {
|
|
89
|
+
return { ...base, reason: `confidence ${confidencePct} < min ${minConf}` };
|
|
90
|
+
}
|
|
91
|
+
if (options.templateName && options.templateName !== match.templateName) {
|
|
92
|
+
return { ...base, reason: `template filter excluded ${match.templateName}` };
|
|
93
|
+
}
|
|
94
|
+
const adapter = getAdapter(match.templateName);
|
|
95
|
+
if (!adapter) {
|
|
96
|
+
return { ...base, reason: `no adapter registered for ${match.templateName}` };
|
|
97
|
+
}
|
|
98
|
+
const sourceFile = project.getSourceFile(filePath);
|
|
99
|
+
if (!sourceFile) {
|
|
100
|
+
return { ...base, reason: `source file not in project: ${filePath}` };
|
|
101
|
+
}
|
|
102
|
+
// 1. Resolve region
|
|
103
|
+
const resolved = adapter.resolveRegion(sourceFile, match);
|
|
104
|
+
if (!resolved.ok) {
|
|
105
|
+
return { ...base, reason: `resolveRegion: ${resolved.reason}` };
|
|
106
|
+
}
|
|
107
|
+
// 2. Extract children
|
|
108
|
+
const extracted = adapter.extractChildren(sourceFile, resolved.region, match);
|
|
109
|
+
if (!extracted.ok) {
|
|
110
|
+
return { ...base, reason: `extractChildren: ${extracted.reason}` };
|
|
111
|
+
}
|
|
112
|
+
// 3. Ensure template registered + parse suggestedKern
|
|
113
|
+
if (!match.suggestedKern) {
|
|
114
|
+
return { ...base, reason: 'match has no suggestedKern' };
|
|
115
|
+
}
|
|
116
|
+
if (!ensureTemplate(match.templateName)) {
|
|
117
|
+
return { ...base, reason: `template ${match.templateName} not found in catalog` };
|
|
118
|
+
}
|
|
119
|
+
let irNode;
|
|
120
|
+
try {
|
|
121
|
+
const ast = parse(match.suggestedKern);
|
|
122
|
+
if (ast.type === match.templateName) {
|
|
123
|
+
irNode = ast;
|
|
124
|
+
}
|
|
125
|
+
else if (ast.children && ast.children.length === 1 && ast.children[0].type === match.templateName) {
|
|
126
|
+
irNode = ast.children[0];
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
return { ...base, reason: `parsed suggestedKern type=${ast.type}, expected ${match.templateName}` };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
return { ...base, reason: `parse suggestedKern: ${err.message}` };
|
|
134
|
+
}
|
|
135
|
+
// 4. Attach handler child with extracted CHILDREN (if any).
|
|
136
|
+
if (extracted.children.length > 0) {
|
|
137
|
+
const handlerChild = {
|
|
138
|
+
type: 'handler',
|
|
139
|
+
props: { code: extracted.children.join('\n') },
|
|
140
|
+
children: [],
|
|
141
|
+
};
|
|
142
|
+
irNode = { ...irNode, children: [...(irNode.children || []), handlerChild] };
|
|
143
|
+
}
|
|
144
|
+
// 5. Expand
|
|
145
|
+
let expandedLines;
|
|
146
|
+
try {
|
|
147
|
+
expandedLines = expandTemplateNode(irNode);
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
return { ...base, reason: `expandTemplateNode: ${err.message}` };
|
|
151
|
+
}
|
|
152
|
+
// 6. Strip template-added imports (file already has them).
|
|
153
|
+
expandedLines = stripTemplateImports(expandedLines);
|
|
154
|
+
while (expandedLines.length > 0 && expandedLines[expandedLines.length - 1].trim() === '') {
|
|
155
|
+
expandedLines.pop();
|
|
156
|
+
}
|
|
157
|
+
const expandedText = expandedLines.join('\n');
|
|
158
|
+
const originalText = sourceFile.getFullText();
|
|
159
|
+
const col = columnAt(originalText, resolved.region.start);
|
|
160
|
+
const indented = indentBlock(expandedText, col);
|
|
161
|
+
// 7. Splice — `before` already contains any leading trivia, so do NOT prepend
|
|
162
|
+
// comments separately (that would duplicate them, per codex-review feedback).
|
|
163
|
+
const before = originalText.slice(0, resolved.region.start);
|
|
164
|
+
const after = originalText.slice(resolved.region.end);
|
|
165
|
+
const newText = before + indented + after;
|
|
166
|
+
if (newText === originalText) {
|
|
167
|
+
return {
|
|
168
|
+
...base,
|
|
169
|
+
decision: 'skipped',
|
|
170
|
+
reason: 'transform produced identical output (idempotent)',
|
|
171
|
+
replacedSpan: { start: resolved.region.start, end: resolved.region.end },
|
|
172
|
+
diff: '',
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// 8. Mutate in place so dependents see the transformed module when we run
|
|
176
|
+
// downstream diagnostics. Revert on any gate failure.
|
|
177
|
+
const wholeProgramBaseline = options.write ? snapshotWholeProgram(project) : undefined;
|
|
178
|
+
try {
|
|
179
|
+
sourceFile.replaceWithText(newText);
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
return {
|
|
183
|
+
...base,
|
|
184
|
+
reason: `reparse failed: ${err.message}`,
|
|
185
|
+
replacedSpan: { start: resolved.region.start, end: resolved.region.end },
|
|
186
|
+
parseOk: false,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const revert = () => {
|
|
190
|
+
sourceFile.replaceWithText(originalText);
|
|
191
|
+
};
|
|
192
|
+
// 9a. Re-detect gate: a canonical rewrite must still be detectable as the
|
|
193
|
+
// same template. If the rewrite broke pattern recognition, reject.
|
|
194
|
+
let reDetectOk = true;
|
|
195
|
+
try {
|
|
196
|
+
const reMatches = detectTemplates(sourceFile);
|
|
197
|
+
if (!reMatches.some((m) => m.templateName === match.templateName)) {
|
|
198
|
+
reDetectOk = false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// Detection errors are non-fatal; tsc gate remains authoritative.
|
|
203
|
+
}
|
|
204
|
+
if (!reDetectOk) {
|
|
205
|
+
revert();
|
|
206
|
+
return {
|
|
207
|
+
...base,
|
|
208
|
+
decision: 'rejected',
|
|
209
|
+
reason: 're-detect failed: template pattern no longer recognized after rewrite',
|
|
210
|
+
replacedSpan: { start: resolved.region.start, end: resolved.region.end },
|
|
211
|
+
diff: minimalDiff(originalText, newText, filePath),
|
|
212
|
+
parseOk: true,
|
|
213
|
+
reDetectOk: false,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
// 9b. Affected-set diagnostics (always). The transformed SourceFile is the
|
|
217
|
+
// real one now, so referencing files are checked against the new module.
|
|
218
|
+
const newDiags = runAffectedSetDiagnostics(project, sourceFile, input.preDiagnostics);
|
|
219
|
+
if (newDiags.length > 0) {
|
|
220
|
+
revert();
|
|
221
|
+
return {
|
|
222
|
+
...base,
|
|
223
|
+
decision: 'rejected',
|
|
224
|
+
reason: `new diagnostics: ${newDiags.length}`,
|
|
225
|
+
replacedSpan: { start: resolved.region.start, end: resolved.region.end },
|
|
226
|
+
diff: minimalDiff(originalText, newText, filePath),
|
|
227
|
+
parseOk: true,
|
|
228
|
+
reDetectOk,
|
|
229
|
+
newDiagnostics: newDiags,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
// 9c. Whole-program final gate (--write only) — catches transitive-dep errors.
|
|
233
|
+
if (options.write && wholeProgramBaseline) {
|
|
234
|
+
const programNewDiags = runWholeProgramDiagnostics(project, wholeProgramBaseline);
|
|
235
|
+
if (programNewDiags.length > 0) {
|
|
236
|
+
revert();
|
|
237
|
+
return {
|
|
238
|
+
...base,
|
|
239
|
+
decision: 'rejected',
|
|
240
|
+
reason: `new whole-program diagnostics: ${programNewDiags.length}`,
|
|
241
|
+
replacedSpan: { start: resolved.region.start, end: resolved.region.end },
|
|
242
|
+
diff: minimalDiff(originalText, newText, filePath),
|
|
243
|
+
parseOk: true,
|
|
244
|
+
reDetectOk,
|
|
245
|
+
newDiagnostics: programNewDiags,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const diff = minimalDiff(originalText, newText, filePath);
|
|
250
|
+
if (options.write) {
|
|
251
|
+
sourceFile.saveSync();
|
|
252
|
+
return {
|
|
253
|
+
...base,
|
|
254
|
+
decision: 'applied',
|
|
255
|
+
replacedSpan: { start: resolved.region.start, end: resolved.region.end },
|
|
256
|
+
diff,
|
|
257
|
+
parseOk: true,
|
|
258
|
+
reDetectOk,
|
|
259
|
+
newDiagnostics: [],
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
// Dry-run path — revert the in-memory edit so subsequent candidates see the
|
|
263
|
+
// original file.
|
|
264
|
+
revert();
|
|
265
|
+
return {
|
|
266
|
+
...base,
|
|
267
|
+
decision: 'dry-run',
|
|
268
|
+
replacedSpan: { start: resolved.region.start, end: resolved.region.end },
|
|
269
|
+
diff,
|
|
270
|
+
parseOk: true,
|
|
271
|
+
reDetectOk,
|
|
272
|
+
newDiagnostics: [],
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=apply.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply.js","sourceRoot":"","sources":["../src/apply.ts"],"names":[],"mappings":"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"}
|
package/dist/audit.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit log — JSONL append-only.
|
|
3
|
+
*
|
|
4
|
+
* Every invocation of applyMatch (including dry-runs and rejections) produces
|
|
5
|
+
* an audit entry. Default path is .kern/codemod-audit.jsonl under cwd, created
|
|
6
|
+
* on demand. Each line is a self-contained JSON object with schema:1.
|
|
7
|
+
*/
|
|
8
|
+
import type { ApplyResult } from './types.js';
|
|
9
|
+
export declare function defaultAuditPath(cwd: string): string;
|
|
10
|
+
export declare function writeAuditEntry(auditPath: string, result: ApplyResult): void;
|
package/dist/audit.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit log — JSONL append-only.
|
|
3
|
+
*
|
|
4
|
+
* Every invocation of applyMatch (including dry-runs and rejections) produces
|
|
5
|
+
* an audit entry. Default path is .kern/codemod-audit.jsonl under cwd, created
|
|
6
|
+
* on demand. Each line is a self-contained JSON object with schema:1.
|
|
7
|
+
*/
|
|
8
|
+
import { appendFileSync, existsSync, mkdirSync } from 'fs';
|
|
9
|
+
import { dirname, resolve } from 'path';
|
|
10
|
+
export function defaultAuditPath(cwd) {
|
|
11
|
+
return resolve(cwd, '.kern', 'codemod-audit.jsonl');
|
|
12
|
+
}
|
|
13
|
+
export function writeAuditEntry(auditPath, result) {
|
|
14
|
+
const entry = { ...result, schema: 1 };
|
|
15
|
+
try {
|
|
16
|
+
const dir = dirname(auditPath);
|
|
17
|
+
if (!existsSync(dir)) {
|
|
18
|
+
mkdirSync(dir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
appendFileSync(auditPath, `${JSON.stringify(entry)}\n`, 'utf-8');
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
console.error(`kern-codemod: audit write failed (${auditPath}): ${err.message}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAGxC,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,OAAO,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,qBAAqB,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,SAAiB,EAAE,MAAmB;IACpE,MAAM,KAAK,GAAe,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACnD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;QAC/B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtC,CAAC;QACD,cAAc,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACnE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,qCAAqC,SAAS,MAAO,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9F,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two-stage diagnostics gate.
|
|
3
|
+
*
|
|
4
|
+
* Stage 1 (always): affected-set — the transformed file plus any source files
|
|
5
|
+
* that directly reference it. Fast enough for --interactive.
|
|
6
|
+
*
|
|
7
|
+
* Stage 2 (--write only): whole-program — full Program diagnostics via the
|
|
8
|
+
* shared Project. Catches cross-file regressions through transitive references
|
|
9
|
+
* that stage 1 misses. Slow (~500ms–2s on medium repos) so we reserve it for
|
|
10
|
+
* actual writes.
|
|
11
|
+
*
|
|
12
|
+
* Both stages compare pre-transform vs post-transform diagnostic fingerprints
|
|
13
|
+
* and return ONLY NEW diagnostics as an array of human-readable strings.
|
|
14
|
+
*/
|
|
15
|
+
import type { Project, SourceFile } from 'ts-morph';
|
|
16
|
+
/**
|
|
17
|
+
* Snapshot diagnostics for a file's affected set (file + direct referencers).
|
|
18
|
+
* Use this once per file BEFORE transform; pass the returned strings back
|
|
19
|
+
* to runAffectedSetDiagnostics as the `preDiagnostics` baseline.
|
|
20
|
+
*/
|
|
21
|
+
export declare function snapshotAffectedSet(_project: Project, sourceFile: SourceFile): string[];
|
|
22
|
+
/**
|
|
23
|
+
* Run the affected-set diagnostics check and return any NEW diagnostics
|
|
24
|
+
* (not present in preDiagnostics baseline).
|
|
25
|
+
*/
|
|
26
|
+
export declare function runAffectedSetDiagnostics(_project: Project, transformedFile: SourceFile, preDiagnostics: ReadonlyArray<string> | undefined): string[];
|
|
27
|
+
/**
|
|
28
|
+
* Snapshot every diagnostic the Program currently produces. Use before --write
|
|
29
|
+
* to establish the whole-program baseline.
|
|
30
|
+
*/
|
|
31
|
+
export declare function snapshotWholeProgram(project: Project): string[];
|
|
32
|
+
/**
|
|
33
|
+
* Whole-program check — diff post-transform Program diagnostics against a
|
|
34
|
+
* baseline snapshot taken before the transform. Returns any NEW entries.
|
|
35
|
+
*/
|
|
36
|
+
export declare function runWholeProgramDiagnostics(project: Project, preDiagnostics: ReadonlyArray<string>): string[];
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two-stage diagnostics gate.
|
|
3
|
+
*
|
|
4
|
+
* Stage 1 (always): affected-set — the transformed file plus any source files
|
|
5
|
+
* that directly reference it. Fast enough for --interactive.
|
|
6
|
+
*
|
|
7
|
+
* Stage 2 (--write only): whole-program — full Program diagnostics via the
|
|
8
|
+
* shared Project. Catches cross-file regressions through transitive references
|
|
9
|
+
* that stage 1 misses. Slow (~500ms–2s on medium repos) so we reserve it for
|
|
10
|
+
* actual writes.
|
|
11
|
+
*
|
|
12
|
+
* Both stages compare pre-transform vs post-transform diagnostic fingerprints
|
|
13
|
+
* and return ONLY NEW diagnostics as an array of human-readable strings.
|
|
14
|
+
*/
|
|
15
|
+
const TEMP_SUFFIX = '.kern-codemod.tmp.ts';
|
|
16
|
+
/**
|
|
17
|
+
* Normalize the temp file path back to its original form so the same
|
|
18
|
+
* diagnostic on the original file and on the temp file share a fingerprint.
|
|
19
|
+
*/
|
|
20
|
+
function normalizePath(p) {
|
|
21
|
+
if (p.endsWith(TEMP_SUFFIX))
|
|
22
|
+
return p.slice(0, -TEMP_SUFFIX.length);
|
|
23
|
+
return p;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Fingerprint a diagnostic for baseline comparison. We exclude start offset —
|
|
27
|
+
* the transform will shift every position in the file, so offset-sensitive
|
|
28
|
+
* fingerprints would report every pre-existing diagnostic as "new". We keep
|
|
29
|
+
* code + normalized file + message, which is coarse but sound: two logically
|
|
30
|
+
* identical diagnostics match, and any NEW compile error shows up as new.
|
|
31
|
+
*/
|
|
32
|
+
function fingerprint(d) {
|
|
33
|
+
const file = normalizePath(d.getSourceFile()?.getFilePath() ?? '<no-file>');
|
|
34
|
+
const code = d.getCode();
|
|
35
|
+
const msg = typeof d.getMessageText() === 'string' ? d.getMessageText() : 'multi-line';
|
|
36
|
+
const shortMsg = msg.length > 120 ? `${msg.slice(0, 120)}…` : msg;
|
|
37
|
+
return `${file}|${code}|${shortMsg}`;
|
|
38
|
+
}
|
|
39
|
+
function readable(d) {
|
|
40
|
+
const file = d.getSourceFile()?.getFilePath() ?? '<no-file>';
|
|
41
|
+
const line = d.getLineNumber() ?? 0;
|
|
42
|
+
const code = d.getCode();
|
|
43
|
+
const msg = typeof d.getMessageText() === 'string' ? d.getMessageText() : 'multi-line diagnostic';
|
|
44
|
+
return `${file}:${line} TS${code}: ${msg}`;
|
|
45
|
+
}
|
|
46
|
+
function affectedFiles(sourceFile) {
|
|
47
|
+
const files = new Set([sourceFile]);
|
|
48
|
+
try {
|
|
49
|
+
for (const ref of sourceFile.getReferencingSourceFiles()) {
|
|
50
|
+
files.add(ref);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// fall back to file-only
|
|
55
|
+
}
|
|
56
|
+
return files;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Snapshot diagnostics for a file's affected set (file + direct referencers).
|
|
60
|
+
* Use this once per file BEFORE transform; pass the returned strings back
|
|
61
|
+
* to runAffectedSetDiagnostics as the `preDiagnostics` baseline.
|
|
62
|
+
*/
|
|
63
|
+
export function snapshotAffectedSet(_project, sourceFile) {
|
|
64
|
+
const files = affectedFiles(sourceFile);
|
|
65
|
+
const fps = [];
|
|
66
|
+
for (const f of files) {
|
|
67
|
+
for (const d of f.getPreEmitDiagnostics()) {
|
|
68
|
+
fps.push(fingerprint(d));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return fps;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Run the affected-set diagnostics check and return any NEW diagnostics
|
|
75
|
+
* (not present in preDiagnostics baseline).
|
|
76
|
+
*/
|
|
77
|
+
export function runAffectedSetDiagnostics(_project, transformedFile, preDiagnostics) {
|
|
78
|
+
const baseline = new Set(preDiagnostics ?? []);
|
|
79
|
+
const newDiags = [];
|
|
80
|
+
const files = affectedFiles(transformedFile);
|
|
81
|
+
for (const f of files) {
|
|
82
|
+
for (const d of f.getPreEmitDiagnostics()) {
|
|
83
|
+
const fp = fingerprint(d);
|
|
84
|
+
if (!baseline.has(fp)) {
|
|
85
|
+
newDiags.push(readable(d));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return newDiags;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Snapshot every diagnostic the Program currently produces. Use before --write
|
|
93
|
+
* to establish the whole-program baseline.
|
|
94
|
+
*/
|
|
95
|
+
export function snapshotWholeProgram(project) {
|
|
96
|
+
const out = [];
|
|
97
|
+
for (const d of project.getPreEmitDiagnostics()) {
|
|
98
|
+
out.push(fingerprint(d));
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Whole-program check — diff post-transform Program diagnostics against a
|
|
104
|
+
* baseline snapshot taken before the transform. Returns any NEW entries.
|
|
105
|
+
*/
|
|
106
|
+
export function runWholeProgramDiagnostics(project, preDiagnostics) {
|
|
107
|
+
const baseline = new Set(preDiagnostics);
|
|
108
|
+
const newDiags = [];
|
|
109
|
+
for (const d of project.getPreEmitDiagnostics()) {
|
|
110
|
+
const fp = fingerprint(d);
|
|
111
|
+
if (!baseline.has(fp)) {
|
|
112
|
+
newDiags.push(readable(d));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return newDiags;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"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"}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional formatting via Biome with canonical fallback.
|
|
3
|
+
*
|
|
4
|
+
* Q3 of the design: canonical generator output is the default, Biome runs
|
|
5
|
+
* only when --format is passed on a touched file. If Biome is missing or
|
|
6
|
+
* fails, we fall back silently to canonical output and note it in audit.
|
|
7
|
+
*/
|
|
8
|
+
export interface FormatResult {
|
|
9
|
+
ran: boolean;
|
|
10
|
+
ok: boolean;
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function formatWithBiome(filePath: string, cwd: string): FormatResult;
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional formatting via Biome with canonical fallback.
|
|
3
|
+
*
|
|
4
|
+
* Q3 of the design: canonical generator output is the default, Biome runs
|
|
5
|
+
* only when --format is passed on a touched file. If Biome is missing or
|
|
6
|
+
* fails, we fall back silently to canonical output and note it in audit.
|
|
7
|
+
*/
|
|
8
|
+
import { spawnSync } from 'child_process';
|
|
9
|
+
import { existsSync } from 'fs';
|
|
10
|
+
import { resolve } from 'path';
|
|
11
|
+
export function formatWithBiome(filePath, cwd) {
|
|
12
|
+
const binCandidates = [
|
|
13
|
+
resolve(cwd, 'node_modules/.bin/biome'),
|
|
14
|
+
resolve(cwd, 'node_modules/@biomejs/biome/bin/biome'),
|
|
15
|
+
];
|
|
16
|
+
const bin = binCandidates.find((p) => existsSync(p));
|
|
17
|
+
if (!bin) {
|
|
18
|
+
return { ran: false, ok: false, error: 'biome binary not found in node_modules/.bin' };
|
|
19
|
+
}
|
|
20
|
+
const proc = spawnSync(bin, ['format', '--write', filePath], { cwd, encoding: 'utf-8' });
|
|
21
|
+
if (proc.error) {
|
|
22
|
+
return { ran: true, ok: false, error: proc.error.message };
|
|
23
|
+
}
|
|
24
|
+
if (proc.status !== 0) {
|
|
25
|
+
const stderr = (proc.stderr || '').trim();
|
|
26
|
+
return { ran: true, ok: false, error: stderr || `biome exited with ${proc.status}` };
|
|
27
|
+
}
|
|
28
|
+
return { ran: true, ok: true };
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAQ/B,MAAM,UAAU,eAAe,CAAC,QAAgB,EAAE,GAAW;IAC3D,MAAM,aAAa,GAAG;QACpB,OAAO,CAAC,GAAG,EAAE,yBAAyB,CAAC;QACvC,OAAO,CAAC,GAAG,EAAE,uCAAuC,CAAC;KACtD,CAAC;IACF,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,6CAA6C,EAAE,CAAC;IACzF,CAAC;IAED,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;IACzF,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;IAC7D,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1C,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,IAAI,qBAAqB,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;IACvF,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACjC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @kernlang/codemod — public API.
|
|
3
|
+
*
|
|
4
|
+
* Turn @kernlang/review TemplateMatch results into transformed TS source.
|
|
5
|
+
* Safety gates: reparse + re-detect + affected-set tsc diagnostics (always),
|
|
6
|
+
* whole-program tsc diagnostics (--write only).
|
|
7
|
+
*/
|
|
8
|
+
import './adapters/index.js';
|
|
9
|
+
export { getAdapter, listAdapters, registerAdapter } from './adapter-registry.js';
|
|
10
|
+
export { applyMatch } from './apply.js';
|
|
11
|
+
export { type ApplyFilesResult, applyFiles } from './apply-files.js';
|
|
12
|
+
export { defaultAuditPath, writeAuditEntry } from './audit.js';
|
|
13
|
+
export { runAffectedSetDiagnostics, runWholeProgramDiagnostics, snapshotAffectedSet, snapshotWholeProgram, } from './diagnostics.js';
|
|
14
|
+
export { type FormatResult, formatWithBiome } from './format.js';
|
|
15
|
+
export { type LoadProjectOptions, loadHostProject } from './project.js';
|
|
16
|
+
export type { ApplyDecision, ApplyOptions, ApplyResult, AuditEntry, ExtractResult, ResolvedRegion, ResolveResult, TemplateAdapter, } from './types.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @kernlang/codemod — public API.
|
|
3
|
+
*
|
|
4
|
+
* Turn @kernlang/review TemplateMatch results into transformed TS source.
|
|
5
|
+
* Safety gates: reparse + re-detect + affected-set tsc diagnostics (always),
|
|
6
|
+
* whole-program tsc diagnostics (--write only).
|
|
7
|
+
*/
|
|
8
|
+
// Register built-in adapters via side-effect import so consumers that only
|
|
9
|
+
// import the package's public surface still get them.
|
|
10
|
+
import './adapters/index.js';
|
|
11
|
+
export { getAdapter, listAdapters, registerAdapter } from './adapter-registry.js';
|
|
12
|
+
export { applyMatch } from './apply.js';
|
|
13
|
+
export { applyFiles } from './apply-files.js';
|
|
14
|
+
export { defaultAuditPath, writeAuditEntry } from './audit.js';
|
|
15
|
+
export { runAffectedSetDiagnostics, runWholeProgramDiagnostics, snapshotAffectedSet, snapshotWholeProgram, } from './diagnostics.js';
|
|
16
|
+
export { formatWithBiome } from './format.js';
|
|
17
|
+
export { loadHostProject } from './project.js';
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2EAA2E;AAC3E,sDAAsD;AACtD,OAAO,qBAAqB,CAAC;AAE7B,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAyB,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EACL,yBAAyB,EACzB,0BAA0B,EAC1B,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAqB,eAAe,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAA2B,eAAe,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared ts-morph Project loader.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the tsconfig lookup from packages/review/src/inferrer.ts so codemod
|
|
5
|
+
* runs against the same type-resolution graph as review. A single Project is
|
|
6
|
+
* reused across all files in a codemod run so affected-set diagnostics amortize
|
|
7
|
+
* well under --interactive.
|
|
8
|
+
*/
|
|
9
|
+
import { Project } from 'ts-morph';
|
|
10
|
+
export interface LoadProjectOptions {
|
|
11
|
+
/** Start directory for tsconfig lookup. Defaults to process.cwd(). */
|
|
12
|
+
cwd?: string;
|
|
13
|
+
/** Force in-memory project (used by unit tests). */
|
|
14
|
+
inMemory?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function loadHostProject(opts?: LoadProjectOptions): Project;
|
package/dist/project.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared ts-morph Project loader.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the tsconfig lookup from packages/review/src/inferrer.ts so codemod
|
|
5
|
+
* runs against the same type-resolution graph as review. A single Project is
|
|
6
|
+
* reused across all files in a codemod run so affected-set diagnostics amortize
|
|
7
|
+
* well under --interactive.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync } from 'fs';
|
|
10
|
+
import { dirname, resolve } from 'path';
|
|
11
|
+
import { Project } from 'ts-morph';
|
|
12
|
+
function findTsConfig(startDir) {
|
|
13
|
+
let dir = startDir;
|
|
14
|
+
for (let i = 0; i < 20; i++) {
|
|
15
|
+
const candidate = resolve(dir, 'tsconfig.json');
|
|
16
|
+
if (existsSync(candidate))
|
|
17
|
+
return candidate;
|
|
18
|
+
const parent = dirname(dir);
|
|
19
|
+
if (parent === dir)
|
|
20
|
+
break;
|
|
21
|
+
dir = parent;
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
export function loadHostProject(opts = {}) {
|
|
26
|
+
if (opts.inMemory) {
|
|
27
|
+
return new Project({
|
|
28
|
+
compilerOptions: { strict: true, target: 99 },
|
|
29
|
+
useInMemoryFileSystem: true,
|
|
30
|
+
skipAddingFilesFromTsConfig: true,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const startDir = opts.cwd ?? process.cwd();
|
|
34
|
+
const tsConfigFilePath = findTsConfig(startDir);
|
|
35
|
+
if (tsConfigFilePath) {
|
|
36
|
+
return new Project({
|
|
37
|
+
tsConfigFilePath,
|
|
38
|
+
skipAddingFilesFromTsConfig: true,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return new Project({
|
|
42
|
+
compilerOptions: { strict: true, target: 99 },
|
|
43
|
+
useInMemoryFileSystem: false,
|
|
44
|
+
skipAddingFilesFromTsConfig: true,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,SAAS,YAAY,CAAC,QAAgB;IACpC,IAAI,GAAG,GAAG,QAAQ,CAAC;IACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;QAChD,IAAI,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;QAC5C,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,MAAM,KAAK,GAAG;YAAE,MAAM;QAC1B,GAAG,GAAG,MAAM,CAAC;IACf,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AASD,MAAM,UAAU,eAAe,CAAC,OAA2B,EAAE;IAC3D,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,OAAO,IAAI,OAAO,CAAC;YACjB,eAAe,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE;YAC7C,qBAAqB,EAAE,IAAI;YAC3B,2BAA2B,EAAE,IAAI;SAClC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAC3C,MAAM,gBAAgB,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IAEhD,IAAI,gBAAgB,EAAE,CAAC;QACrB,OAAO,IAAI,OAAO,CAAC;YACjB,gBAAgB;YAChB,2BAA2B,EAAE,IAAI;SAClC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,IAAI,OAAO,CAAC;QACjB,eAAe,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE;QAC7C,qBAAqB,EAAE,KAAK;QAC5B,2BAA2B,EAAE,IAAI;KAClC,CAAC,CAAC;AACL,CAAC"}
|