@sap/cds-compiler 2.13.8 → 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 (127) hide show
  1. package/CHANGELOG.md +155 -1594
  2. package/bin/cdsc.js +144 -66
  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 +237 -122
  10. package/lib/api/options.js +17 -88
  11. package/lib/api/validate.js +12 -16
  12. package/lib/base/keywords.js +216 -109
  13. package/lib/base/message-registry.js +152 -37
  14. package/lib/base/messages.js +145 -83
  15. package/lib/base/model.js +44 -2
  16. package/lib/base/optionProcessorHelper.js +19 -0
  17. package/lib/checks/actionsFunctions.js +7 -5
  18. package/lib/checks/annotationsOData.js +11 -32
  19. package/lib/checks/arrayOfs.js +1 -34
  20. package/lib/checks/cdsPersistence.js +1 -0
  21. package/lib/checks/elements.js +6 -6
  22. package/lib/checks/invalidTarget.js +1 -1
  23. package/lib/checks/nonexpandableStructured.js +1 -1
  24. package/lib/checks/queryNoDbArtifacts.js +2 -1
  25. package/lib/checks/selectItems.js +5 -1
  26. package/lib/checks/types.js +4 -2
  27. package/lib/checks/utils.js +2 -2
  28. package/lib/checks/validator.js +4 -5
  29. package/lib/compiler/assert-consistency.js +16 -10
  30. package/lib/compiler/base.js +1 -0
  31. package/lib/compiler/builtins.js +98 -9
  32. package/lib/compiler/checks.js +22 -70
  33. package/lib/compiler/define.js +61 -13
  34. package/lib/compiler/extend.js +79 -14
  35. package/lib/compiler/finalize-parse-cdl.js +46 -29
  36. package/lib/compiler/index.js +100 -37
  37. package/lib/compiler/moduleLayers.js +7 -0
  38. package/lib/compiler/populate.js +19 -18
  39. package/lib/compiler/propagator.js +7 -4
  40. package/lib/compiler/resolve.js +297 -234
  41. package/lib/compiler/shared.js +107 -102
  42. package/lib/compiler/tweak-assocs.js +16 -11
  43. package/lib/compiler/utils.js +5 -0
  44. package/lib/edm/annotations/genericTranslation.js +93 -21
  45. package/lib/edm/csn2edm.js +230 -115
  46. package/lib/edm/edm.js +305 -226
  47. package/lib/edm/edmPreprocessor.js +509 -438
  48. package/lib/edm/edmUtils.js +31 -45
  49. package/lib/gen/Dictionary.json +98 -22
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +10 -30
  52. package/lib/gen/language.tokens +105 -114
  53. package/lib/gen/languageLexer.interp +1 -34
  54. package/lib/gen/languageLexer.js +889 -1007
  55. package/lib/gen/languageLexer.tokens +95 -106
  56. package/lib/gen/languageParser.js +20786 -22199
  57. package/lib/json/csnVersion.js +10 -11
  58. package/lib/json/from-csn.js +59 -51
  59. package/lib/json/to-csn.js +10 -10
  60. package/lib/language/antlrParser.js +2 -2
  61. package/lib/language/docCommentParser.js +62 -39
  62. package/lib/language/errorStrategy.js +52 -40
  63. package/lib/language/genericAntlrParser.js +348 -229
  64. package/lib/language/language.g4 +629 -653
  65. package/lib/language/multiLineStringParser.js +14 -42
  66. package/lib/language/textUtils.js +44 -0
  67. package/lib/main.d.ts +46 -43
  68. package/lib/main.js +108 -79
  69. package/lib/model/csnRefs.js +34 -7
  70. package/lib/model/csnUtils.js +337 -332
  71. package/lib/model/enrichCsn.js +1 -0
  72. package/lib/model/revealInternalProperties.js +30 -10
  73. package/lib/model/sortViews.js +32 -31
  74. package/lib/modelCompare/compare.js +6 -6
  75. package/lib/optionProcessor.js +73 -46
  76. package/lib/render/.eslintrc.json +1 -1
  77. package/lib/render/DuplicateChecker.js +4 -7
  78. package/lib/render/manageConstraints.js +70 -2
  79. package/lib/render/toCdl.js +1042 -882
  80. package/lib/render/toHdbcds.js +195 -245
  81. package/lib/render/toRename.js +44 -22
  82. package/lib/render/toSql.js +225 -241
  83. package/lib/render/utils/common.js +145 -15
  84. package/lib/render/utils/sql.js +20 -19
  85. package/lib/sql-identifier.js +6 -0
  86. package/lib/transform/db/.eslintrc.json +4 -3
  87. package/lib/transform/db/associations.js +2 -2
  88. package/lib/transform/db/cdsPersistence.js +5 -15
  89. package/lib/transform/db/constraints.js +4 -2
  90. package/lib/transform/db/expansion.js +22 -16
  91. package/lib/transform/db/flattening.js +109 -80
  92. package/lib/transform/db/transformExists.js +7 -7
  93. package/lib/transform/db/views.js +9 -6
  94. package/lib/transform/draft/.eslintrc.json +2 -2
  95. package/lib/transform/draft/db.js +6 -6
  96. package/lib/transform/draft/odata.js +6 -7
  97. package/lib/transform/forHanaNew.js +62 -48
  98. package/lib/transform/forOdataNew.js +49 -50
  99. package/lib/transform/localized.js +31 -20
  100. package/lib/transform/odata/toFinalBaseType.js +16 -14
  101. package/lib/transform/odata/typesExposure.js +146 -198
  102. package/lib/transform/odata/utils.js +1 -38
  103. package/lib/transform/transformUtilsNew.js +67 -84
  104. package/lib/transform/translateAssocsToJoins.js +7 -3
  105. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  106. package/lib/transform/universalCsn/coreComputed.js +16 -9
  107. package/lib/transform/universalCsn/universalCsnEnricher.js +60 -10
  108. package/lib/utils/file.js +3 -3
  109. package/lib/utils/moduleResolve.js +13 -6
  110. package/lib/utils/timetrace.js +20 -21
  111. package/package.json +35 -4
  112. package/share/messages/message-explanations.json +2 -1
  113. package/share/messages/syntax-expected-integer.md +37 -0
  114. package/doc/ApiMigration.md +0 -237
  115. package/doc/CommandLineMigration.md +0 -58
  116. package/doc/ErrorMessages.md +0 -175
  117. package/doc/FioriAnnotations.md +0 -94
  118. package/doc/ODataTransformation.md +0 -273
  119. package/lib/backends.js +0 -529
  120. package/lib/fix_antlr4-8_warning.js +0 -56
  121. package/lib/transform/odata/attachPath.js +0 -96
  122. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  123. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  124. package/lib/transform/odata/referenceFlattener.js +0 -296
  125. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  126. package/lib/transform/odata/structuralPath.js +0 -72
  127. package/lib/transform/odata/structureFlattener.js +0 -171
@@ -11,6 +11,7 @@ const {
11
11
  setArtifactLink,
12
12
  dependsOn,
13
13
  pathName,
14
+ annotationHasEllipsis,
14
15
  } = require('./utils');
15
16
 
16
17
  function artifactsEnv( art ) {
@@ -157,8 +158,8 @@ function fns( model ) {
157
158
  const VolatileFns = model.$volatileFunctions;
158
159
  Object.assign( model.$functions, {
159
160
  resolveUncheckedPath,
161
+ resolveTypeArgumentsUnchecked,
160
162
  resolvePath,
161
- resolveTypeArguments,
162
163
  defineAnnotations,
163
164
  attachAndEmitValidNames,
164
165
  } );
@@ -459,35 +460,64 @@ function fns( model ) {
459
460
  }
460
461
  }
461
462
 
462
- // Resolve the type arguments provided with a type referenced for artifact or
463
- // element `artifact`. This function does nothing if the referred type
464
- // `typeArtifact` does not have a `parameters` property (currently, only
465
- // builtin-types have it, see ./builtins.js).
466
- //
467
- // For each property name `<prop>` in `typeArtifact.parameters`, we move a number
468
- // in art.$typeArgs (a vector of numbers with locations) to `artifact.<prop>`.
469
- // TODO: error if no parameters applicable
470
- // TODO: also check for number
471
- function resolveTypeArguments(artifact, typeArtifact, user) {
472
- const args = artifact.$typeArgs || [];
463
+ /**
464
+ * Resolve the type arguments of `artifact` according to the type `typeArtifact`.
465
+ * User is used for semantic message location.
466
+ *
467
+ * For builtins, for each property name `<prop>` in `typeArtifact.parameters`, we move a value
468
+ * in art.$typeArgs (a vector of numbers with locations) to `artifact.<prop>`.
469
+ *
470
+ * For non-builtins, we take either one or two arguments and interpret them
471
+ * as `length` or `precision`/`scale`.
472
+ *
473
+ * Left-over arguments are errors for non-builtins and warnings for builtins.
474
+ *
475
+ * @param {object} artifact
476
+ * @param {object} typeArtifact
477
+ * @param {CSN.Artifact} user
478
+ */
479
+ function resolveTypeArgumentsUnchecked(artifact, typeArtifact, user) {
480
+ let args = artifact.$typeArgs || [];
473
481
  const parameters = typeArtifact.parameters || [];
474
- const parLength = parameters.length;
475
482
 
476
- for (let i = 0; i < parLength; ++i) {
477
- let par = parameters[i];
478
- if (!(par instanceof Object))
479
- par = { name: par };
480
- if (!artifact[par.name] && i < args.length)
481
- artifact[par.name] = args[i];
483
+ if (parameters.length > 0) {
484
+ // For Builtins
485
+ for (let i = 0; i < parameters.length; ++i) {
486
+ let par = parameters[i];
487
+ if (!(par instanceof Object))
488
+ par = { name: par };
489
+ if (!artifact[par.name] && i < args.length)
490
+ artifact[par.name] = args[i];
491
+ }
492
+ args = args.slice(parameters.length);
482
493
  }
483
- if (args.length > parLength) {
484
- artifact.$typeArgs = artifact.$typeArgs.slice(parLength);
485
- warning( 'unexpected-type-arg', [ artifact.$typeArgs[0].location, user ],
486
- { art: typeArtifact }, 'Too many arguments for type $(ART)' );
494
+ else if (args.length > 0 && !typeArtifact.builtin) {
495
+ // One or two arguments are interpreted as either length or precision/scale.
496
+ // For builtins, we know what arguments are expected, and we do not need this mapping.
497
+ // Also, we expect non-structured types.
498
+ if (args.length === 1) {
499
+ artifact.length = args[0];
500
+ args = args.slice(1);
501
+ }
502
+ else if (args.length === 2) {
503
+ artifact.precision = args[0];
504
+ artifact.scale = args[1];
505
+ args = args.slice(2);
506
+ }
487
507
  }
488
- else if (artifact.$typeArgs) {
489
- delete artifact.$typeArgs;
508
+
509
+ if (!artifact.$typeArgs)
510
+ return;
511
+
512
+ // Warn about left-over arguments.
513
+ if (args.length > 0) {
514
+ const loc = [ args[args.length - 1].location, user ];
515
+ if (typeArtifact.builtin)
516
+ message( 'type-ignoring-argument', loc, { art: typeArtifact } );
517
+ else
518
+ error( 'type-unexpected-argument', loc, { '#': 'std', art: typeArtifact });
490
519
  }
520
+ artifact.$typeArgs = undefined;
491
521
  }
492
522
 
493
523
  // Return artifact or element referred by name `head`. The first environment
@@ -644,6 +674,9 @@ function fns( model ) {
644
674
  art = item._artifact;
645
675
  if (Array.isArray(art))
646
676
  return false;
677
+ if (art.$requireElementAccess && path.length === 1)
678
+ // Path with only one item, but we expect an element, e.g. `$at.from`.
679
+ signalMissingElementAccess(art, [ item.location, user ]);
647
680
  continue;
648
681
  }
649
682
 
@@ -732,7 +765,7 @@ function fns( model ) {
732
765
  signalNotFound( spec.undefinedDef || 'ref-undefined-def', [ item.location, user ],
733
766
  [ env ], { art: a } );
734
767
  }
735
- else if (art.name.select && art.name.select > 1) {
768
+ else if (art.name && art.name.select && art.name.select > 1) {
736
769
  // TODO: 'The current query has no element $(MEMBER)' with $self.MEMBER
737
770
  // and 'The sub query for alias $(ALIAS) has no element $(MEMBER)'
738
771
  // TODO: probably not extra messageId, but text variant
@@ -748,8 +781,13 @@ function fns( model ) {
748
781
  { param: 'Entity $(ART) has no parameter $(MEMBER)' } );
749
782
  }
750
783
  else {
784
+ const variant = art.kind === 'aspect' && !art.name && 'aspect';
751
785
  signalNotFound( spec.undefinedDef || 'ref-undefined-element', [ item.location, user ],
752
- [ env ], { art: searchName( art, item.id, 'element' ) } );
786
+ [ env ], {
787
+ '#': variant,
788
+ art: (variant ? '' : searchName( art, item.id, 'element' )),
789
+ id: item.id,
790
+ } );
753
791
  }
754
792
  return null;
755
793
  }
@@ -774,11 +812,30 @@ function fns( model ) {
774
812
  attachAndEmitValidNames(err, ...valid.reverse());
775
813
  }
776
814
 
815
+ /**
816
+ * Emit a 'ref-expected-element' error for magic variable references
817
+ * that require element accesses but don't do.
818
+ * For example: `$at`, but `$at.from` or `$at.to` is required.
819
+ *
820
+ * @param {object} art
821
+ * @param {any} location
822
+ */
823
+ function signalMissingElementAccess(art, location) {
824
+ const err = message( 'ref-expected-element', location,
825
+ { '#': 'magicVar', id: art.name.id } );
826
+ // Mapping for better valid names: from -> $at.from
827
+ const valid = Object.keys(art.elements || {}).reduce((prev, curr) => {
828
+ prev[`${ art.name.id }.${ curr }`] = true;
829
+ return prev;
830
+ }, Object.create(null));
831
+ attachAndEmitValidNames(err, valid);
832
+ }
833
+
777
834
  /**
778
835
  * Attaches a dictionary of valid names to the given compiler message.
779
836
  * In test mode, an info message is emitted with a list of valid names.
780
837
  *
781
- * @param {CSN.Message} msg CDS Compiler message
838
+ * @param {CompileMessage} msg CDS Compiler message
782
839
  * @param {...object} validDicts One ore more artifact dictionaries such as in `_block`.
783
840
  */
784
841
  function attachAndEmitValidNames(msg, ...validDicts) {
@@ -804,14 +861,10 @@ function fns( model ) {
804
861
  }
805
862
  }
806
863
 
807
- // Resolve all annotation assignments for the node `art`. Set `art.@` to all
808
- // flattened assignments. This function might issue error message for
809
- // duplicate assignments.
810
- // TODOs:
811
- // * do something for extensions by CSN or Properties parsers
812
- // * make sure that we do not issue repeated warnings due to flattening if an
813
- // annotation definition is missing
814
- function defineAnnotations( construct, art, block, priority = 'define' ) {
864
+ // Set _block links for annotations (necessary for layering).
865
+ // Issue messages for annotations on namespaces and builtins (TODO: really here?)
866
+ // Also copy annotations from `construct` to `art` (TODO: separate that functionality).
867
+ function defineAnnotations( construct, art, block, priority = false ) {
815
868
  if (!options.parseCdl && construct.kind === 'annotate') {
816
869
  // Namespaces cannot be annotated in CSN but because they exist as XSN artifacts
817
870
  // they can still be applied. Namespace annotations are extracted in to-csn.js
@@ -829,76 +882,28 @@ function fns( model ) {
829
882
  'Builtin types should not be annotated. Use custom type instead' );
830
883
  }
831
884
  }
832
- // TODO: block should be construct._block
833
- if (construct.$annotations && construct.$annotations.doc )
834
- art.doc = construct.$annotations.doc; // e.g. through `annotate` statement in CDL
835
- else if (construct.doc)
885
+ if (construct.doc)
836
886
  art.doc = construct.doc; // e.g. through `extensions` array in CSN
837
- if (!construct.$annotations) {
838
- if (!block || block.$frontend !== 'json')
839
- return; // namespace, or in CDL source without @annos:
840
- // CSN input: set _block and $priority, shallow-copy from extension
841
- for (const annoProp in construct) {
842
- if (annoProp.charAt(0) === '@') {
843
- let annos = construct[annoProp];
844
- if (!(Array.isArray(annos)))
845
- annos = [ annos ];
846
- for (const a of annos) {
847
- setLink( a, '_block', block );
848
- a.$priority = priority;
849
- if (construct !== art)
850
- addAnnotation( art, annoProp, a );
851
- }
852
- }
853
- }
854
- return;
855
- }
856
- for (const anno of construct.$annotations) {
857
- const ref = anno.name;
858
- const name = resolveUncheckedPath( ref, 'annotation', { _block: block } );
859
- const annoProp = (anno.name.variant)
860
- ? `@${ name }#${ anno.name.variant.id }`
861
- : `@${ name }`;
862
- flatten( ref.path, annoProp, anno.value || {}, anno.name.variant, anno.name.location );
863
- }
864
- return;
865
-
866
- function flatten( path, annoProp, value, iHaveVariant, location ) {
867
- // Be robust if struct value has duplicate element names
868
- if (Array.isArray(value)) // TODO: do that differently in CDL parser
869
- return; // discard duplicates in flattened form
870
-
871
- if (value.literal === 'struct') {
872
- for (const item of value._struct || []) {
873
- let prop = pathName(item.name.path);
874
- if (item.name.variant) {
875
- if (iHaveVariant) {
876
- error( 'anno-duplicate-variant', [ item.name.variant.location, construct ],
877
- {}, // TODO: params
878
- 'Annotation variant has been already provided' );
879
- }
880
- prop = `${ prop }#${ item.name.variant.id }`; // TODO: check for double variants
887
+ // set _block (for layering) and $priority, shallow-copy from extension
888
+ // TODO: think of removing $priority, then
889
+ // no _block: define, _block: annotate/extend/edmx
890
+ // would fit with extending defs with props like length
891
+ for (const annoProp in construct) {
892
+ if (annoProp.charAt(0) === '@') {
893
+ let annos = construct[annoProp];
894
+ if (!(Array.isArray(annos)))
895
+ annos = [ annos ];
896
+ for (const a of annos) {
897
+ setLink( a, '_block', block );
898
+ a.$priority = priority; // is now: undefined (auto-set) | false | 'annotate' | 'extend'
899
+ if (construct !== art)
900
+ addAnnotation( art, annoProp, a );
901
+ if (!priority && annotationHasEllipsis( a )) {
902
+ error( 'anno-unexpected-ellipsis',
903
+ [ a.name.location, art ], { code: '...' } );
881
904
  }
882
- flatten( [ ...path, ...item.name.path ], `${ annoProp }.${ prop }`, item, iHaveVariant || item.name.variant);
883
- }
884
- for (const prop in value.struct) {
885
- const item = value.struct[prop];
886
- flatten( [ ...path, item.name ], `${ annoProp }.${ prop }`, item, iHaveVariant );
887
905
  }
888
- return;
889
906
  }
890
- const anno = Object.assign( {}, value ); // shallow copy
891
- anno.name = {
892
- path,
893
- location: location ||
894
- value.name && value.name.location ||
895
- value.path && value.path.location,
896
- };
897
- setLink( anno, '_block', block );
898
- // TODO: _parent, _main is set later (if we have ElementRef), or do we
899
- // set _artifact?
900
- anno.$priority = priority;
901
- addAnnotation( art, annoProp, anno );
902
907
  }
903
908
  }
904
909
  }
@@ -39,7 +39,7 @@ function tweakAssocs( model ) {
39
39
  const { environment } = model.$volatileFunctions;
40
40
 
41
41
  // behavior depending on option `deprecated`:
42
- const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
42
+ const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
43
43
  // TODO: we should get rid of noElementsExpansion soon; both
44
44
  // beta.nestedProjections and beta.universalCsn do not work with it.
45
45
 
@@ -95,15 +95,14 @@ function tweakAssocs( model ) {
95
95
  if (!target || target._service) // assoc to other service is OK
96
96
  return;
97
97
  if (!elem.$inferred) { // && !elem.target.$inferred
98
- // TODO: spec meeting 2021-01-22: no warning
99
- warning( 'assoc-target-not-in-service', [ elem.target.location, elem ],
100
- { target, '#': (elem._main.query ? 'select' : 'define') }, {
101
- std: 'Target $(TARGET) of association is outside any service', // not used
102
- // eslint-disable-next-line max-len
103
- define: 'Target $(TARGET) of explicitly defined association is outside any service',
104
- // eslint-disable-next-line max-len
105
- select: 'Target $(TARGET) of explicitly selected association is outside any service',
106
- } );
98
+ info( 'assoc-target-not-in-service', [ elem.target.location, elem ],
99
+ { target, '#': (elem._main.query ? 'select' : 'define') }, {
100
+ std: 'Target $(TARGET) of association is outside any service', // not used
101
+ // eslint-disable-next-line max-len
102
+ define: 'Target $(TARGET) of explicitly defined association is outside any service',
103
+ // eslint-disable-next-line max-len
104
+ select: 'Target $(TARGET) of explicitly selected association is outside any service',
105
+ } );
107
106
  }
108
107
  else {
109
108
  info( 'assoc-outside-service', [ elem.target.location, elem ],
@@ -459,7 +458,13 @@ function tweakAssocs( model ) {
459
458
  if (!forKeys)
460
459
  break;
461
460
  setArtifactLink( item, null );
462
- error( 'rewrite-undefined-key', [ weakLocation( (elem.target || elem).location ), assoc ],
461
+ const culprit = elem.target && !elem.target.$inferred && elem.target ||
462
+ (elem.value && elem.value.path &&
463
+ elem.value.path[elem.value.path.length - 1]) ||
464
+ elem;
465
+ // TODO: probably better to collect the non-projected foreign keys
466
+ // and have one message for all
467
+ error( 'rewrite-undefined-key', [ weakLocation( culprit.location ), assoc ],
463
468
  { id: item.id, art: alias._main },
464
469
  'Foreign key $(ID) has not been found in target $(ART)' );
465
470
  return null;
@@ -31,6 +31,10 @@ function annotationVal( anno ) {
31
31
  function annotationIsFalse( anno ) { // falsy, but not null (unset)
32
32
  return anno && (anno.val === false || anno.val === 0 || anno.val === '');
33
33
  }
34
+ function annotationHasEllipsis( anno ) {
35
+ const { val } = anno || {};
36
+ return Array.isArray( val ) && val.some( v => v.literal === 'token' && v.val === '...' );
37
+ }
34
38
 
35
39
  /**
36
40
  * Set compiler-calculated annotation value.
@@ -381,6 +385,7 @@ module.exports = {
381
385
  pushLink,
382
386
  annotationVal,
383
387
  annotationIsFalse,
388
+ annotationHasEllipsis,
384
389
  annotateWith,
385
390
  setLink,
386
391
  setArtifactLink,
@@ -4,15 +4,16 @@ const edmUtils = require('../edmUtils.js');
4
4
  const preprocessAnnotations = require('./preprocessAnnotations.js');
5
5
  const oDataDictionary = require('../../gen/Dictionary.json');
6
6
  const { forEachDefinition } = require('../../model/csnUtils');
7
+ const { forEach } = require("../../utils/objectUtils");
7
8
 
8
9
 
9
- /* Vocabulary overview as of January 2020:
10
-
10
+ /*
11
11
  OASIS: https://github.com/oasis-tcs/odata-vocabularies/tree/master/vocabularies
12
12
  Aggregation (published)
13
13
  Authorization (published)
14
14
  Capabilities (published)
15
15
  Core (published)
16
+ JSON (published)
16
17
  Measures (published)
17
18
  Repeatability (published)
18
19
  Temporal (not published, not yet finalized)
@@ -23,8 +24,9 @@ const { forEachDefinition } = require('../../model/csnUtils');
23
24
  CodeList (published)
24
25
  Common (pubished)
25
26
  Communication (published)
27
+ DataIntegration (published)
26
28
  Graph (published, experimental)
27
- Hierarchy (not published, still experimental)
29
+ Hierarchy (published, experimental)
28
30
  HTML5 (published, experimental)
29
31
  ODM (published, experimental)
30
32
  PersonalData (published)
@@ -73,16 +75,31 @@ const vocabularyDefinitions = {
73
75
  'inc': { Alias: 'Core', Namespace: 'Org.OData.Core.V1' },
74
76
  'int': { filename: 'Core.xml' }
75
77
  },
78
+ 'DataIntegration': {
79
+ 'ref': { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/DataIntegration.xml' },
80
+ 'inc': { Alias: 'DataIntegration', Namespace: 'com.sap.vocabularies.DataIntegration.v1' },
81
+ 'int': { filename: 'DataIntegration.xml' }
82
+ },
76
83
  'Graph': {
77
84
  'ref': { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/Graph.xml' },
78
85
  'inc': { Alias: 'Graph', Namespace: 'com.sap.vocabularies.Graph.v1' },
79
86
  'int': { filename: 'Graph.xml' }
80
87
  },
88
+ 'Hierarchy': {
89
+ 'ref': { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/Hierarchy.xml' },
90
+ 'inc': { Alias: 'Hierarchy', Namespace: 'com.sap.vocabularies.Hierarchy.v1' },
91
+ 'int': { filename: 'Hierarchy.xml' }
92
+ },
81
93
  'HTML5': {
82
94
  'ref': { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/HTML5.xml' },
83
95
  'inc': { Alias: 'HTML5', Namespace: 'com.sap.vocabularies.HTML5.v1' },
84
96
  'int': { filename: 'HTML5.xml' }
85
97
  },
98
+ 'JSON': {
99
+ 'ref': { Uri: 'https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.JSON.V1.xml' },
100
+ 'inc': { Alias: 'JSON', Namespace: 'Org.OData.JSON.V1' },
101
+ 'int': { filename: 'JSON.xml' }
102
+ },
86
103
  'Measures': {
87
104
  'ref': { Uri: 'https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Measures.V1.xml' },
88
105
  'inc': { Alias: 'Measures', Namespace: 'Org.OData.Measures.V1' },
@@ -316,7 +333,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
316
333
  function handleNestedElements(objname, baseElemName, elementsObj) {
317
334
  if(!elementsObj) return;
318
335
  Object.entries(elementsObj).forEach(([elemName, element]) => {
319
- if (Object.keys(element).filter( x => x.substr(0,1) === '@' ).filter(filterKnownVocabularies).length > 0) {
336
+ if (Object.keys(element).filter( x => x[0] === '@' ).filter(filterKnownVocabularies).length > 0) {
320
337
  message(warning, null, `annotations at nested elements are not yet supported, object ${objname}, element ${baseElemName}.${elemName}`);
321
338
  }
322
339
 
@@ -414,17 +431,23 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
414
431
  // do nothing
415
432
 
416
433
  if(!isEdmPropertyRendered(carrier, options) ||
417
- (isV2() && (edmUtils.isDerivedType(carrier) || carrier['@Core.MediaType']))) {
434
+ (isV2() && (edmUtils.isDerivedType(carrier)))) {
418
435
  return;
419
436
  }
420
437
 
421
438
  // Filter unknown toplevel annotations
422
439
  // Final filtering of all annotations is done in handleTerm
423
- const annoNames = Object.keys(carrier).filter( x => x.substr(0,1) === '@' );
440
+
441
+ let annoNames = Object.keys(carrier).filter( x => x[0] === '@' );
424
442
  const nullWhitelist = [ '@Core.OperationAvailable' ];
425
- const knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
443
+ let knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
426
444
  if (knownAnnos.length === 0) return;
427
445
 
446
+ if(rewriteInnerAnnotations()) {
447
+ annoNames = Object.keys(carrier).filter( x => x[0] === '@' );
448
+ knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
449
+ if (knownAnnos.length === 0) return;
450
+ }
428
451
  const prefixTree = createPrefixTree();
429
452
 
430
453
  // usually, for a given carrier there is one target
@@ -593,6 +616,51 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
593
616
  */
594
617
  }
595
618
 
619
+ function rewriteInnerAnnotations() {
620
+ let rc = false;
621
+ for (let a of knownAnnos) {
622
+ const [ prefix, innerAnnotation ] = a.split('.@');
623
+ /*
624
+ New inner annotation (de-)structuring of the core compiler to make
625
+ $value arrays extendable via ellipsis
626
+ @anno: { $value: [ ... ], @innerAnno: ... } is now cracked up by
627
+ the core compiler into:
628
+ @anno: [ ...]
629
+ @anno.@innerAnno: ...
630
+
631
+ Conflict handling if $value is present:
632
+ @anno
633
+ @anno.$value
634
+ @anno.@innerAnno
635
+
636
+ @anno has precedence (as it was before this change) but now
637
+ @anno.$value is overwritten with @anno and the inner annotations
638
+ are applied.
639
+
640
+ Trigger is always the inner annotation, if no inner annotation
641
+ is available, @anno has precedence.
642
+
643
+ Insert $value into $edmJson with inner annotation as well.
644
+ */
645
+ if(innerAnnotation) {
646
+ if(carrier[prefix]) {
647
+ const valPrefix = prefix + '.$value';
648
+ carrier[valPrefix] = carrier[prefix];
649
+ delete carrier[prefix];
650
+ rc = true;
651
+ }
652
+ const edmJsonPrefix = prefix + '.$edmJson';
653
+ if(carrier[edmJsonPrefix]) {
654
+ const valPrefix = prefix + '.$value.$edmJson';
655
+ carrier[valPrefix] = carrier[edmJsonPrefix];
656
+ delete carrier[edmJsonPrefix];
657
+ rc = true;
658
+ }
659
+ }
660
+ }
661
+ return rc;
662
+ }
663
+
596
664
  function createPrefixTree() {
597
665
  // in csn, all annotations are flattened
598
666
  // => values can be - primitive values (string, number)
@@ -602,6 +670,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
602
670
  // by building a "prefix tree" for the annotations attached to the carrier
603
671
  // see example at definition of function mergePathStepsIntoPrefixTree
604
672
  const prefixTree = {};
673
+
605
674
  for (let a of knownAnnos) {
606
675
  // remove leading @ and split at "."
607
676
  // stop splitting at ".@" (used for nested annotations)
@@ -663,7 +732,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
663
732
  }
664
733
  mergePathStepsIntoPrefixTree(tree[name], pathSteps, index+1, carrier);
665
734
  }
666
- else {
735
+ else if(typeof tree === 'object' ){
667
736
  tree[name] = carrier['@' + pathSteps.join('.')];
668
737
  }
669
738
  }
@@ -714,9 +783,10 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
714
783
  * @type {object}
715
784
  * */
716
785
  let newAnno = undefined;
717
- const nullWhitelist = [ 'Core.OperationAvailable' ];
786
+ const omissions = { 'Aggregation.default':1 };
787
+ const nullList = { 'Core.OperationAvailable':1 };
718
788
  const voc = termName.slice(0, termName.indexOf('.'));
719
- if(vocabularyDefinitions[voc] && annoValue !== null || nullWhitelist.includes(termName)) {
789
+ if(vocabularyDefinitions[voc] && annoValue !== null && !omissions[termName]|| nullList[termName]) {
720
790
  newAnno = new Edm.Annotation(v, termName);
721
791
 
722
792
  // termName may contain a qualifier: @UI.FieldGroup#shippingStatus
@@ -729,8 +799,8 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
729
799
  message(error, context,
730
800
  `OData annotation qualifier "${ p[1] }" must start with a letter or underscore, followed by at most 127 letters, underscores or digits`);
731
801
  }
732
- newAnno.Term = termNameWithoutQualifiers;
733
- newAnno.Qualifier = p[1];
802
+ newAnno.setEdmAttribute('Term', termNameWithoutQualifiers);
803
+ newAnno.setEdmAttribute('Qualifier', p[1]);
734
804
  }
735
805
  if (p.length>2) {
736
806
  message(warning, context, `multiple qualifiers (${ p[1] },${ p[2] }${ p.length > 3 ? ',...' : '' })`);
@@ -827,7 +897,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
827
897
  // "pseudo-structure" used for embedding a piece of JSON that represents "OData CSDL, JSON Representation"
828
898
  oTarget.append(handleEdmJson(cAnnoValue['$edmJson'], context));
829
899
  }
830
- else if ( Object.keys(cAnnoValue).filter( x => x.substr(0,1) !== '@' ).length === 0) {
900
+ else if ( Object.keys(cAnnoValue).filter( x => x[0] !== '@' ).length === 0) {
831
901
  // object consists only of properties starting with "@"
832
902
  message(warning, context, 'nested annotations without corresponding base annotation');
833
903
  }
@@ -934,6 +1004,8 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
934
1004
  // Edm.Decimal -> Decimal
935
1005
  // integer tpye -> Int
936
1006
  function handleSimpleValue(val, dTypeName, context) {
1007
+ // these types must be represented as "String" values in XML:
1008
+ const castToXmlString = [ 'Edm.PrimitiveType', 'Edm.Stream', 'Edm.Untyped' ];
937
1009
  // caller already made sure that val is neither object nor array
938
1010
  dTypeName = resolveType(dTypeName);
939
1011
 
@@ -971,12 +1043,12 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
971
1043
  message(warning, context, `found String, but expected enum type ${ dTypeName }`);
972
1044
  typeName = 'EnumMember';
973
1045
  }
974
- else if (dTypeName && dTypeName.startsWith('Edm.') && dTypeName !== 'Edm.PrimitiveType') {
1046
+ else if (dTypeName && dTypeName.startsWith('Edm.') && !castToXmlString.includes(dTypeName)) {
975
1047
  // this covers also all paths
976
1048
  typeName = dTypeName.substring(4);
977
1049
  }
978
1050
  else {
979
- if(dTypeName == undefined || dTypeName === 'Edm.PrimitiveType')
1051
+ if(dTypeName == undefined || castToXmlString.some(t => t === dTypeName))
980
1052
  dTypeName = 'Edm.String';
981
1053
  // TODO
982
1054
  //message(warning, context, "type is not yet handled: found String, expected type: " + dTypeName);
@@ -1072,7 +1144,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
1072
1144
  // this type doesn't exist
1073
1145
  message(warning, context, `explicitly specified type '${ actualTypeName }' not found in vocabulary`);
1074
1146
  // explicitly mentioned type, render in XML and JSON
1075
- newRecord.Type = actualTypeName;
1147
+ newRecord.setEdmAttribute('Type', actualTypeName);
1076
1148
  }
1077
1149
  else if (dTypeName && !isDerivedFrom(actualTypeName, dTypeName)) {
1078
1150
  // this type doesn't fit the expected one
@@ -1080,7 +1152,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
1080
1152
  }' is not derived from expected type '${ dTypeName }'`);
1081
1153
  actualTypeName = dTypeName;
1082
1154
  // explicitly mentioned type, render in XML and JSON
1083
- newRecord.Type = actualTypeName;
1155
+ newRecord.setEdmAttribute('Type', actualTypeName);
1084
1156
  }
1085
1157
  else if (isAbstractType(actualTypeName)) {
1086
1158
  // this type is abstract
@@ -1088,7 +1160,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
1088
1160
  if(dTypeName)
1089
1161
  actualTypeName = dTypeName;
1090
1162
  // set to definition name and render in XML and JSON
1091
- newRecord.Type = actualTypeName;
1163
+ newRecord.setEdmAttribute('Type', actualTypeName);
1092
1164
  }
1093
1165
  else {
1094
1166
  // ok
@@ -1251,7 +1323,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
1251
1323
  const props = Object.create(null);
1252
1324
  Object.entries(obj).forEach(([k, val]) => {
1253
1325
  if(k === '@type') {
1254
- edmNode.Type = val;
1326
+ edmNode.setEdmAttribute('Type', val);
1255
1327
  }
1256
1328
  else {
1257
1329
  let child = undefined;
@@ -1297,14 +1369,14 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
1297
1369
  edmNode = exprDef.create(obj);
1298
1370
 
1299
1371
  // iterate over each obj.property and translate expression into EDM
1300
- Object.entries(obj).forEach(([name, val]) => {
1372
+ forEach(obj, (name, val) => {
1301
1373
  if(exprDef) {
1302
1374
  if(exprDef.anno && name[0] === '@') {
1303
1375
  edmNode.append(handleTerm(name.slice(1), val, context));
1304
1376
  }
1305
1377
  else if (exprDef.attr && exprDef.attr.includes(name)) {
1306
1378
  if (name[0] === '$') {
1307
- edmNode[name.slice(1)] = val;
1379
+ edmNode.setEdmAttribute(name.slice(1), val);
1308
1380
  }
1309
1381
  }
1310
1382
  else if (exprDef.jsonAttr && exprDef.jsonAttr.includes(name)) {