miniread 1.0.0
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 +21 -0
- package/README.md +86 -0
- package/bin/miniread +17 -0
- package/bin/miniread-evaluate +17 -0
- package/bin/miniread-sample +17 -0
- package/dist/cli/config.d.ts +17 -0
- package/dist/cli/config.js +85 -0
- package/dist/cli/generate-code.d.ts +2 -0
- package/dist/cli/generate-code.js +7 -0
- package/dist/cli/output.d.ts +7 -0
- package/dist/cli/output.js +119 -0
- package/dist/cli/run-transforms.d.ts +16 -0
- package/dist/cli/run-transforms.js +145 -0
- package/dist/cli/runner.d.ts +3 -0
- package/dist/cli/runner.js +2 -0
- package/dist/cli/transform-stdin.d.ts +14 -0
- package/dist/cli/transform-stdin.js +58 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +121 -0
- package/dist/core/paths.d.ts +8 -0
- package/dist/core/paths.js +9 -0
- package/dist/core/project-graph.d.ts +6 -0
- package/dist/core/project-graph.js +40 -0
- package/dist/core/types.d.ts +30 -0
- package/dist/core/types.js +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/scripts/evaluate/create-evaluate-command.d.ts +5 -0
- package/dist/scripts/evaluate/create-evaluate-command.js +47 -0
- package/dist/scripts/evaluate/diff-utilities.d.ts +17 -0
- package/dist/scripts/evaluate/diff-utilities.js +114 -0
- package/dist/scripts/evaluate/evaluation-report.d.ts +22 -0
- package/dist/scripts/evaluate/evaluation-report.js +1 -0
- package/dist/scripts/evaluate/evaluation-types.d.ts +16 -0
- package/dist/scripts/evaluate/evaluation-types.js +1 -0
- package/dist/scripts/evaluate/metrics.d.ts +3 -0
- package/dist/scripts/evaluate/metrics.js +11 -0
- package/dist/scripts/evaluate/pair-evaluator.d.ts +21 -0
- package/dist/scripts/evaluate/pair-evaluator.js +104 -0
- package/dist/scripts/evaluate/parse-evaluate-cli-options.d.ts +40 -0
- package/dist/scripts/evaluate/parse-evaluate-cli-options.js +97 -0
- package/dist/scripts/evaluate/parse-transform-manifest.d.ts +22 -0
- package/dist/scripts/evaluate/parse-transform-manifest.js +29 -0
- package/dist/scripts/evaluate/resolve-evaluate-dependencies.d.ts +9 -0
- package/dist/scripts/evaluate/resolve-evaluate-dependencies.js +66 -0
- package/dist/scripts/evaluate/run-evaluate-cli.d.ts +1 -0
- package/dist/scripts/evaluate/run-evaluate-cli.js +112 -0
- package/dist/scripts/evaluate/run-evaluations.d.ts +25 -0
- package/dist/scripts/evaluate/run-evaluations.js +50 -0
- package/dist/scripts/evaluate/run-pair-transformations.d.ts +21 -0
- package/dist/scripts/evaluate/run-pair-transformations.js +57 -0
- package/dist/scripts/evaluate/sanitize.d.ts +2 -0
- package/dist/scripts/evaluate/sanitize.js +6 -0
- package/dist/scripts/evaluate/shell-utilities.d.ts +15 -0
- package/dist/scripts/evaluate/shell-utilities.js +68 -0
- package/dist/scripts/evaluate/transform-id-set.d.ts +3 -0
- package/dist/scripts/evaluate/transform-id-set.js +29 -0
- package/dist/scripts/evaluate/transform-manifest.d.ts +13 -0
- package/dist/scripts/evaluate/transform-manifest.js +102 -0
- package/dist/scripts/evaluate/write-evaluation-stdout.d.ts +7 -0
- package/dist/scripts/evaluate/write-evaluation-stdout.js +20 -0
- package/dist/scripts/evaluate/write-text-file-atomic.d.ts +5 -0
- package/dist/scripts/evaluate/write-text-file-atomic.js +47 -0
- package/dist/scripts/evaluate.d.ts +2 -0
- package/dist/scripts/evaluate.js +13 -0
- package/dist/scripts/sample/choose-line-window.d.ts +11 -0
- package/dist/scripts/sample/choose-line-window.js +19 -0
- package/dist/scripts/sample/clip-text-around-core.d.ts +12 -0
- package/dist/scripts/sample/clip-text-around-core.js +61 -0
- package/dist/scripts/sample/collect-function-candidates.d.ts +20 -0
- package/dist/scripts/sample/collect-function-candidates.js +82 -0
- package/dist/scripts/sample/create-function-excerpt.d.ts +20 -0
- package/dist/scripts/sample/create-function-excerpt.js +37 -0
- package/dist/scripts/sample/create-sample-command.d.ts +17 -0
- package/dist/scripts/sample/create-sample-command.js +50 -0
- package/dist/scripts/sample/extract-function-samples.d.ts +31 -0
- package/dist/scripts/sample/extract-function-samples.js +105 -0
- package/dist/scripts/sample/find-source-files.d.ts +1 -0
- package/dist/scripts/sample/find-source-files.js +49 -0
- package/dist/scripts/sample/format-sample-output.d.ts +22 -0
- package/dist/scripts/sample/format-sample-output.js +29 -0
- package/dist/scripts/sample/line-offsets.d.ts +4 -0
- package/dist/scripts/sample/line-offsets.js +40 -0
- package/dist/scripts/sample/parse-sample-cli-options.d.ts +18 -0
- package/dist/scripts/sample/parse-sample-cli-options.js +36 -0
- package/dist/scripts/sample/random.d.ts +10 -0
- package/dist/scripts/sample/random.js +60 -0
- package/dist/scripts/sample/run-sample-cli.d.ts +1 -0
- package/dist/scripts/sample/run-sample-cli.js +79 -0
- package/dist/scripts/sample.d.ts +2 -0
- package/dist/scripts/sample.js +13 -0
- package/dist/transforms/add-prefix/add-prefix-transform.d.ts +2 -0
- package/dist/transforms/add-prefix/add-prefix-transform.js +40 -0
- package/dist/transforms/add-suffix/add-suffix-transform.d.ts +2 -0
- package/dist/transforms/add-suffix/add-suffix-transform.js +40 -0
- package/dist/transforms/expand-boolean-literals/expand-boolean-literals-transform.d.ts +2 -0
- package/dist/transforms/expand-boolean-literals/expand-boolean-literals-transform.js +39 -0
- package/dist/transforms/expand-undefined-literals/expand-undefined-literals-transform.d.ts +2 -0
- package/dist/transforms/expand-undefined-literals/expand-undefined-literals-transform.js +40 -0
- package/dist/transforms/rename-catch-parameters/rename-catch-parameters-transform.d.ts +2 -0
- package/dist/transforms/rename-catch-parameters/rename-catch-parameters-transform.js +67 -0
- package/dist/transforms/rename-loop-index-variables/rename-loop-index-variables-transform.d.ts +2 -0
- package/dist/transforms/rename-loop-index-variables/rename-loop-index-variables-transform.js +125 -0
- package/dist/transforms/transform-presets.d.ts +3 -0
- package/dist/transforms/transform-presets.js +19 -0
- package/dist/transforms/transform-registry.d.ts +3 -0
- package/dist/transforms/transform-registry.js +15 -0
- package/package.json +84 -0
- package/transform-manifest.json +70 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { collectFileFunctionCandidates, } from "./collect-function-candidates.js";
|
|
4
|
+
import { createFunctionExcerpt } from "./create-function-excerpt.js";
|
|
5
|
+
import { createLineStartOffsets } from "./line-offsets.js";
|
|
6
|
+
import { sampleIndicesWithoutReplacement } from "./random.js";
|
|
7
|
+
const selectFilePaths = (options) => {
|
|
8
|
+
const { filePaths, maxFiles, rng } = options;
|
|
9
|
+
if (maxFiles <= 0 || maxFiles >= filePaths.length)
|
|
10
|
+
return filePaths;
|
|
11
|
+
return sampleIndicesWithoutReplacement({
|
|
12
|
+
size: filePaths.length,
|
|
13
|
+
count: maxFiles,
|
|
14
|
+
rng,
|
|
15
|
+
})
|
|
16
|
+
.map((index) => filePaths[index])
|
|
17
|
+
.filter((filePath) => filePath !== undefined);
|
|
18
|
+
};
|
|
19
|
+
const groupCandidatesByFile = (reservoir) => {
|
|
20
|
+
const candidatesByFile = new Map();
|
|
21
|
+
for (const entry of reservoir) {
|
|
22
|
+
const existing = candidatesByFile.get(entry.filePath);
|
|
23
|
+
if (existing) {
|
|
24
|
+
existing.push(entry.candidate);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
candidatesByFile.set(entry.filePath, [entry.candidate]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return candidatesByFile;
|
|
31
|
+
};
|
|
32
|
+
export const extractFunctionSamples = async (options) => {
|
|
33
|
+
const { filePaths, rootDirectory, maxFiles, snippets, minLines, maxLines, contextLines, contextChars, maxChars, rng, } = options;
|
|
34
|
+
const selectedFiles = selectFilePaths({ filePaths, maxFiles, rng });
|
|
35
|
+
const reservoir = [];
|
|
36
|
+
let eligibleSeen = 0;
|
|
37
|
+
const consider = (entry) => {
|
|
38
|
+
eligibleSeen++;
|
|
39
|
+
if (reservoir.length < snippets) {
|
|
40
|
+
reservoir.push(entry);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const index = rng.nextInt(eligibleSeen);
|
|
44
|
+
if (index < snippets) {
|
|
45
|
+
reservoir[index] = entry;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
for (const filePath of selectedFiles) {
|
|
49
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
50
|
+
const candidatesResult = collectFileFunctionCandidates({
|
|
51
|
+
filePath,
|
|
52
|
+
content,
|
|
53
|
+
});
|
|
54
|
+
if (!candidatesResult.ok) {
|
|
55
|
+
console.error(`Warning: ${candidatesResult.error}`);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
for (const candidate of candidatesResult.candidates) {
|
|
59
|
+
if (candidate.endLine - candidate.startLine + 1 < minLines)
|
|
60
|
+
continue;
|
|
61
|
+
consider({ filePath, candidate });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const candidatesByFile = groupCandidatesByFile(reservoir);
|
|
65
|
+
const samples = [];
|
|
66
|
+
for (const [filePath, candidates] of candidatesByFile) {
|
|
67
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
68
|
+
const lineStartOffsets = createLineStartOffsets(content);
|
|
69
|
+
const totalLines = lineStartOffsets.length;
|
|
70
|
+
for (const candidate of candidates) {
|
|
71
|
+
const { excerptStartLine, excerptEndLine, excerptStartOffset, excerptEndOffset, truncated, code, } = createFunctionExcerpt({
|
|
72
|
+
content,
|
|
73
|
+
lineStartOffsets,
|
|
74
|
+
totalLines,
|
|
75
|
+
startLine: candidate.startLine,
|
|
76
|
+
endLine: candidate.endLine,
|
|
77
|
+
startOffset: candidate.startOffset,
|
|
78
|
+
endOffset: candidate.endOffset,
|
|
79
|
+
contextLines,
|
|
80
|
+
contextChars,
|
|
81
|
+
maxLines,
|
|
82
|
+
maxChars,
|
|
83
|
+
});
|
|
84
|
+
samples.push({
|
|
85
|
+
filePath,
|
|
86
|
+
filePathRelative: path.relative(rootDirectory, filePath),
|
|
87
|
+
nodeType: candidate.nodeType,
|
|
88
|
+
name: candidate.name,
|
|
89
|
+
startLine: candidate.startLine,
|
|
90
|
+
endLine: candidate.endLine,
|
|
91
|
+
startColumn: candidate.startColumn,
|
|
92
|
+
endColumn: candidate.endColumn,
|
|
93
|
+
startOffset: candidate.startOffset,
|
|
94
|
+
endOffset: candidate.endOffset,
|
|
95
|
+
excerptStartLine,
|
|
96
|
+
excerptEndLine,
|
|
97
|
+
excerptStartOffset,
|
|
98
|
+
excerptEndOffset,
|
|
99
|
+
truncated,
|
|
100
|
+
code,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return samples;
|
|
105
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const findSourceFiles: (inputPath: string) => Promise<string[]>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const isSourceFile = (fileName) => {
|
|
4
|
+
return (!fileName.endsWith(".d.ts") &&
|
|
5
|
+
(fileName.endsWith(".ts") ||
|
|
6
|
+
fileName.endsWith(".tsx") ||
|
|
7
|
+
fileName.endsWith(".js") ||
|
|
8
|
+
fileName.endsWith(".jsx")));
|
|
9
|
+
};
|
|
10
|
+
const findSourceFilesInDirectory = async (directory) => {
|
|
11
|
+
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
12
|
+
const sourceFiles = [];
|
|
13
|
+
for (const entry of entries) {
|
|
14
|
+
const fullPath = path.join(directory, entry.name);
|
|
15
|
+
if (entry.isDirectory()) {
|
|
16
|
+
sourceFiles.push(...(await findSourceFilesInDirectory(fullPath)));
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (!entry.isFile())
|
|
20
|
+
continue;
|
|
21
|
+
if (!isSourceFile(entry.name))
|
|
22
|
+
continue;
|
|
23
|
+
sourceFiles.push(fullPath);
|
|
24
|
+
}
|
|
25
|
+
return sourceFiles;
|
|
26
|
+
};
|
|
27
|
+
export const findSourceFiles = async (inputPath) => {
|
|
28
|
+
let stat;
|
|
29
|
+
try {
|
|
30
|
+
stat = await fs.stat(inputPath);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
throw new Error(`Input path does not exist: ${inputPath}`);
|
|
34
|
+
}
|
|
35
|
+
if (stat.isFile()) {
|
|
36
|
+
if (!isSourceFile(inputPath)) {
|
|
37
|
+
throw new Error(`Input file is not a supported source file: ${inputPath}`);
|
|
38
|
+
}
|
|
39
|
+
return [inputPath];
|
|
40
|
+
}
|
|
41
|
+
if (!stat.isDirectory()) {
|
|
42
|
+
throw new Error(`Input path must be a file or directory: ${inputPath}`);
|
|
43
|
+
}
|
|
44
|
+
const files = await findSourceFilesInDirectory(inputPath);
|
|
45
|
+
if (files.length === 0) {
|
|
46
|
+
throw new Error(`No source files found under: ${inputPath}`);
|
|
47
|
+
}
|
|
48
|
+
return files;
|
|
49
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { FunctionSample } from "./extract-function-samples.js";
|
|
2
|
+
import type { SampleOutputFormat } from "./create-sample-command.js";
|
|
3
|
+
type SampleOutput = {
|
|
4
|
+
generatedAt: string;
|
|
5
|
+
seed: string | undefined;
|
|
6
|
+
inputPath: string;
|
|
7
|
+
settings: {
|
|
8
|
+
snippets: number;
|
|
9
|
+
maxFiles: number;
|
|
10
|
+
minLines: number;
|
|
11
|
+
maxLines: number;
|
|
12
|
+
maxChars: number;
|
|
13
|
+
contextLines: number;
|
|
14
|
+
contextChars: number;
|
|
15
|
+
};
|
|
16
|
+
samples: FunctionSample[];
|
|
17
|
+
};
|
|
18
|
+
export declare const formatSampleOutput: (options: {
|
|
19
|
+
format: SampleOutputFormat;
|
|
20
|
+
output: SampleOutput;
|
|
21
|
+
}) => string;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const formatMarkdown = (output) => {
|
|
2
|
+
const lines = [
|
|
3
|
+
"# Miniread Sample Pack",
|
|
4
|
+
"",
|
|
5
|
+
`Generated at: ${output.generatedAt}`,
|
|
6
|
+
`Input: ${output.inputPath}`,
|
|
7
|
+
`Seed: ${output.seed ?? "(crypto)"}`,
|
|
8
|
+
`Settings: snippets=${output.settings.snippets} maxFiles=${output.settings.maxFiles === 0 ? "all" : output.settings.maxFiles} minLines=${output.settings.minLines} maxLines=${output.settings.maxLines} maxChars=${output.settings.maxChars} contextLines=${output.settings.contextLines} contextChars=${output.settings.contextChars}`,
|
|
9
|
+
"",
|
|
10
|
+
"## Rules for the agent",
|
|
11
|
+
"",
|
|
12
|
+
"- Use ONLY the code samples in this document.",
|
|
13
|
+
"- Do NOT pick additional files/functions beyond these samples.",
|
|
14
|
+
"- Base all analysis and proposed transforms on patterns observed here.",
|
|
15
|
+
"",
|
|
16
|
+
];
|
|
17
|
+
for (const [index, sample] of output.samples.entries()) {
|
|
18
|
+
const nameSuffix = sample.name ? ` (${sample.name})` : "";
|
|
19
|
+
const truncationNote = sample.truncated ? " (clipped)" : "";
|
|
20
|
+
lines.push(`## Sample ${index + 1}`, "", `- File: \`${sample.filePathRelative}\``, `- Node: \`${sample.nodeType}\`${nameSuffix} • lines ${sample.startLine}:${sample.startColumn}-${sample.endLine}:${sample.endColumn} • chars ${sample.startOffset}-${sample.endOffset}${truncationNote}`, `- Excerpt: lines ${sample.excerptStartLine}-${sample.excerptEndLine} • chars ${sample.excerptStartOffset}-${sample.excerptEndOffset}`, "", "```js", sample.code.trimEnd(), "```", "");
|
|
21
|
+
}
|
|
22
|
+
return lines.join("\n");
|
|
23
|
+
};
|
|
24
|
+
export const formatSampleOutput = (options) => {
|
|
25
|
+
const { format, output } = options;
|
|
26
|
+
if (format === "markdown")
|
|
27
|
+
return formatMarkdown(output);
|
|
28
|
+
return `${JSON.stringify(output, undefined, 2)}\n`;
|
|
29
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const createLineStartOffsets: (content: string) => number[];
|
|
2
|
+
export declare const getLineStartOffset: (lineStartOffsets: number[], line: number) => number;
|
|
3
|
+
export declare const getLineEndOffset: (lineStartOffsets: number[], line: number, contentLength: number) => number;
|
|
4
|
+
export declare const offsetToLineNumber: (lineStartOffsets: number[], offset: number) => number;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const createLineStartOffsets = (content) => {
|
|
2
|
+
const offsets = [0];
|
|
3
|
+
for (let index = 0; index < content.length; index++) {
|
|
4
|
+
if (content.codePointAt(index) === 10) {
|
|
5
|
+
offsets.push(index + 1);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
return offsets;
|
|
9
|
+
};
|
|
10
|
+
export const getLineStartOffset = (lineStartOffsets, line) => {
|
|
11
|
+
const offset = lineStartOffsets[line - 1];
|
|
12
|
+
return offset ?? 0;
|
|
13
|
+
};
|
|
14
|
+
export const getLineEndOffset = (lineStartOffsets, line, contentLength) => {
|
|
15
|
+
const offset = lineStartOffsets[line];
|
|
16
|
+
return offset ?? contentLength;
|
|
17
|
+
};
|
|
18
|
+
export const offsetToLineNumber = (lineStartOffsets, offset) => {
|
|
19
|
+
if (lineStartOffsets.length === 0)
|
|
20
|
+
return 1;
|
|
21
|
+
if (offset <= 0)
|
|
22
|
+
return 1;
|
|
23
|
+
let low = 0;
|
|
24
|
+
let high = lineStartOffsets.length - 1;
|
|
25
|
+
while (low <= high) {
|
|
26
|
+
const mid = Math.floor((low + high) / 2);
|
|
27
|
+
const midOffset = lineStartOffsets[mid];
|
|
28
|
+
if (midOffset === undefined)
|
|
29
|
+
break;
|
|
30
|
+
if (midOffset === offset)
|
|
31
|
+
return mid + 1;
|
|
32
|
+
if (midOffset < offset) {
|
|
33
|
+
low = mid + 1;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
high = mid - 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return Math.max(1, high + 1);
|
|
40
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SampleCliRawOptions } from "./create-sample-command.js";
|
|
2
|
+
import type { SampleOutputFormat } from "./create-sample-command.js";
|
|
3
|
+
export type SampleCliOptions = {
|
|
4
|
+
inputPath: string;
|
|
5
|
+
snippets: number;
|
|
6
|
+
maxFiles: number;
|
|
7
|
+
minLines: number;
|
|
8
|
+
maxLines: number;
|
|
9
|
+
maxChars: number;
|
|
10
|
+
contextLines: number;
|
|
11
|
+
contextChars: number;
|
|
12
|
+
seed: string | undefined;
|
|
13
|
+
format: SampleOutputFormat;
|
|
14
|
+
};
|
|
15
|
+
export declare const parseSampleCliOptions: (options: {
|
|
16
|
+
cwd: string;
|
|
17
|
+
rawOptions: SampleCliRawOptions;
|
|
18
|
+
}) => SampleCliOptions;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
export const parseSampleCliOptions = (options) => {
|
|
3
|
+
const { cwd, rawOptions } = options;
|
|
4
|
+
const snippets = rawOptions.snippets;
|
|
5
|
+
const maxFiles = rawOptions.maxFiles;
|
|
6
|
+
if (rawOptions.maxLines < 1) {
|
|
7
|
+
throw new Error("--max-lines must be >= 1");
|
|
8
|
+
}
|
|
9
|
+
if (rawOptions.minLines < 1) {
|
|
10
|
+
throw new Error("--min-lines must be >= 1");
|
|
11
|
+
}
|
|
12
|
+
if (rawOptions.minLines > rawOptions.maxLines) {
|
|
13
|
+
throw new Error("--min-lines must be <= --max-lines");
|
|
14
|
+
}
|
|
15
|
+
if (rawOptions.maxChars < 200) {
|
|
16
|
+
throw new Error("--max-chars must be >= 200");
|
|
17
|
+
}
|
|
18
|
+
if (snippets < 1) {
|
|
19
|
+
throw new Error("--snippets must be >= 1");
|
|
20
|
+
}
|
|
21
|
+
if (maxFiles < 0) {
|
|
22
|
+
throw new Error("--max-files must be >= 0");
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
inputPath: path.resolve(cwd, rawOptions.input),
|
|
26
|
+
snippets,
|
|
27
|
+
maxFiles,
|
|
28
|
+
minLines: rawOptions.minLines,
|
|
29
|
+
maxLines: rawOptions.maxLines,
|
|
30
|
+
maxChars: rawOptions.maxChars,
|
|
31
|
+
contextLines: rawOptions.contextLines,
|
|
32
|
+
contextChars: rawOptions.contextChars,
|
|
33
|
+
seed: rawOptions.seed,
|
|
34
|
+
format: rawOptions.format,
|
|
35
|
+
};
|
|
36
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type RandomNumberGenerator = {
|
|
2
|
+
nextInt: (maxExclusive: number) => number;
|
|
3
|
+
};
|
|
4
|
+
export declare const createCryptoRng: () => RandomNumberGenerator;
|
|
5
|
+
export declare const createSeededRng: (seed: string) => RandomNumberGenerator;
|
|
6
|
+
export declare const sampleIndicesWithoutReplacement: (options: {
|
|
7
|
+
size: number;
|
|
8
|
+
count: number;
|
|
9
|
+
rng: RandomNumberGenerator;
|
|
10
|
+
}) => number[];
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createHash, randomInt } from "node:crypto";
|
|
2
|
+
export const createCryptoRng = () => {
|
|
3
|
+
return {
|
|
4
|
+
nextInt(maxExclusive) {
|
|
5
|
+
if (!Number.isInteger(maxExclusive) || maxExclusive <= 0) {
|
|
6
|
+
throw new Error(`maxExclusive must be a positive integer (got: ${maxExclusive})`);
|
|
7
|
+
}
|
|
8
|
+
return randomInt(maxExclusive);
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
const createMulberry32 = (seed) => {
|
|
13
|
+
let state = seed >>> 0;
|
|
14
|
+
return () => {
|
|
15
|
+
state = (state + 0x6d_2b_79_f5) >>> 0;
|
|
16
|
+
let t = Math.imul(state ^ (state >>> 15), state | 1);
|
|
17
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
18
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4_294_967_296;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export const createSeededRng = (seed) => {
|
|
22
|
+
const digest = createHash("sha256").update(seed).digest();
|
|
23
|
+
const seedInt = digest.readUInt32LE(0);
|
|
24
|
+
const nextFloat = createMulberry32(seedInt === 0 ? 1 : seedInt);
|
|
25
|
+
return {
|
|
26
|
+
nextInt(maxExclusive) {
|
|
27
|
+
if (!Number.isInteger(maxExclusive) || maxExclusive <= 0) {
|
|
28
|
+
throw new Error(`maxExclusive must be a positive integer (got: ${maxExclusive})`);
|
|
29
|
+
}
|
|
30
|
+
return Math.floor(nextFloat() * maxExclusive);
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
export const sampleIndicesWithoutReplacement = (options) => {
|
|
35
|
+
const { size, count, rng } = options;
|
|
36
|
+
if (!Number.isInteger(size) || size < 0) {
|
|
37
|
+
throw new Error(`size must be a non-negative integer (got: ${size})`);
|
|
38
|
+
}
|
|
39
|
+
if (!Number.isInteger(count) || count < 0) {
|
|
40
|
+
throw new Error(`count must be a non-negative integer (got: ${count})`);
|
|
41
|
+
}
|
|
42
|
+
if (count > size) {
|
|
43
|
+
throw new Error(`count (${count}) cannot be greater than size (${size})`);
|
|
44
|
+
}
|
|
45
|
+
if (count === 0)
|
|
46
|
+
return [];
|
|
47
|
+
if (size === 0)
|
|
48
|
+
return [];
|
|
49
|
+
const swaps = new Map();
|
|
50
|
+
const selected = [];
|
|
51
|
+
for (let index = 0; index < count; index++) {
|
|
52
|
+
const randomIndex = index + rng.nextInt(size - index);
|
|
53
|
+
const valueAtRandomIndex = swaps.get(randomIndex) ?? randomIndex;
|
|
54
|
+
const valueAtIndex = swaps.get(index) ?? index;
|
|
55
|
+
swaps.set(randomIndex, valueAtIndex);
|
|
56
|
+
swaps.set(index, valueAtRandomIndex);
|
|
57
|
+
selected.push(valueAtRandomIndex);
|
|
58
|
+
}
|
|
59
|
+
return selected;
|
|
60
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const runSampleCli: (argv: string[]) => Promise<number>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import packageJson from "../../../package.json" with { type: "json" };
|
|
2
|
+
import { createSampleCommand } from "./create-sample-command.js";
|
|
3
|
+
import { parseSampleCliOptions, } from "./parse-sample-cli-options.js";
|
|
4
|
+
import { createCryptoRng, createSeededRng } from "./random.js";
|
|
5
|
+
import { findSourceFiles } from "./find-source-files.js";
|
|
6
|
+
import { extractFunctionSamples } from "./extract-function-samples.js";
|
|
7
|
+
import { formatSampleOutput } from "./format-sample-output.js";
|
|
8
|
+
export const runSampleCli = async (argv) => {
|
|
9
|
+
const program = createSampleCommand({ version: packageJson.version });
|
|
10
|
+
program.parse(argv.filter((argument) => argument !== "--"));
|
|
11
|
+
const rawOptions = program.opts();
|
|
12
|
+
let parsedOptions;
|
|
13
|
+
try {
|
|
14
|
+
parsedOptions = parseSampleCliOptions({
|
|
15
|
+
cwd: process.cwd(),
|
|
16
|
+
rawOptions,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
program.error(error instanceof Error ? error.message : String(error));
|
|
21
|
+
return 1;
|
|
22
|
+
}
|
|
23
|
+
const rng = parsedOptions.seed
|
|
24
|
+
? createSeededRng(parsedOptions.seed)
|
|
25
|
+
: createCryptoRng();
|
|
26
|
+
let filePaths;
|
|
27
|
+
try {
|
|
28
|
+
filePaths = await findSourceFiles(parsedOptions.inputPath);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
32
|
+
console.error(message);
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
let samples;
|
|
36
|
+
try {
|
|
37
|
+
samples = await extractFunctionSamples({
|
|
38
|
+
filePaths,
|
|
39
|
+
rootDirectory: process.cwd(),
|
|
40
|
+
maxFiles: parsedOptions.maxFiles,
|
|
41
|
+
snippets: parsedOptions.snippets,
|
|
42
|
+
minLines: parsedOptions.minLines,
|
|
43
|
+
maxLines: parsedOptions.maxLines,
|
|
44
|
+
maxChars: parsedOptions.maxChars,
|
|
45
|
+
contextLines: parsedOptions.contextLines,
|
|
46
|
+
contextChars: parsedOptions.contextChars,
|
|
47
|
+
rng,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
52
|
+
console.error(message);
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
if (samples.length === 0) {
|
|
56
|
+
console.error("No samples produced. Try lowering --min-lines, increasing --max-lines/--max-chars, increasing --max-files, or increasing --snippets.");
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
const outputText = formatSampleOutput({
|
|
60
|
+
format: parsedOptions.format,
|
|
61
|
+
output: {
|
|
62
|
+
generatedAt: new Date().toISOString(),
|
|
63
|
+
seed: parsedOptions.seed,
|
|
64
|
+
inputPath: parsedOptions.inputPath,
|
|
65
|
+
settings: {
|
|
66
|
+
snippets: parsedOptions.snippets,
|
|
67
|
+
maxFiles: parsedOptions.maxFiles,
|
|
68
|
+
minLines: parsedOptions.minLines,
|
|
69
|
+
maxLines: parsedOptions.maxLines,
|
|
70
|
+
maxChars: parsedOptions.maxChars,
|
|
71
|
+
contextLines: parsedOptions.contextLines,
|
|
72
|
+
contextChars: parsedOptions.contextChars,
|
|
73
|
+
},
|
|
74
|
+
samples,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
process.stdout.write(outputText);
|
|
78
|
+
return 0;
|
|
79
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runSampleCli } from "./sample/run-sample-cli.js";
|
|
3
|
+
try {
|
|
4
|
+
const exitCode = await runSampleCli(process.argv);
|
|
5
|
+
if (exitCode !== 0) {
|
|
6
|
+
process.exitCode = exitCode;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11
|
+
console.error(`Error: ${message}`);
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
const require = createRequire(import.meta.url);
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
4
|
+
const traverse = require("@babel/traverse").default;
|
|
5
|
+
const PREFIX = "renamed_";
|
|
6
|
+
export const addPrefixTransform = {
|
|
7
|
+
id: "add-prefix",
|
|
8
|
+
description: `Adds "${PREFIX}" prefix to variable and function declarations`,
|
|
9
|
+
scope: "file",
|
|
10
|
+
parallelizable: true,
|
|
11
|
+
transform(context) {
|
|
12
|
+
const { projectGraph } = context;
|
|
13
|
+
let nodesVisited = 0;
|
|
14
|
+
let transformationsApplied = 0;
|
|
15
|
+
for (const [, fileInfo] of projectGraph.files) {
|
|
16
|
+
traverse(fileInfo.ast, {
|
|
17
|
+
VariableDeclarator(path) {
|
|
18
|
+
nodesVisited++;
|
|
19
|
+
const id = path.node.id;
|
|
20
|
+
if (id.type === "Identifier" && !id.name.startsWith(PREFIX)) {
|
|
21
|
+
path.scope.rename(id.name, `${PREFIX}${id.name}`);
|
|
22
|
+
transformationsApplied++;
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
FunctionDeclaration(path) {
|
|
26
|
+
nodesVisited++;
|
|
27
|
+
const id = path.node.id;
|
|
28
|
+
if (id && !id.name.startsWith(PREFIX)) {
|
|
29
|
+
path.scope.rename(id.name, `${PREFIX}${id.name}`);
|
|
30
|
+
transformationsApplied++;
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return Promise.resolve({
|
|
36
|
+
nodesVisited,
|
|
37
|
+
transformationsApplied,
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
const require = createRequire(import.meta.url);
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
4
|
+
const traverse = require("@babel/traverse").default;
|
|
5
|
+
const SUFFIX = "_end";
|
|
6
|
+
export const addSuffixTransform = {
|
|
7
|
+
id: "add-suffix",
|
|
8
|
+
description: `Adds "${SUFFIX}" suffix to variable and function declarations`,
|
|
9
|
+
scope: "file",
|
|
10
|
+
parallelizable: true,
|
|
11
|
+
transform(context) {
|
|
12
|
+
const { projectGraph } = context;
|
|
13
|
+
let nodesVisited = 0;
|
|
14
|
+
let transformationsApplied = 0;
|
|
15
|
+
for (const [, fileInfo] of projectGraph.files) {
|
|
16
|
+
traverse(fileInfo.ast, {
|
|
17
|
+
VariableDeclarator(path) {
|
|
18
|
+
nodesVisited++;
|
|
19
|
+
const id = path.node.id;
|
|
20
|
+
if (id.type === "Identifier" && !id.name.endsWith(SUFFIX)) {
|
|
21
|
+
path.scope.rename(id.name, `${id.name}${SUFFIX}`);
|
|
22
|
+
transformationsApplied++;
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
FunctionDeclaration(path) {
|
|
26
|
+
nodesVisited++;
|
|
27
|
+
const id = path.node.id;
|
|
28
|
+
if (id && !id.name.endsWith(SUFFIX)) {
|
|
29
|
+
path.scope.rename(id.name, `${id.name}${SUFFIX}`);
|
|
30
|
+
transformationsApplied++;
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return Promise.resolve({
|
|
36
|
+
nodesVisited,
|
|
37
|
+
transformationsApplied,
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
const require = createRequire(import.meta.url);
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
4
|
+
const traverse = require("@babel/traverse").default;
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
6
|
+
const t = require("@babel/types");
|
|
7
|
+
export const expandBooleanLiteralsTransform = {
|
|
8
|
+
id: "expand-boolean-literals",
|
|
9
|
+
description: "Expands !0 to true and !1 to false",
|
|
10
|
+
scope: "file",
|
|
11
|
+
parallelizable: true,
|
|
12
|
+
transform(context) {
|
|
13
|
+
const { projectGraph } = context;
|
|
14
|
+
let nodesVisited = 0;
|
|
15
|
+
let transformationsApplied = 0;
|
|
16
|
+
for (const [, fileInfo] of projectGraph.files) {
|
|
17
|
+
traverse(fileInfo.ast, {
|
|
18
|
+
UnaryExpression(path) {
|
|
19
|
+
nodesVisited++;
|
|
20
|
+
const { operator, argument } = path.node;
|
|
21
|
+
// Check for !0 or !1 pattern
|
|
22
|
+
if (operator === "!" &&
|
|
23
|
+
argument.type === "NumericLiteral" &&
|
|
24
|
+
(argument.value === 0 || argument.value === 1)) {
|
|
25
|
+
// !0 = true, !1 = false
|
|
26
|
+
const booleanValue = argument.value === 0;
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
28
|
+
path.replaceWith(t.booleanLiteral(booleanValue));
|
|
29
|
+
transformationsApplied++;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return Promise.resolve({
|
|
35
|
+
nodesVisited,
|
|
36
|
+
transformationsApplied,
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
const require = createRequire(import.meta.url);
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
4
|
+
const traverse = require("@babel/traverse").default;
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
6
|
+
const t = require("@babel/types");
|
|
7
|
+
export const expandUndefinedLiteralsTransform = {
|
|
8
|
+
id: "expand-undefined-literals",
|
|
9
|
+
description: "Expands void 0 to undefined (when undefined is not shadowed)",
|
|
10
|
+
scope: "file",
|
|
11
|
+
parallelizable: true,
|
|
12
|
+
transform(context) {
|
|
13
|
+
const { projectGraph } = context;
|
|
14
|
+
let nodesVisited = 0;
|
|
15
|
+
let transformationsApplied = 0;
|
|
16
|
+
for (const [, fileInfo] of projectGraph.files) {
|
|
17
|
+
traverse(fileInfo.ast, {
|
|
18
|
+
UnaryExpression(path) {
|
|
19
|
+
nodesVisited++;
|
|
20
|
+
const { operator, argument } = path.node;
|
|
21
|
+
if (operator !== "void")
|
|
22
|
+
return;
|
|
23
|
+
if (argument.type !== "NumericLiteral")
|
|
24
|
+
return;
|
|
25
|
+
if (argument.value !== 0)
|
|
26
|
+
return;
|
|
27
|
+
if (path.scope.hasBinding("undefined", true))
|
|
28
|
+
return;
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
30
|
+
path.replaceWith(t.identifier("undefined"));
|
|
31
|
+
transformationsApplied++;
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return Promise.resolve({
|
|
36
|
+
nodesVisited,
|
|
37
|
+
transformationsApplied,
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
};
|