@sap/cds-compiler 2.11.2 → 2.13.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 +175 -2
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +23 -17
- package/bin/cdsse.js +2 -2
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +25 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +32 -79
- package/lib/api/options.js +3 -2
- package/lib/api/validate.js +2 -1
- package/lib/backends.js +16 -26
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +10 -19
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +75 -9
- package/lib/base/messages.js +31 -35
- package/lib/base/model.js +2 -62
- package/lib/base/optionProcessorHelper.js +246 -183
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +94 -0
- package/lib/checks/types.js +1 -1
- package/lib/checks/unknownMagic.js +1 -1
- package/lib/checks/validator.js +12 -7
- package/lib/compiler/assert-consistency.js +12 -8
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/builtins.js +42 -21
- package/lib/compiler/checks.js +46 -12
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1103 -0
- package/lib/compiler/extend.js +983 -0
- package/lib/compiler/finalize-parse-cdl.js +231 -0
- package/lib/compiler/index.js +46 -39
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1226 -0
- package/lib/compiler/propagator.js +113 -47
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +100 -65
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +215 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -25
- package/lib/edm/annotations/preprocessAnnotations.js +3 -3
- package/lib/edm/csn2edm.js +10 -9
- package/lib/edm/edm.js +19 -20
- package/lib/edm/edmPreprocessor.js +166 -95
- package/lib/edm/edmUtils.js +127 -34
- package/lib/gen/Dictionary.json +92 -43
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -82
- package/lib/gen/languageLexer.interp +18 -1
- package/lib/gen/languageLexer.js +925 -847
- package/lib/gen/languageLexer.tokens +78 -74
- package/lib/gen/languageParser.js +5434 -4298
- package/lib/json/from-csn.js +59 -17
- package/lib/json/to-csn.js +189 -71
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +3 -3
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +144 -53
- package/lib/language/language.g4 +424 -200
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +550 -61
- package/lib/main.js +38 -11
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +322 -198
- package/lib/model/csnUtils.js +226 -370
- package/lib/model/enrichCsn.js +124 -69
- package/lib/model/revealInternalProperties.js +29 -7
- package/lib/model/sortViews.js +10 -2
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +8 -3
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +36 -33
- package/lib/render/toCdl.js +174 -275
- package/lib/render/toHdbcds.js +203 -122
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +161 -82
- package/lib/render/utils/common.js +22 -8
- package/lib/render/utils/sql.js +10 -7
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +212 -0
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +187 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +61 -56
- package/lib/transform/db/expansion.js +50 -29
- package/lib/transform/db/flattening.js +556 -106
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +103 -28
- package/lib/transform/db/views.js +92 -44
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +9 -7
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +98 -783
- package/lib/transform/forOdataNew.js +22 -175
- package/lib/transform/localized.js +36 -32
- package/lib/transform/odata/generateForeignKeyElements.js +3 -3
- package/lib/transform/odata/referenceFlattener.js +95 -89
- package/lib/transform/odata/structureFlattener.js +1 -1
- package/lib/transform/odata/toFinalBaseType.js +86 -12
- package/lib/transform/odata/typesExposure.js +5 -5
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +47 -33
- package/lib/transform/translateAssocsToJoins.js +13 -30
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +170 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/file.js +8 -3
- package/lib/utils/objectUtils.js +30 -0
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2349
- package/lib/compiler/resolver.js +0 -2922
- package/lib/transform/db/helpers.js +0 -58
- package/lib/transform/universalCsnEnricher.js +0 -67
|
@@ -20,7 +20,6 @@ const {
|
|
|
20
20
|
|
|
21
21
|
const { implicitAs } = require('../../model/csnRefs');
|
|
22
22
|
|
|
23
|
-
|
|
24
23
|
/**
|
|
25
24
|
* Render the given function
|
|
26
25
|
*
|
|
@@ -37,7 +36,7 @@ function renderFunc( funcName, node, dialect, renderArgs) {
|
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
/**
|
|
40
|
-
* Checks
|
|
39
|
+
* Checks whether the given function is to be rendered without parentheses
|
|
41
40
|
*
|
|
42
41
|
* @param {object} node Content of the function
|
|
43
42
|
* @param {string} dialect One of 'hana', 'cap' or 'sqlite' - only 'hana' is relevant atm
|
|
@@ -205,12 +204,11 @@ function addMissingChildContexts(csn, artifactName, killList) {
|
|
|
205
204
|
addPossibleGaps(name.slice(artifactName.length + 1).split('.'), artifactName);
|
|
206
205
|
}
|
|
207
206
|
|
|
208
|
-
function addPossibleGaps(possibleGaps,
|
|
209
|
-
let possibleGap = artifactName;
|
|
207
|
+
function addPossibleGaps(possibleGaps, gapArtifactName) {
|
|
210
208
|
for (const gap of possibleGaps) {
|
|
211
|
-
|
|
212
|
-
if (!csn.definitions[
|
|
213
|
-
const contextName =
|
|
209
|
+
gapArtifactName += `.${gap}`;
|
|
210
|
+
if (!csn.definitions[gapArtifactName]) {
|
|
211
|
+
const contextName = gapArtifactName;
|
|
214
212
|
csn.definitions[contextName] = {
|
|
215
213
|
kind: 'context',
|
|
216
214
|
};
|
|
@@ -332,7 +330,7 @@ function addIntermediateContexts(csn, killList) {
|
|
|
332
330
|
}
|
|
333
331
|
|
|
334
332
|
/**
|
|
335
|
-
* Check
|
|
333
|
+
* Check whether the given artifact or element has a comment that needs to be rendered.
|
|
336
334
|
* Things annotated with @cds.persistence.journal (for HANA SQL), should not get a comment.
|
|
337
335
|
*
|
|
338
336
|
* @param {CSN.Artifact} obj
|
|
@@ -355,6 +353,21 @@ function getHanaComment(obj) {
|
|
|
355
353
|
return obj.doc.split('\n\n')[0].trim().replace(/'/g, "''");
|
|
356
354
|
}
|
|
357
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Get the @sql.prepend/append if set - already add a space after/before.
|
|
358
|
+
* If no value is set, use '';
|
|
359
|
+
*
|
|
360
|
+
* @param {CSN.Options} options
|
|
361
|
+
* @param {object} obj
|
|
362
|
+
* @returns {object} object with .front and .back
|
|
363
|
+
*/
|
|
364
|
+
function getSqlSnippets(options, obj) {
|
|
365
|
+
const front = obj['@sql.prepend'] ? `${obj['@sql.prepend']} ` : '';
|
|
366
|
+
const back = obj['@sql.append'] ? ` ${obj['@sql.append']}` : '';
|
|
367
|
+
|
|
368
|
+
return { front, back };
|
|
369
|
+
}
|
|
370
|
+
|
|
358
371
|
/**
|
|
359
372
|
* @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
|
|
360
373
|
*
|
|
@@ -383,4 +396,5 @@ module.exports = {
|
|
|
383
396
|
getHanaComment,
|
|
384
397
|
findElement,
|
|
385
398
|
funcWithoutParen,
|
|
399
|
+
getSqlSnippets,
|
|
386
400
|
};
|
package/lib/render/utils/sql.js
CHANGED
|
@@ -4,20 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
const { getResultingName } = require('../../model/csnUtils');
|
|
6
6
|
const { smartId, delimitedId } = require('../../sql-identifier');
|
|
7
|
+
const { ModelError } = require('../../base/error');
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Render a given referential constraint as part of a SQL CREATE TABLE statement, or as .hdbconstraint artefact.
|
|
10
11
|
*
|
|
11
12
|
* @param {CSN.ReferentialConstraint} constraint Content of the constraint
|
|
12
13
|
* @param {string} indent Indent to render the SQL with
|
|
13
|
-
* @param {boolean} toUpperCase
|
|
14
|
+
* @param {boolean} toUpperCase Whether to uppercase the identifier
|
|
14
15
|
* @param {CSN.Model} csn CSN
|
|
15
16
|
* @param {CSN.Options} options is needed for the naming mode and the sql dialect
|
|
16
|
-
* @param {boolean} alterConstraint whether the constraint should be rendered as part of an ALTER TABLE statement
|
|
17
|
+
* @param {boolean} [alterConstraint=false] whether the constraint should be rendered as part of an ALTER TABLE statement
|
|
17
18
|
*
|
|
18
19
|
* @returns {string} SQL statement which can be used to create the referential constraint on the db.
|
|
19
20
|
*/
|
|
20
|
-
function renderReferentialConstraint(constraint, indent, toUpperCase, csn, options, alterConstraint) {
|
|
21
|
+
function renderReferentialConstraint(constraint, indent, toUpperCase, csn, options, alterConstraint = false) {
|
|
21
22
|
let quoteId;
|
|
22
23
|
// for to.hana we can't utilize the sql identifier utils
|
|
23
24
|
if (options.transformation === 'hdbcds') {
|
|
@@ -51,14 +52,16 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
|
|
|
51
52
|
if (!alterConstraint) {
|
|
52
53
|
result += `${indent}FOREIGN KEY(${constraint.foreignKey.map(quoteId).join(', ')})\n`;
|
|
53
54
|
result += `${indent}REFERENCES ${quoteId(getResultingName(csn, names, constraint.parentTable))}(${constraint.parentKey.map(quoteId).join(', ')})\n`;
|
|
55
|
+
const onDeleteRemark = constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : '';
|
|
56
|
+
|
|
54
57
|
// omit 'RESTRICT' action for ON UPDATE / ON DELETE, because it interferes with deferred constraint check
|
|
55
58
|
if (forSqlite) {
|
|
56
59
|
if (constraint.onDelete === 'CASCADE' )
|
|
57
|
-
result += `${indent}ON DELETE ${constraint.onDelete}${
|
|
60
|
+
result += `${indent}ON DELETE ${constraint.onDelete}${onDeleteRemark}\n`;
|
|
58
61
|
}
|
|
59
62
|
else {
|
|
60
63
|
result += `${indent}ON UPDATE RESTRICT\n`;
|
|
61
|
-
result += `${indent}ON DELETE ${constraint.onDelete}${
|
|
64
|
+
result += `${indent}ON DELETE ${constraint.onDelete}${onDeleteRemark}\n`;
|
|
62
65
|
}
|
|
63
66
|
}
|
|
64
67
|
// constraint enforcement / validation must be switched off using sqlite pragma statement
|
|
@@ -119,7 +122,7 @@ function getIdentifierUtils(options) {
|
|
|
119
122
|
function prepareIdentifier(name) {
|
|
120
123
|
// Sanity check
|
|
121
124
|
if (options.toSql.dialect === 'sqlite' && options.toSql.names !== 'plain')
|
|
122
|
-
throw new
|
|
125
|
+
throw new ModelError(`Not expecting ${options.toSql.names} names for 'sqlite' dialect`);
|
|
123
126
|
|
|
124
127
|
|
|
125
128
|
switch (options.toSql.names) {
|
|
@@ -130,7 +133,7 @@ function getIdentifierUtils(options) {
|
|
|
130
133
|
case 'hdbcds':
|
|
131
134
|
return name;
|
|
132
135
|
default:
|
|
133
|
-
throw new
|
|
136
|
+
throw new ModelError(`No matching rendering found for naming mode ${options.toSql.names}`);
|
|
134
137
|
}
|
|
135
138
|
}
|
|
136
139
|
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const controlCharacters = /[\u{0000}-\u{001F}]/u;
|
|
4
|
+
const highSurrogate = /[\u{D800}-\u{DBFF}]/u;
|
|
5
|
+
const lowSurrogate = /[\u{DC00}-\u{DFFF}]/u;
|
|
6
|
+
// Either a high surrogate that is NOT followed by a low one or
|
|
7
|
+
// a low surrogate not preceded by a high one.
|
|
8
|
+
const unpairedSurrogate = /[^\u{D800}-\u{DBFF}][\u{DC00}-\u{DFFF}]|[\u{D800}-\u{DBFF}][^\u{DC00}-\u{DFFF}]/u;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns true if the string contains an unpaired unicode surrogate.
|
|
12
|
+
* See <https://en.wikipedia.org/wiki/UTF-16#U+D800_to_U+DFFF>.
|
|
13
|
+
* As a surrogate pair MUST consist of a high one followed by a low surrogate,
|
|
14
|
+
* an unpaired surrogate MUST be escaped.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} str
|
|
17
|
+
* @return {boolean}
|
|
18
|
+
*/
|
|
19
|
+
function hasUnpairedUnicodeSurrogate(str) {
|
|
20
|
+
return unpairedSurrogate.test(str);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Returns true if the string contains control characters such as LF or NUL.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} str
|
|
27
|
+
* @return {boolean}
|
|
28
|
+
*/
|
|
29
|
+
function hasControlCharacters(str) {
|
|
30
|
+
return controlCharacters.test(str);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Escape the given string according to the given specification in `escapes`.
|
|
35
|
+
*
|
|
36
|
+
* `escapes` is an object where the entries are either:
|
|
37
|
+
* - a mapping from character to string, e.g. `{ '"': '"' }`
|
|
38
|
+
* - `control: (codePoint) => str`
|
|
39
|
+
* A function that returns an escape sequence for the given control character.
|
|
40
|
+
* - `unpairedSurrogate: (codePoint) => str`
|
|
41
|
+
* A function that returns an escape sequence for the given unpaired unicode surrogate.
|
|
42
|
+
*
|
|
43
|
+
* Multi-character keys are not allowed.
|
|
44
|
+
*
|
|
45
|
+
* Character escapes take precedence over `control` and `unpairedSurrogate` escapes,
|
|
46
|
+
* i.e. if you do not want to encode LF (`\n`), add an explicit mapping for it, e.g.
|
|
47
|
+
* `{ '\n': '\n' }`.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* You can use `escapeString()` like this:
|
|
51
|
+
* ```js
|
|
52
|
+
* let escaped = escapeString(str, {
|
|
53
|
+
* '"': '\\"',
|
|
54
|
+
* control: (c) => `\\u{${c.toString(16)}}`;
|
|
55
|
+
* unpairedSurrogate: (c) => `\\u{${c.toString(16)}}`;
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @param {string} str
|
|
60
|
+
* @param {object} escapes
|
|
61
|
+
* @returns {string}
|
|
62
|
+
*/
|
|
63
|
+
function escapeString(str, escapes) {
|
|
64
|
+
const output = [];
|
|
65
|
+
|
|
66
|
+
for (let i = 0; i < str.length; ++i) {
|
|
67
|
+
const char = str[i];
|
|
68
|
+
|
|
69
|
+
if (char in escapes) {
|
|
70
|
+
output.push(escapes[char]);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Control Characters: C0
|
|
75
|
+
// See <https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Basic_ASCII_control_codes>
|
|
76
|
+
if (controlCharacters.test(char)) {
|
|
77
|
+
output.push(escapes.control ? escapes.control(char.codePointAt(0)) : char);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Unicode Surrogates
|
|
82
|
+
// These characters appear in _pairs_. A high surrogate must be followed by a low surrogate.
|
|
83
|
+
// If this is not the case, either needs to be encoded. This is also done by JSON.
|
|
84
|
+
// See also <https://docs.microsoft.com/en-us/globalization/encoding/surrogate-pairs>
|
|
85
|
+
if (highSurrogate.test(char)) {
|
|
86
|
+
if (i + 1 >= str.length || !lowSurrogate.test(str[i + 1])) {
|
|
87
|
+
output.push(escapes.unpairedSurrogate ? escapes.unpairedSurrogate(char.codePointAt(0)) : char);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
output.push(char);
|
|
91
|
+
++i;
|
|
92
|
+
output.push(str[i]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else if (lowSurrogate.test(char)) {
|
|
96
|
+
output.push(escapes.unpairedSurrogate ? escapes.unpairedSurrogate(char.codePointAt(0)) : char);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// unhandled / non-special character
|
|
100
|
+
output.push(char);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return output.join('');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = {
|
|
108
|
+
escapeString,
|
|
109
|
+
hasUnpairedUnicodeSurrogate,
|
|
110
|
+
hasControlCharacters,
|
|
111
|
+
};
|
package/lib/sql-identifier.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// an identifier such that the effective name (the one in the DB schema) is
|
|
10
10
|
// the same as the name used in CDS.
|
|
11
11
|
// - 'quoted' and 'hdbcds' for HANA only: similar to the non-provided previous
|
|
12
|
-
// mode, with different
|
|
12
|
+
// mode, with different adaptations to HANA CDS and XS (classic) restrictions.
|
|
13
13
|
|
|
14
14
|
// The main objective of this file is to support the 'plain' mode in a “smart”
|
|
15
15
|
// manner. If we would use the CDS name (after `.` to `_` replacements)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
function isAlreadyBraced(expression, start, end){
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
return start - 1 > -1 &&
|
|
5
|
+
end + 1 < expression.length &&
|
|
6
|
+
expression[start-1] === '(' &&
|
|
7
|
+
expression[end+1] === ')';
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
function binarycomparison(expression, token, index){
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
// Don't enforce stupid descriptions
|
|
14
14
|
"jsdoc/require-param-description": "off",
|
|
15
15
|
"jsdoc/require-returns-description": "off",
|
|
16
|
+
// Sometimes if-else's are more specific
|
|
17
|
+
"sonarjs/prefer-single-boolean-return": "off",
|
|
16
18
|
// Very whiny and nitpicky
|
|
17
19
|
"sonarjs/cognitive-complexity": "off",
|
|
18
20
|
// Does not recognize TS types
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Module for general (partial) CSN looper functions, respecting dictionaries and allowing
|
|
5
|
+
* to pass custom callbacks for certain properties like "ref".
|
|
6
|
+
*
|
|
7
|
+
* Functions are also published in csnUtils.js for convenience.
|
|
8
|
+
*
|
|
9
|
+
* They should stay here due to the stricter linter rules for the time being.
|
|
10
|
+
*
|
|
11
|
+
* @module lib/transform/db/applyTransformations
|
|
12
|
+
*/
|
|
13
|
+
const { setProp } = require('../../base/model');
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {object} parent The "parent" of which we transform a property of
|
|
18
|
+
* @param {string} prop The property of parent to start at
|
|
19
|
+
* @param {object} customTransformers Map of prop to transform and function to apply
|
|
20
|
+
* @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
|
|
21
|
+
* @param {applyTransformationsOptions} [options={}]
|
|
22
|
+
* @param {CSN.Path} path Path to parent
|
|
23
|
+
* @returns {object} parent with transformations applied
|
|
24
|
+
*/
|
|
25
|
+
function applyTransformationsInternal(parent, prop, customTransformers, artifactTransformers, options, path = []) {
|
|
26
|
+
const transformers = {
|
|
27
|
+
elements: dictionary,
|
|
28
|
+
definitions: dictionary,
|
|
29
|
+
actions: dictionary,
|
|
30
|
+
params: dictionary,
|
|
31
|
+
enum: dictionary,
|
|
32
|
+
mixin: dictionary,
|
|
33
|
+
ref: pathRef,
|
|
34
|
+
$origin: () => {}, // no-op
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const csnPath = [ ...path ];
|
|
38
|
+
if (prop === 'definitions')
|
|
39
|
+
definitions( parent, 'definitions', parent.definitions );
|
|
40
|
+
else
|
|
41
|
+
standard(parent, prop, parent[prop]);
|
|
42
|
+
return parent;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Default transformer for things that are not dictionaries, like "type" or "keys".
|
|
46
|
+
* The customTransformers are applied here (and only here).
|
|
47
|
+
*
|
|
48
|
+
* @param {object | Array} _parent the thing that has _prop
|
|
49
|
+
* @param {string|number} _prop the name of the current property
|
|
50
|
+
* @param {object} node The value of node[_prop]
|
|
51
|
+
*/
|
|
52
|
+
function standard( _parent, _prop, node ) {
|
|
53
|
+
if (!node || typeof node !== 'object' ||
|
|
54
|
+
!{}.propertyIsEnumerable.call( _parent, _prop ) ||
|
|
55
|
+
(typeof _prop === 'string' && _prop.startsWith('@')) ||
|
|
56
|
+
(options.skipIgnore && node._ignore) ||
|
|
57
|
+
(options.skipStandard && options.skipStandard[_prop])
|
|
58
|
+
)
|
|
59
|
+
return;
|
|
60
|
+
|
|
61
|
+
csnPath.push( _prop );
|
|
62
|
+
|
|
63
|
+
if (Array.isArray(node)) {
|
|
64
|
+
node.forEach( (n, i) => standard( node, i, n ) );
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
else {
|
|
68
|
+
for (const name of Object.getOwnPropertyNames( node )) {
|
|
69
|
+
const trans = transformers[name] || standard;
|
|
70
|
+
if (customTransformers[name])
|
|
71
|
+
customTransformers[name](node, name, node[name], csnPath, _parent, _prop);
|
|
72
|
+
|
|
73
|
+
trans( node, name, node[name], csnPath );
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
csnPath.pop();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Transformer for things that are dictionaries - like "elements".
|
|
81
|
+
*
|
|
82
|
+
* @param {object | Array} node the thing that has _prop
|
|
83
|
+
* @param {string|number} _prop the name of the current property
|
|
84
|
+
* @param {object} dict The value of node[_prop]
|
|
85
|
+
*/
|
|
86
|
+
function dictionary( node, _prop, dict ) {
|
|
87
|
+
// Allow skipping dicts like actions in forHanaNew
|
|
88
|
+
if (options.skipDict && options.skipDict[_prop] || dict === null || dict === undefined) // with universal CSN, dicts might be null
|
|
89
|
+
return;
|
|
90
|
+
csnPath.push( _prop );
|
|
91
|
+
for (const name of Object.getOwnPropertyNames( dict ))
|
|
92
|
+
standard( dict, name, dict[name] );
|
|
93
|
+
|
|
94
|
+
if (!Object.prototype.propertyIsEnumerable.call( node, _prop ))
|
|
95
|
+
setProp(node, `$${_prop}`, dict);
|
|
96
|
+
csnPath.pop();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Special version of "dictionary" to apply artifactTransformers.
|
|
101
|
+
*
|
|
102
|
+
* @param {object | Array} node the thing that has _prop
|
|
103
|
+
* @param {string|number} _prop the name of the current property
|
|
104
|
+
* @param {object} dict The value of node[_prop]
|
|
105
|
+
*/
|
|
106
|
+
function definitions( node, _prop, dict ) {
|
|
107
|
+
csnPath.push( _prop );
|
|
108
|
+
for (const name of Object.getOwnPropertyNames( dict )) {
|
|
109
|
+
const skip = (options && options.allowArtifact && !options.allowArtifact(dict[name], name)) ||
|
|
110
|
+
(options && options.skipArtifact && options.skipArtifact(dict[name], name)) ||
|
|
111
|
+
(options && options.skip && options.skip.includes(dict[name].kind)) ||
|
|
112
|
+
false;
|
|
113
|
+
if (!skip) {
|
|
114
|
+
artifactTransformers.forEach(fn => fn(dict, name, dict[name]));
|
|
115
|
+
standard( dict, name, dict[name] );
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (!Object.prototype.propertyIsEnumerable.call( node, _prop ))
|
|
119
|
+
setProp(node, `$${_prop}`, dict);
|
|
120
|
+
csnPath.pop();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Keep looping through the pathRef - because in a .ref we can have .args and .where
|
|
125
|
+
*
|
|
126
|
+
* @param {object | Array} node the thing that has _prop
|
|
127
|
+
* @param {string|number} _prop the name of the current property
|
|
128
|
+
* @param {any} _path The value of node[_prop]
|
|
129
|
+
*/
|
|
130
|
+
function pathRef( node, _prop, _path ) {
|
|
131
|
+
csnPath.push( _prop );
|
|
132
|
+
_path.forEach( ( s, i ) => {
|
|
133
|
+
if (s && typeof s === 'object') {
|
|
134
|
+
csnPath.push( i );
|
|
135
|
+
if (options.drillRef) {
|
|
136
|
+
standard(_path, i, s);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
if (s.args)
|
|
140
|
+
standard( s, 'args', s.args );
|
|
141
|
+
if (s.where)
|
|
142
|
+
standard( s, 'where', s.where );
|
|
143
|
+
}
|
|
144
|
+
csnPath.pop();
|
|
145
|
+
}
|
|
146
|
+
} );
|
|
147
|
+
csnPath.pop();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Loop through the model, applying the custom transformations on the node's matching.
|
|
153
|
+
*
|
|
154
|
+
* Each transformer gets:
|
|
155
|
+
* - the parent having the property
|
|
156
|
+
* - the name of the property
|
|
157
|
+
* - the value of the property
|
|
158
|
+
* - the path to the property
|
|
159
|
+
*
|
|
160
|
+
* @param {object} csn CSN to enrich in-place
|
|
161
|
+
* @param {object} customTransformers Map of _prop to transform and function to apply
|
|
162
|
+
* @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
|
|
163
|
+
* @param {applyTransformationsOptions} [options={}]
|
|
164
|
+
* @returns {object} CSN with transformations applied
|
|
165
|
+
*/
|
|
166
|
+
function applyTransformations( csn, customTransformers = {}, artifactTransformers = [], options = { } ) {
|
|
167
|
+
if (options.skipIgnore === undefined)
|
|
168
|
+
options.skipIgnore = true;
|
|
169
|
+
|
|
170
|
+
if (csn && csn.definitions)
|
|
171
|
+
return applyTransformationsInternal(csn, 'definitions', customTransformers, artifactTransformers, options);
|
|
172
|
+
return csn;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Instead of looping through the whole model, start at a given thing (like an on-condition),
|
|
178
|
+
* as long as it is not a dictionary.
|
|
179
|
+
*
|
|
180
|
+
* Each transformer gets:
|
|
181
|
+
* - the parent having the property
|
|
182
|
+
* - the name of the property
|
|
183
|
+
* - the value of the property
|
|
184
|
+
* - the path to the property
|
|
185
|
+
*
|
|
186
|
+
* @param {object} parent The "parent" of which we transform a property of
|
|
187
|
+
* @param {string} prop The property of parent to start at
|
|
188
|
+
* @param {object} customTransformers Map of prop to transform and function to apply
|
|
189
|
+
* @param {applyTransformationsOptions} [options={}]
|
|
190
|
+
* @param {CSN.Path} path Path pointing to parent
|
|
191
|
+
* @returns {object} parent[prop] with transformations applied
|
|
192
|
+
*/
|
|
193
|
+
function applyTransformationsOnNonDictionary(parent, prop, customTransformers = {}, options = {}, path = []) {
|
|
194
|
+
return applyTransformationsInternal(parent, prop, customTransformers, [], options, path)[prop];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
applyTransformations,
|
|
199
|
+
applyTransformationsOnNonDictionary,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* @typedef {object} applyTransformationsOptions
|
|
205
|
+
* @property {(artifact, name) => boolean} [allowArtifact] to only allow certain artifacts
|
|
206
|
+
* @property {(artifact, name) => boolean} [skipArtifact] to skip certain artifacts
|
|
207
|
+
* @property {boolean} [drillRef] whether to drill into infix/args
|
|
208
|
+
* @property {string[]} [skip] skip definitions from certain kind
|
|
209
|
+
* @property {object} [skipStandard] stop drill-down on certain "standard" props
|
|
210
|
+
* @property {object} [skipDict] stop drill-down on certain "dictionary" props
|
|
211
|
+
* @property {boolean} [skipIgnore=true] Whether to skip _ignore elements or not
|
|
212
|
+
*/
|
|
@@ -158,7 +158,7 @@ function processAssertUnique(csn, options, error, info) {
|
|
|
158
158
|
function toRef(val) {
|
|
159
159
|
let ref = val.split('.');
|
|
160
160
|
const [ head, ...tail ] = ref;
|
|
161
|
-
if (
|
|
161
|
+
if (head === '$self' || head === '$projection')
|
|
162
162
|
ref = tail;
|
|
163
163
|
return {
|
|
164
164
|
ref: ref.map((ps) => {
|