@sap/cds-compiler 2.10.4 → 2.12.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 +136 -0
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +58 -35
- package/bin/cdsse.js +1 -0
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +16 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +10 -36
- package/lib/api/options.js +17 -8
- package/lib/api/validate.js +30 -3
- package/lib/backends.js +12 -13
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +3 -2
- package/lib/base/message-registry.js +64 -11
- package/lib/base/messages.js +38 -18
- package/lib/base/model.js +6 -4
- package/lib/base/optionProcessorHelper.js +148 -86
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/sql-snippets.js +93 -0
- package/lib/checks/unknownMagic.js +6 -3
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +14 -5
- package/lib/compiler/base.js +64 -0
- package/lib/compiler/builtins.js +62 -16
- package/lib/compiler/checks.js +34 -10
- package/lib/compiler/definer.js +91 -112
- package/lib/compiler/index.js +30 -30
- package/lib/compiler/propagator.js +8 -4
- package/lib/compiler/resolver.js +279 -63
- package/lib/compiler/shared.js +65 -230
- package/lib/compiler/utils.js +191 -0
- package/lib/edm/annotations/genericTranslation.js +35 -18
- package/lib/edm/annotations/preprocessAnnotations.js +1 -1
- package/lib/edm/csn2edm.js +4 -3
- package/lib/edm/edm.js +8 -8
- package/lib/edm/edmPreprocessor.js +61 -59
- package/lib/edm/edmUtils.js +14 -15
- package/lib/gen/Dictionary.json +82 -40
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +19 -1
- package/lib/gen/language.tokens +80 -73
- package/lib/gen/languageLexer.interp +27 -1
- package/lib/gen/languageLexer.js +925 -826
- package/lib/gen/languageLexer.tokens +72 -65
- package/lib/gen/languageParser.js +4817 -4102
- package/lib/json/from-csn.js +57 -26
- package/lib/json/to-csn.js +244 -51
- package/lib/language/antlrParser.js +12 -1
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +106 -30
- package/lib/language/language.g4 +200 -70
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +220 -21
- package/lib/main.js +6 -3
- package/lib/model/api.js +2 -2
- package/lib/model/csnRefs.js +218 -86
- package/lib/model/csnUtils.js +99 -178
- package/lib/model/enrichCsn.js +84 -43
- package/lib/model/revealInternalProperties.js +25 -8
- package/lib/model/sortViews.js +8 -1
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +33 -18
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +2 -2
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +202 -82
- package/lib/render/toHdbcds.js +194 -135
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +91 -51
- package/lib/render/utils/common.js +24 -5
- package/lib/render/utils/sql.js +6 -4
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/applyTransformations.js +189 -0
- package/lib/transform/db/associations.js +389 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +275 -119
- package/lib/transform/db/draft.js +6 -4
- package/lib/transform/db/expansion.js +10 -9
- package/lib/transform/db/flattening.js +23 -8
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +106 -25
- package/lib/transform/db/views.js +485 -0
- package/lib/transform/forHanaNew.js +90 -1036
- package/lib/transform/forOdataNew.js +11 -3
- package/lib/transform/localized.js +5 -14
- package/lib/transform/odata/generateForeignKeyElements.js +2 -2
- package/lib/transform/transformUtilsNew.js +34 -20
- package/lib/transform/translateAssocsToJoins.js +15 -23
- package/lib/transform/universalCsnEnricher.js +217 -47
- package/lib/utils/file.js +13 -6
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +55 -27
- package/package.json +1 -1
- package/lib/transform/db/helpers.js +0 -58
|
@@ -114,9 +114,10 @@ function sync( recognizer ) {
|
|
|
114
114
|
}
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
|
-
// TODO: expected token is identifier, current is KEYWORD
|
|
118
117
|
|
|
119
118
|
if (nextTokens.contains(antlr4.Token.EPSILON)) {
|
|
119
|
+
// when exiting a (innermost) rule, remember the state to make
|
|
120
|
+
// getExpectedTokensForMessage() calculate the full "expected set"
|
|
120
121
|
if (recognizer.$nextTokensToken !== token) {
|
|
121
122
|
// console.log('SET:',token.type,recognizer.state,recognizer.$nextTokensToken && recognizer.$nextTokensToken.type)
|
|
122
123
|
recognizer.$nextTokensToken = token;
|
|
@@ -126,13 +127,29 @@ function sync( recognizer ) {
|
|
|
126
127
|
return;
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
// Expected token is identifier, current is (reserved) KEYWORD:
|
|
131
|
+
// TODO: do not use this if "close enough" (1 char diff) to a keyword in nextTokens
|
|
132
|
+
//
|
|
133
|
+
// NOTE: it is important to do this only if EPSILON is not in `nextTokens`,
|
|
134
|
+
// which means that we cannot bring the better special syntax-fragile-ident
|
|
135
|
+
// in all cases. Reason: high performance impact of the alternative,
|
|
136
|
+
// i.e. calling method Parser#isExpectedToken() = invoking the ATN
|
|
137
|
+
// interpreter to see behind EPSILON.
|
|
138
|
+
const identType = recognizer.constructor.Identifier;
|
|
139
|
+
if (keywordRegexp.test( token.text ) && nextTokens.contains( identType )) {
|
|
140
|
+
recognizer.message( 'syntax-fragile-ident', token, { id: token.text, delimited: token.text },
|
|
141
|
+
'$(ID) is a reserved name here - write $(DELIMITED) instead if you want to use it' );
|
|
142
|
+
token.type = identType; // make next ANTLR decision assume identifier
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
129
146
|
if (recognizer._ctx._sync === 'nop')
|
|
130
147
|
return;
|
|
131
148
|
switch (s.stateType) {
|
|
132
|
-
case ATNState.BLOCK_START:
|
|
133
|
-
case ATNState.STAR_BLOCK_START:
|
|
134
|
-
case ATNState.PLUS_BLOCK_START:
|
|
135
|
-
case ATNState.STAR_LOOP_ENTRY:
|
|
149
|
+
case ATNState.BLOCK_START: // 3
|
|
150
|
+
case ATNState.STAR_BLOCK_START: // 5
|
|
151
|
+
case ATNState.PLUS_BLOCK_START: // 4
|
|
152
|
+
case ATNState.STAR_LOOP_ENTRY: // 10
|
|
136
153
|
// report error and recover if possible
|
|
137
154
|
if ( token.text !== '}' && // do not just delete a '}'
|
|
138
155
|
this.singleTokenDeletion(recognizer) !== null) { // also calls reportUnwantedToken
|
|
@@ -145,8 +162,8 @@ function sync( recognizer ) {
|
|
|
145
162
|
}
|
|
146
163
|
throw new InputMismatchException(recognizer);
|
|
147
164
|
|
|
148
|
-
case ATNState.PLUS_LOOP_BACK:
|
|
149
|
-
case ATNState.STAR_LOOP_BACK: {
|
|
165
|
+
case ATNState.PLUS_LOOP_BACK: // 11
|
|
166
|
+
case ATNState.STAR_LOOP_BACK: { // 9
|
|
150
167
|
// TODO: do not delete a '}'
|
|
151
168
|
this.reportUnwantedToken(recognizer);
|
|
152
169
|
const expecting = new IntervalSet.IntervalSet();
|
|
@@ -425,7 +442,8 @@ function getExpectedTokensForMessage( recognizer, offendingToken, deadEnds ) {
|
|
|
425
442
|
}
|
|
426
443
|
else if (offendingToken && recognizer.$nextTokensContext &&
|
|
427
444
|
offendingToken === recognizer.$nextTokensToken) {
|
|
428
|
-
//
|
|
445
|
+
// Before exiting a rule, we had a state (via sync()) with a bigger
|
|
446
|
+
// "expecting set" for the same token
|
|
429
447
|
ll1._LOOK( atn.states[recognizer.$nextTokensState], null,
|
|
430
448
|
predictionContext( atn, recognizer.$nextTokensContext ),
|
|
431
449
|
expected, lookBusy, calledRules, true, true );
|
|
@@ -11,14 +11,17 @@ const { ATNState } = require('antlr4/atn/ATNState');
|
|
|
11
11
|
const { dictAdd, dictAddArray } = require('../base/dictionaries');
|
|
12
12
|
const locUtils = require('../base/location');
|
|
13
13
|
const { parseDocComment } = require('./docCommentParser');
|
|
14
|
+
const { parseMultiLineStringLiteral } = require('./multiLineStringParser');
|
|
14
15
|
const { functionsWithoutParens, specialFunctions } = require('../compiler/builtins');
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
// Push message `msg` with location `loc` to array of errors:
|
|
18
19
|
function _message( parser, severity, id, loc, ...args ) {
|
|
19
20
|
const msg = parser.$messageFunctions[severity]; // set in antlrParser.js
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
if (loc instanceof antlr4.CommonToken) {
|
|
22
|
+
loc = parser.tokenLocation(loc);
|
|
23
|
+
}
|
|
24
|
+
return msg( id, loc, ...args );
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
// Class which is to be used as grammar option with
|
|
@@ -49,6 +52,8 @@ GenericAntlrParser.prototype = Object.assign(
|
|
|
49
52
|
attachLocation,
|
|
50
53
|
startLocation,
|
|
51
54
|
tokenLocation,
|
|
55
|
+
valueWithTokenLocation,
|
|
56
|
+
previousTokenAtLocation,
|
|
52
57
|
combinedLocation,
|
|
53
58
|
surroundByParens,
|
|
54
59
|
unaryOpForParens,
|
|
@@ -82,6 +87,7 @@ GenericAntlrParser.prototype = Object.assign(
|
|
|
82
87
|
isStraightBefore,
|
|
83
88
|
meltKeywordToIdentifier,
|
|
84
89
|
prepareGenericKeywords,
|
|
90
|
+
parseMultiLineStringLiteral,
|
|
85
91
|
constructor: GenericAntlrParser, // keep this last
|
|
86
92
|
}
|
|
87
93
|
);
|
|
@@ -245,9 +251,12 @@ function prepareGenericKeywords( pathItem ) {
|
|
|
245
251
|
// TODO: If not just at the beginning, we need a stack for $genericKeywords,
|
|
246
252
|
// as we can have nested special functions
|
|
247
253
|
this.$genericKeywords.argFull = Object.keys( spec );
|
|
254
|
+
// @ts-ignore
|
|
248
255
|
const token = this.getCurrentToken() || { text: '' };
|
|
249
|
-
if (spec[token.text.toUpperCase()] === 'argFull')
|
|
256
|
+
if (spec[token.text.toUpperCase()] === 'argFull') {
|
|
257
|
+
// @ts-ignore
|
|
250
258
|
token.type = this.constructor.GenericArgFull;
|
|
259
|
+
}
|
|
251
260
|
}
|
|
252
261
|
|
|
253
262
|
// Attach location matched by current rule to node `art`. If a location is
|
|
@@ -285,25 +294,75 @@ function startLocation( token = this._ctx.start ) {
|
|
|
285
294
|
*
|
|
286
295
|
* @param {object} token
|
|
287
296
|
* @param {object} endToken
|
|
288
|
-
* @
|
|
297
|
+
* @return {CSN.Location}
|
|
289
298
|
*/
|
|
290
|
-
function tokenLocation( token, endToken
|
|
299
|
+
function tokenLocation( token, endToken = null ) {
|
|
291
300
|
if (!token)
|
|
292
301
|
return undefined;
|
|
293
302
|
if (!endToken) // including null
|
|
294
303
|
endToken = token;
|
|
304
|
+
|
|
295
305
|
/** @type {CSN.Location} */
|
|
296
|
-
const
|
|
306
|
+
const loc = {
|
|
297
307
|
file: this.filename,
|
|
298
308
|
line: token.line,
|
|
299
309
|
col: token.column + 1,
|
|
300
|
-
//
|
|
310
|
+
// Default for single line tokens
|
|
301
311
|
endLine: endToken.line,
|
|
302
312
|
endCol: endToken.stop - endToken.start + endToken.column + 2, // after the last char (special for EOF?)
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// This check is done for performance reason. No need to access a token's
|
|
316
|
+
// data if we know that it spans only one single line.
|
|
317
|
+
const isMultiLineToken = (
|
|
318
|
+
endToken.type === this.constructor.DocComment ||
|
|
319
|
+
endToken.type === this.constructor.String ||
|
|
320
|
+
endToken.type === this.constructor.UnterminatedLiteral
|
|
321
|
+
);
|
|
322
|
+
if (isMultiLineToken) {
|
|
323
|
+
// Count the number of newlines in the token.
|
|
324
|
+
const source = endToken.source[1].data;
|
|
325
|
+
let newLineCount = 0;
|
|
326
|
+
let lastNewlineIndex = endToken.start;
|
|
327
|
+
for (let i = endToken.start; i < endToken.stop; i++) {
|
|
328
|
+
// Note: We do NOT check for CR, LS, and PS (/[\r\u2028\u2029]/)
|
|
329
|
+
// because ANTLR only uses LF for line break detection.
|
|
330
|
+
if (source[i] === 10) { // code point of '\n'
|
|
331
|
+
newLineCount++;
|
|
332
|
+
lastNewlineIndex = i;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (newLineCount > 0) {
|
|
336
|
+
loc.endLine = endToken.line + newLineCount;
|
|
337
|
+
loc.endCol = endToken.stop - lastNewlineIndex + 1;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return loc;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Return `val` with the location of `token`. If `endToken` is provided, use its end
|
|
346
|
+
* location as end location in the result.
|
|
347
|
+
*
|
|
348
|
+
* @param {object} startToken
|
|
349
|
+
* @param {object} endToken
|
|
350
|
+
* @param {any} val
|
|
351
|
+
*/
|
|
352
|
+
function valueWithTokenLocation( val, startToken, endToken = null ) {
|
|
353
|
+
if (!startToken)
|
|
354
|
+
return undefined;
|
|
355
|
+
const loc = this.tokenLocation( startToken, endToken );
|
|
356
|
+
return { location: loc, val };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function previousTokenAtLocation( location ) {
|
|
360
|
+
let k = -1;
|
|
361
|
+
let token = this._input.LT(k);
|
|
362
|
+
while (token.line > location.line ||
|
|
363
|
+
token.line === location.line && token.column >= location.col)
|
|
364
|
+
token = this._input.LT(--k);
|
|
365
|
+
return (token.line === location.line && token.column + 1 === location.col) && token;
|
|
307
366
|
}
|
|
308
367
|
|
|
309
368
|
// Create a location with location properties `filename` and `start` from
|
|
@@ -342,16 +401,20 @@ function unaryOpForParens( query, val ) {
|
|
|
342
401
|
// - would influence the prediction, probably even induce adaptivePredict() calls,
|
|
343
402
|
// - is only slightly "more declarative" in the grammar.
|
|
344
403
|
function docComment( node ) {
|
|
345
|
-
if (!this.options.docComment)
|
|
346
|
-
return;
|
|
347
404
|
const token = this._input.getHiddenTokenToLeft( this.constructor.DocComment );
|
|
348
405
|
if (!token)
|
|
349
406
|
return;
|
|
407
|
+
|
|
408
|
+
// This token is actually used by / assigned to an artifact.
|
|
409
|
+
token.isUsed = true;
|
|
410
|
+
|
|
411
|
+
if (!this.options.docComment)
|
|
412
|
+
return;
|
|
350
413
|
if (node.doc) {
|
|
351
414
|
this.warning( 'syntax-duplicate-doc-comment', token, {},
|
|
352
415
|
'Repeated doc comment - previous doc is replaced' );
|
|
353
416
|
}
|
|
354
|
-
node.doc = this.
|
|
417
|
+
node.doc = this.valueWithTokenLocation( parseDocComment( token.text ), token );
|
|
355
418
|
}
|
|
356
419
|
|
|
357
420
|
// Classify token (identifier category) for implicit names,
|
|
@@ -435,20 +498,24 @@ function valuePathAst( ref ) {
|
|
|
435
498
|
path.length = 1;
|
|
436
499
|
}
|
|
437
500
|
const { args, id, location } = path[0];
|
|
438
|
-
if (args
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
501
|
+
if (args
|
|
502
|
+
? path[0].$syntax === ':'
|
|
503
|
+
: path[0].$delimited || !functionsWithoutParens.includes( id.toUpperCase() ))
|
|
504
|
+
return ref;
|
|
505
|
+
|
|
506
|
+
const implicit = this.previousTokenAtLocation( location );
|
|
507
|
+
if (implicit && implicit.isIdentifier)
|
|
508
|
+
implicit.isIdentifier = 'func';
|
|
509
|
+
const op = { location, val: 'call' };
|
|
510
|
+
return (args)
|
|
511
|
+
? { op, func: ref, location: ref.location, args }
|
|
512
|
+
: { op, func: ref, location: ref.location };
|
|
446
513
|
}
|
|
447
514
|
|
|
448
515
|
// If a '-' is directly before an unsigned number, consider it part of the number;
|
|
449
516
|
// otherwise (including for '+'), represent it as extra unary prefix operator.
|
|
450
517
|
function signedExpression( signToken, expr ) {
|
|
451
|
-
const sign = this.
|
|
518
|
+
const sign = this.valueWithTokenLocation( signToken.text, signToken );
|
|
452
519
|
const nval =
|
|
453
520
|
(signToken.text === '-' &&
|
|
454
521
|
expr && // expr may be null if `-` rule can't be parsed
|
|
@@ -498,8 +565,16 @@ function numberLiteral( token, sign, text = token.text ) {
|
|
|
498
565
|
function quotedLiteral( token, literal ) {
|
|
499
566
|
/** @type {CSN.Location} */
|
|
500
567
|
const location = this.tokenLocation( token );
|
|
501
|
-
|
|
502
|
-
|
|
568
|
+
let pos;
|
|
569
|
+
let val;
|
|
570
|
+
|
|
571
|
+
if (token.text.startsWith('`')) {
|
|
572
|
+
val = this.parseMultiLineStringLiteral(token);
|
|
573
|
+
literal = 'string';
|
|
574
|
+
} else {
|
|
575
|
+
pos = token.text.search( '\'' ) + 1; // pos of char after quote
|
|
576
|
+
val = token.text.slice( pos, -1 ).replace( /''/g, '\'' );
|
|
577
|
+
}
|
|
503
578
|
|
|
504
579
|
if (!literal)
|
|
505
580
|
literal = token.text.slice( 0, pos - 1 ).toLowerCase();
|
|
@@ -529,6 +604,7 @@ function quotedLiteral( token, literal ) {
|
|
|
529
604
|
};
|
|
530
605
|
|
|
531
606
|
function atChar(i) {
|
|
607
|
+
// Is only used with single-line strings.
|
|
532
608
|
return location.col + pos + i;
|
|
533
609
|
}
|
|
534
610
|
}
|
|
@@ -601,7 +677,7 @@ function addDef( parent, env, kind, name, annos, props, location ) {
|
|
|
601
677
|
// which could be tested in name search (then no undefined-ref error)
|
|
602
678
|
return art;
|
|
603
679
|
}
|
|
604
|
-
else if (env === 'artifacts') {
|
|
680
|
+
else if (env === 'artifacts' || env === 'vocabularies') {
|
|
605
681
|
dictAddArray( parent[env], art.name.id, art );
|
|
606
682
|
}
|
|
607
683
|
else if (kind || this.options.parseOnly) {
|
|
@@ -671,7 +747,7 @@ function assignProps( target, annos = [], props = null, location = null) {
|
|
|
671
747
|
for (const key in props) {
|
|
672
748
|
let val = props[key];
|
|
673
749
|
if (val instanceof antlr4.CommonToken)
|
|
674
|
-
val = this.
|
|
750
|
+
val = this.valueWithTokenLocation( true, val);
|
|
675
751
|
// only copy properties which are not undefined, null, {} or []
|
|
676
752
|
if (val != null &&
|
|
677
753
|
(typeof val !== 'object' ||
|
|
@@ -685,15 +761,15 @@ function assignProps( target, annos = [], props = null, location = null) {
|
|
|
685
761
|
|
|
686
762
|
// Create AST node for prefix operator `op` and arguments `args`
|
|
687
763
|
function createPrefixOp( token, args ) {
|
|
688
|
-
const op = this.
|
|
764
|
+
const op = this.valueWithTokenLocation( token.text.toLowerCase(), token );
|
|
689
765
|
return { op, args, location: this.combinedLocation( op, args[args.length - 1] ) };
|
|
690
766
|
}
|
|
691
767
|
|
|
692
768
|
// Create AST node for binary operator `op` and arguments `args`
|
|
693
769
|
function leftAssocBinaryOp( left, opToken, eToken, right, extraProp = 'quantifier' ) {
|
|
694
|
-
const op = this.
|
|
770
|
+
const op = this.valueWithTokenLocation( opToken.text.toLowerCase() , opToken);
|
|
695
771
|
const extra = eToken
|
|
696
|
-
? this.
|
|
772
|
+
? this.valueWithTokenLocation( eToken.text.toLowerCase(), eToken )
|
|
697
773
|
: undefined;
|
|
698
774
|
if (!left.$parens &&
|
|
699
775
|
(left.op && left.op.val) === (op && op.val) &&
|