Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion apidom/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions apidom/packages/@types/minim.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ declare module 'minim' {

constructor(content?: Array<unknown>, meta?: Meta, attributes?: Attributes);

equals(value: any): boolean;

toValue(): any;

getMetaProperty(name: any, value: any): any;
Expand Down Expand Up @@ -109,4 +111,14 @@ declare module 'minim' {

set path(path: unknown);
}

export class ArraySlice {
constructor(elements?: Array<unknown>);

get length(): number;

hasKey(value: string): boolean;

get<T extends Element>(index: number): T;
}
}
2 changes: 2 additions & 0 deletions apidom/packages/apidom/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
"author": "Vladimír Gorej",
"license": "Apache-2.0",
"dependencies": {
"apidom-ast": "file:../apidom-ast",
"stampit": "=4.3.1",
"minim": "=0.23.8",
"ramda": "=0.27.0",
"ramda-adjunct": "=2.27.0"
Expand Down
12 changes: 10 additions & 2 deletions apidom/packages/apidom/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NamespacePlugin, Element } from 'minim';
import { isPlainObject } from 'ramda-adjunct';
import { Namespace as ApiDOMNamespace } from './namespace';

export { default as namespace, Namespace } from './namespace';
Expand All @@ -21,9 +22,16 @@ export {
} from './predicates';
export { default as createPredicate } from './predicates/helpers';

export const createNamespace = (namespacePlugin: NamespacePlugin): ApiDOMNamespace => {
export { ArraySlice } from 'minim';
export { filter, reject, find, some } from './traversal';

export const createNamespace = (namespacePlugin?: NamespacePlugin): ApiDOMNamespace => {
const namespace = new ApiDOMNamespace();
namespace.use(namespacePlugin);

if (isPlainObject(namespacePlugin)) {
namespace.use(namespacePlugin);
}

return namespace;
};

Expand Down
108 changes: 108 additions & 0 deletions apidom/packages/apidom/src/traversal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { visit, BREAK } from 'apidom-ast';
import stampit from 'stampit';
import { ArraySlice } from 'minim';
import { Pred, curry, curryN, pipe, F as stubFalse, complement, pathOr } from 'ramda';
import { isString, isNotUndefined } from 'ramda-adjunct';

import {
isObjectElement,
isArrayElement,
isNumberElement,
isNullElement,
isBooleanElement,
isMemberElement,
isStringElement,
} from './predicates';

// getNodeType :: Node -> String
const getNodeType = <T extends Element>(element: T): string | undefined => {
/*
* We're translating every possible higher element type to primitive minim type here.
* This allows us keep key mapping to minimum.
*/
/* eslint-disable no-nested-ternary */
return isObjectElement(element)
? 'object'
: isArrayElement(element)
? 'array'
: isNumberElement(element)
? 'number'
: isNullElement(element)
? 'null'
: isBooleanElement(element)
? 'boolean'
: isMemberElement(element)
? 'member'
: isStringElement(element)
? 'string'
: undefined;
/* eslint-enable */
};

// isNode :: Node -> Boolean
const isNode = curryN(1, pipe(getNodeType, isString));

const keyMap = {
object: ['content'],
array: ['content'],
member: ['key', 'value'],
};

const Visitor = stampit({
props: {
result: [],
predicate: stubFalse,
return: undefined,
},
init({ predicate }) {
this.result = [];
this.predicate = predicate;
},
methods: {
enter(element) {
if (this.predicate(element)) {
this.result.push(element);
return this.return;
}
return undefined;
},
},
});

// finds all elements matching the predicate
// filter :: Pred -> Element -> ArraySlice
export const filter = curry(
<T extends Element>(predicate: Pred, element: T): ArraySlice => {
const visitor = Visitor({ predicate });

// @ts-ignore
visit(element, visitor, { keyMap, nodeTypeGetter: getNodeType, nodePredicate: isNode });

return new ArraySlice(visitor.result);
},
);

// complement of filter
// reject :: Pred -> Element -> ArraySlice
export const reject = curry(
<T extends Element>(predicate: Pred, element: T): ArraySlice => {
return filter(complement(predicate))(element);
},
);

// first first element in that satisfies the provided predicate
// find :: Pred -> Element -> Element | Undefined
export const find = curry(<T extends Element>(predicate: Pred, element: T): T | undefined => {
const visitor = Visitor({ predicate, return: BREAK });

// @ts-ignore
visit(element, visitor, { keyMap, nodeTypeGetter: getNodeType, nodePredicate: isNode });

return pathOr(undefined, [0], visitor.result);
});

// tests whether at least one element passes the predicate
// some :: Pred -> Element -> Boolean
export const some = curry(<T extends Element>(predicate: Pred, element: T): boolean => {
return isNotUndefined(find(predicate)(element));
});
Loading