@sap/cds-compiler 6.6.2 → 6.7.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 (52) hide show
  1. package/CHANGELOG.md +37 -1
  2. package/bin/cdsc.js +2 -0
  3. package/bin/cdsse.js +1 -1
  4. package/lib/base/message-registry.js +13 -7
  5. package/lib/base/model.js +0 -72
  6. package/lib/checks/elements.js +1 -1
  7. package/lib/checks/featureFlags.js +2 -2
  8. package/lib/checks/manyExpand.js +28 -0
  9. package/lib/checks/validator.js +2 -0
  10. package/lib/compiler/assert-consistency.js +3 -4
  11. package/lib/compiler/base.js +8 -0
  12. package/lib/compiler/builtins.js +8 -9
  13. package/lib/compiler/checks.js +27 -6
  14. package/lib/compiler/cycle-detector.js +4 -4
  15. package/lib/compiler/define.js +65 -83
  16. package/lib/compiler/extend.js +357 -325
  17. package/lib/compiler/finalize-parse-cdl.js +3 -4
  18. package/lib/compiler/generate.js +205 -203
  19. package/lib/compiler/kick-start.js +34 -49
  20. package/lib/compiler/populate.js +95 -28
  21. package/lib/compiler/propagator.js +3 -5
  22. package/lib/compiler/resolve.js +17 -13
  23. package/lib/compiler/shared.js +52 -20
  24. package/lib/compiler/tweak-assocs.js +2 -4
  25. package/lib/compiler/utils.js +84 -31
  26. package/lib/edm/edmAnnoPreprocessor.js +2 -2
  27. package/lib/gen/BaseParser.js +924 -1055
  28. package/lib/gen/CdlGrammar.checksum +1 -1
  29. package/lib/gen/CdlParser.js +5 -2
  30. package/lib/json/from-csn.js +25 -16
  31. package/lib/main.d.ts +13 -0
  32. package/lib/model/revealInternalProperties.js +18 -0
  33. package/lib/parsers/AstBuildingParser.js +22 -5
  34. package/lib/render/toHdbcds.js +2 -2
  35. package/lib/render/toSql.js +1 -1
  36. package/lib/render/utils/sql.js +2 -2
  37. package/lib/render/utils/standardDatabaseFunctions.js +2 -2
  38. package/lib/transform/db/constraints.js +3 -4
  39. package/lib/transform/db/expansion.js +2 -44
  40. package/lib/transform/db/killAnnotations.js +1 -1
  41. package/lib/transform/db/processSqlServices.js +10 -11
  42. package/lib/transform/effective/main.js +2 -1
  43. package/lib/transform/featureFlags.js +6 -1
  44. package/lib/transform/forOdata.js +7 -124
  45. package/lib/transform/forRelationalDB.js +2 -1
  46. package/lib/transform/odata/fioriTreeViews.js +173 -0
  47. package/lib/transform/odata/flattening.js +2 -2
  48. package/lib/transform/translateAssocsToJoins.js +7 -4
  49. package/package.json +1 -1
  50. package/share/messages/message-explanations.json +0 -2
  51. package/share/messages/type-unexpected-foreign-keys.md +0 -52
  52. package/share/messages/type-unexpected-on-condition.md +0 -52
@@ -43,11 +43,15 @@ function fns( model ) {
43
43
  // Map `exprCtx` (is a param of traversal functions) to reference semantics
44
44
  const referenceSemantics = {
45
45
  // global: ------------------------------------------------------------------
46
- using: { // only used to produce error message
47
- isMainRef: 'all',
46
+ using: { // only used to produce warnings
47
+ isMainRef: 'no-leaf-gap',
48
48
  lexical: null,
49
49
  dynamic: modelDefinitions,
50
50
  notFound: undefinedDefinition,
51
+ messageMap: {
52
+ 'ref-undefined-art': 'ref-undefined-using',
53
+ 'ref-undefined-def': 'ref-undefined-using',
54
+ },
51
55
  },
52
56
  // scope:'global': for cds.Association and auto-redirected targets
53
57
  $global: {
@@ -58,14 +62,14 @@ function fns( model ) {
58
62
  },
59
63
  // only used for the main annotate/extend statements, not inner ones:
60
64
  annotate: {
61
- isMainRef: 'all',
65
+ isMainRef: 'no-leaf-gap',
62
66
  lexical: userBlock,
63
67
  dynamic: modelDefinitions,
64
68
  notFound: undefinedForAnnotate,
65
69
  accept: extendableArtifact,
66
70
  },
67
71
  'annotate-sec': {
68
- isMainRef: 'all',
72
+ isMainRef: 'no-leaf-gap',
69
73
  lexical: userBlock,
70
74
  dynamic: modelDefinitions,
71
75
  notFound: undefinedDefinition,
@@ -660,7 +664,6 @@ function fns( model ) {
660
664
  if (!root)
661
665
  return setArtifactLink( ref, root );
662
666
 
663
- // how many path items are for artifacts (rest: elements)
664
667
  let art = getPathItem( ref, semantics, user );
665
668
  if (!art)
666
669
  return setArtifactLink( ref, art );
@@ -838,16 +841,19 @@ function fns( model ) {
838
841
  const dynamicDict = semantics.dynamic( ruser, user._user && user._artifact );
839
842
  if (!dynamicDict) // avoid consequential errors
840
843
  return setArtifactLink( head, null );
844
+
841
845
  const isVar = (semantics.dollar && head.id.charAt( 0 ) === '$');
842
846
  const dict = (isVar) ? model.$magicVariables.elements : dynamicDict;
843
- const r = dict[head.id];
847
+ const r = dict[head.id] || // or a ref like "MyEntity.texts" in CSN
848
+ isMainRef && Functions.generateOnDemand?.( head.id );
844
849
  if (r)
845
850
  return setArtifactLink( head, r );
846
851
 
847
852
  if (!semantics.dollar) {
848
853
  valid.push( dynamicDict );
854
+ const noGen = !definedViaCdl( ruser );
849
855
  if (isMainRef) // eslint-disable-next-line no-return-assign
850
- valid.forEach( ( d, idx ) => (valid[idx] = removeGapArtifact( d )) );
856
+ valid.forEach( ( d, idx ) => (valid[idx] = removeGapArtifact( d, noGen )) );
851
857
  }
852
858
  else {
853
859
  const filterFn = semantics.variableFilter || removeRestrictedVariables;
@@ -871,6 +877,7 @@ function fns( model ) {
871
877
  function getPathItem( ref, semantics, user ) {
872
878
  // let art = (headArt && headArt.kind === '$tableAlias') ? headArt._origin : headArt;
873
879
  const { path } = ref;
880
+ // how many path items are for artifacts (the rest are for elements):
874
881
  let artItemsCount = 0;
875
882
  const { isMainRef } = semantics;
876
883
  if (isMainRef) {
@@ -894,7 +901,10 @@ function fns( model ) {
894
901
  const envFn = (artItemsCount >= 0) ? artifactsEnv : elementsEnv;
895
902
  // TOOD: call envFn with location of last item (for dependency error)
896
903
  const env = envFn( art, path[index - 1].location, user );
897
- const found = env && env[item.id]; // not env?.[item.id] ! …we want to keep the 0
904
+ let found = env && env[item.id]; // not env?.[] - keep a `0`
905
+ if (!found && found !== 0 && artItemsCount >= 0)
906
+ found = Functions.generateOnDemand?.( `${ prev.name.id }.${ item.id }` );
907
+
898
908
  // Reject `$self.$_column_1`: TODO: necessary to do here again?
899
909
  art = setArtifactLink( item, (found?.name?.$inferred === '$internal') ? undefined : found );
900
910
 
@@ -904,24 +914,33 @@ function fns( model ) {
904
914
  const notFound = (artItemsCount >= 0) ? semantics.notFound : undefinedItemElement;
905
915
  // TODO: streamline function arguments (probably: user, path, semantics, prev )
906
916
  // false returned by semantics.navigation: no further error:
907
- if (env !== false)
908
- notFound( user, item, [ env ], null, prev, path, semantics );
917
+ if (env !== false) {
918
+ const exp = (artItemsCount >= 0)
919
+ ? removeGapArtifact( env, !definedViaCdl( user ) )
920
+ : env;
921
+ notFound( user, item, [ exp ], null, prev, path, semantics );
922
+ }
909
923
  return null;
910
924
  }
911
925
  // need to do that here, because we also need to disallow Service.AutoExposed:elem
912
926
  // TODO: but Service.AutoExposed.NotAuto should be fine
913
927
  if (isMainRef && isMainRef !== 'all' && artItemsCount === 0) {
914
928
  if (art.kind === 'namespace') {
929
+ if (Functions.generateOnDemand?.( art ) ||
930
+ isMainRef === 'no-leaf-gap' && art._subArtifacts)
931
+ continue; // art is generated entity assigned to gap object
915
932
  if (env !== false) {
916
- semantics.notFound( user, item, [ removeGapArtifact( env ) ],
933
+ semantics.notFound( user, item, [ removeGapArtifact( env, !definedViaCdl( user ) ) ],
917
934
  null, prev, path, semantics );
918
935
  }
919
936
  return null;
920
937
  }
921
- else if (art.$inferred === 'autoexposed' && !user.$inferred) {
938
+ else if (art.$inferred === 'autoexposed' && !user.$inferred &&
939
+ isMainRef !== 'no-leaf-gap') {
922
940
  // Depending on the processing sequence, the following could be a
923
941
  // simple 'ref-undefined-art'/'ref-undefined-def' - TODO: which we
924
942
  // could "change" to this message at the end of compile():
943
+ // HINT: this does not appear anymore
925
944
  error( 'ref-unexpected-autoexposed', [ item.location, user ], { art },
926
945
  'An auto-exposed entity can\'t be referred to - expose entity $(ART) explicitly' );
927
946
  return null; // continuation semantics: like “not found”
@@ -983,9 +1002,8 @@ function fns( model ) {
983
1002
 
984
1003
  switch (art.kind) {
985
1004
  case 'using': {
986
- const def = model.definitions[art.extern.id];
987
- if (!def)
988
- return def;
1005
+ const def = model.definitions[art.extern.id] ||
1006
+ Functions.createGapArtifact( art.extern.id, art.extern.location );
989
1007
  if (def.$duplicates)
990
1008
  return false;
991
1009
  art = setArtifactLink( head, def ); // we do not want to see the using
@@ -994,15 +1012,21 @@ function fns( model ) {
994
1012
  }
995
1013
  /* FALLTHROUGH */
996
1014
  case 'namespace': {
997
- if (semantics.isMainRef === 'all' || path.length !== 1 && ref.scope !== 1)
1015
+ if (semantics.isMainRef === 'all' ||
1016
+ semantics.isMainRef === 'no-leaf-gap' && art._subArtifacts ||
1017
+ path.length !== 1 && ref.scope !== 1 ||
1018
+ // "MyEntity.texts" in CSN or via `using` which is (at the moment)
1019
+ // just a gap artifact
1020
+ Functions.generateOnDemand?.( art ))
998
1021
  return art;
1022
+
999
1023
  const valid = [];
1000
1024
  const lexical = userBlock( user );
1001
1025
  if (lexical) {
1002
1026
  for (let env = lexical; env; env = env._block)
1003
1027
  valid.push( removeGapArtifact( env.artifacts || Object.create( null ) ) );
1004
1028
  }
1005
- valid.push( removeGapArtifact( model.definitions ) );
1029
+ valid.push( removeGapArtifact( model.definitions, !definedViaCdl( user ) ) );
1006
1030
  semantics.notFound?.( user._user || user, head, valid, model.definitions,
1007
1031
  null, path, semantics );
1008
1032
 
@@ -1488,7 +1512,7 @@ function fns( model ) {
1488
1512
  }
1489
1513
  else {
1490
1514
  const target = art._effectiveType?.target;
1491
- if (target?._artifact) {
1515
+ if (resolvePath( target, 'target', art )) {
1492
1516
  signalNotFound( 'ref-undefined-element', [ item.location, user ], valid,
1493
1517
  { '#': 'target', art: target, id: item.id }, semantics );
1494
1518
  }
@@ -1622,6 +1646,9 @@ function fns( model ) {
1622
1646
  }
1623
1647
 
1624
1648
  function acceptStructOrBare( art, user, ref ) { // for includes[]
1649
+ // eslint-disable-next-line @stylistic/object-curly-newline
1650
+ if (user.kind in { context: 1, service: 1, action: 1, function: 1 })
1651
+ return false;
1625
1652
  // It had been checked before that `includes` is already forbidden for
1626
1653
  // non-entity/aspect/type/event.
1627
1654
  //
@@ -1650,6 +1677,9 @@ function fns( model ) {
1650
1677
  // We might allow includes with elements in the future, they'd probably
1651
1678
  // count as specified elements with lower priority, i.e. annos, types, key
1652
1679
  // etc on columns beat those inherited from the include.
1680
+ // TODO v7 forbid `extend Proj with ZeroElemAspect` even if ZeroElemAspect has
1681
+ // 0 elements. Making the element-length check work requires that effectiveType()
1682
+ // has been called on the include already before.
1653
1683
  if (art.kind === 'aspect' &&
1654
1684
  (!art.elements || base.query && !Object.keys( art.elements ).length))
1655
1685
  return art;
@@ -2181,12 +2211,14 @@ function removeDollarNames( dict ) {
2181
2211
  return r;
2182
2212
  }
2183
2213
 
2184
- function removeGapArtifact( dict ) {
2214
+ function removeGapArtifact( dict, alsoRemoveInferred ) {
2185
2215
  const r = Object.create( null );
2186
2216
  for (const name in dict) {
2187
2217
  const art = dict[name];
2188
- // TODO: for gaps with sub artifacts, we use use `${name}.` as name
2218
+ // TODO: for gaps with sub artifacts, use `${name}.` as name
2189
2219
  // TODO: clarify with LSP
2220
+ if (alsoRemoveInferred && art.$inferred)
2221
+ continue;
2190
2222
  if (art.kind !== 'namespace' || art._subArtifacts)
2191
2223
  r[name] = dict[name];
2192
2224
  }
@@ -2,10 +2,6 @@
2
2
 
3
3
  'use strict';
4
4
 
5
- const {
6
- forEachGeneric,
7
- forEachInOrder,
8
- } = require('../base/model');
9
5
  const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
10
6
 
11
7
  const {
@@ -19,6 +15,8 @@ const {
19
15
  traverseQueryExtra,
20
16
  setExpandStatus,
21
17
  getUnderlyingBuiltinType,
18
+ forEachGeneric,
19
+ forEachInOrder,
22
20
  } = require('./utils');
23
21
  const { Location } = require('../base/location');
24
22
  const { CompilerAssertion } = require('../base/error');
@@ -10,7 +10,7 @@
10
10
 
11
11
  'use strict';
12
12
 
13
- const { dictAdd, pushToDict, dictFirst } = require('../base/dictionaries');
13
+ const { dictAdd, dictFirst } = require('../base/dictionaries');
14
14
  const { Location, weakLocation } = require('../base/location');
15
15
  const { XsnName, XsnArtifact } = require('./xsn-model');
16
16
 
@@ -145,10 +145,11 @@ function proxyCopyMembers( art, dictProp, originDict, location, kind, tmpDepreca
145
145
  * @return {XSN.Artifact}
146
146
  */
147
147
  function initItemsLinks( obj, block ) {
148
+ const parent = obj._parent;
148
149
  let { items } = obj;
149
150
  while (items) {
150
151
  setLink( items, '_outer', obj );
151
- setLink( items, '_parent', obj._parent );
152
+ setLink( items, '_parent', parent );
152
153
  setLink( items, '_block', block );
153
154
  obj = items;
154
155
  items = obj.items;
@@ -162,7 +163,7 @@ function initItemsLinks( obj, block ) {
162
163
  * is most often the property `elem.name.id`.
163
164
  *
164
165
  * If argument `prop` is provided, add `elem` to the dictionary of that name,
165
- * e.g. `elements`.
166
+ * e.g. `elements`. TODO: remove `prop`.
166
167
  */
167
168
  function setMemberParent( elem, name, parent, prop ) {
168
169
  if (prop) { // extension or structure include
@@ -286,21 +287,6 @@ function dependsOnSilent( user, art ) {
286
287
  user._deps.push( { art } );
287
288
  }
288
289
 
289
- function storeExtension( elem, name, prop, parent ) {
290
- if (prop === 'enum')
291
- prop = 'elements';
292
- const kind = `_${ elem.kind }`; // _extend or _annotate
293
- if (!parent[kind])
294
- setLink( parent, kind, {} );
295
- // if (name === '' && prop === 'params') {
296
- // pushToDict( parent[kind], 'returns', elem ); // not really a dict
297
- // return;
298
- // }
299
- if (!parent[kind][prop])
300
- parent[kind][prop] = Object.create( null );
301
- pushToDict( parent[kind][prop], name, elem );
302
- }
303
-
304
290
  /** @type {(a: any, b: any) => boolean} */
305
291
  const testFunctionPlaceholder = () => true;
306
292
 
@@ -348,11 +334,11 @@ function augmentPath( location, ...args ) {
348
334
  return { path: args.map( id => ({ id, location }) ), location };
349
335
  }
350
336
 
351
- function copyExpr( expr, location ) {
337
+ function copyExpr( expr, location, noLinks = false ) {
352
338
  if (!expr || typeof expr !== 'object')
353
339
  return expr;
354
340
  else if (Array.isArray( expr ))
355
- return expr.map( e => copyExpr( e, location ) );
341
+ return expr.map( e => copyExpr( e, location, noLinks ) );
356
342
 
357
343
  const proto = Object.getPrototypeOf( expr );
358
344
  if (proto && proto !== Object.prototype && proto !== XsnName.prototype &&
@@ -363,16 +349,21 @@ function copyExpr( expr, location ) {
363
349
  for (const prop of Object.getOwnPropertyNames( expr )) {
364
350
  const pd = Object.getOwnPropertyDescriptor( expr, prop );
365
351
  if (!proto)
366
- r[prop] = copyExpr( pd.value, location );
367
-
368
- else if (!pd.enumerable || prop.charAt(0) === '$')
369
- Object.defineProperty( r, prop, pd );
352
+ r[prop] = copyExpr( pd.value, location, noLinks );
370
353
 
371
354
  else if (prop === 'location')
372
355
  r[prop] = location || pd.value;
373
356
 
374
- else
375
- r[prop] = copyExpr( pd.value, location );
357
+ else if (prop.charAt(0) === '$')
358
+ Object.defineProperty( r, prop, pd );
359
+
360
+ else if (pd.enumerable)
361
+ r[prop] = copyExpr( pd.value, location, noLinks );
362
+
363
+ else if (!noLinks)
364
+ Object.defineProperty( r, prop, pd );
365
+ // TODO: probably consider noLinks just for element references (_artifact of
366
+ // first path item has _main) - test calc element with `cast`
376
367
  }
377
368
  return r;
378
369
  }
@@ -660,22 +651,37 @@ function isDirectComposition( art ) {
660
651
  return path?.length === 1 && path[0].id === 'cds.Composition';
661
652
  }
662
653
 
663
- function targetCantBeAspect( elem, calledForTargetAspectProp ) {
654
+ function targetCantBeAspect( elem, calledForTargetAspectProp, definitions ) { // see #11658
664
655
  // Remark: we do not check `on` and `keys` here - the error should complain
665
656
  // at the `on`/`keys`, not the aspect
666
657
  if (!isDirectComposition( elem ) || elem.targetAspect && !calledForTargetAspectProp)
667
658
  return (elem.type && !elem.type.$inferred) ? 'std' : 'redirected';
659
+ // TODO: better msg if due to calledForTargetAspectProp
668
660
  if (!elem._main)
669
661
  return elem.kind; // type or annotation
670
662
  // TODO: extra for "in many"?
671
663
  let art = elem;
672
664
  while (art.kind === 'element')
673
665
  art = art._parent;
666
+ // Remark: if we include entity/aspect, we might still have a targetAspects in a
667
+ // type/event - see Error[type-managed-composition]
668
+ // With a future feature “managed compositions in sub elements“, we might allow
669
+ // moving an aspect to targetAspect independently from the main artifact
670
+ if (definitions && art.kind === 'extend')
671
+ art = definitions[art.name.id];
674
672
  if (![ 'entity', 'aspect', 'event' ].includes( art.kind ))
675
673
  return (art.kind !== 'mixin') ? art.kind : 'select';
676
- return ((art.query || art.kind === 'event') && !(calledForTargetAspectProp && elem.target))
677
- ? art.kind
678
- : elem._parent.kind === 'element' && 'sub';
674
+ if ((art.query || art.kind === 'event') && !(calledForTargetAspectProp && elem.target))
675
+ return art.kind;
676
+ if (elem._parent.kind === 'element')
677
+ return 'sub';
678
+ if (!calledForTargetAspectProp) {
679
+ if (elem.on)
680
+ return 'on';
681
+ if (elem.foreignKeys)
682
+ return 'keys';
683
+ }
684
+ return null;
679
685
  }
680
686
 
681
687
  function userQuery( user ) {
@@ -781,6 +787,50 @@ function compositionTextVariant( art, composition, association = 'std' ) {
781
787
  : association;
782
788
  }
783
789
 
790
+ // Looping: ---------------------------------------------------------------------
791
+
792
+ /**
793
+ * Apply function `callback` to all artifacts in dictionary
794
+ * `model.definitions`. See function `forEachGeneric` for details.
795
+ * TODO: should we skip "namespaces" already here?
796
+ */
797
+ function forEachDefinition( model, callback ) {
798
+ forEachGeneric( model, 'definitions', callback );
799
+ }
800
+
801
+ /**
802
+ * Apply function `callback` to all members of object `construct` (main artifact
803
+ * or parent member). Members are considered those in dictionaries `elements`,
804
+ * `enum`, `actions` and `params` of `obj`.
805
+ *
806
+ * @param {XSN.Artifact} construct
807
+ * @param {(member: XSN.Artifact, memberName: string, prop: string) => void} callback
808
+ * @param {XSN.Artifact} [target]
809
+ */
810
+ function forEachMember( construct, callback ) {
811
+ forEachGeneric( construct, 'elements', callback );
812
+ forEachGeneric( construct, 'enum', callback );
813
+ forEachGeneric( construct, 'foreignKeys', callback );
814
+ forEachGeneric( construct, 'actions', callback );
815
+ forEachGeneric( construct, 'params', callback );
816
+ if (construct.returns)
817
+ callback( construct.returns, '', 'params' );
818
+ }
819
+
820
+ // Apply function `callback` to all objects in dictionary `parent[prop]`,
821
+ // including all duplicates (found under the same name). Function `callback` is
822
+ // called with the following arguments: the object, the name and `prop`.
823
+ function forEachGeneric( parent, prop, callback ) {
824
+ const dict = parent[prop];
825
+ for (const name in dict) {
826
+ const obj = dict[name];
827
+ const { $duplicates } = obj;
828
+ callback( obj, name, prop );
829
+ if (Array.isArray( $duplicates )) // redefinitions
830
+ $duplicates.forEach( o => callback( o, name, prop ) );
831
+ }
832
+ }
833
+
784
834
  module.exports = {
785
835
  pushLink,
786
836
  annotationVal,
@@ -801,7 +851,6 @@ module.exports = {
801
851
  initDollarSelf,
802
852
  initDollarParameters,
803
853
  initBoundSelfParam,
804
- storeExtension,
805
854
  withAssociation,
806
855
  pathName,
807
856
  augmentPath,
@@ -827,4 +876,8 @@ module.exports = {
827
876
  definedViaCdl,
828
877
  artifactRefLocation,
829
878
  compositionTextVariant,
879
+ forEachDefinition,
880
+ forEachMember,
881
+ forEachGeneric,
882
+ forEachInOrder: forEachGeneric,
830
883
  };
@@ -49,7 +49,7 @@ function addToSetAttr( carrier, propName, propValue, removeFromType = true ) {
49
49
 
50
50
  function applyAppSpecificLateCsnTransformationOnElement( options, element, struct, error ) {
51
51
  if (options.isV2() && struct['@Aggregation.ApplySupported.PropertyRestrictions'])
52
- mapAnnotationAssignment(element, struct, AnalyticalAnnotations());
52
+ mapAnnotationAssignment(element, struct, GenerateAnalyticalAnnotations());
53
53
 
54
54
 
55
55
  // etag requires Core.OptimisticConcurrency to be set in V4 (cap/issues#2641)
@@ -66,7 +66,7 @@ function applyAppSpecificLateCsnTransformationOnElement( options, element, struc
66
66
  (struct['@Core.OptimisticConcurrency'] || [])/* .push(element.name) */);
67
67
  }
68
68
 
69
- function AnalyticalAnnotations() {
69
+ function GenerateAnalyticalAnnotations() {
70
70
  function mapCommonAttributes( elt, structure, prop ) {
71
71
  const CommonAttributes = elt[prop];
72
72
  if (!Array.isArray(CommonAttributes)) {