From c390fbf3f3f231c33be7def178a824059817817e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:10:25 +0000 Subject: [PATCH 1/5] Initial plan From 1dd073de17195894b360f29c181ff3df67bccaa9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:17:20 +0000 Subject: [PATCH 2/5] Add test fixture for allOf/anyOf header hierarchy issue Co-authored-by: trieloff <39613+trieloff@users.noreply.github.com> --- package-lock.json | 10 --- .../allof-anyof-hierarchy/schema.json | 64 +++++++++++++++++++ 2 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 test/fixtures/allof-anyof-hierarchy/schema.json diff --git a/package-lock.json b/package-lock.json index 660dbf48..e3114763 100644 --- a/package-lock.json +++ b/package-lock.json @@ -625,7 +625,6 @@ "integrity": "sha512-oNXsh2ywth5aowwIa7RKtawnkdH6LgU1ztfP9AIUCQCvzysB+WeU8o2kyyosDPwBZutPpjZDKPQGIzzrfTWweQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.1", @@ -1437,7 +1436,6 @@ "integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -1476,7 +1474,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2722,7 +2719,6 @@ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, - "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -3534,7 +3530,6 @@ "integrity": "sha512-sjc7Y8cUD1IlwYcTS9qPSvGjAC8Ne9LctpxKKu3x/1IC9bnOg98Zy6GxEJUfr1NojMgVPlyANXYns8oE2c1TAA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -6495,7 +6490,6 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -10079,7 +10073,6 @@ "dev": true, "inBundle": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -11500,7 +11493,6 @@ "integrity": "sha512-JL2hY7b1hBli5FS98nAXH0OwfRsgdywuUVhKktyJOHu9tUO0wJGWNpQL69SDhJ5Uwp63rtIvGvy7ZDy4C3RsXg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", @@ -12761,7 +12753,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -12988,7 +12979,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/test/fixtures/allof-anyof-hierarchy/schema.json b/test/fixtures/allof-anyof-hierarchy/schema.json new file mode 100644 index 00000000..28e99a08 --- /dev/null +++ b/test/fixtures/allof-anyof-hierarchy/schema.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Example Schema", + "type": "object", + "properties": { + "tests": { + "title": "Tests Configuration", + "description": "Configure test execution", + "type": "object", + "properties": { + "docker": { + "title": "Docker Tests", + "description": "Run Docker-based tests", + "type": "object", + "allOf": [ + { + "title": "test-options", + "description": "Test execution options", + "anyOf": [ + { + "title": "script", + "properties": { + "testScript": { + "title": "Test Script", + "description": "Script to execute for testing", + "type": "string" + } + }, + "required": ["testScript"] + }, + { + "title": "stepList", + "properties": { + "testSteps": { + "title": "Test Steps", + "description": "List of test steps to execute", + "type": "array", + "items": { + "type": "object" + } + } + }, + "required": ["testSteps"] + } + ] + }, + { + "properties": { + "preTestSteps": { + "title": "Pre-test Steps", + "description": "Steps to execute before running tests", + "type": "array", + "items": { + "type": "object" + } + } + } + } + ] + } + } + } + } +} From 0fa7b061315d24fe888f1fe3ab4438b3ea26b479 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:31:29 +0000 Subject: [PATCH 3/5] Fix header hierarchy for nested allOf/anyOf schemas Co-authored-by: trieloff <39613+trieloff@users.noreply.github.com> --- lib/markdownBuilder.js | 40 +++++++++++--- test/allof-anyof-hierarchy.test.js | 53 +++++++++++++++++++ .../{schema.json => schema.schema.json} | 0 3 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 test/allof-anyof-hierarchy.test.js rename test/fixtures/allof-anyof-hierarchy/{schema.json => schema.schema.json} (100%) diff --git a/lib/markdownBuilder.js b/lib/markdownBuilder.js index fcf381b4..fd368cfb 100644 --- a/lib/markdownBuilder.js +++ b/lib/markdownBuilder.js @@ -847,11 +847,13 @@ export default function build({ /** * Generates the properties section for a schema * @param {*} schema + * @param {*} slugger + * @param {number} level - Base level for this schema */ - function makeproperties(schema, slugger) { + function makeproperties(schema, slugger, level = 1) { if (schema[keyword`properties`] || schema[keyword`patternProperties`] || schema[keyword`additionalProperties`]) { return [ - heading(1, text(i18n`${simpletitle(schema)} Properties`)), + heading(level, text(i18n`${simpletitle(schema)} Properties`)), makeproptable( schema[keyword`properties`], schema[keyword`patternProperties`], @@ -864,7 +866,7 @@ export default function build({ schema[keyword`patternProperties`], schema[keyword`additionalProperties`], schema[keyword`required`], - 1, + level, ), ]; } @@ -874,15 +876,37 @@ export default function build({ console.log('generating markdown'); return (schemas) => foldl(schemas, {}, (pv, schema) => { const slugger = new GhSlugger(); + + // Calculate depth and determine appropriate base level + // Schemas that are part of allOf/anyOf/oneOf constructs within properties + // should use higher header levels to maintain hierarchy + const pointer = schema[s.pointer]; + + // Check if this schema is part of an allOf/anyOf/oneOf within a property + // by looking at the pointer path + const isCompositionSchema = /\/(allOf|anyOf|oneOf)\/\d+/.test(pointer); + const propertyDepth = (pointer.match(/\/properties\//g) || []).length; + + // Base level calculation: + // - Root schemas and direct properties: level 1 + // - Schemas within allOf/anyOf/oneOf of properties: use property depth to offset + // This maintains proper hierarchy when schemas are viewed in context + let baseLevel = 1; + if (isCompositionSchema && propertyDepth > 0) { + // For composition schemas (allOf/anyOf/oneOf), offset the base level + // based on how deep they are in the property hierarchy + baseLevel = propertyDepth; + } + // eslint-disable-next-line no-param-reassign pv[schema[s.slug]] = root([ // todo add more elements ...makeheader(schema), - ...maketypesection(schema, 1), - ...makeconstraintssection(schema, 1), - ...makedefault(schema, 1), - ...makeexamples(schema, 1), - ...makeproperties(schema, slugger), + ...maketypesection(schema, baseLevel), + ...makeconstraintssection(schema, baseLevel), + ...makedefault(schema, baseLevel), + ...makeexamples(schema, baseLevel), + ...makeproperties(schema, slugger, baseLevel), ...makedefinitions(schema, slugger), ]); return pv; diff --git a/test/allof-anyof-hierarchy.test.js b/test/allof-anyof-hierarchy.test.js new file mode 100644 index 00000000..bfe59598 --- /dev/null +++ b/test/allof-anyof-hierarchy.test.js @@ -0,0 +1,53 @@ +/* + * Copyright 2019 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +/* eslint-env mocha */ +/* eslint-disable no-unused-expressions */ +import { assertMarkdown, traverseSchemas } from './testUtils.js'; +import build from '../lib/markdownBuilder.js'; + +describe('Testing Markdown Builder: allOf/anyOf header hierarchy', () => { + let results; + + before(async () => { + const schemas = await traverseSchemas('allof-anyof-hierarchy'); + const builder = build({ header: true }); + results = builder(schemas); + }); + + it('allOf item schemas should use higher base level for proper hierarchy', () => { + // The allOf[1] item is a composition schema within the docker property + // It should use baseLevel = 2 (property depth) instead of 1 + const allof1Slug = 'schema-properties-tests-configuration-properties-docker-tests-allof-1'; + assertMarkdown(results[allof1Slug]) + .contains('## 1 Properties') // Properties heading at level 2 (baseLevel) + .contains('### preTestSteps') // Property detail at level 3 (baseLevel + 1) + .contains('#### preTestSteps Type'); // Type section at level 4 (baseLevel + 1 + 1) + }); + + it('anyOf item schemas should use higher base level for proper hierarchy', () => { + // The anyOf[1] item within allOf[0] is a composition schema + // It should also use baseLevel = 2 (property depth) instead of 1 + const anyof1Slug = 'schema-properties-tests-configuration-properties-docker-tests-allof-test-options-anyof-steplist'; + assertMarkdown(results[anyof1Slug]) + .contains('## 1 Properties') // Properties heading at level 2 (baseLevel) + .contains('### testSteps') // Property detail at level 3 (baseLevel + 1) + .contains('#### testSteps Type'); // Type section at level 4 (baseLevel + 1 + 1) + }); + + it('regular property schemas should still use baseLevel 1', () => { + // Regular property schemas (not in composition constructs) should still use level 1 + const dockerSlug = 'schema-properties-tests-configuration-properties-docker-tests'; + assertMarkdown(results[dockerSlug]) + .contains('# Docker Tests Schema') // Schema header at level 1 + .contains('## docker Type'); // Type section at level 2 (baseLevel 1 + 1) + }); +}); diff --git a/test/fixtures/allof-anyof-hierarchy/schema.json b/test/fixtures/allof-anyof-hierarchy/schema.schema.json similarity index 100% rename from test/fixtures/allof-anyof-hierarchy/schema.json rename to test/fixtures/allof-anyof-hierarchy/schema.schema.json From 510e6fccfdcb8a51399efde95d7dc65a1ba1ef50 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:32:14 +0000 Subject: [PATCH 4/5] Fix linting errors Co-authored-by: trieloff <39613+trieloff@users.noreply.github.com> --- lib/markdownBuilder.js | 8 ++++---- test/allof-anyof-hierarchy.test.js | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/markdownBuilder.js b/lib/markdownBuilder.js index fd368cfb..fb930d45 100644 --- a/lib/markdownBuilder.js +++ b/lib/markdownBuilder.js @@ -876,17 +876,17 @@ export default function build({ console.log('generating markdown'); return (schemas) => foldl(schemas, {}, (pv, schema) => { const slugger = new GhSlugger(); - + // Calculate depth and determine appropriate base level // Schemas that are part of allOf/anyOf/oneOf constructs within properties // should use higher header levels to maintain hierarchy const pointer = schema[s.pointer]; - + // Check if this schema is part of an allOf/anyOf/oneOf within a property // by looking at the pointer path const isCompositionSchema = /\/(allOf|anyOf|oneOf)\/\d+/.test(pointer); const propertyDepth = (pointer.match(/\/properties\//g) || []).length; - + // Base level calculation: // - Root schemas and direct properties: level 1 // - Schemas within allOf/anyOf/oneOf of properties: use property depth to offset @@ -897,7 +897,7 @@ export default function build({ // based on how deep they are in the property hierarchy baseLevel = propertyDepth; } - + // eslint-disable-next-line no-param-reassign pv[schema[s.slug]] = root([ // todo add more elements diff --git a/test/allof-anyof-hierarchy.test.js b/test/allof-anyof-hierarchy.test.js index bfe59598..5c2b904a 100644 --- a/test/allof-anyof-hierarchy.test.js +++ b/test/allof-anyof-hierarchy.test.js @@ -28,9 +28,9 @@ describe('Testing Markdown Builder: allOf/anyOf header hierarchy', () => { // It should use baseLevel = 2 (property depth) instead of 1 const allof1Slug = 'schema-properties-tests-configuration-properties-docker-tests-allof-1'; assertMarkdown(results[allof1Slug]) - .contains('## 1 Properties') // Properties heading at level 2 (baseLevel) - .contains('### preTestSteps') // Property detail at level 3 (baseLevel + 1) - .contains('#### preTestSteps Type'); // Type section at level 4 (baseLevel + 1 + 1) + .contains('## 1 Properties') // Properties heading at level 2 (baseLevel) + .contains('### preTestSteps') // Property detail at level 3 (baseLevel + 1) + .contains('#### preTestSteps Type'); // Type section at level 4 (baseLevel + 1 + 1) }); it('anyOf item schemas should use higher base level for proper hierarchy', () => { @@ -38,16 +38,16 @@ describe('Testing Markdown Builder: allOf/anyOf header hierarchy', () => { // It should also use baseLevel = 2 (property depth) instead of 1 const anyof1Slug = 'schema-properties-tests-configuration-properties-docker-tests-allof-test-options-anyof-steplist'; assertMarkdown(results[anyof1Slug]) - .contains('## 1 Properties') // Properties heading at level 2 (baseLevel) - .contains('### testSteps') // Property detail at level 3 (baseLevel + 1) - .contains('#### testSteps Type'); // Type section at level 4 (baseLevel + 1 + 1) + .contains('## 1 Properties') // Properties heading at level 2 (baseLevel) + .contains('### testSteps') // Property detail at level 3 (baseLevel + 1) + .contains('#### testSteps Type'); // Type section at level 4 (baseLevel + 1 + 1) }); it('regular property schemas should still use baseLevel 1', () => { // Regular property schemas (not in composition constructs) should still use level 1 const dockerSlug = 'schema-properties-tests-configuration-properties-docker-tests'; assertMarkdown(results[dockerSlug]) - .contains('# Docker Tests Schema') // Schema header at level 1 - .contains('## docker Type'); // Type section at level 2 (baseLevel 1 + 1) + .contains('# Docker Tests Schema') // Schema header at level 1 + .contains('## docker Type'); // Type section at level 2 (baseLevel 1 + 1) }); }); From d2d1386cc889487c0c3150dfd9433d18ef226912 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Wed, 29 Oct 2025 16:26:43 +0100 Subject: [PATCH 5/5] refactor: apply code review recommendations - Refactor base level calculation into testable helper function - Improve regex pattern by removing \d+ requirement - Make test assertions more precise using regex patterns - Add test coverage for oneOf constructs - Add JSDoc documentation for calculateBaseLevel function These improvements make the code more maintainable and thoroughly tested. Signed-off-by: Lars Trieloff --- lib/markdownBuilder.js | 42 ++++++++++--------- test/allof-anyof-hierarchy.test.js | 26 ++++++++---- .../allof-anyof-hierarchy/schema.schema.json | 29 +++++++++++++ 3 files changed, 69 insertions(+), 28 deletions(-) diff --git a/lib/markdownBuilder.js b/lib/markdownBuilder.js index fb930d45..21195f89 100644 --- a/lib/markdownBuilder.js +++ b/lib/markdownBuilder.js @@ -873,30 +873,32 @@ export default function build({ return []; } + /** + * Calculates the appropriate base header level for a schema based on its position + * in the schema hierarchy. Schemas within composition constructs (allOf/anyOf/oneOf) + * that are nested within properties should use higher header levels to maintain + * proper document hierarchy. + * + * @param {string} pointer - JSON pointer path to the schema + * @returns {number} The base header level (1-6) + */ + function calculateBaseLevel(pointer) { + // Only adjust level for schemas within composition constructs + const isCompositionSchema = /\/(allOf|anyOf|oneOf)\//.test(pointer); + if (!isCompositionSchema) return 1; + + // Calculate property nesting depth by counting /properties/ segments + const propertyDepth = (pointer.match(/\/properties\//g) || []).length; + return propertyDepth > 0 ? propertyDepth : 1; + } + console.log('generating markdown'); return (schemas) => foldl(schemas, {}, (pv, schema) => { const slugger = new GhSlugger(); - // Calculate depth and determine appropriate base level - // Schemas that are part of allOf/anyOf/oneOf constructs within properties - // should use higher header levels to maintain hierarchy - const pointer = schema[s.pointer]; - - // Check if this schema is part of an allOf/anyOf/oneOf within a property - // by looking at the pointer path - const isCompositionSchema = /\/(allOf|anyOf|oneOf)\/\d+/.test(pointer); - const propertyDepth = (pointer.match(/\/properties\//g) || []).length; - - // Base level calculation: - // - Root schemas and direct properties: level 1 - // - Schemas within allOf/anyOf/oneOf of properties: use property depth to offset - // This maintains proper hierarchy when schemas are viewed in context - let baseLevel = 1; - if (isCompositionSchema && propertyDepth > 0) { - // For composition schemas (allOf/anyOf/oneOf), offset the base level - // based on how deep they are in the property hierarchy - baseLevel = propertyDepth; - } + // Calculate the appropriate base level for this schema based on its position + // in the hierarchy (composition constructs within properties need higher levels) + const baseLevel = calculateBaseLevel(schema[s.pointer]); // eslint-disable-next-line no-param-reassign pv[schema[s.slug]] = root([ diff --git a/test/allof-anyof-hierarchy.test.js b/test/allof-anyof-hierarchy.test.js index 5c2b904a..ac838cd5 100644 --- a/test/allof-anyof-hierarchy.test.js +++ b/test/allof-anyof-hierarchy.test.js @@ -28,9 +28,9 @@ describe('Testing Markdown Builder: allOf/anyOf header hierarchy', () => { // It should use baseLevel = 2 (property depth) instead of 1 const allof1Slug = 'schema-properties-tests-configuration-properties-docker-tests-allof-1'; assertMarkdown(results[allof1Slug]) - .contains('## 1 Properties') // Properties heading at level 2 (baseLevel) - .contains('### preTestSteps') // Property detail at level 3 (baseLevel + 1) - .contains('#### preTestSteps Type'); // Type section at level 4 (baseLevel + 1 + 1) + .matches(/^## 1 Properties$/m) // Properties heading at level 2 (baseLevel) + .matches(/^### preTestSteps/m) // Property detail at level 3 (baseLevel + 1) + .matches(/^#### preTestSteps Type$/m); // Type section at level 4 (baseLevel + 1 + 1) }); it('anyOf item schemas should use higher base level for proper hierarchy', () => { @@ -38,16 +38,26 @@ describe('Testing Markdown Builder: allOf/anyOf header hierarchy', () => { // It should also use baseLevel = 2 (property depth) instead of 1 const anyof1Slug = 'schema-properties-tests-configuration-properties-docker-tests-allof-test-options-anyof-steplist'; assertMarkdown(results[anyof1Slug]) - .contains('## 1 Properties') // Properties heading at level 2 (baseLevel) - .contains('### testSteps') // Property detail at level 3 (baseLevel + 1) - .contains('#### testSteps Type'); // Type section at level 4 (baseLevel + 1 + 1) + .matches(/^## 1 Properties$/m) // Properties heading at level 2 (baseLevel) + .matches(/^### testSteps/m) // Property detail at level 3 (baseLevel + 1) + .matches(/^#### testSteps Type$/m); // Type section at level 4 (baseLevel + 1 + 1) + }); + + it('oneOf item schemas should use higher base level for proper hierarchy', () => { + // The oneOf[0] item within kubernetes property is a composition schema + // It should use baseLevel = 2 (property depth) instead of 1 + const oneof0Slug = 'schema-properties-tests-configuration-properties-kubernetes-tests-oneof-helm'; + assertMarkdown(results[oneof0Slug]) + .matches(/^## 0 Properties$/m) // Properties heading at level 2 (baseLevel) + .matches(/^### helmChart/m) // Property detail at level 3 (baseLevel + 1) + .matches(/^#### helmChart Type$/m); // Type section at level 4 (baseLevel + 1 + 1) }); it('regular property schemas should still use baseLevel 1', () => { // Regular property schemas (not in composition constructs) should still use level 1 const dockerSlug = 'schema-properties-tests-configuration-properties-docker-tests'; assertMarkdown(results[dockerSlug]) - .contains('# Docker Tests Schema') // Schema header at level 1 - .contains('## docker Type'); // Type section at level 2 (baseLevel 1 + 1) + .matches(/^# Docker Tests Schema$/m) // Schema header at level 1 + .matches(/^## docker Type$/m); // Type section at level 2 (baseLevel 1 + 1) }); }); diff --git a/test/fixtures/allof-anyof-hierarchy/schema.schema.json b/test/fixtures/allof-anyof-hierarchy/schema.schema.json index 28e99a08..154acff8 100644 --- a/test/fixtures/allof-anyof-hierarchy/schema.schema.json +++ b/test/fixtures/allof-anyof-hierarchy/schema.schema.json @@ -57,6 +57,35 @@ } } ] + }, + "kubernetes": { + "title": "Kubernetes Tests", + "description": "Run Kubernetes-based tests", + "type": "object", + "oneOf": [ + { + "title": "helm", + "properties": { + "helmChart": { + "title": "Helm Chart", + "description": "Helm chart to deploy for testing", + "type": "string" + } + }, + "required": ["helmChart"] + }, + { + "title": "manifest", + "properties": { + "manifestFile": { + "title": "Manifest File", + "description": "Kubernetes manifest file to apply", + "type": "string" + } + }, + "required": ["manifestFile"] + } + ] } } }