tjs-lang 0.7.7 → 0.8.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/CLAUDE.md +99 -33
- package/bin/docs.js +4 -1
- package/demo/docs.json +104 -22
- package/demo/src/examples.test.ts +1 -0
- package/demo/src/imports.test.ts +16 -4
- package/demo/src/imports.ts +60 -15
- package/demo/src/playground-shared.ts +9 -8
- package/demo/src/tfs-worker.js +205 -147
- package/demo/src/tjs-playground.ts +34 -10
- package/demo/src/ts-examples.ts +8 -8
- package/demo/src/ts-playground.ts +24 -8
- package/dist/index.js +118 -101
- package/dist/index.js.map +4 -4
- package/dist/src/lang/bool-coercion.d.ts +50 -0
- package/dist/src/lang/docs.d.ts +31 -6
- package/dist/src/lang/linter.d.ts +8 -0
- package/dist/src/lang/parser-transforms.d.ts +18 -0
- package/dist/src/lang/parser-types.d.ts +2 -0
- package/dist/src/lang/parser.d.ts +3 -0
- package/dist/src/lang/runtime.d.ts +34 -0
- package/dist/src/lang/types.d.ts +9 -1
- package/dist/src/rbac/index.d.ts +1 -1
- package/dist/src/vm/runtime.d.ts +1 -1
- package/dist/tjs-eval.js +38 -36
- package/dist/tjs-eval.js.map +4 -4
- package/dist/tjs-from-ts.js +20 -20
- package/dist/tjs-from-ts.js.map +3 -3
- package/dist/tjs-lang.js +85 -83
- package/dist/tjs-lang.js.map +4 -4
- package/dist/tjs-vm.js +47 -45
- package/dist/tjs-vm.js.map +4 -4
- package/llms.txt +79 -0
- package/package.json +9 -4
- package/src/cli/commands/convert.test.ts +16 -21
- package/src/lang/bool-coercion.test.ts +203 -0
- package/src/lang/bool-coercion.ts +314 -0
- package/src/lang/codegen.test.ts +137 -0
- package/src/lang/docs.test.ts +476 -1
- package/src/lang/docs.ts +471 -37
- package/src/lang/emitters/ast.ts +11 -12
- package/src/lang/emitters/dts.test.ts +41 -0
- package/src/lang/emitters/dts.ts +9 -0
- package/src/lang/emitters/js-tests.ts +9 -4
- package/src/lang/emitters/js-wasm.ts +57 -65
- package/src/lang/emitters/js.ts +198 -3
- package/src/lang/features.test.ts +4 -3
- package/src/lang/index.ts +9 -0
- package/src/lang/inference.ts +54 -0
- package/src/lang/linter.test.ts +104 -1
- package/src/lang/linter.ts +124 -1
- package/src/lang/module-loader.test.ts +318 -0
- package/src/lang/module-loader.ts +419 -0
- package/src/lang/parser-params.ts +31 -0
- package/src/lang/parser-transforms.ts +640 -0
- package/src/lang/parser-types.ts +35 -0
- package/src/lang/parser.test.ts +73 -1
- package/src/lang/parser.ts +77 -3
- package/src/lang/runtime.ts +98 -0
- package/src/lang/types.ts +6 -0
- package/src/lang/wasm.test.ts +1293 -2
- package/src/lang/wasm.ts +470 -87
- package/src/linalg/index.tjs +119 -0
- package/src/linalg/linalg.test.ts +294 -0
- package/src/linalg/vector-search.bench.test.ts +395 -0
- package/src/rbac/index.ts +2 -2
- package/src/rbac/rules.tjs.d.ts +9 -0
- package/src/vm/atoms/batteries.ts +2 -2
- package/src/vm/runtime.ts +10 -3
- package/dist/src/rbac/rules.d.ts +0 -184
- package/src/rbac/rules.js +0 -338
package/src/lang/docs.test.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { describe, it, expect } from 'bun:test'
|
|
9
|
-
import { generateDocs, generateDocsMarkdown } from './docs'
|
|
9
|
+
import { generateDocs, generateDocsMarkdown, prettifyTestBody } from './docs'
|
|
10
10
|
|
|
11
11
|
describe('generateDocs', () => {
|
|
12
12
|
describe('basic output', () => {
|
|
@@ -157,6 +157,154 @@ function second(x: 0): 0 { return x }
|
|
|
157
157
|
})
|
|
158
158
|
})
|
|
159
159
|
|
|
160
|
+
describe('JSDoc-style doc blocks', () => {
|
|
161
|
+
it('extracts /** */ blocks and strips leading asterisks', () => {
|
|
162
|
+
const source = `
|
|
163
|
+
/**
|
|
164
|
+
* # Title
|
|
165
|
+
*
|
|
166
|
+
* Body line 1
|
|
167
|
+
* Body line 2
|
|
168
|
+
*/
|
|
169
|
+
`
|
|
170
|
+
const result = generateDocs(source)
|
|
171
|
+
|
|
172
|
+
expect(result.items).toHaveLength(1)
|
|
173
|
+
const doc = result.items[0] as any
|
|
174
|
+
expect(doc.type).toBe('doc')
|
|
175
|
+
expect(doc.content).toBe('# Title\n\nBody line 1\nBody line 2')
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('handles single-line JSDoc', () => {
|
|
179
|
+
const source = `/** A short note. */`
|
|
180
|
+
const result = generateDocs(source)
|
|
181
|
+
|
|
182
|
+
const doc = result.items[0] as any
|
|
183
|
+
expect(doc.type).toBe('doc')
|
|
184
|
+
expect(doc.content).toBe('A short note.')
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('preserves markdown lists and tables', () => {
|
|
188
|
+
const source = `
|
|
189
|
+
/**
|
|
190
|
+
* ## Options
|
|
191
|
+
*
|
|
192
|
+
* | Flag | Meaning |
|
|
193
|
+
* |------|---------|
|
|
194
|
+
* | \`-v\` | verbose |
|
|
195
|
+
*
|
|
196
|
+
* - first
|
|
197
|
+
* - second
|
|
198
|
+
*/
|
|
199
|
+
`
|
|
200
|
+
const result = generateDocs(source)
|
|
201
|
+
|
|
202
|
+
const doc = result.items[0] as any
|
|
203
|
+
expect(doc.content).toContain('## Options')
|
|
204
|
+
expect(doc.content).toContain('| Flag | Meaning |')
|
|
205
|
+
expect(doc.content).toContain('- first')
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('leaves @param / @returns as plain markdown', () => {
|
|
209
|
+
const source = `
|
|
210
|
+
/**
|
|
211
|
+
* Square the input.
|
|
212
|
+
*
|
|
213
|
+
* @param x - the input
|
|
214
|
+
* @returns the squared value
|
|
215
|
+
*/
|
|
216
|
+
function square(x: 0): 0 { return x * x }
|
|
217
|
+
`
|
|
218
|
+
const result = generateDocs(source)
|
|
219
|
+
|
|
220
|
+
const doc = result.items[0] as any
|
|
221
|
+
expect(doc.content).toContain('@param x - the input')
|
|
222
|
+
expect(doc.content).toContain('@returns the squared value')
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('skips JSDoc inside function bodies', () => {
|
|
226
|
+
const source = `
|
|
227
|
+
function outer() {
|
|
228
|
+
/**
|
|
229
|
+
* Should not be extracted — inside a body.
|
|
230
|
+
*/
|
|
231
|
+
return 1
|
|
232
|
+
}
|
|
233
|
+
`
|
|
234
|
+
const result = generateDocs(source)
|
|
235
|
+
|
|
236
|
+
// Only the function itself, no doc item
|
|
237
|
+
const docs = result.items.filter((i) => i.type === 'doc')
|
|
238
|
+
expect(docs).toHaveLength(0)
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it('skips empty JSDoc blocks', () => {
|
|
242
|
+
const source = `
|
|
243
|
+
/**
|
|
244
|
+
*
|
|
245
|
+
*/
|
|
246
|
+
function f() {}
|
|
247
|
+
`
|
|
248
|
+
const result = generateDocs(source)
|
|
249
|
+
|
|
250
|
+
const docs = result.items.filter((i) => i.type === 'doc')
|
|
251
|
+
expect(docs).toHaveLength(0)
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
it('does not treat /* ... */ as a doc comment', () => {
|
|
255
|
+
const source = `
|
|
256
|
+
/* just a regular block comment */
|
|
257
|
+
function f() {}
|
|
258
|
+
`
|
|
259
|
+
const result = generateDocs(source)
|
|
260
|
+
|
|
261
|
+
const docs = result.items.filter((i) => i.type === 'doc')
|
|
262
|
+
expect(docs).toHaveLength(0)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it('interleaves JSDoc with functions in document order', () => {
|
|
266
|
+
const source = `
|
|
267
|
+
/**
|
|
268
|
+
* # First
|
|
269
|
+
*/
|
|
270
|
+
function first(x: 0): 0 { return x }
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* # Second
|
|
274
|
+
*/
|
|
275
|
+
function second(x: 0): 0 { return x }
|
|
276
|
+
`
|
|
277
|
+
const result = generateDocs(source)
|
|
278
|
+
|
|
279
|
+
expect(result.items).toHaveLength(4)
|
|
280
|
+
expect(result.items[0].type).toBe('doc')
|
|
281
|
+
expect((result.items[0] as any).content).toContain('# First')
|
|
282
|
+
expect(result.items[1].type).toBe('function')
|
|
283
|
+
expect(result.items[2].type).toBe('doc')
|
|
284
|
+
expect((result.items[2] as any).content).toContain('# Second')
|
|
285
|
+
expect(result.items[3].type).toBe('function')
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
it('coexists with /*# blocks in the same file', () => {
|
|
289
|
+
const source = `
|
|
290
|
+
/*#
|
|
291
|
+
## TJS-native
|
|
292
|
+
*/
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* ## JSDoc-native
|
|
296
|
+
*/
|
|
297
|
+
function f(x: 0): 0 { return x }
|
|
298
|
+
`
|
|
299
|
+
const result = generateDocs(source)
|
|
300
|
+
|
|
301
|
+
const docs = result.items.filter((i) => i.type === 'doc') as any[]
|
|
302
|
+
expect(docs).toHaveLength(2)
|
|
303
|
+
expect(docs[0].content).toContain('TJS-native')
|
|
304
|
+
expect(docs[1].content).toContain('JSDoc-native')
|
|
305
|
+
})
|
|
306
|
+
})
|
|
307
|
+
|
|
160
308
|
describe('markdown output', () => {
|
|
161
309
|
it('renders doc blocks as plain markdown', () => {
|
|
162
310
|
const source = `
|
|
@@ -452,3 +600,330 @@ function second(y: ''): '' {
|
|
|
452
600
|
expect(secondPos).toBeLessThan(afterPos)
|
|
453
601
|
})
|
|
454
602
|
})
|
|
603
|
+
|
|
604
|
+
describe('prettifyTestBody', () => {
|
|
605
|
+
it('translates toBe', () => {
|
|
606
|
+
expect(prettifyTestBody('expect(x).toBe(y)')).toBe('x // → y')
|
|
607
|
+
})
|
|
608
|
+
|
|
609
|
+
it('translates toEqual to ≡', () => {
|
|
610
|
+
expect(prettifyTestBody('expect(a).toEqual(b)')).toBe('a // ≡ b')
|
|
611
|
+
})
|
|
612
|
+
|
|
613
|
+
it('handles balanced parens in expression', () => {
|
|
614
|
+
expect(
|
|
615
|
+
prettifyTestBody('expect(Boolean(new Boolean(false))).toBe(false)')
|
|
616
|
+
).toBe('Boolean(new Boolean(false)) // → false')
|
|
617
|
+
})
|
|
618
|
+
|
|
619
|
+
it('handles balanced parens in expected value', () => {
|
|
620
|
+
expect(prettifyTestBody('expect(x).toBe(f(1, 2))')).toBe('x // → f(1, 2)')
|
|
621
|
+
})
|
|
622
|
+
|
|
623
|
+
it('handles toBeTruthy / toBeFalsy / toBeNull / toBeUndefined', () => {
|
|
624
|
+
expect(prettifyTestBody('expect(x).toBeTruthy()')).toBe('x // → truthy')
|
|
625
|
+
expect(prettifyTestBody('expect(x).toBeFalsy()')).toBe('x // → falsy')
|
|
626
|
+
expect(prettifyTestBody('expect(x).toBeNull()')).toBe('x // → null')
|
|
627
|
+
expect(prettifyTestBody('expect(x).toBeUndefined()')).toBe(
|
|
628
|
+
'x // → undefined'
|
|
629
|
+
)
|
|
630
|
+
})
|
|
631
|
+
|
|
632
|
+
it('translates toContain / toThrow / toBeNaN', () => {
|
|
633
|
+
expect(prettifyTestBody('expect(arr).toContain(3)')).toBe(
|
|
634
|
+
'arr // → contains 3'
|
|
635
|
+
)
|
|
636
|
+
expect(prettifyTestBody('expect(fn).toThrow()')).toBe('fn // → throws')
|
|
637
|
+
expect(prettifyTestBody('expect(x).toBeNaN()')).toBe('x // → NaN')
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
it('translates toBeGreaterThan / toBeLessThan', () => {
|
|
641
|
+
expect(prettifyTestBody('expect(x).toBeGreaterThan(5)')).toBe('x // → > 5')
|
|
642
|
+
expect(prettifyTestBody('expect(y).toBeLessThan(10)')).toBe('y // → < 10')
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
it('preserves non-expect lines', () => {
|
|
646
|
+
expect(prettifyTestBody('console.log("hi")')).toBe('console.log("hi")')
|
|
647
|
+
expect(prettifyTestBody('const x = 5')).toBe('const x = 5')
|
|
648
|
+
})
|
|
649
|
+
|
|
650
|
+
it('handles multiple expects on separate lines', () => {
|
|
651
|
+
const input = ' expect(x).toBe(1)\n expect(y).toBeTruthy()'
|
|
652
|
+
const expected = ' x // → 1\n y // → truthy'
|
|
653
|
+
expect(prettifyTestBody(input)).toBe(expected)
|
|
654
|
+
})
|
|
655
|
+
|
|
656
|
+
it('does not touch parens inside string literals', () => {
|
|
657
|
+
// The `expect(...)` inside the string literal should NOT be transformed
|
|
658
|
+
expect(prettifyTestBody(`const s = "expect(fake).toBe(impossible)"`)).toBe(
|
|
659
|
+
`const s = "expect(fake).toBe(impossible)"`
|
|
660
|
+
)
|
|
661
|
+
})
|
|
662
|
+
|
|
663
|
+
it('falls back gracefully on unknown matchers', () => {
|
|
664
|
+
expect(prettifyTestBody('expect(x).somethingWeird(y)')).toBe(
|
|
665
|
+
'x // .somethingWeird(y)'
|
|
666
|
+
)
|
|
667
|
+
})
|
|
668
|
+
})
|
|
669
|
+
|
|
670
|
+
describe('function extraction', () => {
|
|
671
|
+
it('captures simple function signatures', () => {
|
|
672
|
+
const md = generateDocsMarkdown(
|
|
673
|
+
`function add(a: 0, b: 0): 0 { return a + b }`
|
|
674
|
+
)
|
|
675
|
+
expect(md).toContain('## add')
|
|
676
|
+
expect(md).toContain('function add(a: 0, b: 0): 0')
|
|
677
|
+
})
|
|
678
|
+
|
|
679
|
+
it('handles arrow-function defaults (params with nested parens)', () => {
|
|
680
|
+
// Regression: the old funcPattern used [^)]* and broke on `(x) => x`
|
|
681
|
+
const source = `
|
|
682
|
+
function mapStrings(arr: [''], fn = (x) => x): [''] {
|
|
683
|
+
return arr.map(fn)
|
|
684
|
+
}
|
|
685
|
+
function compose(f = (x) => x, g = (x) => x): 0 {
|
|
686
|
+
return f(g(5))
|
|
687
|
+
}
|
|
688
|
+
`
|
|
689
|
+
const md = generateDocsMarkdown(source)
|
|
690
|
+
expect(md).toContain('## mapStrings')
|
|
691
|
+
expect(md).toContain("function mapStrings(arr: [''], fn = (x) => x): ['']")
|
|
692
|
+
expect(md).toContain('## compose')
|
|
693
|
+
expect(md).toContain('function compose(f = (x) => x, g = (x) => x): 0')
|
|
694
|
+
})
|
|
695
|
+
|
|
696
|
+
it('handles object/array example values in params', () => {
|
|
697
|
+
const md = generateDocsMarkdown(
|
|
698
|
+
`function f(p: { a: 0, b: '' }, q: [0]): {} { return p }`
|
|
699
|
+
)
|
|
700
|
+
expect(md).toContain("function f(p: { a: 0, b: '' }, q: [0]): {}")
|
|
701
|
+
})
|
|
702
|
+
|
|
703
|
+
it('handles return-type annotation with quoted string', () => {
|
|
704
|
+
const md = generateDocsMarkdown(
|
|
705
|
+
`function greet(name: 'World'): 'Hello, World!' { return \`Hello, \${name}!\` }`
|
|
706
|
+
)
|
|
707
|
+
expect(md).toContain("function greet(name: 'World'): 'Hello, World!'")
|
|
708
|
+
})
|
|
709
|
+
|
|
710
|
+
it('does not extract nested function declarations', () => {
|
|
711
|
+
const source = `
|
|
712
|
+
function outer() {
|
|
713
|
+
function nested() { return 1 }
|
|
714
|
+
return nested
|
|
715
|
+
}
|
|
716
|
+
`
|
|
717
|
+
const result = generateDocs(source)
|
|
718
|
+
const fns = result.items.filter((i) => i.type === 'function')
|
|
719
|
+
expect(fns.length).toBe(1)
|
|
720
|
+
expect(fns[0].name).toBe('outer')
|
|
721
|
+
})
|
|
722
|
+
|
|
723
|
+
describe('function param rendering in docs', () => {
|
|
724
|
+
// We need transpiled type metadata for the param-table renderer.
|
|
725
|
+
// Use the lang index's `tjs()` to get types.
|
|
726
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
727
|
+
const { tjs } = require('./index')
|
|
728
|
+
|
|
729
|
+
it('renders an arrow-default param as `(x: any) => any` (not `function`)', () => {
|
|
730
|
+
const r = tjs(`function f(fn = (x) => x): 0 { return 0 }`)
|
|
731
|
+
const md = generateDocsMarkdown(r.code, r.types)
|
|
732
|
+
expect(md).toContain('`fn`: (x: any) => any')
|
|
733
|
+
})
|
|
734
|
+
|
|
735
|
+
it('infers return type from concise arrow body', () => {
|
|
736
|
+
const r = tjs(`function f(make = () => 5): 0 { return 0 }`)
|
|
737
|
+
const md = generateDocsMarkdown(r.code, r.types)
|
|
738
|
+
expect(md).toContain('`make`: () => number')
|
|
739
|
+
})
|
|
740
|
+
|
|
741
|
+
it('infers param types from arrow defaults', () => {
|
|
742
|
+
const r = tjs(
|
|
743
|
+
`function f(reduce = (acc = 0, x = 0) => 0): 0 { return 0 }`
|
|
744
|
+
)
|
|
745
|
+
const md = generateDocsMarkdown(r.code, r.types)
|
|
746
|
+
expect(md).toContain('`reduce`: (acc: number, x: number) => number')
|
|
747
|
+
})
|
|
748
|
+
|
|
749
|
+
it('does not show `e.g. undefined` for function example values', () => {
|
|
750
|
+
const r = tjs(`function f(fn = (x) => x): 0 { return 0 }`)
|
|
751
|
+
const md = generateDocsMarkdown(r.code, r.types)
|
|
752
|
+
expect(md).not.toContain('undefined')
|
|
753
|
+
})
|
|
754
|
+
})
|
|
755
|
+
})
|
|
756
|
+
|
|
757
|
+
describe('class extraction', () => {
|
|
758
|
+
it('extracts a class with constructor and methods', () => {
|
|
759
|
+
const source = `
|
|
760
|
+
class Point {
|
|
761
|
+
constructor(x: 0, y: 0) {
|
|
762
|
+
this.x = x
|
|
763
|
+
this.y = y
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
magnitude() {
|
|
767
|
+
return Math.sqrt(this.x * this.x + this.y * this.y)
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
toString() {
|
|
771
|
+
return \`(\${this.x}, \${this.y})\`
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
`
|
|
775
|
+
const md = generateDocsMarkdown(source)
|
|
776
|
+
expect(md).toContain('## Point')
|
|
777
|
+
expect(md).toContain('class Point {')
|
|
778
|
+
expect(md).toContain('constructor(x: 0, y: 0)')
|
|
779
|
+
expect(md).toContain('magnitude()')
|
|
780
|
+
expect(md).toContain('toString()')
|
|
781
|
+
// Method bodies should NOT appear
|
|
782
|
+
expect(md).not.toContain('this.x = x')
|
|
783
|
+
expect(md).not.toContain('Math.sqrt')
|
|
784
|
+
})
|
|
785
|
+
|
|
786
|
+
it('extracts multiple constructors', () => {
|
|
787
|
+
const source = `
|
|
788
|
+
class Color {
|
|
789
|
+
constructor(r: +0, g: +0, b: +0) { this.r = r; this.g = g; this.b = b }
|
|
790
|
+
constructor(hex: '#000000') { /* parse */ }
|
|
791
|
+
toString() { return 'rgb(...)' }
|
|
792
|
+
}
|
|
793
|
+
`
|
|
794
|
+
const md = generateDocsMarkdown(source)
|
|
795
|
+
expect(md).toContain('constructor(r: +0, g: +0, b: +0)')
|
|
796
|
+
expect(md).toContain("constructor(hex: '#000000')")
|
|
797
|
+
expect(md).toContain('toString()')
|
|
798
|
+
})
|
|
799
|
+
|
|
800
|
+
it('renders extends clause', () => {
|
|
801
|
+
const source = `
|
|
802
|
+
class ColorWithAlpha extends Color {
|
|
803
|
+
constructor(r: 0, g: 0, b: 0, a: 1.0) { super(r, g, b); this.a = a }
|
|
804
|
+
}
|
|
805
|
+
`
|
|
806
|
+
const md = generateDocsMarkdown(source)
|
|
807
|
+
expect(md).toContain('class ColorWithAlpha extends Color {')
|
|
808
|
+
})
|
|
809
|
+
|
|
810
|
+
it('handles static / async / get / set modifiers', () => {
|
|
811
|
+
const source = `
|
|
812
|
+
class Thing {
|
|
813
|
+
static load(path: '') { return path }
|
|
814
|
+
async fetch() { return null }
|
|
815
|
+
get name() { return 'x' }
|
|
816
|
+
set name(v: '') { this._name = v }
|
|
817
|
+
}
|
|
818
|
+
`
|
|
819
|
+
const md = generateDocsMarkdown(source)
|
|
820
|
+
expect(md).toContain("static load(path: '')")
|
|
821
|
+
expect(md).toContain('async fetch()')
|
|
822
|
+
expect(md).toContain('get name()')
|
|
823
|
+
expect(md).toContain("set name(v: '')")
|
|
824
|
+
})
|
|
825
|
+
|
|
826
|
+
it('captures return type annotations', () => {
|
|
827
|
+
const source = `
|
|
828
|
+
class Math2 {
|
|
829
|
+
add(a: 0, b: 0): 0 { return a + b }
|
|
830
|
+
}
|
|
831
|
+
`
|
|
832
|
+
const md = generateDocsMarkdown(source)
|
|
833
|
+
expect(md).toContain('add(a: 0, b: 0): 0')
|
|
834
|
+
})
|
|
835
|
+
|
|
836
|
+
it('does NOT extract `class Foo` text inside /*# */ doc blocks', () => {
|
|
837
|
+
// The doc-block prose is rendered as-is, but we should NOT emit a
|
|
838
|
+
// "## FakeClass" heading from the prose's `class FakeClass` mention.
|
|
839
|
+
const source = `
|
|
840
|
+
/*#
|
|
841
|
+
## Example
|
|
842
|
+
Don't write this:
|
|
843
|
+
|
|
844
|
+
class FakeClass { constructor(x) {} }
|
|
845
|
+
*/
|
|
846
|
+
|
|
847
|
+
class RealClass {
|
|
848
|
+
constructor() {}
|
|
849
|
+
}
|
|
850
|
+
`
|
|
851
|
+
const result = generateDocs(source)
|
|
852
|
+
const classItems = result.items.filter((i) => i.type === 'class')
|
|
853
|
+
expect(classItems.length).toBe(1)
|
|
854
|
+
expect(classItems[0].name).toBe('RealClass')
|
|
855
|
+
})
|
|
856
|
+
|
|
857
|
+
it('does NOT extract `function` text inside /*# */ doc blocks', () => {
|
|
858
|
+
const source = `
|
|
859
|
+
/*#
|
|
860
|
+
Don't write this:
|
|
861
|
+
|
|
862
|
+
function fakeFn() {}
|
|
863
|
+
*/
|
|
864
|
+
|
|
865
|
+
function realFn() { return 1 }
|
|
866
|
+
`
|
|
867
|
+
const result = generateDocs(source)
|
|
868
|
+
const fnItems = result.items.filter((i) => i.type === 'function')
|
|
869
|
+
expect(fnItems.length).toBe(1)
|
|
870
|
+
expect(fnItems[0].name).toBe('realFn')
|
|
871
|
+
})
|
|
872
|
+
|
|
873
|
+
it('handles a class with no members', () => {
|
|
874
|
+
const md = generateDocsMarkdown('class Empty {}')
|
|
875
|
+
expect(md).toContain('## Empty')
|
|
876
|
+
expect(md).toContain('class Empty {')
|
|
877
|
+
expect(md).toContain('}')
|
|
878
|
+
})
|
|
879
|
+
})
|
|
880
|
+
|
|
881
|
+
describe('generateDocsMarkdown — test cases section', () => {
|
|
882
|
+
it('renders each named test as a "### <name> (test cases)" heading with prettified body', () => {
|
|
883
|
+
const source = `
|
|
884
|
+
test 'x is 5' {
|
|
885
|
+
expect(x).toBe(5)
|
|
886
|
+
}
|
|
887
|
+
test 'y is truthy' {
|
|
888
|
+
expect(y).toBeTruthy()
|
|
889
|
+
}
|
|
890
|
+
`
|
|
891
|
+
const md = generateDocsMarkdown(source)
|
|
892
|
+
expect(md).toContain('### x is 5 (test cases)')
|
|
893
|
+
expect(md).toContain('x // → 5')
|
|
894
|
+
expect(md).toContain('### y is truthy (test cases)')
|
|
895
|
+
expect(md).toContain('y // → truthy')
|
|
896
|
+
})
|
|
897
|
+
|
|
898
|
+
it('skips anonymous tests (no description)', () => {
|
|
899
|
+
const source = `
|
|
900
|
+
test {
|
|
901
|
+
expect(x).toBe(5)
|
|
902
|
+
}
|
|
903
|
+
`
|
|
904
|
+
const md = generateDocsMarkdown(source)
|
|
905
|
+
expect(md).not.toContain('(test cases)')
|
|
906
|
+
})
|
|
907
|
+
|
|
908
|
+
it('integrates with doc blocks and functions', () => {
|
|
909
|
+
const source = `
|
|
910
|
+
/*#
|
|
911
|
+
# Module
|
|
912
|
+
Some intro.
|
|
913
|
+
*/
|
|
914
|
+
|
|
915
|
+
function add(a: 0, b: 0): 0 {
|
|
916
|
+
return a + b
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
test 'add(2, 3) is 5' {
|
|
920
|
+
expect(add(2, 3)).toBe(5)
|
|
921
|
+
}
|
|
922
|
+
`
|
|
923
|
+
const md = generateDocsMarkdown(source)
|
|
924
|
+
expect(md).toContain('# Module')
|
|
925
|
+
expect(md).toContain('## add')
|
|
926
|
+
expect(md).toContain('### add(2, 3) is 5 (test cases)')
|
|
927
|
+
expect(md).toContain('add(2, 3) // → 5')
|
|
928
|
+
})
|
|
929
|
+
})
|