@@ -190,6 +190,53 @@ const LAYOUT_ARRAY_NAMES = new Map([
190190
191191const serializeValue = tsLiteral ;
192192
193+ /**
194+ * Schema `values` entries wrapped in `/.../` are regexes, not literals. The
195+ * enumerated `validateFunction` in src/lib/coerce.js unwraps and tests them.
196+ * Each one maps to a template literal type in src/types/lib/common.d.ts that
197+ * accepts the same strings.
198+ *
199+ * A regex stands in for a value set that the static schema cannot list,
200+ * because the real set depends on how many axes a figure declares.
201+ */
202+ const REGEX_VALUE_TYPES = new Map ( [
203+ [ '/^x([2-9]|[1-9][0-9]+)?( domain)?$/' , 'XAxisName' ] ,
204+ [ '/^y([2-9]|[1-9][0-9]+)?( domain)?$/' , 'YAxisName' ] ,
205+ [ '/^x([2-9]|[1-9][0-9]+)?y([2-9]|[1-9][0-9]+)?$/' , 'CartesianSubplotId' ]
206+ ] ) ;
207+
208+ /** Test whether a schema value uses the `/.../` regex convention. */
209+ function isRegexValue ( v ) {
210+ return typeof v === 'string' && v . length > 1 && v . startsWith ( '/' ) && v . endsWith ( '/' ) ;
211+ }
212+
213+ /**
214+ * Serialize one `values` entry from a string or enumerated attribute.
215+ *
216+ * Plain values become string literals. Regex values become the named type
217+ * that accepts the same strings.
218+ *
219+ * @param {string|number|boolean } v - The schema value
220+ * @param {string } attrPath - Dotted attribute path, used in the error message
221+ * @returns {string }
222+ * @throws If `v` is a regex with no entry in `REGEX_VALUE_TYPES`. Emitting the
223+ * regex source as a string literal would produce a type that only accepts
224+ * the pattern text, so failing here is deliberate.
225+ */
226+ function enumValueToTS ( v , attrPath ) {
227+ if ( ! isRegexValue ( v ) ) return serializeValue ( v ) ;
228+
229+ const typeName = REGEX_VALUE_TYPES . get ( v ) ;
230+ if ( ! typeName ) {
231+ throw new Error (
232+ `Unmapped regex value '${ v } ' at '${ attrPath } '. Add an entry to REGEX_VALUE_TYPES ` +
233+ `pointing at a template literal type in src/types/lib/common.d.ts that accepts ` +
234+ `the same strings.`
235+ ) ;
236+ }
237+ return typeName ;
238+ }
239+
193240/**
194241 * Try to match a values array to a known common type.
195242 * Returns the type name string or null.
@@ -211,32 +258,42 @@ function matchCommonType(values) {
211258 * When all elements share the same valType we can emit a precise tuple
212259 * or array type instead of `unknown[]`.
213260 */
214- function infoArrayToTS ( attr ) {
261+ function infoArrayToTS ( attr , attrPath ) {
215262 if ( ! attr . items ) return 'any[]' ;
216263
217264 // items can be an array of item descriptors or a single descriptor
218265 // (single descriptor = freeLength homogeneous array)
219266 if ( ! Array . isArray ( attr . items ) ) {
220- const elemType = simpleValType ( attr . items . valType ) ;
221- return `${ elemType } []` ;
267+ return `${ asArrayElement ( simpleValType ( attr . items , attrPath ) ) } []` ;
222268 }
223269
224- const elemTypes = attr . items . map ( ( item ) => simpleValType ( item . valType ) ) ;
270+ const elemTypes = attr . items . map ( ( item ) => simpleValType ( item , attrPath ) ) ;
225271
226272 if ( attr . freeLength ) {
227273 // Variable-length — use array of the union of element types
228274 const unique = [ ...new Set ( elemTypes ) ] ;
229275 const union = unique . length === 1 ? unique [ 0 ] : unique . join ( ' | ' ) ;
230- return `${ union } []` ;
276+ return `${ asArrayElement ( union ) } []` ;
231277 }
232278
233279 // Fixed-length — emit tuple
234280 return `[${ elemTypes . join ( ', ' ) } ]` ;
235281}
236282
237- /** Map a valType string to a simple TS type (no arrayOk handling). */
238- function simpleValType ( valType ) {
239- switch ( valType ) {
283+ /** Parenthesize a union so that appending `[]` binds to the whole union. */
284+ function asArrayElement ( type ) {
285+ return type . includes ( '|' ) ? `(${ type } )` : type ;
286+ }
287+
288+ /**
289+ * Map an info_array item descriptor to a simple TS type (no arrayOk handling).
290+ *
291+ * @param {object } item - The item descriptor, carrying at least `valType`
292+ * @param {string } attrPath - Dotted path of the owning attribute
293+ * @returns {string }
294+ */
295+ function simpleValType ( item , attrPath ) {
296+ switch ( item . valType ) {
240297 case 'number' :
241298 case 'integer' :
242299 return 'number' ;
@@ -247,6 +304,9 @@ function simpleValType(valType) {
247304 return 'boolean' ;
248305 case 'color' :
249306 return 'Color' ;
307+ case 'enumerated' :
308+ if ( ! Array . isArray ( item . values ) ) return 'any' ;
309+ return item . values . map ( ( v ) => enumValueToTS ( v , attrPath ) ) . join ( ' | ' ) ;
250310 default :
251311 return 'any' ;
252312 }
@@ -344,7 +404,7 @@ function valTypeToTS(attr, attrPath) {
344404 base = common ;
345405 break ;
346406 }
347- base = attr . values . map ( serializeValue ) . join ( ' | ' ) ;
407+ base = attr . values . map ( ( v ) => enumValueToTS ( v , attrPath ) ) . join ( ' | ' ) ;
348408 } else {
349409 base = 'string' ;
350410 }
@@ -381,7 +441,7 @@ function valTypeToTS(attr, attrPath) {
381441 base = common ;
382442 break ;
383443 }
384- base = attr . values . map ( serializeValue ) . join ( ' | ' ) ;
444+ base = attr . values . map ( ( v ) => enumValueToTS ( v , attrPath ) ) . join ( ' | ' ) ;
385445 } else {
386446 base = 'any' ;
387447 }
@@ -408,7 +468,7 @@ function valTypeToTS(attr, attrPath) {
408468 }
409469
410470 case 'info_array' :
411- return infoArrayToTS ( attr ) ;
471+ return infoArrayToTS ( attr , attrPath ) ;
412472
413473 case 'any' :
414474 return 'any' ;
@@ -1057,7 +1117,16 @@ export function generateSchemaTypes(schema, outputPath) {
10571117 command : 'npm run schema'
10581118 } ) ,
10591119 '' ,
1060- "import type { Color, ColorScale, Datum, MarkerSymbol, TypedArray } from '../lib/common';" ,
1120+ 'import type {' ,
1121+ ' CartesianSubplotId,' ,
1122+ ' Color,' ,
1123+ ' ColorScale,' ,
1124+ ' Datum,' ,
1125+ ' MarkerSymbol,' ,
1126+ ' TypedArray,' ,
1127+ ' XAxisName,' ,
1128+ ' YAxisName' ,
1129+ "} from '../lib/common';" ,
10611130 ''
10621131 ] ;
10631132
@@ -1242,7 +1311,21 @@ export function generateSchemaTypes(schema, outputPath) {
12421311 }
12431312 }
12441313
1245- fs . writeFileSync ( outputPath , toFileText ( chunks ) ) ;
1314+ const output = toFileText ( chunks ) ;
1315+
1316+ // Backstop for the `/.../` regex convention. `enumValueToTS` already fails
1317+ // on an unmapped regex and names the attribute, but it only sees `values`
1318+ // entries. This catches a regex that reaches the output by another route.
1319+ const leakedRegexes = output . match ( / ' \/ \^ [ ^ ' ] * ' / g) ;
1320+ if ( leakedRegexes ) {
1321+ throw new Error (
1322+ `Generated types contain regex string literals: ${ [ ...new Set ( leakedRegexes ) ] . join ( ', ' ) } . ` +
1323+ `A schema regex reached the output as a literal type, which only accepts the pattern ` +
1324+ `text itself. Map it in REGEX_VALUE_TYPES.`
1325+ ) ;
1326+ }
1327+
1328+ fs . writeFileSync ( outputPath , output ) ;
12461329
12471330 const sharedCount = sharedList . length ;
12481331 const layoutCount = subplotGroups . size + arrayItems . size + 1 ; // +1 for Layout itself
0 commit comments