@redocly/openapi-core 1.0.0-beta.74 → 1.0.0-beta.78

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.
Files changed (39) hide show
  1. package/lib/bundle.d.ts +4 -0
  2. package/lib/bundle.js +14 -2
  3. package/lib/config/config.d.ts +2 -9
  4. package/lib/format/format.d.ts +1 -1
  5. package/lib/format/format.js +39 -1
  6. package/lib/index.d.ts +1 -1
  7. package/lib/index.js +2 -1
  8. package/lib/rules/builtin.d.ts +2 -0
  9. package/lib/rules/common/remove-x-internal.d.ts +2 -0
  10. package/lib/rules/common/remove-x-internal.js +40 -0
  11. package/lib/rules/oas2/index.d.ts +1 -0
  12. package/lib/rules/oas2/index.js +2 -0
  13. package/lib/rules/oas2/remove-unused-components.d.ts +2 -0
  14. package/lib/rules/oas2/remove-unused-components.js +71 -0
  15. package/lib/rules/oas3/index.d.ts +1 -0
  16. package/lib/rules/oas3/index.js +2 -0
  17. package/lib/rules/oas3/remove-unused-components.d.ts +2 -0
  18. package/lib/rules/oas3/remove-unused-components.js +81 -0
  19. package/lib/typings/swagger.d.ts +14 -0
  20. package/lib/utils.d.ts +2 -0
  21. package/lib/utils.js +9 -1
  22. package/lib/walk.js +4 -7
  23. package/package.json +1 -1
  24. package/src/bundle.ts +17 -1
  25. package/src/config/config.ts +2 -3
  26. package/src/format/format.ts +47 -2
  27. package/src/index.ts +1 -1
  28. package/src/rules/__tests__/config.ts +5 -4
  29. package/src/rules/__tests__/hide-internals.test.ts +234 -0
  30. package/src/rules/common/remove-x-internal.ts +42 -0
  31. package/src/rules/oas2/index.ts +3 -1
  32. package/src/rules/oas2/remove-unused-components.ts +74 -0
  33. package/src/rules/oas3/index.ts +2 -0
  34. package/src/rules/oas3/remove-unused-components.ts +82 -0
  35. package/src/types/oas2.ts +0 -3
  36. package/src/typings/swagger.ts +7 -0
  37. package/src/utils.ts +8 -0
  38. package/src/walk.ts +4 -9
  39. package/tsconfig.tsbuildinfo +1 -1
package/lib/bundle.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { BaseResolver, Document } from './resolve';
2
2
  import { Oas3Rule } from './visitors';
3
3
  import { NormalizedNodeType, NodeType } from './types';
4
+ import { OasMajorVersion } from './oas-types';
4
5
  import type { Config, LintConfig } from './config/config';
5
6
  export declare type Oas3RuleSet = Record<string, Oas3Rule>;
6
7
  export declare enum OasVersion {
@@ -16,6 +17,7 @@ export declare function bundle(opts: {
16
17
  dereference?: boolean;
17
18
  base?: string;
18
19
  skipRedoclyRegistryRefs?: boolean;
20
+ removeUnusedComponents?: boolean;
19
21
  }): Promise<{
20
22
  bundle: Document;
21
23
  problems: import("./walk").NormalizedProblem[];
@@ -31,6 +33,7 @@ export declare function bundleDocument(opts: {
31
33
  externalRefResolver: BaseResolver;
32
34
  dereference?: boolean;
33
35
  skipRedoclyRegistryRefs?: boolean;
36
+ removeUnusedComponents?: boolean;
34
37
  }): Promise<{
35
38
  bundle: Document;
36
39
  problems: import("./walk").NormalizedProblem[];
@@ -39,3 +42,4 @@ export declare function bundleDocument(opts: {
39
42
  refTypes: Map<string, NormalizedNodeType> | undefined;
40
43
  visitorsData: Record<string, Record<string, unknown>>;
41
44
  }>;
45
+ export declare function mapTypeToComponent(typeName: string, version: OasMajorVersion): "headers" | "responses" | "definitions" | "parameters" | "schemas" | "examples" | "requestBodies" | "securitySchemes" | "links" | "callbacks" | null;
package/lib/bundle.js CHANGED
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.bundleDocument = exports.bundle = exports.OasVersion = void 0;
12
+ exports.mapTypeToComponent = exports.bundleDocument = exports.bundle = exports.OasVersion = void 0;
13
13
  const isEqual = require("lodash.isequal");
14
14
  const resolve_1 = require("./resolve");
15
15
  const visitors_1 = require("./visitors");
@@ -24,6 +24,8 @@ const rules_1 = require("./config/rules");
24
24
  const no_unresolved_refs_1 = require("./rules/no-unresolved-refs");
25
25
  const utils_1 = require("./utils");
26
26
  const redocly_1 = require("./redocly");
27
+ const remove_unused_components_1 = require("./rules/oas2/remove-unused-components");
28
+ const remove_unused_components_2 = require("./rules/oas3/remove-unused-components");
27
29
  var OasVersion;
28
30
  (function (OasVersion) {
29
31
  OasVersion["Version2"] = "oas2";
@@ -46,7 +48,7 @@ function bundle(opts) {
46
48
  exports.bundle = bundle;
47
49
  function bundleDocument(opts) {
48
50
  return __awaiter(this, void 0, void 0, function* () {
49
- const { document, config, customTypes, externalRefResolver, dereference = false, skipRedoclyRegistryRefs = false, } = opts;
51
+ const { document, config, customTypes, externalRefResolver, dereference = false, skipRedoclyRegistryRefs = false, removeUnusedComponents = false, } = opts;
50
52
  const oasVersion = oas_types_1.detectOpenAPI(document.parsed);
51
53
  const oasMajorVersion = oas_types_1.openAPIMajor(oasVersion);
52
54
  const rules = config.getRulesForOasVersion(oasMajorVersion);
@@ -63,6 +65,15 @@ function bundleDocument(opts) {
63
65
  refTypes: new Map(),
64
66
  visitorsData: {},
65
67
  };
68
+ if (removeUnusedComponents) {
69
+ decorators.push({
70
+ severity: 'error',
71
+ ruleId: 'remove-unused-components',
72
+ visitor: oasMajorVersion === oas_types_1.OasMajorVersion.Version2
73
+ ? remove_unused_components_1.RemoveUnusedComponents({})
74
+ : remove_unused_components_2.RemoveUnusedComponents({})
75
+ });
76
+ }
66
77
  const bundleVisitor = visitors_1.normalizeVisitors([
67
78
  ...preprocessors,
68
79
  {
@@ -133,6 +144,7 @@ function mapTypeToComponent(typeName, version) {
133
144
  }
134
145
  }
135
146
  }
147
+ exports.mapTypeToComponent = mapTypeToComponent;
136
148
  // function oas3Move
137
149
  function makeBundleVisitor(version, dereference, skipRedoclyRegistryRefs, rootDocument) {
138
150
  let components;
@@ -5,10 +5,9 @@ export declare const IGNORE_FILE = ".redocly.lint-ignore.yaml";
5
5
  export declare type RuleConfig = ProblemSeverity | 'off' | ({
6
6
  severity?: ProblemSeverity;
7
7
  } & Record<string, any>);
8
- export declare type PreprocessorConfig = ProblemSeverity | 'off' | 'on' | {
8
+ export declare type PreprocessorConfig = ProblemSeverity | 'off' | 'on' | ({
9
9
  severity?: ProblemSeverity;
10
- options?: Record<string, any>;
11
- };
10
+ } & Record<string, any>);
12
11
  export declare type DecoratorConfig = PreprocessorConfig;
13
12
  export declare type LintRawConfig = {
14
13
  plugins?: (string | Plugin)[];
@@ -107,15 +106,9 @@ export declare class LintConfig {
107
106
  };
108
107
  getPreprocessorSettings(ruleId: string, oasVersion: OasVersion): {
109
108
  severity: ProblemSeverity | "off";
110
- } | {
111
- severity: ProblemSeverity;
112
- options?: Record<string, any> | undefined;
113
109
  };
114
110
  getDecoratorSettings(ruleId: string, oasVersion: OasVersion): {
115
111
  severity: ProblemSeverity | "off";
116
- } | {
117
- severity: ProblemSeverity;
118
- options?: Record<string, any> | undefined;
119
112
  };
120
113
  getUnusedRules(): {
121
114
  rules: string[];
@@ -4,7 +4,7 @@ export declare type Totals = {
4
4
  warnings: number;
5
5
  ignored: number;
6
6
  };
7
- export declare type OutputFormat = 'codeframe' | 'stylish' | 'json';
7
+ export declare type OutputFormat = 'codeframe' | 'stylish' | 'json' | 'checkstyle';
8
8
  export declare function getTotals(problems: (NormalizedProblem & {
9
9
  ignored?: boolean;
10
10
  })[]): Totals;
@@ -66,7 +66,7 @@ function formatProblems(problems, opts) {
66
66
  process.stderr.write(`${formatCodeframe(problem, i)}\n`);
67
67
  }
68
68
  break;
69
- case 'stylish':
69
+ case 'stylish': {
70
70
  const groupedByFile = groupByFiles(problems);
71
71
  for (const [file, { ruleIdPad, locationPad: positionPad, fileProblems }] of Object.entries(groupedByFile)) {
72
72
  process.stderr.write(`${colorette_1.blue(path.relative(cwd, file))}:\n`);
@@ -77,6 +77,19 @@ function formatProblems(problems, opts) {
77
77
  process.stderr.write('\n');
78
78
  }
79
79
  break;
80
+ }
81
+ case 'checkstyle': {
82
+ const groupedByFile = groupByFiles(problems);
83
+ process.stdout.write('<?xml version="1.0" encoding="UTF-8"?>\n');
84
+ process.stdout.write('<checkstyle version="4.3">\n');
85
+ for (const [file, { fileProblems }] of Object.entries(groupedByFile)) {
86
+ process.stdout.write(`<file name="${xmlEscape(path.relative(cwd, file))}">\n`);
87
+ fileProblems.forEach(formatCheckstyle);
88
+ process.stdout.write(`</file>\n`);
89
+ }
90
+ process.stdout.write(`</checkstyle>\n`);
91
+ break;
92
+ }
80
93
  }
81
94
  if (totalProblems - ignoredProblems > maxProblems) {
82
95
  process.stderr.write(`< ... ${totalProblems - maxProblems} more problems hidden > ${colorette_1.gray('increase with `--max-problems N`')}\n`);
@@ -131,6 +144,13 @@ function formatProblems(problems, opts) {
131
144
  const { start } = problem.location[0];
132
145
  return ` ${`${start.line}:${start.col}`.padEnd(locationPad)} ${severityName} ${problem.ruleId.padEnd(ruleIdPad)} ${problem.message}`;
133
146
  }
147
+ function formatCheckstyle(problem) {
148
+ const { line, col } = problem.location[0].start;
149
+ const severity = problem.severity == 'warn' ? 'warning' : 'error';
150
+ const message = xmlEscape(problem.message);
151
+ const source = xmlEscape(problem.ruleId);
152
+ process.stdout.write(`<error line="${line}" column="${col}" severity="${severity}" message="${message}" source="${source}" />\n`);
153
+ }
134
154
  }
135
155
  exports.formatProblems = formatProblems;
136
156
  function formatFrom(cwd, location) {
@@ -167,3 +187,21 @@ const groupByFiles = (problems) => {
167
187
  }
168
188
  return fileGroups;
169
189
  };
190
+ function xmlEscape(s) {
191
+ return s.replace(/[<>&"'\x00-\x1F\x7F\u0080-\uFFFF]/gu, (char) => {
192
+ switch (char) {
193
+ case '<':
194
+ return '&lt;';
195
+ case '>':
196
+ return '&gt;';
197
+ case '&':
198
+ return '&amp;';
199
+ case '"':
200
+ return '&quot;';
201
+ case "'":
202
+ return '&apos;';
203
+ default:
204
+ return `&#${char.charCodeAt(0)};`;
205
+ }
206
+ });
207
+ }
package/lib/index.d.ts CHANGED
@@ -20,4 +20,4 @@ export { WalkContext, walkDocument, NormalizedProblem, ProblemSeverity, LineColL
20
20
  export { getAstNodeByPointer, getLineColLocation } from './format/codeframes';
21
21
  export { formatProblems, OutputFormat, getTotals, Totals } from './format/format';
22
22
  export { lint, lint as validate, lintDocument, lintFromString, lintConfig } from './lint';
23
- export { bundle, bundleDocument } from './bundle';
23
+ export { bundle, bundleDocument, mapTypeToComponent } from './bundle';
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.bundleDocument = exports.bundle = exports.lintConfig = exports.lintFromString = exports.lintDocument = exports.validate = exports.lint = exports.getTotals = exports.formatProblems = exports.getLineColLocation = exports.getAstNodeByPointer = exports.walkDocument = exports.normalizeVisitors = exports.OasVersion = exports.openAPIMajor = exports.OasMajorVersion = exports.detectOpenAPI = exports.unescapePointer = exports.stringifyYaml = exports.parseYaml = exports.makeDocumentFromString = exports.YamlParseError = exports.ResolveError = exports.resolveDocument = exports.BaseResolver = exports.Source = exports.RedoclyClient = exports.loadConfig = exports.IGNORE_FILE = exports.LintConfig = exports.Config = exports.Stats = exports.normalizeTypes = exports.ConfigTypes = exports.Oas2Types = exports.Oas3Types = exports.Oas3_1Types = exports.slash = exports.readFileFromUrl = void 0;
3
+ exports.mapTypeToComponent = exports.bundleDocument = exports.bundle = exports.lintConfig = exports.lintFromString = exports.lintDocument = exports.validate = exports.lint = exports.getTotals = exports.formatProblems = exports.getLineColLocation = exports.getAstNodeByPointer = exports.walkDocument = exports.normalizeVisitors = exports.OasVersion = exports.openAPIMajor = exports.OasMajorVersion = exports.detectOpenAPI = exports.unescapePointer = exports.stringifyYaml = exports.parseYaml = exports.makeDocumentFromString = exports.YamlParseError = exports.ResolveError = exports.resolveDocument = exports.BaseResolver = exports.Source = exports.RedoclyClient = exports.loadConfig = exports.IGNORE_FILE = exports.LintConfig = exports.Config = exports.Stats = exports.normalizeTypes = exports.ConfigTypes = exports.Oas2Types = exports.Oas3Types = exports.Oas3_1Types = exports.slash = exports.readFileFromUrl = void 0;
4
4
  var utils_1 = require("./utils");
5
5
  Object.defineProperty(exports, "readFileFromUrl", { enumerable: true, get: function () { return utils_1.readFileFromUrl; } });
6
6
  Object.defineProperty(exports, "slash", { enumerable: true, get: function () { return utils_1.slash; } });
@@ -60,3 +60,4 @@ Object.defineProperty(exports, "lintConfig", { enumerable: true, get: function (
60
60
  var bundle_1 = require("./bundle");
61
61
  Object.defineProperty(exports, "bundle", { enumerable: true, get: function () { return bundle_1.bundle; } });
62
62
  Object.defineProperty(exports, "bundleDocument", { enumerable: true, get: function () { return bundle_1.bundleDocument; } });
63
+ Object.defineProperty(exports, "mapTypeToComponent", { enumerable: true, get: function () { return bundle_1.mapTypeToComponent; } });
@@ -10,11 +10,13 @@ export declare const decorators: {
10
10
  'operation-description-override': import("../visitors").Oas3Decorator;
11
11
  'tag-description-override': import("../visitors").Oas3Decorator;
12
12
  'info-description-override': import("../visitors").Oas3Decorator;
13
+ 'remove-x-internal': import("../visitors").Oas3Decorator;
13
14
  };
14
15
  oas2: {
15
16
  'registry-dependencies': import("../visitors").Oas2Decorator;
16
17
  'operation-description-override': import("../visitors").Oas2Decorator;
17
18
  'tag-description-override': import("../visitors").Oas2Decorator;
18
19
  'info-description-override': import("../visitors").Oas2Decorator;
20
+ 'remove-x-internal': import("../visitors").Oas2Decorator;
19
21
  };
20
22
  };
@@ -0,0 +1,2 @@
1
+ import { Oas3Decorator, Oas2Decorator } from '../../visitors';
2
+ export declare const RemoveXInternal: Oas3Decorator | Oas2Decorator;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RemoveXInternal = void 0;
4
+ const utils_1 = require("../../utils");
5
+ const DEFAULT_INTERNAL_PROPERTY_NAME = 'x-internal';
6
+ const RemoveXInternal = ({ internalFlagProperty }) => {
7
+ const hiddenTag = internalFlagProperty || DEFAULT_INTERNAL_PROPERTY_NAME;
8
+ function removeInternal(node, ctx) {
9
+ const { parent, key } = ctx;
10
+ let didDelete = false;
11
+ if (Array.isArray(node)) {
12
+ for (let i = 0; i < node.length; i++) {
13
+ if (node[i] && node[i][hiddenTag]) {
14
+ node.splice(i, 1);
15
+ didDelete = true;
16
+ i--;
17
+ }
18
+ }
19
+ }
20
+ else if (utils_1.isPlainObject(node)) {
21
+ for (const key of Object.keys(node)) {
22
+ if (node[key][hiddenTag]) {
23
+ delete node[key];
24
+ didDelete = true;
25
+ }
26
+ }
27
+ }
28
+ if (didDelete && (utils_1.isEmptyObject(node) || utils_1.isEmptyArray(node))) {
29
+ delete parent[key];
30
+ }
31
+ }
32
+ return {
33
+ any: {
34
+ enter: (node, ctx) => {
35
+ removeInternal(node, ctx);
36
+ }
37
+ }
38
+ };
39
+ };
40
+ exports.RemoveXInternal = RemoveXInternal;
@@ -45,4 +45,5 @@ export declare const decorators: {
45
45
  'operation-description-override': Oas2Decorator;
46
46
  'tag-description-override': Oas2Decorator;
47
47
  'info-description-override': Oas2Decorator;
48
+ 'remove-x-internal': Oas2Decorator;
48
49
  };
@@ -42,6 +42,7 @@ const path_segment_plural_1 = require("../common/path-segment-plural");
42
42
  const operation_description_override_1 = require("../common/operation-description-override");
43
43
  const tag_description_override_1 = require("../common/tag-description-override");
44
44
  const info_description_override_1 = require("../common/info-description-override");
45
+ const remove_x_internal_1 = require("../common/remove-x-internal");
45
46
  exports.rules = {
46
47
  spec: spec_1.OasSpec,
47
48
  'no-invalid-schema-examples': no_invalid_schema_examples_1.NoInvalidSchemaExamples,
@@ -88,4 +89,5 @@ exports.decorators = {
88
89
  'operation-description-override': operation_description_override_1.OperationDescriptionOverride,
89
90
  'tag-description-override': tag_description_override_1.TagDescriptionOverride,
90
91
  'info-description-override': info_description_override_1.InfoDescriptionOverride,
92
+ 'remove-x-internal': remove_x_internal_1.RemoveXInternal
91
93
  };
@@ -0,0 +1,2 @@
1
+ import { Oas2Rule } from '../../visitors';
2
+ export declare const RemoveUnusedComponents: Oas2Rule;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RemoveUnusedComponents = void 0;
4
+ const utils_1 = require("../../utils");
5
+ const RemoveUnusedComponents = () => {
6
+ let components = new Map();
7
+ function registerComponent(location, componentType, name) {
8
+ var _a;
9
+ components.set(location.absolutePointer, {
10
+ used: ((_a = components.get(location.absolutePointer)) === null || _a === void 0 ? void 0 : _a.used) || false,
11
+ componentType,
12
+ name,
13
+ });
14
+ }
15
+ return {
16
+ ref(ref, { type, resolve, key }) {
17
+ if (['Schema', 'Parameter', 'Response', 'SecurityScheme'].includes(type.name)) {
18
+ const resolvedRef = resolve(ref);
19
+ if (!resolvedRef.location)
20
+ return;
21
+ components.set(resolvedRef.location.absolutePointer, {
22
+ used: true,
23
+ name: key.toString(),
24
+ });
25
+ }
26
+ },
27
+ DefinitionRoot: {
28
+ leave(root, ctx) {
29
+ const data = ctx.getVisitorData();
30
+ data.removedCount = 0;
31
+ let rootComponents = new Set();
32
+ components.forEach(usageInfo => {
33
+ const { used, name, componentType } = usageInfo;
34
+ if (!used && componentType) {
35
+ rootComponents.add(componentType);
36
+ delete root[componentType][name];
37
+ data.removedCount++;
38
+ }
39
+ });
40
+ for (const component of rootComponents) {
41
+ if (utils_1.isEmptyObject(root[component])) {
42
+ delete root[component];
43
+ }
44
+ }
45
+ },
46
+ },
47
+ NamedSchemas: {
48
+ Schema(schema, { location, key }) {
49
+ if (!schema.allOf) {
50
+ registerComponent(location, 'definitions', key.toString());
51
+ }
52
+ },
53
+ },
54
+ NamedParameters: {
55
+ Parameter(_parameter, { location, key }) {
56
+ registerComponent(location, 'parameters', key.toString());
57
+ },
58
+ },
59
+ NamedResponses: {
60
+ Response(_response, { location, key }) {
61
+ registerComponent(location, 'responses', key.toString());
62
+ },
63
+ },
64
+ NamedSecuritySchemes: {
65
+ SecurityScheme(_securityScheme, { location, key }) {
66
+ registerComponent(location, 'securityDefinitions', key.toString());
67
+ },
68
+ }
69
+ };
70
+ };
71
+ exports.RemoveUnusedComponents = RemoveUnusedComponents;
@@ -7,4 +7,5 @@ export declare const decorators: {
7
7
  'operation-description-override': Oas3Decorator;
8
8
  'tag-description-override': Oas3Decorator;
9
9
  'info-description-override': Oas3Decorator;
10
+ 'remove-x-internal': Oas3Decorator;
10
11
  };
@@ -50,6 +50,7 @@ const info_description_override_1 = require("../common/info-description-override
50
50
  const path_excludes_patterns_1 = require("../common/path-excludes-patterns");
51
51
  const no_invalid_schema_examples_1 = require("../common/no-invalid-schema-examples");
52
52
  const no_invalid_parameter_examples_1 = require("../common/no-invalid-parameter-examples");
53
+ const remove_x_internal_1 = require("../common/remove-x-internal");
53
54
  exports.rules = {
54
55
  spec: spec_1.OasSpec,
55
56
  'info-description': info_description_1.InfoDescription,
@@ -104,4 +105,5 @@ exports.decorators = {
104
105
  'operation-description-override': operation_description_override_1.OperationDescriptionOverride,
105
106
  'tag-description-override': tag_description_override_1.TagDescriptionOverride,
106
107
  'info-description-override': info_description_override_1.InfoDescriptionOverride,
108
+ 'remove-x-internal': remove_x_internal_1.RemoveXInternal
107
109
  };
@@ -0,0 +1,2 @@
1
+ import { Oas3Rule } from '../../visitors';
2
+ export declare const RemoveUnusedComponents: Oas3Rule;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RemoveUnusedComponents = void 0;
4
+ const utils_1 = require("../../utils");
5
+ const RemoveUnusedComponents = () => {
6
+ let components = new Map();
7
+ function registerComponent(location, componentType, name) {
8
+ var _a;
9
+ components.set(location.absolutePointer, {
10
+ used: ((_a = components.get(location.absolutePointer)) === null || _a === void 0 ? void 0 : _a.used) || false,
11
+ componentType,
12
+ name,
13
+ });
14
+ }
15
+ return {
16
+ ref(ref, { type, resolve, key }) {
17
+ if (['Schema', 'Header', 'Parameter', 'Response', 'Example', 'RequestBody'].includes(type.name)) {
18
+ const resolvedRef = resolve(ref);
19
+ if (!resolvedRef.location)
20
+ return;
21
+ components.set(resolvedRef.location.absolutePointer, {
22
+ used: true,
23
+ name: key.toString(),
24
+ });
25
+ }
26
+ },
27
+ DefinitionRoot: {
28
+ leave(root, ctx) {
29
+ const data = ctx.getVisitorData();
30
+ data.removedCount = 0;
31
+ components.forEach(usageInfo => {
32
+ const { used, componentType, name } = usageInfo;
33
+ if (!used && componentType) {
34
+ let componentChild = root.components[componentType];
35
+ delete componentChild[name];
36
+ data.removedCount++;
37
+ if (utils_1.isEmptyObject(componentChild)) {
38
+ delete root.components[componentType];
39
+ }
40
+ }
41
+ });
42
+ if (utils_1.isEmptyObject(root.components)) {
43
+ delete root.components;
44
+ }
45
+ },
46
+ },
47
+ NamedSchemas: {
48
+ Schema(schema, { location, key }) {
49
+ if (!schema.allOf) {
50
+ registerComponent(location, 'schemas', key.toString());
51
+ }
52
+ },
53
+ },
54
+ NamedParameters: {
55
+ Parameter(_parameter, { location, key }) {
56
+ registerComponent(location, 'parameters', key.toString());
57
+ },
58
+ },
59
+ NamedResponses: {
60
+ Response(_response, { location, key }) {
61
+ registerComponent(location, 'responses', key.toString());
62
+ },
63
+ },
64
+ NamedExamples: {
65
+ Example(_example, { location, key }) {
66
+ registerComponent(location, 'examples', key.toString());
67
+ },
68
+ },
69
+ NamedRequestBodies: {
70
+ RequestBody(_requestBody, { location, key }) {
71
+ registerComponent(location, 'requestBodies', key.toString());
72
+ },
73
+ },
74
+ NamedHeaders: {
75
+ Header(_header, { location, key }) {
76
+ registerComponent(location, 'headers', key.toString());
77
+ },
78
+ },
79
+ };
80
+ };
81
+ exports.RemoveUnusedComponents = RemoveUnusedComponents;
@@ -16,6 +16,20 @@ export interface Oas2Definition {
16
16
  tags?: Oas2Tag[];
17
17
  externalDocs?: Oas2ExternalDocs;
18
18
  }
19
+ export interface Oas2Components {
20
+ definitions?: {
21
+ [name: string]: Record<string, Oas2Schema>;
22
+ };
23
+ securityDefinitions?: {
24
+ [name: string]: Record<string, Oas2SecurityScheme>;
25
+ };
26
+ responses?: {
27
+ [name: string]: Record<string, Oas2Response>;
28
+ };
29
+ parameters?: {
30
+ [name: string]: Record<string, Oas2Parameter>;
31
+ };
32
+ }
19
33
  export interface Oas2Info {
20
34
  title: string;
21
35
  version: string;
package/lib/utils.d.ts CHANGED
@@ -16,6 +16,8 @@ export declare type BundleOutputFormat = 'json' | 'yml' | 'yaml';
16
16
  export declare function loadYaml(filename: string): Promise<unknown>;
17
17
  export declare function notUndefined<T>(x: T | undefined): x is T;
18
18
  export declare function isPlainObject(value: any): value is object;
19
+ export declare function isEmptyObject(value: any): value is object;
20
+ export declare function isEmptyArray(value: any): boolean;
19
21
  export declare function readFileFromUrl(url: string, config: HttpResolveConfig): Promise<{
20
22
  body: any;
21
23
  mimeType: any;
package/lib/utils.js CHANGED
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.isNotEmptyObject = exports.slash = exports.isPathParameter = exports.readFileAsStringSync = exports.isSingular = exports.validateMimeTypeOAS3 = exports.validateMimeType = exports.splitCamelCaseIntoWords = exports.omitObjectProps = exports.pickObjectProps = exports.match = exports.readFileFromUrl = exports.isPlainObject = exports.notUndefined = exports.loadYaml = exports.popStack = exports.pushStack = exports.stringifyYaml = exports.parseYaml = void 0;
12
+ exports.isNotEmptyObject = exports.slash = exports.isPathParameter = exports.readFileAsStringSync = exports.isSingular = exports.validateMimeTypeOAS3 = exports.validateMimeType = exports.splitCamelCaseIntoWords = exports.omitObjectProps = exports.pickObjectProps = exports.match = exports.readFileFromUrl = exports.isEmptyArray = exports.isEmptyObject = exports.isPlainObject = exports.notUndefined = exports.loadYaml = exports.popStack = exports.pushStack = exports.stringifyYaml = exports.parseYaml = void 0;
13
13
  const fs = require("fs");
14
14
  const minimatch = require("minimatch");
15
15
  const node_fetch_1 = require("node-fetch");
@@ -42,6 +42,14 @@ function isPlainObject(value) {
42
42
  return value !== null && typeof value === 'object' && !Array.isArray(value);
43
43
  }
44
44
  exports.isPlainObject = isPlainObject;
45
+ function isEmptyObject(value) {
46
+ return isPlainObject(value) && Object.keys(value).length === 0;
47
+ }
48
+ exports.isEmptyObject = isEmptyObject;
49
+ function isEmptyArray(value) {
50
+ return Array.isArray(value) && value.length === 0;
51
+ }
52
+ exports.isEmptyArray = isEmptyArray;
45
53
  function readFileFromUrl(url, config) {
46
54
  return __awaiter(this, void 0, void 0, function* () {
47
55
  const headers = {};
package/lib/walk.js CHANGED
@@ -101,10 +101,9 @@ function walkDocument(opts) {
101
101
  if (!activatedOn.skipped) {
102
102
  visitedBySome = true;
103
103
  enteredContexts.add(context);
104
- const ignoreNextVisitorsOnNode = visitWithContext(visit, resolvedNode, context, ruleId, severity);
105
- if (ignoreNextVisitorsOnNode) {
104
+ const { ignoreNextVisitorsOnNode } = visitWithContext(visit, resolvedNode, context, ruleId, severity);
105
+ if (ignoreNextVisitorsOnNode)
106
106
  break;
107
- }
108
107
  }
109
108
  }
110
109
  }
@@ -211,12 +210,10 @@ function walkDocument(opts) {
211
210
  key,
212
211
  parentLocations: collectParentsLocations(context),
213
212
  oasVersion: ctx.oasVersion,
214
- ignoreNextVisitorsOnNode: () => {
215
- ignoreNextVisitorsOnNode = true;
216
- },
213
+ ignoreNextVisitorsOnNode: () => { ignoreNextVisitorsOnNode = true; },
217
214
  getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
218
215
  }, collectParents(context), context);
219
- return ignoreNextVisitorsOnNode;
216
+ return { ignoreNextVisitorsOnNode };
220
217
  }
221
218
  function resolve(ref, from = currentLocation.source.absoluteRef) {
222
219
  if (!ref_utils_1.isRef(ref))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/openapi-core",
3
- "version": "1.0.0-beta.74",
3
+ "version": "1.0.0-beta.78",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "engines": {
package/src/bundle.ts CHANGED
@@ -14,6 +14,8 @@ import { reportUnresolvedRef } from './rules/no-unresolved-refs';
14
14
  import { isPlainObject } from './utils';
15
15
  import { OasRef } from './typings/openapi';
16
16
  import { isRedoclyRegistryURL } from './redocly';
17
+ import { RemoveUnusedComponents as RemoveUnusedComponentsOas2 } from './rules/oas2/remove-unused-components';
18
+ import { RemoveUnusedComponents as RemoveUnusedComponentsOas3 } from './rules/oas3/remove-unused-components';
17
19
 
18
20
  export type Oas3RuleSet = Record<string, Oas3Rule>;
19
21
 
@@ -31,6 +33,7 @@ export async function bundle(opts: {
31
33
  dereference?: boolean;
32
34
  base?: string;
33
35
  skipRedoclyRegistryRefs?: boolean;
36
+ removeUnusedComponents?: boolean;
34
37
  }) {
35
38
  const {
36
39
  ref,
@@ -66,6 +69,7 @@ export async function bundleDocument(opts: {
66
69
  externalRefResolver: BaseResolver;
67
70
  dereference?: boolean;
68
71
  skipRedoclyRegistryRefs?: boolean;
72
+ removeUnusedComponents?: boolean;
69
73
  }) {
70
74
  const {
71
75
  document,
@@ -74,6 +78,7 @@ export async function bundleDocument(opts: {
74
78
  externalRefResolver,
75
79
  dereference = false,
76
80
  skipRedoclyRegistryRefs = false,
81
+ removeUnusedComponents = false,
77
82
  } = opts;
78
83
  const oasVersion = detectOpenAPI(document.parsed);
79
84
  const oasMajorVersion = openAPIMajor(oasVersion);
@@ -92,6 +97,7 @@ export async function bundleDocument(opts: {
92
97
 
93
98
  const preprocessors = initRules(rules as any, config, 'preprocessors', oasVersion);
94
99
  const decorators = initRules(rules as any, config, 'decorators', oasVersion);
100
+
95
101
  const ctx: BundleContext = {
96
102
  problems: [],
97
103
  oasVersion: oasVersion,
@@ -99,6 +105,16 @@ export async function bundleDocument(opts: {
99
105
  visitorsData: {},
100
106
  };
101
107
 
108
+ if (removeUnusedComponents) {
109
+ decorators.push({
110
+ severity: 'error',
111
+ ruleId: 'remove-unused-components',
112
+ visitor: oasMajorVersion === OasMajorVersion.Version2
113
+ ? RemoveUnusedComponentsOas2({})
114
+ : RemoveUnusedComponentsOas3({})
115
+ })
116
+ }
117
+
102
118
  const bundleVisitor = normalizeVisitors(
103
119
  [
104
120
  ...preprocessors,
@@ -136,7 +152,7 @@ export async function bundleDocument(opts: {
136
152
  };
137
153
  }
138
154
 
139
- function mapTypeToComponent(typeName: string, version: OasMajorVersion) {
155
+ export function mapTypeToComponent(typeName: string, version: OasMajorVersion) {
140
156
  switch (version) {
141
157
  case OasMajorVersion.Version3:
142
158
  switch (typeName) {
@@ -38,10 +38,9 @@ export type PreprocessorConfig =
38
38
  | ProblemSeverity
39
39
  | 'off'
40
40
  | 'on'
41
- | {
41
+ | ({
42
42
  severity?: ProblemSeverity;
43
- options?: Record<string, any>;
44
- };
43
+ } & Record<string, any>);
45
44
 
46
45
  export type DecoratorConfig = PreprocessorConfig;
47
46