@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
@@ -49,13 +49,13 @@ const { dictAdd } = require('../base/dictionaries');
49
49
  const { dictLocation } = require('../base/location');
50
50
  const { searchName, weakLocation } = require('../base/messages');
51
51
  const { combinedLocation } = require('../base/location');
52
- const { forEachValue } = require('../utils/objectUtils');
53
52
  const { typeParameters } = require('./builtins');
54
53
 
55
54
  const { kindProperties } = require('./base');
56
55
  const {
57
56
  setLink,
58
57
  setArtifactLink,
58
+ annotationHasEllipsis,
59
59
  pathName,
60
60
  linkToOrigin,
61
61
  setMemberParent,
@@ -73,9 +73,6 @@ const layers = require('./moduleLayers');
73
73
 
74
74
  const $location = Symbol.for('cds.$location');
75
75
 
76
- const annotationPriorities = {
77
- define: 1, extend: 2, annotate: 2, edmx: 3,
78
- };
79
76
  const $inferred = Symbol.for('cds.$inferred');
80
77
 
81
78
  // Export function of this file. Resolve type references in augmented CSN
@@ -107,7 +104,7 @@ function resolve( model ) {
107
104
  /** @type {any} may also be a boolean */
108
105
 
109
106
  // behavior depending on option `deprecated`:
110
- const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
107
+ const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
111
108
  // TODO: we should get rid of noElementsExpansion soon; both
112
109
  // beta.nestedProjections and beta.universalCsn do not work with it.
113
110
 
@@ -211,9 +208,9 @@ function resolve( model ) {
211
208
  info( 'query-from-many', [ toMany.location, query ], { art: toMany },
212
209
  {
213
210
  // eslint-disable-next-line max-len
214
- std: 'Selecting from to-many association $(ART) - key properties are not propagated',
211
+ std: 'Key properties are not propagated because a to-many association $(ART) is selected',
215
212
  // eslint-disable-next-line max-len
216
- element: 'Selecting from to-many association $(MEMBER) of $(ART) - key properties are not propagated',
213
+ element: 'Key properties are not propagated because a to-many association $(MEMBER) of $(ART) is selected',
217
214
  } );
218
215
  }
219
216
  // Check that all keys from the source are projected:
@@ -714,241 +711,219 @@ function resolve( model ) {
714
711
  }
715
712
 
716
713
  function chooseAssignment( annoName, art ) {
717
- // TODO: getPath an all names
718
- const anno = art[annoName];
719
- if (!Array.isArray(anno)) { // just one assignment -> use it
720
- if (removeEllipsis( anno )) {
721
- error( 'anno-unexpected-ellipsis',
722
- [ anno.name.location, art ], { code: '...' } );
714
+ let anno = art[annoName];
715
+ if (!Array.isArray( anno )) { // just one assignment -> use it
716
+ if (!annotationHasEllipsis( anno ))
717
+ return;
718
+ anno = [ anno ];
719
+ }
720
+ // console.log('ASSIGN:',art.name.absolute,annoName)
721
+ const scheduledAssignments = [];
722
+ // sort assignment according to layer (define is bottom layer):
723
+ const layeredAnnos = layeredAssignments( anno );
724
+ let cont = true;
725
+ while (cont) {
726
+ const { assignments, issue } = assignmentsOfHighestLayers( layeredAnnos );
727
+ let index = assignments.length;
728
+ cont = !!index; // safety
729
+ while (--index >= 0) {
730
+ const a = assignments[index];
731
+ scheduledAssignments.push( a );
732
+ if (!annotationHasEllipsis( a )) {
733
+ cont = false;
734
+ break;
735
+ }
723
736
  }
724
- return;
725
- }
726
- // sort assignment according to layer
727
- const layerAnnos = Object.create(null);
728
- for (const a of anno) {
729
- const layer = layers.layer( a._block );
737
+ if (issue) {
738
+ // eslint-disable-next-line no-nested-ternary
739
+ const msg = (issue === true)
740
+ ? 'anno-duplicate'
741
+ : (index >= 0) ? 'anno-duplicate-unrelated-layer' : 'anno-unstable-array';
742
+ for (const a of assignments) {
743
+ if (!a.$errorReported)
744
+ message( msg, [ a.name.location, art ], { anno: annoName } );
745
+ }
746
+ }
747
+ // else if (index > 0) -- if we allow multiple assignments in one file - the last wins
748
+ }
749
+ // Now apply the assignments - all but the first have a '...'
750
+ let result = null;
751
+ scheduledAssignments.reverse();
752
+ for (const a of scheduledAssignments)
753
+ result = applyAssignment( result, a, art, annoName );
754
+ art[annoName] = result.name ? result
755
+ : Object.assign( {}, scheduledAssignments[scheduledAssignments.length - 1], result );
756
+ }
757
+
758
+ // Group assignments by their layers. An assignment provided with a definition
759
+ // is considered to be provided in a layer named '', the lowest layer.
760
+ // TODO: make this usable for extend (elements), too =
761
+ // do not use $priority, make assignments on define do not have own _block
762
+ function layeredAssignments( assignment ) {
763
+ const layered = Object.create(null);
764
+ for (const a of assignment) {
765
+ const layer = a.$priority && layers.layer( a );
766
+ // just consider layer if Extend/Annotate, not Define
730
767
  const name = (layer) ? layer.realname : '';
731
- const done = layerAnnos[name];
768
+ const done = layered[name];
732
769
  if (done)
733
- done.annos.push( a );
770
+ done.assignments.push( a );
734
771
  else
735
- layerAnnos[name] = { layer, annos: [ a ] };
772
+ layered[name] = { name, layer, assignments: [ a ] };
773
+ // TODO: file - if set: unique in layer
736
774
  }
737
- mergeArrayInSCCs();
738
- art[annoName] = mergeLayeredArrays( findLayerCandidate( ) );
739
- return;
775
+ return layered;
776
+ }
740
777
 
741
- // Merge annotations in each layer, i.e. multiple annotations in the same layer are
742
- // stored in an array and need to be merged before different layers can be merged.
743
- function mergeArrayInSCCs( ) {
744
- let pos = 0;
745
- forEachValue(layerAnnos, (layer) => {
746
- const mergeSource = layer.annos.find(v => (v.$priority === undefined ||
747
- annotationPriorities[v.$priority] === annotationPriorities.define));
748
- if (mergeSource) {
749
- // If the source annotation (at 'define' level) contains an ellipsis,
750
- // there is no base to apply to.
751
- if (removeEllipsis( mergeSource )) {
752
- error( 'anno-unexpected-ellipsis',
753
- [ mergeSource.name.location, art ], { code: '...' } );
754
- }
755
- // merge source into ellipsis array annotates
756
- layer.annos.forEach( (mergeTarget) => {
757
- if (mergeTarget.$priority &&
758
- annotationPriorities[mergeTarget.$priority] > annotationPriorities.define) {
759
- pos = findEllipsis( mergeTarget );
760
- if (pos > -1) {
761
- if (mergeSource.literal !== 'array') {
762
- error( 'anno-mismatched-ellipsis',
763
- [ mergeSource.name.location, art ], { code: '...' } );
764
- return;
765
- }
766
- mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
767
- }
768
- }
769
- });
770
- }
771
- });
772
- }
773
-
774
- function mergeLayeredArrays( mergeTarget ) {
775
- if (mergeTarget.literal === 'array') {
776
- let layer = layers.layer( mergeTarget._block );
777
- delete layerAnnos[(layer) ? layer.realname : ''];
778
- let pos = findEllipsis( mergeTarget );
779
- let hasRun = false;
780
- while (pos > -1 && Object.keys( layerAnnos ).length ) {
781
- hasRun = true;
782
- const mergeSource = findLayerCandidate();
783
- if (mergeSource.literal !== 'array') {
784
- error( 'anno-mismatched-ellipsis',
785
- [ mergeSource.name.location, art ], { code: '...' } );
786
- return mergeTarget;
787
- }
788
- mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
789
- layer = layers.layer( mergeSource._block );
790
- delete layerAnnos[(layer) ? layer.realname : ''];
791
- pos = findEllipsis( mergeTarget );
792
- }
793
- // All layers were processed. Remove excess ellipsis.
794
- if (removeEllipsis( mergeTarget, pos ) && hasRun) {
795
- // There shouldn't be any ellipsis or we don't have a base annotation.
796
- // But only if the loop above has run. Otherwise the in-layer merge
797
- // already warned about this case.
798
- message( 'anno-unexpected-ellipsis-layers',
799
- [ mergeTarget.name.location, art ], { code: '...' } );
800
- }
778
+ // Return assignments of the highest layers.
779
+ // Also return whether there could be an issue:
780
+ // - false: there is just one assignment
781
+ // - 'unrelated': there is just one assignment per layer
782
+ // - true: there is at least one layer with two or more assignments
783
+ // TODO: make this usable for extend (elements), too
784
+ function assignmentsOfHighestLayers( layeredAnnos ) {
785
+ const layerNames = Object.keys( layeredAnnos );
786
+ // console.log('HIB:',layerNames)
787
+ if (layerNames.length <= 1) {
788
+ const name = layerNames[0];
789
+ const { assignments } = layeredAnnos[name] || { assignments: [] };
790
+ delete layeredAnnos[name];
791
+ return { assignments, issue: assignments.length > 1 };
792
+ }
793
+
794
+ // collect all layers which are lower than another layer
795
+ const allExtends = Object.create(null);
796
+ allExtends[''] = {}; // the "Define" layer
797
+ for (const name of layerNames) {
798
+ if (name) // not the "Define" layer
799
+ Object.assign( allExtends, layeredAnnos[name].layer._layerExtends );
800
+ }
801
+ // console.log('HIE:',Object.keys(allExtends))
802
+ const assignments = [];
803
+ const highest = [];
804
+ for (const name of layerNames) {
805
+ if (!(name in allExtends)) {
806
+ const layer = layeredAnnos[name];
807
+ delete layeredAnnos[name];
808
+ highest.push( layer );
809
+ assignments.push( ...layer.assignments );
801
810
  }
802
- return mergeTarget;
803
811
  }
812
+ assignments.sort( compareAssignments );
813
+ const good = highest.every( layer => layer.assignments.length === 1 );
814
+ // TODO: use layer.file instead
815
+ const issue = !good || highest.length > 1 && 'unrelated';
816
+ // console.log('HI:',highest.map(l=>l.name),issue,issue&&assignments)
817
+ return { assignments, issue };
818
+ }
804
819
 
805
- function mergeArrayValues( previousValue, arraySpec ) {
806
- let prevPos = 0;
807
- const result = [];
808
- for (const item of arraySpec) {
809
- const ell = item && item.literal === 'token' && item.val === '...';
810
- if (!ell) {
811
- result.push( item );
812
- }
813
- else {
814
- let upToSpec = item.upTo && checkUpToSpec( item.upTo, true );
815
- while (prevPos < previousValue.length) {
816
- const prevItem = previousValue[prevPos++];
817
- result.push( prevItem );
818
- if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
819
- upToSpec = false;
820
- break;
821
- }
822
- }
823
- if (upToSpec) { // non-matched UP TO
824
- warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
825
- 'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
820
+ function compareAssignments( a, b ) {
821
+ const fileA = layers.realname( a._block );
822
+ const fileB = layers.realname( b._block );
823
+ if (fileA !== fileB)
824
+ return (fileA > fileB) ? 1 : -1;
825
+ return (a?.location?.line || 0) - (b?.location?.line || 0) ||
826
+ (a?.location?.col || 0) - (b?.location?.col || 0);
827
+ }
828
+
829
+ function applyAssignment( previousAnno, anno, art, annoName ) {
830
+ if (!previousAnno) {
831
+ if (!annotationHasEllipsis( anno ))
832
+ return anno;
833
+ if (anno.$priority) { // already complained about with Define
834
+ message( 'anno-unexpected-ellipsis-layers', // TODO: better location
835
+ [ anno.name.location, art ], { code: '...' } );
836
+ }
837
+ previousAnno = { val: [] };
838
+ }
839
+ else if (previousAnno.literal !== 'array') {
840
+ error( 'anno-mismatched-ellipsis', // TODO: better location
841
+ [ anno.name.location, art ], { code: '...' } );
842
+ previousAnno = { val: [] };
843
+ }
844
+ const previousValue = previousAnno.val;
845
+ let prevPos = 0;
846
+ const result = [];
847
+ for (const item of anno.val) {
848
+ const ell = item && item.literal === 'token' && item.val === '...';
849
+ if (!ell) {
850
+ result.push( item );
851
+ }
852
+ else {
853
+ let upToSpec = item.upTo && checkUpToSpec( item.upTo, art, annoName, true );
854
+ while (prevPos < previousValue.length) {
855
+ const prevItem = previousValue[prevPos++];
856
+ result.push( prevItem );
857
+ if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
858
+ upToSpec = false;
859
+ break;
826
860
  }
827
861
  }
862
+ if (upToSpec) { // non-matched UP TO
863
+ warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
864
+ 'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
865
+ }
828
866
  }
829
- return result;
830
867
  }
868
+ // console.log('TP:',previousValue.map(se),anno.val.map(se),'->',result.map(se))
869
+ return { val: result, literal: 'array' };
870
+ }
871
+ // function se(a) { return a.upTo ? [a.val,a.upTo.val] : a.val ; }
831
872
 
832
- function checkUpToSpec( upToSpec, trueIfFullUpTo ) {
833
- const { literal } = upToSpec;
834
- if (trueIfFullUpTo !== true) { // inside struct of UP TO
835
- if (literal !== 'struct' && literal !== 'array' )
836
- return true;
837
- }
838
- else if (literal === 'struct') {
839
- return Object.values( upToSpec.struct ).every( checkUpToSpec );
840
- }
841
- else if (literal !== 'array' && literal !== 'boolean' && literal !== 'null') {
873
+ function checkUpToSpec( upToSpec, art, annoName, isFullUpTo ) {
874
+ const { literal } = upToSpec;
875
+ if (!isFullUpTo) { // inside struct of UP TO
876
+ if (literal !== 'struct' && literal !== 'array' )
842
877
  return true;
843
- }
844
- error( null, [ upToSpec.location, art ],
845
- { anno: annoName, code: '... up to', '#': literal },
846
- {
847
- std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
848
- array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
849
- // eslint-disable-next-line max-len
850
- struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
851
- boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
852
- null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
853
- } );
854
- return false;
855
878
  }
879
+ else if (literal === 'struct') {
880
+ return Object.values( upToSpec.struct ).every( v => checkUpToSpec( v, art, annoName ) );
881
+ }
882
+ else if (literal !== 'array' && literal !== 'boolean' && literal !== 'null') {
883
+ return true;
884
+ }
885
+ error( null, [ upToSpec.location, art ],
886
+ { anno: annoName, code: '... up to', '#': literal },
887
+ {
888
+ std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
889
+ array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
890
+ // eslint-disable-next-line max-len
891
+ struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
892
+ boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
893
+ null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
894
+ } );
895
+ return false;
896
+ }
856
897
 
857
- function equalUpTo( previousItem, upToSpec ) {
858
- if (!previousItem)
859
- return false;
860
- if ('val' in upToSpec) {
861
- if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
862
- return true;
863
- const typeUpTo = typeof upToSpec.val;
864
- const typePrev = typeof previousItem.val;
865
- if (typeUpTo === 'number')
866
- return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
867
- if (typePrev === 'number')
868
- return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
869
- }
870
- else if (upToSpec.path) {
871
- return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
872
- }
873
- else if (upToSpec.sym) {
874
- return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
875
- }
876
- else if (upToSpec.struct && previousItem.struct) {
877
- return Object.entries( upToSpec.struct )
878
- .every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
879
- }
898
+ function equalUpTo( previousItem, upToSpec ) {
899
+ if (!previousItem)
880
900
  return false;
901
+ if ('val' in upToSpec) {
902
+ if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
903
+ return true;
904
+ const typeUpTo = typeof upToSpec.val;
905
+ const typePrev = typeof previousItem.val;
906
+ if (typeUpTo === 'number')
907
+ return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
908
+ if (typePrev === 'number')
909
+ return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
881
910
  }
882
-
883
- function normalizeRef( node ) { // see to-csn.js
884
- const ref = pathName( node.path );
885
- return node.variant ? `${ ref }#${ node.variant.id }` : ref;
886
- }
887
-
888
- function removeEllipsis(a, pos = findEllipsis( a )) {
889
- let count = 0;
890
- while (a.literal === 'array' && pos > -1) {
891
- count++;
892
- a.val.splice(pos, 1);
893
- pos = findEllipsis( a );
894
- }
895
- return count;
896
- }
897
-
898
- function findEllipsis(a) {
899
- return (a.literal === 'array' && a.val)
900
- ? a.val.findIndex(v => v.literal === 'token' && v.val === '...') : -1;
911
+ else if (upToSpec.path) {
912
+ return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
901
913
  }
902
-
903
- function findLayerCandidate() {
904
- // collect assignments of upper layers (are in no _layerExtends)
905
- const exts = Object.keys( layerAnnos ).map( layerExtends );
906
- const allExtends = Object.assign( Object.create(null), ...exts );
907
- const collected = [];
908
- for (const name in layerAnnos) {
909
- if (!(name in allExtends))
910
- collected.push( prioritizedAnnos( layerAnnos[name].annos ) );
911
- }
912
- // inspect collected assignments - choose the one or signal error
913
- const justOnePerLayer = collected.every( annos => annos.length === 1);
914
- if (!justOnePerLayer || collected.length > 1) {
915
- for (const annos of collected) {
916
- for (const a of annos ) {
917
- // Only the message ID is different.
918
- if (justOnePerLayer) {
919
- message( 'anno-duplicate-unrelated-layer',
920
- [ a.name.location, art ], { anno: annoName },
921
- 'Duplicate assignment with $(ANNO)' );
922
- }
923
- else {
924
- message( 'anno-duplicate', [ a.name.location, art ], { anno: annoName } );
925
- }
926
- }
927
- }
928
- }
929
- return collected[0][0]; // just choose any one with error
914
+ else if (upToSpec.sym) {
915
+ return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
930
916
  }
931
-
932
- function layerExtends( name ) {
933
- const { layer } = layerAnnos[name];
934
- return layer && layer._layerExtends;
917
+ else if (upToSpec.struct && previousItem.struct) {
918
+ return Object.entries( upToSpec.struct )
919
+ .every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
935
920
  }
921
+ return false;
936
922
  }
937
923
 
938
- function prioritizedAnnos( annos ) {
939
- let prio = 0;
940
- let r = [];
941
- for (const a of annos) {
942
- const p = annotationPriorities[a.$priority] || annotationPriorities.define;
943
- if (p === prio) {
944
- r.push(a);
945
- }
946
- else if (p > prio) {
947
- r = [ a ];
948
- prio = p;
949
- }
950
- }
951
- return r;
924
+ function normalizeRef( node ) { // see to-csn.js
925
+ const ref = pathName( node.path );
926
+ return node.variant ? `${ ref }#${ node.variant.id }` : ref;
952
927
  }
953
928
 
954
929
  // Phase 4 - queries and associations --------------------------------------
@@ -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 ) {
@@ -512,7 +513,7 @@ function fns( model ) {
512
513
  if (args.length > 0) {
513
514
  const loc = [ args[args.length - 1].location, user ];
514
515
  if (typeArtifact.builtin)
515
- warning( 'type-ignoring-argument', loc, { art: typeArtifact } );
516
+ message( 'type-ignoring-argument', loc, { art: typeArtifact } );
516
517
  else
517
518
  error( 'type-unexpected-argument', loc, { '#': 'std', art: typeArtifact });
518
519
  }
@@ -673,6 +674,9 @@ function fns( model ) {
673
674
  art = item._artifact;
674
675
  if (Array.isArray(art))
675
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 ]);
676
680
  continue;
677
681
  }
678
682
 
@@ -808,11 +812,30 @@ function fns( model ) {
808
812
  attachAndEmitValidNames(err, ...valid.reverse());
809
813
  }
810
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
+
811
834
  /**
812
835
  * Attaches a dictionary of valid names to the given compiler message.
813
836
  * In test mode, an info message is emitted with a list of valid names.
814
837
  *
815
- * @param {CSN.Message} msg CDS Compiler message
838
+ * @param {CompileMessage} msg CDS Compiler message
816
839
  * @param {...object} validDicts One ore more artifact dictionaries such as in `_block`.
817
840
  */
818
841
  function attachAndEmitValidNames(msg, ...validDicts) {
@@ -838,14 +861,10 @@ function fns( model ) {
838
861
  }
839
862
  }
840
863
 
841
- // Resolve all annotation assignments for the node `art`. Set `art.@` to all
842
- // flattened assignments. This function might issue error message for
843
- // duplicate assignments.
844
- // TODOs:
845
- // * do something for extensions by CSN or Properties parsers
846
- // * make sure that we do not issue repeated warnings due to flattening if an
847
- // annotation definition is missing
848
- 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 ) {
849
868
  if (!options.parseCdl && construct.kind === 'annotate') {
850
869
  // Namespaces cannot be annotated in CSN but because they exist as XSN artifacts
851
870
  // they can still be applied. Namespace annotations are extracted in to-csn.js
@@ -863,76 +882,28 @@ function fns( model ) {
863
882
  'Builtin types should not be annotated. Use custom type instead' );
864
883
  }
865
884
  }
866
- // TODO: block should be construct._block
867
- if (construct.$annotations && construct.$annotations.doc )
868
- art.doc = construct.$annotations.doc; // e.g. through `annotate` statement in CDL
869
- else if (construct.doc)
885
+ if (construct.doc)
870
886
  art.doc = construct.doc; // e.g. through `extensions` array in CSN
871
- if (!construct.$annotations) {
872
- if (!block || block.$frontend !== 'json')
873
- return; // namespace, or in CDL source without @annos:
874
- // CSN input: set _block and $priority, shallow-copy from extension
875
- for (const annoProp in construct) {
876
- if (annoProp.charAt(0) === '@') {
877
- let annos = construct[annoProp];
878
- if (!(Array.isArray(annos)))
879
- annos = [ annos ];
880
- for (const a of annos) {
881
- setLink( a, '_block', block );
882
- a.$priority = priority;
883
- if (construct !== art)
884
- addAnnotation( art, annoProp, a );
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: '...' } );
885
904
  }
886
905
  }
887
906
  }
888
- return;
889
- }
890
- for (const anno of construct.$annotations) {
891
- const ref = anno.name;
892
- const name = resolveUncheckedPath( ref, 'annotation', { _block: block } );
893
- const annoProp = (anno.name.variant)
894
- ? `@${ name }#${ anno.name.variant.id }`
895
- : `@${ name }`;
896
- flatten( ref.path, annoProp, anno.value || {}, anno.name.variant, anno.name.location );
897
- }
898
- return;
899
-
900
- function flatten( path, annoProp, value, iHaveVariant, location ) {
901
- // Be robust if struct value has duplicate element names
902
- if (Array.isArray(value)) // TODO: do that differently in CDL parser
903
- return; // discard duplicates in flattened form
904
-
905
- if (value.literal === 'struct') {
906
- for (const item of value._struct || []) {
907
- let prop = pathName(item.name.path);
908
- if (item.name.variant) {
909
- if (iHaveVariant) {
910
- error( 'anno-duplicate-variant', [ item.name.variant.location, construct ],
911
- {}, // TODO: params
912
- 'Annotation variant has been already provided' );
913
- }
914
- prop = `${ prop }#${ item.name.variant.id }`; // TODO: check for double variants
915
- }
916
- flatten( [ ...path, ...item.name.path ], `${ annoProp }.${ prop }`, item, iHaveVariant || item.name.variant);
917
- }
918
- for (const prop in value.struct) {
919
- const item = value.struct[prop];
920
- flatten( [ ...path, item.name ], `${ annoProp }.${ prop }`, item, iHaveVariant );
921
- }
922
- return;
923
- }
924
- const anno = Object.assign( {}, value ); // shallow copy
925
- anno.name = {
926
- path,
927
- location: location ||
928
- value.name && value.name.location ||
929
- value.path && value.path.location,
930
- };
931
- setLink( anno, '_block', block );
932
- // TODO: _parent, _main is set later (if we have ElementRef), or do we
933
- // set _artifact?
934
- anno.$priority = priority;
935
- addAnnotation( art, annoProp, anno );
936
907
  }
937
908
  }
938
909
  }