@sap/cds-compiler 2.15.4 → 3.0.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 +33 -1590
- package/bin/cdsc.js +36 -33
- package/doc/CHANGELOG_ARCHIVE.md +1592 -0
- package/doc/CHANGELOG_BETA.md +3 -4
- package/doc/CHANGELOG_DEPRECATED.md +35 -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 +220 -103
- package/lib/api/options.js +15 -85
- package/lib/api/validate.js +6 -10
- package/lib/base/keywords.js +216 -109
- package/lib/base/message-registry.js +60 -20
- package/lib/base/messages.js +65 -24
- package/lib/base/model.js +44 -2
- package/lib/checks/actionsFunctions.js +7 -5
- 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 +5 -1
- package/lib/checks/types.js +4 -2
- package/lib/checks/utils.js +2 -2
- package/lib/checks/validator.js +2 -1
- package/lib/compiler/assert-consistency.js +15 -10
- package/lib/compiler/builtins.js +87 -9
- package/lib/compiler/define.js +2 -2
- package/lib/compiler/extend.js +59 -11
- package/lib/compiler/finalize-parse-cdl.js +20 -9
- package/lib/compiler/index.js +25 -11
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +13 -13
- package/lib/compiler/propagator.js +3 -3
- package/lib/compiler/resolve.js +193 -218
- package/lib/compiler/shared.js +47 -76
- package/lib/compiler/tweak-assocs.js +9 -10
- package/lib/compiler/utils.js +5 -0
- package/lib/edm/csn2edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +25 -30
- package/lib/edm/edmUtils.js +10 -24
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +8 -30
- package/lib/gen/language.tokens +105 -114
- package/lib/gen/languageLexer.interp +1 -34
- package/lib/gen/languageLexer.js +889 -1007
- package/lib/gen/languageLexer.tokens +95 -106
- package/lib/gen/languageParser.js +20632 -22313
- package/lib/json/from-csn.js +56 -49
- package/lib/json/to-csn.js +10 -8
- 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 +303 -229
- package/lib/language/language.g4 +573 -629
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +27 -42
- package/lib/main.js +104 -81
- package/lib/model/csnRefs.js +1 -1
- package/lib/model/csnUtils.js +170 -283
- package/lib/model/revealInternalProperties.js +28 -8
- package/lib/model/sortViews.js +32 -31
- package/lib/optionProcessor.js +12 -21
- 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 +334 -339
- package/lib/render/toHdbcds.js +19 -15
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +53 -51
- package/lib/render/utils/common.js +15 -1
- 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/cdsPersistence.js +5 -15
- package/lib/transform/db/constraints.js +1 -1
- package/lib/transform/db/expansion.js +7 -6
- package/lib/transform/db/flattening.js +18 -19
- 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 +19 -22
- package/lib/transform/forOdataNew.js +10 -12
- package/lib/transform/localized.js +22 -16
- package/lib/transform/odata/toFinalBaseType.js +10 -10
- package/lib/transform/odata/typesExposure.js +3 -3
- 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 +3 -3
- package/lib/utils/timetrace.js +20 -21
- package/package.json +35 -4
- 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/fix_antlr4-8_warning.js +0 -56
package/lib/checks/validator.js
CHANGED
|
@@ -103,8 +103,9 @@ const commonMemberValidators
|
|
|
103
103
|
validateAssociationsInItems, checkForInvalidTarget,
|
|
104
104
|
checkVirtualElement, checkElementTypeDefinitionHasType ];
|
|
105
105
|
|
|
106
|
+
// TODO: checkManagedAssoc is a forEachMemberRecursively!
|
|
106
107
|
const commonArtifactValidators = [ checkTypeDefinitionHasType, checkPrimaryKey, checkManagedAssoc ];
|
|
107
|
-
|
|
108
|
+
// TODO: Does it make sense to run the on-condition check as part of a CSN validator?
|
|
108
109
|
const commonQueryValidators = [ validateMixinOnCondition ];
|
|
109
110
|
|
|
110
111
|
/**
|
|
@@ -118,8 +118,10 @@ function assertConsistency( model, stage ) {
|
|
|
118
118
|
'$sources',
|
|
119
119
|
],
|
|
120
120
|
},
|
|
121
|
-
location: {
|
|
122
|
-
|
|
121
|
+
location: {
|
|
122
|
+
// every thing with a $location in CSN must have a XSN location even
|
|
123
|
+
// with syntax errors (currently even internal artifacts like $using):
|
|
124
|
+
isRequired: parent => noSyntaxErrors() || parent && parent.kind,
|
|
123
125
|
kind: true,
|
|
124
126
|
requires: [ 'file' ], // line is optional in top-level location
|
|
125
127
|
optional: [ 'line', 'col', 'endLine', 'endCol', '$notFound' ],
|
|
@@ -185,12 +187,16 @@ function assertConsistency( model, stage ) {
|
|
|
185
187
|
// are missing the location property
|
|
186
188
|
test: isDictionary( definition ),
|
|
187
189
|
requires: [ 'kind', 'name' ],
|
|
188
|
-
optional: [
|
|
190
|
+
optional: [
|
|
191
|
+
'elements', '$autoElement', '$uncheckedElements',
|
|
192
|
+
'$requireElementAccess', '_effectiveType', '_deps',
|
|
193
|
+
],
|
|
189
194
|
schema: {
|
|
190
195
|
kind: { test: isString, enum: [ 'builtin' ] },
|
|
191
196
|
name: { test: isObject, requires: [ 'id', 'element' ] },
|
|
192
197
|
$autoElement: { test: isString },
|
|
193
198
|
$uncheckedElements: { test: isBoolean },
|
|
199
|
+
$requireElementAccess: { test: isBoolean },
|
|
194
200
|
// missing location for normal "elements"
|
|
195
201
|
elements: { test: TODO },
|
|
196
202
|
},
|
|
@@ -217,8 +223,7 @@ function assertConsistency( model, stage ) {
|
|
|
217
223
|
usings: {
|
|
218
224
|
test: isArray(),
|
|
219
225
|
requires: [ 'kind', 'location' ],
|
|
220
|
-
optional: [ 'name', 'extern', 'usings', '
|
|
221
|
-
// TODO: get rid of $annotations: []
|
|
226
|
+
optional: [ 'name', 'extern', 'usings', 'fileDep' ],
|
|
222
227
|
},
|
|
223
228
|
extern: {
|
|
224
229
|
requires: [ 'location', 'path' ],
|
|
@@ -304,7 +309,6 @@ function assertConsistency( model, stage ) {
|
|
|
304
309
|
kind: 'element',
|
|
305
310
|
test: isDictionary( definition ), // definition since redef
|
|
306
311
|
requires: [ 'location', 'name' ],
|
|
307
|
-
optional: [ '$annotations' ], // TODO: get rid of annos: []
|
|
308
312
|
},
|
|
309
313
|
orderBy: { inherits: 'value', test: isArray( expression ) },
|
|
310
314
|
sort: { test: locationVal( isString ), enum: [ 'asc', 'desc' ] },
|
|
@@ -449,11 +453,11 @@ function assertConsistency( model, stage ) {
|
|
|
449
453
|
'@': {
|
|
450
454
|
kind: true,
|
|
451
455
|
inherits: 'value',
|
|
452
|
-
optional: [ 'name', '_block', '$priority', '$
|
|
456
|
+
optional: [ 'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported' ],
|
|
453
457
|
// TODO: name requires if not in parser?
|
|
454
458
|
},
|
|
455
|
-
$priority: { test: TODO },
|
|
456
|
-
$annotations: { parser: true, kind: true, test: TODO },
|
|
459
|
+
$priority: { test: TODO },
|
|
460
|
+
$annotations: { parser: true, kind: true, test: TODO }, // deprecated, still there for cds-lsp
|
|
457
461
|
name: {
|
|
458
462
|
isRequired: stageParser && (() => false), // not required in parser
|
|
459
463
|
kind: true,
|
|
@@ -468,7 +472,7 @@ function assertConsistency( model, stage ) {
|
|
|
468
472
|
],
|
|
469
473
|
},
|
|
470
474
|
absolute: { test: isString },
|
|
471
|
-
variant: { test: TODO }, // TODO: not set in CDL parser
|
|
475
|
+
variant: { test: TODO }, // TODO: not set in CDL parser
|
|
472
476
|
element: { test: TODO }, // TODO: { test: isString },
|
|
473
477
|
action: { test: isString },
|
|
474
478
|
param: { test: isString },
|
|
@@ -581,6 +585,7 @@ function assertConsistency( model, stage ) {
|
|
|
581
585
|
// (it can contain the artifact itself with no/failed autoexposure):
|
|
582
586
|
_descendants: { kind: [ 'entity' ], test: isDictionary( isArray( TODO ) ) },
|
|
583
587
|
|
|
588
|
+
$errorReported: { parser: true, test: isBoolean }, // to avoid duplicate messages
|
|
584
589
|
$duplicates: { parser: true, kind: true, test: TODO }, // array of arts or true
|
|
585
590
|
$extension: { kind: true, test: TODO }, // TODO: introduce $applied instead or $status
|
|
586
591
|
$inferred: { parser: true, kind: true, test: isString },
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -76,19 +76,92 @@ const functionsWithoutParens = [
|
|
|
76
76
|
'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER',
|
|
77
77
|
];
|
|
78
78
|
|
|
79
|
-
const specialFunctions = {
|
|
79
|
+
const specialFunctions = compileFunctions( {
|
|
80
|
+
'': [ // the default
|
|
81
|
+
{
|
|
82
|
+
intro: [ 'ALL', 'DISTINCT' ],
|
|
83
|
+
introMsg: [], // do not list them in code completion
|
|
84
|
+
},
|
|
85
|
+
{},
|
|
86
|
+
],
|
|
80
87
|
ROUND: [
|
|
81
88
|
null, null, { // 3rd argument: rounding mode
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
ROUND_HALF_EVEN: 'argFull',
|
|
85
|
-
ROUND_UP: 'argFull',
|
|
86
|
-
ROUND_DOWN: 'argFull',
|
|
87
|
-
ROUND_CEILING: 'argFull',
|
|
88
|
-
ROUND_FLOOR: 'argFull',
|
|
89
|
+
expr: [ 'ROUND_HALF_UP', 'ROUND_HALF_DOWN', 'ROUND_HALF_EVEN',
|
|
90
|
+
'ROUND_UP', 'ROUND_DOWN', 'ROUND_CEILING', 'ROUND_FLOOR' ],
|
|
89
91
|
},
|
|
90
92
|
],
|
|
91
|
-
|
|
93
|
+
TRIM: [
|
|
94
|
+
{
|
|
95
|
+
intro: [ 'LEADING', 'TRAILING', 'BOTH' ],
|
|
96
|
+
expr: [ 'LEADING', 'TRAILING', 'BOTH' ],
|
|
97
|
+
separator: [ 'FROM' ],
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
EXTRACT: [
|
|
101
|
+
{
|
|
102
|
+
expr: [ 'YEAR', 'MONTH', 'DAY', 'HOUR', 'MINUTE', 'SECOND' ],
|
|
103
|
+
separator: [ 'FROM' ],
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
COUNT: [
|
|
107
|
+
{
|
|
108
|
+
expr: [ '*' ],
|
|
109
|
+
intro: [ 'ALL', 'DISTINCT' ],
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
MIN: 'COUNT',
|
|
113
|
+
MAX: 'COUNT',
|
|
114
|
+
SUM: 'COUNT',
|
|
115
|
+
AVG: 'COUNT',
|
|
116
|
+
STDDDEV: 'COUNT',
|
|
117
|
+
VAR: 'COUNT',
|
|
118
|
+
LOCATE_REGEXPR: [
|
|
119
|
+
{
|
|
120
|
+
intro: [ 'START', 'AFTER' ],
|
|
121
|
+
separator: [ 'FLAG', 'IN', 'FROM', 'OCCURRENCE', 'GROUP' ],
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
OCCURRENCES_REGEXPR: [
|
|
125
|
+
{
|
|
126
|
+
separator: [ 'FLAG', 'IN', 'FROM' ],
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
REPLACE_REGEXPR: [
|
|
130
|
+
{
|
|
131
|
+
separator: [ 'FLAG', 'IN', 'WITH', 'FROM', 'OCCURRENCE' ],
|
|
132
|
+
expr: [ 'ALL' ],
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
SUBSTRING_REGEXPR: [
|
|
136
|
+
{
|
|
137
|
+
separator: [ 'FLAG', 'IN', 'FROM', 'OCCURRENCE', 'GROUP' ],
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
} );
|
|
141
|
+
|
|
142
|
+
function compileFunctions( special ) {
|
|
143
|
+
const compiled = {};
|
|
144
|
+
for (const [ name, val ] of Object.entries( special ))
|
|
145
|
+
compiled[name] = (typeof val === 'string' ? special[val] : val).map( compileArg );
|
|
146
|
+
return compiled;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function compileArg( src ) {
|
|
150
|
+
if (!src)
|
|
151
|
+
return src;
|
|
152
|
+
const tgt = {
|
|
153
|
+
intro: src.intro || [],
|
|
154
|
+
expr: src.expr || [],
|
|
155
|
+
separator: src.separator || [],
|
|
156
|
+
};
|
|
157
|
+
for (const generic of [ 'intro', 'expr', 'separator' ]) { // intro before expr!
|
|
158
|
+
for (const token of src[generic] || [])
|
|
159
|
+
tgt[token] = generic;
|
|
160
|
+
}
|
|
161
|
+
if (tgt.intro) // same token could be in both 'expr' and 'intro':
|
|
162
|
+
tgt.introMsg = src.introMsg || tgt.intro.filter( token => tgt[token] === 'intro' );
|
|
163
|
+
return tgt;
|
|
164
|
+
}
|
|
92
165
|
|
|
93
166
|
/**
|
|
94
167
|
* Variables that have special meaning in CDL/CSN.
|
|
@@ -106,12 +179,15 @@ const magicVariables = {
|
|
|
106
179
|
elements: {
|
|
107
180
|
from: {}, to: {},
|
|
108
181
|
},
|
|
182
|
+
// Require that elements are accessed, i.e. no $at, only $at.<element>.
|
|
183
|
+
$requireElementAccess: true,
|
|
109
184
|
},
|
|
110
185
|
$now: {}, // Dito
|
|
111
186
|
$session: {
|
|
112
187
|
// In ABAP CDS session variables are accessed in a generic way via
|
|
113
188
|
// the pseudo variable $session.
|
|
114
189
|
$uncheckedElements: true,
|
|
190
|
+
$requireElementAccess: true,
|
|
115
191
|
},
|
|
116
192
|
};
|
|
117
193
|
|
|
@@ -286,6 +362,8 @@ function initBuiltins( model ) {
|
|
|
286
362
|
art.$autoElement = magic.$autoElement;
|
|
287
363
|
if (magic.$uncheckedElements)
|
|
288
364
|
art.$uncheckedElements = magic.$uncheckedElements;
|
|
365
|
+
if (magic.$requireElementAccess)
|
|
366
|
+
art.$requireElementAccess = magic.$requireElementAccess;
|
|
289
367
|
|
|
290
368
|
createMagicElements( art, magic.elements );
|
|
291
369
|
if (options.variableReplacements)
|
package/lib/compiler/define.js
CHANGED
|
@@ -505,13 +505,13 @@ function define( model ) {
|
|
|
505
505
|
initParentLink( parent, definitions );
|
|
506
506
|
}
|
|
507
507
|
if (art.kind !== 'namespace' &&
|
|
508
|
-
isDeprecatedEnabled( options, '
|
|
508
|
+
isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' )) {
|
|
509
509
|
let p = parent;
|
|
510
510
|
while (p && kindProperties[p.kind].artifacts)
|
|
511
511
|
p = p._parent;
|
|
512
512
|
if (p) {
|
|
513
513
|
error( 'subartifacts-not-supported', [ art.name.location, art ],
|
|
514
|
-
{ art: p, prop: 'deprecated.
|
|
514
|
+
{ art: p, prop: 'deprecated._generatedEntityNameWithUnderscore' },
|
|
515
515
|
// eslint-disable-next-line max-len
|
|
516
516
|
'With the option $(PROP), no sub artifact can be defined for a non-context/service $(ART)' );
|
|
517
517
|
}
|
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');
|
|
@@ -47,15 +47,16 @@ function extend( model ) {
|
|
|
47
47
|
|
|
48
48
|
applyExtensions();
|
|
49
49
|
|
|
50
|
-
const commonLanguagesEntity
|
|
51
|
-
|
|
52
|
-
model.definitions['sap.common.Languages'];
|
|
50
|
+
const commonLanguagesEntity = options.addTextsLanguageAssoc &&
|
|
51
|
+
model.definitions['sap.common.Languages'];
|
|
53
52
|
const addTextsLanguageAssoc = !!(commonLanguagesEntity && commonLanguagesEntity.elements &&
|
|
54
53
|
commonLanguagesEntity.elements.code);
|
|
55
54
|
Object.keys( model.definitions ).forEach( processArtifact );
|
|
56
55
|
|
|
57
56
|
lateExtensions( false );
|
|
58
57
|
|
|
58
|
+
compositionChildPersistence();
|
|
59
|
+
|
|
59
60
|
/**
|
|
60
61
|
* Process "composition of" artifacts.
|
|
61
62
|
*
|
|
@@ -88,6 +89,25 @@ function extend( model ) {
|
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Copy `@cds.persistence.skip` and `@cds.persistence.skip` from parent to child
|
|
94
|
+
* for managed compositions. This needs to be done after extensions, i.e. annotations,
|
|
95
|
+
* have been applied or `annotate E.comp` would not have an effect on `E.comp.subComp`.
|
|
96
|
+
*/
|
|
97
|
+
function compositionChildPersistence() {
|
|
98
|
+
const processed = new WeakSet();
|
|
99
|
+
forEachDefinition(model, processCompositionPersistence);
|
|
100
|
+
|
|
101
|
+
function processCompositionPersistence(def) {
|
|
102
|
+
if (def.$inferred === 'composition-entity' && !processed.has(def)) {
|
|
103
|
+
if (def._parent)
|
|
104
|
+
processCompositionPersistence(def._parent);
|
|
105
|
+
copyPersistenceAnnotations(def, def._parent, options);
|
|
106
|
+
processed.add(def);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
91
111
|
// extend ------------------------------------------------------------------
|
|
92
112
|
|
|
93
113
|
/**
|
|
@@ -492,7 +512,7 @@ function extend( model ) {
|
|
|
492
512
|
const fioriAnno = art['@fiori.draft.enabled'];
|
|
493
513
|
const fioriEnabled = fioriAnno && (fioriAnno.val === undefined || fioriAnno.val);
|
|
494
514
|
|
|
495
|
-
const textsName = (isDeprecatedEnabled( options, '
|
|
515
|
+
const textsName = (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
|
|
496
516
|
? `${ art.name.absolute }_texts`
|
|
497
517
|
: `${ art.name.absolute }.texts`;
|
|
498
518
|
const textsEntity = model.definitions[textsName];
|
|
@@ -501,8 +521,9 @@ function extend( model ) {
|
|
|
501
521
|
return;
|
|
502
522
|
if (textsEntity) // expanded localized data in source
|
|
503
523
|
return; // -> make it idempotent
|
|
504
|
-
createTextsEntity( art, textsName, localized, fioriEnabled );
|
|
524
|
+
const newTextsEntity = createTextsEntity( art, textsName, localized, fioriEnabled );
|
|
505
525
|
addTextsAssociations( art, textsName, localized );
|
|
526
|
+
copyPersistenceAnnotations(newTextsEntity, art, options);
|
|
506
527
|
}
|
|
507
528
|
|
|
508
529
|
/**
|
|
@@ -636,7 +657,7 @@ function extend( model ) {
|
|
|
636
657
|
};
|
|
637
658
|
dictAdd( art.elements, 'ID_texts', textId );
|
|
638
659
|
}
|
|
639
|
-
if (isDeprecatedEnabled( options, '
|
|
660
|
+
if (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
|
|
640
661
|
setLink( art, '_base', base );
|
|
641
662
|
|
|
642
663
|
dictAdd( art.elements, 'locale', locale );
|
|
@@ -695,6 +716,8 @@ function extend( model ) {
|
|
|
695
716
|
}
|
|
696
717
|
if (fioriEnabled)
|
|
697
718
|
annotateWith( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
|
|
719
|
+
|
|
720
|
+
return art;
|
|
698
721
|
}
|
|
699
722
|
|
|
700
723
|
/**
|
|
@@ -808,7 +831,7 @@ function extend( model ) {
|
|
|
808
831
|
target = resolvePath( origin.targetAspect, 'compositionTarget', origin );
|
|
809
832
|
if (!target || !target.elements)
|
|
810
833
|
return;
|
|
811
|
-
const entityName = (isDeprecatedEnabled( options, '
|
|
834
|
+
const entityName = (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
|
|
812
835
|
? `${ base.name.absolute }_${ elem.name.id }`
|
|
813
836
|
: `${ base.name.absolute }.${ elem.name.id }`;
|
|
814
837
|
const entity = allowAspectComposition( target, elem, keys, entityName ) &&
|
|
@@ -931,7 +954,7 @@ function extend( model ) {
|
|
|
931
954
|
// By default, 'up_' is a managed primary key association.
|
|
932
955
|
// If 'up_' shall be rendered unmanaged, infer the parent
|
|
933
956
|
// primary keys and add the ON condition
|
|
934
|
-
if (isDeprecatedEnabled( options, '
|
|
957
|
+
if (isDeprecatedEnabled( options, '_unmanagedUpInComponent' )) {
|
|
935
958
|
addProxyElements( art, keys, 'aspect-composition', target.name && location,
|
|
936
959
|
'up__', '@odata.containment.ignore' );
|
|
937
960
|
up.on = augmentEqual( location, 'up_', Object.values( keys ), 'up__' );
|
|
@@ -942,7 +965,7 @@ function extend( model ) {
|
|
|
942
965
|
// even if target cardinality is 1..1
|
|
943
966
|
up.notNull = { location, val: true };
|
|
944
967
|
}
|
|
945
|
-
if (isDeprecatedEnabled( options, '
|
|
968
|
+
if (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
|
|
946
969
|
setLink( art, '_base', base._base || base );
|
|
947
970
|
|
|
948
971
|
dictAdd( art.elements, 'up_', up);
|
|
@@ -951,6 +974,10 @@ function extend( model ) {
|
|
|
951
974
|
setLink( art, '_block', model.$internal );
|
|
952
975
|
model.definitions[entityName] = art;
|
|
953
976
|
initArtifact( art );
|
|
977
|
+
|
|
978
|
+
// Copy persistence annotations from aspect.
|
|
979
|
+
copyPersistenceAnnotations(art, target, options);
|
|
980
|
+
|
|
954
981
|
return art;
|
|
955
982
|
}
|
|
956
983
|
|
|
@@ -972,6 +999,27 @@ function extend( model ) {
|
|
|
972
999
|
}
|
|
973
1000
|
}
|
|
974
1001
|
|
|
1002
|
+
/**
|
|
1003
|
+
* Copy the annotations `@cds.persistence.skip`/`@cds.persistence.exists` from
|
|
1004
|
+
* source to target if present on source but not target.
|
|
1005
|
+
*
|
|
1006
|
+
* @param {object} target
|
|
1007
|
+
* @param {object} source
|
|
1008
|
+
* @param {CSN.Options} options
|
|
1009
|
+
*/
|
|
1010
|
+
function copyPersistenceAnnotations(target, source, options) {
|
|
1011
|
+
if (!source)
|
|
1012
|
+
return;
|
|
1013
|
+
// Copy @cds.persistence.skip/exists annotation.
|
|
1014
|
+
const noCopyExists = isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
|
|
1015
|
+
const existsAnno = '@cds.persistence.exists';
|
|
1016
|
+
const skipAnno = '@cds.persistence.skip';
|
|
1017
|
+
if (!noCopyExists && source[existsAnno] && !target[existsAnno])
|
|
1018
|
+
target[existsAnno] = source[existsAnno];
|
|
1019
|
+
if (source[skipAnno] && !target[skipAnno])
|
|
1020
|
+
target[skipAnno] = source[skipAnno];
|
|
1021
|
+
}
|
|
1022
|
+
|
|
975
1023
|
function augmentEqual( location, assocname, relations, prefix = '' ) {
|
|
976
1024
|
const args = relations.map( eq );
|
|
977
1025
|
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 );
|
|
@@ -221,17 +222,27 @@ function finalizeParseCdl( model ) {
|
|
|
221
222
|
|
|
222
223
|
forEachMember(ext, sub => mergeAnnotatesForSameArtifact(sub));
|
|
223
224
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
225
|
+
// do not do a complex merge:
|
|
226
|
+
if (isComplexExtension( ext ) ||
|
|
227
|
+
!Array.isArray( ext.$duplicates ) || ext.$duplicates.some( isComplexExtension ))
|
|
228
|
+
return;
|
|
229
|
+
for (const dup of ext.$duplicates) {
|
|
230
|
+
for (const prop in dup) {
|
|
231
|
+
if (prop.charAt(0) === '@')
|
|
232
|
+
dictAddArray( ext, prop, dup[prop] );
|
|
229
233
|
}
|
|
230
|
-
ext.$duplicates = ext.$duplicates.filter(val => (val.kind !== 'annotate'));
|
|
231
|
-
if (ext.$duplicates.length === 0)
|
|
232
|
-
delete ext.$duplicates;
|
|
233
234
|
}
|
|
235
|
+
delete ext.$duplicates;
|
|
234
236
|
}
|
|
235
237
|
}
|
|
236
238
|
|
|
239
|
+
/**
|
|
240
|
+
* We only de-duplicate an extend/annotate `ext` in function
|
|
241
|
+
* mergeAnnotatesForSameArtifact() if the extend/annotate is simple, i.e. has
|
|
242
|
+
* no members like elements.
|
|
243
|
+
*/
|
|
244
|
+
function isComplexExtension( ext ) {
|
|
245
|
+
return ext.kind !== 'annotate' || ext.elements || ext.parameters || ext.actions;
|
|
246
|
+
}
|
|
247
|
+
|
|
237
248
|
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
|
|
|
@@ -224,6 +225,7 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
|
|
|
224
225
|
* @param {string} [dir=""] Base directory. All files are resolved relatively
|
|
225
226
|
* to this directory
|
|
226
227
|
* @param {object} [options={}] Compilation options.
|
|
228
|
+
* @param {object} [fileCache]
|
|
227
229
|
* @returns {XSN.Model} Augmented CSN
|
|
228
230
|
*/
|
|
229
231
|
function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.create(null) ) {
|
|
@@ -235,7 +237,7 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
|
|
|
235
237
|
const model = { sources: a.sources, options };
|
|
236
238
|
model.$messageFunctions = createMessageFunctions( options, 'compile', model );
|
|
237
239
|
|
|
238
|
-
|
|
240
|
+
const asts = [];
|
|
239
241
|
const errors = [];
|
|
240
242
|
a.files.forEach( val => readAndParseSync( val, (err, ast) => {
|
|
241
243
|
if (err)
|
|
@@ -253,17 +255,16 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
|
|
|
253
255
|
if (!options.parseOnly && !options.parseCdl) {
|
|
254
256
|
while (asts.length) {
|
|
255
257
|
const fileNames = readDependenciesSync( asts );
|
|
256
|
-
asts =
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
fileNames.forEach( (fileName) => {
|
|
258
|
+
asts.length = 0;
|
|
259
|
+
// Push dependencies to `ast`. Only works because readAndParseSync() is synchronous.
|
|
260
|
+
for (const fileName of fileNames) {
|
|
260
261
|
readAndParseSync(fileName, ( err, ast ) => {
|
|
261
262
|
if (err)
|
|
262
263
|
throw err;
|
|
263
264
|
if (ast)
|
|
264
265
|
asts.push( ast );
|
|
265
266
|
});
|
|
266
|
-
}
|
|
267
|
+
}
|
|
267
268
|
}
|
|
268
269
|
}
|
|
269
270
|
|
|
@@ -378,6 +379,15 @@ function compileSourcesX( sourcesDict, options = {} ) {
|
|
|
378
379
|
ast.location = { file: filename };
|
|
379
380
|
assertConsistency( ast, options );
|
|
380
381
|
}
|
|
382
|
+
|
|
383
|
+
for (const dep of sources[filename].dependencies || []) {
|
|
384
|
+
if (!dep.realname) {
|
|
385
|
+
// `realname` is used by setLayers(). For compileSources(), we don't resolve
|
|
386
|
+
// the USING paths and use the literal instead, which may be part of the
|
|
387
|
+
// source dictionary.
|
|
388
|
+
dep.realname = dep.val;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
381
391
|
}
|
|
382
392
|
moduleLayers.setLayers( sources );
|
|
383
393
|
|
|
@@ -391,14 +401,15 @@ function compileSourcesX( sourcesDict, options = {} ) {
|
|
|
391
401
|
* @param {object} options Options
|
|
392
402
|
* @returns {object} XSN
|
|
393
403
|
*
|
|
394
|
-
* TODO:
|
|
404
|
+
* TODO: probably issue message api-recompiled-csn there.
|
|
395
405
|
*/
|
|
396
406
|
function recompileX( csn, options ) {
|
|
397
|
-
options = { ...options, parseCdl: false, $recompile: true };
|
|
398
|
-
// TODO: $recompile: true should be enough
|
|
399
407
|
// Explicitly set parseCdl to false because backends cannot handle it
|
|
400
|
-
|
|
408
|
+
options = { ...options, parseCdl: false, $recompile: true };
|
|
409
|
+
// Reset csnFlavor: Use client style (default)
|
|
410
|
+
delete options.csnFlavor;
|
|
401
411
|
delete options.toCsn;
|
|
412
|
+
// TODO: $recompile: true should be enough
|
|
402
413
|
|
|
403
414
|
const file = csn.$location && csn.$location.file &&
|
|
404
415
|
csn.$location.file.replace(/[.]cds$/, '.cds.csn') || '<recompile>.csn';
|
|
@@ -428,6 +439,9 @@ function compileDoX( model ) {
|
|
|
428
439
|
const { throwWithError } = model.$messageFunctions;
|
|
429
440
|
if (!options.testMode)
|
|
430
441
|
model.meta = {}; // provide initial central meta object
|
|
442
|
+
|
|
443
|
+
checkRemovedDeprecatedFlags( options, model.$messageFunctions );
|
|
444
|
+
|
|
431
445
|
if (options.parseOnly) {
|
|
432
446
|
throwWithError();
|
|
433
447
|
return model;
|
|
@@ -500,7 +514,7 @@ function processFilenamesSync( filenames, dir ) {
|
|
|
500
514
|
// Resolve possible symbolic link; if the file does not exist
|
|
501
515
|
// we just continue using the original name because readFile()
|
|
502
516
|
// already handles non-existent files.
|
|
503
|
-
name = fs.realpathSync(name);
|
|
517
|
+
name = fs.realpathSync.native(name);
|
|
504
518
|
}
|
|
505
519
|
catch (e) {
|
|
506
520
|
// 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
|
@@ -74,21 +74,21 @@ function populate( model ) {
|
|
|
74
74
|
let newAutoExposed = [];
|
|
75
75
|
|
|
76
76
|
// behavior depending on option `deprecated`:
|
|
77
|
-
const enableExpandElements = !isDeprecatedEnabled( options, '
|
|
77
|
+
const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
|
|
78
78
|
// TODO: we should get rid of noElementsExpansion soon; both
|
|
79
79
|
// beta.nestedProjections and beta.universalCsn do not work with it.
|
|
80
80
|
const scopedRedirections
|
|
81
81
|
= enableExpandElements &&
|
|
82
|
-
!isDeprecatedEnabled( options, '
|
|
83
|
-
!isDeprecatedEnabled( options, '
|
|
84
|
-
!isDeprecatedEnabled( options, '
|
|
85
|
-
!isDeprecatedEnabled( options, '
|
|
86
|
-
!isDeprecatedEnabled( options, '
|
|
82
|
+
!isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ) &&
|
|
83
|
+
!isDeprecatedEnabled( options, '_shortAutoexposed' ) &&
|
|
84
|
+
!isDeprecatedEnabled( options, '_longAutoexposed' ) &&
|
|
85
|
+
!isDeprecatedEnabled( options, '_noInheritedAutoexposeViaComposition' ) &&
|
|
86
|
+
!isDeprecatedEnabled( options, '_noScopedRedirections' );
|
|
87
87
|
const autoexposeViaComposition
|
|
88
|
-
= (isDeprecatedEnabled( options, '
|
|
88
|
+
= (isDeprecatedEnabled( options, '_noInheritedAutoexposeViaComposition' ))
|
|
89
89
|
? 'Composition'
|
|
90
90
|
: true;
|
|
91
|
-
const redirectInSubQueries = isDeprecatedEnabled( options, '
|
|
91
|
+
const redirectInSubQueries = isDeprecatedEnabled( options, '_redirectInSubQueries' );
|
|
92
92
|
|
|
93
93
|
forEachDefinition( model, traverseElementEnvironments );
|
|
94
94
|
while (newAutoExposed.length) {
|
|
@@ -1094,7 +1094,7 @@ function populate( model ) {
|
|
|
1094
1094
|
return false;
|
|
1095
1095
|
}
|
|
1096
1096
|
// no @cds.autoexpose or @cds.autoexpose:null
|
|
1097
|
-
// TODO: introduce deprecated.
|
|
1097
|
+
// TODO: introduce deprecated._noInheritedAutoexposeViaComposition
|
|
1098
1098
|
art.$autoexpose = model.$compositionTargets[art.name.absolute]
|
|
1099
1099
|
? autoexposeViaComposition
|
|
1100
1100
|
: null;
|
|
@@ -1103,15 +1103,15 @@ function populate( model ) {
|
|
|
1103
1103
|
|
|
1104
1104
|
function autoExposedName( target, service, elemScope ) {
|
|
1105
1105
|
const { absolute } = target.name;
|
|
1106
|
-
if (isDeprecatedEnabled( options, '
|
|
1106
|
+
if (isDeprecatedEnabled( options, '_shortAutoexposed' )) {
|
|
1107
1107
|
const parent = definitionScope( target )._parent;
|
|
1108
1108
|
const name = (parent) ? absolute.substring( parent.name.absolute.length + 1 ) : absolute;
|
|
1109
|
-
// no need for dedot here (as opposed to deprecated.
|
|
1109
|
+
// no need for dedot here (as opposed to deprecated._longAutoexposed), as
|
|
1110
1110
|
// the name for dependent entities have already been created using `_` then
|
|
1111
1111
|
return `${ service.name.absolute }.${ name }`;
|
|
1112
1112
|
}
|
|
1113
|
-
if (isDeprecatedEnabled( options, '
|
|
1114
|
-
const dedot = isDeprecatedEnabled( options, '
|
|
1113
|
+
if (isDeprecatedEnabled( options, '_longAutoexposed' )) {
|
|
1114
|
+
const dedot = isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' );
|
|
1115
1115
|
return `${ service.name.absolute }.${ dedot ? absolute.replace( /\./g, '_' ) : absolute }`;
|
|
1116
1116
|
}
|
|
1117
1117
|
const base = definitionScope( target );
|
|
@@ -56,9 +56,9 @@ function propagate( model ) {
|
|
|
56
56
|
returns,
|
|
57
57
|
};
|
|
58
58
|
const { options } = model;
|
|
59
|
-
const enableExpandElements = !isDeprecatedEnabled( options, '
|
|
59
|
+
const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
|
|
60
60
|
// eslint-disable-next-line max-len
|
|
61
|
-
const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, '
|
|
61
|
+
const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, '_oldVirtualNotNullPropagation' );
|
|
62
62
|
|
|
63
63
|
forEachDefinition( model, run );
|
|
64
64
|
|
|
@@ -189,7 +189,7 @@ function propagate( model ) {
|
|
|
189
189
|
if (!type || type._main)
|
|
190
190
|
return false;
|
|
191
191
|
// We do not consider the $expand status, as elements are already expanded
|
|
192
|
-
// by the resolve(), and if not due to deprecated.
|
|
192
|
+
// by the resolve(), and if not due to deprecated._noElementsExpansion
|
|
193
193
|
run( type );
|
|
194
194
|
return type[prop];
|
|
195
195
|
}
|