@sap/cds-compiler 2.15.8 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -1590
- package/bin/.eslintrc.json +2 -1
- package/bin/cdsc.js +61 -46
- package/doc/API.md +11 -0
- package/doc/CHANGELOG_ARCHIVE.md +1592 -0
- package/doc/CHANGELOG_BETA.md +26 -5
- package/doc/CHANGELOG_DEPRECATED.md +55 -1
- package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
- package/doc/Versioning.md +20 -1
- package/lib/api/.eslintrc.json +2 -2
- package/lib/api/main.js +282 -156
- package/lib/api/options.js +17 -88
- package/lib/api/validate.js +6 -10
- package/lib/base/keywords.js +280 -110
- package/lib/base/message-registry.js +85 -25
- package/lib/base/messages.js +119 -89
- package/lib/base/model.js +46 -2
- package/lib/base/optionProcessorHelper.js +53 -21
- package/lib/checks/actionsFunctions.js +15 -12
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +1 -0
- package/lib/checks/elements.js +6 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -1
- package/lib/checks/selectItems.js +101 -15
- package/lib/checks/types.js +7 -8
- package/lib/checks/utils.js +2 -2
- package/lib/checks/validator.js +3 -3
- package/lib/compiler/assert-consistency.js +78 -21
- package/lib/compiler/base.js +6 -4
- package/lib/compiler/builtins.js +177 -10
- package/lib/compiler/checks.js +1 -1
- package/lib/compiler/define.js +28 -23
- package/lib/compiler/extend.js +75 -18
- package/lib/compiler/finalize-parse-cdl.js +25 -18
- package/lib/compiler/index.js +27 -11
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +26 -39
- package/lib/compiler/propagator.js +12 -7
- package/lib/compiler/resolve.js +207 -236
- package/lib/compiler/shared.js +100 -93
- package/lib/compiler/tweak-assocs.js +13 -20
- package/lib/compiler/utils.js +20 -6
- package/lib/edm/annotations/preprocessAnnotations.js +12 -13
- package/lib/edm/csn2edm.js +35 -37
- package/lib/edm/edm.js +22 -13
- package/lib/edm/edmAnnoPreprocessor.js +349 -0
- package/lib/edm/edmInboundChecks.js +85 -0
- package/lib/edm/edmPreprocessor.js +338 -689
- package/lib/edm/edmUtils.js +97 -67
- package/lib/gen/Dictionary.json +29 -9
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +8 -31
- package/lib/gen/language.tokens +105 -114
- package/lib/gen/languageLexer.interp +1 -34
- package/lib/gen/languageLexer.js +892 -1007
- package/lib/gen/languageLexer.tokens +95 -106
- package/lib/gen/languageParser.js +20629 -22474
- package/lib/inspect/.eslintrc.json +4 -0
- package/lib/inspect/index.js +14 -0
- package/lib/inspect/inspectModelStatistics.js +81 -0
- package/lib/inspect/inspectPropagation.js +189 -0
- package/lib/inspect/inspectUtils.js +44 -0
- package/lib/json/from-csn.js +74 -69
- package/lib/json/to-csn.js +17 -14
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/docCommentParser.js +61 -38
- package/lib/language/errorStrategy.js +52 -40
- package/lib/language/genericAntlrParser.js +424 -292
- package/lib/language/language.g4 +604 -687
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +28 -42
- package/lib/main.js +104 -81
- package/lib/model/api.js +1 -1
- package/lib/model/csnRefs.js +57 -30
- package/lib/model/csnUtils.js +189 -287
- package/lib/model/revealInternalProperties.js +32 -10
- package/lib/model/sortViews.js +32 -31
- package/lib/modelCompare/compare.js +3 -0
- package/lib/optionProcessor.js +91 -57
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/DuplicateChecker.js +4 -7
- package/lib/render/manageConstraints.js +70 -2
- package/lib/render/toCdl.js +387 -367
- package/lib/render/toHdbcds.js +20 -16
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +81 -59
- package/lib/render/utils/common.js +16 -3
- package/lib/render/utils/sql.js +20 -19
- package/lib/sql-identifier.js +6 -0
- package/lib/transform/db/.eslintrc.json +3 -2
- package/lib/transform/db/associations.js +43 -35
- package/lib/transform/db/cdsPersistence.js +5 -16
- package/lib/transform/db/constraints.js +1 -1
- package/lib/transform/db/expansion.js +7 -6
- package/lib/transform/db/flattening.js +16 -18
- package/lib/transform/db/transformExists.js +7 -5
- package/lib/transform/db/views.js +3 -3
- package/lib/transform/draft/.eslintrc.json +2 -2
- package/lib/transform/draft/db.js +6 -6
- package/lib/transform/draft/odata.js +6 -7
- package/lib/transform/forHanaNew.js +30 -24
- package/lib/transform/forOdataNew.js +14 -16
- package/lib/transform/localized.js +35 -25
- package/lib/transform/odata/toFinalBaseType.js +10 -10
- package/lib/transform/odata/typesExposure.js +17 -8
- package/lib/transform/odata/utils.js +1 -38
- package/lib/transform/transformUtilsNew.js +63 -77
- package/lib/transform/translateAssocsToJoins.js +2 -2
- package/lib/transform/universalCsn/.eslintrc.json +2 -2
- package/lib/transform/universalCsn/coreComputed.js +11 -6
- package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
- package/lib/utils/file.js +31 -21
- package/lib/utils/moduleResolve.js +0 -1
- package/lib/utils/timetrace.js +20 -21
- package/package.json +34 -4
- package/share/messages/syntax-expected-integer.md +9 -8
- package/doc/ApiMigration.md +0 -237
- package/doc/CommandLineMigration.md +0 -58
- package/doc/ErrorMessages.md +0 -175
- package/doc/FioriAnnotations.md +0 -94
- package/doc/ODataTransformation.md +0 -273
- package/lib/backends.js +0 -529
- package/lib/checks/unknownMagic.js +0 -41
- package/lib/fix_antlr4-8_warning.js +0 -56
|
@@ -97,7 +97,6 @@ function assertConsistency( model, stage ) {
|
|
|
97
97
|
'$lateExtensions',
|
|
98
98
|
'_entities', '$entity',
|
|
99
99
|
'$blocks',
|
|
100
|
-
'$newfeatures',
|
|
101
100
|
'$messageFunctions',
|
|
102
101
|
'$functions',
|
|
103
102
|
'$volatileFunctions',
|
|
@@ -118,8 +117,10 @@ function assertConsistency( model, stage ) {
|
|
|
118
117
|
'$sources',
|
|
119
118
|
],
|
|
120
119
|
},
|
|
121
|
-
location: {
|
|
122
|
-
|
|
120
|
+
location: {
|
|
121
|
+
// every thing with a $location in CSN must have a XSN location even
|
|
122
|
+
// with syntax errors (currently even internal artifacts like $using):
|
|
123
|
+
isRequired: parent => noSyntaxErrors() || parent && parent.kind,
|
|
123
124
|
kind: true,
|
|
124
125
|
requires: [ 'file' ], // line is optional in top-level location
|
|
125
126
|
optional: [ 'line', 'col', 'endLine', 'endCol', '$notFound' ],
|
|
@@ -141,7 +142,6 @@ function assertConsistency( model, stage ) {
|
|
|
141
142
|
},
|
|
142
143
|
fileDep: { test: TODO }, // in usings
|
|
143
144
|
$frontend: { parser: true, test: isString, enum: [ 'cdl', 'json', 'xml' ] },
|
|
144
|
-
$newfeatures: { test: TODO }, // if new features have been used which break the old backends
|
|
145
145
|
messages: {
|
|
146
146
|
enumerable: () => true, // does not matter (non-enum std), enum in CSN/XML parser
|
|
147
147
|
test: isArray( TODO ),
|
|
@@ -173,7 +173,6 @@ function assertConsistency( model, stage ) {
|
|
|
173
173
|
return innerDict( val, parent, lang, spec );
|
|
174
174
|
} ),
|
|
175
175
|
},
|
|
176
|
-
_assocSources: { kind: true, test: TODO }, // just null: isArray( inDefinitions ) during resolve
|
|
177
176
|
$magicVariables: {
|
|
178
177
|
// $magicVariables contains "builtin" artifacts that differ from
|
|
179
178
|
// "normal artifacts" and therefore have a custom schema
|
|
@@ -185,12 +184,16 @@ function assertConsistency( model, stage ) {
|
|
|
185
184
|
// are missing the location property
|
|
186
185
|
test: isDictionary( definition ),
|
|
187
186
|
requires: [ 'kind', 'name' ],
|
|
188
|
-
optional: [
|
|
187
|
+
optional: [
|
|
188
|
+
'elements', '$autoElement', '$uncheckedElements',
|
|
189
|
+
'$requireElementAccess', '_effectiveType', '_deps',
|
|
190
|
+
],
|
|
189
191
|
schema: {
|
|
190
192
|
kind: { test: isString, enum: [ 'builtin' ] },
|
|
191
193
|
name: { test: isObject, requires: [ 'id', 'element' ] },
|
|
192
194
|
$autoElement: { test: isString },
|
|
193
195
|
$uncheckedElements: { test: isBoolean },
|
|
196
|
+
$requireElementAccess: { test: isBoolean },
|
|
194
197
|
// missing location for normal "elements"
|
|
195
198
|
elements: { test: TODO },
|
|
196
199
|
},
|
|
@@ -217,8 +220,7 @@ function assertConsistency( model, stage ) {
|
|
|
217
220
|
usings: {
|
|
218
221
|
test: isArray(),
|
|
219
222
|
requires: [ 'kind', 'location' ],
|
|
220
|
-
optional: [ 'name', 'extern', 'usings', '
|
|
221
|
-
// TODO: get rid of $annotations: []
|
|
223
|
+
optional: [ 'name', 'extern', 'usings', 'fileDep' ],
|
|
222
224
|
},
|
|
223
225
|
extern: {
|
|
224
226
|
requires: [ 'location', 'path' ],
|
|
@@ -251,6 +253,7 @@ function assertConsistency( model, stage ) {
|
|
|
251
253
|
'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit',
|
|
252
254
|
'_projections', '_block', '_parent', '_main', '_effectiveType', '$expand',
|
|
253
255
|
'$tableAliases', 'kind', '_$next', '_combined', '$inlines', '_status',
|
|
256
|
+
'_extension', // for unapplied extensions
|
|
254
257
|
],
|
|
255
258
|
},
|
|
256
259
|
none: { optional: () => true }, // parse error
|
|
@@ -304,7 +307,6 @@ function assertConsistency( model, stage ) {
|
|
|
304
307
|
kind: 'element',
|
|
305
308
|
test: isDictionary( definition ), // definition since redef
|
|
306
309
|
requires: [ 'location', 'name' ],
|
|
307
|
-
optional: [ '$annotations' ], // TODO: get rid of annos: []
|
|
308
310
|
},
|
|
309
311
|
orderBy: { inherits: 'value', test: isArray( expression ) },
|
|
310
312
|
sort: { test: locationVal( isString ), enum: [ 'asc', 'desc' ] },
|
|
@@ -371,13 +373,21 @@ function assertConsistency( model, stage ) {
|
|
|
371
373
|
},
|
|
372
374
|
// locations of parentheses pairs around expression:
|
|
373
375
|
$parens: { parser: true, test: TODO },
|
|
376
|
+
$prefix: { test: isString }, // compiler-corrected path prefix
|
|
374
377
|
$syntax: {
|
|
375
378
|
parser: true,
|
|
376
379
|
kind: [ 'entity', 'view', 'type', 'aspect' ],
|
|
377
380
|
test: isString, // CSN parser should check for 'entity', 'view', 'projection'
|
|
378
381
|
},
|
|
379
382
|
value: {
|
|
380
|
-
optional: [
|
|
383
|
+
optional: [
|
|
384
|
+
'location', '$inferred', 'sort', 'nulls',
|
|
385
|
+
'param', 'scope', // for dynamic parameter '?'
|
|
386
|
+
// through cast() with enum through CSN->XSN
|
|
387
|
+
// TODO: re-check #9225, this should be directly in the query element,
|
|
388
|
+
// not inside value, no `enum` inside `cast`!
|
|
389
|
+
'elements', 'items', 'enum', '$expand', 'target',
|
|
390
|
+
],
|
|
381
391
|
|
|
382
392
|
kind: true,
|
|
383
393
|
test: expression, // properties below are "sub specifications"
|
|
@@ -435,7 +445,10 @@ function assertConsistency( model, stage ) {
|
|
|
435
445
|
struct: { inherits: 'val', test: isDictionary( definition ) }, // def because double @
|
|
436
446
|
args: {
|
|
437
447
|
inherits: 'value',
|
|
438
|
-
optional: [
|
|
448
|
+
optional: [
|
|
449
|
+
'name', '$duplicate', '$expected', 'args', 'suffix',
|
|
450
|
+
'param', 'scope', // for dynamic parameter '?'
|
|
451
|
+
],
|
|
439
452
|
test: args,
|
|
440
453
|
},
|
|
441
454
|
on: { kind: true, inherits: 'value', test: expression },
|
|
@@ -449,11 +462,11 @@ function assertConsistency( model, stage ) {
|
|
|
449
462
|
'@': {
|
|
450
463
|
kind: true,
|
|
451
464
|
inherits: 'value',
|
|
452
|
-
optional: [ 'name', '_block', '$priority', '$
|
|
465
|
+
optional: [ 'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported' ],
|
|
453
466
|
// TODO: name requires if not in parser?
|
|
454
467
|
},
|
|
455
|
-
$priority: { test:
|
|
456
|
-
$annotations: { parser: true, kind: true, test: TODO },
|
|
468
|
+
$priority: { test: isOneOf([ undefined, false, 'extend', 'annotate' ]) },
|
|
469
|
+
$annotations: { parser: true, kind: true, test: TODO }, // deprecated, still there for cds-lsp
|
|
457
470
|
name: {
|
|
458
471
|
isRequired: stageParser && (() => false), // not required in parser
|
|
459
472
|
kind: true,
|
|
@@ -468,10 +481,10 @@ function assertConsistency( model, stage ) {
|
|
|
468
481
|
],
|
|
469
482
|
},
|
|
470
483
|
absolute: { test: isString },
|
|
471
|
-
variant: { test: TODO }, // TODO: not set in CDL parser
|
|
484
|
+
variant: { test: TODO }, // TODO: not set in CDL parser
|
|
472
485
|
element: { test: TODO }, // TODO: { test: isString },
|
|
473
486
|
action: { test: isString },
|
|
474
|
-
param: { test:
|
|
487
|
+
param: { test: TODO },
|
|
475
488
|
alias: { test: isString },
|
|
476
489
|
expectedKind: { kind: [ 'extend' ], inherits: 'kind' },
|
|
477
490
|
virtual: { kind: true, test: locationVal() },
|
|
@@ -524,7 +537,6 @@ function assertConsistency( model, stage ) {
|
|
|
524
537
|
_service: { kind: true, test: TODO },
|
|
525
538
|
_main: { kind: true, test: TODO },
|
|
526
539
|
_artifact: { test: TODO },
|
|
527
|
-
_base: { test: TODO, kind: true },
|
|
528
540
|
_navigation: { test: TODO },
|
|
529
541
|
_effectiveType: { kind: true, test: TODO },
|
|
530
542
|
_joinParent: { test: TODO },
|
|
@@ -548,6 +560,7 @@ function assertConsistency( model, stage ) {
|
|
|
548
560
|
'where', 'columns', 'mixin', 'quantifier', 'offset',
|
|
549
561
|
'orderBy', '$orderBy', 'groupBy', 'excludingDict', 'having',
|
|
550
562
|
'limit', '_status',
|
|
563
|
+
'_extension', // for unapplied extensions
|
|
551
564
|
],
|
|
552
565
|
},
|
|
553
566
|
_leadingQuery: { kind: true, test: TODO },
|
|
@@ -581,20 +594,57 @@ function assertConsistency( model, stage ) {
|
|
|
581
594
|
// (it can contain the artifact itself with no/failed autoexposure):
|
|
582
595
|
_descendants: { kind: [ 'entity' ], test: isDictionary( isArray( TODO ) ) },
|
|
583
596
|
|
|
597
|
+
$errorReported: { parser: true, kind: true, test: isString }, // to avoid duplicate messages
|
|
584
598
|
$duplicates: { parser: true, kind: true, test: TODO }, // array of arts or true
|
|
585
599
|
$extension: { kind: true, test: TODO }, // TODO: introduce $applied instead or $status
|
|
586
|
-
$inferred: {
|
|
600
|
+
$inferred: {
|
|
601
|
+
parser: true,
|
|
602
|
+
kind: true,
|
|
603
|
+
test: isOneOf([
|
|
604
|
+
// Uppercase values are used in logic, lowercase value are "just for us", i.e.
|
|
605
|
+
// debugging or to add non-enumerable properties such as $generated in Universal CSN.
|
|
606
|
+
// However, that is no longer true. For example, `autoexposed` is used in populate.js
|
|
607
|
+
// as well.
|
|
608
|
+
'IMPLICIT',
|
|
609
|
+
'REDIRECTED',
|
|
610
|
+
|
|
611
|
+
'$autoElement', // for magicVars: $user is automatically changed to $user.id
|
|
612
|
+
'$generated', // compiler generated annotations, e.g. @Core.Computed
|
|
613
|
+
'*', // inferred from query wildcard
|
|
614
|
+
'as', // query alias name
|
|
615
|
+
'aspect-composition',
|
|
616
|
+
'autoexposed', // for auto-exposed entities (they can't be referred to)
|
|
617
|
+
'cast', // type from cast() function
|
|
618
|
+
'composition-entity',
|
|
619
|
+
'copy', // only used in rewriteCondition(): On-condition is copied
|
|
620
|
+
'duplicate-autoexposed', // just like `autoexposed`, but with `duplicate` error.
|
|
621
|
+
'expand-element', // expanded elements
|
|
622
|
+
'expand-param', // expanded params (difference to expand-element only for debugging)
|
|
623
|
+
'include', // through includes, e.g. `entity E : F {}`
|
|
624
|
+
'keys',
|
|
625
|
+
'localized', // e.g. compiler-generated elements for localized: `text` assoc, etc.
|
|
626
|
+
'localized-entity', // `.texts` entity
|
|
627
|
+
'nav', // only used for MASKED, TODO(v4): Remove
|
|
628
|
+
'none', // only used in ensureColumnName(): Used in object representing empty alias
|
|
629
|
+
'query', // inferred query properties, e.g. `key`
|
|
630
|
+
'rewrite', // on-conditions or FKeys are rewritten
|
|
631
|
+
]),
|
|
632
|
+
},
|
|
587
633
|
|
|
588
634
|
// Helper property for the XSN-to-CSN transformation, see function setExpandStatus():
|
|
589
635
|
// client, universal: render expanded elements? gensrc: produce annotate statements?
|
|
590
|
-
|
|
636
|
+
// TODO: rename it to $elementsExpand ?
|
|
637
|
+
$expand: {
|
|
638
|
+
kind: true,
|
|
639
|
+
// See description of `setExpandStatus()` of in `lib/compiler/utils.js`.
|
|
640
|
+
test: isOneOf([ 'origin', 'annotate', 'target' ]),
|
|
641
|
+
},
|
|
591
642
|
|
|
592
643
|
$autoexpose: { kind: [ 'entity' ], test: isBoolean, also: [ null, 'Composition' ] },
|
|
593
|
-
$a2j: { kind: true, enumerable: true, test: TODO },
|
|
594
644
|
$extra: { parser: true, test: TODO }, // for unexpected properties in CSN
|
|
595
645
|
$withLocalized: { test: isBoolean },
|
|
596
646
|
$sources: { parser: true, test: isArray( isString ) },
|
|
597
|
-
$expected: { parser: true, test:
|
|
647
|
+
$expected: { parser: true, test: isOneOf([ 'approved-exists', 'exists' ]) },
|
|
598
648
|
$messageFunctions: { test: TODO },
|
|
599
649
|
$functions: { test: TODO },
|
|
600
650
|
$volatileFunctions: { test: TODO },
|
|
@@ -854,6 +904,13 @@ function assertConsistency( model, stage ) {
|
|
|
854
904
|
isString(node, parent, prop, spec);
|
|
855
905
|
}
|
|
856
906
|
|
|
907
|
+
function isOneOf(values) {
|
|
908
|
+
return function isOneOfInner( node, parent, prop ) {
|
|
909
|
+
if (!values.includes(node))
|
|
910
|
+
throw new Error( `Unexpected value '${ node }', expected ${ JSON.stringify(values) }${ at( [ node, parent ], prop ) }` );
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
|
|
857
914
|
function isString( node, parent, prop, spec ) {
|
|
858
915
|
if (typeof node !== 'string')
|
|
859
916
|
throw new Error( `Expected string${ at( [ node, parent ], prop ) }` );
|
package/lib/compiler/base.js
CHANGED
|
@@ -16,16 +16,18 @@ const kindProperties = {
|
|
|
16
16
|
namespace: { artifacts: true }, // on-the-fly context
|
|
17
17
|
context: { artifacts: true, normalized: 'namespace' },
|
|
18
18
|
service: { artifacts: true, normalized: 'namespace' },
|
|
19
|
-
entity: {
|
|
19
|
+
entity: {
|
|
20
|
+
elements: true, actions: true, params: () => false, include: true,
|
|
21
|
+
},
|
|
20
22
|
select: { normalized: 'select', elements: true },
|
|
21
23
|
$join: { normalized: 'select' },
|
|
22
24
|
$tableAlias: { normalized: 'alias' }, // table alias in select
|
|
23
25
|
$self: { normalized: 'alias' }, // table alias in select
|
|
24
26
|
$navElement: { normalized: 'element' },
|
|
25
27
|
$inline: { normalized: 'element' }, // column with inline property
|
|
26
|
-
event: { elements: true },
|
|
27
|
-
type: { elements: propExists, enum: propExists },
|
|
28
|
-
aspect: { elements: propExists },
|
|
28
|
+
event: { elements: true, include: true },
|
|
29
|
+
type: { elements: propExists, enum: propExists, include: true },
|
|
30
|
+
aspect: { elements: propExists, actions: true, include: true },
|
|
29
31
|
annotation: { elements: propExists, enum: propExists },
|
|
30
32
|
enum: { normalized: 'element' },
|
|
31
33
|
element: { elements: propExists, enum: propExists, dict: 'elements' },
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The builtin artifacts of CDS
|
|
2
2
|
|
|
3
3
|
// TODO: split this file
|
|
4
|
-
// - in base/: common definitions
|
|
4
|
+
// - in base/: common definitions, datetime formats
|
|
5
5
|
// - in compiler/: XSN-specific
|
|
6
6
|
// - in ?: CSN-specific
|
|
7
7
|
|
|
@@ -76,19 +76,94 @@ 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' ]) {
|
|
158
|
+
// intro before expr: if both intro and expr, tag as 'expr'
|
|
159
|
+
for (const token of src[generic] || [])
|
|
160
|
+
tgt[token] = generic;
|
|
161
|
+
}
|
|
162
|
+
// As GenericIntro is always together with GenericExpr, only mention those
|
|
163
|
+
// which are not already proposed for GenericExpr:
|
|
164
|
+
tgt.introMsg = src.introMsg || tgt.intro.filter( token => tgt[token] === 'intro' );
|
|
165
|
+
return tgt;
|
|
166
|
+
}
|
|
92
167
|
|
|
93
168
|
/**
|
|
94
169
|
* Variables that have special meaning in CDL/CSN.
|
|
@@ -106,17 +181,106 @@ const magicVariables = {
|
|
|
106
181
|
elements: {
|
|
107
182
|
from: {}, to: {},
|
|
108
183
|
},
|
|
184
|
+
// Require that elements are accessed, i.e. no $at, only $at.<element>.
|
|
185
|
+
$requireElementAccess: true,
|
|
109
186
|
},
|
|
110
187
|
$now: {}, // Dito
|
|
111
188
|
$session: {
|
|
112
189
|
// In ABAP CDS session variables are accessed in a generic way via
|
|
113
190
|
// the pseudo variable $session.
|
|
114
191
|
$uncheckedElements: true,
|
|
192
|
+
$requireElementAccess: true,
|
|
115
193
|
},
|
|
116
194
|
};
|
|
117
195
|
|
|
118
196
|
// see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP Cds via function
|
|
119
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Patterns for literal token tests and creation. The value is a map from the
|
|
200
|
+
* `prefix` argument of function `quotedliteral` to the following properties:
|
|
201
|
+
* - `test_msg`: error message which is issued if `test_fn` fails.
|
|
202
|
+
* - `test_fn`: function called with argument `value`, fails falsy return value
|
|
203
|
+
* - `unexpected_msg`: error message which is issued if `unexpected_char` matches
|
|
204
|
+
* - `unexpected_char`: regular expression matching an illegal character in `value`,
|
|
205
|
+
* the error location is only correct for a literal <prefix>'<value>'
|
|
206
|
+
* - `literal`: the value which is used instead of `prefix` in the AST
|
|
207
|
+
* TODO: we might do a range check (consider leap seconds, i.e. max value 60),
|
|
208
|
+
* but always allow Feb 29 (no leap year computation)
|
|
209
|
+
* Notes:
|
|
210
|
+
* - Dates/Times as defined in ISO 8601, see <https://en.wikipedia.org/wiki/ISO_8601>
|
|
211
|
+
*/
|
|
212
|
+
const quotedLiteralPatterns = {
|
|
213
|
+
x: {
|
|
214
|
+
test_variant: 'uneven-hex',
|
|
215
|
+
test_fn: (str => Number.isInteger(str.length / 2)),
|
|
216
|
+
unexpected_variant: 'invalid-hex',
|
|
217
|
+
unexpected_char: /[^0-9a-f]/i,
|
|
218
|
+
json_type: 'string',
|
|
219
|
+
},
|
|
220
|
+
time: {
|
|
221
|
+
test_variant: 'time',
|
|
222
|
+
test_fn: (x) => {
|
|
223
|
+
// Leading `T` allowed in ISO 8601.
|
|
224
|
+
const match = x.match( /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/ );
|
|
225
|
+
return match !== null && checkTime( match[1], match[2], match[3] );
|
|
226
|
+
},
|
|
227
|
+
json_type: 'string',
|
|
228
|
+
},
|
|
229
|
+
date: {
|
|
230
|
+
test_variant: 'date',
|
|
231
|
+
test_fn: (x) => {
|
|
232
|
+
const match = x.match( /^(-?\d{4})-(\d{1,2})-(\d{1,2})$/ );
|
|
233
|
+
return match !== null && checkDate( match[1], match[2], match[3] );
|
|
234
|
+
},
|
|
235
|
+
json_type: 'string',
|
|
236
|
+
},
|
|
237
|
+
timestamp: {
|
|
238
|
+
test_variant: 'timestamp',
|
|
239
|
+
test_fn: (x) => {
|
|
240
|
+
// eslint-disable-next-line max-len
|
|
241
|
+
const match = x.match( /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?$/ );
|
|
242
|
+
return match !== null && checkDate( match[1], match[2], match[3] ) &&
|
|
243
|
+
checkTime( match[4], match[5], match[6] );
|
|
244
|
+
},
|
|
245
|
+
json_type: 'string',
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Check that the given date is within boundaries.
|
|
251
|
+
* We can't use Date.parse() since that also allows non-standard values (2022-02-31 for example).
|
|
252
|
+
* Checks according to ISO 8601.
|
|
253
|
+
*
|
|
254
|
+
* @returns {boolean} True if the date is valid.
|
|
255
|
+
*/
|
|
256
|
+
function checkDate(year, month, day) {
|
|
257
|
+
// Negative years are allowed
|
|
258
|
+
year = Math.abs(Number.parseInt(year, 10));
|
|
259
|
+
month = Number.parseInt(month, 10);
|
|
260
|
+
day = Number.parseInt(day, 10);
|
|
261
|
+
// If any is NaN, the condition will be false.
|
|
262
|
+
// Year 0 does not exist, but ISO 8601 allows it and defines it as 1 BC.
|
|
263
|
+
return !Number.isNaN(year) && month > 0 && month < 13 && day > 0 && day < 32;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Check that the given time is within boundaries.
|
|
268
|
+
* Checks according to ISO 8601.
|
|
269
|
+
*
|
|
270
|
+
* @returns {boolean} True if the date is valid.
|
|
271
|
+
*/
|
|
272
|
+
function checkTime(hour, minutes, seconds) {
|
|
273
|
+
hour = Number.parseInt(hour, 10);
|
|
274
|
+
minutes = Number.parseInt(minutes, 10);
|
|
275
|
+
seconds = seconds ? Number.parseInt(seconds, 10) : 0;
|
|
276
|
+
if (hour === 24) // allow 24:00:00 (ISO 8601 version earlier than 2019)
|
|
277
|
+
return minutes === 0 && seconds === 0;
|
|
278
|
+
// If any is NaN, the condition will be false.
|
|
279
|
+
return hour >= 0 && hour < 24 &&
|
|
280
|
+
minutes >= 0 && minutes < 60 &&
|
|
281
|
+
seconds >= 0 && seconds < 61; // we allow 60 for lead seconds
|
|
282
|
+
}
|
|
283
|
+
|
|
120
284
|
/** All types belong to one category. */
|
|
121
285
|
const typeCategories = {
|
|
122
286
|
string: [],
|
|
@@ -286,6 +450,8 @@ function initBuiltins( model ) {
|
|
|
286
450
|
art.$autoElement = magic.$autoElement;
|
|
287
451
|
if (magic.$uncheckedElements)
|
|
288
452
|
art.$uncheckedElements = magic.$uncheckedElements;
|
|
453
|
+
if (magic.$requireElementAccess)
|
|
454
|
+
art.$requireElementAccess = magic.$requireElementAccess;
|
|
289
455
|
|
|
290
456
|
createMagicElements( art, magic.elements );
|
|
291
457
|
if (options.variableReplacements)
|
|
@@ -325,6 +491,7 @@ module.exports = {
|
|
|
325
491
|
typeParameters,
|
|
326
492
|
functionsWithoutParens,
|
|
327
493
|
specialFunctions,
|
|
494
|
+
quotedLiteralPatterns,
|
|
328
495
|
initBuiltins,
|
|
329
496
|
isInReservedNamespace,
|
|
330
497
|
isBuiltinType,
|
package/lib/compiler/checks.js
CHANGED
|
@@ -414,7 +414,7 @@ function check( model ) { // = XSN
|
|
|
414
414
|
}
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
-
// TODO: make this part of the
|
|
417
|
+
// TODO: make this part of the name resolution in the compiler
|
|
418
418
|
// Check that queries in 'art' do not contain unmanaged associations in GROUP BY or ORDER BY
|
|
419
419
|
function checkNoUnmanagedAssocsInGroupByOrderBy( query ) {
|
|
420
420
|
const art = query._main; // TODO - remove, use query for semantic location
|
package/lib/compiler/define.js
CHANGED
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
|
|
110
110
|
'use strict';
|
|
111
111
|
|
|
112
|
-
const {
|
|
112
|
+
const { forEachGeneric, forEachInOrder } = require('../base/model');
|
|
113
113
|
const {
|
|
114
114
|
dictAdd, dictAddArray, dictForEach, pushToDict,
|
|
115
115
|
} = require('../base/dictionaries');
|
|
@@ -147,6 +147,7 @@ function define( model ) {
|
|
|
147
147
|
} = model.$messageFunctions;
|
|
148
148
|
const {
|
|
149
149
|
resolveUncheckedPath,
|
|
150
|
+
checkAnnotate,
|
|
150
151
|
defineAnnotations,
|
|
151
152
|
} = model.$functions;
|
|
152
153
|
|
|
@@ -197,11 +198,12 @@ function define( model ) {
|
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
// Phase 1: ----------------------------------------------------------------
|
|
201
|
+
// Functions called from top-level: addSource()
|
|
200
202
|
|
|
201
203
|
/**
|
|
202
204
|
* Add definitions of the given source AST, both CDL and CSN
|
|
203
205
|
*
|
|
204
|
-
* @param {XSN.
|
|
206
|
+
* @param {XSN.SourceAst} src
|
|
205
207
|
*/
|
|
206
208
|
function addSource( src ) {
|
|
207
209
|
// handle sub model from parser
|
|
@@ -266,7 +268,7 @@ function define( model ) {
|
|
|
266
268
|
return;
|
|
267
269
|
}
|
|
268
270
|
setLink( art, '_block', block );
|
|
269
|
-
// dictAdd might set $duplicates
|
|
271
|
+
// dictAdd might set $duplicates
|
|
270
272
|
dictAdd( model.definitions, absolute, art );
|
|
271
273
|
}
|
|
272
274
|
|
|
@@ -306,7 +308,7 @@ function define( model ) {
|
|
|
306
308
|
* declaration.
|
|
307
309
|
*
|
|
308
310
|
* @param {XSN.Using} decl Node to be expanded and added to `src`
|
|
309
|
-
* @param {XSN.
|
|
311
|
+
* @param {XSN.SourceAst} src
|
|
310
312
|
*/
|
|
311
313
|
function addUsing( decl, src ) {
|
|
312
314
|
if (decl.usings) {
|
|
@@ -390,14 +392,17 @@ function define( model ) {
|
|
|
390
392
|
setLink( vocab, '_block', block );
|
|
391
393
|
const { name } = vocab;
|
|
392
394
|
if (!name.absolute)
|
|
393
|
-
name.absolute = prefix + name.path
|
|
395
|
+
name.absolute = prefix + pathName( name.path );
|
|
394
396
|
dictAdd( model.vocabularies, name.absolute, vocab );
|
|
395
397
|
}
|
|
396
398
|
|
|
397
399
|
// Phase 2 ("init") --------------------------------------------------------
|
|
400
|
+
// Functions called from top-level: initNamespaceAndUsing(), initArtifact(),
|
|
401
|
+
// initVocabulary()
|
|
398
402
|
|
|
399
403
|
function checkRedefinition( art ) {
|
|
400
|
-
if (!art.$duplicates
|
|
404
|
+
if (!art.$duplicates || art.$errorReported === 'syntax-duplicate-extend' ||
|
|
405
|
+
art.$errorReported === 'syntax-duplicate-annotate')
|
|
401
406
|
return;
|
|
402
407
|
if (art._main) {
|
|
403
408
|
error( 'duplicate-definition', [ art.name.location, art ], {
|
|
@@ -504,18 +509,6 @@ function define( model ) {
|
|
|
504
509
|
definitions[prefix] = parent;
|
|
505
510
|
initParentLink( parent, definitions );
|
|
506
511
|
}
|
|
507
|
-
if (art.kind !== 'namespace' &&
|
|
508
|
-
isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' )) {
|
|
509
|
-
let p = parent;
|
|
510
|
-
while (p && kindProperties[p.kind].artifacts)
|
|
511
|
-
p = p._parent;
|
|
512
|
-
if (p) {
|
|
513
|
-
error( 'subartifacts-not-supported', [ art.name.location, art ],
|
|
514
|
-
{ art: p, prop: 'deprecated.generatedEntityNameWithUnderscore' },
|
|
515
|
-
// eslint-disable-next-line max-len
|
|
516
|
-
'With the option $(PROP), no sub artifact can be defined for a non-context/service $(ART)' );
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
512
|
setLink( art, '_parent', parent );
|
|
520
513
|
if (!parent._subArtifacts)
|
|
521
514
|
setLink( parent, '_subArtifacts', Object.create(null) );
|
|
@@ -611,7 +604,17 @@ function define( model ) {
|
|
|
611
604
|
// Either expression (value), expand or new association (target && type)
|
|
612
605
|
else if (col.value || col.expand || (col.target && col.type)) {
|
|
613
606
|
setLink( col, '_block', parent._block );
|
|
614
|
-
defineAnnotations( col, col, parent._block );
|
|
607
|
+
defineAnnotations( col, col, parent._block );
|
|
608
|
+
if (col.inline) { // `@anno elem.{ * }` does not work
|
|
609
|
+
if (col.doc)
|
|
610
|
+
warning( 'syntax-anno-ignored', [ col.doc.location, col ], { '#': 'doc' } );
|
|
611
|
+
|
|
612
|
+
// col.$annotations no available for CSN input, have to search.
|
|
613
|
+
// Warning about first annotation should be enough to avoid spam.
|
|
614
|
+
const firstAnno = Object.keys(col).find(key => key.startsWith('@'));
|
|
615
|
+
if (firstAnno)
|
|
616
|
+
warning( 'syntax-anno-ignored', [ col[firstAnno].name.location, col ] );
|
|
617
|
+
}
|
|
615
618
|
// TODO: allow sub queries? at least in top-level expand without parallel ref
|
|
616
619
|
if (columns)
|
|
617
620
|
initExprForQuery( col.value, parent );
|
|
@@ -670,6 +673,8 @@ function define( model ) {
|
|
|
670
673
|
* @param {object} exprOrPathElement starts w/ an expr but then subelem from .path or .where.args
|
|
671
674
|
*/
|
|
672
675
|
function approveExistsInChildren(exprOrPathElement) {
|
|
676
|
+
if (!exprOrPathElement) // may be null in case of parse error
|
|
677
|
+
return;
|
|
673
678
|
if (exprOrPathElement.$expected === 'exists')
|
|
674
679
|
exprOrPathElement.$expected = 'approved-exists';
|
|
675
680
|
// Drill down
|
|
@@ -880,9 +885,7 @@ function define( model ) {
|
|
|
880
885
|
|
|
881
886
|
/**
|
|
882
887
|
* Set property `_parent` for all elements in `parent` to `parent` and do so
|
|
883
|
-
* recursively for all sub elements.
|
|
884
|
-
* `name.component` of the element with the help of argument `prefix`
|
|
885
|
-
* (which is basically the component name of the `parent` element plus a dot).
|
|
888
|
+
* recursively for all sub elements.
|
|
886
889
|
*/
|
|
887
890
|
// If not for extensions: construct === parent
|
|
888
891
|
function initMembers( construct, parent, block, initExtensions = false ) {
|
|
@@ -1014,6 +1017,8 @@ function define( model ) {
|
|
|
1014
1017
|
setMemberParent( elem, name, parent, construct !== parent && prop );
|
|
1015
1018
|
// console.log(message( null, elem.location, elem, {}, 'Info', 'INIT').toString())
|
|
1016
1019
|
checkRedefinition( elem );
|
|
1020
|
+
if (elem.kind === 'annotate')
|
|
1021
|
+
checkAnnotate( elem, elem );
|
|
1017
1022
|
defineAnnotations( elem, elem, bl );
|
|
1018
1023
|
initMembers( elem, elem, bl, initExtensions );
|
|
1019
1024
|
|
|
@@ -1125,7 +1130,7 @@ function mergeI18nBlocks( model ) {
|
|
|
1125
1130
|
* Add the source's translations to the model. Warns if the sources translations
|
|
1126
1131
|
* do not match the ones from previous sources.
|
|
1127
1132
|
*
|
|
1128
|
-
* @param {XSN.
|
|
1133
|
+
* @param {XSN.SourceAst} src
|
|
1129
1134
|
*/
|
|
1130
1135
|
function initI18nFromSource( src ) {
|
|
1131
1136
|
for (const langKey of Object.keys( src.i18n )) {
|