@sap/cds-compiler 3.0.0 → 3.1.2
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 +104 -9
- package/bin/.eslintrc.json +2 -1
- package/bin/cdsc.js +28 -16
- 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 +92 -40
- package/lib/api/options.js +2 -3
- package/lib/base/keywords.js +64 -1
- package/lib/base/message-registry.js +33 -5
- package/lib/base/messages.js +54 -65
- package/lib/base/model.js +2 -0
- package/lib/base/optionProcessorHelper.js +53 -21
- 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 +65 -13
- package/lib/compiler/base.js +6 -4
- package/lib/compiler/builtins.js +93 -4
- package/lib/compiler/checks.js +1 -1
- package/lib/compiler/define.js +28 -23
- package/lib/compiler/extend.js +20 -11
- package/lib/compiler/finalize-parse-cdl.js +5 -9
- package/lib/compiler/index.js +2 -0
- package/lib/compiler/populate.js +37 -32
- package/lib/compiler/propagator.js +11 -6
- package/lib/compiler/resolve.js +15 -19
- package/lib/compiler/shared.js +54 -18
- package/lib/compiler/tweak-assocs.js +5 -11
- package/lib/compiler/utils.js +15 -6
- package/lib/edm/annotations/genericTranslation.js +12 -2
- package/lib/edm/annotations/preprocessAnnotations.js +18 -15
- package/lib/edm/csn2edm.js +18 -17
- 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 +336 -665
- package/lib/edm/edmUtils.js +86 -45
- 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 +4332 -4496
- 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 +19 -20
- package/lib/json/to-csn.js +11 -8
- package/lib/language/genericAntlrParser.js +150 -92
- package/lib/language/language.g4 +47 -74
- package/lib/main.d.ts +1 -0
- package/lib/model/api.js +1 -1
- package/lib/model/csnRefs.js +56 -29
- package/lib/model/csnUtils.js +29 -14
- package/lib/model/revealInternalProperties.js +6 -4
- package/lib/modelCompare/compare.js +3 -0
- package/lib/optionProcessor.js +81 -38
- package/lib/render/toCdl.js +57 -32
- package/lib/render/toHdbcds.js +1 -1
- package/lib/render/toSql.js +31 -11
- 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 +4 -4
- package/lib/transform/localized.js +15 -11
- package/lib/transform/odata/typesExposure.js +14 -5
- package/lib/utils/file.js +28 -18
- package/lib/utils/moduleResolve.js +0 -1
- package/package.json +3 -4
- package/share/messages/syntax-expected-integer.md +9 -8
- package/lib/checks/unknownMagic.js +0 -41
|
@@ -12,7 +12,8 @@ const { dictAdd, dictAddArray } = require('../base/dictionaries');
|
|
|
12
12
|
const locUtils = require('../base/location');
|
|
13
13
|
const { parseDocComment } = require('./docCommentParser');
|
|
14
14
|
const { parseMultiLineStringLiteral } = require('./multiLineStringParser');
|
|
15
|
-
const { functionsWithoutParens, specialFunctions } = require('../compiler/builtins');
|
|
15
|
+
const { functionsWithoutParens, specialFunctions, quotedLiteralPatterns } = require('../compiler/builtins');
|
|
16
|
+
const { pathName } = require("../compiler/utils");
|
|
16
17
|
|
|
17
18
|
const $location = Symbol.for('cds.$location');
|
|
18
19
|
|
|
@@ -62,8 +63,13 @@ Object.assign(GenericAntlrParser.prototype, {
|
|
|
62
63
|
info: function(...args) { return _message( this, 'info', ...args ); },
|
|
63
64
|
attachLocation,
|
|
64
65
|
assignAnnotation,
|
|
66
|
+
addAnnotation,
|
|
67
|
+
checkExtensionDict,
|
|
68
|
+
handleDuplicateExtension,
|
|
65
69
|
startLocation,
|
|
66
70
|
tokenLocation,
|
|
71
|
+
isMultiLineToken,
|
|
72
|
+
fixMultiLineTokenEndLocation,
|
|
67
73
|
valueWithTokenLocation,
|
|
68
74
|
previousTokenAtLocation,
|
|
69
75
|
combinedLocation,
|
|
@@ -113,40 +119,6 @@ Object.assign(GenericAntlrParser.prototype, {
|
|
|
113
119
|
parseMultiLineStringLiteral,
|
|
114
120
|
});
|
|
115
121
|
|
|
116
|
-
// Patterns for literal token tests and creation. The value is a map from the
|
|
117
|
-
// `prefix` argument of function `quotedliteral` to the following properties:
|
|
118
|
-
// - `test_msg`: error message which is issued if `test_fn` or `test_re` fail.
|
|
119
|
-
// - `test_fn`: function called with argument `value`, fails falsy return value
|
|
120
|
-
// - `test_re`: regular expression, fails if it does not match argument `value`
|
|
121
|
-
// - `unexpected_msg`: error message which is issued if `unexpected_char` matches
|
|
122
|
-
// - `unexpected_char`: regular expression matching an illegal character in `value`,
|
|
123
|
-
// the error location is only correct for a literal <prefix>'<value>'
|
|
124
|
-
// - `literal`: the value which is used instead of `prefix` in the AST
|
|
125
|
-
// TODO: we might do a range check (consider leap seconds, i.e. max value 60),
|
|
126
|
-
// but always allow Feb 29 (no leap year computation)
|
|
127
|
-
// TODO: make it a configurable error (syntax-invalid-literal)
|
|
128
|
-
// TODO: also use for CSN input
|
|
129
|
-
const quotedLiteralPatterns = {
|
|
130
|
-
x: {
|
|
131
|
-
test_variant: 'uneven-hex',
|
|
132
|
-
test_fn: (str => Number.isInteger(str.length / 2)),
|
|
133
|
-
unexpected_variant: 'invalid-hex',
|
|
134
|
-
unexpected_char: /[^0-9a-f]/i,
|
|
135
|
-
},
|
|
136
|
-
time: {
|
|
137
|
-
test_variant: 'time',
|
|
138
|
-
test_re: /^[0-9]{1,2}:[0-9]{1,2}(:[0-9]{1,2})?$/,
|
|
139
|
-
},
|
|
140
|
-
date: {
|
|
141
|
-
test_variant: 'date',
|
|
142
|
-
test_re: /^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/,
|
|
143
|
-
},
|
|
144
|
-
timestamp: {
|
|
145
|
-
test_variant: 'timestamp',
|
|
146
|
-
test_re: /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}(:[0-9]{2}(\.[0-9]{1,7})?)?$/,
|
|
147
|
-
},
|
|
148
|
-
};
|
|
149
|
-
|
|
150
122
|
// Use the following function for language constructs which we (currently)
|
|
151
123
|
// just being able to parse, in able to run tests from HANA CDS. As soon as we
|
|
152
124
|
// create ASTs for the language construct and put it into a CSN, a
|
|
@@ -221,12 +193,12 @@ function setLocalTokenIfBefore( string, tokenName, before, inSameLine ) {
|
|
|
221
193
|
}
|
|
222
194
|
|
|
223
195
|
function setLocalTokenForId( tokenNameMap ) {
|
|
196
|
+
const tokenName = tokenNameMap[ this._input.LT(2).text || '' ];
|
|
224
197
|
const ll1 = this.getCurrentToken();
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
198
|
+
if (tokenName &&
|
|
199
|
+
(ll1.type === this.constructor.Identifier || /^[a-zA-Z_]+$/.test( ll1.text )))
|
|
200
|
+
ll1.type = this.constructor[tokenName];
|
|
201
|
+
return !!tokenName;
|
|
230
202
|
}
|
|
231
203
|
|
|
232
204
|
// // Special function for rule `requiredSemi` before return $ctx
|
|
@@ -298,9 +270,6 @@ function prepareGenericKeywords( pathItem, expected = null) {
|
|
|
298
270
|
const func = pathItem?.id && specialFunctions[pathItem.id.toUpperCase()];
|
|
299
271
|
const spec = func && func[argPos] || specialFunctions[''][argPos ? 1 : 0];
|
|
300
272
|
this.$genericKeywords = spec;
|
|
301
|
-
// currently, we only have 'TODO', i.e. a keyword which is alternative to expression
|
|
302
|
-
// TODO: If not just at the beginning, we need a stack for $genericKeywords,
|
|
303
|
-
// as we can have nested special functions
|
|
304
273
|
// @ts-ignore
|
|
305
274
|
const token = this.getCurrentToken() || { text: '' };
|
|
306
275
|
const text = token.text.toUpperCase();
|
|
@@ -339,15 +308,26 @@ function reportErrorForGenericKeyword() {
|
|
|
339
308
|
function attachLocation( art ) {
|
|
340
309
|
if (!art || art.$parens)
|
|
341
310
|
return art;
|
|
342
|
-
if (!art.location)
|
|
343
|
-
art.location = this.
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
311
|
+
if (!art.location) {
|
|
312
|
+
art.location = this.tokenLocation(this._ctx.start, this._ctx.stop);
|
|
313
|
+
return art;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// The last token (this._ctx.stop) may be a multi-line string literal, in which
|
|
317
|
+
// case we can't rely on `this._ctx.stop.line`.
|
|
318
|
+
if (this.isMultiLineToken(this._ctx.stop)) {
|
|
319
|
+
this.fixMultiLineTokenEndLocation(this._ctx.stop, art.location);
|
|
320
|
+
|
|
321
|
+
} else {
|
|
322
|
+
const { stop } = this._ctx;
|
|
323
|
+
art.location.endLine = stop.line;
|
|
324
|
+
art.location.endCol = stop.stop - stop.start + stop.column + 2; // after the last char (special for EOF?)
|
|
325
|
+
}
|
|
326
|
+
|
|
347
327
|
return art;
|
|
348
328
|
}
|
|
349
329
|
|
|
350
|
-
function assignAnnotation( art, anno, prefix = '', iHaveVariant ) {
|
|
330
|
+
function assignAnnotation( art, anno, prefix = '', iHaveVariant = false ) {
|
|
351
331
|
const { name, $flatten } = anno;
|
|
352
332
|
const { path } = name;
|
|
353
333
|
if (path.broken || !path[path.length - 1].id)
|
|
@@ -355,7 +335,8 @@ function assignAnnotation( art, anno, prefix = '', iHaveVariant ) {
|
|
|
355
335
|
const pathname = pathName( path );
|
|
356
336
|
let absolute = '';
|
|
357
337
|
if (name.variant) {
|
|
358
|
-
|
|
338
|
+
const variant = pathName( name.variant.path );
|
|
339
|
+
absolute = `${ prefix }${ pathname }#${ variant }`;
|
|
359
340
|
if (iHaveVariant) { // TODO: do we really care in the parser / core compiler?
|
|
360
341
|
this.error( 'anno-duplicate-variant', [ name.variant.location ],
|
|
361
342
|
{}, // TODO: params
|
|
@@ -374,18 +355,9 @@ function assignAnnotation( art, anno, prefix = '', iHaveVariant ) {
|
|
|
374
355
|
}
|
|
375
356
|
else {
|
|
376
357
|
name.absolute = absolute;
|
|
377
|
-
|
|
378
|
-
const old = art[prop];
|
|
379
|
-
if (old && old.$inferred)
|
|
380
|
-
art[prop] = anno;
|
|
381
|
-
else
|
|
382
|
-
dictAddArray( art, prop, anno, (n, location, a) => {
|
|
383
|
-
this.error( 'syntax-duplicate-anno', [ location ], { anno: n },
|
|
384
|
-
'Duplicate assignment with $(ANNO)' );
|
|
385
|
-
a.$errorReported = true; // do not report again later as anno-duplicate-xyz
|
|
386
|
-
} );
|
|
358
|
+
this.addAnnotation( art, '@' + absolute, anno );
|
|
387
359
|
}
|
|
388
|
-
if (!prefix) { // set deprecated $
|
|
360
|
+
if (!prefix) { // set deprecated $annotations for cds-lsp
|
|
389
361
|
if (!art.$annotations)
|
|
390
362
|
art.$annotations = [];
|
|
391
363
|
const location = locUtils.combinedLocation( anno.name, anno );
|
|
@@ -393,6 +365,76 @@ function assignAnnotation( art, anno, prefix = '', iHaveVariant ) {
|
|
|
393
365
|
}
|
|
394
366
|
}
|
|
395
367
|
|
|
368
|
+
function addAnnotation( art, prop, anno ) {
|
|
369
|
+
dictAddArray( art, prop, anno, (n, location, a) => {
|
|
370
|
+
// if we would make it a warning, we would still need to keep it an error
|
|
371
|
+
// with '...'; otherwise parse.cdl would have to split annotate statements
|
|
372
|
+
this.error( 'syntax-duplicate-anno', [ location ], { anno: n },
|
|
373
|
+
'Duplicate assignment with $(ANNO)' );
|
|
374
|
+
a.$errorReported = 'syntax-duplicate-anno';
|
|
375
|
+
// do not report again later as anno-duplicate-xyz
|
|
376
|
+
} );
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const extensionDicts = { elements: true, enum: true, params: true, returns: true };
|
|
380
|
+
|
|
381
|
+
function checkExtensionDict( dict ) {
|
|
382
|
+
for (const name in dict) {
|
|
383
|
+
const def = dict[name];
|
|
384
|
+
if (!def.$duplicates)
|
|
385
|
+
continue;
|
|
386
|
+
|
|
387
|
+
if (def.kind !== 'annotate') {
|
|
388
|
+
const numDefines =
|
|
389
|
+
def.$duplicates.reduce( addOneForDefinition, addOneForDefinition( 0, def ) );
|
|
390
|
+
this.handleDuplicateExtension( def, name, numDefines );
|
|
391
|
+
for (const dup of def.$duplicates)
|
|
392
|
+
this.handleDuplicateExtension( dup, name, numDefines );
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
// move annotations, 'doc' and 'elements' etc to main member
|
|
396
|
+
for (const dup of def.$duplicates) {
|
|
397
|
+
for (const prop of Object.keys( dup )) {
|
|
398
|
+
if (prop.charAt(0) === '@') {
|
|
399
|
+
this.addAnnotation( def, prop, dup[prop] )
|
|
400
|
+
}
|
|
401
|
+
else if (prop === 'doc') {
|
|
402
|
+
if (def.doc)
|
|
403
|
+
this.warning( 'syntax-duplicate-doc-comment', def.doc.location, {},
|
|
404
|
+
'Doc comment is overwritten by another one below' );
|
|
405
|
+
def.doc = dup.doc;
|
|
406
|
+
}
|
|
407
|
+
else if (extensionDicts[prop]) {
|
|
408
|
+
if (def[prop])
|
|
409
|
+
this.message( 'syntax-duplicate-annotate', [ def.name.location ], { name, prop } );
|
|
410
|
+
def[prop] = dup[prop]; // continuation semantics: last wins
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
def.$duplicates = null;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function addOneForDefinition( count, ext ) {
|
|
419
|
+
return (ext.kind === 'extend') ? count : count + 1;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Handle duplicate extensions. Does not handle `annotate`.
|
|
424
|
+
*
|
|
425
|
+
* @param {XSN.Extension} ext
|
|
426
|
+
* @param {string} name
|
|
427
|
+
* @param {number} numDefines
|
|
428
|
+
*/
|
|
429
|
+
function handleDuplicateExtension( ext, name, numDefines ) {
|
|
430
|
+
if (ext.kind === 'extend')
|
|
431
|
+
this.error( 'syntax-duplicate-extend', [ ext.name.location ],
|
|
432
|
+
{ name, '#': (numDefines ? 'define' : 'extend') } );
|
|
433
|
+
else if (numDefines === 1)
|
|
434
|
+
ext.$errorReported = 'syntax-duplicate-extend'; // a definition, but not duplicate
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
|
|
396
438
|
/**
|
|
397
439
|
* Return start location of `token`, or the first token matched by the current
|
|
398
440
|
* rule if `token` is undefined
|
|
@@ -433,31 +475,49 @@ function tokenLocation( token, endToken = null ) {
|
|
|
433
475
|
|
|
434
476
|
// This check is done for performance reason. No need to access a token's
|
|
435
477
|
// data if we know that it spans only one single line.
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
478
|
+
if (this.isMultiLineToken(token))
|
|
479
|
+
this.fixMultiLineTokenEndLocation(token, loc);
|
|
480
|
+
|
|
481
|
+
return loc;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function isMultiLineToken(token) {
|
|
485
|
+
return (
|
|
486
|
+
token.type === this.constructor.DocComment ||
|
|
487
|
+
token.type === this.constructor.String ||
|
|
488
|
+
token.type === this.constructor.UnterminatedLiteral
|
|
440
489
|
);
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Adapt end location of `location` according to `token`, assuming that `token` is a multi-line
|
|
494
|
+
* token such as a multi-line string or doc comment.
|
|
495
|
+
*
|
|
496
|
+
* Sets `endLine`/`endCol`, respecting newline characters in the token.
|
|
497
|
+
*
|
|
498
|
+
* @param token
|
|
499
|
+
* @param {CSN.Location} location
|
|
500
|
+
*/
|
|
501
|
+
function fixMultiLineTokenEndLocation( token, location ) {
|
|
502
|
+
// Count the number of newlines in the token.
|
|
503
|
+
const source = token.source[1].data;
|
|
504
|
+
let newLineCount = 0;
|
|
505
|
+
let lastNewlineIndex = token.start;
|
|
506
|
+
for (let i = token.start; i < token.stop; i++) {
|
|
507
|
+
// Note: We do NOT check for CR, LS, and PS (/[\r\u2028\u2029]/)
|
|
508
|
+
// because ANTLR only uses LF for line break detection.
|
|
509
|
+
if (source[i] === 10) { // code point of '\n'
|
|
510
|
+
newLineCount++;
|
|
511
|
+
lastNewlineIndex = i;
|
|
457
512
|
}
|
|
458
513
|
}
|
|
459
|
-
|
|
460
|
-
|
|
514
|
+
if (newLineCount > 0) {
|
|
515
|
+
location.endLine = token.line + newLineCount;
|
|
516
|
+
location.endCol = token.stop - lastNewlineIndex + 1;
|
|
517
|
+
} else {
|
|
518
|
+
location.endLine = token.line;
|
|
519
|
+
location.endCol = token.stop - token.start + token.column + 2; // after the last char (special for EOF?)
|
|
520
|
+
}
|
|
461
521
|
}
|
|
462
522
|
|
|
463
523
|
/**
|
|
@@ -504,7 +564,7 @@ function surroundByParens( expr, open, close, asQuery = false ) {
|
|
|
504
564
|
}
|
|
505
565
|
|
|
506
566
|
function unaryOpForParens( query, val ) {
|
|
507
|
-
const parens = query
|
|
567
|
+
const parens = query?.$parens;
|
|
508
568
|
if (!parens)
|
|
509
569
|
return query;
|
|
510
570
|
const location = parens[parens.length - 1];
|
|
@@ -530,8 +590,8 @@ function docComment( node ) {
|
|
|
530
590
|
if (!this.options.docComment)
|
|
531
591
|
return;
|
|
532
592
|
if (node.doc) {
|
|
533
|
-
this.warning( 'syntax-duplicate-doc-comment',
|
|
534
|
-
'
|
|
593
|
+
this.warning( 'syntax-duplicate-doc-comment', node.doc.location, {},
|
|
594
|
+
'Doc comment is overwritten by another one below' );
|
|
535
595
|
}
|
|
536
596
|
node.doc = this.valueWithTokenLocation( parseDocComment( token.text ), token );
|
|
537
597
|
}
|
|
@@ -715,8 +775,7 @@ function quotedLiteral( token, literal ) {
|
|
|
715
775
|
literal = token.text.slice( 0, pos - 1 ).toLowerCase();
|
|
716
776
|
const p = quotedLiteralPatterns[literal] || {};
|
|
717
777
|
|
|
718
|
-
if (
|
|
719
|
-
!this.options.parseOnly)
|
|
778
|
+
if (p.test_fn && !p.test_fn(val) && !this.options.parseOnly)
|
|
720
779
|
this.warning( 'syntax-invalid-literal', location, { '#': p.test_variant } );
|
|
721
780
|
|
|
722
781
|
if (p.unexpected_char) {
|
|
@@ -743,10 +802,6 @@ function quotedLiteral( token, literal ) {
|
|
|
743
802
|
}
|
|
744
803
|
}
|
|
745
804
|
|
|
746
|
-
function pathName( path, brokenName ) {
|
|
747
|
-
return (path && !path.broken) ? path.map( id => id.id ).join('.') : brokenName;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
805
|
function pushIdent( path, ident, prefix ) {
|
|
751
806
|
if (!ident) {
|
|
752
807
|
path.broken = true;
|
|
@@ -981,6 +1036,9 @@ function handleComposition( cardinality, isComposition ) {
|
|
|
981
1036
|
}
|
|
982
1037
|
|
|
983
1038
|
function associationInSelectItem( art ) {
|
|
1039
|
+
if (!art.value) // e.g. `expand` without value (for new structures)
|
|
1040
|
+
return;
|
|
1041
|
+
|
|
984
1042
|
const isPath = art.value.path && art.value.path.length
|
|
985
1043
|
const isIdentifier = isPath && art.value.path.length === 1;
|
|
986
1044
|
if (isIdentifier) {
|