@sap/cds-compiler 2.15.4 → 3.0.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 (105) hide show
  1. package/CHANGELOG.md +33 -1590
  2. package/bin/cdsc.js +36 -33
  3. package/doc/CHANGELOG_ARCHIVE.md +1592 -0
  4. package/doc/CHANGELOG_BETA.md +3 -4
  5. package/doc/CHANGELOG_DEPRECATED.md +35 -1
  6. package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
  7. package/doc/Versioning.md +20 -1
  8. package/lib/api/.eslintrc.json +2 -2
  9. package/lib/api/main.js +220 -103
  10. package/lib/api/options.js +15 -85
  11. package/lib/api/validate.js +6 -10
  12. package/lib/base/keywords.js +216 -109
  13. package/lib/base/message-registry.js +60 -20
  14. package/lib/base/messages.js +65 -24
  15. package/lib/base/model.js +44 -2
  16. package/lib/checks/actionsFunctions.js +7 -5
  17. package/lib/checks/annotationsOData.js +1 -1
  18. package/lib/checks/cdsPersistence.js +1 -0
  19. package/lib/checks/elements.js +6 -6
  20. package/lib/checks/invalidTarget.js +1 -1
  21. package/lib/checks/nonexpandableStructured.js +1 -1
  22. package/lib/checks/queryNoDbArtifacts.js +2 -1
  23. package/lib/checks/selectItems.js +5 -1
  24. package/lib/checks/types.js +4 -2
  25. package/lib/checks/utils.js +2 -2
  26. package/lib/checks/validator.js +2 -1
  27. package/lib/compiler/assert-consistency.js +15 -10
  28. package/lib/compiler/builtins.js +87 -9
  29. package/lib/compiler/define.js +2 -2
  30. package/lib/compiler/extend.js +59 -11
  31. package/lib/compiler/finalize-parse-cdl.js +20 -9
  32. package/lib/compiler/index.js +25 -11
  33. package/lib/compiler/moduleLayers.js +7 -0
  34. package/lib/compiler/populate.js +13 -13
  35. package/lib/compiler/propagator.js +3 -3
  36. package/lib/compiler/resolve.js +193 -218
  37. package/lib/compiler/shared.js +47 -76
  38. package/lib/compiler/tweak-assocs.js +9 -10
  39. package/lib/compiler/utils.js +5 -0
  40. package/lib/edm/csn2edm.js +18 -21
  41. package/lib/edm/edmPreprocessor.js +25 -30
  42. package/lib/edm/edmUtils.js +10 -24
  43. package/lib/gen/language.checksum +1 -1
  44. package/lib/gen/language.interp +8 -30
  45. package/lib/gen/language.tokens +105 -114
  46. package/lib/gen/languageLexer.interp +1 -34
  47. package/lib/gen/languageLexer.js +889 -1007
  48. package/lib/gen/languageLexer.tokens +95 -106
  49. package/lib/gen/languageParser.js +20632 -22313
  50. package/lib/json/from-csn.js +56 -49
  51. package/lib/json/to-csn.js +10 -8
  52. package/lib/language/antlrParser.js +2 -2
  53. package/lib/language/docCommentParser.js +61 -38
  54. package/lib/language/errorStrategy.js +52 -40
  55. package/lib/language/genericAntlrParser.js +303 -229
  56. package/lib/language/language.g4 +573 -629
  57. package/lib/language/multiLineStringParser.js +14 -42
  58. package/lib/language/textUtils.js +44 -0
  59. package/lib/main.d.ts +27 -42
  60. package/lib/main.js +104 -81
  61. package/lib/model/csnRefs.js +1 -1
  62. package/lib/model/csnUtils.js +170 -283
  63. package/lib/model/revealInternalProperties.js +28 -8
  64. package/lib/model/sortViews.js +32 -31
  65. package/lib/optionProcessor.js +12 -21
  66. package/lib/render/.eslintrc.json +1 -1
  67. package/lib/render/DuplicateChecker.js +4 -7
  68. package/lib/render/manageConstraints.js +70 -2
  69. package/lib/render/toCdl.js +334 -339
  70. package/lib/render/toHdbcds.js +19 -15
  71. package/lib/render/toRename.js +44 -22
  72. package/lib/render/toSql.js +53 -51
  73. package/lib/render/utils/common.js +15 -1
  74. package/lib/render/utils/sql.js +20 -19
  75. package/lib/sql-identifier.js +6 -0
  76. package/lib/transform/db/.eslintrc.json +3 -2
  77. package/lib/transform/db/cdsPersistence.js +5 -15
  78. package/lib/transform/db/constraints.js +1 -1
  79. package/lib/transform/db/expansion.js +7 -6
  80. package/lib/transform/db/flattening.js +18 -19
  81. package/lib/transform/db/views.js +3 -3
  82. package/lib/transform/draft/.eslintrc.json +2 -2
  83. package/lib/transform/draft/db.js +6 -6
  84. package/lib/transform/draft/odata.js +6 -7
  85. package/lib/transform/forHanaNew.js +19 -22
  86. package/lib/transform/forOdataNew.js +10 -12
  87. package/lib/transform/localized.js +22 -16
  88. package/lib/transform/odata/toFinalBaseType.js +10 -10
  89. package/lib/transform/odata/typesExposure.js +3 -3
  90. package/lib/transform/odata/utils.js +1 -38
  91. package/lib/transform/transformUtilsNew.js +63 -77
  92. package/lib/transform/translateAssocsToJoins.js +2 -2
  93. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  94. package/lib/transform/universalCsn/coreComputed.js +11 -6
  95. package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
  96. package/lib/utils/file.js +3 -3
  97. package/lib/utils/timetrace.js +20 -21
  98. package/package.json +35 -4
  99. package/doc/ApiMigration.md +0 -237
  100. package/doc/CommandLineMigration.md +0 -58
  101. package/doc/ErrorMessages.md +0 -175
  102. package/doc/FioriAnnotations.md +0 -94
  103. package/doc/ODataTransformation.md +0 -273
  104. package/lib/backends.js +0 -529
  105. package/lib/fix_antlr4-8_warning.js +0 -56
@@ -135,7 +135,7 @@ const schemaClasses = {
135
135
  condition: {
136
136
  arrayOf: exprOrString,
137
137
  type: condition,
138
- msgId: 'syntax-csn-expected-term',
138
+ msgId: 'syntax-expected-term',
139
139
  // TODO: also specify requires here, and adapt onlyWith()
140
140
  optional: exprProperties,
141
141
  },
@@ -145,11 +145,11 @@ const schemaClasses = {
145
145
  },
146
146
  natnumOrStar: {
147
147
  type: natnumOrStar,
148
- msgId: 'syntax-csn-expected-cardinality',
148
+ msgId: 'syntax-expected-cardinality',
149
149
  },
150
150
  columns: {
151
151
  arrayOf: selectItem,
152
- msgId: 'syntax-csn-expected-column',
152
+ msgId: 'syntax-expected-column',
153
153
  defaultKind: '$column',
154
154
  validKinds: [], // pseudo kind '$column'
155
155
  // A column with only as+cast.type is a new association
@@ -290,7 +290,7 @@ const schema = compileSchema( {
290
290
  // type properties (except: elements, enum, keys, on): ---------------------
291
291
  type: {
292
292
  type: artifactRef,
293
- msgId: 'syntax-csn-expected-reference',
293
+ msgId: 'syntax-expected-reference',
294
294
  optional: [ 'ref', 'global' ],
295
295
  inKind: [ 'element', 'type', 'param', 'mixin', 'event', 'annotation' ],
296
296
  },
@@ -366,7 +366,7 @@ const schema = compileSchema( {
366
366
  ref: {
367
367
  arrayOf: refItem,
368
368
  type: renameTo( 'path', arrayOf( refItem ) ),
369
- msgId: 'syntax-csn-expected-reference',
369
+ msgId: 'syntax-expected-reference',
370
370
  minLength: 1,
371
371
  requires: 'id',
372
372
  optional: [ 'id', 'args', 'cardinality', 'where' ],
@@ -582,7 +582,7 @@ const schema = compileSchema( {
582
582
  inKind: [ 'element', '$column' ],
583
583
  },
584
584
  masked: {
585
- type: boolOrNull,
585
+ type: masked,
586
586
  inKind: [ 'element' ],
587
587
  },
588
588
  notNull: {
@@ -779,7 +779,7 @@ function arrayOf( fn, filter = undefined ) {
779
779
  } );
780
780
  const minLength = spec.minLength || 0;
781
781
  if (minLength > val.length) {
782
- message( 'syntax-csn-expected-length', location(true),
782
+ message( 'syntax-expected-length', location(true),
783
783
  { prop: spec.prop, n: minLength, '#': minLength === 1 ? 'one' : 'std' });
784
784
  }
785
785
  if (val.length)
@@ -849,7 +849,7 @@ function object( obj, spec ) {
849
849
  if (requires === undefined || requires === true) {
850
850
  // console.log(csnProps,JSON.stringify(spec))
851
851
  if (!relevantProps) {
852
- error( 'syntax-csn-required-subproperty', location(true),
852
+ error( 'syntax-required-subproperty', location(true),
853
853
  {
854
854
  prop: spec.msgProp,
855
855
  '#': (
@@ -875,7 +875,7 @@ function object( obj, spec ) {
875
875
 
876
876
  function vZeroDelete( o, spec ) { // for old-CSN property 'origin'
877
877
  if (!csnVersionZero) {
878
- warning( 'syntax-csn-zero-delete', location(true), { prop: spec.msgProp },
878
+ warning( 'syntax-zero-delete', location(true), { prop: spec.msgProp },
879
879
  'Delete/inline CSN v0.1.0 property $(PROP)' );
880
880
  }
881
881
  string( o, spec );
@@ -960,7 +960,7 @@ function definition( def, spec, xsn, csn, name ) {
960
960
  function dictionaryOf( elementFct ) {
961
961
  return function dictionary( dict, spec ) {
962
962
  if (!dict || typeof dict !== 'object' || Array.isArray( dict )) {
963
- error( 'syntax-csn-expected-object', location(true),
963
+ error( 'syntax-expected-object', location(true),
964
964
  { prop: spec.prop }); // spec.prop, not spec.msgProp!
965
965
  return ignore( dict );
966
966
  }
@@ -972,7 +972,7 @@ function dictionaryOf( elementFct ) {
972
972
  ++virtualLine;
973
973
  for (const name of allNames) {
974
974
  if (!name) {
975
- warning( 'syntax-csn-empty-name', location(true),
975
+ warning( 'syntax-empty-name', location(true),
976
976
  { prop: spec.prop }, // TODO: Error
977
977
  'Property names in dictionary $(PROP) must not be empty' );
978
978
  }
@@ -1049,11 +1049,11 @@ function validKind( val, spec, xsn ) {
1049
1049
  if (val === xsn.kind) // has been set in definition - the same = ok!
1050
1050
  return undefined; // already set in definition
1051
1051
  if (val === 'view' && xsn.kind === 'entity') {
1052
- warning( 'syntax-csn-zero-value', location(true), { prop: spec.msgProp },
1052
+ warning( 'syntax-zero-value', location(true), { prop: spec.msgProp },
1053
1053
  'Replace CSN v0.1.0 value in $(PROP) by something specified' );
1054
1054
  }
1055
1055
  else if ((val === 'entity' || val === 'type') && xsn.kind === 'aspect') {
1056
- info( 'syntax-csn-aspect', location(true), { kind: 'aspect', '#': val },
1056
+ info( 'syntax-aspect', location(true), { kind: 'aspect', '#': val },
1057
1057
  {
1058
1058
  std: 'Use the dedicated kind $(KIND) for aspect definitions',
1059
1059
  // eslint-disable-next-line max-len
@@ -1061,7 +1061,7 @@ function validKind( val, spec, xsn ) {
1061
1061
  } );
1062
1062
  }
1063
1063
  else {
1064
- error( 'syntax-csn-expected-valid', location(true), { prop: spec.msgProp },
1064
+ error( 'syntax-expected-valid', location(true), { prop: spec.msgProp },
1065
1065
  'Expected valid string for property $(PROP)' );
1066
1066
  }
1067
1067
  return ignore( val );
@@ -1103,7 +1103,7 @@ function vZeroRef( name, spec, xsn ) {
1103
1103
  return;
1104
1104
  const path = name.split('.');
1105
1105
  if (!path.every( id => id)) {
1106
- warning( 'syntax-csn-expected-name', location(true), { prop: spec.msgProp },
1106
+ warning( 'syntax-expected-name', location(true), { prop: spec.msgProp },
1107
1107
  'Expected correct name for property $(PROP)' );
1108
1108
  }
1109
1109
  xsn.path = path.map( id => ({ id, location: location() }) );
@@ -1114,7 +1114,7 @@ function vZeroRef( name, spec, xsn ) {
1114
1114
  function boolOrNull( val, spec ) {
1115
1115
  if ([ true, false, null ].includes( val ))
1116
1116
  return { val, location: location() };
1117
- warning( 'syntax-csn-expected-boolean', location(true), { prop: spec.msgProp },
1117
+ warning( 'syntax-expected-boolean', location(true), { prop: spec.msgProp },
1118
1118
  'Expected boolean or null for property $(PROP)' );
1119
1119
  ignore( val );
1120
1120
  return { val: !!val, location: location() };
@@ -1124,7 +1124,7 @@ function string( val, spec ) {
1124
1124
  if (typeof val === 'string' && val)
1125
1125
  // XSN TODO: do not require literal
1126
1126
  return val;
1127
- error( 'syntax-csn-expected-string', location(true), { prop: spec.msgProp },
1127
+ error( 'syntax-expected-string', location(true), { prop: spec.msgProp },
1128
1128
  'Expected non-empty string for property $(PROP)' );
1129
1129
  return ignore( val );
1130
1130
  }
@@ -1133,7 +1133,7 @@ function stringVal( val, spec ) {
1133
1133
  if (typeof val === 'string' && val)
1134
1134
  // XSN TODO: do not require literal
1135
1135
  return { val, literal: 'string', location: location() };
1136
- error( 'syntax-csn-expected-string', location(true), { prop: spec.msgProp },
1136
+ error( 'syntax-expected-string', location(true), { prop: spec.msgProp },
1137
1137
  'Expected non-empty string for property $(PROP)' );
1138
1138
  return ignore( val );
1139
1139
  }
@@ -1155,7 +1155,7 @@ function natnum( val, spec ) {
1155
1155
  if (typeof val === 'number' && val >= 0)
1156
1156
  // XSN TODO: do not require literal
1157
1157
  return { val, literal: 'number', location: location() };
1158
- error( spec.msgId || 'syntax-csn-expected-natnum', location(true),
1158
+ error( spec.msgId || 'syntax-expected-natnum', location(true),
1159
1159
  { prop: spec.msgProp } );
1160
1160
  return ignore( val );
1161
1161
  }
@@ -1190,10 +1190,8 @@ function annoValue( val, spec ) {
1190
1190
  /** @type {string|boolean} */
1191
1191
  let seenEllipsis = false;
1192
1192
  if (arrayLevelCount > 0) { // TODO: also inside structure (possible in CSN!)
1193
- if (val.some( isEllipsis )) {
1194
- error( 'syntax-csn-unexpected-ellipsis', location(true), { code: '...' },
1195
- 'Unexpected $(CODE) in nested array' );
1196
- }
1193
+ if (val.some( isEllipsis ))
1194
+ error( 'syntax-unexpected-ellipsis', location(true), { '#': 'nested-array', code: '...' } );
1197
1195
  }
1198
1196
  else {
1199
1197
  for (const item of val) {
@@ -1202,7 +1200,7 @@ function annoValue( val, spec ) {
1202
1200
  }
1203
1201
  else if (isEllipsis( item )) { // with or without UP TO
1204
1202
  // error position at the beginning of the array, but that is fine
1205
- error( 'syntax-csn-duplicate-ellipsis', location(true), { code: '...' },
1203
+ error( 'syntax-duplicate-ellipsis', location(true), { code: '...' },
1206
1204
  'Expected no more than one $(CODE)' );
1207
1205
  break;
1208
1206
  }
@@ -1216,7 +1214,7 @@ function annoValue( val, spec ) {
1216
1214
  };
1217
1215
  arrayLevelCount--;
1218
1216
  if (seenEllipsis === 'upTo') {
1219
- error( 'syntax-csn-expecting-ellipsis', location(true), // at closing bracket
1217
+ error( 'syntax-expecting-ellipsis', location(true), // at closing bracket
1220
1218
  { code: '... up to', newcode: '...' },
1221
1219
  // TODO: should we be more CSN specific in the message?
1222
1220
  'Expecting an array item $(NEWCODE) after an item with $(CODE)' );
@@ -1263,11 +1261,14 @@ function annoValue( val, spec ) {
1263
1261
  }
1264
1262
 
1265
1263
  function annotation( val, spec, xsn, csn, name ) {
1266
- const variantIndex = name.indexOf('#') + 1 || name.length;
1267
- const n = refSplit( name.substring( (xsn ? 1 : 0), variantIndex ), spec.msgProp );
1264
+ const absolute = (xsn ? name.substring(1) : name);
1265
+ // TODO: really care about variant (qualifier parts)?
1266
+ const variantIndex = absolute.indexOf('#') + 1 || absolute.length;
1267
+ const n = refSplit( absolute.substring( 0, variantIndex ), spec.msgProp );
1268
1268
  if (!n)
1269
1269
  return undefined;
1270
- if (variantIndex < name.length)
1270
+ n.absolute = absolute;
1271
+ if (variantIndex < absolute.length)
1271
1272
  n.variant = { id: name.substring( variantIndex ), location: location() };
1272
1273
  const r = annoValue( val, spec );
1273
1274
  r.name = n;
@@ -1282,7 +1283,7 @@ function value( val, spec, xsn ) { // for CSN property 'val'
1282
1283
  xsn.literal = (val === null) ? 'null' : typeof val;
1283
1284
  return val;
1284
1285
  }
1285
- error( 'syntax-csn-expected-scalar', location(true), { prop: spec.msgProp },
1286
+ error( 'syntax-expected-scalar', location(true), { prop: spec.msgProp },
1286
1287
  'Only scalar values are supported for property $(PROP)' );
1287
1288
  return ignore( val );
1288
1289
  }
@@ -1294,7 +1295,7 @@ function literal( val, spec, xsn, csn ) {
1294
1295
  return val;
1295
1296
  if (typeof val === 'string' && validLiteralsExtra[val] === type)
1296
1297
  return val;
1297
- error( 'syntax-csn-expected-valid', location(true), { prop: spec.msgProp },
1298
+ error( 'syntax-expected-valid', location(true), { prop: spec.msgProp },
1298
1299
  'Expected valid string for property $(PROP)' );
1299
1300
  return ignore( val );
1300
1301
  }
@@ -1309,7 +1310,7 @@ function func( val, spec, xsn ) {
1309
1310
  function xpr( exprs, spec, xsn, csn ) {
1310
1311
  if (csn.func) {
1311
1312
  if (!exprs.length) {
1312
- message( 'syntax-csn-expected-length', location(true),
1313
+ message( 'syntax-expected-length', location(true),
1313
1314
  { prop: 'xpr', otherprop: 'func', '#': 'suffix' });
1314
1315
  }
1315
1316
  xsn.suffix = exprArgs( exprs, spec );
@@ -1342,7 +1343,7 @@ function args( exprs, spec ) {
1342
1343
  return arrayOf( exprOrString )( exprs, spec );
1343
1344
  }
1344
1345
  else if (!exprs || typeof exprs !== 'object') {
1345
- error( 'syntax-csn-expected-args', location(true),
1346
+ error( 'syntax-expected-args', location(true),
1346
1347
  { prop: spec.prop }, // spec.prop, not spec.msgProp!
1347
1348
  'Expected array or object for property $(PROP)' );
1348
1349
  return ignore( exprs );
@@ -1405,12 +1406,12 @@ function condition( cond, spec ) {
1405
1406
  function vZeroValue( obj, spec, xsn ) {
1406
1407
  if (xsn.value) {
1407
1408
  // TODO: also "sign" xsn.value created by inValue to complain about both 'value' and 'ref' etc
1408
- warning( 'syntax-csn-unexpected-property', location(true), { prop: spec.msgProp },
1409
+ warning( 'syntax-unexpected-property', location(true), { prop: spec.msgProp },
1409
1410
  'Unexpected CSN property $(PROP)' );
1410
1411
  return undefined;
1411
1412
  }
1412
1413
  if (!csnVersionZero) {
1413
- warning( 'syntax-csn-zero-delete', location(true), { prop: spec.msgProp },
1414
+ warning( 'syntax-zero-delete', location(true), { prop: spec.msgProp },
1414
1415
  'Delete/inline CSN v0.1.0 property $(PROP)' );
1415
1416
  }
1416
1417
  return expr( obj, spec );
@@ -1457,6 +1458,12 @@ function excluding( array, spec, xsn ) {
1457
1458
  xsn.excludingDict = r;
1458
1459
  }
1459
1460
 
1461
+ function masked( val, spec ) {
1462
+ message('syntax-invalid-masked', location(), { keyword: 'masked' },
1463
+ 'Keyword $(KEYWORD) not supported');
1464
+ return boolOrNull( val, spec );
1465
+ }
1466
+
1460
1467
  function duplicateExcluding( name, loc ) {
1461
1468
  error( 'duplicate-excluding', loc, { name, keyword: 'excluding' },
1462
1469
  'Duplicate $(NAME) in the $(KEYWORD) clause' );
@@ -1477,7 +1484,7 @@ function join( val, spec, xsn ) {
1477
1484
 
1478
1485
  function queryArgs( val, spec, xsn, csn ) {
1479
1486
  if (Array.isArray( val ) && val.length > 1 && !csn.op) {
1480
- warning( 'syntax-csn-expected-property', location(true),
1487
+ warning( 'syntax-expected-property', location(true),
1481
1488
  { prop: 'args', otherprop: 'op' },
1482
1489
  'CSN property $(PROP) expects property $(OTHERPROP) to be specified' );
1483
1490
  xsn.op = { val: 'union', location: location() };
@@ -1496,7 +1503,7 @@ function i18nLang( val, spec, xsn, csn, langKey ) {
1496
1503
  function translations( keyVal, spec, xsn, csn, textKey ) {
1497
1504
  if (typeof keyVal === 'string') // allow empty string
1498
1505
  return { val: keyVal, literal: 'string', location: location() };
1499
- error( 'syntax-csn-expected-translation', location(true),
1506
+ error( 'syntax-expected-translation', location(true),
1500
1507
  { prop: textKey, otherprop: spec.prop },
1501
1508
  'Expected string for text key $(PROP) of language $(OTHERPROP)' );
1502
1509
  return ignore( keyVal );
@@ -1510,7 +1517,7 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
1510
1517
  if (!s || s.noPrefix && prop !== p0 ) {
1511
1518
  if (ourpropsRegex.test( prop )) {
1512
1519
  // TODO v2: Warning only with --sloppy
1513
- warning( 'syntax-csn-unknown-property', location(true), { prop },
1520
+ warning( 'syntax-unknown-property', location(true), { prop },
1514
1521
  'Unknown CSN property $(PROP)' );
1515
1522
  }
1516
1523
  else { // TODO v2: always (i.e. also with message) add to $extra
@@ -1521,7 +1528,7 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
1521
1528
  if (s.ignore)
1522
1529
  return { type: ignore };
1523
1530
  if (s.vZeroIgnore && s.vZeroIgnore === csn[prop]) { // for "op": "call"
1524
- warning( 'syntax-csn-zero-delete', location(true), { prop },
1531
+ warning( 'syntax-zero-delete', location(true), { prop },
1525
1532
  'Delete/inline CSN v0.1.0 property $(PROP)' );
1526
1533
  return { type: ignore };
1527
1534
  }
@@ -1540,7 +1547,7 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
1540
1547
  const variant = kind && s.inKind
1541
1548
  ? ([ 'extend', 'annotate' ].includes(kind) ? kind : 'def')
1542
1549
  : (parentSpec.msgProp ? 'std' : 'top');
1543
- message( 'syntax-csn-unexpected-property', location(true),
1550
+ message( 'syntax-unexpected-property', location(true),
1544
1551
  {
1545
1552
  prop, otherprop: parentSpec.msgProp, kind, '#': variant,
1546
1553
  },
@@ -1598,13 +1605,13 @@ function onlyWith( spec, need, csn, prop, xor, expected ) {
1598
1605
  need = allowed.find( p => !xor[schema[p].xorGroup] ) || allowed[0];
1599
1606
  }
1600
1607
  if (prop) {
1601
- error( 'syntax-csn-dependent-property', location(true),
1608
+ error( 'syntax-dependent-property', location(true),
1602
1609
  { prop, otherprop: need },
1603
1610
  'CSN property $(PROP) can only be used in combination with $(OTHERPROP)');
1604
1611
  xor['no:req'] = prop;
1605
1612
  }
1606
1613
  else if (!xor['no:req']) {
1607
- error( 'syntax-csn-required-property', location(true),
1614
+ error( 'syntax-required-property', location(true),
1608
1615
  { prop: need, otherprop: spec.msgProp, '#': spec.prop },
1609
1616
  { // TODO $(PARENT), TODO: do not use prop===0 hack
1610
1617
  std: 'Object in $(OTHERPROP) must have the property $(PROP)',
@@ -1626,7 +1633,7 @@ function checkAndSetXorGroup( group, prop, xor ) {
1626
1633
  if (prop === 'func' && xor[group] === 'xpr' ||
1627
1634
  prop === 'xpr' && xor[group] === 'func')
1628
1635
  return true; // hack for window function: both func and xpr is allowed
1629
- error( 'syntax-csn-excluded-property', location(true),
1636
+ error( 'syntax-excluded-property', location(true),
1630
1637
  { prop, otherprop: xor[group] },
1631
1638
  'CSN property $(PROP) can only be used alternatively to $(OTHERPROP)');
1632
1639
  return false;
@@ -1641,7 +1648,7 @@ function implicitName( ref ) {
1641
1648
  function replaceZeroProp( otherprop, prop ) {
1642
1649
  if (csnVersionZero)
1643
1650
  return;
1644
- warning( 'syntax-csn-zero-prop', location(true), { prop, otherprop },
1651
+ warning( 'syntax-zero-prop', location(true), { prop, otherprop },
1645
1652
  'Replace CSN v0.1.0 property $(OTHERPROP) by $(PROP)' );
1646
1653
  }
1647
1654
 
@@ -1650,7 +1657,7 @@ function replaceZeroProp( otherprop, prop ) {
1650
1657
  function isArray( array, spec ) {
1651
1658
  if (Array.isArray( array ))
1652
1659
  return array;
1653
- error( 'syntax-csn-expected-array', location(true), { prop: spec.prop },
1660
+ error( 'syntax-expected-array', location(true), { prop: spec.prop },
1654
1661
  'Expected array for property $(PROP)' );
1655
1662
  return ignore( array );
1656
1663
  }
@@ -1658,7 +1665,7 @@ function isArray( array, spec ) {
1658
1665
  function isObject( obj, spec ) {
1659
1666
  if (obj && typeof obj === 'object' && !Array.isArray( obj ))
1660
1667
  return obj;
1661
- error( spec.msgId || 'syntax-csn-expected-object', location(true),
1668
+ error( spec.msgId || 'syntax-expected-object', location(true),
1662
1669
  { prop: spec.msgProp });
1663
1670
  return ignore( obj );
1664
1671
  }
@@ -1666,7 +1673,7 @@ function isObject( obj, spec ) {
1666
1673
  function refSplit( name, prop ) {
1667
1674
  const path = name.split('.');
1668
1675
  if (!path.every( id => id)) {
1669
- warning( 'syntax-csn-expected-name', location(true), { prop },
1676
+ warning( 'syntax-expected-name', location(true), { prop },
1670
1677
  'Expected correct name for property $(PROP)' );
1671
1678
  }
1672
1679
  return { path: path.map( id => ({ id, location: location() }) ), location: location() };
@@ -1674,7 +1681,7 @@ function refSplit( name, prop ) {
1674
1681
 
1675
1682
  function replaceZeroValue( spec ) {
1676
1683
  if (!csnVersionZero && spec.vZeroFor == null) { // but 0 does not match!
1677
- warning( 'syntax-csn-zero-value', location(true), { prop: spec.msgProp },
1684
+ warning( 'syntax-zero-value', location(true), { prop: spec.msgProp },
1678
1685
  'Replace CSN v0.1.0 value in $(PROP) by something specified' );
1679
1686
  }
1680
1687
  }
@@ -1704,7 +1711,7 @@ function pushLocation( obj ) {
1704
1711
  else if (!loc || typeof loc !== 'string') {
1705
1712
  if (loc)
1706
1713
  dollarLocations.push( null ); // must match with popLocation()
1707
- error( 'syntax-csn-expected-object', location(true), { prop: '$location' } );
1714
+ error( 'syntax-expected-object', location(true), { prop: '$location' } );
1708
1715
  }
1709
1716
  // hidden feature: string $location
1710
1717
  const m = /:(\d+)(?::(\d+))?$/.exec( loc ); // extra '^'s at end deliberately left out
@@ -1817,7 +1824,7 @@ function parse( source, filename = 'csn.json', options = {}, messageFunctions )
1817
1824
  line,
1818
1825
  col: column,
1819
1826
  };
1820
- messageFunctions.error( 'syntax-csn-illegal-json', loc, { msg }, 'Illegal JSON: $(MSG)' );
1827
+ messageFunctions.error( 'syntax-illegal-json', loc, { msg }, 'Illegal JSON: $(MSG)' );
1821
1828
  return xsn;
1822
1829
  }
1823
1830
  }
@@ -222,7 +222,7 @@ const operators = {
222
222
  unboundedFollowing: [ 'unbounded', 'following' ],
223
223
  following: postfix( [ 'following' ] ),
224
224
  frameBetween: exprs => [ 'between', ...exprs[0], 'and', ...exprs[1] ],
225
- // xpr: (exprs) => [].concat( ...exprs ), see below - handled extra
225
+ ixpr: exprs => [].concat( ...exprs ), // xpr extra, due to extra parentheses
226
226
  };
227
227
 
228
228
  const csnDictionaries = [
@@ -725,7 +725,8 @@ function annotationsAndDocComment( node, annotated ) {
725
725
  const val = node[prop];
726
726
  // val.$priority isn't set for computed annotations like @Core.Computed
727
727
  // and @odata.containment.ignore
728
- if (val.$priority && (val.$priority !== 'define') === annotated) {
728
+ // TODO: use $inferred instead special $priority value
729
+ if (val.$priority !== undefined && (!!val.$priority) === annotated) {
729
730
  // transformer (= value) takes care to exclude $inferred annotation assignments
730
731
  const sub = transformer( val );
731
732
  // As value() just has one value, so we do not provide ( val, csn, node, prop )
@@ -1588,10 +1589,11 @@ function compactExpr( e ) { // TODO: options
1588
1589
  */
1589
1590
  function initModuleVars( options = { csnFlavor: 'gensrc' } ) {
1590
1591
  gensrcFlavor = options.parseCdl || options.csnFlavor === 'gensrc' ||
1591
- options.toCsn && options.toCsn.flavor === 'gensrc';
1592
- universalCsn = (options.csnFlavor === 'universal' ||
1593
- options.toCsn && options.toCsn.flavor === 'universal' ) &&
1594
- isBetaEnabled( options, 'enableUniversalCsn' ) && !options.parseCdl;
1592
+ ( options.toCsn && options.toCsn.flavor === 'gensrc');
1593
+ universalCsn = ( options.csnFlavor === 'universal' ||
1594
+ ( options.toCsn && options.toCsn.flavor === 'universal') ) &&
1595
+ isBetaEnabled( options, 'enableUniversalCsn' ) &&
1596
+ !options.parseCdl;
1595
1597
  strictMode = options.testMode;
1596
1598
  const proto = options.dictionaryPrototype;
1597
1599
  // eslint-disable-next-line no-nested-ternary
@@ -1599,8 +1601,8 @@ function initModuleVars( options = { csnFlavor: 'gensrc' } ) {
1599
1601
  ? proto
1600
1602
  : (proto) ? Object.prototype : null;
1601
1603
  withLocations = options.withLocations;
1602
- parensAsStrings = isDeprecatedEnabled( options, 'parensAsStrings' );
1603
- projectionAsQuery = isDeprecatedEnabled( options, 'projectionAsQuery' );
1604
+ parensAsStrings = isDeprecatedEnabled( options, '_parensAsStrings' );
1605
+ projectionAsQuery = isDeprecatedEnabled( options, '_projectionAsQuery' );
1604
1606
  }
1605
1607
 
1606
1608
  module.exports = {
@@ -13,8 +13,8 @@ const antlr4 = require('antlr4');
13
13
  const { CompileMessage } = require('../base/messages');
14
14
  const errorStrategy = require('./errorStrategy');
15
15
 
16
- const Parser = require('../gen/languageParser').languageParser;
17
- const Lexer = require('../gen/languageLexer').languageLexer;
16
+ const Parser = require('../gen/languageParser').default;
17
+ const Lexer = require('../gen/languageLexer').default;
18
18
 
19
19
  // Error listener used for ANTLR4-generated parser
20
20
  class ErrorListener extends antlr4.error.ErrorListener {
@@ -1,6 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const { splitLines } = require('../utils/file');
3
+ const {
4
+ isWhitespaceOrNewLineOnly,
5
+ isWhitespaceCharacterNoNewline,
6
+ cdlNewLineRegEx,
7
+ } = require('./textUtils');
4
8
 
5
9
  /**
6
10
  * Get the content of a JSDoc-like comment and remove all surrounding asterisks, etc.
@@ -18,39 +22,40 @@ function parseDocComment(comment) {
18
22
  if (comment.length <= 5) // at least "/***/"
19
23
  return null;
20
24
 
21
- let lines = splitLines(comment);
25
+ let lines = comment.split(cdlNewLineRegEx);
22
26
 
23
27
  if (lines.length === 1) {
24
- // special case for one-liners
25
- // remove "/***/" and trim white space
26
- const content = lines[0].replace(/^\/[*]{2,}/, '').replace(/\*\/$/, '').trim();
27
- return isWhiteSpaceOnly(content) ? null : content;
28
+ // Special case for one-liners.
29
+ // Remove "/***/" and trim white space and asterisks.
30
+ const content = lines[0]
31
+ .replace(/^\/[*]{2,}/, '')
32
+ .replace(/\*+\/$/, '')
33
+ .trim();
34
+ return isWhitespaceOrNewLineOnly(content) ? null : content;
28
35
  }
29
36
 
30
- lines[0] = removeHeaderFence(lines[0]);
37
+ // If the comment already has content on the first line, i.e. after `/**`,
38
+ // its leading whitespace is ignored for whitespace trimming.
39
+ const hasContentOnFirstLine = /\/\*+\s*\S/.test(lines[0]);
40
+
41
+ // First line, i.e. header, is always trimmed from left.
42
+ lines[0] = removeHeaderFence(lines[0]).trimStart();
31
43
  lines[lines.length - 1] = removeFooterFence(lines[lines.length - 1]);
32
44
 
33
- if (isFencedComment(lines)) {
34
- lines = lines.map((line, index) => ((index === 0) ? line : removeFence(line)));
35
- }
36
- else if (lines.length === 2) {
45
+ if (lines.length === 2) {
37
46
  // Comment that is essentially just a header + footer.
38
- // First line, i.e. header, is always trimmed from left.
39
- lines[0] = lines[0].trimStart();
40
-
41
47
  // If the second line starts with an asterisk then remove it.
42
- // Otherwise trim all whitespace.
48
+ // Otherwise, trim all left whitespace.
43
49
  if ((/^\s*[*]/.test(lines[1])))
44
50
  lines[1] = removeFence(lines[1]);
45
51
  else
46
52
  lines[1] = lines[1].trimStart();
47
53
  }
54
+ else if (isFencedComment(lines)) {
55
+ lines = lines.map((line, index) => ((index === 0) ? line : removeFence(line)));
56
+ }
48
57
  else {
49
- const firstNonEmptyLine = lines.find((line, index) => index !== 0 && /[^\s]/.test(line)) || '';
50
- // Tabs are regarded as one space.
51
- const spacesAtBeginning = firstNonEmptyLine.match(/^\s*/)[0].length;
52
- if (spacesAtBeginning > 0)
53
- lines = lines.map(line => removeWhitespace(line, spacesAtBeginning));
58
+ stripCommentIndentation(lines, hasContentOnFirstLine);
54
59
  }
55
60
 
56
61
  // Remove empty header and footer.
@@ -58,18 +63,47 @@ function parseDocComment(comment) {
58
63
  const endIndex = (lines[lines.length - 1] === '') ? lines.length - 1 : lines.length;
59
64
 
60
65
  const content = lines.slice(startIndex, endIndex).join('\n');
61
-
62
- return isWhiteSpaceOnly(content) ? null : content;
66
+ return isWhitespaceOrNewLineOnly(content) ? null : content;
63
67
  }
64
68
 
65
69
  /**
66
- * Checks whether the given string is whitespace only, i.e. newline
67
- * spaces, tabs.
70
+ * Strips and counts the indentation from the given comment string.
71
+ * This function is similar to the one in multiLineStringParser.js, but does not
72
+ * have special handling for the first and last line of the string.
73
+ *
74
+ * @example
75
+ * | hello
76
+ * | world
77
+ * | foo bar
78
+ * becomes
79
+ * | hello
80
+ * | world
81
+ * | foo bar
68
82
  *
69
- * @param {string} content
83
+ * @param {string[]} lines String split into lines.
84
+ * @param {boolean} ignoreFirstLine Whether to ignore the first line for indentation counting.
70
85
  */
71
- function isWhiteSpaceOnly(content) {
72
- return /^\s*$/.test(content);
86
+ function stripCommentIndentation(lines, ignoreFirstLine) {
87
+ const n = lines.length;
88
+
89
+ const minIndent = lines.reduce((min, line, index) => {
90
+ // Blank lines are ignored.
91
+ if (isWhitespaceOrNewLineOnly(line) || (index === 0 && ignoreFirstLine))
92
+ return min;
93
+
94
+ let count = 0;
95
+ const length = Math.min(min, line.length);
96
+ while (count < length && isWhitespaceCharacterNoNewline(line[count])) {
97
+ count++;
98
+ }
99
+ return Math.min(min, count);
100
+ }, Number.MAX_SAFE_INTEGER);
101
+
102
+ for (let i = (ignoreFirstLine ? 1 : 0); i < n; ++i) {
103
+ // Note: Line may be empty and have fewer characters than `min`.
104
+ // In that case, slice() returns an empty string.
105
+ lines[i] = lines[i].slice(minIndent);
106
+ }
73
107
  }
74
108
 
75
109
  /**
@@ -85,17 +119,6 @@ function removeFence(line) {
85
119
  return line.replace(/^\s*[*]\s?/, '');
86
120
  }
87
121
 
88
- /**
89
- * Remove the TODO
90
- *
91
- * @param {string} line
92
- * @param {number} spaces Number of whitespace to remove at the beginning of the line
93
- * @returns {string} line without fence
94
- */
95
- function removeWhitespace(line, spaces) {
96
- return line.replace(new RegExp(`^\\s{0,${ spaces }}`), ''); // Trailing spaces with '*'? => .replace(/\s+[*]$/, '');
97
- }
98
-
99
122
  /**
100
123
  * Removes a header fence, i.e. '/**'.
101
124
  * May remove more than two asterisks e.g. '/*******'