schema-markdown 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/encode.js CHANGED
@@ -108,9 +108,14 @@ export function decodeQueryString(paramStr) {
108
108
  parent[keyParent] = obj;
109
109
  }
110
110
 
111
- // Create the index for this key
112
- if (!(key in obj)) {
113
- obj[key] = null;
111
+ // Create the index for this key - check own properties only, and define "__proto__"
112
+ // directly since assignment would invoke the prototype setter
113
+ if (!Object.hasOwn(obj, key)) {
114
+ if (key === '__proto__') {
115
+ Object.defineProperty(obj, key, {'value': null, 'enumerable': true, 'writable': true, 'configurable': true});
116
+ } else {
117
+ obj[key] = null;
118
+ }
114
119
  }
115
120
  }
116
121
 
package/lib/parser.js CHANGED
@@ -49,6 +49,7 @@ const rValue = new RegExp(`^\\s+(?<id>${rPartId})\\s*$`);
49
49
  const rValueQuoted = /^\s+"(?<id>.*?)"\s*$/;
50
50
  const rURL = /^\s+(?<method>[A-Za-z]+|\*)(?:\s+(?<path>\/\S*))?/;
51
51
  const rLineSplit = /\r?\n/;
52
+ const rIndent = /^\s/;
52
53
 
53
54
 
54
55
  /**
@@ -107,7 +108,7 @@ export function parseSchemaMarkdown(text, {types = {}, filename = '', validate =
107
108
  linenum += 1;
108
109
 
109
110
  // Line continuation?
110
- const linePartNoContinuation = linePart.replace(rLineCont, '');
111
+ const linePartNoContinuation = linePart.includes('\\') ? linePart.replace(rLineCont, '') : linePart;
111
112
  if (lineContinuation.length || linePartNoContinuation !== linePart) {
112
113
  lineContinuation.push(linePartNoContinuation);
113
114
  }
@@ -122,47 +123,49 @@ export function parseSchemaMarkdown(text, {types = {}, filename = '', validate =
122
123
  line = linePart;
123
124
  }
124
125
 
125
- // Match syntax
126
+ // Match syntax - each regex is guarded by a cheap string test that only skips it when it cannot match
127
+ const lineIndented = rIndent.test(line);
128
+ const lineTrimmed = line.trim();
126
129
  let matchName = 'comment';
127
- let match = line.match(rComment);
130
+ let match = (lineTrimmed === '' || lineTrimmed.startsWith('#')) ? line.match(rComment) : null;
128
131
  if (match === null) {
129
132
  matchName = 'group';
130
- match = line.match(rGroup);
133
+ match = line.startsWith('group') ? line.match(rGroup) : null;
131
134
  }
132
135
  if (match === null) {
133
136
  matchName = 'action';
134
- match = line.match(rAction);
137
+ match = line.startsWith('action') ? line.match(rAction) : null;
135
138
  }
136
139
  if (match === null) {
137
140
  matchName = 'definition';
138
- match = line.match(rDefinition);
141
+ match = (line.startsWith('struct') || line.startsWith('union') || line.startsWith('enum')) ? line.match(rDefinition) : null;
139
142
  }
140
- if (match === null && action !== null) {
143
+ if (match === null && action !== null && lineIndented) {
141
144
  matchName = 'section';
142
145
  match = line.match(rSection);
143
146
  }
144
- if (match === null && action !== null) {
147
+ if (match === null && action !== null && lineIndented) {
145
148
  matchName = 'sectionPlain';
146
149
  match = line.match(rSectionPlain);
147
150
  }
148
- if (match === null && userType !== null && 'enum' in userType) {
151
+ if (match === null && userType !== null && 'enum' in userType && lineIndented) {
149
152
  matchName = 'value';
150
153
  match = line.match(rValue);
151
154
  if (match === null) {
152
155
  match = line.match(rValueQuoted);
153
156
  }
154
157
  }
155
- if (match === null && userType !== null && 'struct' in userType) {
158
+ if (match === null && userType !== null && 'struct' in userType && lineIndented) {
156
159
  matchName = 'member';
157
160
  match = line.match(rMember);
158
161
  }
159
- if (match === null && urls !== null) {
162
+ if (match === null && urls !== null && lineIndented) {
160
163
  matchName = 'urls';
161
164
  match = line.match(rURL);
162
165
  }
163
166
  if (match === null) {
164
167
  matchName = 'typedef';
165
- match = line.match(rTypedef);
168
+ match = line.startsWith('typedef') ? line.match(rTypedef) : null;
166
169
  }
167
170
  if (match === null) {
168
171
  matchName = null;
@@ -189,7 +192,7 @@ export function parseSchemaMarkdown(text, {types = {}, filename = '', validate =
189
192
  const actionId = match.groups.id;
190
193
 
191
194
  // Action already defined?
192
- if (actionId in types) {
195
+ if (Object.hasOwn(types, actionId)) {
193
196
  addError(`Redefinition of action '${actionId}'`, filename, linenum);
194
197
  }
195
198
 
@@ -215,7 +218,7 @@ export function parseSchemaMarkdown(text, {types = {}, filename = '', validate =
215
218
  const definitionBaseIds = match.groups.baseIds;
216
219
 
217
220
  // Type already defined?
218
- if (builtinTypes.has(definitionId) || definitionId in builtinDeprecated || definitionId in types) {
221
+ if (builtinTypes.has(definitionId) || Object.hasOwn(builtinDeprecated, definitionId) || Object.hasOwn(types, definitionId)) {
219
222
  addError(`Redefinition of type '${definitionId}'`, filename, linenum);
220
223
  }
221
224
 
@@ -394,7 +397,7 @@ export function parseSchemaMarkdown(text, {types = {}, filename = '', validate =
394
397
  const definitionId = match.groups.id;
395
398
 
396
399
  // Type already defined?
397
- if (builtinTypes.has(definitionId) || definitionId in builtinDeprecated || definitionId in types) {
400
+ if (builtinTypes.has(definitionId) || Object.hasOwn(builtinDeprecated, definitionId) || Object.hasOwn(types, definitionId)) {
398
401
  addError(`Redefinition of type '${definitionId}'`, filename, linenum);
399
402
  }
400
403
 
@@ -449,7 +452,7 @@ export function parseSchemaMarkdown(text, {types = {}, filename = '', validate =
449
452
  errorLinenum = filepos[`${typeName}.${memberName}`] ?? null;
450
453
  }
451
454
  if (errorLinenum === null) {
452
- errorLinenum = filepos[typeName] ?? null;
455
+ errorLinenum = Object.hasOwn(filepos, typeName) ? filepos[typeName] : null;
453
456
  }
454
457
  if (errorLinenum === null) {
455
458
  errorFilename = '';
@@ -527,7 +530,7 @@ function parseTypedef(matchTypedef) {
527
530
 
528
531
  // Helper function to create a type model
529
532
  function createType(typeName) {
530
- if (typeName in builtinDeprecated) {
533
+ if (Object.hasOwn(builtinDeprecated, typeName)) {
531
534
  return {'builtin': builtinDeprecated[typeName]};
532
535
  }
533
536
  if (builtinTypes.has(typeName)) {
package/lib/schema.js CHANGED
@@ -39,9 +39,9 @@ function getReferencedTypesHelper(types, type, referencedTypes) {
39
39
  const typeName = type.user;
40
40
 
41
41
  // Already encountered?
42
- if (!(typeName in referencedTypes)) {
43
- const userType = types[typeName];
44
- referencedTypes[typeName] = userType;
42
+ if (!Object.hasOwn(referencedTypes, typeName)) {
43
+ const userType = getUserType(types, typeName);
44
+ setValue(referencedTypes, typeName, userType);
45
45
 
46
46
  // Struct?
47
47
  if ('struct' in userType) {
@@ -116,6 +116,27 @@ export class ValidationError extends Error {
116
116
  }
117
117
 
118
118
 
119
+ // Helper to look up a user type by name - throws ValidationError for unknown type names
120
+ function getUserType(types, typeName) {
121
+ const userType = Object.hasOwn(types, typeName) ? types[typeName] : null;
122
+ if (userType === null) {
123
+ throw new ValidationError(`Unknown type "${typeName}"`);
124
+ }
125
+ return userType;
126
+ }
127
+
128
+
129
+ // Helper to set an own property on a plain-object copy - the "__proto__" key is defined directly
130
+ // since assignment would invoke the inherited prototype setter instead of creating an own property
131
+ function setValue(obj, key, value) {
132
+ if (key === '__proto__') {
133
+ Object.defineProperty(obj, key, {'value': value, 'enumerable': true, 'writable': true, 'configurable': true});
134
+ } else {
135
+ obj[key] = value;
136
+ }
137
+ }
138
+
139
+
119
140
  /**
120
141
  * Type-validate a value using a user type model. Container values are duplicated since some member types are
121
142
  * transformed during validation.
@@ -134,9 +155,6 @@ export class ValidationError extends Error {
134
155
  * @throws [ValidationError]{@link module:lib/schema.ValidationError}
135
156
  */
136
157
  export function validateType(types, typeName, value, memberFqn = null) {
137
- if (!(typeName in types)) {
138
- throw new ValidationError(`Unknown type "${typeName}"`);
139
- }
140
158
  return validateTypeHelper(types, {'user': typeName}, value, memberFqn);
141
159
  }
142
160
 
@@ -147,6 +165,10 @@ const rDatetime = /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})T\d{2}:\d{2}(?::
147
165
  const rUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
148
166
 
149
167
 
168
+ // Default dict key type used by validateTypeHelper
169
+ const defaultDictKeyType = {'builtin': 'string'};
170
+
171
+
150
172
  function validateTypeHelper(types, type, value, memberFqn) {
151
173
  let valueNew = value;
152
174
 
@@ -161,15 +183,6 @@ function validateTypeHelper(types, type, value, memberFqn) {
161
183
  throwMemberError(type, value, memberFqn);
162
184
  }
163
185
 
164
- // uuid?
165
- } else if (builtin === 'uuid') {
166
- if (typeof value !== 'string') {
167
- throwMemberError(type, value, memberFqn);
168
- }
169
- if (!rUuid.test(value)) {
170
- throwMemberError(type, value, memberFqn);
171
- }
172
-
173
186
  // int or float?
174
187
  } else if (builtin === 'int' || builtin === 'float') {
175
188
  // Convert string?
@@ -253,6 +266,15 @@ function validateTypeHelper(types, type, value, memberFqn) {
253
266
  } else if (!(value instanceof Date)) {
254
267
  throwMemberError(type, value, memberFqn);
255
268
  }
269
+
270
+ // uuid?
271
+ } else if (builtin === 'uuid') {
272
+ if (typeof value !== 'string') {
273
+ throwMemberError(type, value, memberFqn);
274
+ }
275
+ if (!rUuid.test(value)) {
276
+ throwMemberError(type, value, memberFqn);
277
+ }
256
278
  }
257
279
 
258
280
  // array?
@@ -271,13 +293,15 @@ function validateTypeHelper(types, type, value, memberFqn) {
271
293
  const valueCopy = [];
272
294
  const arrayValueNullable = arrayAttr !== null && 'nullable' in arrayAttr && arrayAttr.nullable;
273
295
  for (let ixArrayValue = 0; ixArrayValue < valueNew.length; ixArrayValue++) {
274
- const memberFqnValue = memberFqn !== null ? `${memberFqn}.${ixArrayValue}` : `${ixArrayValue}`;
296
+ const memberFqnValue = [memberFqn, ixArrayValue];
275
297
  let arrayValue = valueNew[ixArrayValue];
276
298
  if (arrayValueNullable && (arrayValue === null || arrayValue === 'null')) {
277
299
  arrayValue = null;
278
300
  } else {
279
301
  arrayValue = validateTypeHelper(types, arrayType, arrayValue, memberFqnValue);
280
- validateAttr(arrayType, arrayAttr, arrayValue, memberFqnValue);
302
+ if (arrayAttr !== null) {
303
+ validateAttr(arrayType, arrayAttr, arrayValue, memberFqnValue);
304
+ }
281
305
  }
282
306
  valueCopy.push(arrayValue);
283
307
  }
@@ -291,7 +315,7 @@ function validateTypeHelper(types, type, value, memberFqn) {
291
315
  const {dict} = type;
292
316
  const dictType = dict.type;
293
317
  const dictAttr = 'attr' in dict ? dict.attr : null;
294
- const dictKeyType = 'keyType' in dict ? dict.keyType : {'builtin': 'string'};
318
+ const dictKeyType = 'keyType' in dict ? dict.keyType : defaultDictKeyType;
295
319
  const dictKeyAttr = 'keyAttr' in dict ? dict.keyAttr : null;
296
320
  if (value === '') {
297
321
  valueNew = {};
@@ -300,18 +324,21 @@ function validateTypeHelper(types, type, value, memberFqn) {
300
324
  }
301
325
 
302
326
  // Validate the dict key/value pairs
303
- const valueCopy = valueNew instanceof Map ? new Map() : {};
327
+ const isMap = valueNew instanceof Map;
328
+ const valueCopy = isMap ? new Map() : {};
304
329
  const dictKeyNullable = dictKeyAttr !== null && 'nullable' in dictKeyAttr && dictKeyAttr.nullable;
305
330
  const dictValueNullable = dictAttr !== null && 'nullable' in dictAttr && dictAttr.nullable;
306
- for (let [dictKey, dictValue] of (valueNew instanceof Map ? valueNew.entries() : Object.entries(valueNew))) {
307
- const memberFqnKey = memberFqn !== null ? `${memberFqn}.${dictKey}` : `${dictKey}`;
331
+ for (let [dictKey, dictValue] of (isMap ? valueNew.entries() : Object.entries(valueNew))) {
332
+ const memberFqnKey = [memberFqn, dictKey];
308
333
 
309
334
  // Validate the key
310
335
  if (dictKeyNullable && (dictKey === null || dictKey === 'null')) {
311
336
  dictKey = null;
312
337
  } else {
313
338
  dictKey = validateTypeHelper(types, dictKeyType, dictKey, memberFqn);
314
- validateAttr(dictKeyType, dictKeyAttr, dictKey, memberFqn);
339
+ if (dictKeyAttr !== null) {
340
+ validateAttr(dictKeyType, dictKeyAttr, dictKey, memberFqn);
341
+ }
315
342
  }
316
343
 
317
344
  // Validate the value
@@ -319,14 +346,16 @@ function validateTypeHelper(types, type, value, memberFqn) {
319
346
  dictValue = null;
320
347
  } else {
321
348
  dictValue = validateTypeHelper(types, dictType, dictValue, memberFqnKey);
322
- validateAttr(dictType, dictAttr, dictValue, memberFqnKey);
349
+ if (dictAttr !== null) {
350
+ validateAttr(dictType, dictAttr, dictValue, memberFqnKey);
351
+ }
323
352
  }
324
353
 
325
354
  // Copy the key/value
326
- if (valueCopy instanceof Map) {
355
+ if (isMap) {
327
356
  valueCopy.set(dictKey, dictValue);
328
357
  } else {
329
- valueCopy[dictKey] = dictValue;
358
+ setValue(valueCopy, dictKey, dictValue);
330
359
  }
331
360
  }
332
361
 
@@ -335,7 +364,7 @@ function validateTypeHelper(types, type, value, memberFqn) {
335
364
 
336
365
  // User type?
337
366
  } else if ('user' in type) {
338
- const userType = types[type.user];
367
+ const userType = getUserType(types, type.user);
339
368
 
340
369
  // action?
341
370
  if ('action' in userType) {
@@ -353,7 +382,9 @@ function validateTypeHelper(types, type, value, memberFqn) {
353
382
  valueNew = null;
354
383
  } else {
355
384
  valueNew = validateTypeHelper(types, typedef.type, value, memberFqn);
356
- validateAttr(type, typedefAttr, valueNew, memberFqn);
385
+ if (typedefAttr !== null) {
386
+ validateAttr(type, typedefAttr, valueNew, memberFqn);
387
+ }
357
388
  }
358
389
 
359
390
  // enum?
@@ -377,52 +408,58 @@ function validateTypeHelper(types, type, value, memberFqn) {
377
408
  }
378
409
 
379
410
  // Valid union?
411
+ const isMap = valueNew instanceof Map;
380
412
  const isUnion = 'union' in struct ? struct.union : false;
381
413
  if (isUnion) {
382
- if (Object.keys(value).length !== 1) {
414
+ if ((isMap ? valueNew.size : Object.keys(valueNew).length) !== 1) {
383
415
  throwMemberError({'user': struct.name}, value, memberFqn);
384
416
  }
385
417
  }
386
418
 
387
419
  // Validate the struct members
388
- const valueCopy = valueNew instanceof Map ? new Map() : {};
420
+ const valueCopy = isMap ? new Map() : {};
389
421
  for (const member of getStructMembers(types, struct)) {
390
422
  const memberName = member.name;
391
- const memberFqnMember = memberFqn !== null ? `${memberFqn}.${memberName}` : `${memberName}`;
423
+ const memberFqnMember = [memberFqn, memberName];
392
424
  const memberOptional = 'optional' in member && member.optional;
393
- const memberNullable = 'attr' in member && 'nullable' in member.attr && member.attr.nullable;
425
+ const memberAttr = 'attr' in member ? member.attr : null;
426
+ const memberNullable = memberAttr !== null && 'nullable' in memberAttr && memberAttr.nullable;
394
427
 
395
- // Missing non-optional member?
396
- if (!(valueNew instanceof Map ? valueNew.has(memberName) : memberName in valueNew)) {
428
+ // Missing non-optional member? Check own properties only - "in" matches inherited
429
+ // Object.prototype properties for member names like "constructor"
430
+ if (!(isMap ? valueNew.has(memberName) : Object.hasOwn(valueNew, memberName))) {
397
431
  if (!memberOptional && !isUnion) {
398
- throw new ValidationError(`Required member "${memberFqnMember}" missing`);
432
+ throw new ValidationError(`Required member "${memberFqnStr(memberFqnMember)}" missing`);
399
433
  }
400
434
  } else {
401
435
  // Validate the member value
402
- let memberValue = valueNew instanceof Map ? valueNew.get(memberName) : valueNew[memberName];
436
+ let memberValue = isMap ? valueNew.get(memberName) : valueNew[memberName];
403
437
  if (memberNullable && (memberValue === null || memberValue === 'null')) {
404
438
  memberValue = null;
405
439
  } else {
406
440
  memberValue = validateTypeHelper(types, member.type, memberValue, memberFqnMember);
407
- validateAttr(member.type, 'attr' in member ? member.attr : null, memberValue, memberFqnMember);
441
+ if (memberAttr !== null) {
442
+ validateAttr(member.type, memberAttr, memberValue, memberFqnMember);
443
+ }
408
444
  }
409
445
 
410
446
  // Copy the validated member
411
- if (valueCopy instanceof Map) {
447
+ if (isMap) {
412
448
  valueCopy.set(memberName, memberValue);
413
449
  } else {
414
- valueCopy[memberName] = memberValue;
450
+ setValue(valueCopy, memberName, memberValue);
415
451
  }
416
452
  }
417
453
  }
418
454
 
419
455
  // Any unknown members?
420
- const valueCopyKeys = valueCopy instanceof Map ? Array.from(valueCopy.keys()) : Object.keys(valueCopy);
421
- const valueNewKeys = valueNew instanceof Map ? Array.from(valueNew.keys()) : Object.keys(valueNew);
422
- if (valueCopyKeys.length !== valueNewKeys.length) {
456
+ const valueCopyCount = isMap ? valueCopy.size : Object.keys(valueCopy).length;
457
+ const valueNewCount = isMap ? valueNew.size : Object.keys(valueNew).length;
458
+ if (valueCopyCount !== valueNewCount) {
423
459
  const memberSet = new Set(getStructMembers(types, struct).map((member) => member.name));
460
+ const valueNewKeys = isMap ? Array.from(valueNew.keys()) : Object.keys(valueNew);
424
461
  const [unknownKey] = valueNewKeys.filter((key) => !memberSet.has(key));
425
- const unknownFqn = memberFqn !== null ? `${memberFqn}.${unknownKey}` : `${unknownKey}`;
462
+ const unknownFqn = memberFqnStr([memberFqn, unknownKey]);
426
463
  throw new ValidationError(`Unknown member "${unknownFqn.slice(0, 100)}"`);
427
464
  }
428
465
 
@@ -435,36 +472,56 @@ function validateTypeHelper(types, type, value, memberFqn) {
435
472
  }
436
473
 
437
474
 
475
+ // Helper to materialize a member's fully-qualified name string. For performance, member FQNs are
476
+ // built lazily as [parent, key] pairs and only converted to strings on error.
477
+ function memberFqnStr(memberFqn) {
478
+ if (memberFqn === null || typeof memberFqn === 'string') {
479
+ return memberFqn;
480
+ }
481
+ const parts = [];
482
+ let fqnPart = memberFqn;
483
+ while (Array.isArray(fqnPart)) {
484
+ parts.push(`${fqnPart[1]}`);
485
+ [fqnPart] = fqnPart;
486
+ }
487
+ if (fqnPart !== null) {
488
+ parts.push(`${fqnPart}`);
489
+ }
490
+ parts.reverse();
491
+ return parts.join('.');
492
+ }
493
+
494
+
438
495
  function throwMemberError(type, value, memberFqn, attr = null) {
439
- const memberPart = memberFqn !== null ? ` for member "${memberFqn}"` : '';
496
+ const memberFqnValue = memberFqnStr(memberFqn);
497
+ const memberPart = memberFqnValue ? ` for member "${memberFqnValue}"` : '';
440
498
  const typeName = 'builtin' in type ? type.builtin : ('array' in type ? 'array' : ('dict' in type ? 'dict' : type.user));
441
499
  const attrPart = attr !== null ? ` [${attr}]` : '';
442
500
  const valueStr = `${JSON.stringify(value)}`;
443
501
  const msg = `Invalid value ${valueStr.slice(0, 100)} (type "${typeof value}")${memberPart}, expected type "${typeName}"${attrPart}`;
444
- throw new ValidationError(msg, memberFqn);
502
+ throw new ValidationError(msg, memberFqnValue);
445
503
  }
446
504
 
447
505
 
448
506
  function validateAttr(type, attr, value, memberFqn) {
449
- if (attr !== null) {
507
+ if ('eq' in attr && !(value === attr.eq)) {
508
+ throwMemberError(type, value, memberFqn, `== ${attr.eq}`);
509
+ }
510
+ if ('lt' in attr && !(value < attr.lt)) {
511
+ throwMemberError(type, value, memberFqn, `< ${attr.lt}`);
512
+ }
513
+ if ('lte' in attr && !(value <= attr.lte)) {
514
+ throwMemberError(type, value, memberFqn, `<= ${attr.lte}`);
515
+ }
516
+ if ('gt' in attr && !(value > attr.gt)) {
517
+ throwMemberError(type, value, memberFqn, `> ${attr.gt}`);
518
+ }
519
+ if ('gte' in attr && !(value >= attr.gte)) {
520
+ throwMemberError(type, value, memberFqn, `>= ${attr.gte}`);
521
+ }
522
+ if ('lenEq' in attr || 'lenLT' in attr || 'lenLTE' in attr || 'lenGT' in attr || 'lenGTE' in attr) {
450
523
  const length = Array.isArray(value) || typeof value === 'string' ? value.length
451
- : (typeof value === 'object' ? Object.keys(value).length : null);
452
-
453
- if ('eq' in attr && !(value === attr.eq)) {
454
- throwMemberError(type, value, memberFqn, `== ${attr.eq}`);
455
- }
456
- if ('lt' in attr && !(value < attr.lt)) {
457
- throwMemberError(type, value, memberFqn, `< ${attr.lt}`);
458
- }
459
- if ('lte' in attr && !(value <= attr.lte)) {
460
- throwMemberError(type, value, memberFqn, `<= ${attr.lte}`);
461
- }
462
- if ('gt' in attr && !(value > attr.gt)) {
463
- throwMemberError(type, value, memberFqn, `> ${attr.gt}`);
464
- }
465
- if ('gte' in attr && !(value >= attr.gte)) {
466
- throwMemberError(type, value, memberFqn, `>= ${attr.gte}`);
467
- }
524
+ : (value instanceof Map ? value.size : (typeof value === 'object' ? Object.keys(value).length : null));
468
525
  if ('lenEq' in attr && !(length === attr.lenEq)) {
469
526
  throwMemberError(type, value, memberFqn, `len == ${attr.lenEq}`);
470
527
  }
@@ -501,9 +558,9 @@ export function getStructMembers(types, struct) {
501
558
  // Get base struct members
502
559
  const members = [];
503
560
  for (const base of struct.bases) {
504
- let baseUserType = types[base];
561
+ let baseUserType = getUserType(types, base);
505
562
  while ('typedef' in baseUserType) {
506
- baseUserType = types[baseUserType.typedef.type.user];
563
+ baseUserType = getUserType(types, baseUserType.typedef.type.user);
507
564
  }
508
565
  members.push(...getStructMembers(types, baseUserType.struct));
509
566
  }
@@ -534,9 +591,9 @@ export function getEnumValues(types, enum_) {
534
591
  // Get base enum values
535
592
  const values = [];
536
593
  for (const base of enum_.bases) {
537
- let baseUserType = types[base];
594
+ let baseUserType = getUserType(types, base);
538
595
  while ('typedef' in baseUserType) {
539
- baseUserType = types[baseUserType.typedef.type.user];
596
+ baseUserType = getUserType(types, baseUserType.typedef.type.user);
540
597
  }
541
598
  values.push(...getEnumValues(types, baseUserType.enum));
542
599
  }
package/lib/schemaUtil.js CHANGED
@@ -43,13 +43,13 @@ export function validateTypeModelErrors(types) {
43
43
 
44
44
  // Iterate the members
45
45
  try {
46
- const members = {};
46
+ const members = new Set();
47
47
  for (const member of getStructMembers(types, struct)) {
48
48
  const memberName = member.name;
49
49
 
50
50
  // Duplicate member?
51
- if (!(memberName in members)) {
52
- members[memberName] = true;
51
+ if (!members.has(memberName)) {
52
+ members.add(memberName);
53
53
  } else {
54
54
  errors.push([typeName, memberName, `Redefinition of "${typeName}" member "${memberName}"`]);
55
55
  }
@@ -82,13 +82,13 @@ export function validateTypeModelErrors(types) {
82
82
 
83
83
  // Get the enumeration values
84
84
  try {
85
- const values = {};
85
+ const values = new Set();
86
86
  for (const value of getEnumValues(types, enum_)) {
87
87
  const valueName = value.name;
88
88
 
89
89
  // Duplicate value?
90
- if (!(valueName in values)) {
91
- values[valueName] = true;
90
+ if (!values.has(valueName)) {
91
+ values.add(valueName);
92
92
  } else {
93
93
  errors.push([typeName, valueName, `Redefinition of "${typeName}" value "${valueName}"`]);
94
94
  }
@@ -129,11 +129,11 @@ export function validateTypeModelErrors(types) {
129
129
  }
130
130
 
131
131
  // Compute effective input member counts
132
- const memberSections = {};
132
+ const memberSections = new Map();
133
133
  for (const section of ['path', 'query', 'input']) {
134
134
  if (section in action) {
135
135
  const sectionTypeName = action[section];
136
- if (sectionTypeName in types) {
136
+ if (Object.hasOwn(types, sectionTypeName)) {
137
137
  const sectionUserType = getEffectiveUserType(types, sectionTypeName);
138
138
  if (sectionUserType !== null && 'struct' in sectionUserType) {
139
139
  const sectionStruct = sectionUserType.struct;
@@ -142,10 +142,10 @@ export function validateTypeModelErrors(types) {
142
142
  try {
143
143
  for (const member of getStructMembers(types, sectionStruct)) {
144
144
  const memberName = member.name;
145
- if (!(memberName in memberSections)) {
146
- memberSections[memberName] = [];
145
+ if (!memberSections.has(memberName)) {
146
+ memberSections.set(memberName, []);
147
147
  }
148
- memberSections[memberName].push(sectionStruct.name);
148
+ memberSections.get(memberName).push(sectionStruct.name);
149
149
  }
150
150
  } catch {
151
151
  // Do nothing
@@ -156,7 +156,7 @@ export function validateTypeModelErrors(types) {
156
156
  }
157
157
 
158
158
  // Check for duplicate input members
159
- for (const [memberName, memberSectionNames] of Object.entries(memberSections)) {
159
+ for (const [memberName, memberSectionNames] of memberSections.entries()) {
160
160
  if (memberSectionNames.length > 1) {
161
161
  for (const sectionType of memberSectionNames.sort()) {
162
162
  errors.push([sectionType, memberName, `Redefinition of "${sectionType}" member "${memberName}"`]);
@@ -171,7 +171,7 @@ export function validateTypeModelErrors(types) {
171
171
 
172
172
 
173
173
  function getEffectiveType(types, type) {
174
- if ('user' in type && type.user in types) {
174
+ if ('user' in type && Object.hasOwn(types, type.user)) {
175
175
  const userType = types[type.user];
176
176
  if ('typedef' in userType) {
177
177
  return getEffectiveType(types, userType.typedef.type);
@@ -182,24 +182,24 @@ function getEffectiveType(types, type) {
182
182
 
183
183
 
184
184
  function getEffectiveUserType(types, userTypeName) {
185
- const userType = userTypeName in types ? types[userTypeName] : null;
185
+ const userType = Object.hasOwn(types, userTypeName) ? types[userTypeName] : null;
186
186
  if (userType !== null && 'typedef' in userType) {
187
187
  const typeEffective = getEffectiveType(types, userType.typedef.type);
188
188
  if (!('user' in typeEffective)) {
189
189
  return null;
190
190
  }
191
- return typeEffective.user in types ? types[typeEffective.user] : null;
191
+ return Object.hasOwn(types, typeEffective.user) ? types[typeEffective.user] : null;
192
192
  }
193
193
  return userType;
194
194
  }
195
195
 
196
196
 
197
- function getStructMembers(types, struct, visited = {}) {
197
+ function getStructMembers(types, struct, visited = new Set()) {
198
198
  return getTypeItems(types, struct, visited, 'struct', 'members');
199
199
  }
200
200
 
201
201
 
202
- function getEnumValues(types, enum_, visited = {}) {
202
+ function getEnumValues(types, enum_, visited = new Set()) {
203
203
  return getTypeItems(types, enum_, visited, 'enum', 'values');
204
204
  }
205
205
 
@@ -211,8 +211,8 @@ function getTypeItems(types, type, visited, defName, memberName) {
211
211
  const userType = getEffectiveUserType(types, base);
212
212
  if (userType !== null && defName in userType) {
213
213
  const userTypeName = userType[defName].name;
214
- if (!(userTypeName in visited)) {
215
- visited[userTypeName] = true;
214
+ if (!visited.has(userTypeName)) {
215
+ visited.add(userTypeName);
216
216
  items.push(...getTypeItems(types, userType[defName], visited, defName, memberName));
217
217
  } else {
218
218
  throw new TypeError();
@@ -285,7 +285,7 @@ function validateTypeModelType(errors, types, type, attr, typeName, memberName)
285
285
 
286
286
  // Valid dict key type (string or enum)
287
287
  if (!('builtin' in dictKeyType && dictKeyType.builtin === 'string') &&
288
- !('user' in dictKeyType && dictKeyType.user in types && 'enum' in types[dictKeyType.user])) {
288
+ !('user' in dictKeyType && Object.hasOwn(types, dictKeyType.user) && 'enum' in types[dictKeyType.user])) {
289
289
  error('Invalid dictionary key type');
290
290
  }
291
291
  }
@@ -295,7 +295,7 @@ function validateTypeModelType(errors, types, type, attr, typeName, memberName)
295
295
  const userTypeName = type.user;
296
296
 
297
297
  // Unknown user type?
298
- if (!(userTypeName in types)) {
298
+ if (!Object.hasOwn(types, userTypeName)) {
299
299
  error(`Unknown type "${userTypeName}"`);
300
300
  } else {
301
301
  const userType = types[userTypeName];
@@ -312,7 +312,7 @@ function validateTypeModelType(errors, types, type, attr, typeName, memberName)
312
312
  const typeEffective = getEffectiveType(types, type);
313
313
  const [typeKey] = Object.keys(typeEffective);
314
314
  const typeAttrKey = typeKey === 'builtin' ? typeEffective[typeKey] : typeKey;
315
- const allowedAttr = typeAttrKey in typeToAllowedAttr ? typeToAllowedAttr[typeAttrKey] : null;
315
+ const allowedAttr = Object.hasOwn(typeToAllowedAttr, typeAttrKey) ? typeToAllowedAttr[typeAttrKey] : null;
316
316
  const disallowedAttr = Object.fromEntries(Object.keys(attr).map((attrKey) => [attrKey, null]));
317
317
  delete disallowedAttr.nullable;
318
318
  if (allowedAttr !== null) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "schema-markdown",
4
- "version": "1.3.0",
4
+ "version": "1.3.1",
5
5
  "description": "A schema definition and validation library",
6
6
  "keywords": [
7
7
  "schema",