Skip to content

Commit ab12a83

Browse files
dmealingclaude
andcommitted
refactor(docs): extract shared constraint validator walk (behavior-preserving)
constraintsCell() (Storage cell) and buildConstraintRow() (Constraints row) each walked field.validators() with byte-identical bucketing logic — a DRIFT risk a reviewer flagged. Extract collectValidatorParts() as the single source of truth for the validator/limit facts (regex pattern, length, numeric, and the @maxlength attr). Each consumer still arranges the raw parts into its own layout (one cell vs. limits/rules columns); emission order and exact strings are unchanged, so all docs goldens stay byte-identical. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 5fc844b commit ab12a83

1 file changed

Lines changed: 57 additions & 53 deletions

File tree

server/typescript/packages/codegen-ts/src/generators/docs-data-builder.ts

Lines changed: 57 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,57 @@ function sqlColumnExpr(spec: ReturnType<typeof mapColumnType>): string {
154154
return `${spec.fnName}(${dbName})`;
155155
}
156156

157+
/** The raw validator/limit facts shared by both constraint walks. Walks the
158+
* field's validators ONCE, bucketed by subtype, plus the `@maxLength` attr.
159+
* Both `constraintsCell()` (Storage cell) and `buildConstraintRow()`
160+
* (Constraints row) consume these — the SINGLE source of truth for the
161+
* validator emission, so the two presentations can't DRIFT. Each caller
162+
* arranges the parts into its own layout (one cell vs. limits/rules columns)
163+
* but the emission ORDER and exact strings come from here:
164+
* regex pattern → maxLength-from-@maxLength → length-validator (min/max) →
165+
* numeric-validator (min/max). */
166+
interface ValidatorParts {
167+
/** `@maxLength` attr value if a finite number, else undefined. */
168+
maxLenAttr: number | undefined;
169+
/** "pattern `...`" entries from regex validators. */
170+
regexParts: string[];
171+
/** "minLength: N" / "maxLength: N" entries from length validators. */
172+
lengthParts: string[];
173+
/** "min: N" / "max: N" entries from numeric validators. */
174+
numericParts: string[];
175+
}
176+
177+
function collectValidatorParts(field: MetaField): ValidatorParts {
178+
const maxLenAttr = field.ownAttr(FIELD_ATTR_MAX_LENGTH);
179+
const regexParts: string[] = [];
180+
const lengthParts: string[] = [];
181+
const numericParts: string[] = [];
182+
for (const v of field.validators()) {
183+
if (v.subType === VALIDATOR_SUBTYPE_REGEX) {
184+
const pattern = v.ownAttr(VALIDATOR_ATTR_PATTERN);
185+
if (typeof pattern === "string" && pattern.length > 0) {
186+
regexParts.push(`pattern \`${pattern}\``);
187+
}
188+
} else if (v.subType === VALIDATOR_SUBTYPE_LENGTH) {
189+
const min = v.ownAttr(VALIDATOR_ATTR_MIN);
190+
const max = v.ownAttr(VALIDATOR_ATTR_MAX);
191+
if (typeof min === "number") lengthParts.push(`minLength: ${min}`);
192+
if (typeof max === "number" && typeof maxLenAttr !== "number") lengthParts.push(`maxLength: ${max}`);
193+
} else if (v.subType === VALIDATOR_SUBTYPE_NUMERIC) {
194+
const min = v.ownAttr(VALIDATOR_ATTR_MIN);
195+
const max = v.ownAttr(VALIDATOR_ATTR_MAX);
196+
if (typeof min === "number") numericParts.push(`min: ${min}`);
197+
if (typeof max === "number") numericParts.push(`max: ${max}`);
198+
}
199+
}
200+
return {
201+
maxLenAttr: typeof maxLenAttr === "number" ? maxLenAttr : undefined,
202+
regexParts,
203+
lengthParts,
204+
numericParts,
205+
};
206+
}
207+
157208
function constraintsCell(
158209
entity: MetaObject,
159210
field: MetaField,
@@ -191,34 +242,9 @@ function constraintsCell(
191242
}
192243
}
193244

194-
// Walk validators once, bucket by subtype. We re-emit in the original
195-
// emission order to preserve byte-identity with the docs-file-basic
196-
// conformance fixture: regex pattern → maxLength-from-@maxLength →
197-
// length-validator (min/max) → numeric-validator (min/max).
198-
const maxLenAttr = field.ownAttr(FIELD_ATTR_MAX_LENGTH);
199-
const regexParts: string[] = [];
200-
const lengthParts: string[] = [];
201-
const numericParts: string[] = [];
202-
for (const v of field.validators()) {
203-
if (v.subType === VALIDATOR_SUBTYPE_REGEX) {
204-
const pattern = v.ownAttr(VALIDATOR_ATTR_PATTERN);
205-
if (typeof pattern === "string" && pattern.length > 0) {
206-
regexParts.push(`pattern \`${pattern}\``);
207-
}
208-
} else if (v.subType === VALIDATOR_SUBTYPE_LENGTH) {
209-
const min = v.ownAttr(VALIDATOR_ATTR_MIN);
210-
const max = v.ownAttr(VALIDATOR_ATTR_MAX);
211-
if (typeof min === "number") lengthParts.push(`minLength: ${min}`);
212-
if (typeof max === "number" && typeof maxLenAttr !== "number") lengthParts.push(`maxLength: ${max}`);
213-
} else if (v.subType === VALIDATOR_SUBTYPE_NUMERIC) {
214-
const min = v.ownAttr(VALIDATOR_ATTR_MIN);
215-
const max = v.ownAttr(VALIDATOR_ATTR_MAX);
216-
if (typeof min === "number") numericParts.push(`min: ${min}`);
217-
if (typeof max === "number") numericParts.push(`max: ${max}`);
218-
}
219-
}
245+
const { maxLenAttr, regexParts, lengthParts, numericParts } = collectValidatorParts(field);
220246
parts.push(...regexParts);
221-
if (typeof maxLenAttr === "number") {
247+
if (maxLenAttr !== undefined) {
222248
parts.push(`maxLength: ${maxLenAttr}`);
223249
}
224250
parts.push(...lengthParts, ...numericParts);
@@ -285,33 +311,11 @@ function buildConstraintRow(
285311
}
286312
}
287313

288-
// Same emission order as constraintsCell(): regex pattern →
289-
// maxLength-from-@maxLength → length-validator (min/max) →
290-
// numeric-validator (min/max).
291-
const maxLenAttr = field.ownAttr(FIELD_ATTR_MAX_LENGTH);
292-
const regexParts: string[] = [];
293-
const lengthParts: string[] = [];
294-
const numericParts: string[] = [];
295-
for (const v of field.validators()) {
296-
if (v.subType === VALIDATOR_SUBTYPE_REGEX) {
297-
const pattern = v.ownAttr(VALIDATOR_ATTR_PATTERN);
298-
if (typeof pattern === "string" && pattern.length > 0) {
299-
regexParts.push(`pattern \`${pattern}\``);
300-
}
301-
} else if (v.subType === VALIDATOR_SUBTYPE_LENGTH) {
302-
const min = v.ownAttr(VALIDATOR_ATTR_MIN);
303-
const max = v.ownAttr(VALIDATOR_ATTR_MAX);
304-
if (typeof min === "number") lengthParts.push(`minLength: ${min}`);
305-
if (typeof max === "number" && typeof maxLenAttr !== "number") lengthParts.push(`maxLength: ${max}`);
306-
} else if (v.subType === VALIDATOR_SUBTYPE_NUMERIC) {
307-
const min = v.ownAttr(VALIDATOR_ATTR_MIN);
308-
const max = v.ownAttr(VALIDATOR_ATTR_MAX);
309-
if (typeof min === "number") numericParts.push(`min: ${min}`);
310-
if (typeof max === "number") numericParts.push(`max: ${max}`);
311-
}
312-
}
314+
// Same validator facts as constraintsCell() (shared walk), arranged across
315+
// the Limits / Rules columns instead of one cell.
316+
const { maxLenAttr, regexParts, lengthParts, numericParts } = collectValidatorParts(field);
313317
rules.push(...regexParts);
314-
if (typeof maxLenAttr === "number") limits.push(`maxLength: ${maxLenAttr}`);
318+
if (maxLenAttr !== undefined) limits.push(`maxLength: ${maxLenAttr}`);
315319
limits.push(...lengthParts, ...numericParts);
316320

317321
const fk = fkMap.get(field.name);

0 commit comments

Comments
 (0)