@shd101wyy/yo 0.1.27 → 0.1.28
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/.github/skills/yo-core-patterns/core-patterns-cheatsheet.md +3 -0
- package/.github/skills/yo-project-workflow/workflow-cheatsheet.md +2 -0
- package/.github/skills/yo-syntax/syntax-cheatsheet.md +152 -4
- package/out/cjs/index.cjs +446 -446
- package/out/cjs/yo-cli.cjs +619 -618
- package/out/cjs/yo-lsp.cjs +559 -559
- package/out/esm/index.mjs +373 -373
- package/out/types/src/env.d.ts +1 -0
- package/out/types/src/evaluator/calls/helper.d.ts +4 -2
- package/out/types/src/evaluator/types/synthesizer.d.ts +1 -0
- package/out/types/src/test-runner.d.ts +2 -0
- package/out/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/scripts/probe-parser-parity.ts +61 -0
- package/scripts/probe-yo-self-parser.sh +33 -0
- package/scripts/validate-yo-self-fmt.ts +184 -0
package/package.json
CHANGED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Probe: for each .yo file in tests/ and yo-self/, parse via TS and check
|
|
2
|
+
// if yo-self's parser via `yo-self compile --emit-c --skip-c-compiler` also
|
|
3
|
+
// succeeds (or fails for the same reason).
|
|
4
|
+
//
|
|
5
|
+
// We don't actually compile — we just lex+parse+evaluate enough to detect
|
|
6
|
+
// parser errors. But yo-self's `compile` subcommand runs the full pipeline
|
|
7
|
+
// which includes evaluation. So we use a lighter probe: try compiling
|
|
8
|
+
// from a tiny stub `main.yo` that imports each file.
|
|
9
|
+
//
|
|
10
|
+
// Simpler: just use the TS parser. If any file fails to TS-parse, log it.
|
|
11
|
+
// Then optionally compare with yo-self by running yo-self-bin and seeing
|
|
12
|
+
// if it also fails.
|
|
13
|
+
import Parser from "../src/parser";
|
|
14
|
+
import * as fs from "fs";
|
|
15
|
+
import * as path from "path";
|
|
16
|
+
|
|
17
|
+
function walk(dir: string, files: string[] = []): string[] {
|
|
18
|
+
if (!fs.existsSync(dir)) return files;
|
|
19
|
+
const stat = fs.statSync(dir);
|
|
20
|
+
if (stat.isFile() && dir.endsWith(".yo")) {
|
|
21
|
+
files.push(dir);
|
|
22
|
+
return files;
|
|
23
|
+
}
|
|
24
|
+
if (stat.isDirectory()) {
|
|
25
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
26
|
+
if ([".git", "node_modules", "out", "yo-out"].includes(entry)) continue;
|
|
27
|
+
walk(path.join(dir, entry), files);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return files;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const files = [
|
|
34
|
+
...walk("tests"),
|
|
35
|
+
...walk("yo-self"),
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
console.log(`Found ${files.length} .yo files\n`);
|
|
39
|
+
let tsOk = 0, tsFail = 0;
|
|
40
|
+
const tsFailures: { file: string; err: string }[] = [];
|
|
41
|
+
|
|
42
|
+
for (const f of files) {
|
|
43
|
+
const src = fs.readFileSync(f, "utf-8");
|
|
44
|
+
try {
|
|
45
|
+
const p = new Parser({ inputString: src, modulePath: f });
|
|
46
|
+
p.getProgram();
|
|
47
|
+
tsOk++;
|
|
48
|
+
} catch (e) {
|
|
49
|
+
tsFail++;
|
|
50
|
+
tsFailures.push({ file: f, err: String(e).split("\n")[0].slice(0, 120) });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log(`TS parser: ${tsOk} OK, ${tsFail} fail`);
|
|
55
|
+
if (tsFailures.length > 0 && tsFailures.length < 30) {
|
|
56
|
+
console.log("\nTS failures:");
|
|
57
|
+
for (const f of tsFailures.slice(0, 20)) {
|
|
58
|
+
console.log(` ${f.file}`);
|
|
59
|
+
console.log(` ${f.err}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Run yo-self-bin fmt on every .yo file, count fail/success.
|
|
3
|
+
BIN=/tmp/yo-self-fmt-bin
|
|
4
|
+
OK=0; FAIL=0; FAILS=()
|
|
5
|
+
|
|
6
|
+
while IFS= read -r f; do
|
|
7
|
+
# Copy to a temp so original isn't modified.
|
|
8
|
+
tmp="/tmp/yo_parse_probe_$$.yo"
|
|
9
|
+
cp "$f" "$tmp"
|
|
10
|
+
if "$BIN" fmt --check "$tmp" >/dev/null 2>&1 ; then
|
|
11
|
+
OK=$((OK+1))
|
|
12
|
+
else
|
|
13
|
+
# --check may exit 1 for "would format" — that's OK. Check stderr.
|
|
14
|
+
out=$("$BIN" fmt --check "$tmp" 2>&1)
|
|
15
|
+
rc=$?
|
|
16
|
+
if [ "$rc" = "0" ] || [ "$rc" = "1" ]; then
|
|
17
|
+
OK=$((OK+1))
|
|
18
|
+
else
|
|
19
|
+
FAIL=$((FAIL+1))
|
|
20
|
+
FAILS+=("$f: $out")
|
|
21
|
+
fi
|
|
22
|
+
fi
|
|
23
|
+
rm -f "$tmp"
|
|
24
|
+
done < <(find tests yo-self -name "*.yo" 2>/dev/null)
|
|
25
|
+
|
|
26
|
+
echo "OK: $OK FAIL: $FAIL"
|
|
27
|
+
if [ "$FAIL" -gt 0 ]; then
|
|
28
|
+
echo
|
|
29
|
+
echo "Failures (first 10):"
|
|
30
|
+
for f in "${FAILS[@]:0:10}"; do
|
|
31
|
+
echo " - $f"
|
|
32
|
+
done
|
|
33
|
+
fi
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// One-off validation: extract each test() block from src/tests/formatter.test.ts,
|
|
2
|
+
// run yo-self-bin fmt on the source, compare to the inline expected output.
|
|
3
|
+
//
|
|
4
|
+
// Usage: bun /tmp/validate-yo-self-fmt.ts
|
|
5
|
+
import { spawnSync } from "child_process";
|
|
6
|
+
import * as fs from "fs";
|
|
7
|
+
|
|
8
|
+
const BIN = "/tmp/yo-self-fmt-bin";
|
|
9
|
+
const TEST_FILE = "src/tests/formatter.test.ts";
|
|
10
|
+
|
|
11
|
+
const source = fs.readFileSync(TEST_FILE, "utf-8");
|
|
12
|
+
|
|
13
|
+
// Match each `test("name", () => { ... });` block at the top level.
|
|
14
|
+
// Within: extract `const source = (template-literal);` and the final
|
|
15
|
+
// `expect(formatYoSource(source)).toBe(template-literal);`.
|
|
16
|
+
//
|
|
17
|
+
// Template literals may contain newlines, embedded ${...}, and escaped backticks.
|
|
18
|
+
// We use a state-machine over the source to find balanced backtick-delimited
|
|
19
|
+
// template literals starting at a given position.
|
|
20
|
+
function findTemplateLiteral(
|
|
21
|
+
s: string,
|
|
22
|
+
start: number
|
|
23
|
+
): { value: string; end: number } | null {
|
|
24
|
+
if (s[start] !== "`") return null;
|
|
25
|
+
let i = start + 1;
|
|
26
|
+
while (i < s.length) {
|
|
27
|
+
const c = s[i];
|
|
28
|
+
if (c === "\\") {
|
|
29
|
+
i += 2;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (c === "`") {
|
|
33
|
+
// Closing backtick.
|
|
34
|
+
return { value: s.slice(start, i + 1), end: i + 1 };
|
|
35
|
+
}
|
|
36
|
+
if (c === "$" && s[i + 1] === "{") {
|
|
37
|
+
// Skip the ${...} interpolation balanced by curlies.
|
|
38
|
+
let depth = 1;
|
|
39
|
+
i += 2;
|
|
40
|
+
while (i < s.length && depth > 0) {
|
|
41
|
+
if (s[i] === "{") depth++;
|
|
42
|
+
else if (s[i] === "}") depth--;
|
|
43
|
+
i++;
|
|
44
|
+
}
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
i++;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function findStringLiteral(
|
|
53
|
+
s: string,
|
|
54
|
+
start: number
|
|
55
|
+
): { value: string; end: number } | null {
|
|
56
|
+
if (s[start] !== '"' && s[start] !== "'" && s[start] !== "`") return null;
|
|
57
|
+
if (s[start] === "`") return findTemplateLiteral(s, start);
|
|
58
|
+
const quote = s[start];
|
|
59
|
+
let i = start + 1;
|
|
60
|
+
while (i < s.length) {
|
|
61
|
+
if (s[i] === "\\") {
|
|
62
|
+
i += 2;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (s[i] === quote) return { value: s.slice(start, i + 1), end: i + 1 };
|
|
66
|
+
i++;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Parse all test("name", () => { ... }); blocks.
|
|
72
|
+
//
|
|
73
|
+
// Anchor matching on `test("name", () => {` so we don't mistake
|
|
74
|
+
// `test("name", { … })` substrings that appear inside test source/expected
|
|
75
|
+
// template literals (e.g. test 7's source includes a literal
|
|
76
|
+
// `test("format effect", { … })`). The `() =>` arrow distinguishes a real
|
|
77
|
+
// top-level `test()` call from the inner content.
|
|
78
|
+
type TestCase = { name: string; source: string; expected: string };
|
|
79
|
+
const cases: TestCase[] = [];
|
|
80
|
+
const skipped: { name: string; reason: string }[] = [];
|
|
81
|
+
|
|
82
|
+
const testStartRe = /test\("([^"]+)",\s*\(\s*\)\s*=>\s*\{/g;
|
|
83
|
+
let m: RegExpExecArray | null;
|
|
84
|
+
while ((m = testStartRe.exec(source)) !== null) {
|
|
85
|
+
const name = m[1];
|
|
86
|
+
const p = m.index + m[0].length;
|
|
87
|
+
|
|
88
|
+
// Find `const source = `...`;`
|
|
89
|
+
const srcMarker = "const source = ";
|
|
90
|
+
const srcIdx = source.indexOf(srcMarker, p);
|
|
91
|
+
if (srcIdx === -1) {
|
|
92
|
+
skipped.push({ name, reason: "no `const source = `" });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const srcLitStart = srcIdx + srcMarker.length;
|
|
96
|
+
const srcLit = findStringLiteral(source, srcLitStart);
|
|
97
|
+
if (!srcLit) {
|
|
98
|
+
skipped.push({ name, reason: "source is not a string literal" });
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Find the first `.toBe(` after the source declaration. Most tests use
|
|
103
|
+
// `expect(formatYoSource(source)).toBe(`<literal>`)` but some use the
|
|
104
|
+
// intermediate `const once = formatYoSource(source); expect(once).toBe(`<literal>`)`
|
|
105
|
+
// form — both are caught here because the first `.toBe(` carries the
|
|
106
|
+
// literal. Tests that compare two identifiers (e.g. `expect(twice).toBe(once)`
|
|
107
|
+
// for idempotency-only checks) have no literal to compare and are skipped.
|
|
108
|
+
const expMarker = ".toBe(";
|
|
109
|
+
const expIdx = source.indexOf(expMarker, srcLit.end);
|
|
110
|
+
if (expIdx === -1) {
|
|
111
|
+
skipped.push({ name, reason: "no `.toBe(` after source" });
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
let expLitStart = expIdx + expMarker.length;
|
|
115
|
+
while (/\s/.test(source[expLitStart])) expLitStart++;
|
|
116
|
+
const expLit = findStringLiteral(source, expLitStart);
|
|
117
|
+
if (!expLit) {
|
|
118
|
+
skipped.push({
|
|
119
|
+
name,
|
|
120
|
+
reason:
|
|
121
|
+
"`.toBe(` argument is not a string literal (idempotency-only test?)",
|
|
122
|
+
});
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// eval the literals (template-strings with embedded vars: those aren't used
|
|
127
|
+
// in our test cases except for backslash-escaped backticks)
|
|
128
|
+
let srcValue: string, expectedValue: string;
|
|
129
|
+
try {
|
|
130
|
+
srcValue = eval(srcLit.value);
|
|
131
|
+
expectedValue = eval(expLit.value);
|
|
132
|
+
} catch (e) {
|
|
133
|
+
skipped.push({ name, reason: `eval failed: ${e}` });
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
cases.push({ name, source: srcValue, expected: expectedValue });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log(
|
|
140
|
+
`Extracted ${cases.length} test cases; skipped ${skipped.length}.\n`
|
|
141
|
+
);
|
|
142
|
+
if (skipped.length > 0) {
|
|
143
|
+
console.log("Skipped:");
|
|
144
|
+
for (const s of skipped) {
|
|
145
|
+
console.log(` - ${s.name}: ${s.reason}`);
|
|
146
|
+
}
|
|
147
|
+
console.log();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Run each through yo-self-bin
|
|
151
|
+
let pass = 0,
|
|
152
|
+
fail = 0;
|
|
153
|
+
const failures: { name: string; expected: string; actual: string }[] = [];
|
|
154
|
+
|
|
155
|
+
for (const tc of cases) {
|
|
156
|
+
const tmpPath = `/tmp/yo_fmt_${Date.now()}_${Math.floor(Math.random() * 1e9)}.yo`;
|
|
157
|
+
fs.writeFileSync(tmpPath, tc.source);
|
|
158
|
+
spawnSync(BIN, ["fmt", tmpPath], { encoding: "utf-8" });
|
|
159
|
+
const actual = fs.readFileSync(tmpPath, "utf-8");
|
|
160
|
+
fs.unlinkSync(tmpPath);
|
|
161
|
+
|
|
162
|
+
if (actual === tc.expected) {
|
|
163
|
+
console.log(`✓ ${tc.name}`);
|
|
164
|
+
pass++;
|
|
165
|
+
} else {
|
|
166
|
+
console.log(`✗ ${tc.name}`);
|
|
167
|
+
failures.push({ name: tc.name, expected: tc.expected, actual });
|
|
168
|
+
fail++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
console.log(`\n${pass} passed, ${fail} failed, ${pass + fail} total\n`);
|
|
173
|
+
|
|
174
|
+
if (failures.length > 0) {
|
|
175
|
+
console.log("=== first 5 failures ===\n");
|
|
176
|
+
for (const f of failures.slice(0, 5)) {
|
|
177
|
+
console.log(`--- ${f.name} ---`);
|
|
178
|
+
console.log("expected:");
|
|
179
|
+
console.log(JSON.stringify(f.expected));
|
|
180
|
+
console.log("actual:");
|
|
181
|
+
console.log(JSON.stringify(f.actual));
|
|
182
|
+
console.log();
|
|
183
|
+
}
|
|
184
|
+
}
|