@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.
Files changed (140) hide show
  1. package/CHANGELOG.md +175 -2
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +23 -17
  5. package/bin/cdsse.js +2 -2
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +25 -6
  9. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  10. package/doc/NameResolution.md +21 -16
  11. package/lib/api/main.js +32 -79
  12. package/lib/api/options.js +3 -2
  13. package/lib/api/validate.js +2 -1
  14. package/lib/backends.js +16 -26
  15. package/lib/base/dictionaries.js +0 -8
  16. package/lib/base/error.js +26 -0
  17. package/lib/base/keywords.js +10 -19
  18. package/lib/base/location.js +9 -4
  19. package/lib/base/message-registry.js +75 -9
  20. package/lib/base/messages.js +31 -35
  21. package/lib/base/model.js +2 -62
  22. package/lib/base/optionProcessorHelper.js +246 -183
  23. package/lib/checks/.eslintrc.json +2 -0
  24. package/lib/checks/actionsFunctions.js +2 -1
  25. package/lib/checks/annotationsOData.js +1 -1
  26. package/lib/checks/cdsPersistence.js +2 -1
  27. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  28. package/lib/checks/enricher.js +17 -1
  29. package/lib/checks/foreignKeys.js +4 -4
  30. package/lib/checks/invalidTarget.js +3 -1
  31. package/lib/checks/managedInType.js +4 -4
  32. package/lib/checks/managedWithoutKeys.js +3 -1
  33. package/lib/checks/queryNoDbArtifacts.js +1 -3
  34. package/lib/checks/selectItems.js +4 -4
  35. package/lib/checks/sql-snippets.js +94 -0
  36. package/lib/checks/types.js +1 -1
  37. package/lib/checks/unknownMagic.js +1 -1
  38. package/lib/checks/validator.js +12 -7
  39. package/lib/compiler/assert-consistency.js +12 -8
  40. package/lib/compiler/base.js +0 -1
  41. package/lib/compiler/builtins.js +42 -21
  42. package/lib/compiler/checks.js +46 -12
  43. package/lib/compiler/cycle-detector.js +1 -1
  44. package/lib/compiler/define.js +1103 -0
  45. package/lib/compiler/extend.js +983 -0
  46. package/lib/compiler/finalize-parse-cdl.js +231 -0
  47. package/lib/compiler/index.js +46 -39
  48. package/lib/compiler/kick-start.js +190 -0
  49. package/lib/compiler/moduleLayers.js +4 -4
  50. package/lib/compiler/populate.js +1226 -0
  51. package/lib/compiler/propagator.js +113 -47
  52. package/lib/compiler/resolve.js +1433 -0
  53. package/lib/compiler/shared.js +100 -65
  54. package/lib/compiler/tweak-assocs.js +529 -0
  55. package/lib/compiler/utils.js +215 -33
  56. package/lib/edm/.eslintrc.json +5 -0
  57. package/lib/edm/annotations/genericTranslation.js +38 -25
  58. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  59. package/lib/edm/csn2edm.js +10 -9
  60. package/lib/edm/edm.js +19 -20
  61. package/lib/edm/edmPreprocessor.js +166 -95
  62. package/lib/edm/edmUtils.js +127 -34
  63. package/lib/gen/Dictionary.json +92 -43
  64. package/lib/gen/language.checksum +1 -1
  65. package/lib/gen/language.interp +11 -1
  66. package/lib/gen/language.tokens +86 -82
  67. package/lib/gen/languageLexer.interp +18 -1
  68. package/lib/gen/languageLexer.js +925 -847
  69. package/lib/gen/languageLexer.tokens +78 -74
  70. package/lib/gen/languageParser.js +5434 -4298
  71. package/lib/json/from-csn.js +59 -17
  72. package/lib/json/to-csn.js +189 -71
  73. package/lib/language/antlrParser.js +3 -3
  74. package/lib/language/docCommentParser.js +3 -3
  75. package/lib/language/errorStrategy.js +26 -8
  76. package/lib/language/genericAntlrParser.js +144 -53
  77. package/lib/language/language.g4 +424 -200
  78. package/lib/language/multiLineStringParser.js +536 -0
  79. package/lib/main.d.ts +550 -61
  80. package/lib/main.js +38 -11
  81. package/lib/model/api.js +3 -1
  82. package/lib/model/csnRefs.js +322 -198
  83. package/lib/model/csnUtils.js +226 -370
  84. package/lib/model/enrichCsn.js +124 -69
  85. package/lib/model/revealInternalProperties.js +29 -7
  86. package/lib/model/sortViews.js +10 -2
  87. package/lib/modelCompare/compare.js +17 -12
  88. package/lib/optionProcessor.js +8 -3
  89. package/lib/render/.eslintrc.json +1 -2
  90. package/lib/render/DuplicateChecker.js +1 -1
  91. package/lib/render/manageConstraints.js +36 -33
  92. package/lib/render/toCdl.js +174 -275
  93. package/lib/render/toHdbcds.js +203 -122
  94. package/lib/render/toRename.js +7 -10
  95. package/lib/render/toSql.js +161 -82
  96. package/lib/render/utils/common.js +22 -8
  97. package/lib/render/utils/sql.js +10 -7
  98. package/lib/render/utils/stringEscapes.js +111 -0
  99. package/lib/sql-identifier.js +1 -1
  100. package/lib/transform/.eslintrc.json +5 -0
  101. package/lib/transform/braceExpression.js +4 -2
  102. package/lib/transform/db/.eslintrc.json +2 -0
  103. package/lib/transform/db/applyTransformations.js +212 -0
  104. package/lib/transform/db/assertUnique.js +1 -1
  105. package/lib/transform/db/associations.js +187 -0
  106. package/lib/transform/db/cdsPersistence.js +150 -0
  107. package/lib/transform/db/constraints.js +61 -56
  108. package/lib/transform/db/expansion.js +50 -29
  109. package/lib/transform/db/flattening.js +556 -106
  110. package/lib/transform/db/groupByOrderBy.js +3 -1
  111. package/lib/transform/db/temporal.js +236 -0
  112. package/lib/transform/db/transformExists.js +103 -28
  113. package/lib/transform/db/views.js +92 -44
  114. package/lib/transform/draft/.eslintrc.json +38 -0
  115. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  116. package/lib/transform/draft/odata.js +227 -0
  117. package/lib/transform/forHanaNew.js +98 -783
  118. package/lib/transform/forOdataNew.js +22 -175
  119. package/lib/transform/localized.js +36 -32
  120. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  121. package/lib/transform/odata/referenceFlattener.js +95 -89
  122. package/lib/transform/odata/structureFlattener.js +1 -1
  123. package/lib/transform/odata/toFinalBaseType.js +86 -12
  124. package/lib/transform/odata/typesExposure.js +5 -5
  125. package/lib/transform/odata/utils.js +2 -2
  126. package/lib/transform/transformUtilsNew.js +47 -33
  127. package/lib/transform/translateAssocsToJoins.js +13 -30
  128. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  129. package/lib/transform/universalCsn/coreComputed.js +170 -0
  130. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  131. package/lib/transform/universalCsn/utils.js +63 -0
  132. package/lib/utils/file.js +8 -3
  133. package/lib/utils/objectUtils.js +30 -0
  134. package/lib/utils/timetrace.js +8 -2
  135. package/package.json +1 -1
  136. package/share/messages/README.md +26 -0
  137. package/lib/compiler/definer.js +0 -2349
  138. package/lib/compiler/resolver.js +0 -2922
  139. package/lib/transform/db/helpers.js +0 -58
  140. 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 wether the given function is to be rendered without parentheses
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, artifactName) {
209
- let possibleGap = artifactName;
207
+ function addPossibleGaps(possibleGaps, gapArtifactName) {
210
208
  for (const gap of possibleGaps) {
211
- possibleGap += `.${gap}`;
212
- if (!csn.definitions[possibleGap]) {
213
- const contextName = possibleGap;
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 wether the given artifact or element has a comment that needs to be rendered.
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
  };
@@ -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 Wether to uppercase the identifier
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}${constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : ''}\n`;
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}${constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : ''}\n`;
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 Error(`Not expecting ${options.toSql.names} names for 'sqlite' dialect`);
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 Error(`No matching rendering found for naming mode ${options.toSql.names}`);
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. `{ '"': '&quot;' }`
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
+ };
@@ -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 adoptations to HANA CDS and XS (classic) restrictions.
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)
@@ -0,0 +1,5 @@
1
+ {
2
+ "rules": {
3
+ "no-shadow": "off"
4
+ }
5
+ }
@@ -1,8 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  function isAlreadyBraced(expression, start, end){
4
- const isBraced = start - 1 > -1 && end + 1 < expression.length && expression[start-1] === '(' && expression[end+1] === ')';
5
- return isBraced;
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 ([ '$self', '$projection' ].includes(head))
161
+ if (head === '$self' || head === '$projection')
162
162
  ref = tail;
163
163
  return {
164
164
  ref: ref.map((ps) => {