@sap/cds-compiler 6.1.0 → 6.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 +43 -0
- package/bin/cdsc.js +6 -2
- package/bin/cdsse.js +1 -1
- package/bin/cdsv2m.js +1 -1
- package/lib/api/main.js +29 -7
- package/lib/base/builtins.js +9 -0
- package/lib/base/keywords.js +1 -1
- package/lib/base/message-registry.js +5 -3
- package/lib/base/messages.js +2 -2
- package/lib/base/model.js +1 -0
- package/lib/base/optionProcessorHelper.js +7 -2
- package/lib/checks/featureFlags.js +4 -1
- package/lib/compiler/assert-consistency.js +3 -1
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/builtins.js +1 -1
- package/lib/compiler/checks.js +38 -21
- package/lib/compiler/define.js +24 -5
- package/lib/compiler/extend.js +1 -1
- package/lib/compiler/finalize-parse-cdl.js +9 -1
- package/lib/compiler/generate.js +4 -4
- package/lib/compiler/lsp-api.js +2 -0
- package/lib/compiler/populate.js +8 -8
- package/lib/compiler/propagator.js +1 -1
- package/lib/compiler/resolve.js +15 -14
- package/lib/compiler/shared.js +6 -6
- package/lib/compiler/tweak-assocs.js +6 -6
- package/lib/compiler/utils.js +9 -16
- package/lib/compiler/xpr-rewrite.js +2 -2
- package/lib/gen/BaseParser.js +35 -29
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1423 -1432
- package/lib/gen/Dictionary.json +1 -0
- package/lib/gen/cdlKeywords.json +26 -0
- package/lib/inspect/inspectPropagation.js +1 -1
- package/lib/json/from-csn.js +2 -2
- package/lib/json/to-csn.js +1 -1
- package/lib/language/multiLineStringParser.js +1 -1
- package/lib/model/cloneCsn.js +1 -0
- package/lib/optionProcessor.js +8 -7
- package/lib/parsers/AstBuildingParser.js +24 -21
- package/lib/parsers/identifiers.js +2 -30
- package/lib/render/toCdl.js +63 -9
- package/lib/render/toSql.js +127 -108
- package/lib/render/utils/sql.js +67 -0
- package/lib/transform/addTenantFields.js +4 -4
- package/lib/transform/db/killAnnotations.js +1 -0
- package/lib/transform/db/processSqlServices.js +20 -2
- package/lib/transform/forOdata.js +91 -2
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/odata/flattening.js +1 -1
- package/lib/transform/translateAssocsToJoins.js +2 -26
- package/lib/utils/moduleResolve.js +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,34 @@ Note: `beta` fixes, changes and features are usually not listed in this ChangeLo
|
|
|
8
8
|
but in [doc/CHANGELOG_BETA.md](doc/CHANGELOG_BETA.md).
|
|
9
9
|
The compiler behavior concerning `beta` features can change at any time without notice.
|
|
10
10
|
|
|
11
|
+
## Version 6.2.2 - 2025-07-28
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- compiler: `@extension.code` was accidentally restricted to non-expression values.
|
|
16
|
+
|
|
17
|
+
## Version 6.2.0 - 2025-07-25
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- parser: CDL-casts in queries now support all type expressions, e.g. `field : many String not null`.
|
|
22
|
+
- compiler: Association paths in annotation expressions can now end with a filter, e.g. `@anno: (assoc[1=1])`.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- compiler: Annotation `@extension.code` is no longer propagated.
|
|
27
|
+
- Update OData vocabularies: Common
|
|
28
|
+
- The list of CDL keywords was updated for the latest CDL grammar.
|
|
29
|
+
- to.cdl: Foreign keys of managed associations are only rendered explicitly if
|
|
30
|
+
the compiler can't infer them when recompiled.
|
|
31
|
+
- cdsc: The command `parseCdl` was renamed to `parse`, since it also supports CSN input.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- compiler:
|
|
36
|
+
+ Calculated elements can now have a localized type.
|
|
37
|
+
+ Associations in sub-queries of an `order by` of a `UNION` are now redirected.
|
|
38
|
+
|
|
11
39
|
## Version 6.1.0 - 2025-06-27
|
|
12
40
|
|
|
13
41
|
### Added
|
|
@@ -135,6 +163,21 @@ The compiler behavior concerning `beta` features can change at any time without
|
|
|
135
163
|
|
|
136
164
|
- to.edm(x): Fixed crash for rare case if annotation expressions were used.
|
|
137
165
|
|
|
166
|
+
## Version 5.9.8 - 2025-07-14
|
|
167
|
+
|
|
168
|
+
### Fixed
|
|
169
|
+
|
|
170
|
+
- compiler: Calculated elements can now have a localized type
|
|
171
|
+
|
|
172
|
+
## Version 5.9.6 - 2025-06-18
|
|
173
|
+
|
|
174
|
+
### Fixed
|
|
175
|
+
|
|
176
|
+
- to.sql: Fix error when calculated element refers to a localized element.
|
|
177
|
+
- to.edm(x):
|
|
178
|
+
+ Fix errors for service entities containing multiple path steps (e.g. `Service.Prefix.MyEntity`).
|
|
179
|
+
+ Support enum references in annotation expressions that were resolved by the compiler.
|
|
180
|
+
|
|
138
181
|
## Version 5.9.4 - 2025-05-22
|
|
139
182
|
|
|
140
183
|
### Fixed
|
package/bin/cdsc.js
CHANGED
|
@@ -151,7 +151,7 @@ function cdscMain() {
|
|
|
151
151
|
cmdLine.options.attachValidNames = true;
|
|
152
152
|
|
|
153
153
|
// Internally, parseCdl/parseOnly are options, so we map the command to it.
|
|
154
|
-
if (cmdLine.command === '
|
|
154
|
+
if (cmdLine.command === 'parse') {
|
|
155
155
|
cmdLine.command = 'toCsn';
|
|
156
156
|
cmdLine.options.toCsn = cmdLine.options.parseCdl;
|
|
157
157
|
cmdLine.options.parseCdl = true;
|
|
@@ -704,7 +704,11 @@ async function executeCommandLine( command, options, args ) {
|
|
|
704
704
|
return;
|
|
705
705
|
if (!omitHeadline) {
|
|
706
706
|
const sqlTypes = {
|
|
707
|
-
sql: true,
|
|
707
|
+
sql: true,
|
|
708
|
+
hdbconstraint: true,
|
|
709
|
+
hdbtable: true,
|
|
710
|
+
hdbview: true,
|
|
711
|
+
hdbprojectionview: true,
|
|
708
712
|
};
|
|
709
713
|
const commentStarter = fileName.split('.').pop() in sqlTypes ? '--$' : '//';
|
|
710
714
|
process.stdout.write(`${ commentStarter } ------------------- ${ fileName } -------------------\n`);
|
package/bin/cdsse.js
CHANGED
package/bin/cdsv2m.js
CHANGED
package/lib/api/main.js
CHANGED
|
@@ -15,6 +15,7 @@ const generateDrafts = lazyload('../transform/draft/odata');
|
|
|
15
15
|
const tenant = lazyload('../transform/addTenantFields');
|
|
16
16
|
const toSql = lazyload('../render/toSql');
|
|
17
17
|
const toCdl = require('../render/toCdl');
|
|
18
|
+
const sqlRenderUtils = lazyload('../render/utils/sql');
|
|
18
19
|
const modelCompare = lazyload('../modelCompare/compare');
|
|
19
20
|
const diffFilter = lazyload('../modelCompare/utils/filter');
|
|
20
21
|
const sortViews = lazyload('../model/sortViews');
|
|
@@ -386,11 +387,13 @@ function hdi( csn, options, messageFunctions ) {
|
|
|
386
387
|
|
|
387
388
|
objectUtils.forEach(flat, (key) => {
|
|
388
389
|
const artifactNameLikeInCsn = key.replace(/\.[^/.]+$/, '');
|
|
389
|
-
|
|
390
|
-
|
|
390
|
+
if (key.endsWith('.hdbtable') || key.endsWith('.hdbview') || key.endsWith('.hdbprojectionview')) {
|
|
391
|
+
nameMapping[artifactNameLikeInCsn] = key;
|
|
391
392
|
sqlArtifactsWithCSNNamesToSort[artifactNameLikeInCsn] = flat[key];
|
|
392
|
-
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
393
395
|
sqlArtifactsNotToSort[key] = flat[key];
|
|
396
|
+
}
|
|
394
397
|
});
|
|
395
398
|
|
|
396
399
|
const sorted = sortViews({ sql: sqlArtifactsWithCSNNamesToSort, csn: sqlCSN })
|
|
@@ -687,7 +690,7 @@ function hdiMigration( csn, options, messageFunctions, beforeImage ) {
|
|
|
687
690
|
return {
|
|
688
691
|
afterImage,
|
|
689
692
|
definitions: createSqlDefinitions(hdbkinds, afterImage),
|
|
690
|
-
deletions: createSqlDeletions(deletions, beforeImage),
|
|
693
|
+
deletions: createSqlDeletions(deletions, beforeImage, options),
|
|
691
694
|
migrations: createSqlMigrations(migrations, afterImage),
|
|
692
695
|
};
|
|
693
696
|
}
|
|
@@ -719,11 +722,30 @@ function createSqlDefinitions( hdbkinds, afterImage ) {
|
|
|
719
722
|
* @param {CSN.Model} beforeImage CSN used to create correct file names in result structure.
|
|
720
723
|
* @returns {object[]} Array of objects, each having: name and suffix - only .hdbtable as suffix for now
|
|
721
724
|
*/
|
|
722
|
-
function createSqlDeletions( deletions, beforeImage ) {
|
|
725
|
+
function createSqlDeletions( deletions, beforeImage, options ) {
|
|
723
726
|
const result = [];
|
|
724
|
-
objectUtils.forEach(deletions, name => result.push({
|
|
727
|
+
objectUtils.forEach(deletions, name => result.push({
|
|
728
|
+
name: getFileName(name, beforeImage), suffix: getSuffix(beforeImage, beforeImage.definitions[name], options),
|
|
729
|
+
}));
|
|
725
730
|
return result;
|
|
726
731
|
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Determines the appropriate file suffix based on the type of the provided artifact.
|
|
735
|
+
*
|
|
736
|
+
* @param {CSN.Artifact} artifact - The artifact object to evaluate.
|
|
737
|
+
* @returns {string} The file suffix corresponding to the artifact type:
|
|
738
|
+
* - '.hdbview' for query artifacts
|
|
739
|
+
* - '.hdbprojectionview' for projection artifacts
|
|
740
|
+
* - '.hdbtable' for other artifacts
|
|
741
|
+
*/
|
|
742
|
+
function getSuffix( csn, artifact, options ) {
|
|
743
|
+
if (artifact.query || artifact.projection)
|
|
744
|
+
return sqlRenderUtils.isProjectionView(csn, artifact, options) ? '.hdbprojectionview' : '.hdbview';
|
|
745
|
+
|
|
746
|
+
return '.hdbtable';
|
|
747
|
+
}
|
|
748
|
+
|
|
727
749
|
/**
|
|
728
750
|
* From the given migrations, create the correct result structure.
|
|
729
751
|
*
|
|
@@ -1382,7 +1404,7 @@ function handleTenantDiscriminator( options, internalOptions, messageFunctions )
|
|
|
1382
1404
|
*/
|
|
1383
1405
|
|
|
1384
1406
|
/**
|
|
1385
|
-
* A map of { <file.hdbtable|hdbview|hdbconstraint...>:<content> }.
|
|
1407
|
+
* A map of { <file.hdbtable|hdbview|hdbprojectionview|hdbconstraint...>:<content> }.
|
|
1386
1408
|
*
|
|
1387
1409
|
* @typedef {object} HDIArtifacts
|
|
1388
1410
|
*/
|
package/lib/base/builtins.js
CHANGED
|
@@ -29,6 +29,14 @@ const propagationRules = {
|
|
|
29
29
|
'@sql.append': 'never',
|
|
30
30
|
'@sql.prepend': 'never',
|
|
31
31
|
'@sql.replace': 'never',
|
|
32
|
+
'@extension.code': 'never', // for cds-oyster, not security relevant, but convenience
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Of the annotations above, only these accept expressions as annotation values.
|
|
36
|
+
*/
|
|
37
|
+
const acceptsExprValues = {
|
|
38
|
+
__proto__: null,
|
|
39
|
+
'@extension.code': true,
|
|
32
40
|
};
|
|
33
41
|
|
|
34
42
|
/**
|
|
@@ -101,6 +109,7 @@ function isAnnotationExpression( val ) {
|
|
|
101
109
|
|
|
102
110
|
module.exports = {
|
|
103
111
|
propagationRules,
|
|
112
|
+
acceptsExprValues,
|
|
104
113
|
xprInAnnoProperties,
|
|
105
114
|
isInReservedNamespace,
|
|
106
115
|
isBuiltinType,
|
package/lib/base/keywords.js
CHANGED
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
// "sloppy": with an upcoming _minor_ version of the compiler, the compilation
|
|
30
30
|
// might lead to an error anyway or the compiled CSN might look different.
|
|
31
31
|
|
|
32
|
-
/* eslint @stylistic/
|
|
33
|
-
/* eslint @stylistic/
|
|
34
|
-
/* eslint @stylistic/
|
|
32
|
+
/* eslint @stylistic/no-multi-spaces: 0 */
|
|
33
|
+
/* eslint @stylistic/max-len: 0 */
|
|
34
|
+
/* eslint @stylistic/key-spacing: 0 */
|
|
35
35
|
|
|
36
36
|
'use strict';
|
|
37
37
|
|
|
@@ -1219,11 +1219,13 @@ const centralMessageTexts = {
|
|
|
1219
1219
|
'type-invalid-cast': {
|
|
1220
1220
|
std: 'Can\'t cast to $(TYPE)',
|
|
1221
1221
|
'to-structure': 'Can\'t cast to a structured type',
|
|
1222
|
+
'to-inline-structure': 'Can\'t cast to a structure',
|
|
1222
1223
|
'from-structure': 'Structured elements can\'t be cast to a different type',
|
|
1223
1224
|
'expr-to-structure': 'Can\'t cast an expression to a structured type',
|
|
1224
1225
|
'val-to-structure': 'Can\'t cast $(VALUE) to a structured type',
|
|
1225
1226
|
'from-assoc': 'Invalid type cast on an association',
|
|
1226
1227
|
assoc: 'Can\'t cast to an association',
|
|
1228
|
+
expand: 'Expands can\'t have a type cast', // TODO: Improve
|
|
1227
1229
|
},
|
|
1228
1230
|
|
|
1229
1231
|
// -----------------------------------------------------------------------------------
|
package/lib/base/messages.js
CHANGED
|
@@ -1329,8 +1329,8 @@ function compareMessage( a, b ) {
|
|
|
1329
1329
|
const aFile = a.$location && a.$location.file;
|
|
1330
1330
|
const bFile = b.$location && b.$location.file;
|
|
1331
1331
|
if (aFile && bFile) {
|
|
1332
|
-
const aEnd = a.$location.endLine && a.$location.endCol && a.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER }; // eslint-disable-line @stylistic/
|
|
1333
|
-
const bEnd = b.$location.endLine && b.$location.endCol && b.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER }; // eslint-disable-line @stylistic/
|
|
1332
|
+
const aEnd = a.$location.endLine && a.$location.endCol && a.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER }; // eslint-disable-line @stylistic/max-len
|
|
1333
|
+
const bEnd = b.$location.endLine && b.$location.endCol && b.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER }; // eslint-disable-line @stylistic/max-len
|
|
1334
1334
|
return ( c( aFile, bFile ) ||
|
|
1335
1335
|
c( a.$location.line, b.$location.line ) ||
|
|
1336
1336
|
c( a.$location.col, b.$location.col ) ||
|
package/lib/base/model.js
CHANGED
|
@@ -71,8 +71,9 @@ function createOptionProcessor() {
|
|
|
71
71
|
/**
|
|
72
72
|
* API: Define a command
|
|
73
73
|
* @param {string} cmdString Command name, short and long form, e.g. 'S, toSql'
|
|
74
|
+
* @param {object} [cmdOptions] Optional options, e.g. `aliases`.
|
|
74
75
|
*/
|
|
75
|
-
function command( cmdString ) {
|
|
76
|
+
function command( cmdString, cmdOptions = null ) {
|
|
76
77
|
/** @type {object} */
|
|
77
78
|
const cmd = {
|
|
78
79
|
options: {},
|
|
@@ -83,6 +84,7 @@ function createOptionProcessor() {
|
|
|
83
84
|
return cmd;
|
|
84
85
|
},
|
|
85
86
|
help: commandHelp,
|
|
87
|
+
aliases: cmdOptions?.aliases,
|
|
86
88
|
..._parseCommandString(cmdString),
|
|
87
89
|
};
|
|
88
90
|
if (optionProcessor.commands[cmd.longName])
|
|
@@ -96,6 +98,10 @@ function createOptionProcessor() {
|
|
|
96
98
|
|
|
97
99
|
optionProcessor.commands[cmd.shortName] = cmd;
|
|
98
100
|
}
|
|
101
|
+
|
|
102
|
+
for (const alias of cmdOptions?.aliases ?? [])
|
|
103
|
+
optionProcessor.commands[alias] = cmd;
|
|
104
|
+
|
|
99
105
|
return cmd;
|
|
100
106
|
|
|
101
107
|
// Command API: Define a command option
|
|
@@ -400,7 +406,6 @@ function createOptionProcessor() {
|
|
|
400
406
|
else
|
|
401
407
|
result.errors.push(errorMsg);
|
|
402
408
|
}
|
|
403
|
-
|
|
404
409
|
return result;
|
|
405
410
|
|
|
406
411
|
/**
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { setProp } = require('../base/model');
|
|
4
4
|
const { featureFlags } = require('../transform/featureFlags');
|
|
5
|
-
const { isSqlService, isDummyService } = require('../transform/db/processSqlServices');
|
|
5
|
+
const { isSqlService, isDummyService, isDataProductService } = require('../transform/db/processSqlServices');
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
*
|
|
@@ -32,5 +32,8 @@ module.exports = {
|
|
|
32
32
|
|
|
33
33
|
if (isDummyService(artifact, this.options))
|
|
34
34
|
setFeatureFlag( '$dummyService' ).call(this);
|
|
35
|
+
|
|
36
|
+
if (isDataProductService(artifact, this.options))
|
|
37
|
+
setFeatureFlag( '$dataProductService' ).call(this);
|
|
35
38
|
},
|
|
36
39
|
};
|
|
@@ -647,6 +647,8 @@ function assertConsistency( model, stage ) {
|
|
|
647
647
|
'where', 'columns', 'mixin', 'quantifier', 'offset',
|
|
648
648
|
'orderBy', '$orderBy', 'groupBy', 'excludingDict', 'having',
|
|
649
649
|
'$limit', 'limit', '_status', '_origin',
|
|
650
|
+
// via casts
|
|
651
|
+
'enum',
|
|
650
652
|
],
|
|
651
653
|
},
|
|
652
654
|
_leadingQuery: { kind: true, test: TODO },
|
|
@@ -1059,7 +1061,7 @@ function assertConsistency( model, stage ) {
|
|
|
1059
1061
|
// TODO
|
|
1060
1062
|
// else if (spec.instanceOf && spec.instanceOf !== 'ignore' &&
|
|
1061
1063
|
// Object.getPrototypeOf( node ) !== spec.instanceOf.prototype)
|
|
1062
|
-
// eslint-disable-next-line @stylistic/
|
|
1064
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1063
1065
|
// throw new InternalConsistencyError( `Expected object of class ${ spec.instanceOf.name } but found ${ found }${ at( [ null, parent ], prop, name ) }` );
|
|
1064
1066
|
}
|
|
1065
1067
|
|
package/lib/compiler/base.js
CHANGED
|
@@ -21,7 +21,7 @@ const kindProperties = {
|
|
|
21
21
|
entity: {
|
|
22
22
|
elements: true, actions: true, params: () => false, include: true,
|
|
23
23
|
},
|
|
24
|
-
select: { normalized: 'select', elements:
|
|
24
|
+
select: { normalized: 'select', elements: propExists, enum: propExists },
|
|
25
25
|
$join: { normalized: 'select' },
|
|
26
26
|
$tableAlias: { normalized: 'alias' }, // table alias in select
|
|
27
27
|
$self: { normalized: 'alias' }, // table alias in select
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -222,7 +222,7 @@ const dateRegEx = /^(-?\d{4})-(\d{1,2})-(\d{1,2})$/;
|
|
|
222
222
|
// YYYY - MM - dd
|
|
223
223
|
const timeRegEx = /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
|
|
224
224
|
// T HH : mm : ss TZD
|
|
225
|
-
// eslint-disable-next-line @stylistic/
|
|
225
|
+
// eslint-disable-next-line @stylistic/max-len, sonarjs/regex-complexity
|
|
226
226
|
const timestampRegEx = /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
|
|
227
227
|
// YYYY - MM - dd T HH : mm : ss . fraction TZD
|
|
228
228
|
// eslint-disable-next-line sonarjs/regex-complexity
|
package/lib/compiler/checks.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
// * Different ad-hoc value/type checks (associations, enum, ...) -
|
|
6
6
|
// specify a proper one and use consistently
|
|
7
7
|
// * Using name comparisons instead proper object comparisons.
|
|
8
|
-
// * effectiveType issues.
|
|
9
8
|
// * Often forgot to consider CSN input
|
|
10
9
|
|
|
11
10
|
'use strict';
|
|
@@ -18,7 +17,7 @@ const {
|
|
|
18
17
|
isDeprecatedEnabled,
|
|
19
18
|
} = require('../base/model');
|
|
20
19
|
const { typeParameters } = require('./builtins');
|
|
21
|
-
const { propagationRules } = require('../base/builtins');
|
|
20
|
+
const { propagationRules, acceptsExprValues } = require('../base/builtins');
|
|
22
21
|
const { annotationVal } = require('./utils');
|
|
23
22
|
|
|
24
23
|
const $location = Symbol.for( 'cds.$location' );
|
|
@@ -84,7 +83,7 @@ function check( model ) {
|
|
|
84
83
|
checkName( art );
|
|
85
84
|
checkTypeArguments( art );
|
|
86
85
|
|
|
87
|
-
if (art.value && !art.$calcDepElement && art.type)
|
|
86
|
+
if (art.value && !art.$calcDepElement && (art.type || art.elements || art.items))
|
|
88
87
|
checkTypeCast( art.value, art );
|
|
89
88
|
|
|
90
89
|
for (const anno of iterateAnnotations( art ))
|
|
@@ -245,25 +244,43 @@ function check( model ) {
|
|
|
245
244
|
}
|
|
246
245
|
|
|
247
246
|
function checkTypeCast( xpr, user ) {
|
|
248
|
-
const
|
|
249
|
-
const elem =
|
|
247
|
+
const isSqlCast = (xpr.op?.val === 'cast');
|
|
248
|
+
const elem = isSqlCast
|
|
250
249
|
? xpr.args?.[0]?._artifact
|
|
251
250
|
: xpr._artifact;
|
|
252
|
-
|
|
253
|
-
|
|
251
|
+
|
|
252
|
+
const typeArt = isSqlCast ? xpr : user;
|
|
253
|
+
if (!elem || !isSqlCast && typeArt.type?.$inferred)
|
|
254
254
|
return; // e.g. $inferred:'generated'
|
|
255
|
-
|
|
256
|
-
|
|
255
|
+
|
|
256
|
+
const { type } = typeArt;
|
|
257
|
+
if (type) { // has explicit type
|
|
258
|
+
if (type._artifact?._effectiveType?.name.id === 'cds.Map') {
|
|
257
259
|
error( 'type-invalid-cast', [ type.location, user ], { '#': 'std', type: 'cds.Map' } );
|
|
258
|
-
|
|
260
|
+
}
|
|
261
|
+
else if (type._artifact?.elements) {
|
|
259
262
|
error( 'type-invalid-cast', [ type.location, user ], { '#': 'to-structure' } );
|
|
260
|
-
|
|
263
|
+
}
|
|
264
|
+
else if (elem.elements) { // TODO: calc elements
|
|
261
265
|
error( 'type-invalid-cast', [ type.location, user ], { '#': 'from-structure' } );
|
|
262
|
-
|
|
266
|
+
}
|
|
267
|
+
else if (elem.target && !type._artifact?.target) {
|
|
263
268
|
error( 'type-invalid-cast', [ type.location, user ], { '#': 'from-assoc' } );
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
269
|
+
}
|
|
270
|
+
else if (!elem.target && // referenced element is not association
|
|
271
|
+
!user.type?.$inferred && // $inferred types already reported in resolve.js.
|
|
272
|
+
(
|
|
273
|
+
// assoc used in SQL cast
|
|
274
|
+
type._artifact?.target && isSqlCast ||
|
|
275
|
+
// there is a target and the type is a direct `cds.Association`;
|
|
276
|
+
// other types handled by resolver already.
|
|
277
|
+
typeArt.target && type._artifact?.category === 'relation'
|
|
278
|
+
)
|
|
279
|
+
) {
|
|
280
|
+
// - redirection-check in resolve.js already checks this for CDL-casts
|
|
281
|
+
// - `"cast": { "target": "…", "type": "cds.Association", … }` via CSN input.
|
|
282
|
+
error('type-invalid-cast', [ type.location, user ], { '#': 'assoc' });
|
|
283
|
+
}
|
|
267
284
|
}
|
|
268
285
|
}
|
|
269
286
|
|
|
@@ -935,16 +952,16 @@ function check( model ) {
|
|
|
935
952
|
// One argument must be "$self" and the other an assoc
|
|
936
953
|
if (xpr.op.val === '=' && xpr.args.length === 2) {
|
|
937
954
|
// Tree-ish expression from the compiler (not augmented)
|
|
938
|
-
// eslint-disable-next-line @stylistic/
|
|
955
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
939
956
|
return (isAssociationOperand( xpr.args[0] ) && isDollarSelfOrProjectionOperand( xpr.args[1] ) ||
|
|
940
|
-
// eslint-disable-next-line @stylistic/
|
|
957
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
941
958
|
isAssociationOperand( xpr.args[1] ) && isDollarSelfOrProjectionOperand( xpr.args[0] ));
|
|
942
959
|
}
|
|
943
960
|
else if (xpr.args.length === 3 && xpr.args[1].val === '=') {
|
|
944
961
|
// Tree-ish expression from the compiler (not augmented)
|
|
945
|
-
// eslint-disable-next-line @stylistic/
|
|
962
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
946
963
|
return (isAssociationOperand( xpr.args[0] ) && isDollarSelfOrProjectionOperand( xpr.args[2] ) ||
|
|
947
|
-
// eslint-disable-next-line @stylistic/
|
|
964
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
948
965
|
isAssociationOperand( xpr.args[2] ) && isDollarSelfOrProjectionOperand( xpr.args[0] ));
|
|
949
966
|
}
|
|
950
967
|
|
|
@@ -963,7 +980,7 @@ function check( model ) {
|
|
|
963
980
|
const name = anno.name?.id;
|
|
964
981
|
if (!name)
|
|
965
982
|
return true;
|
|
966
|
-
if (!propagationRules[`@${ name }`])
|
|
983
|
+
if (!propagationRules[`@${ name }`] || acceptsExprValues[`@${ name }`])
|
|
967
984
|
return true;
|
|
968
985
|
error( 'anno-unexpected-expr', [ anno.location, art ], { anno: name },
|
|
969
986
|
'Unexpected expression as value for $(ANNO)' );
|
|
@@ -1165,7 +1182,7 @@ function check( model ) {
|
|
|
1165
1182
|
value.literal !== 'timestamp' && value.literal !== 'string') {
|
|
1166
1183
|
// Hm, actually date and time cannot be mixed
|
|
1167
1184
|
warning( null, loc, { type, anno },
|
|
1168
|
-
// eslint-disable-next-line @stylistic/
|
|
1185
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1169
1186
|
'A date/time value or a string is required for type $(TYPE) for annotation $(ANNO)' );
|
|
1170
1187
|
}
|
|
1171
1188
|
}
|
package/lib/compiler/define.js
CHANGED
|
@@ -1010,6 +1010,7 @@ function define( model ) {
|
|
|
1010
1010
|
}
|
|
1011
1011
|
// Either expression (value), expand, new virtual or new association
|
|
1012
1012
|
else if (col.value || col.name) {
|
|
1013
|
+
col.kind = 'element';
|
|
1013
1014
|
if (!col._block)
|
|
1014
1015
|
setLink( col, '_block', parent._block );
|
|
1015
1016
|
if (col.inline) { // `@anno elem.{ * }` does not work
|
|
@@ -1031,8 +1032,7 @@ function define( model ) {
|
|
|
1031
1032
|
initSelectItems( col, null, user ); // TODO: use col as user (i.e. remove param)
|
|
1032
1033
|
}
|
|
1033
1034
|
|
|
1034
|
-
|
|
1035
|
-
initExprAnnoBlock( col, parent._block );
|
|
1035
|
+
initCdlTypeCast( col, parent );
|
|
1036
1036
|
}
|
|
1037
1037
|
|
|
1038
1038
|
if (hasItems && !wildcard && parent.excludingDict && !options.$recompile) {
|
|
@@ -1044,6 +1044,26 @@ function define( model ) {
|
|
|
1044
1044
|
}
|
|
1045
1045
|
}
|
|
1046
1046
|
|
|
1047
|
+
function initCdlTypeCast( col, parent ) {
|
|
1048
|
+
if (col.val)
|
|
1049
|
+
return; // e.g. '*' column
|
|
1050
|
+
|
|
1051
|
+
setMemberParent( col, col.name, parent );
|
|
1052
|
+
initMembers( col, col, col._block );
|
|
1053
|
+
|
|
1054
|
+
// We don't allow CDL-style casts to anonymous structures. We reject it already here
|
|
1055
|
+
// and not in checks.js to ensure that it's rejected in parseCdl.
|
|
1056
|
+
if (col.elements) {
|
|
1057
|
+
error('type-invalid-cast', [ col.elements[$location] ?? col.location, col ],
|
|
1058
|
+
{ '#': 'to-inline-structure' });
|
|
1059
|
+
}
|
|
1060
|
+
else if (col.expand && (col.type || col.elements || col.items)) {
|
|
1061
|
+
const loc = (col.type?.location || col.elements?.[$location] ||
|
|
1062
|
+
col.items?.location || col.location);
|
|
1063
|
+
error('type-invalid-cast', [ loc, col ], { '#': 'expand' });
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1047
1067
|
/**
|
|
1048
1068
|
* If we have a valid top-level exists, exists in filters of sub-expressions can be translated,
|
|
1049
1069
|
* since we will have a top-level subquery after exists-processing in the forRelationalDB.
|
|
@@ -1150,7 +1170,7 @@ function define( model ) {
|
|
|
1150
1170
|
// We do not want to complain separately about all element properties:
|
|
1151
1171
|
error( 'ext-unexpected-element', [ e.location, construct ],
|
|
1152
1172
|
{ name: e.name.id, code: 'extend … with enum' },
|
|
1153
|
-
// eslint-disable-next-line @stylistic/
|
|
1173
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1154
1174
|
'Unexpected elements like $(NAME) in an extension for an enum. Additionally, use $(CODE) when extending enums' );
|
|
1155
1175
|
// Don't emit 'ext-expecting-enum' if this error is emitted.
|
|
1156
1176
|
return;
|
|
@@ -1238,7 +1258,6 @@ function define( model ) {
|
|
|
1238
1258
|
if (elem.$duplicates === true && add)
|
|
1239
1259
|
elem.$duplicates = null;
|
|
1240
1260
|
setMemberParent( elem, name, parent, add && prop );
|
|
1241
|
-
// console.log(message( null, elem.location, elem, {}, 'Info', 'INIT').toString())
|
|
1242
1261
|
checkRedefinition( elem );
|
|
1243
1262
|
initMembers( elem, elem, bl, initExtensions );
|
|
1244
1263
|
if (boundSelfParamType && (elem.kind === 'action' || elem.kind === 'function'))
|
|
@@ -1320,7 +1339,7 @@ function define( model ) {
|
|
|
1320
1339
|
// - artifacts (CDL-only anyway) only inside [extend] context|service
|
|
1321
1340
|
if (!dict)
|
|
1322
1341
|
return false;
|
|
1323
|
-
const feature = kindProperties[parent.kind][prop];
|
|
1342
|
+
const feature = kindProperties[parent.kind ?? 'element'][prop];
|
|
1324
1343
|
if (feature &&
|
|
1325
1344
|
(feature === true || construct.kind !== 'extend' || feature( prop, parent )))
|
|
1326
1345
|
return true;
|
package/lib/compiler/extend.js
CHANGED
|
@@ -561,7 +561,7 @@ function extend( model ) {
|
|
|
561
561
|
{
|
|
562
562
|
std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
|
|
563
563
|
array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
|
|
564
|
-
// eslint-disable-next-line @stylistic/
|
|
564
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
565
565
|
struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
|
|
566
566
|
boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
|
|
567
567
|
null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
|
|
@@ -117,7 +117,15 @@ function finalizeParseCdl( model ) {
|
|
|
117
117
|
// containing it. Otherwise some type's aren't properly resolved.
|
|
118
118
|
// TODO: If resolveTypeUnchecked is reworked, we may be able to simplify this coding.
|
|
119
119
|
(artifact.$queries || []).forEach( art => resolveTypesForParseCdl( art, artifact ) );
|
|
120
|
-
(artifact.columns || []).forEach(
|
|
120
|
+
(artifact.columns || []).forEach( (col) => {
|
|
121
|
+
// TODO: Can we use "ensureColumnName" of populate.js? It depends on column indices
|
|
122
|
+
// _after_ wildcards were expanded, though.
|
|
123
|
+
if (!col.name && col.value?.path) {
|
|
124
|
+
const last = col.value.path.at(-1);
|
|
125
|
+
col.name = { id: last?.id || '', location: last?.location, $inferred: 'as' };
|
|
126
|
+
}
|
|
127
|
+
resolveTypesForParseCdl( col, artifact );
|
|
128
|
+
} );
|
|
121
129
|
forEachGeneric( artifact, 'mixin', art => resolveTypesForParseCdl( art, artifact ) );
|
|
122
130
|
|
|
123
131
|
// For better error messages for `type of`s in `returns`, we pass the object as the new main.
|
package/lib/compiler/generate.js
CHANGED
|
@@ -104,7 +104,7 @@ function generate( model ) {
|
|
|
104
104
|
const lang = textsAspect.elements.language;
|
|
105
105
|
error( 'def-unexpected-element', [ lang.name.location, lang ],
|
|
106
106
|
{ option: 'addTextsLanguageAssoc', art: textsAspect, name: 'language' },
|
|
107
|
-
// eslint-disable-next-line @stylistic/
|
|
107
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
108
108
|
'$(ART) is not used because option $(OPTION) conflicts with existing element $(NAME); remove either option or element' );
|
|
109
109
|
hasError = true;
|
|
110
110
|
}
|
|
@@ -209,7 +209,7 @@ function generate( model ) {
|
|
|
209
209
|
conflictingElements.push( elem );
|
|
210
210
|
|
|
211
211
|
const isKey = elem.key && elem.key.val;
|
|
212
|
-
const isLocalized = hasTruthyProp( elem, 'localized' );
|
|
212
|
+
const isLocalized = elem.$syntax !== 'calc' && hasTruthyProp( elem, 'localized' );
|
|
213
213
|
|
|
214
214
|
if (isKey) {
|
|
215
215
|
keys += 1;
|
|
@@ -245,7 +245,7 @@ function generate( model ) {
|
|
|
245
245
|
(fioriEnabled && art.elements.ID_texts)) {
|
|
246
246
|
// TODO if we have too much time: check all elements of texts entity for safety
|
|
247
247
|
warning( null, [ art.name.location, art ], { art: textsEntity },
|
|
248
|
-
// eslint-disable-next-line @stylistic/
|
|
248
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
249
249
|
'Texts entity $(ART) can\'t be created as there is another definition with that name' );
|
|
250
250
|
info( null, [ textsEntity.name.location, textsEntity ], { art },
|
|
251
251
|
'Texts entity for $(ART) can\'t be created with this definition' );
|
|
@@ -640,7 +640,7 @@ function generate( model ) {
|
|
|
640
640
|
}
|
|
641
641
|
if (model.definitions[entityName]) {
|
|
642
642
|
error( null, [ location, elem ], { art: entityName },
|
|
643
|
-
// eslint-disable-next-line @stylistic/
|
|
643
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
644
644
|
'Target entity $(ART) can\'t be created as there is another definition with this name' );
|
|
645
645
|
return false;
|
|
646
646
|
}
|
package/lib/compiler/lsp-api.js
CHANGED
|
@@ -397,6 +397,8 @@ function* nameAsReference( ref, hint = null ) {
|
|
|
397
397
|
function* definitionNameTokens( name, art ) {
|
|
398
398
|
if (!art.kind)
|
|
399
399
|
return null; // e.g. parameter references
|
|
400
|
+
if (!name)
|
|
401
|
+
return null; // e.g. column that couldn't be populated
|
|
400
402
|
if (art.kind === '$annotation')
|
|
401
403
|
return null; // annotation name, e.g. in `@anno: (elem)`
|
|
402
404
|
|