@sap/cds-compiler 2.15.2 → 3.0.2

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 (111) hide show
  1. package/CHANGELOG.md +66 -1590
  2. package/bin/cdsc.js +42 -46
  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 +312 -143
  10. package/lib/api/options.js +15 -85
  11. package/lib/api/validate.js +6 -10
  12. package/lib/base/keywords.js +280 -110
  13. package/lib/base/message-registry.js +80 -24
  14. package/lib/base/messages.js +103 -52
  15. package/lib/base/model.js +44 -2
  16. package/lib/base/optionProcessorHelper.js +53 -21
  17. package/lib/checks/actionsFunctions.js +7 -5
  18. package/lib/checks/annotationsOData.js +1 -1
  19. package/lib/checks/cdsPersistence.js +1 -0
  20. package/lib/checks/elements.js +6 -6
  21. package/lib/checks/invalidTarget.js +1 -1
  22. package/lib/checks/nonexpandableStructured.js +1 -1
  23. package/lib/checks/queryNoDbArtifacts.js +2 -1
  24. package/lib/checks/selectItems.js +5 -1
  25. package/lib/checks/types.js +4 -2
  26. package/lib/checks/utils.js +2 -2
  27. package/lib/checks/validator.js +2 -1
  28. package/lib/compiler/assert-consistency.js +15 -10
  29. package/lib/compiler/builtins.js +127 -10
  30. package/lib/compiler/define.js +6 -4
  31. package/lib/compiler/extend.js +63 -12
  32. package/lib/compiler/finalize-parse-cdl.js +20 -9
  33. package/lib/compiler/index.js +25 -11
  34. package/lib/compiler/moduleLayers.js +7 -0
  35. package/lib/compiler/populate.js +16 -14
  36. package/lib/compiler/propagator.js +3 -3
  37. package/lib/compiler/resolve.js +194 -222
  38. package/lib/compiler/shared.js +56 -76
  39. package/lib/compiler/tweak-assocs.js +9 -10
  40. package/lib/compiler/utils.js +7 -2
  41. package/lib/edm/annotations/genericTranslation.js +60 -6
  42. package/lib/edm/annotations/preprocessAnnotations.js +10 -11
  43. package/lib/edm/csn2edm.js +39 -41
  44. package/lib/edm/edm.js +22 -15
  45. package/lib/edm/edmPreprocessor.js +66 -69
  46. package/lib/edm/edmUtils.js +12 -62
  47. package/lib/gen/Dictionary.json +8 -6
  48. package/lib/gen/language.checksum +1 -1
  49. package/lib/gen/language.interp +8 -30
  50. package/lib/gen/language.tokens +105 -114
  51. package/lib/gen/languageLexer.interp +1 -34
  52. package/lib/gen/languageLexer.js +889 -1007
  53. package/lib/gen/languageLexer.tokens +95 -106
  54. package/lib/gen/languageParser.js +20717 -22376
  55. package/lib/json/from-csn.js +73 -68
  56. package/lib/json/to-csn.js +13 -10
  57. package/lib/language/antlrParser.js +2 -2
  58. package/lib/language/docCommentParser.js +61 -38
  59. package/lib/language/errorStrategy.js +52 -40
  60. package/lib/language/genericAntlrParser.js +333 -259
  61. package/lib/language/language.g4 +600 -645
  62. package/lib/language/multiLineStringParser.js +14 -42
  63. package/lib/language/textUtils.js +44 -0
  64. package/lib/main.d.ts +27 -42
  65. package/lib/main.js +104 -81
  66. package/lib/model/csnRefs.js +2 -1
  67. package/lib/model/csnUtils.js +183 -285
  68. package/lib/model/revealInternalProperties.js +32 -9
  69. package/lib/model/sortViews.js +32 -31
  70. package/lib/optionProcessor.js +64 -57
  71. package/lib/render/.eslintrc.json +1 -1
  72. package/lib/render/DuplicateChecker.js +4 -7
  73. package/lib/render/manageConstraints.js +70 -2
  74. package/lib/render/toCdl.js +334 -339
  75. package/lib/render/toHdbcds.js +20 -16
  76. package/lib/render/toRename.js +44 -22
  77. package/lib/render/toSql.js +60 -54
  78. package/lib/render/utils/common.js +15 -1
  79. package/lib/render/utils/sql.js +20 -19
  80. package/lib/sql-identifier.js +6 -0
  81. package/lib/transform/db/.eslintrc.json +3 -2
  82. package/lib/transform/db/cdsPersistence.js +5 -15
  83. package/lib/transform/db/constraints.js +1 -1
  84. package/lib/transform/db/expansion.js +7 -6
  85. package/lib/transform/db/flattening.js +18 -19
  86. package/lib/transform/db/views.js +3 -3
  87. package/lib/transform/draft/.eslintrc.json +2 -2
  88. package/lib/transform/draft/db.js +6 -6
  89. package/lib/transform/draft/odata.js +6 -7
  90. package/lib/transform/forHanaNew.js +19 -22
  91. package/lib/transform/forOdataNew.js +13 -15
  92. package/lib/transform/localized.js +35 -25
  93. package/lib/transform/odata/toFinalBaseType.js +11 -9
  94. package/lib/transform/odata/typesExposure.js +3 -3
  95. package/lib/transform/odata/utils.js +1 -38
  96. package/lib/transform/transformUtilsNew.js +63 -77
  97. package/lib/transform/translateAssocsToJoins.js +6 -2
  98. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  99. package/lib/transform/universalCsn/coreComputed.js +11 -6
  100. package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
  101. package/lib/utils/file.js +31 -21
  102. package/lib/utils/timetrace.js +20 -21
  103. package/package.json +34 -4
  104. package/share/messages/syntax-expected-integer.md +9 -8
  105. package/doc/ApiMigration.md +0 -237
  106. package/doc/CommandLineMigration.md +0 -58
  107. package/doc/ErrorMessages.md +0 -175
  108. package/doc/FioriAnnotations.md +0 -94
  109. package/doc/ODataTransformation.md +0 -273
  110. package/lib/backends.js +0 -529
  111. 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:
@@ -484,10 +481,7 @@ function resolve( model ) {
484
481
  if (ext.$extension) // extension for known artifact -> already applied
485
482
  return;
486
483
  annotateMembers( ext );
487
- for (const prop in ext) {
488
- if (prop.charAt(0) === '@')
489
- chooseAssignment( prop, ext );
490
- }
484
+ chooseAnnotationsInArtifact( ext );
491
485
  }
492
486
 
493
487
  /**
@@ -714,241 +708,219 @@ function resolve( model ) {
714
708
  }
715
709
 
716
710
  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: '...' } );
711
+ let anno = art[annoName];
712
+ if (!Array.isArray( anno )) { // just one assignment -> use it
713
+ if (!annotationHasEllipsis( anno ))
714
+ return;
715
+ anno = [ anno ];
716
+ }
717
+ // console.log('ASSIGN:',art.name.absolute,annoName)
718
+ const scheduledAssignments = [];
719
+ // sort assignment according to layer (define is bottom layer):
720
+ const layeredAnnos = layeredAssignments( anno );
721
+ let cont = true;
722
+ while (cont) {
723
+ const { assignments, issue } = assignmentsOfHighestLayers( layeredAnnos );
724
+ let index = assignments.length;
725
+ cont = !!index; // safety
726
+ while (--index >= 0) {
727
+ const a = assignments[index];
728
+ scheduledAssignments.push( a );
729
+ if (!annotationHasEllipsis( a )) {
730
+ cont = false;
731
+ break;
732
+ }
723
733
  }
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 );
734
+ if (issue) {
735
+ // eslint-disable-next-line no-nested-ternary
736
+ const msg = (issue === true)
737
+ ? 'anno-duplicate'
738
+ : (index >= 0) ? 'anno-duplicate-unrelated-layer' : 'anno-unstable-array';
739
+ for (const a of assignments) {
740
+ if (!a.$errorReported)
741
+ message( msg, [ a.name.location, art ], { anno: annoName } );
742
+ }
743
+ }
744
+ // else if (index > 0) -- if we allow multiple assignments in one file - the last wins
745
+ }
746
+ // Now apply the assignments - all but the first have a '...'
747
+ let result = null;
748
+ scheduledAssignments.reverse();
749
+ for (const a of scheduledAssignments)
750
+ result = applyAssignment( result, a, art, annoName );
751
+ art[annoName] = result.name ? result
752
+ : Object.assign( {}, scheduledAssignments[scheduledAssignments.length - 1], result );
753
+ }
754
+
755
+ // Group assignments by their layers. An assignment provided with a definition
756
+ // is considered to be provided in a layer named '', the lowest layer.
757
+ // TODO: make this usable for extend (elements), too =
758
+ // do not use $priority, make assignments on define do not have own _block
759
+ function layeredAssignments( assignment ) {
760
+ const layered = Object.create(null);
761
+ for (const a of assignment) {
762
+ const layer = a.$priority && layers.layer( a );
763
+ // just consider layer if Extend/Annotate, not Define
730
764
  const name = (layer) ? layer.realname : '';
731
- const done = layerAnnos[name];
765
+ const done = layered[name];
732
766
  if (done)
733
- done.annos.push( a );
767
+ done.assignments.push( a );
734
768
  else
735
- layerAnnos[name] = { layer, annos: [ a ] };
769
+ layered[name] = { name, layer, assignments: [ a ] };
770
+ // TODO: file - if set: unique in layer
736
771
  }
737
- mergeArrayInSCCs();
738
- art[annoName] = mergeLayeredArrays( findLayerCandidate( ) );
739
- return;
772
+ return layered;
773
+ }
740
774
 
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
- }
775
+ // Return assignments of the highest layers.
776
+ // Also return whether there could be an issue:
777
+ // - false: there is just one assignment
778
+ // - 'unrelated': there is just one assignment per layer
779
+ // - true: there is at least one layer with two or more assignments
780
+ // TODO: make this usable for extend (elements), too
781
+ function assignmentsOfHighestLayers( layeredAnnos ) {
782
+ const layerNames = Object.keys( layeredAnnos );
783
+ // console.log('HIB:',layerNames)
784
+ if (layerNames.length <= 1) {
785
+ const name = layerNames[0];
786
+ const { assignments } = layeredAnnos[name] || { assignments: [] };
787
+ delete layeredAnnos[name];
788
+ return { assignments, issue: assignments.length > 1 };
789
+ }
790
+
791
+ // collect all layers which are lower than another layer
792
+ const allExtends = Object.create(null);
793
+ allExtends[''] = {}; // the "Define" layer
794
+ for (const name of layerNames) {
795
+ if (name) // not the "Define" layer
796
+ Object.assign( allExtends, layeredAnnos[name].layer._layerExtends );
797
+ }
798
+ // console.log('HIE:',Object.keys(allExtends))
799
+ const assignments = [];
800
+ const highest = [];
801
+ for (const name of layerNames) {
802
+ if (!(name in allExtends)) {
803
+ const layer = layeredAnnos[name];
804
+ delete layeredAnnos[name];
805
+ highest.push( layer );
806
+ assignments.push( ...layer.assignments );
801
807
  }
802
- return mergeTarget;
803
808
  }
809
+ assignments.sort( compareAssignments );
810
+ const good = highest.every( layer => layer.assignments.length === 1 );
811
+ // TODO: use layer.file instead
812
+ const issue = !good || highest.length > 1 && 'unrelated';
813
+ // console.log('HI:',highest.map(l=>l.name),issue,issue&&assignments)
814
+ return { assignments, issue };
815
+ }
804
816
 
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)' );
817
+ function compareAssignments( a, b ) {
818
+ const fileA = layers.realname( a._block );
819
+ const fileB = layers.realname( b._block );
820
+ if (fileA !== fileB)
821
+ return (fileA > fileB) ? 1 : -1;
822
+ return (a?.location?.line || 0) - (b?.location?.line || 0) ||
823
+ (a?.location?.col || 0) - (b?.location?.col || 0);
824
+ }
825
+
826
+ function applyAssignment( previousAnno, anno, art, annoName ) {
827
+ if (!previousAnno) {
828
+ if (!annotationHasEllipsis( anno ))
829
+ return anno;
830
+ if (anno.$priority) { // already complained about with Define
831
+ message( 'anno-unexpected-ellipsis-layers', // TODO: better location
832
+ [ anno.name.location, art ], { code: '...' } );
833
+ }
834
+ previousAnno = { val: [] };
835
+ }
836
+ else if (previousAnno.literal !== 'array') {
837
+ error( 'anno-mismatched-ellipsis', // TODO: better location
838
+ [ anno.name.location, art ], { code: '...' } );
839
+ previousAnno = { val: [] };
840
+ }
841
+ const previousValue = previousAnno.val;
842
+ let prevPos = 0;
843
+ const result = [];
844
+ for (const item of anno.val) {
845
+ const ell = item && item.literal === 'token' && item.val === '...';
846
+ if (!ell) {
847
+ result.push( item );
848
+ }
849
+ else {
850
+ let upToSpec = item.upTo && checkUpToSpec( item.upTo, art, annoName, true );
851
+ while (prevPos < previousValue.length) {
852
+ const prevItem = previousValue[prevPos++];
853
+ result.push( prevItem );
854
+ if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
855
+ upToSpec = false;
856
+ break;
826
857
  }
827
858
  }
859
+ if (upToSpec) { // non-matched UP TO
860
+ warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
861
+ 'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
862
+ }
828
863
  }
829
- return result;
830
864
  }
865
+ // console.log('TP:',previousValue.map(se),anno.val.map(se),'->',result.map(se))
866
+ return { val: result, literal: 'array' };
867
+ }
868
+ // function se(a) { return a.upTo ? [a.val,a.upTo.val] : a.val ; }
831
869
 
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') {
870
+ function checkUpToSpec( upToSpec, art, annoName, isFullUpTo ) {
871
+ const { literal } = upToSpec;
872
+ if (!isFullUpTo) { // inside struct of UP TO
873
+ if (literal !== 'struct' && literal !== 'array' )
842
874
  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
875
  }
876
+ else if (literal === 'struct') {
877
+ return Object.values( upToSpec.struct ).every( v => checkUpToSpec( v, art, annoName ) );
878
+ }
879
+ else if (literal !== 'array' && literal !== 'boolean' && literal !== 'null') {
880
+ return true;
881
+ }
882
+ error( null, [ upToSpec.location, art ],
883
+ { anno: annoName, code: '... up to', '#': literal },
884
+ {
885
+ std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
886
+ array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
887
+ // eslint-disable-next-line max-len
888
+ struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
889
+ boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
890
+ null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
891
+ } );
892
+ return false;
893
+ }
856
894
 
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
- }
895
+ function equalUpTo( previousItem, upToSpec ) {
896
+ if (!previousItem)
880
897
  return false;
898
+ if ('val' in upToSpec) {
899
+ if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
900
+ return true;
901
+ const typeUpTo = typeof upToSpec.val;
902
+ const typePrev = typeof previousItem.val;
903
+ if (typeUpTo === 'number')
904
+ return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
905
+ if (typePrev === 'number')
906
+ return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
881
907
  }
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;
908
+ else if (upToSpec.path) {
909
+ return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
901
910
  }
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
911
+ else if (upToSpec.sym) {
912
+ return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
930
913
  }
931
-
932
- function layerExtends( name ) {
933
- const { layer } = layerAnnos[name];
934
- return layer && layer._layerExtends;
914
+ else if (upToSpec.struct && previousItem.struct) {
915
+ return Object.entries( upToSpec.struct )
916
+ .every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
935
917
  }
918
+ return false;
936
919
  }
937
920
 
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;
921
+ function normalizeRef( node ) { // see to-csn.js
922
+ const ref = pathName( node.path );
923
+ return node.variant ? `${ ref }#${ node.variant.id }` : ref;
952
924
  }
953
925
 
954
926
  // Phase 4 - queries and associations --------------------------------------