@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/compiler/define.js
CHANGED
|
@@ -216,7 +216,7 @@ function define( model ) {
|
|
|
216
216
|
|
|
217
217
|
const { $self } = model.definitions;
|
|
218
218
|
if ($self) {
|
|
219
|
-
message( 'name-deprecated-$self', [ $self.location, $self ], { name: '$self' },
|
|
219
|
+
message( 'name-deprecated-$self', [ $self.name.location, $self ], { name: '$self' },
|
|
220
220
|
'Do not use $(NAME) as name for an artifact definition' );
|
|
221
221
|
}
|
|
222
222
|
}
|
|
@@ -364,7 +364,7 @@ function define( model ) {
|
|
|
364
364
|
// must be called after addUsing().
|
|
365
365
|
function addNamespace( namespace, src ) {
|
|
366
366
|
// create using for own namespace:
|
|
367
|
-
// TODO: should we really do that in
|
|
367
|
+
// TODO: should we really do that (in v6)? See also initNamespaceAndUsing().
|
|
368
368
|
const last = namespace.path[namespace.path.length - 1];
|
|
369
369
|
const { id } = last;
|
|
370
370
|
if (src.artifacts[id] || last.id.includes( '.' ))
|
|
@@ -507,9 +507,10 @@ function define( model ) {
|
|
|
507
507
|
// TODO: message ids
|
|
508
508
|
function checkRedefinition( art ) {
|
|
509
509
|
if (!art.$duplicates || !art.name.id ||
|
|
510
|
-
art.$errorReported === 'syntax-duplicate-extend'
|
|
511
|
-
art.$errorReported === 'syntax-duplicate-annotate')
|
|
510
|
+
art.$errorReported === 'syntax-duplicate-extend')
|
|
512
511
|
return;
|
|
512
|
+
if (art.kind === 'annotate' || art.kind === 'extend')
|
|
513
|
+
return; // extensions are merged into a super-annotate; $duplicates are only kept for LSP
|
|
513
514
|
if (art._main) {
|
|
514
515
|
error( 'duplicate-definition', [ art.name.location, art ], {
|
|
515
516
|
name: art.name.id,
|
|
@@ -637,7 +638,6 @@ function define( model ) {
|
|
|
637
638
|
}
|
|
638
639
|
|
|
639
640
|
function initDollarParameters( art ) {
|
|
640
|
-
// TODO: remove $parameters in v5?
|
|
641
641
|
// TODO: use setMemberParent() ?
|
|
642
642
|
const parameters = {
|
|
643
643
|
name: { id: '$parameters' },
|
|
@@ -939,14 +939,14 @@ function define( model ) {
|
|
|
939
939
|
setLink( col, '_block', parent._block );
|
|
940
940
|
if (col.inline) { // `@anno elem.{ * }` does not work
|
|
941
941
|
if (col.doc) {
|
|
942
|
-
message( 'syntax-
|
|
942
|
+
message( 'syntax-unexpected-anno', [ col.doc.location, col ],
|
|
943
943
|
{ '#': 'doc', code: '.{ ‹inline› }' } );
|
|
944
944
|
}
|
|
945
945
|
// col.$annotations no available for CSN input, have to search.
|
|
946
946
|
// Message about first annotation should be enough to avoid spam.
|
|
947
947
|
const firstAnno = Object.keys( col ).find( key => key.startsWith( '@' ) );
|
|
948
948
|
if (firstAnno) {
|
|
949
|
-
message( 'syntax-
|
|
949
|
+
message( 'syntax-unexpected-anno', [ col[firstAnno].name.location, col ],
|
|
950
950
|
{ code: '.{ ‹inline› }' } );
|
|
951
951
|
}
|
|
952
952
|
}
|
|
@@ -1083,7 +1083,7 @@ function define( model ) {
|
|
|
1083
1083
|
hasElement = true; // warning with CDL input or `name: {}` in CSN input
|
|
1084
1084
|
}
|
|
1085
1085
|
if (hasElement) {
|
|
1086
|
-
// This message is similar to the one above. In
|
|
1086
|
+
// This message is similar to the one above. In v6, we could probably
|
|
1087
1087
|
// turn this warning into an error, remove `$syntax: 'element' (also in
|
|
1088
1088
|
// language.g4), and use the above `ext-unexpected-element` only for CSN input.
|
|
1089
1089
|
warning( 'ext-expecting-enum', [ obj.elements[$location], construct ],
|
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');
|
|
@@ -27,10 +27,11 @@ const {
|
|
|
27
27
|
const layers = require('./moduleLayers');
|
|
28
28
|
const { CompilerAssertion } = require('../base/error');
|
|
29
29
|
const { Location } = require('../base/location');
|
|
30
|
+
const { typeParameters } = require('./builtins');
|
|
30
31
|
|
|
31
32
|
const $location = Symbol.for( 'cds.$location' );
|
|
32
33
|
|
|
33
|
-
// attach stupid location - TODO: remove in
|
|
34
|
+
// attach stupid location - TODO: remove in v6
|
|
34
35
|
const genLocation = new Location( '' );
|
|
35
36
|
|
|
36
37
|
const draftElements = [
|
|
@@ -59,6 +60,7 @@ function extend( model ) {
|
|
|
59
60
|
resolvePath,
|
|
60
61
|
resolveUncheckedPath,
|
|
61
62
|
resolveTypeArgumentsUnchecked,
|
|
63
|
+
resolveDefinitionName,
|
|
62
64
|
attachAndEmitValidNames,
|
|
63
65
|
initMembers,
|
|
64
66
|
initSelectItems,
|
|
@@ -68,11 +70,11 @@ function extend( model ) {
|
|
|
68
70
|
createRemainingAnnotateStatements,
|
|
69
71
|
extendArtifactBefore,
|
|
70
72
|
extendArtifactAfter,
|
|
73
|
+
extendForeignKeys,
|
|
71
74
|
applyIncludes, // TODO: re-check
|
|
72
75
|
} );
|
|
73
76
|
|
|
74
77
|
const includesNonShadowedFirst = isDeprecatedEnabled( model.options, 'includesNonShadowedFirst' );
|
|
75
|
-
const isV5preview = isBetaEnabled( model.options, 'v5preview' );
|
|
76
78
|
|
|
77
79
|
sortModelSources();
|
|
78
80
|
const extensionsDict = Object.create( null ); // TODO TMP
|
|
@@ -91,7 +93,8 @@ function extend( model ) {
|
|
|
91
93
|
//-----------------------------------------------------------------------------
|
|
92
94
|
// Extensions: general algorithm
|
|
93
95
|
//-----------------------------------------------------------------------------
|
|
94
|
-
// extendArtifactBefore, extendArtifactAfter, createRemainingAnnotateStatements
|
|
96
|
+
// extendArtifactBefore, extendArtifactAfter, createRemainingAnnotateStatements,
|
|
97
|
+
// extendForeignKeys
|
|
95
98
|
|
|
96
99
|
/**
|
|
97
100
|
* Goes through all (applied) annotations in the given artifact and chooses one
|
|
@@ -161,6 +164,7 @@ function extend( model ) {
|
|
|
161
164
|
moveReturnsExtensions( art, extensionsMap );
|
|
162
165
|
|
|
163
166
|
if (art.returns) {
|
|
167
|
+
ensureArtifactNotProcessed( art.returns );
|
|
164
168
|
pushToDict( art.returns, '_extensions', ...extensionsMap.elements || [] );
|
|
165
169
|
pushToDict( art.returns, '_extensions', ...extensionsMap.enum || [] );
|
|
166
170
|
if (art.kind !== 'annotate') {
|
|
@@ -171,16 +175,66 @@ function extend( model ) {
|
|
|
171
175
|
}
|
|
172
176
|
const sub = art.items || art.targetAspect?.elements && art.targetAspect;
|
|
173
177
|
if (sub) {
|
|
178
|
+
ensureArtifactNotProcessed( sub );
|
|
174
179
|
pushToDict( sub, '_extensions', ...extensionsMap.elements || [] );
|
|
175
180
|
pushToDict( sub, '_extensions', ...extensionsMap.enum || [] );
|
|
176
181
|
}
|
|
177
182
|
else {
|
|
178
|
-
|
|
179
|
-
|
|
183
|
+
let elementsProp = 'elements';
|
|
184
|
+
if (art.kind !== 'annotate')
|
|
185
|
+
elementsProp = art.enum && 'enum' || art.target && 'foreignKeys' || 'elements';
|
|
186
|
+
|
|
187
|
+
// keys are handled in tweak-assocs.js; don't push them down; see extendForeignKeys()
|
|
188
|
+
if (elementsProp !== 'foreignKeys')
|
|
189
|
+
moveDictExtensions( art, extensionsMap, elementsProp, 'elements' );
|
|
180
190
|
moveDictExtensions( art, extensionsMap, 'enum' );
|
|
181
191
|
}
|
|
182
192
|
}
|
|
183
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Apply foreign key extensions. Because foreign keys are handled late in the compiler
|
|
196
|
+
* (in tweak-assocs.js), we can't apply them in effectiveType(), yet.
|
|
197
|
+
* Instead, we postpone applying them until all foreign keys were generated.
|
|
198
|
+
*
|
|
199
|
+
* @param art
|
|
200
|
+
*/
|
|
201
|
+
function extendForeignKeys( art ) {
|
|
202
|
+
// See extendArtifactAfter() for targetAspect/items handling.
|
|
203
|
+
const sub = art.items || art.targetAspect?.elements && art.targetAspect;
|
|
204
|
+
if (!art._extensions || sub)
|
|
205
|
+
return;
|
|
206
|
+
|
|
207
|
+
// push down foreign keys
|
|
208
|
+
moveDictExtensions( art, art._extensions, 'foreignKeys', 'elements' );
|
|
209
|
+
if (!art.foreignKeys)
|
|
210
|
+
return;
|
|
211
|
+
|
|
212
|
+
forEachGeneric(art, 'foreignKeys', (key) => {
|
|
213
|
+
if (!key._effectiveType)
|
|
214
|
+
throw new CompilerAssertion('foreign key should have been processed');
|
|
215
|
+
extendArtifactBefore( key );
|
|
216
|
+
extendArtifactAfter( key );
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Applying extensions is handled in extendArtifactAfter(). And only afterward,
|
|
222
|
+
* an effective sequence number is set. Meaning that if a sub-artifact already
|
|
223
|
+
* has a sequence number, then extensions would be lost.
|
|
224
|
+
*
|
|
225
|
+
* A special case are foreign keys, see extendForeignKeys().
|
|
226
|
+
*/
|
|
227
|
+
function ensureArtifactNotProcessed( art ) {
|
|
228
|
+
if (!model.options.testMode)
|
|
229
|
+
return;
|
|
230
|
+
|
|
231
|
+
if (art.kind !== 'key' && art.$effectiveSeqNo !== 0 && art.$effectiveSeqNo !== undefined) {
|
|
232
|
+
// if the artifact already has a sequence number, then
|
|
233
|
+
// extendArtifactAfter() was already called -> annotations would be lost.
|
|
234
|
+
throw new CompilerAssertion('artifact already processed; extensions would be lost');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
184
238
|
/**
|
|
185
239
|
* Create super annotate statements for remaining extensions
|
|
186
240
|
*/
|
|
@@ -196,15 +250,25 @@ function extend( model ) {
|
|
|
196
250
|
}
|
|
197
251
|
|
|
198
252
|
// TODO: delete again - if not, what about extensions in contexts/services?
|
|
253
|
+
// Check test.lsp-api.js! Links in extensions are needed.
|
|
199
254
|
function setArtifactLinkForExtensions( source ) {
|
|
200
255
|
if (!source.extensions)
|
|
201
256
|
return;
|
|
202
|
-
for (const ext of source.extensions
|
|
257
|
+
for (const ext of source.extensions) {
|
|
258
|
+
if (!ext.name?.id)
|
|
259
|
+
continue;
|
|
260
|
+
|
|
203
261
|
const { name } = ext;
|
|
204
|
-
|
|
262
|
+
const { path } = name;
|
|
263
|
+
if (name._artifact === undefined) {
|
|
205
264
|
const refCtx = (name.id.startsWith( 'localized.' )) ? '_extensions' : ext.kind;
|
|
206
265
|
resolvePath( name, refCtx, ext ); // for LSP
|
|
207
266
|
}
|
|
267
|
+
else if (model.options.lspMode && path?.[0]._artifact === undefined) {
|
|
268
|
+
// we don't use resolvePath(…,'extend'), as that would add a dependency
|
|
269
|
+
resolveDefinitionName( ext );
|
|
270
|
+
setArtifactLink( path[path.length - 1], name._artifact );
|
|
271
|
+
}
|
|
208
272
|
}
|
|
209
273
|
}
|
|
210
274
|
|
|
@@ -224,7 +288,7 @@ function extend( model ) {
|
|
|
224
288
|
code: 'extend … with definitions',
|
|
225
289
|
keyword: 'extend service',
|
|
226
290
|
};
|
|
227
|
-
// TODO(
|
|
291
|
+
// TODO(v6): Discuss: make this an error?
|
|
228
292
|
warning( 'ext-invalid-kind', [ loc, ext ], msgArgs, {
|
|
229
293
|
std: 'Artifact $(ART) is not of kind $(KIND), use $(CODE) instead',
|
|
230
294
|
annotate: 'There is no artifact $(ART), use $(CODE) instead',
|
|
@@ -327,7 +391,7 @@ function extend( model ) {
|
|
|
327
391
|
|
|
328
392
|
function extensionOverwrites( ext, prop ) {
|
|
329
393
|
return (prop.charAt(0) !== '@')
|
|
330
|
-
?
|
|
394
|
+
? (prop === 'doc' || typeParameters.list.includes(prop))
|
|
331
395
|
: !annotationHasEllipsis( ext[prop] );
|
|
332
396
|
}
|
|
333
397
|
|
|
@@ -393,6 +457,9 @@ function extend( model ) {
|
|
|
393
457
|
for (const col of ext.columns)
|
|
394
458
|
col.$extended = true;
|
|
395
459
|
|
|
460
|
+
if (art.kind === 'annotate' && art.$inferred === '')
|
|
461
|
+
return; // internal super-annotate for unknown artifacts
|
|
462
|
+
|
|
396
463
|
if (!query?.from?.path) {
|
|
397
464
|
const variant = (query?.from || query)?.op?.val || 'std';
|
|
398
465
|
error( 'extend-columns', [ ext.columns[$location], ext ], { '#': variant, art } );
|
|
@@ -404,7 +471,7 @@ function extend( model ) {
|
|
|
404
471
|
query.columns.push( ...ext.columns );
|
|
405
472
|
initSelectItems( query, ext.columns, query, true );
|
|
406
473
|
}
|
|
407
|
-
else if (
|
|
474
|
+
else if (typeParameters.list.includes( prop )) {
|
|
408
475
|
const typeExts = art.$typeExts || (art.$typeExts = {});
|
|
409
476
|
typeExts[prop] = ext;
|
|
410
477
|
}
|
|
@@ -490,7 +557,7 @@ function extend( model ) {
|
|
|
490
557
|
if ('val' in upToSpec) {
|
|
491
558
|
if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
|
|
492
559
|
return true;
|
|
493
|
-
// TODO
|
|
560
|
+
// TODO v6: delete the special UP TO comparison?
|
|
494
561
|
const upToVal = upToSpec.val;
|
|
495
562
|
const prevVal = previousItem.val;
|
|
496
563
|
// eslint-disable-next-line eqeqeq
|
|
@@ -540,7 +607,7 @@ function extend( model ) {
|
|
|
540
607
|
// - 'ext-unexpected-type-argument' (TODO) if the artifact does not have the prop
|
|
541
608
|
// - 'ext-invalid-type-argument' if the value is wrong for extend (no overwrite)
|
|
542
609
|
//
|
|
543
|
-
// TODO
|
|
610
|
+
// TODO v6: do not allow `extend … with (precision: …)` alone if original def also has `scale`
|
|
544
611
|
function applyTypeExtensions( art, ext, prop, scaleDiff ) {
|
|
545
612
|
// console.log('ATE:',art?.[prop],ext?.[prop],scaleDiff)
|
|
546
613
|
if (!ext?.[prop])
|
|
@@ -614,20 +681,21 @@ function extend( model ) {
|
|
|
614
681
|
const extensions = extensionsMap[extProp];
|
|
615
682
|
if (!extensions)
|
|
616
683
|
return;
|
|
684
|
+
|
|
617
685
|
const artDict = art[artProp] || annotateFor( art, extProp ); // no auto-correction in annotate
|
|
618
686
|
|
|
619
687
|
for (const ext of extensions) {
|
|
620
|
-
const extDict = ext[extProp];
|
|
621
688
|
let dictCheck = (art.kind !== 'annotate'); // no check in super annotate statement
|
|
622
|
-
|
|
623
|
-
const elemExt = extDict[name];
|
|
689
|
+
forEachGeneric(ext, extProp, (elemExt, name) => {
|
|
624
690
|
if (elemExt.kind !== 'annotate' && elemExt.kind !== 'extend') // TODO: specified elems
|
|
625
|
-
|
|
691
|
+
return; // definitions inside extend, already handled
|
|
626
692
|
dictCheck = dictCheck && checkRemainingMemberExtensions( art, elemExt, artProp, name );
|
|
627
693
|
const elem = artDict[name] || annotateFor( art, extProp, name );
|
|
628
694
|
setLink( elemExt.name, '_artifact', (elem.kind !== 'annotate' ? elem : null ) );
|
|
629
|
-
|
|
630
|
-
|
|
695
|
+
ensureArtifactNotProcessed( elem );
|
|
696
|
+
if (elem.$duplicates !== true)
|
|
697
|
+
pushToDict( elem, '_extensions', elemExt );
|
|
698
|
+
});
|
|
631
699
|
}
|
|
632
700
|
}
|
|
633
701
|
|
|
@@ -729,10 +797,11 @@ function extend( model ) {
|
|
|
729
797
|
const dict = parent[prop];
|
|
730
798
|
if (!dict) {
|
|
731
799
|
// TODO: check - for each name? - better locations
|
|
732
|
-
const location = ext._parent[prop][$location] || ext.name.location;
|
|
800
|
+
const location = ext._parent[prop]?.[$location] || ext.name.location;
|
|
733
801
|
// Remark: no `elements` dict location with `annotate Main:elem`
|
|
734
802
|
switch (prop) {
|
|
735
803
|
// TODO: change texts, somehow similar to checkDefinitions() ?
|
|
804
|
+
case 'foreignKeys':
|
|
736
805
|
case 'elements':
|
|
737
806
|
case 'enum': // TODO: extra?
|
|
738
807
|
warning( 'anno-unexpected-elements', [ location, ext._parent ],
|
|
@@ -778,6 +847,10 @@ function extend( model ) {
|
|
|
778
847
|
{ '#': (inReturns ? 'enum-returns' : 'enum'), art, name },
|
|
779
848
|
parent.enum );
|
|
780
849
|
break;
|
|
850
|
+
case 'foreignKeys':
|
|
851
|
+
notFound( 'ext-undefined-key', ext.name.location, ext,
|
|
852
|
+
{ name }, parent.foreignKeys );
|
|
853
|
+
break;
|
|
781
854
|
case 'params':
|
|
782
855
|
notFound( 'ext-undefined-param', ext.name.location, ext,
|
|
783
856
|
{ '#': 'param', art: parent, name },
|
|
@@ -791,7 +864,8 @@ function extend( model ) {
|
|
|
791
864
|
parent.actions );
|
|
792
865
|
break;
|
|
793
866
|
default:
|
|
794
|
-
|
|
867
|
+
if (model.options.testMode)
|
|
868
|
+
throw new CompilerAssertion(`Missing case for prop: ${ prop }`);
|
|
795
869
|
}
|
|
796
870
|
}
|
|
797
871
|
return true;
|
|
@@ -832,8 +906,9 @@ function extend( model ) {
|
|
|
832
906
|
}
|
|
833
907
|
|
|
834
908
|
function checkRemainingMainExtensions( art, ext, localized ) {
|
|
909
|
+
const isExtend = ext.kind === 'extend';
|
|
835
910
|
if (localized) {
|
|
836
|
-
if (
|
|
911
|
+
if (isExtend) {
|
|
837
912
|
// In v5, reject any `extend` on localized.
|
|
838
913
|
error( 'ref-undefined-art', [ ext.location || ext.name.location, ext ],
|
|
839
914
|
{ '#': 'localized', keyword: 'annotate' } );
|
|
@@ -847,22 +922,21 @@ function extend( model ) {
|
|
|
847
922
|
if (art?.builtin) {
|
|
848
923
|
info( 'anno-builtin', [ ext.name.location, ext ], {} ); // TODO: better location?
|
|
849
924
|
}
|
|
850
|
-
else if (art?.kind === 'namespace') {
|
|
925
|
+
else if (isExtend && art?.kind === 'namespace') {
|
|
926
|
+
// `annotate` on namespaces already handled before
|
|
851
927
|
const hasAnnotations = Object.keys(ext).find(a => a.charAt(0) === '@');
|
|
852
928
|
const firstAnno = ext[hasAnnotations];
|
|
853
929
|
// In v5, extending namespaces is only allowed for `extend with definitions`.
|
|
854
930
|
// Neither annotations nor other extensions are allowed.
|
|
855
931
|
// 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
|
-
}
|
|
932
|
+
// Because "namespaces" are the same as "unknown" artifacts in CSN, we don't report
|
|
933
|
+
// an error for `annotate`s.
|
|
934
|
+
// FIXME: The compiler generates empty `annotate` statements for
|
|
935
|
+
// `extend ns with definitions {…}`. That's why we check the frontend.
|
|
936
|
+
if (hasAnnotations || (!ext.artifacts && ext._block.$frontend !== 'json')) {
|
|
937
|
+
error( 'ref-undefined-art', [ (firstAnno?.name || ext.name).location, ext ], {
|
|
938
|
+
'#': 'namespace', art: ext,
|
|
939
|
+
} );
|
|
866
940
|
}
|
|
867
941
|
}
|
|
868
942
|
}
|
|
@@ -1101,7 +1175,6 @@ function extend( model ) {
|
|
|
1101
1175
|
{ art: artName },
|
|
1102
1176
|
{
|
|
1103
1177
|
std: 'Unknown $(ART) - nothing to extend',
|
|
1104
|
-
// eslint-disable-next-line max-len
|
|
1105
1178
|
element: 'Artifact $(ART) has no element or enum $(MEMBER) - nothing to extend',
|
|
1106
1179
|
action: 'Artifact $(ART) has no action $(MEMBER) - nothing to extend',
|
|
1107
1180
|
} );
|
|
@@ -1232,7 +1305,7 @@ function extend( model ) {
|
|
|
1232
1305
|
if (!parent) // not yet set for EXTEND foo WITH bar => linkToOrigin() did not add it
|
|
1233
1306
|
dictAdd( ext[prop], name, elem );
|
|
1234
1307
|
elem.$inferred = 'include';
|
|
1235
|
-
if (origin.masked) // TODO(
|
|
1308
|
+
if (origin.masked) // TODO(v6): remove 'masked'
|
|
1236
1309
|
elem.masked = Object.assign( { $inferred: 'include' }, origin.masked );
|
|
1237
1310
|
if (origin.key)
|
|
1238
1311
|
elem.key = Object.assign( { $inferred: 'include' }, origin.key );
|
|
@@ -1269,8 +1342,6 @@ function extend( model ) {
|
|
|
1269
1342
|
/**
|
|
1270
1343
|
* Report duplicates in parent[prop] that happen due to multiple includes having the
|
|
1271
1344
|
* 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
1345
|
*/
|
|
1275
1346
|
function checkRedefinitionThroughIncludes( parent, prop ) {
|
|
1276
1347
|
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
|
@@ -34,7 +34,7 @@ const { Location, emptyWeakLocation } = require('../base/location');
|
|
|
34
34
|
const { createMessageFunctions, deduplicateMessages } = require('../base/messages');
|
|
35
35
|
const { checkRemovedDeprecatedFlags } = require('../base/model');
|
|
36
36
|
const { promiseAllDoNotRejectImmediately } = require('../base/node-helpers');
|
|
37
|
-
const { cdsFs } = require('../utils/file');
|
|
37
|
+
const { cdsFs, fileExtension } = require('../utils/file');
|
|
38
38
|
|
|
39
39
|
const fs = require('fs');
|
|
40
40
|
const path = require('path');
|
|
@@ -82,12 +82,8 @@ class ArgumentError extends Error {
|
|
|
82
82
|
function parseX( source, filename, options = {}, messageFunctions = null ) {
|
|
83
83
|
if (!messageFunctions)
|
|
84
84
|
messageFunctions = createMessageFunctions( options, 'parse' );
|
|
85
|
-
const ext =
|
|
86
|
-
|
|
87
|
-
const parser = options.fallbackParser === 'auto!'
|
|
88
|
-
? (source?.startsWith( '{' ) ? parseCsn.parse : parseLanguage)
|
|
89
|
-
: (extensionParsers[ext] || extensionParsers[options.fallbackParser] ||
|
|
90
|
-
source.startsWith( '{' ) && parseCsn.parse);
|
|
85
|
+
const ext = fileExtension( filename );
|
|
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
|
|
@@ -149,10 +166,10 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
|
|
|
149
166
|
.then( () => promiseAllDoNotRejectImmediately( input.files.map( readAndParse ) ) )
|
|
150
167
|
.then( testInvocation, (reason) => {
|
|
151
168
|
// do not reject with PromiseAllError, use InvocationError:
|
|
152
|
-
const errs = reason.valuesOrErrors
|
|
169
|
+
const errs = reason.valuesOrErrors?.filter( e => e instanceof Error ) || [ reason ];
|
|
153
170
|
// internal error if no file IO error (has property `path`)
|
|
154
171
|
return Promise.reject( errs.find( e => !e.path ) ||
|
|
155
|
-
new InvocationError( [ ...input
|
|
172
|
+
new InvocationError( [ ...(input?.repeated || []), ...errs ]) );
|
|
156
173
|
} );
|
|
157
174
|
|
|
158
175
|
if (!options.parseOnly && !options.parseCdl)
|
|
@@ -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;
|