diff --git a/package.json b/package.json index 594a6f6..a8aee59 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,15 @@ "name": "@odata/parser", "version": "0.1.34", "description": "OData(v4) Parser", - "main": "lib/parser.js", - "typings": "lib/parser.d.ts", + "main": "lib/index", + "typings": "lib/index.d.ts", "engines": { "node": ">=10" }, + "repository": { + "type": "git", + "url": "https://github.com/Soontao/odata-v4-parser" + }, "contributors": [ { "name": "Theo Sun", diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..447c63c --- /dev/null +++ b/src/index.ts @@ -0,0 +1,7 @@ +import { Parser } from './parser'; + +export * from './parser'; +export * from './lexer'; +export * from './visitor'; + +export const defaultParser = new Parser(); diff --git a/src/parser.ts b/src/parser.ts index 3e9c901..7281fe8 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -21,7 +21,16 @@ export const parserFactory = function(fn) { }; }; +/** + * odata uri parser + */ export class Parser { + /** + * parser ast node with full odata uri + * + * @param source + * @param options + */ odataUri(source: string, options?: any): Lexer.Token { return parserFactory(ODataUri.odataUri)(source, options); } diff --git a/src/utils.ts b/src/utils.ts index 7e0a648..69c028d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,5 @@ import { Token, TokenType } from './lexer'; -import { forEach, isArray } from '@newdash/newdash'; -import { isPlainObject } from '@newdash/newdash/isPlainObject'; +import { createTraverser } from './visitor'; export type SourceArray = number[] | Uint16Array; @@ -35,30 +34,10 @@ export function required(value: SourceArray, index: number, comparer: Function, return i >= (min || 0) && i <= max ? index + i : 0; } -export type Traverser = { [key in TokenType]?: (token: Token) => void } - -export function createTraverser(traverser: Traverser) { - return function t(node: Token | Array | Object): void { - - if (node instanceof Token) { - if (node.type in traverser) { - traverser[node.type](node); - } - } - - if (isPlainObject(node) || isArray(node) || node instanceof Token) { - // @ts-ignore - forEach(node, (item) => { - t(item); - }); - } - - - }; -} /** - * find one node by type + * find one node in ast node by type + * * @param node * @param type */ @@ -69,7 +48,8 @@ export function findOne(node: Token, type: TokenType): Token { } /** - * find all nodes by type + * find all nodes in ast node by type + * * @param node * @param type */ diff --git a/src/visitor.ts b/src/visitor.ts new file mode 100644 index 0000000..8792f42 --- /dev/null +++ b/src/visitor.ts @@ -0,0 +1,44 @@ +import { TokenType, Token } from './lexer'; +import { isPlainObject } from '@newdash/newdash/isPlainObject'; +import { forEach } from '@newdash/newdash/forEach'; +import { isArray } from '@newdash/newdash/isArray'; + +/** + * AST Traverser + */ +export type Traverser = { [key in TokenType]?: (token: Token) => void } + +/** + * AST Visitor + * + * @alias Traverser + */ +export type Visitor = Traverser + +/** + * Traverse AST with traverser + * + * @param traverser + * @param node + */ +export function traverseAst(traverser: Traverser, node: Token | Array | Object): void { + + if (node instanceof Token) { + if (node.type in traverser) { + traverser[node.type](node); + } + } + + if (isPlainObject(node) || isArray(node) || node instanceof Token) { + // @ts-ignore + forEach(node, (item) => { + traverseAst(traverser, item); + }); + } +} + +export function createTraverser(traverser: Traverser) { + return function t(node: Token | Array | Object): void { + traverseAst(traverser, node); + }; +} diff --git a/test/visitor.spec.ts b/test/visitor.spec.ts new file mode 100644 index 0000000..b8f048c --- /dev/null +++ b/test/visitor.spec.ts @@ -0,0 +1,44 @@ +import { defaultParser, createTraverser } from '../src'; + +describe('Visitor Parse Suite', () => { + + const createSeqTokenProcessor = (key: string, arr: Array) => () => arr.push(key); + + const createSeqTraverser = () => { + const visitSequence = []; + + const visit = createTraverser({ + Top: createSeqTokenProcessor('param:top', visitSequence), + Skip: createSeqTokenProcessor('param:skip', visitSequence), + Filter: createSeqTokenProcessor('param:filter', visitSequence), + OrderBy: createSeqTokenProcessor('param:orderby', visitSequence), + Format: createSeqTokenProcessor('param:format', visitSequence), + Search: createSeqTokenProcessor('param:search', visitSequence), + InlineCount: createSeqTokenProcessor('param:inlinecount', visitSequence), + + AndExpression: createSeqTokenProcessor('and', visitSequence), + BoolParenExpression: createSeqTokenProcessor('paren', visitSequence), + EqualsExpression: createSeqTokenProcessor('eq', visitSequence), + Literal: createSeqTokenProcessor('lit', visitSequence), + FirstMemberExpression: createSeqTokenProcessor('mem', visitSequence) + }); + + return { visit, visitSequence }; + }; + + + it('should visit filter', () => { + + const expectedSeq = ['and', 'paren', 'eq', 'mem', 'lit', 'paren', 'eq', 'mem', 'lit']; + + const { visit, visitSequence } = createSeqTraverser(); + + const node = defaultParser.filter('(A eq 2) and (V eq 3)'); + + visit(node); + + expect(visitSequence).toEqual(expectedSeq); + + }); + +});