From 9e4584a904391672288225eb209bf6ca040a0836 Mon Sep 17 00:00:00 2001 From: kanoru Date: Tue, 31 Mar 2026 11:24:12 +0300 Subject: [PATCH 1/8] fix: add fix --component-renaming-conflicts-severity --- .changeset/quiet-snakes-buy.md | 6 +++ .../__snapshots__/bundle.test.ts.snap | 23 +++++++++ .../core/src/__tests__/bundle-oas.test.ts | 6 ++- packages/core/src/__tests__/bundle.test.ts | 50 ++++++++++++++++++- .../__tests__/fixtures/refs/component-a.yaml | 6 +++ .../__tests__/fixtures/refs/component-b.yaml | 6 +++ ...ternal-refs-pointer-conflicting-names.yaml | 9 ++++ packages/core/src/bundle/bundle-visitor.ts | 24 +++++++-- 8 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 .changeset/quiet-snakes-buy.md create mode 100644 packages/core/src/__tests__/fixtures/refs/component-a.yaml create mode 100644 packages/core/src/__tests__/fixtures/refs/component-b.yaml create mode 100644 packages/core/src/__tests__/fixtures/refs/openapi-with-external-refs-pointer-conflicting-names.yaml diff --git a/.changeset/quiet-snakes-buy.md b/.changeset/quiet-snakes-buy.md new file mode 100644 index 0000000000..ea97781984 --- /dev/null +++ b/.changeset/quiet-snakes-buy.md @@ -0,0 +1,6 @@ +--- +"@redocly/openapi-core": patch +"@redocly/cli": patch +--- + +Fixed `--component-renaming-conflicts-severity` ignoring conflicts when different files have components with the same name but different content. diff --git a/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap b/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap index 92d77e2686..67d15b1b4b 100644 --- a/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap +++ b/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap @@ -1,5 +1,28 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`bundle > should bundle external pointer refs and warn for conflicting names 1`] = ` +openapi: 3.1.0 +paths: + /foo: + put: + parameters: + - $ref: '#/components/parameters/parameters-User' + get: + parameters: + - $ref: '#/components/parameters/User' +components: + parameters: + User: + name: test + in: query + description: test-B + parameters-User: + name: test + in: path + description: test-A + +`; + exports[`bundle > should bundle external refs 1`] = ` openapi: 3.0.0 paths: diff --git a/packages/core/src/__tests__/bundle-oas.test.ts b/packages/core/src/__tests__/bundle-oas.test.ts index 0beba237b7..6be013d8ec 100644 --- a/packages/core/src/__tests__/bundle-oas.test.ts +++ b/packages/core/src/__tests__/bundle-oas.test.ts @@ -47,7 +47,11 @@ describe('bundle-oas', () => { config: await createEmptyRedoclyConfig({}), ref: path.join(__dirname, 'fixtures/refs/openapi-with-external-refs.yaml'), }); - expect(problems).toHaveLength(0); + expect(problems).toHaveLength(1); + expect(problems[0].severity).toBe('warn'); + expect(problems[0].message).toEqual( + `Two schemas are referenced with the same name but different content. Renamed first to param-a-first.` + ); expect(res.parsed).toMatchSnapshot(); }); diff --git a/packages/core/src/__tests__/bundle.test.ts b/packages/core/src/__tests__/bundle.test.ts index c9a88868a9..5fabec62b4 100644 --- a/packages/core/src/__tests__/bundle.test.ts +++ b/packages/core/src/__tests__/bundle.test.ts @@ -64,7 +64,11 @@ describe('bundle', () => { config: await createConfig({}), ref: path.join(__dirname, 'fixtures/refs/openapi-with-external-refs.yaml'), }); - expect(problems).toHaveLength(0); + expect(problems).toHaveLength(1); + expect(problems[0].severity).toBe('warn'); + expect(problems[0].message).toEqual( + `Two schemas are referenced with the same name but different content. Renamed first to param-a-first.` + ); expect(res.parsed).toMatchSnapshot(); }); @@ -119,6 +123,50 @@ describe('bundle', () => { ); }); + it('should bundle external pointer refs and warn for conflicting names', async () => { + const { bundle: res, problems } = await bundle({ + config: await createConfig({}), + ref: path.join( + __dirname, + 'fixtures/refs/openapi-with-external-refs-pointer-conflicting-names.yaml' + ), + }); + expect(problems).toHaveLength(1); + expect(problems[0].severity).toBe('warn'); + expect(problems[0].message).toEqual( + `Two schemas are referenced with the same name but different content. Renamed User to parameters-User.` + ); + expect(res.parsed).toMatchSnapshot(); + }); + + it('should bundle external pointer refs and do not show warnings for conflicting names', async () => { + const { problems } = await bundle({ + config: await createConfig({}), + ref: path.join( + __dirname, + 'fixtures/refs/openapi-with-external-refs-pointer-conflicting-names.yaml' + ), + componentRenamingConflicts: 'off', + }); + expect(problems).toHaveLength(0); + }); + + it('should bundle external pointer refs and show errors for conflicting names', async () => { + const { problems } = await bundle({ + config: await createConfig({}), + ref: path.join( + __dirname, + 'fixtures/refs/openapi-with-external-refs-pointer-conflicting-names.yaml' + ), + componentRenamingConflicts: 'error', + }); + expect(problems).toHaveLength(1); + expect(problems[0].severity).toBe('error'); + expect(problems[0].message).toEqual( + `Two schemas are referenced with the same name but different content. Renamed User to parameters-User.` + ); + }); + it('should dereferenced correctly when used with dereference', async () => { const { bundle: res, problems } = await bundleDocument({ externalRefResolver: new BaseResolver(), diff --git a/packages/core/src/__tests__/fixtures/refs/component-a.yaml b/packages/core/src/__tests__/fixtures/refs/component-a.yaml new file mode 100644 index 0000000000..7a151d3d71 --- /dev/null +++ b/packages/core/src/__tests__/fixtures/refs/component-a.yaml @@ -0,0 +1,6 @@ +components: + parameters: + User: + name: test + in: path + description: test-A diff --git a/packages/core/src/__tests__/fixtures/refs/component-b.yaml b/packages/core/src/__tests__/fixtures/refs/component-b.yaml new file mode 100644 index 0000000000..4c1323f61a --- /dev/null +++ b/packages/core/src/__tests__/fixtures/refs/component-b.yaml @@ -0,0 +1,6 @@ +components: + parameters: + User: + name: test + in: query + description: test-B diff --git a/packages/core/src/__tests__/fixtures/refs/openapi-with-external-refs-pointer-conflicting-names.yaml b/packages/core/src/__tests__/fixtures/refs/openapi-with-external-refs-pointer-conflicting-names.yaml new file mode 100644 index 0000000000..0f189eebc2 --- /dev/null +++ b/packages/core/src/__tests__/fixtures/refs/openapi-with-external-refs-pointer-conflicting-names.yaml @@ -0,0 +1,9 @@ +openapi: 3.1.0 +paths: + /foo: + put: + parameters: + - $ref: 'component-a.yaml#/components/parameters/User' + get: + parameters: + - $ref: 'component-b.yaml#/components/parameters/User' diff --git a/packages/core/src/bundle/bundle-visitor.ts b/packages/core/src/bundle/bundle-visitor.ts index cdb7b06446..27cdc8c34c 100644 --- a/packages/core/src/bundle/bundle-visitor.ts +++ b/packages/core/src/bundle/bundle-visitor.ts @@ -269,6 +269,14 @@ export function makeBundleVisitor({ return dequal(node, target.node); } + function reportComponentConflict(originalName: string, newName: string, ctx: UserContext) { + ctx.report({ + message: `Two schemas are referenced with the same name but different content. Renamed ${originalName} to ${newName}.`, + location: ctx.location, + forceSeverity: componentRenamingConflicts, + }); + } + function getComponentName( target: { node: unknown; location: Location }, componentType: string, @@ -278,6 +286,7 @@ export function makeBundleVisitor({ const componentsGroup = components[componentType]; let name = ''; + let originalName = ''; const refParts = pointer.slice(2).split('/').filter(isTruthy); // slice(2) removes "#/" while (refParts.length > 0) { @@ -287,12 +296,21 @@ export function makeBundleVisitor({ !componentsGroup[name] || isEqualOrEqualRef(componentsGroup[name], target, ctx) ) { + if (originalName && name !== originalName) { + reportComponentConflict(originalName, name, ctx); + } return name; } + if (!originalName) { + originalName = name; + } } name = refBaseName(fileRef) + (name ? `_${name}` : ''); if (!componentsGroup[name] || isEqualOrEqualRef(componentsGroup[name], target, ctx)) { + if (originalName && name !== originalName) { + reportComponentConflict(originalName, name, ctx); + } return name; } @@ -304,11 +322,7 @@ export function makeBundleVisitor({ } if (!componentsGroup[name]) { - ctx.report({ - message: `Two schemas are referenced with the same name but different content. Renamed ${prevName} to ${name}.`, - location: ctx.location, - forceSeverity: componentRenamingConflicts, - }); + reportComponentConflict(originalName || prevName, name, ctx); } return name; From f4ba2625dc3a5dafd2358bea7a5a457f6f8df341 Mon Sep 17 00:00:00 2001 From: kanoru Date: Tue, 31 Mar 2026 12:32:47 +0300 Subject: [PATCH 2/8] fix: update variable names --- packages/core/src/bundle/bundle-visitor.ts | 31 ++++++++++------------ 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/core/src/bundle/bundle-visitor.ts b/packages/core/src/bundle/bundle-visitor.ts index 27cdc8c34c..043935cb55 100644 --- a/packages/core/src/bundle/bundle-visitor.ts +++ b/packages/core/src/bundle/bundle-visitor.ts @@ -269,12 +269,14 @@ export function makeBundleVisitor({ return dequal(node, target.node); } - function reportComponentConflict(originalName: string, newName: string, ctx: UserContext) { - ctx.report({ - message: `Two schemas are referenced with the same name but different content. Renamed ${originalName} to ${newName}.`, - location: ctx.location, - forceSeverity: componentRenamingConflicts, - }); + function reportIfRenamed(fromName: string, toName: string, ctx: UserContext) { + if (fromName && toName !== fromName) { + ctx.report({ + message: `Two schemas are referenced with the same name but different content. Renamed ${fromName} to ${toName}.`, + location: ctx.location, + forceSeverity: componentRenamingConflicts, + }); + } } function getComponentName( @@ -286,7 +288,7 @@ export function makeBundleVisitor({ const componentsGroup = components[componentType]; let name = ''; - let originalName = ''; + let renameSource = ''; const refParts = pointer.slice(2).split('/').filter(isTruthy); // slice(2) removes "#/" while (refParts.length > 0) { @@ -296,24 +298,19 @@ export function makeBundleVisitor({ !componentsGroup[name] || isEqualOrEqualRef(componentsGroup[name], target, ctx) ) { - if (originalName && name !== originalName) { - reportComponentConflict(originalName, name, ctx); - } + reportIfRenamed(renameSource, name, ctx); return name; } - if (!originalName) { - originalName = name; - } + renameSource ||= name; } name = refBaseName(fileRef) + (name ? `_${name}` : ''); if (!componentsGroup[name] || isEqualOrEqualRef(componentsGroup[name], target, ctx)) { - if (originalName && name !== originalName) { - reportComponentConflict(originalName, name, ctx); - } + reportIfRenamed(renameSource, name, ctx); return name; } + renameSource ||= name; const prevName = name; let serialId = 2; while (componentsGroup[name] && !isEqualOrEqualRef(componentsGroup[name], target, ctx)) { @@ -322,7 +319,7 @@ export function makeBundleVisitor({ } if (!componentsGroup[name]) { - reportComponentConflict(originalName || prevName, name, ctx); + reportIfRenamed(renameSource, name, ctx); } return name; From 39f32718001d6babac624b5c8c157e6e40088039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81=C4=99kawa?= <164185257+JLekawa@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:14:13 +0200 Subject: [PATCH 3/8] Apply suggestion from @JLekawa --- .changeset/quiet-snakes-buy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/quiet-snakes-buy.md b/.changeset/quiet-snakes-buy.md index ea97781984..c087935e31 100644 --- a/.changeset/quiet-snakes-buy.md +++ b/.changeset/quiet-snakes-buy.md @@ -3,4 +3,4 @@ "@redocly/cli": patch --- -Fixed `--component-renaming-conflicts-severity` ignoring conflicts when different files have components with the same name but different content. +Fixed an issue where `--component-renaming-conflicts-severity` ignored conflicts when different files had components with the same name but different content. From 40434d0d1b122c512cad1e2a1562c580b5cb5946 Mon Sep 17 00:00:00 2001 From: kanoru Date: Tue, 31 Mar 2026 18:00:57 +0300 Subject: [PATCH 4/8] fix: update test description --- packages/core/src/__tests__/bundle.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/__tests__/bundle.test.ts b/packages/core/src/__tests__/bundle.test.ts index 5fabec62b4..aaa35aaab5 100644 --- a/packages/core/src/__tests__/bundle.test.ts +++ b/packages/core/src/__tests__/bundle.test.ts @@ -151,7 +151,7 @@ describe('bundle', () => { expect(problems).toHaveLength(0); }); - it('should bundle external pointer refs and show errors for conflicting names', async () => { + it('should report error-severity problems for conflicting pointer ref names', async () => { const { problems } = await bundle({ config: await createConfig({}), ref: path.join( From 4c0b911d2ea29fdd51e5aa7519798cec39bf2d48 Mon Sep 17 00:00:00 2001 From: kanoru Date: Fri, 3 Apr 2026 12:49:31 +0300 Subject: [PATCH 5/8] fix: removed logic with prefixes --- .../__snapshots__/bundle-oas.test.ts.snap | 4 +- .../__snapshots__/bundle.test.ts.snap | 8 ++-- .../core/src/__tests__/bundle-oas.test.ts | 2 +- packages/core/src/__tests__/bundle.test.ts | 6 +-- packages/core/src/bundle/bundle-visitor.ts | 45 +++---------------- 5 files changed, 17 insertions(+), 48 deletions(-) diff --git a/packages/core/src/__tests__/__snapshots__/bundle-oas.test.ts.snap b/packages/core/src/__tests__/__snapshots__/bundle-oas.test.ts.snap index 56cad3ec3d..a57b3da45e 100644 --- a/packages/core/src/__tests__/__snapshots__/bundle-oas.test.ts.snap +++ b/packages/core/src/__tests__/__snapshots__/bundle-oas.test.ts.snap @@ -26,7 +26,7 @@ components: $ref: '#/components/schemas/schema-a' examples: first: - $ref: '#/components/examples/param-a-first' + $ref: '#/components/examples/first-2' second: $ref: '#/components/examples/second' path-param: @@ -41,7 +41,7 @@ components: examples: first: value: b1 - param-a-first: + first-2: value: a1 second: value: a2 diff --git a/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap b/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap index 67d15b1b4b..fef2b9cd19 100644 --- a/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap +++ b/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap @@ -6,7 +6,7 @@ paths: /foo: put: parameters: - - $ref: '#/components/parameters/parameters-User' + - $ref: '#/components/parameters/User-2' get: parameters: - $ref: '#/components/parameters/User' @@ -16,7 +16,7 @@ components: name: test in: query description: test-B - parameters-User: + User-2: name: test in: path description: test-A @@ -49,7 +49,7 @@ components: $ref: '#/components/schemas/schema-a' examples: first: - $ref: '#/components/examples/param-a-first' + $ref: '#/components/examples/first-2' second: $ref: '#/components/examples/second' path-param: @@ -64,7 +64,7 @@ components: examples: first: value: b1 - param-a-first: + first-2: value: a1 second: value: a2 diff --git a/packages/core/src/__tests__/bundle-oas.test.ts b/packages/core/src/__tests__/bundle-oas.test.ts index 6be013d8ec..70d6ba7c41 100644 --- a/packages/core/src/__tests__/bundle-oas.test.ts +++ b/packages/core/src/__tests__/bundle-oas.test.ts @@ -50,7 +50,7 @@ describe('bundle-oas', () => { expect(problems).toHaveLength(1); expect(problems[0].severity).toBe('warn'); expect(problems[0].message).toEqual( - `Two schemas are referenced with the same name but different content. Renamed first to param-a-first.` + `Two schemas are referenced with the same name but different content. Renamed first to first-2.` ); expect(res.parsed).toMatchSnapshot(); }); diff --git a/packages/core/src/__tests__/bundle.test.ts b/packages/core/src/__tests__/bundle.test.ts index aaa35aaab5..06a2882a11 100644 --- a/packages/core/src/__tests__/bundle.test.ts +++ b/packages/core/src/__tests__/bundle.test.ts @@ -67,7 +67,7 @@ describe('bundle', () => { expect(problems).toHaveLength(1); expect(problems[0].severity).toBe('warn'); expect(problems[0].message).toEqual( - `Two schemas are referenced with the same name but different content. Renamed first to param-a-first.` + `Two schemas are referenced with the same name but different content. Renamed first to first-2.` ); expect(res.parsed).toMatchSnapshot(); }); @@ -134,7 +134,7 @@ describe('bundle', () => { expect(problems).toHaveLength(1); expect(problems[0].severity).toBe('warn'); expect(problems[0].message).toEqual( - `Two schemas are referenced with the same name but different content. Renamed User to parameters-User.` + `Two schemas are referenced with the same name but different content. Renamed User to User-2.` ); expect(res.parsed).toMatchSnapshot(); }); @@ -163,7 +163,7 @@ describe('bundle', () => { expect(problems).toHaveLength(1); expect(problems[0].severity).toBe('error'); expect(problems[0].message).toEqual( - `Two schemas are referenced with the same name but different content. Renamed User to parameters-User.` + `Two schemas are referenced with the same name but different content. Renamed User to User-2.` ); }); diff --git a/packages/core/src/bundle/bundle-visitor.ts b/packages/core/src/bundle/bundle-visitor.ts index 043935cb55..6651a3f94a 100644 --- a/packages/core/src/bundle/bundle-visitor.ts +++ b/packages/core/src/bundle/bundle-visitor.ts @@ -12,7 +12,6 @@ import { type ResolvedRefMap, type Document } from '../resolve.js'; import { reportUnresolvedRef } from '../rules/common/no-unresolved-refs.js'; import { type OasRef } from '../typings/openapi.js'; import { dequal } from '../utils/dequal.js'; -import { isTruthy } from '../utils/is-truthy.js'; import { makeRefId } from '../utils/make-ref-id.js'; import { type Oas3Visitor, type Oas2Visitor } from '../visitors.js'; import { type UserContext, type ResolveResult } from '../walk.js'; @@ -269,48 +268,14 @@ export function makeBundleVisitor({ return dequal(node, target.node); } - function reportIfRenamed(fromName: string, toName: string, ctx: UserContext) { - if (fromName && toName !== fromName) { - ctx.report({ - message: `Two schemas are referenced with the same name but different content. Renamed ${fromName} to ${toName}.`, - location: ctx.location, - forceSeverity: componentRenamingConflicts, - }); - } - } - function getComponentName( target: { node: unknown; location: Location }, componentType: string, ctx: UserContext ) { - const [fileRef, pointer] = [target.location.source.absoluteRef, target.location.pointer]; const componentsGroup = components[componentType]; + let name = refBaseName(target.location.absolutePointer); - let name = ''; - let renameSource = ''; - - const refParts = pointer.slice(2).split('/').filter(isTruthy); // slice(2) removes "#/" - while (refParts.length > 0) { - name = refParts.pop() + (name ? `-${name}` : ''); - if ( - !componentsGroup || - !componentsGroup[name] || - isEqualOrEqualRef(componentsGroup[name], target, ctx) - ) { - reportIfRenamed(renameSource, name, ctx); - return name; - } - renameSource ||= name; - } - - name = refBaseName(fileRef) + (name ? `_${name}` : ''); - if (!componentsGroup[name] || isEqualOrEqualRef(componentsGroup[name], target, ctx)) { - reportIfRenamed(renameSource, name, ctx); - return name; - } - - renameSource ||= name; const prevName = name; let serialId = 2; while (componentsGroup[name] && !isEqualOrEqualRef(componentsGroup[name], target, ctx)) { @@ -318,8 +283,12 @@ export function makeBundleVisitor({ serialId++; } - if (!componentsGroup[name]) { - reportIfRenamed(renameSource, name, ctx); + if (!componentsGroup[name] && prevName !== name) { + ctx.report({ + message: `Two schemas are referenced with the same name but different content. Renamed ${prevName} to ${name}.`, + location: ctx.location, + forceSeverity: componentRenamingConflicts, + }); } return name; From 6bfdb439aa3f705d9192826ce72bc89d6eff4f01 Mon Sep 17 00:00:00 2001 From: kanoru Date: Fri, 3 Apr 2026 14:21:14 +0300 Subject: [PATCH 6/8] chore: update changeset --- .changeset/quiet-snakes-buy.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.changeset/quiet-snakes-buy.md b/.changeset/quiet-snakes-buy.md index c087935e31..22e49dbf7e 100644 --- a/.changeset/quiet-snakes-buy.md +++ b/.changeset/quiet-snakes-buy.md @@ -4,3 +4,5 @@ --- Fixed an issue where `--component-renaming-conflicts-severity` ignored conflicts when different files had components with the same name but different content. + +**Warning:** Bundled `$ref` paths may differ from older releases. From d2f3ee47d7489b3ebfe0231eb1e3274cfb583f7e Mon Sep 17 00:00:00 2001 From: kanoru Date: Fri, 3 Apr 2026 17:30:31 +0300 Subject: [PATCH 7/8] fix: add support dots in pointer name --- .../__snapshots__/bundle.test.ts.snap | 45 +++++++++++++++++++ packages/core/src/__tests__/bundle.test.ts | 17 +++++++ .../fixtures/refs/external-ref-with-dots.yaml | 12 +++++ ...ternal-schema-names-and-user-conflict.yaml | 29 ++++++++++++ packages/core/src/bundle/bundle-visitor.ts | 4 +- packages/core/src/ref-utils.ts | 2 +- 6 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 packages/core/src/__tests__/fixtures/refs/external-ref-with-dots.yaml create mode 100644 packages/core/src/__tests__/fixtures/refs/openapi-bundle-external-schema-names-and-user-conflict.yaml diff --git a/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap b/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap index fef2b9cd19..a861b60211 100644 --- a/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap +++ b/packages/core/src/__tests__/__snapshots__/bundle.test.ts.snap @@ -147,6 +147,51 @@ components: `; +exports[`bundle > should keep dotted JSON pointer schema keys and rename conflicting User schemas 1`] = ` +openapi: 3.0.0 +info: + title: Test ref with dots in component names + version: '1.0' + description: Demo + license: + name: DEMO + url: https://demo.com +servers: + - url: http://demo.com/api +paths: + /org-user: + get: + responses: + '200': + description: ok + content: + application/json: + schema: + $ref: '#/components/schemas/my.org.User' + post: + responses: + '200': + description: ok + content: + application/json: + schema: + additionalProperties: + $ref: '#/components/schemas/my.User' +components: + schemas: + my.org.User: + type: object + properties: + orgId: + type: string + my.User: + type: object + properties: + displayName: + type: string + +`; + exports[`bundle > should normalize self-file explicit $ref in nested referenced file 1`] = ` openapi: 3.0.0 info: diff --git a/packages/core/src/__tests__/bundle.test.ts b/packages/core/src/__tests__/bundle.test.ts index 06a2882a11..7da48ba2da 100644 --- a/packages/core/src/__tests__/bundle.test.ts +++ b/packages/core/src/__tests__/bundle.test.ts @@ -167,6 +167,23 @@ describe('bundle', () => { ); }); + it('should keep dotted JSON pointer schema keys and rename conflicting User schemas', async () => { + const { bundle: res, problems } = await bundle({ + config: await createConfig({}), + ref: path.join( + __dirname, + 'fixtures/refs/openapi-bundle-external-schema-names-and-user-conflict.yaml' + ), + }); + + expect(problems).toHaveLength(0); + const schemas = (res.parsed as { components: { schemas: Record } }).components + .schemas; + expect(schemas['my.org.User']).toBeDefined(); + expect(schemas['my.User']).toBeDefined(); + expect(res.parsed).toMatchSnapshot(); + }); + it('should dereferenced correctly when used with dereference', async () => { const { bundle: res, problems } = await bundleDocument({ externalRefResolver: new BaseResolver(), diff --git a/packages/core/src/__tests__/fixtures/refs/external-ref-with-dots.yaml b/packages/core/src/__tests__/fixtures/refs/external-ref-with-dots.yaml new file mode 100644 index 0000000000..838d9fa666 --- /dev/null +++ b/packages/core/src/__tests__/fixtures/refs/external-ref-with-dots.yaml @@ -0,0 +1,12 @@ +components: + schemas: + my.org.User: + type: object + properties: + orgId: + type: string + my.User: + type: object + properties: + displayName: + type: string diff --git a/packages/core/src/__tests__/fixtures/refs/openapi-bundle-external-schema-names-and-user-conflict.yaml b/packages/core/src/__tests__/fixtures/refs/openapi-bundle-external-schema-names-and-user-conflict.yaml new file mode 100644 index 0000000000..7a68a00946 --- /dev/null +++ b/packages/core/src/__tests__/fixtures/refs/openapi-bundle-external-schema-names-and-user-conflict.yaml @@ -0,0 +1,29 @@ +openapi: 3.0.0 +info: + title: Test ref with dots in component names + version: '1.0' + description: Demo + license: + name: DEMO + url: https://demo.com +servers: + - url: http://demo.com/api +paths: + /org-user: + get: + responses: + '200': + description: ok + content: + application/json: + schema: + $ref: external-ref-with-dots.yaml#/components/schemas/my.org.User + post: + responses: + '200': + description: ok + content: + application/json: + schema: + additionalProperties: + $ref: external-ref-with-dots.yaml#/components/schemas/my.User diff --git a/packages/core/src/bundle/bundle-visitor.ts b/packages/core/src/bundle/bundle-visitor.ts index 6651a3f94a..480d87cec7 100644 --- a/packages/core/src/bundle/bundle-visitor.ts +++ b/packages/core/src/bundle/bundle-visitor.ts @@ -5,6 +5,7 @@ import { replaceRef, isExternalValue, isRef, + pointerBaseName, refBaseName, type Location, } from '../ref-utils.js'; @@ -274,7 +275,8 @@ export function makeBundleVisitor({ ctx: UserContext ) { const componentsGroup = components[componentType]; - let name = refBaseName(target.location.absolutePointer); + const [fileRef, pointer] = [target.location.source.absoluteRef, target.location.pointer]; + let name = pointerBaseName(pointer) || refBaseName(fileRef); const prevName = name; let serialId = 2; diff --git a/packages/core/src/ref-utils.ts b/packages/core/src/ref-utils.ts index 03e4e64d24..dcfff3a52b 100644 --- a/packages/core/src/ref-utils.ts +++ b/packages/core/src/ref-utils.ts @@ -76,7 +76,7 @@ export function parsePointer(pointer: string) { export function pointerBaseName(pointer: string) { const parts = pointer.split('/'); - return parts[parts.length - 1]; + return unescapePointerFragment(parts[parts.length - 1]); } export function refBaseName(ref: string) { From bdc215b21fae1d477be54c43b39552b76d512d70 Mon Sep 17 00:00:00 2001 From: kanoru Date: Tue, 7 Apr 2026 13:30:56 +0300 Subject: [PATCH 8/8] fix: remove unescapePointerFragment --- packages/core/src/ref-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/ref-utils.ts b/packages/core/src/ref-utils.ts index dcfff3a52b..03e4e64d24 100644 --- a/packages/core/src/ref-utils.ts +++ b/packages/core/src/ref-utils.ts @@ -76,7 +76,7 @@ export function parsePointer(pointer: string) { export function pointerBaseName(pointer: string) { const parts = pointer.split('/'); - return unescapePointerFragment(parts[parts.length - 1]); + return parts[parts.length - 1]; } export function refBaseName(ref: string) {