@redocly/openapi-core 1.0.0-beta.102 → 1.0.0-beta.105
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/__tests__/utils.ts +3 -1
- package/lib/config/config.d.ts +4 -3
- package/lib/config/config.js +23 -16
- package/lib/config/load.d.ts +1 -1
- package/lib/config/load.js +15 -3
- package/lib/config/rules.d.ts +1 -1
- package/lib/config/types.d.ts +4 -2
- package/lib/decorators/common/filters/filter-helper.d.ts +3 -0
- package/lib/decorators/common/filters/filter-helper.js +67 -0
- package/lib/decorators/common/filters/filter-in.d.ts +2 -0
- package/lib/decorators/common/filters/filter-in.js +17 -0
- package/lib/decorators/common/filters/filter-out.d.ts +2 -0
- package/lib/decorators/common/filters/filter-out.js +17 -0
- package/lib/decorators/oas2/index.d.ts +2 -0
- package/lib/decorators/oas2/index.js +5 -1
- package/lib/decorators/oas3/index.d.ts +2 -0
- package/lib/decorators/oas3/index.js +5 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.js +2 -1
- package/lib/lint.d.ts +2 -0
- package/lib/lint.js +2 -2
- package/lib/redocly/registry-api-types.d.ts +2 -0
- package/lib/redocly/registry-api.d.ts +1 -1
- package/lib/redocly/registry-api.js +3 -1
- package/lib/rules/ajv.d.ts +1 -1
- package/lib/rules/ajv.js +1 -1
- package/lib/rules/common/assertions/asserts.d.ts +6 -1
- package/lib/rules/common/assertions/asserts.js +81 -51
- package/lib/rules/common/assertions/utils.d.ts +2 -1
- package/lib/rules/common/assertions/utils.js +27 -8
- package/lib/types/redocly-yaml.js +317 -27
- package/lib/utils.d.ts +5 -3
- package/lib/utils.js +15 -2
- package/lib/walk.d.ts +4 -14
- package/lib/walk.js +35 -26
- package/package.json +3 -2
- package/src/__tests__/fixtures/.redocly.lint-ignore.yaml +5 -0
- package/src/__tests__/lint.test.ts +70 -10
- package/src/__tests__/utils.test.ts +42 -1
- package/src/config/__tests__/load.test.ts +8 -2
- package/src/config/config.ts +31 -27
- package/src/config/load.ts +29 -9
- package/src/config/types.ts +6 -5
- package/src/decorators/__tests__/filter-in.test.ts +310 -0
- package/src/decorators/__tests__/filter-out.test.ts +331 -0
- package/src/decorators/common/filters/filter-helper.ts +72 -0
- package/src/decorators/common/filters/filter-in.ts +18 -0
- package/src/decorators/common/filters/filter-out.ts +18 -0
- package/src/decorators/oas2/index.ts +5 -1
- package/src/decorators/oas3/index.ts +5 -1
- package/src/index.ts +2 -1
- package/src/lint.ts +4 -3
- package/src/redocly/registry-api-types.ts +2 -0
- package/src/redocly/registry-api.ts +4 -0
- package/src/rules/ajv.ts +4 -4
- package/src/rules/common/assertions/__tests__/asserts.test.ts +149 -146
- package/src/rules/common/assertions/asserts.ts +97 -52
- package/src/rules/common/assertions/utils.ts +41 -16
- package/src/types/redocly-yaml.ts +322 -34
- package/src/utils.ts +28 -15
- package/src/walk.ts +59 -47
- package/tsconfig.tsbuildinfo +1 -1
package/lib/walk.js
CHANGED
|
@@ -32,6 +32,26 @@ function walkDocument(opts) {
|
|
|
32
32
|
walkNode(document.parsed, rootType, new ref_utils_1.Location(document.source, '#/'), undefined, '');
|
|
33
33
|
function walkNode(node, type, location, parent, key) {
|
|
34
34
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
35
|
+
const resolve = (ref, from = currentLocation.source.absoluteRef) => {
|
|
36
|
+
if (!ref_utils_1.isRef(ref))
|
|
37
|
+
return { location, node: ref };
|
|
38
|
+
const refId = resolve_1.makeRefId(from, ref.$ref);
|
|
39
|
+
const resolvedRef = resolvedRefMap.get(refId);
|
|
40
|
+
if (!resolvedRef) {
|
|
41
|
+
return {
|
|
42
|
+
location: undefined,
|
|
43
|
+
node: undefined,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const { resolved, node, document, nodePointer, error } = resolvedRef;
|
|
47
|
+
const newLocation = resolved
|
|
48
|
+
? new ref_utils_1.Location(document.source, nodePointer)
|
|
49
|
+
: error instanceof resolve_1.YamlParseError
|
|
50
|
+
? new ref_utils_1.Location(error.source, '')
|
|
51
|
+
: undefined;
|
|
52
|
+
return { location: newLocation, node, error };
|
|
53
|
+
};
|
|
54
|
+
const rawLocation = location;
|
|
35
55
|
let currentLocation = location;
|
|
36
56
|
const { node: resolvedNode, location: resolvedLocation, error } = resolve(node);
|
|
37
57
|
const enteredContexts = new Set();
|
|
@@ -44,13 +64,15 @@ function walkDocument(opts) {
|
|
|
44
64
|
visitor(node, {
|
|
45
65
|
report,
|
|
46
66
|
resolve,
|
|
67
|
+
rawNode: node,
|
|
68
|
+
rawLocation,
|
|
47
69
|
location,
|
|
48
70
|
type,
|
|
49
71
|
parent,
|
|
50
72
|
key,
|
|
51
73
|
parentLocations: {},
|
|
52
74
|
oasVersion: ctx.oasVersion,
|
|
53
|
-
getVisitorData: getVisitorDataFn.bind(undefined, ruleId)
|
|
75
|
+
getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
|
|
54
76
|
}, { node: resolvedNode, location: resolvedLocation, error });
|
|
55
77
|
if ((resolvedLocation === null || resolvedLocation === void 0 ? void 0 : resolvedLocation.source.absoluteRef) && ctx.refTypes) {
|
|
56
78
|
ctx.refTypes.set(resolvedLocation === null || resolvedLocation === void 0 ? void 0 : resolvedLocation.source.absoluteRef, type);
|
|
@@ -101,7 +123,7 @@ function walkDocument(opts) {
|
|
|
101
123
|
if (!activatedOn.skipped) {
|
|
102
124
|
visitedBySome = true;
|
|
103
125
|
enteredContexts.add(context);
|
|
104
|
-
const { ignoreNextVisitorsOnNode } = visitWithContext(visit, resolvedNode, context, ruleId, severity);
|
|
126
|
+
const { ignoreNextVisitorsOnNode } = visitWithContext(visit, resolvedNode, node, context, ruleId, severity);
|
|
105
127
|
if (ignoreNextVisitorsOnNode)
|
|
106
128
|
break;
|
|
107
129
|
}
|
|
@@ -173,7 +195,7 @@ function walkDocument(opts) {
|
|
|
173
195
|
}
|
|
174
196
|
for (const { context, visit, ruleId, severity } of currentLeaveVisitors) {
|
|
175
197
|
if (!context.isSkippedLevel && enteredContexts.has(context)) {
|
|
176
|
-
visitWithContext(visit, resolvedNode, context, ruleId, severity);
|
|
198
|
+
visitWithContext(visit, resolvedNode, node, context, ruleId, severity);
|
|
177
199
|
}
|
|
178
200
|
}
|
|
179
201
|
}
|
|
@@ -186,54 +208,41 @@ function walkDocument(opts) {
|
|
|
186
208
|
visitor(node, {
|
|
187
209
|
report,
|
|
188
210
|
resolve,
|
|
211
|
+
rawNode: node,
|
|
212
|
+
rawLocation,
|
|
189
213
|
location,
|
|
190
214
|
type,
|
|
191
215
|
parent,
|
|
192
216
|
key,
|
|
193
217
|
parentLocations: {},
|
|
194
218
|
oasVersion: ctx.oasVersion,
|
|
195
|
-
getVisitorData: getVisitorDataFn.bind(undefined, ruleId)
|
|
219
|
+
getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
|
|
196
220
|
}, { node: resolvedNode, location: resolvedLocation, error });
|
|
197
221
|
}
|
|
198
222
|
}
|
|
199
223
|
}
|
|
200
224
|
// returns true ignores all the next visitors on the specific node
|
|
201
|
-
function visitWithContext(visit, node, context, ruleId, severity) {
|
|
225
|
+
function visitWithContext(visit, resolvedNode, node, context, ruleId, severity) {
|
|
202
226
|
const report = reportFn.bind(undefined, ruleId, severity);
|
|
203
227
|
let ignoreNextVisitorsOnNode = false;
|
|
204
|
-
visit(
|
|
228
|
+
visit(resolvedNode, {
|
|
205
229
|
report,
|
|
206
230
|
resolve,
|
|
231
|
+
rawNode: node,
|
|
207
232
|
location: currentLocation,
|
|
233
|
+
rawLocation,
|
|
208
234
|
type,
|
|
209
235
|
parent,
|
|
210
236
|
key,
|
|
211
237
|
parentLocations: collectParentsLocations(context),
|
|
212
238
|
oasVersion: ctx.oasVersion,
|
|
213
|
-
ignoreNextVisitorsOnNode: () => {
|
|
239
|
+
ignoreNextVisitorsOnNode: () => {
|
|
240
|
+
ignoreNextVisitorsOnNode = true;
|
|
241
|
+
},
|
|
214
242
|
getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
|
|
215
243
|
}, collectParents(context), context);
|
|
216
244
|
return { ignoreNextVisitorsOnNode };
|
|
217
245
|
}
|
|
218
|
-
function resolve(ref, from = currentLocation.source.absoluteRef) {
|
|
219
|
-
if (!ref_utils_1.isRef(ref))
|
|
220
|
-
return { location, node: ref };
|
|
221
|
-
const refId = resolve_1.makeRefId(from, ref.$ref);
|
|
222
|
-
const resolvedRef = resolvedRefMap.get(refId);
|
|
223
|
-
if (!resolvedRef) {
|
|
224
|
-
return {
|
|
225
|
-
location: undefined,
|
|
226
|
-
node: undefined,
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
const { resolved, node, document, nodePointer, error } = resolvedRef;
|
|
230
|
-
const newLocation = resolved
|
|
231
|
-
? new ref_utils_1.Location(document.source, nodePointer)
|
|
232
|
-
: error instanceof resolve_1.YamlParseError
|
|
233
|
-
? new ref_utils_1.Location(error.source, '')
|
|
234
|
-
: undefined;
|
|
235
|
-
return { location: newLocation, node, error };
|
|
236
|
-
}
|
|
237
246
|
function reportFn(ruleId, severity, opts) {
|
|
238
247
|
const loc = opts.location
|
|
239
248
|
? Array.isArray(opts.location)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/openapi-core",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.105",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"engines": {
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"fs": false,
|
|
17
17
|
"path": "path-browserify",
|
|
18
18
|
"os": false,
|
|
19
|
-
"node-fetch": false
|
|
19
|
+
"node-fetch": false,
|
|
20
|
+
"colorette": false
|
|
20
21
|
},
|
|
21
22
|
"homepage": "https://github.com/Redocly/redocly-cli",
|
|
22
23
|
"keywords": [
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
1
2
|
import { outdent } from 'outdent';
|
|
2
3
|
|
|
3
4
|
import { lintFromString, lintConfig, lintDocument } from '../lint';
|
|
@@ -70,7 +71,7 @@ describe('lint', () => {
|
|
|
70
71
|
links:
|
|
71
72
|
color: '#6CC496'
|
|
72
73
|
`,
|
|
73
|
-
''
|
|
74
|
+
''
|
|
74
75
|
);
|
|
75
76
|
const results = await lintConfig({ document });
|
|
76
77
|
|
|
@@ -85,7 +86,7 @@ describe('lint', () => {
|
|
|
85
86
|
},
|
|
86
87
|
],
|
|
87
88
|
"message": "Expected type \`ConfigApis\` (object) but got \`string\`",
|
|
88
|
-
"ruleId": "spec",
|
|
89
|
+
"ruleId": "configuration spec",
|
|
89
90
|
"severity": "error",
|
|
90
91
|
"suggest": Array [],
|
|
91
92
|
},
|
|
@@ -97,8 +98,8 @@ describe('lint', () => {
|
|
|
97
98
|
"source": "",
|
|
98
99
|
},
|
|
99
100
|
],
|
|
100
|
-
"message": "
|
|
101
|
-
"ruleId": "spec",
|
|
101
|
+
"message": "\`layout\` can be one of the following only: \\"stacked\\", \\"three-panel\\".",
|
|
102
|
+
"ruleId": "configuration spec",
|
|
102
103
|
"severity": "error",
|
|
103
104
|
"suggest": Array [],
|
|
104
105
|
},
|
|
@@ -117,12 +118,25 @@ describe('lint', () => {
|
|
|
117
118
|
plugins:
|
|
118
119
|
- './local-plugin.js'
|
|
119
120
|
`,
|
|
120
|
-
''
|
|
121
|
+
''
|
|
121
122
|
);
|
|
122
123
|
const results = await lintConfig({ document });
|
|
123
124
|
|
|
124
125
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
125
126
|
Array [
|
|
127
|
+
Object {
|
|
128
|
+
"location": Array [
|
|
129
|
+
Object {
|
|
130
|
+
"pointer": "#/apis/lint",
|
|
131
|
+
"reportOnKey": true,
|
|
132
|
+
"source": "",
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
"message": "The field \`root\` must be present on this level.",
|
|
136
|
+
"ruleId": "configuration spec",
|
|
137
|
+
"severity": "error",
|
|
138
|
+
"suggest": Array [],
|
|
139
|
+
},
|
|
126
140
|
Object {
|
|
127
141
|
"location": Array [
|
|
128
142
|
Object {
|
|
@@ -132,7 +146,7 @@ describe('lint', () => {
|
|
|
132
146
|
},
|
|
133
147
|
],
|
|
134
148
|
"message": "Property \`plugins\` is not expected here.",
|
|
135
|
-
"ruleId": "spec",
|
|
149
|
+
"ruleId": "configuration spec",
|
|
136
150
|
"severity": "error",
|
|
137
151
|
"suggest": Array [],
|
|
138
152
|
},
|
|
@@ -169,7 +183,7 @@ describe('lint', () => {
|
|
|
169
183
|
type: string
|
|
170
184
|
const: ABC
|
|
171
185
|
`,
|
|
172
|
-
'foobar.yaml'
|
|
186
|
+
'foobar.yaml'
|
|
173
187
|
);
|
|
174
188
|
|
|
175
189
|
const results = await lintDocument({
|
|
@@ -186,10 +200,10 @@ describe('lint', () => {
|
|
|
186
200
|
outdent`
|
|
187
201
|
openapi: 3.0
|
|
188
202
|
`,
|
|
189
|
-
''
|
|
203
|
+
''
|
|
190
204
|
);
|
|
191
205
|
expect(() => detectOpenAPI(testDocument.parsed)).toThrow(
|
|
192
|
-
`Invalid OpenAPI version: should be a string but got "number"
|
|
206
|
+
`Invalid OpenAPI version: should be a string but got "number"`
|
|
193
207
|
);
|
|
194
208
|
});
|
|
195
209
|
|
|
@@ -218,7 +232,7 @@ describe('lint', () => {
|
|
|
218
232
|
'200':
|
|
219
233
|
description: callback successfully processed
|
|
220
234
|
`,
|
|
221
|
-
'foobar.yaml'
|
|
235
|
+
'foobar.yaml'
|
|
222
236
|
);
|
|
223
237
|
|
|
224
238
|
const results = await lintDocument({
|
|
@@ -229,4 +243,50 @@ describe('lint', () => {
|
|
|
229
243
|
|
|
230
244
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
|
231
245
|
});
|
|
246
|
+
|
|
247
|
+
it('should ignore error because ignore file passed', async () => {
|
|
248
|
+
const absoluteRef = path.join(__dirname, 'fixtures/openapi.yaml');
|
|
249
|
+
const document = parseYamlToDocument(
|
|
250
|
+
outdent`
|
|
251
|
+
openapi: 3.0.0
|
|
252
|
+
info:
|
|
253
|
+
version: 1.0.0
|
|
254
|
+
title: Example OpenAPI 3 definition.
|
|
255
|
+
description: Information about API
|
|
256
|
+
license:
|
|
257
|
+
name: MIT
|
|
258
|
+
url: 'https://opensource.org/licenses/MIT'
|
|
259
|
+
servers:
|
|
260
|
+
- url: 'https://redocly.com/v1'
|
|
261
|
+
paths:
|
|
262
|
+
'/pets/{petId}':
|
|
263
|
+
post:
|
|
264
|
+
responses:
|
|
265
|
+
'201':
|
|
266
|
+
summary: Exist
|
|
267
|
+
description: example description
|
|
268
|
+
`,
|
|
269
|
+
absoluteRef
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const configFilePath = path.join(__dirname, 'fixtures');
|
|
273
|
+
|
|
274
|
+
const result = await lintDocument({
|
|
275
|
+
externalRefResolver: new BaseResolver(),
|
|
276
|
+
document,
|
|
277
|
+
config: await makeConfig({ 'operation-operationId': 'error' }, undefined, configFilePath),
|
|
278
|
+
});
|
|
279
|
+
expect(result).toHaveLength(1);
|
|
280
|
+
expect(result).toMatchObject([
|
|
281
|
+
{
|
|
282
|
+
ignored: true,
|
|
283
|
+
location: [{ pointer: '#/paths/~1pets~1{petId}/post/operationId' }],
|
|
284
|
+
message: 'Operation object should contain `operationId` field.',
|
|
285
|
+
ruleId: 'operation-operationId',
|
|
286
|
+
severity: 'error',
|
|
287
|
+
},
|
|
288
|
+
]);
|
|
289
|
+
expect(result[0]).toHaveProperty('ignored', true);
|
|
290
|
+
expect(result[0]).toHaveProperty('ruleId', 'operation-operationId');
|
|
291
|
+
});
|
|
232
292
|
});
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
pickObjectProps,
|
|
3
|
+
omitObjectProps,
|
|
4
|
+
slash,
|
|
5
|
+
getMatchingStatusCodeRange,
|
|
6
|
+
doesYamlFileExist,
|
|
7
|
+
} from '../utils';
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
2
10
|
|
|
3
11
|
describe('utils', () => {
|
|
4
12
|
const testObject = {
|
|
@@ -81,5 +89,38 @@ describe('utils', () => {
|
|
|
81
89
|
expect(getMatchingStatusCodeRange('2002')).toEqual('2002');
|
|
82
90
|
expect(getMatchingStatusCodeRange(4000)).toEqual('4000');
|
|
83
91
|
});
|
|
92
|
+
|
|
93
|
+
describe('isConfigFileExist', () => {
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
jest
|
|
96
|
+
.spyOn(fs, 'existsSync')
|
|
97
|
+
.mockImplementation((path) => path === 'redocly.yaml' || path === 'redocly.yml');
|
|
98
|
+
jest.spyOn(path, 'extname').mockImplementation((path) => {
|
|
99
|
+
if (path.endsWith('.yaml')) {
|
|
100
|
+
return '.yaml';
|
|
101
|
+
} else if (path.endsWith('.yml')) {
|
|
102
|
+
return '.yml';
|
|
103
|
+
} else {
|
|
104
|
+
return '';
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should return true because of valid path provided', () => {
|
|
110
|
+
expect(doesYamlFileExist('redocly.yaml')).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should return true because of valid path provided with yml', () => {
|
|
114
|
+
expect(doesYamlFileExist('redocly.yml')).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should return false because of fail do not exist', () => {
|
|
118
|
+
expect(doesYamlFileExist('redoccccly.yaml')).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should return false because of it is not yaml file', () => {
|
|
122
|
+
expect(doesYamlFileExist('redocly.yam')).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
84
125
|
});
|
|
85
126
|
});
|
|
@@ -9,7 +9,7 @@ describe('loadConfig', () => {
|
|
|
9
9
|
jest
|
|
10
10
|
.spyOn(RedoclyClient.prototype, 'getTokens')
|
|
11
11
|
.mockImplementation(() =>
|
|
12
|
-
Promise.resolve([{ region: 'us', token: 'accessToken', valid: true }])
|
|
12
|
+
Promise.resolve([{ region: 'us', token: 'accessToken', valid: true }])
|
|
13
13
|
);
|
|
14
14
|
const config = await loadConfig();
|
|
15
15
|
expect(config.resolve.http.headers).toStrictEqual([
|
|
@@ -32,7 +32,7 @@ describe('loadConfig', () => {
|
|
|
32
32
|
jest
|
|
33
33
|
.spyOn(RedoclyClient.prototype, 'getTokens')
|
|
34
34
|
.mockImplementation(() =>
|
|
35
|
-
Promise.resolve([{ region: 'eu', token: 'accessToken', valid: true }])
|
|
35
|
+
Promise.resolve([{ region: 'eu', token: 'accessToken', valid: true }])
|
|
36
36
|
);
|
|
37
37
|
const config = await loadConfig();
|
|
38
38
|
expect(config.resolve.http.headers).toStrictEqual([
|
|
@@ -44,6 +44,12 @@ describe('loadConfig', () => {
|
|
|
44
44
|
},
|
|
45
45
|
]);
|
|
46
46
|
});
|
|
47
|
+
|
|
48
|
+
it('should call callback if such passed', async () => {
|
|
49
|
+
const mockFn = jest.fn();
|
|
50
|
+
await loadConfig(undefined, undefined, mockFn);
|
|
51
|
+
expect(mockFn).toHaveBeenCalled();
|
|
52
|
+
});
|
|
47
53
|
});
|
|
48
54
|
|
|
49
55
|
describe('findConfig', () => {
|
package/src/config/config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { parseYaml, stringifyYaml } from '../js-yaml';
|
|
4
|
-
import { slash } from '../utils';
|
|
4
|
+
import { slash, doesYamlFileExist } from '../utils';
|
|
5
5
|
import { NormalizedProblem } from '../walk';
|
|
6
6
|
import { OasVersion, OasMajorVersion, Oas2RuleSet, Oas3RuleSet } from '../oas-types';
|
|
7
7
|
|
|
@@ -46,6 +46,16 @@ function getDomains() {
|
|
|
46
46
|
return domains;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function getIgnoreFilePath(configFile?: string): string | undefined {
|
|
50
|
+
if (configFile) {
|
|
51
|
+
return doesYamlFileExist(configFile)
|
|
52
|
+
? path.join(path.dirname(configFile), IGNORE_FILE)
|
|
53
|
+
: path.join(configFile, IGNORE_FILE);
|
|
54
|
+
} else {
|
|
55
|
+
return typeof process === 'undefined' ? undefined : path.join(process.cwd(), IGNORE_FILE);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
49
59
|
export const DOMAINS = getDomains();
|
|
50
60
|
export const AVAILABLE_REGIONS = Object.keys(DOMAINS) as Region[];
|
|
51
61
|
|
|
@@ -69,7 +79,7 @@ export class LintConfig {
|
|
|
69
79
|
this.plugins = rawConfig.plugins || [];
|
|
70
80
|
this.doNotResolveExamples = !!rawConfig.doNotResolveExamples;
|
|
71
81
|
|
|
72
|
-
this.recommendedFallback = rawConfig.recommendedFallback || false
|
|
82
|
+
this.recommendedFallback = rawConfig.recommendedFallback || false;
|
|
73
83
|
|
|
74
84
|
this.rules = {
|
|
75
85
|
[OasVersion.Version2]: { ...rawConfig.rules, ...rawConfig.oas2Rules },
|
|
@@ -91,29 +101,25 @@ export class LintConfig {
|
|
|
91
101
|
|
|
92
102
|
this.extendPaths = rawConfig.extendPaths || [];
|
|
93
103
|
this.pluginPaths = rawConfig.pluginPaths || [];
|
|
104
|
+
this.resolveIgnore(getIgnoreFilePath(configFile));
|
|
105
|
+
}
|
|
94
106
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
: (typeof process !== 'undefined' && process.cwd()) || '';
|
|
98
|
-
const ignoreFile = path.join(dir, IGNORE_FILE);
|
|
107
|
+
resolveIgnore(ignoreFile?: string) {
|
|
108
|
+
if (!ignoreFile || !doesYamlFileExist(ignoreFile)) return;
|
|
99
109
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
this.ignore[path.resolve(path.dirname(ignoreFile), fileName)] = this.ignore[fileName];
|
|
112
|
-
for (const ruleId of Object.keys(this.ignore[fileName])) {
|
|
113
|
-
this.ignore[fileName][ruleId] = new Set(this.ignore[fileName][ruleId]);
|
|
114
|
-
}
|
|
115
|
-
delete this.ignore[fileName];
|
|
110
|
+
this.ignore =
|
|
111
|
+
(parseYaml(fs.readFileSync(ignoreFile, 'utf-8')) as Record<
|
|
112
|
+
string,
|
|
113
|
+
Record<string, Set<string>>
|
|
114
|
+
>) || {};
|
|
115
|
+
|
|
116
|
+
// resolve ignore paths
|
|
117
|
+
for (const fileName of Object.keys(this.ignore)) {
|
|
118
|
+
this.ignore[path.resolve(path.dirname(ignoreFile), fileName)] = this.ignore[fileName];
|
|
119
|
+
for (const ruleId of Object.keys(this.ignore[fileName])) {
|
|
120
|
+
this.ignore[fileName][ruleId] = new Set(this.ignore[fileName][ruleId]);
|
|
116
121
|
}
|
|
122
|
+
delete this.ignore[fileName];
|
|
117
123
|
}
|
|
118
124
|
}
|
|
119
125
|
|
|
@@ -224,15 +230,13 @@ export class LintConfig {
|
|
|
224
230
|
|
|
225
231
|
for (const usedVersion of Array.from(this._usedVersions)) {
|
|
226
232
|
rules.push(
|
|
227
|
-
...Object.keys(this.rules[usedVersion]).filter((name) => !this._usedRules.has(name))
|
|
233
|
+
...Object.keys(this.rules[usedVersion]).filter((name) => !this._usedRules.has(name))
|
|
228
234
|
);
|
|
229
235
|
decorators.push(
|
|
230
|
-
...Object.keys(this.decorators[usedVersion]).filter((name) => !this._usedRules.has(name))
|
|
236
|
+
...Object.keys(this.decorators[usedVersion]).filter((name) => !this._usedRules.has(name))
|
|
231
237
|
);
|
|
232
238
|
preprocessors.push(
|
|
233
|
-
...Object.keys(this.preprocessors[usedVersion]).filter(
|
|
234
|
-
(name) => !this._usedRules.has(name),
|
|
235
|
-
),
|
|
239
|
+
...Object.keys(this.preprocessors[usedVersion]).filter((name) => !this._usedRules.has(name))
|
|
236
240
|
);
|
|
237
241
|
}
|
|
238
242
|
|
package/src/config/load.ts
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { RedoclyClient } from '../redocly';
|
|
4
|
-
import { isEmptyObject, loadYaml } from '../utils';
|
|
4
|
+
import { isEmptyObject, loadYaml, doesYamlFileExist } from '../utils';
|
|
5
5
|
import { Config, DOMAINS } from './config';
|
|
6
6
|
import { transformConfig } from './utils';
|
|
7
7
|
import { resolveConfig } from './config-resolvers';
|
|
8
8
|
|
|
9
9
|
import type { RawConfig, Region } from './types';
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
customExtends
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
async function addConfigMetadata({
|
|
12
|
+
rawConfig,
|
|
13
|
+
customExtends,
|
|
14
|
+
configPath,
|
|
15
|
+
}: {
|
|
16
|
+
rawConfig: RawConfig;
|
|
17
|
+
customExtends?: string[];
|
|
18
|
+
configPath?: string;
|
|
19
|
+
}): Promise<Config> {
|
|
16
20
|
if (customExtends !== undefined) {
|
|
17
21
|
rawConfig.lint = rawConfig.lint || {};
|
|
18
22
|
rawConfig.lint.extends = customExtends;
|
|
@@ -48,7 +52,7 @@ export async function loadConfig(
|
|
|
48
52
|
value: item.token,
|
|
49
53
|
},
|
|
50
54
|
]
|
|
51
|
-
: [])
|
|
55
|
+
: [])
|
|
52
56
|
);
|
|
53
57
|
}
|
|
54
58
|
}
|
|
@@ -56,12 +60,28 @@ export async function loadConfig(
|
|
|
56
60
|
return resolveConfig(rawConfig, configPath);
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
export async function loadConfig(
|
|
64
|
+
configPath: string | undefined = findConfig(),
|
|
65
|
+
customExtends?: string[],
|
|
66
|
+
processRawConfig?: (rawConfig: RawConfig) => void | Promise<void>
|
|
67
|
+
): Promise<Config> {
|
|
68
|
+
const rawConfig = await getConfig(configPath);
|
|
69
|
+
if (typeof processRawConfig === 'function') {
|
|
70
|
+
await processRawConfig(rawConfig);
|
|
71
|
+
}
|
|
72
|
+
return await addConfigMetadata({
|
|
73
|
+
rawConfig,
|
|
74
|
+
customExtends,
|
|
75
|
+
configPath,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
59
79
|
export const CONFIG_FILE_NAMES = ['redocly.yaml', 'redocly.yml', '.redocly.yaml', '.redocly.yml'];
|
|
60
80
|
|
|
61
81
|
export function findConfig(dir?: string): string | undefined {
|
|
62
82
|
if (!fs.hasOwnProperty('existsSync')) return;
|
|
63
83
|
const existingConfigFiles = CONFIG_FILE_NAMES.map((name) =>
|
|
64
|
-
dir ? path.resolve(dir, name) : name
|
|
84
|
+
dir ? path.resolve(dir, name) : name
|
|
65
85
|
).filter(fs.existsSync);
|
|
66
86
|
if (existingConfigFiles.length > 1) {
|
|
67
87
|
throw new Error(`
|
|
@@ -74,7 +94,7 @@ export function findConfig(dir?: string): string | undefined {
|
|
|
74
94
|
}
|
|
75
95
|
|
|
76
96
|
export async function getConfig(configPath: string | undefined = findConfig()) {
|
|
77
|
-
if (!configPath) return {};
|
|
97
|
+
if (!configPath || !doesYamlFileExist(configPath)) return {};
|
|
78
98
|
try {
|
|
79
99
|
const rawConfig = ((await loadYaml(configPath)) || {}) as RawConfig;
|
|
80
100
|
return transformConfig(rawConfig);
|
package/src/config/types.ts
CHANGED
|
@@ -11,17 +11,18 @@ import type {
|
|
|
11
11
|
} from '../oas-types';
|
|
12
12
|
import type { NodeType } from '../types';
|
|
13
13
|
|
|
14
|
+
export type RuleSeverity = ProblemSeverity | 'off';
|
|
15
|
+
|
|
16
|
+
export type PreprocessorSeverity = RuleSeverity | 'on';
|
|
17
|
+
|
|
14
18
|
export type RuleConfig =
|
|
15
|
-
|
|
|
16
|
-
| 'off'
|
|
19
|
+
| RuleSeverity
|
|
17
20
|
| ({
|
|
18
21
|
severity?: ProblemSeverity;
|
|
19
22
|
} & Record<string, any>);
|
|
20
23
|
|
|
21
24
|
export type PreprocessorConfig =
|
|
22
|
-
|
|
|
23
|
-
| 'off'
|
|
24
|
-
| 'on'
|
|
25
|
+
| PreprocessorSeverity
|
|
25
26
|
| ({
|
|
26
27
|
severity?: ProblemSeverity;
|
|
27
28
|
} & Record<string, any>);
|