@sap/cds-compiler 5.1.2 → 5.3.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 +58 -0
- package/bin/cdsc.js +7 -2
- package/bin/cdshi.js +24 -17
- package/bin/cdsse.js +17 -18
- package/doc/CHANGELOG_BETA.md +9 -4
- package/lib/api/main.js +19 -2
- package/lib/api/options.js +4 -1
- package/lib/api/validate.js +5 -0
- package/lib/base/builtins.js +1 -0
- package/lib/base/message-registry.js +40 -3
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +0 -11
- package/lib/checks/actionsFunctions.js +0 -12
- package/lib/checks/structuredAnnoExpressions.js +10 -14
- package/lib/compiler/assert-consistency.js +21 -13
- package/lib/compiler/builtins.js +2 -2
- package/lib/compiler/checks.js +25 -6
- package/lib/compiler/define.js +27 -31
- package/lib/compiler/extend.js +16 -18
- package/lib/compiler/generate.js +3 -3
- package/lib/compiler/populate.js +22 -16
- package/lib/compiler/propagator.js +3 -2
- package/lib/compiler/resolve.js +87 -94
- package/lib/compiler/shared.js +12 -13
- package/lib/compiler/tweak-assocs.js +390 -86
- package/lib/compiler/utils.js +41 -33
- package/lib/compiler/xpr-rewrite.js +45 -58
- package/lib/edm/annotations/genericTranslation.js +17 -13
- package/lib/edm/csn2edm.js +28 -4
- package/lib/edm/edm.js +68 -28
- package/lib/edm/edmInboundChecks.js +5 -8
- package/lib/edm/edmPreprocessor.js +66 -40
- package/lib/edm/edmUtils.js +1 -1
- package/lib/gen/BaseParser.js +778 -0
- package/lib/gen/CdlParser.js +4477 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +4072 -4024
- package/lib/inspect/inspectPropagation.js +1 -1
- package/lib/json/from-csn.js +5 -3
- package/lib/json/to-csn.js +7 -10
- package/lib/language/antlrParser.js +96 -0
- package/lib/language/errorStrategy.js +1 -1
- package/lib/language/genericAntlrParser.js +32 -4
- package/lib/language/multiLineStringParser.js +1 -1
- package/lib/main.d.ts +23 -0
- package/lib/model/cloneCsn.js +22 -13
- package/lib/model/csnUtils.js +2 -0
- package/lib/model/revealInternalProperties.js +2 -0
- package/lib/modelCompare/utils/filter.js +70 -42
- package/lib/optionProcessor.js +16 -10
- package/lib/parsers/AstBuildingParser.js +1290 -0
- package/lib/parsers/CdlGrammar.g4 +2013 -0
- package/lib/parsers/Lexer.js +249 -0
- package/lib/render/toCdl.js +46 -45
- package/lib/render/toSql.js +5 -5
- package/lib/transform/addTenantFields.js +4 -4
- package/lib/transform/db/applyTransformations.js +54 -16
- package/lib/transform/draft/odata.js +10 -11
- package/lib/transform/effective/flattening.js +10 -14
- package/lib/transform/forRelationalDB.js +7 -6
- package/lib/transform/odata/flattening.js +42 -31
- package/lib/transform/odata/toFinalBaseType.js +7 -6
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
- package/lib/utils/moduleResolve.js +1 -1
- package/package.json +2 -2
- package/share/messages/redirected-to-ambiguous.md +5 -4
- package/share/messages/redirected-to-complex.md +6 -3
|
@@ -122,7 +122,6 @@ function assertConsistency( model, stage ) {
|
|
|
122
122
|
'dependencies', // for USING..FROM
|
|
123
123
|
'kind', // TODO: remove from parser
|
|
124
124
|
'meta',
|
|
125
|
-
'@sql_mapping', // TODO: it is time that a 'header' attribute replaces 'version'
|
|
126
125
|
'$withLocalized',
|
|
127
126
|
'$sources',
|
|
128
127
|
'tokenStream',
|
|
@@ -289,7 +288,8 @@ function assertConsistency( model, stage ) {
|
|
|
289
288
|
'$calcDepElement',
|
|
290
289
|
'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit', '$limit',
|
|
291
290
|
'_origin', '_block', '$contains',
|
|
292
|
-
'_projections', '
|
|
291
|
+
'_projections', '_complexProjections',
|
|
292
|
+
'_parent', '_main', '_effectiveType', '$effectiveSeqNo', '$expand',
|
|
293
293
|
'$tableAliases', 'kind', '_$next', '_combined', '$inlines', '_status',
|
|
294
294
|
],
|
|
295
295
|
},
|
|
@@ -313,7 +313,7 @@ function assertConsistency( model, stage ) {
|
|
|
313
313
|
'kind', 'name', '$syntax', '_block', '_parent', '_main',
|
|
314
314
|
'elements', '_origin', '_joinParent', '$joinArgsIndex', '$syntax',
|
|
315
315
|
'$parens', '_status', // TODO: only in from
|
|
316
|
-
'scope', '_artifact', '$inferred', 'kind',
|
|
316
|
+
'scope', '_artifact', '_originalArtifact', '$inferred', 'kind',
|
|
317
317
|
'_effectiveType', '$effectiveSeqNo', // TODO:check this
|
|
318
318
|
'$duplicates', // In JOIN if both sides are the same.
|
|
319
319
|
],
|
|
@@ -370,7 +370,8 @@ function assertConsistency( model, stage ) {
|
|
|
370
370
|
requires: [ 'location' ],
|
|
371
371
|
optional: [
|
|
372
372
|
'path', 'elements', '_outer', '_parent', '_main', '_block', 'kind',
|
|
373
|
-
'scope', '_artifact', '$inferred', '$expand', '$inCycle',
|
|
373
|
+
'scope', '_artifact', '$inferred', '$expand', '$inCycle',
|
|
374
|
+
'$tableAliases', '_$next',
|
|
374
375
|
'_origin', '_effectiveType', '$effectiveSeqNo', '_extensions', '$contains',
|
|
375
376
|
],
|
|
376
377
|
},
|
|
@@ -390,7 +391,8 @@ function assertConsistency( model, stage ) {
|
|
|
390
391
|
'args', '$syntax',
|
|
391
392
|
'where', 'groupBy', 'limit', 'orderBy', 'having',
|
|
392
393
|
'cardinality',
|
|
393
|
-
'_artifact', '
|
|
394
|
+
'_artifact', '_originalArtifact',
|
|
395
|
+
'_navigation', '_user',
|
|
394
396
|
'$inferred',
|
|
395
397
|
],
|
|
396
398
|
},
|
|
@@ -443,7 +445,10 @@ function assertConsistency( model, stage ) {
|
|
|
443
445
|
test: expression, // properties below are "sub specifications"
|
|
444
446
|
ref: {
|
|
445
447
|
requires: [ 'location', 'path' ],
|
|
446
|
-
optional: [
|
|
448
|
+
optional: [
|
|
449
|
+
'scope', 'variant', '_artifact', '_originalArtifact',
|
|
450
|
+
'$inferred', '$parens', 'sort', 'nulls',
|
|
451
|
+
],
|
|
447
452
|
},
|
|
448
453
|
none: { optional: () => true }, // parse error
|
|
449
454
|
// TODO: why optional / enough in name?
|
|
@@ -488,7 +493,7 @@ function assertConsistency( model, stage ) {
|
|
|
488
493
|
'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicates', 'upTo',
|
|
489
494
|
// expressions as annotation values
|
|
490
495
|
'$tokenTexts', 'op', 'args', 'func', '_artifact', 'type', '$typeArgs',
|
|
491
|
-
'scale', 'srid', 'length', 'precision', 'scope',
|
|
496
|
+
'scale', 'srid', 'length', 'precision', 'scope', '$parens',
|
|
492
497
|
],
|
|
493
498
|
// TODO: restrict path to #simplePath
|
|
494
499
|
},
|
|
@@ -524,7 +529,7 @@ function assertConsistency( model, stage ) {
|
|
|
524
529
|
'_effectiveType', '$effectiveSeqNo', '_origin', '_deps',
|
|
525
530
|
// CSN parser may let these properties slip through to XSN, even if input is invalid.
|
|
526
531
|
'args', 'op', 'func', 'suffix',
|
|
527
|
-
'$invalidPaths',
|
|
532
|
+
'$invalidPaths', '$parens',
|
|
528
533
|
],
|
|
529
534
|
// TODO: name requires if not in parser?
|
|
530
535
|
},
|
|
@@ -538,7 +543,7 @@ function assertConsistency( model, stage ) {
|
|
|
538
543
|
schema: {
|
|
539
544
|
id: { test: isStringOrNumber },
|
|
540
545
|
select: { test: TODO }, // TODO: remove
|
|
541
|
-
},
|
|
546
|
+
},
|
|
542
547
|
requires: [ 'location' ],
|
|
543
548
|
optional: [
|
|
544
549
|
'path', 'id', '$delimited', 'variant', // TODO: req path, opt id for main, req id for member
|
|
@@ -572,6 +577,7 @@ function assertConsistency( model, stage ) {
|
|
|
572
577
|
'elements', 'cardinality', 'target', 'on', 'foreignKeys', 'items',
|
|
573
578
|
'_outer', '_effectiveType', '$effectiveSeqNo', 'notNull', '_parent',
|
|
574
579
|
'_origin', '_block', '$inferred', '$expand', '$inCycle', '_deps',
|
|
580
|
+
'localized', // really? see #13135
|
|
575
581
|
'$calcDepElement',
|
|
576
582
|
'$syntax', '_extensions',
|
|
577
583
|
'_status', '_redirected',
|
|
@@ -607,6 +613,7 @@ function assertConsistency( model, stage ) {
|
|
|
607
613
|
// - on a path item with a filter condition to the user of the ref (not nested)
|
|
608
614
|
// - on a JOIN node to the query (TODO: _outer?)
|
|
609
615
|
_artifact: { test: TODO },
|
|
616
|
+
_originalArtifact: { test: TODO },
|
|
610
617
|
_navigation: { test: TODO },
|
|
611
618
|
_effectiveType: { kind: true, test: TODO },
|
|
612
619
|
$effectiveSeqNo: { kind: true, test: isNumber },
|
|
@@ -641,7 +648,7 @@ function assertConsistency( model, stage ) {
|
|
|
641
648
|
$replacement: { kind: true, test: TODO }, // for smart * in queries
|
|
642
649
|
_origin: { kind: true, test: TODO },
|
|
643
650
|
_calcOrigin: { kind: true, test: TODO },
|
|
644
|
-
|
|
651
|
+
_columnParent: { kind: [ 'element', undefined ], test: TODO }, // column or * (wildcard)
|
|
645
652
|
_from: { kind: true, test: TODO }, // TODO: not necessary anymore ?
|
|
646
653
|
// array of $tableAlias (or includes) for explicit and implicit redirection:
|
|
647
654
|
_redirected: { kind: true, test: TODO },
|
|
@@ -658,7 +665,8 @@ function assertConsistency( model, stage ) {
|
|
|
658
665
|
_scc: { kind: true, test: TODO }, // for cyclic calculation
|
|
659
666
|
_sccCaller: { kind: true, test: TODO }, // for cyclic calculation
|
|
660
667
|
_status: { kind: true, test: TODO }, // TODO: $status
|
|
661
|
-
_projections: { kind: true, test: TODO },
|
|
668
|
+
_projections: { kind: true, test: TODO },
|
|
669
|
+
_complexProjections: { kind: true, test: TODO }, // for projected paths with filters
|
|
662
670
|
$entity: { kind: true, test: TODO },
|
|
663
671
|
_entities: { test: TODO },
|
|
664
672
|
$compositionTargets: { test: isDictionary( isBoolean ) },
|
|
@@ -972,7 +980,7 @@ function assertConsistency( model, stage ) {
|
|
|
972
980
|
return function valWithLocation( node, parent, prop, spec, name ) {
|
|
973
981
|
const valSchema = { val: Object.assign( {}, spec, { test: func } ) };
|
|
974
982
|
const requires = [ 'val', 'location' ];
|
|
975
|
-
const optional = [ 'literal', '$inferred', '$priority', '
|
|
983
|
+
const optional = [ 'literal', '$inferred', '$priority', '_columnParent' ];
|
|
976
984
|
standard( node, parent, prop, {
|
|
977
985
|
schema: valSchema, requires, optional, instanceOf: spec.instanceOf,
|
|
978
986
|
}, name );
|
|
@@ -1033,7 +1041,7 @@ function assertConsistency( model, stage ) {
|
|
|
1033
1041
|
// TODO
|
|
1034
1042
|
// else if (spec.instanceOf && spec.instanceOf !== 'ignore' &&
|
|
1035
1043
|
// Object.getPrototypeOf( node ) !== spec.instanceOf.prototype)
|
|
1036
|
-
// eslint-disable-next-line max-len
|
|
1044
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1037
1045
|
// throw new InternalConsistencyError( `Expected object of class ${ spec.instanceOf.name } but found ${ found }${ at( [ null, parent ], prop, name ) }` );
|
|
1038
1046
|
}
|
|
1039
1047
|
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -77,6 +77,7 @@ typeParameters.list = Object.keys( typeParameters.expectedLiteralsFor );
|
|
|
77
77
|
|
|
78
78
|
|
|
79
79
|
const specialFunctions = compileFunctions( {
|
|
80
|
+
// TODO: use lower-case
|
|
80
81
|
'': [ // the default
|
|
81
82
|
{
|
|
82
83
|
intro: [ 'ALL', 'DISTINCT' ],
|
|
@@ -218,7 +219,7 @@ const dateRegEx = /^(-?\d{4})-(\d{1,2})-(\d{1,2})$/;
|
|
|
218
219
|
// YYYY - MM - dd
|
|
219
220
|
const timeRegEx = /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
|
|
220
221
|
// T HH : mm : ss TZD
|
|
221
|
-
// eslint-disable-next-line max-len
|
|
222
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
222
223
|
const timestampRegEx = /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
|
|
223
224
|
// YYYY - MM - dd T HH : mm : ss . fraction TZD
|
|
224
225
|
const numberRegEx = /^[ \t]*[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]?\d+)?[ \t]*$/i;
|
|
@@ -231,7 +232,6 @@ const numberRegEx = /^[ \t]*[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]?\d+)?[ \t]*$/i;
|
|
|
231
232
|
* - `unexpected_msg`: error message which is issued if `unexpected_char` matches
|
|
232
233
|
* - `unexpected_char`: regular expression matching an illegal character in `value`,
|
|
233
234
|
* the error location is only correct for a literal <prefix>'<value>'
|
|
234
|
-
* - `literal`: the value which is used instead of `prefix` in the AST
|
|
235
235
|
* TODO: we might do a range check (consider leap seconds, i.e. max value 60),
|
|
236
236
|
* but always allow Feb 29 (no leap year computation)
|
|
237
237
|
* Notes:
|
package/lib/compiler/checks.js
CHANGED
|
@@ -33,6 +33,8 @@ function check( model ) {
|
|
|
33
33
|
error, warning, info, message,
|
|
34
34
|
} = model.$messageFunctions;
|
|
35
35
|
|
|
36
|
+
const { getOrigin } = model.$functions;
|
|
37
|
+
|
|
36
38
|
checkSapCommonLocale( model );
|
|
37
39
|
checkSapCommonTextsAspects( model );
|
|
38
40
|
|
|
@@ -400,13 +402,18 @@ function check( model ) {
|
|
|
400
402
|
|
|
401
403
|
const expectedType = isNumeric ? 'number' : 'string';
|
|
402
404
|
|
|
405
|
+
let art = enumNode;
|
|
406
|
+
while (art?._effectiveType && art.length === undefined)
|
|
407
|
+
art = getOrigin( art );
|
|
408
|
+
const maxLength = art.length?.val ?? model.options.defaultStringLength;
|
|
409
|
+
|
|
403
410
|
// Do not check elements that don't have a value at all or are
|
|
404
411
|
// references to other enum elements. There are other checks for that.
|
|
405
412
|
const hasWrongType = element => element.value &&
|
|
406
413
|
(element.value.literal !== expectedType) &&
|
|
407
414
|
(element.value.literal !== 'enum');
|
|
408
415
|
|
|
409
|
-
for (const key
|
|
416
|
+
for (const key in enumNode.enum) {
|
|
410
417
|
const element = enumNode.enum[key];
|
|
411
418
|
if (hasWrongType( element )) {
|
|
412
419
|
const actualType = element.value.literal;
|
|
@@ -418,6 +425,18 @@ function check( model ) {
|
|
|
418
425
|
string: 'Expected string value for enum element $(NAME) but was $(PROP)',
|
|
419
426
|
} );
|
|
420
427
|
}
|
|
428
|
+
else if (isString && maxLength !== undefined) {
|
|
429
|
+
const value = element.value?.val ?? element.name.id;
|
|
430
|
+
if (value.length > maxLength) {
|
|
431
|
+
const loc = element.value?.location ?? element.name.location;
|
|
432
|
+
warning( 'def-invalid-value', [ loc, element ], {
|
|
433
|
+
'#': element.value ? 'std' : 'implicit', name: element.name.id, value: maxLength,
|
|
434
|
+
}, {
|
|
435
|
+
std: 'Enum value $(NAME) exceeds specified length $(VALUE)',
|
|
436
|
+
implicit: 'Implicit enum value $(NAME) exceeds specified length $(VALUE)',
|
|
437
|
+
} );
|
|
438
|
+
}
|
|
439
|
+
}
|
|
421
440
|
}
|
|
422
441
|
}
|
|
423
442
|
|
|
@@ -850,16 +869,16 @@ function check( model ) {
|
|
|
850
869
|
// One argument must be "$self" and the other an assoc
|
|
851
870
|
if (xpr.op.val === '=' && xpr.args.length === 2) {
|
|
852
871
|
// Tree-ish expression from the compiler (not augmented)
|
|
853
|
-
// eslint-disable-next-line max-len
|
|
872
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
854
873
|
return (isAssociationOperand( xpr.args[0] ) && isDollarSelfOrProjectionOperand( xpr.args[1] ) ||
|
|
855
|
-
// eslint-disable-next-line max-len
|
|
874
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
856
875
|
isAssociationOperand( xpr.args[1] ) && isDollarSelfOrProjectionOperand( xpr.args[0] ));
|
|
857
876
|
}
|
|
858
877
|
else if (xpr.args.length === 3 && xpr.args[1].val === '=') {
|
|
859
878
|
// Tree-ish expression from the compiler (not augmented)
|
|
860
|
-
// eslint-disable-next-line max-len
|
|
879
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
861
880
|
return (isAssociationOperand( xpr.args[0] ) && isDollarSelfOrProjectionOperand( xpr.args[2] ) ||
|
|
862
|
-
// eslint-disable-next-line max-len
|
|
881
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
863
882
|
isAssociationOperand( xpr.args[2] ) && isDollarSelfOrProjectionOperand( xpr.args[0] ));
|
|
864
883
|
}
|
|
865
884
|
|
|
@@ -1054,7 +1073,7 @@ function check( model ) {
|
|
|
1054
1073
|
value.literal !== 'timestamp' && value.literal !== 'string') {
|
|
1055
1074
|
// Hm, actually date and time cannot be mixed
|
|
1056
1075
|
warning( null, loc, { type, anno },
|
|
1057
|
-
// eslint-disable-next-line max-len
|
|
1076
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1058
1077
|
'A date/time value or a string is required for type $(TYPE) for annotation $(ANNO)' );
|
|
1059
1078
|
}
|
|
1060
1079
|
}
|
package/lib/compiler/define.js
CHANGED
|
@@ -174,7 +174,6 @@ function define( model ) {
|
|
|
174
174
|
shuffleArray,
|
|
175
175
|
initArtifact,
|
|
176
176
|
initMembers,
|
|
177
|
-
checkDefinitions, // TODO: remove
|
|
178
177
|
initSelectItems,
|
|
179
178
|
} );
|
|
180
179
|
|
|
@@ -188,13 +187,12 @@ function define( model ) {
|
|
|
188
187
|
function doDefine() {
|
|
189
188
|
if (options.deprecated &&
|
|
190
189
|
messages.every( m => m.messageId !== 'api-deprecated-option' )) {
|
|
191
|
-
warning( 'api-deprecated-option', {},
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
} );
|
|
190
|
+
warning( 'api-deprecated-option', {}, {
|
|
191
|
+
prop: 'deprecated', '#': (options.beta ? 'beta' : 'std'),
|
|
192
|
+
}, {
|
|
193
|
+
std: 'With option $(PROP), recent features are disabled',
|
|
194
|
+
beta: 'With option $(PROP), beta features and other recent features are disabled',
|
|
195
|
+
} );
|
|
198
196
|
}
|
|
199
197
|
model.definitions = Object.create( null );
|
|
200
198
|
setLink( model, '_entities', [] ); // for entities with includes
|
|
@@ -317,7 +315,7 @@ function define( model ) {
|
|
|
317
315
|
if (artifacts[using])
|
|
318
316
|
continue;
|
|
319
317
|
// TODO: enable optional locations
|
|
320
|
-
const location = a.name.path
|
|
318
|
+
const location = a.name.path?.[0]?.location || a.location;
|
|
321
319
|
const absolute = prefix + using;
|
|
322
320
|
artifacts[using] = {
|
|
323
321
|
kind: 'using', // !, not namespace - we do not know artifact yet
|
|
@@ -350,7 +348,7 @@ function define( model ) {
|
|
|
350
348
|
return;
|
|
351
349
|
decl.extern.id = pathName( path );
|
|
352
350
|
if (!decl.name)
|
|
353
|
-
decl.name = { ...path
|
|
351
|
+
decl.name = { ...path.at(-1), $inferred: 'as' };
|
|
354
352
|
const name = decl.name.id;
|
|
355
353
|
// TODO: check name: no "."
|
|
356
354
|
const found = src.artifacts[name];
|
|
@@ -365,7 +363,7 @@ function define( model ) {
|
|
|
365
363
|
function addNamespace( namespace, src ) {
|
|
366
364
|
// create using for own namespace:
|
|
367
365
|
// TODO: should we really do that (in v6)? See also initNamespaceAndUsing().
|
|
368
|
-
const last = namespace.path
|
|
366
|
+
const last = namespace.path.at(-1);
|
|
369
367
|
const { id } = last;
|
|
370
368
|
if (src.artifacts[id] || last.id.includes( '.' ))
|
|
371
369
|
// not used as we have a definition/using with that name, or dotted last path id
|
|
@@ -437,8 +435,8 @@ function define( model ) {
|
|
|
437
435
|
} );
|
|
438
436
|
if (parent.kind !== 'extend')
|
|
439
437
|
return;
|
|
440
|
-
|
|
441
|
-
|
|
438
|
+
// TODO: sub queries? expand/inline?
|
|
439
|
+
parent.columns?.forEach( c => setLink( c, '_block', parent._block ) );
|
|
442
440
|
if (parent.scale && !parent.precision) {
|
|
443
441
|
// TODO: where could we store the location of the name?
|
|
444
442
|
error( 'syntax-missing-type-property', [ parent.scale.location ],
|
|
@@ -794,7 +792,7 @@ function define( model ) {
|
|
|
794
792
|
setMemberParent( table, query.name.id, query ); // sets _parent,_main
|
|
795
793
|
initSubQuery( table ); // init sub queries in ON
|
|
796
794
|
const aliases = Object.keys( table.$tableAliases || {} );
|
|
797
|
-
// Use first
|
|
795
|
+
// Use first table alias name on the right side of the join to name the
|
|
798
796
|
// (internal) query, should only be relevant for --raw-output, not for
|
|
799
797
|
// user messages or references - TODO: correct if join on left?
|
|
800
798
|
table.name.id = aliases[1] || aliases[0] || '<unknown>';
|
|
@@ -903,9 +901,9 @@ function define( model ) {
|
|
|
903
901
|
hasItems = true;
|
|
904
902
|
if (!columns) { // expand or inline
|
|
905
903
|
if (parent.value)
|
|
906
|
-
setLink( col, '
|
|
907
|
-
else if (parent.
|
|
908
|
-
setLink( col, '
|
|
904
|
+
setLink( col, '_columnParent', parent ); // also set for '*' in expand/inline
|
|
905
|
+
else if (parent._columnParent)
|
|
906
|
+
setLink( col, '_columnParent', parent._columnParent );
|
|
909
907
|
}
|
|
910
908
|
if (col.val === '*') {
|
|
911
909
|
if (!wildcard) {
|
|
@@ -919,16 +917,15 @@ function define( model ) {
|
|
|
919
917
|
else {
|
|
920
918
|
// a late syntax error (this code also runs with parse-cdl), i.e.
|
|
921
919
|
// no semantic loc (wouldn't be available for expand/inline anyway)
|
|
922
|
-
error( 'syntax-duplicate-wildcard', [ col.location, null ],
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
} );
|
|
920
|
+
error( 'syntax-duplicate-wildcard', [ col.location, null ], {
|
|
921
|
+
'#': (wildcard.location.col ? 'col' : 'std'),
|
|
922
|
+
prop: '*',
|
|
923
|
+
line: wildcard.location.line,
|
|
924
|
+
col: wildcard.location.col,
|
|
925
|
+
}, {
|
|
926
|
+
std: 'You have provided a $(PROP) already in line $(LINE)',
|
|
927
|
+
col: 'You have provided a $(PROP) already at line $(LINE), column $(COL)',
|
|
928
|
+
} );
|
|
932
929
|
// TODO: extra text variants for expand/inline? - probably not
|
|
933
930
|
col.val = null; // do not consider it for expandWildcard()
|
|
934
931
|
}
|
|
@@ -1073,7 +1070,7 @@ function define( model ) {
|
|
|
1073
1070
|
// We do not want to complain separately about all element properties:
|
|
1074
1071
|
error( 'ext-unexpected-element', [ e.location, construct ],
|
|
1075
1072
|
{ name: e.name.id, code: 'extend … with enum' },
|
|
1076
|
-
// eslint-disable-next-line max-len
|
|
1073
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1077
1074
|
'Unexpected elements like $(NAME) in an extension for an enum. Additionally, use $(CODE) when extending enums' );
|
|
1078
1075
|
// Don't emit 'ext-expecting-enum' if this error is emitted.
|
|
1079
1076
|
return;
|
|
@@ -1094,7 +1091,7 @@ function define( model ) {
|
|
|
1094
1091
|
|
|
1095
1092
|
function initAnonymousAspect() {
|
|
1096
1093
|
// TODO: main?
|
|
1097
|
-
const inEntity = parent._main
|
|
1094
|
+
const inEntity = parent._main?.kind === 'entity';
|
|
1098
1095
|
// TODO: also allow indirectly (component in component in entity)?
|
|
1099
1096
|
setLink( targetAspect, '_outer', obj );
|
|
1100
1097
|
setLink( targetAspect, '_parent', parent._parent );
|
|
@@ -1205,7 +1202,7 @@ function define( model ) {
|
|
|
1205
1202
|
if (path?.length === 1 && path[0]?.id === '$self') { // TODO: no where: ?
|
|
1206
1203
|
const $self = main.$tableAliases?.$self ||
|
|
1207
1204
|
main.kind === 'extend' && { name: { id: '$self' } };
|
|
1208
|
-
// remark: an extend has no "table alias" `$self` (relevant for parse-cdl)
|
|
1205
|
+
// remark: an 'extend' has no "table alias" `$self` (relevant for parse-cdl)
|
|
1209
1206
|
setLink( type, '_artifact', $self );
|
|
1210
1207
|
setLink( path[0], '_artifact', $self );
|
|
1211
1208
|
}
|
|
@@ -1258,7 +1255,6 @@ function define( model ) {
|
|
|
1258
1255
|
return false;
|
|
1259
1256
|
}
|
|
1260
1257
|
}
|
|
1261
|
-
//
|
|
1262
1258
|
else if (parent.kind === 'action' || parent.kind === 'function') {
|
|
1263
1259
|
error( 'ext-unexpected-action', [ construct.location, construct ], { '#': parent.kind }, {
|
|
1264
1260
|
std: 'Actions and functions can\'t be extended, only annotated', // TODO: → ext-unsupported
|
package/lib/compiler/extend.js
CHANGED
|
@@ -441,8 +441,8 @@ function extend( model ) {
|
|
|
441
441
|
function applySingleExtension( art, ext, prop ) {
|
|
442
442
|
if (prop === 'includes') {
|
|
443
443
|
if (ext.kind === 'extend' && art.$inferred) {
|
|
444
|
-
error( 'extend-for-generated', [ ext.name.location, ext ], { art },
|
|
445
|
-
'You can\'t use
|
|
444
|
+
error( 'extend-for-generated', [ ext.name.location, ext ], { art, keyword: 'extend' },
|
|
445
|
+
'You can\'t use $(KEYWORD) on the generated $(ART)' );
|
|
446
446
|
}
|
|
447
447
|
else if (art.kind !== 'annotate' && !art._outer) { // not with elem extension in targetAspect
|
|
448
448
|
const { id } = art.name;
|
|
@@ -543,7 +543,7 @@ function extend( model ) {
|
|
|
543
543
|
{
|
|
544
544
|
std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
|
|
545
545
|
array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
|
|
546
|
-
// eslint-disable-next-line max-len
|
|
546
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
547
547
|
struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
|
|
548
548
|
boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
|
|
549
549
|
null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
|
|
@@ -640,9 +640,9 @@ function extend( model ) {
|
|
|
640
640
|
}
|
|
641
641
|
else if (extVal < artVal + (scaleDiff || 0)) {
|
|
642
642
|
const number = artVal + (scaleDiff || 0);
|
|
643
|
-
error( 'ext-invalid-type-property', [ ext[prop].location, ext ],
|
|
644
|
-
|
|
645
|
-
|
|
643
|
+
error( 'ext-invalid-type-property', [ ext[prop].location, ext ], {
|
|
644
|
+
'#': (scaleDiff ? 'scale' : 'number'), prop, number, otherprop: 'scale',
|
|
645
|
+
} );
|
|
646
646
|
}
|
|
647
647
|
else {
|
|
648
648
|
art[prop] = ext[prop];
|
|
@@ -797,7 +797,7 @@ function extend( model ) {
|
|
|
797
797
|
const dict = parent[prop];
|
|
798
798
|
if (!dict) {
|
|
799
799
|
// TODO: check - for each name? - better locations
|
|
800
|
-
const location = ext._parent[prop]?.[$location] || ext.name.location;
|
|
800
|
+
const location = ext._parent?.[prop]?.[$location] || ext.name.location;
|
|
801
801
|
// Remark: no `elements` dict location with `annotate Main:elem`
|
|
802
802
|
switch (prop) {
|
|
803
803
|
// TODO: change texts, somehow similar to checkDefinitions() ?
|
|
@@ -1052,8 +1052,8 @@ function extend( model ) {
|
|
|
1052
1052
|
if (ext.name._artifact === undefined) { // not already applied
|
|
1053
1053
|
setArtifactLink( ext.name, art );
|
|
1054
1054
|
if (noExtend && ext.kind === 'extend') {
|
|
1055
|
-
error( 'extend-for-generated', [ ext.name.location, ext ], { art },
|
|
1056
|
-
'You can\'t use
|
|
1055
|
+
error( 'extend-for-generated', [ ext.name.location, ext ], { art, keyword: 'extend' },
|
|
1056
|
+
'You can\'t use $(KEYWORD) on the generated $(ART)' );
|
|
1057
1057
|
continue;
|
|
1058
1058
|
}
|
|
1059
1059
|
if (ext.includes) {
|
|
@@ -1170,14 +1170,11 @@ function extend( model ) {
|
|
|
1170
1170
|
// TODO: use shared functionality with notFound in resolver.js
|
|
1171
1171
|
const { location } = ext.name;
|
|
1172
1172
|
extMain.kind = ext.kind;
|
|
1173
|
-
const msg
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
element: 'Artifact $(ART) has no element or enum $(MEMBER) - nothing to extend',
|
|
1179
|
-
action: 'Artifact $(ART) has no action $(MEMBER) - nothing to extend',
|
|
1180
|
-
} );
|
|
1173
|
+
const msg = error( 'extend-undefined', [ location, artName ], { art: artName }, {
|
|
1174
|
+
std: 'Unknown $(ART) - nothing to extend',
|
|
1175
|
+
element: 'Artifact $(ART) has no element or enum $(MEMBER) - nothing to extend',
|
|
1176
|
+
action: 'Artifact $(ART) has no action $(MEMBER) - nothing to extend',
|
|
1177
|
+
} );
|
|
1181
1178
|
attachAndEmitValidNames( msg, validDict );
|
|
1182
1179
|
}
|
|
1183
1180
|
}
|
|
@@ -1192,6 +1189,7 @@ function extend( model ) {
|
|
|
1192
1189
|
*
|
|
1193
1190
|
* @param {XSN.Definition} art
|
|
1194
1191
|
* @param {XSN.Artifact} target
|
|
1192
|
+
* @param {string[]} [justResolveCyclic]
|
|
1195
1193
|
* @returns {boolean}
|
|
1196
1194
|
*/
|
|
1197
1195
|
function canApplyIncludes( art, target, justResolveCyclic ) {
|
|
@@ -1272,7 +1270,7 @@ function extend( model ) {
|
|
|
1272
1270
|
*
|
|
1273
1271
|
* @param {XSN.Extension} ext
|
|
1274
1272
|
* @param {XSN.Artifact} art
|
|
1275
|
-
* @param {string} prop
|
|
1273
|
+
* @param {string} prop 'elements' or 'actions'
|
|
1276
1274
|
*/
|
|
1277
1275
|
function includeMembers( ext, art, prop ) {
|
|
1278
1276
|
// TODO two kind of messages:
|
package/lib/compiler/generate.js
CHANGED
|
@@ -103,7 +103,7 @@ function generate( model ) {
|
|
|
103
103
|
const lang = textsAspect.elements.language;
|
|
104
104
|
error( 'def-unexpected-element', [ lang.name.location, lang ],
|
|
105
105
|
{ option: 'addTextsLanguageAssoc', art: textsAspect, name: 'language' },
|
|
106
|
-
// eslint-disable-next-line max-len
|
|
106
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
107
107
|
'$(ART) is not used because option $(OPTION) conflicts with existing element $(NAME); remove either option or element' );
|
|
108
108
|
hasError = true;
|
|
109
109
|
}
|
|
@@ -244,7 +244,7 @@ function generate( model ) {
|
|
|
244
244
|
(fioriEnabled && art.elements.ID_texts)) {
|
|
245
245
|
// TODO if we have too much time: check all elements of texts entity for safety
|
|
246
246
|
warning( null, [ art.name.location, art ], { art: textsEntity },
|
|
247
|
-
// eslint-disable-next-line max-len
|
|
247
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
248
248
|
'Texts entity $(ART) can\'t be created as there is another definition with that name' );
|
|
249
249
|
info( null, [ textsEntity.name.location, textsEntity ], { art },
|
|
250
250
|
'Texts entity for $(ART) can\'t be created with this definition' );
|
|
@@ -639,7 +639,7 @@ function generate( model ) {
|
|
|
639
639
|
}
|
|
640
640
|
if (model.definitions[entityName]) {
|
|
641
641
|
error( null, [ location, elem ], { art: entityName },
|
|
642
|
-
// eslint-disable-next-line max-len
|
|
642
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
643
643
|
'Target entity $(ART) can\'t be created as there is another definition with this name' );
|
|
644
644
|
return false;
|
|
645
645
|
}
|
package/lib/compiler/populate.js
CHANGED
|
@@ -632,6 +632,12 @@ function populate( model ) {
|
|
|
632
632
|
}
|
|
633
633
|
}
|
|
634
634
|
|
|
635
|
+
/**
|
|
636
|
+
* Set type properties of specified elements on the inferred artifact, but only
|
|
637
|
+
* assign them if their values differs from the inferred ones (for better locations).
|
|
638
|
+
*
|
|
639
|
+
* @param {XSN.Artifact} art
|
|
640
|
+
*/
|
|
635
641
|
function setSpecifiedElementTypeProperties( art ) {
|
|
636
642
|
for (const prop in art.typeProps$) {
|
|
637
643
|
let o = art;
|
|
@@ -789,8 +795,8 @@ function populate( model ) {
|
|
|
789
795
|
return col.name.id;
|
|
790
796
|
}
|
|
791
797
|
}
|
|
792
|
-
else if (col.expand || col.value && (col.
|
|
793
|
-
//
|
|
798
|
+
else if (col.expand || col.value && (col._columnParent || query._parent.kind !== 'select')) {
|
|
799
|
+
// _columnParent => inline/expand; _parent -> only allowed in sub-selects
|
|
794
800
|
error( 'query-req-name', [ col.value?.location || col.location, query ], {},
|
|
795
801
|
'Alias name is required for this select item' );
|
|
796
802
|
}
|
|
@@ -867,7 +873,7 @@ function populate( model ) {
|
|
|
867
873
|
const inferred = query._main.$inferred;
|
|
868
874
|
const excludingDict = (colParent || query).excludingDict || Object.create( null );
|
|
869
875
|
|
|
870
|
-
const envParent = wildcard.
|
|
876
|
+
const envParent = wildcard._columnParent;
|
|
871
877
|
const env = wildcardColumnEnv( wildcard, query );
|
|
872
878
|
if (!env)
|
|
873
879
|
return;
|
|
@@ -931,7 +937,7 @@ function populate( model ) {
|
|
|
931
937
|
// already done in populateQuery (TODO: change that and check whether
|
|
932
938
|
// `*` is allowed at all in definer)
|
|
933
939
|
if (!colParent || colParent.value._artifact) {
|
|
934
|
-
// avoid "not found" messages if
|
|
940
|
+
// avoid "not found" messages if columnParent can't be found
|
|
935
941
|
const user = colParent || query;
|
|
936
942
|
for (const name in user.excludingDict)
|
|
937
943
|
resolveExcluding( name, env, excludingDict, query );
|
|
@@ -939,9 +945,9 @@ function populate( model ) {
|
|
|
939
945
|
}
|
|
940
946
|
}
|
|
941
947
|
|
|
942
|
-
function wildcardColumnEnv( wildcard, query ) { // etc. wildcard.
|
|
948
|
+
function wildcardColumnEnv( wildcard, query ) { // etc. wildcard._columnParent;
|
|
943
949
|
// if (envParent) console.log( 'CE:', envParent._origin, query );
|
|
944
|
-
const colParent = wildcard.
|
|
950
|
+
const colParent = wildcard._columnParent;
|
|
945
951
|
if (!colParent)
|
|
946
952
|
return userQuery( query )._combined; // see combinedSourcesOrParentElements
|
|
947
953
|
|
|
@@ -972,27 +978,27 @@ function populate( model ) {
|
|
|
972
978
|
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
973
979
|
info( 'wildcard-excluding-many', [ sibling.name.location, query ],
|
|
974
980
|
{ id, keyword: 'excluding' },
|
|
975
|
-
// eslint-disable-next-line max-len
|
|
981
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
976
982
|
'This select item replaces $(ID) from two or more sources. Add $(ID) to $(KEYWORD) to silence this message' );
|
|
977
983
|
}
|
|
978
984
|
else {
|
|
979
985
|
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
980
986
|
info( 'wildcard-excluding-one', [ sibling.name.location, query ],
|
|
981
987
|
{ id, alias: navElem._parent.name.id, keyword: 'excluding' },
|
|
982
|
-
// eslint-disable-next-line max-len
|
|
988
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
983
989
|
'This select item replaces $(ID) from table alias $(ALIAS). Add $(ID) to $(KEYWORD) to silence this message' );
|
|
984
990
|
}
|
|
985
991
|
}
|
|
986
992
|
}
|
|
987
993
|
|
|
988
|
-
function setWildcardExpandInline( queryElem,
|
|
989
|
-
setLink( queryElem, '
|
|
994
|
+
function setWildcardExpandInline( queryElem, columnParent, origin, name, location ) {
|
|
995
|
+
setLink( queryElem, '_columnParent', columnParent );
|
|
990
996
|
const path = [ { id: name, location } ];
|
|
991
997
|
queryElem.value = { path, location }; // TODO: can we omit that? We have _origin
|
|
992
998
|
setArtifactLink( path[0], origin );
|
|
993
999
|
setLink( queryElem, '_origin', origin );
|
|
994
1000
|
// set _projections when inline with table alias:
|
|
995
|
-
// const alias =
|
|
1001
|
+
// const alias = columnParent?.value?.path?.[0]?._navigation;
|
|
996
1002
|
// if (alias?.kind === '$tableAlias')
|
|
997
1003
|
// pushLink( alias.elements[name], '_projections', queryElem );
|
|
998
1004
|
}
|
|
@@ -1106,9 +1112,9 @@ function populate( model ) {
|
|
|
1106
1112
|
// art: definitionScope( target ), - TODO extra debug info in message
|
|
1107
1113
|
sorted_arts: exposed,
|
|
1108
1114
|
}, {
|
|
1109
|
-
// eslint-disable-next-line max-len
|
|
1115
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1110
1116
|
std: 'Replace target $(TARGET) by one of $(SORTED_ARTS); can\'t auto-redirect this association if multiple projections exist in this service',
|
|
1111
|
-
// eslint-disable-next-line max-len
|
|
1117
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1112
1118
|
two: 'Replace target $(TARGET) by $(SORTED_ARTS) or $(SECOND); can\'t auto-redirect this association if multiple projections exist in this service',
|
|
1113
1119
|
} );
|
|
1114
1120
|
// continuation semantics: no auto-redirection
|
|
@@ -1130,11 +1136,11 @@ function populate( model ) {
|
|
|
1130
1136
|
anno: 'cds.redirection.target',
|
|
1131
1137
|
sorted_arts: exposed,
|
|
1132
1138
|
}, {
|
|
1133
|
-
// eslint-disable-next-line max-len
|
|
1139
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1134
1140
|
std: 'Add $(ANNO) to one of $(SORTED_ARTS) to select the entity as redirection target for $(TARGET) in this service; can\'t auto-redirect $(ART) otherwise',
|
|
1135
|
-
// eslint-disable-next-line max-len
|
|
1141
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1136
1142
|
two: 'Add $(ANNO) to either $(SORTED_ARTS) or $(SECOND) to select the entity as redirection target for $(TARGET) in this service; can\'t auto-redirect $(ART) otherwise',
|
|
1137
|
-
// eslint-disable-next-line max-len
|
|
1143
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1138
1144
|
justOne: 'Remove $(ANNO) from all but one of $(SORTED_ARTS) to have a unique redirection target for $(TARGET) in this service; can\'t auto-redirect $(ART) otherwise',
|
|
1139
1145
|
} );
|
|
1140
1146
|
}
|
|
@@ -66,6 +66,7 @@ function propagate( model ) {
|
|
|
66
66
|
__proto__: null,
|
|
67
67
|
never,
|
|
68
68
|
onlyViaArtifact,
|
|
69
|
+
onlyViaParent,
|
|
69
70
|
notWithPersistenceTable,
|
|
70
71
|
};
|
|
71
72
|
for (const rule in propagationRules)
|
|
@@ -73,7 +74,7 @@ function propagate( model ) {
|
|
|
73
74
|
|
|
74
75
|
const { options } = model;
|
|
75
76
|
const { rewriteAnnotationsRefs } = xprRewriteFns( model );
|
|
76
|
-
// eslint-disable-next-line max-len
|
|
77
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
77
78
|
const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, '_oldVirtualNotNullPropagation' );
|
|
78
79
|
const { warning, throwWithError } = model.$messageFunctions;
|
|
79
80
|
|
|
@@ -367,7 +368,7 @@ function propagate( model ) {
|
|
|
367
368
|
const art = item && item._artifact;
|
|
368
369
|
if (art && art.virtual && art.virtual.val) {
|
|
369
370
|
warning( 'def-missing-virtual', [ item.location, elem ], { art, keyword: 'virtual' },
|
|
370
|
-
// eslint-disable-next-line max-len
|
|
371
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
371
372
|
'Prepend $(KEYWORD) to current select item - referred element $(ART) is virtual which is not inherited' );
|
|
372
373
|
return;
|
|
373
374
|
}
|