@speclynx/apidom-core 4.0.1 → 4.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/package.json +6 -7
- package/src/fields/fixed-fields.cjs +47 -0
- package/src/fields/fixed-fields.mjs +43 -0
- package/src/fields/fixed-fields.ts +61 -0
- package/src/fields/index.cjs +8 -0
- package/src/fields/index.mjs +1 -0
- package/src/fields/index.ts +2 -0
- package/src/identity/errors/ElementIdentityError.cjs +22 -0
- package/src/identity/errors/ElementIdentityError.mjs +19 -0
- package/src/identity/errors/ElementIdentityError.ts +26 -0
- package/src/identity/index.cjs +64 -0
- package/src/identity/index.mjs +58 -0
- package/src/identity/index.ts +64 -0
- package/src/index.cjs +48 -0
- package/src/index.mjs +41 -0
- package/src/index.ts +81 -0
- package/src/media-types.cjs +21 -0
- package/src/media-types.mjs +18 -0
- package/src/media-types.ts +24 -0
- package/src/merge/deepmerge.cjs +165 -0
- package/src/merge/deepmerge.mjs +149 -0
- package/src/merge/deepmerge.ts +274 -0
- package/src/merge/merge-left.cjs +16 -0
- package/src/merge/merge-left.mjs +11 -0
- package/src/merge/merge-left.ts +14 -0
- package/src/merge/merge-right.cjs +35 -0
- package/src/merge/merge-right.mjs +29 -0
- package/src/merge/merge-right.ts +42 -0
- package/src/namespace.cjs +10 -0
- package/src/namespace.mjs +7 -0
- package/src/namespace.ts +8 -0
- package/src/refractor/plugins/dispatcher/index.cjs +64 -0
- package/src/refractor/plugins/dispatcher/index.mjs +54 -0
- package/src/refractor/plugins/dispatcher/index.ts +102 -0
- package/src/refractor/plugins/element-identity.cjs +31 -0
- package/src/refractor/plugins/element-identity.mjs +26 -0
- package/src/refractor/plugins/element-identity.ts +31 -0
- package/src/refractor/plugins/semantic-element-identity.cjs +33 -0
- package/src/refractor/plugins/semantic-element-identity.mjs +29 -0
- package/src/refractor/plugins/semantic-element-identity.ts +32 -0
- package/src/refractor/toolbox.cjs +47 -0
- package/src/refractor/toolbox.mjs +41 -0
- package/src/refractor/toolbox.ts +88 -0
- package/src/specification.cjs +63 -0
- package/src/specification.mjs +59 -0
- package/src/specification.ts +68 -0
- package/src/transcluder/Transcluder.cjs +111 -0
- package/src/transcluder/Transcluder.mjs +107 -0
- package/src/transcluder/Transcluder.ts +147 -0
- package/src/transcluder/index.cjs +19 -0
- package/src/transcluder/index.mjs +13 -0
- package/src/transcluder/index.ts +15 -0
- package/src/transformers/dehydrate.cjs +15 -0
- package/src/transformers/dehydrate.mjs +10 -0
- package/src/transformers/dehydrate.ts +14 -0
- package/src/transformers/from.cjs +34 -0
- package/src/transformers/from.mjs +29 -0
- package/src/transformers/from.ts +34 -0
- package/src/transformers/serializers/json.cjs +75 -0
- package/src/transformers/serializers/json.mjs +70 -0
- package/src/transformers/serializers/json.ts +107 -0
- package/src/transformers/serializers/value.cjs +50 -0
- package/src/transformers/serializers/value.mjs +47 -0
- package/src/transformers/serializers/value.ts +70 -0
- package/src/transformers/serializers/yaml-1-2.cjs +142 -0
- package/src/transformers/serializers/yaml-1-2.mjs +137 -0
- package/src/transformers/serializers/yaml-1-2.ts +205 -0
- package/src/transformers/sexprs.cjs +31 -0
- package/src/transformers/sexprs.mjs +28 -0
- package/src/transformers/sexprs.ts +30 -0
- package/src/transformers/to-string.cjs +16 -0
- package/src/transformers/to-string.mjs +11 -0
- package/src/transformers/to-string.ts +15 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Namespace,
|
|
3
|
+
isElement,
|
|
4
|
+
isStringElement,
|
|
5
|
+
isNumberElement,
|
|
6
|
+
isNullElement,
|
|
7
|
+
isBooleanElement,
|
|
8
|
+
isArrayElement,
|
|
9
|
+
isObjectElement,
|
|
10
|
+
isMemberElement,
|
|
11
|
+
isPrimitiveElement,
|
|
12
|
+
isLinkElement,
|
|
13
|
+
isRefElement,
|
|
14
|
+
isAnnotationElement,
|
|
15
|
+
isCommentElement,
|
|
16
|
+
isParseResultElement,
|
|
17
|
+
isSourceMapElement,
|
|
18
|
+
hasElementStyle,
|
|
19
|
+
hasElementSourceMap,
|
|
20
|
+
includesSymbols,
|
|
21
|
+
includesClasses,
|
|
22
|
+
} from '@speclynx/apidom-datamodel';
|
|
23
|
+
|
|
24
|
+
import defaultNamespace from '../namespace.ts';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
29
|
+
export interface Predicates {
|
|
30
|
+
isElement: typeof isElement;
|
|
31
|
+
isStringElement: typeof isStringElement;
|
|
32
|
+
isNumberElement: typeof isNumberElement;
|
|
33
|
+
isNullElement: typeof isNullElement;
|
|
34
|
+
isBooleanElement: typeof isBooleanElement;
|
|
35
|
+
isArrayElement: typeof isArrayElement;
|
|
36
|
+
isObjectElement: typeof isObjectElement;
|
|
37
|
+
isMemberElement: typeof isMemberElement;
|
|
38
|
+
isPrimitiveElement: typeof isPrimitiveElement;
|
|
39
|
+
isLinkElement: typeof isLinkElement;
|
|
40
|
+
isRefElement: typeof isRefElement;
|
|
41
|
+
isAnnotationElement: typeof isAnnotationElement;
|
|
42
|
+
isCommentElement: typeof isCommentElement;
|
|
43
|
+
isParseResultElement: typeof isParseResultElement;
|
|
44
|
+
isSourceMapElement: typeof isSourceMapElement;
|
|
45
|
+
hasElementStyle: typeof hasElementStyle;
|
|
46
|
+
hasElementSourceMap: typeof hasElementSourceMap;
|
|
47
|
+
includesSymbols: typeof includesSymbols;
|
|
48
|
+
includesClasses: typeof includesClasses;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const predicates: Predicates = {
|
|
52
|
+
isElement,
|
|
53
|
+
isStringElement,
|
|
54
|
+
isNumberElement,
|
|
55
|
+
isNullElement,
|
|
56
|
+
isBooleanElement,
|
|
57
|
+
isArrayElement,
|
|
58
|
+
isObjectElement,
|
|
59
|
+
isMemberElement,
|
|
60
|
+
isPrimitiveElement,
|
|
61
|
+
isLinkElement,
|
|
62
|
+
isRefElement,
|
|
63
|
+
isAnnotationElement,
|
|
64
|
+
isCommentElement,
|
|
65
|
+
isParseResultElement,
|
|
66
|
+
isSourceMapElement,
|
|
67
|
+
hasElementStyle,
|
|
68
|
+
hasElementSourceMap,
|
|
69
|
+
includesSymbols,
|
|
70
|
+
includesClasses,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @public
|
|
75
|
+
*/
|
|
76
|
+
export interface Toolbox {
|
|
77
|
+
predicates: Predicates;
|
|
78
|
+
namespace: Namespace;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @public
|
|
83
|
+
*/
|
|
84
|
+
const createToolbox = (): Toolbox => {
|
|
85
|
+
return { predicates, namespace: defaultNamespace };
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export default createToolbox;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.resolveSpecification = void 0;
|
|
5
|
+
var _ramda = require("ramda");
|
|
6
|
+
var _ramdaAdjunct = require("ramda-adjunct");
|
|
7
|
+
/**
|
|
8
|
+
* Base type for resolved specification objects.
|
|
9
|
+
* Extend this in namespace packages to add specific type information.
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const specCache = new WeakMap();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolves a specification object by:
|
|
17
|
+
* 1. Dereferencing all $ref pointers
|
|
18
|
+
* 2. Building elementMap from objects with `element` property
|
|
19
|
+
* 3. Caching results for efficiency
|
|
20
|
+
*
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
const resolveSpecification = specification => {
|
|
24
|
+
if (specCache.has(specification)) {
|
|
25
|
+
return specCache.get(specification);
|
|
26
|
+
}
|
|
27
|
+
const elementMap = {};
|
|
28
|
+
const traverse = (obj, root, currentPath) => {
|
|
29
|
+
// Collect element mapping
|
|
30
|
+
if (typeof obj.element === 'string') {
|
|
31
|
+
elementMap[obj.element] = obj.$visitor ? [...currentPath, '$visitor'] : currentPath;
|
|
32
|
+
}
|
|
33
|
+
return (0, _ramda.mapObjIndexed)((val, key) => {
|
|
34
|
+
const newPath = [...currentPath, key];
|
|
35
|
+
if ((0, _ramdaAdjunct.isPlainObject)(val) && (0, _ramda.has)('$ref', val) && (0, _ramda.propSatisfies)(_ramdaAdjunct.isString, '$ref', val)) {
|
|
36
|
+
const $ref = (0, _ramda.path)(['$ref'], val);
|
|
37
|
+
const pointer = (0, _ramdaAdjunct.trimCharsStart)('#/', $ref);
|
|
38
|
+
const resolved = (0, _ramda.path)(pointer.split('/'), root);
|
|
39
|
+
// merge extra properties (e.g. alias) from the $ref object into the resolved value
|
|
40
|
+
const {
|
|
41
|
+
$ref: _,
|
|
42
|
+
...rest
|
|
43
|
+
} = val;
|
|
44
|
+
if (Object.keys(rest).length > 0 && (0, _ramdaAdjunct.isPlainObject)(resolved)) {
|
|
45
|
+
return {
|
|
46
|
+
...resolved,
|
|
47
|
+
...rest
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return resolved;
|
|
51
|
+
}
|
|
52
|
+
if ((0, _ramdaAdjunct.isPlainObject)(val)) {
|
|
53
|
+
return traverse(val, root, newPath);
|
|
54
|
+
}
|
|
55
|
+
return val;
|
|
56
|
+
}, obj);
|
|
57
|
+
};
|
|
58
|
+
const resolved = traverse(specification, specification, []);
|
|
59
|
+
resolved.elementMap = elementMap;
|
|
60
|
+
specCache.set(specification, resolved);
|
|
61
|
+
return resolved;
|
|
62
|
+
};
|
|
63
|
+
exports.resolveSpecification = resolveSpecification;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { mapObjIndexed, path, has, propSatisfies } from 'ramda';
|
|
2
|
+
import { isPlainObject, trimCharsStart, isString } from 'ramda-adjunct';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Base type for resolved specification objects.
|
|
6
|
+
* Extend this in namespace packages to add specific type information.
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const specCache = new WeakMap();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolves a specification object by:
|
|
14
|
+
* 1. Dereferencing all $ref pointers
|
|
15
|
+
* 2. Building elementMap from objects with `element` property
|
|
16
|
+
* 3. Caching results for efficiency
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
export const resolveSpecification = specification => {
|
|
21
|
+
if (specCache.has(specification)) {
|
|
22
|
+
return specCache.get(specification);
|
|
23
|
+
}
|
|
24
|
+
const elementMap = {};
|
|
25
|
+
const traverse = (obj, root, currentPath) => {
|
|
26
|
+
// Collect element mapping
|
|
27
|
+
if (typeof obj.element === 'string') {
|
|
28
|
+
elementMap[obj.element] = obj.$visitor ? [...currentPath, '$visitor'] : currentPath;
|
|
29
|
+
}
|
|
30
|
+
return mapObjIndexed((val, key) => {
|
|
31
|
+
const newPath = [...currentPath, key];
|
|
32
|
+
if (isPlainObject(val) && has('$ref', val) && propSatisfies(isString, '$ref', val)) {
|
|
33
|
+
const $ref = path(['$ref'], val);
|
|
34
|
+
const pointer = trimCharsStart('#/', $ref);
|
|
35
|
+
const resolved = path(pointer.split('/'), root);
|
|
36
|
+
// merge extra properties (e.g. alias) from the $ref object into the resolved value
|
|
37
|
+
const {
|
|
38
|
+
$ref: _,
|
|
39
|
+
...rest
|
|
40
|
+
} = val;
|
|
41
|
+
if (Object.keys(rest).length > 0 && isPlainObject(resolved)) {
|
|
42
|
+
return {
|
|
43
|
+
...resolved,
|
|
44
|
+
...rest
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return resolved;
|
|
48
|
+
}
|
|
49
|
+
if (isPlainObject(val)) {
|
|
50
|
+
return traverse(val, root, newPath);
|
|
51
|
+
}
|
|
52
|
+
return val;
|
|
53
|
+
}, obj);
|
|
54
|
+
};
|
|
55
|
+
const resolved = traverse(specification, specification, []);
|
|
56
|
+
resolved.elementMap = elementMap;
|
|
57
|
+
specCache.set(specification, resolved);
|
|
58
|
+
return resolved;
|
|
59
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { mapObjIndexed, path, has, propSatisfies } from 'ramda';
|
|
2
|
+
import { isPlainObject, trimCharsStart, isString } from 'ramda-adjunct';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Base type for resolved specification objects.
|
|
6
|
+
* Extend this in namespace packages to add specific type information.
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
export interface ResolvedSpecification {
|
|
10
|
+
elementMap: Record<string, string[]>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const specCache = new WeakMap<object, Record<string, unknown>>();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolves a specification object by:
|
|
17
|
+
* 1. Dereferencing all $ref pointers
|
|
18
|
+
* 2. Building elementMap from objects with `element` property
|
|
19
|
+
* 3. Caching results for efficiency
|
|
20
|
+
*
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
export const resolveSpecification = <T extends ResolvedSpecification = ResolvedSpecification>(
|
|
24
|
+
specification: Record<string, unknown>,
|
|
25
|
+
): T => {
|
|
26
|
+
if (specCache.has(specification)) {
|
|
27
|
+
return specCache.get(specification)! as T;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const elementMap: Record<string, string[]> = {};
|
|
31
|
+
|
|
32
|
+
const traverse = (
|
|
33
|
+
obj: Record<string, unknown>,
|
|
34
|
+
root: Record<string, unknown>,
|
|
35
|
+
currentPath: string[],
|
|
36
|
+
): Record<string, unknown> => {
|
|
37
|
+
// Collect element mapping
|
|
38
|
+
if (typeof obj.element === 'string') {
|
|
39
|
+
elementMap[obj.element] = obj.$visitor ? [...currentPath, '$visitor'] : currentPath;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return mapObjIndexed((val, key) => {
|
|
43
|
+
const newPath = [...currentPath, key as string];
|
|
44
|
+
|
|
45
|
+
if (isPlainObject(val) && has('$ref', val) && propSatisfies(isString, '$ref', val)) {
|
|
46
|
+
const $ref = path(['$ref'], val);
|
|
47
|
+
const pointer = trimCharsStart('#/', $ref as string);
|
|
48
|
+
const resolved = path(pointer.split('/'), root);
|
|
49
|
+
// merge extra properties (e.g. alias) from the $ref object into the resolved value
|
|
50
|
+
const { $ref: _, ...rest } = val as Record<string, unknown>;
|
|
51
|
+
if (Object.keys(rest).length > 0 && isPlainObject(resolved)) {
|
|
52
|
+
return { ...(resolved as Record<string, unknown>), ...rest };
|
|
53
|
+
}
|
|
54
|
+
return resolved;
|
|
55
|
+
}
|
|
56
|
+
if (isPlainObject(val)) {
|
|
57
|
+
return traverse(val as Record<string, unknown>, root, newPath);
|
|
58
|
+
}
|
|
59
|
+
return val;
|
|
60
|
+
}, obj);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const resolved = traverse(specification, specification, []);
|
|
64
|
+
resolved.elementMap = elementMap;
|
|
65
|
+
|
|
66
|
+
specCache.set(specification, resolved);
|
|
67
|
+
return resolved as T;
|
|
68
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.default = void 0;
|
|
5
|
+
var _apidomDatamodel = require("@speclynx/apidom-datamodel");
|
|
6
|
+
var _ramdaAdjunct = require("ramda-adjunct");
|
|
7
|
+
const computeEdges = (element, edges = new WeakMap()) => {
|
|
8
|
+
if ((0, _apidomDatamodel.isMemberElement)(element)) {
|
|
9
|
+
// @ts-ignore
|
|
10
|
+
edges.set(element.key, element);
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
computeEdges(element.key, edges);
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
edges.set(element.value, element);
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
computeEdges(element.value, edges);
|
|
17
|
+
} else {
|
|
18
|
+
element.children.forEach(childElement => {
|
|
19
|
+
edges.set(childElement, element);
|
|
20
|
+
computeEdges(childElement, edges);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return edges;
|
|
24
|
+
};
|
|
25
|
+
const transcludeChildOfMemberElement = (search, replace, edges) => {
|
|
26
|
+
const memberElement = edges.get(search);
|
|
27
|
+
if (!(0, _apidomDatamodel.isMemberElement)(memberElement)) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (memberElement.key === search) {
|
|
31
|
+
memberElement.key = replace;
|
|
32
|
+
edges.delete(search);
|
|
33
|
+
edges.set(replace, memberElement);
|
|
34
|
+
}
|
|
35
|
+
if (memberElement.value === search) {
|
|
36
|
+
memberElement.value = replace;
|
|
37
|
+
edges.delete(search);
|
|
38
|
+
edges.set(replace, memberElement);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const transcludeChildOfObjectElement = (search, replace, edges) => {
|
|
42
|
+
const objectElement = edges.get(search);
|
|
43
|
+
if (!(0, _apidomDatamodel.isObjectElement)(objectElement)) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
objectElement.content = objectElement.map((value, key, member) => {
|
|
47
|
+
if (member === search) {
|
|
48
|
+
edges.delete(search);
|
|
49
|
+
edges.set(replace, objectElement);
|
|
50
|
+
return replace;
|
|
51
|
+
}
|
|
52
|
+
return member;
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
const transcludeChildOfArrayElement = (search, replace, edges) => {
|
|
56
|
+
const arrayElement = edges.get(search);
|
|
57
|
+
if (!(0, _apidomDatamodel.isArrayElement)(arrayElement)) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
arrayElement.content = arrayElement.map(element => {
|
|
61
|
+
if (element === search) {
|
|
62
|
+
edges.delete(search);
|
|
63
|
+
edges.set(replace, arrayElement);
|
|
64
|
+
return replace;
|
|
65
|
+
}
|
|
66
|
+
return element;
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* This is a mutating stamp. If you don't want your Element to be mutated,
|
|
72
|
+
* clone in before passing it to initializer of this stamp.
|
|
73
|
+
* @public
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
class Transcluder {
|
|
77
|
+
element;
|
|
78
|
+
edges;
|
|
79
|
+
constructor({
|
|
80
|
+
element
|
|
81
|
+
}) {
|
|
82
|
+
this.element = element;
|
|
83
|
+
}
|
|
84
|
+
transclude(search, replace) {
|
|
85
|
+
// shortcut 1. - replacing entire ApiDOM tree
|
|
86
|
+
if (search === this.element) return replace;
|
|
87
|
+
// shortcut 2. - replacing nothing
|
|
88
|
+
if (search === replace) return this.element;
|
|
89
|
+
this.edges = this.edges ?? computeEdges(this.element);
|
|
90
|
+
const parent = this.edges.get(search);
|
|
91
|
+
if ((0, _ramdaAdjunct.isUndefined)(parent)) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* This predicate must be first because ObjectElement extends ArrayElement.
|
|
97
|
+
* isArrayElement returns true for ObjectElements.
|
|
98
|
+
* (classical problems with polymorphism)
|
|
99
|
+
*/
|
|
100
|
+
if ((0, _apidomDatamodel.isObjectElement)(parent)) {
|
|
101
|
+
// @ts-ignore
|
|
102
|
+
transcludeChildOfObjectElement(search, replace, this.edges);
|
|
103
|
+
} else if ((0, _apidomDatamodel.isArrayElement)(parent)) {
|
|
104
|
+
transcludeChildOfArrayElement(search, replace, this.edges);
|
|
105
|
+
} else if ((0, _apidomDatamodel.isMemberElement)(parent)) {
|
|
106
|
+
transcludeChildOfMemberElement(search, replace, this.edges);
|
|
107
|
+
}
|
|
108
|
+
return this.element;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
var _default = exports.default = Transcluder;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { isObjectElement, isArrayElement, isMemberElement } from '@speclynx/apidom-datamodel';
|
|
2
|
+
import { isUndefined } from 'ramda-adjunct';
|
|
3
|
+
const computeEdges = (element, edges = new WeakMap()) => {
|
|
4
|
+
if (isMemberElement(element)) {
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
edges.set(element.key, element);
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
computeEdges(element.key, edges);
|
|
9
|
+
// @ts-ignore
|
|
10
|
+
edges.set(element.value, element);
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
computeEdges(element.value, edges);
|
|
13
|
+
} else {
|
|
14
|
+
element.children.forEach(childElement => {
|
|
15
|
+
edges.set(childElement, element);
|
|
16
|
+
computeEdges(childElement, edges);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return edges;
|
|
20
|
+
};
|
|
21
|
+
const transcludeChildOfMemberElement = (search, replace, edges) => {
|
|
22
|
+
const memberElement = edges.get(search);
|
|
23
|
+
if (!isMemberElement(memberElement)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (memberElement.key === search) {
|
|
27
|
+
memberElement.key = replace;
|
|
28
|
+
edges.delete(search);
|
|
29
|
+
edges.set(replace, memberElement);
|
|
30
|
+
}
|
|
31
|
+
if (memberElement.value === search) {
|
|
32
|
+
memberElement.value = replace;
|
|
33
|
+
edges.delete(search);
|
|
34
|
+
edges.set(replace, memberElement);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const transcludeChildOfObjectElement = (search, replace, edges) => {
|
|
38
|
+
const objectElement = edges.get(search);
|
|
39
|
+
if (!isObjectElement(objectElement)) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
objectElement.content = objectElement.map((value, key, member) => {
|
|
43
|
+
if (member === search) {
|
|
44
|
+
edges.delete(search);
|
|
45
|
+
edges.set(replace, objectElement);
|
|
46
|
+
return replace;
|
|
47
|
+
}
|
|
48
|
+
return member;
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
const transcludeChildOfArrayElement = (search, replace, edges) => {
|
|
52
|
+
const arrayElement = edges.get(search);
|
|
53
|
+
if (!isArrayElement(arrayElement)) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
arrayElement.content = arrayElement.map(element => {
|
|
57
|
+
if (element === search) {
|
|
58
|
+
edges.delete(search);
|
|
59
|
+
edges.set(replace, arrayElement);
|
|
60
|
+
return replace;
|
|
61
|
+
}
|
|
62
|
+
return element;
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* This is a mutating stamp. If you don't want your Element to be mutated,
|
|
68
|
+
* clone in before passing it to initializer of this stamp.
|
|
69
|
+
* @public
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
class Transcluder {
|
|
73
|
+
element;
|
|
74
|
+
edges;
|
|
75
|
+
constructor({
|
|
76
|
+
element
|
|
77
|
+
}) {
|
|
78
|
+
this.element = element;
|
|
79
|
+
}
|
|
80
|
+
transclude(search, replace) {
|
|
81
|
+
// shortcut 1. - replacing entire ApiDOM tree
|
|
82
|
+
if (search === this.element) return replace;
|
|
83
|
+
// shortcut 2. - replacing nothing
|
|
84
|
+
if (search === replace) return this.element;
|
|
85
|
+
this.edges = this.edges ?? computeEdges(this.element);
|
|
86
|
+
const parent = this.edges.get(search);
|
|
87
|
+
if (isUndefined(parent)) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* This predicate must be first because ObjectElement extends ArrayElement.
|
|
93
|
+
* isArrayElement returns true for ObjectElements.
|
|
94
|
+
* (classical problems with polymorphism)
|
|
95
|
+
*/
|
|
96
|
+
if (isObjectElement(parent)) {
|
|
97
|
+
// @ts-ignore
|
|
98
|
+
transcludeChildOfObjectElement(search, replace, this.edges);
|
|
99
|
+
} else if (isArrayElement(parent)) {
|
|
100
|
+
transcludeChildOfArrayElement(search, replace, this.edges);
|
|
101
|
+
} else if (isMemberElement(parent)) {
|
|
102
|
+
transcludeChildOfMemberElement(search, replace, this.edges);
|
|
103
|
+
}
|
|
104
|
+
return this.element;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export default Transcluder;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArrayElement,
|
|
3
|
+
Element,
|
|
4
|
+
MemberElement,
|
|
5
|
+
ObjectElement,
|
|
6
|
+
isObjectElement,
|
|
7
|
+
isArrayElement,
|
|
8
|
+
isMemberElement,
|
|
9
|
+
} from '@speclynx/apidom-datamodel';
|
|
10
|
+
import { isUndefined } from 'ramda-adjunct';
|
|
11
|
+
|
|
12
|
+
const computeEdges = (element: Element, edges = new WeakMap()): WeakMap<Element, any> => {
|
|
13
|
+
if (isMemberElement(element)) {
|
|
14
|
+
// @ts-ignore
|
|
15
|
+
edges.set(element.key, element);
|
|
16
|
+
// @ts-ignore
|
|
17
|
+
computeEdges(element.key, edges);
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
edges.set(element.value, element);
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
computeEdges(element.value, edges);
|
|
22
|
+
} else {
|
|
23
|
+
element.children.forEach((childElement: Element): void => {
|
|
24
|
+
edges.set(childElement, element);
|
|
25
|
+
computeEdges(childElement, edges);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return edges;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const transcludeChildOfMemberElement = (
|
|
33
|
+
search: Element,
|
|
34
|
+
replace: Element,
|
|
35
|
+
edges: WeakMap<Element, any>,
|
|
36
|
+
): void => {
|
|
37
|
+
const memberElement: MemberElement = edges.get(search);
|
|
38
|
+
|
|
39
|
+
if (!isMemberElement(memberElement)) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (memberElement.key === search) {
|
|
44
|
+
memberElement.key = replace;
|
|
45
|
+
edges.delete(search);
|
|
46
|
+
edges.set(replace, memberElement);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (memberElement.value === search) {
|
|
50
|
+
memberElement.value = replace;
|
|
51
|
+
edges.delete(search);
|
|
52
|
+
edges.set(replace, memberElement);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const transcludeChildOfObjectElement = (
|
|
57
|
+
search: Element,
|
|
58
|
+
replace: MemberElement,
|
|
59
|
+
edges: WeakMap<Element, any>,
|
|
60
|
+
): void => {
|
|
61
|
+
const objectElement: ObjectElement = edges.get(search);
|
|
62
|
+
|
|
63
|
+
if (!isObjectElement(objectElement)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
objectElement.content = objectElement.map(
|
|
68
|
+
(value: Element, key: Element, member: MemberElement): MemberElement => {
|
|
69
|
+
if (member === search) {
|
|
70
|
+
edges.delete(search);
|
|
71
|
+
edges.set(replace, objectElement);
|
|
72
|
+
return replace;
|
|
73
|
+
}
|
|
74
|
+
return member;
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const transcludeChildOfArrayElement = (
|
|
80
|
+
search: Element,
|
|
81
|
+
replace: Element,
|
|
82
|
+
edges: WeakMap<Element, any>,
|
|
83
|
+
): void => {
|
|
84
|
+
const arrayElement: ArrayElement = edges.get(search);
|
|
85
|
+
|
|
86
|
+
if (!isArrayElement(arrayElement)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
arrayElement.content = arrayElement.map((element: Element): Element => {
|
|
91
|
+
if (element === search) {
|
|
92
|
+
edges.delete(search);
|
|
93
|
+
edges.set(replace, arrayElement);
|
|
94
|
+
return replace;
|
|
95
|
+
}
|
|
96
|
+
return element;
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* This is a mutating stamp. If you don't want your Element to be mutated,
|
|
102
|
+
* clone in before passing it to initializer of this stamp.
|
|
103
|
+
* @public
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
class Transcluder {
|
|
107
|
+
protected element: Element;
|
|
108
|
+
|
|
109
|
+
protected edges!: WeakMap<Element, Element | undefined>;
|
|
110
|
+
|
|
111
|
+
constructor({ element }: { element: Element }) {
|
|
112
|
+
this.element = element;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public transclude(search: Element, replace: Element): Element | undefined {
|
|
116
|
+
// shortcut 1. - replacing entire ApiDOM tree
|
|
117
|
+
if (search === this.element) return replace;
|
|
118
|
+
// shortcut 2. - replacing nothing
|
|
119
|
+
if (search === replace) return this.element;
|
|
120
|
+
|
|
121
|
+
this.edges = this.edges ?? computeEdges(this.element);
|
|
122
|
+
|
|
123
|
+
const parent = this.edges.get(search);
|
|
124
|
+
|
|
125
|
+
if (isUndefined(parent)) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* This predicate must be first because ObjectElement extends ArrayElement.
|
|
131
|
+
* isArrayElement returns true for ObjectElements.
|
|
132
|
+
* (classical problems with polymorphism)
|
|
133
|
+
*/
|
|
134
|
+
if (isObjectElement(parent)) {
|
|
135
|
+
// @ts-ignore
|
|
136
|
+
transcludeChildOfObjectElement(search, replace, this.edges);
|
|
137
|
+
} else if (isArrayElement(parent)) {
|
|
138
|
+
transcludeChildOfArrayElement(search, replace, this.edges);
|
|
139
|
+
} else if (isMemberElement(parent)) {
|
|
140
|
+
transcludeChildOfMemberElement(search, replace, this.edges);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return this.element;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export default Transcluder;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
|
|
4
|
+
exports.__esModule = true;
|
|
5
|
+
exports.transclude = exports.default = void 0;
|
|
6
|
+
var _Transcluder = _interopRequireDefault(require("./Transcluder.cjs"));
|
|
7
|
+
/**
|
|
8
|
+
* This is a mutating function. If you don't want your Element to be mutated,
|
|
9
|
+
* clone in before passing it to this function.
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
const transclude = (search, replace, element) => {
|
|
13
|
+
const transcluder = new _Transcluder.default({
|
|
14
|
+
element
|
|
15
|
+
});
|
|
16
|
+
return transcluder.transclude(search, replace);
|
|
17
|
+
};
|
|
18
|
+
exports.transclude = transclude;
|
|
19
|
+
var _default = exports.default = _Transcluder.default;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import Transcluder from "./Transcluder.mjs";
|
|
2
|
+
/**
|
|
3
|
+
* This is a mutating function. If you don't want your Element to be mutated,
|
|
4
|
+
* clone in before passing it to this function.
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
export const transclude = (search, replace, element) => {
|
|
8
|
+
const transcluder = new Transcluder({
|
|
9
|
+
element
|
|
10
|
+
});
|
|
11
|
+
return transcluder.transclude(search, replace);
|
|
12
|
+
};
|
|
13
|
+
export default Transcluder;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Element } from '@speclynx/apidom-datamodel';
|
|
2
|
+
|
|
3
|
+
import Transcluder from './Transcluder.ts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This is a mutating function. If you don't want your Element to be mutated,
|
|
7
|
+
* clone in before passing it to this function.
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
export const transclude = (search: Element, replace: Element, element: Element) => {
|
|
11
|
+
const transcluder = new Transcluder({ element });
|
|
12
|
+
return transcluder.transclude(search, replace);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default Transcluder;
|