diff --git a/apidom/package-lock.json b/apidom/package-lock.json index 1546841039..0b10d25b6f 100644 --- a/apidom/package-lock.json +++ b/apidom/package-lock.json @@ -3163,6 +3163,21 @@ "ts-toolbelt": "^6.3.3" } }, + "@types/sinon": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.10.tgz", + "integrity": "sha512-/faDC0erR06wMdybwI/uR8wEKV/E83T0k4sepIpB7gXuy2gzx2xiOjmztq6a2Y6rIGJ04D+6UU0VBmWy+4HEMA==", + "dev": true, + "requires": { + "@types/sinonjs__fake-timers": "*" + } + }, + "@types/sinonjs__fake-timers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz", + "integrity": "sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==", + "dev": true + }, "@typescript-eslint/eslint-plugin": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.0.2.tgz", diff --git a/apidom/package.json b/apidom/package.json index 049fad0f18..dd978fe7bb 100644 --- a/apidom/package.json +++ b/apidom/package.json @@ -52,6 +52,7 @@ "@commitlint/config-conventional": "=12.0.0", "@types/chai": "=4.2.12", "@types/mocha": "=7.0.2", + "@types/sinon": "=9.0.10", "@typescript-eslint/eslint-plugin": "=3.0.2", "@typescript-eslint/parser": "=3.0.2", "axios-mock-adapter": "=1.19.0", diff --git a/apidom/packages/@types/minim.d.ts b/apidom/packages/@types/minim.d.ts index 204c611210..a8abc1fcf4 100644 --- a/apidom/packages/@types/minim.d.ts +++ b/apidom/packages/@types/minim.d.ts @@ -10,7 +10,7 @@ declare module 'minim' { export function refract(value: any): Element; export class Element { - static refract(value: any, option?: Record): Element; + static refract(value: any, options?: Record): Element; public element: string; diff --git a/apidom/packages/apidom-ns-asyncapi-2-0/src/index.ts b/apidom/packages/apidom-ns-asyncapi-2-0/src/index.ts index eee2140cc5..2e4125b5dc 100644 --- a/apidom/packages/apidom-ns-asyncapi-2-0/src/index.ts +++ b/apidom/packages/apidom-ns-asyncapi-2-0/src/index.ts @@ -1,4 +1,4 @@ -export { default } from './namespace'; +import './refractor/registration'; export { isRefElement, @@ -13,6 +13,8 @@ export { isStringElement, } from 'apidom'; +export { default } from './namespace'; + export { isAsyncApi2_0Element, isAsyncApiVersionElement, diff --git a/apidom/packages/apidom-ns-asyncapi-2-0/src/namespace.ts b/apidom/packages/apidom-ns-asyncapi-2-0/src/namespace.ts index 4c13019307..8c7be551e6 100644 --- a/apidom/packages/apidom-ns-asyncapi-2-0/src/namespace.ts +++ b/apidom/packages/apidom-ns-asyncapi-2-0/src/namespace.ts @@ -19,131 +19,6 @@ import ServerElement from './elements/Server'; import ServerBindingsElement from './elements/ServerBindings'; import ServersElement from './elements/Servers'; import ServerVariableElement from './elements/ServerVariable'; -import { createRefractor } from './refractor'; - -// register refractors specific to element types -AsyncApi2_0Element.refract = createRefractor([ - 'visitors', - 'document', - 'objects', - 'AsyncApi', - '$visitor', -]); -AsyncApiVersionElement.refract = createRefractor([ - 'visitors', - 'document', - 'objects', - 'AsyncApiVersion', - '$visitor', -]); -ChannelBindingsElement.refract = createRefractor([ - 'visitors', - 'document', - 'objects', - 'ChannelBindings', - '$visitor', -]); -ChannelItemElement.refract = createRefractor([ - 'visitors', - 'document', - 'objects', - 'ChannelItem', - '$visitor', -]); -ChannelsElement.refract = createRefractor([ - 'visitors', - 'document', - 'objects', - 'Channels', - '$visitor', -]); -ComponentsElement.refract = createRefractor([ - 'visitors', - 'document', - 'objects', - 'Components', - '$visitor', -]); -ContactElement.refract = createRefractor([ - 'visitors', - 'document', - 'objects', - 'Contact', - '$visitor', -]); -IdentifierElement.refract = createRefractor([ - 'visitors', - 'document', - 'objects', - 'Identifier', - '$visitor', -]); -InfoElement.refract = createRefractor(['visitors', 'document', 'objects', 'Info', '$visitor']); -LicenseElement.refract = createRefractor([ - 'visitors', - 'document', - 'objects', - 'License', - '$visitor', -]); -OperationElement.refract = createRefractor([ - 'visitors', - 'document', - 'objects', - 'Operation', - '$visitor', -]); -ParameterElement.refract = createRefractor([ - 'visitors', - 'document', - 'objects', - 'Parameter', - '$visitor', -]); -ParametersElement.refract = createRefractor([ - 'visitors', - 'document', - 'objects', - 'Parameters', - '$visitor', -]); -ReferenceElement.refract = createRefractor([ - 'visitors', - 'document', - 'objects', - 'Reference', - '$visitor', -]); -SchemaElement.refract = createRefractor(['visitors', 'document', 'objects', 'Schema', '$visitor']); -SecurityRequirementElement.refract = createRefractor([ - 'visitors', - 'document', - 'objects', - 'SecurityRequirement', - '$visitor', -]); -ServerElement.refract = createRefractor(['visitors', 'document', 'objects', 'Server', '$visitor']); -ServerBindingsElement.refract = createRefractor([ - 'visitors', - 'document', - 'objects', - 'ServerBindings', - '$visitor', -]); -ServersElement.refract = createRefractor([ - 'visitors', - 'document', - 'objects', - 'Servers', - '$visitor', -]); -ServerVariableElement.refract = createRefractor([ - 'visitors', - 'document', - 'objects', - 'ServerVariable', - '$visitor', -]); const asyncApi2_0 = { namespace: (options: NamespacePluginOptions) => { diff --git a/apidom/packages/apidom-ns-asyncapi-2-0/src/refractor/index.ts b/apidom/packages/apidom-ns-asyncapi-2-0/src/refractor/index.ts index c86ce120e5..a9bbf6d42d 100644 --- a/apidom/packages/apidom-ns-asyncapi-2-0/src/refractor/index.ts +++ b/apidom/packages/apidom-ns-asyncapi-2-0/src/refractor/index.ts @@ -1,25 +1,49 @@ import { refract as baseRefract } from 'minim'; -import { Element, dereference } from 'apidom'; +import { Element, dereference, mergeAllVisitors, createNamespace } from 'apidom'; +import { propOr } from 'ramda'; import { invokeArgs } from 'ramda-adjunct'; import { visit } from '../traversal/visitor'; import specification from './specification'; +import * as predicates from '../predicates'; +import asyncApi2_0Namespace from '../namespace'; const refract = ( value: any, - { specPath = ['visitors', 'document', 'objects', 'AsyncApi', '$visitor'] } = {}, + { specPath = ['visitors', 'document', 'objects', 'AsyncApi', '$visitor'], plugins = [] } = {}, ): T => { const element = baseRefract(value); const resolvedSpec = dereference(specification); - const visitor = invokeArgs(specPath, [], resolvedSpec); + /** + * This is where generic ApiDOM becomes semantic (namespace applied). + * We don't allow consumers to hook into this translation. + * Though we allow consumers to define their onw plugins on already transformed ApiDOM. + */ + const rootVisitor = invokeArgs(specPath, [], resolvedSpec); // @ts-ignore - visit(element, visitor, { state: { specObj: resolvedSpec } }); + visit(element, rootVisitor, { state: { specObj: resolvedSpec } }); - return visitor.element; + /** + * Run plugins only when necessary. + * Running plugins visitors means extra single traversal. + * This can be optimized in future for performance. + */ + if (plugins.length > 0) { + const namespace = createNamespace(asyncApi2_0Namespace); + const toolbox = { predicates: { ...predicates }, namespace }; + const pluginsSpecs = plugins.map((plugin: any) => plugin(toolbox)); + const pluginsVisitor = mergeAllVisitors(pluginsSpecs.map(propOr({}, 'visitor'))); + pluginsSpecs.forEach(invokeArgs(['pre'], [])); + const newElement: any = visit(rootVisitor.element, pluginsVisitor); + pluginsSpecs.forEach(invokeArgs(['post'], [])); + return newElement; + } + + return rootVisitor.element; }; export const createRefractor = (specPath: string[]) => (value: any, options = {}) => - refract(value, { specPath, ...options }); + refract(value, { ...options, specPath }); export default refract; diff --git a/apidom/packages/apidom-ns-asyncapi-2-0/src/refractor/registration.ts b/apidom/packages/apidom-ns-asyncapi-2-0/src/refractor/registration.ts new file mode 100644 index 0000000000..4e23f721c3 --- /dev/null +++ b/apidom/packages/apidom-ns-asyncapi-2-0/src/refractor/registration.ts @@ -0,0 +1,145 @@ +import AsyncApi2_0Element from '../elements/AsyncApi2-0'; +import AsyncApiVersionElement from '../elements/AsyncApiVersion'; +import ChannelBindingsElement from '../elements/ChannelBindings'; +import ChannelItemElement from '../elements/ChannelItem'; +import ChannelsElement from '../elements/Channels'; +import ComponentsElement from '../elements/Components'; +import ContactElement from '../elements/Contact'; +import IdentifierElement from '../elements/Identifier'; +import InfoElement from '../elements/Info'; +import LicenseElement from '../elements/License'; +import OperationElement from '../elements/Operation'; +import ParameterElement from '../elements/Parameter'; +import ParametersElement from '../elements/Parameters'; +import ReferenceElement from '../elements/Reference'; +import SchemaElement from '../elements/Schema'; +import SecurityRequirementElement from '../elements/SecurityRequirement'; +import ServerElement from '../elements/Server'; +import ServerBindingsElement from '../elements/ServerBindings'; +import ServersElement from '../elements/Servers'; +import ServerVariableElement from '../elements/ServerVariable'; +import { createRefractor } from './index'; + +// register refractors specific to element types +AsyncApi2_0Element.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'AsyncApi', + '$visitor', +]); +AsyncApiVersionElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'AsyncApiVersion', + '$visitor', +]); +ChannelBindingsElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'ChannelBindings', + '$visitor', +]); +ChannelItemElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'ChannelItem', + '$visitor', +]); +ChannelsElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'Channels', + '$visitor', +]); +ComponentsElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'Components', + '$visitor', +]); +ContactElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'Contact', + '$visitor', +]); +IdentifierElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'Identifier', + '$visitor', +]); +InfoElement.refract = createRefractor(['visitors', 'document', 'objects', 'Info', '$visitor']); +LicenseElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'License', + '$visitor', +]); +OperationElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'Operation', + '$visitor', +]); +ParameterElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'Parameter', + '$visitor', +]); +ParametersElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'Parameters', + '$visitor', +]); +ReferenceElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'Reference', + '$visitor', +]); +SchemaElement.refract = createRefractor(['visitors', 'document', 'objects', 'Schema', '$visitor']); +SecurityRequirementElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'SecurityRequirement', + '$visitor', +]); +ServerElement.refract = createRefractor(['visitors', 'document', 'objects', 'Server', '$visitor']); +ServerBindingsElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'ServerBindings', + '$visitor', +]); +ServersElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'Servers', + '$visitor', +]); +ServerVariableElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'ServerVariable', + '$visitor', +]); diff --git a/apidom/packages/apidom-ns-asyncapi-2-0/src/traversal/visitor.ts b/apidom/packages/apidom-ns-asyncapi-2-0/src/traversal/visitor.ts index a169b730a8..e3d5e919a1 100644 --- a/apidom/packages/apidom-ns-asyncapi-2-0/src/traversal/visitor.ts +++ b/apidom/packages/apidom-ns-asyncapi-2-0/src/traversal/visitor.ts @@ -1,81 +1,34 @@ import { propOr } from 'ramda'; -import { - Element, - visit as astVisit, - keyMap as keyMapBase, - getNodeType as getNodeTypeBase, -} from 'apidom'; -import { - isServerElement, - isSchemaElement, - isServerVariableElement, - isParameterElement, - isLicenseElement, - isInfoElement, - isContactElement, - isComponentsElement, - isAsyncApiVersionElement, - isReferenceElement, - isAsyncApi2_0Element, - isChannelItemElement, - isChannelsElement, - isIdentifierElement, - isServersElement, -} from '../predicates'; +import { Element, visit as astVisit, keyMap as keyMapBase, BREAK } from 'apidom'; +import { isString } from 'ramda-adjunct'; -export { BREAK } from 'apidom'; +export { BREAK }; -export const getNodeType = (element: T): string | undefined => { - /* eslint-disable no-nested-ternary */ - return isServerElement(element) - ? 'Server' - : isSchemaElement(element) - ? 'Schema' - : isServerVariableElement(element) - ? 'ServerVariable' - : isParameterElement(element) - ? 'Parameter' - : isLicenseElement(element) - ? 'License' - : isInfoElement(element) - ? 'Info' - : isContactElement(element) - ? 'Contact' - : isComponentsElement(element) - ? 'Components' - : isAsyncApi2_0Element(element) - ? 'AsyncApi2_0' - : isAsyncApiVersionElement(element) - ? 'AsyncApiVersion' - : isChannelItemElement(element) - ? 'ChannelItem' - : isChannelsElement(element) - ? 'Channels' - : isIdentifierElement(element) - ? 'Identifier' - : isServersElement(element) - ? 'Servers' - : isReferenceElement(element) - ? 'Reference' - : getNodeTypeBase(element); - /* eslint-enable */ -}; +export const getNodeType = (element: Element) => + isString(element.element) + ? element.element.charAt(0).toUpperCase() + element.element.slice(1) + : undefined; export const keyMapDefault = { - ...keyMapBase, - Server: ['content'], - Schema: ['content'], - ServerVariable: ['content'], - Parameter: ['content'], - License: ['content'], - Info: ['content'], - Contact: ['content'], - Components: ['content'], AsyncApi2_0: ['content'], + ChannelBindings: ['content'], ChannelItem: ['content'], Channels: ['content'], - Servers: ['content'], + Components: ['content'], + Contact: ['content'], + Info: ['content'], + License: ['content'], + Operation: ['content'], + Parameter: ['content'], + Parameters: ['content'], Reference: ['content'], + Schema: ['content'], + SecurityRequirement: ['content'], + Server: ['content'], + ServerBinding: ['content'], + Servers: ['content'], + ServerVariable: ['content'], + ...keyMapBase, }; export const visit = ( diff --git a/apidom/packages/apidom-ns-asyncapi-2-0/test/refractor/index.ts b/apidom/packages/apidom-ns-asyncapi-2-0/test/refractor/index.ts index 01f2c930d3..1149c04aeb 100644 --- a/apidom/packages/apidom-ns-asyncapi-2-0/test/refractor/index.ts +++ b/apidom/packages/apidom-ns-asyncapi-2-0/test/refractor/index.ts @@ -1,23 +1,268 @@ -import { ObjectElement, toString, toValue } from 'apidom'; +import { assert } from 'chai'; +import sinon from 'sinon'; +import { Namespace } from 'minim'; +import { ObjectElement, toValue } from 'apidom'; -import { AsyncApi2_0Element } from '../../src'; +import * as predicates from '../../src/predicates'; +import { AsyncApi2_0Element, isAsyncApiVersionElement } from '../../src'; describe('refractor', function () { specify('should refract to openapi-3-1 namespace', function () { const genericObject = new ObjectElement({ asyncapi: '2.0.0', - channels: { - 'user/signedup': { - description: 'channel item description', - subscribe: { - summary: 'operation summary', - description: 'operation description', + }); + const asyncApiElement = AsyncApi2_0Element.refract(genericObject); + + // console.log(toString(asyncApiElement)); + assert.deepEqual(toValue(asyncApiElement), { asyncapi: '2.0.0' }); + }); + + context('supports plugins', function () { + let plugin1Spec: any; + let plugin2Spec: any; + let plugin1: any; + let plugin2: any; + + beforeEach(function () { + plugin1Spec = { + pre() {}, + visitor: { + AsyncApiVersion(element: AsyncApi2_0Element) { + // @ts-ignore + element.content = '2.0.1'; // eslint-disable-line no-param-reassign + }, + }, + post() {}, + }; + plugin2Spec = { + pre() {}, + visitor: { + AsyncApiVersion(element: AsyncApi2_0Element) { + // @ts-ignore + element.meta.set('metaKey', 'metaValue'); }, }, - }, + post() {}, + }; + plugin1 = sinon.spy(() => plugin1Spec); + plugin2 = sinon.spy(() => plugin2Spec); + + sinon.spy(plugin1Spec, 'pre'); + sinon.spy(plugin1Spec, 'post'); + sinon.spy(plugin1Spec.visitor, 'AsyncApiVersion'); + + sinon.spy(plugin2Spec, 'pre'); + sinon.spy(plugin2Spec, 'post'); + sinon.spy(plugin2Spec.visitor, 'AsyncApiVersion'); + }); + + context('plugin', function () { + specify('should be called with toolbox object', function () { + const genericObject = new ObjectElement({ + asyncapi: '2.0.0', + }); + AsyncApi2_0Element.refract(genericObject, { + plugins: [plugin1], + }); + + assert.hasAllKeys(plugin1.firstCall.args[0], ['predicates', 'namespace']); + }); + + specify('should have predicates in toolbox object', function () { + const genericObject = new ObjectElement({ + asyncapi: '2.0.0', + }); + AsyncApi2_0Element.refract(genericObject, { + plugins: [plugin1], + }); + + assert.hasAllKeys(plugin1.firstCall.args[0].predicates, Object.keys(predicates)); + }); + + specify('should have namespace in toolbox object', function () { + const genericObject = new ObjectElement({ + asyncapi: '2.0.0', + }); + AsyncApi2_0Element.refract(genericObject, { + plugins: [plugin1], + }); + + assert.instanceOf(plugin1.firstCall.args[0].namespace, Namespace); + }); + }); + + context('pre hook', function () { + specify('should call it once', function () { + const genericObject = new ObjectElement({ + asyncapi: '2.0.0', + }); + AsyncApi2_0Element.refract(genericObject, { + plugins: [plugin1], + }); + + assert.isTrue(plugin1Spec.pre.calledOnce); + }); + + specify('should call it before other plugin pre hook', function () { + const genericObject = new ObjectElement({ + asyncapi: '2.0.0', + }); + AsyncApi2_0Element.refract(genericObject, { + plugins: [plugin1, plugin2], + }); + + assert.isTrue(plugin1Spec.pre.calledBefore(plugin2Spec.pre)); + }); + + specify('should call it before visiting', function () { + const genericObject = new ObjectElement({ + asyncapi: '2.0.0', + }); + AsyncApi2_0Element.refract(genericObject, { + plugins: [plugin1, plugin2], + }); + + assert.isTrue(plugin1Spec.pre.calledBefore(plugin1Spec.visitor.AsyncApiVersion)); + assert.isTrue(plugin1Spec.pre.calledBefore(plugin2Spec.visitor.AsyncApiVersion)); + }); + }); + + context('post hook', function () { + specify('should call it once', function () { + const genericObject = new ObjectElement({ + asyncapi: '2.0.0', + }); + AsyncApi2_0Element.refract(genericObject, { + plugins: [plugin1], + }); + + assert.isTrue(plugin1Spec.post.calledOnce); + }); + + specify('should call it before other plugin post hook', function () { + const genericObject = new ObjectElement({ + asyncapi: '2.0.0', + }); + AsyncApi2_0Element.refract(genericObject, { + plugins: [plugin1, plugin2], + }); + + assert.isTrue(plugin1Spec.post.calledBefore(plugin2Spec.post)); + }); + + specify('should call it after visiting', function () { + const genericObject = new ObjectElement({ + asyncapi: '2.0.0', + }); + AsyncApi2_0Element.refract(genericObject, { + plugins: [plugin1, plugin2], + }); + + assert.isTrue(plugin1Spec.post.calledAfter(plugin1Spec.visitor.AsyncApiVersion)); + assert.isTrue(plugin1Spec.post.calledAfter(plugin2Spec.visitor.AsyncApiVersion)); + }); + }); + + context('visitor', function () { + specify('should be called once', function () { + const genericObject = new ObjectElement({ + asyncapi: '2.0.0', + }); + AsyncApi2_0Element.refract(genericObject, { + plugins: [plugin1, plugin2], + }); + + assert.isTrue(plugin1Spec.visitor.AsyncApiVersion.calledOnce); + assert.isTrue(plugin2Spec.visitor.AsyncApiVersion.calledOnce); + }); + + specify('should be called in proper order', function () { + const genericObject = new ObjectElement({ + asyncapi: '2.0.0', + }); + AsyncApi2_0Element.refract(genericObject, { + plugins: [plugin1, plugin2], + }); + + assert.isTrue( + plugin1Spec.visitor.AsyncApiVersion.calledBefore(plugin2Spec.visitor.AsyncApiVersion), + ); + }); + + context('first plugin', function () { + specify('should receive arguments', function () { + const genericObject = new ObjectElement({ + asyncapi: '2.0.0', + }); + AsyncApi2_0Element.refract(genericObject, { + plugins: [plugin1], + }); + + assert.lengthOf(plugin1Spec.visitor.AsyncApiVersion.firstCall.args, 5); + }); + + specify('should receive node as first argument', function () { + const genericObject = new ObjectElement({ + asyncapi: '2.0.0', + }); + AsyncApi2_0Element.refract(genericObject, { + plugins: [plugin1], + }); + + assert.isTrue( + isAsyncApiVersionElement(plugin1Spec.visitor.AsyncApiVersion.firstCall.args[0]), + ); + }); + + specify('should augment asyncapi version', function () { + const genericObject = new ObjectElement({ + asyncapi: '2.0.0', + }); + const asyncApiElement = AsyncApi2_0Element.refract(genericObject, { + plugins: [plugin1], + }); + + assert.deepEqual(toValue(asyncApiElement), { asyncapi: '2.0.1' }); + }); + }); + + context('second plugin', function () { + specify('should receive arguments', function () { + const genericObject = new ObjectElement({ + asyncapi: '2.0.0', + }); + AsyncApi2_0Element.refract(genericObject, { + plugins: [plugin1, plugin2], + }); + + assert.lengthOf(plugin2Spec.visitor.AsyncApiVersion.firstCall.args, 5); + }); + + specify('should receive node as first argument', function () { + const genericObject = new ObjectElement({ + asyncapi: '2.0.0', + }); + AsyncApi2_0Element.refract(genericObject, { + plugins: [plugin1, plugin2], + }); + + assert.isTrue( + isAsyncApiVersionElement(plugin2Spec.visitor.AsyncApiVersion.firstCall.args[0]), + ); + }); + + specify('should append metadata to asyncapi version', function () { + const genericObject = new ObjectElement({ + asyncapi: '2.0.0', + }); + const asyncApiElement = AsyncApi2_0Element.refract(genericObject, { + plugins: [plugin1, plugin2], + }); + + // @ts-ignore + assert.strictEqual(asyncApiElement.asyncapi.meta.get('metaKey').toValue(), 'metaValue'); + }); + }); }); - const openApiObject = AsyncApi2_0Element.refract(genericObject); - console.log(toString(openApiObject)); - console.dir(toValue(openApiObject)); }); }); diff --git a/apidom/packages/apidom/src/index.ts b/apidom/packages/apidom/src/index.ts index ac27b5e0ca..a56732d5f8 100644 --- a/apidom/packages/apidom/src/index.ts +++ b/apidom/packages/apidom/src/index.ts @@ -46,7 +46,13 @@ export { export { default as createPredicate } from './predicates/helpers'; export { filter, reject, find, findAtOffset, some, traverse } from './traversal'; -export { visit, BREAK, getNodeType, keyMapDefault as keyMap } from './traversal/visitor'; +export { + visit, + BREAK, + mergeAllVisitors, + getNodeType, + keyMapDefault as keyMap, +} from './traversal/visitor'; export { transclude, default as Transcluder } from './transcluder'; export { dereference } from './util'; diff --git a/apidom/packages/apidom/src/traversal/visitor.ts b/apidom/packages/apidom/src/traversal/visitor.ts index 8f5d30db0f..b4a0b522ce 100644 --- a/apidom/packages/apidom/src/traversal/visitor.ts +++ b/apidom/packages/apidom/src/traversal/visitor.ts @@ -2,20 +2,11 @@ import stampit from 'stampit'; import { Element } from 'minim'; import { curryN, F as stubFalse, pipe, propOr } from 'ramda'; import { isString } from 'ramda-adjunct'; -import { visit as astVisit } from 'apidom-ast'; +import { visit as astVisit, BREAK, mergeAllVisitors } from 'apidom-ast'; -import { - isArrayElement, - isBooleanElement, - isMemberElement, - isNullElement, - isNumberElement, - isObjectElement, - isStringElement, - isParseResultElement, -} from '../predicates'; +import { isArrayElement, isMemberElement, isObjectElement } from '../predicates'; -export { BREAK } from 'apidom-ast'; +export { BREAK, mergeAllVisitors }; // getNodeType :: Node -> String export const getNodeType = (element: T): string | undefined => { @@ -24,31 +15,22 @@ export const getNodeType = (element: T): string | undefined = * This allows us keep key mapping to minimum. */ /* eslint-disable no-nested-ternary */ - return isParseResultElement(element) - ? 'ParseResult' - : isObjectElement(element) + return isObjectElement(element) ? 'Object' : isArrayElement(element) ? 'Array' - : isNumberElement(element) - ? 'Number' - : isNullElement(element) - ? 'Null' - : isBooleanElement(element) - ? 'Boolean' : isMemberElement(element) ? 'Member' - : isStringElement(element) - ? 'String' + : isString(element?.element) + ? element.element.charAt(0).toUpperCase() + element.element.slice(1) : undefined; - /* eslint-enable */ + /* eslint-disable no-nested-ternary */ }; // isNode :: Node -> Boolean const isNode = curryN(1, pipe(getNodeType, isString)); export const keyMapDefault = { - ParseResult: ['content'], Object: ['content'], Array: ['content'], Member: ['key', 'value'], diff --git a/apidom/packages/apidom/test/traversal/visitor.ts b/apidom/packages/apidom/test/traversal/visitor.ts index 888340d14a..00a166f5aa 100644 --- a/apidom/packages/apidom/test/traversal/visitor.ts +++ b/apidom/packages/apidom/test/traversal/visitor.ts @@ -11,6 +11,19 @@ import { describe('traversal', function () { context('visitor', function () { + specify('should replace root', function () { + const objectElement = new ObjectElement({ key1: 'value1', key2: 'value2' }); + const replacement = new ObjectElement({ prop: 'val' }); + const visitor = { + Object() { + return replacement; + }, + }; + const newRoot = visit(objectElement, visitor); + + assert.strictEqual(newRoot, replacement); + }); + specify('should replace MemberElement in ObjectElement', function () { const objectElement = new ObjectElement({ key1: 'value1', key2: 'value2' }); const visitor = { @@ -24,7 +37,7 @@ describe('traversal', function () { }; const newObjectElement = visit(objectElement, visitor); - assert.deepEqual({ key3: 'value3', key2: 'value2' }, toValue(newObjectElement)); + assert.deepEqual(toValue(newObjectElement), { key3: 'value3', key2: 'value2' }); }); specify('should remove MemberElement from ObjectElement', function () { @@ -40,7 +53,7 @@ describe('traversal', function () { }; const newObjectElement = visit(objectElement, visitor); - assert.deepEqual({ key2: 'value2' }, toValue(newObjectElement)); + assert.deepEqual(toValue(newObjectElement), { key2: 'value2' }); }); specify('should replace value in MemberElement', function () { @@ -55,7 +68,7 @@ describe('traversal', function () { }; const newObjectElement = visit(objectElement, visitor); - assert.deepEqual({ key: 'replace' }, toValue(newObjectElement)); + assert.deepEqual(toValue(newObjectElement), { key: 'replace' }); }); specify('should replace item in ArrayElement', function () { @@ -67,7 +80,7 @@ describe('traversal', function () { }; const newArrayElement = visit(arrayElement, visitor); - assert.deepEqual([1, 'replace'], toValue(newArrayElement)); + assert.deepEqual(toValue(newArrayElement), [1, 'replace']); }); specify('should remove item from ArrayElement', function () { @@ -79,7 +92,7 @@ describe('traversal', function () { }; const newArrayElement = visit(arrayElement, visitor); - assert.deepEqual([1], toValue(newArrayElement)); + assert.deepEqual(toValue(newArrayElement), [1]); }); }); });