@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/checks/enricher.js
CHANGED
|
@@ -49,7 +49,6 @@ function enrichCsn( csn, options ) {
|
|
|
49
49
|
dictionary( csn, 'definitions', csn.definitions );
|
|
50
50
|
return { csn, cleanup };
|
|
51
51
|
|
|
52
|
-
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
53
52
|
function standard( parent, prop, node ) {
|
|
54
53
|
if (!node || typeof node !== 'object' || !{}.propertyIsEnumerable.call( parent, prop ))
|
|
55
54
|
return;
|
|
@@ -69,7 +68,7 @@ function enrichCsn( csn, options ) {
|
|
|
69
68
|
}
|
|
70
69
|
csnPath.pop();
|
|
71
70
|
}
|
|
72
|
-
|
|
71
|
+
|
|
73
72
|
function dictionary( node, prop, dict ) {
|
|
74
73
|
setProp(node, '$path', [ ...csnPath ]);
|
|
75
74
|
cleanupCallbacks.push(() => delete node.$path);
|
|
@@ -115,7 +114,6 @@ function enrichCsn( csn, options ) {
|
|
|
115
114
|
}
|
|
116
115
|
}
|
|
117
116
|
|
|
118
|
-
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
119
117
|
function columns( parent, prop, node ) {
|
|
120
118
|
// Establish the link relationships
|
|
121
119
|
parent[prop].forEach((column) => {
|
|
@@ -130,7 +128,6 @@ function enrichCsn( csn, options ) {
|
|
|
130
128
|
standard(parent, prop, node);
|
|
131
129
|
}
|
|
132
130
|
|
|
133
|
-
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
134
131
|
function simpleRef( node, prop, ref ) {
|
|
135
132
|
setProp(node, '$path', [ ...csnPath ]);
|
|
136
133
|
cleanupCallbacks.push(() => delete node.$path);
|
|
@@ -158,7 +155,6 @@ function enrichCsn( csn, options ) {
|
|
|
158
155
|
}
|
|
159
156
|
}
|
|
160
157
|
|
|
161
|
-
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
162
158
|
function pathRef( node, prop, path ) {
|
|
163
159
|
const {
|
|
164
160
|
links, art, scope, $env,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isBuiltinType } = require('../base/builtins');
|
|
4
|
+
const { transformExpression, applyTransformationsOnNonDictionary } = require('../model/csnUtils');
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param {object} member
|
|
8
|
+
*/
|
|
9
|
+
function checkAnnotationExpression( member, _memberName, _prop, path ) {
|
|
10
|
+
Object.keys(member).filter(pn => pn[0] === '@').forEach((anno) => {
|
|
11
|
+
applyTransformationsOnNonDictionary(member, anno, {
|
|
12
|
+
xpr: (parent, prop, _xpr, xprPath) => {
|
|
13
|
+
transformExpression(parent, prop, {
|
|
14
|
+
ref: (elemref, __prop, ref, refPath) => {
|
|
15
|
+
const { art, scope } = this.csnUtils.inspectRef(refPath);
|
|
16
|
+
if (scope !== '$magic' && art) {
|
|
17
|
+
const ft = this.csnUtils.getFinalTypeInfo(art.type);
|
|
18
|
+
if (!isBuiltinType(ft?.type))
|
|
19
|
+
this.error('odata-anno-xpr-ref', refPath, { anno, elemref, '#': 'flatten_builtin_type' });
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
}, xprPath);
|
|
23
|
+
},
|
|
24
|
+
}, {}, path);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
checkAnnotationExpression,
|
|
30
|
+
};
|
package/lib/checks/validator.js
CHANGED
|
@@ -15,6 +15,7 @@ const navigationIntoMany = require('./manyNavigations');
|
|
|
15
15
|
const checkUsedTypesForAnonymousAspectComposition = require('./managedInType');
|
|
16
16
|
const validateHasPersistedElements = require('./hasPersistedElements');
|
|
17
17
|
const checkForHanaTypes = require('./checkForTypes');
|
|
18
|
+
const { checkAnnotationExpression } = require('./structuredAnnoExpressions');
|
|
18
19
|
const checkForParams = require('./parameters');
|
|
19
20
|
// forOdata
|
|
20
21
|
const { validateDefaultValues } = require('./defaultValues');
|
|
@@ -210,6 +211,13 @@ function forRelationalDB( csn, that ) {
|
|
|
210
211
|
duplicate messages due to the forEachMemberRecursively.
|
|
211
212
|
TODO: check if this recursion can be factored out of the validator */
|
|
212
213
|
forEachMember(artifact, checkUsedTypesForAnonymousAspectComposition.bind(that));
|
|
214
|
+
},
|
|
215
|
+
(artifact, artifactName) => {
|
|
216
|
+
if (that.options.transformation === 'effective') {
|
|
217
|
+
forEachMemberRecursively(artifact, checkAnnotationExpression.bind(that), [ 'definitions', artifactName ], false, {
|
|
218
|
+
skipArtifact: a => a.returns || (a.params && !a.query),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
213
221
|
}
|
|
214
222
|
),
|
|
215
223
|
forRelationalDBQueryValidators.concat(commonQueryValidators),
|
|
@@ -208,6 +208,7 @@ function assertConsistency( model, stage ) {
|
|
|
208
208
|
'elements', '$autoElement', '$uncheckedElements', '_origin', '_extensions',
|
|
209
209
|
'$requireElementAccess', '_effectiveType', '$effectiveSeqNo', '_deps',
|
|
210
210
|
'$calcDepElement', '$filtered', '$enclosed', '_parent',
|
|
211
|
+
'deprecated', '$onlyInExprCtx',
|
|
211
212
|
],
|
|
212
213
|
schema: {
|
|
213
214
|
kind: { test: isString, enum: [ 'builtin' ] },
|
|
@@ -215,6 +216,8 @@ function assertConsistency( model, stage ) {
|
|
|
215
216
|
$autoElement: { test: isString },
|
|
216
217
|
$uncheckedElements: { test: isBoolean },
|
|
217
218
|
$requireElementAccess: { test: isBoolean },
|
|
219
|
+
deprecated: { test: isBoolean },
|
|
220
|
+
$onlyInExprCtx: { test: TODO },
|
|
218
221
|
// missing location for normal "elements"
|
|
219
222
|
elements: { test: TODO },
|
|
220
223
|
},
|
|
@@ -703,7 +706,7 @@ function assertConsistency( model, stage ) {
|
|
|
703
706
|
'localized', // e.g. compiler-generated elements for localized: `text` assoc, etc.
|
|
704
707
|
'localized-entity', // `.texts` entity
|
|
705
708
|
'localized-origin', // `.texts` entity
|
|
706
|
-
'nav', // only used for MASKED, TODO(
|
|
709
|
+
'nav', // only used for MASKED, TODO(v6): Remove
|
|
707
710
|
'none', // only used in ensureColumnName(): Used in object representing empty alias
|
|
708
711
|
'query', // inferred query properties, e.g. `key`
|
|
709
712
|
'rewrite', // on-conditions or FKeys are rewritten
|
package/lib/compiler/base.js
CHANGED
|
@@ -48,7 +48,7 @@ const kindProperties = {
|
|
|
48
48
|
normalized: 'action',
|
|
49
49
|
dict: 'actions',
|
|
50
50
|
}, // no extend params, only annotate
|
|
51
|
-
key: { normalized: 'element' },
|
|
51
|
+
key: { normalized: 'element', dict: 'elements' }, // dict for annotate
|
|
52
52
|
param: { elements: () => false, enum: () => false, dict: 'params' },
|
|
53
53
|
source: { artifacts: true }, // TODO -> $source
|
|
54
54
|
using: {},
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -178,14 +178,15 @@ const magicVariables = {
|
|
|
178
178
|
// Allow shortcut in CDL: `$user` becomes `$user.id` in CSN.
|
|
179
179
|
$autoElement: 'id',
|
|
180
180
|
},
|
|
181
|
-
$at: {
|
|
181
|
+
$at: { // $at is considered deprecated since cds-compiler v5
|
|
182
182
|
elements: {
|
|
183
183
|
from: {}, to: {},
|
|
184
184
|
},
|
|
185
185
|
// Require that elements are accessed, i.e. no $at, only $at.<element>.
|
|
186
186
|
$requireElementAccess: true,
|
|
187
|
+
deprecated: true, // $at is deprecated; use $valid
|
|
187
188
|
},
|
|
188
|
-
$valid: {
|
|
189
|
+
$valid: {
|
|
189
190
|
elements: {
|
|
190
191
|
from: {}, to: {},
|
|
191
192
|
},
|
|
@@ -200,6 +201,15 @@ const magicVariables = {
|
|
|
200
201
|
$uncheckedElements: true,
|
|
201
202
|
$requireElementAccess: true,
|
|
202
203
|
},
|
|
204
|
+
$draft: {
|
|
205
|
+
elements: {
|
|
206
|
+
IsActiveEntity: {},
|
|
207
|
+
},
|
|
208
|
+
// Require that elements are accessed, i.e. no $draft, only $draft.<element>.
|
|
209
|
+
$requireElementAccess: true,
|
|
210
|
+
// See reference semantics in shared.js
|
|
211
|
+
$onlyInExprCtx: [ 'annotation', 'annoRewrite' ],
|
|
212
|
+
},
|
|
203
213
|
};
|
|
204
214
|
|
|
205
215
|
// see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP CDS via function
|
|
@@ -437,6 +447,10 @@ function initBuiltins( model ) {
|
|
|
437
447
|
art.$uncheckedElements = magic.$uncheckedElements;
|
|
438
448
|
if (magic.$requireElementAccess)
|
|
439
449
|
art.$requireElementAccess = magic.$requireElementAccess;
|
|
450
|
+
if (magic.deprecated)
|
|
451
|
+
art.deprecated = magic.deprecated;
|
|
452
|
+
if (magic.$onlyInExprCtx)
|
|
453
|
+
art.$onlyInExprCtx = magic.$onlyInExprCtx;
|
|
440
454
|
|
|
441
455
|
createMagicElements( art, magic.elements );
|
|
442
456
|
if (options.variableReplacements?.[id])
|
|
@@ -461,6 +475,8 @@ function initBuiltins( model ) {
|
|
|
461
475
|
// Propagate this property so that it is available for sub-elements.
|
|
462
476
|
if (art.$uncheckedElements)
|
|
463
477
|
magic.$uncheckedElements = art.$uncheckedElements;
|
|
478
|
+
if (art.$onlyInExprCtx)
|
|
479
|
+
magic.$onlyInExprCtx = art.$onlyInExprCtx;
|
|
464
480
|
setProp( magic, '_parent', art );
|
|
465
481
|
// setProp( magic, '_effectiveType', magic );
|
|
466
482
|
if (elements[id] && typeof elements[id] === 'object')
|
package/lib/compiler/checks.js
CHANGED
|
@@ -54,9 +54,6 @@ function check( model ) {
|
|
|
54
54
|
function checkEvent( def ) {
|
|
55
55
|
// Ensure that events are structured. Up to compiler v4, we allowed non-structured events,
|
|
56
56
|
// because when we introduced them, it was not fully specified what they are.
|
|
57
|
-
// TODO(v5):
|
|
58
|
-
// - Make this a (configurable) error; move to acceptTypeOrElement
|
|
59
|
-
// - require type/elements to be set in parser
|
|
60
57
|
if (def.kind === 'event' && !def._effectiveType?.elements && !def._effectiveType?.projection)
|
|
61
58
|
message( 'def-expected-structured', [ (def.type || def.name).location, def ] );
|
|
62
59
|
}
|
|
@@ -862,8 +859,8 @@ function check( model ) {
|
|
|
862
859
|
// Tree-ish expression from the compiler (not augmented)
|
|
863
860
|
// eslint-disable-next-line max-len
|
|
864
861
|
return (isAssociationOperand( xpr.args[0] ) && isDollarSelfOrProjectionOperand( xpr.args[2] ) ||
|
|
865
|
-
|
|
866
|
-
|
|
862
|
+
// eslint-disable-next-line max-len
|
|
863
|
+
isAssociationOperand( xpr.args[2] ) && isDollarSelfOrProjectionOperand( xpr.args[0] ));
|
|
867
864
|
}
|
|
868
865
|
|
|
869
866
|
// Nothing else qualifies
|
package/lib/compiler/define.js
CHANGED
|
@@ -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');
|
|
@@ -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;
|