@sap/cds-compiler 4.1.2 → 4.2.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 +101 -1
- package/bin/cdsc.js +6 -3
- package/doc/CHANGELOG_BETA.md +5 -0
- package/doc/CHANGELOG_DEPRECATED.md +15 -0
- package/lib/api/main.js +2 -2
- package/lib/api/options.js +2 -2
- package/lib/api/validate.js +24 -24
- package/lib/base/message-registry.js +41 -6
- package/lib/base/messages.js +7 -0
- package/lib/base/model.js +37 -8
- package/lib/checks/elements.js +11 -10
- package/lib/checks/manyNavigations.js +33 -0
- package/lib/checks/onConditions.js +5 -2
- package/lib/checks/queryNoDbArtifacts.js +2 -3
- package/lib/checks/selectItems.js +4 -55
- package/lib/checks/utils.js +3 -2
- package/lib/checks/validator.js +3 -1
- package/lib/compiler/.eslintrc.json +2 -1
- package/lib/compiler/assert-consistency.js +27 -24
- package/lib/compiler/base.js +6 -2
- package/lib/compiler/builtins.js +34 -34
- package/lib/compiler/checks.js +179 -208
- package/lib/compiler/classes.js +2 -2
- package/lib/compiler/cycle-detector.js +6 -6
- package/lib/compiler/define.js +66 -45
- package/lib/compiler/extend.js +81 -72
- package/lib/compiler/finalize-parse-cdl.js +26 -26
- package/lib/compiler/generate.js +61 -45
- package/lib/compiler/index.js +47 -49
- package/lib/compiler/kick-start.js +8 -7
- package/lib/compiler/moduleLayers.js +1 -1
- package/lib/compiler/populate.js +42 -35
- package/lib/compiler/propagator.js +6 -6
- package/lib/compiler/resolve.js +170 -126
- package/lib/compiler/shared.js +122 -45
- package/lib/compiler/tweak-assocs.js +93 -40
- package/lib/compiler/utils.js +15 -12
- package/lib/edm/.eslintrc.json +40 -1
- package/lib/edm/annotations/genericTranslation.js +721 -707
- package/lib/edm/annotations/preprocessAnnotations.js +88 -77
- package/lib/edm/csn2edm.js +389 -378
- package/lib/edm/edm.js +678 -772
- package/lib/edm/edmAnnoPreprocessor.js +132 -146
- package/lib/edm/edmInboundChecks.js +29 -27
- package/lib/edm/edmPreprocessor.js +686 -646
- package/lib/edm/edmUtils.js +277 -296
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1253 -1276
- package/lib/json/from-csn.js +34 -4
- package/lib/json/to-csn.js +4 -4
- package/lib/language/language.g4 +2 -5
- package/lib/main.d.ts +61 -1
- package/lib/model/csnUtils.js +31 -2
- package/lib/model/revealInternalProperties.js +1 -1
- package/lib/modelCompare/compare.js +37 -2
- package/lib/modelCompare/utils/filter.js +1 -1
- package/lib/optionProcessor.js +15 -3
- package/lib/render/toCdl.js +30 -4
- package/lib/render/toSql.js +5 -9
- package/lib/render/utils/common.js +8 -6
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/constraints.js +47 -17
- package/lib/transform/db/expansion.js +121 -47
- package/lib/transform/db/flattening.js +75 -7
- package/lib/transform/forOdata.js +4 -1
- package/lib/transform/forRelationalDB.js +80 -62
- package/lib/transform/localized.js +91 -54
- package/lib/transform/transformUtils.js +9 -10
- package/lib/utils/file.js +7 -7
- package/lib/utils/moduleResolve.js +210 -121
- package/lib/utils/objectUtils.js +1 -1
- package/package.json +5 -5
package/lib/compiler/generate.js
CHANGED
|
@@ -18,6 +18,7 @@ const {
|
|
|
18
18
|
isDirectComposition,
|
|
19
19
|
copyExpr,
|
|
20
20
|
} = require('./utils');
|
|
21
|
+
const { weakLocation } = require('../base/messages');
|
|
21
22
|
|
|
22
23
|
function generate( model ) {
|
|
23
24
|
const { options } = model;
|
|
@@ -33,7 +34,7 @@ function generate( model ) {
|
|
|
33
34
|
applyIncludes,
|
|
34
35
|
} = model.$functions;
|
|
35
36
|
|
|
36
|
-
const addTextsLanguageAssoc = checkTextsLanguageAssocOption(model, options);
|
|
37
|
+
const addTextsLanguageAssoc = checkTextsLanguageAssocOption( model, options );
|
|
37
38
|
const useTextsAspect = checkTextsAspect();
|
|
38
39
|
|
|
39
40
|
Object.keys( model.definitions ).forEach( processArtifact );
|
|
@@ -63,14 +64,14 @@ function generate( model ) {
|
|
|
63
64
|
*/
|
|
64
65
|
function compositionChildPersistence() {
|
|
65
66
|
const processed = new WeakSet();
|
|
66
|
-
forEachDefinition(model, processCompositionPersistence);
|
|
67
|
+
forEachDefinition( model, processCompositionPersistence );
|
|
67
68
|
|
|
68
69
|
function processCompositionPersistence( def ) {
|
|
69
|
-
if (def.$inferred === 'composition-entity' && !processed.has(def)) {
|
|
70
|
+
if (def.$inferred === 'composition-entity' && !processed.has( def )) {
|
|
70
71
|
if (def._parent)
|
|
71
|
-
processCompositionPersistence(def._parent);
|
|
72
|
+
processCompositionPersistence( def._parent );
|
|
72
73
|
copyPersistenceAnnotations( def, def._parent );
|
|
73
|
-
processed.add(def);
|
|
74
|
+
processed.add( def );
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
}
|
|
@@ -90,18 +91,18 @@ function generate( model ) {
|
|
|
90
91
|
const specialElements = { locale: { key: true } };
|
|
91
92
|
|
|
92
93
|
if (textsAspect.kind !== 'aspect' || !textsAspect.elements) {
|
|
93
|
-
error('def-invalid-texts-aspect', [ textsAspect.name.location, textsAspect ],
|
|
94
|
-
|
|
94
|
+
error( 'def-invalid-texts-aspect', [ textsAspect.name.location, textsAspect ],
|
|
95
|
+
{ '#': 'no-aspect', art: textsAspect } );
|
|
95
96
|
return false;
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
let hasError = false;
|
|
99
100
|
if (addTextsLanguageAssoc && textsAspect.elements.language) {
|
|
100
101
|
const lang = textsAspect.elements.language;
|
|
101
|
-
error('def-unexpected-element', [ lang.name.location, lang ],
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
error( 'def-unexpected-element', [ lang.name.location, lang ],
|
|
103
|
+
{ option: 'addTextsLanguageAssoc', art: textsAspect, name: 'language' },
|
|
104
|
+
// eslint-disable-next-line max-len
|
|
105
|
+
'$(ART) is not used because option $(OPTION) conflicts with existing element $(NAME); remove either option or element' );
|
|
105
106
|
hasError = true;
|
|
106
107
|
}
|
|
107
108
|
|
|
@@ -109,14 +110,14 @@ function generate( model ) {
|
|
|
109
110
|
const expected = specialElements[name];
|
|
110
111
|
const elem = textsAspect.elements[name];
|
|
111
112
|
if (!elem) {
|
|
112
|
-
error('def-invalid-texts-aspect', [ textsAspect.name.location, textsAspect ],
|
|
113
|
-
|
|
113
|
+
error( 'def-invalid-texts-aspect', [ textsAspect.name.location, textsAspect ],
|
|
114
|
+
{ '#': 'missing', art: textsAspect, name } );
|
|
114
115
|
hasError = true;
|
|
115
116
|
}
|
|
116
117
|
else if (expected.key !== undefined && !!elem.key?.val !== expected.key) {
|
|
117
118
|
const loc = elem.key?.location || elem.name?.location || textsAspect.name.location;
|
|
118
|
-
error('def-invalid-texts-aspect', [ loc, elem ],
|
|
119
|
-
|
|
119
|
+
error( 'def-invalid-texts-aspect', [ loc, elem ],
|
|
120
|
+
{ '#': expected.key ? 'key' : 'no-key', art: elem } );
|
|
120
121
|
hasError = true;
|
|
121
122
|
}
|
|
122
123
|
}
|
|
@@ -193,9 +194,9 @@ function generate( model ) {
|
|
|
193
194
|
// usual include-mechanism.
|
|
194
195
|
const protectedElements = [ 'locale', 'texts', 'localized' ];
|
|
195
196
|
if (fioriEnabled)
|
|
196
|
-
protectedElements.push('ID_texts');
|
|
197
|
+
protectedElements.push( 'ID_texts' );
|
|
197
198
|
if (addTextsLanguageAssoc)
|
|
198
|
-
protectedElements.push('language');
|
|
199
|
+
protectedElements.push( 'language' );
|
|
199
200
|
|
|
200
201
|
for (const name in art.elements) {
|
|
201
202
|
const elem = art.elements[name];
|
|
@@ -214,6 +215,13 @@ function generate( model ) {
|
|
|
214
215
|
else if (isLocalized) {
|
|
215
216
|
textElems.push( elem );
|
|
216
217
|
}
|
|
218
|
+
|
|
219
|
+
if (isKey && isLocalized) {
|
|
220
|
+
const errpos = elem.localized || elem.type || elem.name;
|
|
221
|
+
warning( 'def-ignoring-localized', [ errpos.location, elem ],
|
|
222
|
+
{ keyword: 'localized' },
|
|
223
|
+
'Keyword $(KEYWORD) is ignored for primary keys' );
|
|
224
|
+
}
|
|
217
225
|
}
|
|
218
226
|
if (textElems.length <= keys)
|
|
219
227
|
return false;
|
|
@@ -336,8 +344,8 @@ function generate( model ) {
|
|
|
336
344
|
// Because ID_texts is not copied from TextsAspect, the order is messed
|
|
337
345
|
// up. Fix it.
|
|
338
346
|
const { elements } = art;
|
|
339
|
-
art.elements = Object.create(null);
|
|
340
|
-
const names = [ 'ID_texts', 'locale', ...Object.keys(elements) ];
|
|
347
|
+
art.elements = Object.create( null );
|
|
348
|
+
const names = [ 'ID_texts', 'locale', ...Object.keys( elements ) ];
|
|
341
349
|
for (const name of names)
|
|
342
350
|
art.elements[name] = elements[name];
|
|
343
351
|
|
|
@@ -367,7 +375,7 @@ function generate( model ) {
|
|
|
367
375
|
function createTextsEntityWithInclude( base, absolute, fioriEnabled ) {
|
|
368
376
|
const textsAspectName = 'sap.common.TextsAspect';
|
|
369
377
|
const textsAspect = model.definitions['sap.common.TextsAspect'];
|
|
370
|
-
const elements = Object.create(null);
|
|
378
|
+
const elements = Object.create( null );
|
|
371
379
|
const { location } = base.name;
|
|
372
380
|
const art = {
|
|
373
381
|
kind: 'entity',
|
|
@@ -415,7 +423,7 @@ function generate( model ) {
|
|
|
415
423
|
* @param {boolean} fioriEnabled
|
|
416
424
|
*/
|
|
417
425
|
function createTextsEntityWithDefaultElements( base, absolute, fioriEnabled ) {
|
|
418
|
-
const elements = Object.create(null);
|
|
426
|
+
const elements = Object.create( null );
|
|
419
427
|
const { location } = base.name;
|
|
420
428
|
const art = {
|
|
421
429
|
kind: 'entity',
|
|
@@ -528,7 +536,7 @@ function generate( model ) {
|
|
|
528
536
|
* @returns {boolean}
|
|
529
537
|
*/
|
|
530
538
|
function hasTruthyProp( art, prop ) {
|
|
531
|
-
const processed = Object.create(null); // avoid infloops with circular refs
|
|
539
|
+
const processed = Object.create( null ); // avoid infloops with circular refs
|
|
532
540
|
let name = art.name.absolute; // is ok, since no recursive type possible
|
|
533
541
|
while (art && !processed[name]) {
|
|
534
542
|
if (art[prop])
|
|
@@ -568,7 +576,7 @@ function generate( model ) {
|
|
|
568
576
|
return;
|
|
569
577
|
|
|
570
578
|
function baseKeys() {
|
|
571
|
-
const k = Object.create(null);
|
|
579
|
+
const k = Object.create( null );
|
|
572
580
|
for (const name in base.elements) {
|
|
573
581
|
const elem = base.elements[name];
|
|
574
582
|
if (elem.$duplicates)
|
|
@@ -654,22 +662,22 @@ function generate( model ) {
|
|
|
654
662
|
return false;
|
|
655
663
|
}
|
|
656
664
|
const names = Object.keys( target.elements )
|
|
657
|
-
.filter( n => n.startsWith('up__') && keyNames.includes( n.substring(4) ) );
|
|
665
|
+
.filter( n => n.startsWith( 'up__' ) && keyNames.includes( n.substring(4) ) );
|
|
658
666
|
if (names.length) {
|
|
659
667
|
// FUTURE: if named type, add sub info with location of "up_" element
|
|
660
668
|
error( null, [ location, elem ], { target: entityName, names }, {
|
|
661
669
|
std: 'Key elements $(NAMES) can\'t be added to $(TARGET) as these already exist',
|
|
662
670
|
one: 'Key element $(NAMES) can\'t be added to $(TARGET) as it already exist',
|
|
663
|
-
});
|
|
671
|
+
} );
|
|
664
672
|
return false;
|
|
665
673
|
}
|
|
666
674
|
|
|
667
|
-
if (elem.type && !isDirectComposition(elem)) {
|
|
675
|
+
if (elem.type && !isDirectComposition( elem )) {
|
|
668
676
|
// Only issue warning for direct usages, not for projections, includes, etc.
|
|
669
677
|
// TODO: Make it configurable error; v4: error
|
|
670
678
|
warning( 'def-expected-comp-aspect', [ elem.type.location, elem ],
|
|
671
679
|
{ prop: 'Composition of', otherprop: 'Association to' },
|
|
672
|
-
'Expected $(PROP), but found $(OTHERPROP) for composition of aspect');
|
|
680
|
+
'Expected $(PROP), but found $(OTHERPROP) for composition of aspect' );
|
|
673
681
|
}
|
|
674
682
|
|
|
675
683
|
return true;
|
|
@@ -689,9 +697,14 @@ function generate( model ) {
|
|
|
689
697
|
|
|
690
698
|
const art = {
|
|
691
699
|
kind: 'entity',
|
|
692
|
-
name: {
|
|
700
|
+
name: {
|
|
701
|
+
path: splitIntoPath( location, entityName ),
|
|
702
|
+
absolute: entityName,
|
|
703
|
+
// for code navigation (e.g. via `extend`s): point to the element's name
|
|
704
|
+
location: elem.name.location,
|
|
705
|
+
},
|
|
693
706
|
location,
|
|
694
|
-
elements: Object.create(null),
|
|
707
|
+
elements: Object.create( null ),
|
|
695
708
|
$inferred: 'composition-entity',
|
|
696
709
|
};
|
|
697
710
|
if (target.name) { // named target aspect
|
|
@@ -704,17 +717,19 @@ function generate( model ) {
|
|
|
704
717
|
setLink( art, '_upperAspects', elem._main._upperAspects || [] );
|
|
705
718
|
}
|
|
706
719
|
|
|
720
|
+
// Since there is no user-written up_ element, use a weak location to the beginning of {…}.
|
|
721
|
+
const upLocation = weakLocation( location );
|
|
707
722
|
const up = { // elements.up_ = ...
|
|
708
|
-
name: { location, id: 'up_' },
|
|
723
|
+
name: { location: upLocation, id: 'up_' },
|
|
709
724
|
kind: 'element',
|
|
710
|
-
location,
|
|
725
|
+
location: upLocation,
|
|
711
726
|
$inferred: 'aspect-composition',
|
|
712
|
-
type: augmentPath(
|
|
713
|
-
target: augmentPath(
|
|
727
|
+
type: augmentPath( upLocation, 'cds.Association' ),
|
|
728
|
+
target: augmentPath( upLocation, base.name.absolute ),
|
|
714
729
|
cardinality: {
|
|
715
|
-
targetMin: { val: 1, literal: 'number', location },
|
|
716
|
-
targetMax: { val: 1, literal: 'number', location },
|
|
717
|
-
location,
|
|
730
|
+
targetMin: { val: 1, literal: 'number', location: upLocation },
|
|
731
|
+
targetMax: { val: 1, literal: 'number', location: upLocation },
|
|
732
|
+
location: upLocation,
|
|
718
733
|
},
|
|
719
734
|
};
|
|
720
735
|
// By default, 'up_' is a managed primary key association.
|
|
@@ -723,16 +738,17 @@ function generate( model ) {
|
|
|
723
738
|
if (isDeprecatedEnabled( options, '_unmanagedUpInComponent' )) {
|
|
724
739
|
addProxyElements( art, keys, 'aspect-composition', target.name && location,
|
|
725
740
|
'up__', '@odata.containment.ignore' );
|
|
726
|
-
up.on = augmentEqual(
|
|
741
|
+
up.on = augmentEqual( upLocation, 'up_', Object.values( keys ), 'up__' );
|
|
727
742
|
}
|
|
728
743
|
else {
|
|
729
|
-
up.key = { location, val: true };
|
|
744
|
+
up.key = { location: upLocation, val: true };
|
|
730
745
|
// managed associations must be explicitly set to not null
|
|
731
746
|
// even if target cardinality is 1..1
|
|
732
|
-
up.notNull = { location, val: true };
|
|
747
|
+
up.notNull = { location: upLocation, val: true };
|
|
733
748
|
}
|
|
734
749
|
|
|
735
|
-
dictAdd( art.elements, 'up_', up);
|
|
750
|
+
dictAdd( art.elements, 'up_', up );
|
|
751
|
+
// Only for named aspects, use a new location; otherwise use the origin's one.
|
|
736
752
|
addProxyElements( art, target.elements, 'aspect-composition', target.name && location );
|
|
737
753
|
|
|
738
754
|
setLink( art, '_block', model.$internal );
|
|
@@ -750,7 +766,7 @@ function generate( model ) {
|
|
|
750
766
|
for (const name in elements) {
|
|
751
767
|
const pname = `${ prefix }${ name }`;
|
|
752
768
|
const origin = elements[name];
|
|
753
|
-
const proxy = linkToOrigin( origin, pname, null, null, location
|
|
769
|
+
const proxy = linkToOrigin( origin, pname, null, null, location, true );
|
|
754
770
|
setLink( proxy, '_block', origin._block );
|
|
755
771
|
proxy.$inferred = inferred;
|
|
756
772
|
if (origin.masked)
|
|
@@ -801,7 +817,7 @@ function augmentEqual( location, assocname, relations, prefix = '' ) {
|
|
|
801
817
|
: { op: { val: 'and', location }, args, location };
|
|
802
818
|
|
|
803
819
|
function eq( refs ) {
|
|
804
|
-
if (Array.isArray(refs))
|
|
820
|
+
if (Array.isArray( refs ))
|
|
805
821
|
return { op: { val: '=', location }, args: refs.map( ref ), location };
|
|
806
822
|
|
|
807
823
|
const { id } = refs.name;
|
|
@@ -815,7 +831,7 @@ function augmentEqual( location, assocname, relations, prefix = '' ) {
|
|
|
815
831
|
};
|
|
816
832
|
}
|
|
817
833
|
function ref( path ) {
|
|
818
|
-
return { path: path.split('.').map( id => ({ id, location }) ), location };
|
|
834
|
+
return { path: path.split( '.' ).map( id => ({ id, location }) ), location };
|
|
819
835
|
}
|
|
820
836
|
}
|
|
821
837
|
|
|
@@ -826,12 +842,12 @@ function checkTextsLanguageAssocOption( model, options ) {
|
|
|
826
842
|
if (options.addTextsLanguageAssoc && !commonLanguagesEntity) {
|
|
827
843
|
const variant = !languages ? 'std' : 'code';
|
|
828
844
|
const loc = model.definitions['sap.common.Languages']?.name?.location || null;
|
|
829
|
-
model.$messageFunctions.info('api-ignoring-language-assoc', loc, {
|
|
845
|
+
model.$messageFunctions.info( 'api-ignoring-language-assoc', loc, {
|
|
830
846
|
'#': variant, option: 'addTextsLanguageAssoc', art: 'sap.common.Languages', name: 'code',
|
|
831
847
|
}, {
|
|
832
848
|
std: 'Ignoring option $(OPTION) because entity $(ART) is missing',
|
|
833
849
|
code: 'Ignoring option $(OPTION) because entity $(ART) is missing element $(NAME)',
|
|
834
|
-
});
|
|
850
|
+
} );
|
|
835
851
|
}
|
|
836
852
|
|
|
837
853
|
return !!commonLanguagesEntity;
|
package/lib/compiler/index.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
'use strict';
|
|
14
14
|
|
|
15
|
-
const {
|
|
15
|
+
const { makeModuleResolver, makeModuleResolverSync } = require('../utils/moduleResolve');
|
|
16
16
|
const parseLanguage = require('../language/antlrParser'); // TODO: should we do some lazyload here?
|
|
17
17
|
const parseCsn = require('../json/from-csn');
|
|
18
18
|
|
|
@@ -53,7 +53,7 @@ const extensionParsers = {
|
|
|
53
53
|
// `errors`: vector of errors (file IO or ArgumentError)
|
|
54
54
|
class InvocationError extends Error {
|
|
55
55
|
constructor( errs, ...args ) {
|
|
56
|
-
super(...args);
|
|
56
|
+
super( ...args );
|
|
57
57
|
this.code = 'ERR_CDS_COMPILER_INVOCATION';
|
|
58
58
|
this.errors = errs;
|
|
59
59
|
this.hasBeenReported = false;
|
|
@@ -64,7 +64,7 @@ class InvocationError extends Error {
|
|
|
64
64
|
// `argument`: the command argument (repeated file names)
|
|
65
65
|
class ArgumentError extends Error {
|
|
66
66
|
constructor( arg, ...args ) {
|
|
67
|
-
super(...args);
|
|
67
|
+
super( ...args );
|
|
68
68
|
this.code = 'ERR_CDS_COMPILER_ARGUMENT';
|
|
69
69
|
this.argument = arg;
|
|
70
70
|
}
|
|
@@ -86,15 +86,15 @@ function parseX( source, filename, options = {}, messageFunctions = null ) {
|
|
|
86
86
|
const ext = path.extname( filename ).slice(1).toLowerCase();
|
|
87
87
|
// eslint-disable-next-line no-nested-ternary
|
|
88
88
|
const parser = options.fallbackParser === 'auto!'
|
|
89
|
-
? (source?.startsWith('{') ? parseCsn.parse : parseLanguage)
|
|
89
|
+
? (source?.startsWith( '{' ) ? parseCsn.parse : parseLanguage)
|
|
90
90
|
: (extensionParsers[ext] || extensionParsers[options.fallbackParser] ||
|
|
91
|
-
source.startsWith('{') && parseCsn.parse);
|
|
91
|
+
source.startsWith( '{' ) && parseCsn.parse);
|
|
92
92
|
if (parser)
|
|
93
93
|
return parser( source, filename, options, messageFunctions );
|
|
94
94
|
|
|
95
95
|
const model = new XsnSource();
|
|
96
96
|
model.location = new CsnLocation( filename );
|
|
97
|
-
messageFunctions.error( 'file-unknown-ext', emptyWeakLocation(filename),
|
|
97
|
+
messageFunctions.error( 'file-unknown-ext', emptyWeakLocation( filename ),
|
|
98
98
|
{ file: ext, '#': !ext && 'none' }, {
|
|
99
99
|
std: 'Unknown file extension $(FILE)',
|
|
100
100
|
none: 'No file extension',
|
|
@@ -131,29 +131,30 @@ function parseX( source, filename, options = {}, messageFunctions = null ) {
|
|
|
131
131
|
// - 'string' or instanceof Buffer: the file content
|
|
132
132
|
// - { realname: fs.realpath(filename) }: if filename is not canonicalized
|
|
133
133
|
//
|
|
134
|
-
function compileX( filenames, dir = '', options = {}, fileCache = Object.create(null) ) {
|
|
134
|
+
function compileX( filenames, dir = '', options = {}, fileCache = Object.create( null ) ) {
|
|
135
135
|
// A non-proper dictionary (i.e. with prototype) is safe if the keys are
|
|
136
136
|
// absolute file names - they start with `/` or `\` or similar
|
|
137
137
|
// if (Object.getPrototypeOf( fileCache ))
|
|
138
138
|
// fileCache = Object.assign( Object.create(null), fileCache );
|
|
139
|
-
dir = path.resolve(dir);
|
|
139
|
+
dir = path.resolve( dir );
|
|
140
140
|
const model = { sources: null, options };
|
|
141
141
|
model.$messageFunctions = createMessageFunctions( options, 'compile', model );
|
|
142
|
+
const { resolveModule } = makeModuleResolver( options, fileCache, model.$messageFunctions );
|
|
142
143
|
let input = null;
|
|
143
144
|
|
|
144
145
|
let all = processFilenames( filenames, dir )
|
|
145
|
-
.then((processedInput) => {
|
|
146
|
+
.then( (processedInput) => {
|
|
146
147
|
input = processedInput;
|
|
147
148
|
model.sources = input.sources;
|
|
148
|
-
})
|
|
149
|
-
.then(() => promiseAllDoNotRejectImmediately( input.files.map(readAndParse) ))
|
|
149
|
+
} )
|
|
150
|
+
.then( () => promiseAllDoNotRejectImmediately( input.files.map( readAndParse ) ) )
|
|
150
151
|
.then( testInvocation, (reason) => {
|
|
151
152
|
// do not reject with PromiseAllError, use InvocationError:
|
|
152
153
|
const errs = reason.valuesOrErrors.filter( e => e instanceof Error );
|
|
153
154
|
// internal error if no file IO error (has property `path`)
|
|
154
155
|
return Promise.reject( errs.find( e => !e.path ) ||
|
|
155
156
|
new InvocationError( [ ...input.repeated, ...errs ]) );
|
|
156
|
-
});
|
|
157
|
+
} );
|
|
157
158
|
|
|
158
159
|
if (!options.parseOnly && !options.parseCdl)
|
|
159
160
|
all = all.then( readDependencies );
|
|
@@ -161,7 +162,7 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
|
|
|
161
162
|
return all.then( () => {
|
|
162
163
|
moduleLayers.setLayers( input.sources );
|
|
163
164
|
return compileDoX( model );
|
|
164
|
-
});
|
|
165
|
+
} );
|
|
165
166
|
|
|
166
167
|
// Read file `filename` and parse its content, return messages
|
|
167
168
|
async function readAndParse( filename ) {
|
|
@@ -210,10 +211,8 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
|
|
|
210
211
|
}
|
|
211
212
|
// create promises after all usingFroms have been collected, as the
|
|
212
213
|
// Promise executor is called immediately with `new`:
|
|
213
|
-
for (const module in dependencies)
|
|
214
|
-
promises.push( resolveModule( dependencies[module]
|
|
215
|
-
model.$messageFunctions ) );
|
|
216
|
-
}
|
|
214
|
+
for (const module in dependencies)
|
|
215
|
+
promises.push( resolveModule( dependencies[module] ) );
|
|
217
216
|
}
|
|
218
217
|
if (!promises.length)
|
|
219
218
|
return [];
|
|
@@ -236,23 +235,25 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
|
|
|
236
235
|
* @param {object} [fileCache]
|
|
237
236
|
* @returns {XSN.Model} Augmented CSN
|
|
238
237
|
*/
|
|
239
|
-
function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.create(null) ) {
|
|
238
|
+
function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.create( null ) ) {
|
|
240
239
|
// A non-proper dictionary (i.e. with prototype) is safe if the keys are
|
|
241
240
|
// absolute file names - they start with `/` or `\` or similar
|
|
242
|
-
dir = path.resolve(dir);
|
|
241
|
+
dir = path.resolve( dir );
|
|
243
242
|
const a = processFilenamesSync( filenames, dir );
|
|
244
243
|
|
|
245
244
|
const model = { sources: a.sources, options };
|
|
246
245
|
model.$messageFunctions = createMessageFunctions( options, 'compile', model );
|
|
246
|
+
const { resolveModuleSync } = makeModuleResolverSync( options, fileCache,
|
|
247
|
+
model.$messageFunctions );
|
|
247
248
|
|
|
248
249
|
const asts = [];
|
|
249
250
|
const errors = [];
|
|
250
251
|
a.files.forEach( val => readAndParseSync( val, (err, ast) => {
|
|
251
252
|
if (err)
|
|
252
|
-
errors.push(err);
|
|
253
|
+
errors.push( err );
|
|
253
254
|
if (ast)
|
|
254
|
-
asts.push(ast);
|
|
255
|
-
}));
|
|
255
|
+
asts.push( ast );
|
|
256
|
+
} ) );
|
|
256
257
|
|
|
257
258
|
if (errors.length || a.repeated.length) {
|
|
258
259
|
// internal error if no file IO error (has property `path`)
|
|
@@ -266,12 +267,12 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
|
|
|
266
267
|
asts.length = 0;
|
|
267
268
|
// Push dependencies to `ast`. Only works because readAndParseSync() is synchronous.
|
|
268
269
|
for (const fileName of fileNames) {
|
|
269
|
-
readAndParseSync(fileName, ( err, ast ) => {
|
|
270
|
+
readAndParseSync( fileName, ( err, ast ) => {
|
|
270
271
|
if (err)
|
|
271
272
|
throw err;
|
|
272
273
|
if (ast)
|
|
273
274
|
asts.push( ast );
|
|
274
|
-
});
|
|
275
|
+
} );
|
|
275
276
|
}
|
|
276
277
|
}
|
|
277
278
|
}
|
|
@@ -282,12 +283,12 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
|
|
|
282
283
|
// Read file `filename` and parse its content, return messages
|
|
283
284
|
function readAndParseSync( filename, cb ) {
|
|
284
285
|
if ( filename === false ) { // module which has not been found
|
|
285
|
-
cb(null, null);
|
|
286
|
+
cb( null, null );
|
|
286
287
|
return;
|
|
287
288
|
}
|
|
288
289
|
const rel = a.sources[filename] || path.relative( dir, filename );
|
|
289
290
|
if (typeof rel === 'object') { // already parsed
|
|
290
|
-
cb(null, null);
|
|
291
|
+
cb( null, null );
|
|
291
292
|
return; // no further dependency processing
|
|
292
293
|
}
|
|
293
294
|
// no parallel readAndParse with same resolved filename should read the file,
|
|
@@ -296,7 +297,7 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
|
|
|
296
297
|
|
|
297
298
|
cdsFs( fileCache, options.traceFs ).readFileSync( filename, 'utf8', (err, source) => {
|
|
298
299
|
if (err) {
|
|
299
|
-
cb(err, null);
|
|
300
|
+
cb( err, null );
|
|
300
301
|
}
|
|
301
302
|
else {
|
|
302
303
|
try {
|
|
@@ -308,10 +309,10 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
|
|
|
308
309
|
cb( null, ast );
|
|
309
310
|
}
|
|
310
311
|
catch (e) {
|
|
311
|
-
cb(e, null);
|
|
312
|
+
cb( e, null );
|
|
312
313
|
}
|
|
313
314
|
}
|
|
314
|
-
});
|
|
315
|
+
} );
|
|
315
316
|
}
|
|
316
317
|
|
|
317
318
|
function readDependenciesSync( astArray ) {
|
|
@@ -331,10 +332,8 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
|
|
|
331
332
|
}
|
|
332
333
|
// create promises after all usingFroms have been collected, as the
|
|
333
334
|
// Promise executor is called immediately with `new`:
|
|
334
|
-
for (const module in dependencies)
|
|
335
|
-
fileNames.push( resolveModuleSync( dependencies[module]
|
|
336
|
-
model.$messageFunctions ) );
|
|
337
|
-
}
|
|
335
|
+
for (const module in dependencies)
|
|
336
|
+
fileNames.push( resolveModuleSync( dependencies[module] ) );
|
|
338
337
|
}
|
|
339
338
|
if (!fileNames.length)
|
|
340
339
|
return [];
|
|
@@ -366,7 +365,7 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
|
|
|
366
365
|
function compileSourcesX( sourcesDict, options = {} ) {
|
|
367
366
|
if (typeof sourcesDict === 'string')
|
|
368
367
|
sourcesDict = { '<stdin>.cds': sourcesDict };
|
|
369
|
-
const sources = Object.create(null);
|
|
368
|
+
const sources = Object.create( null );
|
|
370
369
|
const model = { sources, options };
|
|
371
370
|
model.$messageFunctions = createMessageFunctions( options, 'compile', model );
|
|
372
371
|
|
|
@@ -424,9 +423,9 @@ function recompileX( csn, options ) {
|
|
|
424
423
|
// TODO: $recompile: true should be enough
|
|
425
424
|
|
|
426
425
|
const file = csn.$location && csn.$location.file &&
|
|
427
|
-
csn.$location.file.replace(/[.]cds$/, '.cds.csn') || '<recompile>.csn';
|
|
426
|
+
csn.$location.file.replace( /[.]cds$/, '.cds.csn' ) || '<recompile>.csn';
|
|
428
427
|
|
|
429
|
-
const sources = Object.create(null);
|
|
428
|
+
const sources = Object.create( null );
|
|
430
429
|
const model = { sources, options };
|
|
431
430
|
model.$messageFunctions = createMessageFunctions( options, 'compile', model );
|
|
432
431
|
// TODO: or use module which invokes the recompilation?
|
|
@@ -435,7 +434,7 @@ function recompileX( csn, options ) {
|
|
|
435
434
|
moduleLayers.setLayers( sources );
|
|
436
435
|
const compiled = compileDoX( model ); // calls throwWithError()
|
|
437
436
|
if (options.messages) // does not help with exception in compileDoX()
|
|
438
|
-
deduplicateMessages(options.messages); // TODO: do better
|
|
437
|
+
deduplicateMessages( options.messages ); // TODO: do better
|
|
439
438
|
return compiled;
|
|
440
439
|
}
|
|
441
440
|
|
|
@@ -480,12 +479,11 @@ function compileDoX( model ) {
|
|
|
480
479
|
resolve( model );
|
|
481
480
|
tweakAssocs( model );
|
|
482
481
|
assertConsistency( model );
|
|
482
|
+
check( model );
|
|
483
483
|
throwWithError();
|
|
484
484
|
if (options.lintMode)
|
|
485
485
|
return model;
|
|
486
486
|
|
|
487
|
-
check(model);
|
|
488
|
-
throwWithError();
|
|
489
487
|
return propagator.propagate( model );
|
|
490
488
|
}
|
|
491
489
|
|
|
@@ -500,7 +498,7 @@ function compileDoX( model ) {
|
|
|
500
498
|
* not normalized - any strings work
|
|
501
499
|
*/
|
|
502
500
|
async function processFilenames( filenames, dir ) {
|
|
503
|
-
const filenameMap = Object.create(null);
|
|
501
|
+
const filenameMap = Object.create( null );
|
|
504
502
|
|
|
505
503
|
const promises = [];
|
|
506
504
|
for (const originalName of filenames) {
|
|
@@ -510,12 +508,12 @@ async function processFilenames( filenames, dir ) {
|
|
|
510
508
|
// Resolve possible symbolic link; if the file does not exist
|
|
511
509
|
// we just continue using the original name because readFile()
|
|
512
510
|
// already handles non-existent files.
|
|
513
|
-
const promise = fs.promises.realpath(path.resolve(dir, originalName))
|
|
514
|
-
.then(setName, () => setName(originalName));
|
|
515
|
-
promises.push(promise);
|
|
511
|
+
const promise = fs.promises.realpath( path.resolve( dir, originalName ) )
|
|
512
|
+
.then( setName, () => setName( originalName ) );
|
|
513
|
+
promises.push( promise );
|
|
516
514
|
}
|
|
517
515
|
|
|
518
|
-
await Promise.all(promises);
|
|
516
|
+
await Promise.all( promises );
|
|
519
517
|
return createSourcesDict( filenames, filenameMap, dir );
|
|
520
518
|
}
|
|
521
519
|
|
|
@@ -523,15 +521,15 @@ async function processFilenames( filenames, dir ) {
|
|
|
523
521
|
* Synchronous version of processFilenames().
|
|
524
522
|
*/
|
|
525
523
|
function processFilenamesSync( filenames, dir ) {
|
|
526
|
-
const filenameMap = Object.create(null);
|
|
524
|
+
const filenameMap = Object.create( null );
|
|
527
525
|
|
|
528
526
|
for (const originalName of filenames) {
|
|
529
|
-
let name = path.resolve(dir, originalName);
|
|
527
|
+
let name = path.resolve( dir, originalName );
|
|
530
528
|
try {
|
|
531
529
|
// Resolve possible symbolic link; if the file does not exist
|
|
532
530
|
// we just continue using the original name because readFile()
|
|
533
531
|
// already handles non-existent files.
|
|
534
|
-
name = fs.realpathSync.native(name);
|
|
532
|
+
name = fs.realpathSync.native( name );
|
|
535
533
|
}
|
|
536
534
|
catch (e) {
|
|
537
535
|
// Ignore the not-found (ENOENT) error
|
|
@@ -553,7 +551,7 @@ function processFilenamesSync( filenames, dir ) {
|
|
|
553
551
|
* @return {{sources: object, files: string[], repeated: ArgumentError[]}}
|
|
554
552
|
*/
|
|
555
553
|
function createSourcesDict( filenames, filenameMap, dir ) {
|
|
556
|
-
const sources = Object.create(null);
|
|
554
|
+
const sources = Object.create( null );
|
|
557
555
|
const files = [];
|
|
558
556
|
const repeated = [];
|
|
559
557
|
|
|
@@ -561,7 +559,7 @@ function createSourcesDict( filenames, filenameMap, dir ) {
|
|
|
561
559
|
const name = filenameMap[originalName];
|
|
562
560
|
if (!sources[name]) {
|
|
563
561
|
sources[name] = path.relative( dir, name );
|
|
564
|
-
files.push(name);
|
|
562
|
+
files.push( name );
|
|
565
563
|
}
|
|
566
564
|
else if (typeof sources[name] === 'string') { // not specified more than twice
|
|
567
565
|
const msg = `Repeated argument: file '${ sources[name] }'`;
|
|
@@ -33,7 +33,7 @@ function kickStart( model ) {
|
|
|
33
33
|
const art = model.definitions[name];
|
|
34
34
|
if (art._parent === undefined)
|
|
35
35
|
return; // nothing to do for builtins and redefinitions
|
|
36
|
-
if (art.query && art._ancestors === undefined)
|
|
36
|
+
if (art.query && art._ancestors === undefined && art.kind === 'entity')
|
|
37
37
|
setProjectionAncestors( art );
|
|
38
38
|
|
|
39
39
|
let parent = art._parent;
|
|
@@ -44,7 +44,7 @@ function kickStart( model ) {
|
|
|
44
44
|
if (!parent || !service)
|
|
45
45
|
return;
|
|
46
46
|
// To be removed when nested services are allowed
|
|
47
|
-
if (!isBetaEnabled(options, 'nestedServices') && art.kind === 'service') {
|
|
47
|
+
if (!isBetaEnabled( options, 'nestedServices' ) && art.kind === 'service') {
|
|
48
48
|
while (parent.kind !== 'service')
|
|
49
49
|
parent = parent._parent;
|
|
50
50
|
message( 'service-nested-service', [ art.name.location, art ], { art: parent },
|
|
@@ -66,15 +66,16 @@ function kickStart( model ) {
|
|
|
66
66
|
// TODO: do not do implicit redirection across services, i.e. Service2.E is
|
|
67
67
|
// no redirection target for E if Service2.E = projection on Service1.E and
|
|
68
68
|
// Service1.E = projection on E
|
|
69
|
+
|
|
70
|
+
// Remark: _ancestors are also set with includes, and there also for aspects,
|
|
71
|
+
// types and events.
|
|
69
72
|
const chain = [];
|
|
70
73
|
const autoexposed = annotationVal( art['@cds.autoexposed'] );
|
|
71
|
-
const preferredRedirectionTarget = annotationVal( art['@cds.redirection.target'] );
|
|
72
74
|
// no need to set preferredRedirectionTarget in the while loop as we would
|
|
73
75
|
// use the projection having @cds.redirection.target anyhow instead of
|
|
74
76
|
// `art` anyway (if we do the no-x-service-implicit-redirection TODO above)
|
|
75
77
|
while (art?.query?.from?.path && // direct select with one source
|
|
76
|
-
art._ancestors !== 0
|
|
77
|
-
(preferredRedirectionTarget || !annotationIsFalse( art['@cds.redirection.target'] ) )) {
|
|
78
|
+
art._ancestors !== 0) { // prevent inf-loop
|
|
78
79
|
chain.push( art );
|
|
79
80
|
setLink( art, '_ancestors', 0 ); // avoid infloop with cyclic from
|
|
80
81
|
const name = resolveUncheckedPath( art.query.from, 'from', art );
|
|
@@ -109,10 +110,10 @@ function kickStart( model ) {
|
|
|
109
110
|
return;
|
|
110
111
|
|
|
111
112
|
function expose( ancestor ) {
|
|
112
|
-
if (ancestor._service === service)
|
|
113
|
+
if (ancestor._service === service || annotationIsFalse( art['@cds.redirection.target'] ))
|
|
113
114
|
return;
|
|
114
115
|
const desc = ancestor._descendants ||
|
|
115
|
-
setLink( ancestor, '_descendants', Object.create(null) );
|
|
116
|
+
setLink( ancestor, '_descendants', Object.create( null ) );
|
|
116
117
|
if (!desc[sname])
|
|
117
118
|
desc[sname] = [ art ];
|
|
118
119
|
else
|
|
@@ -23,7 +23,7 @@ function setLayers( sources ) {
|
|
|
23
23
|
|
|
24
24
|
// It is ensured that the representative is called last in SCC and that
|
|
25
25
|
// dependent SCCs are called first
|
|
26
|
-
function setExtends( node, representative, sccDeps = Object.create(null) ) {
|
|
26
|
+
function setExtends( node, representative, sccDeps = Object.create( null ) ) {
|
|
27
27
|
setLink( node, '_layerRepresentative', representative );
|
|
28
28
|
if (layerRepresentative !== representative) {
|
|
29
29
|
layerRepresentative = representative;
|