@speclynx/apidom-reference 6.1.0 → 6.1.2
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 +13 -0
- package/dist/apidom-reference.browser.js +428 -318
- package/dist/apidom-reference.browser.min.js +1 -1
- package/package.json +29 -29
- package/src/bundle/strategies/asyncapi-2/visitor.cjs +5 -0
- package/src/bundle/strategies/asyncapi-2/visitor.mjs +5 -0
- package/src/bundle/strategies/openapi-2/visitor.cjs +4 -0
- package/src/bundle/strategies/openapi-2/visitor.mjs +4 -0
- package/src/bundle/strategies/openapi-3-0/visitor.cjs +97 -82
- package/src/bundle/strategies/openapi-3-0/visitor.mjs +97 -82
- package/src/bundle/strategies/openapi-3-1/visitor.cjs +5 -0
- package/src/bundle/strategies/openapi-3-1/visitor.mjs +5 -0
- package/src/dereference/strategies/asyncapi-2/visitor.cjs +5 -0
- package/src/dereference/strategies/asyncapi-2/visitor.mjs +5 -0
- package/src/dereference/strategies/openapi-2/visitor.cjs +10 -0
- package/src/dereference/strategies/openapi-2/visitor.mjs +10 -0
- package/src/dereference/strategies/openapi-3-0/visitor.cjs +5 -0
- package/src/dereference/strategies/openapi-3-0/visitor.mjs +5 -0
- package/src/dereference/strategies/openapi-3-1/visitor.cjs +5 -0
- package/src/dereference/strategies/openapi-3-1/visitor.mjs +5 -0
- package/src/dereference/strategies/overlay-1/extends.cjs +52 -28
- package/src/dereference/strategies/overlay-1/extends.mjs +52 -28
- package/src/dereference/strategies/overlay-1/index.cjs +22 -10
- package/src/dereference/strategies/overlay-1/index.mjs +23 -11
- package/src/dereference/strategies/overlay-1/visitor.cjs +27 -2
- package/src/dereference/strategies/overlay-1/visitor.mjs +27 -3
- package/src/parse/parsers/overlay-json-1/extends.cjs +33 -13
- package/src/parse/parsers/overlay-json-1/extends.mjs +33 -14
- package/types/bundle/strategies/openapi-3-0/visitor.d.ts +8 -3
- package/types/dereference/strategies/overlay-1/visitor.d.ts +1 -1
|
@@ -194,6 +194,11 @@ class OpenAPI3_0DereferenceVisitor {
|
|
|
194
194
|
async ReferenceElement(path) {
|
|
195
195
|
const referencingElement = path.node;
|
|
196
196
|
|
|
197
|
+
// ignore Reference Object without a string $ref, e.g. an empty YAML value
|
|
198
|
+
if (!isStringElement(referencingElement.$ref)) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
197
202
|
// skip current referencing element as it's already been access
|
|
198
203
|
if (this.indirections.includes(referencingElement)) {
|
|
199
204
|
path.skip();
|
|
@@ -228,6 +228,11 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
228
228
|
async ReferenceElement(path) {
|
|
229
229
|
const referencingElement = path.node;
|
|
230
230
|
|
|
231
|
+
// ignore Reference Object without a string $ref, e.g. an empty YAML value
|
|
232
|
+
if (!(0, _apidomDatamodel.isStringElement)(referencingElement.$ref)) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
231
236
|
// skip current referencing element as it's already been accessed
|
|
232
237
|
if (this.indirections.includes(referencingElement)) {
|
|
233
238
|
path.skip();
|
|
@@ -219,6 +219,11 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
219
219
|
async ReferenceElement(path) {
|
|
220
220
|
const referencingElement = path.node;
|
|
221
221
|
|
|
222
|
+
// ignore Reference Object without a string $ref, e.g. an empty YAML value
|
|
223
|
+
if (!isStringElement(referencingElement.$ref)) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
222
227
|
// skip current referencing element as it's already been accessed
|
|
223
228
|
if (this.indirections.includes(referencingElement)) {
|
|
224
229
|
path.skip();
|
|
@@ -13,6 +13,8 @@ var _File = _interopRequireDefault(require("../../../File.cjs"));
|
|
|
13
13
|
var url = _interopRequireWildcard(require("../../../util/url.cjs"));
|
|
14
14
|
var _util = require("../../../options/util.cjs");
|
|
15
15
|
var _index = _interopRequireWildcard(require("../../index.cjs"));
|
|
16
|
+
const STRATEGY_NAME = 'overlay-1';
|
|
17
|
+
|
|
16
18
|
/**
|
|
17
19
|
* Dereferences the `extends` target document from an Overlay document's ParseResult.
|
|
18
20
|
*
|
|
@@ -54,39 +56,61 @@ async function dereferenceExtends(parseResult, parseResultRetrievalURI, options)
|
|
|
54
56
|
const extendsDereferenceResult = new _apidomDatamodel.ParseResultElement();
|
|
55
57
|
extendsDereferenceResult.classes.push('extends');
|
|
56
58
|
extendsDereferenceResult.setMetaProperty('retrievalURI', retrievalURI);
|
|
57
|
-
try {
|
|
58
|
-
let extendsDereferenced;
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
},
|
|
68
|
-
resolve: {
|
|
69
|
-
baseURI: retrievalURI
|
|
70
|
-
}
|
|
71
|
-
}));
|
|
72
|
-
} else {
|
|
73
|
-
// no existing parse result - fetch, parse, and dereference
|
|
74
|
-
extendsDereferenced = await (0, _index.default)(retrievalURI, (0, _util.merge)(options, {
|
|
75
|
-
parse: {
|
|
76
|
-
mediaType: 'text/plain' // allow parser plugin detection
|
|
60
|
+
// URIs of the Overlays currently being dereferenced, from the root down to the current one
|
|
61
|
+
const ancestors = [...(options?.dereference?.strategyOpts?.[STRATEGY_NAME]?.extendsAncestors ?? []), file.uri];
|
|
62
|
+
const nestedOptions = {
|
|
63
|
+
dereference: {
|
|
64
|
+
strategyOpts: {
|
|
65
|
+
[STRATEGY_NAME]: {
|
|
66
|
+
extendsAncestors: ancestors
|
|
77
67
|
}
|
|
78
|
-
}
|
|
68
|
+
}
|
|
79
69
|
}
|
|
70
|
+
};
|
|
80
71
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
} catch (error) {
|
|
86
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
87
|
-
const annotation = new _apidomDatamodel.AnnotationElement(`Error dereferencing extends target "${retrievalURI}": ${message}`);
|
|
88
|
-
annotation.classes.push('error');
|
|
72
|
+
// skip if the target is still being dereferenced higher up the chain (cycle detection)
|
|
73
|
+
if (ancestors.includes(retrievalURI)) {
|
|
74
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Extends target "${retrievalURI}" has already been visited. Skipping to prevent cycle`);
|
|
75
|
+
annotation.classes.push('warning');
|
|
89
76
|
extendsDereferenceResult.push(annotation);
|
|
77
|
+
} else {
|
|
78
|
+
try {
|
|
79
|
+
let extendsDereferenced;
|
|
80
|
+
|
|
81
|
+
// check if extends was already parsed (e.g., during parse phase with extends: true)
|
|
82
|
+
const existingParseResult = extendsElement.meta.get('parseResult');
|
|
83
|
+
if ((0, _apidomDatamodel.isParseResultElement)(existingParseResult)) {
|
|
84
|
+
// use existing parsed result - just dereference it (no re-fetch/re-parse)
|
|
85
|
+
extendsDereferenced = await (0, _index.dereferenceApiDOM)(existingParseResult, (0, _util.merge)(options, {
|
|
86
|
+
...nestedOptions,
|
|
87
|
+
parse: {
|
|
88
|
+
mediaType: 'text/plain' // allow dereference strategy detection via ApiDOM inspection
|
|
89
|
+
},
|
|
90
|
+
resolve: {
|
|
91
|
+
baseURI: retrievalURI
|
|
92
|
+
}
|
|
93
|
+
}));
|
|
94
|
+
} else {
|
|
95
|
+
// no existing parse result - fetch, parse, and dereference
|
|
96
|
+
extendsDereferenced = await (0, _index.default)(retrievalURI, (0, _util.merge)(options, {
|
|
97
|
+
...nestedOptions,
|
|
98
|
+
parse: {
|
|
99
|
+
mediaType: 'text/plain' // allow parser plugin detection
|
|
100
|
+
}
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// merge dereferenced result into our wrapper
|
|
105
|
+
for (const item of extendsDereferenced) {
|
|
106
|
+
extendsDereferenceResult.push(item);
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
110
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Error dereferencing extends target "${retrievalURI}": ${message}`);
|
|
111
|
+
annotation.classes.push('error');
|
|
112
|
+
extendsDereferenceResult.push(annotation);
|
|
113
|
+
}
|
|
90
114
|
}
|
|
91
115
|
|
|
92
116
|
// always attach result to extends element meta (even on failure - contains annotations)
|
|
@@ -5,6 +5,8 @@ import File from "../../../File.mjs";
|
|
|
5
5
|
import * as url from "../../../util/url.mjs";
|
|
6
6
|
import { merge as mergeOptions } from "../../../options/util.mjs";
|
|
7
7
|
import dereference, { dereferenceApiDOM } from "../../index.mjs";
|
|
8
|
+
const STRATEGY_NAME = 'overlay-1';
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* Dereferences the `extends` target document from an Overlay document's ParseResult.
|
|
10
12
|
*
|
|
@@ -46,39 +48,61 @@ export async function dereferenceExtends(parseResult, parseResultRetrievalURI, o
|
|
|
46
48
|
const extendsDereferenceResult = new ParseResultElement();
|
|
47
49
|
extendsDereferenceResult.classes.push('extends');
|
|
48
50
|
extendsDereferenceResult.setMetaProperty('retrievalURI', retrievalURI);
|
|
49
|
-
try {
|
|
50
|
-
let extendsDereferenced;
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
},
|
|
60
|
-
resolve: {
|
|
61
|
-
baseURI: retrievalURI
|
|
62
|
-
}
|
|
63
|
-
}));
|
|
64
|
-
} else {
|
|
65
|
-
// no existing parse result - fetch, parse, and dereference
|
|
66
|
-
extendsDereferenced = await dereference(retrievalURI, mergeOptions(options, {
|
|
67
|
-
parse: {
|
|
68
|
-
mediaType: 'text/plain' // allow parser plugin detection
|
|
52
|
+
// URIs of the Overlays currently being dereferenced, from the root down to the current one
|
|
53
|
+
const ancestors = [...(options?.dereference?.strategyOpts?.[STRATEGY_NAME]?.extendsAncestors ?? []), file.uri];
|
|
54
|
+
const nestedOptions = {
|
|
55
|
+
dereference: {
|
|
56
|
+
strategyOpts: {
|
|
57
|
+
[STRATEGY_NAME]: {
|
|
58
|
+
extendsAncestors: ancestors
|
|
69
59
|
}
|
|
70
|
-
}
|
|
60
|
+
}
|
|
71
61
|
}
|
|
62
|
+
};
|
|
72
63
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
} catch (error) {
|
|
78
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
79
|
-
const annotation = new AnnotationElement(`Error dereferencing extends target "${retrievalURI}": ${message}`);
|
|
80
|
-
annotation.classes.push('error');
|
|
64
|
+
// skip if the target is still being dereferenced higher up the chain (cycle detection)
|
|
65
|
+
if (ancestors.includes(retrievalURI)) {
|
|
66
|
+
const annotation = new AnnotationElement(`Extends target "${retrievalURI}" has already been visited. Skipping to prevent cycle`);
|
|
67
|
+
annotation.classes.push('warning');
|
|
81
68
|
extendsDereferenceResult.push(annotation);
|
|
69
|
+
} else {
|
|
70
|
+
try {
|
|
71
|
+
let extendsDereferenced;
|
|
72
|
+
|
|
73
|
+
// check if extends was already parsed (e.g., during parse phase with extends: true)
|
|
74
|
+
const existingParseResult = extendsElement.meta.get('parseResult');
|
|
75
|
+
if (isParseResultElement(existingParseResult)) {
|
|
76
|
+
// use existing parsed result - just dereference it (no re-fetch/re-parse)
|
|
77
|
+
extendsDereferenced = await dereferenceApiDOM(existingParseResult, mergeOptions(options, {
|
|
78
|
+
...nestedOptions,
|
|
79
|
+
parse: {
|
|
80
|
+
mediaType: 'text/plain' // allow dereference strategy detection via ApiDOM inspection
|
|
81
|
+
},
|
|
82
|
+
resolve: {
|
|
83
|
+
baseURI: retrievalURI
|
|
84
|
+
}
|
|
85
|
+
}));
|
|
86
|
+
} else {
|
|
87
|
+
// no existing parse result - fetch, parse, and dereference
|
|
88
|
+
extendsDereferenced = await dereference(retrievalURI, mergeOptions(options, {
|
|
89
|
+
...nestedOptions,
|
|
90
|
+
parse: {
|
|
91
|
+
mediaType: 'text/plain' // allow parser plugin detection
|
|
92
|
+
}
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// merge dereferenced result into our wrapper
|
|
97
|
+
for (const item of extendsDereferenced) {
|
|
98
|
+
extendsDereferenceResult.push(item);
|
|
99
|
+
}
|
|
100
|
+
} catch (error) {
|
|
101
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
102
|
+
const annotation = new AnnotationElement(`Error dereferencing extends target "${retrievalURI}": ${message}`);
|
|
103
|
+
annotation.classes.push('error');
|
|
104
|
+
extendsDereferenceResult.push(annotation);
|
|
105
|
+
}
|
|
82
106
|
}
|
|
83
107
|
|
|
84
108
|
// always attach result to extends element meta (even on failure - contains annotations)
|
|
@@ -74,25 +74,37 @@ class Overlay1DereferenceStrategy extends _DereferenceStrategy.default {
|
|
|
74
74
|
})).forEach(ref => mutableRefSet.add(ref));
|
|
75
75
|
reference = mutableRefSet.find(ref => ref.uri === file.uri);
|
|
76
76
|
}
|
|
77
|
-
const dereferencedElement = reference.value;
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Reusable Action References only occur within `actions`, so the root element is never
|
|
81
|
-
* replaced and traversing it keeps reported locations relative to the document.
|
|
82
|
-
*/
|
|
83
77
|
const visitor = new _visitor.default({
|
|
84
78
|
reference,
|
|
85
79
|
options
|
|
86
80
|
});
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
81
|
+
/**
|
|
82
|
+
* The reference for the dereferenced file is traversed rather than the refSet's root
|
|
83
|
+
* reference: dereferencing the extends target shares the refSet, whose root is then
|
|
84
|
+
* the extending Overlay. The reference may hold a lone element.
|
|
85
|
+
*/
|
|
86
|
+
let dereferencedElement = reference.value;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A ParseResult may also carry the extends target parsed in the parse phase, whose
|
|
90
|
+
* references belong to that document, so only its result is traversed. Reusable Action
|
|
91
|
+
* References only occur within `actions`, so the result itself is never replaced.
|
|
92
|
+
*/
|
|
93
|
+
if ((0, _apidomDatamodel.isParseResultElement)(dereferencedElement)) {
|
|
94
|
+
await (0, _apidomTraverse.traverseAsync)(dereferencedElement.result, visitor, {
|
|
95
|
+
mutable: true
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
dereferencedElement = await (0, _apidomTraverse.traverseAsync)(dereferencedElement, visitor, {
|
|
99
|
+
mutable: true
|
|
100
|
+
});
|
|
101
|
+
}
|
|
90
102
|
|
|
91
103
|
/**
|
|
92
104
|
* Dereference extends target if option is enabled.
|
|
93
105
|
*/
|
|
94
106
|
const shouldDereferenceExtends = options?.dereference?.strategyOpts?.[this.name]?.extends ?? options?.dereference?.strategyOpts?.extends;
|
|
95
|
-
if (shouldDereferenceExtends) {
|
|
107
|
+
if (shouldDereferenceExtends && (0, _apidomDatamodel.isParseResultElement)(dereferencedElement)) {
|
|
96
108
|
await (0, _extends.dereferenceExtends)(dereferencedElement, reference.uri, options);
|
|
97
109
|
}
|
|
98
110
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cloneDeep } from '@speclynx/apidom-datamodel';
|
|
1
|
+
import { cloneDeep, isParseResultElement } from '@speclynx/apidom-datamodel';
|
|
2
2
|
import { isOverlay1Element, mediaTypes } from '@speclynx/apidom-ns-overlay-1';
|
|
3
3
|
import { traverseAsync } from '@speclynx/apidom-traverse';
|
|
4
4
|
import DereferenceStrategy from "../DereferenceStrategy.mjs";
|
|
@@ -54,25 +54,37 @@ class Overlay1DereferenceStrategy extends DereferenceStrategy {
|
|
|
54
54
|
})).forEach(ref => mutableRefSet.add(ref));
|
|
55
55
|
reference = mutableRefSet.find(ref => ref.uri === file.uri);
|
|
56
56
|
}
|
|
57
|
-
const dereferencedElement = reference.value;
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Reusable Action References only occur within `actions`, so the root element is never
|
|
61
|
-
* replaced and traversing it keeps reported locations relative to the document.
|
|
62
|
-
*/
|
|
63
57
|
const visitor = new Overlay1DereferenceVisitor({
|
|
64
58
|
reference,
|
|
65
59
|
options
|
|
66
60
|
});
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
61
|
+
/**
|
|
62
|
+
* The reference for the dereferenced file is traversed rather than the refSet's root
|
|
63
|
+
* reference: dereferencing the extends target shares the refSet, whose root is then
|
|
64
|
+
* the extending Overlay. The reference may hold a lone element.
|
|
65
|
+
*/
|
|
66
|
+
let dereferencedElement = reference.value;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A ParseResult may also carry the extends target parsed in the parse phase, whose
|
|
70
|
+
* references belong to that document, so only its result is traversed. Reusable Action
|
|
71
|
+
* References only occur within `actions`, so the result itself is never replaced.
|
|
72
|
+
*/
|
|
73
|
+
if (isParseResultElement(dereferencedElement)) {
|
|
74
|
+
await traverseAsync(dereferencedElement.result, visitor, {
|
|
75
|
+
mutable: true
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
dereferencedElement = await traverseAsync(dereferencedElement, visitor, {
|
|
79
|
+
mutable: true
|
|
80
|
+
});
|
|
81
|
+
}
|
|
70
82
|
|
|
71
83
|
/**
|
|
72
84
|
* Dereference extends target if option is enabled.
|
|
73
85
|
*/
|
|
74
86
|
const shouldDereferenceExtends = options?.dereference?.strategyOpts?.[this.name]?.extends ?? options?.dereference?.strategyOpts?.extends;
|
|
75
|
-
if (shouldDereferenceExtends) {
|
|
87
|
+
if (shouldDereferenceExtends && isParseResultElement(dereferencedElement)) {
|
|
76
88
|
await dereferenceExtends(dereferencedElement, reference.uri, options);
|
|
77
89
|
}
|
|
78
90
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopRequireWildcard").default;
|
|
3
4
|
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
|
|
4
5
|
Object.defineProperty(exports, "__esModule", {
|
|
5
6
|
value: true
|
|
@@ -9,8 +10,10 @@ var _apidomDatamodel = require("@speclynx/apidom-datamodel");
|
|
|
9
10
|
var _apidomCore = require("@speclynx/apidom-core");
|
|
10
11
|
var _apidomError = require("@speclynx/apidom-error");
|
|
11
12
|
var _apidomJsonPointer = require("@speclynx/apidom-json-pointer");
|
|
13
|
+
var _apidomTraverse = require("@speclynx/apidom-traverse");
|
|
12
14
|
var _apidomNsOverlay = require("@speclynx/apidom-ns-overlay-1");
|
|
13
15
|
var _UnresolvableReferenceError = _interopRequireDefault(require("../../../errors/UnresolvableReferenceError.cjs"));
|
|
16
|
+
var url = _interopRequireWildcard(require("../../../util/url.cjs"));
|
|
14
17
|
/**
|
|
15
18
|
* @public
|
|
16
19
|
*/
|
|
@@ -37,7 +40,14 @@ class Overlay1DereferenceVisitor {
|
|
|
37
40
|
return;
|
|
38
41
|
}
|
|
39
42
|
const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
|
|
40
|
-
|
|
43
|
+
// the reference may hold a lone element; same-document references resolve against the
|
|
44
|
+
// document registered in the refSet under the reference's URI without fragment
|
|
45
|
+
const {
|
|
46
|
+
refSet
|
|
47
|
+
} = this.reference;
|
|
48
|
+
const documentURI = url.stripHash(this.reference.uri);
|
|
49
|
+
const documentReference = refSet.find(ref => ref.uri === documentURI) ?? this.reference;
|
|
50
|
+
const overlayElement = documentReference.value.result;
|
|
41
51
|
const actionElement = new _apidomNsOverlay.ActionElement();
|
|
42
52
|
try {
|
|
43
53
|
if (typeof $ref !== 'string') {
|
|
@@ -108,11 +118,26 @@ class Overlay1DereferenceVisitor {
|
|
|
108
118
|
const {
|
|
109
119
|
continueOnError
|
|
110
120
|
} = this.options.dereference;
|
|
121
|
+
|
|
122
|
+
// find element location by identity in the document tree;
|
|
123
|
+
// falls back to the visitor path when the document doesn't contain the element
|
|
124
|
+
let location;
|
|
125
|
+
if ((0, _apidomDatamodel.isElement)(overlayElement)) {
|
|
126
|
+
(0, _apidomTraverse.traverse)(overlayElement, {
|
|
127
|
+
enter: p => {
|
|
128
|
+
if (p.node === referencingElement) {
|
|
129
|
+
location = p.formatPath();
|
|
130
|
+
p.stop();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
location ??= path.formatPath();
|
|
111
136
|
const unresolvedError = new _UnresolvableReferenceError.default(`Error while dereferencing Reusable Action Reference "${$ref}": ${error.message}`, {
|
|
112
137
|
cause: error,
|
|
113
138
|
type: referencingElement.element,
|
|
114
139
|
uri: this.reference.uri,
|
|
115
|
-
location
|
|
140
|
+
location,
|
|
116
141
|
codeFrame: (0, _apidomCore.toYAML)(referencingElement),
|
|
117
142
|
refFieldName: '$ref',
|
|
118
143
|
refFieldValue: $ref,
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { SourceMapElement, cloneDeep, hasElementSourceMap } from '@speclynx/apidom-datamodel';
|
|
1
|
+
import { SourceMapElement, cloneDeep, hasElementSourceMap, isElement } from '@speclynx/apidom-datamodel';
|
|
2
2
|
import { toValue, toYAML } from '@speclynx/apidom-core';
|
|
3
3
|
import { ApiDOMStructuredError } from '@speclynx/apidom-error';
|
|
4
4
|
import { evaluate as jsonPointerEvaluate, URIFragmentIdentifier } from '@speclynx/apidom-json-pointer';
|
|
5
|
+
import { traverse } from '@speclynx/apidom-traverse';
|
|
5
6
|
import { ActionElement, isActionElement, isReusableActionElement } from '@speclynx/apidom-ns-overlay-1';
|
|
6
7
|
import UnresolvableReferenceError from "../../../errors/UnresolvableReferenceError.mjs";
|
|
8
|
+
import * as url from "../../../util/url.mjs";
|
|
7
9
|
/**
|
|
8
10
|
* @public
|
|
9
11
|
*/
|
|
@@ -29,7 +31,14 @@ class Overlay1DereferenceVisitor {
|
|
|
29
31
|
return;
|
|
30
32
|
}
|
|
31
33
|
const $ref = toValue(referencingElement.$ref);
|
|
32
|
-
|
|
34
|
+
// the reference may hold a lone element; same-document references resolve against the
|
|
35
|
+
// document registered in the refSet under the reference's URI without fragment
|
|
36
|
+
const {
|
|
37
|
+
refSet
|
|
38
|
+
} = this.reference;
|
|
39
|
+
const documentURI = url.stripHash(this.reference.uri);
|
|
40
|
+
const documentReference = refSet.find(ref => ref.uri === documentURI) ?? this.reference;
|
|
41
|
+
const overlayElement = documentReference.value.result;
|
|
33
42
|
const actionElement = new ActionElement();
|
|
34
43
|
try {
|
|
35
44
|
if (typeof $ref !== 'string') {
|
|
@@ -100,11 +109,26 @@ class Overlay1DereferenceVisitor {
|
|
|
100
109
|
const {
|
|
101
110
|
continueOnError
|
|
102
111
|
} = this.options.dereference;
|
|
112
|
+
|
|
113
|
+
// find element location by identity in the document tree;
|
|
114
|
+
// falls back to the visitor path when the document doesn't contain the element
|
|
115
|
+
let location;
|
|
116
|
+
if (isElement(overlayElement)) {
|
|
117
|
+
traverse(overlayElement, {
|
|
118
|
+
enter: p => {
|
|
119
|
+
if (p.node === referencingElement) {
|
|
120
|
+
location = p.formatPath();
|
|
121
|
+
p.stop();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
location ??= path.formatPath();
|
|
103
127
|
const unresolvedError = new UnresolvableReferenceError(`Error while dereferencing Reusable Action Reference "${$ref}": ${error.message}`, {
|
|
104
128
|
cause: error,
|
|
105
129
|
type: referencingElement.element,
|
|
106
130
|
uri: this.reference.uri,
|
|
107
|
-
location
|
|
131
|
+
location,
|
|
108
132
|
codeFrame: toYAML(referencingElement),
|
|
109
133
|
refFieldName: '$ref',
|
|
110
134
|
refFieldValue: $ref,
|
|
@@ -13,6 +13,9 @@ var _File = _interopRequireDefault(require("../../../File.cjs"));
|
|
|
13
13
|
var url = _interopRequireWildcard(require("../../../util/url.cjs"));
|
|
14
14
|
var _util = require("../../../options/util.cjs");
|
|
15
15
|
var _index = _interopRequireDefault(require("../../index.cjs"));
|
|
16
|
+
// shared key for recursion state (works across JSON/YAML parsers)
|
|
17
|
+
const OVERLAY_RECURSION_KEY = 'overlay-1';
|
|
18
|
+
|
|
16
19
|
/**
|
|
17
20
|
* Parses the `extends` target document from an Overlay document's ParseResult.
|
|
18
21
|
*
|
|
@@ -49,21 +52,38 @@ async function parseExtends(parseResult, parseResultRetrievalURI, options) {
|
|
|
49
52
|
const extendsParseResult = new _apidomDatamodel.ParseResultElement();
|
|
50
53
|
extendsParseResult.classes.push('extends');
|
|
51
54
|
extendsParseResult.setMetaProperty('retrievalURI', retrievalURI);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
|
|
56
|
+
// URIs of the Overlays currently being parsed, from the root down to the current one
|
|
57
|
+
const ancestors = [...(options?.parse?.parserOpts?.[OVERLAY_RECURSION_KEY]?.extendsAncestors ?? []), file.uri];
|
|
58
|
+
|
|
59
|
+
// skip if the target is still being parsed higher up the chain (cycle detection)
|
|
60
|
+
if (ancestors.includes(retrievalURI)) {
|
|
61
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Extends target "${retrievalURI}" has already been visited. Skipping to prevent cycle`);
|
|
62
|
+
annotation.classes.push('warning');
|
|
63
|
+
extendsParseResult.push(annotation);
|
|
64
|
+
} else {
|
|
65
|
+
try {
|
|
66
|
+
const targetParseResult = await (0, _index.default)(retrievalURI, (0, _util.merge)(options, {
|
|
67
|
+
parse: {
|
|
68
|
+
mediaType: 'text/plain',
|
|
69
|
+
// force auto-detection
|
|
70
|
+
parserOpts: {
|
|
71
|
+
[OVERLAY_RECURSION_KEY]: {
|
|
72
|
+
extendsAncestors: ancestors
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}));
|
|
77
|
+
// merge parsed result into our wrapper
|
|
78
|
+
for (const item of targetParseResult) {
|
|
79
|
+
extendsParseResult.push(item);
|
|
56
80
|
}
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
81
|
+
} catch (error) {
|
|
82
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
83
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Error parsing extends target "${retrievalURI}": ${message}`);
|
|
84
|
+
annotation.classes.push('error');
|
|
85
|
+
extendsParseResult.push(annotation);
|
|
61
86
|
}
|
|
62
|
-
} catch (error) {
|
|
63
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
64
|
-
const annotation = new _apidomDatamodel.AnnotationElement(`Error parsing extends target "${retrievalURI}": ${message}`);
|
|
65
|
-
annotation.classes.push('error');
|
|
66
|
-
extendsParseResult.push(annotation);
|
|
67
87
|
}
|
|
68
88
|
|
|
69
89
|
// always attach result to extends element meta (even on failure - contains annotations)
|
|
@@ -4,7 +4,9 @@ import { toValue } from '@speclynx/apidom-core';
|
|
|
4
4
|
import File from "../../../File.mjs";
|
|
5
5
|
import * as url from "../../../util/url.mjs";
|
|
6
6
|
import { merge as mergeOptions } from "../../../options/util.mjs";
|
|
7
|
-
import parse from "../../index.mjs";
|
|
7
|
+
import parse from "../../index.mjs"; // shared key for recursion state (works across JSON/YAML parsers)
|
|
8
|
+
const OVERLAY_RECURSION_KEY = 'overlay-1';
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* Parses the `extends` target document from an Overlay document's ParseResult.
|
|
10
12
|
*
|
|
@@ -41,21 +43,38 @@ export async function parseExtends(parseResult, parseResultRetrievalURI, options
|
|
|
41
43
|
const extendsParseResult = new ParseResultElement();
|
|
42
44
|
extendsParseResult.classes.push('extends');
|
|
43
45
|
extendsParseResult.setMetaProperty('retrievalURI', retrievalURI);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
|
|
47
|
+
// URIs of the Overlays currently being parsed, from the root down to the current one
|
|
48
|
+
const ancestors = [...(options?.parse?.parserOpts?.[OVERLAY_RECURSION_KEY]?.extendsAncestors ?? []), file.uri];
|
|
49
|
+
|
|
50
|
+
// skip if the target is still being parsed higher up the chain (cycle detection)
|
|
51
|
+
if (ancestors.includes(retrievalURI)) {
|
|
52
|
+
const annotation = new AnnotationElement(`Extends target "${retrievalURI}" has already been visited. Skipping to prevent cycle`);
|
|
53
|
+
annotation.classes.push('warning');
|
|
54
|
+
extendsParseResult.push(annotation);
|
|
55
|
+
} else {
|
|
56
|
+
try {
|
|
57
|
+
const targetParseResult = await parse(retrievalURI, mergeOptions(options, {
|
|
58
|
+
parse: {
|
|
59
|
+
mediaType: 'text/plain',
|
|
60
|
+
// force auto-detection
|
|
61
|
+
parserOpts: {
|
|
62
|
+
[OVERLAY_RECURSION_KEY]: {
|
|
63
|
+
extendsAncestors: ancestors
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}));
|
|
68
|
+
// merge parsed result into our wrapper
|
|
69
|
+
for (const item of targetParseResult) {
|
|
70
|
+
extendsParseResult.push(item);
|
|
48
71
|
}
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
72
|
+
} catch (error) {
|
|
73
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
74
|
+
const annotation = new AnnotationElement(`Error parsing extends target "${retrievalURI}": ${message}`);
|
|
75
|
+
annotation.classes.push('error');
|
|
76
|
+
extendsParseResult.push(annotation);
|
|
53
77
|
}
|
|
54
|
-
} catch (error) {
|
|
55
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
56
|
-
const annotation = new AnnotationElement(`Error parsing extends target "${retrievalURI}": ${message}`);
|
|
57
|
-
annotation.classes.push('error');
|
|
58
|
-
extendsParseResult.push(annotation);
|
|
59
78
|
}
|
|
60
79
|
|
|
61
80
|
// always attach result to extends element meta (even on failure - contains annotations)
|
|
@@ -110,9 +110,14 @@ declare class OpenAPI3_0BundleVisitor {
|
|
|
110
110
|
* Path Item Object is not a Components Object field in OpenAPI 3.0
|
|
111
111
|
* (unlike 3.1 which has `components.pathItems`). An external Path Item
|
|
112
112
|
* reference therefore cannot be hoisted; it is inlined in place instead,
|
|
113
|
-
* after bundling its own external references.
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
* after bundling its own external references. It is inlined on leave, once
|
|
114
|
+
* the sibling fields of the referencing Path Item have been bundled, so the
|
|
115
|
+
* traversal doesn't descend into the inlined copy and inline the `$ref` left
|
|
116
|
+
* in it to break a cycle again.
|
|
117
|
+
*/
|
|
118
|
+
readonly PathItemElement: {
|
|
119
|
+
leave: (path: Path<Element>) => Promise<void>;
|
|
120
|
+
};
|
|
116
121
|
/**
|
|
117
122
|
* Example Object `externalValue` points at arbitrary external content that
|
|
118
123
|
* has no place in the Components Object. The referenced value is inlined into
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Element } from '@speclynx/apidom-datamodel';
|
|
2
|
-
import type
|
|
2
|
+
import { type Path } from '@speclynx/apidom-traverse';
|
|
3
3
|
import Reference from '../../../Reference.ts';
|
|
4
4
|
import type { ReferenceOptions } from '../../../options/index.ts';
|
|
5
5
|
/**
|