@redocly/openapi-core 1.0.0-beta.49 → 1.0.0-beta.53
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__/normalizeVisitors.test.ts +1 -1
- package/__tests__/utils.ts +1 -1
- package/__tests__/walk.test.ts +77 -4
- package/lib/benchmark/benches/recommended-oas3.bench.js +2 -1
- package/lib/benchmark/utils.d.ts +1 -1
- package/lib/bundle.d.ts +1 -1
- package/lib/bundle.js +10 -10
- package/lib/config/builtIn.d.ts +2 -1
- package/lib/config/builtIn.js +9 -1
- package/lib/config/config.d.ts +5 -7
- package/lib/config/config.js +40 -100
- package/lib/config/load.d.ts +2 -0
- package/lib/config/load.js +65 -0
- package/lib/config/rules.d.ts +1 -1
- package/lib/format/codeframes.js +1 -1
- package/lib/index.d.ts +4 -3
- package/lib/index.js +13 -11
- package/lib/lint.d.ts +6 -19
- package/lib/lint.js +13 -43
- package/lib/oas-types.d.ts +19 -0
- package/lib/oas-types.js +42 -0
- package/lib/resolve.d.ts +6 -2
- package/lib/resolve.js +18 -5
- package/lib/rules/ajv.d.ts +3 -3
- package/lib/rules/ajv.js +21 -18
- package/lib/rules/common/info-contact.js +2 -1
- package/lib/rules/common/info-description.js +2 -1
- package/lib/rules/common/info-license-url.js +2 -1
- package/lib/rules/common/license-url.js +2 -1
- package/lib/rules/common/no-ambiguous-paths.js +2 -1
- package/lib/rules/common/no-enum-type-mismatch.js +2 -1
- package/lib/rules/common/no-identical-paths.js +2 -1
- package/lib/rules/common/no-path-trailing-slash.js +2 -1
- package/lib/rules/common/operation-2xx-response.js +2 -1
- package/lib/rules/common/operation-description.js +2 -1
- package/lib/rules/common/operation-operationId-unique.js +2 -1
- package/lib/rules/common/operation-operationId-url-safe.js +2 -1
- package/lib/rules/common/operation-operationId.js +9 -4
- package/lib/rules/common/operation-parameters-unique.js +2 -1
- package/lib/rules/common/operation-security-defined.js +2 -1
- package/lib/rules/common/operation-singular-tag.js +2 -1
- package/lib/rules/common/operation-summary.js +2 -1
- package/lib/rules/common/operation-tag-defined.js +2 -1
- package/lib/rules/common/parameter-description.js +2 -1
- package/lib/rules/common/path-declaration-must-exist.js +2 -1
- package/lib/rules/common/path-http-verbs-order.js +2 -1
- package/lib/rules/common/path-not-include-query.js +2 -1
- package/lib/rules/common/path-params-defined.js +3 -2
- package/lib/rules/common/paths-kebab-case.js +2 -1
- package/lib/rules/common/registry-dependencies.js +2 -1
- package/lib/rules/common/spec.js +5 -2
- package/lib/rules/common/tag-description.js +2 -1
- package/lib/rules/common/tags-alphabetical.js +2 -1
- package/lib/rules/no-unresolved-refs.js +2 -1
- package/lib/rules/oas2/boolean-parameter-prefixes.js +2 -1
- package/lib/rules/oas2/index.d.ts +1 -1
- package/lib/rules/oas2/index.js +1 -1
- package/lib/rules/oas3/boolean-parameter-prefixes.js +2 -1
- package/lib/rules/oas3/index.d.ts +2 -1
- package/lib/rules/oas3/index.js +2 -2
- package/lib/rules/oas3/no-empty-servers.js +2 -1
- package/lib/rules/oas3/no-example-value-and-externalValue.js +2 -1
- package/lib/rules/oas3/no-invalid-media-type-examples.js +20 -10
- package/lib/rules/oas3/no-server-example.com.js +2 -1
- package/lib/rules/oas3/no-server-trailing-slash.js +2 -1
- package/lib/rules/oas3/no-servers-empty-enum.js +2 -1
- package/lib/rules/oas3/no-undefined-server-variable.js +2 -1
- package/lib/rules/oas3/no-unused-components.js +2 -1
- package/lib/rules/other/stats.js +2 -1
- package/lib/types/oas2.js +1 -1
- package/lib/types/oas3.js +1 -1
- package/lib/types/oas3_1.js +1 -1
- package/lib/utils.d.ts +1 -1
- package/lib/visitors.d.ts +3 -1
- package/lib/visitors.js +4 -4
- package/lib/walk.d.ts +1 -1
- package/lib/walk.js +11 -2
- package/package.json +7 -7
- package/src/__tests__/lint.test.ts +44 -0
- package/src/benchmark/benches/recommended-oas3.bench.ts +2 -1
- package/src/benchmark/benchmark.js +3 -1
- package/src/benchmark/utils.ts +1 -1
- package/src/bundle.ts +2 -2
- package/src/config/__tests__/resolve-plugins.test.ts +1 -1
- package/src/config/builtIn.ts +11 -1
- package/src/config/config.ts +30 -78
- package/src/config/load.ts +60 -0
- package/src/config/rules.ts +1 -1
- package/src/format/codeframes.ts +1 -1
- package/src/index.ts +4 -3
- package/src/lint.ts +19 -56
- package/src/oas-types.ts +58 -0
- package/src/resolve.ts +17 -4
- package/src/rules/__tests__/config.ts +10 -0
- package/src/rules/__tests__/no-unresolved-refs.test.ts +9 -21
- package/src/rules/ajv.ts +27 -23
- package/src/rules/common/__tests__/info-description.test.ts +8 -16
- package/src/rules/common/__tests__/info-license.test.ts +3 -3
- package/src/rules/common/__tests__/license-url.test.ts +3 -3
- package/src/rules/common/__tests__/no-ambiguous-paths.test.ts +2 -2
- package/src/rules/common/__tests__/no-enum-type-mismatch.test.ts +50 -5
- package/src/rules/common/__tests__/no-identical-paths.test.ts +2 -2
- package/src/rules/common/__tests__/no-path-trailing-slash.test.ts +4 -4
- package/src/rules/common/__tests__/operation-2xx-response.test.ts +4 -4
- package/src/rules/common/__tests__/operation-operationId-unique.test.ts +3 -3
- package/src/rules/common/__tests__/operation-operationId-url-safe.test.ts +2 -5
- package/src/rules/common/__tests__/operation-parameters-unique.test.ts +5 -17
- package/src/rules/common/__tests__/operation-security-defined.test.ts +3 -3
- package/src/rules/common/__tests__/operation-singular-tag.test.ts +3 -3
- package/src/rules/common/__tests__/path-http-verbs-order.test.ts +3 -3
- package/src/rules/common/__tests__/path-not-include-query.test.ts +3 -3
- package/src/rules/common/__tests__/path-params-defined.test.ts +4 -4
- package/src/rules/common/__tests__/paths-kebab-case.test.ts +3 -3
- package/src/rules/common/__tests__/tag-description.test.ts +3 -3
- package/src/rules/common/__tests__/tags-alphabetical.test.ts +3 -3
- package/src/rules/common/operation-operationId.ts +7 -3
- package/src/rules/common/spec.ts +4 -1
- package/src/rules/oas2/__tests__/boolean-parameter-prefixes.test.ts +7 -10
- package/src/rules/oas2/__tests__/spec/referenceableScalars.test.ts +3 -6
- package/src/rules/oas2/__tests__/spec/utils.ts +2 -0
- package/src/rules/oas2/index.ts +1 -10
- package/src/rules/oas3/__tests__/boolean-parameter-prefixes.test.ts +7 -10
- package/src/rules/oas3/__tests__/fixtures/common.yaml +11 -0
- package/src/rules/oas3/__tests__/no-empty-enum-servers.com.test.ts +7 -7
- package/src/rules/oas3/__tests__/no-example-value-and-externalValue.test.ts +3 -9
- package/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +103 -29
- package/src/rules/oas3/__tests__/no-server-example.com.test.ts +3 -3
- package/src/rules/oas3/__tests__/no-server-trailing-slash.test.ts +3 -3
- package/src/rules/oas3/__tests__/no-unused-components.test.ts +2 -2
- package/src/rules/oas3/__tests__/spec/spec.test.ts +3 -1
- package/src/rules/oas3/__tests__/spec/utils.ts +2 -0
- package/src/rules/oas3/index.ts +3 -4
- package/src/rules/oas3/no-invalid-media-type-examples.ts +27 -19
- package/src/visitors.ts +12 -8
- package/src/walk.ts +24 -8
- package/tsconfig.tsbuildinfo +1 -3084
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.53",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"engines": {
|
|
@@ -30,22 +30,22 @@
|
|
|
30
30
|
"Andriy Leliv <andriy@redoc.ly> (https://redoc.ly/)"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
+
"@redocly/ajv": "^8.6.2",
|
|
33
34
|
"@types/node": "^14.11.8",
|
|
34
35
|
"colorette": "^1.2.0",
|
|
36
|
+
"js-levenshtein": "^1.1.6",
|
|
35
37
|
"js-yaml": "^3.14.1",
|
|
36
|
-
"
|
|
38
|
+
"lodash.isequal": "^4.5.0",
|
|
37
39
|
"minimatch": "^3.0.4",
|
|
38
40
|
"node-fetch": "^2.6.1",
|
|
39
|
-
"
|
|
40
|
-
"@redocly/ajv": "^6.12.3",
|
|
41
|
-
"lodash.isequal": "^4.5.0"
|
|
41
|
+
"yaml-ast-parser": "0.0.43"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
+
"@types/js-levenshtein": "^1.1.0",
|
|
44
45
|
"@types/js-yaml": "^3.12.4",
|
|
46
|
+
"@types/lodash.isequal": "^4.5.5",
|
|
45
47
|
"@types/minimatch": "^3.0.3",
|
|
46
48
|
"@types/node-fetch": "^2.5.7",
|
|
47
|
-
"@types/js-levenshtein": "^1.1.0",
|
|
48
|
-
"@types/lodash.isequal": "^4.5.5",
|
|
49
49
|
"typescript": "^4.0.5"
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { outdent } from 'outdent';
|
|
2
|
+
|
|
3
|
+
import { lintFromString } from '../lint';
|
|
4
|
+
import { loadConfig } from '../config/load';
|
|
5
|
+
import { replaceSourceWithRef } from '../../__tests__/utils';
|
|
6
|
+
|
|
7
|
+
describe('lint', () => {
|
|
8
|
+
it('lintFromString should work', async () => {
|
|
9
|
+
const results = await lintFromString({
|
|
10
|
+
absoluteRef: '/test/spec.yaml',
|
|
11
|
+
source: outdent`
|
|
12
|
+
openapi: 3.0.0
|
|
13
|
+
info:
|
|
14
|
+
title: Test API
|
|
15
|
+
version: "1.0"
|
|
16
|
+
description: Test
|
|
17
|
+
license: Fail
|
|
18
|
+
|
|
19
|
+
servers:
|
|
20
|
+
- url: http://example.com
|
|
21
|
+
paths: {}
|
|
22
|
+
`,
|
|
23
|
+
config: await loadConfig(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
27
|
+
Array [
|
|
28
|
+
Object {
|
|
29
|
+
"location": Array [
|
|
30
|
+
Object {
|
|
31
|
+
"pointer": "#/info/license",
|
|
32
|
+
"reportOnKey": false,
|
|
33
|
+
"source": "/test/spec.yaml",
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
"message": "Expected type \`License\` (object) but got \`string\`",
|
|
37
|
+
"ruleId": "spec",
|
|
38
|
+
"severity": "error",
|
|
39
|
+
"suggest": Array [],
|
|
40
|
+
},
|
|
41
|
+
]
|
|
42
|
+
`);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -4,6 +4,7 @@ import { lintDocument } from '../../lint';
|
|
|
4
4
|
import { LintConfig } from '../../config/config';
|
|
5
5
|
import { BaseResolver } from '../../resolve';
|
|
6
6
|
import { parseYamlToDocument } from '../utils';
|
|
7
|
+
import { defaultPlugin } from '../../config/builtIn';
|
|
7
8
|
|
|
8
9
|
export const name = 'Validate with recommended rules';
|
|
9
10
|
export const count = 10;
|
|
@@ -17,6 +18,6 @@ export function measureAsync() {
|
|
|
17
18
|
return lintDocument({
|
|
18
19
|
externalRefResolver: new BaseResolver(),
|
|
19
20
|
document: rebillyDocument,
|
|
20
|
-
config: new LintConfig({}),
|
|
21
|
+
config: new LintConfig({ plugins: [defaultPlugin] }),
|
|
21
22
|
});
|
|
22
23
|
}
|
|
@@ -30,7 +30,9 @@ function prepareRevision(revision) {
|
|
|
30
30
|
// Returns the complete git hash for a given git revision reference.
|
|
31
31
|
const hash = exec(`git rev-parse "${revision}"`);
|
|
32
32
|
const dir = path.join(os.tmpdir(), 'openapi-cli-benchmark', hash);
|
|
33
|
-
fs.
|
|
33
|
+
if (fs.existsSync(dir)) {
|
|
34
|
+
fs.rmdirSync(dir, { recursive: true});
|
|
35
|
+
}
|
|
34
36
|
fs.mkdirSync(dir, { recursive: true });
|
|
35
37
|
|
|
36
38
|
exec(`git archive "${hash}" | tar -xC "${dir}"`);
|
package/src/benchmark/utils.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as yaml from 'js-yaml';
|
|
2
2
|
import { Document, Source } from '../resolve';
|
|
3
|
-
import { Oas3RuleSet } from '../
|
|
3
|
+
import { Oas3RuleSet } from '../oas-types';
|
|
4
4
|
import { RuleConfig, LintConfig, Plugin } from '../config/config';
|
|
5
5
|
|
|
6
6
|
export function parseYamlToDocument(body: string, absoluteRef: string = ''): Document {
|
package/src/bundle.ts
CHANGED
|
@@ -6,9 +6,9 @@ import { Oas2Types } from './types/oas2';
|
|
|
6
6
|
import { Oas3_1Types } from './types/oas3_1';
|
|
7
7
|
import { NormalizedNodeType, normalizeTypes, NodeType } from './types';
|
|
8
8
|
import { WalkContext, walkDocument, UserContext, ResolveResult } from './walk';
|
|
9
|
-
import { detectOpenAPI, openAPIMajor, OasMajorVersion } from './
|
|
9
|
+
import { detectOpenAPI, openAPIMajor, OasMajorVersion } from './oas-types';
|
|
10
10
|
import { Location, refBaseName } from './ref-utils';
|
|
11
|
-
import { Config, LintConfig } from './config/config';
|
|
11
|
+
import type { Config, LintConfig } from './config/config';
|
|
12
12
|
import { initRules } from './config/rules';
|
|
13
13
|
import { reportUnresolvedRef } from './rules/no-unresolved-refs';
|
|
14
14
|
import { isPlainObject } from './utils';
|
package/src/config/builtIn.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import recommended from './recommended';
|
|
2
2
|
import all from './all';
|
|
3
3
|
import minimal from './minimal';
|
|
4
|
-
import { LintRawConfig } from './config';
|
|
4
|
+
import { LintRawConfig, Plugin } from './config';
|
|
5
|
+
|
|
6
|
+
import * as builtinRules from '../rules/builtin';
|
|
5
7
|
|
|
6
8
|
export const builtInConfigs: Record<string, LintRawConfig> = {
|
|
7
9
|
recommended,
|
|
@@ -11,3 +13,11 @@ export const builtInConfigs: Record<string, LintRawConfig> = {
|
|
|
11
13
|
decorators: { 'registry-dependencies': 'on' }
|
|
12
14
|
}
|
|
13
15
|
};
|
|
16
|
+
|
|
17
|
+
export const defaultPlugin: Plugin = {
|
|
18
|
+
id: '', // default plugin doesn't have id
|
|
19
|
+
rules: builtinRules.rules,
|
|
20
|
+
preprocessors: builtinRules.preprocessors,
|
|
21
|
+
decorators: builtinRules.decorators,
|
|
22
|
+
configs: builtInConfigs,
|
|
23
|
+
}
|
package/src/config/config.ts
CHANGED
|
@@ -3,9 +3,8 @@ import * as path from 'path';
|
|
|
3
3
|
import * as yaml from 'js-yaml';
|
|
4
4
|
import { dirname } from 'path';
|
|
5
5
|
import { red, blue } from 'colorette';
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
import { loadYaml, notUndefined } from '../utils';
|
|
6
|
+
|
|
7
|
+
import { notUndefined } from '../utils';
|
|
9
8
|
|
|
10
9
|
import {
|
|
11
10
|
OasVersion,
|
|
@@ -15,14 +14,13 @@ import {
|
|
|
15
14
|
Oas2RuleSet,
|
|
16
15
|
Oas2PreprocessorsSet,
|
|
17
16
|
Oas2DecoratorsSet,
|
|
18
|
-
|
|
17
|
+
Oas3RuleSet
|
|
18
|
+
} from '../oas-types';
|
|
19
19
|
|
|
20
20
|
import { ProblemSeverity, NormalizedProblem } from '../walk';
|
|
21
|
-
import { Oas3RuleSet } from '../lint';
|
|
22
21
|
|
|
23
22
|
import recommended from './recommended';
|
|
24
23
|
import { NodeType } from '../types';
|
|
25
|
-
import { RedoclyClient } from '../redocly';
|
|
26
24
|
|
|
27
25
|
export const IGNORE_FILE = '.redocly.lint-ignore.yaml';
|
|
28
26
|
const IGNORE_BANNER =
|
|
@@ -149,13 +147,6 @@ export class LintConfig {
|
|
|
149
147
|
this.plugins = rawConfig.plugins ? resolvePlugins(rawConfig.plugins, configFile) : [];
|
|
150
148
|
this.doNotResolveExamples = !!rawConfig.doNotResolveExamples;
|
|
151
149
|
|
|
152
|
-
this.plugins.push({
|
|
153
|
-
id: '', // default plugin doesn't have id
|
|
154
|
-
rules: builtinRules.rules,
|
|
155
|
-
preprocessors: builtinRules.preprocessors,
|
|
156
|
-
decorators: builtinRules.decorators,
|
|
157
|
-
});
|
|
158
|
-
|
|
159
150
|
if (!rawConfig.extends) {
|
|
160
151
|
this.recommendedFallback = true;
|
|
161
152
|
}
|
|
@@ -192,7 +183,7 @@ export class LintConfig {
|
|
|
192
183
|
[OasVersion.Version3_1]: { ...merged.decorators, ...merged.oas3_1Decorators },
|
|
193
184
|
};
|
|
194
185
|
|
|
195
|
-
const dir = this.configFile ? path.dirname(this.configFile) : process.cwd();
|
|
186
|
+
const dir = this.configFile ? path.dirname(this.configFile) : (typeof process !== 'undefined' && process.cwd() || '');
|
|
196
187
|
const ignoreFile = path.join(dir, IGNORE_FILE);
|
|
197
188
|
|
|
198
189
|
/* no crash when using it on the client */
|
|
@@ -260,6 +251,7 @@ export class LintConfig {
|
|
|
260
251
|
if (plugin.typeExtension !== undefined) {
|
|
261
252
|
switch (version) {
|
|
262
253
|
case OasVersion.Version3_0:
|
|
254
|
+
case OasVersion.Version3_1:
|
|
263
255
|
if (!plugin.typeExtension.oas3) continue;
|
|
264
256
|
extendedTypes = plugin.typeExtension.oas3(extendedTypes, version);
|
|
265
257
|
case OasVersion.Version2:
|
|
@@ -405,80 +397,38 @@ export class Config {
|
|
|
405
397
|
}
|
|
406
398
|
}
|
|
407
399
|
|
|
408
|
-
export async function loadConfig(configPath?: string, customExtends?: string[]): Promise<Config> {
|
|
409
|
-
if (configPath === undefined) {
|
|
410
|
-
configPath = findConfig();
|
|
411
|
-
}
|
|
412
|
-
let rawConfig: RawConfig = {};
|
|
413
|
-
// let resolvedPlugins: Plugin[] = [];
|
|
414
|
-
|
|
415
|
-
if (configPath !== undefined) {
|
|
416
|
-
try {
|
|
417
|
-
rawConfig = (await loadYaml(configPath)) as RawConfig;
|
|
418
|
-
} catch (e) {
|
|
419
|
-
throw new Error(`Error parsing config file at \`${configPath}\`: ${e.message}`);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
if (customExtends !== undefined) {
|
|
423
|
-
rawConfig.lint = rawConfig.lint || {};
|
|
424
|
-
rawConfig.lint.extends = customExtends;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
const redoclyClient = new RedoclyClient();
|
|
428
|
-
if (redoclyClient.hasToken()) {
|
|
429
|
-
if (!rawConfig.resolve) rawConfig.resolve = {};
|
|
430
|
-
if (!rawConfig.resolve.http) rawConfig.resolve.http = {};
|
|
431
|
-
rawConfig.resolve.http.headers = [
|
|
432
|
-
{
|
|
433
|
-
matches: `https://api.${process.env.REDOCLY_DOMAIN || 'redoc.ly'}/registry/**`,
|
|
434
|
-
name: 'Authorization',
|
|
435
|
-
envVariable: undefined,
|
|
436
|
-
value: (redoclyClient && (await redoclyClient.getAuthorizationHeader())) || '',
|
|
437
|
-
},
|
|
438
|
-
...(rawConfig.resolve.http.headers ?? []),
|
|
439
|
-
];
|
|
440
|
-
}
|
|
441
|
-
return new Config(rawConfig, configPath);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
function findConfig() {
|
|
445
|
-
if (fs.existsSync('.redocly.yaml')) {
|
|
446
|
-
return '.redocly.yaml';
|
|
447
|
-
} else if (fs.existsSync('.redocly.yml')) {
|
|
448
|
-
return '.redocly.yml';
|
|
449
|
-
}
|
|
450
|
-
return undefined;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
|
|
454
400
|
function resolvePresets(presets: string[], plugins: Plugin[]) {
|
|
455
401
|
return presets.map((presetName) => {
|
|
456
|
-
|
|
457
|
-
if (!preset && presetName.indexOf('/') > -1) {
|
|
458
|
-
const [pluginName, configName] = presetName.split('/');
|
|
459
|
-
const plugin = plugins.find((p) => p.id === pluginName);
|
|
460
|
-
if (!plugin) {
|
|
461
|
-
throw new Error(`Invalid config ${red(presetName)}: plugin ${pluginName} is not included.`);
|
|
462
|
-
}
|
|
402
|
+
const { pluginId, configName } = parsePresetName(presetName);
|
|
463
403
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
`Invalid config ${red(
|
|
468
|
-
presetName,
|
|
469
|
-
)}: plugin ${pluginName} doesn't export config with name ${configName}.`,
|
|
470
|
-
);
|
|
471
|
-
}
|
|
472
|
-
return preset;
|
|
404
|
+
const plugin = plugins.find((p) => p.id === pluginId);
|
|
405
|
+
if (!plugin) {
|
|
406
|
+
throw new Error(`Invalid config ${red(presetName)}: plugin ${pluginId} is not included.`);
|
|
473
407
|
}
|
|
474
408
|
|
|
409
|
+
const preset = plugin.configs?.[configName]!;
|
|
475
410
|
if (!preset) {
|
|
476
|
-
throw new Error(
|
|
411
|
+
throw new Error(
|
|
412
|
+
pluginId
|
|
413
|
+
? `Invalid config ${red(
|
|
414
|
+
presetName,
|
|
415
|
+
)}: plugin ${pluginId} doesn't export config with name ${configName}.`
|
|
416
|
+
: `Invalid config ${red(presetName)}: there is no such built-in config.`,
|
|
417
|
+
);
|
|
477
418
|
}
|
|
478
419
|
return preset;
|
|
479
420
|
});
|
|
480
421
|
}
|
|
481
422
|
|
|
423
|
+
function parsePresetName(presetName: string): { pluginId: string; configName: string } {
|
|
424
|
+
if (presetName.indexOf('/') > -1) {
|
|
425
|
+
const [pluginId, configName] = presetName.split('/');
|
|
426
|
+
return { pluginId, configName };
|
|
427
|
+
} else {
|
|
428
|
+
return { pluginId: '', configName: presetName };
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
482
432
|
function resolvePlugins(plugins: (string | Plugin)[] | null, configPath: string = ''): Plugin[] {
|
|
483
433
|
if (!plugins) return [];
|
|
484
434
|
|
|
@@ -496,7 +446,7 @@ function resolvePlugins(plugins: (string | Plugin)[] | null, configPath: string
|
|
|
496
446
|
: p;
|
|
497
447
|
|
|
498
448
|
const id = pluginModule.id;
|
|
499
|
-
if (
|
|
449
|
+
if (typeof id !== 'string') {
|
|
500
450
|
throw new Error(red(`Plugin must define \`id\` property in ${blue(p.toString())}.`));
|
|
501
451
|
}
|
|
502
452
|
|
|
@@ -565,6 +515,8 @@ function resolvePlugins(plugins: (string | Plugin)[] | null, configPath: string
|
|
|
565
515
|
}
|
|
566
516
|
|
|
567
517
|
function prefixRules<T extends Record<string, any>>(rules: T, prefix: string) {
|
|
518
|
+
if (!prefix) return rules;
|
|
519
|
+
|
|
568
520
|
const res: any = {};
|
|
569
521
|
for (const name of Object.keys(rules)) {
|
|
570
522
|
res[`${prefix}/${name}`] = rules[name];
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
|
|
3
|
+
import { RedoclyClient } from '../redocly';
|
|
4
|
+
import { loadYaml } from '../utils';
|
|
5
|
+
import { Config, RawConfig } from './config';
|
|
6
|
+
|
|
7
|
+
import { defaultPlugin } from './builtIn';
|
|
8
|
+
|
|
9
|
+
export async function loadConfig(configPath?: string, customExtends?: string[]): Promise<Config> {
|
|
10
|
+
if (configPath === undefined) {
|
|
11
|
+
configPath = findConfig();
|
|
12
|
+
}
|
|
13
|
+
let rawConfig: RawConfig = {};
|
|
14
|
+
|
|
15
|
+
if (configPath !== undefined) {
|
|
16
|
+
try {
|
|
17
|
+
rawConfig = (await loadYaml(configPath)) as RawConfig;
|
|
18
|
+
} catch (e) {
|
|
19
|
+
throw new Error(`Error parsing config file at \`${configPath}\`: ${e.message}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (customExtends !== undefined) {
|
|
23
|
+
rawConfig.lint = rawConfig.lint || {};
|
|
24
|
+
rawConfig.lint.extends = customExtends;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const redoclyClient = new RedoclyClient();
|
|
28
|
+
if (redoclyClient.hasToken()) {
|
|
29
|
+
if (!rawConfig.resolve) rawConfig.resolve = {};
|
|
30
|
+
if (!rawConfig.resolve.http) rawConfig.resolve.http = {};
|
|
31
|
+
rawConfig.resolve.http.headers = [
|
|
32
|
+
{
|
|
33
|
+
matches: `https://api.${process.env.REDOCLY_DOMAIN || 'redoc.ly'}/registry/**`,
|
|
34
|
+
name: 'Authorization',
|
|
35
|
+
envVariable: undefined,
|
|
36
|
+
value: (redoclyClient && (await redoclyClient.getAuthorizationHeader())) || '',
|
|
37
|
+
},
|
|
38
|
+
...(rawConfig.resolve.http.headers ?? []),
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
return new Config(
|
|
42
|
+
{
|
|
43
|
+
...rawConfig,
|
|
44
|
+
lint: {
|
|
45
|
+
...rawConfig?.lint,
|
|
46
|
+
plugins: [...(rawConfig?.lint?.plugins || []), defaultPlugin], // inject default plugin
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
configPath,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function findConfig() {
|
|
54
|
+
if (fs.existsSync('.redocly.yaml')) {
|
|
55
|
+
return '.redocly.yaml';
|
|
56
|
+
} else if (fs.existsSync('.redocly.yml')) {
|
|
57
|
+
return '.redocly.yml';
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
package/src/config/rules.ts
CHANGED
package/src/format/codeframes.ts
CHANGED
|
@@ -134,7 +134,7 @@ export function getLineColLocation(location: LocationObject): LineColLocationObj
|
|
|
134
134
|
if (location.pointer === undefined) return location;
|
|
135
135
|
|
|
136
136
|
const { source, pointer, reportOnKey } = location;
|
|
137
|
-
const ast = source.getAst() as YAMLNode;
|
|
137
|
+
const ast = source.getAst(yamlAst.safeLoad) as YAMLNode;
|
|
138
138
|
const astNode = getAstNodeByPointer(ast, pointer, !!reportOnKey);
|
|
139
139
|
return {
|
|
140
140
|
...location,
|
package/src/index.ts
CHANGED
|
@@ -8,14 +8,15 @@ export { StatsAccumulator, StatsName } from './typings/common';
|
|
|
8
8
|
export { normalizeTypes } from './types';
|
|
9
9
|
export { Stats } from './rules/other/stats';
|
|
10
10
|
|
|
11
|
-
export {
|
|
11
|
+
export { Config, LintConfig, RawConfig, IGNORE_FILE } from './config/config';
|
|
12
|
+
export { loadConfig } from './config/load';
|
|
12
13
|
export { RedoclyClient } from './redocly';
|
|
13
14
|
export { Source, BaseResolver, Document, resolveDocument, ResolveError, YamlParseError } from './resolve';
|
|
14
15
|
export { unescapePointer } from './ref-utils';
|
|
15
|
-
export { detectOpenAPI, OasMajorVersion, openAPIMajor } from './
|
|
16
|
+
export { detectOpenAPI, OasMajorVersion, openAPIMajor, OasVersion } from './oas-types';
|
|
16
17
|
export { normalizeVisitors } from './visitors';
|
|
17
18
|
export { WalkContext, walkDocument, NormalizedProblem, ProblemSeverity, LineColLocationObject, LocationObject, Loc } from './walk';
|
|
18
19
|
|
|
19
20
|
export { formatProblems, OutputFormat, getTotals, Totals } from './format/format';
|
|
20
|
-
export {
|
|
21
|
+
export { lint, lint as validate, lintDocument, lintFromString } from './lint';
|
|
21
22
|
export { bundle } from './bundle';
|
package/src/lint.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import { BaseResolver, resolveDocument, Document } from './resolve';
|
|
1
|
+
import { BaseResolver, resolveDocument, Document, makeDocumentFromString } from './resolve';
|
|
2
2
|
import {
|
|
3
|
-
Oas3Rule,
|
|
4
3
|
normalizeVisitors,
|
|
5
|
-
Oas3Preprocessor,
|
|
6
|
-
Oas2Rule,
|
|
7
|
-
Oas2Preprocessor,
|
|
8
4
|
} from './visitors';
|
|
9
5
|
import { Oas3_1Types } from './types/oas3_1';
|
|
10
6
|
import { Oas3Types } from './types/oas3';
|
|
@@ -15,25 +11,7 @@ import { LintConfig, Config } from './config/config';
|
|
|
15
11
|
import { normalizeTypes } from './types';
|
|
16
12
|
import { initRules } from './config/rules';
|
|
17
13
|
import { releaseAjvInstance } from './rules/ajv';
|
|
18
|
-
|
|
19
|
-
export enum OasVersion {
|
|
20
|
-
Version2 = 'oas2',
|
|
21
|
-
Version3_0 = 'oas3_0',
|
|
22
|
-
Version3_1 = 'oas3_1',
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export enum OasMajorVersion {
|
|
26
|
-
Version2 = 'oas2',
|
|
27
|
-
Version3 = 'oas3',
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export type RuleSet<T> = Record<string, T>;
|
|
31
|
-
export type Oas3RuleSet = Record<string, Oas3Rule>;
|
|
32
|
-
export type Oas2RuleSet = Record<string, Oas2Rule>;
|
|
33
|
-
export type Oas3PreprocessorsSet = Record<string, Oas3Preprocessor>;
|
|
34
|
-
export type Oas2PreprocessorsSet = Record<string, Oas2Preprocessor>;
|
|
35
|
-
export type Oas3DecoratorsSet = Record<string, Oas3Preprocessor>;
|
|
36
|
-
export type Oas2DecoratorsSet = Record<string, Oas2Preprocessor>;
|
|
14
|
+
import { detectOpenAPI, OasMajorVersion, OasVersion, openAPIMajor } from './oas-types';
|
|
37
15
|
|
|
38
16
|
export async function lint(opts: {
|
|
39
17
|
ref: string;
|
|
@@ -51,6 +29,23 @@ export async function lint(opts: {
|
|
|
51
29
|
});
|
|
52
30
|
}
|
|
53
31
|
|
|
32
|
+
export async function lintFromString(opts: {
|
|
33
|
+
source: string;
|
|
34
|
+
absoluteRef?: string;
|
|
35
|
+
config: Config;
|
|
36
|
+
externalRefResolver?: BaseResolver;
|
|
37
|
+
}) {
|
|
38
|
+
const { source, absoluteRef, externalRefResolver = new BaseResolver(opts.config.resolve) } = opts;
|
|
39
|
+
const document = makeDocumentFromString(source, absoluteRef || '/');
|
|
40
|
+
|
|
41
|
+
return lintDocument({
|
|
42
|
+
document,
|
|
43
|
+
...opts,
|
|
44
|
+
externalRefResolver,
|
|
45
|
+
config: opts.config.lint,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
54
49
|
export async function lintDocument(opts: {
|
|
55
50
|
document: Document;
|
|
56
51
|
config: LintConfig;
|
|
@@ -94,35 +89,3 @@ export async function lintDocument(opts: {
|
|
|
94
89
|
});
|
|
95
90
|
return ctx.problems.map((problem) => config.addProblemToIgnore(problem));
|
|
96
91
|
}
|
|
97
|
-
|
|
98
|
-
export function detectOpenAPI(root: any): OasVersion {
|
|
99
|
-
if (typeof root !== 'object') {
|
|
100
|
-
throw new Error(`Document must be JSON object, got ${typeof root}`);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (!(root.openapi || root.swagger)) {
|
|
104
|
-
throw new Error('This doesn’t look like an OpenAPI document.\n');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (root.openapi && root.openapi.startsWith('3.0')) {
|
|
108
|
-
return OasVersion.Version3_0;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (root.openapi && root.openapi.startsWith('3.1')) {
|
|
112
|
-
return OasVersion.Version3_1;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (root.swagger && root.swagger === '2.0') {
|
|
116
|
-
return OasVersion.Version2;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
throw new Error(`Unsupported OpenAPI Version: ${root.openapi || root.swagger}`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function openAPIMajor(version: OasVersion): OasMajorVersion {
|
|
123
|
-
if (version === OasVersion.Version2) {
|
|
124
|
-
return OasMajorVersion.Version2;
|
|
125
|
-
} else {
|
|
126
|
-
return OasMajorVersion.Version3;
|
|
127
|
-
}
|
|
128
|
-
}
|
package/src/oas-types.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Oas3Rule,
|
|
3
|
+
Oas3Preprocessor,
|
|
4
|
+
Oas2Rule,
|
|
5
|
+
Oas2Preprocessor,
|
|
6
|
+
} from './visitors';
|
|
7
|
+
|
|
8
|
+
export type RuleSet<T> = Record<string, T>;
|
|
9
|
+
|
|
10
|
+
export enum OasVersion {
|
|
11
|
+
Version2 = 'oas2',
|
|
12
|
+
Version3_0 = 'oas3_0',
|
|
13
|
+
Version3_1 = 'oas3_1',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export enum OasMajorVersion {
|
|
17
|
+
Version2 = 'oas2',
|
|
18
|
+
Version3 = 'oas3',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type Oas3RuleSet = Record<string, Oas3Rule>;
|
|
22
|
+
export type Oas2RuleSet = Record<string, Oas2Rule>;
|
|
23
|
+
export type Oas3PreprocessorsSet = Record<string, Oas3Preprocessor>;
|
|
24
|
+
export type Oas2PreprocessorsSet = Record<string, Oas2Preprocessor>;
|
|
25
|
+
export type Oas3DecoratorsSet = Record<string, Oas3Preprocessor>;
|
|
26
|
+
export type Oas2DecoratorsSet = Record<string, Oas2Preprocessor>;
|
|
27
|
+
|
|
28
|
+
export function detectOpenAPI(root: any): OasVersion {
|
|
29
|
+
if (typeof root !== 'object') {
|
|
30
|
+
throw new Error(`Document must be JSON object, got ${typeof root}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!(root.openapi || root.swagger)) {
|
|
34
|
+
throw new Error('This doesn’t look like an OpenAPI document.\n');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (root.openapi && root.openapi.startsWith('3.0')) {
|
|
38
|
+
return OasVersion.Version3_0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (root.openapi && root.openapi.startsWith('3.1')) {
|
|
42
|
+
return OasVersion.Version3_1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (root.swagger && root.swagger === '2.0') {
|
|
46
|
+
return OasVersion.Version2;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
throw new Error(`Unsupported OpenAPI Version: ${root.openapi || root.swagger}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function openAPIMajor(version: OasVersion): OasMajorVersion {
|
|
53
|
+
if (version === OasVersion.Version2) {
|
|
54
|
+
return OasMajorVersion.Version2;
|
|
55
|
+
} else {
|
|
56
|
+
return OasMajorVersion.Version3;
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/resolve.ts
CHANGED
|
@@ -4,7 +4,7 @@ import * as url from 'url';
|
|
|
4
4
|
import * as yaml from 'js-yaml';
|
|
5
5
|
import { OasRef } from './typings/openapi';
|
|
6
6
|
import { isRef, joinPointer, escapePointer, parseRef, isAbsoluteUrl } from './ref-utils';
|
|
7
|
-
import {
|
|
7
|
+
import type { YAMLNode, LoadOptions } from 'yaml-ast-parser';
|
|
8
8
|
import { NormalizedNodeType, isNamedType } from './types';
|
|
9
9
|
import { readFileFromUrl } from './utils';
|
|
10
10
|
import { ResolveConfig } from './config/config';
|
|
@@ -17,14 +17,15 @@ export class Source {
|
|
|
17
17
|
private _ast: YAMLNode | undefined;
|
|
18
18
|
private _lines: string[] | undefined;
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
// pass safeLoad as argument to separate it from browser bundle
|
|
21
|
+
getAst(safeLoad: (input: string, options?: LoadOptions | undefined) => YAMLNode) {
|
|
21
22
|
if (this._ast === undefined) {
|
|
22
|
-
this._ast =
|
|
23
|
+
this._ast = safeLoad(this.body, { filename: this.absoluteRef }) ?? undefined;
|
|
23
24
|
|
|
24
25
|
// fix ast representation of file with newlines only
|
|
25
26
|
if (
|
|
26
27
|
this._ast &&
|
|
27
|
-
this._ast.kind ===
|
|
28
|
+
this._ast.kind === 0 && // KIND.scalar = 0
|
|
28
29
|
this._ast.value === '' &&
|
|
29
30
|
this._ast.startPosition !== 1
|
|
30
31
|
) {
|
|
@@ -73,6 +74,18 @@ export type Document = {
|
|
|
73
74
|
parsed: any;
|
|
74
75
|
};
|
|
75
76
|
|
|
77
|
+
export function makeDocumentFromString(sourceString: string, absoluteRef: string) {
|
|
78
|
+
const source = new Source(absoluteRef, sourceString);
|
|
79
|
+
try {
|
|
80
|
+
return {
|
|
81
|
+
source,
|
|
82
|
+
parsed: yaml.safeLoad(sourceString, { filename: absoluteRef }),
|
|
83
|
+
};
|
|
84
|
+
} catch (e) {
|
|
85
|
+
throw new YamlParseError(e, source);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
76
89
|
export class BaseResolver {
|
|
77
90
|
cache: Map<string, Promise<Document | ResolveError>> = new Map();
|
|
78
91
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { LintConfig, RuleConfig } from "../../config/config";
|
|
2
|
+
import { defaultPlugin } from '../../config/builtIn';
|
|
3
|
+
|
|
4
|
+
export function makeConfig(rules: Record<string, RuleConfig>) {
|
|
5
|
+
return new LintConfig({
|
|
6
|
+
plugins: [defaultPlugin],
|
|
7
|
+
extends: [],
|
|
8
|
+
rules
|
|
9
|
+
});
|
|
10
|
+
}
|