@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
@@ -49,12 +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');
52
+ const { typeParameters } = require('./builtins');
53
53
 
54
54
  const { kindProperties } = require('./base');
55
55
  const {
56
56
  setLink,
57
57
  setArtifactLink,
58
+ annotationHasEllipsis,
58
59
  pathName,
59
60
  linkToOrigin,
60
61
  setMemberParent,
@@ -72,9 +73,6 @@ const layers = require('./moduleLayers');
72
73
 
73
74
  const $location = Symbol.for('cds.$location');
74
75
 
75
- const annotationPriorities = {
76
- define: 1, extend: 2, annotate: 2, edmx: 3,
77
- };
78
76
  const $inferred = Symbol.for('cds.$inferred');
79
77
 
80
78
  // Export function of this file. Resolve type references in augmented CSN
@@ -89,13 +87,13 @@ function resolve( model ) {
89
87
  } = model.$messageFunctions;
90
88
  const {
91
89
  resolvePath,
92
- resolveTypeArguments,
93
90
  defineAnnotations,
94
91
  attachAndEmitValidNames,
95
92
  lateExtensions,
96
93
  effectiveType,
97
94
  directType,
98
95
  resolveType,
96
+ resolveTypeArgumentsUnchecked,
99
97
  populateQuery,
100
98
  } = model.$functions;
101
99
  const { environment } = model.$volatileFunctions;
@@ -106,7 +104,7 @@ function resolve( model ) {
106
104
  /** @type {any} may also be a boolean */
107
105
 
108
106
  // behavior depending on option `deprecated`:
109
- const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
107
+ const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
110
108
  // TODO: we should get rid of noElementsExpansion soon; both
111
109
  // beta.nestedProjections and beta.universalCsn do not work with it.
112
110
 
@@ -210,9 +208,9 @@ function resolve( model ) {
210
208
  info( 'query-from-many', [ toMany.location, query ], { art: toMany },
211
209
  {
212
210
  // eslint-disable-next-line max-len
213
- 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',
214
212
  // eslint-disable-next-line max-len
215
- 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',
216
214
  } );
217
215
  }
218
216
  // Check that all keys from the source are projected:
@@ -371,8 +369,8 @@ function resolve( model ) {
371
369
  // console.log(obj.name,obj._origin.name)
372
370
  if (obj._origin && obj._origin.$inferred === 'REDIRECTED')
373
371
  resolveTarget( art, obj._origin );
374
- // console.log(message( null, obj.location, obj, {target:obj.target}, 'Info','TARGET')
375
- // .toString(), obj.target.$inferred)
372
+ // console.log(error( 'test-target', [ obj.location, obj ],
373
+ // { target: obj.target, kind: obj.kind }, 'Target: $(TARGET), Kind $(KIND)'));
376
374
  if (!obj.target.$inferred || obj.target.$inferred === 'aspect-composition')
377
375
  resolveTarget( art, obj );
378
376
  else
@@ -448,8 +446,10 @@ function resolve( model ) {
448
446
  // propagation/rewrite has been done yet, cyclic dependency must have been
449
447
  // checked before!
450
448
  function getAssocSpec( type ) {
449
+ const cyclic = new Set(); // TODO(#8942): May not be necessary if effectiveType() is adapted.
451
450
  // only to be called without cycles
452
- while (type) {
451
+ while (type && !cyclic.has(type)) {
452
+ cyclic.add(type);
453
453
  if (type.on || type.foreignKeys || type.targetAspect)
454
454
  return type;
455
455
  type = directType( type );
@@ -464,7 +464,7 @@ function resolve( model ) {
464
464
  // op.val is also correctly set with CSN input
465
465
  elem.type = { ...type, $inferred: 'cast' };
466
466
  setArtifactLink( elem.type, type._artifact );
467
- for (const prop of [ 'length', 'precision', 'scale', 'srid' ]) {
467
+ for (const prop of typeParameters.list) {
468
468
  if (elem.value[prop])
469
469
  elem[prop] = { ...elem.value[prop], $inferred: 'cast' };
470
470
  }
@@ -711,241 +711,219 @@ function resolve( model ) {
711
711
  }
712
712
 
713
713
  function chooseAssignment( annoName, art ) {
714
- // TODO: getPath an all names
715
- const anno = art[annoName];
716
- if (!Array.isArray(anno)) { // just one assignment -> use it
717
- if (removeEllipsis( anno )) {
718
- error( 'anno-unexpected-ellipsis',
719
- [ 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
+ }
720
736
  }
721
- return;
722
- }
723
- // sort assignment according to layer
724
- const layerAnnos = Object.create(null);
725
- for (const a of anno) {
726
- 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
727
767
  const name = (layer) ? layer.realname : '';
728
- const done = layerAnnos[name];
768
+ const done = layered[name];
729
769
  if (done)
730
- done.annos.push( a );
770
+ done.assignments.push( a );
731
771
  else
732
- layerAnnos[name] = { layer, annos: [ a ] };
772
+ layered[name] = { name, layer, assignments: [ a ] };
773
+ // TODO: file - if set: unique in layer
733
774
  }
734
- mergeArrayInSCCs();
735
- art[annoName] = mergeLayeredArrays( findLayerCandidate( ) );
736
- return;
775
+ return layered;
776
+ }
737
777
 
738
- // Merge annotations in each layer, i.e. multiple annotations in the same layer are
739
- // stored in an array and need to be merged before different layers can be merged.
740
- function mergeArrayInSCCs( ) {
741
- let pos = 0;
742
- forEachValue(layerAnnos, (layer) => {
743
- const mergeSource = layer.annos.find(v => (v.$priority === undefined ||
744
- annotationPriorities[v.$priority] === annotationPriorities.define));
745
- if (mergeSource) {
746
- // If the source annotation (at 'define' level) contains an ellipsis,
747
- // there is no base to apply to.
748
- if (removeEllipsis( mergeSource )) {
749
- error( 'anno-unexpected-ellipsis',
750
- [ mergeSource.name.location, art ], { code: '...' } );
751
- }
752
- // merge source into ellipsis array annotates
753
- layer.annos.forEach( (mergeTarget) => {
754
- if (mergeTarget.$priority &&
755
- annotationPriorities[mergeTarget.$priority] > annotationPriorities.define) {
756
- pos = findEllipsis( mergeTarget );
757
- if (pos > -1) {
758
- if (mergeSource.literal !== 'array') {
759
- error( 'anno-mismatched-ellipsis',
760
- [ mergeSource.name.location, art ], { code: '...' } );
761
- return;
762
- }
763
- mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
764
- }
765
- }
766
- });
767
- }
768
- });
769
- }
770
-
771
- function mergeLayeredArrays( mergeTarget ) {
772
- if (mergeTarget.literal === 'array') {
773
- let layer = layers.layer( mergeTarget._block );
774
- delete layerAnnos[(layer) ? layer.realname : ''];
775
- let pos = findEllipsis( mergeTarget );
776
- let hasRun = false;
777
- while (pos > -1 && Object.keys( layerAnnos ).length ) {
778
- hasRun = true;
779
- const mergeSource = findLayerCandidate();
780
- if (mergeSource.literal !== 'array') {
781
- error( 'anno-mismatched-ellipsis',
782
- [ mergeSource.name.location, art ], { code: '...' } );
783
- return mergeTarget;
784
- }
785
- mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
786
- layer = layers.layer( mergeSource._block );
787
- delete layerAnnos[(layer) ? layer.realname : ''];
788
- pos = findEllipsis( mergeTarget );
789
- }
790
- // All layers were processed. Remove excess ellipsis.
791
- if (removeEllipsis( mergeTarget, pos ) && hasRun) {
792
- // There shouldn't be any ellipsis or we don't have a base annotation.
793
- // But only if the loop above has run. Otherwise the in-layer merge
794
- // already warned about this case.
795
- message( 'anno-unexpected-ellipsis-layers',
796
- [ mergeTarget.name.location, art ], { code: '...' } );
797
- }
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 );
798
810
  }
799
- return mergeTarget;
800
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
+ }
801
819
 
802
- function mergeArrayValues( previousValue, arraySpec ) {
803
- let prevPos = 0;
804
- const result = [];
805
- for (const item of arraySpec) {
806
- const ell = item && item.literal === 'token' && item.val === '...';
807
- if (!ell) {
808
- result.push( item );
809
- }
810
- else {
811
- let upToSpec = item.upTo && checkUpToSpec( item.upTo, true );
812
- while (prevPos < previousValue.length) {
813
- const prevItem = previousValue[prevPos++];
814
- result.push( prevItem );
815
- if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
816
- upToSpec = false;
817
- break;
818
- }
819
- }
820
- if (upToSpec) { // non-matched UP TO
821
- warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
822
- '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;
823
860
  }
824
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
+ }
825
866
  }
826
- return result;
827
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 ; }
828
872
 
829
- function checkUpToSpec( upToSpec, trueIfFullUpTo ) {
830
- const { literal } = upToSpec;
831
- if (trueIfFullUpTo !== true) { // inside struct of UP TO
832
- if (literal !== 'struct' && literal !== 'array' )
833
- return true;
834
- }
835
- else if (literal === 'struct') {
836
- return Object.values( upToSpec.struct ).every( checkUpToSpec );
837
- }
838
- 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' )
839
877
  return true;
840
- }
841
- error( null, [ upToSpec.location, art ],
842
- { anno: annoName, code: '... up to', '#': literal },
843
- {
844
- std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
845
- array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
846
- // eslint-disable-next-line max-len
847
- struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
848
- boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
849
- null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
850
- } );
851
- return false;
852
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
+ }
853
897
 
854
- function equalUpTo( previousItem, upToSpec ) {
855
- if (!previousItem)
856
- return false;
857
- if ('val' in upToSpec) {
858
- if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
859
- return true;
860
- const typeUpTo = typeof upToSpec.val;
861
- const typePrev = typeof previousItem.val;
862
- if (typeUpTo === 'number')
863
- return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
864
- if (typePrev === 'number')
865
- return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
866
- }
867
- else if (upToSpec.path) {
868
- return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
869
- }
870
- else if (upToSpec.sym) {
871
- return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
872
- }
873
- else if (upToSpec.struct && previousItem.struct) {
874
- return Object.entries( upToSpec.struct )
875
- .every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
876
- }
898
+ function equalUpTo( previousItem, upToSpec ) {
899
+ if (!previousItem)
877
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();
878
910
  }
879
-
880
- function normalizeRef( node ) { // see to-csn.js
881
- const ref = pathName( node.path );
882
- return node.variant ? `${ ref }#${ node.variant.id }` : ref;
883
- }
884
-
885
- function removeEllipsis(a, pos = findEllipsis( a )) {
886
- let count = 0;
887
- while (a.literal === 'array' && pos > -1) {
888
- count++;
889
- a.val.splice(pos, 1);
890
- pos = findEllipsis( a );
891
- }
892
- return count;
893
- }
894
-
895
- function findEllipsis(a) {
896
- return (a.literal === 'array' && a.val)
897
- ? a.val.findIndex(v => v.literal === 'token' && v.val === '...') : -1;
911
+ else if (upToSpec.path) {
912
+ return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
898
913
  }
899
-
900
- function findLayerCandidate() {
901
- // collect assignments of upper layers (are in no _layerExtends)
902
- const exts = Object.keys( layerAnnos ).map( layerExtends );
903
- const allExtends = Object.assign( Object.create(null), ...exts );
904
- const collected = [];
905
- for (const name in layerAnnos) {
906
- if (!(name in allExtends))
907
- collected.push( prioritizedAnnos( layerAnnos[name].annos ) );
908
- }
909
- // inspect collected assignments - choose the one or signal error
910
- const justOnePerLayer = collected.every( annos => annos.length === 1);
911
- if (!justOnePerLayer || collected.length > 1) {
912
- for (const annos of collected) {
913
- for (const a of annos ) {
914
- // Only the message ID is different.
915
- if (justOnePerLayer) {
916
- message( 'anno-duplicate-unrelated-layer',
917
- [ a.name.location, art ], { anno: annoName },
918
- 'Duplicate assignment with $(ANNO)' );
919
- }
920
- else {
921
- message( 'anno-duplicate', [ a.name.location, art ], { anno: annoName } );
922
- }
923
- }
924
- }
925
- }
926
- 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;
927
916
  }
928
-
929
- function layerExtends( name ) {
930
- const { layer } = layerAnnos[name];
931
- 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 ) );
932
920
  }
921
+ return false;
933
922
  }
934
923
 
935
- function prioritizedAnnos( annos ) {
936
- let prio = 0;
937
- let r = [];
938
- for (const a of annos) {
939
- const p = annotationPriorities[a.$priority] || annotationPriorities.define;
940
- if (p === prio) {
941
- r.push(a);
942
- }
943
- else if (p > prio) {
944
- r = [ a ];
945
- prio = p;
946
- }
947
- }
948
- 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;
949
927
  }
950
928
 
951
929
  // Phase 4 - queries and associations --------------------------------------
@@ -1026,6 +1004,15 @@ function resolve( model ) {
1026
1004
  return;
1027
1005
  }
1028
1006
  const target = resolvePath( obj.target, 'target', art );
1007
+
1008
+ if (obj._pathHead && obj.type && !obj.type.$inferred && art._main && art._main.query) {
1009
+ // New association inside expand/inline: The on-condition can't be properly checked,
1010
+ // so abort early. See #8797
1011
+ error( 'query-unexpected-assoc', [ obj.name.location, art ], {},
1012
+ 'Unexpected new association in expand/inline' );
1013
+ return; // avoid subsequent errors
1014
+ }
1015
+
1029
1016
  if (obj.on) {
1030
1017
  if (!art._main || !art._parent.elements && !art._parent.items && !art._parent.targetAspect) {
1031
1018
  // TODO: test of .items a bit unclear - we should somehow restrict the
@@ -1057,20 +1044,29 @@ function resolve( model ) {
1057
1044
  else if (art.kind === 'mixin') {
1058
1045
  error( 'assoc-in-mixin', [ obj.target.location, art ], {},
1059
1046
  'Managed associations are not allowed for MIXIN elements' );
1047
+ return; // avoid subsequent errors
1048
+ }
1049
+ else if (obj.type && !obj.type.$inferred && art._parent && art._parent.kind === 'select') {
1050
+ // New association in views, i.e. parent is a query.
1051
+ error( 'query-expected-on-condition', [ obj.target.location, art ], {},
1052
+ 'Expected on-condition for published association' );
1053
+ return; // avoid subsequent errors
1060
1054
  }
1061
1055
  else if (target && !obj.foreignKeys && target.kind === 'entity') {
1062
1056
  if (obj.$inferred === 'REDIRECTED') {
1063
1057
  addImplicitForeignKeys( art, obj, target );
1064
1058
  }
1065
- else if (!obj.type || obj.type.$inferred || obj.target.$inferred) { // REDIRECTED
1066
- resolveRedirected( art, target );
1067
- }
1068
- else if (obj.type._artifact && obj.type._artifact.internal) { // cds.Association, ...
1059
+ else if (obj.type && obj.type._artifact && obj.type._artifact.internal) {
1060
+ // cds.Association, ...
1069
1061
  addImplicitForeignKeys( art, obj, target );
1070
1062
  }
1071
- // else console.log( message( null,obj.location,obj, {target}, 'Info','NOTARGET').toString())
1072
1063
  }
1073
- // else console.log( message( null, obj.location, obj, {target}, 'Info','NORE').toString())
1064
+
1065
+ if (target && !target.$inferred) {
1066
+ if (!obj.type || obj.type.$inferred || obj.target.$inferred) { // REDIRECTED
1067
+ resolveRedirected( art, target );
1068
+ }
1069
+ }
1074
1070
  }
1075
1071
 
1076
1072
  function addImplicitForeignKeys( art, obj, target ) {
@@ -1130,7 +1126,9 @@ function resolve( model ) {
1130
1126
  const assoc = directType( elem );
1131
1127
  const origType = assoc && effectiveType( assoc );
1132
1128
  if (!origType || !origType.target) {
1133
- error( 'redirected-no-assoc', [ elem.target.location, elem ], {},
1129
+ const path = (elem.value && elem.value.path);
1130
+ const loc = (path && path[path.length - 1] || elem.value || elem).location;
1131
+ error( 'redirected-no-assoc', [ loc, elem ], {},
1134
1132
  'Only an association can be redirected' );
1135
1133
  return;
1136
1134
  }
@@ -1138,7 +1136,7 @@ function resolve( model ) {
1138
1136
  // .toString(), elem.value)
1139
1137
  const nav = elem._main && elem._main.query && elem.value && pathNavigation( elem.value );
1140
1138
  if (nav && nav.item !== elem.value.path[elem.value.path.length - 1]) {
1141
- if (origType.on) {
1139
+ if (!elem.on && origType.on) {
1142
1140
  error( 'rewrite-not-supported', [ elem.target.location, elem ], {},
1143
1141
  // TODO: Better text ?
1144
1142
  'The ON condition is not rewritten here - provide an explicit ON condition' );
@@ -1151,7 +1149,9 @@ function resolve( model ) {
1151
1149
 
1152
1150
  const chain = [];
1153
1151
  if (target === origTarget) {
1154
- if (!elem.target.$inferred) {
1152
+ if (!elem.target.$inferred && !elem.on && !elem.foreignKeys) {
1153
+ // Only a managed redirection gets this info message. Because otherwise
1154
+ // we'd have to check whether on-condition/foreignKeys are the same.
1155
1155
  info( 'redirected-to-same', [ elem.target.location, elem ], { art: target },
1156
1156
  'The redirected target is the original $(ART)' );
1157
1157
  }
@@ -1265,8 +1265,71 @@ function resolve( model ) {
1265
1265
  // Resolve the type and its arguments if applicable.
1266
1266
  function resolveTypeExpr( art, user ) {
1267
1267
  const typeArt = resolveType( art.type, user );
1268
- if (typeArt)
1269
- resolveTypeArguments( art, typeArt, user );
1268
+ if (typeArt) {
1269
+ resolveTypeArgumentsUnchecked( art, typeArt, user );
1270
+ checkTypeArguments( art );
1271
+ }
1272
+ }
1273
+
1274
+ /**
1275
+ * Check the type arguments on `artWithType`.
1276
+ * If the effective type is an array or structured type, an error is emitted.
1277
+ */
1278
+ function checkTypeArguments( artWithType ) {
1279
+ // Note: `_effectiveType` may point to `artWithType` itself, if the type is structured.
1280
+ // Also: For enums, it points to the enum type, which is why this trick is needed.
1281
+ // TODO(#8942): May not be necessary if effectiveType() is adapted. Furthermore, the enum
1282
+ // trick may be removed if effectiveType() does not stop at enums.
1283
+ const cyclic = new Set();
1284
+ let effectiveTypeArt = effectiveType( artWithType );
1285
+ while (effectiveTypeArt && effectiveTypeArt.enum && !cyclic.has(effectiveTypeArt)) {
1286
+ cyclic.add(effectiveTypeArt);
1287
+ const underlyingEnumType = directType(effectiveTypeArt);
1288
+ if (underlyingEnumType)
1289
+ effectiveTypeArt = effectiveType(underlyingEnumType);
1290
+ else
1291
+ break;
1292
+ }
1293
+
1294
+ if (!effectiveTypeArt)
1295
+ return; // e.g. illegal definition references
1296
+
1297
+ const params = effectiveTypeArt.parameters &&
1298
+ effectiveTypeArt.parameters.map(p => p.name || p) || [];
1299
+
1300
+ for (const param of typeParameters.list) {
1301
+ if (artWithType[param] !== undefined) {
1302
+ if (!params.includes(param)) {
1303
+ // Whether the type ref itself is a builtin or a custom type with a builtin as base.
1304
+ const type = directType(artWithType);
1305
+
1306
+ let variant;
1307
+ if (type.builtin)
1308
+ // `.type` is already a builtin: use a nicer message.
1309
+ variant = 'builtin';
1310
+ else if (effectiveTypeArt.builtin)
1311
+ // base type is a builtin, i.e. a scalar
1312
+ variant = 'type';
1313
+ else
1314
+ // effectiveType is not a builtin -> array or structured
1315
+ variant = 'non-scalar';
1316
+
1317
+ error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
1318
+ '#': variant, prop: param, art: artWithType.type, type: effectiveTypeArt,
1319
+ });
1320
+ break; // Avoid spam: Only emit the first error.
1321
+ }
1322
+ else if (!typeParameters.expectedLiteralsFor[param].includes(artWithType[param].literal)) {
1323
+ error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
1324
+ '#': 'incorrect-type',
1325
+ prop: param,
1326
+ code: artWithType[param].literal,
1327
+ names: typeParameters.expectedLiteralsFor[param],
1328
+ });
1329
+ break; // Avoid spam: Only emit the first error.
1330
+ }
1331
+ }
1332
+ }
1270
1333
  }
1271
1334
 
1272
1335
  function resolveExpr( expr, expected, user, extDict, expandOrInline) {