jsdoczoom 0.4.11 → 0.4.13

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.
Files changed (61) hide show
  1. package/dist/{src/drilldown.js → drilldown.js} +9 -6
  2. package/package.json +2 -2
  3. package/dist/test/barrel.test.js +0 -77
  4. package/dist/test/cache.test.js +0 -222
  5. package/dist/test/cli.test.js +0 -479
  6. package/dist/test/drilldown-barrel.test.js +0 -383
  7. package/dist/test/drilldown.test.js +0 -469
  8. package/dist/test/errors.test.js +0 -26
  9. package/dist/test/eslint-engine.test.js +0 -130
  10. package/dist/test/eslint-plugin.test.js +0 -291
  11. package/dist/test/file-discovery.test.js +0 -72
  12. package/dist/test/jsdoc-parser.test.js +0 -353
  13. package/dist/test/lint.test.js +0 -413
  14. package/dist/test/selector.test.js +0 -93
  15. package/dist/test/type-declarations.test.js +0 -321
  16. package/dist/test/validate.test.js +0 -361
  17. package/types/test/barrel.test.d.ts +0 -1
  18. package/types/test/cache.test.d.ts +0 -8
  19. package/types/test/cli.test.d.ts +0 -1
  20. package/types/test/drilldown-barrel.test.d.ts +0 -1
  21. package/types/test/drilldown.test.d.ts +0 -1
  22. package/types/test/errors.test.d.ts +0 -1
  23. package/types/test/eslint-engine.test.d.ts +0 -6
  24. package/types/test/eslint-plugin.test.d.ts +0 -1
  25. package/types/test/file-discovery.test.d.ts +0 -1
  26. package/types/test/jsdoc-parser.test.d.ts +0 -1
  27. package/types/test/lint.test.d.ts +0 -9
  28. package/types/test/selector.test.d.ts +0 -1
  29. package/types/test/type-declarations.test.d.ts +0 -1
  30. package/types/test/validate.test.d.ts +0 -1
  31. /package/dist/{src/barrel.js → barrel.js} +0 -0
  32. /package/dist/{src/cache.js → cache.js} +0 -0
  33. /package/dist/{src/cli.js → cli.js} +0 -0
  34. /package/dist/{src/errors.js → errors.js} +0 -0
  35. /package/dist/{src/eslint-engine.js → eslint-engine.js} +0 -0
  36. /package/dist/{src/eslint-plugin.js → eslint-plugin.js} +0 -0
  37. /package/dist/{src/file-discovery.js → file-discovery.js} +0 -0
  38. /package/dist/{src/index.js → index.js} +0 -0
  39. /package/dist/{src/jsdoc-parser.js → jsdoc-parser.js} +0 -0
  40. /package/dist/{src/lint.js → lint.js} +0 -0
  41. /package/dist/{src/selector.js → selector.js} +0 -0
  42. /package/dist/{src/skill-text.js → skill-text.js} +0 -0
  43. /package/dist/{src/type-declarations.js → type-declarations.js} +0 -0
  44. /package/dist/{src/types.js → types.js} +0 -0
  45. /package/dist/{src/validate.js → validate.js} +0 -0
  46. /package/types/{src/barrel.d.ts → barrel.d.ts} +0 -0
  47. /package/types/{src/cache.d.ts → cache.d.ts} +0 -0
  48. /package/types/{src/cli.d.ts → cli.d.ts} +0 -0
  49. /package/types/{src/drilldown.d.ts → drilldown.d.ts} +0 -0
  50. /package/types/{src/errors.d.ts → errors.d.ts} +0 -0
  51. /package/types/{src/eslint-engine.d.ts → eslint-engine.d.ts} +0 -0
  52. /package/types/{src/eslint-plugin.d.ts → eslint-plugin.d.ts} +0 -0
  53. /package/types/{src/file-discovery.d.ts → file-discovery.d.ts} +0 -0
  54. /package/types/{src/index.d.ts → index.d.ts} +0 -0
  55. /package/types/{src/jsdoc-parser.d.ts → jsdoc-parser.d.ts} +0 -0
  56. /package/types/{src/lint.d.ts → lint.d.ts} +0 -0
  57. /package/types/{src/selector.d.ts → selector.d.ts} +0 -0
  58. /package/types/{src/skill-text.d.ts → skill-text.d.ts} +0 -0
  59. /package/types/{src/type-declarations.d.ts → type-declarations.d.ts} +0 -0
  60. /package/types/{src/types.d.ts → types.d.ts} +0 -0
  61. /package/types/{src/validate.d.ts → validate.d.ts} +0 -0
@@ -1,321 +0,0 @@
1
- import { dirname, join } from "node:path";
2
- import { fileURLToPath } from "node:url";
3
- import ts from "typescript";
4
- import { beforeEach, describe, expect, it } from "vitest";
5
- import { JsdocError } from "../src/errors.js";
6
- import {
7
- generateTypeDeclarations,
8
- getLanguageService,
9
- resetCache,
10
- resolveCompilerOptions,
11
- } from "../src/type-declarations.js";
12
-
13
- /**
14
- * Verifies that generateTypeDeclarations produces .d.ts-like output
15
- * including exported types, interfaces, function signatures, const
16
- * declarations, and class signatures while excluding imports, non-exported
17
- * internals, and implementation bodies. JSDoc comments are preserved.
18
- *
19
- * @summary Tests for TypeScript declaration output generation
20
- */
21
- const __dirname = dirname(fileURLToPath(import.meta.url));
22
- const fixturesDir = join(__dirname, "fixtures", "leaf-files");
23
- describe("generateTypeDeclarations", () => {
24
- const exportedTypesPath = join(fixturesDir, "exported-types.ts");
25
- beforeEach(() => {
26
- resetCache();
27
- });
28
- it("includes exported type aliases", () => {
29
- const declarations = generateTypeDeclarations(exportedTypesPath);
30
- expect(declarations).toContain("export type Name");
31
- expect(declarations).toContain("string");
32
- });
33
- it("includes exported interfaces", () => {
34
- const declarations = generateTypeDeclarations(exportedTypesPath);
35
- expect(declarations).toContain("export interface User");
36
- expect(declarations).toContain("id: number");
37
- expect(declarations).toContain("name: string");
38
- expect(declarations).toContain("email?: string");
39
- });
40
- it("includes exported function signatures (no bodies)", () => {
41
- const declarations = generateTypeDeclarations(exportedTypesPath);
42
- // Should include the function signature (TypeScript uses 'declare' keyword)
43
- expect(declarations).toContain("export declare function getUser");
44
- expect(declarations).toContain("id: number");
45
- expect(declarations).toContain(": User");
46
- // Should NOT include the implementation body
47
- expect(declarations).not.toContain("return { id, name: 'test' }");
48
- });
49
- it("includes exported const signatures", () => {
50
- const declarations = generateTypeDeclarations(exportedTypesPath);
51
- expect(declarations).toContain("export declare const DEFAULT_TIMEOUT");
52
- // TypeScript declaration emit includes the literal value for const declarations
53
- expect(declarations).toContain("5000");
54
- });
55
- it("includes exported class declarations (signatures only, no method bodies)", () => {
56
- const declarations = generateTypeDeclarations(exportedTypesPath);
57
- // Should include the class declaration
58
- expect(declarations).toContain("export declare class UserService");
59
- // Should include method signatures
60
- expect(declarations).toContain("add(user: User): void");
61
- expect(declarations).toContain("getAll(): User[]");
62
- // Should NOT include method bodies
63
- expect(declarations).not.toContain("this.users.push(user)");
64
- expect(declarations).not.toContain("return this.users");
65
- // Should NOT include private field initializers
66
- expect(declarations).not.toContain("private users: User[] = []");
67
- // But should include the private field declaration
68
- expect(declarations).toContain("private users");
69
- });
70
- it("preserves all JSDoc comments (file-level and symbol-level)", () => {
71
- const declarations = generateTypeDeclarations(exportedTypesPath);
72
- // File-level JSDoc
73
- expect(declarations).toContain(
74
- "Module for testing type declarations generation",
75
- );
76
- expect(declarations).toContain("Type declarations test module");
77
- // Symbol-level JSDoc
78
- expect(declarations).toContain("A string type alias");
79
- expect(declarations).toContain("A user interface");
80
- expect(declarations).toContain("Gets a user by ID");
81
- expect(declarations).toContain("@param id - The user ID");
82
- expect(declarations).toContain("@returns The user object");
83
- expect(declarations).toContain("The default timeout value");
84
- expect(declarations).toContain("A simple utility class");
85
- expect(declarations).toContain("Add a user");
86
- expect(declarations).toContain("Get all users");
87
- });
88
- it("excludes import statements", () => {
89
- const declarations = generateTypeDeclarations(exportedTypesPath);
90
- // The fixture doesn't have imports, but we can verify no import statements appear
91
- expect(declarations).not.toContain("import ");
92
- expect(declarations).not.toContain("from ");
93
- });
94
- it("excludes non-exported internals", () => {
95
- const declarations = generateTypeDeclarations(exportedTypesPath);
96
- // Should NOT include internal helper function
97
- expect(declarations).not.toContain("internalHelper");
98
- // Should NOT include private constant
99
- expect(declarations).not.toContain("privateConst");
100
- });
101
- it("maintains source order of declarations", () => {
102
- const declarations = generateTypeDeclarations(exportedTypesPath);
103
- // Get positions of each export in the declarations
104
- const namePos = declarations.indexOf("export type Name");
105
- const userPos = declarations.indexOf("export interface User");
106
- const getUserPos = declarations.indexOf("export declare function getUser");
107
- const timeoutPos = declarations.indexOf(
108
- "export declare const DEFAULT_TIMEOUT",
109
- );
110
- const classPos = declarations.indexOf("export declare class UserService");
111
- // Verify they appear in the same order as the source
112
- expect(namePos).toBeGreaterThan(-1);
113
- expect(userPos).toBeGreaterThan(namePos);
114
- expect(getUserPos).toBeGreaterThan(userPos);
115
- expect(timeoutPos).toBeGreaterThan(getUserPos);
116
- expect(classPos).toBeGreaterThan(timeoutPos);
117
- });
118
- it("reuses language service across calls for files sharing the same tsconfig", () => {
119
- // Get the resolved compiler options and language service for the first call
120
- const { tsconfigPath, options } = resolveCompilerOptions(exportedTypesPath);
121
- const firstService = getLanguageService(tsconfigPath, options);
122
- // First call
123
- generateTypeDeclarations(exportedTypesPath);
124
- // Second call should return the same service instance
125
- const secondService = getLanguageService(tsconfigPath, options);
126
- expect(secondService).toBe(firstService);
127
- expect(secondService.service).toBe(firstService.service);
128
- // Second call to generateTypeDeclarations should not create a new service
129
- generateTypeDeclarations(exportedTypesPath);
130
- const thirdService = getLanguageService(tsconfigPath, options);
131
- expect(thirdService).toBe(firstService);
132
- });
133
- it("returns empty string for files with no exports", () => {
134
- const noExportsPath = join(fixturesDir, "no-exports.ts");
135
- const result = generateTypeDeclarations(noExportsPath);
136
- expect(result).toBe("");
137
- });
138
- it("throws PARSE_ERROR for files with syntax errors", () => {
139
- const syntaxErrorPath = join(fixturesDir, "syntax-error.ts");
140
- expect(() => generateTypeDeclarations(syntaxErrorPath)).toThrow(JsdocError);
141
- try {
142
- generateTypeDeclarations(syntaxErrorPath);
143
- expect.fail("Should have thrown JsdocError");
144
- } catch (e) {
145
- expect(e).toBeInstanceOf(JsdocError);
146
- expect(e.code).toBe("PARSE_ERROR");
147
- }
148
- });
149
- });
150
- describe("resolveCompilerOptions", () => {
151
- const tsconfigFixturesDir = join(__dirname, "fixtures");
152
- const tsconfigProjectDir = join(tsconfigFixturesDir, "tsconfig-project");
153
- const sampleTsPath = join(tsconfigProjectDir, "sample.ts");
154
- const malformedTsconfigPath = join(
155
- tsconfigFixturesDir,
156
- "tsconfig-malformed",
157
- "sample.ts",
158
- );
159
- beforeEach(() => {
160
- resetCache();
161
- });
162
- it("returns options with declaration: true and removeComments: false regardless of tsconfig", () => {
163
- const result = resolveCompilerOptions(sampleTsPath);
164
- expect(result.options.declaration).toBe(true);
165
- expect(result.options.emitDeclarationOnly).toBe(true);
166
- expect(result.options.removeComments).toBe(false);
167
- expect(result.options.skipLibCheck).toBe(true);
168
- });
169
- it("uses fallback defaults when no tsconfig.json is found", () => {
170
- // Use a path that won't find any tsconfig.json (root directory)
171
- // This assumes there's no tsconfig.json in the root filesystem
172
- const noTsconfigPath = "/file.ts";
173
- const result = resolveCompilerOptions(noTsconfigPath);
174
- expect(result.tsconfigPath).toBe(null);
175
- expect(result.options).toEqual({
176
- declaration: true,
177
- emitDeclarationOnly: true,
178
- target: ts.ScriptTarget.Latest,
179
- module: ts.ModuleKind.NodeNext,
180
- moduleResolution: ts.ModuleResolutionKind.NodeNext,
181
- skipLibCheck: true,
182
- removeComments: false,
183
- });
184
- });
185
- it("merges tsconfig settings with required overrides", () => {
186
- const result = resolveCompilerOptions(sampleTsPath);
187
- // Should include settings from the tsconfig
188
- expect(result.options.strict).toBe(true);
189
- expect(result.options.target).toBe(ts.ScriptTarget.ES2020);
190
- // Should also include required overrides
191
- expect(result.options.declaration).toBe(true);
192
- expect(result.options.emitDeclarationOnly).toBe(true);
193
- expect(result.options.removeComments).toBe(false);
194
- expect(result.options.skipLibCheck).toBe(true);
195
- // Should have found the tsconfig
196
- expect(result.tsconfigPath).toContain("tsconfig-project");
197
- });
198
- it("returns the same result object for the same tsconfig path (caching)", () => {
199
- const firstResult = resolveCompilerOptions(sampleTsPath);
200
- const secondResult = resolveCompilerOptions(sampleTsPath);
201
- // Should return the exact same object reference (not just equal values)
202
- expect(firstResult).toBe(secondResult);
203
- expect(firstResult.options).toBe(secondResult.options);
204
- });
205
- it("handles malformed tsconfig.json gracefully (falls back to defaults)", () => {
206
- const result = resolveCompilerOptions(malformedTsconfigPath);
207
- // Should fall back to defaults when tsconfig is malformed
208
- expect(result.tsconfigPath).toBe(null);
209
- expect(result.options).toEqual({
210
- declaration: true,
211
- emitDeclarationOnly: true,
212
- target: ts.ScriptTarget.Latest,
213
- module: ts.ModuleKind.NodeNext,
214
- moduleResolution: ts.ModuleResolutionKind.NodeNext,
215
- skipLibCheck: true,
216
- removeComments: false,
217
- });
218
- });
219
- });
220
- describe("getLanguageService", () => {
221
- const tsconfigFixturesDir = join(__dirname, "fixtures");
222
- const tsconfigProjectDir = join(tsconfigFixturesDir, "tsconfig-project");
223
- const sampleTsPath = join(tsconfigProjectDir, "sample.ts");
224
- beforeEach(() => {
225
- resetCache();
226
- });
227
- it("returns a language service that can emit declarations for a registered file", () => {
228
- const { tsconfigPath, options } = resolveCompilerOptions(sampleTsPath);
229
- const { service, files } = getLanguageService(tsconfigPath, options);
230
- // Add the file to the service's file set
231
- files.add(sampleTsPath);
232
- // Get emit output for the file
233
- const emitOutput = service.getEmitOutput(sampleTsPath, true); // true = emitOnlyDtsFiles
234
- // Should have declaration output
235
- expect(emitOutput.outputFiles).toBeDefined();
236
- expect(emitOutput.outputFiles.length).toBeGreaterThan(0);
237
- // Find the .d.ts output
238
- const dtsFile = emitOutput.outputFiles.find((file) =>
239
- file.name.endsWith(".d.ts"),
240
- );
241
- expect(dtsFile).toBeDefined();
242
- expect(dtsFile?.text).toContain("export");
243
- });
244
- it("reuses the same language service instance for files sharing a tsconfig", () => {
245
- const { tsconfigPath, options } = resolveCompilerOptions(sampleTsPath);
246
- // Get the service twice
247
- const firstCall = getLanguageService(tsconfigPath, options);
248
- const secondCall = getLanguageService(tsconfigPath, options);
249
- // Should return the exact same object references
250
- expect(firstCall).toBe(secondCall);
251
- expect(firstCall.service).toBe(secondCall.service);
252
- expect(firstCall.files).toBe(secondCall.files);
253
- });
254
- it("creates separate service instances for different tsconfigs", () => {
255
- // Get service for tsconfig-project
256
- const { tsconfigPath: tsconfigPath1, options: options1 } =
257
- resolveCompilerOptions(sampleTsPath);
258
- const service1 = getLanguageService(tsconfigPath1, options1);
259
- // Get service for a file with no tsconfig (uses fallback)
260
- const noTsconfigPath = "/file.ts";
261
- const { tsconfigPath: tsconfigPath2, options: options2 } =
262
- resolveCompilerOptions(noTsconfigPath);
263
- const service2 = getLanguageService(tsconfigPath2, options2);
264
- // Should be different service instances
265
- expect(service1).not.toBe(service2);
266
- expect(service1.service).not.toBe(service2.service);
267
- expect(service1.files).not.toBe(service2.files);
268
- });
269
- it("adding a new file to the files Set makes it visible to the existing service's getScriptFileNames()", () => {
270
- const { tsconfigPath, options } = resolveCompilerOptions(sampleTsPath);
271
- const { service, files } = getLanguageService(tsconfigPath, options);
272
- // Initially no files
273
- expect(files.size).toBe(0);
274
- // Add a file
275
- files.add(sampleTsPath);
276
- // Service should now see the file via getProgram().getSourceFiles()
277
- const program = service.getProgram();
278
- expect(program).toBeDefined();
279
- const sourceFiles = program?.getSourceFiles() ?? [];
280
- const hasOurFile = sourceFiles.some((sf) => sf.fileName === sampleTsPath);
281
- expect(hasOurFile).toBe(true);
282
- // Add another file
283
- const anotherFilePath = join(tsconfigProjectDir, "another-file.ts");
284
- files.add(anotherFilePath);
285
- // Should now have 2 files in the set
286
- expect(files.size).toBe(2);
287
- expect(files.has(sampleTsPath)).toBe(true);
288
- expect(files.has(anotherFilePath)).toBe(true);
289
- });
290
- });
291
- describe("resetCache", () => {
292
- const tsconfigFixturesDir = join(__dirname, "fixtures");
293
- const tsconfigProjectDir = join(tsconfigFixturesDir, "tsconfig-project");
294
- const sampleTsPath = join(tsconfigProjectDir, "sample.ts");
295
- it("clears the compiler options cache", () => {
296
- // Populate the cache
297
- const firstResult = resolveCompilerOptions(sampleTsPath);
298
- // Reset
299
- resetCache();
300
- // Get options again
301
- const secondResult = resolveCompilerOptions(sampleTsPath);
302
- // Should be different object instances (cache was cleared)
303
- expect(firstResult).not.toBe(secondResult);
304
- // But should have the same values
305
- expect(firstResult.tsconfigPath).toBe(secondResult.tsconfigPath);
306
- });
307
- it("clears the language service cache", () => {
308
- // Populate the cache
309
- const { tsconfigPath, options } = resolveCompilerOptions(sampleTsPath);
310
- const firstService = getLanguageService(tsconfigPath, options);
311
- // Reset
312
- resetCache();
313
- // Get service again (need to resolve options again too since that cache was also cleared)
314
- const { tsconfigPath: newTsconfigPath, options: newOptions } =
315
- resolveCompilerOptions(sampleTsPath);
316
- const secondService = getLanguageService(newTsconfigPath, newOptions);
317
- // Should be different service instances (cache was cleared)
318
- expect(firstService).not.toBe(secondService);
319
- expect(firstService.service).not.toBe(secondService.service);
320
- });
321
- });
@@ -1,361 +0,0 @@
1
- import path from "node:path";
2
- import { fileURLToPath } from "node:url";
3
- import { describe, expect, it } from "vitest";
4
- import { JsdocError } from "../src/errors.js";
5
- import { validate, validateFiles } from "../src/validate.js";
6
-
7
- /**
8
- * Verifies status classification (valid, syntax_error, missing_jsdoc,
9
- * missing_summary, missing_description), priority-order group filling
10
- * under limits, and error handling for missing files and empty globs.
11
- *
12
- * @summary Tests for file-level JSDoc validation and grouped result output
13
- */
14
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
- const fixturesDir = path.resolve(__dirname, "fixtures", "leaf-files");
16
- function fixture(name) {
17
- return path.join(fixturesDir, name);
18
- }
19
- describe("validate", () => {
20
- it("valid file produces empty result", async () => {
21
- const selector = {
22
- type: "path",
23
- pattern: fixture("two-summaries.ts"),
24
- depth: undefined,
25
- };
26
- const result = await validate(selector, fixturesDir);
27
- expect(result.syntax_error).toBeUndefined();
28
- expect(result.missing_jsdoc).toBeUndefined();
29
- expect(result.missing_summary).toBeUndefined();
30
- expect(result.missing_description).toBeUndefined();
31
- });
32
- it("file with @summary but no description is missing_description", async () => {
33
- const selector = {
34
- type: "path",
35
- pattern: fixture("summary-only.ts"),
36
- depth: undefined,
37
- };
38
- const result = await validate(selector, fixturesDir);
39
- expect(result.missing_description?.files).toEqual(["summary-only.ts"]);
40
- expect(result.missing_jsdoc).toBeUndefined();
41
- expect(result.missing_summary).toBeUndefined();
42
- });
43
- it("file with no @summary is missing_summary", async () => {
44
- const selector = {
45
- type: "path",
46
- pattern: fixture("description-only.ts"),
47
- depth: undefined,
48
- };
49
- const result = await validate(selector, fixturesDir);
50
- expect(result.missing_summary?.files).toEqual(["description-only.ts"]);
51
- });
52
- it("file with no file-level JSDoc is missing_jsdoc", async () => {
53
- const selector = {
54
- type: "path",
55
- pattern: fixture("no-jsdoc.ts"),
56
- depth: undefined,
57
- };
58
- const result = await validate(selector, fixturesDir);
59
- expect(result.missing_jsdoc?.files).toEqual(["no-jsdoc.ts"]);
60
- });
61
- it("file with syntax error is syntax_error", async () => {
62
- const selector = {
63
- type: "path",
64
- pattern: fixture("syntax-error.ts"),
65
- depth: undefined,
66
- };
67
- const result = await validate(selector, fixturesDir);
68
- expect(result.syntax_error?.files).toEqual(["syntax-error.ts"]);
69
- });
70
- it("whitespace-only @summary is skipped, uses next non-empty one", async () => {
71
- const selector = {
72
- type: "path",
73
- pattern: fixture("whitespace-summary.ts"),
74
- depth: undefined,
75
- };
76
- const result = await validate(selector, fixturesDir);
77
- expect(result.missing_summary).toBeUndefined();
78
- });
79
- it("flags files with multiple @summary tags", async () => {
80
- const selector = {
81
- type: "path",
82
- pattern: "multiple-summaries.ts",
83
- depth: undefined,
84
- };
85
- const result = await validate(selector, fixturesDir);
86
- expect(result.multiple_summary).toBeDefined();
87
- expect(result.multiple_summary?.files).toEqual(["multiple-summaries.ts"]);
88
- });
89
- it("silently strips @depth suffix", async () => {
90
- const selector = {
91
- type: "glob",
92
- pattern: "**/*.ts",
93
- depth: 1,
94
- };
95
- const result = await validate(selector, fixturesDir);
96
- // Should succeed without throwing — depth is ignored
97
- expect(typeof result).toBe("object");
98
- });
99
- it("output groups only contain invalid files, empty groups omitted", async () => {
100
- const selector = {
101
- type: "glob",
102
- pattern: "*.ts",
103
- depth: undefined,
104
- };
105
- const result = await validate(selector, fixturesDir);
106
- // Every group key that exists should have { guidance, files } shape
107
- for (const key of [
108
- "syntax_error",
109
- "missing_jsdoc",
110
- "missing_summary",
111
- "multiple_summary",
112
- "missing_description",
113
- "missing_barrel",
114
- ]) {
115
- const group = result[key];
116
- if (group !== undefined) {
117
- expect(typeof group.guidance).toBe("string");
118
- expect(group.guidance.length).toBeGreaterThan(0);
119
- expect(Array.isArray(group.files)).toBe(true);
120
- expect(group.files.length).toBeGreaterThan(0);
121
- for (const p of group.files) {
122
- expect(typeof p).toBe("string");
123
- }
124
- }
125
- }
126
- });
127
- it("each group includes guidance text", async () => {
128
- const selector = {
129
- type: "glob",
130
- pattern: "*.ts",
131
- depth: undefined,
132
- };
133
- const result = await validate(selector, fixturesDir);
134
- if (result.syntax_error) {
135
- expect(result.syntax_error.guidance).toContain("### Syntax error");
136
- }
137
- if (result.missing_jsdoc) {
138
- expect(result.missing_jsdoc.guidance).toContain(
139
- "### Missing file-level JSDoc",
140
- );
141
- }
142
- if (result.missing_summary) {
143
- expect(result.missing_summary.guidance).toContain("### The @summary tag");
144
- }
145
- if (result.missing_description) {
146
- expect(result.missing_description.guidance).toContain(
147
- "### The description paragraph",
148
- );
149
- }
150
- });
151
- it("works with glob selectors", async () => {
152
- const selector = {
153
- type: "glob",
154
- pattern: "two-*.ts",
155
- depth: undefined,
156
- };
157
- const result = await validate(selector, fixturesDir);
158
- // two-summaries.ts is valid, so no groups should appear
159
- expect(result.syntax_error).toBeUndefined();
160
- expect(result.missing_jsdoc).toBeUndefined();
161
- expect(result.missing_summary).toBeUndefined();
162
- expect(result.missing_description).toBeUndefined();
163
- });
164
- it("works with path selectors", async () => {
165
- const selector = {
166
- type: "path",
167
- pattern: fixture("two-summaries.ts"),
168
- depth: undefined,
169
- };
170
- const result = await validate(selector, fixturesDir);
171
- expect(result.syntax_error).toBeUndefined();
172
- expect(result.missing_jsdoc).toBeUndefined();
173
- expect(result.missing_summary).toBeUndefined();
174
- expect(result.missing_description).toBeUndefined();
175
- });
176
- it("path targeting nonexistent file throws FILE_NOT_FOUND", async () => {
177
- const selector = {
178
- type: "path",
179
- pattern: fixture("nonexistent.ts"),
180
- depth: undefined,
181
- };
182
- await expect(validate(selector, fixturesDir)).rejects.toThrow(JsdocError);
183
- try {
184
- await validate(selector, fixturesDir);
185
- } catch (e) {
186
- expect(e).toBeInstanceOf(JsdocError);
187
- expect(e.code).toBe("FILE_NOT_FOUND");
188
- }
189
- });
190
- it("glob with no matches throws NO_FILES_MATCHED", async () => {
191
- const selector = {
192
- type: "glob",
193
- pattern: "nonexistent-*.ts",
194
- depth: undefined,
195
- };
196
- await expect(validate(selector, fixturesDir)).rejects.toThrow(JsdocError);
197
- try {
198
- await validate(selector, fixturesDir);
199
- } catch (e) {
200
- expect(e).toBeInstanceOf(JsdocError);
201
- expect(e.code).toBe("NO_FILES_MATCHED");
202
- }
203
- });
204
- it("flags directories with >3 files and no barrel as missing_barrel", async () => {
205
- // leaf-files has 12+ .ts files and no index.ts
206
- const selector = {
207
- type: "glob",
208
- pattern: "*.ts",
209
- depth: undefined,
210
- };
211
- const result = await validate(selector, fixturesDir);
212
- expect(result.missing_barrel).toBeDefined();
213
- expect(result.missing_barrel?.files).toContain(".");
214
- });
215
- it("does not flag directories with <=3 files as missing_barrel", async () => {
216
- // depth-advancement has only 2 files
217
- const depthDir = path.resolve(__dirname, "fixtures", "depth-advancement");
218
- const selector = {
219
- type: "glob",
220
- pattern: "*.ts",
221
- depth: undefined,
222
- };
223
- const result = await validate(selector, depthDir);
224
- expect(result.missing_barrel).toBeUndefined();
225
- });
226
- it("does not flag directories that have a barrel as missing_barrel", async () => {
227
- // barrel-basic has index.ts + helper.ts + utils.ts
228
- const barrelDir = path.resolve(__dirname, "fixtures", "barrel-basic");
229
- const selector = {
230
- type: "glob",
231
- pattern: "**/*.ts",
232
- depth: undefined,
233
- };
234
- const result = await validate(selector, barrelDir);
235
- expect(result.missing_barrel).toBeUndefined();
236
- });
237
- describe("limit", () => {
238
- it("all invalid files shown when within limit", async () => {
239
- const selector = {
240
- type: "glob",
241
- pattern: "*.ts",
242
- depth: undefined,
243
- };
244
- const result = await validate(selector, fixturesDir, 100);
245
- // With a high limit, all invalid files should be present
246
- const totalShown =
247
- (result.syntax_error?.files.length ?? 0) +
248
- (result.missing_jsdoc?.files.length ?? 0) +
249
- (result.missing_summary?.files.length ?? 0) +
250
- (result.missing_description?.files.length ?? 0);
251
- expect(totalShown).toBeGreaterThan(0);
252
- });
253
- it("total shown paths capped at limit", async () => {
254
- const selector = {
255
- type: "glob",
256
- pattern: "*.ts",
257
- depth: undefined,
258
- };
259
- const result = await validate(selector, fixturesDir, 1);
260
- // Total shown paths should equal limit
261
- const shownPaths =
262
- (result.syntax_error?.files.length ?? 0) +
263
- (result.missing_jsdoc?.files.length ?? 0) +
264
- (result.missing_summary?.files.length ?? 0) +
265
- (result.missing_description?.files.length ?? 0);
266
- expect(shownPaths).toBe(1);
267
- });
268
- it("limit of 0 shows no file paths", async () => {
269
- const selector = {
270
- type: "glob",
271
- pattern: "*.ts",
272
- depth: undefined,
273
- };
274
- const result = await validate(selector, fixturesDir, 0);
275
- expect(result.syntax_error).toBeUndefined();
276
- expect(result.missing_jsdoc).toBeUndefined();
277
- expect(result.missing_summary).toBeUndefined();
278
- expect(result.missing_description).toBeUndefined();
279
- });
280
- it("groups are filled in priority order (syntax_error first)", async () => {
281
- const files = [
282
- fixture("syntax-error.ts"),
283
- fixture("no-jsdoc.ts"),
284
- fixture("description-only.ts"),
285
- fixture("summary-only.ts"),
286
- ];
287
- const result = await validateFiles(files, fixturesDir, 2);
288
- // syntax_error should be filled first
289
- expect(result.syntax_error?.files).toEqual(["syntax-error.ts"]);
290
- // Then missing_jsdoc
291
- expect(result.missing_jsdoc?.files).toEqual(["no-jsdoc.ts"]);
292
- // Remaining groups should not appear (limit reached)
293
- expect(result.missing_summary).toBeUndefined();
294
- expect(result.missing_description).toBeUndefined();
295
- });
296
- });
297
- });
298
- describe("validateFiles", () => {
299
- it("validates explicit file list", async () => {
300
- const files = [
301
- fixture("two-summaries.ts"),
302
- fixture("one-summary.ts"),
303
- fixture("no-jsdoc.ts"),
304
- ];
305
- const result = await validateFiles(files, fixturesDir);
306
- expect(result.missing_jsdoc?.files).toEqual(["no-jsdoc.ts"]);
307
- });
308
- it("filters to .ts/.tsx only", async () => {
309
- const files = [
310
- fixture("two-summaries.ts"),
311
- "/some/path/file.js",
312
- "/some/path/file.jsx",
313
- fixture("one-summary.ts"),
314
- ];
315
- const result = await validateFiles(files, fixturesDir);
316
- // Should only validate .ts/.tsx files (filters out .js/.jsx) — all valid
317
- expect(result.syntax_error).toBeUndefined();
318
- expect(result.missing_jsdoc).toBeUndefined();
319
- expect(result.missing_summary).toBeUndefined();
320
- expect(result.missing_description).toBeUndefined();
321
- });
322
- it("produces grouped output format with ValidationGroup shape", async () => {
323
- const files = [fixture("no-jsdoc.ts")];
324
- const result = await validateFiles(files, fixturesDir);
325
- expect(result.missing_jsdoc).toBeDefined();
326
- expect(typeof result.missing_jsdoc?.guidance).toBe("string");
327
- expect(Array.isArray(result.missing_jsdoc?.files)).toBe(true);
328
- });
329
- it("handles empty file list", async () => {
330
- const result = await validateFiles([], fixturesDir);
331
- expect(result.syntax_error).toBeUndefined();
332
- expect(result.missing_jsdoc).toBeUndefined();
333
- expect(result.missing_summary).toBeUndefined();
334
- expect(result.missing_description).toBeUndefined();
335
- });
336
- it("handles mixed pass/fail results", async () => {
337
- const files = [
338
- fixture("one-summary.ts"), // Pass
339
- fixture("description-only.ts"), // Fail — missing_summary
340
- ];
341
- const result = await validateFiles(files, fixturesDir);
342
- expect(result.missing_summary?.files).toEqual(["description-only.ts"]);
343
- });
344
- });
345
- describe("cache integration", () => {
346
- it("validate returns cached classification for unchanged files", async () => {
347
- // Run validate twice on same file, second time should be cached
348
- // (we can't directly verify caching without mocking, but verify correctness)
349
- const files = [fixture("two-summaries.ts")];
350
- const result1 = await validateFiles(files, fixturesDir);
351
- const result2 = await validateFiles(files, fixturesDir);
352
- expect(result1).toEqual(result2);
353
- });
354
- it("validate works with cache disabled", async () => {
355
- const files = [fixture("two-summaries.ts")];
356
- const disabledConfig = { enabled: false, directory: "" };
357
- const result = await validateFiles(files, fixturesDir, 100, disabledConfig);
358
- expect(result.syntax_error).toBeUndefined();
359
- expect(result.missing_jsdoc).toBeUndefined();
360
- });
361
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,8 +0,0 @@
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
- export {};