@sap/cds-compiler 4.2.2 → 4.3.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 +32 -0
- package/bin/cdsc.js +8 -0
- package/bin/cdshi.js +3 -3
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/main.js +19 -0
- package/lib/base/location.js +16 -0
- package/lib/base/message-registry.js +47 -16
- package/lib/base/messages.js +49 -38
- package/lib/base/model.js +2 -1
- package/lib/checks/checkPathsInStoredCalcElement.js +83 -0
- package/lib/checks/existsExpressionsOnlyForeignKeys.js +71 -0
- package/lib/checks/existsMustEndInAssoc.js +27 -0
- package/lib/checks/onConditions.js +47 -1
- package/lib/checks/validator.js +10 -1
- package/lib/compiler/assert-consistency.js +23 -15
- package/lib/compiler/base.js +31 -14
- package/lib/compiler/builtins.js +21 -20
- package/lib/compiler/checks.js +36 -49
- package/lib/compiler/define.js +71 -91
- package/lib/compiler/extend.js +27 -25
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/generate.js +67 -87
- package/lib/compiler/kick-start.js +7 -5
- package/lib/compiler/populate.js +32 -30
- package/lib/compiler/propagator.js +2 -0
- package/lib/compiler/resolve.js +29 -25
- package/lib/compiler/shared.js +57 -31
- package/lib/compiler/tweak-assocs.js +203 -22
- package/lib/compiler/utils.js +0 -18
- package/lib/gen/Dictionary.json +10 -4
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/languageParser.js +3 -3
- package/lib/inspect/inspectPropagation.js +2 -1
- package/lib/json/from-csn.js +63 -28
- package/lib/json/to-csn.js +23 -13
- package/lib/language/antlrParser.js +1 -1
- package/lib/language/errorStrategy.js +5 -1
- package/lib/language/genericAntlrParser.js +67 -61
- package/lib/main.d.ts +26 -1
- package/lib/main.js +2 -1
- package/lib/model/csnRefs.js +1 -0
- package/lib/model/csnUtils.js +28 -0
- package/lib/model/revealInternalProperties.js +3 -9
- package/lib/optionProcessor.js +17 -1
- package/lib/render/toCdl.js +1 -1
- package/lib/transform/db/associations.js +3 -4
- package/lib/transform/db/backlinks.js +293 -0
- package/lib/transform/db/expansion.js +18 -7
- package/lib/transform/db/flattening.js +3 -2
- package/lib/transform/db/rewriteCalculatedElements.js +1 -67
- package/lib/transform/db/transformExists.js +3 -58
- package/lib/transform/db/views.js +8 -14
- package/lib/transform/effective/.eslintrc.json +4 -0
- package/lib/transform/effective/associations.js +101 -0
- package/lib/transform/effective/main.js +88 -0
- package/lib/transform/effective/misc.js +61 -0
- package/lib/transform/effective/queries.js +42 -0
- package/lib/transform/effective/types.js +121 -0
- package/lib/transform/forRelationalDB.js +12 -235
- package/lib/transform/localized.js +22 -3
- package/lib/transform/parseExpr.js +7 -3
- package/lib/transform/transformUtils.js +5 -22
- package/lib/transform/translateAssocsToJoins.js +42 -38
- package/lib/transform/universalCsn/universalCsnEnricher.js +17 -1
- package/package.json +1 -2
- package/lib/language/language.g4 +0 -3260
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { requireForeignKeyAccess } = require('../checks/onConditions');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check that associations in filters (in an exists expression) are only fk-accesses. Everything else is forbidden.
|
|
7
|
+
*
|
|
8
|
+
* @param {CSN.Artifact} parent
|
|
9
|
+
* @param {string} name
|
|
10
|
+
* @param {Array} expr
|
|
11
|
+
*/
|
|
12
|
+
function forbidAssocInExists( parent, name, expr ) {
|
|
13
|
+
for (let i = 0; i < expr.length - 1; i++) {
|
|
14
|
+
if (expr[i] === 'exists' && expr[i + 1].ref) {
|
|
15
|
+
i++;
|
|
16
|
+
const current = expr[i];
|
|
17
|
+
|
|
18
|
+
const { _links } = expr[i];
|
|
19
|
+
|
|
20
|
+
const assocs = _links.filter(link => link.art?.target).map(link => current.ref[link.idx]);
|
|
21
|
+
|
|
22
|
+
checkForInvalidAssoc.call(this, assocs);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check that associations in filters (in an exists expression) are only fk-accesses. Everything else is forbidden.
|
|
29
|
+
*
|
|
30
|
+
* @param {object[]} assocs Array of refs of assocs - possibly with a .where to check
|
|
31
|
+
*/
|
|
32
|
+
function checkForInvalidAssoc( assocs ) {
|
|
33
|
+
for (const assoc of assocs) {
|
|
34
|
+
if (assoc.where) {
|
|
35
|
+
for (let i = 0; i < assoc.where.length; i++) {
|
|
36
|
+
const part = assoc.where[i];
|
|
37
|
+
|
|
38
|
+
if (part._links && !(assoc.where[i - 1] && assoc.where[i - 1] === 'exists')) {
|
|
39
|
+
for (const link of part._links) {
|
|
40
|
+
if (link.art && link.art.target) {
|
|
41
|
+
if (link.art.keys) { // managed - allow FK access
|
|
42
|
+
const next = part._links[link.idx + 1];
|
|
43
|
+
if (next !== undefined) { // there is a next path step - check if it is a fk
|
|
44
|
+
requireForeignKeyAccess(part, i, (errorIndex) => {
|
|
45
|
+
const { ref } = assoc.where[part.$path[part.$path.length - 1]];
|
|
46
|
+
this.error('ref-expecting-foreign-key', part.$path, { alias: ref[errorIndex], id: assoc.id, name: ref[link.idx] });
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
else { // no traversal, ends on managed
|
|
50
|
+
this.error('ref-unexpected-assoc', part.$path, { '#': 'managed-filter', id: assoc.id, name: assoc.where[part.$path[part.$path.length - 1]].ref[link.idx] });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else { // unmanaged - always wrong
|
|
54
|
+
this.error('ref-unexpected-assoc', part.$path, { '#': 'unmanaged-filter', id: assoc.id, name: assoc.where[part.$path[part.$path.length - 1]].ref[link.idx] });
|
|
55
|
+
}
|
|
56
|
+
// Recursively drill down if the assoc-step has a filter
|
|
57
|
+
if (part.ref[link.idx].where)
|
|
58
|
+
checkForInvalidAssoc.call(this, [ part.ref[link.idx] ]);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = {
|
|
68
|
+
having: forbidAssocInExists,
|
|
69
|
+
where: forbidAssocInExists,
|
|
70
|
+
xpr: forbidAssocInExists,
|
|
71
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A path following an “exists” predicate must always end in an association.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} parent
|
|
7
|
+
* @param {string} prop
|
|
8
|
+
* @param {Array} expression
|
|
9
|
+
* @param {CSN.Path} path
|
|
10
|
+
*/
|
|
11
|
+
function existsMustEndInAssoc( parent, prop, expression, path ) {
|
|
12
|
+
for (let i = 0; i < expression?.length - 1; i++) {
|
|
13
|
+
if (expression[i] === 'exists') {
|
|
14
|
+
const next = expression[i + 1];
|
|
15
|
+
const { _art } = next;
|
|
16
|
+
const errorPath = path.concat([ prop, i ]);
|
|
17
|
+
if (!next.SELECT && !_art?.target)
|
|
18
|
+
this.error('ref-expecting-assoc', errorPath, { elemref: next, type: _art.type });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
having: existsMustEndInAssoc,
|
|
25
|
+
where: existsMustEndInAssoc,
|
|
26
|
+
xpr: existsMustEndInAssoc,
|
|
27
|
+
};
|
|
@@ -172,8 +172,16 @@ function validateOnCondition( member, memberName, property, path ) {
|
|
|
172
172
|
* If a fk-step is missing, `errorIndex` will be `> parent.ref.length`.
|
|
173
173
|
*/
|
|
174
174
|
function requireForeignKeyAccess( parent, refIndex, noForeignKeyCallback ) {
|
|
175
|
-
const {
|
|
175
|
+
const { _links } = parent;
|
|
176
|
+
const ref = [ ...parent.ref ]; // copy so the original ref stays untouched
|
|
176
177
|
const assoc = _links[refIndex].art;
|
|
178
|
+
const nextLink = _links[refIndex + 1]?.art;
|
|
179
|
+
|
|
180
|
+
if (nextLink?.value) {
|
|
181
|
+
const resolved = resolveCalculatedElementRef(nextLink);
|
|
182
|
+
if (resolved)
|
|
183
|
+
ref.splice(refIndex + 1, 1, ...resolved);
|
|
184
|
+
}
|
|
177
185
|
|
|
178
186
|
const next = pathId(ref[refIndex + 1]);
|
|
179
187
|
let possibleKeys = next && assoc.keys.filter(r => r.ref[0] === next);
|
|
@@ -219,4 +227,42 @@ function validateMixinOnCondition( query, path ) {
|
|
|
219
227
|
forEachGeneric( query.SELECT, 'mixin', validateOnCondition.bind(this), path );
|
|
220
228
|
}
|
|
221
229
|
|
|
230
|
+
/**
|
|
231
|
+
* As calculated elements are only resolved in a later transformation step,
|
|
232
|
+
* we must provide a way to check whether a calc element references e.g.
|
|
233
|
+
* a foreign key somewhere down the line.
|
|
234
|
+
*
|
|
235
|
+
* In the following example, `G:indirect` is eventually a foreign key of `G:toG`,
|
|
236
|
+
* hence it is allowed to be used in e.g. an infix filter:
|
|
237
|
+
* @example
|
|
238
|
+
* ```
|
|
239
|
+
* entity G {
|
|
240
|
+
* key id : Integer;
|
|
241
|
+
* idx : Integer;
|
|
242
|
+
* toG: Association to G { idx };
|
|
243
|
+
* cidx = idx;
|
|
244
|
+
* indirect = cidx;
|
|
245
|
+
* }
|
|
246
|
+
*
|
|
247
|
+
* view V as select from G where exists toG[toG.indirect = 1];
|
|
248
|
+
* ^^^^^^^^
|
|
249
|
+
* ```
|
|
250
|
+
*
|
|
251
|
+
* @param {CSN.Element} calculatedElement
|
|
252
|
+
* @returns {CSN.ArtifactReference} the resolved element or the calculated element itself if it is complex
|
|
253
|
+
*/
|
|
254
|
+
function resolveCalculatedElementRef( calculatedElement ) {
|
|
255
|
+
if (calculatedElement.value.ref) {
|
|
256
|
+
const { _links } = calculatedElement.value;
|
|
257
|
+
const leaf = _links[_links.length - 1];
|
|
258
|
+
// TODO: once #11538 is available, checking the leaf for `.value`
|
|
259
|
+
// is not enough anymore.
|
|
260
|
+
if (leaf.art.value)
|
|
261
|
+
return resolveCalculatedElementRef(leaf.art);
|
|
262
|
+
return calculatedElement.value.ref;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
|
|
222
268
|
module.exports = { validateOnCondition, validateMixinOnCondition, requireForeignKeyAccess };
|
package/lib/checks/validator.js
CHANGED
|
@@ -39,6 +39,9 @@ const { validateAssociationsInItems } = require('./arrayOfs');
|
|
|
39
39
|
const checkQueryForNoDBArtifacts = require('./queryNoDbArtifacts');
|
|
40
40
|
const checkExplicitlyNullableKeys = require('./nullableKeys');
|
|
41
41
|
const nonexpandableStructuredInExpression = require('./nonexpandableStructured');
|
|
42
|
+
const existsMustEndInAssoc = require('./existsMustEndInAssoc');
|
|
43
|
+
const forbidAssocInExists = require('./existsExpressionsOnlyForeignKeys');
|
|
44
|
+
const checkPathsInStoredCalcElement = require('./checkPathsInStoredCalcElement');
|
|
42
45
|
const managedWithoutKeys = require('./managedWithoutKeys');
|
|
43
46
|
const {
|
|
44
47
|
checkSqlAnnotationOnArtifact,
|
|
@@ -72,7 +75,13 @@ const forRelationalDBArtifactValidators
|
|
|
72
75
|
checkSqlAnnotationOnArtifact,
|
|
73
76
|
];
|
|
74
77
|
|
|
75
|
-
const forRelationalDBCsnValidators = [
|
|
78
|
+
const forRelationalDBCsnValidators = [
|
|
79
|
+
existsMustEndInAssoc,
|
|
80
|
+
forbidAssocInExists,
|
|
81
|
+
nonexpandableStructuredInExpression,
|
|
82
|
+
navigationIntoMany,
|
|
83
|
+
checkPathsInStoredCalcElement,
|
|
84
|
+
];
|
|
76
85
|
/**
|
|
77
86
|
* @type {Array<(query: CSN.Query, path: CSN.Path) => void>}
|
|
78
87
|
*/
|
|
@@ -192,17 +192,17 @@ function assertConsistency( model, stage ) {
|
|
|
192
192
|
$magicVariables: {
|
|
193
193
|
// $magicVariables contains "builtin" artifacts that differ from
|
|
194
194
|
// "normal artifacts" and therefore have a custom schema
|
|
195
|
-
requires: [ 'kind', '
|
|
195
|
+
requires: [ 'kind', 'elements' ],
|
|
196
196
|
schema: {
|
|
197
197
|
kind: { test: isString, enum: [ '$magicVariables' ] },
|
|
198
|
-
|
|
198
|
+
elements: {
|
|
199
199
|
// Do not use "normal" definitions spec because of these artifacts
|
|
200
200
|
// are missing the location property
|
|
201
201
|
test: isDictionary( definition ),
|
|
202
202
|
requires: [ 'kind', 'name' ],
|
|
203
203
|
optional: [
|
|
204
204
|
'elements', '$autoElement', '$uncheckedElements', '_origin', '_extensions',
|
|
205
|
-
'$requireElementAccess', '_effectiveType', '$effectiveSeqNo', '_deps',
|
|
205
|
+
'$requireElementAccess', '_effectiveType', '$effectiveSeqNo', '_deps', '_parent',
|
|
206
206
|
],
|
|
207
207
|
schema: {
|
|
208
208
|
kind: { test: isString, enum: [ 'builtin' ] },
|
|
@@ -239,7 +239,9 @@ function assertConsistency( model, stage ) {
|
|
|
239
239
|
optional: [ 'name', 'extern', 'usings', 'fileDep' ],
|
|
240
240
|
},
|
|
241
241
|
extern: {
|
|
242
|
-
|
|
242
|
+
kind: [ 'using' ],
|
|
243
|
+
requires: [ 'location' ],
|
|
244
|
+
optional: [ 'location', 'path', 'id' ],
|
|
243
245
|
schema: { path: { inherits: 'path', optional: [ '$delimited' ] } },
|
|
244
246
|
},
|
|
245
247
|
elements: { kind: true, inherits: 'definitions', also: [ 0 ] }, // 0 for cyclic expansions
|
|
@@ -344,9 +346,9 @@ function assertConsistency( model, stage ) {
|
|
|
344
346
|
$inlines: { test: TODO },
|
|
345
347
|
type: {
|
|
346
348
|
kind: true,
|
|
347
|
-
requires: [ 'location'
|
|
349
|
+
requires: [ 'location' ],
|
|
348
350
|
optional: [
|
|
349
|
-
'scope', '_artifact', '$inferred', '$parens',
|
|
351
|
+
'path', 'scope', '_artifact', '$inferred', '$parens',
|
|
350
352
|
],
|
|
351
353
|
},
|
|
352
354
|
targetAspect: {
|
|
@@ -516,13 +518,13 @@ function assertConsistency( model, stage ) {
|
|
|
516
518
|
kind: true,
|
|
517
519
|
instanceOf: 'ignore', // TODO: XsnName,
|
|
518
520
|
schema: {
|
|
519
|
-
|
|
521
|
+
id: { test: isStringOrNumber },
|
|
522
|
+
select: { test: TODO }, // TODO: remove
|
|
520
523
|
}, // TODO: rename query prop in name
|
|
521
524
|
requires: [ 'location' ],
|
|
522
525
|
optional: [
|
|
523
526
|
'path', 'id', '$delimited', 'variant', // TODO: req path, opt id for main, req id for member
|
|
524
527
|
'_artifact', '$inferred',
|
|
525
|
-
'absolute', 'select', 'alias', 'element', 'action', 'param',
|
|
526
528
|
],
|
|
527
529
|
},
|
|
528
530
|
absolute: { test: isString },
|
|
@@ -665,6 +667,7 @@ function assertConsistency( model, stage ) {
|
|
|
665
667
|
'$internal', // compiler internal; must not reach CSN output
|
|
666
668
|
'*', // inferred from query wildcard
|
|
667
669
|
'as', // query alias name
|
|
670
|
+
'path-prefix', // using declaration for `entity path.prefix.E`
|
|
668
671
|
'aspect-composition',
|
|
669
672
|
'autoexposed', // for auto-exposed entities (they can't be referred to)
|
|
670
673
|
'cast', // type from cast() function
|
|
@@ -676,7 +679,7 @@ function assertConsistency( model, stage ) {
|
|
|
676
679
|
'keys',
|
|
677
680
|
'localized', // e.g. compiler-generated elements for localized: `text` assoc, etc.
|
|
678
681
|
'localized-entity', // `.texts` entity
|
|
679
|
-
'nav', // only used for MASKED, TODO(
|
|
682
|
+
'nav', // only used for MASKED, TODO(v5): Remove
|
|
680
683
|
'none', // only used in ensureColumnName(): Used in object representing empty alias
|
|
681
684
|
'query', // inferred query properties, e.g. `key`
|
|
682
685
|
'rewrite', // on-conditions or FKeys are rewritten
|
|
@@ -759,7 +762,7 @@ function assertConsistency( model, stage ) {
|
|
|
759
762
|
if (parent.kind !== 'namespace')
|
|
760
763
|
throw new InternalConsistencyError(`Property '${ prop }' must be inside artifact that is a namespace but was '${ parent.kind }'${ at( [ node, parent ], prop, name ) }` );
|
|
761
764
|
|
|
762
|
-
const parentName = parent.name
|
|
765
|
+
const parentName = parent.name?.id;
|
|
763
766
|
if (parentName !== 'cds' && parentName !== 'localized')
|
|
764
767
|
throw new InternalConsistencyError(`Property '${ prop }' must be inside namespace 'cds' or 'localized' but was '${ parentName }'${ at( [ node, parent ], prop, name ) }` );
|
|
765
768
|
}
|
|
@@ -968,6 +971,11 @@ function assertConsistency( model, stage ) {
|
|
|
968
971
|
};
|
|
969
972
|
}
|
|
970
973
|
|
|
974
|
+
function isStringOrNumber( node, parent, prop, spec ) {
|
|
975
|
+
if (typeof node !== 'number')
|
|
976
|
+
isString( node, parent, prop, spec );
|
|
977
|
+
}
|
|
978
|
+
|
|
971
979
|
function isString( node, parent, prop, spec ) {
|
|
972
980
|
if (typeof node !== 'string')
|
|
973
981
|
throw new InternalConsistencyError( `Expected string but found ${ typeof node }${ at( [ node, parent ], prop ) }` );
|
|
@@ -1005,12 +1013,12 @@ function assertConsistency( model, stage ) {
|
|
|
1005
1013
|
if (prop === 'artifacts')
|
|
1006
1014
|
standard( art, parent, prop, spec, name );
|
|
1007
1015
|
}
|
|
1008
|
-
else if (!art.name.
|
|
1009
|
-
!model.definitions[art.name.
|
|
1010
|
-
!
|
|
1016
|
+
else if (!art.name.id ||
|
|
1017
|
+
!model.definitions[art.name.id] &&
|
|
1018
|
+
!model.vocabularies?.[art.name.id]) {
|
|
1011
1019
|
// TODO: sign ignored artifacts with $inferred = 'IGNORED'
|
|
1012
|
-
if (parent.kind === 'source' ||
|
|
1013
|
-
art.name.
|
|
1020
|
+
if (parent.kind === 'source' || art.kind === 'using' ||
|
|
1021
|
+
art.name.id?.startsWith?.( 'localized.' ))
|
|
1014
1022
|
standard( art, parent, prop, spec, name );
|
|
1015
1023
|
else
|
|
1016
1024
|
throw new InternalConsistencyError( `Expected definition${ at( [ art, parent ], prop, name ) }` );
|
package/lib/compiler/base.js
CHANGED
|
@@ -61,8 +61,8 @@ const kindProperties = {
|
|
|
61
61
|
annotate: {
|
|
62
62
|
noDep: 'special', elements: true, enum: true, actions: true, params: true,
|
|
63
63
|
},
|
|
64
|
-
builtin: {},
|
|
65
|
-
$parameters: {}, // $parameters in query entities
|
|
64
|
+
builtin: { normalized: 'element' }, // = $now, $user.id, …
|
|
65
|
+
$parameters: {}, // $parameters in query entities - TODO: normalized: 'alias'?
|
|
66
66
|
};
|
|
67
67
|
|
|
68
68
|
function propExists( prop, parent ) {
|
|
@@ -79,26 +79,43 @@ function propExists( prop, parent ) {
|
|
|
79
79
|
* @returns {XSN.Name}
|
|
80
80
|
*/
|
|
81
81
|
function getArtifactName( art ) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
const { name } = art;
|
|
83
|
+
if (!name) // no name
|
|
84
|
+
return name;
|
|
85
|
+
if (!art.kind) // annotation assignments
|
|
86
|
+
return { ...art.name, absolute: art.name.id };
|
|
87
|
+
if (art.kind === 'using')
|
|
88
|
+
return { ...art.name, absolute: art.extern.id };
|
|
89
|
+
|
|
85
90
|
const namePath = [];
|
|
86
|
-
let parent = art;
|
|
87
|
-
while (parent._main) { // until we hit the main artifact
|
|
88
|
-
|
|
91
|
+
let parent = art._outer || art;
|
|
92
|
+
while (parent._main || parent.kind === 'builtin') { // until we hit the main artifact
|
|
93
|
+
if (parent.name.$inferred !== '$internal' || parent.kind === '$inline')
|
|
94
|
+
namePath.push( parent );
|
|
95
|
+
if (parent.kind === 'select')
|
|
96
|
+
break;
|
|
89
97
|
parent = parent._parent;
|
|
98
|
+
parent = parent._outer || parent; // for anonymous aspect and items
|
|
90
99
|
}
|
|
91
100
|
namePath.reverse();
|
|
92
101
|
// start with id/location of art.name, and absolute of art._main
|
|
93
|
-
const
|
|
102
|
+
const dot = (art._main || typeof name.id !== 'string') ? -1 : name.id.lastIndexOf( '.' );
|
|
103
|
+
const rname = (!parent?.name) ? { id: name.id } : {
|
|
104
|
+
id: (dot < 0 ? name.id : name.id.substring( dot + 1)),
|
|
105
|
+
location: name.location,
|
|
106
|
+
absolute: (parent._main || parent).name.id,
|
|
107
|
+
};
|
|
108
|
+
if (name.path !== undefined)
|
|
109
|
+
rname.path = name.path;
|
|
94
110
|
for (const np of namePath) {
|
|
95
111
|
const prop = getMemberNameProp( np, np.kind );
|
|
96
|
-
|
|
112
|
+
rname[prop] = (rname[prop]) ? `${ rname[prop] }.${ np.name.id }` : np.name.id;
|
|
113
|
+
}
|
|
114
|
+
if (name._artifact !== undefined) {
|
|
115
|
+
Object.defineProperty( rname, '_artifact',
|
|
116
|
+
{ value: name._artifact, configurable: true, writable: true } );
|
|
97
117
|
}
|
|
98
|
-
|
|
99
|
-
if (link !== undefined)
|
|
100
|
-
Object.defineProperty( name, '_artifact', { value: link, configurable: true, writable: true } );
|
|
101
|
-
return name;
|
|
118
|
+
return rname;
|
|
102
119
|
}
|
|
103
120
|
|
|
104
121
|
// TODO: probably store this prop in name
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -119,7 +119,7 @@ const specialFunctions = compileFunctions( {
|
|
|
119
119
|
MAX: 'COUNT',
|
|
120
120
|
SUM: 'COUNT',
|
|
121
121
|
AVG: 'COUNT',
|
|
122
|
-
|
|
122
|
+
STDDEV: 'COUNT',
|
|
123
123
|
VAR: 'COUNT',
|
|
124
124
|
LOCATE_REGEXPR: [
|
|
125
125
|
{
|
|
@@ -441,7 +441,7 @@ function initBuiltins( model ) {
|
|
|
441
441
|
const art = {
|
|
442
442
|
kind: 'namespace',
|
|
443
443
|
// builtin namespaces don't have a cds file, so no location available
|
|
444
|
-
name: {
|
|
444
|
+
name: { id: name, location: builtinLocation() },
|
|
445
445
|
blocks: [],
|
|
446
446
|
builtin,
|
|
447
447
|
location: builtinLocation(),
|
|
@@ -462,10 +462,10 @@ function initBuiltins( model ) {
|
|
|
462
462
|
function env( builtins, prefix, parent ) {
|
|
463
463
|
const artifacts = Object.create( null );
|
|
464
464
|
for (const name of Object.keys( builtins )) {
|
|
465
|
-
const
|
|
465
|
+
const id = prefix + name;
|
|
466
466
|
// TODO: reconsider whether to set a type to itself - looks wrong
|
|
467
467
|
const art = {
|
|
468
|
-
kind: 'type', builtin: true, name: {
|
|
468
|
+
kind: 'type', builtin: true, name: { id },
|
|
469
469
|
};
|
|
470
470
|
if (parent)
|
|
471
471
|
parent._subArtifacts[name] = art;
|
|
@@ -475,21 +475,23 @@ function initBuiltins( model ) {
|
|
|
475
475
|
Object.assign( art, builtins[name] );
|
|
476
476
|
if (!art.internal)
|
|
477
477
|
artifacts[name] = art;
|
|
478
|
-
model.definitions[
|
|
478
|
+
model.definitions[id] = art;
|
|
479
479
|
}
|
|
480
480
|
return artifacts;
|
|
481
481
|
}
|
|
482
482
|
|
|
483
483
|
function setMagicVariables( builtins ) {
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
|
|
484
|
+
const elements = Object.create( null );
|
|
485
|
+
model.$magicVariables = { kind: '$magicVariables', elements };
|
|
486
|
+
for (const id in builtins) {
|
|
487
|
+
const magic = builtins[id];
|
|
487
488
|
// TODO: rename to $builtinFunction
|
|
488
489
|
const art = {
|
|
489
|
-
kind: 'builtin',
|
|
490
|
-
name: { id
|
|
490
|
+
kind: 'builtin', // TODO: $var
|
|
491
|
+
name: { id },
|
|
491
492
|
};
|
|
492
|
-
|
|
493
|
+
elements[id] = art;
|
|
494
|
+
setProp( art, '_parent', model.$magicVariables );
|
|
493
495
|
|
|
494
496
|
if (magic.$autoElement)
|
|
495
497
|
art.$autoElement = magic.$autoElement;
|
|
@@ -499,11 +501,10 @@ function initBuiltins( model ) {
|
|
|
499
501
|
art.$requireElementAccess = magic.$requireElementAccess;
|
|
500
502
|
|
|
501
503
|
createMagicElements( art, magic.elements );
|
|
502
|
-
if (options.variableReplacements?.[
|
|
503
|
-
createMagicElements( art, options.variableReplacements[
|
|
504
|
+
if (options.variableReplacements?.[id])
|
|
505
|
+
createMagicElements( art, options.variableReplacements[id] );
|
|
504
506
|
// setProp( art, '_effectiveType', art );
|
|
505
507
|
}
|
|
506
|
-
model.$magicVariables = { kind: '$magicVariables', artifacts };
|
|
507
508
|
}
|
|
508
509
|
|
|
509
510
|
function createMagicElements( art, elements ) {
|
|
@@ -514,20 +515,20 @@ function initBuiltins( model ) {
|
|
|
514
515
|
if (names.length > 0 && !art.elements)
|
|
515
516
|
art.elements = Object.create( null );
|
|
516
517
|
|
|
517
|
-
for (const
|
|
518
|
+
for (const id of names) {
|
|
518
519
|
const magic = {
|
|
519
|
-
kind: 'builtin', // TODO: '$
|
|
520
|
-
name: { id
|
|
520
|
+
kind: 'builtin', // TODO: '$var'
|
|
521
|
+
name: { id },
|
|
521
522
|
};
|
|
522
523
|
// Propagate this property so that it is available for sub-elements.
|
|
523
524
|
if (art.$uncheckedElements)
|
|
524
525
|
magic.$uncheckedElements = art.$uncheckedElements;
|
|
525
526
|
setProp( magic, '_parent', art );
|
|
526
527
|
// setProp( magic, '_effectiveType', magic );
|
|
527
|
-
if (elements[
|
|
528
|
-
createMagicElements( magic, elements[
|
|
528
|
+
if (elements[id] && typeof elements[id] === 'object')
|
|
529
|
+
createMagicElements( magic, elements[id] );
|
|
529
530
|
|
|
530
|
-
art.elements[
|
|
531
|
+
art.elements[id] = magic;
|
|
531
532
|
}
|
|
532
533
|
}
|
|
533
534
|
}
|