@sap/cds-compiler 3.5.2 → 3.6.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.
Files changed (85) hide show
  1. package/CHANGELOG.md +63 -1
  2. package/bin/cdsc.js +14 -6
  3. package/doc/CHANGELOG_ARCHIVE.md +10 -10
  4. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  5. package/lib/api/main.js +32 -55
  6. package/lib/api/options.js +1 -0
  7. package/lib/api/validate.js +5 -0
  8. package/lib/base/message-registry.js +104 -32
  9. package/lib/base/messages.js +277 -212
  10. package/lib/base/model.js +33 -22
  11. package/lib/base/optionProcessorHelper.js +9 -2
  12. package/lib/base/shuffle.js +50 -0
  13. package/lib/checks/actionsFunctions.js +37 -20
  14. package/lib/checks/foreignKeys.js +13 -6
  15. package/lib/checks/nonexpandableStructured.js +1 -2
  16. package/lib/checks/onConditions.js +21 -19
  17. package/lib/checks/parameters.js +1 -1
  18. package/lib/checks/queryNoDbArtifacts.js +2 -0
  19. package/lib/checks/types.js +16 -22
  20. package/lib/compiler/assert-consistency.js +31 -28
  21. package/lib/compiler/builtins.js +20 -4
  22. package/lib/compiler/checks.js +72 -63
  23. package/lib/compiler/define.js +396 -314
  24. package/lib/compiler/extend.js +55 -49
  25. package/lib/compiler/index.js +5 -0
  26. package/lib/compiler/populate.js +28 -11
  27. package/lib/compiler/propagator.js +2 -1
  28. package/lib/compiler/resolve.js +29 -20
  29. package/lib/compiler/shared.js +15 -10
  30. package/lib/compiler/utils.js +7 -7
  31. package/lib/edm/annotations/genericTranslation.js +51 -46
  32. package/lib/edm/annotations/preprocessAnnotations.js +39 -42
  33. package/lib/edm/csn2edm.js +69 -21
  34. package/lib/edm/edm.js +2 -2
  35. package/lib/edm/edmInboundChecks.js +6 -8
  36. package/lib/edm/edmPreprocessor.js +88 -80
  37. package/lib/edm/edmUtils.js +6 -15
  38. package/lib/gen/Dictionary.json +81 -13
  39. package/lib/gen/language.checksum +1 -1
  40. package/lib/gen/language.interp +2 -1
  41. package/lib/gen/languageParser.js +4680 -4484
  42. package/lib/inspect/inspectModelStatistics.js +2 -1
  43. package/lib/inspect/inspectPropagation.js +2 -1
  44. package/lib/json/from-csn.js +131 -78
  45. package/lib/json/to-csn.js +39 -23
  46. package/lib/language/antlrParser.js +0 -3
  47. package/lib/language/docCommentParser.js +7 -3
  48. package/lib/language/errorStrategy.js +3 -2
  49. package/lib/language/genericAntlrParser.js +96 -41
  50. package/lib/language/language.g4 +112 -128
  51. package/lib/language/multiLineStringParser.js +2 -1
  52. package/lib/main.d.ts +115 -2
  53. package/lib/main.js +16 -3
  54. package/lib/model/csnRefs.js +3 -3
  55. package/lib/model/csnUtils.js +109 -179
  56. package/lib/model/enrichCsn.js +13 -8
  57. package/lib/model/revealInternalProperties.js +4 -3
  58. package/lib/optionProcessor.js +23 -3
  59. package/lib/render/manageConstraints.js +11 -15
  60. package/lib/render/toCdl.js +144 -47
  61. package/lib/render/toHdbcds.js +22 -22
  62. package/lib/render/toRename.js +3 -4
  63. package/lib/render/toSql.js +29 -20
  64. package/lib/render/utils/delta.js +3 -1
  65. package/lib/render/utils/sql.js +3 -16
  66. package/lib/transform/db/associations.js +6 -6
  67. package/lib/transform/db/cdsPersistence.js +3 -3
  68. package/lib/transform/db/constraints.js +8 -8
  69. package/lib/transform/db/expansion.js +4 -4
  70. package/lib/transform/db/flattening.js +12 -15
  71. package/lib/transform/db/temporal.js +4 -3
  72. package/lib/transform/db/transformExists.js +2 -1
  73. package/lib/transform/draft/db.js +7 -7
  74. package/lib/transform/forOdataNew.js +15 -4
  75. package/lib/transform/forRelationalDB.js +53 -39
  76. package/lib/transform/odata/toFinalBaseType.js +106 -82
  77. package/lib/transform/odata/typesExposure.js +26 -17
  78. package/lib/transform/odata/utils.js +1 -1
  79. package/lib/transform/parseExpr.js +1 -1
  80. package/lib/transform/transformUtilsNew.js +33 -10
  81. package/lib/transform/translateAssocsToJoins.js +8 -7
  82. package/lib/transform/universalCsn/coreComputed.js +7 -5
  83. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -4
  84. package/lib/utils/timetrace.js +2 -2
  85. package/package.json +1 -2
@@ -641,8 +641,15 @@ function createOptionProcessor() {
641
641
  // hard-coded option dependencies (they disappear with command)
642
642
  return result;
643
643
 
644
- // Verify parameter value 'param' against option definition 'opt'. Return an error
645
- // string or false for an accepted param. Use 'prefix' when mentioning the option name.
644
+ /**
645
+ * Verify parameter value 'param' against option definition 'opt'. Return an error
646
+ * string or false for an accepted param. Use 'prefix' when mentioning the option name.
647
+ *
648
+ * @param param
649
+ * @param opt
650
+ * @param prefix
651
+ * @return {string|boolean}
652
+ */
646
653
  function verifyOptionParam( param, opt, prefix ) {
647
654
  if (opt.param) {
648
655
  // Parameter is required for this option
@@ -0,0 +1,50 @@
1
+ // Return shuffle functions using pseudo random functions
2
+
3
+ // By <https://github.com/bryc/code/blob/c97a26ad27a9f9d4f48cd3307fd8ee6f1772d4eb/jshash/PRNGs.md>:
4
+
5
+ // The random seed must be an integer between 0 and 4294967295 (or 1 and 4294967296 = 2**32)
6
+ function shuffleGen( seed ) {
7
+ return (Number.isSafeInteger( seed ) && seed > 0)
8
+ ? { shuffleArray, shuffleDict }
9
+ : { shuffleArray: a => a, shuffleDict: d => d };
10
+
11
+ function random() { // from function mulberry32() in doc above
12
+ seed = seed + 0x6D2B79F5 | 0;
13
+ let t = Math.imul( seed ^ seed >>> 15, 1 | seed );
14
+ t = t + Math.imul( t ^ t >>> 7, 61 | t ) ^ t;
15
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
16
+ }
17
+
18
+ /**
19
+ * Shuffles array in place.
20
+ * https://en.wikipedia.org/wiki/Fisher–Yates_shuffle#The_modern_algorithm
21
+ *
22
+ * @param {Array} array items An array containing the items.
23
+ */
24
+ function shuffleArray( array ) {
25
+ let i = array.length;
26
+ while (i > 1) {
27
+ const j = Math.floor( random() * i );
28
+ --i;
29
+ const temp = array[i];
30
+ array[i] = array[j];
31
+ array[j] = temp;
32
+ }
33
+ return array;
34
+ }
35
+
36
+ /**
37
+ * Return a shuffled version of `dict`.
38
+ */
39
+ function shuffleDict( dict ) {
40
+ if (!dict)
41
+ return dict;
42
+ const names = shuffleArray( Object.keys( dict ) );
43
+ const r = Object.create( null );
44
+ for (const n of names)
45
+ r[n] = dict[n];
46
+ return r;
47
+ }
48
+ }
49
+
50
+ module.exports = shuffleGen;
@@ -27,8 +27,9 @@ function checkActionOrFunction( art, artName, prop, path ) {
27
27
  if (art.kind === 'entity') {
28
28
  for (const [ actName, act ] of Object.entries(art.actions)) {
29
29
  if (act.params) {
30
+ checkExplicitBindingParameter.call(this, act.params, path.concat([ 'actions', actName, 'params' ]));
30
31
  for (const [ paramName, param ] of Object.entries(act.params))
31
- checkActionOrFunctionParameter.bind(this)(param, path.concat([ 'actions', actName, 'params', paramName ]), act.kind);
32
+ checkActionOrFunctionParameter.call(this, param, path.concat([ 'actions', actName, 'params', paramName ]), act.kind);
32
33
  }
33
34
  if (act.returns)
34
35
  checkReturns.bind(this)(act.returns, path.concat([ 'actions', actName, 'returns' ]), act.kind);
@@ -37,12 +38,27 @@ function checkActionOrFunction( art, artName, prop, path ) {
37
38
  else {
38
39
  if (art.params) {
39
40
  for (const [ paramName, param ] of Object.entries(art.params))
40
- checkActionOrFunctionParameter.bind(this)(param, path.concat([ 'params', paramName ]), art.kind);
41
+ checkActionOrFunctionParameter.call(this, param, path.concat([ 'params', paramName ]), art.kind);
41
42
  }
42
43
  if (art.returns)
43
44
  checkReturns.bind(this)(art.returns, path.concat('returns'), art.kind);
44
45
  }
45
46
 
47
+ /**
48
+ *
49
+ * @param {object} params parameter dictionary
50
+ * @param {CSN.Path} currPath to the action parameter
51
+ */
52
+ function checkExplicitBindingParameter( params, currPath ) {
53
+ Object.entries(params).forEach(([ pn, p ], i) => {
54
+ const type = p.items?.type || p.type;
55
+ if (type === '$self' && !this.csn.definitions.$self && i > 0) {
56
+ this.error(null, currPath.concat(pn),
57
+ 'Binding parameter is expected to appear on first position only');
58
+ }
59
+ });
60
+ }
61
+
46
62
  /**
47
63
  * Check the parameters of an action
48
64
  *
@@ -51,28 +67,29 @@ function checkActionOrFunction( art, artName, prop, path ) {
51
67
  * @param {string} actKind 'action' or 'function'
52
68
  */
53
69
  function checkActionOrFunctionParameter( param, currPath, actKind ) {
54
- const paramType = param.type ? this.csnUtils.getFinalTypeDef(param.type) : param;
55
-
56
- if (!isBetaEnabled(this.options, 'optionalActionFunctionParameters') && (param.default || paramType.default)) {
57
- this.message('param-default', currPath, { '#': actKind },
58
- {
59
- std: 'Artifact parameters can\'t have a default value', // Not used
60
- action: 'Action parameters can\'t have a default value',
61
- function: 'Function parameters can\'t have a default value',
62
- });
70
+ const paramType = param.type ? this.csnUtils.getFinalBaseTypeWithProps(param.type) : param;
71
+ if (!paramType)
72
+ return; // no type could be resolved
73
+
74
+ // "default" is always propagated to params
75
+ if (param.default && !isBetaEnabled(this.options, 'optionalActionFunctionParameters')) {
76
+ this.message('param-default', currPath, { '#': actKind }, {
77
+ std: 'Artifact parameters can\'t have a default value', // Not used
78
+ action: 'Action parameters can\'t have a default value',
79
+ function: 'Function parameters can\'t have a default value',
80
+ });
63
81
  }
64
82
 
65
- if (paramType.type && this.csnUtils.isAssocOrComposition(param.type)) {
66
- this.error(null, currPath, { '#': actKind },
67
- {
68
- std: 'An association is not allowed as this artifact\'s parameter type', // Not used
69
- action: 'An association is not allowed as action\'s parameter type',
70
- function: 'An association is not allowed as function\'s parameter type',
71
- });
83
+ if (param.type && this.csnUtils.isAssocOrComposition(param.type)) {
84
+ this.error(null, currPath, { '#': actKind }, {
85
+ std: 'An association is not allowed as this artifact\'s parameter type', // Not used
86
+ action: 'An association is not allowed as action\'s parameter type',
87
+ function: 'An association is not allowed as function\'s parameter type',
88
+ });
72
89
  }
73
90
 
74
- if (paramType.items && paramType.items.type)
75
- checkActionOrFunctionParameter.bind(this)(paramType.items, currPath.concat('items'), actKind);
91
+ if (paramType.items?.type)
92
+ checkActionOrFunctionParameter.call(this, paramType.items, currPath.concat('items'), actKind);
76
93
 
77
94
  // check if the structured & user-defined is from the current service
78
95
  checkUserDefinedType.bind(this)(paramType, param.type, currPath);
@@ -11,8 +11,9 @@ const { setProp } = require('../base/model');
11
11
  * - no usage of unmanaged association as foreign keys (also not transitively)
12
12
  *
13
13
  * @param {object} member Member
14
+ * @param {string} memberName Member name
14
15
  */
15
- function validateForeignKeys( member ) {
16
+ function validateForeignKeys( member, memberName ) {
16
17
  // We have a managed association
17
18
  const isManagedAssoc = mem => mem && mem.target && !mem.on;
18
19
  // We have an unmanaged association
@@ -25,7 +26,7 @@ function validateForeignKeys( member ) {
25
26
  if (!key._art)
26
27
  continue;
27
28
  // eslint-disable-next-line no-use-before-define
28
- checkForItems(key._art);
29
+ checkForItemsOrMissingType(key._art, key.ref.join('.'));
29
30
  }
30
31
  }
31
32
  };
@@ -35,13 +36,13 @@ function validateForeignKeys( member ) {
35
36
  for (const elementName of Object.keys(mem.elements)) {
36
37
  const element = mem.elements[elementName];
37
38
  // eslint-disable-next-line no-use-before-define
38
- checkForItems(element);
39
+ checkForItemsOrMissingType(element, elementName);
39
40
  }
40
41
  };
41
42
 
42
43
  // Recursively perform the checks on an element
43
44
  // Declared as arrow-function to keep scope the same (this value)
44
- const checkForItems = (mem) => {
45
+ const checkForItemsOrMissingType = (mem, memName) => {
45
46
  if (mem.items) {
46
47
  this.error(null, member.$path, {}, 'Array-like properties must not be foreign keys');
47
48
  }
@@ -60,14 +61,20 @@ function validateForeignKeys( member ) {
60
61
  : this.csn.definitions[mem.type];
61
62
  if (type && !type.$visited) {
62
63
  setProp(type, '$visited', true);
63
- checkForItems(type);
64
+ checkForItemsOrMissingType(type, memName);
64
65
  delete type.$visited;
65
66
  }
66
67
  }
68
+ else if (mem && !mem.type) {
69
+ const variant = member.type === 'cds.Composition' ? 'managedCompForeignKey' : 'managedAssocForeignKey';
70
+ this.error('check-proper-type-of', member.$path, {
71
+ '#': variant, name: memName, art: memberName,
72
+ });
73
+ }
67
74
  };
68
75
 
69
76
  if (isManagedAssoc(member))
70
- checkForItems(member);
77
+ checkForItemsOrMissingType(member, memberName);
71
78
  }
72
79
 
73
80
  module.exports = validateForeignKeys;
@@ -24,8 +24,7 @@ function nonexpandableStructuredInExpression( parent, name, expression ) {
24
24
  if ((_art?.elements || _art?.keys && (i === 0 || expression[i - 1] !== 'exists')) && !validStructuredElement && $scope !== '$self') { // TODO: Use $self to navigate to struct
25
25
  this.error('ref-unexpected-structured',
26
26
  name === 'on' ? [ ...parent.$path, name, i ] : expression[i].$path,
27
- { elemref: { ref } },
28
- 'Unexpected usage of structured type $(ELEMREF)');
27
+ { '#': 'std', elemref: { ref } } );
29
28
  }
30
29
  }
31
30
  }
@@ -103,28 +103,30 @@ function validateOnCondition( member, memberName, property, path ) {
103
103
  this.error(null, csnPath, { id, elemref }, 'ON-conditions must not contain parameters, step $(ID) of path $(ELEMREF)');
104
104
  }
105
105
  if (_art && $scope !== '$self') {
106
- _art = resolveArtifactType.call(this, _art);
106
+ const type = resolveArtifactType.call(this, _art);
107
+ if (type) {
107
108
  // For error messages
108
- const onPath = path.concat([ 'on', i, 'ref', ref.length - 1 ]);
109
- // Paths of an ON condition may end on a structured element or an association only if:
110
- // 1) Both operands in the expression end on a structured element or on
111
- // a managed association (that are both expandable)
112
- // 2) Path ends on an association (managed or unmanaged) and the other operand is a '$self'
109
+ const onPath = path.concat([ 'on', i, 'ref', ref.length - 1 ]);
110
+ // Paths of an ON condition may end on a structured element or an association only if:
111
+ // 1) Both operands in the expression end on a structured element or on
112
+ // a managed association (that are both expandable)
113
+ // 2) Path ends on an association (managed or unmanaged) and the other operand is a '$self'
113
114
 
114
- // If this path ends structured or on an association, perform the check:
115
- if ((_art.target) &&
116
- !( /* 1) */ (_art.target && _art.keys) && validStructuredElement ||
117
- /* 2) */ (_art.target && validDollarSelf)) &&
118
- !_art.virtual) {
115
+ // If this path ends structured or on an association, perform the check:
116
+ if ((type.target) &&
117
+ !( /* 1) */ (type.target && type.keys) && validStructuredElement ||
118
+ /* 2) */ (type.target && validDollarSelf)) &&
119
+ !type.virtual) {
119
120
  // Do nothing - handled by lib/checks/nonexpandableStructured.js
120
- }
121
- else if (_art.items && !_art.virtual) {
122
- this.error(null, onPath, { elemref: { ref } },
123
- 'ON-conditions can\'t use array-like elements, path $(ELEMREF)');
124
- }
125
- else if (_art.virtual) {
126
- this.error(null, onPath, { elemref: { ref } },
127
- 'Virtual elements can\'t be used in ON-conditions, path $(ELEMREF)');
121
+ }
122
+ else if (type.items && !type.virtual) {
123
+ this.error(null, onPath, { elemref: { ref } },
124
+ 'ON-conditions can\'t use array-like elements, path $(ELEMREF)');
125
+ }
126
+ else if (type.virtual) {
127
+ this.error(null, onPath, { elemref: { ref } },
128
+ 'Virtual elements can\'t be used in ON-conditions, path $(ELEMREF)');
129
+ }
128
130
  }
129
131
  }
130
132
  }
@@ -12,7 +12,7 @@ const { isPersistedOnDatabase } = require('../model/csnUtils.js');
12
12
  */
13
13
  function checkForParams( parent, name, params, path ) {
14
14
  const artifact = this.csn.definitions[path[1]];
15
- if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact) && !(parent.kind !== 'entity')) {
15
+ if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact) && parent.kind === 'entity') {
16
16
  this.error('ref-unexpected-params', [ ...path, 'params' ], { value: this.options.sqlDialect },
17
17
  'Parameterized views can\'t be used with sqlDialect $(VALUE)');
18
18
  }
@@ -25,6 +25,8 @@ function checkQueryForNoDBArtifacts( query ) {
25
25
  */
26
26
  const leafCount = (def) => {
27
27
  let c = 0;
28
+ if (!def)
29
+ return c;
28
30
  if (def.elements) {
29
31
  c += Object.values(def.elements).reduce((acc, e) => {
30
32
  acc += leafCount(e);
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { getUtils, hasAnnotationValue } = require('../model/csnUtils');
3
+ const { hasAnnotationValue } = require('../model/csnUtils');
4
4
 
5
5
  // Only to be used with validator.js - a correct this value needs to be provided!
6
6
 
@@ -46,15 +46,12 @@ function checkTypeIsScalar( member, memberName, prop, path ) {
46
46
  * @param {CSN.Path} path the path to the member
47
47
  */
48
48
  function checkElementTypeDefinitionHasType( member, memberName, prop, path ) {
49
+ if (prop !== 'elements' && prop !== 'params')
50
+ return; // no enum, etc.
49
51
  // Computed elements, e.g. "1+1 as foo" in a view don't have a valid type and
50
- // are skipped here.
51
- // Elements in projections are not tested as well
52
+ // are skipped here. References to such columns are checked further below.
52
53
  const parent = this.csn.definitions[path[1]];
53
- if (parent.projection || parent.query || prop !== 'elements')
54
- return;
55
-
56
- // should only happen with csn input, not in cdl
57
- if (!hasArtifactTypeInformation(member)) {
54
+ if (!parent.projection && !parent.query && !hasArtifactTypeInformation(member)) {
58
55
  errorAboutMissingType(this.error, path, memberName, true);
59
56
  return;
60
57
  }
@@ -63,8 +60,8 @@ function checkElementTypeDefinitionHasType( member, memberName, prop, path ) {
63
60
  if (member.type) {
64
61
  if (member.type.ref) {
65
62
  const isSelfReference = path[1] === member.type.ref[0];
66
- checkTypeOfHasProperType(
67
- member, memberName, this.csn, this.error, path, isSelfReference ? member.type.ref[1] : null
63
+ checkTypeOfHasProperType.call(
64
+ this, member, memberName, this.csn, this.error, path, isSelfReference ? member.type.ref[1] : null
68
65
  );
69
66
  }
70
67
  else if (member._type) {
@@ -77,7 +74,7 @@ function checkElementTypeDefinitionHasType( member, memberName, prop, path ) {
77
74
  // many
78
75
  const { items } = member;
79
76
  if (items)
80
- checkTypeOfHasProperType(items, memberName, this.csn, this.error, path );
77
+ checkTypeOfHasProperType.call(this, items, memberName, this.csn, this.error, path );
81
78
  }
82
79
 
83
80
  /**
@@ -102,14 +99,14 @@ function checkTypeDefinitionHasType( artifact, artifactName, prop, path ) {
102
99
 
103
100
  // Check for `type of`
104
101
  if (artifact.type) {
105
- checkTypeOfHasProperType(artifact, artifactName, this.csn, this.error, path);
102
+ checkTypeOfHasProperType.call(this, artifact, artifactName, this.csn, this.error, path);
106
103
  return;
107
104
  }
108
105
 
109
106
  // many
110
107
  const { items } = artifact;
111
108
  if (items)
112
- checkTypeOfHasProperType(items, artifactName, this.csn, this.error, path );
109
+ checkTypeOfHasProperType.call(this, items, artifactName, this.csn, this.error, path );
113
110
  }
114
111
 
115
112
 
@@ -129,23 +126,20 @@ function checkTypeOfHasProperType( artOrElement, name, model, error, path, deriv
129
126
  if (!artOrElement.type)
130
127
  return;
131
128
 
132
- const { getFinalBaseTypeWithProps } = getUtils(model);
133
- const typeOfType = getFinalBaseTypeWithProps(artOrElement.type);
129
+ const typeOfType = this.csnUtils.getFinalBaseTypeWithProps(artOrElement.type);
134
130
 
135
131
  if (typeOfType === null) {
136
- if (artOrElement.type.ref) {
132
+ if (artOrElement.type.ref && this?.options?.transformation !== 'odata') {
137
133
  const typeOfArt = artOrElement.type.ref[0];
138
- const typeOfElt = artOrElement.type.ref[1];
139
- // TODO: using error() must be consistent to central messages!
140
- error('check-proper-type-of', path, { art: derivedTypeName || typeOfArt, name: typeOfElt, '#': derivedTypeName ? 'derived' : 'std' }, {
141
- std: 'Referred element $(NAME) of $(ART) does not contain proper type information',
142
- derived: 'Referred type of $(ART) does not contain proper type information',
134
+ const typeOfElt = artOrElement.type.ref.slice(1).join('.');
135
+ error('check-proper-type-of', path, {
136
+ art: derivedTypeName || typeOfArt, name: typeOfElt, '#': derivedTypeName ? 'derived' : 'std',
143
137
  });
144
138
  }
145
139
  }
146
140
  else if (typeOfType && typeOfType.items) {
147
141
  derivedTypeName = typeof artOrElement.type === 'string' ? artOrElement.type : artOrElement.type.ref[artOrElement.type.ref.length - 1];
148
- checkTypeOfHasProperType(typeOfType.items, name, model, error, path, derivedTypeName);
142
+ checkTypeOfHasProperType.call(this, typeOfType.items, name, model, error, path, derivedTypeName);
149
143
  }
150
144
  }
151
145
 
@@ -75,6 +75,12 @@ const typeProperties = [
75
75
  '_effectiveType',
76
76
  ];
77
77
 
78
+ class InternalConsistencyError extends Error {
79
+ constructor(msg) {
80
+ super(`cds-compiler XSN consistency: ${ msg }`);
81
+ }
82
+ }
83
+
78
84
  function assertConsistency( model, stage ) {
79
85
  const stageParser = typeof stage === 'object';
80
86
  const options = stageParser && stage || model.options || { testMode: true };
@@ -462,7 +468,7 @@ function assertConsistency( model, stage ) {
462
468
  // preliminary -----------------------------------------------------------
463
469
  doc: {
464
470
  kind: true,
465
- test: locationVal( isStringOrNull ),
471
+ test: TODO,
466
472
  }, // doc comment
467
473
  '@': {
468
474
  kind: true,
@@ -671,25 +677,27 @@ function assertConsistency( model, stage ) {
671
677
  function assertProp( node, parent, prop, extraSpec, noPropertyTest ) {
672
678
  let spec = extraSpec || schema[prop] || schema[prop.charAt(0)];
673
679
  if (!spec)
674
- throw new Error( `Property '${ prop }' has not been specified`);
680
+ throw new InternalConsistencyError( `Property '${ prop }' has not been specified`);
675
681
  spec = inheritSpec( spec );
676
682
 
677
683
  if (!noPropertyTest) {
678
684
  const char = prop.charAt(0);
679
685
  const parser = ('parser' in spec) ? spec.parser : char !== '_' && char !== '$';
680
686
  if (stageParser && !parser)
681
- throw new Error( `Non-parser property '${ prop }' set by ${ model.$frontend || '' } parser${ at( [ node, parent ] ) }` );
687
+ throw new InternalConsistencyError( `Non-parser property '${ prop }' set by ${ model.$frontend || '' } parser${ at( [ node, parent ] ) }` );
682
688
  const enumerable = ('enumerable' in spec) ? spec.enumerable : char !== '_';
683
689
  if (enumerable instanceof Function
684
690
  ? !enumerable( parent, prop )
685
691
  : {}.propertyIsEnumerable.call( parent, prop ) !== enumerable)
686
- throw new Error( `Unexpected enumerability ${ !enumerable }${ at( [ node, parent ], prop ) }` );
692
+ throw new InternalConsistencyError( `Unexpected enumerability ${ !enumerable }${ at( [ node, parent ], prop ) }` );
687
693
  }
688
694
  (spec.test || standard)( node, parent, prop, spec,
689
695
  typeof noPropertyTest === 'string' && noPropertyTest );
690
696
  }
691
697
 
692
698
  function definition( node, parent, prop, spec, name ) {
699
+ if (node.builtin)
700
+ return;
693
701
  if (!Array.isArray( node ))
694
702
  node = [ node ];
695
703
  // TODO: else check that there is a redefinition error
@@ -704,14 +712,14 @@ function assertConsistency( model, stage ) {
704
712
  function builtin( node, parent, prop, spec, name ) {
705
713
  const type = typeof node;
706
714
  if (type !== 'string' && type !== 'boolean')
707
- throw new Error(`Property '${ prop }' must be a boolean or string but was '${ typeof node }'${ at( [ node, parent ], prop, name ) }` );
715
+ throw new InternalConsistencyError(`Property '${ prop }' must be a boolean or string but was '${ typeof node }'${ at( [ node, parent ], prop, name ) }` );
708
716
 
709
717
  if (parent.kind !== 'namespace')
710
- throw new Error(`Property '${ prop }' must be inside artifact that is a namespace but was '${ parent.kind }'${ at( [ node, parent ], prop, name ) }` );
718
+ throw new InternalConsistencyError(`Property '${ prop }' must be inside artifact that is a namespace but was '${ parent.kind }'${ at( [ node, parent ], prop, name ) }` );
711
719
 
712
720
  const parentName = parent.name && parent.name.absolute;
713
721
  if (parentName !== 'cds' && parentName !== 'localized')
714
- throw new Error(`Property '${ prop }' must be inside namespace 'cds' or 'localized' but was '${ parentName }'${ at( [ node, parent ], prop, name ) }` );
722
+ throw new InternalConsistencyError(`Property '${ prop }' must be inside namespace 'cds' or 'localized' but was '${ parentName }'${ at( [ node, parent ], prop, name ) }` );
715
723
  }
716
724
 
717
725
  function column( node, ...rest ) {
@@ -740,7 +748,7 @@ function assertConsistency( model, stage ) {
740
748
  if (!names.includes(p)) {
741
749
  const req = spec.schema && spec.schema[p] && spec.schema[p].isRequired;
742
750
  if ((req || schema[p] && schema[p].isRequired || noSyntaxErrors)( node ))
743
- throw new Error( `Required property '${ p }' missing in object${ at( [ node, parent ], prop, name ) }` );
751
+ throw new InternalConsistencyError( `Required property '${ p }' missing in object${ at( [ node, parent ], prop, name ) }` );
744
752
  }
745
753
  }
746
754
  const optional = spec.optional || [];
@@ -749,7 +757,7 @@ function assertConsistency( model, stage ) {
749
757
  ? optional.includes( n ) || optional.includes( n.charAt(0) )
750
758
  : optional( n, spec );
751
759
  if (!(opt || requires.includes( n ) || n === '$extra'))
752
- throw new Error( `Property '${ n }' is not expected${ at( [ node[n], node, parent ], prop, name ) }` );
760
+ throw new InternalConsistencyError( `Property '${ n }' is not expected${ at( [ node[n], node, parent ], prop, name ) }` );
753
761
 
754
762
  assertProp( node[n], node, n, spec.schema && spec.schema[n] );
755
763
  }
@@ -772,7 +780,7 @@ function assertConsistency( model, stage ) {
772
780
  if (spec[choice])
773
781
  assertProp( node, parent, prop, spec[choice], choice );
774
782
  else
775
- throw new Error( `No specification for computed variant '${ choice }'${ at( [ node, parent ], prop, idx ) }` );
783
+ throw new InternalConsistencyError( `No specification for computed variant '${ choice }'${ at( [ node, parent ], prop, idx ) }` );
776
784
  }
777
785
  }
778
786
 
@@ -787,7 +795,7 @@ function assertConsistency( model, stage ) {
787
795
  if (spec[choice])
788
796
  assertProp( node, parent, prop, spec[choice], choice );
789
797
  else
790
- throw new Error( `No specification for computed variant '${ choice }'${ at( [ node, parent ], prop, idx ) }` );
798
+ throw new InternalConsistencyError( `No specification for computed variant '${ choice }'${ at( [ node, parent ], prop, idx ) }` );
791
799
  }
792
800
  }
793
801
 
@@ -852,7 +860,7 @@ function assertConsistency( model, stage ) {
852
860
  expression( node[n], parent, prop, spec, n );
853
861
  }
854
862
  else {
855
- throw new Error( `Expected array or dictionary${ at( [ null, parent ], prop ) }` );
863
+ throw new InternalConsistencyError( `Expected array or dictionary${ at( [ null, parent ], prop ) }` );
856
864
  }
857
865
  }
858
866
 
@@ -871,7 +879,7 @@ function assertConsistency( model, stage ) {
871
879
  // if (!node || typeof node !== 'object' || Object.getPrototypeOf( node ))
872
880
  // console.log(node,prop,model.$frontend)
873
881
  if (!node || typeof node !== 'object' || Object.getPrototypeOf( node ))
874
- throw new Error( `Expected dictionary${ at( [ null, parent ], prop ) }` );
882
+ throw new InternalConsistencyError( `Expected dictionary${ at( [ null, parent ], prop ) }` );
875
883
  for (const n in node)
876
884
  func( node[n], parent, prop, spec, n );
877
885
  };
@@ -880,7 +888,7 @@ function assertConsistency( model, stage ) {
880
888
  function isArray( func = standard ) {
881
889
  return function vector( node, parent, prop, spec ) {
882
890
  if (!Array.isArray(node))
883
- throw new Error( `Expected array${ at( [ null, parent ], prop ) }` );
891
+ throw new InternalConsistencyError( `Expected array${ at( [ null, parent ], prop ) }` );
884
892
  node.forEach( (item, n) => func( item, parent, prop, spec, n ) );
885
893
  };
886
894
  }
@@ -898,46 +906,41 @@ function assertConsistency( model, stage ) {
898
906
  if ((spec.also) ? spec.also.includes( node ) : (node === null))
899
907
  return;
900
908
  if (typeof node !== 'boolean')
901
- throw new Error( `Expected boolean or null${ at( [ node, parent ], prop ) }` );
909
+ throw new InternalConsistencyError( `Expected boolean or null${ at( [ node, parent ], prop ) }` );
902
910
  }
903
911
 
904
912
  function isNumber( node, parent, prop, spec ) {
905
913
  if (spec.also && spec.also.includes( node ))
906
914
  return;
907
915
  if (typeof node !== 'number')
908
- throw new Error( `Expected number${ at( [ node, parent ], prop ) }` );
909
- }
910
-
911
- function isStringOrNull( node, parent, prop, spec ) {
912
- if (node !== null)
913
- isString(node, parent, prop, spec);
916
+ throw new InternalConsistencyError( `Expected number${ at( [ node, parent ], prop ) }` );
914
917
  }
915
918
 
916
919
  function isOneOf( values ) {
917
920
  return function isOneOfInner( node, parent, prop ) {
918
921
  if (!values.includes(node))
919
- throw new Error( `Unexpected value '${ node }', expected ${ JSON.stringify(values) }${ at( [ node, parent ], prop ) }` );
922
+ throw new InternalConsistencyError( `Unexpected value '${ node }', expected ${ JSON.stringify(values) }${ at( [ node, parent ], prop ) }` );
920
923
  };
921
924
  }
922
925
 
923
926
  function isString( node, parent, prop, spec ) {
924
927
  if (typeof node !== 'string')
925
- throw new Error( `Expected string${ at( [ node, parent ], prop ) }` );
928
+ throw new InternalConsistencyError( `Expected string${ at( [ node, parent ], prop ) }` );
926
929
  // TODO: also check getOwnPropertyNames(node)
927
930
  if (spec.enum && !spec.enum.includes( node ))
928
- throw new Error( `Unexpected value '${ node }'${ at( [ node, parent ], prop ) }` );
931
+ throw new InternalConsistencyError( `Unexpected value '${ node }'${ at( [ node, parent ], prop ) }` );
929
932
  }
930
933
 
931
934
  function isVal( node, parent, prop, spec ) {
932
935
  if (Array.isArray(node))
933
936
  node.forEach( (item, n) => standard( item, parent, prop, spec, n ) );
934
937
  else if (node !== null && ![ 'string', 'number', 'boolean' ].includes( typeof node ))
935
- throw new Error( `Expected array or simple value${ at( [ null, parent ], prop ) }` );
938
+ throw new InternalConsistencyError( `Expected array or simple value${ at( [ null, parent ], prop ) }` );
936
939
  }
937
940
 
938
941
  function isObject( node, parent, prop, spec, name ) {
939
942
  if (!node || typeof node !== 'object' || Object.getPrototypeOf( node ) !== Object.prototype)
940
- throw new Error( `Expected standard object${ at( [ null, parent ], prop, name ) }` );
943
+ throw new InternalConsistencyError( `Expected standard object${ at( [ null, parent ], prop, name ) }` );
941
944
  }
942
945
 
943
946
  function inDefinitions( art, parent, prop, spec, name ) {
@@ -956,7 +959,7 @@ function assertConsistency( model, stage ) {
956
959
  art.name.absolute && art.name.absolute.startsWith('localized.'))
957
960
  standard( art, parent, prop, spec, name );
958
961
  else
959
- throw new Error( `Expected definition${ at( [ art, parent ], prop, name ) }` );
962
+ throw new InternalConsistencyError( `Expected definition${ at( [ art, parent ], prop, name ) }` );
960
963
  }
961
964
  }
962
965
 
@@ -966,7 +969,7 @@ function assertConsistency( model, stage ) {
966
969
  return;
967
970
  const validValues = [ 'typeOf', 'global', 'param' ];
968
971
  if (!validValues.includes(node))
969
- throw new Error( `Property '${ prop }' must be either "${ validValues.join('", "') }" or a number but was "${ node }"` );
972
+ throw new InternalConsistencyError( `Property '${ prop }' must be either "${ validValues.join('", "') }" or a number but was "${ node }"` );
970
973
  }
971
974
 
972
975
  function TODO() { /* no-op */ }
@@ -250,6 +250,22 @@ const quotedLiteralPatterns = {
250
250
  },
251
251
  json_type: 'string',
252
252
  },
253
+ // and only for CSN parser:
254
+ null: {
255
+ json_type: 'null', // modulo JSON typeof weirdness
256
+ },
257
+ boolean: {
258
+ json_type: 'boolean',
259
+ },
260
+ number: {
261
+ test_variant: 'number',
262
+ test_fn: (x => /^[ \t]*[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]\d+)?[ \t]*$/i.test( x )),
263
+ json_type: 'number',
264
+ secondary_json_type: 'string',
265
+ },
266
+ string: {
267
+ json_type: 'string',
268
+ },
253
269
  };
254
270
 
255
271
  /**
@@ -390,14 +406,13 @@ function initBuiltins( model ) {
390
406
  setMagicVariables( magicVariables );
391
407
  // namespace:"cds" stores the builtins ---
392
408
  const cds = createNamespace( 'cds', 'reserved' );
393
- // setProp( model.definitions, 'cds', cds );
394
- model.definitions.cds = cds; // not setProp - oData - TODO: still needed?
409
+ model.definitions.cds = cds;
395
410
  // Also add the core artifacts to model.definitions`
396
411
  model.$builtins = env( core, 'cds.', cds );
397
412
  model.$builtins.cds = cds;
398
413
  // namespace:"cds.hana" stores HANA-specific builtins ---
399
414
  const hana = createNamespace( 'cds.hana', 'reserved' );
400
- setProp( model.definitions, 'cds.hana', hana );
415
+ model.definitions['cds.hana'] = hana;
401
416
  model.$builtins.hana = hana;
402
417
  cds._subArtifacts.hana = hana;
403
418
  env( coreHana, 'cds.hana.', hana );
@@ -430,6 +445,7 @@ function initBuiltins( model ) {
430
445
  const artifacts = Object.create(null);
431
446
  for (const name of Object.keys( builtins )) {
432
447
  const absolute = prefix + name;
448
+ // TODO: reconsider whether to set a type to itself - looks wrong
433
449
  const art = {
434
450
  kind: 'type', builtin: true, name: { absolute }, type: { path: [ { id: absolute } ] },
435
451
  };
@@ -441,7 +457,7 @@ function initBuiltins( model ) {
441
457
  Object.assign( art, builtins[name] );
442
458
  if (!art.internal)
443
459
  artifacts[name] = art;
444
- setProp( model.definitions, absolute, art );
460
+ model.definitions[absolute] = art;
445
461
  }
446
462
  return artifacts;
447
463
  }