@sap/cds-compiler 2.7.0 → 2.11.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.
- package/CHANGELOG.md +167 -0
- package/bin/cdsc.js +42 -25
- package/bin/cdsse.js +1 -0
- package/doc/CHANGELOG_BETA.md +10 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +17 -33
- package/lib/api/options.js +25 -13
- package/lib/api/validate.js +33 -9
- package/lib/backends.js +9 -8
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +26 -2
- package/lib/base/messages.js +25 -9
- package/lib/base/model.js +5 -3
- package/lib/base/optionProcessorHelper.js +56 -22
- package/lib/checks/onConditions.js +5 -0
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +41 -0
- package/lib/checks/validator.js +7 -2
- package/lib/compiler/assert-consistency.js +18 -5
- package/lib/compiler/base.js +65 -0
- package/lib/compiler/builtins.js +30 -1
- package/lib/compiler/checks.js +5 -2
- package/lib/compiler/definer.js +145 -120
- package/lib/compiler/index.js +16 -4
- package/lib/compiler/propagator.js +5 -2
- package/lib/compiler/resolver.js +207 -47
- package/lib/compiler/shared.js +47 -200
- package/lib/compiler/utils.js +173 -0
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +94 -98
- package/lib/edm/edm.js +16 -20
- package/lib/edm/edmPreprocessor.js +302 -115
- package/lib/edm/edmUtils.js +31 -12
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +28 -1
- package/lib/gen/language.tokens +79 -69
- package/lib/gen/languageLexer.interp +28 -1
- package/lib/gen/languageLexer.js +879 -805
- package/lib/gen/languageLexer.tokens +71 -62
- package/lib/gen/languageParser.js +5308 -4308
- package/lib/json/from-csn.js +59 -30
- package/lib/json/to-csn.js +354 -105
- package/lib/language/antlrParser.js +11 -0
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +81 -14
- package/lib/language/language.g4 +163 -31
- package/lib/main.d.ts +136 -17
- package/lib/main.js +7 -1
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +115 -32
- package/lib/model/csnUtils.js +71 -33
- package/lib/model/enrichCsn.js +36 -9
- package/lib/model/revealInternalProperties.js +20 -4
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +33 -16
- package/lib/render/.eslintrc.json +3 -1
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/toCdl.js +60 -17
- package/lib/render/toHdbcds.js +122 -74
- package/lib/render/toSql.js +57 -32
- package/lib/render/utils/common.js +6 -10
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/db/constraints.js +273 -119
- package/lib/transform/db/draft.js +9 -6
- package/lib/transform/db/expansion.js +19 -7
- package/lib/transform/db/flattening.js +31 -7
- package/lib/transform/db/transformExists.js +344 -66
- package/lib/transform/db/views.js +438 -0
- package/lib/transform/forHanaNew.js +65 -436
- package/lib/transform/forOdataNew.js +21 -10
- package/lib/transform/localized.js +2 -0
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/generateForeignKeyElements.js +11 -10
- package/lib/transform/odata/referenceFlattener.js +44 -38
- package/lib/transform/odata/sortByAssociationDependency.js +2 -2
- package/lib/transform/odata/structuralPath.js +72 -0
- package/lib/transform/odata/structureFlattener.js +13 -10
- package/lib/transform/odata/typesExposure.js +22 -12
- package/lib/transform/transformUtilsNew.js +55 -9
- package/lib/transform/translateAssocsToJoins.js +11 -17
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +5 -3
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +48 -26
- package/package.json +1 -1
package/lib/compiler/shared.js
CHANGED
|
@@ -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 {
|
|
7
|
+
const { dictAddArray } = require('../base/dictionaries');
|
|
8
8
|
const { setProp } = require('../base/model');
|
|
9
9
|
|
|
10
|
-
const
|
|
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.
|
|
72
|
-
* "define" and "resolve"
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
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
|
-
|
|
27
|
+
// TODO: yes, this function will be renamed
|
|
28
|
+
function fns( model ) {
|
|
82
29
|
/** @type {CSN.Options} */
|
|
83
|
-
const options = model
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
662
|
-
|
|
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
|
-
|
|
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
|
};
|
package/lib/compiler/utils.js
CHANGED
|
@@ -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
|
};
|