@redocly/openapi-core
Version:
See https://github.com/Redocly/redocly-cli
177 lines • 6.73 kB
JavaScript
import { SpecExtension } from './types/index.js';
import { isPlainObject } from './utils/is-plain-object.js';
const legacyTypesMap = {
Root: 'DefinitionRoot',
ServerVariablesMap: 'ServerVariableMap',
Paths: ['PathMap', 'PathsMap'],
CallbacksMap: 'CallbackMap',
MediaTypesMap: 'MediaTypeMap',
ExamplesMap: 'ExampleMap',
EncodingMap: 'EncodingsMap',
HeadersMap: 'HeaderMap',
LinksMap: 'LinkMap',
OAuth2Flows: 'SecuritySchemeFlows',
Responses: 'ResponsesMap',
};
export function normalizeVisitors(visitorsConfig, types) {
const normalizedVisitors = {};
normalizedVisitors.any = {
enter: [],
leave: [],
};
for (const typeName of Object.keys(types)) {
normalizedVisitors[typeName] = {
enter: [],
leave: [],
};
}
normalizedVisitors.ref = {
enter: [],
leave: [],
};
for (const { ruleId, severity, message, visitor } of visitorsConfig) {
normalizeVisitorLevel({ ruleId, severity, message }, visitor, null);
}
for (const v of Object.keys(normalizedVisitors)) {
normalizedVisitors[v].enter.sort((a, b) => b.depth - a.depth);
normalizedVisitors[v].leave.sort((a, b) => a.depth - b.depth);
}
return normalizedVisitors;
function addWeakNodes(ruleConf, from, to, parentContext, stack = []) {
if (stack.includes(from))
return;
stack = [...stack, from];
const possibleChildren = new Set();
for (const type of Object.values(from.properties)) {
if (type === to) {
addWeakFromStack(ruleConf, stack);
continue;
}
if (typeof type === 'object' && type !== null && type.name) {
possibleChildren.add(type);
}
}
if (from.additionalProperties && typeof from.additionalProperties !== 'function') {
if (from.additionalProperties === to) {
addWeakFromStack(ruleConf, stack);
}
else if (from.additionalProperties.name !== undefined) {
possibleChildren.add(from.additionalProperties);
}
}
if (from.items && typeof from.items !== 'function') {
if (from.items === to) {
addWeakFromStack(ruleConf, stack);
}
else if (from.items.name !== undefined) {
possibleChildren.add(from.items);
}
}
if (from.extensionsPrefix) {
possibleChildren.add(SpecExtension);
}
for (const fromType of Array.from(possibleChildren.values())) {
addWeakNodes(ruleConf, fromType, to, parentContext, stack);
}
function addWeakFromStack(ruleConf, stack) {
for (const interType of stack.slice(1)) {
normalizedVisitors[interType.name] = normalizedVisitors[interType.name] || {
enter: [],
leave: [],
};
normalizedVisitors[interType.name].enter.push({
...ruleConf,
visit: () => undefined,
depth: 0,
context: {
isSkippedLevel: true,
seen: new Set(),
parent: parentContext,
},
});
}
}
}
function findLegacyVisitorNode(visitor, typeName) {
if (Array.isArray(typeName)) {
const name = typeName.find((name) => visitor[name]) || undefined;
return name && visitor[name];
}
return visitor[typeName];
}
function normalizeVisitorLevel(ruleConf, visitor, parentContext, depth = 0) {
const visitorKeys = Object.keys(types);
if (depth === 0) {
visitorKeys.push('any');
visitorKeys.push('ref');
}
else {
if (visitor.any) {
throw new Error('any() is allowed only on top level');
}
if (visitor.ref) {
throw new Error('ref() is allowed only on top level');
}
}
for (const typeName of visitorKeys) {
const typeVisitor = (visitor[typeName] ||
findLegacyVisitorNode(visitor, legacyTypesMap[typeName]));
const normalizedTypeVisitor = normalizedVisitors[typeName];
if (!typeVisitor)
continue;
let visitorEnter;
let visitorLeave;
let visitorSkip;
const isObjectVisitor = isPlainObject(typeVisitor);
if (typeName === 'ref' && isObjectVisitor && typeVisitor.skip) {
throw new Error('ref() visitor does not support skip');
}
if (typeof typeVisitor === 'function') {
visitorEnter = typeVisitor;
}
else if (isObjectVisitor) {
visitorEnter = typeVisitor.enter;
visitorLeave = typeVisitor.leave;
visitorSkip = typeVisitor.skip;
}
const standardKeysCount = (visitorEnter ? 1 : 0) + (visitorLeave ? 1 : 0) + (visitorSkip ? 1 : 0);
const hasNestedVisitors = isObjectVisitor && Object.keys(typeVisitor).length > standardKeysCount;
const context = {
activatedOn: null,
type: types[typeName],
parent: parentContext,
isSkippedLevel: false,
};
if (hasNestedVisitors) {
normalizeVisitorLevel(ruleConf, typeVisitor, context, depth + 1);
}
if (parentContext) {
addWeakNodes(ruleConf, parentContext.type, types[typeName], parentContext);
}
if (visitorEnter || isObjectVisitor) {
if (visitorEnter && typeof visitorEnter !== 'function') {
throw new Error('DEV: should be function');
}
normalizedTypeVisitor.enter.push({
...ruleConf,
visit: visitorEnter || (() => undefined),
skip: visitorSkip,
depth,
context,
});
}
if (visitorLeave) {
if (typeof visitorLeave !== 'function') {
throw new Error('DEV: should be function');
}
normalizedTypeVisitor.leave.push({
...ruleConf,
visit: visitorLeave,
depth,
context,
});
}
}
}
}
//# sourceMappingURL=visitors.js.map