@sap/cds-compiler 3.0.2 → 3.1.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 (72) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/bin/.eslintrc.json +2 -1
  3. package/bin/cdsc.js +19 -0
  4. package/doc/API.md +11 -0
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +24 -2
  7. package/doc/CHANGELOG_DEPRECATED.md +21 -1
  8. package/lib/api/main.js +7 -7
  9. package/lib/api/options.js +2 -3
  10. package/lib/base/message-registry.js +17 -5
  11. package/lib/base/messages.js +18 -39
  12. package/lib/base/model.js +2 -0
  13. package/lib/checks/actionsFunctions.js +8 -7
  14. package/lib/checks/selectItems.js +96 -14
  15. package/lib/checks/types.js +5 -8
  16. package/lib/checks/validator.js +1 -2
  17. package/lib/compiler/assert-consistency.js +64 -12
  18. package/lib/compiler/base.js +6 -4
  19. package/lib/compiler/builtins.js +58 -8
  20. package/lib/compiler/checks.js +1 -1
  21. package/lib/compiler/define.js +25 -22
  22. package/lib/compiler/extend.js +16 -10
  23. package/lib/compiler/finalize-parse-cdl.js +5 -9
  24. package/lib/compiler/index.js +2 -0
  25. package/lib/compiler/populate.js +34 -31
  26. package/lib/compiler/propagator.js +11 -6
  27. package/lib/compiler/resolve.js +14 -15
  28. package/lib/compiler/shared.js +53 -26
  29. package/lib/compiler/tweak-assocs.js +5 -11
  30. package/lib/compiler/utils.js +13 -4
  31. package/lib/edm/annotations/preprocessAnnotations.js +8 -4
  32. package/lib/edm/csn2edm.js +3 -3
  33. package/lib/edm/edm.js +9 -1
  34. package/lib/edm/edmAnnoPreprocessor.js +349 -0
  35. package/lib/edm/edmInboundChecks.js +85 -0
  36. package/lib/edm/edmPreprocessor.js +295 -638
  37. package/lib/edm/edmUtils.js +85 -5
  38. package/lib/gen/Dictionary.json +29 -9
  39. package/lib/gen/language.checksum +1 -1
  40. package/lib/gen/language.interp +1 -2
  41. package/lib/gen/languageLexer.js +3 -0
  42. package/lib/gen/languageParser.js +4344 -4530
  43. package/lib/inspect/.eslintrc.json +4 -0
  44. package/lib/inspect/index.js +14 -0
  45. package/lib/inspect/inspectModelStatistics.js +81 -0
  46. package/lib/inspect/inspectPropagation.js +189 -0
  47. package/lib/inspect/inspectUtils.js +44 -0
  48. package/lib/json/from-csn.js +3 -2
  49. package/lib/json/to-csn.js +8 -6
  50. package/lib/language/genericAntlrParser.js +121 -63
  51. package/lib/language/language.g4 +19 -57
  52. package/lib/main.d.ts +1 -0
  53. package/lib/model/api.js +1 -1
  54. package/lib/model/csnRefs.js +55 -29
  55. package/lib/model/csnUtils.js +11 -7
  56. package/lib/model/revealInternalProperties.js +2 -3
  57. package/lib/modelCompare/compare.js +3 -0
  58. package/lib/optionProcessor.js +27 -0
  59. package/lib/render/toCdl.js +57 -32
  60. package/lib/render/toSql.js +24 -8
  61. package/lib/render/utils/common.js +3 -4
  62. package/lib/transform/db/associations.js +43 -35
  63. package/lib/transform/db/cdsPersistence.js +0 -1
  64. package/lib/transform/db/flattening.js +3 -4
  65. package/lib/transform/db/transformExists.js +7 -5
  66. package/lib/transform/draft/db.js +1 -1
  67. package/lib/transform/forHanaNew.js +11 -2
  68. package/lib/transform/forOdataNew.js +1 -1
  69. package/lib/transform/odata/typesExposure.js +14 -5
  70. package/lib/utils/moduleResolve.js +0 -1
  71. package/package.json +2 -2
  72. package/lib/checks/unknownMagic.js +0 -41
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { getUtils, isBuiltinType, hasAnnotationValue } = require('../model/csnUtils');
3
+ const { getUtils, hasAnnotationValue } = require('../model/csnUtils');
4
4
 
5
5
  // Only to be used with validator.js - a correct this value needs to be provided!
6
6
 
@@ -55,7 +55,7 @@ function checkElementTypeDefinitionHasType(member, memberName, prop, path) {
55
55
 
56
56
  // should only happen with csn input, not in cdl
57
57
  if (!hasArtifactTypeInformation(member)) {
58
- warnAboutMissingType(this.error, path, memberName, true);
58
+ errorAboutMissingType(this.error, path, memberName, true);
59
59
  return;
60
60
  }
61
61
 
@@ -96,7 +96,7 @@ function checkTypeDefinitionHasType(artifact, artifactName, prop, path) {
96
96
 
97
97
  // should only happen with csn input, not in cdl
98
98
  if (!hasArtifactTypeInformation(artifact)) {
99
- warnAboutMissingType(this.error, path, artifactName);
99
+ errorAboutMissingType(this.error, path, artifactName);
100
100
  return;
101
101
  }
102
102
 
@@ -157,9 +157,8 @@ function checkTypeOfHasProperType(artOrElement, name, model, error, path, derive
157
157
  * @param {CSN.Path} path the path to the element or the artifact
158
158
  * @param {string} name of the element or the artifact which is dubious
159
159
  * @param {boolean} isElement indicates whether we are dealing with an element or an artifact
160
- * @todo Rename, is an error not a warning
161
160
  */
162
- function warnAboutMissingType(error, path, name, isElement = false) {
161
+ function errorAboutMissingType(error, path, name, isElement = false) {
163
162
  error('check-proper-type', path, { art: name, '#': isElement ? 'elm' : 'std' }, {
164
163
  std: 'Dubious type $(ART) without type information',
165
164
  elm: 'Dubious element $(ART) without type information',
@@ -174,12 +173,10 @@ function warnAboutMissingType(error, path, name, isElement = false) {
174
173
  *
175
174
  * @param {CSN.Artifact} artifact the artifact to check
176
175
  * @returns {boolean} indicates whether the artifact has type information
177
- * @todo What is the point of isBuiltinType here if we check for artifact.type at the end?
178
176
  */
179
177
  function hasArtifactTypeInformation(artifact) {
180
178
  // When is what property set?
181
- return isBuiltinType(artifact.type) || // => `Integer`
182
- artifact.elements || // => `type A {}`
179
+ return artifact.elements || // => `type A {}`
183
180
  artifact.items || // => `type A : array of Integer`
184
181
  artifact.enum || // => `type A : Integer enum {}`, `type` also set
185
182
  artifact.target || // => `type A : Association to B;`
@@ -32,7 +32,6 @@ const { validateAssociationsInItems } = require('./arrayOfs');
32
32
  const checkQueryForNoDBArtifacts = require('./queryNoDbArtifacts');
33
33
  const checkExplicitlyNullableKeys = require('./nullableKeys');
34
34
  const nonexpandableStructuredInExpression = require('./nonexpandableStructured');
35
- const unknownMagic = require('./unknownMagic');
36
35
  const managedWithoutKeys = require('./managedWithoutKeys');
37
36
  const {
38
37
  checkSqlAnnotationOnArtifact,
@@ -62,7 +61,7 @@ const forHanaArtifactValidators
62
61
  checkSqlAnnotationOnArtifact,
63
62
  ];
64
63
 
65
- const forHanaCsnValidators = [ nonexpandableStructuredInExpression, unknownMagic ];
64
+ const forHanaCsnValidators = [ nonexpandableStructuredInExpression ];
66
65
  /**
67
66
  * @type {Array<(query: CSN.Query, path: CSN.Path) => void>}
68
67
  */
@@ -97,7 +97,6 @@ function assertConsistency( model, stage ) {
97
97
  '$lateExtensions',
98
98
  '_entities', '$entity',
99
99
  '$blocks',
100
- '$newfeatures',
101
100
  '$messageFunctions',
102
101
  '$functions',
103
102
  '$volatileFunctions',
@@ -143,7 +142,6 @@ function assertConsistency( model, stage ) {
143
142
  },
144
143
  fileDep: { test: TODO }, // in usings
145
144
  $frontend: { parser: true, test: isString, enum: [ 'cdl', 'json', 'xml' ] },
146
- $newfeatures: { test: TODO }, // if new features have been used which break the old backends
147
145
  messages: {
148
146
  enumerable: () => true, // does not matter (non-enum std), enum in CSN/XML parser
149
147
  test: isArray( TODO ),
@@ -175,7 +173,6 @@ function assertConsistency( model, stage ) {
175
173
  return innerDict( val, parent, lang, spec );
176
174
  } ),
177
175
  },
178
- _assocSources: { kind: true, test: TODO }, // just null: isArray( inDefinitions ) during resolve
179
176
  $magicVariables: {
180
177
  // $magicVariables contains "builtin" artifacts that differ from
181
178
  // "normal artifacts" and therefore have a custom schema
@@ -256,6 +253,7 @@ function assertConsistency( model, stage ) {
256
253
  'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit',
257
254
  '_projections', '_block', '_parent', '_main', '_effectiveType', '$expand',
258
255
  '$tableAliases', 'kind', '_$next', '_combined', '$inlines', '_status',
256
+ '_extension', // for unapplied extensions
259
257
  ],
260
258
  },
261
259
  none: { optional: () => true }, // parse error
@@ -375,13 +373,21 @@ function assertConsistency( model, stage ) {
375
373
  },
376
374
  // locations of parentheses pairs around expression:
377
375
  $parens: { parser: true, test: TODO },
376
+ $prefix: { test: isString }, // compiler-corrected path prefix
378
377
  $syntax: {
379
378
  parser: true,
380
379
  kind: [ 'entity', 'view', 'type', 'aspect' ],
381
380
  test: isString, // CSN parser should check for 'entity', 'view', 'projection'
382
381
  },
383
382
  value: {
384
- optional: [ 'location', '$inferred', 'sort', 'nulls' ],
383
+ optional: [
384
+ 'location', '$inferred', 'sort', 'nulls',
385
+ 'param', 'scope', // for dynamic parameter '?'
386
+ // through cast() with enum through CSN->XSN
387
+ // TODO: re-check #9225, this should be directly in the query element,
388
+ // not inside value, no `enum` inside `cast`!
389
+ 'elements', 'items', 'enum', '$expand', 'target',
390
+ ],
385
391
 
386
392
  kind: true,
387
393
  test: expression, // properties below are "sub specifications"
@@ -439,7 +445,10 @@ function assertConsistency( model, stage ) {
439
445
  struct: { inherits: 'val', test: isDictionary( definition ) }, // def because double @
440
446
  args: {
441
447
  inherits: 'value',
442
- optional: [ 'name', '$duplicate', '$expected', 'args', 'suffix' ],
448
+ optional: [
449
+ 'name', '$duplicate', '$expected', 'args', 'suffix',
450
+ 'param', 'scope', // for dynamic parameter '?'
451
+ ],
443
452
  test: args,
444
453
  },
445
454
  on: { kind: true, inherits: 'value', test: expression },
@@ -456,7 +465,7 @@ function assertConsistency( model, stage ) {
456
465
  optional: [ 'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported' ],
457
466
  // TODO: name requires if not in parser?
458
467
  },
459
- $priority: { test: TODO },
468
+ $priority: { test: isOneOf([ undefined, false, 'extend', 'annotate' ]) },
460
469
  $annotations: { parser: true, kind: true, test: TODO }, // deprecated, still there for cds-lsp
461
470
  name: {
462
471
  isRequired: stageParser && (() => false), // not required in parser
@@ -475,7 +484,7 @@ function assertConsistency( model, stage ) {
475
484
  variant: { test: TODO }, // TODO: not set in CDL parser
476
485
  element: { test: TODO }, // TODO: { test: isString },
477
486
  action: { test: isString },
478
- param: { test: isString },
487
+ param: { test: TODO },
479
488
  alias: { test: isString },
480
489
  expectedKind: { kind: [ 'extend' ], inherits: 'kind' },
481
490
  virtual: { kind: true, test: locationVal() },
@@ -528,7 +537,6 @@ function assertConsistency( model, stage ) {
528
537
  _service: { kind: true, test: TODO },
529
538
  _main: { kind: true, test: TODO },
530
539
  _artifact: { test: TODO },
531
- _base: { test: TODO, kind: true },
532
540
  _navigation: { test: TODO },
533
541
  _effectiveType: { kind: true, test: TODO },
534
542
  _joinParent: { test: TODO },
@@ -552,6 +560,7 @@ function assertConsistency( model, stage ) {
552
560
  'where', 'columns', 'mixin', 'quantifier', 'offset',
553
561
  'orderBy', '$orderBy', 'groupBy', 'excludingDict', 'having',
554
562
  'limit', '_status',
563
+ '_extension', // for unapplied extensions
555
564
  ],
556
565
  },
557
566
  _leadingQuery: { kind: true, test: TODO },
@@ -588,18 +597,54 @@ function assertConsistency( model, stage ) {
588
597
  $errorReported: { parser: true, kind: true, test: isString }, // to avoid duplicate messages
589
598
  $duplicates: { parser: true, kind: true, test: TODO }, // array of arts or true
590
599
  $extension: { kind: true, test: TODO }, // TODO: introduce $applied instead or $status
591
- $inferred: { parser: true, kind: true, test: isString },
600
+ $inferred: {
601
+ parser: true,
602
+ kind: true,
603
+ test: isOneOf([
604
+ // Uppercase values are used in logic, lowercase value are "just for us", i.e.
605
+ // debugging or to add non-enumerable properties such as $generated in Universal CSN.
606
+ // However, that is no longer true. For example, `autoexposed` is used in populate.js
607
+ // as well.
608
+ 'IMPLICIT',
609
+ 'REDIRECTED',
610
+
611
+ '$autoElement', // for magicVars: $user is automatically changed to $user.id
612
+ '$generated', // compiler generated annotations, e.g. @Core.Computed
613
+ '*', // inferred from query wildcard
614
+ 'as', // query alias name
615
+ 'aspect-composition',
616
+ 'autoexposed', // for auto-exposed entities (they can't be referred to)
617
+ 'cast', // type from cast() function
618
+ 'composition-entity',
619
+ 'copy', // only used in rewriteCondition(): On-condition is copied
620
+ 'duplicate-autoexposed', // just like `autoexposed`, but with `duplicate` error.
621
+ 'expand-element', // expanded elements
622
+ 'expand-param', // expanded params (difference to expand-element only for debugging)
623
+ 'include', // through includes, e.g. `entity E : F {}`
624
+ 'keys',
625
+ 'localized', // e.g. compiler-generated elements for localized: `text` assoc, etc.
626
+ 'localized-entity', // `.texts` entity
627
+ 'nav', // only used for MASKED, TODO(v4): Remove
628
+ 'none', // only used in ensureColumnName(): Used in object representing empty alias
629
+ 'query', // inferred query properties, e.g. `key`
630
+ 'rewrite', // on-conditions or FKeys are rewritten
631
+ ]),
632
+ },
592
633
 
593
634
  // Helper property for the XSN-to-CSN transformation, see function setExpandStatus():
594
635
  // client, universal: render expanded elements? gensrc: produce annotate statements?
595
- $expand: { kind: true, test: isString }, // TODO: rename it to $elementsExpand ?
636
+ // TODO: rename it to $elementsExpand ?
637
+ $expand: {
638
+ kind: true,
639
+ // See description of `setExpandStatus()` of in `lib/compiler/utils.js`.
640
+ test: isOneOf([ 'origin', 'annotate', 'target' ]),
641
+ },
596
642
 
597
643
  $autoexpose: { kind: [ 'entity' ], test: isBoolean, also: [ null, 'Composition' ] },
598
- $a2j: { kind: true, enumerable: true, test: TODO },
599
644
  $extra: { parser: true, test: TODO }, // for unexpected properties in CSN
600
645
  $withLocalized: { test: isBoolean },
601
646
  $sources: { parser: true, test: isArray( isString ) },
602
- $expected: { parser: true, test: isString },
647
+ $expected: { parser: true, test: isOneOf([ 'approved-exists', 'exists' ]) },
603
648
  $messageFunctions: { test: TODO },
604
649
  $functions: { test: TODO },
605
650
  $volatileFunctions: { test: TODO },
@@ -859,6 +904,13 @@ function assertConsistency( model, stage ) {
859
904
  isString(node, parent, prop, spec);
860
905
  }
861
906
 
907
+ function isOneOf(values) {
908
+ return function isOneOfInner( node, parent, prop ) {
909
+ if (!values.includes(node))
910
+ throw new Error( `Unexpected value '${ node }', expected ${ JSON.stringify(values) }${ at( [ node, parent ], prop ) }` );
911
+ };
912
+ }
913
+
862
914
  function isString( node, parent, prop, spec ) {
863
915
  if (typeof node !== 'string')
864
916
  throw new Error( `Expected string${ at( [ node, parent ], prop ) }` );
@@ -16,16 +16,18 @@ const kindProperties = {
16
16
  namespace: { artifacts: true }, // on-the-fly context
17
17
  context: { artifacts: true, normalized: 'namespace' },
18
18
  service: { artifacts: true, normalized: 'namespace' },
19
- entity: { elements: true, actions: true, params: () => false },
19
+ entity: {
20
+ elements: true, actions: true, params: () => false, include: true,
21
+ },
20
22
  select: { normalized: 'select', elements: true },
21
23
  $join: { normalized: 'select' },
22
24
  $tableAlias: { normalized: 'alias' }, // table alias in select
23
25
  $self: { normalized: 'alias' }, // table alias in select
24
26
  $navElement: { normalized: 'element' },
25
27
  $inline: { normalized: 'element' }, // column with inline property
26
- event: { elements: true },
27
- type: { elements: propExists, enum: propExists },
28
- aspect: { elements: propExists },
28
+ event: { elements: true, include: true },
29
+ type: { elements: propExists, enum: propExists, include: true },
30
+ aspect: { elements: propExists, actions: true, include: true },
29
31
  annotation: { elements: propExists, enum: propExists },
30
32
  enum: { normalized: 'element' },
31
33
  element: { elements: propExists, enum: propExists, dict: 'elements' },
@@ -154,12 +154,14 @@ function compileArg( src ) {
154
154
  expr: src.expr || [],
155
155
  separator: src.separator || [],
156
156
  };
157
- for (const generic of [ 'intro', 'expr', 'separator' ]) { // intro before expr!
157
+ for (const generic of [ 'intro', 'expr', 'separator' ]) {
158
+ // intro before expr: if both intro and expr, tag as 'expr'
158
159
  for (const token of src[generic] || [])
159
160
  tgt[token] = generic;
160
161
  }
161
- if (tgt.intro) // same token could be in both 'expr' and 'intro':
162
- tgt.introMsg = src.introMsg || tgt.intro.filter( token => tgt[token] === 'intro' );
162
+ // As GenericIntro is always together with GenericExpr, only mention those
163
+ // which are not already proposed for GenericExpr:
164
+ tgt.introMsg = src.introMsg || tgt.intro.filter( token => tgt[token] === 'intro' );
163
165
  return tgt;
164
166
  }
165
167
 
@@ -196,15 +198,16 @@ const magicVariables = {
196
198
  /**
197
199
  * Patterns for literal token tests and creation. The value is a map from the
198
200
  * `prefix` argument of function `quotedliteral` to the following properties:
199
- * - `test_msg`: error message which is issued if `test_fn` or `test_re` fail.
201
+ * - `test_msg`: error message which is issued if `test_fn` fails.
200
202
  * - `test_fn`: function called with argument `value`, fails falsy return value
201
- * - `test_re`: regular expression, fails if it does not match argument `value`
202
203
  * - `unexpected_msg`: error message which is issued if `unexpected_char` matches
203
204
  * - `unexpected_char`: regular expression matching an illegal character in `value`,
204
205
  * the error location is only correct for a literal <prefix>'<value>'
205
206
  * - `literal`: the value which is used instead of `prefix` in the AST
206
207
  * TODO: we might do a range check (consider leap seconds, i.e. max value 60),
207
208
  * but always allow Feb 29 (no leap year computation)
209
+ * Notes:
210
+ * - Dates/Times as defined in ISO 8601, see <https://en.wikipedia.org/wiki/ISO_8601>
208
211
  */
209
212
  const quotedLiteralPatterns = {
210
213
  x: {
@@ -216,21 +219,68 @@ const quotedLiteralPatterns = {
216
219
  },
217
220
  time: {
218
221
  test_variant: 'time',
219
- test_re: /^[0-9]{1,2}:[0-9]{1,2}(:[0-9]{1,2})?$/,
222
+ test_fn: (x) => {
223
+ // Leading `T` allowed in ISO 8601.
224
+ const match = x.match( /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/ );
225
+ return match !== null && checkTime( match[1], match[2], match[3] );
226
+ },
220
227
  json_type: 'string',
221
228
  },
222
229
  date: {
223
230
  test_variant: 'date',
224
- test_re: /^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/,
231
+ test_fn: (x) => {
232
+ const match = x.match( /^(-?\d{4})-(\d{1,2})-(\d{1,2})$/ );
233
+ return match !== null && checkDate( match[1], match[2], match[3] );
234
+ },
225
235
  json_type: 'string',
226
236
  },
227
237
  timestamp: {
228
238
  test_variant: 'timestamp',
229
- test_re: /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}(:[0-9]{2}(\.[0-9]{1,7})?)?$/,
239
+ test_fn: (x) => {
240
+ // eslint-disable-next-line max-len
241
+ const match = x.match( /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?$/ );
242
+ return match !== null && checkDate( match[1], match[2], match[3] ) &&
243
+ checkTime( match[4], match[5], match[6] );
244
+ },
230
245
  json_type: 'string',
231
246
  },
232
247
  };
233
248
 
249
+ /**
250
+ * Check that the given date is within boundaries.
251
+ * We can't use Date.parse() since that also allows non-standard values (2022-02-31 for example).
252
+ * Checks according to ISO 8601.
253
+ *
254
+ * @returns {boolean} True if the date is valid.
255
+ */
256
+ function checkDate(year, month, day) {
257
+ // Negative years are allowed
258
+ year = Math.abs(Number.parseInt(year, 10));
259
+ month = Number.parseInt(month, 10);
260
+ day = Number.parseInt(day, 10);
261
+ // If any is NaN, the condition will be false.
262
+ // Year 0 does not exist, but ISO 8601 allows it and defines it as 1 BC.
263
+ return !Number.isNaN(year) && month > 0 && month < 13 && day > 0 && day < 32;
264
+ }
265
+
266
+ /**
267
+ * Check that the given time is within boundaries.
268
+ * Checks according to ISO 8601.
269
+ *
270
+ * @returns {boolean} True if the date is valid.
271
+ */
272
+ function checkTime(hour, minutes, seconds) {
273
+ hour = Number.parseInt(hour, 10);
274
+ minutes = Number.parseInt(minutes, 10);
275
+ seconds = seconds ? Number.parseInt(seconds, 10) : 0;
276
+ if (hour === 24) // allow 24:00:00 (ISO 8601 version earlier than 2019)
277
+ return minutes === 0 && seconds === 0;
278
+ // If any is NaN, the condition will be false.
279
+ return hour >= 0 && hour < 24 &&
280
+ minutes >= 0 && minutes < 60 &&
281
+ seconds >= 0 && seconds < 61; // we allow 60 for lead seconds
282
+ }
283
+
234
284
  /** All types belong to one category. */
235
285
  const typeCategories = {
236
286
  string: [],
@@ -414,7 +414,7 @@ function check( model ) { // = XSN
414
414
  }
415
415
  }
416
416
 
417
- // TODO: make this part of the the name resolution in the compiler
417
+ // TODO: make this part of the name resolution in the compiler
418
418
  // Check that queries in 'art' do not contain unmanaged associations in GROUP BY or ORDER BY
419
419
  function checkNoUnmanagedAssocsInGroupByOrderBy( query ) {
420
420
  const art = query._main; // TODO - remove, use query for semantic location
@@ -109,7 +109,7 @@
109
109
 
110
110
  'use strict';
111
111
 
112
- const { isDeprecatedEnabled, forEachGeneric, forEachInOrder } = require('../base/model');
112
+ const { forEachGeneric, forEachInOrder } = require('../base/model');
113
113
  const {
114
114
  dictAdd, dictAddArray, dictForEach, pushToDict,
115
115
  } = require('../base/dictionaries');
@@ -147,6 +147,7 @@ function define( model ) {
147
147
  } = model.$messageFunctions;
148
148
  const {
149
149
  resolveUncheckedPath,
150
+ checkAnnotate,
150
151
  defineAnnotations,
151
152
  } = model.$functions;
152
153
 
@@ -197,11 +198,12 @@ function define( model ) {
197
198
  }
198
199
 
199
200
  // Phase 1: ----------------------------------------------------------------
201
+ // Functions called from top-level: addSource()
200
202
 
201
203
  /**
202
204
  * Add definitions of the given source AST, both CDL and CSN
203
205
  *
204
- * @param {XSN.AST} src
206
+ * @param {XSN.SourceAst} src
205
207
  */
206
208
  function addSource( src ) {
207
209
  // handle sub model from parser
@@ -266,7 +268,7 @@ function define( model ) {
266
268
  return;
267
269
  }
268
270
  setLink( art, '_block', block );
269
- // dictAdd might set $duplicates to true
271
+ // dictAdd might set $duplicates
270
272
  dictAdd( model.definitions, absolute, art );
271
273
  }
272
274
 
@@ -306,7 +308,7 @@ function define( model ) {
306
308
  * declaration.
307
309
  *
308
310
  * @param {XSN.Using} decl Node to be expanded and added to `src`
309
- * @param {XSN.AST} src
311
+ * @param {XSN.SourceAst} src
310
312
  */
311
313
  function addUsing( decl, src ) {
312
314
  if (decl.usings) {
@@ -395,9 +397,12 @@ function define( model ) {
395
397
  }
396
398
 
397
399
  // Phase 2 ("init") --------------------------------------------------------
400
+ // Functions called from top-level: initNamespaceAndUsing(), initArtifact(),
401
+ // initVocabulary()
398
402
 
399
403
  function checkRedefinition( art ) {
400
- if (!art.$duplicates || art.$errorReported === 'syntax-duplicate-extend')
404
+ if (!art.$duplicates || art.$errorReported === 'syntax-duplicate-extend' ||
405
+ art.$errorReported === 'syntax-duplicate-annotate')
401
406
  return;
402
407
  if (art._main) {
403
408
  error( 'duplicate-definition', [ art.name.location, art ], {
@@ -504,18 +509,6 @@ function define( model ) {
504
509
  definitions[prefix] = parent;
505
510
  initParentLink( parent, definitions );
506
511
  }
507
- if (art.kind !== 'namespace' &&
508
- isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' )) {
509
- let p = parent;
510
- while (p && kindProperties[p.kind].artifacts)
511
- p = p._parent;
512
- if (p) {
513
- error( 'subartifacts-not-supported', [ art.name.location, art ],
514
- { art: p, prop: 'deprecated._generatedEntityNameWithUnderscore' },
515
- // eslint-disable-next-line max-len
516
- 'With the option $(PROP), no sub artifact can be defined for a non-context/service $(ART)' );
517
- }
518
- }
519
512
  setLink( art, '_parent', parent );
520
513
  if (!parent._subArtifacts)
521
514
  setLink( parent, '_subArtifacts', Object.create(null) );
@@ -611,7 +604,17 @@ function define( model ) {
611
604
  // Either expression (value), expand or new association (target && type)
612
605
  else if (col.value || col.expand || (col.target && col.type)) {
613
606
  setLink( col, '_block', parent._block );
614
- defineAnnotations( col, col, parent._block ); // TODO: complain with inline
607
+ defineAnnotations( col, col, parent._block );
608
+ if (col.inline) { // `@anno elem.{ * }` does not work
609
+ if (col.doc)
610
+ warning( 'syntax-anno-ignored', [ col.doc.location, col ], { '#': 'doc' } );
611
+
612
+ // col.$annotations no available for CSN input, have to search.
613
+ // Warning about first annotation should be enough to avoid spam.
614
+ const firstAnno = Object.keys(col).find(key => key.startsWith('@'));
615
+ if (firstAnno)
616
+ warning( 'syntax-anno-ignored', [ col[firstAnno].name.location, col ] );
617
+ }
615
618
  // TODO: allow sub queries? at least in top-level expand without parallel ref
616
619
  if (columns)
617
620
  initExprForQuery( col.value, parent );
@@ -882,9 +885,7 @@ function define( model ) {
882
885
 
883
886
  /**
884
887
  * Set property `_parent` for all elements in `parent` to `parent` and do so
885
- * recursively for all sub elements. Also set the property
886
- * `name.component` of the element with the help of argument `prefix`
887
- * (which is basically the component name of the `parent` element plus a dot).
888
+ * recursively for all sub elements.
888
889
  */
889
890
  // If not for extensions: construct === parent
890
891
  function initMembers( construct, parent, block, initExtensions = false ) {
@@ -1016,6 +1017,8 @@ function define( model ) {
1016
1017
  setMemberParent( elem, name, parent, construct !== parent && prop );
1017
1018
  // console.log(message( null, elem.location, elem, {}, 'Info', 'INIT').toString())
1018
1019
  checkRedefinition( elem );
1020
+ if (elem.kind === 'annotate')
1021
+ checkAnnotate( elem, elem );
1019
1022
  defineAnnotations( elem, elem, bl );
1020
1023
  initMembers( elem, elem, bl, initExtensions );
1021
1024
 
@@ -1127,7 +1130,7 @@ function mergeI18nBlocks( model ) {
1127
1130
  * Add the source's translations to the model. Warns if the sources translations
1128
1131
  * do not match the ones from previous sources.
1129
1132
  *
1130
- * @param {XSN.AST} src
1133
+ * @param {XSN.SourceAst} src
1131
1134
  */
1132
1135
  function initI18nFromSource( src ) {
1133
1136
  for (const langKey of Object.keys( src.i18n )) {
@@ -33,6 +33,7 @@ function extend( model ) {
33
33
  const {
34
34
  resolvePath,
35
35
  resolveUncheckedPath,
36
+ checkAnnotate,
36
37
  defineAnnotations,
37
38
  attachAndEmitValidNames,
38
39
  checkDefinitions,
@@ -179,6 +180,10 @@ function extend( model ) {
179
180
  checkDefinitions( ext, art, 'actions');
180
181
  checkDefinitions( ext, art, 'params');
181
182
  checkDefinitions( ext, art, 'columns');
183
+ if (ext.includes)
184
+ applyIncludes( ext, art ); // emits error if `includes` is set
185
+ if (ext.kind === 'annotate')
186
+ checkAnnotate( ext, art );
182
187
  defineAnnotations( ext, art, ext._block, ext.kind );
183
188
  }
184
189
  return true;
@@ -238,6 +243,8 @@ function extend( model ) {
238
243
  art.includes = [ ...ext.includes ];
239
244
  applyIncludes( ext, art );
240
245
  }
246
+ if (ext.kind === 'annotate')
247
+ checkAnnotate( ext, art );
241
248
  defineAnnotations( ext, art, ext._block, ext.kind );
242
249
  // TODO: do we allow to add elements with array of {...}? If yes, adapt
243
250
  initMembers( ext, art, ext._block ); // might set _extend, _annotate
@@ -395,6 +402,7 @@ function extend( model ) {
395
402
  resolvePath( ext.name, ext.kind, ext )) { // should issue error/info
396
403
  // should issue error for cds extensions (annotate ok)
397
404
  if (art.kind === 'namespace') {
405
+ // TODO: Emit error if namespace is extended by non-definitions.
398
406
  info( 'anno-namespace', [ ext.name.location, ext ], {},
399
407
  'Namespaces can\'t be annotated' );
400
408
  }
@@ -454,6 +462,12 @@ function extend( model ) {
454
462
  * @param {XSN.Artifact} art
455
463
  */
456
464
  function applyIncludes( ext, art ) {
465
+ if (kindProperties[art.kind].include !== true) {
466
+ error('extend-unexpected-include', [ ext.includes[0]?.location, ext ], { kind: art.kind },
467
+ 'Can\'t extend $(KIND) with includes');
468
+ return;
469
+ }
470
+
457
471
  if (!art._ancestors)
458
472
  setLink( art, '_ancestors', [] ); // recursive array of includes
459
473
  for (const ref of ext.includes) {
@@ -515,9 +529,7 @@ function extend( model ) {
515
529
  const fioriAnno = art['@fiori.draft.enabled'];
516
530
  const fioriEnabled = fioriAnno && (fioriAnno.val === undefined || fioriAnno.val);
517
531
 
518
- const textsName = (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
519
- ? `${ art.name.absolute }_texts`
520
- : `${ art.name.absolute }.texts`;
532
+ const textsName = `${ art.name.absolute }.texts`;
521
533
  const textsEntity = model.definitions[textsName];
522
534
  const localized = localizedData( art, textsEntity, fioriEnabled );
523
535
  if (!localized)
@@ -660,8 +672,6 @@ function extend( model ) {
660
672
  };
661
673
  dictAdd( art.elements, 'ID_texts', textId );
662
674
  }
663
- if (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
664
- setLink( art, '_base', base );
665
675
 
666
676
  dictAdd( art.elements, 'locale', locale );
667
677
  if (addTextsLanguageAssoc) {
@@ -834,9 +844,7 @@ function extend( model ) {
834
844
  target = resolvePath( origin.targetAspect, 'compositionTarget', origin );
835
845
  if (!target || !target.elements)
836
846
  return;
837
- const entityName = (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
838
- ? `${ base.name.absolute }_${ elem.name.id }`
839
- : `${ base.name.absolute }.${ elem.name.id }`;
847
+ const entityName = `${ base.name.absolute }.${ elem.name.id }`;
840
848
  const entity = allowAspectComposition( target, elem, keys, entityName ) &&
841
849
  createTargetEntity( target, elem, keys, entityName, base );
842
850
  elem.target = {
@@ -968,8 +976,6 @@ function extend( model ) {
968
976
  // even if target cardinality is 1..1
969
977
  up.notNull = { location, val: true };
970
978
  }
971
- if (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
972
- setLink( art, '_base', base._base || base );
973
979
 
974
980
  dictAdd( art.elements, 'up_', up);
975
981
  addProxyElements( art, target.elements, 'aspect-composition', target.name && location );
@@ -82,8 +82,12 @@ function finalizeParseCdl( model ) {
82
82
  for (const include of artifact.includes || [])
83
83
  resolveUncheckedPath(include, 'include', main);
84
84
 
85
+ // define.js takes care that `target` is a ref and
86
+ // `targetAspect` is a structure.
85
87
  if (artifact.target)
86
88
  resolveUncheckedPath(artifact.target, 'target', main);
89
+ if (artifact.targetAspect)
90
+ resolveTypesForParseCdl(artifact.targetAspect, main);
87
91
 
88
92
  if (artifact.from) {
89
93
  const { from } = artifact;
@@ -93,12 +97,6 @@ function finalizeParseCdl( model ) {
93
97
  resolveTypesForParseCdl(from, main);
94
98
  }
95
99
 
96
- if (artifact.targetAspect) {
97
- if (artifact.targetAspect.path)
98
- resolveUncheckedPath(artifact.targetAspect, 'target', main);
99
- resolveTypesForParseCdl(artifact.targetAspect, main);
100
- }
101
-
102
100
  // Recursively go through all XSN properties. There are a few that need to be
103
101
  // handled specifically. Refer to the code below this loop for details.
104
102
  for (const prop in artifact) {
@@ -158,8 +156,6 @@ function finalizeParseCdl( model ) {
158
156
  * @param {XSN.Artifact} user
159
157
  */
160
158
  function resolveTypeUnchecked(artWithType, user) {
161
- if (!artWithType.type)
162
- return;
163
159
  const root = artWithType.type.path && artWithType.type.path[0];
164
160
  if (!root) // parse error
165
161
  return;
@@ -187,7 +183,7 @@ function finalizeParseCdl( model ) {
187
183
  let struct = artWithType;
188
184
  while (struct.kind === 'element')
189
185
  struct = struct._parent;
190
- if (struct.kind === 'select' || struct !== user._main) {
186
+ if (struct.kind === 'select' || struct.kind === 'annotation' || struct !== user._main) {
191
187
  message( 'type-unexpected-typeof', [ artWithType.type.location, user ],
192
188
  { keyword: 'type of', '#': struct.kind } );
193
189
  return;
@@ -84,6 +84,8 @@ function parseX( source, filename, options = {}, messageFunctions = null ) {
84
84
  return parseCsn.parse( source, filename, options, messageFunctions );
85
85
  if (options.fallbackParser) // any other value: like 'cdl' (historic reasons)
86
86
  return parseLanguage( source, filename, options, messageFunctions );
87
+ if (source.startsWith('{')) // Source may be JSON.
88
+ return parseCsn.parse( source, filename, options, messageFunctions );
87
89
 
88
90
  const model = { location: { file: filename } };
89
91
  messageFunctions.error( 'file-unknown-ext', emptyWeakLocation(filename),