@@ -163,40 +163,56 @@ export function objectSchema(params: {
163163 properties = properties || { } ;
164164 patternProperties = patternProperties || { } ;
165165 // deno-lint-ignore no-explicit-any
166- const tags : Record < string , any > = { } ;
166+ let tags : Record < string , any > = { } ;
167167 let tagsAreSet = false ;
168168 let propertyNames : Schema | undefined = propertyNamesSchema ;
169169
170- const objectKeys = Object . getOwnPropertyNames ( completionsParam || properties ) ;
170+ if ( completionsParam ) {
171+ tags [ "completions" ] = completionsParam ;
172+ tagsAreSet = true ;
173+ }
171174
172- if ( namingConvention !== "ignore" ) {
175+ const createCaseConventionSchema = (
176+ props : { [ k : string ] : Schema } ,
177+ ) : StringSchema | undefined => {
178+ // 2023-01-17: we no longer support propertyNames _and_ case convention detection.
179+ // if propertyNames are defined, we don't add case convention detection.
180+ // this simplifies the logic and makes schema debugging easier.
181+ if ( namingConvention === "ignore" ) {
182+ return undefined ;
183+ }
184+ const objectKeys = Object . getOwnPropertyNames (
185+ props ,
186+ ) ;
173187 const { pattern, list } = resolveCaseConventionRegex (
174188 objectKeys ,
175189 namingConvention ,
176190 ) ;
177- if ( pattern !== undefined ) {
178- if ( propertyNames === undefined ) {
179- propertyNames = {
180- "type" : "string" ,
181- pattern,
182- } ;
183- } else {
184- propertyNames = allOfSchema (
185- propertyNames ,
186- {
187- "type" : "string" ,
188- pattern,
189- } ,
190- ) ;
191- }
192- tags [ "case-convention" ] = list ;
193- tagsAreSet = true ;
191+ if ( pattern === undefined ) {
192+ return undefined ;
194193 }
195- }
196- if ( completionsParam ) {
197- tags [ "completions" ] = completionsParam ;
198- tagsAreSet = true ;
199- }
194+ if ( propertyNames !== undefined ) {
195+ console . error (
196+ "Warning: propertyNames and case convention detection are mutually exclusive." ,
197+ ) ;
198+ console . error (
199+ "Add `namingConvention: 'ignore'` to your schema definition to remove this warning." ,
200+ ) ;
201+ return undefined ;
202+ }
203+ const tags = {
204+ "case-convention" : list ,
205+ "error-importance" : - 5 ,
206+ "case-detection" : true ,
207+ } ;
208+ return {
209+ errorMessage : "property ${value} does not match case convention " +
210+ `${ objectKeys . join ( "," ) } ` ,
211+ "type" : "string" ,
212+ pattern,
213+ tags,
214+ } ;
215+ } ;
200216
201217 const hasDescription = description !== undefined ;
202218 description = description || "be an object" ;
@@ -237,14 +253,48 @@ export function objectSchema(params: {
237253 result . description = description ;
238254 }
239255
256+ const m : Map < string , [ Schema , string ] [ ] > = new Map ( ) ;
257+ for ( const base of baseSchema ) {
258+ for ( const [ k , v ] of Object . entries ( base . properties || { } ) ) {
259+ if ( ! m . has ( k ) ) {
260+ m . set ( k , [ ] ) ;
261+ }
262+ // deno-lint-ignore no-explicit-any
263+ m . get ( k ) ! . push ( [ v , ( base as any ) . $id ] ) ;
264+ }
265+ }
266+ const errorMsgs = new Set < string > ( ) ;
267+ for ( const [ k , l ] of m ) {
268+ if ( l . length > 1 ) {
269+ errorMsgs . add (
270+ `Internal Error: base schemas ${
271+ l
272+ . map ( ( x ) => x [ 1 ] )
273+ . join ( ", " )
274+ } share property ${ k } .`,
275+ ) ;
276+ }
277+ }
278+ if ( errorMsgs . size > 0 ) {
279+ console . error (
280+ [ ...errorMsgs ] . toSorted ( ( a , b ) => a . localeCompare ( b ) ) . join ( "\n" ) ,
281+ ) ;
282+ console . error ( "This is a bug in quarto's schemas." ) ;
283+ console . error (
284+ "Note that we don't throw in order to allow build-js to finish, but the generated schemas will be invalid." ,
285+ ) ;
286+ }
287+
240288 result . properties = Object . assign (
241289 { } ,
242290 ...( baseSchema . map ( ( s ) => s . properties ) ) ,
243291 properties ,
244292 ) ;
245293 result . patternProperties = Object . assign (
246294 { } ,
247- ...( baseSchema . map ( ( s ) => s . patternProperties ) ) ,
295+ ...( baseSchema . map ( ( s ) => s . patternProperties ) . filter ( ( s ) =>
296+ s !== undefined
297+ ) ) ,
248298 patternProperties ,
249299 ) ;
250300
@@ -279,18 +329,46 @@ export function objectSchema(params: {
279329 //
280330 // as a result, we only set propertyNames on the extended schema if both
281331 // all propertyNames fields are defined.
332+
333+ // if we subclass from something with patternProperties that came from case convention
334+ // detection, ignore that.
335+
336+ let filtered = false ;
282337 const propNamesArray = baseSchema . map ( ( s ) => s . propertyNames )
338+ . filter ( ( s ) => {
339+ if ( typeof s !== "object" ) return true ;
340+ if ( s . tags === undefined ) return true ;
341+ if ( s . tags [ "case-detection" ] === true ) {
342+ filtered = true ;
343+ return false ;
344+ }
345+ return true ;
346+ } )
283347 . filter ( ( s ) => s !== undefined ) as Schema [ ] ;
284- if ( propertyNames ) {
285- propNamesArray . push ( propertyNames ) ;
286- }
287- if ( propNamesArray . length === baseSchema . length + 1 ) {
348+ // if (propertyNames) {
349+ // propNamesArray.push(propertyNames);
350+ // }
351+ if ( propNamesArray . length === 1 ) {
352+ result . propertyNames = propNamesArray [ 0 ] ;
353+ } else if ( propNamesArray . length > 1 ) {
288354 result . propertyNames = anyOfSchema ( ...propNamesArray ) ;
355+ } else {
356+ delete result . propertyNames ;
289357 }
290358
291359 // if either of schema or base schema is closed, the derived schema is also closed.
292360 result . closed = closed || baseSchema . some ( ( s ) => s . closed ) ;
293361 } else {
362+ const caseConventionSchema = createCaseConventionSchema ( properties ) ;
363+ if ( caseConventionSchema !== undefined ) {
364+ propertyNames = caseConventionSchema ;
365+ tags = {
366+ ...tags ,
367+ ...caseConventionSchema . tags ,
368+ } ;
369+ tagsAreSet = true ;
370+ }
371+
294372 result = {
295373 ...internalId ( ) ,
296374 "type" : "object" ,
0 commit comments