@sap/cds-compiler 2.10.4 → 2.12.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 (103) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +58 -35
  5. package/bin/cdsse.js +1 -0
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +16 -0
  9. package/lib/api/.eslintrc.json +2 -0
  10. package/lib/api/main.js +10 -36
  11. package/lib/api/options.js +17 -8
  12. package/lib/api/validate.js +30 -3
  13. package/lib/backends.js +12 -13
  14. package/lib/base/dictionaries.js +2 -1
  15. package/lib/base/keywords.js +3 -2
  16. package/lib/base/message-registry.js +64 -11
  17. package/lib/base/messages.js +38 -18
  18. package/lib/base/model.js +6 -4
  19. package/lib/base/optionProcessorHelper.js +148 -86
  20. package/lib/checks/.eslintrc.json +2 -0
  21. package/lib/checks/actionsFunctions.js +2 -1
  22. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  23. package/lib/checks/foreignKeys.js +4 -4
  24. package/lib/checks/managedInType.js +4 -4
  25. package/lib/checks/queryNoDbArtifacts.js +1 -3
  26. package/lib/checks/selectItems.js +4 -0
  27. package/lib/checks/sql-snippets.js +93 -0
  28. package/lib/checks/unknownMagic.js +6 -3
  29. package/lib/checks/validator.js +8 -0
  30. package/lib/compiler/assert-consistency.js +14 -5
  31. package/lib/compiler/base.js +64 -0
  32. package/lib/compiler/builtins.js +62 -16
  33. package/lib/compiler/checks.js +34 -10
  34. package/lib/compiler/definer.js +91 -112
  35. package/lib/compiler/index.js +30 -30
  36. package/lib/compiler/propagator.js +8 -4
  37. package/lib/compiler/resolver.js +279 -63
  38. package/lib/compiler/shared.js +65 -230
  39. package/lib/compiler/utils.js +191 -0
  40. package/lib/edm/annotations/genericTranslation.js +35 -18
  41. package/lib/edm/annotations/preprocessAnnotations.js +1 -1
  42. package/lib/edm/csn2edm.js +4 -3
  43. package/lib/edm/edm.js +8 -8
  44. package/lib/edm/edmPreprocessor.js +61 -59
  45. package/lib/edm/edmUtils.js +14 -15
  46. package/lib/gen/Dictionary.json +82 -40
  47. package/lib/gen/language.checksum +1 -1
  48. package/lib/gen/language.interp +19 -1
  49. package/lib/gen/language.tokens +80 -73
  50. package/lib/gen/languageLexer.interp +27 -1
  51. package/lib/gen/languageLexer.js +925 -826
  52. package/lib/gen/languageLexer.tokens +72 -65
  53. package/lib/gen/languageParser.js +4817 -4102
  54. package/lib/json/from-csn.js +57 -26
  55. package/lib/json/to-csn.js +244 -51
  56. package/lib/language/antlrParser.js +12 -1
  57. package/lib/language/docCommentParser.js +1 -1
  58. package/lib/language/errorStrategy.js +26 -8
  59. package/lib/language/genericAntlrParser.js +106 -30
  60. package/lib/language/language.g4 +200 -70
  61. package/lib/language/multiLineStringParser.js +536 -0
  62. package/lib/main.d.ts +220 -21
  63. package/lib/main.js +6 -3
  64. package/lib/model/api.js +2 -2
  65. package/lib/model/csnRefs.js +218 -86
  66. package/lib/model/csnUtils.js +99 -178
  67. package/lib/model/enrichCsn.js +84 -43
  68. package/lib/model/revealInternalProperties.js +25 -8
  69. package/lib/model/sortViews.js +8 -1
  70. package/lib/modelCompare/compare.js +2 -1
  71. package/lib/optionProcessor.js +33 -18
  72. package/lib/render/.eslintrc.json +1 -2
  73. package/lib/render/DuplicateChecker.js +2 -2
  74. package/lib/render/manageConstraints.js +1 -1
  75. package/lib/render/toCdl.js +202 -82
  76. package/lib/render/toHdbcds.js +194 -135
  77. package/lib/render/toRename.js +7 -10
  78. package/lib/render/toSql.js +91 -51
  79. package/lib/render/utils/common.js +24 -5
  80. package/lib/render/utils/sql.js +6 -4
  81. package/lib/transform/braceExpression.js +4 -2
  82. package/lib/transform/db/applyTransformations.js +189 -0
  83. package/lib/transform/db/associations.js +389 -0
  84. package/lib/transform/db/cdsPersistence.js +150 -0
  85. package/lib/transform/db/constraints.js +275 -119
  86. package/lib/transform/db/draft.js +6 -4
  87. package/lib/transform/db/expansion.js +10 -9
  88. package/lib/transform/db/flattening.js +23 -8
  89. package/lib/transform/db/temporal.js +236 -0
  90. package/lib/transform/db/transformExists.js +106 -25
  91. package/lib/transform/db/views.js +485 -0
  92. package/lib/transform/forHanaNew.js +90 -1036
  93. package/lib/transform/forOdataNew.js +11 -3
  94. package/lib/transform/localized.js +5 -14
  95. package/lib/transform/odata/generateForeignKeyElements.js +2 -2
  96. package/lib/transform/transformUtilsNew.js +34 -20
  97. package/lib/transform/translateAssocsToJoins.js +15 -23
  98. package/lib/transform/universalCsnEnricher.js +217 -47
  99. package/lib/utils/file.js +13 -6
  100. package/lib/utils/term.js +65 -42
  101. package/lib/utils/timetrace.js +55 -27
  102. package/package.json +1 -1
  103. package/lib/transform/db/helpers.js +0 -58
@@ -1,86 +1,32 @@
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 ) {
82
- /** @type {CSN.Options} */
83
- const options = model.options || {};
27
+ // TODO: yes, this function will be renamed
28
+ function fns( model ) {
29
+ const { options } = model;
84
30
  const {
85
31
  info, warning, error, message,
86
32
  } = model.$messageFunctions;
@@ -204,13 +150,15 @@ function fns( model, environment = artifactsEnv ) {
204
150
  },
205
151
  };
206
152
 
207
- return {
153
+ const VolatileFns = model.$volatileFunctions;
154
+ Object.assign( model.$functions, {
208
155
  resolveUncheckedPath,
209
156
  resolvePath,
210
157
  resolveTypeArguments,
211
158
  defineAnnotations,
212
159
  attachAndEmitValidNames,
213
- };
160
+ } );
161
+ return;
214
162
 
215
163
  function checkConstRef( art ) {
216
164
  return ![ 'builtin', 'param' ].includes( art.kind );
@@ -224,16 +172,25 @@ function fns( model, environment = artifactsEnv ) {
224
172
  return !(art.elements && !art.query && !art.type && !art.params);
225
173
  }
226
174
 
175
+ /**
176
+ * @returns {boolean|string}
177
+ */
227
178
  function checkTypeRef( art ) {
228
179
  if (art.kind === 'type' || art.kind === 'element')
229
180
  return false;
230
181
  return ![ 'entity', 'aspect', 'event' ].includes( art.kind ) || 'sloppy';
231
182
  }
232
183
 
184
+ /**
185
+ * @returns {boolean|string}
186
+ */
233
187
  function checkActionParamTypeRef( art ) {
234
188
  return !(art.kind === 'entity' && art._service) && checkTypeRef( art );
235
189
  }
236
190
 
191
+ /**
192
+ * @returns {boolean|string}
193
+ */
237
194
  function checkEventTypeRef( art ) {
238
195
  return art.kind !== 'event' && checkActionParamTypeRef( art );
239
196
  }
@@ -242,6 +199,9 @@ function fns( model, environment = artifactsEnv ) {
242
199
  return art.kind !== 'entity';
243
200
  }
244
201
 
202
+ /**
203
+ * @returns {boolean|string}
204
+ */
245
205
  function checkTargetRef( art ) {
246
206
  if (art.kind === 'entity' || art.kind === 'aspect')
247
207
  return false;
@@ -257,7 +217,7 @@ function fns( model, environment = artifactsEnv ) {
257
217
  // TODO: better error location if error for main
258
218
  if (elem._main.kind !== 'entity' )
259
219
  return true; // elem not starting at entity
260
- environment( art ); // sets _effectiveType on art
220
+ VolatileFns.environment( art ); // sets _effectiveType on art
261
221
  return !(art._effectiveType || art).target;
262
222
  }
263
223
 
@@ -345,7 +305,7 @@ function fns( model, environment = artifactsEnv ) {
345
305
  // first step: only use _combined of real query - TODO:
346
306
  // reject if not visible, but not allow more (!)
347
307
  (query._combined || query._parent._combined) ||
348
- environment( user._main ? user._parent : user );
308
+ VolatileFns.environment( user._main ? user._parent : user );
349
309
  }
350
310
  }
351
311
 
@@ -439,7 +399,7 @@ function fns( model, environment = artifactsEnv ) {
439
399
  }
440
400
  else {
441
401
  dependsOn( user, art._main, location );
442
- environment( art, location, user );
402
+ VolatileFns.environment( art, location, user );
443
403
  // Without on-demand resolve, we can simply signal 'undefined "x"'
444
404
  // instead of 'illegal cycle' in the following case:
445
405
  // element elem: type of elem.x;
@@ -513,7 +473,7 @@ function fns( model, environment = artifactsEnv ) {
513
473
  function getPathRoot( path, spec, user, env, extDict, msgArt ) {
514
474
  if (!spec.envFn && user._pathHead) {
515
475
  // TODO: not necessarily for explicit ON condition in expand
516
- environment( user._pathHead ); // make sure _origin is set
476
+ VolatileFns.environment( user._pathHead ); // make sure _origin is set
517
477
  return user._pathHead._origin;
518
478
  }
519
479
  const head = path[0];
@@ -570,8 +530,10 @@ function fns( model, environment = artifactsEnv ) {
570
530
  if (extDict && (!spec.dollar || head.id[0] !== '$')) {
571
531
  const r = extDict[head.id];
572
532
  if (Array.isArray(r)) {
573
- if (r[0].kind === '$navElement') {
574
- const names = r.filter( e => !e.$duplicates)
533
+ if (r[0].kind === '$navElement' && r.every( e => !e._parent.$duplicates )) {
534
+ // only complain about ambiguous source elements if we do not have
535
+ // duplicate table aliases, only mention non-ambiguous source elems
536
+ const names = r.filter( e => !e.$duplicates )
575
537
  .map( e => `${ e.name.alias }.${ e.name.element }` );
576
538
  if (names.length) {
577
539
  error( 'ref-ambiguous', [ head.location, user ], { id: head.id, names },
@@ -658,31 +620,26 @@ function fns( model, environment = artifactsEnv ) {
658
620
  continue;
659
621
  }
660
622
 
661
- const fn = (spec.envFn && artItemsCount >= 0) ? spec.envFn : environment;
623
+ const fn = (spec.envFn && artItemsCount >= 0) ? spec.envFn : VolatileFns.environment;
662
624
  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
625
  const sub = setLink( item, env && env[item.id] );
681
626
 
682
- if (!sub)
683
- return (sub === 0) ? 0 : errorNotFound( item, env );
684
- else if (Array.isArray(sub)) // redefinitions
627
+ if (!sub) {
628
+ // element was not found in environment
629
+ if (sub === 0)
630
+ return 0;
631
+ if (art.$uncheckedElements) { // magic variable / replacement variable
632
+ signalNotFound( 'ref-unknown-var', [ item.location, user ], [ env ],
633
+ { id: path.map(n => n.id).join('.') } );
634
+ }
635
+ else {
636
+ errorNotFound( item, env );
637
+ }
638
+ return null;
639
+ }
640
+ else if (Array.isArray(sub)) { // redefinitions
685
641
  return false;
642
+ }
686
643
 
687
644
  if (nav) { // we have already "pseudo-followed" a managed association
688
645
  // We currently rely on the check that targetElement references do
@@ -764,7 +721,7 @@ function fns( model, environment = artifactsEnv ) {
764
721
  { param: 'Entity $(ART) has no parameter $(MEMBER)' } );
765
722
  }
766
723
  else {
767
- signalNotFound( 'ref-undefined-element', [ item.location, user ],
724
+ signalNotFound( spec.undefinedDef || 'ref-undefined-element', [ item.location, user ],
768
725
  [ env ], { art: searchName( art, item.id, 'element' ) } );
769
726
  }
770
727
  return null;
@@ -847,7 +804,9 @@ function fns( model, environment = artifactsEnv ) {
847
804
  }
848
805
  // TODO: block should be construct._block
849
806
  if (construct.$annotations && construct.$annotations.doc )
850
- art.doc = construct.$annotations.doc;
807
+ art.doc = construct.$annotations.doc; // e.g. through `annotate` statement in CDL
808
+ else if (construct.doc)
809
+ art.doc = construct.doc; // e.g. through `extensions` array in CSN
851
810
  if (!construct.$annotations) {
852
811
  if (!block || block.$frontend !== 'json')
853
812
  return; // namespace, or in CDL source without @annos:
@@ -861,7 +820,7 @@ function fns( model, environment = artifactsEnv ) {
861
820
  setProp( a, '_block', block );
862
821
  a.$priority = priority;
863
822
  if (construct !== art)
864
- dictAddArray( art, annoProp, a );
823
+ addAnnotation( art, annoProp, a );
865
824
  }
866
825
  }
867
826
  }
@@ -912,143 +871,19 @@ function fns( model, environment = artifactsEnv ) {
912
871
  // TODO: _parent, _main is set later (if we have ElementRef), or do we
913
872
  // set _artifact?
914
873
  anno.$priority = priority;
915
- dictAddArray( art, annoProp, anno );
874
+ addAnnotation( art, annoProp, anno );
916
875
  }
917
876
  }
918
877
  }
919
878
 
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;
879
+ // Add annotation to definition - overwriting $inferred annos
880
+ function addAnnotation( art, annoProp, anno ) {
881
+ const old = art[annoProp];
882
+ if (old && old.$inferred)
883
+ delete art[annoProp];
884
+ dictAddArray( art, annoProp, anno );
1041
885
  }
1042
886
 
1043
887
  module.exports = {
1044
- dictKinds,
1045
- kindProperties,
1046
888
  fns,
1047
- setLink,
1048
- linkToOrigin,
1049
- dependsOn,
1050
- dependsOnSilent,
1051
- setMemberParent,
1052
- storeExtension,
1053
- withAssociation,
1054
889
  };
@@ -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 ) {
@@ -27,6 +30,13 @@ function annotationIsFalse( anno ) { // falsy, but not null (u
27
30
  return anno && (anno.val === false || anno.val === 0 || anno.val === '');
28
31
  }
29
32
 
33
+ /**
34
+ * @param {XSN.Artifact} art
35
+ * @param {string} anno
36
+ * @param {XSN.Location} [location]
37
+ * @param {*} [val]
38
+ * @param {string} [literal]
39
+ */
30
40
  function annotateWith( art, anno, location = art.location, val = true, literal = 'boolean' ) {
31
41
  if (art[anno]) // do not overwrite user-defined including null
32
42
  return;
@@ -38,10 +48,191 @@ function annotateWith( art, anno, location = art.location, val = true, literal =
38
48
  };
39
49
  }
40
50
 
51
+ // TODO: define setLink() like the current setProp(), we might have setArtifactLink()
52
+ // Do not share this function with CSN processors!
53
+
54
+ // The link (_artifact,_effectiveType,...) usually has the artifact as value.
55
+ // Falsy values are:
56
+ // - undefined: not computed yet, parse error, no ref
57
+ // - null: no valid reference, param:true if that is not allowed
58
+ // - false (only complete ref): multiple definitions, rejected
59
+ // - 0 (for _effectiveType only): circular reference
60
+ function setLink( obj, value = null, prop = '_artifact' ) {
61
+ Object.defineProperty( obj, prop, { value, configurable: true, writable: true } );
62
+ return value;
63
+ }
64
+
65
+ /**
66
+ * Like `obj.prop = value`, but not contained in JSON / CSN
67
+ * It's important to set enumerable explicitly to false (although 'false' is the default),
68
+ * as else, if the property already exists, it keeps the old setting for enumerable.
69
+ *
70
+ * @param {object} obj
71
+ * @param {string} prop
72
+ * @param {any} value
73
+ */
74
+ function setProp(obj, prop, value) {
75
+ const descriptor = {
76
+ value,
77
+ configurable: true,
78
+ writable: true,
79
+ enumerable: false,
80
+ };
81
+ Object.defineProperty( obj, prop, descriptor );
82
+ return value;
83
+ }
84
+
85
+ function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
86
+ const elem = {
87
+ name: { location: location || origin.name.location, id: name },
88
+ kind: origin.kind,
89
+ location: location || origin.location,
90
+ };
91
+ if (origin.name.$inferred)
92
+ elem.name.$inferred = origin.name.$inferred;
93
+ if (parent)
94
+ setMemberParent( elem, name, parent, prop ); // TODO: redef in template
95
+ setProp( elem, '_origin', origin );
96
+ // TODO: should we use silent dependencies also for other things, like
97
+ // included elements? (Currently for $inferred: 'expand-element' only)
98
+ if (silentDep)
99
+ dependsOnSilent( elem, origin );
100
+ else
101
+ dependsOn( elem, origin, location );
102
+ return elem;
103
+ }
104
+
105
+ function setMemberParent( elem, name, parent, prop ) {
106
+ if (prop) { // extension or structure include
107
+ // TODO: consider nested ARRAY OF and RETURNS, COMPOSITION OF type
108
+ const p = parent.items || parent.targetAspect || parent;
109
+ if (!(prop in p))
110
+ p[prop] = Object.create(null);
111
+ dictAdd( p[prop], name, elem );
112
+ }
113
+ if (parent._outer)
114
+ parent = parent._outer;
115
+ setProp( elem, '_parent', parent );
116
+ setProp( elem, '_main', parent._main || parent );
117
+ elem.name.absolute = elem._main.name.absolute;
118
+ if (name == null)
119
+ return;
120
+ const normalized = kindProperties[elem.kind].normalized || elem.kind;
121
+ [ 'element', 'alias', 'select', 'param', 'action' ].forEach( ( kind ) => {
122
+ if (normalized === kind)
123
+ elem.name[kind] = (parent.name[kind] != null && kind !== 'select' && kind !== 'alias') ? `${ parent.name[kind] }.${ name }` : name;
124
+
125
+ else if (parent.name[kind] != null)
126
+ elem.name[kind] = parent.name[kind];
127
+
128
+ else
129
+ delete elem.name[kind];
130
+ });
131
+ // try { throw new Error('Foo') } catch (e) { elem.name.stack = e; };
132
+ }
133
+
134
+ /**
135
+ * Adds a dependency user -> art with the given location.
136
+ *
137
+ * @param {XSN.Artifact} user
138
+ * @param {XSN.Artifact} art
139
+ * @param {XSN.Location} location
140
+ */
141
+ function dependsOn( user, art, location ) {
142
+ if (!user._deps)
143
+ setProp( user, '_deps', [] );
144
+ user._deps.push( { art, location } );
145
+ }
146
+
147
+ /**
148
+ * Same as "dependsOn" but the dependency from user -> art is silent,
149
+ * i.e. not reported to the user.
150
+ *
151
+ * @param {XSN.Artifact} user
152
+ * @param {XSN.Artifact} art
153
+ */
154
+ function dependsOnSilent( user, art ) {
155
+ if (!user._deps)
156
+ setProp( user, '_deps', [] );
157
+ user._deps.push( { art } );
158
+ }
159
+
160
+ function storeExtension( elem, name, prop, parent, block ) {
161
+ if (prop === 'enum')
162
+ prop = 'elements';
163
+ setProp( elem, '_block', block );
164
+ const kind = `_${ elem.kind }`; // _extend or _annotate
165
+ if (!parent[kind])
166
+ setProp( parent, kind, {} );
167
+ // if (name === '' && prop === 'params') {
168
+ // pushToDict( parent[kind], 'returns', elem ); // not really a dict
169
+ // return;
170
+ // }
171
+ if (!parent[kind][prop])
172
+ parent[kind][prop] = Object.create(null);
173
+ pushToDict( parent[kind][prop], name, elem );
174
+ }
175
+
176
+ /** @type {(a: any, b: any) => boolean} */
177
+ const testFunctionPlaceholder = () => true;
178
+
179
+ // Return path step if the path navigates along an association whose final type
180
+ // satisfies function `test`; "navigates along" = last path item not considered
181
+ // without truthy optional argument `alsoTestLast`.
182
+ function withAssociation( ref, test = testFunctionPlaceholder, alsoTestLast = false ) {
183
+ for (const item of ref.path || []) {
184
+ const art = item && item._artifact; // item can be null with parse error
185
+ if (art && art._effectiveType && art._effectiveType.target && test( art._effectiveType, item ))
186
+ return (alsoTestLast || item !== ref.path[ref.path.length - 1]) && item;
187
+ }
188
+ return false;
189
+ }
190
+
191
+ /**
192
+ * Return string 'A.B.C' for parsed source `A.B.C` (is vector of ids with
193
+ * locations).
194
+ *
195
+ * @param {XSN.Path} path
196
+ */
197
+ function pathName(path) {
198
+ return (path.broken) ? '' : path.map( id => id.id ).join('.');
199
+ }
200
+
201
+ /**
202
+ * Generates an XSN path out of the given name. Path segments are delimited by a dot.
203
+ * Each segment will have the given location assigned.
204
+ *
205
+ * @param {CSN.Location} location
206
+ * @param {string} name
207
+ * @returns {XSN.Path}
208
+ */
209
+ function splitIntoPath( location, name ) {
210
+ return name.split('.').map( id => ({ id, location }) );
211
+ }
212
+
213
+ /**
214
+ * @param {CSN.Location} location
215
+ * @param {...any} args
216
+ */
217
+ function augmentPath( location, ...args ) {
218
+ return { path: args.map( id => ({ id, location }) ), location };
219
+ }
220
+
41
221
 
42
222
  module.exports = {
43
223
  pushLink,
44
224
  annotationVal,
45
225
  annotationIsFalse,
46
226
  annotateWith,
227
+ setLink,
228
+ setProp,
229
+ linkToOrigin,
230
+ dependsOn,
231
+ dependsOnSilent,
232
+ setMemberParent,
233
+ storeExtension,
234
+ withAssociation,
235
+ pathName,
236
+ augmentPath,
237
+ splitIntoPath,
47
238
  };