Skip to content

feat: add support for the strategy parameter in component-name-unique rule - #3011

Open
harshit078 wants to merge 10 commits into
Redocly:mainfrom
harshit078:feat-support-for-strategy-parameter
Open

feat: add support for the strategy parameter in component-name-unique rule#3011
harshit078 wants to merge 10 commits into
Redocly:mainfrom
harshit078:feat-support-for-strategy-parameter

Conversation

@harshit078

@harshit078 harshit078 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

What/Why/How?

#2898

Reference

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

Low Risk
Lint/bundle naming alignment only; default behavior unchanged and coverage is strong. Misconfigured strategy could cause false positives/negatives relative to bundle, not runtime security issues.

Overview
Adds a strategy option (basename or title, default basename) to the component-name-unique rule so lint can predict the component keys bundle will produce when using --component-names-strategy.

With strategy: title, externally referenced schemas are checked for collisions on title-derived names (PascalCase + sanitization), not file basename—matching title bundling. Schemas in the root components/schemas still use their keys; referenced schemas without title fall back to basename-based naming.

Title-to-name logic is moved into shared componentNameFromTitle and reused in bundle-visitor (replacing inline logic). Docs for the rule and bundle command are updated; tests cover title strategy cases.

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

@changeset-bot

changeset-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: cd72939

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

This PR includes changesets to release 4 packages
Name Type
@redocly/cli Minor
@redocly/openapi-core Minor
@redocly/client-generator Patch
@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

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Performance Benchmark (Lower is Faster)

CLI Version Bundle Lint Check Config
cli-latest ▓ 1.00x ± 0.01 ▓ 1.00x (Fastest) ▓ 1.00x (Fastest)
cli-next ▓ 1.00x (Fastest) ▓ 1.00x ± 0.01 ▓ 1.00x ± 0.01

@harshit078
harshit078 marked this pull request as ready for review August 5, 2026 07:52
@harshit078
harshit078 requested review from a team as code owners August 5, 2026 07:52
Comment thread packages/core/src/rules/oas3/component-name-unique.ts
@adamaltman

Copy link
Copy Markdown
Member

Interesting idea!

@kanoru3101 kanoru3101 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you for your contribution.

Three descriptions now behave differently under lint and bundle: two in my comments, plus the Bugbot one above — that one is correct, I reproduced it.
Left to do:

  • make the rule agree with the bundler in all three cases;
  • mirror the bundler's tests — none of the three cases is covered today.
    The rest is in the inline comments.

Comment thread .changeset/vast-kids-add.md Outdated
if (
!useTitleStrategy ||
typeName !== TYPE_NAME_SCHEMA ||
resolved.location.source.absoluteRef === rootSourceRef

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The rule skips the title strategy for every schema in the root file. The bundle command does not skip those schemas. It renames a root schema if an external file refers to it. So two schemas end up wanting the same name and the rule stays quiet.
Example

# openapi.yaml
components:
  schemas:
    Foo:
      title: Bar thing
      type: object
# ...a response in this file refers to ./Other.yaml
# Other.yaml
title: Bar thing
type: object
properties:
  inner:
    $ref: './openapi.yaml#/components/schemas/Foo'

The bundle is still written, but Other.yaml gets Other, not the name from its title. The rule sees no problem here, the bundler does — both should see the same. Please check where the $ref comes from, not only where it points.


const { node } = resolved;
const title = isPlainObject(node) && isString(node.title) ? node.title.trim() : '';
return title === '' ? null : componentNameFromTitle(title);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If a schema has no title, the rule uses the file name and says nothing. The bundler doesn't — it stops.

# openapi.yaml
openapi: 3.0.0
info:
  title: Referenced schema without a title
  version: 1.0.0
paths:
  /carts:
    get:
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                $ref: './Cart.yaml'
# Cart.yaml — no title here
type: object
properties:
  total:
    type: number

Lint says the description is fine, and then there is no bundle at all. The two commands disagree again, and this is the most common way the title strategy breaks — so the rule should report it:

}

const { node } = resolved;
const title = isPlainObject(node) && isString(node.title) ? node.title.trim() : '';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This duplicates code that already exists; you should consider reusing the existing logic

addComponentFromAbsoluteLocation(typeName, resolvedRef.location);
const titleName = getTitleComponentName(typeName, resolvedRef);
if (titleName) {
addFoundComponent(typeName, titleName, resolvedRef.location);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The report points at the start of the file, but the line to change is title. Pass resolvedRef.location.child('title') here — the same place the bundler reports at bundle-visitor.ts.

harshit078 and others added 2 commits August 11, 2026 16:09
Co-authored-by: Viktor Sydor <31951646+kanoru3101@users.noreply.github.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 using default effort 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.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit cd72939. Configure here.

resolved.location.source.absoluteRef === rootSourceRef
) {
return null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Root schemas skip title strategy

High Severity

getTitleComponentName returns null for every schema whose target lives in the root file. bundle only skips renaming when both the target and the $ref are in the root; an external file that references a root schema still renames it from title. Collisions between that renamed root schema and another title-named schema go unreported.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cd72939. Configure here.

addComponentFromAbsoluteLocation(typeName, resolvedRef.location);
const titleName = getTitleComponentName(typeName, resolvedRef);
if (titleName) {
addFoundComponent(typeName, titleName, resolvedRef.location);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reports point at file root

Medium Severity

When the title strategy records a component, it stores resolvedRef.location, so collisions report at the file root. The field that determines the bundled name is title; bundle-visitor.ts reports at location.child('title') for the same reason.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cd72939. Configure here.

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.

3 participants