@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.
- package/CHANGELOG.md +90 -5
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +3 -1
- package/bin/cdsc.js +49 -25
- package/bin/cdsse.js +1 -0
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_BETA.md +10 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +8 -36
- package/lib/api/options.js +15 -6
- package/lib/api/validate.js +30 -3
- package/lib/backends.js +12 -13
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +3 -2
- package/lib/base/message-registry.js +34 -10
- package/lib/base/messages.js +38 -18
- package/lib/base/model.js +5 -4
- package/lib/base/optionProcessorHelper.js +57 -23
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/unknownMagic.js +6 -3
- package/lib/compiler/assert-consistency.js +9 -2
- package/lib/compiler/base.js +65 -0
- package/lib/compiler/builtins.js +62 -16
- package/lib/compiler/checks.js +2 -1
- package/lib/compiler/definer.js +66 -108
- package/lib/compiler/index.js +29 -29
- package/lib/compiler/propagator.js +5 -2
- package/lib/compiler/resolver.js +225 -58
- package/lib/compiler/shared.js +53 -229
- package/lib/compiler/utils.js +184 -0
- package/lib/edm/annotations/genericTranslation.js +1 -1
- package/lib/edm/csn2edm.js +3 -2
- package/lib/edm/edmPreprocessor.js +34 -38
- package/lib/edm/edmUtils.js +3 -3
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +17 -1
- package/lib/gen/language.tokens +79 -73
- package/lib/gen/languageLexer.interp +19 -1
- package/lib/gen/languageLexer.js +779 -731
- package/lib/gen/languageLexer.tokens +71 -65
- package/lib/gen/languageParser.js +4668 -4072
- package/lib/json/from-csn.js +10 -10
- package/lib/json/to-csn.js +228 -47
- package/lib/language/antlrParser.js +11 -0
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +73 -14
- package/lib/language/language.g4 +79 -3
- package/lib/main.d.ts +215 -18
- package/lib/main.js +3 -1
- package/lib/model/api.js +2 -2
- package/lib/model/csnRefs.js +117 -33
- package/lib/model/csnUtils.js +65 -133
- package/lib/model/enrichCsn.js +62 -37
- package/lib/model/revealInternalProperties.js +25 -8
- package/lib/model/sortViews.js +8 -1
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +33 -18
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/toCdl.js +15 -8
- package/lib/render/toHdbcds.js +26 -49
- package/lib/render/toSql.js +61 -39
- package/lib/render/utils/common.js +1 -1
- package/lib/transform/db/applyTransformations.js +189 -0
- package/lib/transform/db/constraints.js +273 -119
- package/lib/transform/db/draft.js +3 -2
- package/lib/transform/db/expansion.js +6 -4
- package/lib/transform/db/flattening.js +19 -3
- package/lib/transform/db/transformExists.js +102 -9
- package/lib/transform/db/views.js +485 -0
- package/lib/transform/forHanaNew.js +93 -448
- package/lib/transform/forOdataNew.js +9 -2
- package/lib/transform/localized.js +2 -0
- package/lib/transform/odata/structuralPath.js +1 -5
- package/lib/transform/transformUtilsNew.js +22 -8
- package/lib/transform/translateAssocsToJoins.js +7 -15
- package/lib/utils/file.js +11 -5
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +48 -26
- package/package.json +1 -1
- package/lib/transform/db/helpers.js +0 -58
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.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.
|
|
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 },
|
|
@@ -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
|
-
|
|
684
|
-
|
|
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
|
-
|
|
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
|
-
|
|
863
|
+
addAnnotation( art, annoProp, anno );
|
|
916
864
|
}
|
|
917
865
|
}
|
|
918
866
|
}
|
|
919
867
|
|
|
920
|
-
//
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
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
|
};
|
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,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
|
}
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -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
|
-
|
|
394
|
+
message('odata-spec-violation-no-key', loc);
|
|
394
395
|
}
|
|
395
396
|
properties.forEach(p => {
|
|
396
397
|
const pLoc = [...loc, 'elements', p.Name];
|