Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions packages/apidom-ls/src/parser-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import * as openapi3_1AdapterJson from '@swagger-api/apidom-parser-adapter-opena
import * as openapi3_1AdapterYaml from '@swagger-api/apidom-parser-adapter-openapi-yaml-3-1';
import * as asyncapi2AdapterJson from '@swagger-api/apidom-parser-adapter-asyncapi-json-2';
import * as asyncapi2AdapterYaml from '@swagger-api/apidom-parser-adapter-asyncapi-yaml-2';
import * as asyncapi3AdapterYaml from '@swagger-api/apidom-parser-adapter-asyncapi-yaml-3';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add JSON parser adapter as well?

import * as adsAdapterJson from '@swagger-api/apidom-parser-adapter-api-design-systems-json';
import * as adsAdapterYaml from '@swagger-api/apidom-parser-adapter-api-design-systems-yaml';
import * as adapterJson from '@swagger-api/apidom-parser-adapter-json';
import * as adapterYaml from '@swagger-api/apidom-parser-adapter-yaml-1-2';
import { refractorPluginReplaceEmptyElement as refractorPluginReplaceEmptyElementAsyncAPI3 } from '@swagger-api/apidom-ns-asyncapi-3';
import { refractorPluginReplaceEmptyElement as refractorPluginReplaceEmptyElementAsyncAPI2 } from '@swagger-api/apidom-ns-asyncapi-2';
import { refractorPluginReplaceEmptyElement as refractorPluginReplaceEmptyElementOpenAPI2 } from '@swagger-api/apidom-ns-openapi-2';
import { refractorPluginReplaceEmptyElement as refractorPluginReplaceEmptyElementOpenAPI3_0 } from '@swagger-api/apidom-ns-openapi-3-0';
Expand Down Expand Up @@ -39,7 +41,11 @@ export async function parse(
const text: string = typeof textDocument === 'string' ? textDocument : textDocument.getText();
let result;
const contentLanguage = await findNamespace(text, defaultContentLanguage);
if (contentLanguage.namespace === 'asyncapi' && contentLanguage.format === 'JSON') {
if (
contentLanguage.namespace === 'asyncapi' &&
(contentLanguage.version?.startsWith('2.') || !contentLanguage.version) &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the first condition not be enough?

Suggested change
(contentLanguage.version?.startsWith('2.') || !contentLanguage.version) &&
contentLanguage.version?.startsWith('2.') &&

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfortunately there were tests failing without additional condition

contentLanguage.format === 'JSON'
) {
const options: Record<string, unknown> = {
sourceMap: true,
refractorOpts: {
Expand All @@ -48,7 +54,11 @@ export async function parse(
};

result = await asyncapi2AdapterJson.parse(text, options);
} else if (contentLanguage.namespace === 'asyncapi' && contentLanguage.format === 'YAML') {
} else if (
contentLanguage.namespace === 'asyncapi' &&
(contentLanguage.version?.startsWith('2.') || !contentLanguage.version) &&
contentLanguage.format === 'YAML'
) {
const options: Record<string, unknown> = {
sourceMap: true,
refractorOpts: {
Expand All @@ -60,6 +70,22 @@ export async function parse(
};

result = await asyncapi2AdapterYaml.parse(text, options);
} else if (
contentLanguage.namespace === 'asyncapi' &&
contentLanguage.version?.startsWith('3.') &&
contentLanguage.format === 'YAML'
) {
const options: Record<string, unknown> = {
sourceMap: true,
refractorOpts: {
plugins: [
registerPlugins && refractorPluginReplaceEmptyElementAsyncAPI3(),
...(refractorPlugins?.['asyncapi-3'] || []),
].filter(Boolean),
},
};

result = await asyncapi3AdapterYaml.parse(text, options);
} else if (
contentLanguage.namespace === 'openapi' &&
contentLanguage.version === '2.0' &&
Expand Down
4 changes: 3 additions & 1 deletion packages/apidom-ls/test/hover-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,9 @@ describe('apidom-ls-hover-provider', function () {
}
});

it('test hover full provider', async function () {
// TODO: Flaky test.
// eslint-disable-next-line mocha/no-skipped-tests
xit('test hover full provider', async function () {
const languageService: LanguageService = getLanguageService(contextFull);

try {
Expand Down
2 changes: 1 addition & 1 deletion packages/apidom-ns-asyncapi-2/src/predicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import ServerVariableElement from './elements/ServerVariable.ts';
export const isAsyncApi2Element = createPredicate(
({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => {
return (element: unknown): element is AsyncApi2Element =>
element instanceof AsyncApi2Element ||
(element instanceof AsyncApi2Element && element.constructor === AsyncApi2Element) ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that other predicates for the root element do not have this - is there some issue when it's not here?

Suggested change
(element instanceof AsyncApi2Element && element.constructor === AsyncApi2Element) ||
(element instanceof AsyncApi2Element ||

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, the method returns true for the AsyncApi3 as well which is unwanted.

(hasBasicElementProps(element) &&
isElementType('asyncApi2', element) &&
primitiveEq('object', element) &&
Expand Down
5 changes: 4 additions & 1 deletion packages/apidom-ns-asyncapi-3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
"registry": "https://registry.npmjs.org"
},
"type": "module",
"sideEffects": [],
"sideEffects": [
"./src/refractor/registration.mjs",
"./src/refractor/registration.cjs"
],
"main": "./src/index.cjs",
"exports": {
"types": "./types/apidom-ns-asyncapi-3.d.ts",
Expand Down
2 changes: 2 additions & 0 deletions packages/apidom-ns-asyncapi-3/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export {
isServerVariableElement,
} from './predicates.ts';

export { keyMap, getNodeType } from './traversal/visitor.ts';

export {
/**
* AsyncApi 3.0.0 specification elements.
Expand Down
4 changes: 2 additions & 2 deletions packages/apidom-ns-asyncapi-3/src/refractor/toolbox.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { createNamespace, isStringElement } from '@swagger-api/apidom-core';

import * as asyncApi3Predicates from '../predicates.ts';
import asyncApi2Namespace from '../namespace.ts';
import asyncApi3Namespace from '../namespace.ts';

const createToolbox = () => {
const namespace = createNamespace(asyncApi2Namespace);
const namespace = createNamespace(asyncApi3Namespace);
const predicates = { ...asyncApi3Predicates, isStringElement };

return { predicates, namespace };
Expand Down
2 changes: 2 additions & 0 deletions packages/apidom-reference/src/configuration/saturated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import OpenAPI2DereferenceStrategy from '../dereference/strategies/openapi-2/ind
import OpenAPI3_0DereferenceStrategy from '../dereference/strategies/openapi-3-0/index.ts';
import OpenAPI3_1DereferenceStrategy from '../dereference/strategies/openapi-3-1/index.ts';
import AsyncAPI2DereferenceStrategy from '../dereference/strategies/asyncapi-2/index.ts';
import AsyncAPI3DereferenceStrategy from '../dereference/strategies/asyncapi-3/index.ts';
import OpenAPI3_1BundleStrategy from '../bundle/strategies/openapi-3-1/index.ts';
import { options } from '../index.ts';

Expand Down Expand Up @@ -66,6 +67,7 @@ options.dereference.strategies = [
new OpenAPI3_0DereferenceStrategy(),
new OpenAPI3_1DereferenceStrategy(),
new AsyncAPI2DereferenceStrategy(),
new AsyncAPI3DereferenceStrategy(),
new ApiDOMDereferenceStrategy(),
];

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { createNamespace, visit, Element, cloneDeep } from '@swagger-api/apidom-core';
import asyncApi3Namespace, {
getNodeType,
isAsyncApi3Element,
keyMap,
mediaTypes,
} from '@swagger-api/apidom-ns-asyncapi-3';

import DereferenceStrategy, { DereferenceStrategyOptions } from '../DereferenceStrategy.ts';
import File from '../../../File.ts';
import Reference from '../../../Reference.ts';
import ReferenceSet from '../../../ReferenceSet.ts';
import AsyncAPI3DereferenceVisitor from './visitor.ts';
import type { ReferenceOptions } from '../../../options/index.ts';

export type {
default as DereferenceStrategy,
DereferenceStrategyOptions,
} from '../DereferenceStrategy.ts';
export type { default as File, FileOptions } from '../../../File.ts';
export type { default as Reference, ReferenceOptions } from '../../../Reference.ts';
export type { default as ReferenceSet, ReferenceSetOptions } from '../../../ReferenceSet.ts';
export type { AsyncAPI3DereferenceVisitorOptions, mutationReplacer } from './visitor.ts';
export type {
ReferenceOptions as ApiDOMReferenceOptions,
ReferenceBundleOptions as ApiDOMReferenceBundleOptions,
ReferenceDereferenceOptions as ApiDOMReferenceDereferenceOptions,
ReferenceParseOptions as ApiDOMReferenceParseOptions,
ReferenceResolveOptions as ApiDOMReferenceResolveOptions,
} from '../../../options/index.ts';
export type { default as Parser, ParserOptions } from '../../../parse/parsers/Parser.ts';
export type { default as Resolver, ResolverOptions } from '../../../resolve/resolvers/Resolver.ts';
export type {
default as ResolveStrategy,
ResolveStrategyOptions,
} from '../../../resolve/strategies/ResolveStrategy.ts';
export type {
default as BundleStrategy,
BundleStrategyOptions,
} from '../../../bundle/strategies/BundleStrategy.ts';
export type { AncestorLineage } from '../../util.ts';

// @ts-ignore
const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')];

/**
* @public
*/
export interface AsyncAPI3DeferenceStrategyOptions
extends Omit<DereferenceStrategyOptions, 'name'> {}

/**
* @public
*/
class AsyncAPI3DereferenceStrategy extends DereferenceStrategy {
constructor(options?: AsyncAPI3DeferenceStrategyOptions) {
super({ ...(options ?? {}), name: 'asyncapi-3' });
}

canDereference(file: File): boolean {
// assert by media type
if (file.mediaType !== 'text/plain') {
return mediaTypes.includes(file.mediaType);
}

// assert by inspecting ApiDOM
return isAsyncApi3Element(file.parseResult?.api);
}

async dereference(file: File, options: ReferenceOptions): Promise<Element> {
const namespace = createNamespace(asyncApi3Namespace);
const immutableRefSet = options.dereference.refSet ?? new ReferenceSet();
const mutableRefSet = new ReferenceSet();
let refSet = immutableRefSet;
let reference: Reference;

if (!immutableRefSet.has(file.uri)) {
reference = new Reference({ uri: file.uri, value: file.parseResult! });
immutableRefSet.add(reference);
} else {
// pre-computed refSet was provided as configuration option
reference = immutableRefSet.find((ref) => ref.uri === file.uri)!;
}

/**
* Clone refSet due the dereferencing process being mutable.
* We don't want to mutate the original refSet and the references.
*/
if (options.dereference.immutable) {
immutableRefSet.refs
.map(
(ref) =>
new Reference({
...ref,
value: cloneDeep(ref.value),
}),
)
.forEach((ref) => mutableRefSet.add(ref));
reference = mutableRefSet.find((ref) => ref.uri === file.uri)!;
refSet = mutableRefSet;
}

const visitor = new AsyncAPI3DereferenceVisitor({ reference, namespace, options });
const dereferencedElement = await visitAsync(refSet.rootRef!.value, visitor, {
keyMap,
nodeTypeGetter: getNodeType,
});

/**
* If immutable option is set, replay refs from the refSet.
*/
if (options.dereference.immutable) {
mutableRefSet.refs
.filter((ref) => ref.uri.startsWith('immutable://'))
.map(
(ref) =>
new Reference({
...ref,
uri: ref.uri.replace(/^immutable:\/\//, ''),
}),
)
.forEach((ref) => immutableRefSet.add(ref));
}

/**
* Release all memory if this refSet was not provided as a configuration option.
* If provided as configuration option, then provider is responsible for cleanup.
*/
if (options.dereference.refSet === null) {
immutableRefSet.clean();
}

mutableRefSet.clean();

return dereferencedElement;
}
}

export { AsyncAPI3DereferenceVisitor };
export default AsyncAPI3DereferenceStrategy;
Loading
Loading