@sap/cds-compiler 2.15.8 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -1590
- package/bin/.eslintrc.json +2 -1
- package/bin/cdsc.js +61 -46
- package/doc/API.md +11 -0
- package/doc/CHANGELOG_ARCHIVE.md +1592 -0
- package/doc/CHANGELOG_BETA.md +26 -5
- package/doc/CHANGELOG_DEPRECATED.md +55 -1
- package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
- package/doc/Versioning.md +20 -1
- package/lib/api/.eslintrc.json +2 -2
- package/lib/api/main.js +282 -156
- package/lib/api/options.js +17 -88
- package/lib/api/validate.js +6 -10
- package/lib/base/keywords.js +280 -110
- package/lib/base/message-registry.js +85 -25
- package/lib/base/messages.js +119 -89
- package/lib/base/model.js +46 -2
- package/lib/base/optionProcessorHelper.js +53 -21
- package/lib/checks/actionsFunctions.js +15 -12
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +1 -0
- package/lib/checks/elements.js +6 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -1
- package/lib/checks/selectItems.js +101 -15
- package/lib/checks/types.js +7 -8
- package/lib/checks/utils.js +2 -2
- package/lib/checks/validator.js +3 -3
- package/lib/compiler/assert-consistency.js +78 -21
- package/lib/compiler/base.js +6 -4
- package/lib/compiler/builtins.js +177 -10
- package/lib/compiler/checks.js +1 -1
- package/lib/compiler/define.js +28 -23
- package/lib/compiler/extend.js +75 -18
- package/lib/compiler/finalize-parse-cdl.js +25 -18
- package/lib/compiler/index.js +27 -11
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +26 -39
- package/lib/compiler/propagator.js +12 -7
- package/lib/compiler/resolve.js +207 -236
- package/lib/compiler/shared.js +100 -93
- package/lib/compiler/tweak-assocs.js +13 -20
- package/lib/compiler/utils.js +20 -6
- package/lib/edm/annotations/preprocessAnnotations.js +12 -13
- package/lib/edm/csn2edm.js +35 -37
- package/lib/edm/edm.js +22 -13
- package/lib/edm/edmAnnoPreprocessor.js +349 -0
- package/lib/edm/edmInboundChecks.js +85 -0
- package/lib/edm/edmPreprocessor.js +338 -689
- package/lib/edm/edmUtils.js +97 -67
- package/lib/gen/Dictionary.json +29 -9
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +8 -31
- package/lib/gen/language.tokens +105 -114
- package/lib/gen/languageLexer.interp +1 -34
- package/lib/gen/languageLexer.js +892 -1007
- package/lib/gen/languageLexer.tokens +95 -106
- package/lib/gen/languageParser.js +20629 -22474
- package/lib/inspect/.eslintrc.json +4 -0
- package/lib/inspect/index.js +14 -0
- package/lib/inspect/inspectModelStatistics.js +81 -0
- package/lib/inspect/inspectPropagation.js +189 -0
- package/lib/inspect/inspectUtils.js +44 -0
- package/lib/json/from-csn.js +74 -69
- package/lib/json/to-csn.js +17 -14
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/docCommentParser.js +61 -38
- package/lib/language/errorStrategy.js +52 -40
- package/lib/language/genericAntlrParser.js +424 -292
- package/lib/language/language.g4 +604 -687
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +28 -42
- package/lib/main.js +104 -81
- package/lib/model/api.js +1 -1
- package/lib/model/csnRefs.js +57 -30
- package/lib/model/csnUtils.js +189 -287
- package/lib/model/revealInternalProperties.js +32 -10
- package/lib/model/sortViews.js +32 -31
- package/lib/modelCompare/compare.js +3 -0
- package/lib/optionProcessor.js +91 -57
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/DuplicateChecker.js +4 -7
- package/lib/render/manageConstraints.js +70 -2
- package/lib/render/toCdl.js +387 -367
- package/lib/render/toHdbcds.js +20 -16
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +81 -59
- package/lib/render/utils/common.js +16 -3
- package/lib/render/utils/sql.js +20 -19
- package/lib/sql-identifier.js +6 -0
- package/lib/transform/db/.eslintrc.json +3 -2
- package/lib/transform/db/associations.js +43 -35
- package/lib/transform/db/cdsPersistence.js +5 -16
- package/lib/transform/db/constraints.js +1 -1
- package/lib/transform/db/expansion.js +7 -6
- package/lib/transform/db/flattening.js +16 -18
- package/lib/transform/db/transformExists.js +7 -5
- package/lib/transform/db/views.js +3 -3
- package/lib/transform/draft/.eslintrc.json +2 -2
- package/lib/transform/draft/db.js +6 -6
- package/lib/transform/draft/odata.js +6 -7
- package/lib/transform/forHanaNew.js +30 -24
- package/lib/transform/forOdataNew.js +14 -16
- package/lib/transform/localized.js +35 -25
- package/lib/transform/odata/toFinalBaseType.js +10 -10
- package/lib/transform/odata/typesExposure.js +17 -8
- package/lib/transform/odata/utils.js +1 -38
- package/lib/transform/transformUtilsNew.js +63 -77
- package/lib/transform/translateAssocsToJoins.js +2 -2
- package/lib/transform/universalCsn/.eslintrc.json +2 -2
- package/lib/transform/universalCsn/coreComputed.js +11 -6
- package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
- package/lib/utils/file.js +31 -21
- package/lib/utils/moduleResolve.js +0 -1
- package/lib/utils/timetrace.js +20 -21
- package/package.json +34 -4
- package/share/messages/syntax-expected-integer.md +9 -8
- package/doc/ApiMigration.md +0 -237
- package/doc/CommandLineMigration.md +0 -58
- package/doc/ErrorMessages.md +0 -175
- package/doc/FioriAnnotations.md +0 -94
- package/doc/ODataTransformation.md +0 -273
- package/lib/backends.js +0 -529
- package/lib/checks/unknownMagic.js +0 -41
- package/lib/fix_antlr4-8_warning.js +0 -56
package/lib/compiler/extend.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
const { searchName, weakLocation } = require('../base/messages');
|
|
9
9
|
const {
|
|
10
|
-
isDeprecatedEnabled,
|
|
11
|
-
forEachGeneric, forEachInOrder,
|
|
10
|
+
isDeprecatedEnabled,
|
|
11
|
+
forEachGeneric, forEachInOrder, forEachDefinition,
|
|
12
12
|
} = require('../base/model');
|
|
13
13
|
const { dictAdd } = require('../base/dictionaries');
|
|
14
14
|
const { kindProperties, dictKinds } = require('./base');
|
|
@@ -33,6 +33,7 @@ function extend( model ) {
|
|
|
33
33
|
const {
|
|
34
34
|
resolvePath,
|
|
35
35
|
resolveUncheckedPath,
|
|
36
|
+
checkAnnotate,
|
|
36
37
|
defineAnnotations,
|
|
37
38
|
attachAndEmitValidNames,
|
|
38
39
|
checkDefinitions,
|
|
@@ -47,15 +48,16 @@ function extend( model ) {
|
|
|
47
48
|
|
|
48
49
|
applyExtensions();
|
|
49
50
|
|
|
50
|
-
const commonLanguagesEntity
|
|
51
|
-
|
|
52
|
-
model.definitions['sap.common.Languages'];
|
|
51
|
+
const commonLanguagesEntity = options.addTextsLanguageAssoc &&
|
|
52
|
+
model.definitions['sap.common.Languages'];
|
|
53
53
|
const addTextsLanguageAssoc = !!(commonLanguagesEntity && commonLanguagesEntity.elements &&
|
|
54
54
|
commonLanguagesEntity.elements.code);
|
|
55
55
|
Object.keys( model.definitions ).forEach( processArtifact );
|
|
56
56
|
|
|
57
57
|
lateExtensions( false );
|
|
58
58
|
|
|
59
|
+
compositionChildPersistence();
|
|
60
|
+
|
|
59
61
|
/**
|
|
60
62
|
* Process "composition of" artifacts.
|
|
61
63
|
*
|
|
@@ -88,6 +90,25 @@ function extend( model ) {
|
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Copy `@cds.persistence.skip` and `@cds.persistence.skip` from parent to child
|
|
95
|
+
* for managed compositions. This needs to be done after extensions, i.e. annotations,
|
|
96
|
+
* have been applied or `annotate E.comp` would not have an effect on `E.comp.subComp`.
|
|
97
|
+
*/
|
|
98
|
+
function compositionChildPersistence() {
|
|
99
|
+
const processed = new WeakSet();
|
|
100
|
+
forEachDefinition(model, processCompositionPersistence);
|
|
101
|
+
|
|
102
|
+
function processCompositionPersistence(def) {
|
|
103
|
+
if (def.$inferred === 'composition-entity' && !processed.has(def)) {
|
|
104
|
+
if (def._parent)
|
|
105
|
+
processCompositionPersistence(def._parent);
|
|
106
|
+
copyPersistenceAnnotations(def, def._parent, options);
|
|
107
|
+
processed.add(def);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
91
112
|
// extend ------------------------------------------------------------------
|
|
92
113
|
|
|
93
114
|
/**
|
|
@@ -159,6 +180,10 @@ function extend( model ) {
|
|
|
159
180
|
checkDefinitions( ext, art, 'actions');
|
|
160
181
|
checkDefinitions( ext, art, 'params');
|
|
161
182
|
checkDefinitions( ext, art, 'columns');
|
|
183
|
+
if (ext.includes)
|
|
184
|
+
applyIncludes( ext, art ); // emits error if `includes` is set
|
|
185
|
+
if (ext.kind === 'annotate')
|
|
186
|
+
checkAnnotate( ext, art );
|
|
162
187
|
defineAnnotations( ext, art, ext._block, ext.kind );
|
|
163
188
|
}
|
|
164
189
|
return true;
|
|
@@ -218,6 +243,8 @@ function extend( model ) {
|
|
|
218
243
|
art.includes = [ ...ext.includes ];
|
|
219
244
|
applyIncludes( ext, art );
|
|
220
245
|
}
|
|
246
|
+
if (ext.kind === 'annotate')
|
|
247
|
+
checkAnnotate( ext, art );
|
|
221
248
|
defineAnnotations( ext, art, ext._block, ext.kind );
|
|
222
249
|
// TODO: do we allow to add elements with array of {...}? If yes, adapt
|
|
223
250
|
initMembers( ext, art, ext._block ); // might set _extend, _annotate
|
|
@@ -369,9 +396,13 @@ function extend( model ) {
|
|
|
369
396
|
for (const ext of exts) {
|
|
370
397
|
delete ext.name.path[0]._artifact; // get message for root
|
|
371
398
|
// TODO: make resolvePath('extend'/'annotate') ignore namespaces
|
|
372
|
-
|
|
399
|
+
// Don't try to apply annotations in the `localized.` namespace.
|
|
400
|
+
// That's done in `localized.js`.
|
|
401
|
+
if (!name.startsWith('localized.') &&
|
|
402
|
+
resolvePath( ext.name, ext.kind, ext )) { // should issue error/info
|
|
373
403
|
// should issue error for cds extensions (annotate ok)
|
|
374
404
|
if (art.kind === 'namespace') {
|
|
405
|
+
// TODO: Emit error if namespace is extended by non-definitions.
|
|
375
406
|
info( 'anno-namespace', [ ext.name.location, ext ], {},
|
|
376
407
|
'Namespaces can\'t be annotated' );
|
|
377
408
|
}
|
|
@@ -431,6 +462,12 @@ function extend( model ) {
|
|
|
431
462
|
* @param {XSN.Artifact} art
|
|
432
463
|
*/
|
|
433
464
|
function applyIncludes( ext, art ) {
|
|
465
|
+
if (kindProperties[art.kind].include !== true) {
|
|
466
|
+
error('extend-unexpected-include', [ ext.includes[0]?.location, ext ], { kind: art.kind },
|
|
467
|
+
'Can\'t extend $(KIND) with includes');
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
434
471
|
if (!art._ancestors)
|
|
435
472
|
setLink( art, '_ancestors', [] ); // recursive array of includes
|
|
436
473
|
for (const ref of ext.includes) {
|
|
@@ -492,17 +529,16 @@ function extend( model ) {
|
|
|
492
529
|
const fioriAnno = art['@fiori.draft.enabled'];
|
|
493
530
|
const fioriEnabled = fioriAnno && (fioriAnno.val === undefined || fioriAnno.val);
|
|
494
531
|
|
|
495
|
-
const textsName =
|
|
496
|
-
? `${ art.name.absolute }_texts`
|
|
497
|
-
: `${ art.name.absolute }.texts`;
|
|
532
|
+
const textsName = `${ art.name.absolute }.texts`;
|
|
498
533
|
const textsEntity = model.definitions[textsName];
|
|
499
534
|
const localized = localizedData( art, textsEntity, fioriEnabled );
|
|
500
535
|
if (!localized)
|
|
501
536
|
return;
|
|
502
537
|
if (textsEntity) // expanded localized data in source
|
|
503
538
|
return; // -> make it idempotent
|
|
504
|
-
createTextsEntity( art, textsName, localized, fioriEnabled );
|
|
539
|
+
const newTextsEntity = createTextsEntity( art, textsName, localized, fioriEnabled );
|
|
505
540
|
addTextsAssociations( art, textsName, localized );
|
|
541
|
+
copyPersistenceAnnotations(newTextsEntity, art, options);
|
|
506
542
|
}
|
|
507
543
|
|
|
508
544
|
/**
|
|
@@ -636,8 +672,6 @@ function extend( model ) {
|
|
|
636
672
|
};
|
|
637
673
|
dictAdd( art.elements, 'ID_texts', textId );
|
|
638
674
|
}
|
|
639
|
-
if (isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ))
|
|
640
|
-
setLink( art, '_base', base );
|
|
641
675
|
|
|
642
676
|
dictAdd( art.elements, 'locale', locale );
|
|
643
677
|
if (addTextsLanguageAssoc) {
|
|
@@ -695,6 +729,8 @@ function extend( model ) {
|
|
|
695
729
|
}
|
|
696
730
|
if (fioriEnabled)
|
|
697
731
|
annotateWith( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
|
|
732
|
+
|
|
733
|
+
return art;
|
|
698
734
|
}
|
|
699
735
|
|
|
700
736
|
/**
|
|
@@ -808,9 +844,7 @@ function extend( model ) {
|
|
|
808
844
|
target = resolvePath( origin.targetAspect, 'compositionTarget', origin );
|
|
809
845
|
if (!target || !target.elements)
|
|
810
846
|
return;
|
|
811
|
-
const entityName =
|
|
812
|
-
? `${ base.name.absolute }_${ elem.name.id }`
|
|
813
|
-
: `${ base.name.absolute }.${ elem.name.id }`;
|
|
847
|
+
const entityName = `${ base.name.absolute }.${ elem.name.id }`;
|
|
814
848
|
const entity = allowAspectComposition( target, elem, keys, entityName ) &&
|
|
815
849
|
createTargetEntity( target, elem, keys, entityName, base );
|
|
816
850
|
elem.target = {
|
|
@@ -931,7 +965,7 @@ function extend( model ) {
|
|
|
931
965
|
// By default, 'up_' is a managed primary key association.
|
|
932
966
|
// If 'up_' shall be rendered unmanaged, infer the parent
|
|
933
967
|
// primary keys and add the ON condition
|
|
934
|
-
if (isDeprecatedEnabled( options, '
|
|
968
|
+
if (isDeprecatedEnabled( options, '_unmanagedUpInComponent' )) {
|
|
935
969
|
addProxyElements( art, keys, 'aspect-composition', target.name && location,
|
|
936
970
|
'up__', '@odata.containment.ignore' );
|
|
937
971
|
up.on = augmentEqual( location, 'up_', Object.values( keys ), 'up__' );
|
|
@@ -942,8 +976,6 @@ function extend( model ) {
|
|
|
942
976
|
// even if target cardinality is 1..1
|
|
943
977
|
up.notNull = { location, val: true };
|
|
944
978
|
}
|
|
945
|
-
if (isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ))
|
|
946
|
-
setLink( art, '_base', base._base || base );
|
|
947
979
|
|
|
948
980
|
dictAdd( art.elements, 'up_', up);
|
|
949
981
|
addProxyElements( art, target.elements, 'aspect-composition', target.name && location );
|
|
@@ -951,6 +983,10 @@ function extend( model ) {
|
|
|
951
983
|
setLink( art, '_block', model.$internal );
|
|
952
984
|
model.definitions[entityName] = art;
|
|
953
985
|
initArtifact( art );
|
|
986
|
+
|
|
987
|
+
// Copy persistence annotations from aspect.
|
|
988
|
+
copyPersistenceAnnotations(art, target, options);
|
|
989
|
+
|
|
954
990
|
return art;
|
|
955
991
|
}
|
|
956
992
|
|
|
@@ -972,6 +1008,27 @@ function extend( model ) {
|
|
|
972
1008
|
}
|
|
973
1009
|
}
|
|
974
1010
|
|
|
1011
|
+
/**
|
|
1012
|
+
* Copy the annotations `@cds.persistence.skip`/`@cds.persistence.exists` from
|
|
1013
|
+
* source to target if present on source but not target.
|
|
1014
|
+
*
|
|
1015
|
+
* @param {object} target
|
|
1016
|
+
* @param {object} source
|
|
1017
|
+
* @param {CSN.Options} options
|
|
1018
|
+
*/
|
|
1019
|
+
function copyPersistenceAnnotations(target, source, options) {
|
|
1020
|
+
if (!source)
|
|
1021
|
+
return;
|
|
1022
|
+
// Copy @cds.persistence.skip/exists annotation.
|
|
1023
|
+
const noCopyExists = isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
|
|
1024
|
+
const existsAnno = '@cds.persistence.exists';
|
|
1025
|
+
const skipAnno = '@cds.persistence.skip';
|
|
1026
|
+
if (!noCopyExists && source[existsAnno] && !target[existsAnno])
|
|
1027
|
+
target[existsAnno] = source[existsAnno];
|
|
1028
|
+
if (source[skipAnno] && !target[skipAnno])
|
|
1029
|
+
target[skipAnno] = source[skipAnno];
|
|
1030
|
+
}
|
|
1031
|
+
|
|
975
1032
|
function augmentEqual( location, assocname, relations, prefix = '' ) {
|
|
976
1033
|
const args = relations.map( eq );
|
|
977
1034
|
return (args.length === 1)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
+
const { dictAddArray } = require('../base/dictionaries');
|
|
5
6
|
const { forEachGeneric, forEachMember } = require('../base/model');
|
|
6
7
|
const { setLink, setArtifactLink } = require('./utils');
|
|
7
8
|
|
|
@@ -33,7 +34,7 @@ function finalizeParseCdl( model ) {
|
|
|
33
34
|
for (const ext of extensionsDict[name]) {
|
|
34
35
|
ext.name.absolute = resolveUncheckedPath( ext.name, 'extend', ext );
|
|
35
36
|
// Define annotations of this top-level extension
|
|
36
|
-
defineAnnotations( ext, ext, ext._block );
|
|
37
|
+
defineAnnotations( ext, ext, ext._block, 'extend' );
|
|
37
38
|
mergeAnnotatesForSameArtifact( ext );
|
|
38
39
|
// Initialize members and define annotations in sub-elements.
|
|
39
40
|
initMembers( ext, ext, ext._block, true );
|
|
@@ -81,8 +82,12 @@ function finalizeParseCdl( model ) {
|
|
|
81
82
|
for (const include of artifact.includes || [])
|
|
82
83
|
resolveUncheckedPath(include, 'include', main);
|
|
83
84
|
|
|
85
|
+
// define.js takes care that `target` is a ref and
|
|
86
|
+
// `targetAspect` is a structure.
|
|
84
87
|
if (artifact.target)
|
|
85
88
|
resolveUncheckedPath(artifact.target, 'target', main);
|
|
89
|
+
if (artifact.targetAspect)
|
|
90
|
+
resolveTypesForParseCdl(artifact.targetAspect, main);
|
|
86
91
|
|
|
87
92
|
if (artifact.from) {
|
|
88
93
|
const { from } = artifact;
|
|
@@ -92,12 +97,6 @@ function finalizeParseCdl( model ) {
|
|
|
92
97
|
resolveTypesForParseCdl(from, main);
|
|
93
98
|
}
|
|
94
99
|
|
|
95
|
-
if (artifact.targetAspect) {
|
|
96
|
-
if (artifact.targetAspect.path)
|
|
97
|
-
resolveUncheckedPath(artifact.targetAspect, 'target', main);
|
|
98
|
-
resolveTypesForParseCdl(artifact.targetAspect, main);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
100
|
// Recursively go through all XSN properties. There are a few that need to be
|
|
102
101
|
// handled specifically. Refer to the code below this loop for details.
|
|
103
102
|
for (const prop in artifact) {
|
|
@@ -157,8 +156,6 @@ function finalizeParseCdl( model ) {
|
|
|
157
156
|
* @param {XSN.Artifact} user
|
|
158
157
|
*/
|
|
159
158
|
function resolveTypeUnchecked(artWithType, user) {
|
|
160
|
-
if (!artWithType.type)
|
|
161
|
-
return;
|
|
162
159
|
const root = artWithType.type.path && artWithType.type.path[0];
|
|
163
160
|
if (!root) // parse error
|
|
164
161
|
return;
|
|
@@ -186,7 +183,7 @@ function finalizeParseCdl( model ) {
|
|
|
186
183
|
let struct = artWithType;
|
|
187
184
|
while (struct.kind === 'element')
|
|
188
185
|
struct = struct._parent;
|
|
189
|
-
if (struct.kind === 'select' || struct !== user._main) {
|
|
186
|
+
if (struct.kind === 'select' || struct.kind === 'annotation' || struct !== user._main) {
|
|
190
187
|
message( 'type-unexpected-typeof', [ artWithType.type.location, user ],
|
|
191
188
|
{ keyword: 'type of', '#': struct.kind } );
|
|
192
189
|
return;
|
|
@@ -221,17 +218,27 @@ function finalizeParseCdl( model ) {
|
|
|
221
218
|
|
|
222
219
|
forEachMember(ext, sub => mergeAnnotatesForSameArtifact(sub));
|
|
223
220
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
221
|
+
// do not do a complex merge:
|
|
222
|
+
if (isComplexExtension( ext ) ||
|
|
223
|
+
!Array.isArray( ext.$duplicates ) || ext.$duplicates.some( isComplexExtension ))
|
|
224
|
+
return;
|
|
225
|
+
for (const dup of ext.$duplicates) {
|
|
226
|
+
for (const prop in dup) {
|
|
227
|
+
if (prop.charAt(0) === '@')
|
|
228
|
+
dictAddArray( ext, prop, dup[prop] );
|
|
229
229
|
}
|
|
230
|
-
ext.$duplicates = ext.$duplicates.filter(val => (val.kind !== 'annotate'));
|
|
231
|
-
if (ext.$duplicates.length === 0)
|
|
232
|
-
delete ext.$duplicates;
|
|
233
230
|
}
|
|
231
|
+
delete ext.$duplicates;
|
|
234
232
|
}
|
|
235
233
|
}
|
|
236
234
|
|
|
235
|
+
/**
|
|
236
|
+
* We only de-duplicate an extend/annotate `ext` in function
|
|
237
|
+
* mergeAnnotatesForSameArtifact() if the extend/annotate is simple, i.e. has
|
|
238
|
+
* no members like elements.
|
|
239
|
+
*/
|
|
240
|
+
function isComplexExtension( ext ) {
|
|
241
|
+
return ext.kind !== 'annotate' || ext.elements || ext.parameters || ext.actions;
|
|
242
|
+
}
|
|
243
|
+
|
|
237
244
|
module.exports = finalizeParseCdl;
|
package/lib/compiler/index.js
CHANGED
|
@@ -33,6 +33,7 @@ const check = require('./checks');
|
|
|
33
33
|
|
|
34
34
|
const { emptyWeakLocation } = require('../base/location');
|
|
35
35
|
const { createMessageFunctions, deduplicateMessages } = require('../base/messages');
|
|
36
|
+
const { checkRemovedDeprecatedFlags } = require('../base/model');
|
|
36
37
|
const { promiseAllDoNotRejectImmediately } = require('../base/node-helpers');
|
|
37
38
|
const { cdsFs } = require('../utils/file');
|
|
38
39
|
|
|
@@ -83,6 +84,8 @@ function parseX( source, filename, options = {}, messageFunctions = null ) {
|
|
|
83
84
|
return parseCsn.parse( source, filename, options, messageFunctions );
|
|
84
85
|
if (options.fallbackParser) // any other value: like 'cdl' (historic reasons)
|
|
85
86
|
return parseLanguage( source, filename, options, messageFunctions );
|
|
87
|
+
if (source.startsWith('{')) // Source may be JSON.
|
|
88
|
+
return parseCsn.parse( source, filename, options, messageFunctions );
|
|
86
89
|
|
|
87
90
|
const model = { location: { file: filename } };
|
|
88
91
|
messageFunctions.error( 'file-unknown-ext', emptyWeakLocation(filename),
|
|
@@ -224,6 +227,7 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
|
|
|
224
227
|
* @param {string} [dir=""] Base directory. All files are resolved relatively
|
|
225
228
|
* to this directory
|
|
226
229
|
* @param {object} [options={}] Compilation options.
|
|
230
|
+
* @param {object} [fileCache]
|
|
227
231
|
* @returns {XSN.Model} Augmented CSN
|
|
228
232
|
*/
|
|
229
233
|
function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.create(null) ) {
|
|
@@ -235,7 +239,7 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
|
|
|
235
239
|
const model = { sources: a.sources, options };
|
|
236
240
|
model.$messageFunctions = createMessageFunctions( options, 'compile', model );
|
|
237
241
|
|
|
238
|
-
|
|
242
|
+
const asts = [];
|
|
239
243
|
const errors = [];
|
|
240
244
|
a.files.forEach( val => readAndParseSync( val, (err, ast) => {
|
|
241
245
|
if (err)
|
|
@@ -253,17 +257,16 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
|
|
|
253
257
|
if (!options.parseOnly && !options.parseCdl) {
|
|
254
258
|
while (asts.length) {
|
|
255
259
|
const fileNames = readDependenciesSync( asts );
|
|
256
|
-
asts =
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
fileNames.forEach( (fileName) => {
|
|
260
|
+
asts.length = 0;
|
|
261
|
+
// Push dependencies to `ast`. Only works because readAndParseSync() is synchronous.
|
|
262
|
+
for (const fileName of fileNames) {
|
|
260
263
|
readAndParseSync(fileName, ( err, ast ) => {
|
|
261
264
|
if (err)
|
|
262
265
|
throw err;
|
|
263
266
|
if (ast)
|
|
264
267
|
asts.push( ast );
|
|
265
268
|
});
|
|
266
|
-
}
|
|
269
|
+
}
|
|
267
270
|
}
|
|
268
271
|
}
|
|
269
272
|
|
|
@@ -378,6 +381,15 @@ function compileSourcesX( sourcesDict, options = {} ) {
|
|
|
378
381
|
ast.location = { file: filename };
|
|
379
382
|
assertConsistency( ast, options );
|
|
380
383
|
}
|
|
384
|
+
|
|
385
|
+
for (const dep of sources[filename].dependencies || []) {
|
|
386
|
+
if (!dep.realname) {
|
|
387
|
+
// `realname` is used by setLayers(). For compileSources(), we don't resolve
|
|
388
|
+
// the USING paths and use the literal instead, which may be part of the
|
|
389
|
+
// source dictionary.
|
|
390
|
+
dep.realname = dep.val;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
381
393
|
}
|
|
382
394
|
moduleLayers.setLayers( sources );
|
|
383
395
|
|
|
@@ -391,14 +403,15 @@ function compileSourcesX( sourcesDict, options = {} ) {
|
|
|
391
403
|
* @param {object} options Options
|
|
392
404
|
* @returns {object} XSN
|
|
393
405
|
*
|
|
394
|
-
* TODO:
|
|
406
|
+
* TODO: probably issue message api-recompiled-csn there.
|
|
395
407
|
*/
|
|
396
408
|
function recompileX( csn, options ) {
|
|
397
|
-
options = { ...options, parseCdl: false, $recompile: true };
|
|
398
|
-
// TODO: $recompile: true should be enough
|
|
399
409
|
// Explicitly set parseCdl to false because backends cannot handle it
|
|
400
|
-
|
|
410
|
+
options = { ...options, parseCdl: false, $recompile: true };
|
|
411
|
+
// Reset csnFlavor: Use client style (default)
|
|
412
|
+
delete options.csnFlavor;
|
|
401
413
|
delete options.toCsn;
|
|
414
|
+
// TODO: $recompile: true should be enough
|
|
402
415
|
|
|
403
416
|
const file = csn.$location && csn.$location.file &&
|
|
404
417
|
csn.$location.file.replace(/[.]cds$/, '.cds.csn') || '<recompile>.csn';
|
|
@@ -428,6 +441,9 @@ function compileDoX( model ) {
|
|
|
428
441
|
const { throwWithError } = model.$messageFunctions;
|
|
429
442
|
if (!options.testMode)
|
|
430
443
|
model.meta = {}; // provide initial central meta object
|
|
444
|
+
|
|
445
|
+
checkRemovedDeprecatedFlags( options, model.$messageFunctions );
|
|
446
|
+
|
|
431
447
|
if (options.parseOnly) {
|
|
432
448
|
throwWithError();
|
|
433
449
|
return model;
|
|
@@ -500,7 +516,7 @@ function processFilenamesSync( filenames, dir ) {
|
|
|
500
516
|
// Resolve possible symbolic link; if the file does not exist
|
|
501
517
|
// we just continue using the original name because readFile()
|
|
502
518
|
// already handles non-existent files.
|
|
503
|
-
name = fs.realpathSync(name);
|
|
519
|
+
name = fs.realpathSync.native(name);
|
|
504
520
|
}
|
|
505
521
|
catch (e) {
|
|
506
522
|
// Ignore the not-found (ENOENT) error
|
|
@@ -53,6 +53,12 @@ function layer( art ) {
|
|
|
53
53
|
return art && art._layerRepresentative;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
function realname( art ) {
|
|
57
|
+
while (art && art.kind !== 'source')
|
|
58
|
+
art = art._block;
|
|
59
|
+
return art && art.realname || '';
|
|
60
|
+
}
|
|
61
|
+
|
|
56
62
|
function compareLayer( a, b ) {
|
|
57
63
|
while (a && a.kind !== 'source')
|
|
58
64
|
a = a._block;
|
|
@@ -64,5 +70,6 @@ function compareLayer( a, b ) {
|
|
|
64
70
|
module.exports = {
|
|
65
71
|
setLayers,
|
|
66
72
|
layer,
|
|
73
|
+
realname,
|
|
67
74
|
compareLayer,
|
|
68
75
|
};
|
package/lib/compiler/populate.js
CHANGED
|
@@ -73,22 +73,16 @@ function populate( model ) {
|
|
|
73
73
|
/** @type {any} may also be a boolean */
|
|
74
74
|
let newAutoExposed = [];
|
|
75
75
|
|
|
76
|
-
// behavior depending on option `deprecated`:
|
|
77
|
-
const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
|
|
78
|
-
// TODO: we should get rid of noElementsExpansion soon; both
|
|
79
|
-
// beta.nestedProjections and beta.universalCsn do not work with it.
|
|
80
76
|
const scopedRedirections
|
|
81
|
-
=
|
|
82
|
-
!isDeprecatedEnabled( options, '
|
|
83
|
-
!isDeprecatedEnabled( options, '
|
|
84
|
-
!isDeprecatedEnabled( options, '
|
|
85
|
-
!isDeprecatedEnabled( options, 'noInheritedAutoexposeViaComposition' ) &&
|
|
86
|
-
!isDeprecatedEnabled( options, 'noScopedRedirections' );
|
|
77
|
+
= !isDeprecatedEnabled( options, '_shortAutoexposed' ) &&
|
|
78
|
+
!isDeprecatedEnabled( options, '_longAutoexposed' ) &&
|
|
79
|
+
!isDeprecatedEnabled( options, '_noInheritedAutoexposeViaComposition' ) &&
|
|
80
|
+
!isDeprecatedEnabled( options, '_noScopedRedirections' );
|
|
87
81
|
const autoexposeViaComposition
|
|
88
|
-
= (isDeprecatedEnabled( options, '
|
|
82
|
+
= (isDeprecatedEnabled( options, '_noInheritedAutoexposeViaComposition' ))
|
|
89
83
|
? 'Composition'
|
|
90
84
|
: true;
|
|
91
|
-
const redirectInSubQueries = isDeprecatedEnabled( options, '
|
|
85
|
+
const redirectInSubQueries = isDeprecatedEnabled( options, '_redirectInSubQueries' );
|
|
92
86
|
|
|
93
87
|
forEachDefinition( model, traverseElementEnvironments );
|
|
94
88
|
while (newAutoExposed.length) {
|
|
@@ -167,7 +161,7 @@ function populate( model ) {
|
|
|
167
161
|
// console.log(message( null, art.location, art, {}, 'Info','FT').toString())
|
|
168
162
|
const chain = [];
|
|
169
163
|
while (art && !('_effectiveType' in art) &&
|
|
170
|
-
(art.type || art._origin || art.value
|
|
164
|
+
(art.type || art._origin || art.value?.path || art.value?.type) &&
|
|
171
165
|
// TODO: really stop at art.enum? See #8942
|
|
172
166
|
!art.target && !art.enum && !art.elements && !art.items) {
|
|
173
167
|
chain.push( art );
|
|
@@ -197,7 +191,7 @@ function populate( model ) {
|
|
|
197
191
|
let eType = art;
|
|
198
192
|
if (eType._outer)
|
|
199
193
|
eType = effectiveType( eType._outer );
|
|
200
|
-
// collect the "latest" cardinality (calculate
|
|
194
|
+
// collect the "latest" cardinality (calculate lazily if necessary)
|
|
201
195
|
let cardinality = art.cardinality ||
|
|
202
196
|
art._effectiveType && (() => getCardinality( art._effectiveType ));
|
|
203
197
|
let prev = art;
|
|
@@ -224,10 +218,12 @@ function populate( model ) {
|
|
|
224
218
|
return art._origin;
|
|
225
219
|
if (art.type)
|
|
226
220
|
return resolveType( art.type, art );
|
|
221
|
+
if (art.value?.type)
|
|
222
|
+
return resolveType( art.value.type, art );
|
|
227
223
|
// console.log( 'EXPR-IN', art.kind, refString(art.name) )
|
|
228
224
|
if (!art._main || !art.value || !art.value.path)
|
|
229
225
|
return undefined;
|
|
230
|
-
if (art._pathHead && art.value) {
|
|
226
|
+
if (art._pathHead && art.value.path) {
|
|
231
227
|
setLink( art, '_origin', resolvePath( art.value, 'expr', art, null ) );
|
|
232
228
|
return art._origin;
|
|
233
229
|
}
|
|
@@ -248,7 +244,9 @@ function populate( model ) {
|
|
|
248
244
|
let struct = user;
|
|
249
245
|
while (struct.kind === 'element')
|
|
250
246
|
struct = struct._parent;
|
|
251
|
-
if (struct.kind === 'select') {
|
|
247
|
+
if (struct.kind === 'select' || struct.kind === 'annotation') {
|
|
248
|
+
// `type of` in annotation definitions can't work, because csn type refs
|
|
249
|
+
// always refer to definitions.
|
|
252
250
|
message( 'type-unexpected-typeof', [ ref.location, user ],
|
|
253
251
|
{ keyword: 'type of', '#': struct.kind } );
|
|
254
252
|
// we actually refer to an element in _combined; TODO: return null if
|
|
@@ -295,7 +293,7 @@ function populate( model ) {
|
|
|
295
293
|
|
|
296
294
|
|
|
297
295
|
function expandItems( art, origin, eType ) {
|
|
298
|
-
if (
|
|
296
|
+
if (art.items)
|
|
299
297
|
return false;
|
|
300
298
|
if (isInParents( art, eType )) {
|
|
301
299
|
art.items = 0; // circular
|
|
@@ -312,8 +310,6 @@ function populate( model ) {
|
|
|
312
310
|
}
|
|
313
311
|
|
|
314
312
|
function expandElements( art, struct, eType ) {
|
|
315
|
-
if (!enableExpandElements)
|
|
316
|
-
return false;
|
|
317
313
|
if (art.elements || art.kind === '$tableAlias' ||
|
|
318
314
|
// no element expansions for "non-proper" types like
|
|
319
315
|
// entities (as parameter types) etc:
|
|
@@ -347,7 +343,7 @@ function populate( model ) {
|
|
|
347
343
|
}
|
|
348
344
|
|
|
349
345
|
function expandEnum( art, origin ) {
|
|
350
|
-
if (
|
|
346
|
+
if (art.enum)
|
|
351
347
|
return false;
|
|
352
348
|
const ref = art.type || art.value || art.name;
|
|
353
349
|
const location = weakLocation( ref && ref.location || art.location );
|
|
@@ -469,14 +465,11 @@ function populate( model ) {
|
|
|
469
465
|
}
|
|
470
466
|
}
|
|
471
467
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
error( 'query-unspecified-element', [ selem.name.location, selem ], { id },
|
|
478
|
-
'Element $(ID) does not result from the query' );
|
|
479
|
-
}
|
|
468
|
+
for (const id in art.elements$) {
|
|
469
|
+
const selem = art.elements$[id]; // specified element
|
|
470
|
+
if (!selem.$replacement) {
|
|
471
|
+
error( 'query-unspecified-element', [ selem.name.location, selem ], { id },
|
|
472
|
+
'Element $(ID) does not result from the query' );
|
|
480
473
|
}
|
|
481
474
|
}
|
|
482
475
|
}
|
|
@@ -490,8 +483,6 @@ function populate( model ) {
|
|
|
490
483
|
forEachGeneric( query, '$tableAliases', resolveTabRef );
|
|
491
484
|
|
|
492
485
|
initFromColumns( query, query.columns );
|
|
493
|
-
// TODO: already in definer: complain about EXCLUDING with no wildcard
|
|
494
|
-
// (would have been automatically with a good CDL syntax: `* without (...)`)
|
|
495
486
|
if (query.excludingDict) {
|
|
496
487
|
for (const name in query.excludingDict)
|
|
497
488
|
resolveExcluding( name, query._combined, query.excludingDict, query );
|
|
@@ -1055,8 +1046,6 @@ function populate( model ) {
|
|
|
1055
1046
|
// which is not a context/service/namespace, or the definition itself.
|
|
1056
1047
|
// If inside service, it is the direct child of the (most inner) service.
|
|
1057
1048
|
function definitionScope( art ) {
|
|
1058
|
-
if (art._base) // with deprecated.generatedEntityNameWithUnderscore
|
|
1059
|
-
return art._base;
|
|
1060
1049
|
let base = art;
|
|
1061
1050
|
while (art._parent) {
|
|
1062
1051
|
if (art._parent.kind === 'service')
|
|
@@ -1112,7 +1101,7 @@ function populate( model ) {
|
|
|
1112
1101
|
return false;
|
|
1113
1102
|
}
|
|
1114
1103
|
// no @cds.autoexpose or @cds.autoexpose:null
|
|
1115
|
-
// TODO: introduce deprecated.
|
|
1104
|
+
// TODO: introduce deprecated._noInheritedAutoexposeViaComposition
|
|
1116
1105
|
art.$autoexpose = model.$compositionTargets[art.name.absolute]
|
|
1117
1106
|
? autoexposeViaComposition
|
|
1118
1107
|
: null;
|
|
@@ -1121,17 +1110,15 @@ function populate( model ) {
|
|
|
1121
1110
|
|
|
1122
1111
|
function autoExposedName( target, service, elemScope ) {
|
|
1123
1112
|
const { absolute } = target.name;
|
|
1124
|
-
if (isDeprecatedEnabled( options, '
|
|
1113
|
+
if (isDeprecatedEnabled( options, '_shortAutoexposed' )) {
|
|
1125
1114
|
const parent = definitionScope( target )._parent;
|
|
1126
1115
|
const name = (parent) ? absolute.substring( parent.name.absolute.length + 1 ) : absolute;
|
|
1127
|
-
// no need for dedot here (as opposed to deprecated.
|
|
1116
|
+
// no need for dedot here (as opposed to deprecated._longAutoexposed), as
|
|
1128
1117
|
// the name for dependent entities have already been created using `_` then
|
|
1129
1118
|
return `${ service.name.absolute }.${ name }`;
|
|
1130
1119
|
}
|
|
1131
|
-
if (isDeprecatedEnabled( options, '
|
|
1132
|
-
|
|
1133
|
-
return `${ service.name.absolute }.${ dedot ? absolute.replace( /\./g, '_' ) : absolute }`;
|
|
1134
|
-
}
|
|
1120
|
+
if (isDeprecatedEnabled( options, '_longAutoexposed' ))
|
|
1121
|
+
return `${ service.name.absolute }.${ absolute }`;
|
|
1135
1122
|
const base = definitionScope( target );
|
|
1136
1123
|
if (base === target)
|
|
1137
1124
|
return `${ service.name.absolute }.${ absolute.substring( absolute.lastIndexOf('.') + 1 ) }`;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Propagate properties in XSN
|
|
2
|
+
|
|
3
|
+
// See also internalDoc/PropagatedCsn.md.
|
|
4
|
+
// As opposed to that document, the propagator here works on XSN, not CSN.
|
|
5
|
+
// We also do not deep-copy member dictionaries here, but create proxy members
|
|
6
|
+
// which get their properties via propagation: we use function `onlyViaParent`
|
|
7
|
+
// if that property would not be propagated otherwise.
|
|
2
8
|
|
|
3
9
|
'use strict';
|
|
4
10
|
|
|
@@ -11,6 +17,7 @@ const {
|
|
|
11
17
|
const { setLink, linkToOrigin, withAssociation } = require('./utils');
|
|
12
18
|
// const { refString } = require( '../base/messages')
|
|
13
19
|
|
|
20
|
+
// Note that propagation here is also used for deep-copying (function `onlyViaParent`)
|
|
14
21
|
function propagate( model ) {
|
|
15
22
|
const props = {
|
|
16
23
|
'@com.sap.gtt.core.CoreModel.Indexable': never,
|
|
@@ -56,9 +63,8 @@ function propagate( model ) {
|
|
|
56
63
|
returns,
|
|
57
64
|
};
|
|
58
65
|
const { options } = model;
|
|
59
|
-
const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
|
|
60
66
|
// eslint-disable-next-line max-len
|
|
61
|
-
const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, '
|
|
67
|
+
const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, '_oldVirtualNotNullPropagation' );
|
|
62
68
|
|
|
63
69
|
forEachDefinition( model, run );
|
|
64
70
|
|
|
@@ -189,7 +195,7 @@ function propagate( model ) {
|
|
|
189
195
|
if (!type || type._main)
|
|
190
196
|
return false;
|
|
191
197
|
// We do not consider the $expand status, as elements are already expanded
|
|
192
|
-
// by the resolve()
|
|
198
|
+
// by the resolve()
|
|
193
199
|
run( type );
|
|
194
200
|
return type[prop];
|
|
195
201
|
}
|
|
@@ -313,11 +319,10 @@ function propagate( model ) {
|
|
|
313
319
|
|
|
314
320
|
function items( prop, target, source ) {
|
|
315
321
|
// usually considered expensive, except:
|
|
316
|
-
// - array of Entity
|
|
322
|
+
// - array of Entity
|
|
317
323
|
const line = availableAtType( prop, target, source );
|
|
318
324
|
if (!line ||
|
|
319
|
-
line.type && line.type._artifact && line.type._artifact.kind === 'entity'
|
|
320
|
-
!line.elements && !line.enum && !line.items && !enableExpandElements)
|
|
325
|
+
line.type && line.type._artifact && line.type._artifact.kind === 'entity')
|
|
321
326
|
returns( prop, target, source, true );
|
|
322
327
|
}
|
|
323
328
|
}
|