@sap/cds-compiler 2.10.2 → 2.11.4

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 (82) hide show
  1. package/CHANGELOG.md +90 -5
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +3 -1
  4. package/bin/cdsc.js +49 -25
  5. package/bin/cdsse.js +1 -0
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_BETA.md +10 -0
  8. package/lib/api/.eslintrc.json +2 -0
  9. package/lib/api/main.js +8 -36
  10. package/lib/api/options.js +15 -6
  11. package/lib/api/validate.js +30 -3
  12. package/lib/backends.js +12 -13
  13. package/lib/base/dictionaries.js +2 -1
  14. package/lib/base/keywords.js +3 -2
  15. package/lib/base/message-registry.js +34 -10
  16. package/lib/base/messages.js +38 -18
  17. package/lib/base/model.js +5 -4
  18. package/lib/base/optionProcessorHelper.js +57 -23
  19. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  20. package/lib/checks/selectItems.js +4 -0
  21. package/lib/checks/unknownMagic.js +6 -3
  22. package/lib/compiler/assert-consistency.js +9 -2
  23. package/lib/compiler/base.js +65 -0
  24. package/lib/compiler/builtins.js +62 -16
  25. package/lib/compiler/checks.js +2 -1
  26. package/lib/compiler/definer.js +66 -108
  27. package/lib/compiler/index.js +29 -29
  28. package/lib/compiler/propagator.js +5 -2
  29. package/lib/compiler/resolver.js +225 -58
  30. package/lib/compiler/shared.js +53 -229
  31. package/lib/compiler/utils.js +184 -0
  32. package/lib/edm/annotations/genericTranslation.js +1 -1
  33. package/lib/edm/csn2edm.js +3 -2
  34. package/lib/edm/edmPreprocessor.js +34 -38
  35. package/lib/edm/edmUtils.js +3 -3
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +17 -1
  38. package/lib/gen/language.tokens +79 -73
  39. package/lib/gen/languageLexer.interp +19 -1
  40. package/lib/gen/languageLexer.js +779 -731
  41. package/lib/gen/languageLexer.tokens +71 -65
  42. package/lib/gen/languageParser.js +4668 -4072
  43. package/lib/json/from-csn.js +10 -10
  44. package/lib/json/to-csn.js +228 -47
  45. package/lib/language/antlrParser.js +11 -0
  46. package/lib/language/errorStrategy.js +26 -8
  47. package/lib/language/genericAntlrParser.js +73 -14
  48. package/lib/language/language.g4 +79 -3
  49. package/lib/main.d.ts +215 -18
  50. package/lib/main.js +3 -1
  51. package/lib/model/api.js +2 -2
  52. package/lib/model/csnRefs.js +117 -33
  53. package/lib/model/csnUtils.js +65 -133
  54. package/lib/model/enrichCsn.js +62 -37
  55. package/lib/model/revealInternalProperties.js +25 -8
  56. package/lib/model/sortViews.js +8 -1
  57. package/lib/modelCompare/compare.js +2 -1
  58. package/lib/optionProcessor.js +33 -18
  59. package/lib/render/.eslintrc.json +1 -2
  60. package/lib/render/DuplicateChecker.js +1 -1
  61. package/lib/render/toCdl.js +15 -8
  62. package/lib/render/toHdbcds.js +26 -49
  63. package/lib/render/toSql.js +61 -39
  64. package/lib/render/utils/common.js +1 -1
  65. package/lib/transform/db/applyTransformations.js +189 -0
  66. package/lib/transform/db/constraints.js +273 -119
  67. package/lib/transform/db/draft.js +3 -2
  68. package/lib/transform/db/expansion.js +6 -4
  69. package/lib/transform/db/flattening.js +19 -3
  70. package/lib/transform/db/transformExists.js +102 -9
  71. package/lib/transform/db/views.js +485 -0
  72. package/lib/transform/forHanaNew.js +93 -448
  73. package/lib/transform/forOdataNew.js +9 -2
  74. package/lib/transform/localized.js +2 -0
  75. package/lib/transform/odata/structuralPath.js +1 -5
  76. package/lib/transform/transformUtilsNew.js +22 -8
  77. package/lib/transform/translateAssocsToJoins.js +7 -15
  78. package/lib/utils/file.js +11 -5
  79. package/lib/utils/term.js +65 -42
  80. package/lib/utils/timetrace.js +48 -26
  81. package/package.json +1 -1
  82. package/lib/transform/db/helpers.js +0 -58
@@ -1,86 +1,33 @@
1
1
  // Compiler functions and utilities shared across all phases
2
-
2
+ // TODO: rename to paths.js and move non resolve-paths functions to somewhere else
3
3
 
4
4
  'use strict';
5
5
 
6
6
  const { searchName } = require('../base/messages');
7
- const { dictAdd, dictAddArray, pushToDict } = require('../base/dictionaries');
7
+ const { dictAddArray } = require('../base/dictionaries');
8
8
  const { setProp } = require('../base/model');
9
9
 
10
- const dictKinds = {
11
- definitions: 'absolute',
12
- elements: 'element',
13
- enum: 'enum',
14
- foreignKeys: 'key',
15
- actions: 'action',
16
- params: 'param',
17
- };
18
-
19
- const kindProperties = {
20
- // TODO: also foreignKeys ?
21
- namespace: { artifacts: true }, // on-the-fly context
22
- context: { artifacts: true, normalized: 'namespace' },
23
- service: { artifacts: true, normalized: 'namespace' },
24
- entity: { elements: true, actions: true, params: () => false },
25
- select: { normalized: 'select', elements: true },
26
- $join: { normalized: 'select' },
27
- $tableAlias: { normalized: 'alias' }, // table alias in select
28
- $self: { normalized: 'alias' }, // table alias in select
29
- $navElement: { normalized: 'element' },
30
- $inline: { normalized: 'element' }, // column with inline property
31
- event: { elements: true },
32
- type: { elements: propExists, enum: propExists },
33
- aspect: { elements: propExists },
34
- annotation: { elements: propExists, enum: propExists },
35
- enum: { normalized: 'element' },
36
- element: { elements: propExists, enum: propExists, dict: 'elements' },
37
- mixin: { normalized: 'alias' },
38
- action: {
39
- params: () => false, elements: () => false, enum: () => false, dict: 'actions',
40
- }, // no extend params, only annotate
41
- function: {
42
- params: () => false, elements: () => false, enum: () => false, normalized: 'action',
43
- }, // no extend params, only annotate
44
- key: { normalized: 'element' },
45
- param: { elements: () => false, enum: () => false, dict: 'params' },
46
- source: { artifacts: true }, // TODO -> $source
47
- using: {},
48
- extend: {
49
- isExtension: true,
50
- noDep: 'special',
51
- elements: true, /* only for parse-cdl */
52
- actions: true, /* only for parse-cdl */
53
- },
54
- annotate: {
55
- isExtension: true, noDep: 'special', elements: true, enum: true, actions: true, params: true,
56
- },
57
- builtin: {}, // = CURRENT_DATE, TODO: improve
58
- $parameters: {}, // $parameters in query entities
59
- };
60
-
61
- function propExists( prop, parent ) {
62
- const obj = parent.returns || parent;
63
- return (obj.items || obj.targetAspect || obj)[prop];
64
- }
10
+ const { setLink, dependsOn, pathName } = require('./utils');
65
11
 
66
12
  function artifactsEnv( art ) {
67
13
  return art._subArtifacts || Object.create(null);
68
14
  }
69
15
 
70
16
  /**
71
- * Main export function of this file. Return "resolve" functions shared for phase
72
- * "define" and "resolve". Argument `model` is the augmented CSN. Optional
73
- * argument `environment` is a function which returns the search environment
74
- * defined by its argument - it defaults to the dictionary of subartifacts of
75
- * the argument.
17
+ * Main export function of this file. Attach "resolve" functions shared for phase
18
+ * "define" and "resolve" to `model.$functions`, where argument `model` is the XSN.
19
+ *
20
+ * Before calling these functions, make sure that the following function
21
+ * in model.$volatileFunctions is set:
22
+ * - `environment`: a function which returns the search environment defined by
23
+ * its argument, e.g. a function which returns the dictionary of subartifacts.
76
24
  *
77
25
  * @param {XSN.Model} model
78
- * @param {(a, b?, c?) => any} environment
79
- * @returns {object} Commonly used "resolve" functions.
80
26
  */
81
- function fns( model, environment = artifactsEnv ) {
27
+ // TODO: yes, this function will be renamed
28
+ function fns( model ) {
82
29
  /** @type {CSN.Options} */
83
- const options = model.options || {};
30
+ const { options } = model;
84
31
  const {
85
32
  info, warning, error, message,
86
33
  } = model.$messageFunctions;
@@ -204,13 +151,15 @@ function fns( model, environment = artifactsEnv ) {
204
151
  },
205
152
  };
206
153
 
207
- return {
154
+ const VolatileFns = model.$volatileFunctions;
155
+ Object.assign( model.$functions, {
208
156
  resolveUncheckedPath,
209
157
  resolvePath,
210
158
  resolveTypeArguments,
211
159
  defineAnnotations,
212
160
  attachAndEmitValidNames,
213
- };
161
+ } );
162
+ return;
214
163
 
215
164
  function checkConstRef( art ) {
216
165
  return ![ 'builtin', 'param' ].includes( art.kind );
@@ -257,7 +206,7 @@ function fns( model, environment = artifactsEnv ) {
257
206
  // TODO: better error location if error for main
258
207
  if (elem._main.kind !== 'entity' )
259
208
  return true; // elem not starting at entity
260
- environment( art ); // sets _effectiveType on art
209
+ VolatileFns.environment( art ); // sets _effectiveType on art
261
210
  return !(art._effectiveType || art).target;
262
211
  }
263
212
 
@@ -345,7 +294,7 @@ function fns( model, environment = artifactsEnv ) {
345
294
  // first step: only use _combined of real query - TODO:
346
295
  // reject if not visible, but not allow more (!)
347
296
  (query._combined || query._parent._combined) ||
348
- environment( user._main ? user._parent : user );
297
+ VolatileFns.environment( user._main ? user._parent : user );
349
298
  }
350
299
  }
351
300
 
@@ -439,7 +388,7 @@ function fns( model, environment = artifactsEnv ) {
439
388
  }
440
389
  else {
441
390
  dependsOn( user, art._main, location );
442
- environment( art, location, user );
391
+ VolatileFns.environment( art, location, user );
443
392
  // Without on-demand resolve, we can simply signal 'undefined "x"'
444
393
  // instead of 'illegal cycle' in the following case:
445
394
  // element elem: type of elem.x;
@@ -513,7 +462,7 @@ function fns( model, environment = artifactsEnv ) {
513
462
  function getPathRoot( path, spec, user, env, extDict, msgArt ) {
514
463
  if (!spec.envFn && user._pathHead) {
515
464
  // TODO: not necessarily for explicit ON condition in expand
516
- environment( user._pathHead ); // make sure _origin is set
465
+ VolatileFns.environment( user._pathHead ); // make sure _origin is set
517
466
  return user._pathHead._origin;
518
467
  }
519
468
  const head = path[0];
@@ -570,8 +519,10 @@ function fns( model, environment = artifactsEnv ) {
570
519
  if (extDict && (!spec.dollar || head.id[0] !== '$')) {
571
520
  const r = extDict[head.id];
572
521
  if (Array.isArray(r)) {
573
- if (r[0].kind === '$navElement') {
574
- const names = r.filter( e => !e.$duplicates)
522
+ if (r[0].kind === '$navElement' && r.every( e => !e._parent.$duplicates )) {
523
+ // only complain about ambiguous source elements if we do not have
524
+ // duplicate table aliases, only mention non-ambiguous source elems
525
+ const names = r.filter( e => !e.$duplicates )
575
526
  .map( e => `${ e.name.alias }.${ e.name.element }` );
576
527
  if (names.length) {
577
528
  error( 'ref-ambiguous', [ head.location, user ], { id: head.id, names },
@@ -658,31 +609,26 @@ function fns( model, environment = artifactsEnv ) {
658
609
  continue;
659
610
  }
660
611
 
661
- const fn = (spec.envFn && artItemsCount >= 0) ? spec.envFn : environment;
612
+ const fn = (spec.envFn && artItemsCount >= 0) ? spec.envFn : VolatileFns.environment;
662
613
  const env = fn( art, item.location, user, spec.assoc );
663
-
664
- // do not check any elements of the path, e.g. $session - but still don't return path-head
665
- if (art && art.$uncheckedElements) {
666
- if (env && env[item.id]) // something like $user.id/$user.locale
667
- return env[item.id];
668
-
669
- // $user.foo - build our own valid path step obj
670
- // Important: Don't directly modify item!
671
- const obj = {
672
- location: item.location,
673
- kind: 'builtin',
674
- name: { id: item.id, element: path.map(p => p.id).join('.') },
675
- };
676
- setLink(obj, art, '_parent');
677
- return obj;
678
- }
679
-
680
614
  const sub = setLink( item, env && env[item.id] );
681
615
 
682
- if (!sub)
683
- return (sub === 0) ? 0 : errorNotFound( item, env );
684
- else if (Array.isArray(sub)) // redefinitions
616
+ if (!sub) {
617
+ // element was not found in environment
618
+ if (sub === 0)
619
+ return 0;
620
+ if (art.$uncheckedElements) { // magic variable / replacement variable
621
+ signalNotFound( 'ref-unknown-var', [ item.location, user ], [ env ],
622
+ { id: path.map(n => n.id).join('.') } );
623
+ }
624
+ else {
625
+ errorNotFound( item, env );
626
+ }
627
+ return null;
628
+ }
629
+ else if (Array.isArray(sub)) { // redefinitions
685
630
  return false;
631
+ }
686
632
 
687
633
  if (nav) { // we have already "pseudo-followed" a managed association
688
634
  // We currently rely on the check that targetElement references do
@@ -764,7 +710,7 @@ function fns( model, environment = artifactsEnv ) {
764
710
  { param: 'Entity $(ART) has no parameter $(MEMBER)' } );
765
711
  }
766
712
  else {
767
- signalNotFound( 'ref-undefined-element', [ item.location, user ],
713
+ signalNotFound( spec.undefinedDef || 'ref-undefined-element', [ item.location, user ],
768
714
  [ env ], { art: searchName( art, item.id, 'element' ) } );
769
715
  }
770
716
  return null;
@@ -847,7 +793,9 @@ function fns( model, environment = artifactsEnv ) {
847
793
  }
848
794
  // TODO: block should be construct._block
849
795
  if (construct.$annotations && construct.$annotations.doc )
850
- art.doc = construct.$annotations.doc;
796
+ art.doc = construct.$annotations.doc; // e.g. through `annotate` statement in CDL
797
+ else if (construct.doc)
798
+ art.doc = construct.doc; // e.g. through `extensions` array in CSN
851
799
  if (!construct.$annotations) {
852
800
  if (!block || block.$frontend !== 'json')
853
801
  return; // namespace, or in CDL source without @annos:
@@ -861,7 +809,7 @@ function fns( model, environment = artifactsEnv ) {
861
809
  setProp( a, '_block', block );
862
810
  a.$priority = priority;
863
811
  if (construct !== art)
864
- dictAddArray( art, annoProp, a );
812
+ addAnnotation( art, annoProp, a );
865
813
  }
866
814
  }
867
815
  }
@@ -912,143 +860,19 @@ function fns( model, environment = artifactsEnv ) {
912
860
  // TODO: _parent, _main is set later (if we have ElementRef), or do we
913
861
  // set _artifact?
914
862
  anno.$priority = priority;
915
- dictAddArray( art, annoProp, anno );
863
+ addAnnotation( art, annoProp, anno );
916
864
  }
917
865
  }
918
866
  }
919
867
 
920
- // Return string 'A.B.C' for parsed source `A.B.C` (is vector of ids with
921
- // locations):
922
- function pathName(path) {
923
- return (path.broken) ? '' : path.map( id => id.id ).join('.');
924
- }
925
-
926
- // The link (_artifact,_effectiveType,...) usually has the artifact as value.
927
- // Falsy values are:
928
- // - undefined: not computed yet, parse error, no ref
929
- // - null: no valid reference, param:true if that is not allowed
930
- // - false (only complete ref): multiple definitions, rejected
931
- // - 0 (for _effectiveType only): circular reference
932
- function setLink( obj, value = null, prop = '_artifact' ) {
933
- Object.defineProperty( obj, prop, { value, configurable: true, writable: true } );
934
- return value;
935
- }
936
-
937
- function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
938
- const elem = {
939
- name: { location: location || origin.name.location, id: name },
940
- kind: origin.kind,
941
- location: location || origin.location,
942
- };
943
- if (origin.name.$inferred)
944
- elem.name.$inferred = origin.name.$inferred;
945
- if (parent)
946
- setMemberParent( elem, name, parent, prop ); // TODO: redef in template
947
- setProp( elem, '_origin', origin );
948
- // TODO: should we use silent dependencies also for other things, like
949
- // included elements? (Currently for $inferred: 'expand-element' only)
950
- if (silentDep)
951
- dependsOnSilent( elem, origin );
952
- else
953
- dependsOn( elem, origin, location );
954
- return elem;
955
- }
956
-
957
- function setMemberParent( elem, name, parent, prop ) {
958
- if (prop) { // extension or structure include
959
- // TODO: consider nested ARRAY OF and RETURNS, COMPOSITION OF type
960
- const p = parent.items || parent.targetAspect || parent;
961
- if (!(prop in p))
962
- p[prop] = Object.create(null);
963
- dictAdd( p[prop], name, elem );
964
- }
965
- if (parent._outer)
966
- parent = parent._outer;
967
- setProp( elem, '_parent', parent );
968
- setProp( elem, '_main', parent._main || parent );
969
- elem.name.absolute = elem._main.name.absolute;
970
- if (name == null)
971
- return;
972
- const normalized = kindProperties[elem.kind].normalized || elem.kind;
973
- [ 'element', 'alias', 'select', 'param', 'action' ].forEach( ( kind ) => {
974
- if (normalized === kind)
975
- elem.name[kind] = (parent.name[kind] != null && kind !== 'select' && kind !== 'alias') ? `${ parent.name[kind] }.${ name }` : name;
976
-
977
- else if (parent.name[kind] != null)
978
- elem.name[kind] = parent.name[kind];
979
-
980
- else
981
- delete elem.name[kind];
982
- });
983
- // try { throw new Error('Foo') } catch (e) { elem.name.stack = e; };
984
- }
985
-
986
- /**
987
- * Adds a dependency user -> art with the given location.
988
- *
989
- * @param {XSN.Artifact} user
990
- * @param {XSN.Artifact} art
991
- * @param {XSN.Location} location
992
- */
993
- function dependsOn( user, art, location ) {
994
- if (!user._deps)
995
- setProp( user, '_deps', [] );
996
- user._deps.push( { art, location } );
997
- }
998
-
999
- /**
1000
- * Same as "dependsOn" but the dependency from user -> art is silent,
1001
- * i.e. not reported to the user.
1002
- *
1003
- * @param {XSN.Artifact} user
1004
- * @param {XSN.Artifact} art
1005
- */
1006
- function dependsOnSilent( user, art ) {
1007
- if (!user._deps)
1008
- setProp( user, '_deps', [] );
1009
- user._deps.push( { art } );
1010
- }
1011
-
1012
- function storeExtension( elem, name, prop, parent, block ) {
1013
- if (prop === 'enum')
1014
- prop = 'elements';
1015
- setProp( elem, '_block', block );
1016
- const kind = `_${ elem.kind }`; // _extend or _annotate
1017
- if (!parent[kind])
1018
- setProp( parent, kind, {} );
1019
- // if (name === '' && prop === 'params') {
1020
- // pushToDict( parent[kind], 'returns', elem ); // not really a dict
1021
- // return;
1022
- // }
1023
- if (!parent[kind][prop])
1024
- parent[kind][prop] = Object.create(null);
1025
- pushToDict( parent[kind][prop], name, elem );
1026
- }
1027
-
1028
- /** @type {(a: any, b: any) => boolean} */
1029
- const testFunctionPlaceholder = () => true;
1030
-
1031
- // Return path step if the path navigates along an association whose final type
1032
- // satisfies function `test`; "navigates along" = last path item not considered
1033
- // without truthy optional argument `alsoTestLast`.
1034
- function withAssociation( ref, test = testFunctionPlaceholder, alsoTestLast = false ) {
1035
- for (const item of ref.path || []) {
1036
- const art = item && item._artifact; // item can be null with parse error
1037
- if (art && art._effectiveType && art._effectiveType.target && test( art._effectiveType, item ))
1038
- return (alsoTestLast || item !== ref.path[ref.path.length - 1]) && item;
1039
- }
1040
- return false;
868
+ // Add annotation to definition - overwriting $inferred annos
869
+ function addAnnotation( art, annoProp, anno ) {
870
+ const old = art[annoProp];
871
+ if (old && old.$inferred)
872
+ delete art[annoProp];
873
+ dictAddArray( art, annoProp, anno );
1041
874
  }
1042
875
 
1043
876
  module.exports = {
1044
- dictKinds,
1045
- kindProperties,
1046
877
  fns,
1047
- setLink,
1048
- linkToOrigin,
1049
- dependsOn,
1050
- dependsOnSilent,
1051
- setMemberParent,
1052
- storeExtension,
1053
- withAssociation,
1054
878
  };
@@ -8,6 +8,9 @@
8
8
 
9
9
  'use strict';
10
10
 
11
+ const { dictAdd, pushToDict } = require('../base/dictionaries');
12
+ const { kindProperties } = require('./base');
13
+
11
14
  // for links, i.e., properties starting with an underscore '_':
12
15
 
13
16
  function pushLink( obj, prop, value ) {
@@ -38,10 +41,191 @@ function annotateWith( art, anno, location = art.location, val = true, literal =
38
41
  };
39
42
  }
40
43
 
44
+ // TODO: define setLink() like the current setProp(), we might have setArtifactLink()
45
+ // Do not share this function with CSN processors!
46
+
47
+ // The link (_artifact,_effectiveType,...) usually has the artifact as value.
48
+ // Falsy values are:
49
+ // - undefined: not computed yet, parse error, no ref
50
+ // - null: no valid reference, param:true if that is not allowed
51
+ // - false (only complete ref): multiple definitions, rejected
52
+ // - 0 (for _effectiveType only): circular reference
53
+ function setLink( obj, value = null, prop = '_artifact' ) {
54
+ Object.defineProperty( obj, prop, { value, configurable: true, writable: true } );
55
+ return value;
56
+ }
57
+
58
+ /**
59
+ * Like `obj.prop = value`, but not contained in JSON / CSN
60
+ * It's important to set enumerable explicitly to false (although 'false' is the default),
61
+ * as else, if the property already exists, it keeps the old setting for enumerable.
62
+ *
63
+ * @param {object} obj
64
+ * @param {string} prop
65
+ * @param {any} value
66
+ */
67
+ function setProp(obj, prop, value) {
68
+ const descriptor = {
69
+ value,
70
+ configurable: true,
71
+ writable: true,
72
+ enumerable: false,
73
+ };
74
+ Object.defineProperty( obj, prop, descriptor );
75
+ return value;
76
+ }
77
+
78
+ function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
79
+ const elem = {
80
+ name: { location: location || origin.name.location, id: name },
81
+ kind: origin.kind,
82
+ location: location || origin.location,
83
+ };
84
+ if (origin.name.$inferred)
85
+ elem.name.$inferred = origin.name.$inferred;
86
+ if (parent)
87
+ setMemberParent( elem, name, parent, prop ); // TODO: redef in template
88
+ setProp( elem, '_origin', origin );
89
+ // TODO: should we use silent dependencies also for other things, like
90
+ // included elements? (Currently for $inferred: 'expand-element' only)
91
+ if (silentDep)
92
+ dependsOnSilent( elem, origin );
93
+ else
94
+ dependsOn( elem, origin, location );
95
+ return elem;
96
+ }
97
+
98
+ function setMemberParent( elem, name, parent, prop ) {
99
+ if (prop) { // extension or structure include
100
+ // TODO: consider nested ARRAY OF and RETURNS, COMPOSITION OF type
101
+ const p = parent.items || parent.targetAspect || parent;
102
+ if (!(prop in p))
103
+ p[prop] = Object.create(null);
104
+ dictAdd( p[prop], name, elem );
105
+ }
106
+ if (parent._outer)
107
+ parent = parent._outer;
108
+ setProp( elem, '_parent', parent );
109
+ setProp( elem, '_main', parent._main || parent );
110
+ elem.name.absolute = elem._main.name.absolute;
111
+ if (name == null)
112
+ return;
113
+ const normalized = kindProperties[elem.kind].normalized || elem.kind;
114
+ [ 'element', 'alias', 'select', 'param', 'action' ].forEach( ( kind ) => {
115
+ if (normalized === kind)
116
+ elem.name[kind] = (parent.name[kind] != null && kind !== 'select' && kind !== 'alias') ? `${ parent.name[kind] }.${ name }` : name;
117
+
118
+ else if (parent.name[kind] != null)
119
+ elem.name[kind] = parent.name[kind];
120
+
121
+ else
122
+ delete elem.name[kind];
123
+ });
124
+ // try { throw new Error('Foo') } catch (e) { elem.name.stack = e; };
125
+ }
126
+
127
+ /**
128
+ * Adds a dependency user -> art with the given location.
129
+ *
130
+ * @param {XSN.Artifact} user
131
+ * @param {XSN.Artifact} art
132
+ * @param {XSN.Location} location
133
+ */
134
+ function dependsOn( user, art, location ) {
135
+ if (!user._deps)
136
+ setProp( user, '_deps', [] );
137
+ user._deps.push( { art, location } );
138
+ }
139
+
140
+ /**
141
+ * Same as "dependsOn" but the dependency from user -> art is silent,
142
+ * i.e. not reported to the user.
143
+ *
144
+ * @param {XSN.Artifact} user
145
+ * @param {XSN.Artifact} art
146
+ */
147
+ function dependsOnSilent( user, art ) {
148
+ if (!user._deps)
149
+ setProp( user, '_deps', [] );
150
+ user._deps.push( { art } );
151
+ }
152
+
153
+ function storeExtension( elem, name, prop, parent, block ) {
154
+ if (prop === 'enum')
155
+ prop = 'elements';
156
+ setProp( elem, '_block', block );
157
+ const kind = `_${ elem.kind }`; // _extend or _annotate
158
+ if (!parent[kind])
159
+ setProp( parent, kind, {} );
160
+ // if (name === '' && prop === 'params') {
161
+ // pushToDict( parent[kind], 'returns', elem ); // not really a dict
162
+ // return;
163
+ // }
164
+ if (!parent[kind][prop])
165
+ parent[kind][prop] = Object.create(null);
166
+ pushToDict( parent[kind][prop], name, elem );
167
+ }
168
+
169
+ /** @type {(a: any, b: any) => boolean} */
170
+ const testFunctionPlaceholder = () => true;
171
+
172
+ // Return path step if the path navigates along an association whose final type
173
+ // satisfies function `test`; "navigates along" = last path item not considered
174
+ // without truthy optional argument `alsoTestLast`.
175
+ function withAssociation( ref, test = testFunctionPlaceholder, alsoTestLast = false ) {
176
+ for (const item of ref.path || []) {
177
+ const art = item && item._artifact; // item can be null with parse error
178
+ if (art && art._effectiveType && art._effectiveType.target && test( art._effectiveType, item ))
179
+ return (alsoTestLast || item !== ref.path[ref.path.length - 1]) && item;
180
+ }
181
+ return false;
182
+ }
183
+
184
+ /**
185
+ * Return string 'A.B.C' for parsed source `A.B.C` (is vector of ids with
186
+ * locations).
187
+ *
188
+ * @param {XSN.Path} path
189
+ */
190
+ function pathName(path) {
191
+ return (path.broken) ? '' : path.map( id => id.id ).join('.');
192
+ }
193
+
194
+ /**
195
+ * Generates an XSN path out of the given name. Path segments are delimited by a dot.
196
+ * Each segment will have the given location assigned.
197
+ *
198
+ * @param {CSN.Location} location
199
+ * @param {string} name
200
+ * @returns {XSN.Path}
201
+ */
202
+ function splitIntoPath( location, name ) {
203
+ return name.split('.').map( id => ({ id, location }) );
204
+ }
205
+
206
+ /**
207
+ * @param {CSN.Location} location
208
+ * @param {...any} args
209
+ */
210
+ function augmentPath( location, ...args ) {
211
+ return { path: args.map( id => ({ id, location }) ), location };
212
+ }
213
+
41
214
 
42
215
  module.exports = {
43
216
  pushLink,
44
217
  annotationVal,
45
218
  annotationIsFalse,
46
219
  annotateWith,
220
+ setLink,
221
+ setProp,
222
+ linkToOrigin,
223
+ dependsOn,
224
+ dependsOnSilent,
225
+ setMemberParent,
226
+ storeExtension,
227
+ withAssociation,
228
+ pathName,
229
+ augmentPath,
230
+ splitIntoPath,
47
231
  };
@@ -1119,7 +1119,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
1119
1119
  let dictPropertyTypeName = null;
1120
1120
  if (dictProperties) {
1121
1121
  dictPropertyTypeName = dictProperties[i];
1122
- if (!dictPropertyTypeName){
1122
+ if (!dictPropertyTypeName && !getDictType(actualTypeName).OpenType){
1123
1123
  message(warning, context, `record type '${ actualTypeName }' doesn't have a property '${ i }'`);
1124
1124
  }
1125
1125
  }
@@ -142,7 +142,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
142
142
  definitions: Object.create(null)
143
143
  }
144
144
  };
145
-
145
+
146
146
  if(options.isV4()) {
147
147
  // tunnel schema xref and servicename in options to edm.Typebase to rectify
148
148
  // type references that are eventually also prefixed with the service schema name.
@@ -304,6 +304,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
304
304
  warning('odata-spec-violation-namespace',
305
305
  [ 'definitions', schema.name ], { names: reservedNames });
306
306
  }
307
+ /** @type {any} */
307
308
  const Schema = new Edm.Schema(v, schema.name, undefined /* unset alias */, schema._csn, /* annotations */ [], schema.container);
308
309
  const EntityContainer = Schema._ec || (LeadSchema && LeadSchema._ec);
309
310
  // now namespace and alias are used to create the fullQualified(name)
@@ -390,7 +391,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
390
391
  if(properties.length === 0) {
391
392
  warning(null, loc, { type }, 'EDM EntityType $(TYPE) has no properties');
392
393
  } else if(entityCsn.$edmKeyPaths.length === 0) {
393
- warning(null, loc, { type }, 'EDM EntityType $(TYPE) has no primary key');
394
+ message('odata-spec-violation-no-key', loc);
394
395
  }
395
396
  properties.forEach(p => {
396
397
  const pLoc = [...loc, 'elements', p.Name];