jsdoczoom 0.4.5 → 0.4.11
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/dist/test/barrel.test.js +77 -0
- package/dist/test/cache.test.js +222 -0
- package/dist/test/cli.test.js +479 -0
- package/dist/test/drilldown-barrel.test.js +383 -0
- package/dist/test/drilldown.test.js +469 -0
- package/dist/test/errors.test.js +26 -0
- package/dist/test/eslint-engine.test.js +130 -0
- package/dist/test/eslint-plugin.test.js +291 -0
- package/dist/test/file-discovery.test.js +72 -0
- package/dist/test/jsdoc-parser.test.js +353 -0
- package/dist/test/lint.test.js +413 -0
- package/dist/test/selector.test.js +93 -0
- package/dist/test/type-declarations.test.js +321 -0
- package/dist/test/validate.test.js +361 -0
- package/package.json +3 -7
- package/types/test/barrel.test.d.ts +1 -0
- package/types/test/cache.test.d.ts +8 -0
- package/types/test/cli.test.d.ts +1 -0
- package/types/test/drilldown-barrel.test.d.ts +1 -0
- package/types/test/drilldown.test.d.ts +1 -0
- package/types/test/errors.test.d.ts +1 -0
- package/types/test/eslint-engine.test.d.ts +6 -0
- package/types/test/eslint-plugin.test.d.ts +1 -0
- package/types/test/file-discovery.test.d.ts +1 -0
- package/types/test/jsdoc-parser.test.d.ts +1 -0
- package/types/test/lint.test.d.ts +9 -0
- package/types/test/selector.test.d.ts +1 -0
- package/types/test/type-declarations.test.d.ts +1 -0
- package/types/test/validate.test.d.ts +1 -0
- /package/dist/{barrel.js → src/barrel.js} +0 -0
- /package/dist/{cache.js → src/cache.js} +0 -0
- /package/dist/{cli.js → src/cli.js} +0 -0
- /package/dist/{drilldown.js → src/drilldown.js} +0 -0
- /package/dist/{errors.js → src/errors.js} +0 -0
- /package/dist/{eslint-engine.js → src/eslint-engine.js} +0 -0
- /package/dist/{eslint-plugin.js → src/eslint-plugin.js} +0 -0
- /package/dist/{file-discovery.js → src/file-discovery.js} +0 -0
- /package/dist/{index.js → src/index.js} +0 -0
- /package/dist/{jsdoc-parser.js → src/jsdoc-parser.js} +0 -0
- /package/dist/{lint.js → src/lint.js} +0 -0
- /package/dist/{selector.js → src/selector.js} +0 -0
- /package/dist/{skill-text.js → src/skill-text.js} +0 -0
- /package/dist/{type-declarations.js → src/type-declarations.js} +0 -0
- /package/dist/{types.js → src/types.js} +0 -0
- /package/dist/{validate.js → src/validate.js} +0 -0
- /package/types/{barrel.d.ts → src/barrel.d.ts} +0 -0
- /package/types/{cache.d.ts → src/cache.d.ts} +0 -0
- /package/types/{cli.d.ts → src/cli.d.ts} +0 -0
- /package/types/{drilldown.d.ts → src/drilldown.d.ts} +0 -0
- /package/types/{errors.d.ts → src/errors.d.ts} +0 -0
- /package/types/{eslint-engine.d.ts → src/eslint-engine.d.ts} +0 -0
- /package/types/{eslint-plugin.d.ts → src/eslint-plugin.d.ts} +0 -0
- /package/types/{file-discovery.d.ts → src/file-discovery.d.ts} +0 -0
- /package/types/{index.d.ts → src/index.d.ts} +0 -0
- /package/types/{jsdoc-parser.d.ts → src/jsdoc-parser.d.ts} +0 -0
- /package/types/{lint.d.ts → src/lint.d.ts} +0 -0
- /package/types/{selector.d.ts → src/selector.d.ts} +0 -0
- /package/types/{skill-text.d.ts → src/skill-text.d.ts} +0 -0
- /package/types/{type-declarations.d.ts → src/type-declarations.d.ts} +0 -0
- /package/types/{types.d.ts → src/types.d.ts} +0 -0
- /package/types/{validate.d.ts → src/validate.d.ts} +0 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { dirname, resolve } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { getBarrelChildren, isBarrel } from "../src/barrel.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Verifies that isBarrel correctly identifies index.ts/index.tsx files and
|
|
8
|
+
* that getBarrelChildren discovers sibling files and child barrels while
|
|
9
|
+
* respecting .d.ts exclusion and index.ts-over-index.tsx priority.
|
|
10
|
+
*
|
|
11
|
+
* @summary Tests for barrel detection and child discovery logic
|
|
12
|
+
*/
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const fixturesDir = resolve(__dirname, "fixtures");
|
|
15
|
+
describe("isBarrel", () => {
|
|
16
|
+
it("detects index.ts as barrel", () => {
|
|
17
|
+
expect(isBarrel("/some/path/index.ts")).toBe(true);
|
|
18
|
+
expect(isBarrel("index.ts")).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
it("detects index.tsx as barrel", () => {
|
|
21
|
+
expect(isBarrel("/some/path/index.tsx")).toBe(true);
|
|
22
|
+
expect(isBarrel("index.tsx")).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
it("non-index files are not barrels", () => {
|
|
25
|
+
expect(isBarrel("/some/path/helper.ts")).toBe(false);
|
|
26
|
+
expect(isBarrel("/some/path/index.test.ts")).toBe(false);
|
|
27
|
+
expect(isBarrel("/some/path/index.d.ts")).toBe(false);
|
|
28
|
+
expect(isBarrel("/some/path/index.stories.tsx")).toBe(false);
|
|
29
|
+
expect(isBarrel("/some/path/main.ts")).toBe(false);
|
|
30
|
+
expect(isBarrel("/some/path/utils.tsx")).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe("getBarrelChildren", () => {
|
|
34
|
+
it("returns leaf children from barrel directory", () => {
|
|
35
|
+
const barrelPath = resolve(fixturesDir, "barrel-basic", "index.ts");
|
|
36
|
+
const children = getBarrelChildren(barrelPath, fixturesDir);
|
|
37
|
+
const childNames = children.map((c) => c.split("/").pop());
|
|
38
|
+
expect(childNames).toContain("helper.ts");
|
|
39
|
+
expect(childNames).toContain("utils.ts");
|
|
40
|
+
expect(childNames).not.toContain("index.ts");
|
|
41
|
+
});
|
|
42
|
+
it("returns child barrel from subdirectory", () => {
|
|
43
|
+
const barrelPath = resolve(fixturesDir, "barrel-nested", "index.ts");
|
|
44
|
+
const children = getBarrelChildren(barrelPath, fixturesDir);
|
|
45
|
+
const childPaths = children.map((c) => c.replace(`${fixturesDir}/`, ""));
|
|
46
|
+
expect(childPaths).toContain("barrel-nested/leaf.ts");
|
|
47
|
+
expect(childPaths).toContain("barrel-nested/sub/index.ts");
|
|
48
|
+
expect(childPaths).not.toContain("barrel-nested/index.ts");
|
|
49
|
+
});
|
|
50
|
+
it("index.ts takes priority over index.tsx in same directory", () => {
|
|
51
|
+
const barrelPath = resolve(
|
|
52
|
+
fixturesDir,
|
|
53
|
+
"barrel-ts-tsx-priority",
|
|
54
|
+
"index.ts",
|
|
55
|
+
);
|
|
56
|
+
const children = getBarrelChildren(barrelPath, fixturesDir);
|
|
57
|
+
const childNames = children.map((c) => c.split("/").pop());
|
|
58
|
+
// index.tsx should be included as a leaf (sibling, not the barrel)
|
|
59
|
+
expect(childNames).toContain("index.tsx");
|
|
60
|
+
expect(childNames).toContain("other.ts");
|
|
61
|
+
// The barrel itself (index.ts) should not be included
|
|
62
|
+
expect(childNames).not.toContain("index.ts");
|
|
63
|
+
});
|
|
64
|
+
it("barrel with zero children returns empty array", () => {
|
|
65
|
+
const barrelPath = resolve(fixturesDir, "barrel-zero-children", "index.ts");
|
|
66
|
+
const children = getBarrelChildren(barrelPath, fixturesDir);
|
|
67
|
+
expect(children).toEqual([]);
|
|
68
|
+
});
|
|
69
|
+
it("excludes .d.ts files", () => {
|
|
70
|
+
// Use barrel-basic which has no .d.ts files; verify none appear
|
|
71
|
+
const barrelPath = resolve(fixturesDir, "barrel-basic", "index.ts");
|
|
72
|
+
const children = getBarrelChildren(barrelPath, fixturesDir);
|
|
73
|
+
for (const child of children) {
|
|
74
|
+
expect(child).not.toMatch(/\.d\.ts$/);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the content-hash-based disk cache module. Verifies content hashing,
|
|
3
|
+
* cache read/write operations, directory creation, graceful error handling,
|
|
4
|
+
* concurrency safety, and mode namespacing using temporary test directories.
|
|
5
|
+
*
|
|
6
|
+
* @summary Unit and integration tests for cache operations
|
|
7
|
+
*/
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import os from "node:os";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { describe, expect, it, vi } from "vitest";
|
|
12
|
+
import {
|
|
13
|
+
computeContentHash,
|
|
14
|
+
ensureCacheDir,
|
|
15
|
+
processWithCache,
|
|
16
|
+
readCacheEntry,
|
|
17
|
+
writeCacheEntry,
|
|
18
|
+
} from "../src/cache.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a temporary directory, run a callback with it, then clean up.
|
|
22
|
+
*
|
|
23
|
+
* @param fn - Callback receiving the temp directory path
|
|
24
|
+
* @returns Promise that resolves when the callback and cleanup are done
|
|
25
|
+
*/
|
|
26
|
+
function withTempDir(fn) {
|
|
27
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "jsdoczoom-cache-"));
|
|
28
|
+
return fn(tmpDir).finally(() => fs.rmSync(tmpDir, { recursive: true }));
|
|
29
|
+
}
|
|
30
|
+
describe("computeContentHash", () => {
|
|
31
|
+
it("returns consistent SHA-256 hex for same input", () => {
|
|
32
|
+
const content = "hello world";
|
|
33
|
+
const hash1 = computeContentHash(content);
|
|
34
|
+
const hash2 = computeContentHash(content);
|
|
35
|
+
expect(hash1).toBe(hash2);
|
|
36
|
+
expect(hash1).toMatch(/^[a-f0-9]{64}$/);
|
|
37
|
+
});
|
|
38
|
+
it("returns different hashes for different input", () => {
|
|
39
|
+
const hash1 = computeContentHash("content A");
|
|
40
|
+
const hash2 = computeContentHash("content B");
|
|
41
|
+
expect(hash1).not.toBe(hash2);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe("readCacheEntry", () => {
|
|
45
|
+
it("returns null for cache miss (file not found)", async () => {
|
|
46
|
+
await withTempDir(async (dir) => {
|
|
47
|
+
const config = { enabled: true, directory: dir };
|
|
48
|
+
const result = await readCacheEntry(config, "drilldown", "nonexistent");
|
|
49
|
+
expect(result).toBeNull();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
it("returns null for corrupted/unparseable cache file (invalid JSON)", async () => {
|
|
53
|
+
await withTempDir(async (dir) => {
|
|
54
|
+
const config = { enabled: true, directory: dir };
|
|
55
|
+
const mode = "drilldown";
|
|
56
|
+
const hash = "corrupted";
|
|
57
|
+
// Create directory and write invalid JSON
|
|
58
|
+
await ensureCacheDir(config, mode);
|
|
59
|
+
const filePath = path.join(dir, mode, `${hash}.json`);
|
|
60
|
+
fs.writeFileSync(filePath, "{ invalid json }", "utf-8");
|
|
61
|
+
const result = await readCacheEntry(config, mode, hash);
|
|
62
|
+
expect(result).toBeNull();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
describe("writeCacheEntry and readCacheEntry", () => {
|
|
67
|
+
it("returns stored data after writing", async () => {
|
|
68
|
+
await withTempDir(async (dir) => {
|
|
69
|
+
const config = { enabled: true, directory: dir };
|
|
70
|
+
const mode = "lint";
|
|
71
|
+
const hash = "test-hash";
|
|
72
|
+
const data = { foo: "bar", count: 42 };
|
|
73
|
+
await ensureCacheDir(config, mode);
|
|
74
|
+
await writeCacheEntry(config, mode, hash, data);
|
|
75
|
+
const retrieved = await readCacheEntry(config, mode, hash);
|
|
76
|
+
expect(retrieved).toEqual(data);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe("processWithCache", () => {
|
|
81
|
+
it("calls compute on cache miss", async () => {
|
|
82
|
+
await withTempDir(async (dir) => {
|
|
83
|
+
const config = { enabled: true, directory: dir };
|
|
84
|
+
const compute = vi.fn(() => ({ result: "computed" }));
|
|
85
|
+
const result = await processWithCache(
|
|
86
|
+
config,
|
|
87
|
+
"validate",
|
|
88
|
+
"file content",
|
|
89
|
+
compute,
|
|
90
|
+
);
|
|
91
|
+
expect(compute).toHaveBeenCalledTimes(1);
|
|
92
|
+
expect(result).toEqual({ result: "computed" });
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
it("returns cached data on cache hit without calling compute", async () => {
|
|
96
|
+
await withTempDir(async (dir) => {
|
|
97
|
+
const config = { enabled: true, directory: dir };
|
|
98
|
+
const content = "cached file content";
|
|
99
|
+
const expectedData = { result: "cached" };
|
|
100
|
+
// First call populates cache
|
|
101
|
+
await processWithCache(config, "drilldown", content, () => expectedData);
|
|
102
|
+
// Second call should hit cache
|
|
103
|
+
const compute = vi.fn(() => ({ result: "should not be called" }));
|
|
104
|
+
const result = await processWithCache(
|
|
105
|
+
config,
|
|
106
|
+
"drilldown",
|
|
107
|
+
content,
|
|
108
|
+
compute,
|
|
109
|
+
);
|
|
110
|
+
expect(compute).not.toHaveBeenCalled();
|
|
111
|
+
expect(result).toEqual(expectedData);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
it("works when cache is disabled (always calls compute)", async () => {
|
|
115
|
+
await withTempDir(async (dir) => {
|
|
116
|
+
const config = { enabled: false, directory: dir };
|
|
117
|
+
const compute = vi.fn(() => ({ result: "computed" }));
|
|
118
|
+
const result1 = await processWithCache(
|
|
119
|
+
config,
|
|
120
|
+
"lint",
|
|
121
|
+
"content",
|
|
122
|
+
compute,
|
|
123
|
+
);
|
|
124
|
+
const result2 = await processWithCache(
|
|
125
|
+
config,
|
|
126
|
+
"lint",
|
|
127
|
+
"content",
|
|
128
|
+
compute,
|
|
129
|
+
);
|
|
130
|
+
expect(compute).toHaveBeenCalledTimes(2);
|
|
131
|
+
expect(result1).toEqual({ result: "computed" });
|
|
132
|
+
expect(result2).toEqual({ result: "computed" });
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
it("degrades gracefully when cache directory is unwritable (calls compute, does not throw)", async () => {
|
|
136
|
+
// Use a path that doesn't exist and can't be created
|
|
137
|
+
const config = {
|
|
138
|
+
enabled: true,
|
|
139
|
+
directory: "/nonexistent/readonly/path",
|
|
140
|
+
};
|
|
141
|
+
const compute = vi.fn(() => ({ result: "computed" }));
|
|
142
|
+
const result = await processWithCache(
|
|
143
|
+
config,
|
|
144
|
+
"drilldown",
|
|
145
|
+
"content",
|
|
146
|
+
compute,
|
|
147
|
+
);
|
|
148
|
+
expect(compute).toHaveBeenCalledTimes(1);
|
|
149
|
+
expect(result).toEqual({ result: "computed" });
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
describe("ensureCacheDir", () => {
|
|
153
|
+
it("creates nested directory structure", async () => {
|
|
154
|
+
await withTempDir(async (dir) => {
|
|
155
|
+
const config = { enabled: true, directory: dir };
|
|
156
|
+
const mode = "validate";
|
|
157
|
+
await ensureCacheDir(config, mode);
|
|
158
|
+
const expectedPath = path.join(dir, mode);
|
|
159
|
+
expect(fs.existsSync(expectedPath)).toBe(true);
|
|
160
|
+
expect(fs.statSync(expectedPath).isDirectory()).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
describe("concurrent writes", () => {
|
|
165
|
+
it("concurrent writes to same key don't corrupt", async () => {
|
|
166
|
+
await withTempDir(async (dir) => {
|
|
167
|
+
const config = { enabled: true, directory: dir };
|
|
168
|
+
const mode = "drilldown";
|
|
169
|
+
const hash = "concurrent-key";
|
|
170
|
+
await ensureCacheDir(config, mode);
|
|
171
|
+
// Launch multiple concurrent writes
|
|
172
|
+
const writes = Array.from({ length: 10 }, (_, i) =>
|
|
173
|
+
writeCacheEntry(config, mode, hash, { value: i }),
|
|
174
|
+
);
|
|
175
|
+
await Promise.all(writes);
|
|
176
|
+
// Read back - should be valid JSON (one of the written values)
|
|
177
|
+
const retrieved = await readCacheEntry(config, mode, hash);
|
|
178
|
+
expect(retrieved).not.toBeNull();
|
|
179
|
+
expect(typeof retrieved?.value).toBe("number");
|
|
180
|
+
expect(retrieved?.value).toBeGreaterThanOrEqual(0);
|
|
181
|
+
expect(retrieved?.value).toBeLessThan(10);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
describe("cache mode namespacing", () => {
|
|
186
|
+
it("cache entries are namespaced by mode", async () => {
|
|
187
|
+
await withTempDir(async (dir) => {
|
|
188
|
+
const config = { enabled: true, directory: dir };
|
|
189
|
+
const content = "shared content";
|
|
190
|
+
const drilldownData = { mode: "drilldown", value: 1 };
|
|
191
|
+
const validateData = { mode: "validate", value: 2 };
|
|
192
|
+
const lintData = { mode: "lint", value: 3 };
|
|
193
|
+
// Store same content in different modes
|
|
194
|
+
await processWithCache(config, "drilldown", content, () => drilldownData);
|
|
195
|
+
await processWithCache(config, "validate", content, () => validateData);
|
|
196
|
+
await processWithCache(config, "lint", content, () => lintData);
|
|
197
|
+
// Verify each mode returns its own cached data
|
|
198
|
+
const drilldownResult = await processWithCache(
|
|
199
|
+
config,
|
|
200
|
+
"drilldown",
|
|
201
|
+
content,
|
|
202
|
+
() => {
|
|
203
|
+
throw new Error("Should not compute");
|
|
204
|
+
},
|
|
205
|
+
);
|
|
206
|
+
const validateResult = await processWithCache(
|
|
207
|
+
config,
|
|
208
|
+
"validate",
|
|
209
|
+
content,
|
|
210
|
+
() => {
|
|
211
|
+
throw new Error("Should not compute");
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
const lintResult = await processWithCache(config, "lint", content, () => {
|
|
215
|
+
throw new Error("Should not compute");
|
|
216
|
+
});
|
|
217
|
+
expect(drilldownResult).toEqual(drilldownData);
|
|
218
|
+
expect(validateResult).toEqual(validateData);
|
|
219
|
+
expect(lintResult).toEqual(lintData);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
});
|