@sap/cds-compiler 4.0.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 +200 -5
- package/bin/cdsc.js +18 -15
- package/doc/CHANGELOG_BETA.md +16 -0
- package/doc/CHANGELOG_DEPRECATED.md +15 -0
- package/lib/api/main.js +33 -13
- package/lib/api/options.js +2 -2
- package/lib/api/validate.js +25 -25
- package/lib/base/location.js +6 -7
- package/lib/base/message-registry.js +123 -42
- package/lib/base/messages.js +18 -10
- package/lib/base/model.js +43 -10
- package/lib/checks/defaultValues.js +6 -6
- package/lib/checks/elements.js +11 -10
- package/lib/checks/foreignKeys.js +0 -5
- package/lib/checks/manyNavigations.js +33 -0
- package/lib/checks/onConditions.js +22 -14
- package/lib/checks/queryNoDbArtifacts.js +132 -73
- package/lib/checks/selectItems.js +4 -55
- package/lib/checks/sql-snippets.js +15 -4
- package/lib/checks/types.js +3 -3
- package/lib/checks/utils.js +4 -3
- package/lib/checks/validator.js +3 -1
- package/lib/compiler/.eslintrc.json +2 -1
- package/lib/compiler/assert-consistency.js +71 -40
- package/lib/compiler/base.js +7 -2
- package/lib/compiler/builtins.js +40 -41
- package/lib/compiler/checks.js +415 -367
- package/lib/compiler/classes.js +62 -0
- package/lib/compiler/cycle-detector.js +9 -9
- package/lib/compiler/define.js +124 -90
- package/lib/compiler/extend.js +115 -88
- package/lib/compiler/finalize-parse-cdl.js +26 -25
- package/lib/compiler/generate.js +57 -49
- package/lib/compiler/index.js +56 -56
- package/lib/compiler/kick-start.js +10 -7
- package/lib/compiler/moduleLayers.js +1 -1
- package/lib/compiler/populate.js +180 -144
- package/lib/compiler/propagator.js +10 -9
- package/lib/compiler/resolve.js +321 -246
- package/lib/compiler/shared.js +812 -433
- package/lib/compiler/tweak-assocs.js +114 -50
- package/lib/compiler/utils.js +241 -46
- 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 +679 -770
- package/lib/edm/edmAnnoPreprocessor.js +132 -146
- package/lib/edm/edmInboundChecks.js +29 -27
- package/lib/edm/edmPreprocessor.js +689 -648
- package/lib/edm/edmUtils.js +279 -300
- package/lib/gen/Dictionary.json +34 -10
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +2857 -2856
- package/lib/json/from-csn.js +77 -51
- package/lib/json/to-csn.js +15 -15
- package/lib/language/antlrParser.js +2 -1
- package/lib/language/genericAntlrParser.js +52 -43
- package/lib/language/language.g4 +61 -64
- package/lib/language/multiLineStringParser.js +2 -0
- package/lib/main.d.ts +65 -0
- package/lib/model/csnRefs.js +37 -19
- package/lib/model/csnUtils.js +51 -18
- package/lib/model/revealInternalProperties.js +30 -22
- package/lib/modelCompare/compare.js +149 -41
- package/lib/modelCompare/utils/filter.js +55 -25
- package/lib/optionProcessor.js +21 -9
- package/lib/render/manageConstraints.js +20 -17
- package/lib/render/toCdl.js +63 -23
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/toRename.js +4 -9
- package/lib/render/toSql.js +82 -35
- package/lib/render/utils/common.js +11 -9
- package/lib/render/utils/unique.js +52 -0
- package/lib/transform/db/applyTransformations.js +62 -21
- package/lib/transform/db/assertUnique.js +7 -8
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +9 -9
- package/lib/transform/db/constraints.js +47 -17
- package/lib/transform/db/expansion.js +138 -68
- package/lib/transform/db/flattening.js +98 -30
- package/lib/transform/db/rewriteCalculatedElements.js +20 -14
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/transformExists.js +8 -7
- package/lib/transform/db/views.js +73 -33
- package/lib/transform/draft/db.js +11 -9
- package/lib/transform/draft/odata.js +1 -1
- package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
- package/lib/transform/forRelationalDB.js +148 -136
- package/lib/transform/localized.js +92 -54
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
- package/lib/transform/translateAssocsToJoins.js +14 -28
- 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/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
- package/share/messages/message-explanations.json +1 -1
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];
|
|
@@ -215,9 +216,10 @@ function generate( model ) {
|
|
|
215
216
|
textElems.push( elem );
|
|
216
217
|
}
|
|
217
218
|
|
|
218
|
-
if (isKey && isLocalized) {
|
|
219
|
+
if (isKey && isLocalized) {
|
|
219
220
|
const errpos = elem.localized || elem.type || elem.name;
|
|
220
|
-
warning( 'def-ignoring-localized
|
|
221
|
+
warning( 'def-ignoring-localized', [ errpos.location, elem ],
|
|
222
|
+
{ keyword: 'localized' },
|
|
221
223
|
'Keyword $(KEYWORD) is ignored for primary keys' );
|
|
222
224
|
}
|
|
223
225
|
}
|
|
@@ -342,8 +344,8 @@ function generate( model ) {
|
|
|
342
344
|
// Because ID_texts is not copied from TextsAspect, the order is messed
|
|
343
345
|
// up. Fix it.
|
|
344
346
|
const { elements } = art;
|
|
345
|
-
art.elements = Object.create(null);
|
|
346
|
-
const names = [ 'ID_texts', 'locale', ...Object.keys(elements) ];
|
|
347
|
+
art.elements = Object.create( null );
|
|
348
|
+
const names = [ 'ID_texts', 'locale', ...Object.keys( elements ) ];
|
|
347
349
|
for (const name of names)
|
|
348
350
|
art.elements[name] = elements[name];
|
|
349
351
|
|
|
@@ -373,7 +375,7 @@ function generate( model ) {
|
|
|
373
375
|
function createTextsEntityWithInclude( base, absolute, fioriEnabled ) {
|
|
374
376
|
const textsAspectName = 'sap.common.TextsAspect';
|
|
375
377
|
const textsAspect = model.definitions['sap.common.TextsAspect'];
|
|
376
|
-
const elements = Object.create(null);
|
|
378
|
+
const elements = Object.create( null );
|
|
377
379
|
const { location } = base.name;
|
|
378
380
|
const art = {
|
|
379
381
|
kind: 'entity',
|
|
@@ -386,7 +388,6 @@ function generate( model ) {
|
|
|
386
388
|
|
|
387
389
|
if (!fioriEnabled) {
|
|
388
390
|
// To be compatible, we switch off draft without @fiori.draft.enabled
|
|
389
|
-
// TODO (next major version): remove?
|
|
390
391
|
setAnnotation( art, '@odata.draft.enabled', art.location, false );
|
|
391
392
|
}
|
|
392
393
|
else {
|
|
@@ -422,7 +423,7 @@ function generate( model ) {
|
|
|
422
423
|
* @param {boolean} fioriEnabled
|
|
423
424
|
*/
|
|
424
425
|
function createTextsEntityWithDefaultElements( base, absolute, fioriEnabled ) {
|
|
425
|
-
const elements = Object.create(null);
|
|
426
|
+
const elements = Object.create( null );
|
|
426
427
|
const { location } = base.name;
|
|
427
428
|
const art = {
|
|
428
429
|
kind: 'entity',
|
|
@@ -446,7 +447,6 @@ function generate( model ) {
|
|
|
446
447
|
if (!fioriEnabled) {
|
|
447
448
|
locale.key = { val: true, location };
|
|
448
449
|
// To be compatible, we switch off draft without @fiori.draft.enabled
|
|
449
|
-
// TODO (next major version): remove?
|
|
450
450
|
setAnnotation( art, '@odata.draft.enabled', art.location, false );
|
|
451
451
|
}
|
|
452
452
|
else {
|
|
@@ -536,7 +536,7 @@ function generate( model ) {
|
|
|
536
536
|
* @returns {boolean}
|
|
537
537
|
*/
|
|
538
538
|
function hasTruthyProp( art, prop ) {
|
|
539
|
-
const processed = Object.create(null); // avoid infloops with circular refs
|
|
539
|
+
const processed = Object.create( null ); // avoid infloops with circular refs
|
|
540
540
|
let name = art.name.absolute; // is ok, since no recursive type possible
|
|
541
541
|
while (art && !processed[name]) {
|
|
542
542
|
if (art[prop])
|
|
@@ -576,7 +576,7 @@ function generate( model ) {
|
|
|
576
576
|
return;
|
|
577
577
|
|
|
578
578
|
function baseKeys() {
|
|
579
|
-
const k = Object.create(null);
|
|
579
|
+
const k = Object.create( null );
|
|
580
580
|
for (const name in base.elements) {
|
|
581
581
|
const elem = base.elements[name];
|
|
582
582
|
if (elem.$duplicates)
|
|
@@ -662,22 +662,22 @@ function generate( model ) {
|
|
|
662
662
|
return false;
|
|
663
663
|
}
|
|
664
664
|
const names = Object.keys( target.elements )
|
|
665
|
-
.filter( n => n.startsWith('up__') && keyNames.includes( n.substring(4) ) );
|
|
665
|
+
.filter( n => n.startsWith( 'up__' ) && keyNames.includes( n.substring(4) ) );
|
|
666
666
|
if (names.length) {
|
|
667
667
|
// FUTURE: if named type, add sub info with location of "up_" element
|
|
668
668
|
error( null, [ location, elem ], { target: entityName, names }, {
|
|
669
669
|
std: 'Key elements $(NAMES) can\'t be added to $(TARGET) as these already exist',
|
|
670
670
|
one: 'Key element $(NAMES) can\'t be added to $(TARGET) as it already exist',
|
|
671
|
-
});
|
|
671
|
+
} );
|
|
672
672
|
return false;
|
|
673
673
|
}
|
|
674
674
|
|
|
675
|
-
if (elem.type && !isDirectComposition(elem)) {
|
|
675
|
+
if (elem.type && !isDirectComposition( elem )) {
|
|
676
676
|
// Only issue warning for direct usages, not for projections, includes, etc.
|
|
677
677
|
// TODO: Make it configurable error; v4: error
|
|
678
678
|
warning( 'def-expected-comp-aspect', [ elem.type.location, elem ],
|
|
679
679
|
{ prop: 'Composition of', otherprop: 'Association to' },
|
|
680
|
-
'Expected $(PROP), but found $(OTHERPROP) for composition of aspect');
|
|
680
|
+
'Expected $(PROP), but found $(OTHERPROP) for composition of aspect' );
|
|
681
681
|
}
|
|
682
682
|
|
|
683
683
|
return true;
|
|
@@ -697,9 +697,14 @@ function generate( model ) {
|
|
|
697
697
|
|
|
698
698
|
const art = {
|
|
699
699
|
kind: 'entity',
|
|
700
|
-
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
|
+
},
|
|
701
706
|
location,
|
|
702
|
-
elements: Object.create(null),
|
|
707
|
+
elements: Object.create( null ),
|
|
703
708
|
$inferred: 'composition-entity',
|
|
704
709
|
};
|
|
705
710
|
if (target.name) { // named target aspect
|
|
@@ -712,17 +717,19 @@ function generate( model ) {
|
|
|
712
717
|
setLink( art, '_upperAspects', elem._main._upperAspects || [] );
|
|
713
718
|
}
|
|
714
719
|
|
|
720
|
+
// Since there is no user-written up_ element, use a weak location to the beginning of {…}.
|
|
721
|
+
const upLocation = weakLocation( location );
|
|
715
722
|
const up = { // elements.up_ = ...
|
|
716
|
-
name: { location, id: 'up_' },
|
|
723
|
+
name: { location: upLocation, id: 'up_' },
|
|
717
724
|
kind: 'element',
|
|
718
|
-
location,
|
|
725
|
+
location: upLocation,
|
|
719
726
|
$inferred: 'aspect-composition',
|
|
720
|
-
type: augmentPath(
|
|
721
|
-
target: augmentPath(
|
|
727
|
+
type: augmentPath( upLocation, 'cds.Association' ),
|
|
728
|
+
target: augmentPath( upLocation, base.name.absolute ),
|
|
722
729
|
cardinality: {
|
|
723
|
-
targetMin: { val: 1, literal: 'number', location },
|
|
724
|
-
targetMax: { val: 1, literal: 'number', location },
|
|
725
|
-
location,
|
|
730
|
+
targetMin: { val: 1, literal: 'number', location: upLocation },
|
|
731
|
+
targetMax: { val: 1, literal: 'number', location: upLocation },
|
|
732
|
+
location: upLocation,
|
|
726
733
|
},
|
|
727
734
|
};
|
|
728
735
|
// By default, 'up_' is a managed primary key association.
|
|
@@ -731,16 +738,17 @@ function generate( model ) {
|
|
|
731
738
|
if (isDeprecatedEnabled( options, '_unmanagedUpInComponent' )) {
|
|
732
739
|
addProxyElements( art, keys, 'aspect-composition', target.name && location,
|
|
733
740
|
'up__', '@odata.containment.ignore' );
|
|
734
|
-
up.on = augmentEqual(
|
|
741
|
+
up.on = augmentEqual( upLocation, 'up_', Object.values( keys ), 'up__' );
|
|
735
742
|
}
|
|
736
743
|
else {
|
|
737
|
-
up.key = { location, val: true };
|
|
744
|
+
up.key = { location: upLocation, val: true };
|
|
738
745
|
// managed associations must be explicitly set to not null
|
|
739
746
|
// even if target cardinality is 1..1
|
|
740
|
-
up.notNull = { location, val: true };
|
|
747
|
+
up.notNull = { location: upLocation, val: true };
|
|
741
748
|
}
|
|
742
749
|
|
|
743
|
-
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.
|
|
744
752
|
addProxyElements( art, target.elements, 'aspect-composition', target.name && location );
|
|
745
753
|
|
|
746
754
|
setLink( art, '_block', model.$internal );
|
|
@@ -758,7 +766,7 @@ function generate( model ) {
|
|
|
758
766
|
for (const name in elements) {
|
|
759
767
|
const pname = `${ prefix }${ name }`;
|
|
760
768
|
const origin = elements[name];
|
|
761
|
-
const proxy = linkToOrigin( origin, pname, null, null, location
|
|
769
|
+
const proxy = linkToOrigin( origin, pname, null, null, location, true );
|
|
762
770
|
setLink( proxy, '_block', origin._block );
|
|
763
771
|
proxy.$inferred = inferred;
|
|
764
772
|
if (origin.masked)
|
|
@@ -809,7 +817,7 @@ function augmentEqual( location, assocname, relations, prefix = '' ) {
|
|
|
809
817
|
: { op: { val: 'and', location }, args, location };
|
|
810
818
|
|
|
811
819
|
function eq( refs ) {
|
|
812
|
-
if (Array.isArray(refs))
|
|
820
|
+
if (Array.isArray( refs ))
|
|
813
821
|
return { op: { val: '=', location }, args: refs.map( ref ), location };
|
|
814
822
|
|
|
815
823
|
const { id } = refs.name;
|
|
@@ -823,7 +831,7 @@ function augmentEqual( location, assocname, relations, prefix = '' ) {
|
|
|
823
831
|
};
|
|
824
832
|
}
|
|
825
833
|
function ref( path ) {
|
|
826
|
-
return { path: path.split('.').map( id => ({ id, location }) ), location };
|
|
834
|
+
return { path: path.split( '.' ).map( id => ({ id, location }) ), location };
|
|
827
835
|
}
|
|
828
836
|
}
|
|
829
837
|
|
|
@@ -834,12 +842,12 @@ function checkTextsLanguageAssocOption( model, options ) {
|
|
|
834
842
|
if (options.addTextsLanguageAssoc && !commonLanguagesEntity) {
|
|
835
843
|
const variant = !languages ? 'std' : 'code';
|
|
836
844
|
const loc = model.definitions['sap.common.Languages']?.name?.location || null;
|
|
837
|
-
model.$messageFunctions.info('api-ignoring-language-assoc', loc, {
|
|
845
|
+
model.$messageFunctions.info( 'api-ignoring-language-assoc', loc, {
|
|
838
846
|
'#': variant, option: 'addTextsLanguageAssoc', art: 'sap.common.Languages', name: 'code',
|
|
839
847
|
}, {
|
|
840
848
|
std: 'Ignoring option $(OPTION) because entity $(ART) is missing',
|
|
841
849
|
code: 'Ignoring option $(OPTION) because entity $(ART) is missing element $(NAME)',
|
|
842
|
-
});
|
|
850
|
+
} );
|
|
843
851
|
}
|
|
844
852
|
|
|
845
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
|
|
|
@@ -38,6 +38,7 @@ const { cdsFs } = require('../utils/file');
|
|
|
38
38
|
|
|
39
39
|
const fs = require('fs');
|
|
40
40
|
const path = require('path');
|
|
41
|
+
const { CsnLocation, XsnSource } = require('./classes');
|
|
41
42
|
|
|
42
43
|
const extensionParsers = {
|
|
43
44
|
csn: parseCsn.parse,
|
|
@@ -52,7 +53,7 @@ const extensionParsers = {
|
|
|
52
53
|
// `errors`: vector of errors (file IO or ArgumentError)
|
|
53
54
|
class InvocationError extends Error {
|
|
54
55
|
constructor( errs, ...args ) {
|
|
55
|
-
super(...args);
|
|
56
|
+
super( ...args );
|
|
56
57
|
this.code = 'ERR_CDS_COMPILER_INVOCATION';
|
|
57
58
|
this.errors = errs;
|
|
58
59
|
this.hasBeenReported = false;
|
|
@@ -63,7 +64,7 @@ class InvocationError extends Error {
|
|
|
63
64
|
// `argument`: the command argument (repeated file names)
|
|
64
65
|
class ArgumentError extends Error {
|
|
65
66
|
constructor( arg, ...args ) {
|
|
66
|
-
super(...args);
|
|
67
|
+
super( ...args );
|
|
67
68
|
this.code = 'ERR_CDS_COMPILER_ARGUMENT';
|
|
68
69
|
this.argument = arg;
|
|
69
70
|
}
|
|
@@ -85,14 +86,15 @@ function parseX( source, filename, options = {}, messageFunctions = null ) {
|
|
|
85
86
|
const ext = path.extname( filename ).slice(1).toLowerCase();
|
|
86
87
|
// eslint-disable-next-line no-nested-ternary
|
|
87
88
|
const parser = options.fallbackParser === 'auto!'
|
|
88
|
-
? (source?.startsWith('{') ? parseCsn.parse : parseLanguage)
|
|
89
|
+
? (source?.startsWith( '{' ) ? parseCsn.parse : parseLanguage)
|
|
89
90
|
: (extensionParsers[ext] || extensionParsers[options.fallbackParser] ||
|
|
90
|
-
source.startsWith('{') && parseCsn.parse);
|
|
91
|
+
source.startsWith( '{' ) && parseCsn.parse);
|
|
91
92
|
if (parser)
|
|
92
93
|
return parser( source, filename, options, messageFunctions );
|
|
93
94
|
|
|
94
|
-
const model =
|
|
95
|
-
|
|
95
|
+
const model = new XsnSource();
|
|
96
|
+
model.location = new CsnLocation( filename );
|
|
97
|
+
messageFunctions.error( 'file-unknown-ext', emptyWeakLocation( filename ),
|
|
96
98
|
{ file: ext, '#': !ext && 'none' }, {
|
|
97
99
|
std: 'Unknown file extension $(FILE)',
|
|
98
100
|
none: 'No file extension',
|
|
@@ -129,29 +131,30 @@ function parseX( source, filename, options = {}, messageFunctions = null ) {
|
|
|
129
131
|
// - 'string' or instanceof Buffer: the file content
|
|
130
132
|
// - { realname: fs.realpath(filename) }: if filename is not canonicalized
|
|
131
133
|
//
|
|
132
|
-
function compileX( filenames, dir = '', options = {}, fileCache = Object.create(null) ) {
|
|
134
|
+
function compileX( filenames, dir = '', options = {}, fileCache = Object.create( null ) ) {
|
|
133
135
|
// A non-proper dictionary (i.e. with prototype) is safe if the keys are
|
|
134
136
|
// absolute file names - they start with `/` or `\` or similar
|
|
135
137
|
// if (Object.getPrototypeOf( fileCache ))
|
|
136
138
|
// fileCache = Object.assign( Object.create(null), fileCache );
|
|
137
|
-
dir = path.resolve(dir);
|
|
139
|
+
dir = path.resolve( dir );
|
|
138
140
|
const model = { sources: null, options };
|
|
139
141
|
model.$messageFunctions = createMessageFunctions( options, 'compile', model );
|
|
142
|
+
const { resolveModule } = makeModuleResolver( options, fileCache, model.$messageFunctions );
|
|
140
143
|
let input = null;
|
|
141
144
|
|
|
142
145
|
let all = processFilenames( filenames, dir )
|
|
143
|
-
.then((processedInput) => {
|
|
146
|
+
.then( (processedInput) => {
|
|
144
147
|
input = processedInput;
|
|
145
148
|
model.sources = input.sources;
|
|
146
|
-
})
|
|
147
|
-
.then(() => promiseAllDoNotRejectImmediately( input.files.map(readAndParse) ))
|
|
149
|
+
} )
|
|
150
|
+
.then( () => promiseAllDoNotRejectImmediately( input.files.map( readAndParse ) ) )
|
|
148
151
|
.then( testInvocation, (reason) => {
|
|
149
152
|
// do not reject with PromiseAllError, use InvocationError:
|
|
150
153
|
const errs = reason.valuesOrErrors.filter( e => e instanceof Error );
|
|
151
154
|
// internal error if no file IO error (has property `path`)
|
|
152
155
|
return Promise.reject( errs.find( e => !e.path ) ||
|
|
153
156
|
new InvocationError( [ ...input.repeated, ...errs ]) );
|
|
154
|
-
});
|
|
157
|
+
} );
|
|
155
158
|
|
|
156
159
|
if (!options.parseOnly && !options.parseCdl)
|
|
157
160
|
all = all.then( readDependencies );
|
|
@@ -159,7 +162,7 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
|
|
|
159
162
|
return all.then( () => {
|
|
160
163
|
moduleLayers.setLayers( input.sources );
|
|
161
164
|
return compileDoX( model );
|
|
162
|
-
});
|
|
165
|
+
} );
|
|
163
166
|
|
|
164
167
|
// Read file `filename` and parse its content, return messages
|
|
165
168
|
async function readAndParse( filename ) {
|
|
@@ -171,12 +174,12 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
|
|
|
171
174
|
return []; // no further dependency processing
|
|
172
175
|
// no parallel readAndParse with same resolved filename should read the file,
|
|
173
176
|
// also ensure deterministic sequence in sources:
|
|
174
|
-
sources[filename] = { location:
|
|
177
|
+
sources[filename] = { location: new CsnLocation( rel ) };
|
|
175
178
|
|
|
176
179
|
const source = await cdsFs( fileCache, options.traceFs ).readFileAsync( filename, 'utf8' );
|
|
177
180
|
const ast = parseX( source, rel, options, model.$messageFunctions );
|
|
178
181
|
sources[filename] = ast;
|
|
179
|
-
ast.location =
|
|
182
|
+
ast.location = new CsnLocation( rel );
|
|
180
183
|
ast.dirname = path.dirname( filename );
|
|
181
184
|
assertConsistency( ast, options );
|
|
182
185
|
|
|
@@ -208,10 +211,8 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
|
|
|
208
211
|
}
|
|
209
212
|
// create promises after all usingFroms have been collected, as the
|
|
210
213
|
// Promise executor is called immediately with `new`:
|
|
211
|
-
for (const module in dependencies)
|
|
212
|
-
promises.push( resolveModule( dependencies[module]
|
|
213
|
-
model.$messageFunctions ) );
|
|
214
|
-
}
|
|
214
|
+
for (const module in dependencies)
|
|
215
|
+
promises.push( resolveModule( dependencies[module] ) );
|
|
215
216
|
}
|
|
216
217
|
if (!promises.length)
|
|
217
218
|
return [];
|
|
@@ -234,23 +235,25 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
|
|
|
234
235
|
* @param {object} [fileCache]
|
|
235
236
|
* @returns {XSN.Model} Augmented CSN
|
|
236
237
|
*/
|
|
237
|
-
function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.create(null) ) {
|
|
238
|
+
function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.create( null ) ) {
|
|
238
239
|
// A non-proper dictionary (i.e. with prototype) is safe if the keys are
|
|
239
240
|
// absolute file names - they start with `/` or `\` or similar
|
|
240
|
-
dir = path.resolve(dir);
|
|
241
|
+
dir = path.resolve( dir );
|
|
241
242
|
const a = processFilenamesSync( filenames, dir );
|
|
242
243
|
|
|
243
244
|
const model = { sources: a.sources, options };
|
|
244
245
|
model.$messageFunctions = createMessageFunctions( options, 'compile', model );
|
|
246
|
+
const { resolveModuleSync } = makeModuleResolverSync( options, fileCache,
|
|
247
|
+
model.$messageFunctions );
|
|
245
248
|
|
|
246
249
|
const asts = [];
|
|
247
250
|
const errors = [];
|
|
248
251
|
a.files.forEach( val => readAndParseSync( val, (err, ast) => {
|
|
249
252
|
if (err)
|
|
250
|
-
errors.push(err);
|
|
253
|
+
errors.push( err );
|
|
251
254
|
if (ast)
|
|
252
|
-
asts.push(ast);
|
|
253
|
-
}));
|
|
255
|
+
asts.push( ast );
|
|
256
|
+
} ) );
|
|
254
257
|
|
|
255
258
|
if (errors.length || a.repeated.length) {
|
|
256
259
|
// internal error if no file IO error (has property `path`)
|
|
@@ -264,12 +267,12 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
|
|
|
264
267
|
asts.length = 0;
|
|
265
268
|
// Push dependencies to `ast`. Only works because readAndParseSync() is synchronous.
|
|
266
269
|
for (const fileName of fileNames) {
|
|
267
|
-
readAndParseSync(fileName, ( err, ast ) => {
|
|
270
|
+
readAndParseSync( fileName, ( err, ast ) => {
|
|
268
271
|
if (err)
|
|
269
272
|
throw err;
|
|
270
273
|
if (ast)
|
|
271
274
|
asts.push( ast );
|
|
272
|
-
});
|
|
275
|
+
} );
|
|
273
276
|
}
|
|
274
277
|
}
|
|
275
278
|
}
|
|
@@ -280,36 +283,36 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
|
|
|
280
283
|
// Read file `filename` and parse its content, return messages
|
|
281
284
|
function readAndParseSync( filename, cb ) {
|
|
282
285
|
if ( filename === false ) { // module which has not been found
|
|
283
|
-
cb(null, null);
|
|
286
|
+
cb( null, null );
|
|
284
287
|
return;
|
|
285
288
|
}
|
|
286
289
|
const rel = a.sources[filename] || path.relative( dir, filename );
|
|
287
290
|
if (typeof rel === 'object') { // already parsed
|
|
288
|
-
cb(null, null);
|
|
291
|
+
cb( null, null );
|
|
289
292
|
return; // no further dependency processing
|
|
290
293
|
}
|
|
291
294
|
// no parallel readAndParse with same resolved filename should read the file,
|
|
292
295
|
// also ensure deterministic sequence in a.sources:
|
|
293
|
-
a.sources[filename] = { location:
|
|
296
|
+
a.sources[filename] = { location: new CsnLocation( rel ) };
|
|
294
297
|
|
|
295
298
|
cdsFs( fileCache, options.traceFs ).readFileSync( filename, 'utf8', (err, source) => {
|
|
296
299
|
if (err) {
|
|
297
|
-
cb(err, null);
|
|
300
|
+
cb( err, null );
|
|
298
301
|
}
|
|
299
302
|
else {
|
|
300
303
|
try {
|
|
301
304
|
const ast = parseX( source, rel, options, model.$messageFunctions );
|
|
302
305
|
a.sources[filename] = ast;
|
|
303
|
-
ast.location =
|
|
306
|
+
ast.location = new CsnLocation( rel );
|
|
304
307
|
ast.dirname = path.dirname( filename );
|
|
305
308
|
assertConsistency( ast, options );
|
|
306
309
|
cb( null, ast );
|
|
307
310
|
}
|
|
308
311
|
catch (e) {
|
|
309
|
-
cb(e, null);
|
|
312
|
+
cb( e, null );
|
|
310
313
|
}
|
|
311
314
|
}
|
|
312
|
-
});
|
|
315
|
+
} );
|
|
313
316
|
}
|
|
314
317
|
|
|
315
318
|
function readDependenciesSync( astArray ) {
|
|
@@ -329,10 +332,8 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
|
|
|
329
332
|
}
|
|
330
333
|
// create promises after all usingFroms have been collected, as the
|
|
331
334
|
// Promise executor is called immediately with `new`:
|
|
332
|
-
for (const module in dependencies)
|
|
333
|
-
fileNames.push( resolveModuleSync( dependencies[module]
|
|
334
|
-
model.$messageFunctions ) );
|
|
335
|
-
}
|
|
335
|
+
for (const module in dependencies)
|
|
336
|
+
fileNames.push( resolveModuleSync( dependencies[module] ) );
|
|
336
337
|
}
|
|
337
338
|
if (!fileNames.length)
|
|
338
339
|
return [];
|
|
@@ -364,7 +365,7 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
|
|
|
364
365
|
function compileSourcesX( sourcesDict, options = {} ) {
|
|
365
366
|
if (typeof sourcesDict === 'string')
|
|
366
367
|
sourcesDict = { '<stdin>.cds': sourcesDict };
|
|
367
|
-
const sources = Object.create(null);
|
|
368
|
+
const sources = Object.create( null );
|
|
368
369
|
const model = { sources, options };
|
|
369
370
|
model.$messageFunctions = createMessageFunctions( options, 'compile', model );
|
|
370
371
|
|
|
@@ -373,7 +374,7 @@ function compileSourcesX( sourcesDict, options = {} ) {
|
|
|
373
374
|
if (typeof source === 'string') {
|
|
374
375
|
const ast = parseX( source, filename, options, model.$messageFunctions );
|
|
375
376
|
sources[filename] = ast;
|
|
376
|
-
ast.location =
|
|
377
|
+
ast.location = new CsnLocation( filename );
|
|
377
378
|
assertConsistency( ast, options );
|
|
378
379
|
}
|
|
379
380
|
else if (options.$xsnObjects) { // source is a XSN object with option $xsnObjects
|
|
@@ -382,7 +383,7 @@ function compileSourcesX( sourcesDict, options = {} ) {
|
|
|
382
383
|
else { // source is a CSN object
|
|
383
384
|
const ast = parseCsn.augment( source, filename, options, model.$messageFunctions );
|
|
384
385
|
sources[filename] = ast;
|
|
385
|
-
ast.location =
|
|
386
|
+
ast.location = new CsnLocation( filename );
|
|
386
387
|
assertConsistency( ast, options );
|
|
387
388
|
}
|
|
388
389
|
|
|
@@ -422,9 +423,9 @@ function recompileX( csn, options ) {
|
|
|
422
423
|
// TODO: $recompile: true should be enough
|
|
423
424
|
|
|
424
425
|
const file = csn.$location && csn.$location.file &&
|
|
425
|
-
csn.$location.file.replace(/[.]cds$/, '.cds.csn') || '<recompile>.csn';
|
|
426
|
+
csn.$location.file.replace( /[.]cds$/, '.cds.csn' ) || '<recompile>.csn';
|
|
426
427
|
|
|
427
|
-
const sources = Object.create(null);
|
|
428
|
+
const sources = Object.create( null );
|
|
428
429
|
const model = { sources, options };
|
|
429
430
|
model.$messageFunctions = createMessageFunctions( options, 'compile', model );
|
|
430
431
|
// TODO: or use module which invokes the recompilation?
|
|
@@ -433,7 +434,7 @@ function recompileX( csn, options ) {
|
|
|
433
434
|
moduleLayers.setLayers( sources );
|
|
434
435
|
const compiled = compileDoX( model ); // calls throwWithError()
|
|
435
436
|
if (options.messages) // does not help with exception in compileDoX()
|
|
436
|
-
deduplicateMessages(options.messages); // TODO: do better
|
|
437
|
+
deduplicateMessages( options.messages ); // TODO: do better
|
|
437
438
|
return compiled;
|
|
438
439
|
}
|
|
439
440
|
|
|
@@ -478,12 +479,11 @@ function compileDoX( model ) {
|
|
|
478
479
|
resolve( model );
|
|
479
480
|
tweakAssocs( model );
|
|
480
481
|
assertConsistency( model );
|
|
482
|
+
check( model );
|
|
481
483
|
throwWithError();
|
|
482
484
|
if (options.lintMode)
|
|
483
485
|
return model;
|
|
484
486
|
|
|
485
|
-
check(model);
|
|
486
|
-
throwWithError();
|
|
487
487
|
return propagator.propagate( model );
|
|
488
488
|
}
|
|
489
489
|
|
|
@@ -498,7 +498,7 @@ function compileDoX( model ) {
|
|
|
498
498
|
* not normalized - any strings work
|
|
499
499
|
*/
|
|
500
500
|
async function processFilenames( filenames, dir ) {
|
|
501
|
-
const filenameMap = Object.create(null);
|
|
501
|
+
const filenameMap = Object.create( null );
|
|
502
502
|
|
|
503
503
|
const promises = [];
|
|
504
504
|
for (const originalName of filenames) {
|
|
@@ -508,12 +508,12 @@ async function processFilenames( filenames, dir ) {
|
|
|
508
508
|
// Resolve possible symbolic link; if the file does not exist
|
|
509
509
|
// we just continue using the original name because readFile()
|
|
510
510
|
// already handles non-existent files.
|
|
511
|
-
const promise = fs.promises.realpath(path.resolve(dir, originalName))
|
|
512
|
-
.then(setName, () => setName(originalName));
|
|
513
|
-
promises.push(promise);
|
|
511
|
+
const promise = fs.promises.realpath( path.resolve( dir, originalName ) )
|
|
512
|
+
.then( setName, () => setName( originalName ) );
|
|
513
|
+
promises.push( promise );
|
|
514
514
|
}
|
|
515
515
|
|
|
516
|
-
await Promise.all(promises);
|
|
516
|
+
await Promise.all( promises );
|
|
517
517
|
return createSourcesDict( filenames, filenameMap, dir );
|
|
518
518
|
}
|
|
519
519
|
|
|
@@ -521,15 +521,15 @@ async function processFilenames( filenames, dir ) {
|
|
|
521
521
|
* Synchronous version of processFilenames().
|
|
522
522
|
*/
|
|
523
523
|
function processFilenamesSync( filenames, dir ) {
|
|
524
|
-
const filenameMap = Object.create(null);
|
|
524
|
+
const filenameMap = Object.create( null );
|
|
525
525
|
|
|
526
526
|
for (const originalName of filenames) {
|
|
527
|
-
let name = path.resolve(dir, originalName);
|
|
527
|
+
let name = path.resolve( dir, originalName );
|
|
528
528
|
try {
|
|
529
529
|
// Resolve possible symbolic link; if the file does not exist
|
|
530
530
|
// we just continue using the original name because readFile()
|
|
531
531
|
// already handles non-existent files.
|
|
532
|
-
name = fs.realpathSync.native(name);
|
|
532
|
+
name = fs.realpathSync.native( name );
|
|
533
533
|
}
|
|
534
534
|
catch (e) {
|
|
535
535
|
// Ignore the not-found (ENOENT) error
|
|
@@ -551,7 +551,7 @@ function processFilenamesSync( filenames, dir ) {
|
|
|
551
551
|
* @return {{sources: object, files: string[], repeated: ArgumentError[]}}
|
|
552
552
|
*/
|
|
553
553
|
function createSourcesDict( filenames, filenameMap, dir ) {
|
|
554
|
-
const sources = Object.create(null);
|
|
554
|
+
const sources = Object.create( null );
|
|
555
555
|
const files = [];
|
|
556
556
|
const repeated = [];
|
|
557
557
|
|
|
@@ -559,7 +559,7 @@ function createSourcesDict( filenames, filenameMap, dir ) {
|
|
|
559
559
|
const name = filenameMap[originalName];
|
|
560
560
|
if (!sources[name]) {
|
|
561
561
|
sources[name] = path.relative( dir, name );
|
|
562
|
-
files.push(name);
|
|
562
|
+
files.push( name );
|
|
563
563
|
}
|
|
564
564
|
else if (typeof sources[name] === 'string') { // not specified more than twice
|
|
565
565
|
const msg = `Repeated argument: file '${ sources[name] }'`;
|