@sap/cds-compiler 2.5.2 → 2.11.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 (102) hide show
  1. package/CHANGELOG.md +235 -9
  2. package/bin/cdsc.js +44 -27
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +37 -3
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +37 -123
  7. package/lib/api/options.js +27 -15
  8. package/lib/api/validate.js +34 -9
  9. package/lib/backends.js +9 -89
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +73 -11
  13. package/lib/base/messages.js +86 -30
  14. package/lib/base/model.js +6 -6
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/defaultValues.js +27 -2
  17. package/lib/checks/elements.js +1 -6
  18. package/lib/checks/foreignKeys.js +0 -6
  19. package/lib/checks/managedWithoutKeys.js +17 -0
  20. package/lib/checks/nonexpandableStructured.js +38 -0
  21. package/lib/checks/onConditions.js +9 -45
  22. package/lib/checks/queryNoDbArtifacts.js +25 -7
  23. package/lib/checks/selectItems.js +29 -2
  24. package/lib/checks/types.js +26 -2
  25. package/lib/checks/unknownMagic.js +41 -0
  26. package/lib/checks/utils.js +61 -0
  27. package/lib/checks/validator.js +60 -7
  28. package/lib/compiler/assert-consistency.js +23 -7
  29. package/lib/compiler/base.js +65 -0
  30. package/lib/compiler/builtins.js +30 -1
  31. package/lib/compiler/checks.js +8 -5
  32. package/lib/compiler/definer.js +157 -133
  33. package/lib/compiler/index.js +89 -31
  34. package/lib/compiler/propagator.js +5 -2
  35. package/lib/compiler/resolver.js +375 -185
  36. package/lib/compiler/shared.js +49 -202
  37. package/lib/compiler/utils.js +173 -0
  38. package/lib/edm/annotations/genericTranslation.js +183 -187
  39. package/lib/edm/csn2edm.js +104 -108
  40. package/lib/edm/edm.js +18 -21
  41. package/lib/edm/edmPreprocessor.js +388 -146
  42. package/lib/edm/edmUtils.js +104 -34
  43. package/lib/gen/Dictionary.json +22 -0
  44. package/lib/gen/language.checksum +1 -1
  45. package/lib/gen/language.interp +28 -1
  46. package/lib/gen/language.tokens +79 -69
  47. package/lib/gen/languageLexer.interp +28 -1
  48. package/lib/gen/languageLexer.js +879 -805
  49. package/lib/gen/languageLexer.tokens +71 -62
  50. package/lib/gen/languageParser.js +5330 -4300
  51. package/lib/json/from-csn.js +110 -52
  52. package/lib/json/to-csn.js +434 -120
  53. package/lib/language/antlrParser.js +15 -3
  54. package/lib/language/errorStrategy.js +1 -0
  55. package/lib/language/genericAntlrParser.js +93 -26
  56. package/lib/language/language.g4 +172 -31
  57. package/lib/main.d.ts +216 -19
  58. package/lib/main.js +32 -7
  59. package/lib/model/api.js +78 -0
  60. package/lib/model/csnRefs.js +413 -149
  61. package/lib/model/csnUtils.js +286 -75
  62. package/lib/model/enrichCsn.js +50 -6
  63. package/lib/model/revealInternalProperties.js +22 -5
  64. package/lib/modelCompare/compare.js +39 -21
  65. package/lib/optionProcessor.js +35 -18
  66. package/lib/render/.eslintrc.json +4 -1
  67. package/lib/render/DuplicateChecker.js +9 -6
  68. package/lib/render/toCdl.js +121 -36
  69. package/lib/render/toHdbcds.js +148 -98
  70. package/lib/render/toSql.js +114 -43
  71. package/lib/render/utils/common.js +8 -13
  72. package/lib/render/utils/sql.js +3 -3
  73. package/lib/sql-identifier.js +6 -1
  74. package/lib/transform/db/assertUnique.js +5 -6
  75. package/lib/transform/db/constraints.js +281 -106
  76. package/lib/transform/db/draft.js +11 -8
  77. package/lib/transform/db/expansion.js +584 -0
  78. package/lib/transform/db/flattening.js +341 -0
  79. package/lib/transform/db/groupByOrderBy.js +2 -2
  80. package/lib/transform/db/transformExists.js +345 -65
  81. package/lib/transform/db/views.js +438 -0
  82. package/lib/transform/forHanaNew.js +131 -793
  83. package/lib/transform/forOdataNew.js +30 -24
  84. package/lib/transform/localized.js +39 -10
  85. package/lib/transform/odata/attachPath.js +19 -4
  86. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  87. package/lib/transform/odata/referenceFlattener.js +60 -39
  88. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  89. package/lib/transform/odata/structuralPath.js +72 -0
  90. package/lib/transform/odata/structureFlattener.js +19 -18
  91. package/lib/transform/odata/typesExposure.js +22 -12
  92. package/lib/transform/transformUtilsNew.js +144 -78
  93. package/lib/transform/translateAssocsToJoins.js +22 -27
  94. package/lib/transform/universalCsnEnricher.js +67 -0
  95. package/lib/utils/file.js +5 -14
  96. package/lib/utils/moduleResolve.js +6 -8
  97. package/lib/utils/term.js +65 -42
  98. package/lib/utils/timetrace.js +48 -26
  99. package/package.json +1 -1
  100. package/lib/json/walker.js +0 -26
  101. package/lib/transform/sqlite +0 -0
  102. package/lib/utils/string.js +0 -17
@@ -1,89 +1,36 @@
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
- const { searchName, makeMessageFunction } = require('../base/messages');
7
- const { dictAdd, dictAddArray, pushToDict } = require('../base/dictionaries');
6
+ const { searchName } = require('../base/messages');
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)[prop];
64
- }
10
+ const { setLink, dependsOn } = 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
- } = makeMessageFunction( model, model.options, 'compile' );
33
+ } = model.$messageFunctions;
87
34
  // TODO: combine envFn and assoc ?
88
35
  const specExpected = {
89
36
  global: { // for using declaration
@@ -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 },
@@ -657,13 +608,28 @@ function fns( model, environment = artifactsEnv ) {
657
608
  return false;
658
609
  continue;
659
610
  }
611
+
612
+ const fn = (spec.envFn && artItemsCount >= 0) ? spec.envFn : VolatileFns.environment;
613
+ const env = fn( art, item.location, user, spec.assoc );
614
+
615
+ // do not check any elements of the path, e.g. $session - but still don't return path-head
660
616
  if (art && art.$uncheckedElements) {
661
- // do not check any elements of the path, e.g. $session
662
- return art;
617
+ if (env && env[item.id]) // something like $user.id/$user.locale
618
+ return env[item.id];
619
+
620
+ // $user.foo - build our own valid path step obj
621
+ // Important: Don't directly modify item!
622
+ const obj = {
623
+ location: item.location,
624
+ kind: 'builtin',
625
+ name: { id: item.id, element: path.map(p => p.id).join('.') },
626
+ };
627
+ setLink(obj, art, '_parent');
628
+ return obj;
663
629
  }
664
- const fn = (spec.envFn && artItemsCount >= 0) ? spec.envFn : environment;
665
- const env = fn( art, item.location, user, spec.assoc );
630
+
666
631
  const sub = setLink( item, env && env[item.id] );
632
+
667
633
  if (!sub)
668
634
  return (sub === 0) ? 0 : errorNotFound( item, env );
669
635
  else if (Array.isArray(sub)) // redefinitions
@@ -832,7 +798,9 @@ function fns( model, environment = artifactsEnv ) {
832
798
  }
833
799
  // TODO: block should be construct._block
834
800
  if (construct.$annotations && construct.$annotations.doc )
835
- art.doc = construct.$annotations.doc;
801
+ art.doc = construct.$annotations.doc; // e.g. through `annotate` statement in CDL
802
+ else if (construct.doc)
803
+ art.doc = construct.doc; // e.g. through `extensions` array in CSN
836
804
  if (!construct.$annotations) {
837
805
  if (!block || block.$frontend !== 'json')
838
806
  return; // namespace, or in CDL source without @annos:
@@ -908,127 +876,6 @@ function pathName(path) {
908
876
  return (path.broken) ? '' : path.map( id => id.id ).join('.');
909
877
  }
910
878
 
911
- // The link (_artifact,_effectiveType,...) usually has the artifact as value.
912
- // Falsy values are:
913
- // - undefined: not computed yet, parse error, no ref
914
- // - null: no valid reference, param:true if that is not allowed
915
- // - false (only complete ref): multiple definitions, rejected
916
- // - 0 (for _effectiveType only): circular reference
917
- function setLink( obj, value = null, prop = '_artifact' ) {
918
- Object.defineProperty( obj, prop, { value, configurable: true, writable: true } );
919
- return value;
920
- }
921
-
922
- function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
923
- const elem = {
924
- name: { location: location || origin.name.location, id: name },
925
- kind: origin.kind,
926
- location: location || origin.location,
927
- };
928
- if (origin.name.$inferred)
929
- elem.name.$inferred = origin.name.$inferred;
930
- if (parent)
931
- setMemberParent( elem, name, parent, prop ); // TODO: redef in template
932
- setProp( elem, '_origin', origin );
933
- // TODO: should we use silent dependencies also for other things, like
934
- // included elements? (Currently for $inferred: 'expand-element' only)
935
- if (silentDep)
936
- dependsOnSilent( elem, origin );
937
- else
938
- dependsOn( elem, origin, location );
939
- return elem;
940
- }
941
-
942
- function setMemberParent( elem, name, parent, prop ) {
943
- if (prop) { // extension or structure include
944
- // TODO: consider ARRAY OF and RETURNS, COMPOSITION OF type
945
- if (!(prop in parent))
946
- parent[prop] = Object.create(null);
947
- dictAdd( parent[prop], name, elem );
948
- }
949
- if (parent._outer)
950
- parent = parent._outer;
951
- setProp( elem, '_parent', parent );
952
- setProp( elem, '_main', parent._main || parent );
953
- elem.name.absolute = elem._main.name.absolute;
954
- if (name == null)
955
- return;
956
- const normalized = kindProperties[elem.kind].normalized || elem.kind;
957
- [ 'element', 'alias', 'select', 'param', 'action' ].forEach( ( kind ) => {
958
- if (normalized === kind)
959
- elem.name[kind] = (parent.name[kind] != null && kind !== 'select' && kind !== 'alias') ? `${ parent.name[kind] }.${ name }` : name;
960
-
961
- else if (parent.name[kind] != null)
962
- elem.name[kind] = parent.name[kind];
963
-
964
- else
965
- delete elem.name[kind];
966
- });
967
- // try { throw new Error('Foo') } catch (e) { elem.name.stack = e; };
968
- }
969
-
970
- /**
971
- * Adds a dependency user -> art with the given location.
972
- *
973
- * @param {XSN.Artifact} user
974
- * @param {XSN.Artifact} art
975
- * @param {XSN.Location} location
976
- */
977
- function dependsOn( user, art, location ) {
978
- if (!user._deps)
979
- setProp( user, '_deps', [] );
980
- user._deps.push( { art, location } );
981
- }
982
-
983
- /**
984
- * Same as "dependsOn" but the dependency from user -> art is silent,
985
- * i.e. not reported to the user.
986
- *
987
- * @param {XSN.Artifact} user
988
- * @param {XSN.Artifact} art
989
- */
990
- function dependsOnSilent( user, art ) {
991
- if (!user._deps)
992
- setProp( user, '_deps', [] );
993
- user._deps.push( { art } );
994
- }
995
-
996
- function storeExtension( elem, name, prop, parent, block ) {
997
- if (prop === 'enum')
998
- prop = 'elements';
999
- setProp( elem, '_block', block );
1000
- const kind = `_${ elem.kind }`; // _extend or _annotate
1001
- if (!parent[kind])
1002
- setProp( parent, kind, {} );
1003
- if (!parent[kind][prop])
1004
- parent[kind][prop] = Object.create(null);
1005
- pushToDict( parent[kind][prop], name, elem );
1006
- }
1007
-
1008
- /** @type {(a: any, b: any) => boolean} */
1009
- const testFunctionPlaceholder = () => true;
1010
-
1011
- // Return path step if the path navigates along an association whose final type
1012
- // satisfies function `test`; "navigates along" = last path item not considered
1013
- // without truthy optional argument `alsoTestLast`.
1014
- function withAssociation( ref, test = testFunctionPlaceholder, alsoTestLast = false ) {
1015
- for (const item of ref.path || []) {
1016
- const art = item && item._artifact; // item can be null with parse error
1017
- if (art && art._effectiveType && art._effectiveType.target && test( art._effectiveType, item ))
1018
- return (alsoTestLast || item !== ref.path[ref.path.length - 1]) && item;
1019
- }
1020
- return false;
1021
- }
1022
-
1023
879
  module.exports = {
1024
- dictKinds,
1025
- kindProperties,
1026
880
  fns,
1027
- setLink,
1028
- linkToOrigin,
1029
- dependsOn,
1030
- dependsOnSilent,
1031
- setMemberParent,
1032
- storeExtension,
1033
- withAssociation,
1034
881
  };
@@ -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,180 @@ 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
+ * Generates an XSN path out of the given name. Path segments are delimited by a dot.
186
+ * Each segment will have the given location assigned.
187
+ *
188
+ * @param {CSN.Location} location
189
+ * @param {string} name
190
+ * @returns {XSN.Path}
191
+ */
192
+ function splitIntoPath( location, name ) {
193
+ return name.split('.').map( id => ({ id, location }) );
194
+ }
195
+
196
+ /**
197
+ * @param {CSN.Location} location
198
+ * @param {...any} args
199
+ */
200
+ function augmentPath( location, ...args ) {
201
+ return { path: args.map( id => ({ id, location }) ), location };
202
+ }
203
+
41
204
 
42
205
  module.exports = {
43
206
  pushLink,
44
207
  annotationVal,
45
208
  annotationIsFalse,
46
209
  annotateWith,
210
+ setLink,
211
+ setProp,
212
+ linkToOrigin,
213
+ dependsOn,
214
+ dependsOnSilent,
215
+ setMemberParent,
216
+ storeExtension,
217
+ withAssociation,
218
+ augmentPath,
219
+ splitIntoPath,
47
220
  };