@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
@@ -103,8 +103,9 @@ const commonMemberValidators
103
103
  validateAssociationsInItems, checkForInvalidTarget,
104
104
  checkVirtualElement, checkElementTypeDefinitionHasType ];
105
105
 
106
+ // TODO: checkManagedAssoc is a forEachMemberRecursively!
106
107
  const commonArtifactValidators = [ checkTypeDefinitionHasType, checkPrimaryKey, checkManagedAssoc ];
107
-
108
+ // TODO: Does it make sense to run the on-condition check as part of a CSN validator?
108
109
  const commonQueryValidators = [ validateMixinOnCondition ];
109
110
 
110
111
  /**
@@ -118,8 +118,10 @@ function assertConsistency( model, stage ) {
118
118
  '$sources',
119
119
  ],
120
120
  },
121
- location: { // location req if at least one property:
122
- isRequired: parent => noSyntaxErrors() || Object.keys( parent ).length,
121
+ location: {
122
+ // every thing with a $location in CSN must have a XSN location even
123
+ // with syntax errors (currently even internal artifacts like $using):
124
+ isRequired: parent => noSyntaxErrors() || parent && parent.kind,
123
125
  kind: true,
124
126
  requires: [ 'file' ], // line is optional in top-level location
125
127
  optional: [ 'line', 'col', 'endLine', 'endCol', '$notFound' ],
@@ -185,12 +187,16 @@ function assertConsistency( model, stage ) {
185
187
  // are missing the location property
186
188
  test: isDictionary( definition ),
187
189
  requires: [ 'kind', 'name' ],
188
- optional: [ 'elements', '$autoElement', '$uncheckedElements', '_effectiveType', '_deps' ],
190
+ optional: [
191
+ 'elements', '$autoElement', '$uncheckedElements',
192
+ '$requireElementAccess', '_effectiveType', '_deps',
193
+ ],
189
194
  schema: {
190
195
  kind: { test: isString, enum: [ 'builtin' ] },
191
196
  name: { test: isObject, requires: [ 'id', 'element' ] },
192
197
  $autoElement: { test: isString },
193
198
  $uncheckedElements: { test: isBoolean },
199
+ $requireElementAccess: { test: isBoolean },
194
200
  // missing location for normal "elements"
195
201
  elements: { test: TODO },
196
202
  },
@@ -217,8 +223,7 @@ function assertConsistency( model, stage ) {
217
223
  usings: {
218
224
  test: isArray(),
219
225
  requires: [ 'kind', 'location' ],
220
- optional: [ 'name', 'extern', 'usings', '$annotations', 'fileDep' ],
221
- // TODO: get rid of $annotations: []
226
+ optional: [ 'name', 'extern', 'usings', 'fileDep' ],
222
227
  },
223
228
  extern: {
224
229
  requires: [ 'location', 'path' ],
@@ -304,7 +309,6 @@ function assertConsistency( model, stage ) {
304
309
  kind: 'element',
305
310
  test: isDictionary( definition ), // definition since redef
306
311
  requires: [ 'location', 'name' ],
307
- optional: [ '$annotations' ], // TODO: get rid of annos: []
308
312
  },
309
313
  orderBy: { inherits: 'value', test: isArray( expression ) },
310
314
  sort: { test: locationVal( isString ), enum: [ 'asc', 'desc' ] },
@@ -449,11 +453,11 @@ function assertConsistency( model, stage ) {
449
453
  '@': {
450
454
  kind: true,
451
455
  inherits: 'value',
452
- optional: [ 'name', '_block', '$priority', '$duplicate', '$inferred', '$duplicates' ],
456
+ optional: [ 'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported' ],
453
457
  // TODO: name requires if not in parser?
454
458
  },
455
- $priority: { test: TODO }, // TODO: rename to $priority
456
- $annotations: { parser: true, kind: true, test: TODO },
459
+ $priority: { test: TODO },
460
+ $annotations: { parser: true, kind: true, test: TODO }, // deprecated, still there for cds-lsp
457
461
  name: {
458
462
  isRequired: stageParser && (() => false), // not required in parser
459
463
  kind: true,
@@ -468,7 +472,7 @@ function assertConsistency( model, stage ) {
468
472
  ],
469
473
  },
470
474
  absolute: { test: isString },
471
- variant: { test: TODO }, // TODO: not set in CDL parser, only in $annotations
475
+ variant: { test: TODO }, // TODO: not set in CDL parser
472
476
  element: { test: TODO }, // TODO: { test: isString },
473
477
  action: { test: isString },
474
478
  param: { test: isString },
@@ -581,6 +585,7 @@ function assertConsistency( model, stage ) {
581
585
  // (it can contain the artifact itself with no/failed autoexposure):
582
586
  _descendants: { kind: [ 'entity' ], test: isDictionary( isArray( TODO ) ) },
583
587
 
588
+ $errorReported: { parser: true, test: isBoolean }, // to avoid duplicate messages
584
589
  $duplicates: { parser: true, kind: true, test: TODO }, // array of arts or true
585
590
  $extension: { kind: true, test: TODO }, // TODO: introduce $applied instead or $status
586
591
  $inferred: { parser: true, kind: true, test: isString },
@@ -76,19 +76,92 @@ const functionsWithoutParens = [
76
76
  'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER',
77
77
  ];
78
78
 
79
- const specialFunctions = {
79
+ const specialFunctions = compileFunctions( {
80
+ '': [ // the default
81
+ {
82
+ intro: [ 'ALL', 'DISTINCT' ],
83
+ introMsg: [], // do not list them in code completion
84
+ },
85
+ {},
86
+ ],
80
87
  ROUND: [
81
88
  null, null, { // 3rd argument: rounding mode
82
- ROUND_HALF_UP: 'argFull',
83
- ROUND_HALF_DOWN: 'argFull',
84
- ROUND_HALF_EVEN: 'argFull',
85
- ROUND_UP: 'argFull',
86
- ROUND_DOWN: 'argFull',
87
- ROUND_CEILING: 'argFull',
88
- ROUND_FLOOR: 'argFull',
89
+ expr: [ 'ROUND_HALF_UP', 'ROUND_HALF_DOWN', 'ROUND_HALF_EVEN',
90
+ 'ROUND_UP', 'ROUND_DOWN', 'ROUND_CEILING', 'ROUND_FLOOR' ],
89
91
  },
90
92
  ],
91
- };
93
+ TRIM: [
94
+ {
95
+ intro: [ 'LEADING', 'TRAILING', 'BOTH' ],
96
+ expr: [ 'LEADING', 'TRAILING', 'BOTH' ],
97
+ separator: [ 'FROM' ],
98
+ },
99
+ ],
100
+ EXTRACT: [
101
+ {
102
+ expr: [ 'YEAR', 'MONTH', 'DAY', 'HOUR', 'MINUTE', 'SECOND' ],
103
+ separator: [ 'FROM' ],
104
+ },
105
+ ],
106
+ COUNT: [
107
+ {
108
+ expr: [ '*' ],
109
+ intro: [ 'ALL', 'DISTINCT' ],
110
+ },
111
+ ],
112
+ MIN: 'COUNT',
113
+ MAX: 'COUNT',
114
+ SUM: 'COUNT',
115
+ AVG: 'COUNT',
116
+ STDDDEV: 'COUNT',
117
+ VAR: 'COUNT',
118
+ LOCATE_REGEXPR: [
119
+ {
120
+ intro: [ 'START', 'AFTER' ],
121
+ separator: [ 'FLAG', 'IN', 'FROM', 'OCCURRENCE', 'GROUP' ],
122
+ },
123
+ ],
124
+ OCCURRENCES_REGEXPR: [
125
+ {
126
+ separator: [ 'FLAG', 'IN', 'FROM' ],
127
+ },
128
+ ],
129
+ REPLACE_REGEXPR: [
130
+ {
131
+ separator: [ 'FLAG', 'IN', 'WITH', 'FROM', 'OCCURRENCE' ],
132
+ expr: [ 'ALL' ],
133
+ },
134
+ ],
135
+ SUBSTRING_REGEXPR: [
136
+ {
137
+ separator: [ 'FLAG', 'IN', 'FROM', 'OCCURRENCE', 'GROUP' ],
138
+ },
139
+ ],
140
+ } );
141
+
142
+ function compileFunctions( special ) {
143
+ const compiled = {};
144
+ for (const [ name, val ] of Object.entries( special ))
145
+ compiled[name] = (typeof val === 'string' ? special[val] : val).map( compileArg );
146
+ return compiled;
147
+ }
148
+
149
+ function compileArg( src ) {
150
+ if (!src)
151
+ return src;
152
+ const tgt = {
153
+ intro: src.intro || [],
154
+ expr: src.expr || [],
155
+ separator: src.separator || [],
156
+ };
157
+ for (const generic of [ 'intro', 'expr', 'separator' ]) { // intro before expr!
158
+ for (const token of src[generic] || [])
159
+ tgt[token] = generic;
160
+ }
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' );
163
+ return tgt;
164
+ }
92
165
 
93
166
  /**
94
167
  * Variables that have special meaning in CDL/CSN.
@@ -106,12 +179,15 @@ const magicVariables = {
106
179
  elements: {
107
180
  from: {}, to: {},
108
181
  },
182
+ // Require that elements are accessed, i.e. no $at, only $at.<element>.
183
+ $requireElementAccess: true,
109
184
  },
110
185
  $now: {}, // Dito
111
186
  $session: {
112
187
  // In ABAP CDS session variables are accessed in a generic way via
113
188
  // the pseudo variable $session.
114
189
  $uncheckedElements: true,
190
+ $requireElementAccess: true,
115
191
  },
116
192
  };
117
193
 
@@ -286,6 +362,8 @@ function initBuiltins( model ) {
286
362
  art.$autoElement = magic.$autoElement;
287
363
  if (magic.$uncheckedElements)
288
364
  art.$uncheckedElements = magic.$uncheckedElements;
365
+ if (magic.$requireElementAccess)
366
+ art.$requireElementAccess = magic.$requireElementAccess;
289
367
 
290
368
  createMagicElements( art, magic.elements );
291
369
  if (options.variableReplacements)
@@ -505,13 +505,13 @@ function define( model ) {
505
505
  initParentLink( parent, definitions );
506
506
  }
507
507
  if (art.kind !== 'namespace' &&
508
- isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' )) {
508
+ isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' )) {
509
509
  let p = parent;
510
510
  while (p && kindProperties[p.kind].artifacts)
511
511
  p = p._parent;
512
512
  if (p) {
513
513
  error( 'subartifacts-not-supported', [ art.name.location, art ],
514
- { art: p, prop: 'deprecated.generatedEntityNameWithUnderscore' },
514
+ { art: p, prop: 'deprecated._generatedEntityNameWithUnderscore' },
515
515
  // eslint-disable-next-line max-len
516
516
  'With the option $(PROP), no sub artifact can be defined for a non-context/service $(ART)' );
517
517
  }
@@ -7,8 +7,8 @@
7
7
 
8
8
  const { searchName, weakLocation } = require('../base/messages');
9
9
  const {
10
- isDeprecatedEnabled, isBetaEnabled,
11
- forEachGeneric, forEachInOrder,
10
+ isDeprecatedEnabled,
11
+ forEachGeneric, forEachInOrder, forEachDefinition,
12
12
  } = require('../base/model');
13
13
  const { dictAdd } = require('../base/dictionaries');
14
14
  const { kindProperties, dictKinds } = require('./base');
@@ -47,15 +47,16 @@ function extend( model ) {
47
47
 
48
48
  applyExtensions();
49
49
 
50
- const commonLanguagesEntity // TODO: remove beta after a grace period
51
- = (options.addTextsLanguageAssoc || isBetaEnabled( options, 'addTextsLanguageAssoc' )) &&
52
- model.definitions['sap.common.Languages'];
50
+ const commonLanguagesEntity = options.addTextsLanguageAssoc &&
51
+ model.definitions['sap.common.Languages'];
53
52
  const addTextsLanguageAssoc = !!(commonLanguagesEntity && commonLanguagesEntity.elements &&
54
53
  commonLanguagesEntity.elements.code);
55
54
  Object.keys( model.definitions ).forEach( processArtifact );
56
55
 
57
56
  lateExtensions( false );
58
57
 
58
+ compositionChildPersistence();
59
+
59
60
  /**
60
61
  * Process "composition of" artifacts.
61
62
  *
@@ -88,6 +89,25 @@ function extend( model ) {
88
89
  }
89
90
  }
90
91
 
92
+ /**
93
+ * Copy `@cds.persistence.skip` and `@cds.persistence.skip` from parent to child
94
+ * for managed compositions. This needs to be done after extensions, i.e. annotations,
95
+ * have been applied or `annotate E.comp` would not have an effect on `E.comp.subComp`.
96
+ */
97
+ function compositionChildPersistence() {
98
+ const processed = new WeakSet();
99
+ forEachDefinition(model, processCompositionPersistence);
100
+
101
+ function processCompositionPersistence(def) {
102
+ if (def.$inferred === 'composition-entity' && !processed.has(def)) {
103
+ if (def._parent)
104
+ processCompositionPersistence(def._parent);
105
+ copyPersistenceAnnotations(def, def._parent, options);
106
+ processed.add(def);
107
+ }
108
+ }
109
+ }
110
+
91
111
  // extend ------------------------------------------------------------------
92
112
 
93
113
  /**
@@ -492,7 +512,7 @@ function extend( model ) {
492
512
  const fioriAnno = art['@fiori.draft.enabled'];
493
513
  const fioriEnabled = fioriAnno && (fioriAnno.val === undefined || fioriAnno.val);
494
514
 
495
- const textsName = (isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ))
515
+ const textsName = (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
496
516
  ? `${ art.name.absolute }_texts`
497
517
  : `${ art.name.absolute }.texts`;
498
518
  const textsEntity = model.definitions[textsName];
@@ -501,8 +521,9 @@ function extend( model ) {
501
521
  return;
502
522
  if (textsEntity) // expanded localized data in source
503
523
  return; // -> make it idempotent
504
- createTextsEntity( art, textsName, localized, fioriEnabled );
524
+ const newTextsEntity = createTextsEntity( art, textsName, localized, fioriEnabled );
505
525
  addTextsAssociations( art, textsName, localized );
526
+ copyPersistenceAnnotations(newTextsEntity, art, options);
506
527
  }
507
528
 
508
529
  /**
@@ -636,7 +657,7 @@ function extend( model ) {
636
657
  };
637
658
  dictAdd( art.elements, 'ID_texts', textId );
638
659
  }
639
- if (isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ))
660
+ if (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
640
661
  setLink( art, '_base', base );
641
662
 
642
663
  dictAdd( art.elements, 'locale', locale );
@@ -695,6 +716,8 @@ function extend( model ) {
695
716
  }
696
717
  if (fioriEnabled)
697
718
  annotateWith( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
719
+
720
+ return art;
698
721
  }
699
722
 
700
723
  /**
@@ -808,7 +831,7 @@ function extend( model ) {
808
831
  target = resolvePath( origin.targetAspect, 'compositionTarget', origin );
809
832
  if (!target || !target.elements)
810
833
  return;
811
- const entityName = (isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ))
834
+ const entityName = (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
812
835
  ? `${ base.name.absolute }_${ elem.name.id }`
813
836
  : `${ base.name.absolute }.${ elem.name.id }`;
814
837
  const entity = allowAspectComposition( target, elem, keys, entityName ) &&
@@ -931,7 +954,7 @@ function extend( model ) {
931
954
  // By default, 'up_' is a managed primary key association.
932
955
  // If 'up_' shall be rendered unmanaged, infer the parent
933
956
  // primary keys and add the ON condition
934
- if (isDeprecatedEnabled( options, 'unmanagedUpInComponent' )) {
957
+ if (isDeprecatedEnabled( options, '_unmanagedUpInComponent' )) {
935
958
  addProxyElements( art, keys, 'aspect-composition', target.name && location,
936
959
  'up__', '@odata.containment.ignore' );
937
960
  up.on = augmentEqual( location, 'up_', Object.values( keys ), 'up__' );
@@ -942,7 +965,7 @@ function extend( model ) {
942
965
  // even if target cardinality is 1..1
943
966
  up.notNull = { location, val: true };
944
967
  }
945
- if (isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ))
968
+ if (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
946
969
  setLink( art, '_base', base._base || base );
947
970
 
948
971
  dictAdd( art.elements, 'up_', up);
@@ -951,6 +974,10 @@ function extend( model ) {
951
974
  setLink( art, '_block', model.$internal );
952
975
  model.definitions[entityName] = art;
953
976
  initArtifact( art );
977
+
978
+ // Copy persistence annotations from aspect.
979
+ copyPersistenceAnnotations(art, target, options);
980
+
954
981
  return art;
955
982
  }
956
983
 
@@ -972,6 +999,27 @@ function extend( model ) {
972
999
  }
973
1000
  }
974
1001
 
1002
+ /**
1003
+ * Copy the annotations `@cds.persistence.skip`/`@cds.persistence.exists` from
1004
+ * source to target if present on source but not target.
1005
+ *
1006
+ * @param {object} target
1007
+ * @param {object} source
1008
+ * @param {CSN.Options} options
1009
+ */
1010
+ function copyPersistenceAnnotations(target, source, options) {
1011
+ if (!source)
1012
+ return;
1013
+ // Copy @cds.persistence.skip/exists annotation.
1014
+ const noCopyExists = isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
1015
+ const existsAnno = '@cds.persistence.exists';
1016
+ const skipAnno = '@cds.persistence.skip';
1017
+ if (!noCopyExists && source[existsAnno] && !target[existsAnno])
1018
+ target[existsAnno] = source[existsAnno];
1019
+ if (source[skipAnno] && !target[skipAnno])
1020
+ target[skipAnno] = source[skipAnno];
1021
+ }
1022
+
975
1023
  function augmentEqual( location, assocname, relations, prefix = '' ) {
976
1024
  const args = relations.map( eq );
977
1025
  return (args.length === 1)
@@ -2,6 +2,7 @@
2
2
 
3
3
  'use strict';
4
4
 
5
+ const { dictAddArray } = require('../base/dictionaries');
5
6
  const { forEachGeneric, forEachMember } = require('../base/model');
6
7
  const { setLink, setArtifactLink } = require('./utils');
7
8
 
@@ -33,7 +34,7 @@ function finalizeParseCdl( model ) {
33
34
  for (const ext of extensionsDict[name]) {
34
35
  ext.name.absolute = resolveUncheckedPath( ext.name, 'extend', ext );
35
36
  // Define annotations of this top-level extension
36
- defineAnnotations( ext, ext, ext._block );
37
+ defineAnnotations( ext, ext, ext._block, 'extend' );
37
38
  mergeAnnotatesForSameArtifact( ext );
38
39
  // Initialize members and define annotations in sub-elements.
39
40
  initMembers( ext, ext, ext._block, true );
@@ -221,17 +222,27 @@ function finalizeParseCdl( model ) {
221
222
 
222
223
  forEachMember(ext, sub => mergeAnnotatesForSameArtifact(sub));
223
224
 
224
- if (ext.$annotations && Array.isArray(ext.$duplicates)) {
225
- const annotates = ext.$duplicates.filter(val => (val.kind === 'annotate'));
226
- for (const dup of annotates) {
227
- ext.$annotations.push(...dup.$annotations);
228
- delete dup.$annotations;
225
+ // do not do a complex merge:
226
+ if (isComplexExtension( ext ) ||
227
+ !Array.isArray( ext.$duplicates ) || ext.$duplicates.some( isComplexExtension ))
228
+ return;
229
+ for (const dup of ext.$duplicates) {
230
+ for (const prop in dup) {
231
+ if (prop.charAt(0) === '@')
232
+ dictAddArray( ext, prop, dup[prop] );
229
233
  }
230
- ext.$duplicates = ext.$duplicates.filter(val => (val.kind !== 'annotate'));
231
- if (ext.$duplicates.length === 0)
232
- delete ext.$duplicates;
233
234
  }
235
+ delete ext.$duplicates;
234
236
  }
235
237
  }
236
238
 
239
+ /**
240
+ * We only de-duplicate an extend/annotate `ext` in function
241
+ * mergeAnnotatesForSameArtifact() if the extend/annotate is simple, i.e. has
242
+ * no members like elements.
243
+ */
244
+ function isComplexExtension( ext ) {
245
+ return ext.kind !== 'annotate' || ext.elements || ext.parameters || ext.actions;
246
+ }
247
+
237
248
  module.exports = finalizeParseCdl;
@@ -33,6 +33,7 @@ const check = require('./checks');
33
33
 
34
34
  const { emptyWeakLocation } = require('../base/location');
35
35
  const { createMessageFunctions, deduplicateMessages } = require('../base/messages');
36
+ const { checkRemovedDeprecatedFlags } = require('../base/model');
36
37
  const { promiseAllDoNotRejectImmediately } = require('../base/node-helpers');
37
38
  const { cdsFs } = require('../utils/file');
38
39
 
@@ -224,6 +225,7 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
224
225
  * @param {string} [dir=""] Base directory. All files are resolved relatively
225
226
  * to this directory
226
227
  * @param {object} [options={}] Compilation options.
228
+ * @param {object} [fileCache]
227
229
  * @returns {XSN.Model} Augmented CSN
228
230
  */
229
231
  function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.create(null) ) {
@@ -235,7 +237,7 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
235
237
  const model = { sources: a.sources, options };
236
238
  model.$messageFunctions = createMessageFunctions( options, 'compile', model );
237
239
 
238
- let asts = [];
240
+ const asts = [];
239
241
  const errors = [];
240
242
  a.files.forEach( val => readAndParseSync( val, (err, ast) => {
241
243
  if (err)
@@ -253,17 +255,16 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
253
255
  if (!options.parseOnly && !options.parseCdl) {
254
256
  while (asts.length) {
255
257
  const fileNames = readDependenciesSync( asts );
256
- asts = [];
257
- // TODO: check the following eslint error
258
- // eslint-disable-next-line no-loop-func
259
- fileNames.forEach( (fileName) => {
258
+ asts.length = 0;
259
+ // Push dependencies to `ast`. Only works because readAndParseSync() is synchronous.
260
+ for (const fileName of fileNames) {
260
261
  readAndParseSync(fileName, ( err, ast ) => {
261
262
  if (err)
262
263
  throw err;
263
264
  if (ast)
264
265
  asts.push( ast );
265
266
  });
266
- } );
267
+ }
267
268
  }
268
269
  }
269
270
 
@@ -378,6 +379,15 @@ function compileSourcesX( sourcesDict, options = {} ) {
378
379
  ast.location = { file: filename };
379
380
  assertConsistency( ast, options );
380
381
  }
382
+
383
+ for (const dep of sources[filename].dependencies || []) {
384
+ if (!dep.realname) {
385
+ // `realname` is used by setLayers(). For compileSources(), we don't resolve
386
+ // the USING paths and use the literal instead, which may be part of the
387
+ // source dictionary.
388
+ dep.realname = dep.val;
389
+ }
390
+ }
381
391
  }
382
392
  moduleLayers.setLayers( sources );
383
393
 
@@ -391,14 +401,15 @@ function compileSourcesX( sourcesDict, options = {} ) {
391
401
  * @param {object} options Options
392
402
  * @returns {object} XSN
393
403
  *
394
- * TODO: probaby issue message api-recompiled-csn there.
404
+ * TODO: probably issue message api-recompiled-csn there.
395
405
  */
396
406
  function recompileX( csn, options ) {
397
- options = { ...options, parseCdl: false, $recompile: true };
398
- // TODO: $recompile: true should be enough
399
407
  // Explicitly set parseCdl to false because backends cannot handle it
400
- // Explicitly delete all toCsn options:
408
+ options = { ...options, parseCdl: false, $recompile: true };
409
+ // Reset csnFlavor: Use client style (default)
410
+ delete options.csnFlavor;
401
411
  delete options.toCsn;
412
+ // TODO: $recompile: true should be enough
402
413
 
403
414
  const file = csn.$location && csn.$location.file &&
404
415
  csn.$location.file.replace(/[.]cds$/, '.cds.csn') || '<recompile>.csn';
@@ -428,6 +439,9 @@ function compileDoX( model ) {
428
439
  const { throwWithError } = model.$messageFunctions;
429
440
  if (!options.testMode)
430
441
  model.meta = {}; // provide initial central meta object
442
+
443
+ checkRemovedDeprecatedFlags( options, model.$messageFunctions );
444
+
431
445
  if (options.parseOnly) {
432
446
  throwWithError();
433
447
  return model;
@@ -500,7 +514,7 @@ function processFilenamesSync( filenames, dir ) {
500
514
  // Resolve possible symbolic link; if the file does not exist
501
515
  // we just continue using the original name because readFile()
502
516
  // already handles non-existent files.
503
- name = fs.realpathSync(name);
517
+ name = fs.realpathSync.native(name);
504
518
  }
505
519
  catch (e) {
506
520
  // Ignore the not-found (ENOENT) error
@@ -53,6 +53,12 @@ function layer( art ) {
53
53
  return art && art._layerRepresentative;
54
54
  }
55
55
 
56
+ function realname( art ) {
57
+ while (art && art.kind !== 'source')
58
+ art = art._block;
59
+ return art && art.realname || '';
60
+ }
61
+
56
62
  function compareLayer( a, b ) {
57
63
  while (a && a.kind !== 'source')
58
64
  a = a._block;
@@ -64,5 +70,6 @@ function compareLayer( a, b ) {
64
70
  module.exports = {
65
71
  setLayers,
66
72
  layer,
73
+ realname,
67
74
  compareLayer,
68
75
  };
@@ -74,21 +74,21 @@ function populate( model ) {
74
74
  let newAutoExposed = [];
75
75
 
76
76
  // behavior depending on option `deprecated`:
77
- const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
77
+ const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
78
78
  // TODO: we should get rid of noElementsExpansion soon; both
79
79
  // beta.nestedProjections and beta.universalCsn do not work with it.
80
80
  const scopedRedirections
81
81
  = enableExpandElements &&
82
- !isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ) &&
83
- !isDeprecatedEnabled( options, 'shortAutoexposed' ) &&
84
- !isDeprecatedEnabled( options, 'longAutoexposed' ) &&
85
- !isDeprecatedEnabled( options, 'noInheritedAutoexposeViaComposition' ) &&
86
- !isDeprecatedEnabled( options, 'noScopedRedirections' );
82
+ !isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ) &&
83
+ !isDeprecatedEnabled( options, '_shortAutoexposed' ) &&
84
+ !isDeprecatedEnabled( options, '_longAutoexposed' ) &&
85
+ !isDeprecatedEnabled( options, '_noInheritedAutoexposeViaComposition' ) &&
86
+ !isDeprecatedEnabled( options, '_noScopedRedirections' );
87
87
  const autoexposeViaComposition
88
- = (isDeprecatedEnabled( options, 'noInheritedAutoexposeViaComposition' ))
88
+ = (isDeprecatedEnabled( options, '_noInheritedAutoexposeViaComposition' ))
89
89
  ? 'Composition'
90
90
  : true;
91
- const redirectInSubQueries = isDeprecatedEnabled( options, 'redirectInSubQueries' );
91
+ const redirectInSubQueries = isDeprecatedEnabled( options, '_redirectInSubQueries' );
92
92
 
93
93
  forEachDefinition( model, traverseElementEnvironments );
94
94
  while (newAutoExposed.length) {
@@ -1094,7 +1094,7 @@ function populate( model ) {
1094
1094
  return false;
1095
1095
  }
1096
1096
  // no @cds.autoexpose or @cds.autoexpose:null
1097
- // TODO: introduce deprecated.noInheritedAutoexposeViaComposition
1097
+ // TODO: introduce deprecated._noInheritedAutoexposeViaComposition
1098
1098
  art.$autoexpose = model.$compositionTargets[art.name.absolute]
1099
1099
  ? autoexposeViaComposition
1100
1100
  : null;
@@ -1103,15 +1103,15 @@ function populate( model ) {
1103
1103
 
1104
1104
  function autoExposedName( target, service, elemScope ) {
1105
1105
  const { absolute } = target.name;
1106
- if (isDeprecatedEnabled( options, 'shortAutoexposed' )) {
1106
+ if (isDeprecatedEnabled( options, '_shortAutoexposed' )) {
1107
1107
  const parent = definitionScope( target )._parent;
1108
1108
  const name = (parent) ? absolute.substring( parent.name.absolute.length + 1 ) : absolute;
1109
- // no need for dedot here (as opposed to deprecated.longAutoexposed), as
1109
+ // no need for dedot here (as opposed to deprecated._longAutoexposed), as
1110
1110
  // the name for dependent entities have already been created using `_` then
1111
1111
  return `${ service.name.absolute }.${ name }`;
1112
1112
  }
1113
- if (isDeprecatedEnabled( options, 'longAutoexposed' )) {
1114
- const dedot = isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' );
1113
+ if (isDeprecatedEnabled( options, '_longAutoexposed' )) {
1114
+ const dedot = isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' );
1115
1115
  return `${ service.name.absolute }.${ dedot ? absolute.replace( /\./g, '_' ) : absolute }`;
1116
1116
  }
1117
1117
  const base = definitionScope( target );
@@ -56,9 +56,9 @@ function propagate( model ) {
56
56
  returns,
57
57
  };
58
58
  const { options } = model;
59
- const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
59
+ const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
60
60
  // eslint-disable-next-line max-len
61
- const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, 'oldVirtualNotNullPropagation' );
61
+ const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, '_oldVirtualNotNullPropagation' );
62
62
 
63
63
  forEachDefinition( model, run );
64
64
 
@@ -189,7 +189,7 @@ function propagate( model ) {
189
189
  if (!type || type._main)
190
190
  return false;
191
191
  // We do not consider the $expand status, as elements are already expanded
192
- // by the resolve(), and if not due to deprecated.noElementsExpansion
192
+ // by the resolve(), and if not due to deprecated._noElementsExpansion
193
193
  run( type );
194
194
  return type[prop];
195
195
  }