From 358e8b742884fd21c0f02efe8cd7e78f05dc9951 Mon Sep 17 00:00:00 2001 From: simonseyock Date: Sun, 9 Jan 2022 13:44:09 +0100 Subject: [PATCH] docs: improve README.md and some jsdoc --- README.md | 119 +++++++++++++++++-------------- package.json | 3 +- src/parslets/predicateParslet.ts | 2 +- src/traverse.ts | 13 ++++ 4 files changed, 81 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 454813d..aa335f9 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,14 @@ # Jsdoc Type Pratt Parser -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, instead it is written as a pratt parser. -* https://github.com/hegemonic/catharsis -* https://github.com/jsdoctypeparser/jsdoctypeparser +This project is a parser for jsdoc types. It takes jsdoc type expressions like `Array` and creates an abstract +syntax tree (AST) out of it. It is heavily inspired by the existing libraries [catharsis](https://github.com/hegemonic/catharsis) and [jsdoctypeparser](https://github.com/jsdoctypeparser/jsdoctypeparser), but does +not use [PEG.js](https://pegjs.org/), instead it is written as a pratt parser. + +You can find some more information about pratt parsers here: +* https://en.wikipedia.org/wiki/Operator-precedence_parser#Pratt_parsing * http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/ +* https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html ## Table of Contents * [Live Demo](#live-demo) @@ -18,6 +21,7 @@ This project is a parser for jsdoc types. It is heavily inspired by the existing * [API Documentation](#api-documentation) * [Available Grammars](#available-grammars) * [Transforms](#transforms) +* [Traverse](#traverse) * [Tests Status](#tests-status) * [Performance](#performance) * [Development](#development) @@ -35,12 +39,13 @@ npm install jsdoc-type-pratt-parser@alpha ```js import { parse } from 'jsdoc-type-pratt-parser' -const result = parse('myType.', 'closure') +const result = parse('SomeType', 'typescript') ``` ## API Documentation -An API documentation can be found here: https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html +An API documentation can be found [here](https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html). +It is still lacking in some points. Feel free to create issues or PRs to improve this. ## Available Grammars @@ -48,49 +53,67 @@ Three different modes (grammars) are supported: `'jsdoc'`, `'closure'` and `'typ ## Transforms -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: +A common task to do on ASTs are transforms, for example a stringification. This library includes some transform and +utilities to implement your own. + +[`stringify`](https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html#stringify): ```js -import { parse, catharsisTransform } from 'jsdoc-type-pratt-parser' +import { stringify } from 'jsdoc-type-pratt-parser' -const result = catharsisTransform(parse('myType.', 'closure')) +const val = stringify({ type: 'JsdocTypeName', value: 'name'}) // -> 'name' ``` -Jsdoctypeparser compat mode: +You can customize the stringification by using [`stringifyRules`](https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html#stringifyRules) +and [`transform`](https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html#transform): ```js -import { parse, jtpTransform } from 'jsdoc-type-pratt-parser' +import { stringifyRules, transform } from 'jsdoc-type-pratt-parser' -const result = jtpTransform(parse('myType.', 'closure')) +const rules = stringifyRules() + +// `result` is the current node and `transform` is a function to transform child nodes. +rules.NAME = (result, transform) => 'something else' + +const val = transform(rules, { type: 'JsdocTypeName', value: 'name'}) // -> 'something else' ``` -Stringify: +You can also build your own transform rules by implementing the [`TransformRules`](https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html#TransformRules) interface or you +can build upon the [identity ruleset](https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html#identityTransformRules) like this: ```js -import { stringify } from 'jsdoc-type-pratt-parser' +import { identityTransformRules, transform } from 'jsdoc-type-pratt-parser' -const val = stringify({ type: 'JsdocTypeName', value: 'name'}) // -> 'name' +const myRules = identityTransformRules() +myRules.NAME = () => ({ type: 'JsdocTypeName', value: 'funky' }) + +const val = transform(myRules, result) ``` -You can customize the stringification by using `stringifyRules` and `transform`: +This library also 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](https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html#catharsisTransform): ```js -import { stringifyRules, transform } from 'jsdoc-type-pratt-parser' +import { parse, catharsisTransform } from 'jsdoc-type-pratt-parser' -const rules = stringifyRules() +const result = catharsisTransform(parse('myType.', 'closure')) +``` -// `result` is the current node and `transform` is a function to transform child nodes. -rules.NAME = (result, transform) => 'something else' +[Jsdoctypeparser compat mode](https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html#jtpTransform): -const val = transform(rules, { type: 'JsdocTypeName', value: 'name'}) // -> 'something else' +```js +import { parse, jtpTransform } from 'jsdoc-type-pratt-parser' + +const result = jtpTransform(parse('myType.', 'closure')) ``` -You can traverse a result tree with the `traverse` function: +## Traverse + +You can traverse an AST with the [`traverse`](https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/docs/modules.html#traverse) function: ```js import { traverse } from 'jsdoc-type-pratt-parser' @@ -104,51 +127,39 @@ function onEnter(node, parent, property) { traverse({ type: 'JsdocTypeName', value: 'name'}, onEnter, console.log) ``` -You can also build your own transform rules by implementing the `TransformRules` interface or you -can build upon the identity ruleset like this: - -```js -import { identityTransformRules, transform } from 'jsdoc-type-pratt-parser' - -const myRules = identityTransformRules() -myRules.NAME = () => ({ type: 'JsdocTypeName', value: 'funky' }) - -const val = transform(myRules, result) -``` - ## Tests Status This parser runs most tests of https://github.com/hegemonic/catharsis and - 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. +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. ## Performance -A simple performance [comparision](benchmark/benchmark.js) using [Benchmark.js](https://benchmarkjs.com/) produced the following results: +A simple [performance comparision](benchmark/benchmark.js) using [Benchmark.js](https://benchmarkjs.com/) produced the following results: ``` Testing expression: Name -catharsis x 36,338 ops/sec ±1.10% (1071 runs sampled) -jsdoc-type-pratt-parser x 400,260 ops/sec ±0.87% (1070 runs sampled) -jsdoctypeparser x 61,847 ops/sec ±1.18% (1071 runs sampled) +catharsis x 37,816 ops/sec ±1.22% (1086 runs sampled) +jsdoc-type-pratt-parser x 602,617 ops/sec ±0.16% (1090 runs sampled) +jsdoctypeparser x 53,256 ops/sec ±0.73% (1081 runs sampled) The fastest was jsdoc-type-pratt-parser Testing expression: Array -catharsis x 7,969 ops/sec ±1.05% (1079 runs sampled) -jsdoc-type-pratt-parser x 159,001 ops/sec ±0.95% (1074 runs sampled) -jsdoctypeparser x 42,278 ops/sec ±1.01% (1070 runs sampled) +catharsis x 10,124 ops/sec ±0.56% (1084 runs sampled) +jsdoc-type-pratt-parser x 228,660 ops/sec ±0.40% (1084 runs sampled) +jsdoctypeparser x 42,365 ops/sec ±0.60% (1070 runs sampled) The fastest was jsdoc-type-pratt-parser Testing expression: { keyA: Type, keyB: function(string, B): A } -catharsis x 933 ops/sec ±1.15% (1070 runs sampled) -jsdoc-type-pratt-parser x 29,596 ops/sec ±0.90% (1068 runs sampled) -jsdoctypeparser x 16,206 ops/sec ±1.38% (1055 runs sampled) +catharsis x 1,138 ops/sec ±0.66% (1087 runs sampled) +jsdoc-type-pratt-parser x 46,535 ops/sec ±0.47% (1090 runs sampled) +jsdoctypeparser x 18,291 ops/sec ±0.71% (1084 runs sampled) The fastest was jsdoc-type-pratt-parser ``` -the test uses catharsis without cache, as this is just a simple lookup table that could easily be implemented for any parser. +The benchmark test uses catharsis without cache. ## Development If you want to contribute see the [Development Guide](DEVELOPMENT.md) to get some pointers. Feel free to create issues if -there is missing information. +there is information missing. diff --git a/package.json b/package.json index cfe35fa..f7ed4a9 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "apidoc": "typedoc src/index.ts --out docs", "preversion": "npm test", "prepublishOnly": "npm run build", - "semantic-release": "semantic-release" + "semantic-release": "semantic-release", + "benchmark": "npm run build && node benchmark/benchmark.js" }, "author": "Simon Seyock (https://github.com/simonseyock)", "contributors": [ diff --git a/src/parslets/predicateParslet.ts b/src/parslets/predicateParslet.ts index e1295a8..b4e948f 100644 --- a/src/parslets/predicateParslet.ts +++ b/src/parslets/predicateParslet.ts @@ -9,7 +9,7 @@ export const predicateParslet = composeParslet({ accept: type => type === 'is', parseInfix: (parser, left) => { if (left.type !== 'JsdocTypeName') { - throw new UnexpectedTypeError(left, 'Predicate always have to have names on the left side.') + throw new UnexpectedTypeError(left, 'A typescript predicate always has to have a name on the left side.') } parser.consume('is') diff --git a/src/traverse.ts b/src/traverse.ts index 5127318..7326c90 100644 --- a/src/traverse.ts +++ b/src/traverse.ts @@ -2,6 +2,13 @@ import { NonRootResult } from './result/NonRootResult' import { RootResult } from './result/RootResult' import { visitorKeys } from './visitorKeys' +/** + * A node visitor function. + * @param node the visited node. + * @param parentNode the parent node. + * @param property the property on the parent node that contains the visited node. It can be the node itself or + * an array of nodes. + */ type NodeVisitor = (node: NonRootResult, parentNode?: NonRootResult, property?: string) => void function _traverse (node: T, parentNode?: U, property?: keyof U, onEnter?: NodeVisitor, onLeave?: NodeVisitor): void { @@ -27,6 +34,12 @@ function _traverse (node: T, p onLeave?.(node, parentNode, property as string) } +/** + * A function to traverse an AST. It traverses it depth first. + * @param node the node to start traversing at. + * @param onEnter node visitor function that will be called on entering the node. This corresponds to preorder traversing. + * @param onLeave node visitor function that will be called on leaving the node. This corresponds to postorder traversing. + */ export function traverse (node: RootResult, onEnter?: NodeVisitor, onLeave?: NodeVisitor): void { _traverse(node, undefined, undefined, onEnter, onLeave) }