jsdoc-type-pratt-parser
Version:
[](https://www.npmjs.com/package/jsdoc-type-pratt-parser) []
1,277 lines (1,247 loc) • 154 kB
JavaScript
(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;
}