-
Notifications
You must be signed in to change notification settings - Fork 217
Expand file tree
/
Copy pathref-utils.ts
More file actions
149 lines (125 loc) · 4.3 KB
/
ref-utils.ts
File metadata and controls
149 lines (125 loc) · 4.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import * as path from 'node:path';
import type { Source } from './resolve.js';
import type { Oas3Example, OasRef } from './typings/openapi.js';
import { getOwn } from './utils/get-own.js';
import { isPlainObject } from './utils/is-plain-object.js';
import { isTruthy } from './utils/is-truthy.js';
import type { ResolveResult, UserContext } from './walk.js';
export function joinPointer(base: string, key: string | number) {
if (base === '') base = '#/';
return base[base.length - 1] === '/' ? base + key : base + '/' + key;
}
export function isRef(node: unknown): node is OasRef {
return isPlainObject(node) && typeof node.$ref === 'string';
}
export function isExternalValue(node: unknown): node is Oas3Example & { externalValue: string } {
return isPlainObject(node) && typeof node.externalValue === 'string';
}
export function refHasSibling<Prop extends string>(
node: OasRef,
propName: Prop
): node is OasRef & Record<Prop, unknown> {
return getOwn(node, propName) !== undefined;
}
export class Location {
constructor(
public source: Source,
public pointer: string
) {}
child(components: (string | number)[] | string | number) {
return new Location(
this.source,
joinPointer(
this.pointer,
(Array.isArray(components) ? components : [components]).map(escapePointerFragment).join('/')
)
);
}
key() {
return { ...this, reportOnKey: true };
}
get absolutePointer() {
return this.source.absoluteRef + (this.pointer === '#/' ? '' : this.pointer);
}
}
export function unescapePointerFragment(fragment: string): string {
const unescaped =
fragment.indexOf('~') === -1 ? fragment : fragment.replaceAll('~1', '/').replaceAll('~0', '~');
try {
return decodeURIComponent(unescaped);
} catch (e) {
return unescaped;
}
}
export function escapePointerFragment<T extends string | number>(fragment: T): T {
if (typeof fragment === 'number') return fragment;
if (fragment.indexOf('/') === -1 && fragment.indexOf('~') === -1) return fragment;
return fragment.replaceAll('~', '~0').replaceAll('/', '~1') as T;
}
export function parseRef(ref: string): { uri: string | null; pointer: string[] } {
const [uri, pointer = ''] = ref.split('#/');
return {
uri: (uri.endsWith('#') ? uri.slice(0, -1) : uri) || null,
pointer: parsePointer(pointer),
};
}
export function parsePointer(pointer: string) {
return pointer.split('/').map(unescapePointerFragment).filter(isTruthy);
}
export function pointerBaseName(pointer: string) {
const parts = pointer.split('/');
return parts[parts.length - 1];
}
export function refBaseName(ref: string) {
// eslint-disable-next-line no-useless-escape
const parts = ref.split(/[\/\\]/); // split by '\' and '/'
return parts[parts.length - 1].replace(/\.[^.]+$/, ''); // replace extension with empty string
}
export function isAbsoluteUrl(ref: string) {
return (
ref.startsWith('http://') ||
ref.startsWith('https://') ||
ref.startsWith('file://') ||
ref.startsWith('data:')
);
}
export function getDir(filePath: string): string {
if (!path.extname(filePath)) {
return filePath;
}
return isAbsoluteUrl(filePath)
? filePath.substring(0, filePath.lastIndexOf('/'))
: path.dirname(filePath);
}
export function resolvePath(base: string, relative: string): string {
if (isAbsoluteUrl(base)) {
return new URL(relative, base.endsWith('/') ? base : `${base}/`).href;
}
return path.resolve(base, relative);
}
export function isMappingRef(mapping: string) {
// TODO: proper detection of mapping refs
return (
typeof mapping === 'string' &&
(mapping.startsWith('#') ||
isAbsoluteUrl(mapping) ||
mapping.startsWith('./') ||
mapping.startsWith('../') ||
mapping.indexOf('/') > -1 ||
/\.(ya?ml|json)$/i.test(mapping))
);
}
export function isAnchor(ref: string) {
return /^#[A-Za-z][A-Za-z0-9\-_:.]*$/.test(ref);
}
export function replaceRef(ref: OasRef, resolved: ResolveResult<any>, ctx: UserContext) {
if (!isPlainObject(resolved.node)) {
ctx.parent[ctx.key] = resolved.node;
} else {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete ref.$ref;
const obj = Object.assign({}, resolved.node, ref);
Object.assign(ref, obj); // assign ref itself again so ref fields take precedence
}
}