diff --git a/apidom/package-lock.json b/apidom/package-lock.json index d1f2133e1f..205a116130 100644 --- a/apidom/package-lock.json +++ b/apidom/package-lock.json @@ -5423,6 +5423,38 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, + "copyfiles": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.3.0.tgz", + "integrity": "sha512-73v7KFuDFJ/ofkQjZBMjMBFWGgkS76DzXvBMUh7djsMOE5EELWtAO/hRB6Wr5Vj5Zg+YozvoHemv0vnXpqxmOQ==", + "dev": true, + "requires": { + "glob": "^7.0.5", + "minimatch": "^3.0.3", + "mkdirp": "^1.0.4", + "noms": "0.0.0", + "through2": "^2.0.1", + "yargs": "^15.3.1" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, "core-js": { "version": "3.6.5", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", @@ -10525,6 +10557,42 @@ "integrity": "sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==", "dev": true }, + "noms": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", + "integrity": "sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "~1.0.31" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, "noop-logger": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", diff --git a/apidom/package.json b/apidom/package.json index 06d9f81d96..515a0d9d02 100644 --- a/apidom/package.json +++ b/apidom/package.json @@ -8,12 +8,12 @@ }, "comments": { "scripts": { - "prebuild": "We have prebuild script to download and initialize the docker image used to build WASM fragments" + "prebuild": "We build WASM here once and copy into specific packages to avoid multiple WASM build" } }, "scripts": { "postinstall": "npm run link-parent-bin", - "prebuild": "tree-sitter build-wasm ./node_modules/tree-sitter-json && rimraf tree-sitter-json.wasm", + "prebuild": "tree-sitter build-wasm ./node_modules/tree-sitter-json && copyfiles tree-sitter-json.wasm packages/apidom-parser-adapter-asyncapi2-0-json && copyfiles tree-sitter-json.wasm packages/apidom-parser-adapter-openapi3-1-json", "build": "lerna run build", "lint": "lerna run lint", "lint:fix": "lerna run lint", @@ -55,6 +55,7 @@ "@typescript-eslint/parser": "=3.0.2", "babel-loader": "=8.1.0", "chai": "=4.2.0", + "copyfiles": "=2.3.0", "core-js": "=3.6.5", "cross-env": "=7.0.2", "eslint": "=7.1.0", diff --git a/apidom/packages/apidom-ns-asyncapi2-0/src/elements/AsyncApi2-0.ts b/apidom/packages/apidom-ns-asyncapi2-0/src/elements/AsyncApi2-0.ts index 06c828383f..eec6aedb1c 100644 --- a/apidom/packages/apidom-ns-asyncapi2-0/src/elements/AsyncApi2-0.ts +++ b/apidom/packages/apidom-ns-asyncapi2-0/src/elements/AsyncApi2-0.ts @@ -4,7 +4,7 @@ import IdentifierElement from './Identifier'; import ComponentsElement from './Components'; import InfoElement from './Info'; -class OpenApi3_1 extends ObjectElement { +class AsyncApi2_0 extends ObjectElement { constructor(content: Array, meta: Meta, attributes: Attributes) { super(content, meta, attributes); this.element = 'asyncApi2-0'; @@ -12,7 +12,7 @@ class OpenApi3_1 extends ObjectElement { } get asyncapi(): AsyncapiElement { - return this.get('openapi'); + return this.get('asyncapi'); } get id(): IdentifierElement { @@ -28,4 +28,4 @@ class OpenApi3_1 extends ObjectElement { } } -export default OpenApi3_1; +export default AsyncApi2_0; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/package.json b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/package.json index 858dce83a9..ea90837508 100644 --- a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/package.json +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/package.json @@ -12,10 +12,10 @@ "build:es": "cross-env BABEL_ENV=es babel src --out-dir es --extensions '.ts' --root-mode 'upward'", "build:commonjs": "cross-env BABEL_ENV=commonjs babel src --out-dir lib --extensions '.ts' --root-mode 'upward'", "build:umd:browser": "npm run build:wasm && cross-env BABEL_ENV=browser webpack --config config/webpack/browser.config.js --progress", - "build:wasm": "tree-sitter build-wasm ../../node_modules/tree-sitter-json", + "build:wasm": "node ./scripts/file-exists.js tree-sitter-json.wasm && exit 0 || tree-sitter build-wasm ../../node_modules/tree-sitter-json", "lint": "eslint ./", "lint:fix": "eslint ./ --fix", - "clean": "rimraf ./es ./lib ./dist", + "clean": "rimraf ./es ./lib ./dist tree-sitter-json.wasm", "test": "cross-env BABEL_ENV=commonjs mocha", "security-audit": "run-s -sc security-audit:prod security-audit:dev", "security-audit:prod": "npm-audit --production --only=prod --audit-level=low", diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/scripts/file-exists.js b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/scripts/file-exists.js new file mode 100644 index 0000000000..3effd19a80 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/scripts/file-exists.js @@ -0,0 +1,10 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const cwd = process.cwd(); + +if (!fs.existsSync(path.join(cwd, process.argv[2]))) { + process.exit(1); +} diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/adapter-browser.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/adapter-browser.ts index e69de29bb2..912790e209 100644 --- a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/adapter-browser.ts +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/adapter-browser.ts @@ -0,0 +1,2 @@ +export { default as parse, namespace } from './parser/index-browser'; +export { mediaTypes, detect } from './adapter'; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/adapter-node.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/adapter-node.ts index e69de29bb2..a88c94f9ed 100644 --- a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/adapter-node.ts +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/adapter-node.ts @@ -0,0 +1,2 @@ +export { default as parse, namespace } from './parser/index-node'; +export { mediaTypes, detect } from './adapter'; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/adapter.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/adapter.ts new file mode 100644 index 0000000000..13bbae7a4f --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/adapter.ts @@ -0,0 +1,8 @@ +export const mediaTypes = [ + 'application/vnd.aai.asyncapi;version=2.0.0', + 'application/vnd.aai.asyncapi+json;version=2.0.0', + 'application/vnd.aai.asyncapi+yaml;version=2.0.0', +]; + +export const detect = (source: string): boolean => + !!source.match(/(["']?)asyncapi\1\s*:\s*(["']?)2\.\d+\.\d+\2/g); diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/index-browser-patch.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/index-browser-patch.ts new file mode 100644 index 0000000000..7eec50c4d0 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/index-browser-patch.ts @@ -0,0 +1,15 @@ +import { tail } from 'ramda'; +import { isString } from 'ramda-adjunct'; +// @ts-ignore +import treeSitterWasm from 'web-tree-sitter/tree-sitter.wasm'; + +// patch fetch() to let emscripten load the WASM file +const realFetch = window.fetch; +window.fetch = (...args) => { + // @ts-ignore + if (isString(args[0]) && args[0].endsWith('/tree-sitter.wasm')) { + // @ts-ignore + return realFetch.apply(window, [treeSitterWasm, tail(args)]); + } + return realFetch.apply(window, args); +}; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/index-browser.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/index-browser.ts new file mode 100644 index 0000000000..fa9de5c13b --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/index-browser.ts @@ -0,0 +1,29 @@ +import './index-browser-patch'; + +import Parser from 'web-tree-sitter'; +import * as apiDOM from 'apidom'; + +import parse from '.'; +// @ts-ignore +import treeSitterJson from '../../tree-sitter-json.wasm'; + +export { namespace } from './index'; + +const parserP = (async () => { + await Parser.init(); + const JsonLanguage = await Parser.Language.load(treeSitterJson); + const parser = new Parser(); + parser.setLanguage(JsonLanguage); + return parser; +})(); + +const parseBrowser = async ( + source: string, + options: Record = {}, +): Promise => { + const parser = await parserP; + // @ts-ignore + return parse(source, { ...options, parser }); +}; + +export default parseBrowser; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/index-node.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/index-node.ts new file mode 100644 index 0000000000..13a3b00b98 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/index-node.ts @@ -0,0 +1,21 @@ +import Parser from 'tree-sitter'; +// @ts-ignore +import JSONLanguage from 'tree-sitter-json'; +import * as apiDOM from 'apidom'; + +import parse from '.'; + +export { namespace } from './index'; + +const parseNode = async ( + source: string, + options: Record = {}, +): Promise => { + const parser = new Parser(); + parser.setLanguage(JSONLanguage); + + // @ts-ignore + return parse(source, { ...options, parser }); +}; + +export default parseNode; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/index.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/index.ts new file mode 100644 index 0000000000..aaa5e3e581 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/index.ts @@ -0,0 +1,58 @@ +import $RefParser from '@apidevtools/json-schema-ref-parser'; +import { createNamespace, ParseResultElement } from 'apidom'; +import { + Error, + JsonArray, + JsonDocument, + JsonObject, + JsonProperty, + transformTreeSitterJsonCST, +} from 'apidom-ast'; +import asyncapi2_0 from 'apidom-ns-asyncapi2-0'; +import specification from './specification'; +import { visit } from './visitors'; + +export const namespace = createNamespace(asyncapi2_0); + +const parse = async ( + source: string, + { sourceMap = false, specObj = specification, parser = null } = {}, +): Promise => { + const resolvedSpecObj = await $RefParser.dereference(specObj); + // @ts-ignore + const parseResultElement = new namespace.elements.ParseResult(); + // @ts-ignore + const documentVisitor = resolvedSpecObj.visitors.document.$visitor(); + + // @ts-ignore + const cst = parser.parse(source); + const ast = transformTreeSitterJsonCST(cst); + + const keyMap = { + // @ts-ignore + [JsonDocument.type]: ['children'], + // @ts-ignore + [JsonObject.type]: ['children'], + // @ts-ignore + [JsonProperty.type]: ['children'], + // @ts-ignore + [JsonArray.type]: ['children'], + // @ts-ignore + [Error.type]: ['children'], + }; + + visit(ast.rootNode, documentVisitor, { + keyMap, + // @ts-ignore + state: { + namespace, + specObj: resolvedSpecObj, + sourceMap, + element: parseResultElement, + }, + }); + + return parseResultElement; +}; + +export default parse; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/predicates.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/predicates.ts new file mode 100644 index 0000000000..4322188c40 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/predicates.ts @@ -0,0 +1,7 @@ +import { pathEq, pathSatisfies, startsWith, both, curry } from 'ramda'; + +// isAsyncApiExtension :: (Options, PropertyNode) -> Boolean +// eslint-disable-next-line import/prefer-default-export +export const isAsyncApiExtension = curry((options, node) => + both(pathEq(['type'], 'property'), pathSatisfies(startsWith('x-'), ['key', 'value']))(node), +); diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/source-map.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/source-map.ts new file mode 100644 index 0000000000..910a0f5171 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/source-map.ts @@ -0,0 +1,16 @@ +import { namespace } from 'apidom'; + +/* eslint-disable import/prefer-default-export */ + +// @ts-ignore +export const addSourceMap = (node, element) => { + // @ts-ignore + const sourceMap = new namespace.elements.SourceMap(); + + sourceMap.position = node.position; + sourceMap.astNode = node; + + element.meta.set('sourceMap', sourceMap); + + return element; +}; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/specification.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/specification.ts new file mode 100644 index 0000000000..179a4c675b --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/specification.ts @@ -0,0 +1,101 @@ +import DocumentVisitor from './visitors/DocumentVisitor'; +import AsyncApi2_0Visitor from './visitors/async-api-2-0'; +import SpecificationExtensionVisitor from './visitors/SpecificationExtensionVisitor'; +import AsyncapiVisitor from './visitors/async-api-2-0/AsyncapiVisitor'; +import IdentifierVisitor from './visitors/async-api-2-0/IdentifierVisitor'; +import InfoVisitor from './visitors/async-api-2-0/info'; +import InfoTitleVisitor from './visitors/async-api-2-0/info/TitleVisitor'; +import InfoDescriptionVisitor from './visitors/async-api-2-0/info/DescriptionVisitor'; +import InfoSummaryVisitor from './visitors/async-api-2-0/info/SummaryVisitor'; +import InfoTermsOfServiceVisitor from './visitors/async-api-2-0/info/TermsOfServiceVisitor'; +import InfoVersionVisitor from './visitors/async-api-2-0/info/VersionVisitor'; +import ContactVisitor from './visitors/async-api-2-0/contact'; +import ContactNameVisitor from './visitors/async-api-2-0/contact/NameVisitor'; +import ContactUrlVisitor from './visitors/async-api-2-0/contact/UrlVisitor'; +import ContactEmailVisitor from './visitors/async-api-2-0/contact/EmailVisitor'; +import LicenseVisitor from './visitors/async-api-2-0/license'; +import LicenseNameVisitor from './visitors/async-api-2-0/license/NameVisitor'; +import LicenseUrlVisitor from './visitors/async-api-2-0/license/UrlVisitor'; +import ErrorVisitor from './visitors/ErrorVisitor'; +import { ValueVisitor, ObjectVisitor, ArrayVisitor } from './visitors/generics'; +import SchemaVisitor from './visitors/async-api-2-0/SchemaVisitor'; +import ComponentsVisitor from './visitors/async-api-2-0/components'; +import SchemasVisitor from './visitors/async-api-2-0/components/SchemasVisitor'; + +/** + * Specification object allows us to have complete control over visitors + * when traversing the AST. + * Specification also allows us to create new parser adapters from + * existing ones by manipulating it. + * + * Note: Specification object allows to use relative JSON pointers. + */ +const specification = { + visitors: { + value: ValueVisitor, + object: ObjectVisitor, + array: ArrayVisitor, + error: ErrorVisitor, + document: { + $visitor: DocumentVisitor, + objects: { + AsyncApi: { + $visitor: AsyncApi2_0Visitor, + fields: { + asyncapi: AsyncapiVisitor, + id: IdentifierVisitor, + info: { + $ref: '#/visitors/document/objects/Info', + }, + components: { + $ref: '#/visitors/document/objects/Components', + }, + }, + }, + Info: { + $visitor: InfoVisitor, + fields: { + title: InfoTitleVisitor, + description: InfoDescriptionVisitor, + summary: InfoSummaryVisitor, + termsOfService: InfoTermsOfServiceVisitor, + version: InfoVersionVisitor, + contact: { + $ref: '#/visitors/document/objects/Contact', + }, + license: { + $ref: '#/visitors/document/objects/License', + }, + }, + }, + Contact: { + $visitor: ContactVisitor, + fields: { + name: ContactNameVisitor, + url: ContactUrlVisitor, + email: ContactEmailVisitor, + }, + }, + License: { + $visitor: LicenseVisitor, + fields: { + name: LicenseNameVisitor, + url: LicenseUrlVisitor, + }, + }, + Schema: { + $visitor: SchemaVisitor, + }, + Components: { + $visitor: ComponentsVisitor, + fields: { + schemas: SchemasVisitor, + }, + }, + }, + extension: SpecificationExtensionVisitor, + }, + }, +}; + +export default specification; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/DocumentVisitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/DocumentVisitor.ts new file mode 100644 index 0000000000..3750ff4ee3 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/DocumentVisitor.ts @@ -0,0 +1,29 @@ +import stampit from 'stampit'; +import { visit } from '.'; +import SpecificationVisitor from './SpecificationVisitor'; + +const DocumentVisitor = stampit(SpecificationVisitor, { + methods: { + literal(literalNode) { + if (literalNode.isMissing) { + const errorVisitor = this.retrieveVisitorInstance(['error']); + visit(literalNode, errorVisitor); + this.element.content.push(errorVisitor.element); + } + }, + + document(documentNode) { + const openApiVisitor = this.retrieveVisitorInstance(['document', 'objects', 'AsyncApi']); + visit(documentNode.child, openApiVisitor); + this.element.content.push(openApiVisitor.element); + }, + + error(errorNode) { + const errorVisitor = this.retrieveVisitorInstance(['error']); + visit(errorNode, errorVisitor); + this.element.content.push(errorVisitor.element); + }, + }, +}); + +export default DocumentVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/ErrorVisitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/ErrorVisitor.ts new file mode 100644 index 0000000000..8b81fecc46 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/ErrorVisitor.ts @@ -0,0 +1,35 @@ +import stampit from 'stampit'; +import { BREAK } from '.'; +import SpecificationVisitor from './SpecificationVisitor'; + +const ErrorVisitor = stampit(SpecificationVisitor, { + methods: { + literal(literalNode) { + if (literalNode.isMissing) { + const message = `(Missing ${literalNode.value})`; + this.element = new this.namespace.elements.Annotation(message); + + this.maybeAddSourceMap(literalNode, this.element); + + return BREAK; + } + + return undefined; + }, + + error(errorNode) { + const message = errorNode.isUnexpected + ? `(Unexpected ${errorNode.value})` + : `(Error ${errorNode.value})`; + + this.element = new this.namespace.elements.Annotation(message); + this.element.classes.push('error'); + + this.maybeAddSourceMap(errorNode, this.element); + + return BREAK; + }, + }, +}); + +export default ErrorVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/SpecificationExtensionVisitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/SpecificationExtensionVisitor.ts new file mode 100644 index 0000000000..cbf184b18a --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/SpecificationExtensionVisitor.ts @@ -0,0 +1,34 @@ +import stampit from 'stampit'; +import { ValueVisitor } from './generics'; +import SpecificationVisitor from './SpecificationVisitor'; +import { isAsyncApiExtension } from '../predicates'; +import { BREAK, visit } from '.'; + +const SpecificationExtensionVisitor = stampit(SpecificationVisitor, { + methods: { + property(propertyNode) { + const keyElement = new this.namespace.elements.String(propertyNode.key.value); + const { MemberElement } = this.namespace.elements.Element.prototype; + const state = { namespace: this.namespace, sourceMap: this.sourceMap, specObj: this.specObj }; + const valueVisitor = ValueVisitor(); + + // @ts-ignore + visit(propertyNode.value, valueVisitor, { state }); + + const memberElement = new MemberElement( + this.maybeAddSourceMap(propertyNode.key, keyElement), + this.maybeAddSourceMap(propertyNode.value, valueVisitor.element), + ); + + if (isAsyncApiExtension({}, propertyNode)) { + memberElement.classes.push('specificationExtension'); + } + + this.element = memberElement; + + return BREAK; + }, + }, +}); + +export default SpecificationExtensionVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/SpecificationVisitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/SpecificationVisitor.ts new file mode 100644 index 0000000000..83d3720899 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/SpecificationVisitor.ts @@ -0,0 +1,44 @@ +import stampit from 'stampit'; +import { pathSatisfies, path, pick } from 'ramda'; +import { isFunction } from 'ramda-adjunct'; +import Visitor from './Visitor'; +import { visit } from './index'; + +/** + * This is a base Type for every visitor that does + * internal look-ups to retrieve other child visitors. + */ +const SpecificationVisitor = stampit(Visitor, { + props: { + specObj: null, + }, + // @ts-ignore + init({ specObj = this.specObj }) { + this.specObj = specObj; + }, + methods: { + retrieveVisitor(specPath) { + if (pathSatisfies(isFunction, ['visitors', ...specPath], this.specObj)) { + return path(['visitors', ...specPath], this.specObj); + } + + return path(['visitors', ...specPath, '$visitor'], this.specObj); + }, + + retrieveVisitorInstance(specPath, options = {}) { + const passingOpts = pick(['namespace', 'sourceMap', 'specObj'], this); + + return this.retrieveVisitor(specPath)({ ...passingOpts, ...options }); + }, + + mapPropertyNodeToMemberElement(specPath, propertyNode) { + const visitor = this.retrieveVisitorInstance(specPath); + + visit(propertyNode, visitor); + + return visitor.element; + }, + }, +}); + +export default SpecificationVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/Visitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/Visitor.ts new file mode 100644 index 0000000000..d66900a13b --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/Visitor.ts @@ -0,0 +1,26 @@ +import stampit from 'stampit'; +import { addSourceMap } from '../source-map'; + +const Visitor = stampit({ + props: { + element: null, + namespace: null, + sourceMap: false, + }, + // @ts-ignore + init({ namespace = this.namespace, sourceMap = this.sourceMap } = {}) { + this.namespace = namespace; + this.sourceMap = sourceMap; + }, + methods: { + maybeAddSourceMap(node, element) { + if (!this.sourceMap) { + return element; + } + + return addSourceMap(node, element); + }, + }, +}); + +export default Visitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/AsyncapiVisitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/AsyncapiVisitor.ts new file mode 100644 index 0000000000..7c44ce9808 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/AsyncapiVisitor.ts @@ -0,0 +1,19 @@ +import stampit from 'stampit'; +import SpecificationVisitor from '../SpecificationVisitor'; + +const AsyncapiVisitor = stampit(SpecificationVisitor, { + methods: { + property(propertyNode) { + const keyElement = new this.namespace.elements.String(propertyNode.key.value); + const asyncapiElement = new this.namespace.elements.Asyncapi(propertyNode.value.value); + const { MemberElement } = this.namespace.elements.Element.prototype; + + this.element = new MemberElement( + this.maybeAddSourceMap(propertyNode.key, keyElement), + this.maybeAddSourceMap(propertyNode.value, asyncapiElement), + ); + }, + }, +}); + +export default AsyncapiVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/IdentifierVisitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/IdentifierVisitor.ts new file mode 100644 index 0000000000..bf76e30ee5 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/IdentifierVisitor.ts @@ -0,0 +1,19 @@ +import stampit from 'stampit'; +import SpecificationVisitor from '../SpecificationVisitor'; + +const IdentifierVisitor = stampit(SpecificationVisitor, { + methods: { + property(propertyNode) { + const keyElement = new this.namespace.elements.String(propertyNode.key.value); + const identifierElement = new this.namespace.elements.Identifier(propertyNode.value.value); + const { MemberElement } = this.namespace.elements.Element.prototype; + + this.element = new MemberElement( + this.maybeAddSourceMap(propertyNode.key, keyElement), + this.maybeAddSourceMap(propertyNode.value, identifierElement), + ); + }, + }, +}); + +export default IdentifierVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/SchemaVisitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/SchemaVisitor.ts new file mode 100644 index 0000000000..bc754526eb --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/SchemaVisitor.ts @@ -0,0 +1,35 @@ +import stampit from 'stampit'; +import { visit, BREAK } from '..'; +import SpecificationVisitor from '../SpecificationVisitor'; + +const SchemaVisitor = stampit(SpecificationVisitor, { + props: { + keyElement: null, + }, + methods: { + key(keyNode) { + this.keyElement = this.maybeAddSourceMap( + keyNode, + new this.namespace.elements.String(keyNode.value), + ); + }, + + object(objectNode) { + const { MemberElement } = this.namespace.elements.Element.prototype; + const objectVisitor = this.retrieveVisitorInstance(['object']); + + visit(objectNode, objectVisitor); + + const schemaElement = new this.namespace.elements.Schema(objectVisitor.element.content); + + this.element = new MemberElement( + this.keyElement, + this.maybeAddSourceMap(objectNode, schemaElement), + ); + + return BREAK; + }, + }, +}); + +export default SchemaVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/components/SchemasVisitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/components/SchemasVisitor.ts new file mode 100644 index 0000000000..3bfc40d53d --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/components/SchemasVisitor.ts @@ -0,0 +1,40 @@ +import stampit from 'stampit'; +import SpecificationVisitor from '../../SpecificationVisitor'; +import { BREAK } from '../..'; + +const SchemasVisitor = stampit(SpecificationVisitor, { + props: { + keyElement: null, + }, + methods: { + key(keyNode) { + this.keyElement = this.maybeAddSourceMap( + keyNode, + new this.namespace.elements.String('schemas'), + ); + }, + + object(objectNode) { + const schemasElement = new this.namespace.elements.Object(); + const { MemberElement } = this.namespace.elements.Element.prototype; + + // @ts-ignore + objectNode.properties.forEach((propertyNode) => { + schemasElement.content.push( + this.mapPropertyNodeToMemberElement(['document', 'objects', 'Schema'], propertyNode), + ); + }); + + schemasElement.classes.push('schemas'); + + this.element = new MemberElement( + this.keyElement, + this.maybeAddSourceMap(objectNode, schemasElement), + ); + + return BREAK; + }, + }, +}); + +export default SchemasVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/components/index.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/components/index.ts new file mode 100644 index 0000000000..e7a99bd1ed --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/components/index.ts @@ -0,0 +1,49 @@ +import stampit from 'stampit'; +import { BREAK } from '../..'; +import SpecificationVisitor from '../../SpecificationVisitor'; +import { isAsyncApiExtension } from '../../../predicates'; + +const ComponentsVisitor = stampit(SpecificationVisitor, { + props: { + keyElement: null, + }, + methods: { + key(keyNode) { + this.keyElement = this.maybeAddSourceMap( + keyNode, + new this.namespace.elements.String('components'), + ); + }, + + object(objectNode) { + const componentsElement = new this.namespace.elements.Components(); + const { MemberElement } = this.namespace.elements.Element.prototype; + const supportedProps = ['schemas']; + + // @ts-ignore + objectNode.properties.forEach((propertyNode) => { + if (supportedProps.includes(propertyNode.key.value)) { + componentsElement.content.push( + this.mapPropertyNodeToMemberElement( + ['document', 'objects', 'Components', 'fields', propertyNode.key.value], + propertyNode, + ), + ); + } else if (isAsyncApiExtension({}, propertyNode)) { + componentsElement.content.push( + this.mapPropertyNodeToMemberElement(['document', 'extension'], propertyNode), + ); + } + }); + + this.element = new MemberElement( + this.keyElement, + this.maybeAddSourceMap(objectNode, componentsElement), + ); + + return BREAK; + }, + }, +}); + +export default ComponentsVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/contact/EmailVisitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/contact/EmailVisitor.ts new file mode 100644 index 0000000000..85c88eced2 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/contact/EmailVisitor.ts @@ -0,0 +1,11 @@ +import stampit from 'stampit'; +import PropertyVisitor from '../../generics/property-visitor'; + +const EmailVisitor = stampit(PropertyVisitor, { + props: { + name: 'email', + type: 'String', + }, +}); + +export default EmailVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/contact/NameVisitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/contact/NameVisitor.ts new file mode 100644 index 0000000000..970f723988 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/contact/NameVisitor.ts @@ -0,0 +1,11 @@ +import stampit from 'stampit'; +import PropertyVisitor from '../../generics/property-visitor'; + +const NameVisitor = stampit(PropertyVisitor, { + props: { + name: 'name', + type: 'String', + }, +}); + +export default NameVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/contact/UrlVisitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/contact/UrlVisitor.ts new file mode 100644 index 0000000000..59d09efd39 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/contact/UrlVisitor.ts @@ -0,0 +1,11 @@ +import stampit from 'stampit'; +import PropertyVisitor from '../../generics/property-visitor'; + +const UrlVisitor = stampit(PropertyVisitor, { + props: { + name: 'url', + type: 'String', + }, +}); + +export default UrlVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/contact/index.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/contact/index.ts new file mode 100644 index 0000000000..4b9e9f472f --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/contact/index.ts @@ -0,0 +1,48 @@ +import stampit from 'stampit'; +import { BREAK } from '../..'; +import SpecificationVisitor from '../../SpecificationVisitor'; +import { isAsyncApiExtension } from '../../../predicates'; + +const ContactVisitor = stampit(SpecificationVisitor, { + props: { + keyElement: null, + }, + methods: { + key(keyNode) { + this.keyElement = this.maybeAddSourceMap( + keyNode, + new this.namespace.elements.String('contact'), + ); + }, + + object(objectNode) { + const contactElement = new this.namespace.elements.Contact(); + const { MemberElement } = this.namespace.elements.Element.prototype; + + // @ts-ignore + objectNode.properties.forEach((propertyNode) => { + if (['name', 'url', 'email'].includes(propertyNode.key.value)) { + contactElement.content.push( + this.mapPropertyNodeToMemberElement( + ['document', 'objects', 'Contact', 'fields', propertyNode.key.value], + propertyNode, + ), + ); + } else if (isAsyncApiExtension({}, propertyNode)) { + contactElement.content.push( + this.mapPropertyNodeToMemberElement(['document', 'extension'], propertyNode), + ); + } + }); + + this.element = new MemberElement( + this.keyElement, + this.maybeAddSourceMap(objectNode, contactElement), + ); + + return BREAK; + }, + }, +}); + +export default ContactVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/index.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/index.ts new file mode 100644 index 0000000000..124072c524 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/index.ts @@ -0,0 +1,37 @@ +import stampit from 'stampit'; +import { BREAK } from '..'; +import { isAsyncApiExtension } from '../../predicates'; +import SpecificationVisitor from '../SpecificationVisitor'; + +const AsyncApi2_0Visitor = stampit(SpecificationVisitor, { + methods: { + object(objectNode) { + const asyncApi2_0Element = new this.namespace.elements.AsyncApi2_0(); + + const supportedProps = ['asyncapi', 'id', 'info', 'components']; + + // @ts-ignore + objectNode.properties.forEach((propertyNode) => { + // @ts-ignore + if (supportedProps.includes(propertyNode.key.value)) { + asyncApi2_0Element.content.push( + this.mapPropertyNodeToMemberElement( + ['document', 'objects', 'AsyncApi', 'fields', propertyNode.key.value], + propertyNode, + ), + ); + } else if (isAsyncApiExtension({}, propertyNode)) { + asyncApi2_0Element.content.push( + this.mapPropertyNodeToMemberElement(['document', 'extension'], propertyNode), + ); + } + }); + + this.element = this.maybeAddSourceMap(objectNode, asyncApi2_0Element); + + return BREAK; + }, + }, +}); + +export default AsyncApi2_0Visitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/info/DescriptionVisitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/info/DescriptionVisitor.ts new file mode 100644 index 0000000000..e125a3d586 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/info/DescriptionVisitor.ts @@ -0,0 +1,11 @@ +import stampit from 'stampit'; +import PropertyVisitor from '../../generics/property-visitor'; + +const DescriptionVisitor = stampit(PropertyVisitor, { + props: { + name: 'description', + type: 'String', + }, +}); + +export default DescriptionVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/info/SummaryVisitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/info/SummaryVisitor.ts new file mode 100644 index 0000000000..92794e79a1 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/info/SummaryVisitor.ts @@ -0,0 +1,11 @@ +import stampit from 'stampit'; +import PropertyVisitor from '../../generics/property-visitor'; + +const DescriptionVisitor = stampit(PropertyVisitor, { + props: { + name: 'summary', + type: 'String', + }, +}); + +export default DescriptionVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/info/TermsOfServiceVisitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/info/TermsOfServiceVisitor.ts new file mode 100644 index 0000000000..2f2521b440 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/info/TermsOfServiceVisitor.ts @@ -0,0 +1,11 @@ +import stampit from 'stampit'; +import PropertyVisitor from '../../generics/property-visitor'; + +const TermsOfServiceVisitor = stampit(PropertyVisitor, { + props: { + name: 'termsOfService', + type: 'String', + }, +}); + +export default TermsOfServiceVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/info/TitleVisitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/info/TitleVisitor.ts new file mode 100644 index 0000000000..2f831857b5 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/info/TitleVisitor.ts @@ -0,0 +1,11 @@ +import stampit from 'stampit'; +import PropertyVisitor from '../../generics/property-visitor'; + +const TitleVisitor = stampit(PropertyVisitor, { + props: { + name: 'title', + type: 'String', + }, +}); + +export default TitleVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/info/VersionVisitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/info/VersionVisitor.ts new file mode 100644 index 0000000000..a478d80c5f --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/info/VersionVisitor.ts @@ -0,0 +1,11 @@ +import stampit from 'stampit'; +import PropertyVisitor from '../../generics/property-visitor'; + +const VersionVisitor = stampit(PropertyVisitor, { + props: { + name: 'version', + type: 'String', + }, +}); + +export default VersionVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/info/index.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/info/index.ts new file mode 100644 index 0000000000..8ee5debd47 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/info/index.ts @@ -0,0 +1,54 @@ +import stampit from 'stampit'; +import { BREAK } from '../..'; +import SpecificationVisitor from '../../SpecificationVisitor'; +import { isAsyncApiExtension } from '../../../predicates'; + +const InfoVisitor = stampit(SpecificationVisitor, { + props: { + keyElement: null, + }, + methods: { + key(keyNode) { + this.keyElement = this.maybeAddSourceMap(keyNode, new this.namespace.elements.String('info')); + }, + + object(objectNode) { + const infoElement = new this.namespace.elements.Info(); + const { MemberElement } = this.namespace.elements.Element.prototype; + const supportedProps = [ + 'title', + 'description', + 'summary', + 'termsOfService', + 'version', + 'contact', + 'license', + ]; + + // @ts-ignore + objectNode.properties.forEach((propertyNode) => { + if (supportedProps.includes(propertyNode.key.value)) { + infoElement.content.push( + this.mapPropertyNodeToMemberElement( + ['document', 'objects', 'Info', 'fields', propertyNode.key.value], + propertyNode, + ), + ); + } else if (isAsyncApiExtension({}, propertyNode)) { + infoElement.content.push( + this.mapPropertyNodeToMemberElement(['document', 'extension'], propertyNode), + ); + } + }); + + this.element = new MemberElement( + this.keyElement, + this.maybeAddSourceMap(objectNode, infoElement), + ); + + return BREAK; + }, + }, +}); + +export default InfoVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/license/NameVisitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/license/NameVisitor.ts new file mode 100644 index 0000000000..970f723988 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/license/NameVisitor.ts @@ -0,0 +1,11 @@ +import stampit from 'stampit'; +import PropertyVisitor from '../../generics/property-visitor'; + +const NameVisitor = stampit(PropertyVisitor, { + props: { + name: 'name', + type: 'String', + }, +}); + +export default NameVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/license/UrlVisitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/license/UrlVisitor.ts new file mode 100644 index 0000000000..59d09efd39 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/license/UrlVisitor.ts @@ -0,0 +1,11 @@ +import stampit from 'stampit'; +import PropertyVisitor from '../../generics/property-visitor'; + +const UrlVisitor = stampit(PropertyVisitor, { + props: { + name: 'url', + type: 'String', + }, +}); + +export default UrlVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/license/index.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/license/index.ts new file mode 100644 index 0000000000..cfef580bdb --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/async-api-2-0/license/index.ts @@ -0,0 +1,48 @@ +import stampit from 'stampit'; +import { BREAK } from '../..'; +import SpecificationVisitor from '../../SpecificationVisitor'; +import { isAsyncApiExtension } from '../../../predicates'; + +const LicenseVisitor = stampit(SpecificationVisitor, { + props: { + keyElement: null, + }, + methods: { + key(keyNode) { + this.keyElement = this.maybeAddSourceMap( + keyNode, + new this.namespace.elements.String('license'), + ); + }, + + object(objectNode) { + const licenseElement = new this.namespace.elements.License(); + const { MemberElement } = this.namespace.elements.Element.prototype; + + // @ts-ignore + objectNode.properties.forEach((propertyNode) => { + if (['name', 'url'].includes(propertyNode.key.value)) { + licenseElement.content.push( + this.mapPropertyNodeToMemberElement( + ['document', 'objects', 'License', 'fields', propertyNode.key.value], + propertyNode, + ), + ); + } else if (isAsyncApiExtension({}, propertyNode)) { + licenseElement.content.push( + this.mapPropertyNodeToMemberElement(['document', 'extension'], propertyNode), + ); + } + }); + + this.element = new MemberElement( + this.keyElement, + this.maybeAddSourceMap(objectNode, licenseElement), + ); + + return BREAK; + }, + }, +}); + +export default LicenseVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/generics/index.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/generics/index.ts new file mode 100644 index 0000000000..e8e25afbec --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/generics/index.ts @@ -0,0 +1,223 @@ +import stampit from 'stampit'; +import { last } from 'ramda'; +import { isNotNull } from 'ramda-adjunct'; +import { + JsonArray, + JsonFalse, + JsonNull, + JsonNumber, + JsonObject, + JsonProperty, + JsonString, + JsonTrue, +} from 'apidom-ast'; +import { visit, BREAK } from '..'; +import { isAsyncApiExtension } from '../../predicates'; +import SpecificationVisitor from '../SpecificationVisitor'; + +export const ArrayVisitor = stampit(SpecificationVisitor).init(function ArrayVisitor() { + // @ts-ignore + const stack = []; + + // @ts-ignore + this.object = function object(objectNode: JsonObject) { + // @ts-ignore + const arrayElement = last(stack); + const objectVisitor = this.retrieveVisitorInstance(['object']); + + visit(objectNode, objectVisitor); + + const { element: objectElement } = objectVisitor; + + arrayElement.push(this.maybeAddSourceMap(objectNode, objectElement)); + + return false; + }; + + this.string = function string(stringNode: JsonString) { + // @ts-ignore + const arrayElement = last(stack); + const valueVisitor = this.retrieveVisitorInstance(['value']); + + valueVisitor.string(stringNode); + arrayElement.push(valueVisitor.element); + }; + + this.number = function number(numberNode: JsonNumber) { + // @ts-ignore + const arrayElement = last(stack); + const valueVisitor = this.retrieveVisitorInstance(['value']); + + valueVisitor.number(numberNode); + arrayElement.push(valueVisitor.element); + }; + + this.true = function _true(trueNode: JsonTrue) { + // @ts-ignore + const arrayElement = last(stack); + const valueVisitor = this.retrieveVisitorInstance(['value']); + + valueVisitor.true(trueNode); + arrayElement.push(valueVisitor.element); + }; + + this.false = function _false(falseNode: JsonFalse) { + // @ts-ignore + const arrayElement = last(stack); + const valueVisitor = this.retrieveVisitorInstance(['value']); + + valueVisitor.false(falseNode); + arrayElement.push(valueVisitor.element); + }; + + this.null = function _null(nullNode: JsonNull) { + // @ts-ignore + const arrayElement = last(stack); + const valueVisitor = this.retrieveVisitorInstance(['value']); + + valueVisitor.null(nullNode); + arrayElement.push(valueVisitor.element); + }; + + this.array = { + enter: (arrayNode: JsonArray) => { + const arrayElement = this.maybeAddSourceMap(arrayNode, new this.namespace.elements.Array()); + + stack.push(arrayElement); + + if (isNotNull(this.element)) { + this.element.push(arrayElement); + } else { + this.element = arrayElement; + } + }, + leave: () => { + // @ts-ignore + this.element = stack.pop(); + }, + }; +}); + +export const ObjectVisitor = stampit(SpecificationVisitor).init(function ObjectVisitor() { + // @ts-ignore + const stack = []; + + this.property = function property(propertyNode: JsonProperty) { + // @ts-ignore + const objElement = last(stack); + const { MemberElement } = this.namespace.elements.Element.prototype; + const keyElement = new this.namespace.elements.String(propertyNode.key.value); + let valueElement; + + // object property value handling + // @ts-ignore + if (propertyNode.value.type === 'object') { + const objectVisitor = this.retrieveVisitorInstance(['object']); + + visit(propertyNode.value, objectVisitor); + + ({ element: valueElement } = objectVisitor); + // @ts-ignore + } else if (propertyNode.value.type === 'array') { + const arrayVisitor = this.retrieveVisitorInstance(['array']); + + visit(propertyNode.value, arrayVisitor); + + ({ element: valueElement } = arrayVisitor); + } else if (propertyNode.key.value === '$ref') { + // $ref property key special handling + // @ts-ignore + valueElement = new this.namespace.elements.Ref(propertyNode.value.value); + // @ts-ignore + valueElement.path = propertyNode.value.value; + } else if (!isAsyncApiExtension({}, propertyNode)) { + // @ts-ignore + valueElement = this.namespace.toElement(propertyNode.value.value); + } + + if (isAsyncApiExtension({}, propertyNode)) { + objElement.content.push( + this.mapPropertyNodeToMemberElement(['document', 'extension'], propertyNode), + ); + } else { + objElement.content.push( + new MemberElement( + this.maybeAddSourceMap(propertyNode.key, keyElement), + this.maybeAddSourceMap(propertyNode.value, valueElement), + ), + ); + } + + return false; + }; + + this.object = { + enter: (objectNode: JsonObject) => { + const objectElement = this.maybeAddSourceMap( + objectNode, + new this.namespace.elements.Object(), + ); + + // @ts-ignore + stack.push(objectElement); + }, + leave: () => { + // @ts-ignore + this.element = stack.pop(); + }, + }; +}); + +export const ValueVisitor = stampit(SpecificationVisitor, { + methods: { + array(arrayNode: JsonArray) { + const arrayVisitor = this.retrieveVisitorInstance(['array']); + + visit(arrayNode, arrayVisitor); + + this.element = arrayVisitor.element; + + return BREAK; + }, + + object(objectNode: JsonObject) { + const objectVisitor = this.retrieveVisitorInstance(['object']); + + visit(objectNode, objectVisitor); + + this.element = objectVisitor.element; + + return BREAK; + }, + + string(stringNode: JsonString) { + const stringElement = new this.namespace.elements.String(stringNode.value); + this.element = this.maybeAddSourceMap(stringNode, stringElement); + return BREAK; + }, + + number(numberNode: JsonNumber) { + const numberElement = new this.namespace.elements.Number(Number(numberNode.value)); + this.element = this.maybeAddSourceMap(numberNode, numberElement); + return BREAK; + }, + + true(trueNode: JsonTrue) { + const booleanElement = new this.namespace.elements.Boolean(trueNode.value); + this.element = this.maybeAddSourceMap(trueNode, booleanElement); + return BREAK; + }, + + false(falseNode: JsonFalse) { + const booleanElement = new this.namespace.elements.Boolean(falseNode.value); + this.element = this.maybeAddSourceMap(falseNode, booleanElement); + return BREAK; + }, + + null(nullNode: JsonNull) { + const nullElement = new this.namespace.elements.Null(nullNode.value); + this.element = this.maybeAddSourceMap(nullNode, nullElement); + return BREAK; + }, + }, +}); diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/generics/property-visitor.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/generics/property-visitor.ts new file mode 100644 index 0000000000..979203733a --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/generics/property-visitor.ts @@ -0,0 +1,31 @@ +import stampit from 'stampit'; +import { defaultTo } from 'ramda'; +import SpecificationVisitor from '../SpecificationVisitor'; +import { BREAK } from '..'; + +const PropertyVisitor = stampit(SpecificationVisitor, { + props: { + name: null, + type: null, + }, + methods: { + property(propertyNode) { + const name = defaultTo(propertyNode.key.value, this.name); + const keyElement = new this.namespace.elements.String(name); + const valueElement = + this.type === null + ? this.namespace.toElement(propertyNode.value.value) // type inference + : new this.namespace.elements[this.type](propertyNode.value.value); + const { MemberElement } = this.namespace.elements.Element.prototype; + + this.element = new MemberElement( + this.maybeAddSourceMap(propertyNode.key, keyElement), + this.maybeAddSourceMap(propertyNode.value, valueElement), + ); + + return BREAK; + }, + }, +}); + +export default PropertyVisitor; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/index.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/index.ts new file mode 100644 index 0000000000..05a7dbf38b --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/src/parser/visitors/index.ts @@ -0,0 +1,31 @@ +import { + JsonArray, + JsonDocument, + JsonObject, + JsonProperty, + Error, + visit as astVisit, +} from 'apidom-ast'; + +export { BREAK } from 'apidom-ast'; + +/* eslint-disable import/prefer-default-export */ + +const keyMapDefault = { + // @ts-ignore + [JsonDocument.type]: ['child'], + // @ts-ignore + [JsonObject.type]: ['properties'], + // @ts-ignore + [JsonProperty.type]: ['key', 'value'], + // @ts-ignore + [JsonArray.type]: ['items'], + // @ts-ignore + [Error.type]: ['children'], +}; + +// @ts-ignore +export const visit = (root, visitor, { keyMap = keyMapDefault, ...rest } = {}) => { + // @ts-ignore + return astVisit(root, visitor, { ...rest, keyMap }); +}; diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/test/fixtures/sample-api.json b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/test/fixtures/sample-api.json index 0967ef424b..81363b32cf 100644 --- a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/test/fixtures/sample-api.json +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/test/fixtures/sample-api.json @@ -1 +1,55 @@ -{} +{ + "asyncapi": "2.0.0", + "id": "urn:com:smartylighting:streetlights:server", + "info": { + "title": "AsyncAPI Sample App", + "description": "This is a sample server.", + "termsOfService": "http://asyncapi.org/terms/", + "contact": { + "name": "API Support", + "url": "http://www.asyncapi.org/support", + "email": "support@asyncapi.org" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0.1" + }, + "components": { + "x-extension": "value", + "schemas": { + "x-model": { + "type": "object", + "properties": { + "id": { + "type:": "integer" + } + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "profile": { + "$ref": "#/components/schemas/UserProfile" + } + } + }, + "UserProfile": { + "type": "object", + "properties": { + "email": { + "type": "string", + "x-nullable": true + } + } + } + } + } +} diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/test/index.ts b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/test/index.ts index e69de29bb2..0c2e161e8c 100644 --- a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/test/index.ts +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/test/index.ts @@ -0,0 +1,19 @@ +import fs from 'fs'; +import path from 'path'; +import * as apiDOM from 'apidom'; + +import * as adapter from '../src/adapter-node'; + +const spec = fs.readFileSync(path.join(__dirname, 'fixtures', 'sample-api.json')).toString(); +// const namespace = apiDOM.createNamespace(openapi3); + +describe('apidom-parser-adapter-asyncapi2-0-json', function () { + it('test', async function () { + console.log(adapter.detect(spec)); + console.log(adapter.mediaTypes); + + const parseResult = await adapter.parse(spec, { sourceMap: true }); + console.log(JSON.stringify(apiDOM.toValue(parseResult), null, 2)); + // console.log (JSON.stringify(apiDOM.toJSON(namespace, parseResult), null, null)); + }); +}); diff --git a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/tsconfig.json b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/tsconfig.json index 0576762373..4fdc42b860 100644 --- a/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/tsconfig.json +++ b/apidom/packages/apidom-parser-adapter-asyncapi2-0-json/tsconfig.json @@ -3,6 +3,6 @@ "include": [ "../@types/**/*.d.ts", "src/**/*" -// "test/**/*" + "test/**/*" ] } diff --git a/apidom/packages/apidom-parser-adapter-openapi3-1-json/package.json b/apidom/packages/apidom-parser-adapter-openapi3-1-json/package.json index 7700c45e58..1fa6423bd7 100644 --- a/apidom/packages/apidom-parser-adapter-openapi3-1-json/package.json +++ b/apidom/packages/apidom-parser-adapter-openapi3-1-json/package.json @@ -12,10 +12,10 @@ "build:es": "cross-env BABEL_ENV=es babel src --out-dir es --extensions '.ts' --root-mode 'upward'", "build:commonjs": "cross-env BABEL_ENV=commonjs babel src --out-dir lib --extensions '.ts' --root-mode 'upward'", "build:umd:browser": "npm run build:wasm && cross-env BABEL_ENV=browser webpack --config config/webpack/browser.config.js --progress", - "build:wasm": "tree-sitter build-wasm ../../node_modules/tree-sitter-json", + "build:wasm": "node ./scripts/file-exists.js tree-sitter-json.wasm && exit 0 || tree-sitter build-wasm ../../node_modules/tree-sitter-json", "lint": "eslint ./", "lint:fix": "eslint ./ --fix", - "clean": "rimraf ./es ./lib ./dist", + "clean": "rimraf ./es ./lib ./dist tree-sitter-json.wasm", "test": "cross-env BABEL_ENV=commonjs mocha", "security-audit": "run-s -sc security-audit:prod security-audit:dev", "security-audit:prod": "npm-audit --production --only=prod --audit-level=low", diff --git a/apidom/packages/apidom-parser-adapter-openapi3-1-json/scripts/file-exists.js b/apidom/packages/apidom-parser-adapter-openapi3-1-json/scripts/file-exists.js new file mode 100644 index 0000000000..3effd19a80 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-openapi3-1-json/scripts/file-exists.js @@ -0,0 +1,10 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const cwd = process.cwd(); + +if (!fs.existsSync(path.join(cwd, process.argv[2]))) { + process.exit(1); +} diff --git a/apidom/packages/apidom-parser-adapter-openapi3-1-json/src/adapter-browser.ts b/apidom/packages/apidom-parser-adapter-openapi3-1-json/src/adapter-browser.ts index 629df98e45..3d418c68d2 100644 --- a/apidom/packages/apidom-parser-adapter-openapi3-1-json/src/adapter-browser.ts +++ b/apidom/packages/apidom-parser-adapter-openapi3-1-json/src/adapter-browser.ts @@ -1,6 +1,2 @@ export { default as parse, namespace } from './parser/index-browser'; - -export const mediaTypes = ['application/vnd.oai.openapi', 'application/vnd.oai.openapi+json']; - -export const detect = (source: string): boolean => - !!source.match(/(["']?)openapi\1\s*:\s*(["']?)3\.\d+\.\d+\2/g); +export { detect, mediaTypes } from './adapter'; diff --git a/apidom/packages/apidom-parser-adapter-openapi3-1-json/src/adapter-node.ts b/apidom/packages/apidom-parser-adapter-openapi3-1-json/src/adapter-node.ts index 6b8cd29522..a88c94f9ed 100644 --- a/apidom/packages/apidom-parser-adapter-openapi3-1-json/src/adapter-node.ts +++ b/apidom/packages/apidom-parser-adapter-openapi3-1-json/src/adapter-node.ts @@ -1,6 +1,2 @@ export { default as parse, namespace } from './parser/index-node'; - -export const mediaTypes = ['application/vnd.oai.openapi', 'application/vnd.oai.openapi+json']; - -export const detect = (source: string): boolean => - !!source.match(/(["']?)openapi\1\s*:\s*(["']?)3\.\d+\.\d+\2/g); +export { mediaTypes, detect } from './adapter'; diff --git a/apidom/packages/apidom-parser-adapter-openapi3-1-json/src/adapter.ts b/apidom/packages/apidom-parser-adapter-openapi3-1-json/src/adapter.ts new file mode 100644 index 0000000000..b20d21d1a0 --- /dev/null +++ b/apidom/packages/apidom-parser-adapter-openapi3-1-json/src/adapter.ts @@ -0,0 +1,8 @@ +export const mediaTypes = [ + 'application/vnd.oai.openapi;version=3.1.0', + 'application/vnd.oai.openapi+json;version=3.1.0', + 'application/vnd.oai.openapi+yaml;version=3.1.0', +]; + +export const detect = (source: string): boolean => + !!source.match(/(["']?)openapi\1\s*:\s*(["']?)3\.\d+\.\d+\2/g); diff --git a/apidom/tree-sitter-json.wasm b/apidom/tree-sitter-json.wasm new file mode 100644 index 0000000000..7b4ae8b4e4 Binary files /dev/null and b/apidom/tree-sitter-json.wasm differ