• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jsdoc-type-pratt-parser / jsdoc-type-pratt-parser / 17481381036

05 Sep 2025 01:51AM UTC coverage: 98.639% (+0.7%) from 97.96%
17481381036

push

github

brettz9
feat: add ESM export; fixes #173

BREAKING CHANGE:

Adds `exports`

777 of 788 branches covered (98.6%)

Branch coverage included in aggregate %.

4005 of 4060 relevant lines covered (98.65%)

1434.64 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.55
/src/transforms/jtpTransform.ts
1
import { extractSpecialParams, notAvailableTransform, transform, type TransformRules } from './transform'
1✔
2
import { type QuoteStyle, type RootResult } from '../result/RootResult'
1✔
3
import { assertRootResult } from '../assertTypes'
1✔
4
import { type NonRootResult } from '../result/NonRootResult'
1✔
5

1✔
6
export type JtpResult =
1✔
7
  JtpNameResult
1✔
8
  | JtpNullableResult
1✔
9
  | JtpNotNullableResult
1✔
10
  | JtpOptionalResult
1✔
11
  | JtpVariadicResult
1✔
12
  | JtpTypeOfResult
1✔
13
  | JtpTupleResult
1✔
14
  | JtpKeyOfResult
1✔
15
  | JtpStringValueResult
1✔
16
  | JtpImportResult
1✔
17
  | JtpAnyResult
1✔
18
  | JtpUnknownResult
1✔
19
  | JtpFunctionResult
1✔
20
  | JtpGenericResult
1✔
21
  | JtpRecordEntryResult
1✔
22
  | JtpRecordResult
1✔
23
  | JtpMemberResult
1✔
24
  | JtpUnionResult
1✔
25
  | JtpParenthesisResult
1✔
26
  | JtpNamedParameterResult
1✔
27
  | JtpModuleResult
1✔
28
  | JtpFilePath
1✔
29
  | JtpIntersectionResult
1✔
30
  | JtpNumberResult
1✔
31

1✔
32
type JtpQuoteStyle = 'single' | 'double' | 'none'
1✔
33

1✔
34
export interface JtpNullableResult {
1✔
35
  type: 'NULLABLE'
1✔
36
  value: JtpResult
1✔
37
  meta: {
1✔
38
    syntax: 'PREFIX_QUESTION_MARK' | 'SUFFIX_QUESTION_MARK'
1✔
39
  }
1✔
40
}
1✔
41

1✔
42
export interface JtpNotNullableResult {
1✔
43
  type: 'NOT_NULLABLE'
1✔
44
  value: JtpResult
1✔
45
  meta: {
1✔
46
    syntax: 'PREFIX_BANG' | 'SUFFIX_BANG'
1✔
47
  }
1✔
48
}
1✔
49

1✔
50
export interface JtpOptionalResult {
1✔
51
  type: 'OPTIONAL'
1✔
52
  value: JtpResult
1✔
53
  meta: {
1✔
54
    syntax: 'PREFIX_EQUAL_SIGN' | 'SUFFIX_EQUALS_SIGN' | 'SUFFIX_KEY_QUESTION_MARK'
1✔
55
  }
1✔
56
}
1✔
57

1✔
58
export interface JtpVariadicResult {
1✔
59
  type: 'VARIADIC'
1✔
60
  value?: JtpResult
1✔
61
  meta: {
1✔
62
    syntax: 'PREFIX_DOTS' | 'SUFFIX_DOTS' | 'ONLY_DOTS'
1✔
63
  }
1✔
64
}
1✔
65

1✔
66
export interface JtpNameResult {
1✔
67
  type: 'NAME'
1✔
68
  name: string
1✔
69
}
1✔
70

1✔
71
export interface JtpTypeOfResult {
1✔
72
  type: 'TYPE_QUERY'
1✔
73
  name?: JtpResult
1✔
74
}
1✔
75

1✔
76
export interface JtpKeyOfResult {
1✔
77
  type: 'KEY_QUERY'
1✔
78
  value?: JtpResult
1✔
79
}
1✔
80

1✔
81
export interface JtpTupleResult {
1✔
82
  type: 'TUPLE'
1✔
83
  entries: JtpResult[]
1✔
84
}
1✔
85

1✔
86
export interface JtpStringValueResult {
1✔
87
  type: 'STRING_VALUE'
1✔
88
  quoteStyle: JtpQuoteStyle
1✔
89
  string: string
1✔
90
}
1✔
91

1✔
92
export interface JtpImportResult {
1✔
93
  type: 'IMPORT'
1✔
94
  path: JtpStringValueResult
1✔
95
}
1✔
96

1✔
97
export interface JtpAnyResult {
1✔
98
  type: 'ANY'
1✔
99
}
1✔
100

1✔
101
export interface JtpUnknownResult {
1✔
102
  type: 'UNKNOWN'
1✔
103
}
1✔
104

1✔
105
export interface JtpFunctionResult {
1✔
106
  type: 'FUNCTION' | 'ARROW'
1✔
107
  params: JtpResult[]
1✔
108
  returns: JtpResult | null
1✔
109
  new: JtpResult | null
1✔
110
  this?: JtpResult | null
1✔
111
}
1✔
112

1✔
113
export interface JtpGenericResult {
1✔
114
  type: 'GENERIC'
1✔
115
  subject: JtpResult
1✔
116
  objects: JtpResult[]
1✔
117
  meta: {
1✔
118
    syntax: 'ANGLE_BRACKET' | 'ANGLE_BRACKET_WITH_DOT' | 'SQUARE_BRACKET'
1✔
119
  }
1✔
120
}
1✔
121

1✔
122
export interface JtpRecordEntryResult {
1✔
123
  type: 'RECORD_ENTRY'
1✔
124
  key: string
1✔
125
  quoteStyle: JtpQuoteStyle
1✔
126
  value: JtpResult | null
1✔
127
  readonly: false
1✔
128
}
1✔
129

1✔
130
export interface JtpRecordResult {
1✔
131
  type: 'RECORD'
1✔
132
  entries: JtpRecordEntryResult[]
1✔
133
}
1✔
134

1✔
135
export interface JtpMemberResult {
1✔
136
  type: 'MEMBER' | 'INNER_MEMBER' | 'INSTANCE_MEMBER'
1✔
137
  owner: JtpResult
1✔
138
  name: string
1✔
139
  quoteStyle: JtpQuoteStyle
1✔
140
  hasEventPrefix: boolean
1✔
141
}
1✔
142

1✔
143
export interface JtpUnionResult {
1✔
144
  type: 'UNION'
1✔
145
  left: JtpResult
1✔
146
  right: JtpResult
1✔
147
}
1✔
148

1✔
149
export interface JtpIntersectionResult {
1✔
150
  type: 'INTERSECTION'
1✔
151
  left: JtpResult
1✔
152
  right: JtpResult
1✔
153
}
1✔
154

1✔
155
export interface JtpParenthesisResult {
1✔
156
  type: 'PARENTHESIS'
1✔
157
  value: JtpResult
1✔
158
}
1✔
159

1✔
160
export interface JtpNamedParameterResult {
1✔
161
  type: 'NAMED_PARAMETER'
1✔
162
  name: string
1✔
163
  typeName: JtpResult
1✔
164
}
1✔
165

1✔
166
export interface JtpModuleResult {
1✔
167
  type: 'MODULE'
1✔
168
  value: JtpResult
1✔
169
}
1✔
170

1✔
171
export interface JtpFilePath {
1✔
172
  type: 'FILE_PATH'
1✔
173
  quoteStyle: JtpQuoteStyle
1✔
174
  path: string
1✔
175
}
1✔
176

1✔
177
export interface JtpNumberResult {
1✔
178
  type: 'NUMBER_VALUE'
1✔
179
  number: string
1✔
180
}
1✔
181

1✔
182
function getQuoteStyle (quote: QuoteStyle | undefined): JtpQuoteStyle {
248✔
183
  switch (quote) {
248✔
184
    case undefined:
248✔
185
      return 'none'
203✔
186
    case 'single':
248✔
187
      return 'single'
14✔
188
    case 'double':
248✔
189
      return 'double'
31✔
190
  }
248✔
191
}
248✔
192

1✔
193
function getMemberType (type: 'property' | 'inner' | 'instance' | 'property-brackets'): JtpMemberResult['type'] {
129✔
194
  switch (type) {
129✔
195
    case 'inner':
129✔
196
      return 'INNER_MEMBER'
12✔
197
    case 'instance':
129✔
198
      return 'INSTANCE_MEMBER'
10✔
199
    case 'property':
129✔
200
      return 'MEMBER'
106✔
201
    case 'property-brackets':
129✔
202
      return 'MEMBER'
1✔
203
  }
129✔
204
}
129✔
205

1✔
206
function nestResults (type: 'UNION' | 'INTERSECTION', results: JtpResult[]): JtpResult {
158✔
207
  if (results.length === 2) {
158✔
208
    return {
121✔
209
      type,
121✔
210
      left: results[0],
121✔
211
      right: results[1]
121✔
212
    }
121✔
213
  } else {
158✔
214
    return {
37✔
215
      type,
37✔
216
      left: results[0],
37✔
217
      right: nestResults(type, results.slice(1))
37✔
218
    }
37✔
219
  }
37✔
220
}
158✔
221

1✔
222
const jtpRules: TransformRules<JtpResult> = {
1✔
223
  JsdocTypeOptional: (result, transform) => ({
1✔
224
    type: 'OPTIONAL',
52✔
225
    value: transform(result.element),
52✔
226
    meta: {
52✔
227
      syntax: result.meta.position === 'prefix' ? 'PREFIX_EQUAL_SIGN' : 'SUFFIX_EQUALS_SIGN'
52✔
228
    }
52✔
229
  }),
1✔
230

1✔
231
  JsdocTypeNullable: (result, transform) => ({
1✔
232
    type: 'NULLABLE',
34✔
233
    value: transform(result.element),
34✔
234
    meta: {
34✔
235
      syntax: result.meta.position === 'prefix' ? 'PREFIX_QUESTION_MARK' : 'SUFFIX_QUESTION_MARK'
34✔
236
    }
34✔
237
  }),
1✔
238

1✔
239
  JsdocTypeNotNullable: (result, transform) => ({
1✔
240
    type: 'NOT_NULLABLE',
43✔
241
    value: transform(result.element),
43✔
242
    meta: {
43✔
243
      syntax: result.meta.position === 'prefix' ? 'PREFIX_BANG' : 'SUFFIX_BANG'
43✔
244
    }
43✔
245
  }),
1✔
246

1✔
247
  JsdocTypeVariadic: (result, transform) => {
1✔
248
    const transformed: JtpVariadicResult = {
87✔
249
      type: 'VARIADIC',
87✔
250
      meta: {
87✔
251
        syntax: result.meta.position === 'prefix'
87✔
252
          ? 'PREFIX_DOTS'
87✔
253
          : result.meta.position === 'suffix' ? 'SUFFIX_DOTS' : 'ONLY_DOTS'
87✔
254
      }
87✔
255
    }
87✔
256
    if (result.element !== undefined) {
87✔
257
      transformed.value = transform(result.element)
86✔
258
    }
86✔
259

87✔
260
    return transformed
87✔
261
  },
1✔
262

1✔
263
  JsdocTypeName: result => ({
1✔
264
    type: 'NAME',
903✔
265
    name: result.value
903✔
266
  }),
1✔
267

1✔
268
  JsdocTypeTypeof: (result, transform) => ({
1✔
269
    type: 'TYPE_QUERY',
39✔
270
    name: transform(result.element)
39✔
271
  }),
1✔
272

1✔
273
  JsdocTypeTuple: (result, transform) => ({
1✔
274
    type: 'TUPLE',
34✔
275
    entries: (result.elements as NonRootResult[]).map(transform)
34✔
276
  }),
1✔
277

1✔
278
  JsdocTypeKeyof: (result, transform) => ({
1✔
279
    type: 'KEY_QUERY',
28✔
280
    value: transform(result.element)
28✔
281
  }),
1✔
282

1✔
283
  JsdocTypeImport: result => ({
1✔
284
    type: 'IMPORT',
12✔
285
    path: {
12✔
286
      type: 'STRING_VALUE',
12✔
287
      quoteStyle: getQuoteStyle(result.element.meta.quote),
12✔
288
      string: result.element.value
12✔
289
    }
12✔
290
  }),
1✔
291

1✔
292
  JsdocTypeUndefined: () => ({
1✔
293
    type: 'NAME',
20✔
294
    name: 'undefined'
20✔
295
  }),
1✔
296

1✔
297
  JsdocTypeAny: () => ({
1✔
298
    type: 'ANY'
18✔
299
  }),
1✔
300

1✔
301
  JsdocTypeFunction: (result, transform) => {
1✔
302
    const specialParams = extractSpecialParams(result)
150✔
303

150✔
304
    const transformed: JtpFunctionResult = {
150✔
305
      type: result.arrow ? 'ARROW' : 'FUNCTION',
150✔
306
      params: specialParams.params.map(param => {
150✔
307
        if (param.type === 'JsdocTypeKeyValue') {
115✔
308
          if (param.right === undefined) {
21✔
309
            throw new Error('Function parameter without \':\' is not expected to be \'KEY_VALUE\'')
1✔
310
          }
1✔
311
          return {
20✔
312
            type: 'NAMED_PARAMETER',
20✔
313
            name: param.key,
20✔
314
            typeName: transform(param.right)
20✔
315
          }
20✔
316
        } else {
115✔
317
          return transform(param)
94✔
318
        }
94✔
319
      }),
150✔
320
      new: null,
150✔
321
      returns: null
150✔
322
    }
150✔
323

150✔
324
    if (specialParams.this !== undefined) {
150✔
325
      transformed.this = transform(specialParams.this)
21✔
326
    } else if (!result.arrow) {
150✔
327
      transformed.this = null
100✔
328
    }
100✔
329

149✔
330
    if (specialParams.new !== undefined) {
150✔
331
      transformed.new = transform(specialParams.new)
16✔
332
    }
16✔
333

149✔
334
    if (result.returnType !== undefined) {
150✔
335
      transformed.returns = transform(result.returnType)
108✔
336
    }
108✔
337

149✔
338
    return transformed
149✔
339
  },
1✔
340

1✔
341
  JsdocTypeGeneric: (result, transform) => {
1✔
342
    const transformed: JtpGenericResult = {
107✔
343
      type: 'GENERIC',
107✔
344
      subject: transform(result.left),
107✔
345
      objects: result.elements.map(transform),
107✔
346
      meta: {
107✔
347
        syntax: result.meta.brackets === 'square' ? 'SQUARE_BRACKET' : result.meta.dot ? 'ANGLE_BRACKET_WITH_DOT' : 'ANGLE_BRACKET'
107✔
348
      }
107✔
349
    }
107✔
350

107✔
351
    if (result.meta.brackets === 'square' && result.elements[0].type === 'JsdocTypeFunction' && !result.elements[0].parenthesis) {
107✔
352
      transformed.objects[0] = {
1✔
353
        type: 'NAME',
1✔
354
        name: 'function'
1✔
355
      }
1✔
356
    }
1✔
357

107✔
358
    return transformed
107✔
359
  },
1✔
360

1✔
361
  JsdocTypeObjectField: (result, transform) => {
1✔
362
    if (typeof result.key !== 'string') {
90✔
363
      throw new Error('Index signatures and mapped types are not supported')
1✔
364
    }
1✔
365

89✔
366
    if (result.right === undefined) {
90✔
367
      return {
8✔
368
        type: 'RECORD_ENTRY',
8✔
369
        key: result.key,
8✔
370
        quoteStyle: getQuoteStyle(result.meta.quote),
8✔
371
        value: null,
8✔
372
        readonly: false
8✔
373
      }
8✔
374
    }
8✔
375

81✔
376
    let right = transform(result.right)
81✔
377
    if (result.optional) {
90✔
378
      right = {
2✔
379
        type: 'OPTIONAL',
2✔
380
        value: right,
2✔
381
        meta: {
2✔
382
          syntax: 'SUFFIX_KEY_QUESTION_MARK'
2✔
383
        }
2✔
384
      }
2✔
385
    }
2✔
386

81✔
387
    return {
81✔
388
      type: 'RECORD_ENTRY',
81✔
389
      key: result.key.toString(),
81✔
390
      quoteStyle: getQuoteStyle(result.meta.quote),
81✔
391
      value: right,
81✔
392
      readonly: false
81✔
393
    }
81✔
394
  },
1✔
395

1✔
396
  JsdocTypeJsdocObjectField: () => {
1✔
397
    throw new Error('Keys may not be typed in jsdoctypeparser.')
1✔
398
  },
1✔
399

1✔
400
  JsdocTypeKeyValue: (result, transform) => {
1✔
401
    if (result.right === undefined) {
×
402
      return {
×
403
        type: 'RECORD_ENTRY',
×
404
        key: result.key,
×
405
        quoteStyle: 'none',
×
406
        value: null,
×
407
        readonly: false
×
408
      }
×
409
    }
×
410

×
411
    let right = transform(result.right)
×
412
    if (result.optional) {
×
413
      right = {
×
414
        type: 'OPTIONAL',
×
415
        value: right,
×
416
        meta: {
×
417
          syntax: 'SUFFIX_KEY_QUESTION_MARK'
×
418
        }
×
419
      }
×
420
    }
×
421

×
422
    return {
×
423
      type: 'RECORD_ENTRY',
×
424
      key: result.key,
×
425
      quoteStyle: 'none',
×
426
      value: right,
×
427
      readonly: false
×
428
    }
×
429
  },
1✔
430

1✔
431
  JsdocTypeObject: (result, transform) => {
1✔
432
    const entries: JtpRecordEntryResult[] = []
86✔
433
    for (const field of result.elements) {
86✔
434
      if (field.type === 'JsdocTypeObjectField' || field.type === 'JsdocTypeJsdocObjectField') {
92✔
435
        entries.push(transform(field) as JtpRecordEntryResult)
91✔
436
      }
91✔
437
    }
92✔
438
    return {
84✔
439
      type: 'RECORD',
84✔
440
      entries
84✔
441
    }
84✔
442
  },
1✔
443

1✔
444
  JsdocTypeSpecialNamePath: result => {
1✔
445
    if (result.specialType !== 'module') {
13✔
446
      throw new Error(`jsdoctypeparser does not support type ${result.specialType} at this point.`)
1✔
447
    }
1✔
448
    return {
12✔
449
      type: 'MODULE',
12✔
450
      value: {
12✔
451
        type: 'FILE_PATH',
12✔
452
        quoteStyle: getQuoteStyle(result.meta.quote),
12✔
453
        path: result.value
12✔
454
      }
12✔
455
    }
12✔
456
  },
1✔
457

1✔
458
  JsdocTypeNamePath: (result, transform) => {
1✔
459
    let hasEventPrefix = false
129✔
460
    let name
129✔
461
    let quoteStyle
129✔
462
    if (result.right.type === 'JsdocTypeSpecialNamePath' && result.right.specialType === 'event') {
129✔
463
      hasEventPrefix = true
1✔
464
      name = result.right.value
1✔
465
      quoteStyle = getQuoteStyle(result.right.meta.quote)
1✔
466
    } else {
129✔
467
      name = result.right.value
128✔
468
      quoteStyle = getQuoteStyle(result.right.meta.quote)
128✔
469
    }
128✔
470

129✔
471
    const transformed: JtpMemberResult = {
129✔
472
      type: getMemberType(result.pathType),
129✔
473
      owner: transform(result.left),
129✔
474
      name,
129✔
475
      quoteStyle,
129✔
476
      hasEventPrefix
129✔
477
    }
129✔
478

129✔
479
    if (transformed.owner.type === 'MODULE') {
129✔
480
      const tModule = transformed.owner
6✔
481
      transformed.owner = transformed.owner.value
6✔
482
      tModule.value = transformed
6✔
483
      return tModule
6✔
484
    } else {
129✔
485
      return transformed
123✔
486
    }
123✔
487
  },
1✔
488

1✔
489
  JsdocTypeUnion: (result, transform) => nestResults('UNION', result.elements.map(transform)),
1✔
490

1✔
491
  JsdocTypeParenthesis: (result, transform) => ({
1✔
492
    type: 'PARENTHESIS',
94✔
493
    value: transform(assertRootResult(result.element))
94✔
494
  }),
1✔
495

1✔
496
  JsdocTypeNull: () => ({
1✔
497
    type: 'NAME',
8✔
498
    name: 'null'
8✔
499
  }),
1✔
500

1✔
501
  JsdocTypeUnknown: () => ({
1✔
502
    type: 'UNKNOWN'
20✔
503
  }),
1✔
504

1✔
505
  JsdocTypeStringValue: result => ({
1✔
506
    type: 'STRING_VALUE',
6✔
507
    quoteStyle: getQuoteStyle(result.meta.quote),
6✔
508
    string: result.value
6✔
509
  }),
1✔
510

1✔
511
  JsdocTypeIntersection: (result, transform) => nestResults('INTERSECTION', result.elements.map(transform)),
1✔
512

1✔
513
  JsdocTypeNumber: result => ({
1✔
514
    type: 'NUMBER_VALUE',
16✔
515
    number: result.value.toString()
16✔
516
  }),
1✔
517

1✔
518
  JsdocTypeSymbol: notAvailableTransform,
1✔
519

1✔
520
  JsdocTypeProperty: notAvailableTransform,
1✔
521

1✔
522
  JsdocTypePredicate: notAvailableTransform,
1✔
523

1✔
524
  JsdocTypeMappedType: notAvailableTransform,
1✔
525

1✔
526
  JsdocTypeIndexSignature: notAvailableTransform,
1✔
527

1✔
528
  JsdocTypeAsserts: notAvailableTransform,
1✔
529

1✔
530
  JsdocTypeReadonlyArray: notAvailableTransform,
1✔
531

1✔
532
  JsdocTypeAssertsPlain: notAvailableTransform,
1✔
533

1✔
534
  JsdocTypeConditional: notAvailableTransform,
1✔
535

1✔
536
  JsdocTypeTypeParameter: notAvailableTransform
1✔
537
}
1✔
538

1✔
539
export function jtpTransform (result: RootResult): JtpResult {
1✔
540
  return transform(jtpRules, result)
565✔
541
}
565✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc