UNPKG

jsdoc-type-pratt-parser

Version:

[![Npm Package](https://badgen.net/npm/v/jsdoc-type-pratt-parser)](https://www.npmjs.com/package/jsdoc-type-pratt-parser) [![Test Status](https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/actions/workflows/test.yml/badge.svg?branch=main)]

561 lines (470 loc) 12.9 kB
import { extractSpecialParams, notAvailableTransform, transform, type TransformRules } from './transform.js' import type { QuoteStyle, RootResult } from '../result/RootResult.js' import { assertRootResult } from '../assertTypes.js' import type { NonRootResult } from '../result/NonRootResult.js' export type JtpResult = JtpNameResult | JtpNullableResult | JtpNotNullableResult | JtpOptionalResult | JtpVariadicResult | JtpTypeOfResult | JtpTupleResult | JtpKeyOfResult | JtpStringValueResult | JtpImportResult | JtpAnyResult | JtpUnknownResult | JtpFunctionResult | JtpGenericResult | JtpRecordEntryResult | JtpRecordResult | JtpMemberResult | JtpUnionResult | JtpParenthesisResult | JtpNamedParameterResult | JtpModuleResult | JtpFilePath | JtpIntersectionResult | JtpNumberResult type JtpQuoteStyle = 'single' | 'double' | 'none' export interface JtpNullableResult { type: 'NULLABLE' value: JtpResult meta: { syntax: 'PREFIX_QUESTION_MARK' | 'SUFFIX_QUESTION_MARK' } } export interface JtpNotNullableResult { type: 'NOT_NULLABLE' value: JtpResult meta: { syntax: 'PREFIX_BANG' | 'SUFFIX_BANG' } } export interface JtpOptionalResult { type: 'OPTIONAL' value: JtpResult meta: { syntax: 'PREFIX_EQUAL_SIGN' | 'SUFFIX_EQUALS_SIGN' | 'SUFFIX_KEY_QUESTION_MARK' } } export interface JtpVariadicResult { type: 'VARIADIC' value?: JtpResult meta: { syntax: 'PREFIX_DOTS' | 'SUFFIX_DOTS' | 'ONLY_DOTS' } } export interface JtpNameResult { type: 'NAME' name: string } export interface JtpTypeOfResult { type: 'TYPE_QUERY' name?: JtpResult } export interface JtpKeyOfResult { type: 'KEY_QUERY' value?: JtpResult } export interface JtpTupleResult { type: 'TUPLE' entries: JtpResult[] } export interface JtpStringValueResult { type: 'STRING_VALUE' quoteStyle: JtpQuoteStyle string: string } export interface JtpImportResult { type: 'IMPORT' path: JtpStringValueResult } export interface JtpAnyResult { type: 'ANY' } export interface JtpUnknownResult { type: 'UNKNOWN' } export interface JtpFunctionResult { type: 'FUNCTION' | 'ARROW' params: JtpResult[] returns: JtpResult | null new: JtpResult | null this?: JtpResult | null } export interface JtpGenericResult { type: 'GENERIC' subject: JtpResult objects: JtpResult[] meta: { syntax: 'ANGLE_BRACKET' | 'ANGLE_BRACKET_WITH_DOT' | 'SQUARE_BRACKET' } } export interface JtpRecordEntryResult { type: 'RECORD_ENTRY' key: string quoteStyle: JtpQuoteStyle value: JtpResult | null readonly: false } export interface JtpRecordResult { type: 'RECORD' entries: JtpRecordEntryResult[] } export interface JtpMemberResult { type: 'MEMBER' | 'INNER_MEMBER' | 'INSTANCE_MEMBER' owner: JtpResult name: string quoteStyle: JtpQuoteStyle hasEventPrefix: boolean } export interface JtpUnionResult { type: 'UNION' left: JtpResult right: JtpResult } export interface JtpIntersectionResult { type: 'INTERSECTION' left: JtpResult right: JtpResult } export interface JtpParenthesisResult { type: 'PARENTHESIS' value: JtpResult } export interface JtpNamedParameterResult { type: 'NAMED_PARAMETER' name: string typeName: JtpResult } export interface JtpModuleResult { type: 'MODULE' value: JtpResult } export interface JtpFilePath { type: 'FILE_PATH' quoteStyle: JtpQuoteStyle path: string } export interface JtpNumberResult { type: 'NUMBER_VALUE' number: string } function getQuoteStyle (quote: QuoteStyle | undefined): JtpQuoteStyle { switch (quote) { case undefined: return 'none' case 'single': return 'single' case 'double': return 'double' } } function getMemberType (type: 'property' | 'inner' | 'instance' | 'property-brackets'): JtpMemberResult['type'] { switch (type) { case 'inner': return 'INNER_MEMBER' case 'instance': return 'INSTANCE_MEMBER' case 'property': return 'MEMBER' case 'property-brackets': return 'MEMBER' } } function nestResults (type: 'UNION' | 'INTERSECTION', results: JtpResult[]): JtpResult { if (results.length === 2) { return { type, left: results[0], right: results[1] } } else { return { type, left: results[0], right: nestResults(type, results.slice(1)) } } } const jtpRules: TransformRules<JtpResult> = { JsdocTypeOptional: (result, transform) => ({ type: 'OPTIONAL', value: transform(result.element), meta: { syntax: result.meta.position === 'prefix' ? 'PREFIX_EQUAL_SIGN' : 'SUFFIX_EQUALS_SIGN' } }), JsdocTypeNullable: (result, transform) => ({ type: 'NULLABLE', value: transform(result.element), meta: { syntax: result.meta.position === 'prefix' ? 'PREFIX_QUESTION_MARK' : 'SUFFIX_QUESTION_MARK' } }), JsdocTypeNotNullable: (result, transform) => ({ type: 'NOT_NULLABLE', value: transform(result.element), meta: { syntax: result.meta.position === 'prefix' ? 'PREFIX_BANG' : 'SUFFIX_BANG' } }), JsdocTypeVariadic: (result, transform) => { const transformed: JtpVariadicResult = { type: 'VARIADIC', meta: { syntax: result.meta.position === 'prefix' ? 'PREFIX_DOTS' : result.meta.position === 'suffix' ? 'SUFFIX_DOTS' : 'ONLY_DOTS' } } if (result.element !== undefined) { transformed.value = transform(result.element) } return transformed }, JsdocTypeName: result => ({ type: 'NAME', name: result.value }), JsdocTypeTypeof: (result, transform) => ({ type: 'TYPE_QUERY', name: transform(result.element) }), JsdocTypeTuple: (result, transform) => ({ type: 'TUPLE', entries: (result.elements as NonRootResult[]).map(transform) }), JsdocTypeKeyof: (result, transform) => ({ type: 'KEY_QUERY', value: transform(result.element) }), JsdocTypeImport: result => ({ type: 'IMPORT', path: { type: 'STRING_VALUE', quoteStyle: getQuoteStyle(result.element.meta.quote), string: result.element.value } }), JsdocTypeUndefined: () => ({ type: 'NAME', name: 'undefined' }), JsdocTypeAny: () => ({ type: 'ANY' }), JsdocTypeFunction: (result, transform) => { const specialParams = extractSpecialParams(result) const transformed: JtpFunctionResult = { type: result.arrow ? 'ARROW' : 'FUNCTION', params: specialParams.params.map(param => { if (param.type === 'JsdocTypeKeyValue') { if (param.right === undefined) { throw new Error('Function parameter without \':\' is not expected to be \'KEY_VALUE\'') } return { type: 'NAMED_PARAMETER', name: param.key, typeName: transform(param.right) } } else { return transform(param) } }), new: null, returns: null } if (specialParams.this !== undefined) { transformed.this = transform(specialParams.this) } else if (!result.arrow) { transformed.this = null } if (specialParams.new !== undefined) { transformed.new = transform(specialParams.new) } if (result.returnType !== undefined) { transformed.returns = transform(result.returnType) } return transformed }, JsdocTypeGeneric: (result, transform) => { const transformed: JtpGenericResult = { type: 'GENERIC', subject: transform(result.left), objects: result.elements.map(transform), meta: { syntax: result.meta.brackets === 'square' ? 'SQUARE_BRACKET' : result.meta.dot ? 'ANGLE_BRACKET_WITH_DOT' : 'ANGLE_BRACKET' } } if (result.meta.brackets === 'square' && result.elements[0].type === 'JsdocTypeFunction' && !result.elements[0].parenthesis) { transformed.objects[0] = { type: 'NAME', name: 'function' } } return transformed }, JsdocTypeObjectField: (result, transform) => { if (typeof result.key !== 'string') { throw new Error('Index signatures and mapped types are not supported') } if (result.right === undefined) { return { type: 'RECORD_ENTRY', key: result.key, quoteStyle: getQuoteStyle(result.meta.quote), value: null, readonly: false } } let right = transform(result.right) if (result.optional) { right = { type: 'OPTIONAL', value: right, meta: { syntax: 'SUFFIX_KEY_QUESTION_MARK' } } } return { type: 'RECORD_ENTRY', key: result.key, quoteStyle: getQuoteStyle(result.meta.quote), value: right, readonly: false } }, JsdocTypeJsdocObjectField: () => { throw new Error('Keys may not be typed in jsdoctypeparser.') }, JsdocTypeKeyValue: (result, transform) => { if (result.right === undefined) { return { type: 'RECORD_ENTRY', key: result.key, quoteStyle: 'none', value: null, readonly: false } } let right = transform(result.right) if (result.optional) { right = { type: 'OPTIONAL', value: right, meta: { syntax: 'SUFFIX_KEY_QUESTION_MARK' } } } return { type: 'RECORD_ENTRY', key: result.key, quoteStyle: 'none', value: right, readonly: false } }, JsdocTypeObject: (result, transform) => { const entries: JtpRecordEntryResult[] = [] for (const field of result.elements) { if (field.type === 'JsdocTypeObjectField' || field.type === 'JsdocTypeJsdocObjectField') { entries.push(transform(field) as JtpRecordEntryResult) } } return { type: 'RECORD', entries } }, JsdocTypeSpecialNamePath: result => { if (result.specialType !== 'module') { throw new Error(`jsdoctypeparser does not support type ${result.specialType} at this point.`) } return { type: 'MODULE', value: { type: 'FILE_PATH', quoteStyle: getQuoteStyle(result.meta.quote), path: result.value } } }, JsdocTypeNamePath: (result, transform) => { let hasEventPrefix = false let name let quoteStyle if (result.right.type === 'JsdocTypeIndexedAccessIndex') { throw new TypeError('JsdocTypeIndexedAccessIndex not allowed in jtp') } if (result.right.type === 'JsdocTypeSpecialNamePath' && result.right.specialType === 'event') { hasEventPrefix = true name = result.right.value quoteStyle = getQuoteStyle(result.right.meta.quote) } else { name = result.right.value quoteStyle = getQuoteStyle(result.right.meta.quote) } const transformed: JtpMemberResult = { type: getMemberType(result.pathType), owner: transform(result.left), name, quoteStyle, hasEventPrefix } if (transformed.owner.type === 'MODULE') { const tModule = transformed.owner transformed.owner = transformed.owner.value tModule.value = transformed return tModule } else { return transformed } }, JsdocTypeUnion: (result, transform) => nestResults('UNION', result.elements.map(transform)), JsdocTypeParenthesis: (result, transform) => ({ type: 'PARENTHESIS', value: transform(assertRootResult(result.element)) }), JsdocTypeNull: () => ({ type: 'NAME', name: 'null' }), JsdocTypeUnknown: () => ({ type: 'UNKNOWN' }), JsdocTypeStringValue: result => ({ type: 'STRING_VALUE', quoteStyle: getQuoteStyle(result.meta.quote), string: result.value }), JsdocTypeIntersection: (result, transform) => nestResults('INTERSECTION', result.elements.map(transform)), JsdocTypeNumber: result => ({ type: 'NUMBER_VALUE', number: result.value.toString() }), JsdocTypeSymbol: notAvailableTransform, JsdocTypeProperty: notAvailableTransform, JsdocTypePredicate: notAvailableTransform, JsdocTypeMappedType: notAvailableTransform, JsdocTypeIndexSignature: notAvailableTransform, JsdocTypeAsserts: notAvailableTransform, JsdocTypeReadonlyArray: notAvailableTransform, JsdocTypeAssertsPlain: notAvailableTransform, JsdocTypeConditional: notAvailableTransform, JsdocTypeTypeParameter: notAvailableTransform, JsdocTypeCallSignature: notAvailableTransform, JsdocTypeConstructorSignature: notAvailableTransform, JsdocTypeMethodSignature: notAvailableTransform, JsdocTypeIndexedAccessIndex: notAvailableTransform, JsdocTypeTemplateLiteral: notAvailableTransform, JsdocTypeComputedProperty: notAvailableTransform, JsdocTypeComputedMethod: notAvailableTransform } export function jtpTransform (result: RootResult): JtpResult { return transform(jtpRules, result) }