@sap/cds-compiler 3.3.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 (74) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/bin/cdsc.js +3 -1
  3. package/doc/CHANGELOG_BETA.md +17 -0
  4. package/lib/api/main.js +147 -18
  5. package/lib/api/validate.js +8 -3
  6. package/lib/base/dictionaries.js +6 -6
  7. package/lib/base/keywords.js +104 -0
  8. package/lib/base/message-registry.js +136 -67
  9. package/lib/base/messages.js +59 -48
  10. package/lib/base/model.js +1 -0
  11. package/lib/checks/actionsFunctions.js +1 -1
  12. package/lib/checks/cdsPersistence.js +1 -1
  13. package/lib/checks/checkForTypes.js +13 -8
  14. package/lib/checks/defaultValues.js +3 -1
  15. package/lib/checks/elements.js +1 -1
  16. package/lib/checks/parameters.js +4 -2
  17. package/lib/checks/queryNoDbArtifacts.js +1 -1
  18. package/lib/checks/sql-snippets.js +12 -10
  19. package/lib/checks/validator.js +14 -4
  20. package/lib/compiler/assert-consistency.js +8 -7
  21. package/lib/compiler/checks.js +30 -20
  22. package/lib/compiler/define.js +89 -25
  23. package/lib/compiler/extend.js +21 -18
  24. package/lib/compiler/finalize-parse-cdl.js +14 -9
  25. package/lib/compiler/populate.js +30 -8
  26. package/lib/compiler/propagator.js +4 -2
  27. package/lib/compiler/resolve.js +11 -5
  28. package/lib/compiler/shared.js +66 -48
  29. package/lib/compiler/tweak-assocs.js +2 -3
  30. package/lib/compiler/utils.js +11 -0
  31. package/lib/edm/annotations/genericTranslation.js +7 -4
  32. package/lib/edm/csn2edm.js +1 -1
  33. package/lib/gen/language.checksum +1 -1
  34. package/lib/gen/language.interp +1 -1
  35. package/lib/gen/languageParser.js +3565 -3544
  36. package/lib/json/csnVersion.js +13 -13
  37. package/lib/json/from-csn.js +140 -158
  38. package/lib/json/to-csn.js +23 -5
  39. package/lib/language/.eslintrc.json +4 -0
  40. package/lib/language/antlrParser.js +7 -10
  41. package/lib/language/docCommentParser.js +1 -2
  42. package/lib/language/errorStrategy.js +54 -27
  43. package/lib/language/genericAntlrParser.js +115 -84
  44. package/lib/language/language.g4 +29 -25
  45. package/lib/language/multiLineStringParser.js +75 -63
  46. package/lib/main.js +1 -0
  47. package/lib/model/csnRefs.js +4 -3
  48. package/lib/model/csnUtils.js +39 -7
  49. package/lib/model/sortViews.js +7 -3
  50. package/lib/modelCompare/compare.js +49 -15
  51. package/lib/modelCompare/filter.js +83 -0
  52. package/lib/optionProcessor.js +5 -1
  53. package/lib/render/manageConstraints.js +9 -5
  54. package/lib/render/toCdl.js +120 -62
  55. package/lib/render/toHdbcds.js +1 -1
  56. package/lib/render/toSql.js +6 -2
  57. package/lib/render/utils/common.js +7 -0
  58. package/lib/sql-identifier.js +7 -0
  59. package/lib/transform/db/assertUnique.js +27 -38
  60. package/lib/transform/db/expansion.js +11 -4
  61. package/lib/transform/db/temporal.js +3 -1
  62. package/lib/transform/db/transformExists.js +7 -1
  63. package/lib/transform/db/views.js +42 -13
  64. package/lib/transform/draft/db.js +2 -2
  65. package/lib/transform/forRelationalDB.js +12 -6
  66. package/lib/transform/localized.js +1 -1
  67. package/lib/transform/odata/typesExposure.js +2 -1
  68. package/lib/transform/parseExpr.js +245 -0
  69. package/lib/transform/transformUtilsNew.js +23 -14
  70. package/lib/transform/translateAssocsToJoins.js +12 -12
  71. package/lib/utils/term.js +5 -5
  72. package/package.json +2 -2
  73. package/share/messages/message-explanations.json +1 -1
  74. package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +1 -1
@@ -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.
@@ -138,7 +136,7 @@ const schemaClasses = {
138
136
  condition: {
139
137
  arrayOf: exprOrString,
140
138
  type: condition,
141
- msgId: 'syntax-expected-term',
139
+ msgId: 'syntax-expecting-term',
142
140
  // TODO: also specify requires here, and adapt onlyWith()
143
141
  optional: exprProperties,
144
142
  },
@@ -148,11 +146,11 @@ const schemaClasses = {
148
146
  },
149
147
  natnumOrStar: {
150
148
  type: natnumOrStar,
151
- msgId: 'syntax-expected-cardinality',
149
+ msgId: 'syntax-expecting-cardinality',
152
150
  },
153
151
  columns: {
154
152
  arrayOf: selectItem,
155
- msgId: 'syntax-expected-column',
153
+ msgId: 'syntax-expecting-column',
156
154
  defaultKind: '$column',
157
155
  validKinds: [], // pseudo kind '$column'
158
156
  // A column with only as+cast.type is a new association
@@ -296,17 +294,21 @@ const schema = compileSchema( {
296
294
  // type properties (except: elements, enum, keys, on): ---------------------
297
295
  type: {
298
296
  type: artifactRef,
299
- msgId: 'syntax-expected-reference',
297
+ msgId: 'syntax-expecting-reference',
300
298
  optional: [ 'ref', 'global' ],
301
299
  inKind: [ 'element', 'type', 'param', 'mixin', 'event', 'annotation', 'extend' ],
302
300
  },
303
301
  targetAspect: {
304
302
  type: artifactRef,
303
+ msgId: 'syntax-expecting-reference',
304
+ requires: 'elements',
305
305
  optional: [ 'elements' ], // 'elements' for ad-hoc aspect compositions
306
306
  inKind: [ 'element', 'type' ],
307
307
  },
308
308
  target: {
309
309
  type: artifactRef,
310
+ msgId: 'syntax-expecting-reference',
311
+ requires: 'elements',
310
312
  optional: [ 'elements' ], // 'elements' for ad-hoc COMPOSITION OF (gensrc style CSN)
311
313
  inKind: [ 'element', 'type', 'mixin', 'param' ],
312
314
  },
@@ -372,7 +374,7 @@ const schema = compileSchema( {
372
374
  ref: {
373
375
  arrayOf: refItem,
374
376
  type: renameTo( 'path', arrayOf( refItem ) ),
375
- msgId: 'syntax-expected-reference',
377
+ msgId: 'syntax-expecting-reference',
376
378
  minLength: 1,
377
379
  requires: 'id',
378
380
  optional: [ 'id', 'args', 'cardinality', 'where' ],
@@ -638,14 +640,13 @@ const schema = compileSchema( {
638
640
  origin: { // old-style CSN
639
641
  type: vZeroDelete, ignore: true,
640
642
  },
641
- source: { // CSN v0.1.0 query not supported
643
+ source: { // CSN v0.1.0 query not supported (is error)
642
644
  type: ignore,
643
645
  },
644
646
  value: {
645
- vZeroFor: false, // CSN v0.1.0 property, but handled specially
646
- type: vZeroValue,
647
- optional: exprProperties,
648
- inKind: [ '$column', 'enum' ],
647
+ vZeroFor: 'val', // CSN v0.1.0 property for `val` in enum def
648
+ type: annoValue,
649
+ // inKind: [ 'enum' ],
649
650
  },
650
651
  // ignored: ----------------------------------------------------------------
651
652
  $location: { // special
@@ -780,8 +781,8 @@ function arrayOf( fn, filter = undefined ) {
780
781
  } );
781
782
  const minLength = spec.minLength || 0;
782
783
  if (minLength > val.length) {
783
- message( 'syntax-expected-length', location(true),
784
- { 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' });
785
786
  }
786
787
  if (val.length)
787
788
  ++virtualLine; // [] in one JSON line
@@ -850,20 +851,8 @@ function object( obj, spec ) {
850
851
  if (requires === undefined || requires === true) {
851
852
  // console.log(csnProps,JSON.stringify(spec))
852
853
  if (!relevantProps) {
853
- error( 'syntax-required-subproperty', location(true),
854
- {
855
- prop: spec.msgProp,
856
- '#': (
857
- // eslint-disable-next-line no-nested-ternary
858
- !csnProps.length ? 'std'
859
- : csnProps.length === 1 && csnProps[0] === 'as' ? 'as'
860
- : 'relevant'),
861
- },
862
- {
863
- std: 'Object in $(PROP) must have at least one property',
864
- as: 'Object in $(PROP) must have at least one property other than \'as\'',
865
- relevant: 'Object in $(PROP) must have at least one relevant property',
866
- } );
854
+ error( 'syntax-incomplete-object', location(true),
855
+ { '#': (obj.as != null ? 'as' : 'std'), prop: spec.msgProp, otherprop: 'as' } );
867
856
  }
868
857
  }
869
858
  else if (requires) {
@@ -876,10 +865,10 @@ function object( obj, spec ) {
876
865
 
877
866
  function vZeroDelete( o, spec ) { // for old-CSN property 'origin'
878
867
  if (!csnVersionZero) {
879
- warning( 'syntax-zero-delete', location(true), { prop: spec.msgProp },
880
- 'Delete/inline CSN v0.1.0 property $(PROP)' );
868
+ warning( 'syntax-deprecated-property', location(true),
869
+ { '#': 'zero', prop: spec.msgProp } );
881
870
  }
882
- string( o, spec );
871
+ ignore( o );
883
872
  }
884
873
 
885
874
  // Definitions, dictionaries and arrays of definitions (std signature) -------
@@ -889,7 +878,7 @@ function definition( def, spec, xsn, csn, name ) {
889
878
  return {
890
879
  kind: (inExtensions ? 'annotate' : spec.defaultKind),
891
880
  name: {
892
- id: '', path: [], absolute: name, location: location(),
881
+ id: '', path: [], absolute: name || '', location: location(),
893
882
  },
894
883
  location: location(),
895
884
  };
@@ -922,7 +911,6 @@ function definition( def, spec, xsn, csn, name ) {
922
911
  r.name.$inferred = 'as';
923
912
  // TODO the following 'if' (if necessary) should be part of the core compiler
924
913
  if (prop === 'definitions' || prop === 'vocabularies') { // as spec property
925
- // xsn.name.path = name.split('.').map( id => ({ id, location: location() }) );
926
914
  r.name = {
927
915
  absolute: name,
928
916
  id: name.substring( name.lastIndexOf('.') + 1 ),
@@ -961,7 +949,7 @@ function definition( def, spec, xsn, csn, name ) {
961
949
  function dictionaryOf( elementFct ) {
962
950
  return function dictionary( dict, spec ) {
963
951
  if (!dict || typeof dict !== 'object' || Array.isArray( dict )) {
964
- error( 'syntax-expected-object', location(true),
952
+ error( 'syntax-expecting-object', location(true),
965
953
  { prop: spec.prop }); // spec.prop, not spec.msgProp!
966
954
  return ignore( dict );
967
955
  }
@@ -973,9 +961,8 @@ function dictionaryOf( elementFct ) {
973
961
  ++virtualLine;
974
962
  for (const name of allNames) {
975
963
  if (!name) {
976
- warning( 'syntax-empty-name', location(true),
977
- { prop: spec.prop }, // TODO: Error
978
- 'Property names in dictionary $(PROP) must not be empty' );
964
+ warning( 'syntax-invalid-name', location(true), // TODO: Error
965
+ { '#': 'csn', parentprop: spec.prop } );
979
966
  }
980
967
  const val = elementFct( dict[name], spec, r, dict, name );
981
968
  if (val !== undefined)
@@ -1050,9 +1037,11 @@ function validKind( val, spec, xsn ) {
1050
1037
  if (val === xsn.kind) // has been set in definition - the same = ok!
1051
1038
  return undefined; // already set in definition
1052
1039
  if (val === 'view' && xsn.kind === 'entity') {
1053
- warning( 'syntax-zero-value', location(true), { prop: spec.msgProp },
1054
- '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' } );
1055
1042
  }
1043
+ // TODO: rather issue info at `abstract` and `$syntax`, i.e. current location is strange
1044
+ // change message id in a later change
1056
1045
  else if ((val === 'entity' || val === 'type') && xsn.kind === 'aspect') {
1057
1046
  info( 'syntax-aspect', location(true), { kind: 'aspect', '#': val },
1058
1047
  {
@@ -1062,8 +1051,8 @@ function validKind( val, spec, xsn ) {
1062
1051
  } );
1063
1052
  }
1064
1053
  else {
1065
- error( 'syntax-expected-valid', location(true), { prop: spec.msgProp },
1066
- 'Expected valid string for property $(PROP)' );
1054
+ error( 'syntax-invalid-kind', location(true), { prop: spec.msgProp },
1055
+ 'Invalid value for property $(PROP)' );
1067
1056
  }
1068
1057
  return ignore( val );
1069
1058
  }
@@ -1071,12 +1060,16 @@ function validKind( val, spec, xsn ) {
1071
1060
  function artifactRef( ref, spec ) {
1072
1061
  if (!ref || typeof ref !== 'string')
1073
1062
  return object( ref, spec );
1074
- if (spec.prop !== 'type' || !csnVersionZero)
1063
+ if (spec.prop !== 'type')
1075
1064
  return stringRef( ref, spec );
1076
- // 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
1077
1066
  const idx = ref.indexOf('..');
1078
1067
  if (idx < 0)
1079
1068
  return stringRef( ref, spec );
1069
+ if (!csnVersionZero) {
1070
+ warning( 'syntax-deprecated-value', location(true),
1071
+ { '#': 'zero-replace', prop: spec.msgProp, value: '{ ref: […] }' } );
1072
+ }
1080
1073
  const r = refSplit( ref.substring( idx + 2 ), spec.msgProp );
1081
1074
  r.path.unshift( { id: ref.substring( 0, idx ), location: location() } );
1082
1075
  return r;
@@ -1103,9 +1096,9 @@ function vZeroRef( name, spec, xsn ) {
1103
1096
  if (!string( name, spec ))
1104
1097
  return;
1105
1098
  const path = name.split('.');
1106
- if (!path.every( id => id)) {
1107
- warning( 'syntax-expected-name', location(true), { prop: spec.msgProp },
1108
- '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)' );
1109
1102
  }
1110
1103
  xsn.path = path.map( id => ({ id, location: location() }) );
1111
1104
  }
@@ -1115,18 +1108,17 @@ function vZeroRef( name, spec, xsn ) {
1115
1108
  function boolOrNull( val, spec ) {
1116
1109
  if ([ true, false, null ].includes( val ))
1117
1110
  return { val, location: location() };
1118
- warning( 'syntax-expected-boolean', location(true), { prop: spec.msgProp },
1119
- '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)' );
1120
1113
  ignore( val );
1121
1114
  return { val: !!val, location: location() };
1122
1115
  }
1123
1116
 
1124
1117
  function string( val, spec ) {
1125
1118
  if (typeof val === 'string' && val)
1126
- // XSN TODO: do not require literal
1127
1119
  return val;
1128
- error( 'syntax-expected-string', location(true), { prop: spec.msgProp },
1129
- '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)' );
1130
1122
  return ignore( val );
1131
1123
  }
1132
1124
 
@@ -1134,8 +1126,8 @@ function stringVal( val, spec ) {
1134
1126
  if (typeof val === 'string' && val)
1135
1127
  // XSN TODO: do not require literal
1136
1128
  return { val, literal: 'string', location: location() };
1137
- error( 'syntax-expected-string', location(true), { prop: spec.msgProp },
1138
- '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)' );
1139
1131
  return ignore( val );
1140
1132
  }
1141
1133
 
@@ -1156,7 +1148,7 @@ function natnum( val, spec ) {
1156
1148
  if (typeof val === 'number' && val >= 0)
1157
1149
  // XSN TODO: do not require literal
1158
1150
  return { val, literal: 'number', location: location() };
1159
- error( spec.msgId || 'syntax-expected-natnum', location(true),
1151
+ error( spec.msgId || 'syntax-expecting-natnum', location(true),
1160
1152
  { prop: spec.msgProp } );
1161
1153
  return ignore( val );
1162
1154
  }
@@ -1191,18 +1183,19 @@ function annoValue( val, spec ) {
1191
1183
  /** @type {string|boolean} */
1192
1184
  let seenEllipsis = false;
1193
1185
  if (arrayLevelCount > 0) { // TODO: also inside structure (possible in CSN!)
1194
- if (val.some( isEllipsis ))
1195
- 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
+ }
1196
1190
  }
1197
1191
  else {
1198
1192
  for (const item of val) {
1199
- if (seenEllipsis !== true) {
1193
+ if (seenEllipsis !== true) { // no `...` yet, or only `... up to`
1200
1194
  seenEllipsis = isEllipsis( item ) || seenEllipsis;
1201
1195
  }
1202
- else if (isEllipsis( item )) { // with or without UP TO
1203
- // error position at the beginning of the array, but that is fine
1204
- error( 'syntax-duplicate-ellipsis', location(true), { code: '...' },
1205
- '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 }' } );
1206
1199
  break;
1207
1200
  }
1208
1201
  }
@@ -1215,10 +1208,8 @@ function annoValue( val, spec ) {
1215
1208
  };
1216
1209
  arrayLevelCount--;
1217
1210
  if (seenEllipsis === 'upTo') {
1218
- error( 'syntax-expecting-ellipsis', location(true), // at closing bracket
1219
- { code: '... up to', newcode: '...' },
1220
- // TODO: should we be more CSN specific in the message?
1221
- '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 }' } );
1222
1213
  }
1223
1214
  return retval;
1224
1215
  }
@@ -1234,8 +1225,10 @@ function annoValue( val, spec ) {
1234
1225
  }
1235
1226
  else if (typeof val['='] === 'string') {
1236
1227
  if (Object.keys( val ).length === 1) {
1237
- virtualLine += 2;
1238
- return refSplit( val['='], '=' );
1228
+ ++virtualLine;
1229
+ const r = refSplit( val['='], '=' );
1230
+ ++virtualLine;
1231
+ return r;
1239
1232
  }
1240
1233
  }
1241
1234
  else if (val['...'] && Object.keys( val ).length === 1) {
@@ -1284,26 +1277,29 @@ function value( val, spec, xsn ) { // for CSN property 'val'
1284
1277
  xsn.literal = (val === null) ? 'null' : typeof val;
1285
1278
  return val;
1286
1279
  }
1287
- error( 'syntax-expected-scalar', location(true), { prop: spec.msgProp },
1280
+ error( 'syntax-expecting-scalar', location(true), { prop: spec.msgProp },
1288
1281
  'Only scalar values are supported for property $(PROP)' );
1289
1282
  return ignore( val );
1290
1283
  }
1291
1284
 
1292
1285
  function literal( lit, spec, xsn, csn ) {
1293
1286
  // TODO: general: requires other property (here: 'val')
1287
+ if (!string( lit ))
1288
+ return undefined;
1294
1289
  const type = (csn.val == null) ? 'null' : typeof csn.val;
1295
1290
  if (lit === type) // also for 'object' which is an error for 'val'
1296
1291
  return lit;
1297
- if (typeof lit === 'string' && quotedLiteralPatterns[lit]?.json_type === type) {
1298
- 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
1299
1296
  if (p && p.test_fn && !p.test_fn(csn.val))
1300
1297
  warning( 'syntax-invalid-literal', location(), { '#': p.test_variant } );
1301
1298
  return lit;
1302
1299
  }
1303
1300
  if (lit === 'number' && type === 'string') // special case, not a quoted literal in CDL
1304
1301
  return lit;
1305
- error( 'syntax-expected-valid', location(true), { prop: spec.msgProp },
1306
- 'Expected valid string for property $(PROP)' );
1302
+ error( 'syntax-invalid-string', location(true), { prop: spec.msgProp } );
1307
1303
  return ignore( lit );
1308
1304
  }
1309
1305
 
@@ -1317,8 +1313,8 @@ function func( val, spec, xsn ) {
1317
1313
  function xpr( exprs, spec, xsn, csn ) {
1318
1314
  if (csn.func) {
1319
1315
  if (!exprs.length) {
1320
- message( 'syntax-expected-length', location(true),
1321
- { prop: 'xpr', otherprop: 'func', '#': 'suffix' });
1316
+ error( 'syntax-incomplete-array', location(true),
1317
+ { prop: 'xpr', siblingprop: 'func', '#': 'suffix' });
1322
1318
  }
1323
1319
  xsn.suffix = exprArgs( exprs, spec );
1324
1320
  }
@@ -1350,9 +1346,9 @@ function args( exprs, spec ) {
1350
1346
  return arrayOf( exprOrString )( exprs, spec );
1351
1347
  }
1352
1348
  else if (!exprs || typeof exprs !== 'object') {
1353
- error( 'syntax-expected-args', location(true),
1349
+ error( 'syntax-expecting-args', location(true),
1354
1350
  { prop: spec.prop }, // spec.prop, not spec.msgProp!
1355
- 'Expected array or object for property $(PROP)' );
1351
+ 'Expecting array or object for property $(PROP)' );
1356
1352
  return ignore( exprs );
1357
1353
  }
1358
1354
  const r = Object.create(null);
@@ -1370,16 +1366,24 @@ function args( exprs, spec ) {
1370
1366
  }
1371
1367
 
1372
1368
  function expr( e, spec ) {
1373
- if (Array.isArray( e ) && e.length === 1) {
1374
- 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' );
1375
1373
  ++virtualLine;
1376
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 ];
1377
1381
  ++virtualLine;
1378
- return [ r || { location: location() } ];
1382
+ return r;
1379
1383
  }
1380
1384
  else if (e === null || [ 'string', 'number', 'boolean' ].includes( typeof e )) {
1381
1385
  // && spec.optional.includes( 'val' )) ?
1382
- replaceZeroValue( spec );
1386
+ replaceZeroValue( spec, 'zero-replace', '{ val: ‹value› }' );
1383
1387
  return annoValue( e, spec );
1384
1388
  }
1385
1389
  return object( e, spec );
@@ -1410,20 +1414,6 @@ function condition( cond, spec ) {
1410
1414
  };
1411
1415
  }
1412
1416
 
1413
- function vZeroValue( obj, spec, xsn ) {
1414
- if (xsn.value) {
1415
- // TODO: also "sign" xsn.value created by inValue to complain about both 'value' and 'ref' etc
1416
- warning( 'syntax-unexpected-property', location(true), { prop: spec.msgProp },
1417
- 'Unexpected CSN property $(PROP)' );
1418
- return undefined;
1419
- }
1420
- if (!csnVersionZero) {
1421
- warning( 'syntax-zero-delete', location(true), { prop: spec.msgProp },
1422
- 'Delete/inline CSN v0.1.0 property $(PROP)' );
1423
- }
1424
- return expr( obj, spec );
1425
- }
1426
-
1427
1417
  // Queries (std signature) ---------------------------------------------------
1428
1418
 
1429
1419
  function queryTerm( term, spec, xsn ) { // for CSN properties 'SELECT' and 'SET'
@@ -1465,17 +1455,16 @@ function excluding( array, spec, xsn ) {
1465
1455
  xsn.excludingDict = r;
1466
1456
  }
1467
1457
 
1468
- function masked( val, spec ) {
1469
- message('syntax-invalid-masked', location(), { keyword: 'masked' },
1470
- 'Keyword $(KEYWORD) not supported');
1471
- return boolOrNull( val, spec );
1472
- }
1473
-
1474
1458
  function duplicateExcluding( name, loc ) {
1475
- error( 'duplicate-excluding', loc, { name, keyword: 'excluding' },
1459
+ error( 'syntax-duplicate-excluding', loc, { name, keyword: 'excluding' }, // TODO: also CDL
1476
1460
  'Duplicate $(NAME) in the $(KEYWORD) clause' );
1477
1461
  }
1478
1462
 
1463
+ function masked( val, spec ) {
1464
+ message( 'syntax-unsupported-masked', location(), { '#': 'csn', prop: 'masked' } );
1465
+ return boolOrNull( val, spec );
1466
+ }
1467
+
1479
1468
  function setOp( val, spec ) { // UNION, ...
1480
1469
  // similar to string(), but without literal
1481
1470
  return string( val, spec ) && { val, location: location() };
@@ -1491,9 +1480,10 @@ function join( val, spec, xsn ) {
1491
1480
 
1492
1481
  function queryArgs( val, spec, xsn, csn ) {
1493
1482
  if (Array.isArray( val ) && val.length > 1 && !csn.op) {
1494
- warning( 'syntax-expected-property', location(true),
1495
- { prop: 'args', otherprop: 'op' },
1496
- '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)' );
1497
1487
  xsn.op = { val: 'union', location: location() };
1498
1488
  }
1499
1489
  return arrayOf( object )( val, spec ).map( q => q.query );
@@ -1510,9 +1500,9 @@ function i18nLang( val, spec, xsn, csn, langKey ) {
1510
1500
  function translations( keyVal, spec, xsn, csn, textKey ) {
1511
1501
  if (typeof keyVal === 'string') // allow empty string
1512
1502
  return { val: keyVal, literal: 'string', location: location() };
1513
- error( 'syntax-expected-translation', location(true),
1514
- { prop: textKey, otherprop: spec.prop },
1515
- '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)' );
1516
1506
  return ignore( keyVal );
1517
1507
  }
1518
1508
 
@@ -1522,21 +1512,18 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
1522
1512
  const p0 = schema[prop] ? prop : prop.charAt(0);
1523
1513
  const s = (parentSpec.schema || schema)[p0];
1524
1514
  if (!s || s.noPrefix && prop !== p0 ) {
1525
- if (ourpropsRegex.test( prop )) {
1526
- // TODO v2: Warning only with --sloppy
1527
- warning( 'syntax-unknown-property', location(true), { prop },
1528
- 'Unknown CSN property $(PROP)' );
1529
- }
1530
- else { // TODO v2: always (i.e. also with message) add to $extra
1515
+ if (!ourpropsRegex.test( prop ))
1531
1516
  return { prop, type: extra };
1532
- }
1517
+ // TODO v4: No warning with --sloppy
1518
+ warning( 'syntax-unknown-property', location(true), { prop },
1519
+ 'Unknown CSN property $(PROP)' );
1520
+ return { type: ignore };
1533
1521
  }
1534
1522
  else if (!expected( p0, s )) {
1535
1523
  if (s.ignore)
1536
1524
  return { type: ignore };
1537
1525
  if (s.vZeroIgnore && s.vZeroIgnore === csn[prop]) { // for "op": "call"
1538
- warning( 'syntax-zero-delete', location(true), { prop },
1539
- 'Delete/inline CSN v0.1.0 property $(PROP)' );
1526
+ warning( 'syntax-deprecated-property', location(true), { '#': 'zero', prop } );
1540
1527
  return { type: ignore };
1541
1528
  }
1542
1529
  const zero = s.vZeroFor;
@@ -1552,20 +1539,16 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
1552
1539
  }
1553
1540
  // eslint-disable-next-line no-nested-ternary
1554
1541
  const variant = kind && s.inKind
1555
- ? ([ 'extend', 'annotate' ].includes(kind) ? kind : 'def')
1556
- : (parentSpec.msgProp ? 'std' : 'top');
1542
+ ? ([ 'extend', 'annotate' ].includes(kind) ? kind : 'kind')
1543
+ : (parentSpec.msgProp ? 'prop' : 'top');
1557
1544
  message( 'syntax-unexpected-property', location(true),
1558
1545
  {
1559
- prop, otherprop: parentSpec.msgProp, kind, '#': variant,
1560
- },
1561
- {
1562
- std: 'CSN property $(PROP) is not expected in $(OTHERPROP)',
1563
- top: 'CSN property $(PROP) is not expected top-level',
1564
- def: 'CSN property $(PROP) is not expected by a definition of kind $(KIND)',
1565
- extend: 'CSN property $(PROP) is not expected by an extend in $(OTHERPROP)',
1566
- annotate: 'CSN property $(PROP) is not expected by an annotate in $(OTHERPROP)',
1546
+ '#': variant,
1547
+ prop,
1548
+ parentprop:
1549
+ parentSpec.msgProp,
1550
+ kind,
1567
1551
  } );
1568
- // TODO: or still augment it? (but then also handle xorGroup)
1569
1552
  }
1570
1553
  else if (checkAndSetXorGroup( s.xorGroup, s.xorException, prop, xor )) {
1571
1554
  onlyWith( s, s.onlyWith, csn, prop, xor, expected );
@@ -1612,19 +1595,18 @@ function onlyWith( spec, need, csn, prop, xor, expected ) {
1612
1595
  need = allowed.find( p => !xor[schema[p].xorGroup] ) || allowed[0];
1613
1596
  }
1614
1597
  if (prop) {
1615
- error( 'syntax-dependent-property', location(true),
1616
- { prop, otherprop: need },
1617
- '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 } );
1618
1600
  xor['no:req'] = prop;
1619
1601
  }
1602
+ // TODO: does no:req work? check test3/NestedProjections/Basics/SyntaxErrorsCsn.err.csn
1620
1603
  else if (!xor['no:req']) {
1621
- error( 'syntax-required-property', location(true),
1622
- { prop: need, otherprop: spec.msgProp, '#': spec.prop },
1623
- { // TODO $(PARENT), TODO: do not use prop===0 hack
1624
- std: 'Object in $(OTHERPROP) must have the property $(PROP)',
1625
- columns: 'Object in $(OTHERPROP) must have an expression property like $(PROP)',
1626
- // eslint-disable-next-line max-len
1627
- 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',
1628
1610
  } );
1629
1611
  }
1630
1612
  return spec;
@@ -1633,15 +1615,14 @@ function onlyWith( spec, need, csn, prop, xor, expected ) {
1633
1615
  function checkAndSetXorGroup( group, exception, prop, xor ) {
1634
1616
  if (!group)
1635
1617
  return true;
1636
- const otherprop = xor[group];
1637
- if (!otherprop) {
1618
+ const siblingprop = xor[group];
1619
+ if (!siblingprop) {
1638
1620
  xor[group] = prop;
1639
1621
  return true;
1640
1622
  }
1641
- if (otherprop === exception)
1623
+ if (siblingprop === exception)
1642
1624
  return true;
1643
- error( 'syntax-excluded-property', location(true), { prop, otherprop },
1644
- 'CSN property $(PROP) can only be used alternatively to $(OTHERPROP)');
1625
+ message( 'syntax-unexpected-property', location(true), { '#': 'sibling', prop, siblingprop } );
1645
1626
  return false;
1646
1627
  }
1647
1628
 
@@ -1651,11 +1632,11 @@ function implicitName( ref ) {
1651
1632
  return (typeof item === 'object') ? item && item.id : item;
1652
1633
  }
1653
1634
 
1654
- function replaceZeroProp( otherprop, prop ) {
1635
+ function replaceZeroProp( prop, otherprop ) {
1655
1636
  if (csnVersionZero)
1656
1637
  return;
1657
- warning( 'syntax-zero-prop', location(true), { prop, otherprop },
1658
- 'Replace CSN v0.1.0 property $(OTHERPROP) by $(PROP)' );
1638
+ warning( 'syntax-deprecated-property', location(true),
1639
+ { '#': 'zero-replace', prop, otherprop } );
1659
1640
  }
1660
1641
 
1661
1642
  // Other helper functions, locations -----------------------------------------
@@ -1663,15 +1644,15 @@ function replaceZeroProp( otherprop, prop ) {
1663
1644
  function isArray( array, spec ) {
1664
1645
  if (Array.isArray( array ))
1665
1646
  return array;
1666
- error( 'syntax-expected-array', location(true), { prop: spec.prop },
1667
- 'Expected array for property $(PROP)' );
1647
+ error( 'syntax-expecting-array', location(true), { prop: spec.prop },
1648
+ 'Expecting array for property $(PROP)' );
1668
1649
  return ignore( array );
1669
1650
  }
1670
1651
 
1671
1652
  function isObject( obj, spec ) {
1672
1653
  if (obj && typeof obj === 'object' && !Array.isArray( obj ))
1673
1654
  return obj;
1674
- error( spec.msgId || 'syntax-expected-object', location(true),
1655
+ error( spec.msgId || 'syntax-expecting-object', location(true),
1675
1656
  { prop: spec.msgProp });
1676
1657
  return ignore( obj );
1677
1658
  }
@@ -1679,16 +1660,16 @@ function isObject( obj, spec ) {
1679
1660
  function refSplit( name, prop ) {
1680
1661
  const path = name.split('.');
1681
1662
  if (!path.every( id => id)) {
1682
- warning( 'syntax-expected-name', location(true), { prop },
1683
- 'Expected correct name for property $(PROP)' );
1663
+ warning( 'syntax-expecting-name', location(true), { prop }, // TODO: re-check
1664
+ 'Expecting correct name for property $(PROP)' );
1684
1665
  }
1685
1666
  return { path: path.map( id => ({ id, location: location() }) ), location: location() };
1686
1667
  }
1687
1668
 
1688
- function replaceZeroValue( spec ) {
1689
- if (!csnVersionZero && spec.vZeroFor == null) { // but 0 does not match!
1690
- warning( 'syntax-zero-value', location(true), { prop: spec.msgProp },
1691
- '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 } );
1692
1673
  }
1693
1674
  }
1694
1675
 
@@ -1717,7 +1698,7 @@ function pushLocation( obj ) {
1717
1698
  else if (!loc || typeof loc !== 'string') {
1718
1699
  if (loc)
1719
1700
  dollarLocations.push( null ); // must match with popLocation()
1720
- error( 'syntax-expected-object', location(true), { prop: '$location' } );
1701
+ error( 'syntax-expecting-object', location(true), { prop: '$location' } );
1721
1702
  }
1722
1703
  // hidden feature: string $location
1723
1704
  const m = /:(\d+)(?::(\d+))?$/.exec( loc ); // extra '^'s at end deliberately left out
@@ -1774,7 +1755,7 @@ function toXsn( csn, filename, options, messageFunctions ) {
1774
1755
  ({ message, error, warning, info } = messageFunctions);
1775
1756
 
1776
1757
  if (csnVersionZero) {
1777
- warning( 'syntax-csn-zero-version', location(true), {},
1758
+ warning( 'syntax-deprecated-csn-version', location(true), {},
1778
1759
  'Parsing CSN version 0.1.0' );
1779
1760
  }
1780
1761
  const r = object( csn, topLevelSpec );
@@ -1830,7 +1811,8 @@ function parse( source, filename = 'csn.json', options = {}, messageFunctions =
1830
1811
  line,
1831
1812
  col: column,
1832
1813
  };
1833
- messageFunctions.error( 'syntax-illegal-json', loc, { msg }, 'Illegal JSON: $(MSG)' );
1814
+ messageFunctions.error( 'syntax-invalid-json', loc, { msg },
1815
+ 'Invalid JSON: $(MSG)' );
1834
1816
  return xsn;
1835
1817
  }
1836
1818
  }