From 15869e05f23efa42b0f3aaa70366caf5b5297d07 Mon Sep 17 00:00:00 2001 From: Vladimir Gorej Date: Mon, 29 Nov 2021 13:04:12 +0100 Subject: [PATCH] perf: implement optimized tree-sitter CST access Refs #691 --- packages/apidom-ast/src/index.ts | 2 +- .../apidom-ast/src/{ => traversal}/visitor.ts | 2 +- packages/apidom-ast/test/visitor.ts | 2 +- .../apidom-parser-adapter-json/.mocharc.js | 2 +- .../apidom-parser-adapter-json/package.json | 1 + .../PreOrderCursorChildrenIterator.ts | 51 ++++++++ .../PreOrderCusrorIterator.ts | 61 +++++++++ .../direct/CursorIterator.ts | 116 ++++++++++++++++++ .../src/syntactic-analysis/direct/index.ts | 7 +- .../__snapshots__/adapter-browser.ts.snap | 15 +++ .../test/__snapshots__/adapter-node.ts.snap | 15 +++ .../test/adapter-browser.ts | 95 ++++++++++++++ .../test/adapter-node.ts | 22 +++- .../apidom-parser-adapter-json/test/index.ts | 24 ---- .../test/mocha-bootstrap.js | 14 ++- .../test/perf/index.js | 2 + .../test/perf/lexical-analysis.js | 37 ++++++ .../PreOrderCursorChildrenIterator.ts | 31 +++++ .../PreOrderCursorChildrenIterator.ts.snap | 8 ++ 19 files changed, 473 insertions(+), 34 deletions(-) rename packages/apidom-ast/src/{ => traversal}/visitor.ts (99%) create mode 100644 packages/apidom-parser-adapter-json/src/syntactic-analysis/PreOrderCursorChildrenIterator.ts create mode 100644 packages/apidom-parser-adapter-json/src/syntactic-analysis/PreOrderCusrorIterator.ts create mode 100644 packages/apidom-parser-adapter-json/src/syntactic-analysis/direct/CursorIterator.ts create mode 100644 packages/apidom-parser-adapter-json/test/__snapshots__/adapter-browser.ts.snap create mode 100644 packages/apidom-parser-adapter-json/test/__snapshots__/adapter-node.ts.snap create mode 100644 packages/apidom-parser-adapter-json/test/adapter-browser.ts delete mode 100644 packages/apidom-parser-adapter-json/test/index.ts create mode 100644 packages/apidom-parser-adapter-json/test/perf/lexical-analysis.js create mode 100644 packages/apidom-parser-adapter-json/test/syntactic-analysis/PreOrderCursorChildrenIterator.ts create mode 100644 packages/apidom-parser-adapter-json/test/syntactic-analysis/__snapshots__/PreOrderCursorChildrenIterator.ts.snap diff --git a/packages/apidom-ast/src/index.ts b/packages/apidom-ast/src/index.ts index c28b94c5a..867eb8084 100644 --- a/packages/apidom-ast/src/index.ts +++ b/packages/apidom-ast/src/index.ts @@ -69,4 +69,4 @@ export { visit, getNodeType, isNode, -} from './visitor'; +} from './traversal/visitor'; diff --git a/packages/apidom-ast/src/visitor.ts b/packages/apidom-ast/src/traversal/visitor.ts similarity index 99% rename from packages/apidom-ast/src/visitor.ts rename to packages/apidom-ast/src/traversal/visitor.ts index 539aa2bb0..3d70aa60a 100644 --- a/packages/apidom-ast/src/visitor.ts +++ b/packages/apidom-ast/src/traversal/visitor.ts @@ -98,7 +98,7 @@ export const mergeAll = ( /* eslint-disable no-continue, no-nested-ternary, no-param-reassign */ /** - * visit() will walk through an AST using a depth first traversal, calling + * visit() will walk through an AST using a preorder depth first traversal, calling * the visitor's enter function at each node in the traversal, and calling the * leave function after visiting that node and all of its child nodes. * diff --git a/packages/apidom-ast/test/visitor.ts b/packages/apidom-ast/test/visitor.ts index c1b0ff7ab..805619905 100644 --- a/packages/apidom-ast/test/visitor.ts +++ b/packages/apidom-ast/test/visitor.ts @@ -1,7 +1,7 @@ import sinon from 'sinon'; import { assert } from 'chai'; -import { visit } from '../src/visitor'; +import { visit } from '../src/traversal/visitor'; describe('visitor', function () { context('given structure with cycle', function () { diff --git a/packages/apidom-parser-adapter-json/.mocharc.js b/packages/apidom-parser-adapter-json/.mocharc.js index a4a860cde..55e015a40 100644 --- a/packages/apidom-parser-adapter-json/.mocharc.js +++ b/packages/apidom-parser-adapter-json/.mocharc.js @@ -3,5 +3,5 @@ module.exports = { recursive: true, spec: 'test/**/*.ts', - require: ['test/mocha-bootstrap.js'], + file: ['test/mocha-bootstrap.js'], }; diff --git a/packages/apidom-parser-adapter-json/package.json b/packages/apidom-parser-adapter-json/package.json index 758ef5aac..e775c558d 100644 --- a/packages/apidom-parser-adapter-json/package.json +++ b/packages/apidom-parser-adapter-json/package.json @@ -25,6 +25,7 @@ "typescript:declaration": "tsc -p declaration.tsconfig.json && rollup -c config/rollup/types.dist.js", "test": "cross-env BABEL_ENV=cjs mocha", "perf": "cross-env BABEL_ENV=cjs node ./test/perf/index.js", + "perf:lexical-analysis": "cross-env BABEL_ENV=cjs node ./test/perf/lexical-analysis.js", "perf:parse-syntactic-analysis-direct": "cross-env BABEL_ENV=cjs node ./test/perf/parse-syntactic-analysis-direct.js", "perf:parse-syntactic-analysis-indirect": "cross-env BABEL_ENV=cjs node ./test/perf/parse-syntactic-analysis-indirect.js", "prepack": "copyfiles -u 3 ../../LICENSES/* LICENSES && copyfiles -u 2 ../../NOTICE .", diff --git a/packages/apidom-parser-adapter-json/src/syntactic-analysis/PreOrderCursorChildrenIterator.ts b/packages/apidom-parser-adapter-json/src/syntactic-analysis/PreOrderCursorChildrenIterator.ts new file mode 100644 index 000000000..a44caf974 --- /dev/null +++ b/packages/apidom-parser-adapter-json/src/syntactic-analysis/PreOrderCursorChildrenIterator.ts @@ -0,0 +1,51 @@ +import { TreeCursor as NodeTreeCursor, Point as NodePoint } from 'tree-sitter'; +import { TreeCursor as WebTreeCursor, Point as WebPoint } from 'web-tree-sitter'; + +type TreeCursor = NodeTreeCursor | WebTreeCursor; +type Point = NodePoint | WebPoint; + +export interface SyntaxNodeSurrogate { + type: string; + startPosition: Point; + endPosition: Point; + children: SyntaxNodeSurrogate[]; + [key: string]: unknown; +} + +class PreOrderCursorChildrenIterator { + protected cursor; + + constructor(cursor: TreeCursor) { + this.cursor = cursor; + } + + protected createNode(): SyntaxNodeSurrogate { + return { + type: this.cursor.nodeType, + startPosition: this.cursor.startPosition, + endPosition: this.cursor.endPosition, + children: [], + }; + } + + public *[Symbol.iterator]() { + // @ts-ignore + const method = this[this.cursor.nodeType]; + const currentNode = (method || this.createNode).call(this) as SyntaxNodeSurrogate; + const constructor = this.constructor as any; + + if (this.cursor.gotoFirstChild()) { + currentNode.children.push(...[...new constructor(this.cursor)]); + + while (this.cursor.gotoNextSibling()) { + currentNode.children.push(...[...new constructor(this.cursor)]); + } + + this.cursor.gotoParent(); + } + + yield currentNode; + } +} + +export default PreOrderCursorChildrenIterator; diff --git a/packages/apidom-parser-adapter-json/src/syntactic-analysis/PreOrderCusrorIterator.ts b/packages/apidom-parser-adapter-json/src/syntactic-analysis/PreOrderCusrorIterator.ts new file mode 100644 index 000000000..c42fe766c --- /dev/null +++ b/packages/apidom-parser-adapter-json/src/syntactic-analysis/PreOrderCusrorIterator.ts @@ -0,0 +1,61 @@ +import { TreeCursor as NodeTreeCursor, Point as NodePoint } from 'tree-sitter'; +import { TreeCursor as WebTreeCursor, Point as WebPoint } from 'web-tree-sitter'; + +type TreeCursor = NodeTreeCursor | WebTreeCursor; +type Point = NodePoint | WebPoint; + +interface SyntaxNodeSurrogate { + type: string; + startPosition: Point; + endPosition: Point; + [key: string]: unknown; +} + +class PreOrderCursorIterator { + protected cursor; + + constructor(cursor: TreeCursor) { + this.cursor = cursor; + } + + protected createNode(): SyntaxNodeSurrogate { + return { + type: this.cursor.nodeType, + startPosition: this.cursor.startPosition, + endPosition: this.cursor.endPosition, + }; + } + + public *[Symbol.iterator]() { + let reachedRoot = false; + + while (!reachedRoot) { + // @ts-ignore + const method = this[this.cursor.nodeType]; + + yield (method || this.createNode).call(this) as SyntaxNodeSurrogate; + + if (this.cursor.gotoFirstChild()) { + continue; // eslint-disable-line no-continue + } + + if (this.cursor.gotoNextSibling()) { + continue; // eslint-disable-line no-continue + } + + let retracting = true; + while (retracting) { + if (!this.cursor.gotoParent()) { + retracting = false; + reachedRoot = true; + } + + if (this.cursor.gotoNextSibling()) { + retracting = false; + } + } + } + } +} + +export default PreOrderCursorIterator; diff --git a/packages/apidom-parser-adapter-json/src/syntactic-analysis/direct/CursorIterator.ts b/packages/apidom-parser-adapter-json/src/syntactic-analysis/direct/CursorIterator.ts new file mode 100644 index 000000000..7d8f78cbd --- /dev/null +++ b/packages/apidom-parser-adapter-json/src/syntactic-analysis/direct/CursorIterator.ts @@ -0,0 +1,116 @@ +import PreOrderCursorChildrenIterator, { + SyntaxNodeSurrogate, +} from '../PreOrderCursorChildrenIterator'; + +class CursorIterator extends PreOrderCursorChildrenIterator { + document(): SyntaxNodeSurrogate { + return { + type: this.cursor.nodeType, + startPosition: this.cursor.startPosition, + endPosition: this.cursor.endPosition, + children: [], + }; + } + + object(): SyntaxNodeSurrogate { + return { + type: this.cursor.nodeType, + startPosition: this.cursor.startPosition, + endPosition: this.cursor.endPosition, + fieldName: this.cursor.currentFieldName, + children: [], + }; + } + + array(): SyntaxNodeSurrogate { + return { + type: this.cursor.nodeType, + startPosition: this.cursor.startPosition, + endPosition: this.cursor.endPosition, + fieldName: this.cursor.currentFieldName, + children: [], + }; + } + + pair(): SyntaxNodeSurrogate { + return { + type: this.cursor.nodeType, + startPosition: this.cursor.startPosition, + endPosition: this.cursor.endPosition, + get keyNode() { + return this.children.find((node: any) => node.fieldName === 'key'); + }, + get valueNode() { + return this.children.find((node: any) => node.fieldName === 'value'); + }, + children: [], + }; + } + + string(): SyntaxNodeSurrogate { + return { + type: this.cursor.nodeType, + startPosition: this.cursor.startPosition, + endPosition: this.cursor.endPosition, + text: this.cursor.nodeText, + fieldName: this.cursor.currentFieldName, + children: [], + }; + } + + number(): SyntaxNodeSurrogate { + return { + type: this.cursor.nodeType, + startPosition: this.cursor.startPosition, + endPosition: this.cursor.endPosition, + text: this.cursor.nodeText, + fieldName: this.cursor.currentFieldName, + children: [], + }; + } + + null(): SyntaxNodeSurrogate { + return { + type: this.cursor.nodeType, + startPosition: this.cursor.startPosition, + endPosition: this.cursor.endPosition, + fieldName: this.cursor.currentFieldName, + children: [], + }; + } + + true(): SyntaxNodeSurrogate { + return { + type: this.cursor.nodeType, + startPosition: this.cursor.startPosition, + endPosition: this.cursor.endPosition, + fieldName: this.cursor.currentFieldName, + children: [], + }; + } + + false(): SyntaxNodeSurrogate { + return { + type: this.cursor.nodeType, + startPosition: this.cursor.startPosition, + endPosition: this.cursor.endPosition, + fieldName: this.cursor.currentFieldName, + children: [], + }; + } + + ERROR(): SyntaxNodeSurrogate { + const { currentNode } = this.cursor; + + return { + type: this.cursor.nodeType, + startPosition: this.cursor.startPosition, + endPosition: this.cursor.endPosition, + // @ts-ignore + hasError: () => currentNode.hasError(), + children: [], + }; + } +} + +export default CursorIterator; diff --git a/packages/apidom-parser-adapter-json/src/syntactic-analysis/direct/index.ts b/packages/apidom-parser-adapter-json/src/syntactic-analysis/direct/index.ts index d62422bff..26af67b3b 100644 --- a/packages/apidom-parser-adapter-json/src/syntactic-analysis/direct/index.ts +++ b/packages/apidom-parser-adapter-json/src/syntactic-analysis/direct/index.ts @@ -1,6 +1,6 @@ import stampit from 'stampit'; -import { Tree as NodeTree, SyntaxNode as NodeSyntaxNode } from 'tree-sitter'; -import { Tree as WebTree, SyntaxNode as WebSyntaxNode } from 'web-tree-sitter'; +import { SyntaxNode as NodeSyntaxNode } from 'tree-sitter'; +import { SyntaxNode as WebSyntaxNode } from 'web-tree-sitter'; import { visit, getNodeType as getCSTNodeType, isNode as isCSTNode } from '@swagger-api/apidom-ast'; import { BooleanElement, @@ -23,7 +23,6 @@ import { /* eslint-disable no-underscore-dangle */ -type Tree = WebTree | NodeTree; type SyntaxNode = WebSyntaxNode | NodeSyntaxNode; const keyMap = { @@ -261,7 +260,7 @@ const Visitor = stampit({ * This version of syntactic analysis translates TreeSitter CTS into ApiDOM. * Single traversal pass is needed to get from CST to ApiDOM. */ -const analyze = (cst: Tree, { sourceMap = false } = {}): ParseResultElement => { +const analyze = (cst: { rootNode: unknown }, { sourceMap = false } = {}): ParseResultElement => { const visitor = Visitor(); return visit(cst.rootNode, visitor, { diff --git a/packages/apidom-parser-adapter-json/test/__snapshots__/adapter-browser.ts.snap b/packages/apidom-parser-adapter-json/test/__snapshots__/adapter-browser.ts.snap new file mode 100644 index 000000000..53fa69786 --- /dev/null +++ b/packages/apidom-parser-adapter-json/test/__snapshots__/adapter-browser.ts.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`adapter-browser should parse 1`] = ` +(ParseResultElement + (ObjectElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (BooleanElement)) + (MemberElement + (StringElement) + (StringElement)))) +`; diff --git a/packages/apidom-parser-adapter-json/test/__snapshots__/adapter-node.ts.snap b/packages/apidom-parser-adapter-json/test/__snapshots__/adapter-node.ts.snap new file mode 100644 index 000000000..4f5499188 --- /dev/null +++ b/packages/apidom-parser-adapter-json/test/__snapshots__/adapter-node.ts.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`adapter-node should parse 1`] = ` +(ParseResultElement + (ObjectElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (BooleanElement)) + (MemberElement + (StringElement) + (StringElement)))) +`; diff --git a/packages/apidom-parser-adapter-json/test/adapter-browser.ts b/packages/apidom-parser-adapter-json/test/adapter-browser.ts new file mode 100644 index 000000000..6f26e36b3 --- /dev/null +++ b/packages/apidom-parser-adapter-json/test/adapter-browser.ts @@ -0,0 +1,95 @@ +import fs from 'fs'; +import path from 'path'; +import { expect, assert } from 'chai'; +import { sexprs, isObjectElement, isParseResultElement } from '@swagger-api/apidom-core'; + +import * as adapter from '../src/adapter-browser'; + +const spec = fs.readFileSync(path.join(__dirname, 'fixtures', 'sample-data.json')).toString(); + +describe('adapter-browser', function () { + it('should detect proper media type', async function () { + assert.isTrue(await adapter.detect(spec)); + }); + + it('should parse', async function () { + const parseResult = await adapter.parse(spec, { + syntacticAnalysis: 'direct', + sourceMap: true, + }); + + assert.isTrue(isParseResultElement(parseResult)); + assert.isTrue(isObjectElement(parseResult.result)); + expect(sexprs(parseResult)).toMatchSnapshot(); + }); + + context('given direct syntactic analysis', function () { + context('given zero byte empty file', function () { + specify('should return empty parse result', async function () { + const parseResult = await adapter.parse('', { + sourceMap: true, + syntacticAnalysis: 'direct', + }); + + assert.isTrue(parseResult.isEmpty); + }); + }); + + context('given non-zero byte empty file', function () { + specify('should return empty parser result', async function () { + const parseResult = await adapter.parse(' ', { + sourceMap: true, + syntacticAnalysis: 'direct', + }); + + assert.isTrue(parseResult.isEmpty); + }); + }); + + context('given invalid json file', function () { + specify('should return empty parser result', async function () { + const parseResult = await adapter.parse(' a ', { + sourceMap: true, + syntacticAnalysis: 'direct', + }); + + assert.isTrue(parseResult.isEmpty); + }); + }); + }); + + context('given indirect syntactic analysis', function () { + context('given zero byte empty file', function () { + specify('should return empty parse result', async function () { + const parseResult = await adapter.parse('', { + sourceMap: true, + syntacticAnalysis: 'indirect', + }); + + assert.isTrue(parseResult.isEmpty); + }); + }); + + context('given non-zero byte empty file', function () { + specify('should return empty parser result', async function () { + const parseResult = await adapter.parse(' ', { + sourceMap: true, + syntacticAnalysis: 'indirect', + }); + + assert.isTrue(parseResult.isEmpty); + }); + }); + + context('given invalid json file', function () { + specify('should return empty parser result', async function () { + const parseResult = await adapter.parse(' a ', { + sourceMap: true, + syntacticAnalysis: 'indirect', + }); + + assert.isTrue(parseResult.isEmpty); + }); + }); + }); +}); diff --git a/packages/apidom-parser-adapter-json/test/adapter-node.ts b/packages/apidom-parser-adapter-json/test/adapter-node.ts index a8a701ce8..acfb1cdf5 100644 --- a/packages/apidom-parser-adapter-json/test/adapter-node.ts +++ b/packages/apidom-parser-adapter-json/test/adapter-node.ts @@ -1,8 +1,28 @@ -import { assert } from 'chai'; +import fs from 'fs'; +import path from 'path'; +import { expect, assert } from 'chai'; +import { sexprs, isObjectElement, isParseResultElement } from '@swagger-api/apidom-core'; import * as adapter from '../src/adapter-node'; +const spec = fs.readFileSync(path.join(__dirname, 'fixtures', 'sample-data.json')).toString(); + describe('adapter-node', function () { + it('should detect proper media type', async function () { + assert.isTrue(await adapter.detect(spec)); + }); + + it('should parse', async function () { + const parseResult = await adapter.parse(spec, { + syntacticAnalysis: 'direct', + sourceMap: true, + }); + + assert.isTrue(isParseResultElement(parseResult)); + assert.isTrue(isObjectElement(parseResult.result)); + expect(sexprs(parseResult)).toMatchSnapshot(); + }); + context('given direct syntactic analysis', function () { context('given zero byte empty file', function () { specify('should return empty parse result', async function () { diff --git a/packages/apidom-parser-adapter-json/test/index.ts b/packages/apidom-parser-adapter-json/test/index.ts deleted file mode 100644 index cda8ba2ad..000000000 --- a/packages/apidom-parser-adapter-json/test/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { assert } from 'chai'; -import { isObjectElement, isParseResultElement } from '@swagger-api/apidom-core'; - -import * as adapter from '../src/adapter-node'; - -const spec = fs.readFileSync(path.join(__dirname, 'fixtures', 'sample-data.json')).toString(); - -describe('apidom-parser-adapter-json', function () { - it('should detect proper media type', async function () { - assert.isTrue(await adapter.detect(spec)); - }); - - it('should parse', async function () { - const parseResult = await adapter.parse(spec, { - syntacticAnalysis: 'direct', - sourceMap: true, - }); - - assert.isTrue(isParseResultElement(parseResult)); - assert.isTrue(isObjectElement(parseResult.result)); - }); -}); diff --git a/packages/apidom-parser-adapter-json/test/mocha-bootstrap.js b/packages/apidom-parser-adapter-json/test/mocha-bootstrap.js index df9a1f6f8..747f45dba 100644 --- a/packages/apidom-parser-adapter-json/test/mocha-bootstrap.js +++ b/packages/apidom-parser-adapter-json/test/mocha-bootstrap.js @@ -1,7 +1,19 @@ /* eslint-disable */ - require('@babel/register')({ extensions: ['.js', '.ts'], rootMode: 'upward' }); +/** + * Configure snapshot testing. + */ +const chai = require('chai'); +const { jestSnapshotPlugin, addSerializer } = require('mocha-chai-jest-snapshot'); + +const jestApiDOMSerializer = require('../../../scripts/jest-serializer-apidom'); +const jestStringSerializer = require('../../../scripts/jest-serializer-string'); + +chai.use(jestSnapshotPlugin()); +addSerializer(jestApiDOMSerializer); +addSerializer(jestStringSerializer); + /** * JSDOM setup. */ diff --git a/packages/apidom-parser-adapter-json/test/perf/index.js b/packages/apidom-parser-adapter-json/test/perf/index.js index c976352e6..899442b82 100644 --- a/packages/apidom-parser-adapter-json/test/perf/index.js +++ b/packages/apidom-parser-adapter-json/test/perf/index.js @@ -2,12 +2,14 @@ require('@babel/register')({ extensions: ['.js', '.ts'], rootMode: 'upward' }); const Benchmark = require('benchmark'); +const lexicalAnalysisBench = require('./lexical-analysis'); const parseSyntacticAnalysisDirectBench = require('./parse-syntactic-analysis-direct'); const parseSyntacticAnalysisIndirectBench = require('./parse-syntactic-analysis-indirect'); const suite = new Benchmark.Suite(); suite + .add(lexicalAnalysisBench) .add(parseSyntacticAnalysisDirectBench) .add(parseSyntacticAnalysisIndirectBench) // add listeners diff --git a/packages/apidom-parser-adapter-json/test/perf/lexical-analysis.js b/packages/apidom-parser-adapter-json/test/perf/lexical-analysis.js new file mode 100644 index 000000000..839186281 --- /dev/null +++ b/packages/apidom-parser-adapter-json/test/perf/lexical-analysis.js @@ -0,0 +1,37 @@ +require('@babel/register')({ extensions: ['.js', '.ts'], rootMode: 'upward' }); + +const fs = require('fs'); +const path = require('path'); +const Benchmark = require('benchmark'); + +const { default: analyze } = require('../../src/lexical-analysis/node'); + +const fixturePath = path.join(__dirname, 'fixtures/data.json'); +const source = fs.readFileSync(fixturePath).toString(); + +const options = { + name: 'lexical-analysis', + defer: true, + minSamples: 600, + expected: '814 ops/sec ±0.48% (677 runs sampled)', + async fn(deferred) { + await analyze(source); + deferred.resolve(); + }, +}; + +module.exports = options; + +// we're running as a script +if (module.parent === null) { + const bench = new Benchmark({ + ...options, + onComplete(event) { + console.info(String(event.target)); + }, + onError(event) { + console.error(event); + }, + }); + bench.run(); +} diff --git a/packages/apidom-parser-adapter-json/test/syntactic-analysis/PreOrderCursorChildrenIterator.ts b/packages/apidom-parser-adapter-json/test/syntactic-analysis/PreOrderCursorChildrenIterator.ts new file mode 100644 index 000000000..e74bcb547 --- /dev/null +++ b/packages/apidom-parser-adapter-json/test/syntactic-analysis/PreOrderCursorChildrenIterator.ts @@ -0,0 +1,31 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { lexicalAnalysis, syntacticAnalysisDirect } from '../../src/adapter-node'; +import PreOrderCursorChildrenIterator from '../../src/syntactic-analysis/PreOrderCursorChildrenIterator'; +import PreOrderCursorIterator from '../../src/syntactic-analysis/PreOrderCusrorIterator'; + +describe('syntactic-analysis', function () { + context('PreOrderCursorChildrenIterator', function () { + specify('should create optimized CST', async function () { + const cst = await lexicalAnalysis('[1, 2]'); + const cursor = cst.walk(); + const iterator = new PreOrderCursorChildrenIterator(cursor); + const optimizedCst = { rootNode: [...iterator][0] }; + const apiDOM = syntacticAnalysisDirect(optimizedCst); + + expect(sexprs(apiDOM)).toMatchSnapshot(); + }); + }); + + context('PreOrderCursorIterator', function () { + specify('should create optimized list of surrogate syntax nodes', async function () { + const cst = await lexicalAnalysis('[1, 2]'); + const cursor = cst.walk(); + const iterator = new PreOrderCursorIterator(cursor); + const optimizedList = [...iterator]; + + expect(optimizedList).to.have.lengthOf(7); + }); + }); +}); diff --git a/packages/apidom-parser-adapter-json/test/syntactic-analysis/__snapshots__/PreOrderCursorChildrenIterator.ts.snap b/packages/apidom-parser-adapter-json/test/syntactic-analysis/__snapshots__/PreOrderCursorChildrenIterator.ts.snap new file mode 100644 index 000000000..396d9fbe6 --- /dev/null +++ b/packages/apidom-parser-adapter-json/test/syntactic-analysis/__snapshots__/PreOrderCursorChildrenIterator.ts.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`syntactic-analysis PreOrderCursorChildrenIterator should create optimized CST 1`] = ` +(ParseResultElement + (ArrayElement + (NumberElement) + (NumberElement))) +`;