@sap/cds-compiler 3.1.2 → 3.4.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 (117) hide show
  1. package/CHANGELOG.md +101 -3
  2. package/bin/cdsc.js +4 -2
  3. package/doc/CHANGELOG_BETA.md +35 -0
  4. package/lib/api/main.js +153 -29
  5. package/lib/api/validate.js +8 -3
  6. package/lib/base/dictionaries.js +6 -6
  7. package/lib/base/error.js +2 -2
  8. package/lib/base/keywords.js +106 -24
  9. package/lib/base/message-registry.js +177 -79
  10. package/lib/base/messages.js +78 -57
  11. package/lib/base/model.js +2 -1
  12. package/lib/checks/actionsFunctions.js +1 -1
  13. package/lib/checks/annotationsOData.js +2 -2
  14. package/lib/checks/arrayOfs.js +15 -7
  15. package/lib/checks/cdsPersistence.js +1 -1
  16. package/lib/checks/checkForTypes.js +53 -0
  17. package/lib/checks/defaultValues.js +4 -2
  18. package/lib/checks/elements.js +81 -6
  19. package/lib/checks/foreignKeys.js +12 -13
  20. package/lib/checks/invalidTarget.js +10 -11
  21. package/lib/checks/managedInType.js +21 -15
  22. package/lib/checks/nullableKeys.js +1 -1
  23. package/lib/checks/onConditions.js +9 -9
  24. package/lib/checks/parameters.js +23 -0
  25. package/lib/checks/queryNoDbArtifacts.js +1 -1
  26. package/lib/checks/selectItems.js +1 -1
  27. package/lib/checks/sql-snippets.js +12 -10
  28. package/lib/checks/types.js +2 -2
  29. package/lib/checks/utils.js +17 -7
  30. package/lib/checks/validator.js +36 -14
  31. package/lib/compiler/assert-consistency.js +21 -13
  32. package/lib/compiler/builtins.js +8 -0
  33. package/lib/compiler/checks.js +57 -40
  34. package/lib/compiler/define.js +139 -69
  35. package/lib/compiler/extend.js +319 -50
  36. package/lib/compiler/finalize-parse-cdl.js +14 -9
  37. package/lib/compiler/kick-start.js +2 -35
  38. package/lib/compiler/populate.js +111 -68
  39. package/lib/compiler/propagator.js +5 -3
  40. package/lib/compiler/resolve.js +71 -108
  41. package/lib/compiler/shared.js +82 -54
  42. package/lib/compiler/tweak-assocs.js +26 -14
  43. package/lib/compiler/utils.js +13 -2
  44. package/lib/edm/annotations/genericTranslation.js +10 -7
  45. package/lib/edm/csn2edm.js +11 -11
  46. package/lib/edm/edm.js +17 -9
  47. package/lib/edm/edmPreprocessor.js +53 -30
  48. package/lib/edm/edmUtils.js +7 -2
  49. package/lib/gen/Dictionary.json +14 -0
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -2
  52. package/lib/gen/languageParser.js +4312 -4186
  53. package/lib/inspect/inspectModelStatistics.js +1 -1
  54. package/lib/inspect/inspectPropagation.js +23 -9
  55. package/lib/json/csnVersion.js +13 -13
  56. package/lib/json/from-csn.js +161 -172
  57. package/lib/json/to-csn.js +70 -10
  58. package/lib/language/.eslintrc.json +4 -0
  59. package/lib/language/antlrParser.js +8 -11
  60. package/lib/language/docCommentParser.js +1 -2
  61. package/lib/language/errorStrategy.js +54 -27
  62. package/lib/language/genericAntlrParser.js +140 -93
  63. package/lib/language/language.g4 +57 -33
  64. package/lib/language/multiLineStringParser.js +75 -63
  65. package/lib/main.d.ts +3 -6
  66. package/lib/main.js +1 -0
  67. package/lib/model/.eslintrc.json +13 -0
  68. package/lib/model/api.js +4 -2
  69. package/lib/model/csnRefs.js +78 -50
  70. package/lib/model/csnUtils.js +272 -222
  71. package/lib/model/enrichCsn.js +41 -31
  72. package/lib/model/revealInternalProperties.js +61 -57
  73. package/lib/model/sortViews.js +35 -31
  74. package/lib/modelCompare/compare.js +52 -18
  75. package/lib/modelCompare/filter.js +83 -0
  76. package/lib/optionProcessor.js +10 -1
  77. package/lib/render/manageConstraints.js +11 -7
  78. package/lib/render/toCdl.js +151 -106
  79. package/lib/render/toHdbcds.js +8 -6
  80. package/lib/render/toRename.js +4 -4
  81. package/lib/render/toSql.js +17 -7
  82. package/lib/render/utils/common.js +27 -9
  83. package/lib/render/utils/sql.js +5 -5
  84. package/lib/sql-identifier.js +7 -0
  85. package/lib/transform/db/applyTransformations.js +32 -3
  86. package/lib/transform/db/assertUnique.js +27 -38
  87. package/lib/transform/db/expansion.js +92 -41
  88. package/lib/transform/db/flattening.js +1 -1
  89. package/lib/transform/db/temporal.js +3 -1
  90. package/lib/transform/db/transformExists.js +8 -2
  91. package/lib/transform/db/views.js +42 -13
  92. package/lib/transform/draft/db.js +2 -2
  93. package/lib/transform/forOdataNew.js +10 -7
  94. package/lib/transform/{forHanaNew.js → forRelationalDB.js} +18 -12
  95. package/lib/transform/localized.js +29 -20
  96. package/lib/transform/odata/toFinalBaseType.js +8 -11
  97. package/lib/transform/odata/typesExposure.js +2 -1
  98. package/lib/transform/parseExpr.js +245 -0
  99. package/lib/transform/transformUtilsNew.js +122 -51
  100. package/lib/transform/translateAssocsToJoins.js +17 -16
  101. package/lib/utils/moduleResolve.js +5 -5
  102. package/lib/utils/objectUtils.js +3 -3
  103. package/lib/utils/term.js +5 -5
  104. package/package.json +2 -2
  105. package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
  106. package/share/messages/check-proper-type-of.md +4 -4
  107. package/share/messages/check-proper-type.md +2 -2
  108. package/share/messages/duplicate-autoexposed.md +4 -4
  109. package/share/messages/extend-repeated-intralayer.md +4 -5
  110. package/share/messages/extend-unrelated-layer.md +4 -4
  111. package/share/messages/message-explanations.json +3 -1
  112. package/share/messages/redirected-to-ambiguous.md +7 -6
  113. package/share/messages/redirected-to-complex.md +63 -0
  114. package/share/messages/redirected-to-unrelated.md +6 -5
  115. package/share/messages/rewrite-not-supported.md +4 -4
  116. package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +4 -4
  117. package/share/messages/wildcard-excluding-one.md +37 -0
@@ -68,10 +68,8 @@
68
68
  * may not.
69
69
  * @property {string} [xsnOp] Defines the operator to be used for XSN. Used for SET
70
70
  * and SELECT. See queryTerm().
71
- * @property {string|false} [vZeroFor] Marks the property as a CSN 0.1.0 property. It is
71
+ * @property {string} [vZeroFor] Marks the property as a CSN 0.1.0 property. It is
72
72
  * replaced by this CSN 1.0 property (value of vZeroFor).
73
- * "false" indicates that the property may be a v0.1 one
74
- * which is handled specially, e.g. with "type:vZeroValue"
75
73
  * @property {string} [vZeroIgnore] Marks the property as a CSN 0.1.0 property. The
76
74
  * property is ignored and a warning may be issues about
77
75
  * it.
@@ -117,13 +115,14 @@ const exprProperties = [
117
115
  const xorGroups = {
118
116
  // include CSN v0.1.0 properties here:
119
117
  ':type': [ 'target', 'elements', 'enum', 'items' ],
120
- ':expr': [
121
- 'ref', 'xpr', 'list', 'val', '#', 'func', 'SELECT', 'SET',
118
+ ':expr': [ // see also xorException property in schema
119
+ 'ref', 'xpr', 'list', 'val', '#', 'func', 'SELECT', 'SET', 'expand',
122
120
  '=', 'path', 'value', 'op', // '='/'path' is CSN v0.1.0 here
123
121
  ],
124
122
  ':ext': [ 'annotate', 'extend' ], // TODO: better msg for test/negative/UnexpectedProperties.csn
125
123
  ':assoc': [ 'on', 'keys', 'foreignKeys', 'onCond' ], // 'foreignKeys'/'onCond' is CSN v0.1.0
126
- ':join': [ 'join', 'as' ],
124
+ // TODO - improve consequential errors: assume no name given with `join` or `inline`?
125
+ as: [ 'as', 'join', 'inline' ],
127
126
  scope: [ 'param', 'global' ],
128
127
  quantifier: [ 'some', 'any', 'distinct', 'all' ],
129
128
  // quantifiers 'some' and 'any are 'xpr' token strings in CSN v1.0
@@ -137,7 +136,7 @@ const schemaClasses = {
137
136
  condition: {
138
137
  arrayOf: exprOrString,
139
138
  type: condition,
140
- msgId: 'syntax-expected-term',
139
+ msgId: 'syntax-expecting-term',
141
140
  // TODO: also specify requires here, and adapt onlyWith()
142
141
  optional: exprProperties,
143
142
  },
@@ -147,19 +146,20 @@ const schemaClasses = {
147
146
  },
148
147
  natnumOrStar: {
149
148
  type: natnumOrStar,
150
- msgId: 'syntax-expected-cardinality',
149
+ msgId: 'syntax-expecting-cardinality',
151
150
  },
152
151
  columns: {
153
152
  arrayOf: selectItem,
154
- msgId: 'syntax-expected-column',
153
+ msgId: 'syntax-expecting-column',
155
154
  defaultKind: '$column',
156
155
  validKinds: [], // pseudo kind '$column'
157
156
  // A column with only as+cast.type is a new association
158
- requires: [ 'ref', 'as', 'xpr', 'val', '#', 'func', 'list', 'SELECT', 'SET', 'expand' ],
157
+ requires: [ 'ref', 'cast', 'xpr', 'val', '#', 'func', 'list', 'SELECT', 'SET', 'expand' ],
159
158
  schema: {
160
159
  xpr: {
161
160
  class: 'condition',
162
161
  type: xprInValue,
162
+ xorException: 'func', // see xorGroup :expr
163
163
  inKind: [ '$column' ],
164
164
  inValue: true,
165
165
  },
@@ -248,10 +248,12 @@ const schema = compileSchema( {
248
248
  },
249
249
  expand: {
250
250
  class: 'columns',
251
- inKind: [ '$column' ], // only valid in $column
251
+ xorException: 'ref', // see xorGroup :expr
252
+ inKind: [ '$column' ], // only valid in $column
252
253
  },
253
254
  inline: {
254
255
  class: 'columns',
256
+ onlyWith: 'ref',
255
257
  inKind: [ '$column' ], // only valid in $column
256
258
  },
257
259
  keys: {
@@ -292,17 +294,21 @@ const schema = compileSchema( {
292
294
  // type properties (except: elements, enum, keys, on): ---------------------
293
295
  type: {
294
296
  type: artifactRef,
295
- msgId: 'syntax-expected-reference',
297
+ msgId: 'syntax-expecting-reference',
296
298
  optional: [ 'ref', 'global' ],
297
- inKind: [ 'element', 'type', 'param', 'mixin', 'event', 'annotation' ],
299
+ inKind: [ 'element', 'type', 'param', 'mixin', 'event', 'annotation', 'extend' ],
298
300
  },
299
301
  targetAspect: {
300
302
  type: artifactRef,
303
+ msgId: 'syntax-expecting-reference',
304
+ requires: 'elements',
301
305
  optional: [ 'elements' ], // 'elements' for ad-hoc aspect compositions
302
306
  inKind: [ 'element', 'type' ],
303
307
  },
304
308
  target: {
305
309
  type: artifactRef,
310
+ msgId: 'syntax-expecting-reference',
311
+ requires: 'elements',
306
312
  optional: [ 'elements' ], // 'elements' for ad-hoc COMPOSITION OF (gensrc style CSN)
307
313
  inKind: [ 'element', 'type', 'mixin', 'param' ],
308
314
  },
@@ -323,20 +329,20 @@ const schema = compileSchema( {
323
329
  },
324
330
  length: {
325
331
  type: natnum,
326
- inKind: [ 'element', 'type', 'param', 'annotation' ],
332
+ inKind: [ 'element', 'type', 'param', 'annotation', 'extend' ],
327
333
  // we do not require a 'type', too - could be useful alone in a 'cast'
328
334
  },
329
335
  precision: {
330
336
  type: natnum,
331
- inKind: [ 'element', 'type', 'param', 'annotation' ],
337
+ inKind: [ 'element', 'type', 'param', 'annotation', 'extend' ],
332
338
  },
333
339
  scale: {
334
340
  type: scalenum,
335
- inKind: [ 'element', 'type', 'param', 'annotation' ],
341
+ inKind: [ 'element', 'type', 'param', 'annotation', 'extend' ],
336
342
  },
337
343
  srid: {
338
344
  type: natnum,
339
- inKind: [ 'element', 'type', 'param', 'annotation' ],
345
+ inKind: [ 'element', 'type', 'param', 'annotation' ], // no 'extend'!
340
346
  },
341
347
  srcmin: { // in 'cardinality'
342
348
  type: renameTo( 'sourceMin', natnum ),
@@ -368,10 +374,11 @@ const schema = compileSchema( {
368
374
  ref: {
369
375
  arrayOf: refItem,
370
376
  type: renameTo( 'path', arrayOf( refItem ) ),
371
- msgId: 'syntax-expected-reference',
377
+ msgId: 'syntax-expecting-reference',
372
378
  minLength: 1,
373
379
  requires: 'id',
374
380
  optional: [ 'id', 'args', 'cardinality', 'where' ],
381
+ xorException: 'expand', // see xorGroup :expr
375
382
  inKind: [ '$column', 'key' ],
376
383
  },
377
384
  id: { // in 'ref' item
@@ -389,6 +396,7 @@ const schema = compileSchema( {
389
396
  },
390
397
  func: {
391
398
  type: func,
399
+ xorException: 'xpr', // see xorGroup :expr
392
400
  inKind: [ '$column' ],
393
401
  },
394
402
  args: {
@@ -405,6 +413,7 @@ const schema = compileSchema( {
405
413
  xpr: {
406
414
  class: 'condition',
407
415
  type: xpr,
416
+ xorException: 'func', // see xorGroup :expr
408
417
  // special treatment in $column
409
418
  },
410
419
  list: {
@@ -631,14 +640,13 @@ const schema = compileSchema( {
631
640
  origin: { // old-style CSN
632
641
  type: vZeroDelete, ignore: true,
633
642
  },
634
- source: { // CSN v0.1.0 query not supported
643
+ source: { // CSN v0.1.0 query not supported (is error)
635
644
  type: ignore,
636
645
  },
637
646
  value: {
638
- vZeroFor: false, // CSN v0.1.0 property, but handled specially
639
- type: vZeroValue,
640
- optional: exprProperties,
641
- inKind: [ '$column', 'enum' ],
647
+ vZeroFor: 'val', // CSN v0.1.0 property for `val` in enum def
648
+ type: annoValue,
649
+ // inKind: [ 'enum' ],
642
650
  },
643
651
  // ignored: ----------------------------------------------------------------
644
652
  $location: { // special
@@ -747,6 +755,7 @@ function compileSchema( specs, proto = null) {
747
755
  }
748
756
  if (proto)
749
757
  return r;
758
+ // Set property 'inValue' in main schema only:
750
759
  for (const prop of exprProperties) {
751
760
  if (r[prop].inValue === undefined)
752
761
  r[prop].inValue = true;
@@ -772,8 +781,8 @@ function arrayOf( fn, filter = undefined ) {
772
781
  } );
773
782
  const minLength = spec.minLength || 0;
774
783
  if (minLength > val.length) {
775
- message( 'syntax-expected-length', location(true),
776
- { prop: spec.prop, n: minLength, '#': minLength === 1 ? 'one' : 'std' });
784
+ error( 'syntax-incomplete-array', location(true),
785
+ { prop: spec.prop, n: minLength, '#': minLength === 1 ? 'one' : 'std' });
777
786
  }
778
787
  if (val.length)
779
788
  ++virtualLine; // [] in one JSON line
@@ -842,20 +851,8 @@ function object( obj, spec ) {
842
851
  if (requires === undefined || requires === true) {
843
852
  // console.log(csnProps,JSON.stringify(spec))
844
853
  if (!relevantProps) {
845
- error( 'syntax-required-subproperty', location(true),
846
- {
847
- prop: spec.msgProp,
848
- '#': (
849
- // eslint-disable-next-line no-nested-ternary
850
- !csnProps.length ? 'std'
851
- : csnProps.length === 1 && csnProps[0] === 'as' ? 'as'
852
- : 'relevant'),
853
- },
854
- {
855
- std: 'Object in $(PROP) must have at least one property',
856
- as: 'Object in $(PROP) must have at least one property other than \'as\'',
857
- relevant: 'Object in $(PROP) must have at least one relevant property',
858
- } );
854
+ error( 'syntax-incomplete-object', location(true),
855
+ { '#': (obj.as != null ? 'as' : 'std'), prop: spec.msgProp, otherprop: 'as' } );
859
856
  }
860
857
  }
861
858
  else if (requires) {
@@ -868,10 +865,10 @@ function object( obj, spec ) {
868
865
 
869
866
  function vZeroDelete( o, spec ) { // for old-CSN property 'origin'
870
867
  if (!csnVersionZero) {
871
- warning( 'syntax-zero-delete', location(true), { prop: spec.msgProp },
872
- 'Delete/inline CSN v0.1.0 property $(PROP)' );
868
+ warning( 'syntax-deprecated-property', location(true),
869
+ { '#': 'zero', prop: spec.msgProp } );
873
870
  }
874
- string( o, spec );
871
+ ignore( o );
875
872
  }
876
873
 
877
874
  // Definitions, dictionaries and arrays of definitions (std signature) -------
@@ -881,7 +878,7 @@ function definition( def, spec, xsn, csn, name ) {
881
878
  return {
882
879
  kind: (inExtensions ? 'annotate' : spec.defaultKind),
883
880
  name: {
884
- id: '', path: [], absolute: name, location: location(),
881
+ id: '', path: [], absolute: name || '', location: location(),
885
882
  },
886
883
  location: location(),
887
884
  };
@@ -914,7 +911,6 @@ function definition( def, spec, xsn, csn, name ) {
914
911
  r.name.$inferred = 'as';
915
912
  // TODO the following 'if' (if necessary) should be part of the core compiler
916
913
  if (prop === 'definitions' || prop === 'vocabularies') { // as spec property
917
- // xsn.name.path = name.split('.').map( id => ({ id, location: location() }) );
918
914
  r.name = {
919
915
  absolute: name,
920
916
  id: name.substring( name.lastIndexOf('.') + 1 ),
@@ -953,7 +949,7 @@ function definition( def, spec, xsn, csn, name ) {
953
949
  function dictionaryOf( elementFct ) {
954
950
  return function dictionary( dict, spec ) {
955
951
  if (!dict || typeof dict !== 'object' || Array.isArray( dict )) {
956
- error( 'syntax-expected-object', location(true),
952
+ error( 'syntax-expecting-object', location(true),
957
953
  { prop: spec.prop }); // spec.prop, not spec.msgProp!
958
954
  return ignore( dict );
959
955
  }
@@ -965,9 +961,8 @@ function dictionaryOf( elementFct ) {
965
961
  ++virtualLine;
966
962
  for (const name of allNames) {
967
963
  if (!name) {
968
- warning( 'syntax-empty-name', location(true),
969
- { prop: spec.prop }, // TODO: Error
970
- 'Property names in dictionary $(PROP) must not be empty' );
964
+ warning( 'syntax-invalid-name', location(true), // TODO: Error
965
+ { '#': 'csn', parentprop: spec.prop } );
971
966
  }
972
967
  const val = elementFct( dict[name], spec, r, dict, name );
973
968
  if (val !== undefined)
@@ -1042,9 +1037,11 @@ function validKind( val, spec, xsn ) {
1042
1037
  if (val === xsn.kind) // has been set in definition - the same = ok!
1043
1038
  return undefined; // already set in definition
1044
1039
  if (val === 'view' && xsn.kind === 'entity') {
1045
- warning( 'syntax-zero-value', location(true), { prop: spec.msgProp },
1046
- 'Replace CSN v0.1.0 value in $(PROP) by something specified' );
1040
+ warning( 'syntax-deprecated-value', location(true),
1041
+ { '#': 'replace', prop: spec.msgProp, value: 'entity' } );
1047
1042
  }
1043
+ // TODO: rather issue info at `abstract` and `$syntax`, i.e. current location is strange
1044
+ // change message id in a later change
1048
1045
  else if ((val === 'entity' || val === 'type') && xsn.kind === 'aspect') {
1049
1046
  info( 'syntax-aspect', location(true), { kind: 'aspect', '#': val },
1050
1047
  {
@@ -1054,8 +1051,8 @@ function validKind( val, spec, xsn ) {
1054
1051
  } );
1055
1052
  }
1056
1053
  else {
1057
- error( 'syntax-expected-valid', location(true), { prop: spec.msgProp },
1058
- 'Expected valid string for property $(PROP)' );
1054
+ error( 'syntax-invalid-kind', location(true), { prop: spec.msgProp },
1055
+ 'Invalid value for property $(PROP)' );
1059
1056
  }
1060
1057
  return ignore( val );
1061
1058
  }
@@ -1063,12 +1060,16 @@ function validKind( val, spec, xsn ) {
1063
1060
  function artifactRef( ref, spec ) {
1064
1061
  if (!ref || typeof ref !== 'string')
1065
1062
  return object( ref, spec );
1066
- if (spec.prop !== 'type' || !csnVersionZero)
1063
+ if (spec.prop !== 'type')
1067
1064
  return stringRef( ref, spec );
1068
- // now the CSN v0.1.0 type of: 'Artifact..e1.e2'
1065
+ // now the CSN v0.1.0 type of: 'Artifact..e1.e2'; error if not csnVersionZero
1069
1066
  const idx = ref.indexOf('..');
1070
1067
  if (idx < 0)
1071
1068
  return stringRef( ref, spec );
1069
+ if (!csnVersionZero) {
1070
+ warning( 'syntax-deprecated-value', location(true),
1071
+ { '#': 'zero-replace', prop: spec.msgProp, value: '{ ref: […] }' } );
1072
+ }
1072
1073
  const r = refSplit( ref.substring( idx + 2 ), spec.msgProp );
1073
1074
  r.path.unshift( { id: ref.substring( 0, idx ), location: location() } );
1074
1075
  return r;
@@ -1095,9 +1096,9 @@ function vZeroRef( name, spec, xsn ) {
1095
1096
  if (!string( name, spec ))
1096
1097
  return;
1097
1098
  const path = name.split('.');
1098
- if (!path.every( id => id)) {
1099
- warning( 'syntax-expected-name', location(true), { prop: spec.msgProp },
1100
- 'Expected correct name for property $(PROP)' );
1099
+ if (!path.every( id => id)) { // TODO: why just warning?
1100
+ warning( 'syntax-invalid-zero-ref', location(true), { prop: spec.msgProp },
1101
+ 'Invalid string reference in property $(PROP)' );
1101
1102
  }
1102
1103
  xsn.path = path.map( id => ({ id, location: location() }) );
1103
1104
  }
@@ -1107,18 +1108,17 @@ function vZeroRef( name, spec, xsn ) {
1107
1108
  function boolOrNull( val, spec ) {
1108
1109
  if ([ true, false, null ].includes( val ))
1109
1110
  return { val, location: location() };
1110
- warning( 'syntax-expected-boolean', location(true), { prop: spec.msgProp },
1111
- 'Expected boolean or null for property $(PROP)' );
1111
+ warning( 'syntax-expecting-boolean', location(true), { prop: spec.msgProp },
1112
+ 'Expecting boolean or null for property $(PROP)' );
1112
1113
  ignore( val );
1113
1114
  return { val: !!val, location: location() };
1114
1115
  }
1115
1116
 
1116
1117
  function string( val, spec ) {
1117
1118
  if (typeof val === 'string' && val)
1118
- // XSN TODO: do not require literal
1119
1119
  return val;
1120
- error( 'syntax-expected-string', location(true), { prop: spec.msgProp },
1121
- 'Expected non-empty string for property $(PROP)' );
1120
+ error( 'syntax-expecting-string', location(true), { prop: spec.msgProp },
1121
+ 'Expecting non-empty string for property $(PROP)' );
1122
1122
  return ignore( val );
1123
1123
  }
1124
1124
 
@@ -1126,8 +1126,8 @@ function stringVal( val, spec ) {
1126
1126
  if (typeof val === 'string' && val)
1127
1127
  // XSN TODO: do not require literal
1128
1128
  return { val, literal: 'string', location: location() };
1129
- error( 'syntax-expected-string', location(true), { prop: spec.msgProp },
1130
- 'Expected non-empty string for property $(PROP)' );
1129
+ error( 'syntax-expecting-string', location(true), { prop: spec.msgProp },
1130
+ 'Expecting non-empty string for property $(PROP)' );
1131
1131
  return ignore( val );
1132
1132
  }
1133
1133
 
@@ -1148,7 +1148,7 @@ function natnum( val, spec ) {
1148
1148
  if (typeof val === 'number' && val >= 0)
1149
1149
  // XSN TODO: do not require literal
1150
1150
  return { val, literal: 'number', location: location() };
1151
- error( spec.msgId || 'syntax-expected-natnum', location(true),
1151
+ error( spec.msgId || 'syntax-expecting-natnum', location(true),
1152
1152
  { prop: spec.msgProp } );
1153
1153
  return ignore( val );
1154
1154
  }
@@ -1183,18 +1183,19 @@ function annoValue( val, spec ) {
1183
1183
  /** @type {string|boolean} */
1184
1184
  let seenEllipsis = false;
1185
1185
  if (arrayLevelCount > 0) { // TODO: also inside structure (possible in CSN!)
1186
- if (val.some( isEllipsis ))
1187
- error( 'syntax-unexpected-ellipsis', location(true), { '#': 'nested-array', code: '...' } );
1186
+ if (val.some( isEllipsis )) { // remark: check is via parsing rules in CDL
1187
+ error( 'syntax-unexpected-ellipsis', location(true),
1188
+ { '#': 'csn-nested', prop: '...' } );
1189
+ }
1188
1190
  }
1189
1191
  else {
1190
1192
  for (const item of val) {
1191
- if (seenEllipsis !== true) {
1193
+ if (seenEllipsis !== true) { // no `...` yet, or only `... up to`
1192
1194
  seenEllipsis = isEllipsis( item ) || seenEllipsis;
1193
1195
  }
1194
- else if (isEllipsis( item )) { // with or without UP TO
1195
- // error position at the beginning of the array, but that is fine
1196
- error( 'syntax-duplicate-ellipsis', location(true), { code: '...' },
1197
- 'Expected no more than one $(CODE)' );
1196
+ else if (isEllipsis( item )) { // `...`with or without UP TO
1197
+ error( 'syntax-unexpected-ellipsis', location(true),
1198
+ { '#': 'csn-duplicate', prop: '...', code: '{ "...": true }' } );
1198
1199
  break;
1199
1200
  }
1200
1201
  }
@@ -1207,10 +1208,8 @@ function annoValue( val, spec ) {
1207
1208
  };
1208
1209
  arrayLevelCount--;
1209
1210
  if (seenEllipsis === 'upTo') {
1210
- error( 'syntax-expecting-ellipsis', location(true), // at closing bracket
1211
- { code: '... up to', newcode: '...' },
1212
- // TODO: should we be more CSN specific in the message?
1213
- 'Expecting an array item $(NEWCODE) after an item with $(CODE)' );
1211
+ error( 'syntax-missing-ellipsis', location(true), // at closing bracket
1212
+ { code: '{ "...": up to value› }', newcode: '{ "...": true }' } );
1214
1213
  }
1215
1214
  return retval;
1216
1215
  }
@@ -1226,8 +1225,10 @@ function annoValue( val, spec ) {
1226
1225
  }
1227
1226
  else if (typeof val['='] === 'string') {
1228
1227
  if (Object.keys( val ).length === 1) {
1229
- virtualLine += 2;
1230
- return refSplit( val['='], '=' );
1228
+ ++virtualLine;
1229
+ const r = refSplit( val['='], '=' );
1230
+ ++virtualLine;
1231
+ return r;
1231
1232
  }
1232
1233
  }
1233
1234
  else if (val['...'] && Object.keys( val ).length === 1) {
@@ -1276,26 +1277,29 @@ function value( val, spec, xsn ) { // for CSN property 'val'
1276
1277
  xsn.literal = (val === null) ? 'null' : typeof val;
1277
1278
  return val;
1278
1279
  }
1279
- error( 'syntax-expected-scalar', location(true), { prop: spec.msgProp },
1280
+ error( 'syntax-expecting-scalar', location(true), { prop: spec.msgProp },
1280
1281
  'Only scalar values are supported for property $(PROP)' );
1281
1282
  return ignore( val );
1282
1283
  }
1283
1284
 
1284
1285
  function literal( lit, spec, xsn, csn ) {
1285
1286
  // TODO: general: requires other property (here: 'val')
1287
+ if (!string( lit ))
1288
+ return undefined;
1286
1289
  const type = (csn.val == null) ? 'null' : typeof csn.val;
1287
1290
  if (lit === type) // also for 'object' which is an error for 'val'
1288
1291
  return lit;
1289
- if (typeof lit === 'string' && quotedLiteralPatterns[lit]?.json_type === type) {
1290
- const p = quotedLiteralPatterns[lit];
1292
+ const p = quotedLiteralPatterns[lit];
1293
+ if (p?.json_type === type) {
1294
+ // TODO: wrong position, we complain about the format here, that is in 'val',
1295
+ // TODO: make it then also more CSN-specific? Next change
1291
1296
  if (p && p.test_fn && !p.test_fn(csn.val))
1292
1297
  warning( 'syntax-invalid-literal', location(), { '#': p.test_variant } );
1293
1298
  return lit;
1294
1299
  }
1295
1300
  if (lit === 'number' && type === 'string') // special case, not a quoted literal in CDL
1296
1301
  return lit;
1297
- error( 'syntax-expected-valid', location(true), { prop: spec.msgProp },
1298
- 'Expected valid string for property $(PROP)' );
1302
+ error( 'syntax-invalid-string', location(true), { prop: spec.msgProp } );
1299
1303
  return ignore( lit );
1300
1304
  }
1301
1305
 
@@ -1309,8 +1313,8 @@ function func( val, spec, xsn ) {
1309
1313
  function xpr( exprs, spec, xsn, csn ) {
1310
1314
  if (csn.func) {
1311
1315
  if (!exprs.length) {
1312
- message( 'syntax-expected-length', location(true),
1313
- { prop: 'xpr', otherprop: 'func', '#': 'suffix' });
1316
+ error( 'syntax-incomplete-array', location(true),
1317
+ { prop: 'xpr', siblingprop: 'func', '#': 'suffix' });
1314
1318
  }
1315
1319
  xsn.suffix = exprArgs( exprs, spec );
1316
1320
  }
@@ -1342,9 +1346,9 @@ function args( exprs, spec ) {
1342
1346
  return arrayOf( exprOrString )( exprs, spec );
1343
1347
  }
1344
1348
  else if (!exprs || typeof exprs !== 'object') {
1345
- error( 'syntax-expected-args', location(true),
1349
+ error( 'syntax-expecting-args', location(true),
1346
1350
  { prop: spec.prop }, // spec.prop, not spec.msgProp!
1347
- 'Expected array or object for property $(PROP)' );
1351
+ 'Expecting array or object for property $(PROP)' );
1348
1352
  return ignore( exprs );
1349
1353
  }
1350
1354
  const r = Object.create(null);
@@ -1362,16 +1366,24 @@ function args( exprs, spec ) {
1362
1366
  }
1363
1367
 
1364
1368
  function expr( e, spec ) {
1365
- if (Array.isArray( e ) && e.length === 1) {
1366
- replaceZeroValue( spec );
1369
+ if (Array.isArray( e ) && e.length === 1) { // CSN v.0.1.0 way for parentheses
1370
+ const loc = location();
1371
+ if (e[0] && !e[0].op) // do not complain with 'op' (for which we complain)
1372
+ replaceZeroValue( spec, 'zero-parens' );
1367
1373
  ++virtualLine;
1368
1374
  const r = expr( e[0], spec );
1375
+ if (!r)
1376
+ return r;
1377
+ if (r.$parens)
1378
+ r.$parens.push( loc );
1379
+ else
1380
+ r.$parens = [ loc ];
1369
1381
  ++virtualLine;
1370
- return [ r || { location: location() } ];
1382
+ return r;
1371
1383
  }
1372
1384
  else if (e === null || [ 'string', 'number', 'boolean' ].includes( typeof e )) {
1373
1385
  // && spec.optional.includes( 'val' )) ?
1374
- replaceZeroValue( spec );
1386
+ replaceZeroValue( spec, 'zero-replace', '{ val: ‹value› }' );
1375
1387
  return annoValue( e, spec );
1376
1388
  }
1377
1389
  return object( e, spec );
@@ -1402,20 +1414,6 @@ function condition( cond, spec ) {
1402
1414
  };
1403
1415
  }
1404
1416
 
1405
- function vZeroValue( obj, spec, xsn ) {
1406
- if (xsn.value) {
1407
- // TODO: also "sign" xsn.value created by inValue to complain about both 'value' and 'ref' etc
1408
- warning( 'syntax-unexpected-property', location(true), { prop: spec.msgProp },
1409
- 'Unexpected CSN property $(PROP)' );
1410
- return undefined;
1411
- }
1412
- if (!csnVersionZero) {
1413
- warning( 'syntax-zero-delete', location(true), { prop: spec.msgProp },
1414
- 'Delete/inline CSN v0.1.0 property $(PROP)' );
1415
- }
1416
- return expr( obj, spec );
1417
- }
1418
-
1419
1417
  // Queries (std signature) ---------------------------------------------------
1420
1418
 
1421
1419
  function queryTerm( term, spec, xsn ) { // for CSN properties 'SELECT' and 'SET'
@@ -1457,17 +1455,16 @@ function excluding( array, spec, xsn ) {
1457
1455
  xsn.excludingDict = r;
1458
1456
  }
1459
1457
 
1460
- function masked( val, spec ) {
1461
- message('syntax-invalid-masked', location(), { keyword: 'masked' },
1462
- 'Keyword $(KEYWORD) not supported');
1463
- return boolOrNull( val, spec );
1464
- }
1465
-
1466
1458
  function duplicateExcluding( name, loc ) {
1467
- error( 'duplicate-excluding', loc, { name, keyword: 'excluding' },
1459
+ error( 'syntax-duplicate-excluding', loc, { name, keyword: 'excluding' }, // TODO: also CDL
1468
1460
  'Duplicate $(NAME) in the $(KEYWORD) clause' );
1469
1461
  }
1470
1462
 
1463
+ function masked( val, spec ) {
1464
+ message( 'syntax-unsupported-masked', location(), { '#': 'csn', prop: 'masked' } );
1465
+ return boolOrNull( val, spec );
1466
+ }
1467
+
1471
1468
  function setOp( val, spec ) { // UNION, ...
1472
1469
  // similar to string(), but without literal
1473
1470
  return string( val, spec ) && { val, location: location() };
@@ -1483,9 +1480,10 @@ function join( val, spec, xsn ) {
1483
1480
 
1484
1481
  function queryArgs( val, spec, xsn, csn ) {
1485
1482
  if (Array.isArray( val ) && val.length > 1 && !csn.op) {
1486
- warning( 'syntax-expected-property', location(true),
1487
- { prop: 'args', otherprop: 'op' },
1488
- 'CSN property $(PROP) expects property $(OTHERPROP) to be specified' );
1483
+ // Make it error 'syntax-missing-property#sibling' in v4:
1484
+ warning( 'syntax-deprecated-auto-union', location(true),
1485
+ { siblingprop: 'args', prop: 'op' },
1486
+ 'Object with property $(SIBLINGPROP) must also have a property $(PROP)' );
1489
1487
  xsn.op = { val: 'union', location: location() };
1490
1488
  }
1491
1489
  return arrayOf( object )( val, spec ).map( q => q.query );
@@ -1502,9 +1500,9 @@ function i18nLang( val, spec, xsn, csn, langKey ) {
1502
1500
  function translations( keyVal, spec, xsn, csn, textKey ) {
1503
1501
  if (typeof keyVal === 'string') // allow empty string
1504
1502
  return { val: keyVal, literal: 'string', location: location() };
1505
- error( 'syntax-expected-translation', location(true),
1506
- { prop: textKey, otherprop: spec.prop },
1507
- 'Expected string for text key $(PROP) of language $(OTHERPROP)' );
1503
+ error( 'syntax-expecting-translation', location(true),
1504
+ { prop: textKey, language: spec.prop },
1505
+ 'Expecting string for text key $(PROP) of language $(LANGUAGE)' );
1508
1506
  return ignore( keyVal );
1509
1507
  }
1510
1508
 
@@ -1514,21 +1512,18 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
1514
1512
  const p0 = schema[prop] ? prop : prop.charAt(0);
1515
1513
  const s = (parentSpec.schema || schema)[p0];
1516
1514
  if (!s || s.noPrefix && prop !== p0 ) {
1517
- if (ourpropsRegex.test( prop )) {
1518
- // TODO v2: Warning only with --sloppy
1519
- warning( 'syntax-unknown-property', location(true), { prop },
1520
- 'Unknown CSN property $(PROP)' );
1521
- }
1522
- else { // TODO v2: always (i.e. also with message) add to $extra
1515
+ if (!ourpropsRegex.test( prop ))
1523
1516
  return { prop, type: extra };
1524
- }
1517
+ // TODO v4: No warning with --sloppy
1518
+ warning( 'syntax-unknown-property', location(true), { prop },
1519
+ 'Unknown CSN property $(PROP)' );
1520
+ return { type: ignore };
1525
1521
  }
1526
1522
  else if (!expected( p0, s )) {
1527
1523
  if (s.ignore)
1528
1524
  return { type: ignore };
1529
1525
  if (s.vZeroIgnore && s.vZeroIgnore === csn[prop]) { // for "op": "call"
1530
- warning( 'syntax-zero-delete', location(true), { prop },
1531
- 'Delete/inline CSN v0.1.0 property $(PROP)' );
1526
+ warning( 'syntax-deprecated-property', location(true), { '#': 'zero', prop } );
1532
1527
  return { type: ignore };
1533
1528
  }
1534
1529
  const zero = s.vZeroFor;
@@ -1544,22 +1539,18 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
1544
1539
  }
1545
1540
  // eslint-disable-next-line no-nested-ternary
1546
1541
  const variant = kind && s.inKind
1547
- ? ([ 'extend', 'annotate' ].includes(kind) ? kind : 'def')
1548
- : (parentSpec.msgProp ? 'std' : 'top');
1542
+ ? ([ 'extend', 'annotate' ].includes(kind) ? kind : 'kind')
1543
+ : (parentSpec.msgProp ? 'prop' : 'top');
1549
1544
  message( 'syntax-unexpected-property', location(true),
1550
1545
  {
1551
- prop, otherprop: parentSpec.msgProp, kind, '#': variant,
1552
- },
1553
- {
1554
- std: 'CSN property $(PROP) is not expected in $(OTHERPROP)',
1555
- top: 'CSN property $(PROP) is not expected top-level',
1556
- def: 'CSN property $(PROP) is not expected by a definition of kind $(KIND)',
1557
- extend: 'CSN property $(PROP) is not expected by an extend in $(OTHERPROP)',
1558
- annotate: 'CSN property $(PROP) is not expected by an annotate in $(OTHERPROP)',
1546
+ '#': variant,
1547
+ prop,
1548
+ parentprop:
1549
+ parentSpec.msgProp,
1550
+ kind,
1559
1551
  } );
1560
- // TODO: or still augment it? (but then also handle xorGroup)
1561
1552
  }
1562
- else if (checkAndSetXorGroup( s.xorGroup, prop, xor )) {
1553
+ else if (checkAndSetXorGroup( s.xorGroup, s.xorException, prop, xor )) {
1563
1554
  onlyWith( s, s.onlyWith, csn, prop, xor, expected );
1564
1555
  return s;
1565
1556
  }
@@ -1604,37 +1595,34 @@ function onlyWith( spec, need, csn, prop, xor, expected ) {
1604
1595
  need = allowed.find( p => !xor[schema[p].xorGroup] ) || allowed[0];
1605
1596
  }
1606
1597
  if (prop) {
1607
- error( 'syntax-dependent-property', location(true),
1608
- { prop, otherprop: need },
1609
- 'CSN property $(PROP) can only be used in combination with $(OTHERPROP)');
1598
+ error( 'syntax-missing-property', location(true), // location at $(PROP)
1599
+ { '#': 'sibling', prop: need, siblingprop: prop } );
1610
1600
  xor['no:req'] = prop;
1611
1601
  }
1602
+ // TODO: does no:req work? check test3/NestedProjections/Basics/SyntaxErrorsCsn.err.csn
1612
1603
  else if (!xor['no:req']) {
1613
- error( 'syntax-required-property', location(true),
1614
- { prop: need, otherprop: spec.msgProp, '#': spec.prop },
1615
- { // TODO $(PARENT), TODO: do not use prop===0 hack
1616
- std: 'Object in $(OTHERPROP) must have the property $(PROP)',
1617
- columns: 'Object in $(OTHERPROP) must have an expression property like $(PROP)',
1618
- // eslint-disable-next-line max-len
1619
- extensions: 'Object in $(OTHERPROP) must have the property \'annotate\' or \'extend\'',
1604
+ error( 'syntax-missing-property', location(true), // TODO: re-check columns, expand, inline
1605
+ {
1606
+ '#': spec.prop,
1607
+ prop: need,
1608
+ parentprop: spec.msgProp,
1609
+ otherprop: 'annotate',
1620
1610
  } );
1621
1611
  }
1622
1612
  return spec;
1623
1613
  }
1624
1614
 
1625
- function checkAndSetXorGroup( group, prop, xor ) {
1615
+ function checkAndSetXorGroup( group, exception, prop, xor ) {
1626
1616
  if (!group)
1627
1617
  return true;
1628
- if (!xor[group]) {
1618
+ const siblingprop = xor[group];
1619
+ if (!siblingprop) {
1629
1620
  xor[group] = prop;
1630
1621
  return true;
1631
1622
  }
1632
- if (prop === 'func' && xor[group] === 'xpr' ||
1633
- prop === 'xpr' && xor[group] === 'func')
1634
- return true; // hack for window function: both func and xpr is allowed
1635
- error( 'syntax-excluded-property', location(true),
1636
- { prop, otherprop: xor[group] },
1637
- 'CSN property $(PROP) can only be used alternatively to $(OTHERPROP)');
1623
+ if (siblingprop === exception)
1624
+ return true;
1625
+ message( 'syntax-unexpected-property', location(true), { '#': 'sibling', prop, siblingprop } );
1638
1626
  return false;
1639
1627
  }
1640
1628
 
@@ -1644,11 +1632,11 @@ function implicitName( ref ) {
1644
1632
  return (typeof item === 'object') ? item && item.id : item;
1645
1633
  }
1646
1634
 
1647
- function replaceZeroProp( otherprop, prop ) {
1635
+ function replaceZeroProp( prop, otherprop ) {
1648
1636
  if (csnVersionZero)
1649
1637
  return;
1650
- warning( 'syntax-zero-prop', location(true), { prop, otherprop },
1651
- 'Replace CSN v0.1.0 property $(OTHERPROP) by $(PROP)' );
1638
+ warning( 'syntax-deprecated-property', location(true),
1639
+ { '#': 'zero-replace', prop, otherprop } );
1652
1640
  }
1653
1641
 
1654
1642
  // Other helper functions, locations -----------------------------------------
@@ -1656,15 +1644,15 @@ function replaceZeroProp( otherprop, prop ) {
1656
1644
  function isArray( array, spec ) {
1657
1645
  if (Array.isArray( array ))
1658
1646
  return array;
1659
- error( 'syntax-expected-array', location(true), { prop: spec.prop },
1660
- 'Expected array for property $(PROP)' );
1647
+ error( 'syntax-expecting-array', location(true), { prop: spec.prop },
1648
+ 'Expecting array for property $(PROP)' );
1661
1649
  return ignore( array );
1662
1650
  }
1663
1651
 
1664
1652
  function isObject( obj, spec ) {
1665
1653
  if (obj && typeof obj === 'object' && !Array.isArray( obj ))
1666
1654
  return obj;
1667
- error( spec.msgId || 'syntax-expected-object', location(true),
1655
+ error( spec.msgId || 'syntax-expecting-object', location(true),
1668
1656
  { prop: spec.msgProp });
1669
1657
  return ignore( obj );
1670
1658
  }
@@ -1672,16 +1660,16 @@ function isObject( obj, spec ) {
1672
1660
  function refSplit( name, prop ) {
1673
1661
  const path = name.split('.');
1674
1662
  if (!path.every( id => id)) {
1675
- warning( 'syntax-expected-name', location(true), { prop },
1676
- 'Expected correct name for property $(PROP)' );
1663
+ warning( 'syntax-expecting-name', location(true), { prop }, // TODO: re-check
1664
+ 'Expecting correct name for property $(PROP)' );
1677
1665
  }
1678
1666
  return { path: path.map( id => ({ id, location: location() }) ), location: location() };
1679
1667
  }
1680
1668
 
1681
- function replaceZeroValue( spec ) {
1682
- if (!csnVersionZero && spec.vZeroFor == null) { // but 0 does not match!
1683
- warning( 'syntax-zero-value', location(true), { prop: spec.msgProp },
1684
- 'Replace CSN v0.1.0 value in $(PROP) by something specified' );
1669
+ function replaceZeroValue( spec, msgid, otherprop ) {
1670
+ if (!csnVersionZero && !spec.vZeroFor) {
1671
+ warning( 'syntax-deprecated-value', location(true),
1672
+ { '#': msgid, prop: spec.msgProp, otherprop } );
1685
1673
  }
1686
1674
  }
1687
1675
 
@@ -1710,7 +1698,7 @@ function pushLocation( obj ) {
1710
1698
  else if (!loc || typeof loc !== 'string') {
1711
1699
  if (loc)
1712
1700
  dollarLocations.push( null ); // must match with popLocation()
1713
- error( 'syntax-expected-object', location(true), { prop: '$location' } );
1701
+ error( 'syntax-expecting-object', location(true), { prop: '$location' } );
1714
1702
  }
1715
1703
  // hidden feature: string $location
1716
1704
  const m = /:(\d+)(?::(\d+))?$/.exec( loc ); // extra '^'s at end deliberately left out
@@ -1767,7 +1755,7 @@ function toXsn( csn, filename, options, messageFunctions ) {
1767
1755
  ({ message, error, warning, info } = messageFunctions);
1768
1756
 
1769
1757
  if (csnVersionZero) {
1770
- warning( 'syntax-csn-zero-version', location(true),
1758
+ warning( 'syntax-deprecated-csn-version', location(true), {},
1771
1759
  'Parsing CSN version 0.1.0' );
1772
1760
  }
1773
1761
  const r = object( csn, topLevelSpec );
@@ -1823,7 +1811,8 @@ function parse( source, filename = 'csn.json', options = {}, messageFunctions =
1823
1811
  line,
1824
1812
  col: column,
1825
1813
  };
1826
- messageFunctions.error( 'syntax-illegal-json', loc, { msg }, 'Illegal JSON: $(MSG)' );
1814
+ messageFunctions.error( 'syntax-invalid-json', loc, { msg },
1815
+ 'Invalid JSON: $(MSG)' );
1827
1816
  return xsn;
1828
1817
  }
1829
1818
  }