@sap/cds-compiler 2.12.0 → 2.15.2

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 (128) hide show
  1. package/CHANGELOG.md +221 -15
  2. package/bin/cdsc.js +125 -50
  3. package/bin/cdsse.js +2 -2
  4. package/doc/CHANGELOG_BETA.md +13 -6
  5. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  6. package/doc/NameResolution.md +21 -16
  7. package/lib/api/main.js +47 -84
  8. package/lib/api/options.js +5 -6
  9. package/lib/api/validate.js +6 -11
  10. package/lib/backends.js +15 -23
  11. package/lib/base/dictionaries.js +0 -8
  12. package/lib/base/error.js +26 -0
  13. package/lib/base/keywords.js +7 -17
  14. package/lib/base/location.js +9 -4
  15. package/lib/base/message-registry.js +114 -18
  16. package/lib/base/messages.js +101 -90
  17. package/lib/base/model.js +2 -63
  18. package/lib/base/optionProcessorHelper.js +177 -123
  19. package/lib/checks/annotationsOData.js +12 -33
  20. package/lib/checks/arrayOfs.js +1 -34
  21. package/lib/checks/cdsPersistence.js +2 -1
  22. package/lib/checks/enricher.js +17 -1
  23. package/lib/checks/invalidTarget.js +3 -1
  24. package/lib/checks/managedWithoutKeys.js +3 -1
  25. package/lib/checks/selectItems.js +4 -4
  26. package/lib/checks/sql-snippets.js +27 -26
  27. package/lib/checks/types.js +1 -1
  28. package/lib/checks/validator.js +6 -11
  29. package/lib/compiler/assert-consistency.js +6 -3
  30. package/lib/compiler/base.js +1 -0
  31. package/lib/compiler/builtins.js +19 -6
  32. package/lib/compiler/checks.js +23 -60
  33. package/lib/compiler/cycle-detector.js +1 -1
  34. package/lib/compiler/define.js +1151 -0
  35. package/lib/compiler/extend.js +1000 -0
  36. package/lib/compiler/finalize-parse-cdl.js +237 -0
  37. package/lib/compiler/index.js +107 -39
  38. package/lib/compiler/kick-start.js +190 -0
  39. package/lib/compiler/moduleLayers.js +4 -4
  40. package/lib/compiler/populate.js +1227 -0
  41. package/lib/compiler/propagator.js +114 -46
  42. package/lib/compiler/resolve.js +1521 -0
  43. package/lib/compiler/shared.js +126 -65
  44. package/lib/compiler/tweak-assocs.js +535 -0
  45. package/lib/compiler/utils.js +197 -33
  46. package/lib/edm/.eslintrc.json +5 -0
  47. package/lib/edm/annotations/genericTranslation.js +38 -24
  48. package/lib/edm/annotations/preprocessAnnotations.js +2 -2
  49. package/lib/edm/csn2edm.js +219 -100
  50. package/lib/edm/edm.js +302 -230
  51. package/lib/edm/edmPreprocessor.js +554 -419
  52. package/lib/edm/edmUtils.js +138 -44
  53. package/lib/gen/Dictionary.json +100 -19
  54. package/lib/gen/language.checksum +1 -1
  55. package/lib/gen/language.interp +11 -1
  56. package/lib/gen/language.tokens +86 -83
  57. package/lib/gen/languageLexer.interp +10 -1
  58. package/lib/gen/languageLexer.js +860 -833
  59. package/lib/gen/languageLexer.tokens +78 -75
  60. package/lib/gen/languageParser.js +5765 -4480
  61. package/lib/json/csnVersion.js +10 -11
  62. package/lib/json/from-csn.js +15 -3
  63. package/lib/json/to-csn.js +126 -68
  64. package/lib/language/docCommentParser.js +4 -4
  65. package/lib/language/genericAntlrParser.js +123 -5
  66. package/lib/language/language.g4 +355 -156
  67. package/lib/language/multiLineStringParser.js +5 -5
  68. package/lib/main.d.ts +486 -59
  69. package/lib/main.js +41 -9
  70. package/lib/model/api.js +3 -1
  71. package/lib/model/csnRefs.js +252 -156
  72. package/lib/model/csnUtils.js +384 -297
  73. package/lib/model/enrichCsn.js +71 -29
  74. package/lib/model/revealInternalProperties.js +29 -8
  75. package/lib/model/sortViews.js +2 -1
  76. package/lib/modelCompare/compare.js +23 -18
  77. package/lib/optionProcessor.js +63 -26
  78. package/lib/render/manageConstraints.js +35 -32
  79. package/lib/render/toCdl.js +897 -947
  80. package/lib/render/toHdbcds.js +205 -257
  81. package/lib/render/toSql.js +264 -225
  82. package/lib/render/utils/common.js +136 -25
  83. package/lib/render/utils/sql.js +4 -3
  84. package/lib/render/utils/stringEscapes.js +111 -0
  85. package/lib/sql-identifier.js +1 -1
  86. package/lib/transform/.eslintrc.json +5 -0
  87. package/lib/transform/db/.eslintrc.json +3 -1
  88. package/lib/transform/db/applyTransformations.js +35 -12
  89. package/lib/transform/db/assertUnique.js +1 -1
  90. package/lib/transform/db/associations.js +104 -306
  91. package/lib/transform/db/cdsPersistence.js +2 -2
  92. package/lib/transform/db/constraints.js +58 -53
  93. package/lib/transform/db/expansion.js +60 -33
  94. package/lib/transform/db/flattening.js +582 -104
  95. package/lib/transform/db/groupByOrderBy.js +3 -1
  96. package/lib/transform/db/transformExists.js +66 -13
  97. package/lib/transform/db/views.js +11 -7
  98. package/lib/transform/draft/.eslintrc.json +38 -0
  99. package/lib/transform/{db/draft.js → draft/db.js} +6 -5
  100. package/lib/transform/draft/odata.js +227 -0
  101. package/lib/transform/forHanaNew.js +109 -208
  102. package/lib/transform/forOdataNew.js +59 -212
  103. package/lib/transform/localized.js +46 -26
  104. package/lib/transform/odata/toFinalBaseType.js +85 -11
  105. package/lib/transform/odata/typesExposure.js +147 -199
  106. package/lib/transform/odata/utils.js +2 -2
  107. package/lib/transform/transformUtilsNew.js +44 -33
  108. package/lib/transform/translateAssocsToJoins.js +3 -20
  109. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  110. package/lib/transform/universalCsn/coreComputed.js +172 -0
  111. package/lib/transform/universalCsn/universalCsnEnricher.js +737 -0
  112. package/lib/transform/universalCsn/utils.js +63 -0
  113. package/lib/utils/moduleResolve.js +13 -6
  114. package/lib/utils/objectUtils.js +30 -0
  115. package/package.json +1 -1
  116. package/share/messages/README.md +26 -0
  117. package/share/messages/message-explanations.json +2 -1
  118. package/share/messages/syntax-expected-integer.md +37 -0
  119. package/lib/compiler/definer.js +0 -2361
  120. package/lib/compiler/resolver.js +0 -3079
  121. package/lib/transform/odata/attachPath.js +0 -96
  122. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  123. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  124. package/lib/transform/odata/referenceFlattener.js +0 -290
  125. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  126. package/lib/transform/odata/structuralPath.js +0 -72
  127. package/lib/transform/odata/structureFlattener.js +0 -171
  128. package/lib/transform/universalCsnEnricher.js +0 -237
@@ -2,6 +2,8 @@
2
2
 
3
3
  'use strict';
4
4
 
5
+ const { ModelError } = require('../../base/error');
6
+
5
7
  const functionsWithoutParams = {
6
8
  hana: {
7
9
  CURRENT_CONNECTION: {},
@@ -19,8 +21,6 @@ const {
19
21
  } = require('../../model/csnUtils');
20
22
 
21
23
  const { implicitAs } = require('../../model/csnRefs');
22
- const { isBetaEnabled } = require('../../base/model');
23
-
24
24
 
25
25
  /**
26
26
  * Render the given function
@@ -38,7 +38,7 @@ function renderFunc( funcName, node, dialect, renderArgs) {
38
38
  }
39
39
 
40
40
  /**
41
- * Checks wether the given function is to be rendered without parentheses
41
+ * Checks whether the given function is to be rendered without parentheses
42
42
  *
43
43
  * @param {object} node Content of the function
44
44
  * @param {string} dialect One of 'hana', 'cap' or 'sqlite' - only 'hana' is relevant atm
@@ -282,12 +282,12 @@ const cdsToSqlTypes = {
282
282
  };
283
283
 
284
284
  /**
285
- * Get the element matching the column
286
- *
287
- * @param {CSN.Elements} elements Elements of a query
288
- * @param {CSN.Column} column Column from the same query
289
- * @returns {CSN.Element}
290
- */
285
+ * Get the element matching the column
286
+ *
287
+ * @param {CSN.Elements} elements Elements of a query
288
+ * @param {CSN.Column} column Column from the same query
289
+ * @returns {CSN.Element}
290
+ */
291
291
  function findElement(elements, column) {
292
292
  if (!elements)
293
293
  return undefined;
@@ -332,7 +332,7 @@ function addIntermediateContexts(csn, killList) {
332
332
  }
333
333
 
334
334
  /**
335
- * Check wether the given artifact or element has a comment that needs to be rendered.
335
+ * Check whether the given artifact or element has a comment that needs to be rendered.
336
336
  * Things annotated with @cds.persistence.journal (for HANA SQL), should not get a comment.
337
337
  *
338
338
  * @param {CSN.Artifact} obj
@@ -343,16 +343,17 @@ function hasHanaComment(obj, options) {
343
343
  return !options.disableHanaComments && typeof obj.doc === 'string';
344
344
  }
345
345
  /**
346
- * Return the comment of the given artifact or element.
347
- * Uses the first block (everything up to the first empty line (double \n)).
348
- * Remove leading/trailing whitespace.
349
- *
350
- * @param {CSN.Artifact|CSN.Element} obj
351
- * @returns {string}
352
- * @todo Warning/info to user?
353
- */
346
+ * Return the comment of the given artifact or element.
347
+ * Uses the first block (everything up to the first empty line (double \n)).
348
+ * Remove leading/trailing whitespace.
349
+ * Does not escape any characters.
350
+ *
351
+ * @param {CSN.Artifact|CSN.Element} obj
352
+ * @returns {string}
353
+ * @todo Warning/info to user?
354
+ */
354
355
  function getHanaComment(obj) {
355
- return obj.doc.split('\n\n')[0].trim().replace(/'/g, "''");
356
+ return obj.doc.split('\n\n')[0].trim();
356
357
  }
357
358
 
358
359
  /**
@@ -364,13 +365,122 @@ function getHanaComment(obj) {
364
365
  * @returns {object} object with .front and .back
365
366
  */
366
367
  function getSqlSnippets(options, obj) {
367
- if (isBetaEnabled(options, 'sqlSnippets')) {
368
- const front = obj['@sql.prepend'] ? `${obj['@sql.prepend']} ` : '';
369
- const back = obj['@sql.append'] ? ` ${obj['@sql.append']}` : '';
368
+ const front = obj['@sql.prepend'] ? `${obj['@sql.prepend']} ` : '';
369
+ const back = obj['@sql.append'] ? ` ${obj['@sql.append']}` : '';
370
370
 
371
- return { front, back };
372
- }
373
- return { front: '', back: '' };
371
+ return { front, back };
372
+ }
373
+
374
+ /**
375
+ * A function used to render a certain part of an expression object
376
+ *
377
+ * @callback renderPart
378
+ * @param {object||array} expression
379
+ * @param {CdlRenderEnvironment} env
380
+ * @this {{inline: Boolean, nestedExpr: Boolean}}
381
+ * @returns {string}
382
+ */
383
+
384
+ /**
385
+ * The object containing the concrete rendering functions for the different parts
386
+ * of an expression
387
+ *
388
+ * @typedef {object} ExpressionConfiguration
389
+ * @property {(x: any) => string} finalize The final function to call on the expression(-string) before returning
390
+ * @property {renderPart} explicitTypeCast
391
+ * @property {renderPart} val
392
+ * @property {renderPart} enum
393
+ * @property {renderPart} ref
394
+ * @property {renderPart} aliasOnly
395
+ * @property {renderPart} windowFunction
396
+ * @property {renderPart} func
397
+ * @property {renderPart} xpr
398
+ * @property {renderPart} SELECT
399
+ * @property {renderPart} SET
400
+ */
401
+
402
+ /**
403
+ * Render an expression (including paths and values) or condition 'x'.
404
+ * (no trailing LF, don't indent if inline)
405
+ *
406
+ * @param {ExpressionConfiguration} renderer
407
+ * @returns {Function} Rendered expression
408
+ */
409
+ function getExpressionRenderer(renderer) {
410
+ /**
411
+ * Render an expression (including paths and values) or condition 'x'.
412
+ * (no trailing LF, don't indent if inline)
413
+ *
414
+ * @todo Reuse this with toCdl
415
+ * @param {Array|object|string} expr Expression to render
416
+ * @param {object} env Render environment
417
+ * @param {boolean} [inline=true] Whether to render the expression inline
418
+ * @param {boolean} [nestedExpr=false] Whether to treat the expression as nested
419
+ * @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `nestedExpr === false`.
420
+ * Note: This is a hack for casts() inside groupBy.
421
+ * @returns {string} Rendered expression
422
+ */
423
+ return function renderExpr(expr, env, inline = true, nestedExpr = false, alwaysRenderCast = false) {
424
+ // Compound expression
425
+ if (Array.isArray(expr)) {
426
+ const tokens = expr.map(item => renderExpr(item, env, inline, nestedExpr));
427
+ return beautifyExprArray(tokens);
428
+ }
429
+ else if (typeof expr === 'object' && expr !== null) {
430
+ if ((nestedExpr || alwaysRenderCast) && expr.cast && expr.cast.type && !expr.cast.target)
431
+ return renderer.explicitTypeCast.call({ inline, nestedExpr }, expr, env);
432
+ return renderExprObject(expr);
433
+ }
434
+ // Not a literal value but part of an operator, function etc - just leave as it is
435
+ // FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
436
+ return renderer.finalize.call({ inline, nestedExpr }, expr, env);
437
+
438
+
439
+ /**
440
+ * Various special cases represented as objects
441
+ *
442
+ * @param {object} x Expression
443
+ * @returns {string} String representation of the expression
444
+ */
445
+ function renderExprObject(x) {
446
+ if (x.list) { // TODO: Does this still exist?
447
+ return `(${x.list.map(item => renderExpr(item, env, inline, false)).join(', ')})`;
448
+ }
449
+ else if (x.val !== undefined) {
450
+ return renderer.val.call({ inline, nestedExpr }, x, env);
451
+ }
452
+ // Enum symbol
453
+ else if (x['#']) {
454
+ return renderer.enum.call({ inline, nestedExpr }, x, env);
455
+ }
456
+ // Reference: Array of path steps, possibly preceded by ':'
457
+ else if (x.ref) {
458
+ return renderer.ref.call({ inline, nestedExpr }, x, env);
459
+ }
460
+ // Function call, possibly with args (use '=>' for named args)
461
+ else if (x.func) {
462
+ if (x.xpr)
463
+ return renderer.windowFunction.call({ inline, nestedExpr }, x, env);
464
+ return renderer.func.call({ inline, nestedExpr }, x, env);
465
+ }
466
+ // Nested expression
467
+ else if (x.xpr) {
468
+ return renderer.xpr.call({ inline, nestedExpr }, x, env);
469
+ }
470
+ // Sub-select
471
+ else if (x.SELECT) {
472
+ return renderer.SELECT.call({ inline, nestedExpr }, x, env);
473
+ }
474
+ else if (x.SET) {
475
+ return renderer.SET.call({ inline, nestedExpr }, x, env);
476
+ }
477
+ else if (x.as && x.cast && x.cast.type && x.cast.target) {
478
+ return renderer.aliasOnly.call({ inline, nestedExpr }, x, env);
479
+ }
480
+
481
+ throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
482
+ }
483
+ };
374
484
  }
375
485
 
376
486
  /**
@@ -391,6 +501,7 @@ function getSqlSnippets(options, obj) {
391
501
 
392
502
  module.exports = {
393
503
  renderFunc,
504
+ getExpressionRenderer,
394
505
  beautifyExprArray,
395
506
  getNamespace,
396
507
  getRealName,
@@ -4,13 +4,14 @@
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
17
  * @param {boolean} [alterConstraint=false] whether the constraint should be rendered as part of an ALTER TABLE statement
@@ -121,7 +122,7 @@ function getIdentifierUtils(options) {
121
122
  function prepareIdentifier(name) {
122
123
  // Sanity check
123
124
  if (options.toSql.dialect === 'sqlite' && options.toSql.names !== 'plain')
124
- 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`);
125
126
 
126
127
 
127
128
  switch (options.toSql.names) {
@@ -132,7 +133,7 @@ function getIdentifierUtils(options) {
132
133
  case 'hdbcds':
133
134
  return name;
134
135
  default:
135
- 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}`);
136
137
  }
137
138
  }
138
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
+ }
@@ -8,11 +8,13 @@
8
8
  "prefer-template": "error",
9
9
  "no-trailing-spaces": "error",
10
10
  "template-curly-spacing":["error", "never"],
11
- "complexity": ["warn", 30],
11
+ "complexity": ["warn", 40],
12
12
  "max-len": "off",
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
@@ -18,12 +18,11 @@ const { setProp } = require('../../base/model');
18
18
  * @param {string} prop The property of parent to start at
19
19
  * @param {object} customTransformers Map of prop to transform and function to apply
20
20
  * @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
21
- * @param {boolean} [skipIgnore=true] Wether to skip _ignore elements or not
22
- * @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts, drillRef: boolean - whether to drill into infix/args
21
+ * @param {applyTransformationsOptions} [options={}]
23
22
  * @param {CSN.Path} path Path to parent
24
23
  * @returns {object} parent with transformations applied
25
24
  */
26
- function applyTransformationsInternal(parent, prop, customTransformers, artifactTransformers, skipIgnore, options, path = []) {
25
+ function applyTransformationsInternal(parent, prop, customTransformers, artifactTransformers, options, path = []) {
27
26
  const transformers = {
28
27
  elements: dictionary,
29
28
  definitions: dictionary,
@@ -32,6 +31,7 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
32
31
  enum: dictionary,
33
32
  mixin: dictionary,
34
33
  ref: pathRef,
34
+ $origin: () => {}, // no-op
35
35
  };
36
36
 
37
37
  const csnPath = [ ...path ];
@@ -50,7 +50,12 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
50
50
  * @param {object} node The value of node[_prop]
51
51
  */
52
52
  function standard( _parent, _prop, node ) {
53
- if (!node || typeof node !== 'object' || !{}.propertyIsEnumerable.call( _parent, _prop ) || (typeof _prop === 'string' && _prop.startsWith('@')) || (skipIgnore && node._ignore))
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
+ )
54
59
  return;
55
60
 
56
61
  csnPath.push( _prop );
@@ -80,7 +85,7 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
80
85
  */
81
86
  function dictionary( node, _prop, dict ) {
82
87
  // Allow skipping dicts like actions in forHanaNew
83
- if (options.skipDict && options.skipDict[_prop])
88
+ if (options.skipDict && options.skipDict[_prop] || dict === null || dict === undefined) // with universal CSN, dicts might be null
84
89
  return;
85
90
  csnPath.push( _prop );
86
91
  for (const name of Object.getOwnPropertyNames( dict ))
@@ -101,7 +106,10 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
101
106
  function definitions( node, _prop, dict ) {
102
107
  csnPath.push( _prop );
103
108
  for (const name of Object.getOwnPropertyNames( dict )) {
104
- const skip = options && options.skipArtifact && options.skipArtifact(dict[name], name) || false;
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;
105
113
  if (!skip) {
106
114
  artifactTransformers.forEach(fn => fn(dict, name, dict[name]));
107
115
  standard( dict, name, dict[name] );
@@ -152,13 +160,15 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
152
160
  * @param {object} csn CSN to enrich in-place
153
161
  * @param {object} customTransformers Map of _prop to transform and function to apply
154
162
  * @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
155
- * @param {boolean} [skipIgnore=true] Wether to skip _ignore elements or not
156
- * @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts, drillRef: boolean - whether to drill into infix/args
163
+ * @param {applyTransformationsOptions} [options={}]
157
164
  * @returns {object} CSN with transformations applied
158
165
  */
159
- function applyTransformations( csn, customTransformers = {}, artifactTransformers = [], skipIgnore = true, options = {} ) {
166
+ function applyTransformations( csn, customTransformers = {}, artifactTransformers = [], options = { } ) {
167
+ if (options.skipIgnore === undefined)
168
+ options.skipIgnore = true;
169
+
160
170
  if (csn && csn.definitions)
161
- return applyTransformationsInternal(csn, 'definitions', customTransformers, artifactTransformers, skipIgnore, options);
171
+ return applyTransformationsInternal(csn, 'definitions', customTransformers, artifactTransformers, options);
162
172
  return csn;
163
173
  }
164
174
 
@@ -176,14 +186,27 @@ function applyTransformations( csn, customTransformers = {}, artifactTransformer
176
186
  * @param {object} parent The "parent" of which we transform a property of
177
187
  * @param {string} prop The property of parent to start at
178
188
  * @param {object} customTransformers Map of prop to transform and function to apply
189
+ * @param {applyTransformationsOptions} [options={}]
179
190
  * @param {CSN.Path} path Path pointing to parent
180
191
  * @returns {object} parent[prop] with transformations applied
181
192
  */
182
- function applyTransformationsOnNonDictionary(parent, prop, customTransformers = {}, path = []) {
183
- return applyTransformationsInternal(parent, prop, customTransformers, [], true, {}, path)[prop];
193
+ function applyTransformationsOnNonDictionary(parent, prop, customTransformers = {}, options = {}, path = []) {
194
+ return applyTransformationsInternal(parent, prop, customTransformers, [], options, path)[prop];
184
195
  }
185
196
 
186
197
  module.exports = {
187
198
  applyTransformations,
188
199
  applyTransformationsOnNonDictionary,
189
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) => {