From 9d3ef431b000f3ca7934a0b662fa2a1656dfa4a9 Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 4 Jul 2026 22:40:58 +0200 Subject: [PATCH] chore: fix namespaces --- README.md | 31 +++++++++++++++++++++++++++++++ doctests.config.ts | 3 ++- package.json | 13 ++++++++++--- src/generator.ts | 13 ++++++++----- src/index.ts | 8 -------- src/parser.ts | 24 +++++++++++++++--------- 6 files changed, 66 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 5f70126..f97e6b6 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ Generate tests from your JSDoc documentation for whichever testing harness you p > See `gen-doctests --help` for more detail. +### Generate tests + Write a doctest alongside your source code: ````ts @@ -47,6 +49,8 @@ test("isEven()", () => { }); ``` +### Config + You can also define a `doctests.config.ts` file to skip the command arguments: ```ts @@ -58,3 +62,30 @@ export default defineConfig({ outDir: "dist/", }); ``` + +### Skipping tests + +You can specify that a test should be ignored or that it fails by specifying +that next to the language tag of the code block: + +````ts +/** + * ... + * @example + * ```ts ignore + * // this test is ignored + * ``` + * @example + * ```ts fails + * // this test is expected to fail + * expect(1).toBe(2) + * ``` + */ +```` + +If you use the `--must-assert` flag or `onlyGenerateTests` option then tests +which do not use any assertions will not be included in emitted test files. + +## License + +Published under [MIT License](./LICENSE) diff --git a/doctests.config.ts b/doctests.config.ts index c507f44..a9fecfc 100644 --- a/doctests.config.ts +++ b/doctests.config.ts @@ -1,7 +1,8 @@ import { defineConfig } from "./dist/config"; export default defineConfig({ - include: ["src/**.ts"], + include: ["src/**/*.ts"], outDir: "tests/generated", templateHeader: ["console.log('hello, world!');"], + emitRegions: true, }); diff --git a/package.json b/package.json index 8188eab..5e0d1e3 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,12 @@ { "name": "gen-doctests", + "author": { + "name": "BluePlum", + "url": "https://git.louiscreates.com/blueplum" + }, "description": "Tool for generating tests from JSDoc @example:s for various test harnesses", "keywords": ["tool", "docs", "test", "doctest"], - "private": false, - "version": "0.2.2", + "version": "0.2.3", "type": "module", "bin": "dist/index.js", "scripts": { @@ -42,5 +45,9 @@ "jiti": "^2.7.0", "picomatch": "^4.0.5", "typescript": "^6.0.3" - } + }, + "repository": { + "url": "https://git.louiscreates.com/blueplum/gen-doctests" + }, + "license": "MIT" } diff --git a/src/generator.ts b/src/generator.ts index 7a31672..0e279c4 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -182,14 +182,14 @@ export function createGenerator(options: Options) { fn = harness.skip; else fn = harness.fail; - let node: Region | null = null; + let node: Region = regions; for (const region of test.namespace) { - regions[region] ??= {}; - node = regions[region]; + node[region] ??= {}; + node = node[region]; } const out = fn(src, name); - if (node !== null) node[Symbol()] = out; + if (test.namespace.length !== 0) node[Symbol()] = out; else source += out + "\n\n"; } @@ -205,8 +205,11 @@ export function createGenerator(options: Options) { typeof value === "object" && typeof key === "string" ) { + const src = emitRegions(value); regions.push( - harness.region(emitRegions(value), key), + options.emitRegions + ? harness.region(src, key) + : src, ); } } diff --git a/src/index.ts b/src/index.ts index 8ce86ab..e92db51 100644 --- a/src/index.ts +++ b/src/index.ts @@ -104,17 +104,9 @@ async function main() { function parseWithOptions(options: Options) { const doctest = picomatch("**/*" + options.fileExtension + ".*"); - /** - * @example - * Test 1 - */ const files = globSync(options.include) .filter((v) => !doctest(v)) .filter((path) => !lstatSync(path).isDirectory()); - /** - * @example - * Test 2 - */ const parsed = files .map((path) => parseFile(path, options)) .filter((v) => v !== null); diff --git a/src/parser.ts b/src/parser.ts index 1278adc..b398da7 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -161,10 +161,18 @@ export function parseFile( kind = PathFragmentType.Other; if (identifier !== null) { name = identifier; - newPath.push({ - kind: PathFragmentType.Other, - fragment: identifier, - }); + if (node.type && ts.isFunctionLike(node.type)) { + kind = PathFragmentType.Function; + newPath.push({ + kind: PathFragmentType.Function, + fragment: identifier, + }); + } else { + newPath.push({ + kind: PathFragmentType.Namespace, + fragment: identifier, + }); + } } else if ( ts.isComputedPropertyName(node.name) && ts.isBinaryExpression(node.name.expression) @@ -176,6 +184,7 @@ export function parseFile( fragment: "unknown", }); } + visitThis = false; if (node.type) node.type.forEachChild((node) => { @@ -223,15 +232,14 @@ export function parseFile( .filter((tag) => tag.tagName.text === "example"); if (examples.length === 0) return; - let p = [...path]; + const p = [...path]; if (name !== null && kind !== null) p.push({ kind, fragment: name }); - const pOld = p; - const namespace = []; while ( + options.emitRegions && p[0].kind === PathFragmentType.Namespace && p.length > 1 ) { @@ -239,8 +247,6 @@ export function parseFile( namespace.push(p.shift()!.fragment); } - if (!options.emitRegions) p = pOld; - name = computeName(p); for (const example of examples) {