@sap/cds-compiler 3.0.2 → 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 +65 -0
- package/bin/.eslintrc.json +2 -1
- package/bin/cdsc.js +19 -0
- package/doc/API.md +11 -0
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +24 -2
- package/doc/CHANGELOG_DEPRECATED.md +21 -1
- package/lib/api/main.js +7 -7
- package/lib/api/options.js +2 -3
- package/lib/base/message-registry.js +17 -5
- package/lib/base/messages.js +18 -39
- package/lib/base/model.js +2 -0
- package/lib/checks/actionsFunctions.js +8 -7
- package/lib/checks/selectItems.js +96 -14
- package/lib/checks/types.js +5 -8
- package/lib/checks/validator.js +1 -2
- package/lib/compiler/assert-consistency.js +64 -12
- package/lib/compiler/base.js +6 -4
- package/lib/compiler/builtins.js +58 -8
- package/lib/compiler/checks.js +1 -1
- package/lib/compiler/define.js +25 -22
- package/lib/compiler/extend.js +16 -10
- package/lib/compiler/finalize-parse-cdl.js +5 -9
- package/lib/compiler/index.js +2 -0
- package/lib/compiler/populate.js +34 -31
- package/lib/compiler/propagator.js +11 -6
- package/lib/compiler/resolve.js +14 -15
- package/lib/compiler/shared.js +53 -26
- package/lib/compiler/tweak-assocs.js +5 -11
- package/lib/compiler/utils.js +13 -4
- package/lib/edm/annotations/preprocessAnnotations.js +8 -4
- package/lib/edm/csn2edm.js +3 -3
- package/lib/edm/edm.js +9 -1
- package/lib/edm/edmAnnoPreprocessor.js +349 -0
- package/lib/edm/edmInboundChecks.js +85 -0
- package/lib/edm/edmPreprocessor.js +295 -638
- package/lib/edm/edmUtils.js +85 -5
- package/lib/gen/Dictionary.json +29 -9
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -2
- package/lib/gen/languageLexer.js +3 -0
- package/lib/gen/languageParser.js +4344 -4530
- 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 +3 -2
- package/lib/json/to-csn.js +8 -6
- package/lib/language/genericAntlrParser.js +121 -63
- package/lib/language/language.g4 +19 -57
- package/lib/main.d.ts +1 -0
- package/lib/model/api.js +1 -1
- package/lib/model/csnRefs.js +55 -29
- package/lib/model/csnUtils.js +11 -7
- package/lib/model/revealInternalProperties.js +2 -3
- package/lib/modelCompare/compare.js +3 -0
- package/lib/optionProcessor.js +27 -0
- package/lib/render/toCdl.js +57 -32
- package/lib/render/toSql.js +24 -8
- package/lib/render/utils/common.js +3 -4
- package/lib/transform/db/associations.js +43 -35
- package/lib/transform/db/cdsPersistence.js +0 -1
- package/lib/transform/db/flattening.js +3 -4
- package/lib/transform/db/transformExists.js +7 -5
- package/lib/transform/draft/db.js +1 -1
- package/lib/transform/forHanaNew.js +11 -2
- package/lib/transform/forOdataNew.js +1 -1
- package/lib/transform/odata/typesExposure.js +14 -5
- package/lib/utils/moduleResolve.js +0 -1
- package/package.json +2 -2
- package/lib/checks/unknownMagic.js +0 -41
package/lib/checks/types.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { getUtils,
|
|
3
|
+
const { getUtils, hasAnnotationValue } = require('../model/csnUtils');
|
|
4
4
|
|
|
5
5
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
6
6
|
|
|
@@ -55,7 +55,7 @@ function checkElementTypeDefinitionHasType(member, memberName, prop, path) {
|
|
|
55
55
|
|
|
56
56
|
// should only happen with csn input, not in cdl
|
|
57
57
|
if (!hasArtifactTypeInformation(member)) {
|
|
58
|
-
|
|
58
|
+
errorAboutMissingType(this.error, path, memberName, true);
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -96,7 +96,7 @@ function checkTypeDefinitionHasType(artifact, artifactName, prop, path) {
|
|
|
96
96
|
|
|
97
97
|
// should only happen with csn input, not in cdl
|
|
98
98
|
if (!hasArtifactTypeInformation(artifact)) {
|
|
99
|
-
|
|
99
|
+
errorAboutMissingType(this.error, path, artifactName);
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
102
|
|
|
@@ -157,9 +157,8 @@ function checkTypeOfHasProperType(artOrElement, name, model, error, path, derive
|
|
|
157
157
|
* @param {CSN.Path} path the path to the element or the artifact
|
|
158
158
|
* @param {string} name of the element or the artifact which is dubious
|
|
159
159
|
* @param {boolean} isElement indicates whether we are dealing with an element or an artifact
|
|
160
|
-
* @todo Rename, is an error not a warning
|
|
161
160
|
*/
|
|
162
|
-
function
|
|
161
|
+
function errorAboutMissingType(error, path, name, isElement = false) {
|
|
163
162
|
error('check-proper-type', path, { art: name, '#': isElement ? 'elm' : 'std' }, {
|
|
164
163
|
std: 'Dubious type $(ART) without type information',
|
|
165
164
|
elm: 'Dubious element $(ART) without type information',
|
|
@@ -174,12 +173,10 @@ function warnAboutMissingType(error, path, name, isElement = false) {
|
|
|
174
173
|
*
|
|
175
174
|
* @param {CSN.Artifact} artifact the artifact to check
|
|
176
175
|
* @returns {boolean} indicates whether the artifact has type information
|
|
177
|
-
* @todo What is the point of isBuiltinType here if we check for artifact.type at the end?
|
|
178
176
|
*/
|
|
179
177
|
function hasArtifactTypeInformation(artifact) {
|
|
180
178
|
// When is what property set?
|
|
181
|
-
return
|
|
182
|
-
artifact.elements || // => `type A {}`
|
|
179
|
+
return artifact.elements || // => `type A {}`
|
|
183
180
|
artifact.items || // => `type A : array of Integer`
|
|
184
181
|
artifact.enum || // => `type A : Integer enum {}`, `type` also set
|
|
185
182
|
artifact.target || // => `type A : Association to B;`
|
package/lib/checks/validator.js
CHANGED
|
@@ -32,7 +32,6 @@ const { validateAssociationsInItems } = require('./arrayOfs');
|
|
|
32
32
|
const checkQueryForNoDBArtifacts = require('./queryNoDbArtifacts');
|
|
33
33
|
const checkExplicitlyNullableKeys = require('./nullableKeys');
|
|
34
34
|
const nonexpandableStructuredInExpression = require('./nonexpandableStructured');
|
|
35
|
-
const unknownMagic = require('./unknownMagic');
|
|
36
35
|
const managedWithoutKeys = require('./managedWithoutKeys');
|
|
37
36
|
const {
|
|
38
37
|
checkSqlAnnotationOnArtifact,
|
|
@@ -62,7 +61,7 @@ const forHanaArtifactValidators
|
|
|
62
61
|
checkSqlAnnotationOnArtifact,
|
|
63
62
|
];
|
|
64
63
|
|
|
65
|
-
const forHanaCsnValidators = [ nonexpandableStructuredInExpression
|
|
64
|
+
const forHanaCsnValidators = [ nonexpandableStructuredInExpression ];
|
|
66
65
|
/**
|
|
67
66
|
* @type {Array<(query: CSN.Query, path: CSN.Path) => void>}
|
|
68
67
|
*/
|
|
@@ -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',
|
|
@@ -143,7 +142,6 @@ function assertConsistency( model, stage ) {
|
|
|
143
142
|
},
|
|
144
143
|
fileDep: { test: TODO }, // in usings
|
|
145
144
|
$frontend: { parser: true, test: isString, enum: [ 'cdl', 'json', 'xml' ] },
|
|
146
|
-
$newfeatures: { test: TODO }, // if new features have been used which break the old backends
|
|
147
145
|
messages: {
|
|
148
146
|
enumerable: () => true, // does not matter (non-enum std), enum in CSN/XML parser
|
|
149
147
|
test: isArray( TODO ),
|
|
@@ -175,7 +173,6 @@ function assertConsistency( model, stage ) {
|
|
|
175
173
|
return innerDict( val, parent, lang, spec );
|
|
176
174
|
} ),
|
|
177
175
|
},
|
|
178
|
-
_assocSources: { kind: true, test: TODO }, // just null: isArray( inDefinitions ) during resolve
|
|
179
176
|
$magicVariables: {
|
|
180
177
|
// $magicVariables contains "builtin" artifacts that differ from
|
|
181
178
|
// "normal artifacts" and therefore have a custom schema
|
|
@@ -256,6 +253,7 @@ function assertConsistency( model, stage ) {
|
|
|
256
253
|
'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit',
|
|
257
254
|
'_projections', '_block', '_parent', '_main', '_effectiveType', '$expand',
|
|
258
255
|
'$tableAliases', 'kind', '_$next', '_combined', '$inlines', '_status',
|
|
256
|
+
'_extension', // for unapplied extensions
|
|
259
257
|
],
|
|
260
258
|
},
|
|
261
259
|
none: { optional: () => true }, // parse error
|
|
@@ -375,13 +373,21 @@ function assertConsistency( model, stage ) {
|
|
|
375
373
|
},
|
|
376
374
|
// locations of parentheses pairs around expression:
|
|
377
375
|
$parens: { parser: true, test: TODO },
|
|
376
|
+
$prefix: { test: isString }, // compiler-corrected path prefix
|
|
378
377
|
$syntax: {
|
|
379
378
|
parser: true,
|
|
380
379
|
kind: [ 'entity', 'view', 'type', 'aspect' ],
|
|
381
380
|
test: isString, // CSN parser should check for 'entity', 'view', 'projection'
|
|
382
381
|
},
|
|
383
382
|
value: {
|
|
384
|
-
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
|
+
],
|
|
385
391
|
|
|
386
392
|
kind: true,
|
|
387
393
|
test: expression, // properties below are "sub specifications"
|
|
@@ -439,7 +445,10 @@ function assertConsistency( model, stage ) {
|
|
|
439
445
|
struct: { inherits: 'val', test: isDictionary( definition ) }, // def because double @
|
|
440
446
|
args: {
|
|
441
447
|
inherits: 'value',
|
|
442
|
-
optional: [
|
|
448
|
+
optional: [
|
|
449
|
+
'name', '$duplicate', '$expected', 'args', 'suffix',
|
|
450
|
+
'param', 'scope', // for dynamic parameter '?'
|
|
451
|
+
],
|
|
443
452
|
test: args,
|
|
444
453
|
},
|
|
445
454
|
on: { kind: true, inherits: 'value', test: expression },
|
|
@@ -456,7 +465,7 @@ function assertConsistency( model, stage ) {
|
|
|
456
465
|
optional: [ 'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported' ],
|
|
457
466
|
// TODO: name requires if not in parser?
|
|
458
467
|
},
|
|
459
|
-
$priority: { test:
|
|
468
|
+
$priority: { test: isOneOf([ undefined, false, 'extend', 'annotate' ]) },
|
|
460
469
|
$annotations: { parser: true, kind: true, test: TODO }, // deprecated, still there for cds-lsp
|
|
461
470
|
name: {
|
|
462
471
|
isRequired: stageParser && (() => false), // not required in parser
|
|
@@ -475,7 +484,7 @@ function assertConsistency( model, stage ) {
|
|
|
475
484
|
variant: { test: TODO }, // TODO: not set in CDL parser
|
|
476
485
|
element: { test: TODO }, // TODO: { test: isString },
|
|
477
486
|
action: { test: isString },
|
|
478
|
-
param: { test:
|
|
487
|
+
param: { test: TODO },
|
|
479
488
|
alias: { test: isString },
|
|
480
489
|
expectedKind: { kind: [ 'extend' ], inherits: 'kind' },
|
|
481
490
|
virtual: { kind: true, test: locationVal() },
|
|
@@ -528,7 +537,6 @@ function assertConsistency( model, stage ) {
|
|
|
528
537
|
_service: { kind: true, test: TODO },
|
|
529
538
|
_main: { kind: true, test: TODO },
|
|
530
539
|
_artifact: { test: TODO },
|
|
531
|
-
_base: { test: TODO, kind: true },
|
|
532
540
|
_navigation: { test: TODO },
|
|
533
541
|
_effectiveType: { kind: true, test: TODO },
|
|
534
542
|
_joinParent: { test: TODO },
|
|
@@ -552,6 +560,7 @@ function assertConsistency( model, stage ) {
|
|
|
552
560
|
'where', 'columns', 'mixin', 'quantifier', 'offset',
|
|
553
561
|
'orderBy', '$orderBy', 'groupBy', 'excludingDict', 'having',
|
|
554
562
|
'limit', '_status',
|
|
563
|
+
'_extension', // for unapplied extensions
|
|
555
564
|
],
|
|
556
565
|
},
|
|
557
566
|
_leadingQuery: { kind: true, test: TODO },
|
|
@@ -588,18 +597,54 @@ function assertConsistency( model, stage ) {
|
|
|
588
597
|
$errorReported: { parser: true, kind: true, test: isString }, // to avoid duplicate messages
|
|
589
598
|
$duplicates: { parser: true, kind: true, test: TODO }, // array of arts or true
|
|
590
599
|
$extension: { kind: true, test: TODO }, // TODO: introduce $applied instead or $status
|
|
591
|
-
$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
|
+
},
|
|
592
633
|
|
|
593
634
|
// Helper property for the XSN-to-CSN transformation, see function setExpandStatus():
|
|
594
635
|
// client, universal: render expanded elements? gensrc: produce annotate statements?
|
|
595
|
-
|
|
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
|
+
},
|
|
596
642
|
|
|
597
643
|
$autoexpose: { kind: [ 'entity' ], test: isBoolean, also: [ null, 'Composition' ] },
|
|
598
|
-
$a2j: { kind: true, enumerable: true, test: TODO },
|
|
599
644
|
$extra: { parser: true, test: TODO }, // for unexpected properties in CSN
|
|
600
645
|
$withLocalized: { test: isBoolean },
|
|
601
646
|
$sources: { parser: true, test: isArray( isString ) },
|
|
602
|
-
$expected: { parser: true, test:
|
|
647
|
+
$expected: { parser: true, test: isOneOf([ 'approved-exists', 'exists' ]) },
|
|
603
648
|
$messageFunctions: { test: TODO },
|
|
604
649
|
$functions: { test: TODO },
|
|
605
650
|
$volatileFunctions: { test: TODO },
|
|
@@ -859,6 +904,13 @@ function assertConsistency( model, stage ) {
|
|
|
859
904
|
isString(node, parent, prop, spec);
|
|
860
905
|
}
|
|
861
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
|
+
|
|
862
914
|
function isString( node, parent, prop, spec ) {
|
|
863
915
|
if (typeof node !== 'string')
|
|
864
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
|
@@ -154,12 +154,14 @@ function compileArg( src ) {
|
|
|
154
154
|
expr: src.expr || [],
|
|
155
155
|
separator: src.separator || [],
|
|
156
156
|
};
|
|
157
|
-
for (const generic of [ 'intro', 'expr', 'separator' ]) {
|
|
157
|
+
for (const generic of [ 'intro', 'expr', 'separator' ]) {
|
|
158
|
+
// intro before expr: if both intro and expr, tag as 'expr'
|
|
158
159
|
for (const token of src[generic] || [])
|
|
159
160
|
tgt[token] = generic;
|
|
160
161
|
}
|
|
161
|
-
|
|
162
|
-
|
|
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' );
|
|
163
165
|
return tgt;
|
|
164
166
|
}
|
|
165
167
|
|
|
@@ -196,15 +198,16 @@ const magicVariables = {
|
|
|
196
198
|
/**
|
|
197
199
|
* Patterns for literal token tests and creation. The value is a map from the
|
|
198
200
|
* `prefix` argument of function `quotedliteral` to the following properties:
|
|
199
|
-
* - `test_msg`: error message which is issued if `test_fn`
|
|
201
|
+
* - `test_msg`: error message which is issued if `test_fn` fails.
|
|
200
202
|
* - `test_fn`: function called with argument `value`, fails falsy return value
|
|
201
|
-
* - `test_re`: regular expression, fails if it does not match argument `value`
|
|
202
203
|
* - `unexpected_msg`: error message which is issued if `unexpected_char` matches
|
|
203
204
|
* - `unexpected_char`: regular expression matching an illegal character in `value`,
|
|
204
205
|
* the error location is only correct for a literal <prefix>'<value>'
|
|
205
206
|
* - `literal`: the value which is used instead of `prefix` in the AST
|
|
206
207
|
* TODO: we might do a range check (consider leap seconds, i.e. max value 60),
|
|
207
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>
|
|
208
211
|
*/
|
|
209
212
|
const quotedLiteralPatterns = {
|
|
210
213
|
x: {
|
|
@@ -216,21 +219,68 @@ const quotedLiteralPatterns = {
|
|
|
216
219
|
},
|
|
217
220
|
time: {
|
|
218
221
|
test_variant: 'time',
|
|
219
|
-
|
|
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
|
+
},
|
|
220
227
|
json_type: 'string',
|
|
221
228
|
},
|
|
222
229
|
date: {
|
|
223
230
|
test_variant: 'date',
|
|
224
|
-
|
|
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
|
+
},
|
|
225
235
|
json_type: 'string',
|
|
226
236
|
},
|
|
227
237
|
timestamp: {
|
|
228
238
|
test_variant: 'timestamp',
|
|
229
|
-
|
|
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
|
+
},
|
|
230
245
|
json_type: 'string',
|
|
231
246
|
},
|
|
232
247
|
};
|
|
233
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
|
+
|
|
234
284
|
/** All types belong to one category. */
|
|
235
285
|
const typeCategories = {
|
|
236
286
|
string: [],
|
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) {
|
|
@@ -395,9 +397,12 @@ function define( model ) {
|
|
|
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 || art.$errorReported === 'syntax-duplicate-extend'
|
|
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 );
|
|
@@ -882,9 +885,7 @@ function define( model ) {
|
|
|
882
885
|
|
|
883
886
|
/**
|
|
884
887
|
* Set property `_parent` for all elements in `parent` to `parent` and do so
|
|
885
|
-
* recursively for all sub elements.
|
|
886
|
-
* `name.component` of the element with the help of argument `prefix`
|
|
887
|
-
* (which is basically the component name of the `parent` element plus a dot).
|
|
888
|
+
* recursively for all sub elements.
|
|
888
889
|
*/
|
|
889
890
|
// If not for extensions: construct === parent
|
|
890
891
|
function initMembers( construct, parent, block, initExtensions = false ) {
|
|
@@ -1016,6 +1017,8 @@ function define( model ) {
|
|
|
1016
1017
|
setMemberParent( elem, name, parent, construct !== parent && prop );
|
|
1017
1018
|
// console.log(message( null, elem.location, elem, {}, 'Info', 'INIT').toString())
|
|
1018
1019
|
checkRedefinition( elem );
|
|
1020
|
+
if (elem.kind === 'annotate')
|
|
1021
|
+
checkAnnotate( elem, elem );
|
|
1019
1022
|
defineAnnotations( elem, elem, bl );
|
|
1020
1023
|
initMembers( elem, elem, bl, initExtensions );
|
|
1021
1024
|
|
|
@@ -1127,7 +1130,7 @@ function mergeI18nBlocks( model ) {
|
|
|
1127
1130
|
* Add the source's translations to the model. Warns if the sources translations
|
|
1128
1131
|
* do not match the ones from previous sources.
|
|
1129
1132
|
*
|
|
1130
|
-
* @param {XSN.
|
|
1133
|
+
* @param {XSN.SourceAst} src
|
|
1131
1134
|
*/
|
|
1132
1135
|
function initI18nFromSource( src ) {
|
|
1133
1136
|
for (const langKey of Object.keys( src.i18n )) {
|
package/lib/compiler/extend.js
CHANGED
|
@@ -33,6 +33,7 @@ function extend( model ) {
|
|
|
33
33
|
const {
|
|
34
34
|
resolvePath,
|
|
35
35
|
resolveUncheckedPath,
|
|
36
|
+
checkAnnotate,
|
|
36
37
|
defineAnnotations,
|
|
37
38
|
attachAndEmitValidNames,
|
|
38
39
|
checkDefinitions,
|
|
@@ -179,6 +180,10 @@ function extend( model ) {
|
|
|
179
180
|
checkDefinitions( ext, art, 'actions');
|
|
180
181
|
checkDefinitions( ext, art, 'params');
|
|
181
182
|
checkDefinitions( ext, art, 'columns');
|
|
183
|
+
if (ext.includes)
|
|
184
|
+
applyIncludes( ext, art ); // emits error if `includes` is set
|
|
185
|
+
if (ext.kind === 'annotate')
|
|
186
|
+
checkAnnotate( ext, art );
|
|
182
187
|
defineAnnotations( ext, art, ext._block, ext.kind );
|
|
183
188
|
}
|
|
184
189
|
return true;
|
|
@@ -238,6 +243,8 @@ function extend( model ) {
|
|
|
238
243
|
art.includes = [ ...ext.includes ];
|
|
239
244
|
applyIncludes( ext, art );
|
|
240
245
|
}
|
|
246
|
+
if (ext.kind === 'annotate')
|
|
247
|
+
checkAnnotate( ext, art );
|
|
241
248
|
defineAnnotations( ext, art, ext._block, ext.kind );
|
|
242
249
|
// TODO: do we allow to add elements with array of {...}? If yes, adapt
|
|
243
250
|
initMembers( ext, art, ext._block ); // might set _extend, _annotate
|
|
@@ -395,6 +402,7 @@ function extend( model ) {
|
|
|
395
402
|
resolvePath( ext.name, ext.kind, ext )) { // should issue error/info
|
|
396
403
|
// should issue error for cds extensions (annotate ok)
|
|
397
404
|
if (art.kind === 'namespace') {
|
|
405
|
+
// TODO: Emit error if namespace is extended by non-definitions.
|
|
398
406
|
info( 'anno-namespace', [ ext.name.location, ext ], {},
|
|
399
407
|
'Namespaces can\'t be annotated' );
|
|
400
408
|
}
|
|
@@ -454,6 +462,12 @@ function extend( model ) {
|
|
|
454
462
|
* @param {XSN.Artifact} art
|
|
455
463
|
*/
|
|
456
464
|
function applyIncludes( ext, art ) {
|
|
465
|
+
if (kindProperties[art.kind].include !== true) {
|
|
466
|
+
error('extend-unexpected-include', [ ext.includes[0]?.location, ext ], { kind: art.kind },
|
|
467
|
+
'Can\'t extend $(KIND) with includes');
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
457
471
|
if (!art._ancestors)
|
|
458
472
|
setLink( art, '_ancestors', [] ); // recursive array of includes
|
|
459
473
|
for (const ref of ext.includes) {
|
|
@@ -515,9 +529,7 @@ function extend( model ) {
|
|
|
515
529
|
const fioriAnno = art['@fiori.draft.enabled'];
|
|
516
530
|
const fioriEnabled = fioriAnno && (fioriAnno.val === undefined || fioriAnno.val);
|
|
517
531
|
|
|
518
|
-
const textsName =
|
|
519
|
-
? `${ art.name.absolute }_texts`
|
|
520
|
-
: `${ art.name.absolute }.texts`;
|
|
532
|
+
const textsName = `${ art.name.absolute }.texts`;
|
|
521
533
|
const textsEntity = model.definitions[textsName];
|
|
522
534
|
const localized = localizedData( art, textsEntity, fioriEnabled );
|
|
523
535
|
if (!localized)
|
|
@@ -660,8 +672,6 @@ function extend( model ) {
|
|
|
660
672
|
};
|
|
661
673
|
dictAdd( art.elements, 'ID_texts', textId );
|
|
662
674
|
}
|
|
663
|
-
if (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
|
|
664
|
-
setLink( art, '_base', base );
|
|
665
675
|
|
|
666
676
|
dictAdd( art.elements, 'locale', locale );
|
|
667
677
|
if (addTextsLanguageAssoc) {
|
|
@@ -834,9 +844,7 @@ function extend( model ) {
|
|
|
834
844
|
target = resolvePath( origin.targetAspect, 'compositionTarget', origin );
|
|
835
845
|
if (!target || !target.elements)
|
|
836
846
|
return;
|
|
837
|
-
const entityName =
|
|
838
|
-
? `${ base.name.absolute }_${ elem.name.id }`
|
|
839
|
-
: `${ base.name.absolute }.${ elem.name.id }`;
|
|
847
|
+
const entityName = `${ base.name.absolute }.${ elem.name.id }`;
|
|
840
848
|
const entity = allowAspectComposition( target, elem, keys, entityName ) &&
|
|
841
849
|
createTargetEntity( target, elem, keys, entityName, base );
|
|
842
850
|
elem.target = {
|
|
@@ -968,8 +976,6 @@ function extend( model ) {
|
|
|
968
976
|
// even if target cardinality is 1..1
|
|
969
977
|
up.notNull = { location, val: true };
|
|
970
978
|
}
|
|
971
|
-
if (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
|
|
972
|
-
setLink( art, '_base', base._base || base );
|
|
973
979
|
|
|
974
980
|
dictAdd( art.elements, 'up_', up);
|
|
975
981
|
addProxyElements( art, target.elements, 'aspect-composition', target.name && location );
|
|
@@ -82,8 +82,12 @@ function finalizeParseCdl( model ) {
|
|
|
82
82
|
for (const include of artifact.includes || [])
|
|
83
83
|
resolveUncheckedPath(include, 'include', main);
|
|
84
84
|
|
|
85
|
+
// define.js takes care that `target` is a ref and
|
|
86
|
+
// `targetAspect` is a structure.
|
|
85
87
|
if (artifact.target)
|
|
86
88
|
resolveUncheckedPath(artifact.target, 'target', main);
|
|
89
|
+
if (artifact.targetAspect)
|
|
90
|
+
resolveTypesForParseCdl(artifact.targetAspect, main);
|
|
87
91
|
|
|
88
92
|
if (artifact.from) {
|
|
89
93
|
const { from } = artifact;
|
|
@@ -93,12 +97,6 @@ function finalizeParseCdl( model ) {
|
|
|
93
97
|
resolveTypesForParseCdl(from, main);
|
|
94
98
|
}
|
|
95
99
|
|
|
96
|
-
if (artifact.targetAspect) {
|
|
97
|
-
if (artifact.targetAspect.path)
|
|
98
|
-
resolveUncheckedPath(artifact.targetAspect, 'target', main);
|
|
99
|
-
resolveTypesForParseCdl(artifact.targetAspect, main);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
100
|
// Recursively go through all XSN properties. There are a few that need to be
|
|
103
101
|
// handled specifically. Refer to the code below this loop for details.
|
|
104
102
|
for (const prop in artifact) {
|
|
@@ -158,8 +156,6 @@ function finalizeParseCdl( model ) {
|
|
|
158
156
|
* @param {XSN.Artifact} user
|
|
159
157
|
*/
|
|
160
158
|
function resolveTypeUnchecked(artWithType, user) {
|
|
161
|
-
if (!artWithType.type)
|
|
162
|
-
return;
|
|
163
159
|
const root = artWithType.type.path && artWithType.type.path[0];
|
|
164
160
|
if (!root) // parse error
|
|
165
161
|
return;
|
|
@@ -187,7 +183,7 @@ function finalizeParseCdl( model ) {
|
|
|
187
183
|
let struct = artWithType;
|
|
188
184
|
while (struct.kind === 'element')
|
|
189
185
|
struct = struct._parent;
|
|
190
|
-
if (struct.kind === 'select' || struct !== user._main) {
|
|
186
|
+
if (struct.kind === 'select' || struct.kind === 'annotation' || struct !== user._main) {
|
|
191
187
|
message( 'type-unexpected-typeof', [ artWithType.type.location, user ],
|
|
192
188
|
{ keyword: 'type of', '#': struct.kind } );
|
|
193
189
|
return;
|
package/lib/compiler/index.js
CHANGED
|
@@ -84,6 +84,8 @@ function parseX( source, filename, options = {}, messageFunctions = null ) {
|
|
|
84
84
|
return parseCsn.parse( source, filename, options, messageFunctions );
|
|
85
85
|
if (options.fallbackParser) // any other value: like 'cdl' (historic reasons)
|
|
86
86
|
return parseLanguage( source, filename, options, messageFunctions );
|
|
87
|
+
if (source.startsWith('{')) // Source may be JSON.
|
|
88
|
+
return parseCsn.parse( source, filename, options, messageFunctions );
|
|
87
89
|
|
|
88
90
|
const model = { location: { file: filename } };
|
|
89
91
|
messageFunctions.error( 'file-unknown-ext', emptyWeakLocation(filename),
|