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)]

1,277 lines (1,247 loc) 154 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.jtpp = {})); })(this, (function (exports) { 'use strict'; function tokenToString(token) { if (token.text !== undefined && token.text !== '') { return `'${token.type}' with value '${token.text}'`; } else { return `'${token.type}'`; } } class NoParsletFoundError extends Error { constructor(token) { super(`No parslet found for token: ${tokenToString(token)}`); this.token = token; Object.setPrototypeOf(this, NoParsletFoundError.prototype); } getToken() { return this.token; } } class EarlyEndOfParseError extends Error { constructor(token) { super(`The parsing ended early. The next token was: ${tokenToString(token)}`); this.token = token; Object.setPrototypeOf(this, EarlyEndOfParseError.prototype); } getToken() { return this.token; } } class UnexpectedTypeError extends Error { constructor(result, message) { let error = `Unexpected type: '${result.type}'.`; if (message !== undefined) { error += ` Message: ${message}`; } super(error); Object.setPrototypeOf(this, UnexpectedTypeError.prototype); } } // export class UnexpectedTokenError extends Error { // private expected: Token // private found: Token // // constructor (expected: Token, found: Token) { // super(`The parsing ended early. The next token was: ${tokenToString(token)}`) // // this.token = token // // Object.setPrototypeOf(this, EarlyEndOfParseError.prototype) // } // // getToken() { // return this.token // } // } const baseNameTokens = [ 'module', 'keyof', 'event', 'external', 'readonly', 'is', 'typeof', 'in', 'null', 'undefined', 'function', 'asserts', 'infer', 'extends', 'import' ]; const reservedWordsAsRootTSTypes = [ 'false', 'null', 'true', 'void' ]; const reservedWords$1 = { always: [ 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do', 'else', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'new', 'null', 'return', 'super', 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'var', 'void', 'while', 'with' ], strictMode: [ 'let', 'static', 'yield' ], moduleOrAsyncFunctionBodies: [ 'await' ] }; const futureReservedWords = { always: ['enum'], strictMode: [ 'implements', 'interface', 'package', 'private', 'protected', 'public' ] }; const strictModeNonIdentifiers = [ 'arguments', 'eval' ]; function assertResultIsNotReservedWord(parser, result) { let text; if (result.type === 'JsdocTypeName') { text = result.value; } else if (result.type === 'JsdocTypeParenthesis') { let res = result; while (res.type === 'JsdocTypeParenthesis') { res = res.element; } if (res.type === 'JsdocTypeName') { text = res.value; } else { return result; } } else { return result; } if (reservedWords$1.always.includes(text) && !reservedWordsAsRootTSTypes.includes(text) && (text !== 'this' || parser.classContext !== true)) { throw new Error(`Unexpected reserved keyword "${text}"`); } if (futureReservedWords.always.includes(text)) { throw new Error(`Unexpected future reserved keyword "${text}"`); } if ((parser.module !== undefined && parser.module) || (parser.strictMode !== undefined && parser.strictMode)) { if (reservedWords$1.strictMode.includes(text)) { throw new Error(`Unexpected reserved keyword "${text}" for strict mode`); } if (futureReservedWords.strictMode.includes(text)) { throw new Error(`Unexpected future reserved keyword "${text}" for strict mode`); } if (strictModeNonIdentifiers.includes(text)) { throw new Error(`The item "${text}" is not an identifier in strict mode`); } } if ((parser.module !== undefined && parser.module) || (parser.asyncFunctionBody !== undefined && parser.asyncFunctionBody)) { if (reservedWords$1.moduleOrAsyncFunctionBodies.includes(text)) { throw new Error(`Unexpected reserved keyword "${text}" for modules or async function bodies`); } } return result; } /** * Throws an error if the provided result is not a {@link RootResult} */ function assertRootResult(result) { if (result === undefined) { throw new Error('Unexpected undefined'); } if (result.type === 'JsdocTypeKeyValue' || result.type === 'JsdocTypeParameterList' || result.type === 'JsdocTypeProperty' || result.type === 'JsdocTypeReadonlyProperty' || result.type === 'JsdocTypeObjectField' || result.type === 'JsdocTypeJsdocObjectField' || result.type === 'JsdocTypeIndexSignature' || result.type === 'JsdocTypeMappedType' || result.type === 'JsdocTypeTypeParameter' || result.type === 'JsdocTypeCallSignature' || result.type === 'JsdocTypeConstructorSignature' || result.type === 'JsdocTypeMethodSignature' || result.type === 'JsdocTypeIndexedAccessIndex' || result.type === 'JsdocTypeComputedProperty' || result.type === 'JsdocTypeComputedMethod') { throw new UnexpectedTypeError(result); } return result; } function assertPlainKeyValueOrRootResult(result) { if (result.type === 'JsdocTypeKeyValue') { return assertPlainKeyValueResult(result); } return assertRootResult(result); } function assertPlainKeyValueOrNameResult(result) { if (result.type === 'JsdocTypeName') { return result; } return assertPlainKeyValueResult(result); } function assertPlainKeyValueResult(result) { if (result.type !== 'JsdocTypeKeyValue') { throw new UnexpectedTypeError(result); } return result; } function assertNumberOrVariadicNameResult(result) { var _a; if (result.type === 'JsdocTypeVariadic') { if (((_a = result.element) === null || _a === void 0 ? void 0 : _a.type) === 'JsdocTypeName') { return result; } throw new UnexpectedTypeError(result); } if (result.type !== 'JsdocTypeNumber' && result.type !== 'JsdocTypeName') { throw new UnexpectedTypeError(result); } return result; } function assertArrayOrTupleResult(result) { if (result.type === 'JsdocTypeTuple') { return result; } if (result.type === 'JsdocTypeGeneric' && result.meta.brackets === 'square') { return result; } throw new UnexpectedTypeError(result); } function isSquaredProperty(result) { return result.type === 'JsdocTypeIndexSignature' || result.type === 'JsdocTypeMappedType'; } // higher precedence = higher importance var Precedence; (function (Precedence) { Precedence[Precedence["ALL"] = 0] = "ALL"; Precedence[Precedence["PARAMETER_LIST"] = 1] = "PARAMETER_LIST"; Precedence[Precedence["OBJECT"] = 2] = "OBJECT"; Precedence[Precedence["KEY_VALUE"] = 3] = "KEY_VALUE"; Precedence[Precedence["INDEX_BRACKETS"] = 4] = "INDEX_BRACKETS"; Precedence[Precedence["UNION"] = 5] = "UNION"; Precedence[Precedence["INTERSECTION"] = 6] = "INTERSECTION"; Precedence[Precedence["PREFIX"] = 7] = "PREFIX"; Precedence[Precedence["INFIX"] = 8] = "INFIX"; Precedence[Precedence["TUPLE"] = 9] = "TUPLE"; Precedence[Precedence["SYMBOL"] = 10] = "SYMBOL"; Precedence[Precedence["OPTIONAL"] = 11] = "OPTIONAL"; Precedence[Precedence["NULLABLE"] = 12] = "NULLABLE"; Precedence[Precedence["KEY_OF_TYPE_OF"] = 13] = "KEY_OF_TYPE_OF"; Precedence[Precedence["FUNCTION"] = 14] = "FUNCTION"; Precedence[Precedence["ARROW"] = 15] = "ARROW"; Precedence[Precedence["ARRAY_BRACKETS"] = 16] = "ARRAY_BRACKETS"; Precedence[Precedence["GENERIC"] = 17] = "GENERIC"; Precedence[Precedence["NAME_PATH"] = 18] = "NAME_PATH"; Precedence[Precedence["PARENTHESIS"] = 19] = "PARENTHESIS"; Precedence[Precedence["SPECIAL_TYPES"] = 20] = "SPECIAL_TYPES"; })(Precedence || (Precedence = {})); class Parser { constructor(grammar, lexer, baseParser, { module, strictMode, asyncFunctionBody, classContext, range = false, rangeStart = 0, loc = false, locStart = { line: 1, column: 0 }, externalParsers } = {}) { this.grammar = grammar; this._lexer = lexer; this.baseParser = baseParser; this.externalParsers = externalParsers; this.module = module; this.strictMode = strictMode; this.asyncFunctionBody = asyncFunctionBody; this.classContext = classContext; this.rangeStart = rangeStart; this.range = range; this.locStart = locStart; this.loc = loc; } get lexer() { return this._lexer; } /** * Parses a given string and throws an error if the parse ended before the end of the string. */ parse() { const result = this.parseType(Precedence.ALL); if (this.lexer.current.type !== 'EOF') { throw new EarlyEndOfParseError(this.lexer.current); } return result; } /** * Parses with the current lexer and asserts that the result is a {@link RootResult}. */ parseType(precedence) { return assertRootResult(this.parseIntermediateType(precedence)); } /** * The main parsing function. First it tries to parse the current state in the prefix step, and then it continues * to parse the state in the infix step. */ parseIntermediateType(precedence) { const result = this.tryParslets(null, precedence); if (result === null) { throw new NoParsletFoundError(this.lexer.current); } return this.parseInfixIntermediateType(result, precedence); } /** * In the infix parsing step the parser continues to parse the current state with all parslets until none returns * a result. */ parseInfixIntermediateType(left, precedence) { let result = this.tryParslets(left, precedence); while (result !== null) { left = result; result = this.tryParslets(left, precedence); } return left; } /** * Tries to parse the current state with all parslets in the grammar and returns the first non null result. */ tryParslets(left, precedence) { for (const parslet of this.grammar) { const rangeStart = this.rangeStart; const locStartLine = this.locStart.line; const locStartColumn = this.locStart.column; const result = parslet(this, precedence, left); if (result !== null) { if (this.range) { result.range = [ rangeStart, this.rangeStart ]; } if (this.loc) { result.loc = { end: { line: this.locStart.line, column: this.locStart.column }, start: { line: locStartLine, column: locStartColumn } }; } return result; } } return null; } /** * If the given type equals the current type of the {@link Lexer} advance the lexer. Return true if the lexer was * advanced. */ consume(types) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; if (!Array.isArray(types)) { types = [types]; } if (types.includes(this.lexer.current.type)) { if (this.range) { /* c8 ignore next -- Default is for TS */ this.rangeStart += (_b = (_a = this.lexer.current) === null || _a === void 0 ? void 0 : _a.reduced) !== null && _b !== void 0 ? _b : 0; } if (this.loc) { /* c8 ignore next 4 -- Defaults are for TS */ this.locStart.line += (_d = (_c = this.lexer.current) === null || _c === void 0 ? void 0 : _c.line) !== null && _d !== void 0 ? _d : 0; this.locStart.column = ((_f = (_e = this.lexer.current) === null || _e === void 0 ? void 0 : _e.line) !== null && _f !== void 0 ? _f : 0) > 0 ? (_h = (_g = this.lexer.current) === null || _g === void 0 ? void 0 : _g.column) !== null && _h !== void 0 ? _h : 0 : this.locStart.column + ((_k = (_j = this.lexer.current) === null || _j === void 0 ? void 0 : _j.column) !== null && _k !== void 0 ? _k : 0); } this._lexer = this.lexer.advance(); return true; } else { return false; } } acceptLexerState(parser) { this._lexer = parser.lexer; } } function isQuestionMarkUnknownType(next) { return next === '}' || next === 'EOF' || next === '|' || next === ',' || next === ')' || next === '>'; } const nullableParslet = (parser, precedence, left) => { const type = parser.lexer.current.type; const next = parser.lexer.next.type; const accept = ((left == null) && type === '?' && !isQuestionMarkUnknownType(next)) || ((left != null) && type === '?'); if (!accept) { return null; } parser.consume('?'); if (left == null) { return { type: 'JsdocTypeNullable', element: parser.parseType(Precedence.NULLABLE), meta: { position: 'prefix' } }; } else { return { type: 'JsdocTypeNullable', element: assertRootResult(left), meta: { position: 'suffix' } }; } }; function composeParslet(options) { const parslet = (parser, curPrecedence, left) => { const type = parser.lexer.current.type; const next = parser.lexer.next.type; if (left === null) { if ('parsePrefix' in options) { if (options.accept(type, next)) { return options.parsePrefix(parser); } } } else { if ('parseInfix' in options) { if (options.precedence > curPrecedence && options.accept(type, next)) { return options.parseInfix(parser, left); } } } return null; }; // for debugging Object.defineProperty(parslet, 'name', { value: options.name }); return parslet; } const optionalParslet = composeParslet({ name: 'optionalParslet', accept: type => type === '=', precedence: Precedence.OPTIONAL, parsePrefix: parser => { parser.consume('='); return { type: 'JsdocTypeOptional', element: parser.parseType(Precedence.OPTIONAL), meta: { position: 'prefix' } }; }, parseInfix: (parser, left) => { parser.consume('='); return { type: 'JsdocTypeOptional', element: assertRootResult(left), meta: { position: 'suffix' } }; } }); const numberParslet = composeParslet({ name: 'numberParslet', accept: type => type === 'Number', parsePrefix: parser => { const value = parseFloat(parser.lexer.current.text); parser.consume('Number'); return { type: 'JsdocTypeNumber', value }; } }); const parenthesisParslet = composeParslet({ name: 'parenthesisParslet', accept: type => type === '(', parsePrefix: parser => { parser.consume('('); if (parser.consume(')')) { return { type: 'JsdocTypeParameterList', elements: [] }; } const result = parser.parseIntermediateType(Precedence.ALL); if (!parser.consume(')')) { throw new Error('Unterminated parenthesis'); } if (result.type === 'JsdocTypeParameterList') { return result; } else if (result.type === 'JsdocTypeKeyValue') { return { type: 'JsdocTypeParameterList', elements: [result] }; } return { type: 'JsdocTypeParenthesis', element: assertRootResult(result) }; } }); const specialTypesParslet = composeParslet({ name: 'specialTypesParslet', accept: (type, next) => (type === '?' && isQuestionMarkUnknownType(next)) || type === 'null' || type === 'undefined' || type === '*', parsePrefix: parser => { if (parser.consume('null')) { return { type: 'JsdocTypeNull' }; } if (parser.consume('undefined')) { return { type: 'JsdocTypeUndefined' }; } if (parser.consume('*')) { return { type: 'JsdocTypeAny' }; } if (parser.consume('?')) { return { type: 'JsdocTypeUnknown' }; } throw new Error('Unacceptable token: ' + parser.lexer.current.text); } }); const notNullableParslet = composeParslet({ name: 'notNullableParslet', accept: type => type === '!', precedence: Precedence.NULLABLE, parsePrefix: parser => { parser.consume('!'); return { type: 'JsdocTypeNotNullable', element: parser.parseType(Precedence.NULLABLE), meta: { position: 'prefix' } }; }, parseInfix: (parser, left) => { parser.consume('!'); return { type: 'JsdocTypeNotNullable', element: assertRootResult(left), meta: { position: 'suffix' } }; } }); function createParameterListParslet({ allowTrailingComma }) { return composeParslet({ name: 'parameterListParslet', accept: type => type === ',', precedence: Precedence.PARAMETER_LIST, parseInfix: (parser, left) => { const elements = [ assertPlainKeyValueOrRootResult(left) ]; parser.consume(','); do { try { const next = parser.parseIntermediateType(Precedence.PARAMETER_LIST); elements.push(assertPlainKeyValueOrRootResult(next)); } catch (e) { if (e instanceof NoParsletFoundError) { break; } else { throw e; } } } while (parser.consume(',')); if (elements.length > 0 && elements.slice(0, -1).some(e => e.type === 'JsdocTypeVariadic')) { throw new Error('Only the last parameter may be a rest parameter'); } return { type: 'JsdocTypeParameterList', elements }; } }); } const genericParslet = composeParslet({ name: 'genericParslet', accept: (type, next) => type === '<' || (type === '.' && next === '<'), precedence: Precedence.GENERIC, parseInfix: (parser, left) => { const dot = parser.consume('.'); parser.consume('<'); const objects = []; let infer = false; if (parser.consume('infer')) { infer = true; const left = parser.parseIntermediateType(Precedence.SYMBOL); if (left.type !== 'JsdocTypeName') { throw new UnexpectedTypeError(left, 'A typescript infer always has to have a name.'); } objects.push(left); } else { do { objects.push(parser.parseType(Precedence.PARAMETER_LIST)); } while (parser.consume(',')); } if (!parser.consume('>')) { throw new Error('Unterminated generic parameter list'); } return Object.assign(Object.assign({ type: 'JsdocTypeGeneric', left: assertRootResult(left), elements: objects }, (infer ? { infer: true } : {})), { meta: { brackets: 'angle', dot } }); } }); const unionParslet = composeParslet({ name: 'unionParslet', accept: type => type === '|', precedence: Precedence.UNION, parseInfix: (parser, left) => { parser.consume('|'); const elements = []; do { elements.push(parser.parseType(Precedence.UNION)); } while (parser.consume('|')); return { type: 'JsdocTypeUnion', elements: [ assertResultIsNotReservedWord(parser, assertRootResult(left)), ...elements.map((element) => assertResultIsNotReservedWord(parser, element)) ] }; } }); const baseGrammar = [ nullableParslet, optionalParslet, numberParslet, parenthesisParslet, specialTypesParslet, notNullableParslet, createParameterListParslet({ allowTrailingComma: true }), genericParslet, unionParslet, optionalParslet ]; function createNamePathParslet({ allowSquareBracketsOnAnyType, allowJsdocNamePaths, pathGrammar }) { return function namePathParslet(parser, precedence, left) { if ((left == null) || precedence >= Precedence.NAME_PATH) { return null; } const type = parser.lexer.current.type; const next = parser.lexer.next.type; const accept = (type === '.' && next !== '<') || (type === '[' && (allowSquareBracketsOnAnyType || left.type === 'JsdocTypeName')) || (allowJsdocNamePaths && (type === '~' || type === '#')); if (!accept) { return null; } let pathType; let brackets = false; if (parser.consume('.')) { pathType = 'property'; } else if (parser.consume('[')) { pathType = 'property-brackets'; brackets = true; } else if (parser.consume('~')) { pathType = 'inner'; } else { parser.consume('#'); pathType = 'instance'; } const pathParser = brackets && allowSquareBracketsOnAnyType ? parser : pathGrammar !== null ? new Parser(pathGrammar, parser.lexer, parser) : parser; const parsed = pathParser.parseType(Precedence.NAME_PATH); parser.acceptLexerState(pathParser); let right; switch (parsed.type) { case 'JsdocTypeName': right = { type: 'JsdocTypeProperty', value: parsed.value, meta: { quote: undefined } }; break; case 'JsdocTypeNumber': right = { type: 'JsdocTypeProperty', value: parsed.value.toString(10), meta: { quote: undefined } }; break; case 'JsdocTypeStringValue': right = { type: 'JsdocTypeProperty', value: parsed.value, meta: { quote: parsed.meta.quote } }; break; case 'JsdocTypeSpecialNamePath': if (parsed.specialType === 'event') { right = parsed; } else { throw new UnexpectedTypeError(parsed, 'Type \'JsdocTypeSpecialNamePath\' is only allowed with specialType \'event\''); } break; default: if (!brackets || !allowSquareBracketsOnAnyType) { throw new UnexpectedTypeError(parsed, 'Expecting \'JsdocTypeName\', \'JsdocTypeNumber\', \'JsdocStringValue\' or \'JsdocTypeSpecialNamePath\''); } right = { type: 'JsdocTypeIndexedAccessIndex', right: parsed }; } if (brackets && !parser.consume(']')) { const token = parser.lexer.current; throw new Error(`Unterminated square brackets. Next token is '${token.type}' ` + `with text '${token.text}'`); } return { type: 'JsdocTypeNamePath', left: assertRootResult(left), right, pathType }; }; } function createNameParslet({ allowedAdditionalTokens }) { return composeParslet({ name: 'nameParslet', accept: type => type === 'Identifier' || type === 'this' || type === 'new' || allowedAdditionalTokens.includes(type), parsePrefix: parser => { const { type, text } = parser.lexer.current; parser.consume(type); return { type: 'JsdocTypeName', value: text }; } }); } const stringValueParslet = composeParslet({ name: 'stringValueParslet', accept: type => type === 'StringValue', parsePrefix: parser => { const text = parser.lexer.current.text; parser.consume('StringValue'); return { type: 'JsdocTypeStringValue', value: text.slice(1, -1), meta: { quote: text.startsWith('\'') ? 'single' : 'double' } }; } }); function createSpecialNamePathParslet({ pathGrammar, allowedTypes }) { return composeParslet({ name: 'specialNamePathParslet', accept: type => allowedTypes.includes(type), parsePrefix: parser => { const type = parser.lexer.current.type; parser.consume(type); if (!parser.consume(':')) { return { type: 'JsdocTypeName', value: type }; } let result; let token = parser.lexer.current; if (parser.consume('StringValue')) { result = { type: 'JsdocTypeSpecialNamePath', value: token.text.slice(1, -1), specialType: type, meta: { quote: token.text.startsWith('\'') ? 'single' : 'double' } }; } else { let value = ''; const allowed = ['Identifier', '@', '/']; while (allowed.some(type => parser.consume(type))) { value += token.text; token = parser.lexer.current; } result = { type: 'JsdocTypeSpecialNamePath', value, specialType: type, meta: { quote: undefined } }; } const moduleParser = new Parser(pathGrammar, parser.lexer, parser); const moduleResult = moduleParser.parseInfixIntermediateType(result, Precedence.ALL); parser.acceptLexerState(moduleParser); return assertRootResult(moduleResult); } }); } const basePathGrammar = [ createNameParslet({ allowedAdditionalTokens: ['external', 'module'] }), stringValueParslet, numberParslet, createNamePathParslet({ allowSquareBracketsOnAnyType: false, allowJsdocNamePaths: true, pathGrammar: null }) ]; const pathGrammar = [ ...basePathGrammar, createSpecialNamePathParslet({ allowedTypes: ['event'], pathGrammar: basePathGrammar }), createNameParslet({ allowedAdditionalTokens: baseNameTokens }) ]; function getParameters(value) { let parameters = []; if (value.type === 'JsdocTypeParameterList') { parameters = value.elements; } else if (value.type === 'JsdocTypeParenthesis') { parameters = [value.element]; } else { throw new UnexpectedTypeError(value); } return parameters.map(p => assertPlainKeyValueOrRootResult(p)); } function getUnnamedParameters(value) { const parameters = getParameters(value); if (parameters.some(p => p.type === 'JsdocTypeKeyValue')) { throw new Error('No parameter should be named'); } return parameters; } function createFunctionParslet({ allowNamedParameters, allowNoReturnType, allowWithoutParenthesis, allowNewAsFunctionKeyword }) { return composeParslet({ name: 'functionParslet', accept: (type, next) => type === 'function' || (allowNewAsFunctionKeyword && type === 'new' && next === '('), parsePrefix: parser => { const newKeyword = parser.consume('new'); parser.consume('function'); const hasParenthesis = parser.lexer.current.type === '('; if (!hasParenthesis) { if (!allowWithoutParenthesis) { throw new Error('function is missing parameter list'); } return { type: 'JsdocTypeName', value: 'function' }; } let result = { type: 'JsdocTypeFunction', parameters: [], arrow: false, constructor: newKeyword, parenthesis: hasParenthesis }; const value = parser.parseIntermediateType(Precedence.FUNCTION); if (allowNamedParameters === undefined) { result.parameters = getUnnamedParameters(value); } else if (newKeyword && value.type === 'JsdocTypeFunction' && value.arrow) { result = value; result.constructor = true; return result; } else { result.parameters = getParameters(value); for (const p of result.parameters) { if (p.type === 'JsdocTypeKeyValue' && (!allowNamedParameters.includes(p.key))) { throw new Error(`only allowed named parameters are ${allowNamedParameters.join(', ')} but got ${p.type}`); } } } if (parser.consume(':')) { result.returnType = parser.parseType(Precedence.PREFIX); } return result; } }); } function createVariadicParslet({ allowPostfix, allowEnclosingBrackets }) { return composeParslet({ name: 'variadicParslet', accept: type => type === '...', precedence: Precedence.PREFIX, parsePrefix: parser => { parser.consume('...'); const brackets = allowEnclosingBrackets && parser.consume('['); try { const element = parser.parseType(Precedence.PREFIX); if (brackets && !parser.consume(']')) { throw new Error('Unterminated variadic type. Missing \']\''); } return { type: 'JsdocTypeVariadic', element: assertRootResult(element), meta: { position: 'prefix', squareBrackets: brackets } }; } catch (e) { if (e instanceof NoParsletFoundError) { if (brackets) { throw new Error('Empty square brackets for variadic are not allowed.', { cause: e }); } return { type: 'JsdocTypeVariadic', meta: { position: undefined, squareBrackets: false } }; } else { throw e; } } }, parseInfix: allowPostfix ? (parser, left) => { parser.consume('...'); return { type: 'JsdocTypeVariadic', element: assertRootResult(left), meta: { position: 'suffix', squareBrackets: false } }; } : undefined }); } const symbolParslet = composeParslet({ name: 'symbolParslet', accept: type => type === '(', precedence: Precedence.SYMBOL, parseInfix: (parser, left) => { if (left.type !== 'JsdocTypeName') { throw new Error('Symbol expects a name on the left side. (Reacting on \'(\')'); } parser.consume('('); const result = { type: 'JsdocTypeSymbol', value: left.value }; if (!parser.consume(')')) { const next = parser.parseIntermediateType(Precedence.SYMBOL); result.element = assertNumberOrVariadicNameResult(next); if (!parser.consume(')')) { throw new Error('Symbol does not end after value'); } } return result; } }); const arrayBracketsParslet = composeParslet({ name: 'arrayBracketsParslet', precedence: Precedence.ARRAY_BRACKETS, accept: (type, next) => type === '[' && next === ']', parseInfix: (parser, left) => { parser.consume('['); parser.consume(']'); return { type: 'JsdocTypeGeneric', left: { type: 'JsdocTypeName', value: 'Array' }, elements: [ assertRootResult(left) ], meta: { brackets: 'square', dot: false } }; } }); function createObjectParslet({ signatureGrammar, objectFieldGrammar, allowKeyTypes }) { return composeParslet({ name: 'objectParslet', accept: type => type === '{', parsePrefix: parser => { var _a; parser.consume('{'); const result = { type: 'JsdocTypeObject', meta: { separator: 'comma' }, elements: [] }; if (!parser.consume('}')) { let separator; const fieldParser = new Parser(objectFieldGrammar, parser.lexer, parser, ((_a = parser.externalParsers) === null || _a === void 0 ? void 0 : _a.computedPropertyParser) !== undefined ? { externalParsers: { computedPropertyParser: parser.externalParsers.computedPropertyParser } } : undefined); while (true) { fieldParser.acceptLexerState(parser); let field = fieldParser.parseIntermediateType(Precedence.OBJECT); parser.acceptLexerState(fieldParser); if (field === undefined && allowKeyTypes) { field = parser.parseIntermediateType(Precedence.OBJECT); } let optional = false; if (field.type === 'JsdocTypeNullable') { optional = true; field = field.element; } if (field.type === 'JsdocTypeNumber' || field.type === 'JsdocTypeName' || field.type === 'JsdocTypeStringValue') { let quote; if (field.type === 'JsdocTypeStringValue') { quote = field.meta.quote; } result.elements.push({ type: 'JsdocTypeObjectField', key: field.value.toString(), right: undefined, optional, readonly: false, meta: { quote } }); } else if (signatureGrammar !== undefined && (field.type === 'JsdocTypeCallSignature' || field.type === 'JsdocTypeConstructorSignature' || field.type === 'JsdocTypeMethodSignature')) { const signatureParser = new Parser([ ...signatureGrammar, ...parser.grammar.flatMap((grammar) => { // We're supplying our own version if (grammar.name === 'keyValueParslet') { return []; } return [grammar]; }) ], parser.lexer, parser); signatureParser.acceptLexerState(parser); const params = signatureParser.parseIntermediateType(Precedence.OBJECT); parser.acceptLexerState(signatureParser); field.parameters = getParameters(params); const returnType = parser.parseType(Precedence.OBJECT); field.returnType = returnType; result.elements.push(field); } else if (field.type === 'JsdocTypeObjectField' || field.type === 'JsdocTypeJsdocObjectField') { result.elements.push(field); } else if (field.type === 'JsdocTypeReadonlyProperty' && field.element.type === 'JsdocTypeObjectField') { if (typeof field.element.key === 'object' && field.element.key.type === 'JsdocTypeComputedMethod') { throw new Error('Computed method may not be readonly'); } field.element.readonly = true; result.elements.push(field.element); } else { throw new UnexpectedTypeError(field); } if (parser.lexer.current.startOfLine) { separator !== null && separator !== void 0 ? separator : (separator = 'linebreak'); // Handle single stray comma/semi-colon parser.consume(',') || parser.consume(';'); } else if (parser.consume(',')) { if (parser.lexer.current.startOfLine) { separator = 'comma-and-linebreak'; } else { separator = 'comma'; } } else if (parser.consume(';')) { if (parser.lexer.current.startOfLine) { separator = 'semicolon-and-linebreak'; } else { separator = 'semicolon'; } } else { break; } const type = parser.lexer.current.type; if (type === '}') { break; } } result.meta.separator = separator !== null && separator !== void 0 ? separator : 'comma'; // TODO: use undefined here if ((separator !== null && separator !== void 0 ? separator : '').endsWith('linebreak')) { // TODO: Consume appropriate whitespace result.meta.propertyIndent = ' '; } if (!parser.consume('}')) { throw new Error('Unterminated record type. Missing \'}\''); } } return result; } }); } function createObjectFieldParslet({ allowSquaredProperties, allowKeyTypes, allowReadonly, allowOptional }) { return composeParslet({ name: 'objectFieldParslet', precedence: Precedence.KEY_VALUE, accept: type => type === ':', parseInfix: (parser, left) => { var _a; let optional = false; let readonlyProperty = false; if (allowOptional && left.type === 'JsdocTypeNullable') { optional = true; left = left.element; } if (allowReadonly && left.type === 'JsdocTypeReadonlyProperty') { readonlyProperty = true; left = left.element; } /* c8 ignore next 2 -- Always has base parser? */ // object parslet uses a special grammar and for the value we want to switch back to the parent const parentParser = (_a = parser.baseParser) !== null && _a !== void 0 ? _a : parser; parentParser.acceptLexerState(parser); if (left.type === 'JsdocTypeNumber' || left.type === 'JsdocTypeName' || left.type === 'JsdocTypeStringValue' || isSquaredProperty(left)) { /* c8 ignore next 3 -- Guard */ if (isSquaredProperty(left) && !allowSquaredProperties) { throw new UnexpectedTypeError(left); } parentParser.consume(':'); let quote; if (left.type === 'JsdocTypeStringValue') { quote = left.meta.quote; } const right = parentParser.parseType(Precedence.KEY_VALUE); parser.acceptLexerState(parentParser); return { type: 'JsdocTypeObjectField', /* c8 ignore next -- Guard; not needed anymore? */ key: isSquaredProperty(left) ? left : left.value.toString(), right, optional, readonly: readonlyProperty, meta: { quote } }; } else { if (!allowKeyTypes) { throw new UnexpectedTypeError(left); } parentParser.consume(':'); const right = parentParser.parseType(Precedence.KEY_VALUE); parser.acceptLexerState(parentParser); return { type: 'JsdocTypeJsdocObjectField', left: assertRootResult(left), right }; } } }); } function createKeyValueParslet({ allowOptional, allowVariadic, acceptParameterList }) { return composeParslet({ name: 'keyValueParslet', precedence: Precedence.KEY_VALUE, accept: type => type === ':', parseInfix: (parser, left) => { let optional = false; let variadic = false; if (allowOptional && left.type === 'JsdocTypeNullable') { optional = true; left = left.element; } if (allowVariadic && left.type === 'JsdocTypeVariadic' && left.element !== undefined) { variadic = true; left = left.element; } if (left.type !== 'JsdocTypeName') { if (acceptParameterList !== undefined && left.type === 'JsdocTypeParameterList') { parser.consume(':'); return left; }