Skip to content

feat: setup the post-bundle decorators#2733

Merged
AlbinaBlazhko17 merged 22 commits intomainfrom
feat/add-post-decorator-phase
Apr 15, 2026
Merged

feat: setup the post-bundle decorators#2733
AlbinaBlazhko17 merged 22 commits intomainfrom
feat/add-post-decorator-phase

Conversation

@AlbinaBlazhko17
Copy link
Copy Markdown
Contributor

@AlbinaBlazhko17 AlbinaBlazhko17 commented Apr 9, 2026

What/Why/How?

Currently we run remove-unused-components as a regular decorator in the same walk as bundle phase. This creates a lot of side issues with refs in this decorator, which is hard to fix right in the decorator. I decided to run remove-unused-components after doc is bundled.

What i did:

  • Added to bundleDocument new phase to walk through the doc one more time and invoke remove-unused-components on bundled doc.
  • Refactored remove-unused-components to track components by local pointer strings (#/components/schemas/Foo) instead of resolved Location objects. This removes the dependency on resolve() during ref tracking and makes "used-in" tracking simpler and more reliable when running post-bundle (where all refs are already inlined).
  • Added tests to fully cover cases with remove-unused-components.

Reference

Resolves #2350
Resolves #1783

Testing

Screenshots (optional)

Check yourself

  • This PR follows the contributing guide
  • All new/updated code is covered by tests
  • Core code changed? - Tested with other Redocly products (internal contributions only)
  • New package installed? - Tested in different environments (browser/node)
  • Documentation update has been considered

Security

  • The security impact of the change has been considered
  • Code follows company security practices and guidelines

Note

Medium Risk
Changes bundling flow to run remove-unused-components in a new post-bundle pass and refactors its reference-tracking logic; this can change final bundled output and component pruning behavior across OAS2/OAS3.

Overview
Moves remove-unused-components out of the main bundle walk and into a dedicated post-bundle pass (with a fresh resolveDocument), ensuring components that become unused only after $ref resolution are removed.

Refactors the OAS2/OAS3 remove-unused-components decorators to track component usage via stable #/... pointer-derived keys (using parseRef) instead of resolved Location objects, and adds new e2e bundle fixtures covering parameter-to-schema refs and recursive refs. Includes a changeset bumping @redocly/openapi-core and @redocly/cli as minor.

Reviewed by Cursor Bugbot for commit 83d363a. Bugbot is set up for automated code reviews on this repo. Configure here.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 9, 2026

🦋 Changeset detected

Latest commit: 83d363a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@redocly/openapi-core Minor
@redocly/cli Minor
@redocly/respect-core Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

CLI Version Mean Time ± Std Dev (s) Relative Performance (Lower is Faster)
cli-latest 3.375s ± 0.065s ▓ 1.02x
cli-next 3.313s ± 0.032s ▓ 1.00x (Fastest)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 79.94% (🎯 79%) 6732 / 8421
🔵 Statements 79.39% (🎯 79%) 6975 / 8785
🔵 Functions 83.16% (🎯 82%) 1368 / 1645
🔵 Branches 71.6% (🎯 71%) 4579 / 6395
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/core/src/bundle/bundle-document.ts 90.47% 90.9% 100% 90.47% 78-89
packages/core/src/decorators/oas2/index.ts 100% 100% 100% 100%
packages/core/src/decorators/oas2/remove-unused-components.ts 87.8% 87.09% 72.72% 88.88% 31, 50, 96-106
packages/core/src/decorators/oas3/index.ts 100% 100% 100% 100%
packages/core/src/decorators/oas3/remove-unused-components.ts 97.72% 90.9% 100% 100% 20
Generated in workflow #9446 for commit 83d363a by the Vitest Coverage Report Action

@AlbinaBlazhko17
Copy link
Copy Markdown
Contributor Author

@cursor review

@AlbinaBlazhko17 AlbinaBlazhko17 marked this pull request as ready for review April 10, 2026 06:47
@AlbinaBlazhko17 AlbinaBlazhko17 requested review from a team as code owners April 10, 2026 06:47
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: OAS2 getContainingComponentKey lacks local-ref guard unlike OAS3
    • Added if (!pointer.startsWith('#/')) return so external/URL refs are ignored before parseRef strips the URI, matching OAS3 behavior and preventing mis-keying.

Create PR

Or push these changes by commenting:

@cursor push 72816a3152
Preview (72816a3152)
diff --git a/packages/core/src/decorators/oas2/remove-unused-components.ts b/packages/core/src/decorators/oas2/remove-unused-components.ts
--- a/packages/core/src/decorators/oas2/remove-unused-components.ts
+++ b/packages/core/src/decorators/oas2/remove-unused-components.ts
@@ -26,6 +26,7 @@
   }
 
   function getContainingComponentKey(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;

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e144213. Configure here.


const componentLevelLocalPointer = localPointer.split('/').slice(0, 4).join('/');
const pointer = `${fileLocation}#${componentLevelLocalPointer}`;
const targetPointer = getContainingComponentKey(ref.$ref);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const targetPointer = getContainingComponentKey(ref.$ref);
const targetPointer = getComponentKey(ref.$ref);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use something like isComponent instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is better to keep componentKey instead of raw location.pointer, because we have one unified type of targetPointers and we can easily compare them. For example, when we have a circular ref, we can easily compare it. In this case we will have two equal refs: schemas/RecursiveRef. In case of just local pointers we will have #/components/schemas/RecursiveRef and #/components/schemas/RecursiveRef/properties/prop/items/anyOf/0 and it will be hard to compare.

components: 
  schemas: 
    RecursiveRef:
      type: object
      properties: 
        prop:
          type: array
          items: 
            anyOf: 
              - $ref: '#/components/schemas/RecursiveRef'

Copy link
Copy Markdown
Collaborator

@tatomyr tatomyr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left minor comments. Otherwise looks good to me.

@@ -0,0 +1,5 @@
---
"@redocly/openapi-core": minor
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also affects how the CLI behaves and I think it would be useful to specify it directly in it's changelog.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, will add it.

@AlbinaBlazhko17 AlbinaBlazhko17 requested a review from JLekawa April 14, 2026 10:08
@AlbinaBlazhko17 AlbinaBlazhko17 self-assigned this Apr 14, 2026
@AlbinaBlazhko17 AlbinaBlazhko17 merged commit 78205e7 into main Apr 15, 2026
47 checks passed
@AlbinaBlazhko17 AlbinaBlazhko17 deleted the feat/add-post-decorator-phase branch April 15, 2026 07:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

remove-unused-components removes parameters that are in use remove-unused-components doesn't remove recursive schemas

3 participants