@sap/cds-compiler 4.9.4 → 5.0.6
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 +66 -0
- package/bin/cds_remove_invalid_whitespace.js +2 -1
- package/bin/cdsc.js +15 -11
- package/bin/cdshi.js +1 -0
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/main.js +6 -18
- package/lib/api/options.js +3 -11
- package/lib/api/trace.js +0 -1
- package/lib/base/builtins.js +1 -0
- package/lib/base/location.js +4 -1
- package/lib/base/message-registry.js +29 -29
- package/lib/base/messages.js +22 -26
- package/lib/base/model.js +0 -2
- package/lib/base/node-helpers.js +0 -1
- package/lib/checks/enricher.js +1 -5
- package/lib/checks/structuredAnnoExpressions.js +30 -0
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +4 -1
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/builtins.js +18 -2
- package/lib/compiler/checks.js +2 -5
- package/lib/compiler/define.js +7 -7
- package/lib/compiler/extend.js +68 -33
- package/lib/compiler/generate.js +1 -1
- package/lib/compiler/index.js +23 -6
- package/lib/compiler/lsp-api.js +501 -2
- package/lib/compiler/populate.js +2 -2
- package/lib/compiler/propagator.js +1 -4
- package/lib/compiler/resolve.js +2 -15
- package/lib/compiler/shared.js +112 -31
- package/lib/compiler/tweak-assocs.js +2 -16
- package/lib/compiler/utils.js +2 -1
- package/lib/compiler/xsn-model.js +4 -0
- package/lib/edm/annotations/genericTranslation.js +95 -42
- package/lib/edm/csn2edm.js +16 -4
- package/lib/edm/edm.js +2 -3
- package/lib/edm/edmAnnoPreprocessor.js +1 -2
- package/lib/edm/edmPreprocessor.js +1 -7
- package/lib/gen/Dictionary.json +29 -2
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +2 -1
- package/lib/gen/languageParser.js +4995 -4817
- package/lib/json/csnVersion.js +1 -1
- package/lib/json/from-csn.js +4 -7
- package/lib/json/to-csn.js +23 -12
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/errorStrategy.js +0 -1
- package/lib/language/genericAntlrParser.js +35 -12
- package/lib/language/multiLineStringParser.js +3 -2
- package/lib/language/textUtils.js +1 -0
- package/lib/main.d.ts +28 -9
- package/lib/main.js +7 -4
- package/lib/model/csnRefs.js +20 -4
- package/lib/model/csnUtils.js +0 -2
- package/lib/model/revealInternalProperties.js +1 -1
- package/lib/modelCompare/compare.js +1 -1
- package/lib/optionProcessor.js +28 -9
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +36 -7
- package/lib/render/toSql.js +1 -0
- package/lib/render/utils/common.js +12 -9
- package/lib/render/utils/stringEscapes.js +1 -0
- package/lib/transform/db/applyTransformations.js +13 -8
- package/lib/transform/db/associations.js +62 -54
- package/lib/transform/db/expansion.js +1 -6
- package/lib/transform/db/flattening.js +89 -111
- package/lib/transform/db/temporal.js +3 -4
- package/lib/transform/db/views.js +0 -1
- package/lib/transform/draft/odata.js +51 -3
- package/lib/transform/effective/annotations.js +3 -2
- package/lib/transform/effective/flattening.js +135 -0
- package/lib/transform/effective/main.js +6 -6
- package/lib/transform/effective/types.js +13 -9
- package/lib/transform/forOdata.js +0 -2
- package/lib/transform/forRelationalDB.js +0 -19
- package/lib/transform/localized.js +7 -8
- package/lib/transform/odata/flattening.js +39 -31
- package/lib/transform/odata/typesExposure.js +5 -17
- package/lib/transform/transformUtils.js +1 -1
- package/lib/transform/translateAssocsToJoins.js +21 -3
- package/lib/utils/file.js +13 -7
- package/lib/utils/moduleResolve.js +59 -8
- package/lib/utils/term.js +3 -2
- package/package.json +7 -3
- package/share/messages/message-explanations.json +2 -0
- package/share/messages/type-unexpected-foreign-keys.md +52 -0
- package/share/messages/type-unexpected-on-condition.md +52 -0
package/lib/json/csnVersion.js
CHANGED
|
@@ -32,7 +32,7 @@ function isNewCSN( csn, options ) {
|
|
|
32
32
|
function checkCSNVersion( csn, options ) {
|
|
33
33
|
if (!isNewCSN(csn, options)) {
|
|
34
34
|
// the new transformer works only with new CSN
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
const { makeMessageFunction } = require('../base/messages');
|
|
37
37
|
const { error, throwWithAnyError } = makeMessageFunction(csn, options);
|
|
38
38
|
|
package/lib/json/from-csn.js
CHANGED
|
@@ -650,7 +650,7 @@ const schema = compileSchema( {
|
|
|
650
650
|
prop: '@‹anno›', // which property name do messages use for annotation assignments?
|
|
651
651
|
type: annotation,
|
|
652
652
|
// allowed in all definitions except mixins (including columns and extensions)
|
|
653
|
-
inKind:
|
|
653
|
+
inKind: kind => (kind !== 'mixin'),
|
|
654
654
|
schema: {
|
|
655
655
|
'-expr': { // '-expr' and '-' must not exist top-level
|
|
656
656
|
prop: '@‹anno›',
|
|
@@ -1495,9 +1495,6 @@ function annotation( val, spec, xsn, csn, name ) {
|
|
|
1495
1495
|
if (!id) // `"@": …` is already syntax-unknown-property
|
|
1496
1496
|
message( 'syntax-invalid-name', location(true), { '#': '{}' } );
|
|
1497
1497
|
|
|
1498
|
-
if (xsn && xsn.kind === 'mixin') // TODO(v5): Remove, adapt spec['@'].inKind
|
|
1499
|
-
message('anno-unexpected-mixin', location(true));
|
|
1500
|
-
|
|
1501
1498
|
const n = { id, location: location() };
|
|
1502
1499
|
const r = annoValue( val, spec );
|
|
1503
1500
|
r.name = n;
|
|
@@ -1784,7 +1781,8 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
|
|
|
1784
1781
|
return { prop, type: ignore };
|
|
1785
1782
|
return { prop, type: extra };
|
|
1786
1783
|
}
|
|
1787
|
-
// TODO
|
|
1784
|
+
// TODO v6: No warning with --sloppy (does not exist, yet)
|
|
1785
|
+
// Intention: Ignore unknown properties.
|
|
1788
1786
|
warning( 'syntax-unknown-property', location(true), { prop },
|
|
1789
1787
|
'Unknown CSN property $(PROP)' );
|
|
1790
1788
|
return { type: ignore };
|
|
@@ -2076,8 +2074,7 @@ function toXsn( csn, filename, options, messageFunctions ) {
|
|
|
2076
2074
|
inExtensions = null;
|
|
2077
2075
|
vocabInDefinitions = null;
|
|
2078
2076
|
|
|
2079
|
-
const xsn = new XsnSource();
|
|
2080
|
-
xsn.$frontend = 'json';
|
|
2077
|
+
const xsn = new XsnSource( 'json' ); // TODO: 'csn'? LSP does not use $frontend
|
|
2081
2078
|
|
|
2082
2079
|
// eslint-disable-next-line object-curly-newline
|
|
2083
2080
|
({ message, error, warning, info } = messageFunctions);
|
package/lib/json/to-csn.js
CHANGED
|
@@ -93,8 +93,7 @@ const transformers = {
|
|
|
93
93
|
cardinality, // also in pathItem: after 'id', before 'where'
|
|
94
94
|
targetAspect,
|
|
95
95
|
target,
|
|
96
|
-
$
|
|
97
|
-
$enclosed: value, // comp+filter v5
|
|
96
|
+
$enclosed: value, // comp+filter since v5
|
|
98
97
|
foreignKeys,
|
|
99
98
|
enum: enumDict,
|
|
100
99
|
items,
|
|
@@ -120,7 +119,7 @@ const transformers = {
|
|
|
120
119
|
// definitions, extensions, members ----------------------------------------
|
|
121
120
|
notNull: value,
|
|
122
121
|
default: expression,
|
|
123
|
-
|
|
122
|
+
targetElement, // special display of foreign key
|
|
124
123
|
value: enumValueOrCalc, // do not list for select items as elements
|
|
125
124
|
query,
|
|
126
125
|
elements,
|
|
@@ -344,18 +343,24 @@ function sources( srcDict, csn, model ) {
|
|
|
344
343
|
let names = model._sources || Object.keys( srcDict );
|
|
345
344
|
const $sources = names.length && srcDict[names[0]].$sources;
|
|
346
345
|
if ($sources) {
|
|
347
|
-
setHidden( csn, '$sources', $sources );
|
|
346
|
+
setHidden( csn, '$sources', normalize$sources( $sources ) );
|
|
348
347
|
return undefined;
|
|
349
348
|
}
|
|
350
349
|
if (model._sortedSources)
|
|
351
350
|
names = model._sortedSources.map( s => s.realname );
|
|
352
|
-
|
|
351
|
+
names = (!strictMode) ? names : normalize$sources( names.map( relativeName ) );
|
|
352
|
+
setHidden( csn, '$sources', names );
|
|
353
353
|
return undefined;
|
|
354
354
|
|
|
355
355
|
function relativeName( name ) {
|
|
356
356
|
const loc = srcDict[name].location;
|
|
357
357
|
return loc && loc.file || name;
|
|
358
358
|
}
|
|
359
|
+
function normalize$sources( src ) {
|
|
360
|
+
return strictMode
|
|
361
|
+
? src.map( name => locationString( name, true ) )
|
|
362
|
+
: src;
|
|
363
|
+
}
|
|
359
364
|
}
|
|
360
365
|
|
|
361
366
|
function attachAnnotations( annotate, prop, dict, inferred, insideReturns = false ) {
|
|
@@ -666,19 +671,19 @@ function returns( art, csn, node, prop ) {
|
|
|
666
671
|
return definition( art, csn, node, prop );
|
|
667
672
|
}
|
|
668
673
|
|
|
669
|
-
function definition( art,
|
|
674
|
+
function definition( art, csn, _node, prop ) {
|
|
670
675
|
if (!art || typeof art !== 'object' || art.builtin)
|
|
671
676
|
return undefined; // TODO: complain with strict
|
|
672
677
|
// Do not include namespace definitions or inferred construct (in gensrc):
|
|
673
678
|
if (art.kind === 'namespace' || art.$inferred && gensrcFlavor)
|
|
674
679
|
return undefined;
|
|
675
|
-
if (art.kind === 'key') {
|
|
676
|
-
const key =
|
|
677
|
-
|
|
678
|
-
if (!art.$inferred)
|
|
680
|
+
if (art.kind === 'key') { // foreign key
|
|
681
|
+
const key = standard( art );
|
|
682
|
+
if (!art.$inferred) // override location; otherwise only alias would be used
|
|
679
683
|
addLocation( art.targetElement.location, key );
|
|
680
684
|
return extra( key, art );
|
|
681
685
|
}
|
|
686
|
+
|
|
682
687
|
const c = standard( art );
|
|
683
688
|
// The XSN of actions in extensions do not contain a returns yet - TODO?
|
|
684
689
|
const elems = c.elements;
|
|
@@ -946,7 +951,7 @@ function originRef( art, user ) {
|
|
|
946
951
|
const name = parent.name || parent._outer.name;
|
|
947
952
|
if (name.id && parent.kind !== '$inline' || !r.length)
|
|
948
953
|
// Return parameter is in XSN - kind: 'param', name.id: ''
|
|
949
|
-
// eslint-disable-next-line no-nested-ternary
|
|
954
|
+
// eslint-disable-next-line no-nested-ternary
|
|
950
955
|
r.push( !nkind ? name.id : name.id ? { [nkind]: name.id } : { returns: true } );
|
|
951
956
|
parent = parent._parent;
|
|
952
957
|
}
|
|
@@ -1142,10 +1147,16 @@ function value( node ) {
|
|
|
1142
1147
|
return r;
|
|
1143
1148
|
}
|
|
1144
1149
|
|
|
1150
|
+
function targetElement( val, csn, node ) {
|
|
1151
|
+
const key = addExplicitAs( { ref: val.path.map( pathItem ) },
|
|
1152
|
+
node.name, neqPath( val ) );
|
|
1153
|
+
Object.assign(csn, key);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1145
1156
|
function enumValueOrCalc( v, csn, node ) {
|
|
1146
1157
|
if (v.$inferred && (universalCsn || gensrcFlavor))
|
|
1147
1158
|
return undefined;
|
|
1148
|
-
//
|
|
1159
|
+
// Enum values in CSN are without outer `value: { … }`:
|
|
1149
1160
|
if (node.kind === 'enum') {
|
|
1150
1161
|
Object.assign( csn, expression( v ) );
|
|
1151
1162
|
}
|
|
@@ -20,7 +20,6 @@ const Lexer = require('../gen/languageLexer').default;
|
|
|
20
20
|
// Error listener used for ANTLR4-generated parser
|
|
21
21
|
class ErrorListener extends antlr4.error.ErrorListener {
|
|
22
22
|
// method which is called by generated parser with --trace-parser[-amg]:
|
|
23
|
-
// eslint-disable-next-line class-methods-use-this
|
|
24
23
|
syntaxError( recognizer, offendingSymbol, line, column, msg, e ) {
|
|
25
24
|
if (!(e instanceof CompileMessage)) // not already reported
|
|
26
25
|
// Ignore warning, because only relevant for --trace-parser
|
|
@@ -50,7 +49,7 @@ class RewriteTypeTokenStream extends antlr4.CommonTokenStream {
|
|
|
50
49
|
}
|
|
51
50
|
else if (t.type === this.NEW) {
|
|
52
51
|
const n = super.LT(k + 1);
|
|
53
|
-
// TODO
|
|
52
|
+
// TODO: rewrite token in grammar via `this.setLocalToken`
|
|
54
53
|
if (n?.type === this.Identifier) {
|
|
55
54
|
const o = super.LT(k + 2);
|
|
56
55
|
if (o?.type === this.PAREN)
|
|
@@ -84,6 +83,7 @@ function initTokenRewrite( recognizer, ts ) { // ts = tokenStream
|
|
|
84
83
|
ts.AT = tokenTypeOf( recognizer, "'@'" );
|
|
85
84
|
ts.SEMICOLON = tokenTypeOf( recognizer, "';'" );
|
|
86
85
|
ts.NEW = Parser.NEW;
|
|
86
|
+
ts.RETURNS = Parser.RETURNS;
|
|
87
87
|
ts.Identifier = Parser.Identifier;
|
|
88
88
|
ts.PAREN = tokenTypeOf( recognizer, "'('" );
|
|
89
89
|
|
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
'use strict';
|
|
30
30
|
|
|
31
31
|
const antlr4 = require('antlr4');
|
|
32
|
-
// eslint-disable-next-line camelcase
|
|
33
32
|
const Antlr4LL1Analyzer = require('antlr4/src/antlr4/LL1Analyzer');
|
|
34
33
|
const { DefaultErrorStrategy } = require('antlr4/src/antlr4/error/ErrorStrategy');
|
|
35
34
|
const { InputMismatchException } = require('antlr4/src/antlr4/error/Errors');
|
|
@@ -447,6 +447,7 @@ function checkExtensionDict( dict ) {
|
|
|
447
447
|
for (const prop of Object.keys( dup )) {
|
|
448
448
|
if (prop.charAt(0) === '@') {
|
|
449
449
|
this.addAnnotation( def, prop, dup[prop] );
|
|
450
|
+
delete dup[prop]; // we want to keep $duplicates, but not have duplicate props
|
|
450
451
|
}
|
|
451
452
|
else if (prop === 'doc') {
|
|
452
453
|
// With explicit docComment:false, we don't emit a warning.
|
|
@@ -455,11 +456,13 @@ function checkExtensionDict( dict ) {
|
|
|
455
456
|
'Doc comment is overwritten by another one below' );
|
|
456
457
|
}
|
|
457
458
|
def.doc = dup.doc;
|
|
459
|
+
delete dup[prop]; // we want to keep $duplicates for LSP, but not have duplicate props
|
|
458
460
|
}
|
|
459
461
|
else if (extensionDicts[prop]) {
|
|
460
462
|
if (def[prop])
|
|
461
463
|
this.message( 'syntax-duplicate-annotate', [ def.name.location ], { name, prop } );
|
|
462
464
|
def[prop] = dup[prop]; // continuation semantics: last wins
|
|
465
|
+
delete dup[prop]; // we want to keep $duplicates for LSP, but not have duplicate props
|
|
463
466
|
}
|
|
464
467
|
}
|
|
465
468
|
if (dup.$annotations) { // update deprecated $annotations for cds-lsp / annotation modeler
|
|
@@ -469,7 +472,9 @@ function checkExtensionDict( dict ) {
|
|
|
469
472
|
def.$annotations = dup.$annotations;
|
|
470
473
|
}
|
|
471
474
|
}
|
|
472
|
-
|
|
475
|
+
|
|
476
|
+
// We keep duplicate statements for LSP, as it needs to traverse all identifiers;
|
|
477
|
+
// annotations were removed above to avoid traversing annotations twice.
|
|
473
478
|
}
|
|
474
479
|
}
|
|
475
480
|
|
|
@@ -709,14 +714,26 @@ function docComment( node ) {
|
|
|
709
714
|
node.doc = this.valueWithTokenLocation( val, token );
|
|
710
715
|
}
|
|
711
716
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
717
|
+
/**
|
|
718
|
+
* Classify token (identifier category) for implicit names. To be used in the
|
|
719
|
+
* empty alternative to AS <explicitName>. If `ref` is given, uses the last
|
|
720
|
+
* path segment's `tokenIndex`. The return value can be used to reset the
|
|
721
|
+
* token's category, e.g. for inline select items.
|
|
722
|
+
*
|
|
723
|
+
* @param {string} category
|
|
724
|
+
* @param [ref]
|
|
725
|
+
*/
|
|
726
|
+
function classifyImplicitName( category, ref ) {
|
|
727
|
+
if (!ref || ref.path) {
|
|
728
|
+
const tokenIndex = ref?.path[ref.path.length - 1]?.location.tokenIndex;
|
|
729
|
+
const implicit = (tokenIndex === undefined) ? this._input.LT(-1) : this._input.get(tokenIndex);
|
|
730
|
+
if (implicit.isIdentifier) {
|
|
731
|
+
const previous = implicit.isIdentifier;
|
|
718
732
|
implicit.isIdentifier = category;
|
|
733
|
+
return { token: implicit, previous };
|
|
734
|
+
}
|
|
719
735
|
}
|
|
736
|
+
return null;
|
|
720
737
|
}
|
|
721
738
|
|
|
722
739
|
function fragileAlias( ast, safe = false ) {
|
|
@@ -942,8 +959,7 @@ function numberLiteral( sign, text = this._input.LT(-1).text ) {
|
|
|
942
959
|
token.stop + 1 === nextToken.start &&
|
|
943
960
|
(nextToken.type === this.constructor.Identifier ||
|
|
944
961
|
nextToken.type < this.constructor.Identifier && /^[a-z]+$/i.test( nextToken.text ))) {
|
|
945
|
-
|
|
946
|
-
this.warning('syntax-expecting-space', nextToken, {},
|
|
962
|
+
this.message('syntax-expecting-space', nextToken, {},
|
|
947
963
|
'Expecting a space between a number and a keyword/identifier');
|
|
948
964
|
}
|
|
949
965
|
|
|
@@ -1111,7 +1127,8 @@ function pushItem( array, val ) {
|
|
|
1111
1127
|
|
|
1112
1128
|
// For :param, #variant, #symbol, @(…) and @Begin and `@` inside annotation paths
|
|
1113
1129
|
function reportUnexpectedSpace( prefix = this._input.LT(-1),
|
|
1114
|
-
location = this.tokenLocation( this._input.LT(1) )
|
|
1130
|
+
location = this.tokenLocation( this._input.LT(1) ),
|
|
1131
|
+
isError = false ) {
|
|
1115
1132
|
const prefixLoc = this.tokenLocation( prefix );
|
|
1116
1133
|
if (prefixLoc.endLine !== location.line ||
|
|
1117
1134
|
prefixLoc.endCol !== location.col) {
|
|
@@ -1122,8 +1139,14 @@ function reportUnexpectedSpace( prefix = this._input.LT(-1),
|
|
|
1122
1139
|
endLine: location.line,
|
|
1123
1140
|
endCol: location.col,
|
|
1124
1141
|
};
|
|
1125
|
-
|
|
1126
|
-
|
|
1142
|
+
if (isError) {
|
|
1143
|
+
this.message( 'syntax-invalid-space', wsLocation, { op: prefix.text },
|
|
1144
|
+
'Delete the whitespace after $(OP)' );
|
|
1145
|
+
}
|
|
1146
|
+
else {
|
|
1147
|
+
this.warning( 'syntax-unexpected-space', wsLocation, { op: prefix.text },
|
|
1148
|
+
'Delete the whitespace after $(OP)' );
|
|
1149
|
+
}
|
|
1127
1150
|
}
|
|
1128
1151
|
return prefixLoc;
|
|
1129
1152
|
}
|
|
@@ -256,7 +256,7 @@ class MultiLineStringParser {
|
|
|
256
256
|
try {
|
|
257
257
|
this.output.push(String.fromCodePoint(n));
|
|
258
258
|
}
|
|
259
|
-
catch
|
|
259
|
+
catch {
|
|
260
260
|
// RangeError is thrown if number isn't a valid code point
|
|
261
261
|
reportInvalidCodePoint();
|
|
262
262
|
}
|
|
@@ -484,7 +484,7 @@ class MultiLineStringParser {
|
|
|
484
484
|
* @param {string} code
|
|
485
485
|
* @private
|
|
486
486
|
*/
|
|
487
|
-
_makeCode(code) {
|
|
487
|
+
_makeCode(code) {
|
|
488
488
|
// For characters that may be rendered as newline,
|
|
489
489
|
// see <https://www.unicode.org/reports/tr14/tr14-32.html>.
|
|
490
490
|
//
|
|
@@ -500,6 +500,7 @@ class MultiLineStringParser {
|
|
|
500
500
|
//
|
|
501
501
|
// For Visualization, see <https://en.wikipedia.org/wiki/Newline#Unicode>
|
|
502
502
|
// U+23CE: ⏎
|
|
503
|
+
// eslint-disable-next-line no-control-regex
|
|
503
504
|
const allNewLineCharacters = /[\u{000A}\u{000B}\u{000C}\u{000D}\u{0085}\u{2028}\u{2029}]/ug;
|
|
504
505
|
return code.replace(allNewLineCharacters, '\u{23CE}');
|
|
505
506
|
}
|
package/lib/main.d.ts
CHANGED
|
@@ -68,18 +68,22 @@ declare namespace compiler {
|
|
|
68
68
|
* additional "extend" or "annotate" statements, but not suitable
|
|
69
69
|
* for consumption by clients or backends.
|
|
70
70
|
* - universal : In development (BETA)
|
|
71
|
+
*
|
|
72
|
+
* @default 'client'
|
|
71
73
|
*/
|
|
72
74
|
csnFlavor?: string | 'client' | 'gensrc' | 'universal'
|
|
73
75
|
/**
|
|
74
|
-
* If set, backends will
|
|
75
|
-
* that only have an association to a localized entity/view.
|
|
76
|
-
* a convenience view, if they themselves contain localized elements (i.e. either
|
|
76
|
+
* If set to false, backends will create localized convenience views for those views,
|
|
77
|
+
* that only have an association to a localized entity/view. If set to true, views will
|
|
78
|
+
* only get a convenience view, if they themselves contain localized elements (i.e. either
|
|
77
79
|
* have simple projection on localized elements and CDL-casts to a localized element).
|
|
78
80
|
*
|
|
79
|
-
*
|
|
81
|
+
* If true, the OData backend will not set `$localized: true` markers for such cases.
|
|
80
82
|
*
|
|
81
83
|
* Does not work for backends to.hdi(), to.hdbcds() or to.sql() with `sqlDialect: 'hana'`,
|
|
82
84
|
* since in all those dialects, associations still exist in generated artifacts.
|
|
85
|
+
*
|
|
86
|
+
* @default true
|
|
83
87
|
*/
|
|
84
88
|
fewerLocalizedViews?: boolean
|
|
85
89
|
}
|
|
@@ -698,10 +702,6 @@ declare namespace compiler {
|
|
|
698
702
|
* @param config.noMessageId
|
|
699
703
|
* If true, will _not_ show the message ID (+ explanation hint) in the output.
|
|
700
704
|
*
|
|
701
|
-
* @param config.idInBrackets
|
|
702
|
-
* If true, the message ID (if there is one and noMessageId is falsey) will be put in brackets.
|
|
703
|
-
* This will be the default in cds-compiler v5.
|
|
704
|
-
*
|
|
705
705
|
* @param config.noHome
|
|
706
706
|
* If true, will _not_ show message's semantic location.
|
|
707
707
|
*
|
|
@@ -714,7 +714,6 @@ declare namespace compiler {
|
|
|
714
714
|
export function messageString(msg: CompileMessage, config?: {
|
|
715
715
|
normalizeFilename?: boolean
|
|
716
716
|
noMessageId?: boolean
|
|
717
|
-
idInBrackets?: boolean
|
|
718
717
|
noHome?: boolean
|
|
719
718
|
module?: string
|
|
720
719
|
}): string;
|
|
@@ -1379,6 +1378,26 @@ declare namespace compiler {
|
|
|
1379
1378
|
* @private
|
|
1380
1379
|
*/
|
|
1381
1380
|
function getArtifactName(artifact: object): object;
|
|
1381
|
+
|
|
1382
|
+
type LspSemanticTokenEvent = {
|
|
1383
|
+
event: 'reference' | 'definition',
|
|
1384
|
+
semanticToken: object,
|
|
1385
|
+
hint?: string
|
|
1386
|
+
node?: object
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1389
|
+
* Traverse the given XSN model and yield all _semantic tokens_ that are required by
|
|
1390
|
+
* the LSP. These semantic tokens mostly include _identifiers_, that is, references
|
|
1391
|
+
* or definitions. They also include the `returns` structure, as it is an annotation
|
|
1392
|
+
* target as well.
|
|
1393
|
+
*/
|
|
1394
|
+
function traverseSemanticTokens(xsn: object, options: CompileOptions): Generator<LspSemanticTokenEvent>;
|
|
1395
|
+
/**
|
|
1396
|
+
* Given an XSN reference object, e.g. the `semanticToken` value of a `traverseSemanticTokens`
|
|
1397
|
+
* event, return a generator that yields the reference's target and their origins until the
|
|
1398
|
+
* base definition is reached.
|
|
1399
|
+
*/
|
|
1400
|
+
function getSemanticTokenOrigin(obj: LspSemanticTokenEvent): Generator<object>;
|
|
1382
1401
|
}
|
|
1383
1402
|
|
|
1384
1403
|
/**
|
package/lib/main.js
CHANGED
|
@@ -29,6 +29,7 @@ const define = lazyload('./compiler/define');
|
|
|
29
29
|
const builtins = lazyload('./base/builtins');
|
|
30
30
|
const base = lazyload('./compiler/base');
|
|
31
31
|
const finalizeParseCdl = lazyload('./compiler/finalize-parse-cdl');
|
|
32
|
+
const lsp = lazyload('./compiler/lsp-api');
|
|
32
33
|
|
|
33
34
|
// The compiler version (taken from package.json)
|
|
34
35
|
function version() {
|
|
@@ -94,7 +95,7 @@ module.exports = {
|
|
|
94
95
|
value: messages.CompilationError,
|
|
95
96
|
writable: false,
|
|
96
97
|
configurable: false,
|
|
97
|
-
enumerable:
|
|
98
|
+
enumerable: true
|
|
98
99
|
});
|
|
99
100
|
return messages.CompilationError;
|
|
100
101
|
},
|
|
@@ -183,7 +184,9 @@ module.exports = {
|
|
|
183
184
|
$lsp: {
|
|
184
185
|
parse: (...args) => compiler.parseX(...args),
|
|
185
186
|
compile: (...args) => compiler.compileX(...args),
|
|
186
|
-
getArtifactName: (
|
|
187
|
+
getArtifactName: (art) => base.getArtifactName(art),
|
|
188
|
+
traverseSemanticTokens: (xsn, options) => lsp.traverseSemanticTokens(xsn, options),
|
|
189
|
+
getSemanticTokenOrigin: (obj) => lsp.getSemanticTokenOrigin(obj),
|
|
187
190
|
},
|
|
188
191
|
|
|
189
192
|
// CSN Model related functionality
|
|
@@ -201,7 +204,7 @@ module.exports = {
|
|
|
201
204
|
function lazyload(moduleName) {
|
|
202
205
|
let module;
|
|
203
206
|
return new Proxy(((...args) => {
|
|
204
|
-
if (!module)
|
|
207
|
+
if (!module)
|
|
205
208
|
module = require(moduleName);
|
|
206
209
|
|
|
207
210
|
if (module.apply && typeof module.apply === 'function')
|
|
@@ -209,7 +212,7 @@ function lazyload(moduleName) {
|
|
|
209
212
|
return module; // for destructured calls
|
|
210
213
|
}), {
|
|
211
214
|
get(target, name) {
|
|
212
|
-
if (!module)
|
|
215
|
+
if (!module)
|
|
213
216
|
module = require(moduleName);
|
|
214
217
|
|
|
215
218
|
return module[name];
|
package/lib/model/csnRefs.js
CHANGED
|
@@ -511,6 +511,8 @@ function csnRefs( csn, universalReady ) {
|
|
|
511
511
|
|
|
512
512
|
function initNode( art, parent, kind, name ) {
|
|
513
513
|
setCache( art, '_parent', parent );
|
|
514
|
+
if (art.keys)
|
|
515
|
+
setCache(art, '_keys', getKeysDict( art ));
|
|
514
516
|
if (kind === 'target') {
|
|
515
517
|
// Prevent re-initialization of anonymous aspect with initDefinition():
|
|
516
518
|
// (that would be with parent: null which would be wrong)
|
|
@@ -650,9 +652,12 @@ function csnRefs( csn, universalReady ) {
|
|
|
650
652
|
return resolvePath( path, elemParent.elements[head], null, 'query' );
|
|
651
653
|
}
|
|
652
654
|
if (!query) { // outside queries - TODO: items?
|
|
653
|
-
let art = parent.elements[head];
|
|
654
|
-
|
|
655
|
-
|
|
655
|
+
let art = parent.elements?.[head];
|
|
656
|
+
if (parent.keys) {
|
|
657
|
+
const keysDict = getCache( parent, '_keys' );
|
|
658
|
+
art = keysDict[head];
|
|
659
|
+
} // Ref to up_ in anonymous aspect
|
|
660
|
+
else if (!art && head === 'up_') {
|
|
656
661
|
const up = getCache( parent, '_parent' );
|
|
657
662
|
const target = up && typeof up.target === 'string' && csn.definitions[up.target];
|
|
658
663
|
if (target && target.elements) {
|
|
@@ -989,6 +994,17 @@ function csnRefs( csn, universalReady ) {
|
|
|
989
994
|
}
|
|
990
995
|
}
|
|
991
996
|
|
|
997
|
+
/**
|
|
998
|
+
* Foreign keys are stored in an array; for easier name resolution, create
|
|
999
|
+
* a dictionary of them.
|
|
1000
|
+
*/
|
|
1001
|
+
function getKeysDict( art ) {
|
|
1002
|
+
const dict = Object.create(null);
|
|
1003
|
+
for (const key of art.keys)
|
|
1004
|
+
dict[key.as || implicitAs( key.ref )] = key;
|
|
1005
|
+
return dict;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
992
1008
|
/**
|
|
993
1009
|
* Return value of a query SELECT for the query node, or the main artifact,
|
|
994
1010
|
* i.e. a value with an `elements` property.
|
|
@@ -1180,7 +1196,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
1180
1196
|
|
|
1181
1197
|
const prop = csnPath[index];
|
|
1182
1198
|
if (refCtx === 'annotation' && typeof obj === 'object') {
|
|
1183
|
-
// we do not know yet whether the annotation value is
|
|
1199
|
+
// we do not know yet whether the annotation value is an expression or not →
|
|
1184
1200
|
// loop over outer array and records (structure values):
|
|
1185
1201
|
if (Array.isArray( obj ) || !isAnnotationExpression( obj )) {
|
|
1186
1202
|
obj = obj[prop];
|
package/lib/model/csnUtils.js
CHANGED
|
@@ -706,7 +706,6 @@ function isEdmPropertyRendered( elementCsn, options ) {
|
|
|
706
706
|
* @param {('sqlite'|'hana'|'plain'|string)} [sqlDialect='plain'] The SQL dialect to use
|
|
707
707
|
* @returns {string} The resulting database name for (absolute) 'artifactName', depending on the current naming mode.
|
|
708
708
|
*/
|
|
709
|
-
// eslint-disable-next-line no-unused-vars
|
|
710
709
|
function getArtifactDatabaseNameOf( artifactName, sqlMapping, csn, sqlDialect = 'plain' ) {
|
|
711
710
|
if (csn && typeof csn === 'object' && csn.definitions) {
|
|
712
711
|
isValidMappingDialectCombi(sqlDialect, sqlMapping);
|
|
@@ -826,7 +825,6 @@ function isValidMappingDialectCombi( sqlDialect, sqlMapping ) {
|
|
|
826
825
|
* @param {('sqlite'|'hana'|'plain'|string)} [sqlDialect='plain'] The SQL dialect to use
|
|
827
826
|
* @returns {string} The resulting database element name for 'elemName', depending on the current naming mode.
|
|
828
827
|
*/
|
|
829
|
-
// eslint-disable-next-line no-unused-vars
|
|
830
828
|
function getElementDatabaseNameOf( elemName, sqlMapping, sqlDialect = 'plain' ) {
|
|
831
829
|
isValidMappingDialectCombi(sqlDialect, sqlMapping);
|
|
832
830
|
if (sqlMapping === 'hdbcds')
|
|
@@ -385,7 +385,7 @@ function quoted( name, undef = '‹undefined›' ) {
|
|
|
385
385
|
// To be used for tracing, e.g. by
|
|
386
386
|
// require('../model/revealInternalProperties').log(model, 'E_purposes')
|
|
387
387
|
function logXsnModel( model, name ) {
|
|
388
|
-
// eslint-disable-next-line no-console
|
|
388
|
+
// eslint-disable-next-line no-console
|
|
389
389
|
console.log( require('util').inspect( revealInternalProperties( model, name ), false, null ) );
|
|
390
390
|
}
|
|
391
391
|
|
|
@@ -67,7 +67,7 @@ function validateCsnVersions(beforeModel, afterModel, options) {
|
|
|
67
67
|
}
|
|
68
68
|
if (beforeVersionParts[0] > afterVersionParts[0] && !(options && options.allowCsnDowngrade)) {
|
|
69
69
|
const { error, throwWithAnyError } = makeMessageFunction(afterModel, options, 'modelCompare');
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
const { version } = require('../../package.json');
|
|
72
72
|
error(null, null, { value: afterVersion, othervalue: beforeVersion, version },
|
|
73
73
|
'Incompatible CSN versions: $(VALUE) is a major downgrade from $(OTHERVALUE). Is @sap/cds-compiler version $(VERSION) outdated?');
|
package/lib/optionProcessor.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
// Compiler options
|
|
2
|
+
|
|
3
|
+
// Remarks:
|
|
4
|
+
// - The specification is client-tool centric (bin/cdsc.js):
|
|
5
|
+
// an option named `fooBar` is “produced” by `.option(' --foo-bar')`.
|
|
6
|
+
// - Also list the option in the `help` text, used with `cdsc -h`.
|
|
7
|
+
// - Specify valid values for non-boolean options in lib/api/validate.js.
|
|
8
|
+
|
|
1
9
|
'use strict';
|
|
2
10
|
|
|
3
11
|
const { createOptionProcessor } = require('./base/optionProcessorHelper');
|
|
@@ -34,7 +42,7 @@ optionProcessor
|
|
|
34
42
|
.option(' --beta <list>')
|
|
35
43
|
.option(' --deprecated <list>')
|
|
36
44
|
.option(' --direct-backend')
|
|
37
|
-
.option(' --fallback-parser <type>', { valid: ['cdl', 'csn', 'csn!'] })
|
|
45
|
+
.option(' --fallback-parser <type>', { valid: [ 'auto!', 'cdl', 'csn', 'csn!' ] })
|
|
38
46
|
.option(' --shuffle <seed>') // 0 | 1..4294967296
|
|
39
47
|
.option(' --test-mode')
|
|
40
48
|
.option(' --test-sort-csn')
|
|
@@ -117,9 +125,10 @@ optionProcessor
|
|
|
117
125
|
eagerPersistenceForGeneratedEntities
|
|
118
126
|
--fallback-parser <type> If the language cannot be deduced by the file's extensions, use this
|
|
119
127
|
parser as a fallback. Valid values are:
|
|
120
|
-
cdl
|
|
121
|
-
csn
|
|
122
|
-
csn!
|
|
128
|
+
cdl : Use CDL parser
|
|
129
|
+
csn : Use CSN parser
|
|
130
|
+
csn! : Use CSN parser even with extension cds, cdl, hdbcds and hdbdd
|
|
131
|
+
auto! : Ignore file extension; use CSN parser if file content starts with '{'
|
|
123
132
|
--direct-backend Do not compile the given CSN but directly pass it to the backend.
|
|
124
133
|
Can only be used with certain new CSN based backends. Combination with
|
|
125
134
|
other flags is limited, e.g. --test-mode will not run a consistency check.
|
|
@@ -161,6 +170,8 @@ optionProcessor
|
|
|
161
170
|
Environment variables
|
|
162
171
|
NO_COLOR If set, compiler messages (/output) will not be colored.
|
|
163
172
|
Can be overwritten by '--color'
|
|
173
|
+
FORCE_COLOR If set, compiler messages (/output) will be colored. Overrides NO_COLOR.
|
|
174
|
+
Can be overwritten by '--color'
|
|
164
175
|
CDSC_TRACE_TIME If set, additional timing information is printed to stderr.
|
|
165
176
|
CDSC_TRACE_API If set, additional API calling information is printed to stderr.
|
|
166
177
|
`);
|
|
@@ -318,9 +329,9 @@ optionProcessor.command('Q, toSql')
|
|
|
318
329
|
.option(' --pre2134ReferentialConstraintNames')
|
|
319
330
|
.option(' --disable-hana-comments')
|
|
320
331
|
.option(' --generated-by-comment')
|
|
321
|
-
.option(' --better-sqlite-session-variables')
|
|
332
|
+
.option(' --better-sqlite-session-variables <bool>')
|
|
322
333
|
.option(' --fewer-localized-views')
|
|
323
|
-
.option(' --
|
|
334
|
+
.option(' --with-hana-associations <bool>', { valid: [ 'true', 'false' ] })
|
|
324
335
|
.help(`
|
|
325
336
|
Usage: cdsc toSql [options] <files...>
|
|
326
337
|
|
|
@@ -372,11 +383,17 @@ optionProcessor.command('Q, toSql')
|
|
|
372
383
|
--pre2134ReferentialConstraintNames Do not prefix the constraint identifier with "c__"
|
|
373
384
|
--disable-hana-comments Disable rendering of doc comments as SAP HANA comments.
|
|
374
385
|
--generated-by-comment Enable rendering of the initial SQL comment for HDI-based artifacts
|
|
375
|
-
--better-sqlite-session-variables
|
|
376
|
-
|
|
386
|
+
--better-sqlite-session-variables <bool>
|
|
387
|
+
Enable better-sqlite compatible rendering of $user. Only
|
|
388
|
+
active if sqlDialect is \`sqlite\`:
|
|
389
|
+
true : (default) Render better-sqlite session_context(…)
|
|
390
|
+
false : Render session variables as string literals, used e.g. with sqlite3 driver
|
|
377
391
|
--fewer-localized-views If set, the backends will not create localized convenience views for
|
|
378
392
|
those views, that only have an association to a localized entity/view.
|
|
379
|
-
--
|
|
393
|
+
--with-hana-associations <bool>
|
|
394
|
+
Enable and disable rendering of "WITH ASSOCIATIONS" for sqlDialect 'hana'.
|
|
395
|
+
true : (default) Render "WITH ASSOCIATIONS"
|
|
396
|
+
false : Do not render "WITH ASSOCIATIONS"
|
|
380
397
|
`);
|
|
381
398
|
|
|
382
399
|
optionProcessor.command('toRename')
|
|
@@ -482,6 +499,7 @@ optionProcessor.command('toCsn')
|
|
|
482
499
|
optionProcessor.command('parseCdl')
|
|
483
500
|
.option('-h, --help')
|
|
484
501
|
.positionalArgument('<file>')
|
|
502
|
+
.option(' --with-locations')
|
|
485
503
|
.help(`
|
|
486
504
|
Usage: cdsc parseCdl [options] <file>
|
|
487
505
|
|
|
@@ -489,6 +507,7 @@ optionProcessor.command('parseCdl')
|
|
|
489
507
|
resolve imports, apply extensions or expand any queries.
|
|
490
508
|
|
|
491
509
|
Options
|
|
510
|
+
--with-locations Add $location to CSN artifacts.
|
|
492
511
|
-h, --help Show this help text
|
|
493
512
|
`);
|
|
494
513
|
|
|
@@ -58,7 +58,7 @@ function alterConstraintsWithCsn( csn, options, messageFunctions ) {
|
|
|
58
58
|
// TODO: Remove / Move to api/options.js once alterConstraintsWithCsn is available outside bin/cdsc
|
|
59
59
|
function _transformSqlOptions( csn, options ) {
|
|
60
60
|
const { src } = options;
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
const prepareOptions = require('../api/options');
|
|
63
63
|
options = prepareOptions.to.sql(options);
|
|
64
64
|
options.src = src;
|