@sap/cds-compiler 3.5.2 → 3.6.0

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.
Files changed (85) hide show
  1. package/CHANGELOG.md +63 -1
  2. package/bin/cdsc.js +14 -6
  3. package/doc/CHANGELOG_ARCHIVE.md +10 -10
  4. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  5. package/lib/api/main.js +32 -55
  6. package/lib/api/options.js +1 -0
  7. package/lib/api/validate.js +5 -0
  8. package/lib/base/message-registry.js +104 -32
  9. package/lib/base/messages.js +277 -212
  10. package/lib/base/model.js +33 -22
  11. package/lib/base/optionProcessorHelper.js +9 -2
  12. package/lib/base/shuffle.js +50 -0
  13. package/lib/checks/actionsFunctions.js +37 -20
  14. package/lib/checks/foreignKeys.js +13 -6
  15. package/lib/checks/nonexpandableStructured.js +1 -2
  16. package/lib/checks/onConditions.js +21 -19
  17. package/lib/checks/parameters.js +1 -1
  18. package/lib/checks/queryNoDbArtifacts.js +2 -0
  19. package/lib/checks/types.js +16 -22
  20. package/lib/compiler/assert-consistency.js +31 -28
  21. package/lib/compiler/builtins.js +20 -4
  22. package/lib/compiler/checks.js +72 -63
  23. package/lib/compiler/define.js +396 -314
  24. package/lib/compiler/extend.js +55 -49
  25. package/lib/compiler/index.js +5 -0
  26. package/lib/compiler/populate.js +28 -11
  27. package/lib/compiler/propagator.js +2 -1
  28. package/lib/compiler/resolve.js +29 -20
  29. package/lib/compiler/shared.js +15 -10
  30. package/lib/compiler/utils.js +7 -7
  31. package/lib/edm/annotations/genericTranslation.js +51 -46
  32. package/lib/edm/annotations/preprocessAnnotations.js +39 -42
  33. package/lib/edm/csn2edm.js +69 -21
  34. package/lib/edm/edm.js +2 -2
  35. package/lib/edm/edmInboundChecks.js +6 -8
  36. package/lib/edm/edmPreprocessor.js +88 -80
  37. package/lib/edm/edmUtils.js +6 -15
  38. package/lib/gen/Dictionary.json +81 -13
  39. package/lib/gen/language.checksum +1 -1
  40. package/lib/gen/language.interp +2 -1
  41. package/lib/gen/languageParser.js +4680 -4484
  42. package/lib/inspect/inspectModelStatistics.js +2 -1
  43. package/lib/inspect/inspectPropagation.js +2 -1
  44. package/lib/json/from-csn.js +131 -78
  45. package/lib/json/to-csn.js +39 -23
  46. package/lib/language/antlrParser.js +0 -3
  47. package/lib/language/docCommentParser.js +7 -3
  48. package/lib/language/errorStrategy.js +3 -2
  49. package/lib/language/genericAntlrParser.js +96 -41
  50. package/lib/language/language.g4 +112 -128
  51. package/lib/language/multiLineStringParser.js +2 -1
  52. package/lib/main.d.ts +115 -2
  53. package/lib/main.js +16 -3
  54. package/lib/model/csnRefs.js +3 -3
  55. package/lib/model/csnUtils.js +109 -179
  56. package/lib/model/enrichCsn.js +13 -8
  57. package/lib/model/revealInternalProperties.js +4 -3
  58. package/lib/optionProcessor.js +23 -3
  59. package/lib/render/manageConstraints.js +11 -15
  60. package/lib/render/toCdl.js +144 -47
  61. package/lib/render/toHdbcds.js +22 -22
  62. package/lib/render/toRename.js +3 -4
  63. package/lib/render/toSql.js +29 -20
  64. package/lib/render/utils/delta.js +3 -1
  65. package/lib/render/utils/sql.js +3 -16
  66. package/lib/transform/db/associations.js +6 -6
  67. package/lib/transform/db/cdsPersistence.js +3 -3
  68. package/lib/transform/db/constraints.js +8 -8
  69. package/lib/transform/db/expansion.js +4 -4
  70. package/lib/transform/db/flattening.js +12 -15
  71. package/lib/transform/db/temporal.js +4 -3
  72. package/lib/transform/db/transformExists.js +2 -1
  73. package/lib/transform/draft/db.js +7 -7
  74. package/lib/transform/forOdataNew.js +15 -4
  75. package/lib/transform/forRelationalDB.js +53 -39
  76. package/lib/transform/odata/toFinalBaseType.js +106 -82
  77. package/lib/transform/odata/typesExposure.js +26 -17
  78. package/lib/transform/odata/utils.js +1 -1
  79. package/lib/transform/parseExpr.js +1 -1
  80. package/lib/transform/transformUtilsNew.js +33 -10
  81. package/lib/transform/translateAssocsToJoins.js +8 -7
  82. package/lib/transform/universalCsn/coreComputed.js +7 -5
  83. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -4
  84. package/lib/utils/timetrace.js +2 -2
  85. package/package.json +1 -2
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { forEach } = require('../utils/objectUtils');
4
4
  const { term } = require('../utils/term');
5
+ const { CompilerAssertion } = require('../base/error');
5
6
 
6
7
  /**
7
8
  * Return a string representation of the inspected results.
@@ -70,7 +71,7 @@ function countDefinitionKinds( xsn ) {
70
71
  else if (result[def.kind] !== undefined)
71
72
  ++result[def.kind];
72
73
  else
73
- throw new Error(`Unhandled kind: ${ def.kind } for ${ name }`);
74
+ throw new CompilerAssertion(`Unhandled kind: ${ def.kind } for ${ name }`);
74
75
  });
75
76
  return result;
76
77
  }
@@ -4,6 +4,7 @@ const { createMessageFunctions } = require('../base/messages');
4
4
  const { locationString } = require('../base/location');
5
5
  const { findArtifact, stringRefToPath } = require('./inspectUtils');
6
6
  const { term } = require('../utils/term');
7
+ const { CompilerAssertion } = require('../base/error');
7
8
 
8
9
  /**
9
10
  * @param {XSN.Model} xsn
@@ -97,7 +98,7 @@ function _inspectAnnotations( artifactXsn ) {
97
98
  }
98
99
  // fallthrough
99
100
  default:
100
- throw new Error(`inspect anno: Unhandled Case: ${ annoXsn.$priority }`);
101
+ throw new CompilerAssertion(`inspect anno: Unhandled Case: ${ annoXsn.$priority }`);
101
102
  }
102
103
 
103
104
  maxAnnoLength = Math.max(maxAnnoLength, anno.length);
@@ -33,7 +33,7 @@
33
33
  * dictionary key by default.
34
34
  * @property {string} [msgProp] Display name of the property. compileSchema() sets it to
35
35
  * the dictionary key (+ optional '[]') by default.
36
- * @property {string} [msgId] Use this message id instead of the default one.
36
+ * @property {string} [msgVariant] Use this message variant instead of the default one.
37
37
  * Allows more precise and detailed error messages.
38
38
  * @property {string|string[]|false} [requires] If the value is a string, then the given sub-
39
39
  * property is required. If 'undefined', then at
@@ -63,7 +63,7 @@
63
63
  * @property {boolean} [inValue] Puts the value into an XSN property "value",
64
64
  * e.g. { value: ... }
65
65
  * @property {string} [xorGroup] Corresponding xor group. It references a value of
66
- * $xorGroups. If set then only one property of of the
66
+ * xorGroups. If set then only one property of of the
67
67
  * xorGroup may be set, e.g. if target is set, elements
68
68
  * may not.
69
69
  * @property {string} [xsnOp] Defines the operator to be used for XSN. Used for SET
@@ -89,6 +89,7 @@
89
89
 
90
90
  const { dictAdd } = require('../base/dictionaries');
91
91
  const { quotedLiteralPatterns } = require('../compiler/builtins');
92
+ const { CompilerAssertion } = require('../base/error');
92
93
 
93
94
  const $location = Symbol.for('cds.$location');
94
95
 
@@ -97,7 +98,7 @@ let inExtensions = null;
97
98
  let vocabInDefinitions = null; // must be reset!
98
99
 
99
100
  // CSN property names reserved for CAP
100
- const ourpropsRegex = /^[_$]?[a-zA-Z]+[0-9]*$/;
101
+ const ourpropsRegex = /^(?:[_$=#@][a-zA-Z]*[0-9]*|[a-zA-Z]+[0-9]*)$/;
101
102
 
102
103
  // Sync with definition in to-csn.js:
103
104
  const typeProperties = [
@@ -120,7 +121,7 @@ const xorGroups = {
120
121
  ':expr': [ // see also xorException property in schema
121
122
  'ref', 'xpr', 'list', 'val', '#', 'func', 'SELECT', 'SET', 'expand',
122
123
  '=', 'path', 'value', 'op', // '='/'path' is CSN v0.1.0 here
123
- ],
124
+ ], // also set xorGroupTwo ':col' for 'expand' and 'inline'
124
125
  ':ext': [ 'annotate', 'extend' ], // TODO: better msg for test/negative/UnexpectedProperties.csn
125
126
  ':assoc': [ 'on', 'keys', 'foreignKeys', 'onCond' ], // 'foreignKeys'/'onCond' is CSN v0.1.0
126
127
  // TODO - improve consequential errors: assume no name given with `join` or `inline`?
@@ -138,7 +139,7 @@ const schemaClasses = {
138
139
  condition: {
139
140
  arrayOf: exprOrString,
140
141
  type: condition,
141
- msgId: 'syntax-expecting-term',
142
+ msgVariant: 'or-string', // for 'syntax-expecting-object'
142
143
  // TODO: also specify requires here, and adapt onlyWith()
143
144
  optional: exprProperties,
144
145
  },
@@ -148,11 +149,11 @@ const schemaClasses = {
148
149
  },
149
150
  natnumOrStar: {
150
151
  type: natnumOrStar,
151
- msgId: 'syntax-expecting-cardinality',
152
+ msgVariant: 'or-asterisk', // for 'syntax-expecting-unsigned-int'
152
153
  },
153
154
  columns: {
154
155
  arrayOf: selectItem,
155
- msgId: 'syntax-expecting-column',
156
+ msgVariant: 'or-asterisk', // for 'syntax-expecting-object'
156
157
  defaultKind: '$column',
157
158
  validKinds: [], // pseudo kind '$column'
158
159
  // A column with only as+cast.type is a new association
@@ -251,10 +252,12 @@ const schema = compileSchema( {
251
252
  expand: {
252
253
  class: 'columns',
253
254
  xorException: 'ref', // see xorGroup :expr
255
+ xorGroupTwo: ':col',
254
256
  inKind: [ '$column' ], // only valid in $column
255
257
  },
256
258
  inline: {
257
259
  class: 'columns',
260
+ xorGroupTwo: ':col',
258
261
  onlyWith: 'ref',
259
262
  inKind: [ '$column' ], // only valid in $column
260
263
  },
@@ -296,20 +299,20 @@ const schema = compileSchema( {
296
299
  // type properties (except: elements, enum, keys, on): ---------------------
297
300
  type: {
298
301
  type: artifactRef,
299
- msgId: 'syntax-expecting-reference',
302
+ msgVariant: 'or-object', // for 'syntax-expecting-string',
300
303
  optional: [ 'ref', 'global' ],
301
304
  inKind: [ 'element', 'type', 'param', 'mixin', 'event', 'annotation', 'extend' ],
302
305
  },
303
306
  targetAspect: {
304
307
  type: artifactRef,
305
- msgId: 'syntax-expecting-reference',
308
+ msgVariant: 'or-object', // for 'syntax-expecting-string',
306
309
  requires: 'elements',
307
310
  optional: [ 'elements' ], // 'elements' for ad-hoc aspect compositions
308
311
  inKind: [ 'element', 'type' ],
309
312
  },
310
313
  target: {
311
314
  type: artifactRef,
312
- msgId: 'syntax-expecting-reference',
315
+ msgVariant: 'or-object', // for 'syntax-expecting-string',
313
316
  requires: 'elements',
314
317
  optional: [ 'elements' ], // 'elements' for ad-hoc COMPOSITION OF (gensrc style CSN)
315
318
  inKind: [ 'element', 'type', 'mixin', 'param' ],
@@ -376,7 +379,7 @@ const schema = compileSchema( {
376
379
  ref: {
377
380
  arrayOf: refItem,
378
381
  type: renameTo( 'path', arrayOf( refItem ) ),
379
- msgId: 'syntax-expecting-reference',
382
+ msgVariant: 'or-object', // for 'syntax-expecting-string',
380
383
  minLength: 1,
381
384
  requires: 'id',
382
385
  optional: [ 'id', 'args', 'cardinality', 'where' ],
@@ -424,20 +427,21 @@ const schema = compileSchema( {
424
427
  },
425
428
  val: {
426
429
  type: value,
427
- inKind: [ '$column', 'enum', 'element' ],
430
+ inKind: [ '$column', 'enum' ],
431
+ // see also extra handling for 'element' in extension, see definition()
428
432
  },
429
433
  literal: {
430
434
  type: literal,
431
435
  onlyWith: 'val',
432
- inKind: [ '$column', 'enum' ],
436
+ inKind: [ '$column', 'enum' ], // 'element' sometimes in extension
433
437
  },
434
438
  '#': {
435
439
  noPrefix: true, // schema spec for '#', not for '#whatever'
436
440
  type: symbol,
437
441
  // Note: We emit a warning if '#' is used in enums. Because the compiler
438
442
  // can generate CSN like this, we need to be able to parse it.
439
- // Also, in "extensions", an entity's element of type enum has kind "element".
440
- inKind: [ '$column', 'enum', 'element' ],
443
+ inKind: [ '$column', 'enum' ],
444
+ // see also extra handling for 'element' in extension, see definition()
441
445
  },
442
446
  path: { // in CSN v0.1.0 'foreignKeys'
443
447
  vZeroFor: 'ref',
@@ -446,6 +450,7 @@ const schema = compileSchema( {
446
450
  type: vZeroRef,
447
451
  },
448
452
  '=': { // v0.1.0 { "=": "A.B" } for v1.0 { "ref": ["A", "B"] }
453
+ noPrefix: true, // schema spec for '=', not for '=whatever'
449
454
  vZeroFor: 'ref',
450
455
  inKind: [], // still used in annotation assignments...
451
456
  type: vZeroRef, // ...see property '@' / function annotation()
@@ -579,10 +584,12 @@ const schema = compileSchema( {
579
584
  // miscellaneous properties in definitions: --------------------------------
580
585
  doc: {
581
586
  type: stringValOrNull,
587
+ msgVariant: 'or-null', // for 'syntax-expecting-string'
582
588
  inKind: () => true, // allowed in all definitions (including columns and extensions)
583
589
  },
584
590
  '@': { // for all properties starting with '@'
585
- prop: '@<anno>', // which property name do messages use for annotation assignments?
591
+ noPrefix: false, // just '@' is no CSN property
592
+ prop: '@‹anno›', // which property name do messages use for annotation assignments?
586
593
  type: annotation,
587
594
  inKind: () => true, // allowed in all definitions (including columns and extensions)
588
595
  },
@@ -646,9 +653,10 @@ const schema = compileSchema( {
646
653
  type: ignore,
647
654
  },
648
655
  value: {
649
- vZeroFor: 'val', // CSN v0.1.0 property for `val` in enum def
650
- type: annoValue,
651
- // inKind: [ 'enum' ],
656
+ class: 'expression', // calculated elements
657
+ vZeroFor: 'val', // CSN v0.1.0 property for `val` in enum def
658
+ // type: annoValue,
659
+ inKind: [ 'element' ],
652
660
  },
653
661
  // ignored: ----------------------------------------------------------------
654
662
  $location: { // special
@@ -679,8 +687,16 @@ const schema = compileSchema( {
679
687
  $generated: {
680
688
  type: string,
681
689
  },
682
- $: { type: ignore, ignore: true }, // including $origin
683
- _: { type: ignore, ignore: true },
690
+ $: {
691
+ noPrefix: false, // just '$' is no CSN property
692
+ type: ignore,
693
+ ignore: true,
694
+ }, // including $origin
695
+ _: {
696
+ noPrefix: false, // just '_' is no CSN property
697
+ type: ignore,
698
+ ignore: true,
699
+ },
684
700
  } );
685
701
 
686
702
  const topLevelSpec = {
@@ -745,7 +761,7 @@ function compileSchema( specs, proto = null ) {
745
761
  else if (s.dictionaryOf)
746
762
  s.type = dictionaryOf( s.dictionaryOf );
747
763
  else
748
- throw new Error( `Missing type specification for property "${ p }"` );
764
+ throw new CompilerAssertion( `Missing type specification for property "${ p }"` );
749
765
  }
750
766
  }
751
767
  // Set property 'xorGroup' in main and sub schema:
@@ -814,6 +830,7 @@ function extra( node, spec, xsn ) {
814
830
  if (!xsn.$extra)
815
831
  xsn.$extra = Object.create(null);
816
832
  xsn.$extra[spec.prop] = node;
833
+ return ignore( node );
817
834
  }
818
835
 
819
836
  function eventualCast( obj, spec, xsn ) {
@@ -887,13 +904,22 @@ function definition( def, spec, xsn, csn, name ) {
887
904
  }
888
905
  pushLocation( def );
889
906
  const savedInExtensions = inExtensions;
890
- const kind = calculateKind( def, spec ); // might set inExtensions
907
+ let kind = calculateKind( def, spec ); // might set inExtensions
891
908
  const r = (kind === '$column') ? { location: location() } : { location: location(), kind };
892
909
  const xor = {};
893
910
  const { prop } = spec;
894
911
  const kind0 = (spec.validKinds.length || spec.prop === 'extensions') && kind;
895
912
  const csnProps = Object.keys( def );
896
913
 
914
+ // If we have `extend … with { name = 3 }`, it could mean adding an element or an enum.
915
+ // We use `elements: { name: { val: 3 } }` to represent this ambiguity.
916
+ if (savedInExtensions === '' && prop === 'elements' && !def.value &&
917
+ (def.val !== undefined || def['#'] !== undefined) &&
918
+ def.virtual === undefined && def.key === undefined && def.masked === undefined &&
919
+ def.type === undefined && def.elements === undefined) {
920
+ r.$syntax = 'enum';
921
+ kind = 'enum'; // for function expected()
922
+ }
897
923
  if (csnProps.length) {
898
924
  const valueName = (prop === 'keys' || prop === 'foreignKeys' ? 'targetElement' : 'value');
899
925
  // the next is basically object() + the inValue handling
@@ -964,7 +990,7 @@ function dictionaryOf( elementFct ) {
964
990
  for (const name of allNames) {
965
991
  if (!name) {
966
992
  message( 'syntax-invalid-name', location(true),
967
- { '#': 'csn', parentprop: spec.prop } );
993
+ { '#': 'dict', parentprop: spec.prop } );
968
994
  }
969
995
  const val = elementFct( dict[name], spec, r, dict, name );
970
996
  if (val !== undefined)
@@ -992,6 +1018,7 @@ function keys( array, spec, xsn ) {
992
1018
  xsn.foreignKeys = r;
993
1019
  }
994
1020
 
1021
+ // Use with spec.msgVariant: 'or-asterisk'
995
1022
  function selectItem( def, spec, xsn, csn ) {
996
1023
  if (def === '*') // compile() will complain about repeated '*'s
997
1024
  return { val: '*', location: location() };
@@ -1083,9 +1110,14 @@ function validKind( val, spec, xsn ) {
1083
1110
  return ignore( val );
1084
1111
  }
1085
1112
 
1113
+ // Use with spec.msgVariant: 'or-object'
1086
1114
  function artifactRef( ref, spec ) {
1087
- if (!ref || typeof ref !== 'string')
1088
- return object( ref, spec );
1115
+ if (!ref || typeof ref !== 'string') {
1116
+ if (ref && typeof ref === 'object' && !Array.isArray( ref ))
1117
+ return object( ref, spec );
1118
+ // use error message 'syntax-expecting-string' (string more likely than object):
1119
+ return string( ref, spec );
1120
+ }
1089
1121
  if (spec.prop !== 'type')
1090
1122
  return stringRef( ref, spec );
1091
1123
  // now the CSN v0.1.0 type of: 'Artifact..e1.e2'; error if not csnVersionZero
@@ -1096,7 +1128,7 @@ function artifactRef( ref, spec ) {
1096
1128
  warning( 'syntax-deprecated-value', location(true),
1097
1129
  { '#': 'zero-replace', prop: spec.msgProp, value: '{ ref: […] }' } );
1098
1130
  }
1099
- const r = refSplit( ref.substring( idx + 2 ), spec.msgProp );
1131
+ const r = refSplit( ref.substring( idx + 2 ), 'type' );
1100
1132
  r.path.unshift( { id: ref.substring( 0, idx ), location: location() } );
1101
1133
  return r;
1102
1134
  }
@@ -1106,10 +1138,14 @@ function stringRef( ref, spec ) {
1106
1138
  { path: [ { id: ref, location: location() } ], location: location() };
1107
1139
  }
1108
1140
 
1141
+ // with spec.msgVariant: 'or-object'
1109
1142
  function refItem( item, spec ) {
1110
1143
  if (typeof item === 'string' && item)
1111
1144
  return { id: item, location: location() };
1112
- return object( item, spec );
1145
+ if (item && typeof item === 'object' && !Array.isArray( item ))
1146
+ return object( item, spec );
1147
+ // use error message 'syntax-expecting-string' (string more likely than object):
1148
+ return string( item, spec );
1113
1149
  }
1114
1150
 
1115
1151
  function asScope( scope, spec, xsn ) {
@@ -1143,8 +1179,8 @@ function boolOrNull( val, spec ) {
1143
1179
  function string( val, spec ) {
1144
1180
  if (typeof val === 'string' && val)
1145
1181
  return val;
1146
- error( 'syntax-expecting-string', location(true), { prop: spec.msgProp },
1147
- 'Expecting non-empty string for property $(PROP)' );
1182
+ error( 'syntax-expecting-string', location(true),
1183
+ { '#': spec.msgVariant, prop: spec.msgProp } );
1148
1184
  return ignore( val );
1149
1185
  }
1150
1186
 
@@ -1175,14 +1211,12 @@ function natnum( val, spec ) {
1175
1211
  // XSN TODO: do not require literal
1176
1212
  return { val, literal: 'number', location: location() };
1177
1213
  const loc = location(true);
1178
- if (spec.msgId)
1179
- error( spec.msgId, loc, { prop: spec.msgProp } );
1180
- else
1181
- error( 'syntax-expecting-unsigned-int', loc, { '#': 'csn', prop: spec.msgProp } );
1214
+ error( 'syntax-expecting-unsigned-int', loc,
1215
+ { '#': spec.msgVariant || 'csn', prop: spec.msgProp, op: '*' } );
1182
1216
  return ignore( val );
1183
1217
  }
1184
1218
 
1185
- // Use with spec.msgId !
1219
+ // Use with spec.msgVariant !
1186
1220
  function natnumOrStar( val, spec ) {
1187
1221
  return (val === '*')
1188
1222
  ? { val, literal: 'string', location: location() }
@@ -1196,7 +1230,14 @@ function symbol( id, spec, xsn ) { // for CSN property '#'
1196
1230
  xsn.sym = { id, location: location() };
1197
1231
  }
1198
1232
 
1199
- // returns: false = no "...", true = "..." without UP TO, 'upTo' = "..." with UP TO
1233
+ /**
1234
+ * returns:
1235
+ * - false = no "...",
1236
+ * - true = "..." without UP TO,
1237
+ * - 'upTo' = "..." with UP TO
1238
+ *
1239
+ * @returns {string|boolean}
1240
+ */
1200
1241
  function isEllipsis( val ) {
1201
1242
  return val && typeof val === 'object' && '...' in val && Object.keys(val).length === 1 &&
1202
1243
  (val['...'] === true || 'upTo');
@@ -1244,12 +1285,11 @@ function annoValue( val, spec ) {
1244
1285
  }
1245
1286
  if (typeof val['#'] === 'string') {
1246
1287
  if (Object.keys( val ).length === 1) {
1247
- virtualLine += 2;
1248
- return {
1249
- sym: { id: val['#'], location: location() },
1250
- literal: 'enum',
1251
- location: location(),
1252
- };
1288
+ ++virtualLine;
1289
+ const xsn = { location: location() };
1290
+ symbol( val['#'], schema['#'], xsn );
1291
+ ++virtualLine;
1292
+ return xsn;
1253
1293
  }
1254
1294
  }
1255
1295
  else if (typeof val['='] === 'string') {
@@ -1287,7 +1327,7 @@ function annotation( val, spec, xsn, csn, name ) {
1287
1327
  const absolute = (xsn ? name.substring(1) : name);
1288
1328
  // TODO: really care about variant (qualifier parts)?
1289
1329
  const variantIndex = absolute.indexOf('#') + 1 || absolute.length;
1290
- const n = refSplit( absolute.substring( 0, variantIndex ), spec.msgProp );
1330
+ const n = refSplit( absolute.substring( 0, variantIndex ), !xsn && '{}' );
1291
1331
  if (!n)
1292
1332
  return undefined;
1293
1333
  n.absolute = absolute;
@@ -1300,35 +1340,50 @@ function annotation( val, spec, xsn, csn, name ) {
1300
1340
 
1301
1341
  // Expressions, conditions (std signature) -----------------------------------
1302
1342
 
1303
- function value( val, spec, xsn ) { // for CSN property 'val'
1304
- if ((val == null) ? val === null : typeof val !== 'object') {
1305
- if (!xsn.literal) // might be overwritten; only set if literal type is valid
1306
- xsn.literal = (val === null) ? 'null' : typeof val;
1307
- return val;
1343
+ function value( val, spec, xsn, csn ) { // for CSN property 'val'
1344
+ if (val && typeof val === 'object') {
1345
+ error( 'syntax-expecting-scalar', location(true), { prop: spec.msgProp },
1346
+ 'Expecting scalar values for property $(PROP)' );
1347
+ return ignore( val );
1308
1348
  }
1309
- error( 'syntax-expecting-scalar', location(true), { prop: spec.msgProp },
1310
- 'Only scalar values are supported for property $(PROP)' );
1311
- return ignore( val );
1349
+ if (!xsn.literal) // might be overwritten; only set if literal type is valid
1350
+ xsn.literal = (val === null) ? 'null' : typeof val;
1351
+
1352
+ const valType = (val == null) ? val === null && 'null' : typeof val;
1353
+ const pattern = typeof csn.literal === 'string' && quotedLiteralPatterns[csn.literal];
1354
+ if (pattern && valType &&
1355
+ (valType === pattern.json_type || valType === pattern.secondary_json_type)) {
1356
+ if (pattern.test_fn && !pattern.test_fn( val )) {
1357
+ warning( 'syntax-invalid-literal', location(),
1358
+ { '#': pattern.test_variant, prop: spec.msgProp } );
1359
+ }
1360
+ if (pattern.unexpected_char && pattern.unexpected_char.test( val ))
1361
+ warning( 'syntax-invalid-literal', location(), { '#': pattern.unexpected_variant } );
1362
+ }
1363
+ return val;
1312
1364
  }
1313
1365
 
1314
1366
  function literal( lit, spec, xsn, csn ) {
1315
- // TODO: general: requires other property (here: 'val')
1316
- if (!string( lit ))
1367
+ if (!string( lit, spec ))
1317
1368
  return undefined;
1318
- const type = (csn.val == null) ? 'null' : typeof csn.val;
1319
- if (lit === type) // also for 'object' which is an error for 'val'
1320
- return lit;
1321
- const p = quotedLiteralPatterns[lit];
1322
- if (p?.json_type === type) {
1323
- // TODO: wrong position, we complain about the format here, that is in 'val',
1324
- // TODO: make it then also more CSN-specific? Next change
1325
- if (p && p.test_fn && !p.test_fn(csn.val))
1326
- warning( 'syntax-invalid-literal', location(), { '#': p.test_variant } );
1327
- return lit;
1369
+ const valType = (csn.val == null) ? csn.val === null && 'null' : typeof csn.val;
1370
+ const pattern = quotedLiteralPatterns[lit];
1371
+ if (!pattern) {
1372
+ error( 'syntax-invalid-string', location(true), { prop: spec.msgProp } );
1373
+ }
1374
+ else if (valType &&
1375
+ valType !== pattern.json_type && valType !== pattern.secondary_json_type) {
1376
+ warning( 'syntax-invalid-literal', location(), {
1377
+ // 'literal' value can be different to 'string' with JSON string type:
1378
+ '#': (valType === 'string') ? 'typeof' : 'expecting',
1379
+ otherprop: 'val',
1380
+ rawvalue: lit,
1381
+ op: valType,
1382
+ } );
1328
1383
  }
1329
- if (lit === 'number' && type === 'string') // special case, not a quoted literal in CDL
1384
+ else {
1330
1385
  return lit;
1331
- error( 'syntax-invalid-string', location(true), { prop: spec.msgProp } );
1386
+ }
1332
1387
  return ignore( lit );
1333
1388
  }
1334
1389
 
@@ -1430,6 +1485,7 @@ function expr( e, spec ) {
1430
1485
  return object( e, spec );
1431
1486
  }
1432
1487
 
1488
+ // with spec.msgVariant: 'or-string'
1433
1489
  function exprOrString( val, spec ) {
1434
1490
  return (typeof val === 'string' && !csnVersionZero)
1435
1491
  ? { val, literal: 'token', location: location() }
@@ -1555,8 +1611,8 @@ function translations( keyVal, spec, xsn, csn, textKey ) {
1555
1611
  function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
1556
1612
  const p0 = schema[prop] ? prop : prop.charAt(0);
1557
1613
  const s = (parentSpec.schema || schema)[p0];
1558
- if (!s || s.noPrefix && prop !== p0 ) {
1559
- if (!ourpropsRegex.test( prop ))
1614
+ if (!s || s.noPrefix === (prop !== p0) ) {
1615
+ if (prop && !ourpropsRegex.test( prop ))
1560
1616
  return { prop, type: extra };
1561
1617
  // TODO v4: No warning with --sloppy
1562
1618
  warning( 'syntax-unknown-property', location(true), { prop },
@@ -1594,7 +1650,8 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
1594
1650
  kind,
1595
1651
  } );
1596
1652
  }
1597
- else if (checkAndSetXorGroup( s.xorGroup, s.xorException, prop, xor )) {
1653
+ else if (checkAndSetXorGroup( s.xorGroup, s.xorException, prop, xor ) &&
1654
+ checkAndSetXorGroup( s.xorGroupTwo, s.xorException, prop, xor ) ) {
1598
1655
  onlyWith( s, s.onlyWith, csn, prop, xor, expected );
1599
1656
  return s;
1600
1657
  }
@@ -1697,26 +1754,22 @@ function isObject( obj, spec ) {
1697
1754
  if (obj && typeof obj === 'object' && !Array.isArray( obj ))
1698
1755
  return obj;
1699
1756
  const loc = location(true);
1700
- if (spec.msgId)
1701
- error( spec.msgId, loc, { prop: spec.msgProp });
1702
- else
1703
- error( 'syntax-expecting-object', loc, { prop: spec.msgProp });
1757
+ error( 'syntax-expecting-object', loc,
1758
+ { '#': spec.msgVariant || 'std', prop: spec.msgProp, op: '*' });
1704
1759
  return ignore( obj );
1705
1760
  }
1706
1761
 
1707
1762
  function refSplit( name, prop ) {
1708
1763
  const path = name.split('.');
1709
- if (!path.every( id => id)) {
1710
- warning( 'syntax-expecting-name', location(true), { prop }, // TODO: re-check
1711
- 'Expecting correct name for property $(PROP)' );
1712
- }
1764
+ if (prop && (prop === '{}' || prop === '#' ? !name : !path.every( id => id)))
1765
+ message( 'syntax-invalid-name', location(true), { '#': prop, prop } );
1713
1766
  return { path: path.map( id => ({ id, location: location() }) ), location: location() };
1714
1767
  }
1715
1768
 
1716
- function replaceZeroValue( spec, msgid, otherprop ) {
1769
+ function replaceZeroValue( spec, msgVariant, otherprop ) {
1717
1770
  if (!csnVersionZero && !spec.vZeroFor) {
1718
1771
  warning( 'syntax-deprecated-value', location(true),
1719
- { '#': msgid, prop: spec.msgProp, otherprop } );
1772
+ { '#': msgVariant, prop: spec.msgProp, otherprop } );
1720
1773
  }
1721
1774
  }
1722
1775