jsdoc-type-pratt-parser
Advanced tools
Comparing version 1.0.0-alpha.2 to 1.0.0-alpha.3.0
1510
dist/index.js
@@ -7,2 +7,53 @@ (function (global, factory) { | ||
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) { | ||
super(`Unexpected type: '${result.type}'`); | ||
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 | ||
// } | ||
// } | ||
function makePunctuationRule(type) { | ||
@@ -132,2 +183,3 @@ return text => { | ||
eofRule, | ||
makePunctuationRule('=>'), | ||
makePunctuationRule('('), | ||
@@ -156,3 +208,2 @@ makePunctuationRule(')'), | ||
makeKeyWordRule('null'), | ||
makeKeyWordRule('void'), | ||
makeKeyWordRule('function'), | ||
@@ -204,6 +255,7 @@ makeKeyWordRule('this'), | ||
read() { | ||
const text = this.text.trim(); | ||
for (const rule of rules) { | ||
const token = rule(this.text); | ||
const token = rule(text); | ||
if (token !== null) { | ||
this.text = this.text.slice(token.text.length).trim(); | ||
this.text = text.slice(token.text.length); | ||
return token; | ||
@@ -233,9 +285,6 @@ } | ||
class UnexpectedTypeError extends Error { | ||
constructor(result) { | ||
super(`Unexpected type: '${result.type}'`); | ||
Object.setPrototypeOf(this, UnexpectedTypeError.prototype); | ||
function assertTerminal(result) { | ||
if (result === undefined) { | ||
throw new Error('Unexpected undefined'); | ||
} | ||
} | ||
function assertTerminal(result) { | ||
if (result.type === 'KEY_VALUE' || result.type === 'NUMBER' || result.type === 'PARAMETER_LIST') { | ||
@@ -248,3 +297,3 @@ throw new UnexpectedTypeError(result); | ||
if (result.type === 'KEY_VALUE') { | ||
if (result.key.type !== 'NAME') { | ||
if (result.left.type !== 'NAME') { | ||
throw new UnexpectedTypeError(result); | ||
@@ -258,3 +307,3 @@ } | ||
if (result.type === 'KEY_VALUE') { | ||
if (result.key.type !== 'NAME') { | ||
if (result.left.type !== 'NAME') { | ||
throw new UnexpectedTypeError(result); | ||
@@ -269,2 +318,15 @@ } | ||
} | ||
function assertNumberOrVariadicName(result) { | ||
var _a; | ||
if (result.type === 'VARIADIC') { | ||
if (((_a = result.element) === null || _a === void 0 ? void 0 : _a.type) === 'NAME') { | ||
return result; | ||
} | ||
throw new UnexpectedTypeError(result); | ||
} | ||
if (result.type !== 'NUMBER' && result.type !== 'NAME') { | ||
throw new UnexpectedTypeError(result); | ||
} | ||
return result; | ||
} | ||
@@ -276,26 +338,21 @@ // higher precedence = higher importance | ||
Precedence[Precedence["PARAMETER_LIST"] = 1] = "PARAMETER_LIST"; | ||
Precedence[Precedence["PARENTHESIS"] = 2] = "PARENTHESIS"; | ||
Precedence[Precedence["UNION"] = 3] = "UNION"; | ||
Precedence[Precedence["PREFIX"] = 4] = "PREFIX"; | ||
Precedence[Precedence["POSTFIX"] = 5] = "POSTFIX"; | ||
Precedence[Precedence["RECORD"] = 6] = "RECORD"; | ||
Precedence[Precedence["UNION"] = 2] = "UNION"; | ||
Precedence[Precedence["PREFIX"] = 3] = "PREFIX"; | ||
Precedence[Precedence["POSTFIX"] = 4] = "POSTFIX"; | ||
Precedence[Precedence["TUPLE"] = 5] = "TUPLE"; | ||
Precedence[Precedence["OBJECT"] = 6] = "OBJECT"; | ||
Precedence[Precedence["SYMBOL"] = 7] = "SYMBOL"; | ||
Precedence[Precedence["OPTIONAL"] = 8] = "OPTIONAL"; | ||
Precedence[Precedence["NULLABLE"] = 9] = "NULLABLE"; | ||
Precedence[Precedence["FUNCTION"] = 10] = "FUNCTION"; | ||
Precedence[Precedence["ARROW"] = 11] = "ARROW"; | ||
Precedence[Precedence["KEY_VALUE"] = 12] = "KEY_VALUE"; | ||
Precedence[Precedence["GENERIC"] = 13] = "GENERIC"; | ||
Precedence[Precedence["PROPERTY_PATH"] = 14] = "PROPERTY_PATH"; | ||
Precedence[Precedence["KEY_OF_TYPE_OF"] = 15] = "KEY_OF_TYPE_OF"; | ||
Precedence[Precedence["KEY_OF_TYPE_OF"] = 10] = "KEY_OF_TYPE_OF"; | ||
Precedence[Precedence["KEY_VALUE"] = 11] = "KEY_VALUE"; | ||
Precedence[Precedence["FUNCTION"] = 12] = "FUNCTION"; | ||
Precedence[Precedence["ARROW"] = 13] = "ARROW"; | ||
Precedence[Precedence["GENERIC"] = 14] = "GENERIC"; | ||
Precedence[Precedence["NAME_PATH"] = 15] = "NAME_PATH"; | ||
Precedence[Precedence["ARRAY_BRACKETS"] = 16] = "ARRAY_BRACKETS"; | ||
Precedence[Precedence["SPECIAL_TYPES"] = 17] = "SPECIAL_TYPES"; | ||
Precedence[Precedence["PARENTHESIS"] = 17] = "PARENTHESIS"; | ||
Precedence[Precedence["SPECIAL_TYPES"] = 18] = "SPECIAL_TYPES"; | ||
})(Precedence || (Precedence = {})); | ||
class NoParsletFoundError extends Error { | ||
constructor(token) { | ||
super(`No parslet found for token: '${token.type}' with value '${token.text}'`); | ||
Object.setPrototypeOf(this, NoParsletFoundError.prototype); | ||
} | ||
} | ||
class ParserEngine { | ||
@@ -312,3 +369,3 @@ constructor(grammar) { | ||
if (!this.consume('EOF')) { | ||
throw new Error(`Unexpected early end of parse. Next token: '${this.getToken().text}'`); | ||
throw new EarlyEndOfParseError(this.getToken()); | ||
} | ||
@@ -328,3 +385,3 @@ return result; | ||
try { | ||
return this.parseType(precedence); | ||
return this.parseNonTerminalType(precedence); | ||
} | ||
@@ -395,61 +452,2 @@ catch (e) { | ||
const reservedWords = [ | ||
'null', | ||
'true', | ||
'false', | ||
'break', | ||
'case', | ||
'catch', | ||
'class', | ||
'const', | ||
'continue', | ||
'debugger', | ||
'default', | ||
'delete', | ||
'do', | ||
'else', | ||
'export', | ||
'extends', | ||
'finally', | ||
'for', | ||
'function', | ||
'if', | ||
'import', | ||
'in', | ||
'instanceof', | ||
'new', | ||
'return', | ||
'super', | ||
'switch', | ||
'this', | ||
'throw', | ||
'try', | ||
'typeof', | ||
'var', | ||
'void', | ||
'while', | ||
'with', | ||
'yield' | ||
]; | ||
class NameParslet { | ||
accepts(type, next) { | ||
return type === 'Identifier' || type === 'this' || type === 'new'; | ||
} | ||
getPrecedence() { | ||
return Precedence.PREFIX; | ||
} | ||
parsePrefix(parser) { | ||
const token = parser.getToken(); | ||
parser.consume('Identifier') || parser.consume('this') || parser.consume('new'); | ||
const result = { | ||
type: 'NAME', | ||
name: token.text | ||
}; | ||
if (reservedWords.includes(token.text)) { | ||
result.reservedWord = true; | ||
} | ||
return result; | ||
} | ||
} | ||
function isQuestionMarkUnknownType(next) { | ||
@@ -467,52 +465,24 @@ return next === 'EOF' || next === '|' || next === ',' || next === ')' || next === '>'; | ||
parsePrefix(parser) { | ||
switch (parser.getToken().type) { | ||
case 'null': | ||
parser.consume('null'); | ||
return { | ||
type: 'NULL' | ||
}; | ||
case 'undefined': | ||
parser.consume('undefined'); | ||
return { | ||
type: 'UNDEFINED' | ||
}; | ||
case '*': | ||
parser.consume('*'); | ||
return { | ||
type: 'ALL' | ||
}; | ||
case '?': | ||
parser.consume('?'); | ||
return { | ||
type: 'UNKNOWN' | ||
}; | ||
default: | ||
throw new Error('Unacceptable token: ' + parser.getToken().text); | ||
if (parser.consume('null')) { | ||
return { | ||
type: 'NULL' | ||
}; | ||
} | ||
} | ||
} | ||
class VariadicParslet { | ||
accepts(type) { | ||
return type === '...'; | ||
} | ||
getPrecedence() { | ||
return Precedence.PREFIX; | ||
} | ||
parsePrefix(parser) { | ||
parser.consume('...'); | ||
const shouldClose = parser.consume('['); | ||
const value = parser.parseType(Precedence.PREFIX); | ||
if (shouldClose && !parser.consume(']')) { | ||
throw new Error('Unterminated variadic type. Missing \']\''); | ||
if (parser.consume('undefined')) { | ||
return { | ||
type: 'UNDEFINED' | ||
}; | ||
} | ||
value.repeatable = true; | ||
return value; | ||
if (parser.consume('*')) { | ||
return { | ||
type: 'ANY' | ||
}; | ||
} | ||
if (parser.consume('?')) { | ||
return { | ||
type: 'UNKNOWN' | ||
}; | ||
} | ||
throw new Error('Unacceptable token: ' + parser.getToken().text); | ||
} | ||
parseInfix(parser, left) { | ||
parser.consume('...'); | ||
const result = assertTerminal(left); | ||
result.repeatable = true; | ||
return result; | ||
} | ||
} | ||
@@ -525,3 +495,3 @@ | ||
getPrecedence() { | ||
return Precedence.RECORD; | ||
return Precedence.OBJECT; | ||
} | ||
@@ -531,12 +501,12 @@ parsePrefix(parser) { | ||
const result = { | ||
type: 'RECORD', | ||
fields: [] | ||
type: 'OBJECT', | ||
elements: [] | ||
}; | ||
if (!parser.consume('}')) { | ||
do { | ||
const field = parser.parseNonTerminalType(Precedence.RECORD); | ||
const field = parser.parseNonTerminalType(Precedence.OBJECT); | ||
if (field.type !== 'NAME' && field.type !== 'NUMBER' && field.type !== 'KEY_VALUE') { | ||
throw new Error('records may only contain \'NAME\', \'NUMBER\' or \'KEY_VALUE\' fields.'); | ||
} | ||
result.fields.push(field); | ||
result.elements.push(field); | ||
} while (parser.consume(',')); | ||
@@ -551,29 +521,2 @@ if (!parser.consume('}')) { | ||
class ModuleParslet { | ||
accepts(type, next) { | ||
return type === 'module'; | ||
} | ||
getPrecedence() { | ||
return Precedence.PREFIX; | ||
} | ||
parsePrefix(parser) { | ||
parser.consume('module'); | ||
if (!parser.consume(':')) { | ||
throw new Error('module needs to have a \':\' afterwards.'); | ||
} | ||
let result = 'module:'; | ||
const allowed = ['Identifier', '~', '@', '/', '#']; | ||
let token = parser.getToken(); | ||
while (allowed.includes(token.type)) { | ||
result += token.text; | ||
parser.consume(token.type); | ||
token = parser.getToken(); | ||
} | ||
return { | ||
type: 'MODULE', | ||
path: result | ||
}; | ||
} | ||
} | ||
class GenericParslet { | ||
@@ -587,3 +530,3 @@ accepts(type, next) { | ||
parseInfix(parser, left) { | ||
parser.consume('.'); | ||
const dot = parser.consume('.'); | ||
parser.consume('<'); | ||
@@ -599,48 +542,8 @@ const objects = []; | ||
type: 'GENERIC', | ||
subject: assertTerminal(left), | ||
objects | ||
}; | ||
} | ||
} | ||
class OptionalParslet { | ||
accepts(type, next) { | ||
return type === '=' && next !== '>'; | ||
} | ||
getPrecedence() { | ||
return Precedence.OPTIONAL; | ||
} | ||
parseInfix(parser, left) { | ||
parser.consume('='); | ||
const result = assertTerminal(left); | ||
result.optional = true; | ||
return result; | ||
} | ||
} | ||
class PropertyPathParslet { | ||
accepts(type, next) { | ||
return type === '.' && next !== '<'; | ||
} | ||
getPrecedence() { | ||
return Precedence.PROPERTY_PATH; | ||
} | ||
parseInfix(parser, left) { | ||
parser.consume('.'); | ||
const path = []; | ||
const allowed = ['Identifier', 'Number', 'StringValue']; | ||
let next; | ||
do { | ||
const token = parser.getToken(); | ||
if (!allowed.includes(token.type)) { | ||
throw new Error(`The token type '${token.type}' is not allowed in a property path.`); | ||
left: assertTerminal(left), | ||
elements: objects, | ||
meta: { | ||
brackets: '<>', | ||
dot | ||
} | ||
path.push(token.text); | ||
parser.consume(token.type); | ||
next = parser.peekToken(); | ||
} while (next.type !== '<' && parser.consume('.')); | ||
return { | ||
type: 'PROPERTY_PATH', | ||
left: assertTerminal(left), | ||
path | ||
}; | ||
@@ -652,3 +555,3 @@ } | ||
accepts(type, next) { | ||
return type === '(' && next !== ')'; | ||
return type === '('; | ||
} | ||
@@ -660,24 +563,9 @@ getPrecedence() { | ||
parser.consume('('); | ||
const result = parser.parseNonTerminalType(Precedence.ALL); | ||
const result = parser.tryParseType(Precedence.ALL); | ||
if (!parser.consume(')')) { | ||
throw new Error('Unterminated parenthesis'); | ||
} | ||
return result; | ||
} | ||
} | ||
class KeyValueParslet { | ||
accepts(type, next) { | ||
return type === ':'; | ||
} | ||
getPrecedence() { | ||
return Precedence.KEY_VALUE; | ||
} | ||
parseInfix(parser, left) { | ||
parser.consume(':'); | ||
const value = parser.parseType(Precedence.KEY_VALUE); | ||
return { | ||
type: 'KEY_VALUE', | ||
key: left.type === 'NUMBER' ? left : assertTerminal(left), | ||
value: value | ||
type: 'PARENTHESIS', | ||
element: result // NOTE: this can only be non-terminal or undefined if it is a parameter list | ||
}; | ||
@@ -720,5 +608,18 @@ } | ||
do { | ||
const next = parser.parseNonTerminalType(Precedence.PARAMETER_LIST); | ||
elements.push(assertNamedKeyValueOrTerminal(next)); | ||
try { | ||
const next = parser.parseNonTerminalType(Precedence.PARAMETER_LIST); | ||
elements.push(assertNamedKeyValueOrTerminal(next)); | ||
} | ||
catch (e) { | ||
if (this.allowTrailingComma && e instanceof NoParsletFoundError) { | ||
break; | ||
} | ||
else { | ||
throw e; | ||
} | ||
} | ||
} while (parser.consume(',')); | ||
if (elements.length > 0 && elements.slice(0, -1).some(e => e.type === 'VARIADIC')) { | ||
throw new Error('Only the last parameter may be a rest parameter'); | ||
} | ||
return { | ||
@@ -733,3 +634,3 @@ type: 'PARAMETER_LIST', | ||
accepts(type, next) { | ||
return (type === '?' && !isQuestionMarkUnknownType(next)) || type === '!'; | ||
return type === '?' && !isQuestionMarkUnknownType(next); | ||
} | ||
@@ -740,9 +641,10 @@ getPrecedence() { | ||
parsePrefix(parser) { | ||
const nullable = parser.consume('?') || !parser.consume('!'); | ||
const value = parser.parseType(Precedence.NULLABLE); | ||
if (value.nullable !== undefined) { | ||
throw new Error('Multiple nullable modifiers on same type'); | ||
} | ||
value.nullable = nullable; | ||
return value; | ||
parser.consume('?'); | ||
return { | ||
type: 'NULLABLE', | ||
element: parser.parseType(Precedence.NULLABLE), | ||
meta: { | ||
position: 'PREFIX' | ||
} | ||
}; | ||
} | ||
@@ -752,3 +654,3 @@ } | ||
accepts(type, next) { | ||
return type === '?' || type === '!'; | ||
return type === '?'; | ||
} | ||
@@ -759,35 +661,60 @@ getPrecedence() { | ||
parseInfix(parser, left) { | ||
const nullable = parser.consume('?') || !parser.consume('!'); | ||
const value = assertTerminal(left); | ||
if (value.nullable !== undefined) { | ||
throw new Error('Multiple nullable modifiers on same type'); | ||
} | ||
value.nullable = nullable; | ||
return value; | ||
parser.consume('?'); | ||
return { | ||
type: 'NULLABLE', | ||
element: assertTerminal(left), | ||
meta: { | ||
position: 'SUFFIX' | ||
} | ||
}; | ||
} | ||
} | ||
class OptionalParslet { | ||
accepts(type, next) { | ||
return type === '='; | ||
} | ||
getPrecedence() { | ||
return Precedence.OPTIONAL; | ||
} | ||
parsePrefix(parser) { | ||
parser.consume('='); | ||
return { | ||
type: 'OPTIONAL', | ||
element: parser.parseType(Precedence.OPTIONAL), | ||
meta: { | ||
position: 'PREFIX' | ||
} | ||
}; | ||
} | ||
parseInfix(parser, left) { | ||
parser.consume('='); | ||
return { | ||
type: 'OPTIONAL', | ||
element: assertTerminal(left), | ||
meta: { | ||
position: 'SUFFIX' | ||
} | ||
}; | ||
} | ||
} | ||
const baseGrammar = () => { | ||
return { | ||
prefixParslets: [ | ||
new NameParslet(), | ||
new SpecialTypesParslet(), | ||
new NullablePrefixParslet(), | ||
new VariadicParslet(), | ||
new OptionalParslet(), | ||
new RecordParslet(), | ||
new ModuleParslet(), | ||
new NumberParslet(), | ||
new ParenthesisParslet() | ||
new ParenthesisParslet(), | ||
new SpecialTypesParslet() | ||
], | ||
infixParslets: [ | ||
new ParameterListParslet({ | ||
allowTrailingComma: false | ||
allowTrailingComma: true | ||
}), | ||
new PropertyPathParslet(), | ||
new KeyValueParslet(), | ||
new GenericParslet(), | ||
new UnenclosedUnionParslet(), | ||
new OptionalParslet(), | ||
new NullableInfixParslet(), | ||
new PropertyPathParslet() | ||
new NullableInfixParslet() | ||
] | ||
@@ -800,9 +727,12 @@ }; | ||
let parameters; | ||
if (value.type === 'PARAMETER_LIST') { | ||
parameters = value.elements; | ||
if (value.element === undefined) { | ||
parameters = []; | ||
} | ||
else if (value.element.type === 'PARAMETER_LIST') { | ||
parameters = value.element.elements; | ||
} | ||
else { | ||
parameters = [assertNamedKeyValueOrTerminal(value)]; | ||
parameters = [value.element]; | ||
} | ||
return parameters; | ||
return parameters.map(p => assertNamedKeyValueOrTerminal(p)); | ||
} | ||
@@ -840,3 +770,3 @@ getNamedParameters(value) { | ||
parser.consume('function'); | ||
const hasParenthesis = parser.consume('('); | ||
const hasParenthesis = parser.getToken().type === '('; | ||
if (!this.allowWithoutParenthesis && !hasParenthesis) { | ||
@@ -847,26 +777,26 @@ throw new Error('function is missing parameter list'); | ||
type: 'FUNCTION', | ||
parameters: [] | ||
parameters: [], | ||
meta: { | ||
arrow: false, | ||
parenthesis: hasParenthesis | ||
} | ||
}; | ||
if (hasParenthesis) { | ||
if (!parser.consume(')')) { | ||
const value = parser.parseNonTerminalType(Precedence.ALL); | ||
if (this.allowNamedParameters === undefined) { | ||
result.parameters = this.getUnnamedParameters(value); | ||
} | ||
else { | ||
result.parameters = this.getParameters(value); | ||
for (const p of result.parameters) { | ||
if (p.type === 'KEY_VALUE' && !this.allowNamedParameters.includes(p.key.name)) { | ||
throw new Error(`only allowed named parameters are ${this.allowNamedParameters.join(',')} but got ${p.type}`); | ||
} | ||
const value = parser.parseNonTerminalType(Precedence.FUNCTION); | ||
if (value.type !== 'PARENTHESIS') { | ||
throw new UnexpectedTypeError(value); | ||
} | ||
if (this.allowNamedParameters === undefined) { | ||
result.parameters = this.getUnnamedParameters(value); | ||
} | ||
else { | ||
result.parameters = this.getParameters(value); | ||
for (const p of result.parameters) { | ||
if (p.type === 'KEY_VALUE' && !this.allowNamedParameters.includes(p.left.value)) { | ||
throw new Error(`only allowed named parameters are ${this.allowNamedParameters.join(',')} but got ${p.type}`); | ||
} | ||
} | ||
if (!parser.consume(')')) { | ||
throw new Error('function parameter list is not terminated'); | ||
} | ||
} | ||
if (parser.consume(':')) { | ||
if (!parser.consume('void')) { | ||
result.returnType = parser.parseType(Precedence.PREFIX); | ||
} | ||
result.returnType = parser.parseType(Precedence.PREFIX); | ||
} | ||
@@ -883,2 +813,238 @@ else { | ||
class StringValueParslet { | ||
accepts(type) { | ||
return type === 'StringValue'; | ||
} | ||
getPrecedence() { | ||
return Precedence.PREFIX; | ||
} | ||
parsePrefix(parser) { | ||
const token = parser.getToken(); | ||
parser.consume('StringValue'); | ||
return { | ||
type: 'STRING_VALUE', | ||
value: token.text.slice(1, -1), | ||
meta: { | ||
quote: token.text[0] | ||
} | ||
}; | ||
} | ||
} | ||
class NamePathParslet { | ||
constructor(opts) { | ||
this.allowJsdocNamePaths = opts.allowJsdocNamePaths; | ||
this.stringValueParslet = new StringValueParslet(); | ||
} | ||
accepts(type, next) { | ||
return (type === '.' && next !== '<') || (this.allowJsdocNamePaths && (type === '~' || type === '#')); | ||
} | ||
getPrecedence() { | ||
return Precedence.NAME_PATH; | ||
} | ||
parseInfix(parser, left) { | ||
const type = parser.getToken().text; | ||
parser.consume('.') || parser.consume('~') || parser.consume('#'); | ||
let next; | ||
if (parser.getToken().type === 'StringValue') { | ||
next = this.stringValueParslet.parsePrefix(parser); | ||
} | ||
else { | ||
next = parser.parseNonTerminalType(Precedence.NAME_PATH); | ||
if (next.type !== 'NAME' && next.type !== 'NUMBER') { | ||
throw new UnexpectedTypeError(next); | ||
} | ||
} | ||
return { | ||
type: 'NAME_PATH', | ||
left: assertTerminal(left), | ||
right: next, | ||
meta: { | ||
type | ||
} | ||
}; | ||
} | ||
} | ||
class KeyValueParslet { | ||
constructor(opts) { | ||
this.allowOnlyNameOrNumberProperties = opts.allowOnlyNameOrNumberProperties; | ||
} | ||
accepts(type, next) { | ||
return type === ':'; | ||
} | ||
getPrecedence() { | ||
return Precedence.KEY_VALUE; | ||
} | ||
parseInfix(parser, left) { | ||
if (this.allowOnlyNameOrNumberProperties && left.type !== 'NUMBER' && left.type !== 'NAME') { | ||
throw new UnexpectedTypeError(left); | ||
} | ||
parser.consume(':'); | ||
const value = parser.parseType(Precedence.KEY_VALUE); | ||
return { | ||
type: 'KEY_VALUE', | ||
left: left.type === 'NUMBER' ? left : assertTerminal(left), | ||
right: value | ||
}; | ||
} | ||
} | ||
class TypeOfParslet { | ||
accepts(type, next) { | ||
return type === 'typeof'; | ||
} | ||
getPrecedence() { | ||
return Precedence.KEY_OF_TYPE_OF; | ||
} | ||
parsePrefix(parser) { | ||
parser.consume('typeof'); | ||
return { | ||
type: 'TYPE_OF', | ||
element: assertTerminal(parser.parseType(Precedence.KEY_OF_TYPE_OF)) | ||
}; | ||
} | ||
} | ||
class VariadicParslet { | ||
constructor(opts) { | ||
this.allowEnclosingBrackets = opts.allowEnclosingBrackets; | ||
} | ||
accepts(type) { | ||
return type === '...'; | ||
} | ||
getPrecedence() { | ||
return Precedence.PREFIX; | ||
} | ||
parsePrefix(parser) { | ||
parser.consume('...'); | ||
const brackets = this.allowEnclosingBrackets && parser.consume('['); | ||
const value = parser.tryParseType(Precedence.PREFIX); | ||
if (brackets && !parser.consume(']')) { | ||
throw new Error('Unterminated variadic type. Missing \']\''); | ||
} | ||
if (value !== undefined) { | ||
return { | ||
type: 'VARIADIC', | ||
element: assertTerminal(value), | ||
meta: { | ||
position: 'PREFIX', | ||
squareBrackets: brackets | ||
} | ||
}; | ||
} | ||
else { | ||
return { | ||
type: 'VARIADIC', | ||
meta: { | ||
position: 'ONLY_DOTS', | ||
squareBrackets: false | ||
} | ||
}; | ||
} | ||
} | ||
parseInfix(parser, left) { | ||
parser.consume('...'); | ||
return { | ||
type: 'VARIADIC', | ||
element: assertTerminal(left), | ||
meta: { | ||
position: 'SUFFIX', | ||
squareBrackets: false | ||
} | ||
}; | ||
} | ||
} | ||
const reservedWords = [ | ||
'null', | ||
'true', | ||
'false', | ||
'break', | ||
'case', | ||
'catch', | ||
'class', | ||
'const', | ||
'continue', | ||
'debugger', | ||
'default', | ||
'delete', | ||
'do', | ||
'else', | ||
'export', | ||
'extends', | ||
'finally', | ||
'for', | ||
'function', | ||
'if', | ||
'import', | ||
'in', | ||
'instanceof', | ||
'new', | ||
'return', | ||
'super', | ||
'switch', | ||
'this', | ||
'throw', | ||
'try', | ||
'typeof', | ||
'var', | ||
'void', | ||
'while', | ||
'with', | ||
'yield' | ||
]; | ||
class NameParslet { | ||
constructor(options) { | ||
this.allowedAdditionalTokens = options.allowedAdditionalTokens; | ||
} | ||
accepts(type, next) { | ||
return type === 'Identifier' || type === 'this' || type === 'new' || this.allowedAdditionalTokens.includes(type); | ||
} | ||
getPrecedence() { | ||
return Precedence.PREFIX; | ||
} | ||
parsePrefix(parser) { | ||
const token = parser.getToken(); | ||
parser.consume('Identifier') || parser.consume('this') || parser.consume('new') || | ||
this.allowedAdditionalTokens.some(type => parser.consume(type)); | ||
return { | ||
type: 'NAME', | ||
value: token.text, | ||
meta: { | ||
reservedWord: reservedWords.includes(token.text) | ||
} | ||
}; | ||
} | ||
} | ||
class NotNullableParslet { | ||
accepts(type, next) { | ||
return type === '!'; | ||
} | ||
getPrecedence() { | ||
return Precedence.NULLABLE; | ||
} | ||
parsePrefix(parser) { | ||
parser.consume('!'); | ||
return { | ||
type: 'NOT_NULLABLE', | ||
element: parser.parseType(Precedence.NULLABLE), | ||
meta: { | ||
position: 'PREFIX' | ||
} | ||
}; | ||
} | ||
parseInfix(parser, left) { | ||
parser.consume('!'); | ||
return { | ||
type: 'NOT_NULLABLE', | ||
element: assertTerminal(left), | ||
meta: { | ||
position: 'SUFFIX' | ||
} | ||
}; | ||
} | ||
} | ||
const closureGrammar = () => { | ||
@@ -889,2 +1055,6 @@ const { prefixParslets, infixParslets } = baseGrammar(); | ||
...prefixParslets, | ||
new NameParslet({ | ||
allowedAdditionalTokens: [] | ||
}), | ||
new TypeOfParslet(), | ||
new FunctionParslet({ | ||
@@ -894,5 +1064,21 @@ allowWithoutParenthesis: false, | ||
allowNoReturnType: true | ||
}) | ||
}), | ||
new VariadicParslet({ | ||
allowEnclosingBrackets: false | ||
}), | ||
new NameParslet({ | ||
allowedAdditionalTokens: ['keyof'] | ||
}), | ||
new NotNullableParslet() | ||
], | ||
infixParslets | ||
infixParslets: [ | ||
...infixParslets, | ||
new NamePathParslet({ | ||
allowJsdocNamePaths: false | ||
}), | ||
new KeyValueParslet({ | ||
allowOnlyNameOrNumberProperties: true | ||
}), | ||
new NotNullableParslet() | ||
] | ||
}; | ||
@@ -915,10 +1101,7 @@ }; | ||
type: 'SYMBOL', | ||
name: left.name | ||
value: left.value | ||
}; | ||
if (!parser.consume(')')) { | ||
const next = parser.parseNonTerminalType(Precedence.SYMBOL); | ||
if (next.type !== 'NUMBER' && next.type !== 'NAME') { | ||
throw new Error('Symbol value must be a number or a name'); | ||
} | ||
result.value = next; | ||
result.element = assertNumberOrVariadicName(next); | ||
if (!parser.consume(')')) { | ||
@@ -932,30 +1115,2 @@ throw new Error('Symbol does not end after value'); | ||
class ClassPathParslet { | ||
accepts(type, next) { | ||
return type === '#' || type === '~'; | ||
} | ||
getPrecedence() { | ||
return Precedence.POSTFIX; | ||
} | ||
parseInfix(parser, left) { | ||
if (left.type !== 'NAME') { | ||
throw new Error('All elements of class path have to be identifiers'); | ||
} | ||
let result = left.name; | ||
let lastToken = parser.getToken(); | ||
while (parser.consume('#') || parser.consume('~') || parser.consume('/')) { | ||
const next = parser.parseType(Precedence.POSTFIX); | ||
if (next.type !== 'NAME') { | ||
throw new Error('All elements of class path have to be identifiers'); | ||
} | ||
result += lastToken.text + next.name; | ||
lastToken = parser.getToken(); | ||
} | ||
return { | ||
type: 'NAME', | ||
name: result | ||
}; | ||
} | ||
} | ||
class ArrayBracketsParslet { | ||
@@ -973,9 +1128,16 @@ accepts(type, next) { | ||
type: 'GENERIC', | ||
subject: { | ||
left: { | ||
type: 'NAME', | ||
name: 'Array' | ||
value: 'Array', | ||
meta: { | ||
reservedWord: false | ||
} | ||
}, | ||
objects: [ | ||
elements: [ | ||
assertTerminal(left) | ||
] | ||
], | ||
meta: { | ||
brackets: '[]', | ||
dot: false | ||
} | ||
}; | ||
@@ -985,5 +1147,5 @@ } | ||
class StringValueParslet { | ||
accepts(type) { | ||
return type === 'StringValue'; | ||
class ModuleParslet { | ||
accepts(type, next) { | ||
return type === 'module'; | ||
} | ||
@@ -994,9 +1156,31 @@ getPrecedence() { | ||
parsePrefix(parser) { | ||
const token = parser.getToken(); | ||
parser.consume('StringValue'); | ||
return { | ||
type: 'STRING_VALUE', | ||
value: token.text.slice(1, -1), | ||
quote: token.text[0] | ||
}; | ||
parser.consume('module'); | ||
if (!parser.consume(':')) { | ||
throw new Error('module needs to have a \':\' afterwards.'); | ||
} | ||
let token = parser.getToken(); | ||
if (parser.consume('StringValue')) { | ||
return { | ||
type: 'MODULE', | ||
value: token.text.slice(1, -1), | ||
meta: { | ||
quote: token.text[0] | ||
} | ||
}; | ||
} | ||
else { | ||
let result = ''; | ||
const allowed = ['Identifier', '@', '/']; | ||
while (allowed.some(type => parser.consume(type))) { | ||
result += token.text; | ||
token = parser.getToken(); | ||
} | ||
return { | ||
type: 'MODULE', | ||
value: result, | ||
meta: { | ||
quote: undefined | ||
} | ||
}; | ||
} | ||
} | ||
@@ -1015,3 +1199,11 @@ } | ||
}), | ||
new StringValueParslet() | ||
new StringValueParslet(), | ||
new ModuleParslet(), | ||
new VariadicParslet({ | ||
allowEnclosingBrackets: true | ||
}), | ||
new NameParslet({ | ||
allowedAdditionalTokens: ['keyof'] | ||
}), | ||
new NotNullableParslet() | ||
], | ||
@@ -1021,4 +1213,13 @@ infixParslets: [ | ||
new SymbolParslet(), | ||
new ClassPathParslet(), | ||
new ArrayBracketsParslet() | ||
new ArrayBracketsParslet(), | ||
new NamePathParslet({ | ||
allowJsdocNamePaths: true | ||
}), | ||
new KeyValueParslet({ | ||
allowOnlyNameOrNumberProperties: false | ||
}), | ||
new VariadicParslet({ | ||
allowEnclosingBrackets: true | ||
}), | ||
new NotNullableParslet() | ||
] | ||
@@ -1028,18 +1229,34 @@ }; | ||
class TypeOfParslet { | ||
class TupleParslet { | ||
constructor(opts) { | ||
this.allowQuestionMark = opts.allowQuestionMark; | ||
} | ||
accepts(type, next) { | ||
return type === 'typeof'; | ||
return type === '['; | ||
} | ||
getPrecedence() { | ||
return Precedence.KEY_OF_TYPE_OF; | ||
return Precedence.TUPLE; | ||
} | ||
parsePrefix(parser) { | ||
parser.consume('typeof'); | ||
parser.consume('['); | ||
const result = { | ||
type: 'TYPE_OF' | ||
type: 'TUPLE', | ||
elements: [] | ||
}; | ||
const value = parser.tryParseType(Precedence.KEY_OF_TYPE_OF); | ||
if (value !== undefined) { | ||
result.value = assertTerminal(value); | ||
if (parser.consume(']')) { | ||
return result; | ||
} | ||
const typeList = parser.parseNonTerminalType(Precedence.ALL); | ||
if (typeList.type === 'PARAMETER_LIST') { | ||
result.elements = typeList.elements.map(assertTerminal); | ||
} | ||
else { | ||
result.elements = [assertTerminal(typeList)]; | ||
} | ||
if (!parser.consume(']')) { | ||
throw new Error('Unterminated \'[\''); | ||
} | ||
if (!this.allowQuestionMark && result.elements.some(e => e.type === 'UNKNOWN')) { | ||
throw new Error('Question mark in tuple not allowed'); | ||
} | ||
return result; | ||
@@ -1058,10 +1275,6 @@ } | ||
parser.consume('keyof'); | ||
const result = { | ||
type: 'KEY_OF' | ||
return { | ||
type: 'KEY_OF', | ||
element: assertTerminal(parser.parseType(Precedence.KEY_OF_TYPE_OF)) | ||
}; | ||
const value = parser.tryParseType(Precedence.KEY_OF_TYPE_OF); | ||
if (value !== undefined) { | ||
result.value = assertTerminal(value); | ||
} | ||
return result; | ||
} | ||
@@ -1091,3 +1304,3 @@ } | ||
type: 'IMPORT', | ||
path | ||
element: path | ||
}; | ||
@@ -1105,17 +1318,16 @@ } | ||
parsePrefix(parser) { | ||
parser.consume('('); | ||
const hasParenthesis = parser.consume('('); | ||
parser.consume(')'); | ||
if (!parser.consume('=') || !parser.consume('>')) { | ||
if (!parser.consume('=>')) { | ||
throw new Error('Unexpected empty parenthesis. Expected \'=>\' afterwards.'); | ||
} | ||
const result = { | ||
return { | ||
type: 'FUNCTION', | ||
parameters: [], | ||
arrow: true | ||
meta: { | ||
arrow: true, | ||
parenthesis: hasParenthesis | ||
}, | ||
returnType: parser.parseType(Precedence.ALL) | ||
}; | ||
if (!parser.consume('void')) { | ||
const right = parser.parseType(Precedence.ALL); | ||
result.returnType = right; | ||
} | ||
return result; | ||
} | ||
@@ -1125,3 +1337,3 @@ } | ||
accepts(type, next) { | ||
return type === '=' && next === '>'; | ||
return type === '=>'; | ||
} | ||
@@ -1132,18 +1344,15 @@ getPrecedence() { | ||
parseInfix(parser, left) { | ||
var _a; | ||
if (((_a = parser.previousToken()) === null || _a === void 0 ? void 0 : _a.type) !== ')') { | ||
throw new Error('Unexpected Arrow. Expected parenthesis before.'); | ||
if (left.type !== 'PARENTHESIS') { | ||
throw new UnexpectedTypeError(left); | ||
} | ||
parser.consume('='); | ||
parser.consume('>'); | ||
const result = { | ||
parser.consume('=>'); | ||
return { | ||
type: 'FUNCTION', | ||
parameters: this.getParameters(left).map(assertNamedKeyValueOrName), | ||
arrow: true | ||
meta: { | ||
arrow: true, | ||
parenthesis: true | ||
}, | ||
returnType: parser.parseType(Precedence.ALL) | ||
}; | ||
if (!parser.consume('void')) { | ||
const right = parser.parseType(Precedence.ALL); | ||
result.returnType = right; | ||
} | ||
return result; | ||
} | ||
@@ -1154,2 +1363,5 @@ } | ||
const { prefixParslets, infixParslets } = baseGrammar(); | ||
// typescript does not support explicit non nullability | ||
// https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#patterns-that-are-known-not-to-be-supported | ||
// module seems not to be supported | ||
return { | ||
@@ -1164,4 +1376,14 @@ prefixParslets: [ | ||
new FunctionParslet({ | ||
allowWithoutParenthesis: true, | ||
allowNoReturnType: false | ||
allowWithoutParenthesis: false, | ||
allowNoReturnType: false, | ||
allowNamedParameters: ['this', 'new'] | ||
}), | ||
new TupleParslet({ | ||
allowQuestionMark: false | ||
}), | ||
new VariadicParslet({ | ||
allowEnclosingBrackets: false | ||
}), | ||
new NameParslet({ | ||
allowedAdditionalTokens: [] | ||
}) | ||
@@ -1172,4 +1394,9 @@ ], | ||
new ArrayBracketsParslet(), | ||
new VariadicParslet(), | ||
new ArrowFunctionWithParametersParslet() | ||
new ArrowFunctionWithParametersParslet(), | ||
new NamePathParslet({ | ||
allowJsdocNamePaths: false | ||
}), | ||
new KeyValueParslet({ | ||
allowOnlyNameOrNumberProperties: true | ||
}) | ||
] | ||
@@ -1200,121 +1427,425 @@ }; | ||
/** | ||
* @internal | ||
*/ | ||
function catharsisTransform(object) { | ||
var _a, _b, _c; | ||
const newObject = Object.assign({}, object); | ||
let value; | ||
switch (object.type) { | ||
case 'ALL': | ||
newObject.type = 'AllLiteral'; | ||
break; | ||
case 'NULL': | ||
newObject.type = 'NullLiteral'; | ||
break; | ||
case 'STRING_VALUE': | ||
newObject.type = 'NameExpression'; | ||
delete newObject.value; | ||
delete newObject.quote; | ||
newObject.name = `${object.quote}${object.value}${object.quote}`; | ||
break; | ||
case 'UNDEFINED': | ||
newObject.type = 'UndefinedLiteral'; | ||
break; | ||
case 'UNKNOWN': | ||
newObject.type = 'UnknownLiteral'; | ||
break; | ||
case 'FUNCTION': | ||
newObject.type = 'FunctionType'; | ||
delete newObject.parameters; | ||
newObject.params = object.parameters.filter(p => { | ||
if (p.type === 'KEY_VALUE' && p.key.type === 'NAME') { | ||
if (p.key.name === 'this') { | ||
newObject.this = catharsisTransform(p.value); | ||
return false; | ||
} | ||
if (p.key.name === 'new') { | ||
newObject.new = catharsisTransform(p.value); | ||
return false; | ||
} | ||
} | ||
return true; | ||
}).map(catharsisTransform); | ||
if (object.returnType !== undefined) { | ||
delete newObject.returnType; | ||
newObject.result = catharsisTransform(object.returnType); | ||
function transform(rules, parseResult) { | ||
const rule = rules[parseResult.type]; | ||
if (rule === undefined) { | ||
throw new Error(`In this set of transform rules exists no rule for type ${parseResult.type}.`); | ||
} | ||
return rule(parseResult, aParseResult => transform(rules, aParseResult)); | ||
} | ||
function notAvailableTransform(parseResult) { | ||
throw new Error('This transform is not available. Are you trying the correct parsing mode?'); | ||
} | ||
function extractSpecialParams(source) { | ||
const result = { | ||
params: [] | ||
}; | ||
for (const param of source.parameters) { | ||
if (param.type === 'KEY_VALUE' && param.left.type === 'NAME') { | ||
if (param.left.value === 'this') { | ||
result.this = param.right; | ||
} | ||
break; | ||
case 'GENERIC': | ||
newObject.type = 'TypeApplication'; | ||
delete newObject.objects; | ||
newObject.applications = object.objects.map(catharsisTransform); | ||
delete newObject.subject; | ||
newObject.expression = catharsisTransform(object.subject); | ||
break; | ||
case 'MODULE': | ||
newObject.type = 'NameExpression'; | ||
delete newObject.path; | ||
newObject.name = object.path; | ||
break; | ||
case 'NAME': | ||
newObject.type = 'NameExpression'; | ||
break; | ||
case 'NUMBER': | ||
newObject.type = 'NameExpression'; | ||
delete newObject.value; | ||
newObject.name = object.value.toString(10); | ||
break; | ||
case 'RECORD': | ||
newObject.type = 'RecordType'; | ||
newObject.fields = object.fields.map(field => { | ||
if (field.type !== 'KEY_VALUE') { | ||
else if (param.left.value === 'new') { | ||
result.new = param.right; | ||
} | ||
else { | ||
result.params.push(param); | ||
} | ||
} | ||
else { | ||
result.params.push(param); | ||
} | ||
} | ||
return result; | ||
} | ||
const catharsisTransformRules = { | ||
OPTIONAL: (result, transform) => { | ||
const transformed = transform(result.element); | ||
transformed.optional = true; | ||
return transformed; | ||
}, | ||
NULLABLE: (result, transform) => { | ||
const transformed = transform(result.element); | ||
transformed.nullable = true; | ||
return transformed; | ||
}, | ||
NOT_NULLABLE: (result, transform) => { | ||
const transformed = transform(result.element); | ||
transformed.nullable = false; | ||
return transformed; | ||
}, | ||
VARIADIC: (result, transform) => { | ||
if (result.element === undefined) { | ||
throw new Error('dots without value are not allowed in catharsis mode'); | ||
} | ||
const transformed = transform(result.element); | ||
transformed.repeatable = true; | ||
return transformed; | ||
}, | ||
ANY: () => ({ | ||
type: 'AllLiteral' | ||
}), | ||
NULL: () => ({ | ||
type: 'NullLiteral' | ||
}), | ||
STRING_VALUE: result => ({ | ||
type: 'NameExpression', | ||
name: `${result.meta.quote}${result.value}${result.meta.quote}` | ||
}), | ||
UNDEFINED: () => ({ | ||
type: 'UndefinedLiteral' | ||
}), | ||
UNKNOWN: () => ({ | ||
type: 'UnknownLiteral' | ||
}), | ||
FUNCTION: (result, transform) => { | ||
const params = extractSpecialParams(result); | ||
const transformed = { | ||
type: 'FunctionType', | ||
params: params.params.map(transform) | ||
}; | ||
if (params.this !== undefined) { | ||
transformed.this = transform(params.this); | ||
} | ||
if (params.new !== undefined) { | ||
transformed.new = transform(params.new); | ||
} | ||
if (result.returnType !== undefined) { | ||
transformed.result = transform(result.returnType); | ||
} | ||
return transformed; | ||
}, | ||
GENERIC: (result, transform) => ({ | ||
type: 'TypeApplication', | ||
applications: result.elements.map(o => transform(o)), | ||
expression: transform(result.left) | ||
}), | ||
MODULE: result => { | ||
var _a; | ||
const quote = (_a = result.meta.quote) !== null && _a !== void 0 ? _a : ''; | ||
return { | ||
type: 'NameExpression', | ||
name: 'module:' + quote + result.value + quote | ||
}; | ||
}, | ||
NAME: result => { | ||
const transformed = { | ||
type: 'NameExpression', | ||
name: result.value | ||
}; | ||
if (result.meta.reservedWord) { | ||
transformed.reservedWord = true; | ||
} | ||
return transformed; | ||
}, | ||
NUMBER: result => ({ | ||
type: 'NameExpression', | ||
name: result.value.toString() | ||
}), | ||
OBJECT: (result, transform) => { | ||
const transformed = { | ||
type: 'RecordType', | ||
fields: [] | ||
}; | ||
for (const field of result.elements) { | ||
if (field.type !== 'KEY_VALUE') { | ||
transformed.fields.push({ | ||
type: 'FieldType', | ||
key: transform(field), | ||
value: undefined | ||
}); | ||
} | ||
else { | ||
transformed.fields.push(transform(field)); | ||
} | ||
} | ||
return transformed; | ||
}, | ||
UNION: (result, transform) => ({ | ||
type: 'TypeUnion', | ||
elements: result.elements.map(e => transform(e)) | ||
}), | ||
KEY_VALUE: (result, transform) => ({ | ||
type: 'FieldType', | ||
key: transform(result.left), | ||
value: transform(result.right) | ||
}), | ||
NAME_PATH: (result, transform) => { | ||
const leftResult = transform(result.left); | ||
const rightResult = transform(result.right); | ||
return { | ||
type: 'NameExpression', | ||
name: `${leftResult.name}${result.meta.type}${rightResult.name}` | ||
}; | ||
}, | ||
SYMBOL: result => { | ||
let value = ''; | ||
let element = result.element; | ||
let trailingDots = false; | ||
if ((element === null || element === void 0 ? void 0 : element.type) === 'VARIADIC') { | ||
if (element.meta.position === 'PREFIX') { | ||
value = '...'; | ||
} | ||
else { | ||
trailingDots = true; | ||
} | ||
element = element.element; | ||
} | ||
if ((element === null || element === void 0 ? void 0 : element.type) === 'NAME') { | ||
value += element.value; | ||
} | ||
else if ((element === null || element === void 0 ? void 0 : element.type) === 'NUMBER') { | ||
value += element.value.toString(); | ||
} | ||
if (trailingDots) { | ||
value += '...'; | ||
} | ||
return { | ||
type: 'NameExpression', | ||
name: `${result.value}(${value})` | ||
}; | ||
}, | ||
PARENTHESIS: (result, transform) => transform(assertTerminal(result.element)), | ||
IMPORT: notAvailableTransform, | ||
KEY_OF: notAvailableTransform, | ||
PARAMETER_LIST: notAvailableTransform, | ||
TUPLE: notAvailableTransform, | ||
TYPE_OF: notAvailableTransform | ||
}; | ||
function catharsisTransform(result) { | ||
return transform(catharsisTransformRules, result); | ||
} | ||
function getQuoteStyle(quote) { | ||
switch (quote) { | ||
case undefined: | ||
return 'none'; | ||
case '\'': | ||
return 'single'; | ||
case '"': | ||
return 'double'; | ||
} | ||
} | ||
function getMemberType(type) { | ||
switch (type) { | ||
case '~': | ||
return 'INNER_MEMBER'; | ||
case '#': | ||
return 'INSTANCE_MEMBER'; | ||
case '.': | ||
return 'MEMBER'; | ||
} | ||
} | ||
const jtpRules = { | ||
OPTIONAL: (result, transform) => ({ | ||
type: 'OPTIONAL', | ||
value: transform(result.element), | ||
meta: { | ||
syntax: result.meta.position === 'PREFIX' ? 'PREFIX_EQUAL_SIGN' : 'SUFFIX_EQUALS_SIGN' | ||
} | ||
}), | ||
NULLABLE: (result, transform) => ({ | ||
type: 'NULLABLE', | ||
value: transform(result.element), | ||
meta: { | ||
syntax: result.meta.position === 'PREFIX' ? 'PREFIX_QUESTION_MARK' : 'SUFFIX_QUESTION_MARK' | ||
} | ||
}), | ||
NOT_NULLABLE: (result, transform) => ({ | ||
type: 'NOT_NULLABLE', | ||
value: transform(result.element), | ||
meta: { | ||
syntax: result.meta.position === 'PREFIX' ? 'PREFIX_BANG' : 'SUFFIX_BANG' | ||
} | ||
}), | ||
VARIADIC: (result, transform) => { | ||
const transformed = { | ||
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; | ||
}, | ||
NAME: result => ({ | ||
type: 'NAME', | ||
name: result.value | ||
}), | ||
TYPE_OF: (result, transform) => ({ | ||
type: 'TYPE_QUERY', | ||
name: transform(result.element) | ||
}), | ||
TUPLE: (result, transform) => ({ | ||
type: 'TUPLE', | ||
entries: result.elements.map(transform) | ||
}), | ||
KEY_OF: (result, transform) => ({ | ||
type: 'KEY_QUERY', | ||
value: transform(result.element) | ||
}), | ||
IMPORT: result => ({ | ||
type: 'IMPORT', | ||
path: { | ||
type: 'STRING_VALUE', | ||
quoteStyle: getQuoteStyle(result.element.meta.quote), | ||
string: result.element.value | ||
} | ||
}), | ||
UNDEFINED: () => ({ | ||
type: 'NAME', | ||
name: 'undefined' | ||
}), | ||
ANY: () => ({ | ||
type: 'ANY' | ||
}), | ||
FUNCTION: (result, transform) => { | ||
const specialParams = extractSpecialParams(result); | ||
const transformed = { | ||
type: result.meta.arrow ? 'ARROW' : 'FUNCTION', | ||
params: specialParams.params.map(param => { | ||
if (param.type === 'KEY_VALUE') { | ||
return { | ||
type: 'FieldType', | ||
key: catharsisTransform(field), | ||
value: undefined | ||
type: 'NAMED_PARAMETER', | ||
name: param.left.value, | ||
typeName: transform(param.right) | ||
}; | ||
} | ||
else { | ||
return catharsisTransform(field); | ||
return transform(param); | ||
} | ||
}); | ||
break; | ||
case 'UNION': | ||
newObject.type = 'TypeUnion'; | ||
newObject.elements = object.elements.map(catharsisTransform); | ||
break; | ||
case 'KEY_VALUE': | ||
newObject.type = 'FieldType'; | ||
newObject.key = catharsisTransform(object.key); | ||
if (object.value !== undefined) { | ||
newObject.value = catharsisTransform(object.value); | ||
}), | ||
new: null, | ||
returns: null | ||
}; | ||
if (specialParams.this !== undefined) { | ||
transformed.this = transform(specialParams.this); | ||
} | ||
else if (!result.meta.arrow) { | ||
transformed.this = null; | ||
} | ||
if (specialParams.new !== undefined) { | ||
transformed.new = transform(specialParams.new); | ||
} | ||
if (result.returnType !== undefined) { | ||
transformed.returns = transform(result.returnType); | ||
} | ||
return transformed; | ||
}, | ||
GENERIC: (result, transform) => { | ||
const transformed = { | ||
type: 'GENERIC', | ||
subject: transform(result.left), | ||
objects: result.elements.map(transform), | ||
meta: { | ||
syntax: result.meta.brackets === '[]' ? 'SQUARE_BRACKET' : result.meta.dot ? 'ANGLE_BRACKET_WITH_DOT' : 'ANGLE_BRACKET' | ||
} | ||
break; | ||
case 'PROPERTY_PATH': | ||
newObject.type = 'NameExpression'; | ||
if (object.left.type !== 'NAME') { | ||
throw new Error('Other left types than \'NAME\' are not supported for catharsis compat mode'); | ||
}; | ||
if (result.meta.brackets === '[]' && result.elements[0].type === 'FUNCTION' && !result.elements[0].meta.parenthesis) { | ||
transformed.objects[0] = { | ||
type: 'NAME', | ||
name: 'function' | ||
}; | ||
} | ||
return transformed; | ||
}, | ||
KEY_VALUE: (result, transform) => { | ||
if (result.left.type !== 'NAME' && result.left.type !== 'NUMBER') { | ||
throw new Error('In JTP mode only name and number left values are allowed for record entries.'); | ||
} | ||
return { | ||
type: 'RECORD_ENTRY', | ||
key: result.left.value.toString(), | ||
quoteStyle: 'none', | ||
value: transform(result.right), | ||
readonly: false | ||
}; | ||
}, | ||
OBJECT: (result, transform) => { | ||
const entries = []; | ||
for (const field of result.elements) { | ||
if (field.type === 'KEY_VALUE') { | ||
entries.push(transform(field)); | ||
} | ||
delete newObject.left; | ||
delete newObject.path; | ||
newObject.name = object.left.name + '.' + object.path.join('.'); | ||
break; | ||
case 'SYMBOL': | ||
newObject.type = 'NameExpression'; | ||
delete newObject.value; | ||
value = ''; | ||
if (((_a = object.value) === null || _a === void 0 ? void 0 : _a.repeatable) === true) { | ||
value = '...'; | ||
else { | ||
const key = field.value; | ||
entries.push({ | ||
type: 'RECORD_ENTRY', | ||
key: `${key}`, | ||
quoteStyle: 'none', | ||
value: null, | ||
readonly: false | ||
}); | ||
} | ||
if (((_b = object.value) === null || _b === void 0 ? void 0 : _b.type) === 'NAME') { | ||
value += object.value.name; | ||
} | ||
else if (((_c = object.value) === null || _c === void 0 ? void 0 : _c.type) === 'NUMBER') { | ||
value += object.value.value.toString(10); | ||
} | ||
newObject.name = `${object.name}(${value})`; | ||
break; | ||
} | ||
return newObject; | ||
} | ||
return { | ||
type: 'RECORD', | ||
entries | ||
}; | ||
}, | ||
MODULE: result => ({ | ||
type: 'MODULE', | ||
value: { | ||
type: 'FILE_PATH', | ||
quoteStyle: getQuoteStyle(result.meta.quote), | ||
path: result.value | ||
} | ||
}), | ||
NAME_PATH: (result, transform) => { | ||
const transformed = { | ||
type: getMemberType(result.meta.type), | ||
owner: transform(result.left), | ||
name: `${result.right.value}`, | ||
quoteStyle: result.right.type === 'STRING_VALUE' ? getQuoteStyle(result.right.meta.quote) : 'none', | ||
hasEventPrefix: false | ||
}; | ||
if (transformed.owner.type === 'MODULE') { | ||
const tModule = transformed.owner; | ||
transformed.owner = transformed.owner.value; | ||
tModule.value = transformed; | ||
return tModule; | ||
} | ||
else { | ||
return transformed; | ||
} | ||
}, | ||
UNION: (result, transform) => { | ||
let index = result.elements.length; | ||
let rightTransformed = transform(result.elements[--index]); | ||
let left; | ||
do { | ||
left = result.elements[--index]; | ||
rightTransformed = { | ||
type: 'UNION', | ||
left: transform(left), | ||
right: rightTransformed | ||
}; | ||
} while (index > 0); | ||
return rightTransformed; | ||
}, | ||
PARENTHESIS: (result, transform) => ({ | ||
type: 'PARENTHESIS', | ||
value: transform(assertTerminal(result.element)) | ||
}), | ||
NULL: () => ({ | ||
type: 'NAME', | ||
name: 'null' | ||
}), | ||
UNKNOWN: () => ({ | ||
type: 'UNKNOWN' | ||
}), | ||
STRING_VALUE: result => ({ | ||
type: 'STRING_VALUE', | ||
quoteStyle: getQuoteStyle(result.meta.quote), | ||
string: result.value | ||
}), | ||
NUMBER: notAvailableTransform, | ||
SYMBOL: notAvailableTransform, | ||
PARAMETER_LIST: notAvailableTransform | ||
}; | ||
function jtpTransform(result) { | ||
return transform(jtpRules, result); | ||
} | ||
@@ -1324,2 +1855,3 @@ | ||
exports.catharsisTransform = catharsisTransform; | ||
exports.jtpTransform = jtpTransform; | ||
@@ -1326,0 +1858,0 @@ Object.defineProperty(exports, '__esModule', { value: true }); |
@@ -1,4 +0,5 @@ | ||
import { KeyValueResult, NameResult, NonTerminalResult, ParseResult } from './ParseResult'; | ||
export declare function assertTerminal(result: NonTerminalResult): ParseResult; | ||
import { KeyValueResult, NameResult, NonTerminalResult, NumberResult, ParseResult, VariadicResult } from './ParseResult'; | ||
export declare function assertTerminal(result?: NonTerminalResult): ParseResult; | ||
export declare function assertNamedKeyValueOrTerminal(result: NonTerminalResult): KeyValueResult | ParseResult; | ||
export declare function assertNamedKeyValueOrName(result: NonTerminalResult): KeyValueResult | NameResult; | ||
export declare function assertNumberOrVariadicName(result: NonTerminalResult): NumberResult | NameResult | VariadicResult<NameResult>; |
/** | ||
* @packageDocumentation | ||
* @package | ||
* This package provides a parser for jsdoc types. | ||
@@ -7,2 +7,3 @@ */ | ||
export * from './ParseResult'; | ||
export * from './catharsisTransform'; | ||
export * from './transforms/catharsisTransform'; | ||
export * from './transforms/jtpTransform'; |
@@ -1,2 +0,2 @@ | ||
export declare type TokenType = '(' | ')' | '[' | ']' | '{' | '}' | '|' | '<' | '>' | ',' | '*' | '?' | '!' | '=' | ':' | '.' | '@' | '#' | '~' | '/' | '...' | 'null' | 'undefined' | 'void' | 'function' | 'this' | 'new' | 'module' | 'typeof' | 'keyof' | 'import' | 'Identifier' | 'StringValue' | 'Number' | 'EOF'; | ||
export declare type TokenType = '(' | ')' | '[' | ']' | '{' | '}' | '|' | '<' | '>' | ',' | '*' | '?' | '!' | '=' | ':' | '.' | '@' | '#' | '~' | '/' | '=>' | '...' | 'null' | 'undefined' | 'function' | 'this' | 'new' | 'module' | 'typeof' | 'keyof' | 'import' | 'Identifier' | 'StringValue' | 'Number' | 'EOF'; | ||
export interface Token { | ||
@@ -3,0 +3,0 @@ type: TokenType; |
@@ -5,3 +5,3 @@ import { Token, TokenType } from './lexer/Token'; | ||
import { Grammar } from './grammars/Grammar'; | ||
import { Precedence } from './parslets/Precedence'; | ||
import { Precedence } from './Precedence'; | ||
export declare class ParserEngine { | ||
@@ -8,0 +8,0 @@ private readonly prefixParslets; |
@@ -1,89 +0,125 @@ | ||
export declare type ParseResult = NameResult | UnionResult | GenericResult | StringValueResult | NullResult | UndefinedResult | AllResult | UnknownResult | FunctionResult | RecordResult | ModuleResult | PropertyPathResult | SymbolResult | TypeOfResult | KeyOfResult | ImportResult | ArrowResult | TupleResult; | ||
export declare type ParseResult = NameResult | UnionResult | GenericResult | StringValueResult | NullResult | UndefinedResult | AnyResult | UnknownResult | FunctionResult | RecordResult | ModuleResult | NamePathResult | SymbolResult | TypeOfResult | KeyOfResult | ImportResult | TupleResult | OptionalResult<ParseResult> | NullableResult<ParseResult> | NotNullableResult<ParseResult> | VariadicResult<ParseResult> | ParenthesisResult; | ||
export declare type NonTerminalResult = ParseResult | KeyValueResult<ParseResult | NameResult | NumberResult> | NumberResult | ParameterList; | ||
export interface ModifiableResult { | ||
optional?: boolean; | ||
nullable?: boolean; | ||
repeatable?: boolean; | ||
export interface OptionalResult<T extends ParseResult> { | ||
type: 'OPTIONAL'; | ||
element: T; | ||
meta: { | ||
position: 'PREFIX' | 'SUFFIX'; | ||
}; | ||
} | ||
export declare type NameResult = ModifiableResult & { | ||
export interface NullableResult<T extends ParseResult> { | ||
type: 'NULLABLE'; | ||
element: T; | ||
meta: { | ||
position: 'PREFIX' | 'SUFFIX'; | ||
}; | ||
} | ||
export interface NotNullableResult<T extends ParseResult> { | ||
type: 'NOT_NULLABLE'; | ||
element: T; | ||
meta: { | ||
position: 'PREFIX' | 'SUFFIX'; | ||
}; | ||
} | ||
export interface VariadicResult<T extends ParseResult> { | ||
type: 'VARIADIC'; | ||
element?: T; | ||
meta: { | ||
position: 'PREFIX' | 'SUFFIX' | 'ONLY_DOTS'; | ||
squareBrackets: boolean; | ||
}; | ||
} | ||
export interface NameResult { | ||
type: 'NAME'; | ||
name: string; | ||
reservedWord?: boolean; | ||
}; | ||
export declare type UnionResult = ModifiableResult & { | ||
value: string; | ||
meta: { | ||
reservedWord: boolean; | ||
}; | ||
} | ||
export interface UnionResult { | ||
type: 'UNION'; | ||
elements: ParseResult[]; | ||
}; | ||
export declare type GenericResult = ModifiableResult & { | ||
} | ||
export interface GenericResult { | ||
type: 'GENERIC'; | ||
subject: ParseResult; | ||
objects: ParseResult[]; | ||
}; | ||
export declare type StringValueResult = ModifiableResult & { | ||
left: ParseResult; | ||
elements: ParseResult[]; | ||
meta: { | ||
brackets: '<>' | '[]'; | ||
dot: boolean; | ||
}; | ||
} | ||
export interface StringValueResult { | ||
type: 'STRING_VALUE'; | ||
value: string; | ||
quote: string; | ||
}; | ||
export declare type NullResult = ModifiableResult & { | ||
meta: { | ||
quote: '\'' | '"'; | ||
}; | ||
} | ||
export interface NullResult { | ||
type: 'NULL'; | ||
}; | ||
export declare type UndefinedResult = ModifiableResult & { | ||
} | ||
export interface UndefinedResult { | ||
type: 'UNDEFINED'; | ||
}; | ||
export declare type AllResult = ModifiableResult & { | ||
type: 'ALL'; | ||
}; | ||
export declare type UnknownResult = ModifiableResult & { | ||
} | ||
export interface AnyResult { | ||
type: 'ANY'; | ||
} | ||
export interface UnknownResult { | ||
type: 'UNKNOWN'; | ||
}; | ||
export declare type FunctionResult = ModifiableResult & { | ||
} | ||
export interface FunctionResult { | ||
type: 'FUNCTION'; | ||
parameters: Array<ParseResult | KeyValueResult>; | ||
returnType?: ParseResult; | ||
}; | ||
export declare type ArrowResult = ModifiableResult & { | ||
type: 'FUNCTION'; | ||
parameters: Array<NameResult | KeyValueResult>; | ||
returnType?: ParseResult; | ||
arrow: true; | ||
}; | ||
export declare type KeyValueResult<KeyType = NameResult> = ModifiableResult & { | ||
meta: { | ||
arrow: boolean; | ||
parenthesis: boolean; | ||
}; | ||
} | ||
export interface KeyValueResult<KeyType = NameResult> { | ||
type: 'KEY_VALUE'; | ||
key: KeyType; | ||
value: ParseResult; | ||
}; | ||
export declare type RecordResult = ModifiableResult & { | ||
type: 'RECORD'; | ||
fields: Array<KeyValueResult<ParseResult | NumberResult> | ParseResult | NumberResult>; | ||
}; | ||
export declare type ModuleResult = ModifiableResult & { | ||
left: KeyType; | ||
right: ParseResult; | ||
} | ||
export interface RecordResult { | ||
type: 'OBJECT'; | ||
elements: Array<KeyValueResult<ParseResult | NumberResult> | ParseResult | NumberResult>; | ||
} | ||
export interface ModuleResult { | ||
type: 'MODULE'; | ||
path: string; | ||
}; | ||
export declare type PropertyPathResult = ModifiableResult & { | ||
type: 'PROPERTY_PATH'; | ||
value: string; | ||
meta: { | ||
quote: '\'' | '"' | undefined; | ||
}; | ||
} | ||
export interface NamePathResult { | ||
type: 'NAME_PATH'; | ||
left: ParseResult; | ||
path: string[]; | ||
}; | ||
export declare type NumberResult = ModifiableResult & { | ||
right: NameResult | NumberResult | StringValueResult; | ||
meta: { | ||
type: '~' | '#' | '.'; | ||
}; | ||
} | ||
export interface NumberResult { | ||
type: 'NUMBER'; | ||
value: number; | ||
}; | ||
export declare type SymbolResult = ModifiableResult & { | ||
} | ||
export interface SymbolResult { | ||
type: 'SYMBOL'; | ||
name: string; | ||
value?: NumberResult | NameResult; | ||
}; | ||
export declare type TypeOfResult = ModifiableResult & { | ||
value: string; | ||
element?: NumberResult | NameResult | VariadicResult<NameResult>; | ||
} | ||
export interface TypeOfResult { | ||
type: 'TYPE_OF'; | ||
value?: ParseResult; | ||
}; | ||
export declare type KeyOfResult = ModifiableResult & { | ||
element: ParseResult; | ||
} | ||
export interface KeyOfResult { | ||
type: 'KEY_OF'; | ||
value?: ParseResult; | ||
}; | ||
export declare type ImportResult = ModifiableResult & { | ||
element: ParseResult; | ||
} | ||
export interface ImportResult { | ||
type: 'IMPORT'; | ||
path: StringValueResult; | ||
}; | ||
element: StringValueResult; | ||
} | ||
export interface ParameterList { | ||
@@ -93,5 +129,9 @@ type: 'PARAMETER_LIST'; | ||
} | ||
export declare type TupleResult = ModifiableResult & { | ||
export interface TupleResult { | ||
type: 'TUPLE'; | ||
elements: ParseResult[]; | ||
}; | ||
} | ||
export interface ParenthesisResult { | ||
type: 'PARENTHESIS'; | ||
element: NonTerminalResult | undefined; | ||
} |
@@ -5,3 +5,3 @@ import { InfixParslet } from './Parslet'; | ||
import { NonTerminalResult, ParseResult } from '../ParseResult'; | ||
import { Precedence } from './Precedence'; | ||
import { Precedence } from '../Precedence'; | ||
export declare class ArrayBracketsParslet implements InfixParslet { | ||
@@ -8,0 +8,0 @@ accepts(type: TokenType, next: TokenType): boolean; |
import { InfixParslet, PrefixParslet } from './Parslet'; | ||
import { TokenType } from '../lexer/Token'; | ||
import { Precedence } from './Precedence'; | ||
import { Precedence } from '../Precedence'; | ||
import { ParserEngine } from '../ParserEngine'; | ||
import { ArrowResult, NonTerminalResult } from '../ParseResult'; | ||
import { FunctionResult, NonTerminalResult } from '../ParseResult'; | ||
import { BaseFunctionParslet } from './BaseFunctionParslet'; | ||
@@ -10,3 +10,3 @@ export declare class ArrowFunctionWithoutParametersParslet implements PrefixParslet { | ||
getPrecedence(): Precedence; | ||
parsePrefix(parser: ParserEngine): ArrowResult; | ||
parsePrefix(parser: ParserEngine): FunctionResult; | ||
} | ||
@@ -16,3 +16,3 @@ export declare class ArrowFunctionWithParametersParslet extends BaseFunctionParslet implements InfixParslet { | ||
getPrecedence(): Precedence; | ||
parseInfix(parser: ParserEngine, left: NonTerminalResult): ArrowResult; | ||
parseInfix(parser: ParserEngine, left: NonTerminalResult): FunctionResult; | ||
} |
@@ -1,6 +0,6 @@ | ||
import { KeyValueResult, NonTerminalResult, ParseResult } from '../ParseResult'; | ||
import { KeyValueResult, ParenthesisResult, ParseResult } from '../ParseResult'; | ||
export declare class BaseFunctionParslet { | ||
protected getParameters(value: NonTerminalResult): Array<ParseResult | KeyValueResult>; | ||
protected getNamedParameters(value: NonTerminalResult): KeyValueResult[]; | ||
protected getUnnamedParameters(value: NonTerminalResult): ParseResult[]; | ||
protected getParameters(value: ParenthesisResult): Array<ParseResult | KeyValueResult>; | ||
protected getNamedParameters(value: ParenthesisResult): KeyValueResult[]; | ||
protected getUnnamedParameters(value: ParenthesisResult): ParseResult[]; | ||
} |
@@ -5,3 +5,3 @@ import { PrefixParslet } from './Parslet'; | ||
import { ParseResult } from '../ParseResult'; | ||
import { Precedence } from './Precedence'; | ||
import { Precedence } from '../Precedence'; | ||
import { BaseFunctionParslet } from './BaseFunctionParslet'; | ||
@@ -8,0 +8,0 @@ export interface FunctionParsletOptions { |
@@ -5,3 +5,3 @@ import { ParserEngine } from '../ParserEngine'; | ||
import { InfixParslet } from './Parslet'; | ||
import { Precedence } from './Precedence'; | ||
import { Precedence } from '../Precedence'; | ||
export declare class GenericParslet implements InfixParslet { | ||
@@ -8,0 +8,0 @@ accepts(type: TokenType, next: TokenType): boolean; |
@@ -5,3 +5,3 @@ import { PrefixParslet } from './Parslet'; | ||
import { ParseResult } from '../ParseResult'; | ||
import { Precedence } from './Precedence'; | ||
import { Precedence } from '../Precedence'; | ||
export declare class ImportParslet implements PrefixParslet { | ||
@@ -8,0 +8,0 @@ accepts(type: TokenType, next: TokenType): boolean; |
import { PrefixParslet } from './Parslet'; | ||
import { TokenType } from '../lexer/Token'; | ||
import { ParserEngine } from '../ParserEngine'; | ||
import { ParseResult } from '../ParseResult'; | ||
import { Precedence } from './Precedence'; | ||
import { KeyOfResult } from '../ParseResult'; | ||
import { Precedence } from '../Precedence'; | ||
export declare class KeyOfParslet implements PrefixParslet { | ||
accepts(type: TokenType, next: TokenType): boolean; | ||
getPrecedence(): Precedence; | ||
parsePrefix(parser: ParserEngine): ParseResult; | ||
parsePrefix(parser: ParserEngine): KeyOfResult; | ||
} |
import { InfixParslet } from './Parslet'; | ||
import { TokenType } from '../lexer/Token'; | ||
import { Precedence } from './Precedence'; | ||
import { Precedence } from '../Precedence'; | ||
import { ParserEngine } from '../ParserEngine'; | ||
import { NonTerminalResult } from '../ParseResult'; | ||
interface KeyValueParsletOptions { | ||
allowOnlyNameOrNumberProperties: boolean; | ||
} | ||
export declare class KeyValueParslet implements InfixParslet { | ||
private readonly allowOnlyNameOrNumberProperties; | ||
constructor(opts: KeyValueParsletOptions); | ||
accepts(type: TokenType, next: TokenType): boolean; | ||
@@ -11,1 +16,2 @@ getPrecedence(): Precedence; | ||
} | ||
export {}; |
import { PrefixParslet } from './Parslet'; | ||
import { TokenType } from '../lexer/Token'; | ||
import { Precedence } from './Precedence'; | ||
import { Precedence } from '../Precedence'; | ||
import { ParserEngine } from '../ParserEngine'; | ||
@@ -5,0 +5,0 @@ import { ParseResult } from '../ParseResult'; |
import { ParserEngine } from '../ParserEngine'; | ||
import { TokenType } from '../lexer/Token'; | ||
import { ParseResult } from '../ParseResult'; | ||
import { NameResult } from '../ParseResult'; | ||
import { PrefixParslet } from './Parslet'; | ||
import { Precedence } from './Precedence'; | ||
import { Precedence } from '../Precedence'; | ||
interface NameParsletOptions { | ||
allowedAdditionalTokens: TokenType[]; | ||
} | ||
export declare class NameParslet implements PrefixParslet { | ||
private readonly allowedAdditionalTokens; | ||
constructor(options: NameParsletOptions); | ||
accepts(type: TokenType, next: TokenType): boolean; | ||
getPrecedence(): Precedence; | ||
parsePrefix(parser: ParserEngine): ParseResult; | ||
parsePrefix(parser: ParserEngine): NameResult; | ||
} | ||
export {}; |
@@ -5,3 +5,3 @@ import { InfixParslet, PrefixParslet } from './Parslet'; | ||
import { NonTerminalResult, ParseResult } from '../ParseResult'; | ||
import { Precedence } from './Precedence'; | ||
import { Precedence } from '../Precedence'; | ||
export declare class NullablePrefixParslet implements PrefixParslet { | ||
@@ -8,0 +8,0 @@ accepts(type: TokenType, next: TokenType): boolean; |
import { PrefixParslet } from './Parslet'; | ||
import { TokenType } from '../lexer/Token'; | ||
import { Precedence } from './Precedence'; | ||
import { Precedence } from '../Precedence'; | ||
import { ParserEngine } from '../ParserEngine'; | ||
@@ -5,0 +5,0 @@ import { NonTerminalResult } from '../ParseResult'; |
@@ -1,10 +0,11 @@ | ||
import { InfixParslet } from './Parslet'; | ||
import { InfixParslet, PrefixParslet } from './Parslet'; | ||
import { TokenType } from '../lexer/Token'; | ||
import { ParserEngine } from '../ParserEngine'; | ||
import { NonTerminalResult, ParseResult } from '../ParseResult'; | ||
import { Precedence } from './Precedence'; | ||
export declare class OptionalParslet implements InfixParslet { | ||
import { Precedence } from '../Precedence'; | ||
export declare class OptionalParslet implements PrefixParslet, InfixParslet { | ||
accepts(type: TokenType, next: TokenType): boolean; | ||
getPrecedence(): Precedence; | ||
parsePrefix(parser: ParserEngine): ParseResult; | ||
parseInfix(parser: ParserEngine, left: NonTerminalResult): ParseResult; | ||
} |
@@ -5,3 +5,3 @@ import { InfixParslet } from './Parslet'; | ||
import { NonTerminalResult } from '../ParseResult'; | ||
import { Precedence } from './Precedence'; | ||
import { Precedence } from '../Precedence'; | ||
interface ParameterListParsletOptions { | ||
@@ -8,0 +8,0 @@ allowTrailingComma: boolean; |
import { PrefixParslet } from './Parslet'; | ||
import { TokenType } from '../lexer/Token'; | ||
import { Precedence } from './Precedence'; | ||
import { Precedence } from '../Precedence'; | ||
import { ParserEngine } from '../ParserEngine'; | ||
import { NonTerminalResult } from '../ParseResult'; | ||
import { ParenthesisResult } from '../ParseResult'; | ||
export declare class ParenthesisParslet implements PrefixParslet { | ||
accepts(type: TokenType, next: TokenType): boolean; | ||
getPrecedence(): Precedence; | ||
parsePrefix(parser: ParserEngine): NonTerminalResult; | ||
parsePrefix(parser: ParserEngine): ParenthesisResult; | ||
} |
import { TokenType } from '../lexer/Token'; | ||
import { ParserEngine } from '../ParserEngine'; | ||
import { NonTerminalResult } from '../ParseResult'; | ||
import { Precedence } from './Precedence'; | ||
import { Precedence } from '../Precedence'; | ||
export interface Parslet { | ||
@@ -6,0 +6,0 @@ accepts: (type: TokenType, next: TokenType) => boolean; |
@@ -8,14 +8,15 @@ export declare enum Precedence { | ||
POSTFIX = 5, | ||
RECORD = 6, | ||
SYMBOL = 7, | ||
OPTIONAL = 8, | ||
NULLABLE = 9, | ||
FUNCTION = 10, | ||
ARROW = 11, | ||
KEY_VALUE = 12, | ||
GENERIC = 13, | ||
PROPERTY_PATH = 14, | ||
KEY_OF_TYPE_OF = 15, | ||
ARRAY_BRACKETS = 16, | ||
SPECIAL_TYPES = 17 | ||
TUPLE = 6, | ||
OBJECT = 7, | ||
SYMBOL = 8, | ||
OPTIONAL = 9, | ||
NULLABLE = 10, | ||
FUNCTION = 11, | ||
ARROW = 12, | ||
KEY_VALUE = 13, | ||
GENERIC = 14, | ||
NAME_PATH = 15, | ||
KEY_OF_TYPE_OF = 16, | ||
ARRAY_BRACKETS = 17, | ||
SPECIAL_TYPES = 18 | ||
} |
@@ -5,3 +5,3 @@ import { PrefixParslet } from './Parslet'; | ||
import { ParseResult } from '../ParseResult'; | ||
import { Precedence } from './Precedence'; | ||
import { Precedence } from '../Precedence'; | ||
export declare class RecordParslet implements PrefixParslet { | ||
@@ -8,0 +8,0 @@ accepts(type: TokenType): boolean; |
@@ -5,3 +5,3 @@ import { PrefixParslet } from './Parslet'; | ||
import { ParseResult } from '../ParseResult'; | ||
import { Precedence } from './Precedence'; | ||
import { Precedence } from '../Precedence'; | ||
export declare class SpecialTypesParslet implements PrefixParslet { | ||
@@ -8,0 +8,0 @@ accepts(type: TokenType, next: TokenType): boolean; |
import { PrefixParslet } from './Parslet'; | ||
import { TokenType } from '../lexer/Token'; | ||
import { ParserEngine } from '../ParserEngine'; | ||
import { ParseResult } from '../ParseResult'; | ||
import { Precedence } from './Precedence'; | ||
import { StringValueResult } from '../ParseResult'; | ||
import { Precedence } from '../Precedence'; | ||
export declare class StringValueParslet implements PrefixParslet { | ||
accepts(type: TokenType): boolean; | ||
getPrecedence(): Precedence; | ||
parsePrefix(parser: ParserEngine): ParseResult; | ||
parsePrefix(parser: ParserEngine): StringValueResult; | ||
} |
@@ -5,3 +5,3 @@ import { InfixParslet } from './Parslet'; | ||
import { NonTerminalResult, ParseResult } from '../ParseResult'; | ||
import { Precedence } from './Precedence'; | ||
import { Precedence } from '../Precedence'; | ||
export declare class SymbolParslet implements InfixParslet { | ||
@@ -8,0 +8,0 @@ accepts(type: TokenType): boolean; |
import { PrefixParslet } from './Parslet'; | ||
import { TokenType } from '../lexer/Token'; | ||
import { ParserEngine } from '../ParserEngine'; | ||
import { ParseResult } from '../ParseResult'; | ||
import { Precedence } from './Precedence'; | ||
import { TypeOfResult } from '../ParseResult'; | ||
import { Precedence } from '../Precedence'; | ||
export declare class TypeOfParslet implements PrefixParslet { | ||
accepts(type: TokenType, next: TokenType): boolean; | ||
getPrecedence(): Precedence; | ||
parsePrefix(parser: ParserEngine): ParseResult; | ||
parsePrefix(parser: ParserEngine): TypeOfResult; | ||
} |
@@ -5,3 +5,3 @@ import { InfixParslet } from './Parslet'; | ||
import { TokenType } from '../lexer/Token'; | ||
import { Precedence } from './Precedence'; | ||
import { Precedence } from '../Precedence'; | ||
export declare class UnenclosedUnionParslet implements InfixParslet { | ||
@@ -8,0 +8,0 @@ accepts(type: TokenType): boolean; |
import { InfixParslet, PrefixParslet } from './Parslet'; | ||
import { TokenType } from '../lexer/Token'; | ||
import { ParserEngine } from '../ParserEngine'; | ||
import { NonTerminalResult, ParseResult } from '../ParseResult'; | ||
import { Precedence } from './Precedence'; | ||
import { NonTerminalResult, ParseResult, VariadicResult } from '../ParseResult'; | ||
import { Precedence } from '../Precedence'; | ||
interface VariadicParsletOptions { | ||
allowEnclosingBrackets: boolean; | ||
} | ||
export declare class VariadicParslet implements PrefixParslet, InfixParslet { | ||
private readonly allowEnclosingBrackets; | ||
constructor(opts: VariadicParsletOptions); | ||
accepts(type: TokenType): boolean; | ||
getPrecedence(): Precedence; | ||
parsePrefix(parser: ParserEngine): ParseResult; | ||
parsePrefix(parser: ParserEngine): VariadicResult<ParseResult>; | ||
parseInfix(parser: ParserEngine, left: NonTerminalResult): ParseResult; | ||
} | ||
export {}; |
@@ -1,2 +0,2 @@ | ||
import { DiffFixture } from '../Fixture'; | ||
export declare const miscDiffs: DiffFixture[]; | ||
import { Fixture } from '../Fixture'; | ||
export declare const miscDiffs: Fixture[]; |
import { ParseResult, ParserMode } from '../../src'; | ||
import 'mocha'; | ||
declare type JtpMode = 'jsdoc' | 'closure' | 'typescript' | 'permissive'; | ||
declare type CatharsisMode = 'jsdoc' | 'closure'; | ||
declare type CompareMode = ParserMode | 'fail' | 'differ'; | ||
export interface Fixture { | ||
description: string; | ||
input: string; | ||
modes: ParserMode[]; | ||
jtp: { | ||
[K in JtpMode]: CompareMode; | ||
}; | ||
catharsis: { | ||
[K in CatharsisMode]: CompareMode; | ||
}; | ||
expected?: ParseResult; | ||
shouldFail?: true; | ||
} | ||
export interface DiffFixture { | ||
description: string; | ||
modes: { | ||
[K in ParserMode]: boolean; | ||
diffExpected?: { | ||
[K in ParserMode]?: ParseResult; | ||
}; | ||
input: string; | ||
} | ||
export declare function testFixture(fixture: Fixture): void; | ||
export {}; |
{ | ||
"name": "jsdoc-type-pratt-parser", | ||
"version": "1.0.0-alpha.2", | ||
"version": "1.0.0-alpha.3.0", | ||
"description": "", | ||
@@ -12,3 +12,3 @@ "main": "dist/index.js", | ||
"test": "npm run typecheck && npm run lint && npm run test:spec", | ||
"test:spec": "mocha -r ts-node/register test/**/*.spec.ts", | ||
"test:spec": "mocha", | ||
"lint": "ts-standard", | ||
@@ -22,5 +22,3 @@ "typecheck": "tsc --noEmit", | ||
"license": "MIT", | ||
"dependencies": { | ||
"typescript": "^4.1.3" | ||
}, | ||
"dependencies": {}, | ||
"devDependencies": { | ||
@@ -31,3 +29,6 @@ "@rollup/plugin-typescript": "^8.1.1", | ||
"@types/mocha": "^8.2.0", | ||
"@types/node": "^14.14.31", | ||
"catharsis": "^0.9.0", | ||
"chai": "^4.3.0", | ||
"jsdoctypeparser": "^9.0.0", | ||
"lodash": "^4.17.20", | ||
@@ -38,3 +39,4 @@ "mocha": "^8.2.1", | ||
"ts-standard": "^10.0.0", | ||
"typedoc": "^0.20.24" | ||
"typedoc": "^0.20.24", | ||
"typescript": "^4.1.3" | ||
}, | ||
@@ -41,0 +43,0 @@ "ts-standard": { |
@@ -1,3 +0,11 @@ | ||
This project is parser for jsdoc types. It is heavily inspired by http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/, https://github.com/hegemonic/catharsis and https://github.com/jsdoctypeparser/jsdoctypeparser. | ||
[![Test Status](https://github.com/simonseyock/jsdoc-type-pratt-parser/actions/workflows/node.js.yml/badge.svg?branch=main)](https://github.com/simonseyock/jsdoc-type-pratt-parser/actions?query=branch%3Amain) | ||
[![Npm Package](https://badgen.net/npm/v/jsdoc-type-pratt-parser)](https://www.npmjs.com/package/jsdoc-type-pratt-parser) | ||
[![Code Style](https://badgen.net/badge/code%20style/ts-standard/blue?icon=typescript)](https://github.com/standard/ts-standard) | ||
This project is a parser for jsdoc types. It is heavily inspired by the existing libraries catharsis and | ||
jsdoctypeparser, but does not use PEG.js, but is written as a pratt parser. | ||
* http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/ | ||
* https://github.com/hegemonic/catharsis | ||
* https://github.com/jsdoctypeparser/jsdoctypeparser | ||
Live Demo | ||
@@ -11,7 +19,10 @@ --------- | ||
The usage is not perfect for now as it is not published as a package for now. Dependening on your needs you might want to run `npm run build` before using. An `index.js` in umd format will be built. All exports from `index.ts` should be available. | ||
``` | ||
import { Parser } from 'src/index' | ||
npm install jsdoc-type-pratt-parser | ||
``` | ||
```js | ||
import { Parser } from 'jsdoc-type-pratt-parser' | ||
const parser = new Parser({ | ||
@@ -24,6 +35,11 @@ mode: 'closure' | ||
This library supports compatibility modes for catharsis and jsdoctypeparser. The provided transform functions attempt to | ||
transform the output to the expected output of the target library. This will not always be the same as some types are | ||
parsed differently. These modes are thought to make transition easier, but it is advised to use the native output as | ||
this will be more uniform and will contain more information. | ||
Catharsis compat mode: | ||
``` | ||
import { Parser, catharsisTransform } from 'src/index' | ||
```js | ||
import { Parser, catharsisTransform } from 'jsdoc-type-pratt-parser' | ||
@@ -37,6 +53,18 @@ const parser = new Parser({ | ||
Jsdoctypeparser compat mode: | ||
```js | ||
import { Parser, jtpTransform } from 'jsdoc-type-pratt-parser' | ||
const parser = new Parser({ | ||
mode: 'closure' | ||
}) | ||
const result = jtpTransform(parser.parse('myType.<string>')) | ||
``` | ||
Available Grammars | ||
------------------ | ||
At the moment there are 3 modes supported: 'jsdoc', 'closure', 'typescipt' where 'jsdoc' is a superset of 'closure' | ||
At the moment there are 3 modes supported: 'jsdoc', 'closure' and 'typescipt' | ||
@@ -46,10 +74,11 @@ Tests Status | ||
This parser runs most tests of https://github.com/hegemonic/catharsis and also some of the typescript tests of https://github.com/jsdoctypeparser/jsdoctypeparser | ||
This parser runs most tests of https://github.com/hegemonic/catharsis and of | ||
https://github.com/jsdoctypeparser/jsdoctypeparser. It compares the results of the different parsing libraries. If you | ||
want to find out where the output differs, look in the tests for the comments `// This seems to be an error of ...` or | ||
the `differ` keyword which indicates that differing results are produced. | ||
It adds an increasing number of tests on its own, especially the tests to assure the differences between the modes. | ||
The current status can be checked in the github action results: https://github.com/simonseyock/jsdoc-type-pratt-parser/actions | ||
API Documentation | ||
----------------- | ||
A simple api doc can be found here: https://simonseyock.github.io/jsdoc-type-pratt-parser/docs/ | ||
An API documentation can be found here: https://simonseyock.github.io/jsdoc-type-pratt-parser/docs/modules.html |
@@ -1,12 +0,8 @@ | ||
import { KeyValueResult, NameResult, NonTerminalResult, ParseResult } from './ParseResult' | ||
import { KeyValueResult, NameResult, NonTerminalResult, NumberResult, ParseResult, VariadicResult } from './ParseResult' | ||
import { UnexpectedTypeError } from './errors' | ||
class UnexpectedTypeError extends Error { | ||
constructor (result: NonTerminalResult) { | ||
super(`Unexpected type: '${result.type}'`) | ||
Object.setPrototypeOf(this, UnexpectedTypeError.prototype) | ||
export function assertTerminal (result?: NonTerminalResult): ParseResult { | ||
if (result === undefined) { | ||
throw new Error('Unexpected undefined') | ||
} | ||
} | ||
export function assertTerminal (result: NonTerminalResult): ParseResult { | ||
if (result.type === 'KEY_VALUE' || result.type === 'NUMBER' || result.type === 'PARAMETER_LIST') { | ||
@@ -20,3 +16,3 @@ throw new UnexpectedTypeError(result) | ||
if (result.type === 'KEY_VALUE') { | ||
if (result.key.type !== 'NAME') { | ||
if (result.left.type !== 'NAME') { | ||
throw new UnexpectedTypeError(result) | ||
@@ -31,3 +27,3 @@ } | ||
if (result.type === 'KEY_VALUE') { | ||
if (result.key.type !== 'NAME') { | ||
if (result.left.type !== 'NAME') { | ||
throw new UnexpectedTypeError(result) | ||
@@ -41,1 +37,14 @@ } | ||
} | ||
export function assertNumberOrVariadicName (result: NonTerminalResult): NumberResult | NameResult | VariadicResult<NameResult> { | ||
if (result.type === 'VARIADIC') { | ||
if (result.element?.type === 'NAME') { | ||
return result as VariadicResult<NameResult> | ||
} | ||
throw new UnexpectedTypeError(result) | ||
} | ||
if (result.type !== 'NUMBER' && result.type !== 'NAME') { | ||
throw new UnexpectedTypeError(result) | ||
} | ||
return result | ||
} |
import { Grammar } from './Grammar' | ||
import { UnenclosedUnionParslet } from '../parslets/UnionParslets' | ||
import { NameParslet } from '../parslets/NameParslet' | ||
import { SpecialTypesParslet } from '../parslets/SpecialTypesParslet' | ||
import { VariadicParslet } from '../parslets/VariadicParslet' | ||
import { RecordParslet } from '../parslets/RecordParslet' | ||
import { ModuleParslet } from '../parslets/ModuleParslet' | ||
import { GenericParslet } from '../parslets/GenericParslet' | ||
import { OptionalParslet } from '../parslets/OptionalParslet' | ||
import { PropertyPathParslet } from '../parslets/PropertyPathParslet' | ||
import { ParenthesisParslet } from '../parslets/ParenthesisParslet' | ||
import { KeyValueParslet } from '../parslets/KeyValueParslet' | ||
import { NumberParslet } from '../parslets/NumberParslet' | ||
import { ParameterListParslet } from '../parslets/ParameterListParslet' | ||
import { NullableInfixParslet, NullablePrefixParslet } from '../parslets/NullableParslets' | ||
import { OptionalParslet } from '../parslets/OptionalParslet' | ||
@@ -20,24 +15,19 @@ export const baseGrammar: Grammar = () => { | ||
prefixParslets: [ | ||
new NameParslet(), | ||
new SpecialTypesParslet(), | ||
new NullablePrefixParslet(), | ||
new VariadicParslet(), | ||
new OptionalParslet(), | ||
new RecordParslet(), | ||
new ModuleParslet(), | ||
new NumberParslet(), | ||
new ParenthesisParslet() | ||
new ParenthesisParslet(), | ||
new SpecialTypesParslet() | ||
], | ||
infixParslets: [ | ||
new ParameterListParslet({ | ||
allowTrailingComma: false | ||
allowTrailingComma: true | ||
}), | ||
new PropertyPathParslet(), | ||
new KeyValueParslet(), | ||
new GenericParslet(), | ||
new UnenclosedUnionParslet(), | ||
new OptionalParslet(), | ||
new NullableInfixParslet(), | ||
new PropertyPathParslet() | ||
new NullableInfixParslet() | ||
] | ||
} | ||
} |
import { Grammar } from './Grammar' | ||
import { baseGrammar } from './baseGrammar' | ||
import { FunctionParslet } from '../parslets/FunctionParslet' | ||
import { NamePathParslet } from '../parslets/NamePathParslet' | ||
import { KeyValueParslet } from '../parslets/KeyValueParslet' | ||
import { TypeOfParslet } from '../parslets/TypeOfParslet' | ||
import { VariadicParslet } from '../parslets/VariadicParslet' | ||
import { NameParslet } from '../parslets/NameParslet' | ||
import { NotNullableParslet } from '../parslets/NotNullableParslet' | ||
@@ -14,2 +20,6 @@ export const closureGrammar: Grammar = () => { | ||
...prefixParslets, | ||
new NameParslet({ | ||
allowedAdditionalTokens: [] | ||
}), | ||
new TypeOfParslet(), | ||
new FunctionParslet({ | ||
@@ -19,6 +29,22 @@ allowWithoutParenthesis: false, | ||
allowNoReturnType: true | ||
}) | ||
}), | ||
new VariadicParslet({ | ||
allowEnclosingBrackets: false | ||
}), | ||
new NameParslet({ | ||
allowedAdditionalTokens: ['keyof'] | ||
}), | ||
new NotNullableParslet() | ||
], | ||
infixParslets | ||
infixParslets: [ | ||
...infixParslets, | ||
new NamePathParslet({ | ||
allowJsdocNamePaths: false | ||
}), | ||
new KeyValueParslet({ | ||
allowOnlyNameOrNumberProperties: true | ||
}), | ||
new NotNullableParslet() | ||
] | ||
} | ||
} |
import { Grammar } from './Grammar' | ||
import { SymbolParslet } from '../parslets/SymbolParslet' | ||
import { ClassPathParslet } from '../parslets/ClassPathParslet' | ||
import { ArrayBracketsParslet } from '../parslets/ArrayBracketsParslet' | ||
@@ -8,2 +7,8 @@ import { StringValueParslet } from '../parslets/StringValueParslet' | ||
import { baseGrammar } from './baseGrammar' | ||
import { NamePathParslet } from '../parslets/NamePathParslet' | ||
import { KeyValueParslet } from '../parslets/KeyValueParslet' | ||
import { VariadicParslet } from '../parslets/VariadicParslet' | ||
import { ModuleParslet } from '../parslets/ModuleParslet' | ||
import { NameParslet } from '../parslets/NameParslet' | ||
import { NotNullableParslet } from '../parslets/NotNullableParslet' | ||
@@ -24,3 +29,11 @@ export const jsdocGrammar: Grammar = () => { | ||
}), | ||
new StringValueParslet() | ||
new StringValueParslet(), | ||
new ModuleParslet(), | ||
new VariadicParslet({ | ||
allowEnclosingBrackets: true | ||
}), | ||
new NameParslet({ | ||
allowedAdditionalTokens: ['keyof'] | ||
}), | ||
new NotNullableParslet() | ||
], | ||
@@ -30,6 +43,15 @@ infixParslets: [ | ||
new SymbolParslet(), | ||
new ClassPathParslet(), | ||
new ArrayBracketsParslet() | ||
new ArrayBracketsParslet(), | ||
new NamePathParslet({ | ||
allowJsdocNamePaths: true | ||
}), | ||
new KeyValueParslet({ | ||
allowOnlyNameOrNumberProperties: false | ||
}), | ||
new VariadicParslet({ | ||
allowEnclosingBrackets: true | ||
}), | ||
new NotNullableParslet() | ||
] | ||
} | ||
} |
@@ -0,1 +1,2 @@ | ||
import { TupleParslet } from '../parslets/TupleParslet' | ||
import { Grammar } from './Grammar' | ||
@@ -9,3 +10,2 @@ import { ArrayBracketsParslet } from '../parslets/ArrayBracketsParslet' | ||
import { FunctionParslet } from '../parslets/FunctionParslet' | ||
import { VariadicParslet } from '../parslets/VariadicParslet' | ||
import { | ||
@@ -15,2 +15,6 @@ ArrowFunctionWithoutParametersParslet, | ||
} from '../parslets/ArrowFunctionParslet' | ||
import { NamePathParslet } from '../parslets/NamePathParslet' | ||
import { KeyValueParslet } from '../parslets/KeyValueParslet' | ||
import { VariadicParslet } from '../parslets/VariadicParslet' | ||
import { NameParslet } from '../parslets/NameParslet' | ||
@@ -23,2 +27,7 @@ export const typescriptGrammar: Grammar = () => { | ||
// typescript does not support explicit non nullability | ||
// https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#patterns-that-are-known-not-to-be-supported | ||
// module seems not to be supported | ||
return { | ||
@@ -33,4 +42,14 @@ prefixParslets: [ | ||
new FunctionParslet({ | ||
allowWithoutParenthesis: true, | ||
allowNoReturnType: false | ||
allowWithoutParenthesis: false, | ||
allowNoReturnType: false, | ||
allowNamedParameters: ['this', 'new'] | ||
}), | ||
new TupleParslet({ | ||
allowQuestionMark: false | ||
}), | ||
new VariadicParslet({ | ||
allowEnclosingBrackets: false | ||
}), | ||
new NameParslet({ | ||
allowedAdditionalTokens: [] | ||
}) | ||
@@ -41,6 +60,11 @@ ], | ||
new ArrayBracketsParslet(), | ||
new VariadicParslet(), | ||
new ArrowFunctionWithParametersParslet() | ||
new ArrowFunctionWithParametersParslet(), | ||
new NamePathParslet({ | ||
allowJsdocNamePaths: false | ||
}), | ||
new KeyValueParslet({ | ||
allowOnlyNameOrNumberProperties: true | ||
}) | ||
] | ||
} | ||
} |
/** | ||
* @packageDocumentation | ||
* @package | ||
* This package provides a parser for jsdoc types. | ||
@@ -8,2 +8,3 @@ */ | ||
export * from './ParseResult' | ||
export * from './catharsisTransform' | ||
export * from './transforms/catharsisTransform' | ||
export * from './transforms/jtpTransform' |
@@ -142,2 +142,3 @@ import { Token, TokenType } from './Token' | ||
eofRule, | ||
makePunctuationRule('=>'), | ||
makePunctuationRule('('), | ||
@@ -166,3 +167,2 @@ makePunctuationRule(')'), | ||
makeKeyWordRule('null'), | ||
makeKeyWordRule('void'), | ||
makeKeyWordRule('function'), | ||
@@ -223,6 +223,7 @@ makeKeyWordRule('this'), | ||
private read (): Token { | ||
const text = this.text.trim() | ||
for (const rule of rules) { | ||
const token = rule(this.text) | ||
const token = rule(text) | ||
if (token !== null) { | ||
this.text = this.text.slice(token.text.length).trim() | ||
this.text = text.slice(token.text.length) | ||
return token | ||
@@ -229,0 +230,0 @@ } |
export type TokenType = | ||
'(' | | ||
')' | | ||
'[' | | ||
']' | | ||
'{' | | ||
'}' | | ||
'|' | | ||
'<' | | ||
'>' | | ||
',' | | ||
'*' | | ||
'?' | | ||
'!' | | ||
'=' | | ||
':' | | ||
'.' | | ||
'@' | | ||
'#' | | ||
'~' | | ||
'/' | | ||
'...' | | ||
'null' | | ||
'undefined' | | ||
'void' | | ||
'function' | | ||
'this' | | ||
'new' | | ||
'module' | | ||
'typeof' | | ||
'keyof' | | ||
'import' | | ||
'Identifier' | | ||
'StringValue' | | ||
'Number' | | ||
'EOF' | ||
'(' | ||
| ')' | ||
| '[' | ||
| ']' | ||
| '{' | ||
| '}' | ||
| '|' | ||
| '<' | ||
| '>' | ||
| ',' | ||
| '*' | ||
| '?' | ||
| '!' | ||
| '=' | ||
| ':' | ||
| '.' | ||
| '@' | ||
| '#' | ||
| '~' | ||
| '/' | ||
| '=>' | ||
| '...' | ||
| 'null' | ||
| 'undefined' | ||
| 'function' | ||
| 'this' | ||
| 'new' | ||
| 'module' | ||
| 'typeof' | ||
| 'keyof' | ||
| 'import' | ||
| 'Identifier' | ||
| 'StringValue' | ||
| 'Number' | ||
| 'EOF' | ||
@@ -38,0 +38,0 @@ export interface Token { |
@@ -0,1 +1,2 @@ | ||
import { EarlyEndOfParseError, NoParsletFoundError } from './errors' | ||
import { Token, TokenType } from './lexer/Token' | ||
@@ -7,12 +8,4 @@ import { Lexer } from './lexer/Lexer' | ||
import { assertTerminal } from './assertTypes' | ||
import { Precedence } from './parslets/Precedence' | ||
import { Precedence } from './Precedence' | ||
class NoParsletFoundError extends Error { | ||
constructor (token: Token) { | ||
super(`No parslet found for token: '${token.type}' with value '${token.text}'`) | ||
Object.setPrototypeOf(this, NoParsletFoundError.prototype) | ||
} | ||
} | ||
export class ParserEngine { | ||
@@ -41,3 +34,3 @@ private readonly prefixParslets: PrefixParslet[] | ||
if (!this.consume('EOF')) { | ||
throw new Error(`Unexpected early end of parse. Next token: '${this.getToken().text}'`) | ||
throw new EarlyEndOfParseError(this.getToken()) | ||
} | ||
@@ -60,3 +53,3 @@ return result | ||
try { | ||
return this.parseType(precedence) | ||
return this.parseNonTerminalType(precedence) | ||
} catch (e) { | ||
@@ -63,0 +56,0 @@ if (e instanceof NoParsletFoundError) { |
@@ -8,3 +8,3 @@ export type ParseResult = | ||
| UndefinedResult | ||
| AllResult | ||
| AnyResult | ||
| UnknownResult | ||
@@ -14,3 +14,3 @@ | FunctionResult | ||
| ModuleResult | ||
| PropertyPathResult | ||
| NamePathResult | ||
| SymbolResult | ||
@@ -20,4 +20,8 @@ | TypeOfResult | ||
| ImportResult | ||
| ArrowResult | ||
| TupleResult | ||
| OptionalResult<ParseResult> | ||
| NullableResult<ParseResult> | ||
| NotNullableResult<ParseResult> | ||
| VariadicResult<ParseResult> | ||
| ParenthesisResult | ||
@@ -30,15 +34,44 @@ export type NonTerminalResult = | ||
export interface ModifiableResult { | ||
optional?: boolean | ||
nullable?: boolean | ||
repeatable?: boolean | ||
export interface OptionalResult<T extends ParseResult> { | ||
type: 'OPTIONAL' | ||
element: T | ||
meta: { | ||
position: 'PREFIX' | 'SUFFIX' | ||
} | ||
} | ||
export type NameResult = ModifiableResult & { | ||
export interface NullableResult<T extends ParseResult> { | ||
type: 'NULLABLE' | ||
element: T | ||
meta: { | ||
position: 'PREFIX' | 'SUFFIX' | ||
} | ||
} | ||
export interface NotNullableResult<T extends ParseResult> { | ||
type: 'NOT_NULLABLE' | ||
element: T | ||
meta: { | ||
position: 'PREFIX' | 'SUFFIX' | ||
} | ||
} | ||
export interface VariadicResult<T extends ParseResult> { | ||
type: 'VARIADIC' | ||
element?: T | ||
meta: { | ||
position: 'PREFIX' | 'SUFFIX' | 'ONLY_DOTS' | ||
squareBrackets: boolean | ||
} | ||
} | ||
export interface NameResult { | ||
type: 'NAME' | ||
name: string | ||
reservedWord?: boolean | ||
value: string | ||
meta: { | ||
reservedWord: boolean | ||
} | ||
} | ||
export type UnionResult = ModifiableResult & { | ||
export interface UnionResult { | ||
type: 'UNION' | ||
@@ -48,66 +81,75 @@ elements: ParseResult[] | ||
export type GenericResult = ModifiableResult & { | ||
export interface GenericResult { | ||
type: 'GENERIC' | ||
subject: ParseResult | ||
objects: ParseResult[] | ||
left: ParseResult | ||
elements: ParseResult[] | ||
meta: { | ||
brackets: '<>' | '[]' | ||
dot: boolean | ||
} | ||
} | ||
export type StringValueResult = ModifiableResult & { | ||
export interface StringValueResult { | ||
type: 'STRING_VALUE' | ||
value: string | ||
quote: string | ||
meta: { | ||
quote: '\'' | '"' | ||
} | ||
} | ||
export type NullResult = ModifiableResult & { | ||
export interface NullResult { | ||
type: 'NULL' | ||
} | ||
export type UndefinedResult = ModifiableResult & { | ||
export interface UndefinedResult { | ||
type: 'UNDEFINED' | ||
} | ||
export type AllResult = ModifiableResult & { | ||
type: 'ALL' | ||
export interface AnyResult { | ||
type: 'ANY' | ||
} | ||
export type UnknownResult = ModifiableResult & { | ||
export interface UnknownResult { | ||
type: 'UNKNOWN' | ||
} | ||
export type FunctionResult = ModifiableResult & { | ||
export interface FunctionResult { | ||
type: 'FUNCTION' | ||
parameters: Array<ParseResult | KeyValueResult> | ||
returnType?: ParseResult | ||
meta: { | ||
arrow: boolean | ||
parenthesis: boolean | ||
} | ||
} | ||
export type ArrowResult = ModifiableResult & { | ||
type: 'FUNCTION' | ||
parameters: Array<NameResult | KeyValueResult> | ||
returnType?: ParseResult | ||
arrow: true | ||
} | ||
export type KeyValueResult<KeyType = NameResult> = ModifiableResult & { | ||
export interface KeyValueResult<KeyType = NameResult> { | ||
type: 'KEY_VALUE' | ||
key: KeyType | ||
value: ParseResult | ||
left: KeyType | ||
right: ParseResult | ||
} | ||
export type RecordResult = ModifiableResult & { | ||
type: 'RECORD' | ||
fields: Array<KeyValueResult<ParseResult | NumberResult> | ParseResult | NumberResult> | ||
export interface RecordResult { | ||
type: 'OBJECT' | ||
elements: Array<KeyValueResult<ParseResult | NumberResult> | ParseResult | NumberResult> | ||
} | ||
export type ModuleResult = ModifiableResult & { | ||
export interface ModuleResult { | ||
type: 'MODULE' | ||
path: string | ||
value: string | ||
meta: { | ||
quote: '\'' | '"' | undefined | ||
} | ||
} | ||
export type PropertyPathResult = ModifiableResult & { | ||
type: 'PROPERTY_PATH' | ||
export interface NamePathResult { | ||
type: 'NAME_PATH' | ||
left: ParseResult | ||
path: string[] | ||
right: NameResult | NumberResult | StringValueResult | ||
meta: { | ||
type: '~' | '#' | '.' | ||
} | ||
} | ||
export type NumberResult = ModifiableResult & { | ||
export interface NumberResult { | ||
type: 'NUMBER' | ||
@@ -117,21 +159,21 @@ value: number | ||
export type SymbolResult = ModifiableResult & { | ||
export interface SymbolResult { | ||
type: 'SYMBOL' | ||
name: string | ||
value?: NumberResult | NameResult | ||
value: string | ||
element?: NumberResult | NameResult | VariadicResult<NameResult> | ||
} | ||
export type TypeOfResult = ModifiableResult & { | ||
export interface TypeOfResult { | ||
type: 'TYPE_OF' | ||
value?: ParseResult | ||
element: ParseResult | ||
} | ||
export type KeyOfResult = ModifiableResult & { | ||
export interface KeyOfResult { | ||
type: 'KEY_OF' | ||
value?: ParseResult | ||
element: ParseResult | ||
} | ||
export type ImportResult = ModifiableResult & { | ||
export interface ImportResult { | ||
type: 'IMPORT' | ||
path: StringValueResult | ||
element: StringValueResult | ||
} | ||
@@ -144,5 +186,10 @@ | ||
export type TupleResult = ModifiableResult & { | ||
export interface TupleResult { | ||
type: 'TUPLE' | ||
elements: ParseResult[] | ||
} | ||
export interface ParenthesisResult { | ||
type: 'PARENTHESIS' | ||
element: NonTerminalResult | undefined | ||
} |
@@ -5,3 +5,3 @@ import { InfixParslet } from './Parslet' | ||
import { NonTerminalResult, ParseResult } from '../ParseResult' | ||
import { Precedence } from './Precedence' | ||
import { Precedence } from '../Precedence' | ||
import { assertTerminal } from '../assertTypes' | ||
@@ -23,11 +23,18 @@ | ||
type: 'GENERIC', | ||
subject: { | ||
left: { | ||
type: 'NAME', | ||
name: 'Array' | ||
value: 'Array', | ||
meta: { | ||
reservedWord: false | ||
} | ||
}, | ||
objects: [ | ||
elements: [ | ||
assertTerminal(left) | ||
] | ||
], | ||
meta: { | ||
brackets: '[]', | ||
dot: false | ||
} | ||
} | ||
} | ||
} |
import { InfixParslet, PrefixParslet } from './Parslet' | ||
import { TokenType } from '../lexer/Token' | ||
import { Precedence } from './Precedence' | ||
import { Precedence } from '../Precedence' | ||
import { ParserEngine } from '../ParserEngine' | ||
import { ArrowResult, NonTerminalResult } from '../ParseResult' | ||
import { FunctionResult, NonTerminalResult } from '../ParseResult' | ||
import { BaseFunctionParslet } from './BaseFunctionParslet' | ||
import { assertNamedKeyValueOrName } from '../assertTypes' | ||
import { UnexpectedTypeError } from '../errors' | ||
@@ -18,18 +19,18 @@ export class ArrowFunctionWithoutParametersParslet implements PrefixParslet { | ||
parsePrefix (parser: ParserEngine): ArrowResult { | ||
parser.consume('(') | ||
parsePrefix (parser: ParserEngine): FunctionResult { | ||
const hasParenthesis = parser.consume('(') | ||
parser.consume(')') | ||
if (!parser.consume('=') || !parser.consume('>')) { | ||
if (!parser.consume('=>')) { | ||
throw new Error('Unexpected empty parenthesis. Expected \'=>\' afterwards.') | ||
} | ||
const result: ArrowResult = { | ||
return { | ||
type: 'FUNCTION', | ||
parameters: [], | ||
arrow: true | ||
meta: { | ||
arrow: true, | ||
parenthesis: hasParenthesis | ||
}, | ||
returnType: parser.parseType(Precedence.ALL) | ||
} | ||
if (!parser.consume('void')) { | ||
const right = parser.parseType(Precedence.ALL) | ||
result.returnType = right | ||
} | ||
return result | ||
} | ||
@@ -40,3 +41,3 @@ } | ||
accepts (type: TokenType, next: TokenType): boolean { | ||
return type === '=' && next === '>' | ||
return type === '=>' | ||
} | ||
@@ -48,19 +49,18 @@ | ||
parseInfix (parser: ParserEngine, left: NonTerminalResult): ArrowResult { | ||
if (parser.previousToken()?.type !== ')') { | ||
throw new Error('Unexpected Arrow. Expected parenthesis before.') | ||
parseInfix (parser: ParserEngine, left: NonTerminalResult): FunctionResult { | ||
if (left.type !== 'PARENTHESIS') { | ||
throw new UnexpectedTypeError(left) | ||
} | ||
parser.consume('=') | ||
parser.consume('>') | ||
const result: ArrowResult = { | ||
parser.consume('=>') | ||
return { | ||
type: 'FUNCTION', | ||
parameters: this.getParameters(left).map(assertNamedKeyValueOrName), | ||
arrow: true | ||
meta: { | ||
arrow: true, | ||
parenthesis: true | ||
}, | ||
returnType: parser.parseType(Precedence.ALL) | ||
} | ||
if (!parser.consume('void')) { | ||
const right = parser.parseType(Precedence.ALL) | ||
result.returnType = right | ||
} | ||
return result | ||
} | ||
} |
@@ -1,16 +0,18 @@ | ||
import { KeyValueResult, NonTerminalResult, ParseResult } from '../ParseResult' | ||
import { KeyValueResult, NonTerminalResult, ParenthesisResult, ParseResult } from '../ParseResult' | ||
import { assertNamedKeyValueOrTerminal } from '../assertTypes' | ||
export class BaseFunctionParslet { | ||
protected getParameters (value: NonTerminalResult): Array<ParseResult | KeyValueResult> { | ||
let parameters: Array<ParseResult | KeyValueResult> | ||
if (value.type === 'PARAMETER_LIST') { | ||
parameters = value.elements | ||
protected getParameters (value: ParenthesisResult): Array<ParseResult | KeyValueResult> { | ||
let parameters: NonTerminalResult[] | ||
if (value.element === undefined) { | ||
parameters = [] | ||
} else if (value.element.type === 'PARAMETER_LIST') { | ||
parameters = value.element.elements | ||
} else { | ||
parameters = [assertNamedKeyValueOrTerminal(value)] | ||
parameters = [value.element] | ||
} | ||
return parameters | ||
return parameters.map(p => assertNamedKeyValueOrTerminal(p)) | ||
} | ||
protected getNamedParameters (value: NonTerminalResult): KeyValueResult[] { | ||
protected getNamedParameters (value: ParenthesisResult): KeyValueResult[] { | ||
const parameters = this.getParameters(value) | ||
@@ -23,3 +25,3 @@ if (parameters.some(p => p.type !== 'KEY_VALUE')) { | ||
protected getUnnamedParameters (value: NonTerminalResult): ParseResult[] { | ||
protected getUnnamedParameters (value: ParenthesisResult): ParseResult[] { | ||
const parameters = this.getParameters(value) | ||
@@ -26,0 +28,0 @@ if (parameters.some(p => p.type === 'KEY_VALUE')) { |
@@ -5,4 +5,5 @@ import { PrefixParslet } from './Parslet' | ||
import { FunctionResult, ParseResult } from '../ParseResult' | ||
import { Precedence } from './Precedence' | ||
import { Precedence } from '../Precedence' | ||
import { BaseFunctionParslet } from './BaseFunctionParslet' | ||
import { UnexpectedTypeError } from '../errors' | ||
@@ -38,3 +39,3 @@ export interface FunctionParsletOptions { | ||
const hasParenthesis = parser.consume('(') | ||
const hasParenthesis = parser.getToken().type === '(' | ||
@@ -46,28 +47,29 @@ if (!this.allowWithoutParenthesis && !hasParenthesis) { | ||
type: 'FUNCTION', | ||
parameters: [] | ||
parameters: [], | ||
meta: { | ||
arrow: false, | ||
parenthesis: hasParenthesis | ||
} | ||
} | ||
if (hasParenthesis) { | ||
if (!parser.consume(')')) { | ||
const value = parser.parseNonTerminalType(Precedence.ALL) | ||
if (this.allowNamedParameters === undefined) { | ||
result.parameters = this.getUnnamedParameters(value) | ||
} else { | ||
result.parameters = this.getParameters(value) | ||
for (const p of result.parameters) { | ||
if (p.type === 'KEY_VALUE' && !this.allowNamedParameters.includes(p.key.name)) { | ||
throw new Error(`only allowed named parameters are ${this.allowNamedParameters.join(',')} but got ${p.type}`) | ||
} | ||
const value = parser.parseNonTerminalType(Precedence.FUNCTION) | ||
if (value.type !== 'PARENTHESIS') { | ||
throw new UnexpectedTypeError(value) | ||
} | ||
if (this.allowNamedParameters === undefined) { | ||
result.parameters = this.getUnnamedParameters(value) | ||
} else { | ||
result.parameters = this.getParameters(value) | ||
for (const p of result.parameters) { | ||
if (p.type === 'KEY_VALUE' && !this.allowNamedParameters.includes(p.left.value)) { | ||
throw new Error(`only allowed named parameters are ${this.allowNamedParameters.join(',')} but got ${p.type}`) | ||
} | ||
} | ||
if (!parser.consume(')')) { | ||
throw new Error('function parameter list is not terminated') | ||
} | ||
} | ||
if (parser.consume(':')) { | ||
if (!parser.consume('void')) { | ||
result.returnType = parser.parseType(Precedence.PREFIX) | ||
} | ||
result.returnType = parser.parseType(Precedence.PREFIX) | ||
} else { | ||
@@ -74,0 +76,0 @@ if (!this.allowNoReturnType) { |
@@ -5,3 +5,3 @@ import { ParserEngine } from '../ParserEngine' | ||
import { InfixParslet } from './Parslet' | ||
import { Precedence } from './Precedence' | ||
import { Precedence } from '../Precedence' | ||
import { assertTerminal } from '../assertTypes' | ||
@@ -19,3 +19,3 @@ | ||
parseInfix (parser: ParserEngine, left: NonTerminalResult): ParseResult { | ||
parser.consume('.') | ||
const dot = parser.consume('.') | ||
parser.consume('<') | ||
@@ -34,6 +34,10 @@ | ||
type: 'GENERIC', | ||
subject: assertTerminal(left), | ||
objects | ||
left: assertTerminal(left), | ||
elements: objects, | ||
meta: { | ||
brackets: '<>', | ||
dot | ||
} | ||
} | ||
} | ||
} |
@@ -5,3 +5,3 @@ import { PrefixParslet } from './Parslet' | ||
import { ParseResult } from '../ParseResult' | ||
import { Precedence } from './Precedence' | ||
import { Precedence } from '../Precedence' | ||
@@ -31,5 +31,5 @@ export class ImportParslet implements PrefixParslet { | ||
type: 'IMPORT', | ||
path | ||
element: path | ||
} | ||
} | ||
} |
import { PrefixParslet } from './Parslet' | ||
import { TokenType } from '../lexer/Token' | ||
import { ParserEngine } from '../ParserEngine' | ||
import { KeyOfResult, ParseResult } from '../ParseResult' | ||
import { Precedence } from './Precedence' | ||
import { KeyOfResult } from '../ParseResult' | ||
import { Precedence } from '../Precedence' | ||
import { assertTerminal } from '../assertTypes' | ||
@@ -17,13 +17,9 @@ | ||
parsePrefix (parser: ParserEngine): ParseResult { | ||
parsePrefix (parser: ParserEngine): KeyOfResult { | ||
parser.consume('keyof') | ||
const result: KeyOfResult = { | ||
type: 'KEY_OF' | ||
return { | ||
type: 'KEY_OF', | ||
element: assertTerminal(parser.parseType(Precedence.KEY_OF_TYPE_OF)) | ||
} | ||
const value = parser.tryParseType(Precedence.KEY_OF_TYPE_OF) | ||
if (value !== undefined) { | ||
result.value = assertTerminal(value) | ||
} | ||
return result | ||
} | ||
} |
import { InfixParslet } from './Parslet' | ||
import { TokenType } from '../lexer/Token' | ||
import { Precedence } from './Precedence' | ||
import { Precedence } from '../Precedence' | ||
import { ParserEngine } from '../ParserEngine' | ||
import { NonTerminalResult } from '../ParseResult' | ||
import { assertTerminal } from '../assertTypes' | ||
import { UnexpectedTypeError } from '../errors' | ||
interface KeyValueParsletOptions { | ||
allowOnlyNameOrNumberProperties: boolean | ||
} | ||
export class KeyValueParslet implements InfixParslet { | ||
private readonly allowOnlyNameOrNumberProperties | ||
constructor (opts: KeyValueParsletOptions) { | ||
this.allowOnlyNameOrNumberProperties = opts.allowOnlyNameOrNumberProperties | ||
} | ||
accepts (type: TokenType, next: TokenType): boolean { | ||
@@ -18,2 +29,5 @@ return type === ':' | ||
parseInfix (parser: ParserEngine, left: NonTerminalResult): NonTerminalResult { | ||
if (this.allowOnlyNameOrNumberProperties && left.type !== 'NUMBER' && left.type !== 'NAME') { | ||
throw new UnexpectedTypeError(left) | ||
} | ||
parser.consume(':') | ||
@@ -23,6 +37,6 @@ const value = parser.parseType(Precedence.KEY_VALUE) | ||
type: 'KEY_VALUE', | ||
key: left.type === 'NUMBER' ? left : assertTerminal(left), | ||
value: value | ||
left: left.type === 'NUMBER' ? left : assertTerminal(left), | ||
right: value | ||
} | ||
} | ||
} |
import { PrefixParslet } from './Parslet' | ||
import { TokenType } from '../lexer/Token' | ||
import { Precedence } from './Precedence' | ||
import { Precedence } from '../Precedence' | ||
import { ParserEngine } from '../ParserEngine' | ||
@@ -21,15 +21,27 @@ import { ParseResult } from '../ParseResult' | ||
} | ||
let result = 'module:' | ||
const allowed: TokenType[] = ['Identifier', '~', '@', '/', '#'] | ||
let token = parser.getToken() | ||
while (allowed.includes(token.type)) { | ||
result += token.text | ||
parser.consume(token.type) | ||
token = parser.getToken() | ||
if (parser.consume('StringValue')) { | ||
return { | ||
type: 'MODULE', | ||
value: token.text.slice(1, -1), | ||
meta: { | ||
quote: token.text[0] as '\'' | '"' | ||
} | ||
} | ||
} else { | ||
let result = '' | ||
const allowed: TokenType[] = ['Identifier', '@', '/'] | ||
while (allowed.some(type => parser.consume(type))) { | ||
result += token.text | ||
token = parser.getToken() | ||
} | ||
return { | ||
type: 'MODULE', | ||
value: result, | ||
meta: { | ||
quote: undefined | ||
} | ||
} | ||
} | ||
return { | ||
type: 'MODULE', | ||
path: result | ||
} | ||
} | ||
} |
import { ParserEngine } from '../ParserEngine' | ||
import { TokenType } from '../lexer/Token' | ||
import { NameResult, ParseResult } from '../ParseResult' | ||
import { NameResult } from '../ParseResult' | ||
import { PrefixParslet } from './Parslet' | ||
import { Precedence } from './Precedence' | ||
import { Precedence } from '../Precedence' | ||
@@ -46,5 +46,15 @@ const reservedWords = [ | ||
interface NameParsletOptions { | ||
allowedAdditionalTokens: TokenType[] | ||
} | ||
export class NameParslet implements PrefixParslet { | ||
private readonly allowedAdditionalTokens: TokenType[] | ||
constructor (options: NameParsletOptions) { | ||
this.allowedAdditionalTokens = options.allowedAdditionalTokens | ||
} | ||
accepts (type: TokenType, next: TokenType): boolean { | ||
return type === 'Identifier' || type === 'this' || type === 'new' | ||
return type === 'Identifier' || type === 'this' || type === 'new' || this.allowedAdditionalTokens.includes(type) | ||
} | ||
@@ -56,14 +66,15 @@ | ||
parsePrefix (parser: ParserEngine): ParseResult { | ||
parsePrefix (parser: ParserEngine): NameResult { | ||
const token = parser.getToken() | ||
parser.consume('Identifier') || parser.consume('this') || parser.consume('new') | ||
const result: NameResult = { | ||
parser.consume('Identifier') || parser.consume('this') || parser.consume('new') || | ||
this.allowedAdditionalTokens.some(type => parser.consume(type)) | ||
return { | ||
type: 'NAME', | ||
name: token.text | ||
value: token.text, | ||
meta: { | ||
reservedWord: reservedWords.includes(token.text) | ||
} | ||
} | ||
if (reservedWords.includes(token.text)) { | ||
result.reservedWord = true | ||
} | ||
return result | ||
} | ||
} |
@@ -5,3 +5,3 @@ import { InfixParslet, PrefixParslet } from './Parslet' | ||
import { NonTerminalResult, ParseResult } from '../ParseResult' | ||
import { Precedence } from './Precedence' | ||
import { Precedence } from '../Precedence' | ||
import { isQuestionMarkUnknownType } from './isQuestionMarkUnkownType' | ||
@@ -12,3 +12,3 @@ import { assertTerminal } from '../assertTypes' | ||
accepts (type: TokenType, next: TokenType): boolean { | ||
return (type === '?' && !isQuestionMarkUnknownType(next)) || type === '!' | ||
return type === '?' && !isQuestionMarkUnknownType(next) | ||
} | ||
@@ -21,9 +21,10 @@ | ||
parsePrefix (parser: ParserEngine): ParseResult { | ||
const nullable = parser.consume('?') || !parser.consume('!') | ||
const value = parser.parseType(Precedence.NULLABLE) | ||
if (value.nullable !== undefined) { | ||
throw new Error('Multiple nullable modifiers on same type') | ||
parser.consume('?') | ||
return { | ||
type: 'NULLABLE', | ||
element: parser.parseType(Precedence.NULLABLE), | ||
meta: { | ||
position: 'PREFIX' | ||
} | ||
} | ||
value.nullable = nullable | ||
return value | ||
} | ||
@@ -34,3 +35,3 @@ } | ||
accepts (type: TokenType, next: TokenType): boolean { | ||
return type === '?' || type === '!' | ||
return type === '?' | ||
} | ||
@@ -43,10 +44,11 @@ | ||
parseInfix (parser: ParserEngine, left: NonTerminalResult): ParseResult { | ||
const nullable = parser.consume('?') || !parser.consume('!') | ||
const value = assertTerminal(left) | ||
if (value.nullable !== undefined) { | ||
throw new Error('Multiple nullable modifiers on same type') | ||
parser.consume('?') | ||
return { | ||
type: 'NULLABLE', | ||
element: assertTerminal(left), | ||
meta: { | ||
position: 'SUFFIX' | ||
} | ||
} | ||
value.nullable = nullable | ||
return value | ||
} | ||
} |
import { PrefixParslet } from './Parslet' | ||
import { TokenType } from '../lexer/Token' | ||
import { Precedence } from './Precedence' | ||
import { Precedence } from '../Precedence' | ||
import { ParserEngine } from '../ParserEngine' | ||
@@ -5,0 +5,0 @@ import { NonTerminalResult } from '../ParseResult' |
@@ -1,11 +0,11 @@ | ||
import { InfixParslet } from './Parslet' | ||
import { InfixParslet, PrefixParslet } from './Parslet' | ||
import { TokenType } from '../lexer/Token' | ||
import { ParserEngine } from '../ParserEngine' | ||
import { NonTerminalResult, ParseResult } from '../ParseResult' | ||
import { Precedence } from './Precedence' | ||
import { Precedence } from '../Precedence' | ||
import { assertTerminal } from '../assertTypes' | ||
export class OptionalParslet implements InfixParslet { | ||
export class OptionalParslet implements PrefixParslet, InfixParslet { | ||
accepts (type: TokenType, next: TokenType): boolean { | ||
return type === '=' && next !== '>' | ||
return type === '=' | ||
} | ||
@@ -17,8 +17,23 @@ | ||
parsePrefix (parser: ParserEngine): ParseResult { | ||
parser.consume('=') | ||
return { | ||
type: 'OPTIONAL', | ||
element: parser.parseType(Precedence.OPTIONAL), | ||
meta: { | ||
position: 'PREFIX' | ||
} | ||
} | ||
} | ||
parseInfix (parser: ParserEngine, left: NonTerminalResult): ParseResult { | ||
parser.consume('=') | ||
const result = assertTerminal(left) | ||
result.optional = true | ||
return result | ||
return { | ||
type: 'OPTIONAL', | ||
element: assertTerminal(left), | ||
meta: { | ||
position: 'SUFFIX' | ||
} | ||
} | ||
} | ||
} |
@@ -5,4 +5,5 @@ import { InfixParslet } from './Parslet' | ||
import { KeyValueResult, NonTerminalResult, ParseResult } from '../ParseResult' | ||
import { Precedence } from './Precedence' | ||
import { Precedence } from '../Precedence' | ||
import { assertNamedKeyValueOrTerminal } from '../assertTypes' | ||
import { NoParsletFoundError } from '../errors' | ||
@@ -14,3 +15,3 @@ interface ParameterListParsletOptions { | ||
export class ParameterListParslet implements InfixParslet { | ||
private readonly allowTrailingComma: boolean // TODO | ||
private readonly allowTrailingComma: boolean | ||
@@ -35,5 +36,18 @@ constructor (option: ParameterListParsletOptions) { | ||
do { | ||
const next = parser.parseNonTerminalType(Precedence.PARAMETER_LIST) | ||
elements.push(assertNamedKeyValueOrTerminal(next)) | ||
try { | ||
const next = parser.parseNonTerminalType(Precedence.PARAMETER_LIST) | ||
elements.push(assertNamedKeyValueOrTerminal(next)) | ||
} catch (e) { | ||
if (this.allowTrailingComma && e instanceof NoParsletFoundError) { | ||
break | ||
} else { | ||
throw e | ||
} | ||
} | ||
} while (parser.consume(',')) | ||
if (elements.length > 0 && elements.slice(0, -1).some(e => e.type === 'VARIADIC')) { | ||
throw new Error('Only the last parameter may be a rest parameter') | ||
} | ||
return { | ||
@@ -40,0 +54,0 @@ type: 'PARAMETER_LIST', |
import { PrefixParslet } from './Parslet' | ||
import { TokenType } from '../lexer/Token' | ||
import { Precedence } from './Precedence' | ||
import { Precedence } from '../Precedence' | ||
import { ParserEngine } from '../ParserEngine' | ||
import { NonTerminalResult } from '../ParseResult' | ||
import { ParenthesisResult } from '../ParseResult' | ||
export class ParenthesisParslet implements PrefixParslet { | ||
accepts (type: TokenType, next: TokenType): boolean { | ||
return type === '(' && next !== ')' | ||
return type === '(' | ||
} | ||
@@ -16,10 +16,13 @@ | ||
parsePrefix (parser: ParserEngine): NonTerminalResult { | ||
parsePrefix (parser: ParserEngine): ParenthesisResult { | ||
parser.consume('(') | ||
const result = parser.parseNonTerminalType(Precedence.ALL) | ||
const result = parser.tryParseType(Precedence.ALL) | ||
if (!parser.consume(')')) { | ||
throw new Error('Unterminated parenthesis') | ||
} | ||
return result | ||
return { | ||
type: 'PARENTHESIS', | ||
element: result // NOTE: this can only be non-terminal or undefined if it is a parameter list | ||
} | ||
} | ||
} |
import { TokenType } from '../lexer/Token' | ||
import { ParserEngine } from '../ParserEngine' | ||
import { NonTerminalResult } from '../ParseResult' | ||
import { Precedence } from './Precedence' | ||
import { Precedence } from '../Precedence' | ||
@@ -6,0 +6,0 @@ export interface Parslet { |
@@ -5,3 +5,3 @@ import { PrefixParslet } from './Parslet' | ||
import { ParseResult, RecordResult } from '../ParseResult' | ||
import { Precedence } from './Precedence' | ||
import { Precedence } from '../Precedence' | ||
@@ -14,3 +14,3 @@ export class RecordParslet implements PrefixParslet { | ||
getPrecedence (): Precedence { | ||
return Precedence.RECORD | ||
return Precedence.OBJECT | ||
} | ||
@@ -21,4 +21,4 @@ | ||
const result: RecordResult = { | ||
type: 'RECORD', | ||
fields: [] | ||
type: 'OBJECT', | ||
elements: [] | ||
} | ||
@@ -28,3 +28,3 @@ | ||
do { | ||
const field = parser.parseNonTerminalType(Precedence.RECORD) | ||
const field = parser.parseNonTerminalType(Precedence.OBJECT) | ||
if (field.type !== 'NAME' && field.type !== 'NUMBER' && field.type !== 'KEY_VALUE') { | ||
@@ -34,3 +34,3 @@ throw new Error('records may only contain \'NAME\', \'NUMBER\' or \'KEY_VALUE\' fields.') | ||
result.fields.push(field) | ||
result.elements.push(field) | ||
} while (parser.consume(',')) | ||
@@ -37,0 +37,0 @@ if (!parser.consume('}')) { |
@@ -5,3 +5,3 @@ import { PrefixParslet } from './Parslet' | ||
import { ParseResult } from '../ParseResult' | ||
import { Precedence } from './Precedence' | ||
import { Precedence } from '../Precedence' | ||
import { isQuestionMarkUnknownType } from './isQuestionMarkUnkownType' | ||
@@ -19,27 +19,28 @@ | ||
parsePrefix (parser: ParserEngine): ParseResult { | ||
switch (parser.getToken().type) { | ||
case 'null': | ||
parser.consume('null') | ||
return { | ||
type: 'NULL' | ||
} | ||
case 'undefined': | ||
parser.consume('undefined') | ||
return { | ||
type: 'UNDEFINED' | ||
} | ||
case '*': | ||
parser.consume('*') | ||
return { | ||
type: 'ALL' | ||
} | ||
case '?': | ||
parser.consume('?') | ||
return { | ||
type: 'UNKNOWN' | ||
} | ||
default: | ||
throw new Error('Unacceptable token: ' + parser.getToken().text) | ||
if (parser.consume('null')) { | ||
return { | ||
type: 'NULL' | ||
} | ||
} | ||
if (parser.consume('undefined')) { | ||
return { | ||
type: 'UNDEFINED' | ||
} | ||
} | ||
if (parser.consume('*')) { | ||
return { | ||
type: 'ANY' | ||
} | ||
} | ||
if (parser.consume('?')) { | ||
return { | ||
type: 'UNKNOWN' | ||
} | ||
} | ||
throw new Error('Unacceptable token: ' + parser.getToken().text) | ||
} | ||
} |
import { PrefixParslet } from './Parslet' | ||
import { TokenType } from '../lexer/Token' | ||
import { ParserEngine } from '../ParserEngine' | ||
import { ParseResult } from '../ParseResult' | ||
import { Precedence } from './Precedence' | ||
import { StringValueResult } from '../ParseResult' | ||
import { Precedence } from '../Precedence' | ||
@@ -16,3 +16,3 @@ export class StringValueParslet implements PrefixParslet { | ||
parsePrefix (parser: ParserEngine): ParseResult { | ||
parsePrefix (parser: ParserEngine): StringValueResult { | ||
const token = parser.getToken() | ||
@@ -23,5 +23,7 @@ parser.consume('StringValue') | ||
value: token.text.slice(1, -1), | ||
quote: token.text[0] | ||
meta: { | ||
quote: token.text[0] as '\'' | '"' | ||
} | ||
} | ||
} | ||
} |
@@ -5,3 +5,4 @@ import { InfixParslet } from './Parslet' | ||
import { NonTerminalResult, ParseResult, SymbolResult } from '../ParseResult' | ||
import { Precedence } from './Precedence' | ||
import { Precedence } from '../Precedence' | ||
import { assertNumberOrVariadicName } from '../assertTypes' | ||
@@ -24,10 +25,7 @@ export class SymbolParslet implements InfixParslet { | ||
type: 'SYMBOL', | ||
name: left.name | ||
value: left.value | ||
} | ||
if (!parser.consume(')')) { | ||
const next = parser.parseNonTerminalType(Precedence.SYMBOL) | ||
if (next.type !== 'NUMBER' && next.type !== 'NAME') { | ||
throw new Error('Symbol value must be a number or a name') | ||
} | ||
result.value = next | ||
result.element = assertNumberOrVariadicName(next) | ||
if (!parser.consume(')')) { | ||
@@ -34,0 +32,0 @@ throw new Error('Symbol does not end after value') |
import { PrefixParslet } from './Parslet' | ||
import { TokenType } from '../lexer/Token' | ||
import { ParserEngine } from '../ParserEngine' | ||
import { ParseResult, TypeOfResult } from '../ParseResult' | ||
import { Precedence } from './Precedence' | ||
import { TypeOfResult } from '../ParseResult' | ||
import { Precedence } from '../Precedence' | ||
import { assertTerminal } from '../assertTypes' | ||
@@ -17,13 +17,9 @@ | ||
parsePrefix (parser: ParserEngine): ParseResult { | ||
parsePrefix (parser: ParserEngine): TypeOfResult { | ||
parser.consume('typeof') | ||
const result: TypeOfResult = { | ||
type: 'TYPE_OF' | ||
return { | ||
type: 'TYPE_OF', | ||
element: assertTerminal(parser.parseType(Precedence.KEY_OF_TYPE_OF)) | ||
} | ||
const value = parser.tryParseType(Precedence.KEY_OF_TYPE_OF) | ||
if (value !== undefined) { | ||
result.value = assertTerminal(value) | ||
} | ||
return result | ||
} | ||
} |
@@ -5,3 +5,3 @@ import { InfixParslet } from './Parslet' | ||
import { TokenType } from '../lexer/Token' | ||
import { Precedence } from './Precedence' | ||
import { Precedence } from '../Precedence' | ||
import { assertTerminal } from '../assertTypes' | ||
@@ -8,0 +8,0 @@ |
import { InfixParslet, PrefixParslet } from './Parslet' | ||
import { TokenType } from '../lexer/Token' | ||
import { ParserEngine } from '../ParserEngine' | ||
import { NonTerminalResult, ParseResult } from '../ParseResult' | ||
import { Precedence } from './Precedence' | ||
import { NonTerminalResult, ParseResult, VariadicResult } from '../ParseResult' | ||
import { Precedence } from '../Precedence' | ||
import { assertTerminal } from '../assertTypes' | ||
interface VariadicParsletOptions { | ||
allowEnclosingBrackets: boolean | ||
} | ||
export class VariadicParslet implements PrefixParslet, InfixParslet { | ||
private readonly allowEnclosingBrackets: boolean | ||
constructor (opts: VariadicParsletOptions) { | ||
this.allowEnclosingBrackets = opts.allowEnclosingBrackets | ||
} | ||
accepts (type: TokenType): boolean { | ||
@@ -17,11 +27,29 @@ return type === '...' | ||
parsePrefix (parser: ParserEngine): ParseResult { | ||
parsePrefix (parser: ParserEngine): VariadicResult<ParseResult> { | ||
parser.consume('...') | ||
const shouldClose = parser.consume('[') | ||
const value = parser.parseType(Precedence.PREFIX) | ||
if (shouldClose && !parser.consume(']')) { | ||
const brackets = this.allowEnclosingBrackets && parser.consume('[') | ||
const value = parser.tryParseType(Precedence.PREFIX) | ||
if (brackets && !parser.consume(']')) { | ||
throw new Error('Unterminated variadic type. Missing \']\'') | ||
} | ||
value.repeatable = true | ||
return value | ||
if (value !== undefined) { | ||
return { | ||
type: 'VARIADIC', | ||
element: assertTerminal(value), | ||
meta: { | ||
position: 'PREFIX', | ||
squareBrackets: brackets | ||
} | ||
} | ||
} else { | ||
return { | ||
type: 'VARIADIC', | ||
meta: { | ||
position: 'ONLY_DOTS', | ||
squareBrackets: false | ||
} | ||
} | ||
} | ||
} | ||
@@ -31,6 +59,11 @@ | ||
parser.consume('...') | ||
const result = assertTerminal(left) | ||
result.repeatable = true | ||
return result | ||
return { | ||
type: 'VARIADIC', | ||
element: assertTerminal(left), | ||
meta: { | ||
position: 'SUFFIX', | ||
squareBrackets: false | ||
} | ||
} | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
422347
0
182
13493
81
15
1
- Removedtypescript@^4.1.3
- Removedtypescript@4.9.5(transitive)