@sap/cds-compiler 4.9.6 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +92 -0
- package/bin/cds_remove_invalid_whitespace.js +2 -1
- package/bin/cdsc.js +49 -19
- package/bin/cdshi.js +3 -1
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/main.js +16 -19
- package/lib/api/options.js +5 -14
- 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 +43 -29
- package/lib/base/messages.js +23 -26
- package/lib/base/meta.js +10 -0
- package/lib/base/model.js +0 -2
- package/lib/base/node-helpers.js +0 -1
- package/lib/base/optionProcessorHelper.js +11 -0
- package/lib/checks/dbFeatureFlags.js +5 -0
- 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 +5 -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 +8 -8
- package/lib/compiler/extend.js +108 -37
- package/lib/compiler/generate.js +1 -1
- package/lib/compiler/index.js +27 -10
- package/lib/compiler/lsp-api.js +501 -2
- package/lib/compiler/populate.js +60 -13
- package/lib/compiler/propagator.js +10 -8
- package/lib/compiler/resolve.js +117 -94
- package/lib/compiler/shared.js +114 -32
- package/lib/compiler/tweak-assocs.js +31 -21
- package/lib/compiler/utils.js +2 -1
- package/lib/compiler/xsn-model.js +4 -0
- package/lib/edm/annotations/genericTranslation.js +69 -35
- package/lib/edm/csn2edm.js +16 -4
- package/lib/edm/edm.js +10 -3
- package/lib/edm/edmAnnoPreprocessor.js +1 -2
- package/lib/edm/edmPreprocessor.js +8 -10
- package/lib/gen/Dictionary.json +66 -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 +25 -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 +9 -9
- package/lib/model/cloneCsn.js +1 -0
- package/lib/model/csnRefs.js +22 -5
- package/lib/model/csnUtils.js +0 -14
- package/lib/model/revealInternalProperties.js +1 -2
- package/lib/modelCompare/compare.js +13 -11
- package/lib/optionProcessor.js +30 -9
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +44 -14
- package/lib/render/toHdbcds.js +1 -2
- package/lib/render/toSql.js +45 -8
- 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/backlinks.js +20 -5
- package/lib/transform/db/expansion.js +1 -6
- package/lib/transform/db/flattening.js +86 -109
- package/lib/transform/db/killAnnotations.js +3 -0
- package/lib/transform/db/processSqlServices.js +63 -0
- package/lib/transform/db/temporal.js +3 -4
- package/lib/transform/db/views.js +0 -1
- package/lib/transform/draft/odata.js +56 -3
- package/lib/transform/effective/annotations.js +3 -2
- package/lib/transform/effective/flattening.js +135 -0
- package/lib/transform/effective/main.js +6 -4
- package/lib/transform/effective/types.js +13 -9
- package/lib/transform/forOdata.js +0 -2
- package/lib/transform/forRelationalDB.js +9 -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 +0 -1
- package/lib/utils/file.js +87 -8
- 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 ) {
|
|
@@ -379,6 +384,8 @@ function attachAnnotations( annotate, prop, dict, inferred, insideReturns = fals
|
|
|
379
384
|
attachAnnotations( sub, 'elements', elems, inf, entry.returns );
|
|
380
385
|
else if (many.enum) // make 'enum' annotations appear in 'elements' annotate
|
|
381
386
|
attachAnnotations( sub, 'elements', many.enum, inf, entry.returns );
|
|
387
|
+
else if (entry.foreignKeys) // make 'foreignKeys' annotations appear in 'elements' annotate
|
|
388
|
+
attachAnnotations( sub, 'elements', entry.foreignKeys, inf );
|
|
382
389
|
}
|
|
383
390
|
if (Object.keys( sub ).length)
|
|
384
391
|
annoDict[name] = sub;
|
|
@@ -666,19 +673,19 @@ function returns( art, csn, node, prop ) {
|
|
|
666
673
|
return definition( art, csn, node, prop );
|
|
667
674
|
}
|
|
668
675
|
|
|
669
|
-
function definition( art,
|
|
676
|
+
function definition( art, csn, _node, prop ) {
|
|
670
677
|
if (!art || typeof art !== 'object' || art.builtin)
|
|
671
678
|
return undefined; // TODO: complain with strict
|
|
672
679
|
// Do not include namespace definitions or inferred construct (in gensrc):
|
|
673
680
|
if (art.kind === 'namespace' || art.$inferred && gensrcFlavor)
|
|
674
681
|
return undefined;
|
|
675
|
-
if (art.kind === 'key') {
|
|
676
|
-
const key =
|
|
677
|
-
|
|
678
|
-
if (!art.$inferred)
|
|
682
|
+
if (art.kind === 'key') { // foreign key
|
|
683
|
+
const key = standard( art );
|
|
684
|
+
if (!art.$inferred) // override location; otherwise only alias would be used
|
|
679
685
|
addLocation( art.targetElement.location, key );
|
|
680
686
|
return extra( key, art );
|
|
681
687
|
}
|
|
688
|
+
|
|
682
689
|
const c = standard( art );
|
|
683
690
|
// The XSN of actions in extensions do not contain a returns yet - TODO?
|
|
684
691
|
const elems = c.elements;
|
|
@@ -946,7 +953,7 @@ function originRef( art, user ) {
|
|
|
946
953
|
const name = parent.name || parent._outer.name;
|
|
947
954
|
if (name.id && parent.kind !== '$inline' || !r.length)
|
|
948
955
|
// Return parameter is in XSN - kind: 'param', name.id: ''
|
|
949
|
-
// eslint-disable-next-line no-nested-ternary
|
|
956
|
+
// eslint-disable-next-line no-nested-ternary
|
|
950
957
|
r.push( !nkind ? name.id : name.id ? { [nkind]: name.id } : { returns: true } );
|
|
951
958
|
parent = parent._parent;
|
|
952
959
|
}
|
|
@@ -1142,10 +1149,16 @@ function value( node ) {
|
|
|
1142
1149
|
return r;
|
|
1143
1150
|
}
|
|
1144
1151
|
|
|
1152
|
+
function targetElement( val, csn, node ) {
|
|
1153
|
+
const key = addExplicitAs( { ref: val.path.map( pathItem ) },
|
|
1154
|
+
node.name, neqPath( val ) );
|
|
1155
|
+
Object.assign(csn, key);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1145
1158
|
function enumValueOrCalc( v, csn, node ) {
|
|
1146
1159
|
if (v.$inferred && (universalCsn || gensrcFlavor))
|
|
1147
1160
|
return undefined;
|
|
1148
|
-
//
|
|
1161
|
+
// Enum values in CSN are without outer `value: { … }`:
|
|
1149
1162
|
if (node.kind === 'enum') {
|
|
1150
1163
|
Object.assign( csn, expression( v ) );
|
|
1151
1164
|
}
|
|
@@ -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,11 +29,9 @@ 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');
|
|
33
|
+
const meta = lazyload('./base/meta');
|
|
32
34
|
|
|
33
|
-
// The compiler version (taken from package.json)
|
|
34
|
-
function version() {
|
|
35
|
-
return require('../package.json').version;
|
|
36
|
-
}
|
|
37
35
|
|
|
38
36
|
const toCsn = lazyload('./json/to-csn')
|
|
39
37
|
|
|
@@ -75,7 +73,7 @@ function parseExpr( cdlSource, filename = '<expr>.cds', options = {} ) {
|
|
|
75
73
|
// ATTENTION: Keep in sync with main.d.ts!
|
|
76
74
|
module.exports = {
|
|
77
75
|
// Compiler
|
|
78
|
-
version,
|
|
76
|
+
version: () => meta.version(),
|
|
79
77
|
compile: (filenames, dir, options, fileCache) => { // main function
|
|
80
78
|
traceApi( 'compile', options );
|
|
81
79
|
return compiler.compileX(filenames, dir, options, fileCache).then(toCsn.compactModel);
|
|
@@ -94,7 +92,7 @@ module.exports = {
|
|
|
94
92
|
value: messages.CompilationError,
|
|
95
93
|
writable: false,
|
|
96
94
|
configurable: false,
|
|
97
|
-
enumerable:
|
|
95
|
+
enumerable: true
|
|
98
96
|
});
|
|
99
97
|
return messages.CompilationError;
|
|
100
98
|
},
|
|
@@ -183,7 +181,9 @@ module.exports = {
|
|
|
183
181
|
$lsp: {
|
|
184
182
|
parse: (...args) => compiler.parseX(...args),
|
|
185
183
|
compile: (...args) => compiler.compileX(...args),
|
|
186
|
-
getArtifactName: (
|
|
184
|
+
getArtifactName: (art) => base.getArtifactName(art),
|
|
185
|
+
traverseSemanticTokens: (xsn, options) => lsp.traverseSemanticTokens(xsn, options),
|
|
186
|
+
getSemanticTokenOrigin: (obj) => lsp.getSemanticTokenOrigin(obj),
|
|
187
187
|
},
|
|
188
188
|
|
|
189
189
|
// CSN Model related functionality
|
|
@@ -201,7 +201,7 @@ module.exports = {
|
|
|
201
201
|
function lazyload(moduleName) {
|
|
202
202
|
let module;
|
|
203
203
|
return new Proxy(((...args) => {
|
|
204
|
-
if (!module)
|
|
204
|
+
if (!module)
|
|
205
205
|
module = require(moduleName);
|
|
206
206
|
|
|
207
207
|
if (module.apply && typeof module.apply === 'function')
|
|
@@ -209,7 +209,7 @@ function lazyload(moduleName) {
|
|
|
209
209
|
return module; // for destructured calls
|
|
210
210
|
}), {
|
|
211
211
|
get(target, name) {
|
|
212
|
-
if (!module)
|
|
212
|
+
if (!module)
|
|
213
213
|
module = require(moduleName);
|
|
214
214
|
|
|
215
215
|
return module[name];
|
package/lib/model/cloneCsn.js
CHANGED
|
@@ -30,6 +30,7 @@ const internalCsnProps = {
|
|
|
30
30
|
$tableConstraints: shallowCopy,
|
|
31
31
|
$default: shallowCopy, // used for HANA CSN migrations
|
|
32
32
|
$notNull: shallowCopy, // used for HANA CSN migrations
|
|
33
|
+
$sqlService: shallowCopy,
|
|
33
34
|
};
|
|
34
35
|
const internalEnumerableCsnProps = {
|
|
35
36
|
__proto__: null,
|
package/lib/model/csnRefs.js
CHANGED
|
@@ -448,7 +448,7 @@ function csnRefs( csn, universalReady ) {
|
|
|
448
448
|
if (!step)
|
|
449
449
|
return null;
|
|
450
450
|
if (!effectiveType( art ))
|
|
451
|
-
throw new
|
|
451
|
+
throw new ModelError( 'Cyclic type definition' );
|
|
452
452
|
if (typeof step === 'string')
|
|
453
453
|
return navigationEnv( art, true ).elements[step];
|
|
454
454
|
|
|
@@ -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];
|
|
@@ -1308,6 +1324,7 @@ module.exports = {
|
|
|
1308
1324
|
traverseQuery,
|
|
1309
1325
|
artifactProperties,
|
|
1310
1326
|
implicitAs,
|
|
1327
|
+
getKeysDict,
|
|
1311
1328
|
analyseCsnPath,
|
|
1312
1329
|
pathId,
|
|
1313
1330
|
columnAlias,
|
package/lib/model/csnUtils.js
CHANGED
|
@@ -12,7 +12,6 @@ const { isBuiltinType, isAnnotationExpression } = require('../base/builtins');
|
|
|
12
12
|
const { ModelError, CompilerAssertion } = require('../base/error');
|
|
13
13
|
const { typeParameters } = require('../compiler/builtins');
|
|
14
14
|
const { forEach } = require('../utils/objectUtils');
|
|
15
|
-
const { version } = require('../../package.json');
|
|
16
15
|
const { cloneAnnotationValue } = require('./cloneCsn');
|
|
17
16
|
|
|
18
17
|
// Low-level utility functions to work with compact CSN.
|
|
@@ -706,7 +705,6 @@ function isEdmPropertyRendered( elementCsn, options ) {
|
|
|
706
705
|
* @param {('sqlite'|'hana'|'plain'|string)} [sqlDialect='plain'] The SQL dialect to use
|
|
707
706
|
* @returns {string} The resulting database name for (absolute) 'artifactName', depending on the current naming mode.
|
|
708
707
|
*/
|
|
709
|
-
// eslint-disable-next-line no-unused-vars
|
|
710
708
|
function getArtifactDatabaseNameOf( artifactName, sqlMapping, csn, sqlDialect = 'plain' ) {
|
|
711
709
|
if (csn && typeof csn === 'object' && csn.definitions) {
|
|
712
710
|
isValidMappingDialectCombi(sqlDialect, sqlMapping);
|
|
@@ -826,7 +824,6 @@ function isValidMappingDialectCombi( sqlDialect, sqlMapping ) {
|
|
|
826
824
|
* @param {('sqlite'|'hana'|'plain'|string)} [sqlDialect='plain'] The SQL dialect to use
|
|
827
825
|
* @returns {string} The resulting database element name for 'elemName', depending on the current naming mode.
|
|
828
826
|
*/
|
|
829
|
-
// eslint-disable-next-line no-unused-vars
|
|
830
827
|
function getElementDatabaseNameOf( elemName, sqlMapping, sqlDialect = 'plain' ) {
|
|
831
828
|
isValidMappingDialectCombi(sqlDialect, sqlMapping);
|
|
832
829
|
if (sqlMapping === 'hdbcds')
|
|
@@ -948,16 +945,6 @@ function isPersistedAsTable( artifact ) {
|
|
|
948
945
|
!hasAnnotationValue(artifact, '@cds.persistence.exists');
|
|
949
946
|
}
|
|
950
947
|
|
|
951
|
-
/**
|
|
952
|
-
* Central generated by cds-compiler string generator function without further decoration
|
|
953
|
-
* for unified tagging of generated content
|
|
954
|
-
*
|
|
955
|
-
* @returns {string} String containing compiler version that was used to generate content
|
|
956
|
-
*/
|
|
957
|
-
function generatedByCompilerVersion() {
|
|
958
|
-
return `generated by cds-compiler version ${ version }`;
|
|
959
|
-
}
|
|
960
|
-
|
|
961
948
|
/**
|
|
962
949
|
* Return the projection to look like a query.
|
|
963
950
|
*
|
|
@@ -1440,7 +1427,6 @@ module.exports = {
|
|
|
1440
1427
|
isPersistedOnDatabase,
|
|
1441
1428
|
isPersistedAsView,
|
|
1442
1429
|
isPersistedAsTable,
|
|
1443
|
-
generatedByCompilerVersion,
|
|
1444
1430
|
getNormalizedQuery,
|
|
1445
1431
|
getRootArtifactName,
|
|
1446
1432
|
getLastPartOfRef,
|
|
@@ -73,7 +73,6 @@ function revealInternalProperties( model, nameOrPath ) {
|
|
|
73
73
|
artifacts: artifactDictionary,
|
|
74
74
|
definitions: artifactDictionary,
|
|
75
75
|
vocabularies: dictionary,
|
|
76
|
-
$lateExtensions: dictionary,
|
|
77
76
|
elements,
|
|
78
77
|
columns,
|
|
79
78
|
expand: columns,
|
|
@@ -385,7 +384,7 @@ function quoted( name, undef = '‹undefined›' ) {
|
|
|
385
384
|
// To be used for tracing, e.g. by
|
|
386
385
|
// require('../model/revealInternalProperties').log(model, 'E_purposes')
|
|
387
386
|
function logXsnModel( model, name ) {
|
|
388
|
-
// eslint-disable-next-line no-console
|
|
387
|
+
// eslint-disable-next-line no-console
|
|
389
388
|
console.log( require('util').inspect( revealInternalProperties( model, name ), false, null ) );
|
|
390
389
|
}
|
|
391
390
|
|
|
@@ -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?');
|
|
@@ -257,17 +257,19 @@ function getExtensionAndMigrations(beforeModel, options, {
|
|
|
257
257
|
function getDeletions(afterModel, options, { deletions }) {
|
|
258
258
|
return function compareArtifacts(artifact, name) {
|
|
259
259
|
const otherArtifact = afterModel.definitions[name];
|
|
260
|
-
const
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
260
|
+
const isPersistedTable = isPersistedAsTable(artifact);
|
|
261
|
+
const isPersistedView = isPersistedAsView(artifact);
|
|
262
|
+
const isPersistedTableOther = otherArtifact && isPersistedAsTable(otherArtifact);
|
|
263
|
+
const isPersistedViewOther = otherArtifact && isPersistedAsView(otherArtifact);
|
|
264
|
+
|
|
265
|
+
// Looking for deleted entities or table -> view / view -> table
|
|
266
|
+
if (
|
|
267
|
+
(isPersistedTable && isPersistedViewOther) || // table -> view
|
|
268
|
+
(isPersistedView && isPersistedTableOther) || // view -> table
|
|
269
|
+
((isPersistedTable || isPersistedView) && // deleted
|
|
270
|
+
!(isPersistedTableOther || isPersistedViewOther))
|
|
271
|
+
) // view turned into table - need to render a drop for the view
|
|
265
272
|
deletions[name] = artifact;
|
|
266
|
-
}
|
|
267
|
-
// eslint-disable-next-line sonarjs/no-duplicated-branches
|
|
268
|
-
else if (isPersistedAsView(artifact) && isPersistedOther) { // view turned into table - need to render a drop for the view
|
|
269
|
-
deletions[name] = artifact;
|
|
270
|
-
}
|
|
271
273
|
};
|
|
272
274
|
}
|
|
273
275
|
|