tjs-lang 0.6.44 → 0.7.3

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 (86) hide show
  1. package/CLAUDE.md +85 -422
  2. package/README.md +15 -82
  3. package/bin/benchmarks.ts +7 -7
  4. package/bin/dev.ts +2 -1
  5. package/demo/autocomplete.test.ts +1 -1
  6. package/demo/docs.json +744 -48
  7. package/demo/src/demo-nav.ts +5 -5
  8. package/demo/src/index.ts +28 -36
  9. package/demo/src/module-sw.ts +1 -1
  10. package/demo/src/playground-shared.ts +17 -17
  11. package/demo/src/playground.ts +13 -1
  12. package/demo/src/style.ts +4 -1
  13. package/demo/src/tjs-playground.ts +5 -5
  14. package/demo/src/user-store.ts +2 -1
  15. package/demo/static/favicon.svg +17 -24
  16. package/demo/static/tosi-platform.json +9304 -0
  17. package/dist/index.js +158 -156
  18. package/dist/index.js.map +14 -13
  19. package/dist/scripts/compat-effect.d.ts +16 -0
  20. package/dist/scripts/compat-kysely.d.ts +13 -0
  21. package/dist/scripts/compat-radash.d.ts +13 -0
  22. package/dist/scripts/compat-superstruct.d.ts +13 -0
  23. package/dist/scripts/compat-ts-pattern.d.ts +13 -0
  24. package/dist/scripts/compat-zod.d.ts +12 -0
  25. package/dist/src/lang/emitters/from-ts.d.ts +1 -1
  26. package/dist/src/lang/emitters/js-tests.d.ts +4 -0
  27. package/dist/src/lang/emitters/js.d.ts +2 -2
  28. package/dist/src/lang/index.d.ts +1 -0
  29. package/dist/src/lang/json-schema.d.ts +40 -0
  30. package/dist/src/lang/parser-transforms.d.ts +14 -0
  31. package/dist/src/lang/runtime.d.ts +39 -6
  32. package/dist/src/types/Type.d.ts +5 -0
  33. package/dist/tjs-full.js +158 -156
  34. package/dist/tjs-full.js.map +14 -13
  35. package/dist/tjs-vm.js +44 -43
  36. package/dist/tjs-vm.js.map +5 -5
  37. package/docs/README.md +21 -20
  38. package/docs/WASM-QUICKSTART.md +283 -0
  39. package/docs/diagrams/architecture-shift.svg +117 -0
  40. package/docs/diagrams/compile-runtime.svg +130 -0
  41. package/docs/diagrams/icon-riff-1.svg +55 -0
  42. package/docs/diagrams/icon-riff-2.svg +62 -0
  43. package/docs/diagrams/icon-riff-3.svg +61 -0
  44. package/docs/diagrams/platform-overview.svg +114 -0
  45. package/docs/diagrams/safe-eval.svg +147 -0
  46. package/docs/eval-v4/arch-comparison.svg +277 -0
  47. package/docs/eval-v4/bundler-tree.svg +250 -0
  48. package/docs/eval-v4/http-lifecycle.svg +148 -0
  49. package/docs/function-predicate-design.md +8 -8
  50. package/docs/native-engine-integration.md +2 -2
  51. package/editors/codemirror/autocomplete.test.ts +29 -29
  52. package/package.json +10 -4
  53. package/src/cli/commands/convert.test.ts +11 -8
  54. package/src/cli/tjs.ts +1 -1
  55. package/src/lang/codegen.test.ts +117 -112
  56. package/src/lang/docs.test.ts +22 -22
  57. package/src/lang/docs.ts +5 -8
  58. package/src/lang/emitters/dts.test.ts +13 -13
  59. package/src/lang/emitters/from-ts.ts +36 -9
  60. package/src/lang/emitters/js-tests.ts +143 -28
  61. package/src/lang/emitters/js.ts +49 -28
  62. package/src/lang/features.test.ts +259 -43
  63. package/src/lang/from-ts.test.ts +3 -3
  64. package/src/lang/function-predicate.test.ts +1 -1
  65. package/src/lang/index.ts +8 -47
  66. package/src/lang/json-schema.test.ts +261 -0
  67. package/src/lang/json-schema.ts +167 -0
  68. package/src/lang/parser-params.ts +28 -44
  69. package/src/lang/parser-transforms.ts +255 -0
  70. package/src/lang/parser.test.ts +32 -13
  71. package/src/lang/parser.ts +49 -11
  72. package/src/lang/perf.test.ts +11 -11
  73. package/src/lang/roundtrip.test.ts +3 -3
  74. package/src/lang/runtime.test.ts +167 -0
  75. package/src/lang/runtime.ts +234 -46
  76. package/src/lang/transpiler.test.ts +21 -21
  77. package/src/lang/typescript-syntax.test.ts +11 -9
  78. package/src/types/Type.ts +38 -1
  79. package/src/use-cases/bootstrap.test.ts +7 -7
  80. package/src/use-cases/client-server.test.ts +1 -1
  81. package/src/use-cases/malicious-actor.test.ts +1 -1
  82. package/src/use-cases/rag-processor.test.ts +1 -1
  83. package/src/use-cases/sophisticated-agents.test.ts +2 -2
  84. package/src/use-cases/transpiler-llm.test.ts +1 -1
  85. package/src/use-cases/unbundled-imports.test.ts +9 -9
  86. package/tjs-lang.svg +17 -25
@@ -0,0 +1,261 @@
1
+ import { describe, it, expect } from 'bun:test'
2
+ import {
3
+ typeDescriptorToJSONSchema,
4
+ exampleToJSONSchema,
5
+ functionMetaToJSONSchema,
6
+ } from './json-schema'
7
+ // Also used directly in tests below
8
+ import type { TypeDescriptor } from './types'
9
+ import { Type, TString, TInteger } from '../types/Type'
10
+ import { tjs } from './index'
11
+ import { createRuntime, installRuntime } from './runtime'
12
+
13
+ describe('json-schema', () => {
14
+ describe('typeDescriptorToJSONSchema', () => {
15
+ it('handles primitives', () => {
16
+ expect(typeDescriptorToJSONSchema({ kind: 'string' })).toEqual({
17
+ type: 'string',
18
+ })
19
+ expect(typeDescriptorToJSONSchema({ kind: 'number' })).toEqual({
20
+ type: 'number',
21
+ })
22
+ expect(typeDescriptorToJSONSchema({ kind: 'integer' })).toEqual({
23
+ type: 'integer',
24
+ })
25
+ expect(typeDescriptorToJSONSchema({ kind: 'boolean' })).toEqual({
26
+ type: 'boolean',
27
+ })
28
+ expect(typeDescriptorToJSONSchema({ kind: 'null' })).toEqual({
29
+ type: 'null',
30
+ })
31
+ })
32
+
33
+ it('handles non-negative-integer', () => {
34
+ expect(
35
+ typeDescriptorToJSONSchema({ kind: 'non-negative-integer' })
36
+ ).toEqual({ type: 'integer', minimum: 0 })
37
+ })
38
+
39
+ it('handles any and undefined', () => {
40
+ expect(typeDescriptorToJSONSchema({ kind: 'any' })).toEqual({})
41
+ expect(typeDescriptorToJSONSchema({ kind: 'undefined' })).toEqual({})
42
+ })
43
+
44
+ it('handles arrays', () => {
45
+ expect(
46
+ typeDescriptorToJSONSchema({
47
+ kind: 'array',
48
+ items: { kind: 'string' },
49
+ })
50
+ ).toEqual({ type: 'array', items: { type: 'string' } })
51
+ })
52
+
53
+ it('handles objects', () => {
54
+ const td: TypeDescriptor = {
55
+ kind: 'object',
56
+ shape: {
57
+ name: { kind: 'string' },
58
+ age: { kind: 'integer' },
59
+ },
60
+ }
61
+ expect(typeDescriptorToJSONSchema(td)).toEqual({
62
+ type: 'object',
63
+ properties: {
64
+ name: { type: 'string' },
65
+ age: { type: 'integer' },
66
+ },
67
+ required: ['name', 'age'],
68
+ additionalProperties: false,
69
+ })
70
+ })
71
+
72
+ it('handles nullable', () => {
73
+ expect(
74
+ typeDescriptorToJSONSchema({ kind: 'string', nullable: true })
75
+ ).toEqual({ anyOf: [{ type: 'string' }, { type: 'null' }] })
76
+ })
77
+
78
+ it('handles unions', () => {
79
+ expect(
80
+ typeDescriptorToJSONSchema({
81
+ kind: 'union',
82
+ members: [{ kind: 'string' }, { kind: 'integer' }],
83
+ })
84
+ ).toEqual({ anyOf: [{ type: 'string' }, { type: 'integer' }] })
85
+ })
86
+
87
+ it('handles nested objects', () => {
88
+ const td: TypeDescriptor = {
89
+ kind: 'object',
90
+ shape: {
91
+ user: {
92
+ kind: 'object',
93
+ shape: { name: { kind: 'string' } },
94
+ },
95
+ tags: {
96
+ kind: 'array',
97
+ items: { kind: 'string' },
98
+ },
99
+ },
100
+ }
101
+ const schema = typeDescriptorToJSONSchema(td)
102
+ expect(schema.properties?.user).toEqual({
103
+ type: 'object',
104
+ properties: { name: { type: 'string' } },
105
+ required: ['name'],
106
+ additionalProperties: false,
107
+ })
108
+ expect(schema.properties?.tags).toEqual({
109
+ type: 'array',
110
+ items: { type: 'string' },
111
+ })
112
+ })
113
+ })
114
+
115
+ describe('exampleToJSONSchema', () => {
116
+ it('infers from primitive examples', () => {
117
+ expect(exampleToJSONSchema('hello')).toEqual({ type: 'string' })
118
+ expect(exampleToJSONSchema(42)).toEqual({ type: 'integer' })
119
+ expect(exampleToJSONSchema(3.14)).toEqual({ type: 'number' })
120
+ expect(exampleToJSONSchema(true)).toEqual({ type: 'boolean' })
121
+ expect(exampleToJSONSchema(null)).toEqual({ type: 'null' })
122
+ })
123
+
124
+ it('infers from object examples', () => {
125
+ expect(exampleToJSONSchema({ name: '', age: 0 })).toEqual({
126
+ type: 'object',
127
+ properties: {
128
+ name: { type: 'string' },
129
+ age: { type: 'integer' },
130
+ },
131
+ required: ['name', 'age'],
132
+ additionalProperties: false,
133
+ })
134
+ })
135
+
136
+ it('infers from array examples', () => {
137
+ expect(exampleToJSONSchema(['hello'])).toEqual({
138
+ type: 'array',
139
+ items: { type: 'string' },
140
+ })
141
+ expect(exampleToJSONSchema([])).toEqual({ type: 'array' })
142
+ })
143
+ })
144
+
145
+ describe('RuntimeType.toJSONSchema', () => {
146
+ it('generates schema from example-based types', () => {
147
+ const User = Type('user', { name: '', age: 0 })
148
+ const schema = User.toJSONSchema()
149
+ expect(schema.type).toBe('object')
150
+ expect(schema.properties?.name).toEqual({ type: 'string' })
151
+ expect(schema.properties?.age).toEqual({ type: 'integer' })
152
+ })
153
+
154
+ it('generates schema for predicate-only types', () => {
155
+ const schema = TString.toJSONSchema()
156
+ // Predicate-only types have no example or schema, just description
157
+ expect(schema.description).toBe('string')
158
+ })
159
+
160
+ it('generates schema from simple types', () => {
161
+ const Name = Type('name', 'Alice')
162
+ const schema = Name.toJSONSchema()
163
+ expect(schema.type).toBe('string')
164
+ })
165
+ })
166
+
167
+ describe('RuntimeType.strip', () => {
168
+ it('strips extra fields from objects', () => {
169
+ const User = Type('user', { name: '', age: 0 })
170
+ const input = { name: 'Alice', age: 30, secret: 'password' }
171
+ const stripped = User.strip(input) as any
172
+ expect(stripped.name).toBe('Alice')
173
+ expect(stripped.age).toBe(30)
174
+ expect(stripped.secret).toBeUndefined()
175
+ })
176
+
177
+ it('returns value as-is for predicate-only types', () => {
178
+ const result = TString.strip('hello')
179
+ expect(result).toBe('hello')
180
+ })
181
+ })
182
+
183
+ describe('functionMetaToJSONSchema', () => {
184
+ it('generates input/output schema from function metadata', () => {
185
+ const meta = {
186
+ params: {
187
+ name: {
188
+ type: { kind: 'string' as const },
189
+ required: true,
190
+ example: 'Alice',
191
+ },
192
+ age: {
193
+ type: { kind: 'integer' as const },
194
+ required: true,
195
+ example: 0,
196
+ },
197
+ },
198
+ returns: {
199
+ type: {
200
+ kind: 'object' as const,
201
+ shape: { id: { kind: 'integer' as const } },
202
+ },
203
+ },
204
+ }
205
+
206
+ const { input, output } = functionMetaToJSONSchema(meta)
207
+ expect(input.type).toBe('object')
208
+ expect(input.properties?.name).toEqual({
209
+ type: 'string',
210
+ examples: ['Alice'],
211
+ })
212
+ expect(input.required).toContain('name')
213
+ expect(input.required).toContain('age')
214
+ expect(output?.type).toBe('object')
215
+ })
216
+ })
217
+
218
+ describe('fn.__tjs.schema() on transpiled functions', () => {
219
+ const savedTjs = globalThis.__tjs
220
+
221
+ it('provides schema via functionMetaToJSONSchema on emitted metadata', () => {
222
+ const runtime = createRuntime()
223
+ try {
224
+ globalThis.__tjs = runtime
225
+ const result = tjs(`function greet(name: 'World'): 'Hello, World' {
226
+ return 'Hello, ' + name
227
+ }`)
228
+ const fn = new Function(result.code + '\nreturn greet')()
229
+ expect(fn.__tjs).toBeDefined()
230
+ // Use standalone function on the metadata
231
+ const { input, output } = functionMetaToJSONSchema(fn.__tjs)
232
+ expect(input.type).toBe('object')
233
+ expect(input.properties?.name?.type).toBe('string')
234
+ expect(output?.type).toBe('string')
235
+ } finally {
236
+ globalThis.__tjs = savedTjs
237
+ }
238
+ })
239
+
240
+ it('provides schema via .schema() when wrap() is used', () => {
241
+ const runtime = createRuntime()
242
+ try {
243
+ globalThis.__tjs = runtime
244
+ installRuntime()
245
+ const result = tjs(`function greet(name: 'World'): 'Hello, World' {
246
+ return 'Hello, ' + name
247
+ }`)
248
+ const fn = new Function(result.code + '\nreturn greet')()
249
+ // After installRuntime(), wrap() should have attached .schema()
250
+ if (typeof fn.__tjs.schema === 'function') {
251
+ const { input, output } = fn.__tjs.schema()
252
+ expect(input.type).toBe('object')
253
+ expect(input.properties?.name?.type).toBe('string')
254
+ expect(output?.type).toBe('string')
255
+ }
256
+ } finally {
257
+ globalThis.__tjs = savedTjs
258
+ }
259
+ })
260
+ })
261
+ })
@@ -0,0 +1,167 @@
1
+ /**
2
+ * JSON Schema generation from TJS TypeDescriptors and example values
3
+ *
4
+ * Converts TJS type information into standard JSON Schema (draft 2020-12).
5
+ */
6
+
7
+ import type { TypeDescriptor } from './types'
8
+
9
+ export interface JSONSchemaObject {
10
+ type?: string | string[]
11
+ properties?: Record<string, JSONSchemaObject>
12
+ items?: JSONSchemaObject | JSONSchemaObject[]
13
+ required?: string[]
14
+ additionalProperties?: boolean
15
+ anyOf?: JSONSchemaObject[]
16
+ minimum?: number
17
+ examples?: unknown[]
18
+ description?: string
19
+ [key: string]: unknown
20
+ }
21
+
22
+ /**
23
+ * Convert a TypeDescriptor to JSON Schema
24
+ */
25
+ export function typeDescriptorToJSONSchema(
26
+ td: TypeDescriptor
27
+ ): JSONSchemaObject {
28
+ if (td.nullable) {
29
+ const base = typeDescriptorToJSONSchema({ ...td, nullable: false })
30
+ return { anyOf: [base, { type: 'null' }] }
31
+ }
32
+
33
+ switch (td.kind) {
34
+ case 'string':
35
+ return { type: 'string' }
36
+ case 'number':
37
+ return { type: 'number' }
38
+ case 'integer':
39
+ return { type: 'integer' }
40
+ case 'non-negative-integer':
41
+ return { type: 'integer', minimum: 0 }
42
+ case 'boolean':
43
+ return { type: 'boolean' }
44
+ case 'null':
45
+ return { type: 'null' }
46
+ case 'undefined':
47
+ return {}
48
+ case 'any':
49
+ return {}
50
+ case 'array':
51
+ if (td.items) {
52
+ return { type: 'array', items: typeDescriptorToJSONSchema(td.items) }
53
+ }
54
+ return { type: 'array' }
55
+ case 'object':
56
+ if (td.shape) {
57
+ const properties: Record<string, JSONSchemaObject> = {}
58
+ const required: string[] = []
59
+ for (const [key, fieldTd] of Object.entries(td.shape)) {
60
+ properties[key] = typeDescriptorToJSONSchema(fieldTd)
61
+ required.push(key)
62
+ }
63
+ return {
64
+ type: 'object',
65
+ properties,
66
+ required,
67
+ additionalProperties: false,
68
+ }
69
+ }
70
+ return { type: 'object' }
71
+ case 'union':
72
+ if (td.members) {
73
+ return { anyOf: td.members.map(typeDescriptorToJSONSchema) }
74
+ }
75
+ return {}
76
+ default:
77
+ return {}
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Infer a JSON Schema from an example value (without going through TypeDescriptor)
83
+ */
84
+ export function exampleToJSONSchema(value: unknown): JSONSchemaObject {
85
+ if (value === null) return { type: 'null' }
86
+ if (value === undefined) return {}
87
+
88
+ switch (typeof value) {
89
+ case 'string':
90
+ return { type: 'string' }
91
+ case 'number':
92
+ return Number.isInteger(value) ? { type: 'integer' } : { type: 'number' }
93
+ case 'boolean':
94
+ return { type: 'boolean' }
95
+ case 'object': {
96
+ if (Array.isArray(value)) {
97
+ if (value.length === 0) return { type: 'array' }
98
+ // Infer item type from first element
99
+ return { type: 'array', items: exampleToJSONSchema(value[0]) }
100
+ }
101
+ // Object
102
+ const properties: Record<string, JSONSchemaObject> = {}
103
+ const required: string[] = []
104
+ for (const [key, val] of Object.entries(
105
+ value as Record<string, unknown>
106
+ )) {
107
+ properties[key] = exampleToJSONSchema(val)
108
+ required.push(key)
109
+ }
110
+ return {
111
+ type: 'object',
112
+ properties,
113
+ required,
114
+ additionalProperties: false,
115
+ }
116
+ }
117
+ default:
118
+ return {}
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Generate JSON Schema for function parameters (as an object schema)
124
+ * and return type from __tjs metadata
125
+ */
126
+ export function functionMetaToJSONSchema(meta: {
127
+ params: Record<string, any>
128
+ returns?: { type: any; example?: any }
129
+ }): { input: JSONSchemaObject; output?: JSONSchemaObject } {
130
+ const properties: Record<string, JSONSchemaObject> = {}
131
+ const required: string[] = []
132
+
133
+ for (const [name, paramInfo] of Object.entries(meta.params)) {
134
+ if (paramInfo?.type?.kind) {
135
+ // Has TypeDescriptor
136
+ properties[name] = typeDescriptorToJSONSchema(paramInfo.type)
137
+ } else if (paramInfo?.example !== undefined) {
138
+ // Has example value
139
+ properties[name] = exampleToJSONSchema(paramInfo.example)
140
+ } else {
141
+ properties[name] = {}
142
+ }
143
+ if (paramInfo?.required !== false) {
144
+ required.push(name)
145
+ }
146
+ if (paramInfo?.example !== undefined) {
147
+ properties[name].examples = [paramInfo.example]
148
+ }
149
+ }
150
+
151
+ const input: JSONSchemaObject = {
152
+ type: 'object',
153
+ properties,
154
+ required,
155
+ }
156
+
157
+ let output: JSONSchemaObject | undefined
158
+ if (meta.returns) {
159
+ if (meta.returns.type?.kind) {
160
+ output = typeDescriptorToJSONSchema(meta.returns.type)
161
+ } else if (meta.returns.example !== undefined) {
162
+ output = exampleToJSONSchema(meta.returns.example)
163
+ }
164
+ }
165
+
166
+ return { input, output }
167
+ }
@@ -307,34 +307,28 @@ export function transformParenExpressions(
307
307
  const processedParams = processParamString(params, ctx, true)
308
308
  result += processedParams + ')'
309
309
 
310
- // Check what follows the closing paren: whitespace then -> or -? or -! (return type)
310
+ // Check what follows the closing paren: return type annotation (:, :?, :!)
311
311
  let j = i
312
312
  while (j < source.length && /\s/.test(source[j])) j++
313
313
 
314
- const returnArrow = source.slice(j, j + 2)
315
- if (
316
- returnArrow === '->' ||
317
- returnArrow === '-?' ||
318
- returnArrow === '-!'
319
- ) {
320
- // Extract return type
321
- j += 2
322
- // Skip whitespace after arrow
314
+ if (source[j] === ':') {
315
+ const colonMarker = source.slice(j, j + 2)
316
+ let safety: 'safe' | 'unsafe' | undefined
317
+ if (colonMarker === ':?' || colonMarker === ':!') {
318
+ j += 2
319
+ safety = colonMarker === ':?' ? 'safe' : 'unsafe'
320
+ } else {
321
+ j += 1
322
+ }
323
323
  while (j < source.length && /\s/.test(source[j])) j++
324
324
 
325
325
  const typeResult = extractReturnTypeValue(source, j)
326
326
  if (typeResult) {
327
- const { type, endPos: typeEnd } = typeResult
328
- // Record first return type for metadata
329
327
  if (firstReturnType === undefined) {
330
- firstReturnType = type
331
- if (returnArrow === '-?') {
332
- firstReturnSafety = 'safe'
333
- } else if (returnArrow === '-!') {
334
- firstReturnSafety = 'unsafe'
335
- }
328
+ firstReturnType = typeResult.type
329
+ if (safety) firstReturnSafety = safety
336
330
  }
337
- i = typeEnd
331
+ i = typeResult.endPos
338
332
  }
339
333
  }
340
334
  continue
@@ -398,27 +392,17 @@ export function transformParenExpressions(
398
392
  const processedParams = processParamString(params, ctx, true)
399
393
  result += processedParams + ')'
400
394
 
401
- // Check for return type annotation: ) -> type, ) -! type, ) -? type, ): type
395
+ // Check for return type annotation: ): type, ):! type, ):? type
402
396
  let j = i
403
397
  while (j < source.length && /\s/.test(source[j])) j++
404
398
 
405
- // Handle ->, -!, -? return type (TJS style)
406
- const returnArrow = source.slice(j, j + 2)
407
- if (
408
- returnArrow === '->' ||
409
- returnArrow === '-!' ||
410
- returnArrow === '-?'
411
- ) {
412
- j += 2
413
- while (j < source.length && /\s/.test(source[j])) j++
414
- const typeResult = extractReturnTypeValue(source, j)
415
- if (typeResult) {
416
- i = typeResult.endPos
399
+ if (source[j] === ':') {
400
+ const colonMarker = source.slice(j, j + 2)
401
+ if (colonMarker === ':?' || colonMarker === ':!') {
402
+ j += 2
403
+ } else {
404
+ j++
417
405
  }
418
- }
419
- // Handle : return type (TS style) - just strip it
420
- else if (source[j] === ':') {
421
- j++
422
406
  while (j < source.length && /\s/.test(source[j])) j++
423
407
  const typeResult = extractReturnTypeValue(source, j)
424
408
  if (typeResult) {
@@ -448,15 +432,15 @@ export function transformParenExpressions(
448
432
  let j = endPos
449
433
  while (j < source.length && /\s/.test(source[j])) j++
450
434
 
451
- // Check for return type annotation on arrow function: ) -> type =>
435
+ // Check for return type annotation on arrow function: ): type =>
452
436
  let arrowReturnType: string | undefined
453
- const returnArrow = source.slice(j, j + 2)
454
- if (
455
- returnArrow === '->' ||
456
- returnArrow === '-?' ||
457
- returnArrow === '-!'
458
- ) {
459
- j += 2
437
+ if (source[j] === ':') {
438
+ const colonMarker = source.slice(j, j + 2)
439
+ if (colonMarker === ':?' || colonMarker === ':!') {
440
+ j += 2
441
+ } else {
442
+ j++
443
+ }
460
444
  while (j < source.length && /\s/.test(source[j])) j++
461
445
  const typeResult = extractReturnTypeValue(source, j)
462
446
  if (typeResult) {