diff --git a/.changeset/tame-spoons-show.md b/.changeset/tame-spoons-show.md new file mode 100644 index 0000000000..8a1dc36d07 --- /dev/null +++ b/.changeset/tame-spoons-show.md @@ -0,0 +1,6 @@ +--- +"@redocly/openapi-core": minor +"@redocly/cli": minor +--- + +Moved the `remove-unused-components` decorator to the post-bundle phase so that components that become unused only after `$ref` resolution are correctly removed. diff --git a/packages/core/src/bundle/bundle-document.ts b/packages/core/src/bundle/bundle-document.ts index 3a645e5433..fb6895ebe7 100644 --- a/packages/core/src/bundle/bundle-document.ts +++ b/packages/core/src/bundle/bundle-document.ts @@ -67,17 +67,6 @@ export async function bundleDocument(opts: { visitorsData: {}, }; - if (removeUnusedComponents && !decorators.some((d) => d.ruleId === 'remove-unused-components')) { - decorators.push({ - severity: 'error', - ruleId: 'remove-unused-components', - visitor: - specMajorVersion === 'oas2' - ? RemoveUnusedComponentsOas2({}) - : RemoveUnusedComponentsOas3({}), - }); - } - let resolvedRefMap = await resolveDocument({ rootDocument: document, rootType: normalizedTypes.Root, @@ -114,7 +103,7 @@ export async function bundleDocument(opts: { componentRenamingConflicts, }), }, - ...decorators, + ...decorators.filter((decorator) => decorator.ruleId !== 'remove-unused-components'), ], normalizedTypes ); @@ -127,6 +116,38 @@ export async function bundleDocument(opts: { ctx, }); + if ( + removeUnusedComponents || + config.getDecoratorSettings('remove-unused-components', specVersion).severity !== 'off' + ) { + const postBundleRefMap = await resolveDocument({ + rootDocument: document, + rootType: normalizedTypes.Root, + externalRefResolver, + }); + const postBundleVisitors = normalizeVisitors( + [ + { + severity: 'error', + ruleId: 'remove-unused-components', + visitor: + specMajorVersion === 'oas2' + ? RemoveUnusedComponentsOas2({}) + : RemoveUnusedComponentsOas3({}), + }, + ], + normalizedTypes + ); + + walkDocument({ + document, + rootType: normalizedTypes.Root, + normalizedVisitors: postBundleVisitors, + resolvedRefMap: postBundleRefMap, + ctx, + }); + } + return { bundle: document, problems: ctx.problems.map((problem) => config.addProblemToIgnore(problem)), diff --git a/packages/core/src/decorators/oas2/index.ts b/packages/core/src/decorators/oas2/index.ts index 7130b186e9..60bf7f36f7 100644 --- a/packages/core/src/decorators/oas2/index.ts +++ b/packages/core/src/decorators/oas2/index.ts @@ -16,5 +16,5 @@ export const decorators = { 'remove-x-internal': RemoveXInternal as Oas2Decorator, 'filter-in': FilterIn as Oas2Decorator, 'filter-out': FilterOut as Oas2Decorator, - 'remove-unused-components': RemoveUnusedComponents, // always the last one + 'remove-unused-components': RemoveUnusedComponents, }; diff --git a/packages/core/src/decorators/oas2/remove-unused-components.ts b/packages/core/src/decorators/oas2/remove-unused-components.ts index cc9331d33c..38fc07a247 100644 --- a/packages/core/src/decorators/oas2/remove-unused-components.ts +++ b/packages/core/src/decorators/oas2/remove-unused-components.ts @@ -1,77 +1,77 @@ -import type { Location } from '../../ref-utils.js'; +import { parseRef } from '../../ref-utils.js'; import type { Oas2Components, Oas2Definition } from '../../typings/swagger.js'; import { isEmptyObject } from '../../utils/is-empty-object.js'; import type { Oas2Decorator } from '../../visitors.js'; +const OAS2_COMPONENT_TYPES: (keyof Oas2Components)[] = [ + 'definitions', + 'parameters', + 'responses', + 'securityDefinitions', +]; + export const RemoveUnusedComponents: Oas2Decorator = () => { const components = new Map< string, - { usedIn: Location[]; componentType?: keyof Oas2Components; name: string } + { usedIn: string[]; componentType?: keyof Oas2Components; name: string } >(); - function registerComponent( - location: Location, - componentType: keyof Oas2Components, - name: string - ): void { - components.set(location.absolutePointer, { - usedIn: components.get(location.absolutePointer)?.usedIn ?? [], + function registerComponent(componentType: keyof Oas2Components, name: string): void { + const key = `${componentType}/${name}`; + components.set(key, { + usedIn: components.get(key)?.usedIn ?? [], componentType, name, }); } - function removeUnusedComponents(root: Oas2Definition, removedPaths: string[]): number { - const removedLengthStart = removedPaths.length; + function getComponentKey(pointer: string): string | undefined { + if (!pointer.startsWith('#/')) return; + const [type, name] = parseRef(pointer).pointer; + if (!type || !name) return undefined; + if (!OAS2_COMPONENT_TYPES.includes(type as keyof Oas2Components)) return undefined; + return `${type}/${name}`; + } + + function removeUnusedComponents( + root: Oas2Definition, + removedKeys: Set = new Set() + ): number { + const removedCountBefore = removedKeys.size; + + for (const [key, { usedIn, name, componentType }] of components) { + const used = usedIn.some((sourceKey) => sourceKey !== key && !removedKeys.has(sourceKey)); - for (const [path, { usedIn, name, componentType }] of components) { - const used = usedIn.some( - (location) => - !removedPaths.some( - (removed) => - // Check if the current location's absolute pointer starts with the 'removed' path - // and either its length matches exactly with 'removed' or the character after the 'removed' path is a '/' - location.absolutePointer.startsWith(removed) && - (location.absolutePointer.length === removed.length || - location.absolutePointer[removed.length] === '/') - ) - ); if (!used && componentType) { - removedPaths.push(path); + removedKeys.add(key); delete root[componentType]![name]; - components.delete(path); - + components.delete(key); if (isEmptyObject(root[componentType])) { delete root[componentType]; } } } - return removedPaths.length > removedLengthStart - ? removeUnusedComponents(root, removedPaths) - : removedPaths.length; + return removedKeys.size > removedCountBefore + ? removeUnusedComponents(root, removedKeys) + : removedKeys.size; } return { ref: { - leave(ref, { location, type, resolve, key }) { + leave(ref, { location, type, key }) { if (['Schema', 'Parameter', 'Response', 'SecurityScheme'].includes(type.name)) { - const resolvedRef = resolve(ref); - if (!resolvedRef.location) return; - - const [fileLocation, localPointer] = resolvedRef.location.absolutePointer.split('#', 2); - if (!localPointer) return; - - const componentLevelLocalPointer = localPointer.split('/').slice(0, 3).join('/'); - const pointer = `${fileLocation}#${componentLevelLocalPointer}`; + const targetPointer = getComponentKey(ref.$ref); + if (!targetPointer) return; - const registered = components.get(pointer); + const sourcePointer = getComponentKey(location.pointer) ?? location.pointer; + const registered = components.get(targetPointer); if (registered) { - registered.usedIn.push(location); + registered.usedIn.push(sourcePointer); } else { - components.set(pointer, { - usedIn: [location], + components.set(targetPointer, { + usedIn: [sourcePointer], name: key.toString(), }); } @@ -81,29 +81,29 @@ export const RemoveUnusedComponents: Oas2Decorator = () => { Root: { leave(root, ctx) { const data = ctx.getVisitorData() as { removedCount: number }; - data.removedCount = removeUnusedComponents(root, []); + data.removedCount = removeUnusedComponents(root); }, }, NamedSchemas: { - Schema(schema, { location, key }) { + Schema(schema, { key }) { if (!schema.allOf) { - registerComponent(location, 'definitions', key.toString()); + registerComponent('definitions', key.toString()); } }, }, NamedParameters: { - Parameter(_parameter, { location, key }) { - registerComponent(location, 'parameters', key.toString()); + Parameter(_parameter, { key }) { + registerComponent('parameters', key.toString()); }, }, NamedResponses: { - Response(_response, { location, key }) { - registerComponent(location, 'responses', key.toString()); + Response(_response, { key }) { + registerComponent('responses', key.toString()); }, }, NamedSecuritySchemes: { - SecurityScheme(_securityScheme, { location, key }) { - registerComponent(location, 'securityDefinitions', key.toString()); + SecurityScheme(_securityScheme, { key }) { + registerComponent('securityDefinitions', key.toString()); }, }, }; diff --git a/packages/core/src/decorators/oas3/index.ts b/packages/core/src/decorators/oas3/index.ts index 4f8b0675a2..746915d80a 100644 --- a/packages/core/src/decorators/oas3/index.ts +++ b/packages/core/src/decorators/oas3/index.ts @@ -19,5 +19,5 @@ export const decorators = { 'filter-in': FilterIn as Oas3Decorator, 'filter-out': FilterOut as Oas3Decorator, 'media-type-examples-override': MediaTypeExamplesOverride as Oas3Decorator, - 'remove-unused-components': RemoveUnusedComponents, // always the last one + 'remove-unused-components': RemoveUnusedComponents, }; diff --git a/packages/core/src/decorators/oas3/remove-unused-components.ts b/packages/core/src/decorators/oas3/remove-unused-components.ts index b31b0ced93..5f5a419279 100644 --- a/packages/core/src/decorators/oas3/remove-unused-components.ts +++ b/packages/core/src/decorators/oas3/remove-unused-components.ts @@ -1,4 +1,4 @@ -import type { Location } from '../../ref-utils.js'; +import { parseRef } from '../../ref-utils.js'; import type { Oas3Definition, Oas3_1Definition, @@ -14,41 +14,40 @@ import type { Oas3Decorator } from '../../visitors.js'; type AnyOas3Definition = Oas3Definition | Oas3_1Definition | Oas3_2Definition; type AnyOas3ComponentsKey = keyof Oas3Components | keyof Oas3_1Components | keyof Oas3_2Components; +function getComponentKey(pointer: string): string | undefined { + if (!pointer.startsWith('#/components/')) return; + const [_component, type, name] = parseRef(pointer).pointer; + if (!type || !name) return; + return `${type}/${name}`; +} + export const RemoveUnusedComponents: Oas3Decorator = () => { const components = new Map< string, { - usedIn: Location[]; + usedIn: string[]; componentType?: AnyOas3ComponentsKey; name: string; } >(); - function registerComponent( - location: Location, - componentType: AnyOas3ComponentsKey, - name: string - ): void { - components.set(location.absolutePointer, { - usedIn: components.get(location.absolutePointer)?.usedIn ?? [], + function registerComponent(componentType: AnyOas3ComponentsKey, name: string): void { + const key = `${componentType}/${name}`; + components.set(key, { + usedIn: components.get(key)?.usedIn ?? [], componentType, name, }); } - function removeUnusedComponents(root: AnyOas3Definition, removedPaths: string[]): number { - const removedLengthStart = removedPaths.length; + function removeUnusedComponents( + root: AnyOas3Definition, + removedKeys: Set = new Set() + ): number { + const removedCountBefore = removedKeys.size; - for (const [path, { usedIn, name, componentType }] of components) { - const used = usedIn.some( - (location) => - !removedPaths.some( - (removed) => - location.absolutePointer.startsWith(removed) && - (location.absolutePointer.length === removed.length || - location.absolutePointer[removed.length] === '/') - ) - ); + for (const [key, { usedIn, name, componentType }] of components) { + const used = usedIn.some((sourceKey) => sourceKey !== key && !removedKeys.has(sourceKey)); if ( !used && @@ -56,24 +55,24 @@ export const RemoveUnusedComponents: Oas3Decorator = () => { root.components && hasComponent(root.components, componentType) ) { - removedPaths.push(path); + removedKeys.add(key); const componentChild = root.components[componentType]; delete componentChild![name]; - components.delete(path); + components.delete(key); if (isEmptyObject(componentChild)) { delete root.components[componentType]; } } } - return removedPaths.length > removedLengthStart - ? removeUnusedComponents(root, removedPaths) - : removedPaths.length; + return removedKeys.size > removedCountBefore + ? removeUnusedComponents(root, removedKeys) + : removedKeys.size; } return { ref: { - leave(ref, { location, type, resolve, key }) { + leave(ref, { location, type, key }) { if ( [ 'Schema', @@ -85,22 +84,17 @@ export const RemoveUnusedComponents: Oas3Decorator = () => { 'MediaTypesMap', ].includes(type.name) ) { - const resolvedRef = resolve(ref); - if (!resolvedRef.location) return; - - const [fileLocation, localPointer] = resolvedRef.location.absolutePointer.split('#', 2); - if (!localPointer) return; - - const componentLevelLocalPointer = localPointer.split('/').slice(0, 4).join('/'); - const pointer = `${fileLocation}#${componentLevelLocalPointer}`; + const targetPointer = getComponentKey(ref.$ref); + if (!targetPointer) return; - const registered = components.get(pointer); + const sourcePointer = getComponentKey(location.pointer) ?? location.pointer; + const registered = components.get(targetPointer); if (registered) { - registered.usedIn.push(location); + registered.usedIn.push(sourcePointer); } else { - components.set(pointer, { - usedIn: [location], + components.set(targetPointer, { + usedIn: [sourcePointer], name: key.toString(), }); } @@ -110,7 +104,7 @@ export const RemoveUnusedComponents: Oas3Decorator = () => { Root: { leave(root, ctx) { const data = ctx.getVisitorData() as { removedCount: number }; - data.removedCount = removeUnusedComponents(root, []); + data.removedCount = removeUnusedComponents(root); if (isEmptyObject(root.components)) { delete root.components; @@ -118,40 +112,40 @@ export const RemoveUnusedComponents: Oas3Decorator = () => { }, }, NamedSchemas: { - Schema(schema, { location, key }) { + Schema(schema, { key }) { if (!schema.allOf) { - registerComponent(location, 'schemas', key.toString()); + registerComponent('schemas', key.toString()); } }, }, NamedParameters: { - Parameter(_parameter, { location, key }) { - registerComponent(location, 'parameters', key.toString()); + Parameter(_parameter, { key }) { + registerComponent('parameters', key.toString()); }, }, NamedResponses: { - Response(_response, { location, key }) { - registerComponent(location, 'responses', key.toString()); + Response(_response, { key }) { + registerComponent('responses', key.toString()); }, }, NamedExamples: { - Example(_example, { location, key }) { - registerComponent(location, 'examples', key.toString()); + Example(_example, { key }) { + registerComponent('examples', key.toString()); }, }, NamedRequestBodies: { - RequestBody(_requestBody, { location, key }) { - registerComponent(location, 'requestBodies', key.toString()); + RequestBody(_requestBody, { key }) { + registerComponent('requestBodies', key.toString()); }, }, NamedHeaders: { - Header(_header, { location, key }) { - registerComponent(location, 'headers', key.toString()); + Header(_header, { key }) { + registerComponent('headers', key.toString()); }, }, NamedMediaTypes: { - MediaTypesMap(_mediaTypesMap, { location, key }) { - registerComponent(location, 'mediaTypes', key.toString()); + MediaTypesMap(_mediaTypesMap, { key }) { + registerComponent('mediaTypes', key.toString()); }, }, }; diff --git a/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-opposite-ref/openapi.yaml b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-opposite-ref/openapi.yaml new file mode 100644 index 0000000000..c4bd8c36cf --- /dev/null +++ b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-opposite-ref/openapi.yaml @@ -0,0 +1,21 @@ +openapi: 3.0.0 +info: + title: Sample API + version: 1.0.0 +paths: + /users: + get: + operationId: getUsers + summary: Get a list of users + parameters: + - $ref: parameters.yaml#/Param + responses: + '200': + description: A list of users + content: + application/json: + schema: {} +components: + schemas: + User: + $ref: schemas.yaml#/User diff --git a/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-opposite-ref/parameters.yaml b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-opposite-ref/parameters.yaml new file mode 100644 index 0000000000..04188a821e --- /dev/null +++ b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-opposite-ref/parameters.yaml @@ -0,0 +1,6 @@ +Param: + name: param + in: query + required: true + schema: + $ref: schemas.yaml#/Schema diff --git a/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-opposite-ref/redocly.yaml b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-opposite-ref/redocly.yaml new file mode 100644 index 0000000000..3c417243f8 --- /dev/null +++ b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-opposite-ref/redocly.yaml @@ -0,0 +1,6 @@ +apis: + test-api: + root: openapi.yaml + +decorators: + remove-unused-components: on diff --git a/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-opposite-ref/schemas.yaml b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-opposite-ref/schemas.yaml new file mode 100644 index 0000000000..9adc67534d --- /dev/null +++ b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-opposite-ref/schemas.yaml @@ -0,0 +1,4 @@ +Schema: + type: string +User: + type: string diff --git a/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-opposite-ref/snapshot.txt b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-opposite-ref/snapshot.txt new file mode 100644 index 0000000000..792cc34683 --- /dev/null +++ b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-opposite-ref/snapshot.txt @@ -0,0 +1,32 @@ +openapi: 3.0.0 +info: + title: Sample API + version: 1.0.0 +paths: + /users: + get: + operationId: getUsers + summary: Get a list of users + parameters: + - $ref: '#/components/parameters/Param' + responses: + '200': + description: A list of users + content: + application/json: + schema: {} +components: + schemas: + Schema: + type: string + parameters: + Param: + name: param + in: query + required: true + schema: + $ref: '#/components/schemas/Schema' + +bundling openapi.yaml using configuration for api 'test-api'... +๐Ÿ“ฆ Created a bundle for openapi.yaml at stdout ms. +๐Ÿงน Removed 1 unused components. diff --git a/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-same-ref/openapi.yaml b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-same-ref/openapi.yaml new file mode 100644 index 0000000000..4e31c80daf --- /dev/null +++ b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-same-ref/openapi.yaml @@ -0,0 +1,21 @@ +openapi: 3.1.0 +info: + title: Sample API + version: 1.0.0 +paths: + /users: + get: + operationId: getUsers + summary: Get a list of users + parameters: + - $ref: parameters.yaml#/Param + responses: + '200': + description: A list of users + content: + application/json: + schema: {} +components: + schemas: + User: + $ref: schemas.yaml#/Schema diff --git a/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-same-ref/parameters.yaml b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-same-ref/parameters.yaml new file mode 100644 index 0000000000..04188a821e --- /dev/null +++ b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-same-ref/parameters.yaml @@ -0,0 +1,6 @@ +Param: + name: param + in: query + required: true + schema: + $ref: schemas.yaml#/Schema diff --git a/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-same-ref/redocly.yaml b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-same-ref/redocly.yaml new file mode 100644 index 0000000000..3c417243f8 --- /dev/null +++ b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-same-ref/redocly.yaml @@ -0,0 +1,6 @@ +apis: + test-api: + root: openapi.yaml + +decorators: + remove-unused-components: on diff --git a/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-same-ref/schemas.yaml b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-same-ref/schemas.yaml new file mode 100644 index 0000000000..72170c47b6 --- /dev/null +++ b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-same-ref/schemas.yaml @@ -0,0 +1,2 @@ +Schema: + type: string diff --git a/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-same-ref/snapshot.txt b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-same-ref/snapshot.txt new file mode 100644 index 0000000000..ae192b2ad8 --- /dev/null +++ b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema-with-unused-schema-same-ref/snapshot.txt @@ -0,0 +1,32 @@ +openapi: 3.1.0 +info: + title: Sample API + version: 1.0.0 +paths: + /users: + get: + operationId: getUsers + summary: Get a list of users + parameters: + - $ref: '#/components/parameters/Param' + responses: + '200': + description: A list of users + content: + application/json: + schema: {} +components: + schemas: + Schema: + type: string + parameters: + Param: + name: param + in: query + required: true + schema: + $ref: '#/components/schemas/Schema' + +bundling openapi.yaml using configuration for api 'test-api'... +๐Ÿ“ฆ Created a bundle for openapi.yaml at stdout ms. +๐Ÿงน Removed 1 unused components. diff --git a/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema/openapi.yaml b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema/openapi.yaml new file mode 100644 index 0000000000..e7f89495e4 --- /dev/null +++ b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema/openapi.yaml @@ -0,0 +1,14 @@ +openapi: 3.1.0 +info: + title: Sample API + version: 1.0.0 +paths: + /users: + get: + parameters: + - $ref: parameters.yaml#/Param + responses: + '200': + content: + application/json: + schema: {} diff --git a/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema/parameters.yaml b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema/parameters.yaml new file mode 100644 index 0000000000..04188a821e --- /dev/null +++ b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema/parameters.yaml @@ -0,0 +1,6 @@ +Param: + name: param + in: query + required: true + schema: + $ref: schemas.yaml#/Schema diff --git a/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema/redocly.yaml b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema/redocly.yaml new file mode 100644 index 0000000000..3c417243f8 --- /dev/null +++ b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema/redocly.yaml @@ -0,0 +1,6 @@ +apis: + test-api: + root: openapi.yaml + +decorators: + remove-unused-components: on diff --git a/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema/schemas.yaml b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema/schemas.yaml new file mode 100644 index 0000000000..72170c47b6 --- /dev/null +++ b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema/schemas.yaml @@ -0,0 +1,2 @@ +Schema: + type: string diff --git a/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema/snapshot.txt b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema/snapshot.txt new file mode 100644 index 0000000000..d5657fc7e6 --- /dev/null +++ b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-parameter-ref-to-schema/snapshot.txt @@ -0,0 +1,28 @@ +openapi: 3.1.0 +info: + title: Sample API + version: 1.0.0 +paths: + /users: + get: + parameters: + - $ref: '#/components/parameters/Param' + responses: + '200': + content: + application/json: + schema: {} +components: + schemas: + Schema: + type: string + parameters: + Param: + name: param + in: query + required: true + schema: + $ref: '#/components/schemas/Schema' + +bundling openapi.yaml using configuration for api 'test-api'... +๐Ÿ“ฆ Created a bundle for openapi.yaml at stdout ms. diff --git a/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-recursive-ref/openapi.yaml b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-recursive-ref/openapi.yaml new file mode 100644 index 0000000000..08c09b4eaa --- /dev/null +++ b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-recursive-ref/openapi.yaml @@ -0,0 +1,24 @@ +openapi: 3.1.0 +info: + title: Sample API + version: 1.0.0 +paths: + /users: + get: + responses: + '200': + content: + application/json: + schema: {} +components: + schemas: + RecursiveRef: + type: object + properties: + prop: + type: array + items: + anyOf: + - $ref: '#/components/schemas/RecursiveRef' + UnusedComponent: + type: string diff --git a/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-recursive-ref/redocly.yaml b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-recursive-ref/redocly.yaml new file mode 100644 index 0000000000..3c417243f8 --- /dev/null +++ b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-recursive-ref/redocly.yaml @@ -0,0 +1,6 @@ +apis: + test-api: + root: openapi.yaml + +decorators: + remove-unused-components: on diff --git a/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-recursive-ref/snapshot.txt b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-recursive-ref/snapshot.txt new file mode 100644 index 0000000000..d6907852f9 --- /dev/null +++ b/tests/e2e/bundle/bundle-remove-unused-components-from-config/oas3-recursive-ref/snapshot.txt @@ -0,0 +1,16 @@ +openapi: 3.1.0 +info: + title: Sample API + version: 1.0.0 +paths: + /users: + get: + responses: + '200': + content: + application/json: + schema: {} + +bundling openapi.yaml using configuration for api 'test-api'... +๐Ÿ“ฆ Created a bundle for openapi.yaml at stdout ms. +๐Ÿงน Removed 2 unused components. diff --git a/tests/e2e/bundle/bundle.test.ts b/tests/e2e/bundle/bundle.test.ts index 3e9ab9ca03..0188c55411 100644 --- a/tests/e2e/bundle/bundle.test.ts +++ b/tests/e2e/bundle/bundle.test.ts @@ -73,6 +73,18 @@ describe('bundle with option in config: remove-unused-components', () => { ); }); + test.each([ + 'oas3-parameter-ref-to-schema', + 'oas3-parameter-ref-to-schema-with-unused-schema-same-ref', + 'oas3-parameter-ref-to-schema-with-unused-schema-opposite-ref', + 'oas3-recursive-ref', + ])('%s: should remove unused components', async (type) => { + const testPath = join(__dirname, `bundle-remove-unused-components-from-config/${type}`); + const args = getParams(indexEntryPoint, ['bundle', '--config=redocly.yaml']); + const result = getCommandOutput(args, { testPath }); + await expect(cleanupOutput(result)).toMatchFileSnapshot(join(testPath, 'snapshot.txt')); + }); + test.each(['oas2-without-option', 'oas3-without-option'])( "%s: shouldn't remove unused components", async (type) => {