Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions src/components/Tables/ConfigOptions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest';
import { getValueTypeText } from '~/util/schemaToMarkdown';
import { getValueType } from './ConfigOptions';

// The x-has-data-type contract, render side: a node flagged as a documented data
// The x-mergify-has-data-type contract, render side: a node flagged as a documented data
// type becomes a link to its data-types section instead of an expansion of
// its shape, for both the HTML tables (getValueType) and the markdown/LLM
// export (getValueTypeText). The node's `title` drives the label and the
Expand All @@ -15,7 +15,7 @@ const HREF = '/configuration/data-types#queue-dequeue-reason';

const inlineMarked = {
title: 'Queue dequeue reason',
'x-has-data-type': true,
'x-mergify-has-data-type': true,
anyOf: [{ enum: ['PR_MERGED', 'PR_DEQUEUED'], type: 'string' }, { type: 'null' }],
};

Expand All @@ -24,7 +24,7 @@ function render(schema: object, definition: object): string {
return element === null ? '' : renderToStaticMarkup(element);
}

describe('getValueType with x-has-data-type', () => {
describe('getValueType with x-mergify-has-data-type', () => {
it('links an inline-flagged node instead of expanding its enum', () => {
const html = render({}, inlineMarked);
expect(html).toContain(`href="${HREF}"`);
Expand All @@ -34,15 +34,19 @@ describe('getValueType with x-has-data-type', () => {

it('links a flag published as a $ref sibling, taking the title from the target', () => {
const schema = { $defs: { QueueCode: { title: 'Queue dequeue reason', type: 'string' } } };
const html = render(schema, { $ref: '#/$defs/QueueCode', 'x-has-data-type': true });
const html = render(schema, { $ref: '#/$defs/QueueCode', 'x-mergify-has-data-type': true });
expect(html).toContain(`href="${HREF}"`);
expect(html).toContain('Queue dequeue reason');
});

it('links a flag on a $defs entry reached through a plain $ref', () => {
const schema = {
$defs: {
QueueCode: { 'x-has-data-type': true, title: 'Queue dequeue reason', type: 'string' },
QueueCode: {
'x-mergify-has-data-type': true,
title: 'Queue dequeue reason',
type: 'string',
},
},
};
const html = render(schema, { $ref: '#/$defs/QueueCode' });
Expand All @@ -53,7 +57,7 @@ describe('getValueType with x-has-data-type', () => {
const schema = {
$defs: {
ReportMode: {
'x-has-data-type': true,
'x-mergify-has-data-type': true,
title: 'Report Modes',
enum: ['check', 'comment'],
type: 'string',
Expand All @@ -68,7 +72,7 @@ describe('getValueType with x-has-data-type', () => {
});

it('falls back to the previous rendering when a flagged node has no title', () => {
const html = render({}, { 'x-has-data-type': true, enum: ['check', 'comment'] });
const html = render({}, { 'x-mergify-has-data-type': true, enum: ['check', 'comment'] });
expect(html).not.toContain('href');
expect(html).toContain('check');
});
Expand Down Expand Up @@ -137,7 +141,7 @@ describe('getValueTypeText with an untitled $ref target', () => {
});
});

describe('getValueTypeText with x-has-data-type', () => {
describe('getValueTypeText with x-mergify-has-data-type', () => {
it('emits a markdown link instead of the enum dump', () => {
expect(getValueTypeText({} as never, inlineMarked)).toBe(`[Queue dequeue reason](${HREF})`);
});
Expand All @@ -146,7 +150,7 @@ describe('getValueTypeText with x-has-data-type', () => {
const schema = {
$defs: {
ReportMode: {
'x-has-data-type': true,
'x-mergify-has-data-type': true,
title: 'Report Modes',
enum: ['check', 'comment'],
type: 'string',
Expand Down
2 changes: 1 addition & 1 deletion src/components/Tables/ConfigOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ function getTitle(schema: object, ref: OptionDefinitionRef): string {
return item?.title || item?.name || '';
}

// A node flagged `x-has-data-type: true` is a documented data type: link to its
// A node flagged `x-mergify-has-data-type: true` is a documented data type: link to its
// data-types section instead of expanding the node's shape — an enum such as
// `queue-dequeue-reason` would otherwise dump its forty codes into the type
// cell. The node's `title` drives both the label and the anchor (slugified
Expand Down
26 changes: 19 additions & 7 deletions src/util/dataType.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@ import { collectDataTypeTitles, getDataTypeHref, isDataType } from './dataType';
import { dataTypesHeadingAnchors, missingDataTypeAnchors } from './dataTypeAnchors';

describe('isDataType', () => {
it('recognizes flagged nodes only', () => {
expect(isDataType({ 'x-has-data-type': true })).toBe(true);
expect(isDataType({ 'x-has-data-type': 'queue-dequeue-reason' })).toBe(false);
it('recognizes the namespaced marker', () => {
expect(isDataType({ 'x-mergify-has-data-type': true })).toBe(true);
expect(isDataType({ 'x-mergify-has-data-type': 'queue-dequeue-reason' })).toBe(false);
});

// Reading only the new spelling is what lets the engine drop the old one:
// a consumer that accepted either would leave the removal unprovable.
it('ignores the legacy marker', () => {
expect(isDataType({ 'x-has-data-type': true })).toBe(false);
});

it('rejects everything else', () => {
expect(isDataType({ type: 'string' })).toBe(false);
expect(isDataType(null)).toBe(false);
expect(isDataType('string')).toBe(false);
Expand All @@ -26,14 +35,17 @@ describe('collectDataTypeTitles', () => {
it('finds flagged nodes wherever they appear in a schema', () => {
const schema = {
$defs: {
ReportMode: { 'x-has-data-type': true, title: 'Report Mode' },
ReportMode: { 'x-mergify-has-data-type': true, title: 'Report Mode' },
},
properties: {
reason: {
anyOf: [{ 'x-has-data-type': true, title: 'Queue dequeue reason' }, { type: 'null' }],
anyOf: [
{ 'x-mergify-has-data-type': true, title: 'Queue dequeue reason' },
{ type: 'null' },
],
},
report_mode: { type: 'array', items: { $ref: '#/$defs/ReportMode' } },
untitled: { 'x-has-data-type': true },
untitled: { 'x-mergify-has-data-type': true },
},
};
expect(collectDataTypeTitles(schema).sort()).toEqual([
Expand All @@ -52,7 +64,7 @@ describe('data-types page anchors', () => {
it('exposes every anchor the site links to', () => {
const anchors = dataTypesHeadingAnchors();
for (const expected of [
// derived from titles the engine marks with x-has-data-type
// derived from titles the engine marks with x-mergify-has-data-type
'queue-dequeue-reason',
'priority',
'report-mode',
Expand Down
25 changes: 15 additions & 10 deletions src/util/dataType.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { slugify } from './slugify';

// The engine flags a schema node that corresponds to a documented Mergify
// data type with `x-has-data-type: true`. Everything else derives from the node's
// standard `title`: the link label is the title, and the link target is the
// slugified title, which by convention equals the section heading anchor on
// /configuration/data-types. The convention is enforced where drift can
// data type with `x-mergify-has-data-type: true`. Everything else derives from
// the node's standard `title`: the link label is the title, and the link target
// is the slugified title, which by convention equals the section heading anchor
// on /configuration/data-types. The convention is enforced where drift can
// actually arrive: the Astro build fails when a marked title has no matching
// heading anchor (schema syncs land as direct pushes to main, so the deploy
// build is the gate), and dataType.test.ts gives the same signal earlier, in
// PR CI.
const DATA_TYPE_KEY = 'x-has-data-type';
//
// The engine published this as `x-has-data-type` until it was namespaced. It
// emits both spellings today, so reading only the new one is safe — and it is
// what lets the engine drop the old one: while a consumer accepts either, you
// cannot tell which it depends on, so nothing can prove the removal safe.
const DATA_TYPE_KEY = 'x-mergify-has-data-type';

const DATA_TYPES_PAGE = '/configuration/data-types';

Expand All @@ -26,11 +31,11 @@ export function getDataTypeHref(title: string): string {
}

/**
* Walk a JSON schema and return the `title` of every node flagged
* `x-has-data-type: true`, wherever the flag appears (inline property nodes,
* `$ref` siblings, `$defs` entries, array items, `anyOf` branches). A marked
* node without a title yields `undefined` — an invalid marker the anchor
* check reports.
* Walk a JSON schema and return the `title` of every node flagged as a
* documented data type, in either spelling, wherever the flag appears (inline
* property nodes, `$ref` siblings, `$defs` entries, array items, `anyOf`
* branches). A marked node without a title yields `undefined` — an invalid
* marker the anchor check reports.
*/
export function collectDataTypeTitles(
node: unknown,
Expand Down
4 changes: 2 additions & 2 deletions src/util/dataTypeAnchors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Node-only helpers for the x-has-data-type title↔anchor convention (they read
// Node-only helpers for the x-mergify-has-data-type title↔anchor convention (they read
// the data-types page from disk). Kept separate from dataType.ts so
// components can import the marker readers without dragging node:fs into a
// client bundle.
Expand Down Expand Up @@ -52,7 +52,7 @@ export function missingDataTypeAnchors(schema: unknown): string[] {
const problems = new Set<string>();
for (const title of collectDataTypeTitles(schema)) {
if (title === undefined) {
problems.add('(x-has-data-type node without a title)');
problems.add('(x-mergify-has-data-type node without a title)');
} else if (!anchors.has(slugify(title))) {
problems.add(`"${title}" → #${slugify(title)}`);
}
Expand Down
2 changes: 1 addition & 1 deletion src/util/enumChoices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ function ownValues(node: SchemaNode): string[] {
* Pydantic publishes an annotation inline for an inlined type but as a
* *sibling of `$ref`* for a type hoisted into `$defs`, so both the raw node
* and the resolved target have to be consulted — the same walk
* `ConfigOptions.getDataTypeLink` performs for `x-has-data-type`. Nearest wins:
* `ConfigOptions.getDataTypeLink` performs for `x-mergify-has-data-type`. Nearest wins:
* a sibling on the referring node overrides the shared component.
*/
function collectMetadata(root: unknown, node: unknown): SchemaNode {
Expand Down
2 changes: 1 addition & 1 deletion src/util/schemaToMarkdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function getTitle(schema: Schema, ref: string): string {
}

// Markdown twin of ConfigOptions.getDataTypeLink: a node flagged
// `x-has-data-type: true` links to its data-types section instead of expanding
// `x-mergify-has-data-type: true` links to its data-types section instead of expanding
// its shape, with the node's `title` driving both label and anchor. The flag
// may sit inline or beside a `$ref`, so check each node along the chain.
function getDataTypeLinkText(schema: Schema, definition: unknown): string | undefined {
Expand Down