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

jsdoc-type-pratt-parser / jsdoc-type-pratt-parser / 4424204709

pending completion
4424204709

push

github

GitHub
Merge pull request #156 from jsdoc-type-pratt-parser/dev

540 of 554 branches covered (97.47%)

Branch coverage included in aggregate %.

129 of 129 new or added lines in 19 files covered. (100.0%)

1110 of 1125 relevant lines covered (98.67%)

2612.96 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

260
    return transformed
87✔
261
  },
262

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

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

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

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

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

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

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

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

304
    const transformed: JtpFunctionResult = {
150✔
305
      type: result.arrow ? 'ARROW' : 'FUNCTION',
150✔
306
      params: specialParams.params.map(param => {
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
          }
311
          return {
20✔
312
            type: 'NAMED_PARAMETER',
313
            name: param.key,
314
            typeName: transform(param.right)
315
          }
316
        } else {
317
          return transform(param)
94✔
318
        }
319
      }),
320
      new: null,
321
      returns: null
322
    }
323

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

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

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

338
    return transformed
149✔
339
  },
340

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

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

358
    return transformed
107✔
359
  },
360

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

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

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

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

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

400
  JsdocTypeKeyValue: (result, transform) => {
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
  },
430

431
  JsdocTypeObject: (result, transform) => {
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
      }
437
    }
438
    return {
84✔
439
      type: 'RECORD',
440
      entries
441
    }
442
  },
443

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

458
  JsdocTypeNamePath: (result, transform) => {
459
    let hasEventPrefix = false
129✔
460
    let name
461
    let quoteStyle
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 {
467
      name = result.right.value
128✔
468
      quoteStyle = getQuoteStyle(result.right.meta.quote)
128✔
469
    }
470

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

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 {
485
      return transformed
123✔
486
    }
487
  },
488

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

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

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

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

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

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

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

518
  JsdocTypeSymbol: notAvailableTransform,
519

520
  JsdocTypeProperty: notAvailableTransform,
521

522
  JsdocTypePredicate: notAvailableTransform,
523

524
  JsdocTypeMappedType: notAvailableTransform,
525

526
  JsdocTypeIndexSignature: notAvailableTransform
527
}
528

529
export function jtpTransform (result: RootResult): JtpResult {
1✔
530
  return transform(jtpRules, result)
565✔
531
}
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