@sap/cds-compiler 4.9.2 → 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 +74 -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 +7 -19
- package/lib/api/options.js +5 -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/constraints.js +23 -25
- 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/compiler/extend.js
CHANGED
|
@@ -8,8 +8,8 @@ const {
|
|
|
8
8
|
forEachInOrder,
|
|
9
9
|
forEachDefinition,
|
|
10
10
|
forEachMember,
|
|
11
|
+
forEachGeneric,
|
|
11
12
|
isDeprecatedEnabled,
|
|
12
|
-
isBetaEnabled,
|
|
13
13
|
} = require('../base/model');
|
|
14
14
|
const { dictAdd, pushToDict } = require('../base/dictionaries');
|
|
15
15
|
const { kindProperties, dictKinds } = require('./base');
|
|
@@ -30,7 +30,7 @@ const { Location } = require('../base/location');
|
|
|
30
30
|
|
|
31
31
|
const $location = Symbol.for( 'cds.$location' );
|
|
32
32
|
|
|
33
|
-
// attach stupid location - TODO: remove in
|
|
33
|
+
// attach stupid location - TODO: remove in v6
|
|
34
34
|
const genLocation = new Location( '' );
|
|
35
35
|
|
|
36
36
|
const draftElements = [
|
|
@@ -59,6 +59,7 @@ function extend( model ) {
|
|
|
59
59
|
resolvePath,
|
|
60
60
|
resolveUncheckedPath,
|
|
61
61
|
resolveTypeArgumentsUnchecked,
|
|
62
|
+
resolveDefinitionName,
|
|
62
63
|
attachAndEmitValidNames,
|
|
63
64
|
initMembers,
|
|
64
65
|
initSelectItems,
|
|
@@ -72,7 +73,6 @@ function extend( model ) {
|
|
|
72
73
|
} );
|
|
73
74
|
|
|
74
75
|
const includesNonShadowedFirst = isDeprecatedEnabled( model.options, 'includesNonShadowedFirst' );
|
|
75
|
-
const isV5preview = isBetaEnabled( model.options, 'v5preview' );
|
|
76
76
|
|
|
77
77
|
sortModelSources();
|
|
78
78
|
const extensionsDict = Object.create( null ); // TODO TMP
|
|
@@ -161,6 +161,7 @@ function extend( model ) {
|
|
|
161
161
|
moveReturnsExtensions( art, extensionsMap );
|
|
162
162
|
|
|
163
163
|
if (art.returns) {
|
|
164
|
+
ensureArtifactNotProcessed( art.returns );
|
|
164
165
|
pushToDict( art.returns, '_extensions', ...extensionsMap.elements || [] );
|
|
165
166
|
pushToDict( art.returns, '_extensions', ...extensionsMap.enum || [] );
|
|
166
167
|
if (art.kind !== 'annotate') {
|
|
@@ -171,16 +172,35 @@ function extend( model ) {
|
|
|
171
172
|
}
|
|
172
173
|
const sub = art.items || art.targetAspect?.elements && art.targetAspect;
|
|
173
174
|
if (sub) {
|
|
175
|
+
ensureArtifactNotProcessed( sub );
|
|
174
176
|
pushToDict( sub, '_extensions', ...extensionsMap.elements || [] );
|
|
175
177
|
pushToDict( sub, '_extensions', ...extensionsMap.enum || [] );
|
|
176
178
|
}
|
|
177
179
|
else {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
+
let elementsProp = 'elements';
|
|
181
|
+
if (art.kind !== 'annotate')
|
|
182
|
+
elementsProp = art.enum && 'enum' || art.foreignKeys && 'foreignKeys' || 'elements';
|
|
183
|
+
moveDictExtensions( art, extensionsMap, elementsProp, 'elements' );
|
|
180
184
|
moveDictExtensions( art, extensionsMap, 'enum' );
|
|
181
185
|
}
|
|
182
186
|
}
|
|
183
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Applying extensions is handled in extendArtifactAfter(). And only afterward,
|
|
190
|
+
* an effective sequence number is set. Meaning that if a sub-artifact already
|
|
191
|
+
* has a sequence number, then extensions would be lost.
|
|
192
|
+
*/
|
|
193
|
+
function ensureArtifactNotProcessed( art ) {
|
|
194
|
+
if (!model.options.testMode)
|
|
195
|
+
return;
|
|
196
|
+
|
|
197
|
+
if (art.$effectiveSeqNo !== 0 && art.$effectiveSeqNo !== undefined) {
|
|
198
|
+
// if the artifact already has a sequence number, then
|
|
199
|
+
// extendArtifactAfter() was already called -> annotations would be lost.
|
|
200
|
+
throw new CompilerAssertion('artifact already processed; extensions would be lost');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
184
204
|
/**
|
|
185
205
|
* Create super annotate statements for remaining extensions
|
|
186
206
|
*/
|
|
@@ -196,15 +216,25 @@ function extend( model ) {
|
|
|
196
216
|
}
|
|
197
217
|
|
|
198
218
|
// TODO: delete again - if not, what about extensions in contexts/services?
|
|
219
|
+
// Check test.lsp-api.js! Links in extensions are needed.
|
|
199
220
|
function setArtifactLinkForExtensions( source ) {
|
|
200
221
|
if (!source.extensions)
|
|
201
222
|
return;
|
|
202
|
-
for (const ext of source.extensions
|
|
223
|
+
for (const ext of source.extensions) {
|
|
224
|
+
if (!ext.name?.id)
|
|
225
|
+
continue;
|
|
226
|
+
|
|
203
227
|
const { name } = ext;
|
|
204
|
-
|
|
228
|
+
const { path } = name;
|
|
229
|
+
if (name._artifact === undefined) {
|
|
205
230
|
const refCtx = (name.id.startsWith( 'localized.' )) ? '_extensions' : ext.kind;
|
|
206
231
|
resolvePath( name, refCtx, ext ); // for LSP
|
|
207
232
|
}
|
|
233
|
+
else if (model.options.lspMode && path?.[0]._artifact === undefined) {
|
|
234
|
+
// we don't use resolvePath(…,'extend'), as that would add a dependency
|
|
235
|
+
resolveDefinitionName( ext );
|
|
236
|
+
setArtifactLink( path[path.length - 1], name._artifact );
|
|
237
|
+
}
|
|
208
238
|
}
|
|
209
239
|
}
|
|
210
240
|
|
|
@@ -224,7 +254,7 @@ function extend( model ) {
|
|
|
224
254
|
code: 'extend … with definitions',
|
|
225
255
|
keyword: 'extend service',
|
|
226
256
|
};
|
|
227
|
-
// TODO(
|
|
257
|
+
// TODO(v6): Discuss: make this an error?
|
|
228
258
|
warning( 'ext-invalid-kind', [ loc, ext ], msgArgs, {
|
|
229
259
|
std: 'Artifact $(ART) is not of kind $(KIND), use $(CODE) instead',
|
|
230
260
|
annotate: 'There is no artifact $(ART), use $(CODE) instead',
|
|
@@ -393,6 +423,9 @@ function extend( model ) {
|
|
|
393
423
|
for (const col of ext.columns)
|
|
394
424
|
col.$extended = true;
|
|
395
425
|
|
|
426
|
+
if (art.kind === 'annotate' && art.$inferred === '')
|
|
427
|
+
return; // internal super-annotate for unknown artifacts
|
|
428
|
+
|
|
396
429
|
if (!query?.from?.path) {
|
|
397
430
|
const variant = (query?.from || query)?.op?.val || 'std';
|
|
398
431
|
error( 'extend-columns', [ ext.columns[$location], ext ], { '#': variant, art } );
|
|
@@ -490,7 +523,7 @@ function extend( model ) {
|
|
|
490
523
|
if ('val' in upToSpec) {
|
|
491
524
|
if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
|
|
492
525
|
return true;
|
|
493
|
-
// TODO
|
|
526
|
+
// TODO v6: delete the special UP TO comparison?
|
|
494
527
|
const upToVal = upToSpec.val;
|
|
495
528
|
const prevVal = previousItem.val;
|
|
496
529
|
// eslint-disable-next-line eqeqeq
|
|
@@ -540,7 +573,7 @@ function extend( model ) {
|
|
|
540
573
|
// - 'ext-unexpected-type-argument' (TODO) if the artifact does not have the prop
|
|
541
574
|
// - 'ext-invalid-type-argument' if the value is wrong for extend (no overwrite)
|
|
542
575
|
//
|
|
543
|
-
// TODO
|
|
576
|
+
// TODO v6: do not allow `extend … with (precision: …)` alone if original def also has `scale`
|
|
544
577
|
function applyTypeExtensions( art, ext, prop, scaleDiff ) {
|
|
545
578
|
// console.log('ATE:',art?.[prop],ext?.[prop],scaleDiff)
|
|
546
579
|
if (!ext?.[prop])
|
|
@@ -617,17 +650,17 @@ function extend( model ) {
|
|
|
617
650
|
const artDict = art[artProp] || annotateFor( art, extProp ); // no auto-correction in annotate
|
|
618
651
|
|
|
619
652
|
for (const ext of extensions) {
|
|
620
|
-
const extDict = ext[extProp];
|
|
621
653
|
let dictCheck = (art.kind !== 'annotate'); // no check in super annotate statement
|
|
622
|
-
|
|
623
|
-
const elemExt = extDict[name];
|
|
654
|
+
forEachGeneric(ext, extProp, (elemExt, name) => {
|
|
624
655
|
if (elemExt.kind !== 'annotate' && elemExt.kind !== 'extend') // TODO: specified elems
|
|
625
|
-
|
|
656
|
+
return; // definitions inside extend, already handled
|
|
626
657
|
dictCheck = dictCheck && checkRemainingMemberExtensions( art, elemExt, artProp, name );
|
|
627
658
|
const elem = artDict[name] || annotateFor( art, extProp, name );
|
|
628
659
|
setLink( elemExt.name, '_artifact', (elem.kind !== 'annotate' ? elem : null ) );
|
|
629
|
-
|
|
630
|
-
|
|
660
|
+
ensureArtifactNotProcessed( elem );
|
|
661
|
+
if (elem.$duplicates !== true)
|
|
662
|
+
pushToDict( elem, '_extensions', elemExt );
|
|
663
|
+
});
|
|
631
664
|
}
|
|
632
665
|
}
|
|
633
666
|
|
|
@@ -778,6 +811,10 @@ function extend( model ) {
|
|
|
778
811
|
{ '#': (inReturns ? 'enum-returns' : 'enum'), art, name },
|
|
779
812
|
parent.enum );
|
|
780
813
|
break;
|
|
814
|
+
case 'foreignKeys':
|
|
815
|
+
notFound( 'ext-undefined-key', ext.name.location, ext,
|
|
816
|
+
{ name }, parent.foreignKeys );
|
|
817
|
+
break;
|
|
781
818
|
case 'params':
|
|
782
819
|
notFound( 'ext-undefined-param', ext.name.location, ext,
|
|
783
820
|
{ '#': 'param', art: parent, name },
|
|
@@ -791,7 +828,8 @@ function extend( model ) {
|
|
|
791
828
|
parent.actions );
|
|
792
829
|
break;
|
|
793
830
|
default:
|
|
794
|
-
|
|
831
|
+
if (model.options.testMode)
|
|
832
|
+
throw new CompilerAssertion(`Missing case for prop: ${ prop }`);
|
|
795
833
|
}
|
|
796
834
|
}
|
|
797
835
|
return true;
|
|
@@ -832,8 +870,9 @@ function extend( model ) {
|
|
|
832
870
|
}
|
|
833
871
|
|
|
834
872
|
function checkRemainingMainExtensions( art, ext, localized ) {
|
|
873
|
+
const isExtend = ext.kind === 'extend';
|
|
835
874
|
if (localized) {
|
|
836
|
-
if (
|
|
875
|
+
if (isExtend) {
|
|
837
876
|
// In v5, reject any `extend` on localized.
|
|
838
877
|
error( 'ref-undefined-art', [ ext.location || ext.name.location, ext ],
|
|
839
878
|
{ '#': 'localized', keyword: 'annotate' } );
|
|
@@ -847,22 +886,21 @@ function extend( model ) {
|
|
|
847
886
|
if (art?.builtin) {
|
|
848
887
|
info( 'anno-builtin', [ ext.name.location, ext ], {} ); // TODO: better location?
|
|
849
888
|
}
|
|
850
|
-
else if (art?.kind === 'namespace') {
|
|
889
|
+
else if (isExtend && art?.kind === 'namespace') {
|
|
890
|
+
// `annotate` on namespaces already handled before
|
|
851
891
|
const hasAnnotations = Object.keys(ext).find(a => a.charAt(0) === '@');
|
|
852
892
|
const firstAnno = ext[hasAnnotations];
|
|
853
893
|
// In v5, extending namespaces is only allowed for `extend with definitions`.
|
|
854
894
|
// Neither annotations nor other extensions are allowed.
|
|
855
895
|
// Non-artifact extensions are reported in resolvePath() already (for v5).
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
'Namespaces can\'t be annotated nor extended' );
|
|
865
|
-
}
|
|
896
|
+
// Because "namespaces" are the same as "unknown" artifacts in CSN, we don't report
|
|
897
|
+
// an error for `annotate`s.
|
|
898
|
+
// FIXME: The compiler generates empty `annotate` statements for
|
|
899
|
+
// `extend ns with definitions {…}`. That's why we check the frontend.
|
|
900
|
+
if (hasAnnotations || (!ext.artifacts && ext._block.$frontend !== 'json')) {
|
|
901
|
+
error( 'ref-undefined-art', [ (firstAnno?.name || ext.name).location, ext ], {
|
|
902
|
+
'#': 'namespace', art: ext,
|
|
903
|
+
} );
|
|
866
904
|
}
|
|
867
905
|
}
|
|
868
906
|
}
|
|
@@ -1101,7 +1139,6 @@ function extend( model ) {
|
|
|
1101
1139
|
{ art: artName },
|
|
1102
1140
|
{
|
|
1103
1141
|
std: 'Unknown $(ART) - nothing to extend',
|
|
1104
|
-
// eslint-disable-next-line max-len
|
|
1105
1142
|
element: 'Artifact $(ART) has no element or enum $(MEMBER) - nothing to extend',
|
|
1106
1143
|
action: 'Artifact $(ART) has no action $(MEMBER) - nothing to extend',
|
|
1107
1144
|
} );
|
|
@@ -1232,7 +1269,7 @@ function extend( model ) {
|
|
|
1232
1269
|
if (!parent) // not yet set for EXTEND foo WITH bar => linkToOrigin() did not add it
|
|
1233
1270
|
dictAdd( ext[prop], name, elem );
|
|
1234
1271
|
elem.$inferred = 'include';
|
|
1235
|
-
if (origin.masked) // TODO(
|
|
1272
|
+
if (origin.masked) // TODO(v6): remove 'masked'
|
|
1236
1273
|
elem.masked = Object.assign( { $inferred: 'include' }, origin.masked );
|
|
1237
1274
|
if (origin.key)
|
|
1238
1275
|
elem.key = Object.assign( { $inferred: 'include' }, origin.key );
|
|
@@ -1269,8 +1306,6 @@ function extend( model ) {
|
|
|
1269
1306
|
/**
|
|
1270
1307
|
* Report duplicates in parent[prop] that happen due to multiple includes having the
|
|
1271
1308
|
* same member. Covers `entity G : E, G {};` but not `entity G : E {}; extend G with F;`.
|
|
1272
|
-
*
|
|
1273
|
-
* TODO(v5): Make this a hard error; see checkRedefinition(); maybe combine both;
|
|
1274
1309
|
*/
|
|
1275
1310
|
function checkRedefinitionThroughIncludes( parent, prop ) {
|
|
1276
1311
|
if (!parent[prop])
|
package/lib/compiler/generate.js
CHANGED
|
@@ -656,7 +656,7 @@ function generate( model ) {
|
|
|
656
656
|
|
|
657
657
|
if (elem.type && !isDirectComposition( elem )) {
|
|
658
658
|
// Only issue warning for direct usages, not for projections, includes, etc.
|
|
659
|
-
// TODO: Make it configurable error;
|
|
659
|
+
// TODO: Make it configurable error; v6: error
|
|
660
660
|
// TODO: move to resolve.js where we test the targetAspect,
|
|
661
661
|
warning( 'type-expecting-composition', [ elem.type.location, elem ],
|
|
662
662
|
{ newcode: 'Composition of', code: 'Association to' },
|
package/lib/compiler/index.js
CHANGED
|
@@ -83,11 +83,7 @@ function parseX( source, filename, options = {}, messageFunctions = null ) {
|
|
|
83
83
|
if (!messageFunctions)
|
|
84
84
|
messageFunctions = createMessageFunctions( options, 'parse' );
|
|
85
85
|
const ext = path.extname( filename ).slice(1).toLowerCase();
|
|
86
|
-
|
|
87
|
-
const parser = options.fallbackParser === 'auto!'
|
|
88
|
-
? (source?.startsWith( '{' ) ? parseCsn.parse : parseLanguage)
|
|
89
|
-
: (extensionParsers[ext] || extensionParsers[options.fallbackParser] ||
|
|
90
|
-
source.startsWith( '{' ) && parseCsn.parse);
|
|
86
|
+
const parser = parserForFile( source, ext, options );
|
|
91
87
|
if (parser)
|
|
92
88
|
return parser( source, filename, options, messageFunctions );
|
|
93
89
|
|
|
@@ -101,6 +97,27 @@ function parseX( source, filename, options = {}, messageFunctions = null ) {
|
|
|
101
97
|
return model;
|
|
102
98
|
}
|
|
103
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Get the correct parser for the given source / file extension.
|
|
102
|
+
* Respects the set fallback parser.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} source
|
|
105
|
+
* @param {string} ext
|
|
106
|
+
* @param {object} options
|
|
107
|
+
*/
|
|
108
|
+
function parserForFile( source, ext, options ) {
|
|
109
|
+
// 'auto!' ignores the file's extension
|
|
110
|
+
if (options.fallbackParser === 'auto!')
|
|
111
|
+
return (source?.startsWith( '{' ) ? parseCsn.parse : parseLanguage);
|
|
112
|
+
|
|
113
|
+
if (options.fallbackParser === 'csn!')
|
|
114
|
+
return parseCsn.parse;
|
|
115
|
+
|
|
116
|
+
return extensionParsers[ext] ||
|
|
117
|
+
extensionParsers[options.fallbackParser] ||
|
|
118
|
+
(source.startsWith( '{' ) && parseCsn.parse);
|
|
119
|
+
}
|
|
120
|
+
|
|
104
121
|
// Main function: Compile the sources from the files given by the array of
|
|
105
122
|
// `filenames`. As usual with the `fs` library, relative file names are
|
|
106
123
|
// relative to the working directory `process.cwd()`. With argument `dir`, the
|
|
@@ -530,7 +547,7 @@ function processFilenamesSync( filenames, dir ) {
|
|
|
530
547
|
// already handles non-existent files.
|
|
531
548
|
name = fs.realpathSync.native( name );
|
|
532
549
|
}
|
|
533
|
-
catch
|
|
550
|
+
catch {
|
|
534
551
|
// Ignore the not-found (ENOENT) error
|
|
535
552
|
}
|
|
536
553
|
filenameMap[originalName] = name;
|