@sap/cds-compiler 4.4.4 → 4.6.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 +88 -0
- package/bin/cdsc.js +18 -11
- package/bin/cdsv2m.js +7 -5
- package/doc/CHANGELOG_BETA.md +22 -0
- package/lib/api/main.js +306 -144
- package/lib/api/options.js +18 -6
- package/lib/api/validate.js +1 -1
- package/lib/base/message-registry.js +45 -10
- package/lib/base/messages.js +33 -16
- package/lib/base/model.js +4 -0
- package/lib/base/optionProcessorHelper.js +45 -176
- package/lib/checks/annotationsOData.js +49 -0
- package/lib/checks/elements.js +32 -34
- package/lib/checks/enricher.js +39 -3
- package/lib/checks/validator.js +8 -7
- package/lib/compiler/assert-consistency.js +40 -17
- package/lib/compiler/builtins.js +30 -53
- package/lib/compiler/checks.js +46 -14
- package/lib/compiler/cycle-detector.js +1 -4
- package/lib/compiler/define.js +35 -10
- package/lib/compiler/extend.js +21 -7
- package/lib/compiler/generate.js +3 -0
- package/lib/compiler/populate.js +5 -1
- package/lib/compiler/propagator.js +46 -9
- package/lib/compiler/resolve.js +94 -35
- package/lib/compiler/shared.js +60 -33
- package/lib/compiler/tweak-assocs.js +188 -92
- package/lib/compiler/utils.js +11 -1
- package/lib/edm/annotations/edmJson.js +41 -66
- package/lib/edm/annotations/genericTranslation.js +27 -9
- package/lib/edm/annotations/preprocessAnnotations.js +2 -3
- package/lib/edm/csn2edm.js +28 -11
- package/lib/edm/edmInboundChecks.js +58 -15
- package/lib/edm/edmPreprocessor.js +12 -16
- package/lib/edm/edmUtils.js +5 -2
- package/lib/gen/Dictionary.json +10 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +15 -2
- package/lib/gen/language.tokens +1 -0
- package/lib/gen/languageParser.js +6557 -5618
- package/lib/json/from-csn.js +4 -5
- package/lib/json/to-csn.js +29 -4
- package/lib/language/antlrParser.js +19 -1
- package/lib/language/errorStrategy.js +28 -7
- package/lib/language/genericAntlrParser.js +118 -24
- package/lib/language/textUtils.js +16 -0
- package/lib/main.d.ts +28 -3
- package/lib/main.js +3 -0
- package/lib/model/csnRefs.js +4 -1
- package/lib/model/csnUtils.js +20 -14
- package/lib/model/revealInternalProperties.js +5 -2
- package/lib/optionProcessor.js +23 -22
- package/lib/render/manageConstraints.js +13 -29
- package/lib/render/toCdl.js +47 -26
- package/lib/render/toHdbcds.js +63 -42
- package/lib/render/toRename.js +6 -10
- package/lib/render/toSql.js +71 -117
- package/lib/render/utils/common.js +41 -6
- package/lib/transform/.eslintrc.json +9 -1
- package/lib/transform/addTenantFields.js +228 -0
- package/lib/transform/db/applyTransformations.js +57 -4
- package/lib/transform/db/assertUnique.js +4 -4
- package/lib/transform/db/backlinks.js +13 -1
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/expansion.js +24 -3
- package/lib/transform/db/flattening.js +70 -71
- package/lib/transform/db/killAnnotations.js +37 -0
- package/lib/transform/db/rewriteCalculatedElements.js +46 -6
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/draft/db.js +2 -16
- package/lib/transform/draft/odata.js +3 -3
- package/lib/transform/effective/associations.js +3 -5
- package/lib/transform/effective/main.js +6 -9
- package/lib/transform/forOdata.js +26 -55
- package/lib/transform/forRelationalDB.js +38 -18
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/odata/typesExposure.js +14 -5
- package/lib/transform/transformUtils.js +47 -34
- package/lib/transform/translateAssocsToJoins.js +45 -11
- package/lib/transform/universalCsn/coreComputed.js +1 -1
- package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
- package/package.json +7 -6
package/lib/json/from-csn.js
CHANGED
|
@@ -376,7 +376,7 @@ const schema = compileSchema( {
|
|
|
376
376
|
type: object,
|
|
377
377
|
optional: [ 'src', 'min', 'max' ],
|
|
378
378
|
inKind: [ 'element', 'type', 'mixin' ],
|
|
379
|
-
onlyWith: [ 'target', 'targetAspect', '
|
|
379
|
+
onlyWith: [ 'target', 'targetAspect', 'where' ], // also in 'ref[]'
|
|
380
380
|
},
|
|
381
381
|
items: {
|
|
382
382
|
type: object,
|
|
@@ -1325,14 +1325,13 @@ function stringValOrNull( val, spec ) {
|
|
|
1325
1325
|
|
|
1326
1326
|
function scalenum( val, spec ) {
|
|
1327
1327
|
if ([ 'floating', 'variable' ].includes(val))
|
|
1328
|
-
return { val, literal: 'string', location: location() };
|
|
1328
|
+
return { val, literal: 'string', location: location() }; // XSN TODO: remove `literal`
|
|
1329
1329
|
return natnum(val, spec );
|
|
1330
1330
|
}
|
|
1331
1331
|
|
|
1332
1332
|
function natnum( val, spec ) {
|
|
1333
1333
|
if (typeof val === 'number' && val >= 0 && Number.isSafeInteger( val ))
|
|
1334
|
-
|
|
1335
|
-
return { val, literal: 'number', location: location() };
|
|
1334
|
+
return { val, location: location() };
|
|
1336
1335
|
const loc = location(true);
|
|
1337
1336
|
error( 'syntax-expecting-unsigned-int', loc,
|
|
1338
1337
|
{ '#': spec.msgVariant || 'csn', prop: spec.msgProp, op: '*' } );
|
|
@@ -1342,7 +1341,7 @@ function natnum( val, spec ) {
|
|
|
1342
1341
|
// Use with spec.msgVariant !
|
|
1343
1342
|
function natnumOrStar( val, spec ) {
|
|
1344
1343
|
return (val === '*')
|
|
1345
|
-
? { val,
|
|
1344
|
+
? { val, location: location() }
|
|
1346
1345
|
: natnum( val, spec );
|
|
1347
1346
|
}
|
|
1348
1347
|
|
package/lib/json/to-csn.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
const { locationString } = require('../base/messages');
|
|
15
15
|
const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
|
|
16
16
|
const { pathName } = require('../compiler/utils');
|
|
17
|
-
const { CompilerAssertion } = require('../base/error');
|
|
17
|
+
const { CompilerAssertion, ModelError } = require('../base/error');
|
|
18
18
|
|
|
19
19
|
const compilerVersion = require('../../package.json').version;
|
|
20
20
|
const creator = `CDS Compiler v${ compilerVersion }`;
|
|
@@ -93,6 +93,7 @@ const transformers = {
|
|
|
93
93
|
cardinality, // also in pathItem: after 'id', before 'where'
|
|
94
94
|
targetAspect,
|
|
95
95
|
target,
|
|
96
|
+
$filtered: value, // assoc+filter
|
|
96
97
|
foreignKeys,
|
|
97
98
|
enum: enumDict,
|
|
98
99
|
items,
|
|
@@ -205,6 +206,10 @@ const typeProperties = [
|
|
|
205
206
|
'foreignKeys', 'on', // for explicit ON/keys with REDIRECTED
|
|
206
207
|
'$typeArgs', // for unresolved type arguments, e.g. through parseCql
|
|
207
208
|
];
|
|
209
|
+
// Properties which cause a `cast` property to be rendered
|
|
210
|
+
const castProperties = [
|
|
211
|
+
'target', 'enum', 'items', 'type',
|
|
212
|
+
];
|
|
208
213
|
|
|
209
214
|
const csnDictionaries = [
|
|
210
215
|
'args', 'params', 'enum', 'mixin', 'elements', 'actions', 'definitions',
|
|
@@ -305,8 +310,14 @@ function csnDictionary( csn, sort, cloneOptions = false ) {
|
|
|
305
310
|
? proto
|
|
306
311
|
: (proto) ? Object.prototype : null;
|
|
307
312
|
const r = Object.create( dictProto );
|
|
308
|
-
for (const n of (sort) ? Object.keys(csn).sort() : Object.keys(csn))
|
|
309
|
-
|
|
313
|
+
for (const n of (sort) ? Object.keys(csn).sort() : Object.keys(csn)) {
|
|
314
|
+
// CSN does not allow any dictionary that are not objects.
|
|
315
|
+
// The compiler handles it, but a pre-transformed OData CSN won't trigger recompilation.
|
|
316
|
+
if (csn[n] && typeof csn[n] === 'object')
|
|
317
|
+
r[n] = sortCsn(csn[n], cloneOptions);
|
|
318
|
+
else
|
|
319
|
+
throw new ModelError(`Found non-object dictionary entry: "${ n }" of type "${ typeof csn[n] }"`);
|
|
320
|
+
}
|
|
310
321
|
|
|
311
322
|
return r;
|
|
312
323
|
}
|
|
@@ -1176,6 +1187,18 @@ function args( node ) {
|
|
|
1176
1187
|
}
|
|
1177
1188
|
|
|
1178
1189
|
function anno( node ) {
|
|
1190
|
+
if (!node)
|
|
1191
|
+
return true; // `@aBool` short for `@aBool: true`
|
|
1192
|
+
if (universalCsn && node.$inferred) {
|
|
1193
|
+
// TODO: return undefined for all values of node.$inferred (except 'NULL')?
|
|
1194
|
+
if (node.$inferred === 'prop' || node.$inferred === '$generated' || // via propagator.js
|
|
1195
|
+
node.$inferred === 'parent-origin')
|
|
1196
|
+
return undefined;
|
|
1197
|
+
else if (node.$inferred === 'NULL')
|
|
1198
|
+
return null;
|
|
1199
|
+
}
|
|
1200
|
+
if (node.$inferred && gensrcFlavor)
|
|
1201
|
+
return undefined;
|
|
1179
1202
|
if (node.$tokenTexts) // expressions in annotation values
|
|
1180
1203
|
return Object.assign({ '=': node.$tokenTexts }, expression( node ));
|
|
1181
1204
|
return value(node);
|
|
@@ -1476,7 +1499,9 @@ function addElementAsColumn( elem, cols ) {
|
|
|
1476
1499
|
// elements of sub queries (in expr) are hidden (not set via Object.assign):
|
|
1477
1500
|
if (!expr.cast && expr.elements)
|
|
1478
1501
|
setHidden( col, 'elements', expr.elements );
|
|
1479
|
-
|
|
1502
|
+
// CDL-style cast with explicit type properties
|
|
1503
|
+
if (castProperties.findIndex(prop => (elem[prop] &&
|
|
1504
|
+
!elem[prop].$inferred && !elem[prop][$inferred])) > -1)
|
|
1480
1505
|
cast( col, elem );
|
|
1481
1506
|
}
|
|
1482
1507
|
finally {
|
|
@@ -78,9 +78,11 @@ class RewriteTypeTokenStream extends antlr4.CommonTokenStream {
|
|
|
78
78
|
function initTokenRewrite( recognizer, ts ) { // ts = tokenStream
|
|
79
79
|
ts.DOTbeforeBRACE = Parser.DOTbeforeBRACE;
|
|
80
80
|
ts.BRACE = tokenTypeOf( recognizer, "'{'" );
|
|
81
|
+
ts.BRACE_CLOSE = tokenTypeOf( recognizer, "'}'" );
|
|
81
82
|
ts.DOT = tokenTypeOf( recognizer, "'.'" );
|
|
82
83
|
ts.ASTERISK = tokenTypeOf( recognizer, "'*'" );
|
|
83
84
|
ts.AT = tokenTypeOf( recognizer, "'@'" );
|
|
85
|
+
ts.SEMICOLON = tokenTypeOf( recognizer, "';'" );
|
|
84
86
|
ts.NEW = Parser.NEW;
|
|
85
87
|
ts.Identifier = Parser.Identifier;
|
|
86
88
|
ts.PAREN = tokenTypeOf( recognizer, "'('" );
|
|
@@ -90,7 +92,22 @@ function initTokenRewrite( recognizer, ts ) { // ts = tokenStream
|
|
|
90
92
|
if (ts.DOT && ts.DOTbeforeBRACE)
|
|
91
93
|
recognizer.tokenRewrite[ts.DOTbeforeBRACE - Parser.Identifier] = ts.DOT;
|
|
92
94
|
}
|
|
93
|
-
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function initCodeCompletionTokenArrays( parser ) {
|
|
98
|
+
// Set of top-level keywords used for code completion after token '}'
|
|
99
|
+
// belonging to a top-level definition
|
|
100
|
+
const startRuleIndex = parser.ruleNames.indexOf('start');
|
|
101
|
+
const startState = parser.atn.ruleToStartState[startRuleIndex].stateNumber;
|
|
102
|
+
const tokens = parser.atn.nextTokens(parser.atn.states[startState]);
|
|
103
|
+
tokens.removeOne(parser.symbolicNames.indexOf('NAMESPACE'));
|
|
104
|
+
tokens.removeOne(parser.symbolicNames.indexOf('HideAlternatives'));
|
|
105
|
+
|
|
106
|
+
parser.topLevelKeywords = [];
|
|
107
|
+
for (const interval of tokens.intervals) {
|
|
108
|
+
for (let i = interval.start; i < interval.stop; i++)
|
|
109
|
+
parser.topLevelKeywords.push(i);
|
|
110
|
+
}
|
|
94
111
|
}
|
|
95
112
|
|
|
96
113
|
function tokenTypeOf( recognizer, literalName ) {
|
|
@@ -123,6 +140,7 @@ function parse( source, filename = '<undefined>.cds',
|
|
|
123
140
|
parser.$messageFunctions = messageFunctions;
|
|
124
141
|
|
|
125
142
|
initTokenRewrite( parser, tokenStream );
|
|
143
|
+
initCodeCompletionTokenArrays(parser);
|
|
126
144
|
// comment the following 2 lines if you want to output the parser errors directly:
|
|
127
145
|
parser.messageErrorListener = errorListener;
|
|
128
146
|
parser._errHandler = new errorStrategy.KeywordErrorStrategy();
|
|
@@ -447,16 +447,33 @@ function intervalSetToArray( recognizer, expected, excludesForNextToken ) {
|
|
|
447
447
|
for (let j = v.start; j < v.stop; j++) {
|
|
448
448
|
// a generic keyword as such does not appear in messages, only its replacements,
|
|
449
449
|
// which are function name and argument position dependent:
|
|
450
|
-
if (j === pc.GenericExpr)
|
|
450
|
+
if (j === pc.GenericExpr) {
|
|
451
451
|
names.push( ...recognizer.$genericKeywords.expr );
|
|
452
|
-
|
|
452
|
+
}
|
|
453
|
+
else if (j === pc.GenericSeparator) {
|
|
453
454
|
names.push( ...recognizer.$genericKeywords.separator );
|
|
454
|
-
|
|
455
|
+
}
|
|
456
|
+
else if (j === pc.GenericIntro) {
|
|
455
457
|
names.push( ...recognizer.$genericKeywords.introMsg );
|
|
458
|
+
}
|
|
459
|
+
else if (j === pc.SemicolonTopLevel) {
|
|
460
|
+
// We only insert a semikolon (i.e. make it optional) after a closing brace.
|
|
461
|
+
// If the previous token is not `}`, don't propose these keywords, as ';' is required.
|
|
462
|
+
if (recognizer._input.LA(-1) === recognizer._input.BRACE_CLOSE) {
|
|
463
|
+
const name = recognizer.topLevelKeywords.map(i => expected
|
|
464
|
+
.elementName(recognizer.literalNames, recognizer.symbolicNames, i));
|
|
465
|
+
names.push(...name);
|
|
466
|
+
if (recognizer._ctx.outer?.kind !== 'source') {
|
|
467
|
+
if (names.includes('<EOF>'))
|
|
468
|
+
names.splice(names.indexOf('<EOF>'), 1);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
456
472
|
// other expected tokens usually appear in messages, except the helper tokens
|
|
457
473
|
// which are used to solve ambiguities via the parser method setLocalToken():
|
|
458
|
-
else if (j !== pc.HelperToken1 && j !== pc.HelperToken2)
|
|
474
|
+
else if (j !== pc.HelperToken1 && j !== pc.HelperToken2) {
|
|
459
475
|
names.push( expected.elementName(recognizer.literalNames, recognizer.symbolicNames, j ) );
|
|
476
|
+
}
|
|
460
477
|
}
|
|
461
478
|
}
|
|
462
479
|
// The parser method excludeExpected() additionally removes some tokens from the message:
|
|
@@ -524,10 +541,14 @@ function getTokenDisplay( token, recognizer ) {
|
|
|
524
541
|
if (!token)
|
|
525
542
|
return '<EOF>';
|
|
526
543
|
const t = token.type;
|
|
527
|
-
if (t === antlr4.Token.EOF || t === antlr4.Token.EPSILON )
|
|
544
|
+
if (t === antlr4.Token.EOF || t === antlr4.Token.EPSILON ) {
|
|
528
545
|
return '<EOF>';
|
|
529
|
-
|
|
530
|
-
|
|
546
|
+
}
|
|
547
|
+
else if (t === recognizer.constructor.DOTbeforeBRACE) {
|
|
548
|
+
if (recognizer.getTokenStream().LT(2).text === '{')
|
|
549
|
+
return "'.{'";
|
|
550
|
+
return "'.*'";
|
|
551
|
+
}
|
|
531
552
|
return recognizer.literalNames[t] || recognizer.symbolicNames[t];
|
|
532
553
|
}
|
|
533
554
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
const antlr4 = require('antlr4');
|
|
10
10
|
const { ATNState } = require('antlr4/src/antlr4/atn/ATNState');
|
|
11
|
+
const { DEFAULT: CommonTokenFactory } = require('antlr4/src/antlr4/CommonTokenFactory');
|
|
11
12
|
const { dictAdd, dictAddArray } = require('../base/dictionaries');
|
|
12
13
|
const locUtils = require('../base/location');
|
|
13
14
|
const { parseDocComment } = require('./docCommentParser');
|
|
@@ -23,7 +24,7 @@ const {
|
|
|
23
24
|
} = require('../compiler/classes');
|
|
24
25
|
const { isBetaEnabled } = require('../base/model');
|
|
25
26
|
const { weakLocation } = require('../base/location');
|
|
26
|
-
const { normalizeNewLine } = require('./textUtils');
|
|
27
|
+
const { normalizeNewLine, normalizeNumberString } = require('./textUtils');
|
|
27
28
|
|
|
28
29
|
const $location = Symbol.for('cds.$location');
|
|
29
30
|
|
|
@@ -107,6 +108,8 @@ Object.assign(GenericAntlrParser.prototype, {
|
|
|
107
108
|
fixNewKeywordPlacement,
|
|
108
109
|
signedExpression,
|
|
109
110
|
numberLiteral,
|
|
111
|
+
unsignedIntegerLiteral,
|
|
112
|
+
assignAnnotationValue,
|
|
110
113
|
quotedLiteral,
|
|
111
114
|
pathName,
|
|
112
115
|
docComment,
|
|
@@ -117,13 +120,14 @@ Object.assign(GenericAntlrParser.prototype, {
|
|
|
117
120
|
createDict,
|
|
118
121
|
createArray,
|
|
119
122
|
finalizeDictOrArray,
|
|
120
|
-
|
|
123
|
+
insertSemicolon,
|
|
121
124
|
setMaxCardinality,
|
|
122
125
|
setNullability,
|
|
123
126
|
reportDuplicateClause,
|
|
124
127
|
reportUnexpectedExtension,
|
|
125
128
|
reportUnexpectedSpace,
|
|
126
129
|
pushIdent,
|
|
130
|
+
pushItem,
|
|
127
131
|
handleComposition,
|
|
128
132
|
associationInSelectItem,
|
|
129
133
|
reportExpandInline,
|
|
@@ -911,10 +915,6 @@ function expressionAsAnnotationValue( assignment, cond ) {
|
|
|
911
915
|
return;
|
|
912
916
|
Object.assign(assignment, cond.cond);
|
|
913
917
|
assignment.$tokenTexts = this.tokensToStringRepresentation(cond);
|
|
914
|
-
if (!this.isBetaEnabled(this.options, 'annotationExpressions')) {
|
|
915
|
-
this.error( 'syntax-unsupported-expression', [ cond.cond.location ], {},
|
|
916
|
-
'Expressions in annotation values are not supported' );
|
|
917
|
-
}
|
|
918
918
|
}
|
|
919
919
|
|
|
920
920
|
// If a '-' is directly before an unsigned number, consider it part of the number;
|
|
@@ -941,12 +941,27 @@ function signedExpression( args, expr ) {
|
|
|
941
941
|
}
|
|
942
942
|
}
|
|
943
943
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
944
|
+
/**
|
|
945
|
+
* Return number literal (XSN) for number token `token` with optional token `sign`.
|
|
946
|
+
* Represent the number as a JS number in property `val` if the number can safely be
|
|
947
|
+
* represented as one. Represent the number by a string, the token lexeme, if the
|
|
948
|
+
* stringified version of the number does not match the token lexeme.
|
|
949
|
+
*
|
|
950
|
+
* TODO: Always use text !== `${ num }`
|
|
951
|
+
*/
|
|
952
|
+
function numberLiteral( sign, text = this._input.LT(-1).text ) {
|
|
953
|
+
const token = this._input.LT(-1);
|
|
949
954
|
let location = this.tokenLocation( token );
|
|
955
|
+
const nextToken = this._input.LT(1);
|
|
956
|
+
if (token.type === this.constructor.Number &&
|
|
957
|
+
token.stop + 1 === nextToken.start &&
|
|
958
|
+
(nextToken.type === this.constructor.Identifier ||
|
|
959
|
+
nextToken.type < this.constructor.Identifier && /^[a-z]+$/i.test( nextToken.text ))) {
|
|
960
|
+
// TODO: Make it an error in v5
|
|
961
|
+
this.warning('syntax-expecting-space', nextToken, {},
|
|
962
|
+
'Expecting a space between a number and a keyword/identifier');
|
|
963
|
+
}
|
|
964
|
+
|
|
950
965
|
if (sign) {
|
|
951
966
|
const { endLine, endCol } = location;
|
|
952
967
|
location = this.startLocation( sign );
|
|
@@ -957,17 +972,65 @@ function numberLiteral( token, sign, text = token.text ) {
|
|
|
957
972
|
}
|
|
958
973
|
|
|
959
974
|
const num = Number.parseFloat( text || '0' ); // not Number.parseInt() !
|
|
975
|
+
const normalized = normalizeNumberString(text);
|
|
976
|
+
if (normalized !== `${ num }` && normalized !== `${ sign.text }${ num }`)
|
|
977
|
+
return { literal: 'number', val: normalized, location };
|
|
978
|
+
return { literal: 'number', val: num, location };
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Given `token`, return a number literal (XSN). If the number is not an unsigned integer
|
|
983
|
+
* or it can't be represented in JS, emit an error.
|
|
984
|
+
*/
|
|
985
|
+
function unsignedIntegerLiteral() {
|
|
986
|
+
const token = this._input.LT(-1);
|
|
987
|
+
const location = this.tokenLocation( token );
|
|
988
|
+
const text = token.text || '0';
|
|
989
|
+
const num = Number.parseFloat( text ); // not Number.parseInt() !
|
|
960
990
|
if (!Number.isSafeInteger(num)) {
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
991
|
+
this.error( 'syntax-expecting-unsigned-int', token,
|
|
992
|
+
{ '#': !text.match(/^\d*$/) ? 'normal' : 'unsafe' } );
|
|
993
|
+
}
|
|
994
|
+
else if (text.match(/^\d+[.]\d+$/)) {
|
|
995
|
+
// More restrictive check: 10.0 emits a message, because we don't expect
|
|
996
|
+
// any decimal places.
|
|
997
|
+
const dotLoc = { ...location };
|
|
998
|
+
dotLoc.col += text.indexOf('.');
|
|
999
|
+
dotLoc.endCol = dotLoc.col + 1;
|
|
1000
|
+
this.info( 'syntax-ignoring-decimal', dotLoc );
|
|
1001
|
+
}
|
|
1002
|
+
return { literal: 'number', val: num, location };
|
|
1003
|
+
}
|
|
965
1004
|
|
|
966
|
-
|
|
967
|
-
|
|
1005
|
+
// Make the annotation `anno` have `value` as value. This function is basically
|
|
1006
|
+
// just `Object.assign`, but we really try to represent the provided CDL number as
|
|
1007
|
+
// JSON number. We give a warning if this is not possible or leads to a precision
|
|
1008
|
+
// loss.
|
|
1009
|
+
function assignAnnotationValue( anno, value ) {
|
|
1010
|
+
const { val } = value;
|
|
1011
|
+
if (value.literal === 'number' && typeof val !== 'number') {
|
|
1012
|
+
// a number in CDL, but stored as string in `val` - due to rounding or scientific notation
|
|
1013
|
+
let num = Number.parseFloat( val || '0' );
|
|
1014
|
+
const inf = !Number.isFinite( num );
|
|
1015
|
+
if (inf)
|
|
1016
|
+
num = val;
|
|
1017
|
+
if (inf || relevantDigits( val ) !== relevantDigits( num.toString() )) {
|
|
1018
|
+
this.warning( 'syntax-invalid-anno-number', value.location,
|
|
1019
|
+
{ '#': (inf ? 'infinite' : 'rounded' ), rawvalue: val, value: num },
|
|
1020
|
+
{
|
|
1021
|
+
std: 'Annotation number $(RAWVALUE) is put as $(VALUE) into the CSN',
|
|
1022
|
+
rounded: 'Annotation number $(RAWVALUE) is rounded to $(VALUE)',
|
|
1023
|
+
// eslint-disable-next-line max-len
|
|
1024
|
+
infinite: 'Annotation value $(RAWVALUE) is infinite as number and put as string into the CSN',
|
|
1025
|
+
} );
|
|
968
1026
|
}
|
|
1027
|
+
value.val = num;
|
|
969
1028
|
}
|
|
970
|
-
|
|
1029
|
+
Object.assign( anno, value );
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function relevantDigits( val ) {
|
|
1033
|
+
return val.replace( /0*(e.+)?$/i, '' ).replace( /\./, '' ).replace( /^[-+0]+/, '' );
|
|
971
1034
|
}
|
|
972
1035
|
|
|
973
1036
|
// Create AST node for quoted literals like string and e.g. date'2017-02-22'.
|
|
@@ -1036,6 +1099,16 @@ function pushIdent( path, ident, prefix ) {
|
|
|
1036
1099
|
}
|
|
1037
1100
|
}
|
|
1038
1101
|
|
|
1102
|
+
function pushItem( array, val ) {
|
|
1103
|
+
if (!array)
|
|
1104
|
+
return;
|
|
1105
|
+
|
|
1106
|
+
if (val != null)
|
|
1107
|
+
array.push(val);
|
|
1108
|
+
else
|
|
1109
|
+
array.broken = true;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1039
1112
|
// For :param, #variant, #symbol, @(…) and @Begin and `@` inside annotation paths
|
|
1040
1113
|
function reportUnexpectedSpace( prefix = this._input.LT(-1),
|
|
1041
1114
|
location = this.tokenLocation( this._input.LT(1) ) ) {
|
|
@@ -1173,14 +1246,35 @@ function finalizeDictOrArray( dict ) {
|
|
|
1173
1246
|
loc.endCol = stop.stop - stop.start + stop.column + 2;
|
|
1174
1247
|
}
|
|
1175
1248
|
|
|
1176
|
-
function
|
|
1177
|
-
|
|
1249
|
+
function insertSemicolon() {
|
|
1250
|
+
const currentToken = this._input.tokens[this._input.index];
|
|
1251
|
+
const requireSemicolon = this.topLevelKeywords.includes(currentToken.type);
|
|
1252
|
+
|
|
1253
|
+
if (requireSemicolon) {
|
|
1254
|
+
this.noAssignmentInSameLine();
|
|
1255
|
+
const prev = this._input.LT(-1);
|
|
1256
|
+
const t = CommonTokenFactory.create(
|
|
1257
|
+
currentToken.source,
|
|
1258
|
+
this.literalNames.indexOf( "';'" ),
|
|
1259
|
+
'', antlr4.Token.DEFAULT_CHANNEL,
|
|
1260
|
+
prev.stop, prev.stop,
|
|
1261
|
+
prev.line, prev.column
|
|
1262
|
+
);
|
|
1263
|
+
|
|
1264
|
+
t.tokenIndex = prev.tokenIndex + 1;
|
|
1265
|
+
|
|
1266
|
+
this._input.tokens.splice(t.tokenIndex, 0, t);
|
|
1267
|
+
|
|
1268
|
+
// Update tokenIndex: There could have been comments between two non-hidden tokens.
|
|
1269
|
+
for (let tokenIndex = t.tokenIndex + 1; tokenIndex < this._input.tokens.length; tokenIndex++)
|
|
1270
|
+
this._input.tokens[tokenIndex].tokenIndex += 1;
|
|
1271
|
+
|
|
1272
|
+
this._input.index = t.tokenIndex;
|
|
1273
|
+
}
|
|
1178
1274
|
}
|
|
1179
1275
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
const op = this.valueWithTokenLocation( token.text.toLowerCase(), token );
|
|
1183
|
-
return { op, args, location: this.combinedLocation( op, args[args.length - 1] ) };
|
|
1276
|
+
function createSource() {
|
|
1277
|
+
return new XsnSource();
|
|
1184
1278
|
}
|
|
1185
1279
|
|
|
1186
1280
|
// Create AST node for binary operator `op` and arguments `args`
|
|
@@ -49,9 +49,25 @@ function normalizeNewLine( str ) {
|
|
|
49
49
|
return str.replace(new RegExp(cdlNewLineRegEx, 'ug'), '\n');
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Normalizes the given number (as a string):
|
|
54
|
+
*
|
|
55
|
+
* - removes leading zeroes (`0`) to avoid accidental octal-conversion
|
|
56
|
+
*
|
|
57
|
+
* @param {string} str
|
|
58
|
+
* @return {string}
|
|
59
|
+
*/
|
|
60
|
+
function normalizeNumberString( str ) {
|
|
61
|
+
const num = str.replace(/^([+-]?)0+(\d)/, '$1$2');
|
|
62
|
+
if (!num.includes('.'))
|
|
63
|
+
return num;
|
|
64
|
+
return num.replace(/([.]\d)0+$/, '$1');
|
|
65
|
+
}
|
|
66
|
+
|
|
52
67
|
module.exports = {
|
|
53
68
|
isWhitespaceOrNewLineOnly,
|
|
54
69
|
isWhitespaceCharacterNoNewline,
|
|
55
70
|
cdlNewLineRegEx,
|
|
56
71
|
normalizeNewLine,
|
|
72
|
+
normalizeNumberString,
|
|
57
73
|
};
|
package/lib/main.d.ts
CHANGED
|
@@ -27,9 +27,9 @@ declare namespace compiler {
|
|
|
27
27
|
messages?: object[]
|
|
28
28
|
/**
|
|
29
29
|
* Dictionary of message-ids and their reclassified severity. This option
|
|
30
|
-
* can be used to increase the severity of messages. The compiler
|
|
31
|
-
* ignore decreased severities as this may lead to issues
|
|
32
|
-
* compilation otherwise.
|
|
30
|
+
* can be used to increase the severity of messages. The compiler may
|
|
31
|
+
* ignore decreased severities of error messages as this may lead to issues
|
|
32
|
+
* during compilation otherwise.
|
|
33
33
|
*/
|
|
34
34
|
severities?: { [messageId: string]: MessageSeverity}
|
|
35
35
|
/**
|
|
@@ -1023,6 +1023,31 @@ declare namespace compiler {
|
|
|
1023
1023
|
function migration(csn: CSN, options: SqlOptions, beforeImage: CSN): SqlMigrationResult;
|
|
1024
1024
|
}
|
|
1025
1025
|
|
|
1026
|
+
/**
|
|
1027
|
+
* Renders the given CSN into EDM in the JSON format _and_ XML format.
|
|
1028
|
+
* That is, it is a combination of `to.edm()` and `to.edmx()`.
|
|
1029
|
+
* Requires `options.service` to be set.
|
|
1030
|
+
*
|
|
1031
|
+
* Not to be confused with `for.odata()`, which returns an OData transformed CSN.
|
|
1032
|
+
*
|
|
1033
|
+
* @returns An object `'<protocol>': object` where the value is `'<serviceName>': object` entry
|
|
1034
|
+
* which consists of `{edmx: string, edm?: object}`.
|
|
1035
|
+
* @since v4.6.0
|
|
1036
|
+
*/
|
|
1037
|
+
function odata(csn: CSN, options?: ODataOptions): Record<string, object>;
|
|
1038
|
+
namespace odata {
|
|
1039
|
+
/**
|
|
1040
|
+
* Renders the given CSN into EDM in JSON format _and_ XML format for each service.
|
|
1041
|
+
* That is, it is a combination of `to.edm.all()` and `to.edmx.all()`.
|
|
1042
|
+
* If `options.serviceNames` is not set, all services will be rendered.
|
|
1043
|
+
*
|
|
1044
|
+
* @returns A map of `'<protocol>': object` where each entry is `'<serviceName>': object` entries where
|
|
1045
|
+
* each entry consists of `{edmx: string, edm?: object}`.
|
|
1046
|
+
* @since v4.6.0
|
|
1047
|
+
*/
|
|
1048
|
+
function all(csn: CSN, options: ODataOptions): Record<string, object>;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1026
1051
|
/**
|
|
1027
1052
|
* Renders the given CSN into EDM (JSON format). Requires `options.service` to be set.
|
|
1028
1053
|
*
|
package/lib/main.js
CHANGED
|
@@ -158,6 +158,9 @@ module.exports = {
|
|
|
158
158
|
edmx: Object.assign((...args) => snapi.edmx(...args), {
|
|
159
159
|
all: (...args) => snapi.edmx.all(...args)
|
|
160
160
|
}),
|
|
161
|
+
odata: Object.assign((...args) => snapi.odata2(...args), {
|
|
162
|
+
all: (...args) => snapi.odata2.all(...args)
|
|
163
|
+
}),
|
|
161
164
|
},
|
|
162
165
|
// Convenience for hdbtabledata calculation in @sap/cds
|
|
163
166
|
getArtifactCdsPersistenceName: (...args) => csnUtils.getArtifactDatabaseNameOf(...args),
|
package/lib/model/csnRefs.js
CHANGED
|
@@ -613,7 +613,7 @@ function csnRefs( csn, universalReady ) {
|
|
|
613
613
|
return resolvePath( path, alias._select || alias._ref, null,
|
|
614
614
|
'alias', ncache.$queryNumber );
|
|
615
615
|
}
|
|
616
|
-
const mixin = ncache._select.mixin
|
|
616
|
+
const mixin = ncache._select.mixin?.[head];
|
|
617
617
|
if (mixin && {}.hasOwnProperty.call( ncache._select.mixin, head )) {
|
|
618
618
|
setCache( mixin, '_parent', qcache._select );
|
|
619
619
|
return resolvePath( path, mixin, null, 'mixin', ncache.$queryNumber );
|
|
@@ -818,6 +818,9 @@ function csnRefs( csn, universalReady ) {
|
|
|
818
818
|
as = `$_column_${ colIndex + 1 }`;
|
|
819
819
|
setCache( col, '$as', as );
|
|
820
820
|
let type = parentElementOrQueryCache;
|
|
821
|
+
if (col.cast)
|
|
822
|
+
traverseType( col.cast, col, 'column', colIndex, initNode );
|
|
823
|
+
|
|
821
824
|
while (type.items)
|
|
822
825
|
type = type.items;
|
|
823
826
|
const elem = setCache( col, '_element', type.elements[as] );
|
package/lib/model/csnUtils.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { csnRefs, implicitAs } = require('
|
|
3
|
+
const { csnRefs, implicitAs, pathId } = require('./csnRefs');
|
|
4
4
|
const { applyTransformations, applyTransformationsOnNonDictionary, applyTransformationsOnDictionary } = require('../transform/db/applyTransformations');
|
|
5
|
-
const { isBuiltinType } = require('../compiler/builtins.js');
|
|
5
|
+
const { isBuiltinType, isMagicVariable, isAnnotationExpression } = require('../compiler/builtins.js');
|
|
6
6
|
const {
|
|
7
7
|
sortCsn,
|
|
8
8
|
cloneCsnDictionary: _cloneCsnDictionary,
|
|
@@ -1088,18 +1088,21 @@ function getLastPartOfRef( ref ) {
|
|
|
1088
1088
|
* @param {object} toNode
|
|
1089
1089
|
* @param {boolean} [overwrite]
|
|
1090
1090
|
* @param {object} excludes
|
|
1091
|
+
* @param {array} annoNames (copy only these annotations or all if undefined)
|
|
1092
|
+
* @returns {array} copiedAnnoNames
|
|
1091
1093
|
*/
|
|
1092
|
-
function copyAnnotations( fromNode, toNode, overwrite = false, excludes = {} ) {
|
|
1094
|
+
function copyAnnotations( fromNode, toNode, overwrite = false, excludes = {}, annoNames = undefined ) {
|
|
1093
1095
|
// Ignore if no toNode (in case of errors)
|
|
1094
1096
|
if (!toNode)
|
|
1095
1097
|
return;
|
|
1096
1098
|
|
|
1097
|
-
|
|
1099
|
+
if (annoNames == null)
|
|
1100
|
+
annoNames = Object.keys(fromNode).filter(key => key.startsWith('@'));
|
|
1098
1101
|
|
|
1099
|
-
|
|
1102
|
+
annoNames.forEach((anno) => {
|
|
1100
1103
|
if ((toNode[anno] === undefined || overwrite) && !excludes[anno])
|
|
1101
|
-
toNode[anno] = fromNode[anno];
|
|
1102
|
-
}
|
|
1104
|
+
toNode[anno] = cloneAnnotationValue(fromNode[anno]);
|
|
1105
|
+
});
|
|
1103
1106
|
}
|
|
1104
1107
|
|
|
1105
1108
|
|
|
@@ -1118,13 +1121,9 @@ function copyAnnotationsAndDoc( fromNode, toNode, overwrite = false ) {
|
|
|
1118
1121
|
if (!toNode)
|
|
1119
1122
|
return;
|
|
1120
1123
|
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
for (const anno of annotations) {
|
|
1125
|
-
if (toNode[anno] === undefined || overwrite)
|
|
1126
|
-
toNode[anno] = fromNode[anno];
|
|
1127
|
-
}
|
|
1124
|
+
copyAnnotations(fromNode, toNode, overwrite);
|
|
1125
|
+
if (toNode.doc === undefined || overwrite)
|
|
1126
|
+
toNode.doc = fromNode.doc;
|
|
1128
1127
|
}
|
|
1129
1128
|
|
|
1130
1129
|
/**
|
|
@@ -1449,6 +1448,10 @@ function isAssociationOperand( arg, path, inspectRef ) {
|
|
|
1449
1448
|
return art && art.target !== undefined;
|
|
1450
1449
|
}
|
|
1451
1450
|
|
|
1451
|
+
function pathName( ref ) {
|
|
1452
|
+
return ref ? ref.map( pathId ).join( '.' ) : '';
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1452
1455
|
module.exports = {
|
|
1453
1456
|
getUtils,
|
|
1454
1457
|
cloneCsn: cloneCsnNonDict, // Umbrella relies on this name
|
|
@@ -1456,6 +1459,8 @@ module.exports = {
|
|
|
1456
1459
|
cloneCsnDictionary,
|
|
1457
1460
|
cloneAnnotationValue,
|
|
1458
1461
|
isBuiltinType,
|
|
1462
|
+
isMagicVariable,
|
|
1463
|
+
isAnnotationExpression,
|
|
1459
1464
|
applyAnnotationsFromExtensions,
|
|
1460
1465
|
forEachGeneric,
|
|
1461
1466
|
forEachDefinition,
|
|
@@ -1496,4 +1501,5 @@ module.exports = {
|
|
|
1496
1501
|
cardinality2str,
|
|
1497
1502
|
isAssociationOperand,
|
|
1498
1503
|
isDollarSelfOrProjectionOperand,
|
|
1504
|
+
pathName,
|
|
1499
1505
|
};
|
|
@@ -321,8 +321,11 @@ function artifactIdentifier( node, parent ) {
|
|
|
321
321
|
Object.defineProperty( node, '__unique_id__', { value: uniqueId-- } );
|
|
322
322
|
const outerNum = node.$effectiveSeqNo || node.__unique_id__;
|
|
323
323
|
let outer = outerNum != null ? `##${ outerNum }` : '';
|
|
324
|
-
if (node._outer) {
|
|
325
|
-
outer = (node.
|
|
324
|
+
if (node._outer) { // anon aspect in targetAspect | items | $calcDepElement
|
|
325
|
+
outer = (node.kind === '$annotation')
|
|
326
|
+
// eslint-disable-next-line prefer-template
|
|
327
|
+
? `/${ quoted( '@' + node.name.id ) }`
|
|
328
|
+
: `/${ node.kind || 'items' }${ outer }`;
|
|
326
329
|
node = node._outer;
|
|
327
330
|
}
|
|
328
331
|
if (node === parent)
|