@sap/cds-compiler 5.1.2 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/bin/cdsc.js +7 -2
  3. package/bin/cdshi.js +24 -17
  4. package/bin/cdsse.js +17 -18
  5. package/doc/CHANGELOG_BETA.md +9 -4
  6. package/lib/api/main.js +19 -2
  7. package/lib/api/options.js +4 -1
  8. package/lib/api/validate.js +5 -0
  9. package/lib/base/builtins.js +1 -0
  10. package/lib/base/message-registry.js +40 -3
  11. package/lib/base/messages.js +1 -1
  12. package/lib/base/model.js +0 -11
  13. package/lib/checks/actionsFunctions.js +0 -12
  14. package/lib/checks/structuredAnnoExpressions.js +10 -14
  15. package/lib/compiler/assert-consistency.js +21 -13
  16. package/lib/compiler/builtins.js +2 -2
  17. package/lib/compiler/checks.js +25 -6
  18. package/lib/compiler/define.js +27 -31
  19. package/lib/compiler/extend.js +16 -18
  20. package/lib/compiler/generate.js +3 -3
  21. package/lib/compiler/populate.js +22 -16
  22. package/lib/compiler/propagator.js +3 -2
  23. package/lib/compiler/resolve.js +87 -94
  24. package/lib/compiler/shared.js +12 -13
  25. package/lib/compiler/tweak-assocs.js +390 -86
  26. package/lib/compiler/utils.js +41 -33
  27. package/lib/compiler/xpr-rewrite.js +45 -58
  28. package/lib/edm/annotations/genericTranslation.js +17 -13
  29. package/lib/edm/csn2edm.js +28 -4
  30. package/lib/edm/edm.js +68 -28
  31. package/lib/edm/edmInboundChecks.js +5 -8
  32. package/lib/edm/edmPreprocessor.js +66 -40
  33. package/lib/edm/edmUtils.js +1 -1
  34. package/lib/gen/BaseParser.js +778 -0
  35. package/lib/gen/CdlParser.js +4477 -0
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +1 -1
  38. package/lib/gen/languageParser.js +4072 -4024
  39. package/lib/inspect/inspectPropagation.js +1 -1
  40. package/lib/json/from-csn.js +5 -3
  41. package/lib/json/to-csn.js +7 -10
  42. package/lib/language/antlrParser.js +96 -0
  43. package/lib/language/errorStrategy.js +1 -1
  44. package/lib/language/genericAntlrParser.js +32 -4
  45. package/lib/language/multiLineStringParser.js +1 -1
  46. package/lib/main.d.ts +23 -0
  47. package/lib/model/cloneCsn.js +22 -13
  48. package/lib/model/csnUtils.js +2 -0
  49. package/lib/model/revealInternalProperties.js +2 -0
  50. package/lib/modelCompare/utils/filter.js +70 -42
  51. package/lib/optionProcessor.js +16 -10
  52. package/lib/parsers/AstBuildingParser.js +1290 -0
  53. package/lib/parsers/CdlGrammar.g4 +2013 -0
  54. package/lib/parsers/Lexer.js +249 -0
  55. package/lib/render/toCdl.js +46 -45
  56. package/lib/render/toSql.js +5 -5
  57. package/lib/transform/addTenantFields.js +4 -4
  58. package/lib/transform/db/applyTransformations.js +54 -16
  59. package/lib/transform/draft/odata.js +10 -11
  60. package/lib/transform/effective/flattening.js +10 -14
  61. package/lib/transform/forRelationalDB.js +7 -6
  62. package/lib/transform/odata/flattening.js +42 -31
  63. package/lib/transform/odata/toFinalBaseType.js +7 -6
  64. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  65. package/lib/utils/moduleResolve.js +1 -1
  66. package/package.json +2 -2
  67. package/share/messages/redirected-to-ambiguous.md +5 -4
  68. package/share/messages/redirected-to-complex.md +6 -3
@@ -0,0 +1,249 @@
1
+ // Lexer for CDL grammar
2
+
3
+ // The lexer only cares about potential keywords, not the exact list. That is, it
4
+ // sets the `keyword` property for all non-delimited `Id` tokens.
5
+
6
+ // General remarks about regular expressions in node.js (or in general):
7
+ //
8
+ // - Alternatives in regexps are searched left to right, not longest as in scanner
9
+ // generator!
10
+ // - Beware if a regular expression fails (or matches just one char) after having
11
+ // tested k characters in an input. A regexp having a non-optional match or
12
+ // assertion after a loop (Kleene star) could lead to lexer execution time of
13
+ // O(n*n). Therefore, regexps for strings etc only cover the opening delimiter.
14
+
15
+ 'use strict';
16
+
17
+ const { Location } = require('../base/location'); // TODO main: add tokenIndex
18
+
19
+ const rules = [ // must not contain capturing groups!
20
+ { type: comment, re: '/[*/]' },
21
+ // token type = token text (`type: null`):
22
+ { type: null, re: '[-+*?()\\[\\]{},;:/@#]|\\.(?:\\.\\.?)?|<[=>]?|>=?|=>?|!=|\\|\\|' },
23
+ { type: ident, re: '[$_\\p{ID_Start}][$\\p{ID_Continue}\u200C\u200D]*|!\\[|"' },
24
+ { type: string, re: '[\'"]|`(?:``)?' }, // strings, template literal without …${}
25
+ { type: 'Number', re: '\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?' },
26
+ { type: 'IllegalToken', re: '\\S' }, // must be last
27
+ ];
28
+
29
+ const rulesRegexp = new RegExp( `(${ rules.map( r => r.re ).join( ')|(' ) })`, 'iugm' );
30
+ if (rulesRegexp.exec( '§' )[rules.length] !== '§')
31
+ throw Error( 'Invalid capturing group in rules regexp' );
32
+ const newlineRegexp = /\n/g; // TODO: \r?, PS, LS
33
+
34
+ const commentRegexps = { '//': /$/gm, '/*': /\*\//g };
35
+ const stringRegexps = { "'": /'|$/gm, '`': /[`\\]/g, '```': /```|\\/g };
36
+ const identRegexps = { '![': /\]|$/gm, '"': /"|$/gm };
37
+
38
+ const quotedLiterals = [ 'date', 'time', 'timestamp', 'x' ];
39
+
40
+ class Token {
41
+ type;
42
+ text;
43
+ keyword;
44
+ location;
45
+ parsedAs;
46
+ get isIdentifier() { // compatibility method
47
+ return this.parsedAs !== 'keyword' && this.parsedAs !== 'token' && this.parsedAs;
48
+ }
49
+ get tokenIndex() {
50
+ return this.location.tokenIndex;
51
+ }
52
+ }
53
+
54
+ class Lexer {
55
+ constructor( file, input ) {
56
+ this.file = file;
57
+ this.input = input; // string
58
+ this.linePositions = undefined;
59
+ this.location = undefined;
60
+ }
61
+
62
+ characterPos( line, col ) {
63
+ return this.linePositions[line - 1] + col - 1;
64
+ }
65
+
66
+ tokenize( parser ) {
67
+ this.linePositions = [ 0 ];
68
+ parser.tokens = [];
69
+ parser.docComments = [];
70
+ newlineRegexp.lastIndex = 0;
71
+ while (newlineRegexp.test( this.input ))
72
+ this.linePositions.push( newlineRegexp.lastIndex );
73
+
74
+ const { file } = this;
75
+ let line = 1;
76
+ rulesRegexp.lastIndex = 0;
77
+ let match;
78
+ // eslint-disable-next-line no-cond-assign
79
+ while (match = rulesRegexp.exec( this.input )) {
80
+ let text = match[0];
81
+ const group = match.indexOf( text, 1 ) - 1;
82
+ let type = rules[group].type || text;
83
+ const pos = match.index;
84
+ while (pos >= this.linePositions[line])
85
+ ++line;
86
+ const col = pos - this.linePositions[line - 1] + 1;
87
+ this.location = {
88
+ __proto__: Location.prototype,
89
+ file,
90
+ line,
91
+ col,
92
+ endLine: line,
93
+ endCol: col + text.length,
94
+ // remark: end positions of multi-line tokens must be set by function
95
+ tokenIndex: parser.tokens.length + parser.docComments.length + parser.comments.length,
96
+ };
97
+ let keyword;
98
+ if (typeof type !== 'function' ||
99
+ ([ type, text, keyword ] = type( text, this, parser, pos )) && type) {
100
+ parser.tokens.push( {
101
+ __proto__: Token.prototype,
102
+ type,
103
+ text,
104
+ keyword,
105
+ location: this.location,
106
+ parsedAs: undefined,
107
+ } );
108
+ }
109
+ }
110
+ line = this.linePositions.length;
111
+ const endCol = this.input.length - this.linePositions[line - 1] + 1;
112
+ const location = {
113
+ __proto__: Location.prototype,
114
+ file,
115
+ line,
116
+ col: endCol,
117
+ endLine: line,
118
+ endCol,
119
+ tokenIndex: parser.tokens.length + parser.docComments.length + parser.comments.length,
120
+ };
121
+ parser.tokens.push( {
122
+ __proto__: Token.prototype,
123
+ type: 'EOF',
124
+ text: '',
125
+ keyword: false,
126
+ location,
127
+ parsedAs: undefined,
128
+ } );
129
+ }
130
+ }
131
+
132
+ function comment( text, lexer, parser, beg ) {
133
+ const re = commentRegexps[text];
134
+ re.lastIndex = rulesRegexp.lastIndex;
135
+ if (!re.test( lexer.input )) {
136
+ // eslint-disable-next-line cds-compiler/message-texts
137
+ parser.error( 'syntax-missing-token-end', lexer.location,
138
+ { '#': 'comment', code: '/*', newCode: '*/' }, {
139
+ comment: 'Comments starting with $(CODE) must end with $(NEWCODE)',
140
+ } );
141
+ }
142
+ else if (text === '/*' && lexer.input.charAt( rulesRegexp.lastIndex ) === '*' &&
143
+ rulesRegexp.lastIndex + 2 < re.lastIndex) { // not just `/**/`
144
+ parser.docComments.push( {
145
+ __proto__: Token.prototype,
146
+ type: 'DocComment',
147
+ text: lexer.input.substring( beg, re.lastIndex ),
148
+ keyword: false,
149
+ location: lexer.location,
150
+ parsedAs: undefined,
151
+ } );
152
+ adaptEndLocation( lexer, re.lastIndex ); // also works after push ?
153
+ }
154
+ else { // TODO: only attach with option `attachTokens` ?
155
+ parser.comments.push( {
156
+ __proto__: Token.prototype,
157
+ type: 'Comment',
158
+ text: lexer.input.substring( beg, re.lastIndex ),
159
+ keyword: false,
160
+ location: lexer.location,
161
+ parsedAs: undefined,
162
+ } );
163
+ adaptEndLocation( lexer, re.lastIndex ); // also works after push ?
164
+ }
165
+ rulesRegexp.lastIndex = re.lastIndex || lexer.input.length;
166
+ return [];
167
+ }
168
+
169
+ function string( text, lexer, parser, beg ) {
170
+ let prefix = null;
171
+ const re = stringRegexps[text];
172
+ re.lastIndex = rulesRegexp.lastIndex;
173
+ let esc = 0;
174
+ if (text !== "'") { // single or triple back-quote
175
+ while (re.test( lexer.input ) && lexer.input[re.lastIndex - 1] === '\\')
176
+ esc = ++re.lastIndex;
177
+ }
178
+ else { // try with previous date/time/timestamp/x
179
+ prefix = parser.tokens[parser.tokens.length - 1];
180
+ if (prefix.location.endLine !== lexer.location.line ||
181
+ prefix.location.endCol !== lexer.location.col ||
182
+ !quotedLiterals.includes( prefix.keyword ))
183
+ prefix = null;
184
+ while (re.test( lexer.input ) && lexer.input[re.lastIndex] === "'")
185
+ esc = ++re.lastIndex;
186
+ }
187
+
188
+ let keyword;
189
+ const { lastIndex } = re;
190
+ if (!lastIndex || // reached EOF with template literal
191
+ lexer.input[lastIndex - 1] !== lexer.input[beg] || esc === lastIndex) {
192
+ const before = (lastIndex) ? 'string' : 'multi';
193
+ // eslint-disable-next-line cds-compiler/message-texts
194
+ parser.error( 'syntax-missing-token-end', lexer.location,
195
+ { '#': before, newCode: text }, {
196
+ string: 'The string literal must end with $(NEWCODE) before the end of line',
197
+ multi: 'The multi-line string literal must end with $(NEWCODE)',
198
+ } );
199
+ keyword = 0;
200
+ // TODO: set parsedAs to 0 → no further error if string is not expected?
201
+ prefix = null; // no combination with date/time/…
202
+ }
203
+ adaptEndLocation( lexer, (rulesRegexp.lastIndex = lastIndex || lexer.input.length) );
204
+
205
+ if (!prefix)
206
+ return [ 'String', lexer.input.substring( beg, rulesRegexp.lastIndex ), keyword ];
207
+ prefix.type = 'QuotedLiteral';
208
+ prefix.text += lexer.input.substring( beg, rulesRegexp.lastIndex );
209
+ prefix.keyword = undefined;
210
+ prefix.location.endLine = lexer.location.endLine;
211
+ prefix.location.endCol = lexer.location.endCol;
212
+ return [];
213
+ }
214
+
215
+ function ident( text, lexer, parser, beg ) {
216
+ if (!Object.hasOwn( identRegexps, text ))
217
+ return [ 'Id', text, text.toLowerCase() ];
218
+ const re = identRegexps[text];
219
+ const close = (text === '"') ? '"' : ']';
220
+ re.lastIndex = rulesRegexp.lastIndex;
221
+ let esc = 0;
222
+ while (re.test( lexer.input ) && lexer.input[re.lastIndex] === close)
223
+ esc = ++re.lastIndex;
224
+
225
+ let keyword;
226
+ const { lastIndex } = re;
227
+ if (lexer.input[lastIndex - 1] !== close || esc === lastIndex) {
228
+ // eslint-disable-next-line cds-compiler/message-texts
229
+ parser.error( 'syntax-missing-token-end', lexer.location,
230
+ { '#': 'ident', newcode: close }, {
231
+ ident: 'The delimited id must end with $(NEWCODE) before the end of line',
232
+ } );
233
+ keyword = 0;
234
+ // TODO: set parsedAs to 0 → no further error if string is not expected?
235
+ }
236
+ adaptEndLocation( lexer, (rulesRegexp.lastIndex = lastIndex || lexer.input.length) );
237
+ return [ 'Id', lexer.input.substring( beg, rulesRegexp.lastIndex ), keyword ];
238
+ }
239
+
240
+ function adaptEndLocation( lexer, pos ) {
241
+ let { line } = lexer.location;
242
+ while (pos >= lexer.linePositions[line])
243
+ ++line;
244
+ lexer.location.endLine = line;
245
+ lexer.location.endCol = pos - lexer.linePositions[line - 1] + 1;
246
+ }
247
+
248
+ Lexer.Token = Token;
249
+ module.exports = Lexer;
@@ -320,19 +320,12 @@ function csnToCdl( csn, options, msg ) {
320
320
  result += renderAnnotateParamsInParentheses(ext, env);
321
321
 
322
322
  // Element extensions and annotations (possibly nested)
323
- if (ext.elements) {
324
- env.path.push('elements');
325
- result += ` ${renderAnnotateStatementElements(ext.elements, env)}`;
326
- env.path.length -= 1;
327
- }
328
- else if (ext.enum) {
329
- env.path.push('enum');
330
- result += ` ${renderAnnotateStatementElements(ext.enum, env)}`;
331
- env.path.length -= 1;
332
- }
333
- else if (ext.returns) {
323
+ if (ext.elements || ext.enum)
324
+ result += ` ${renderAnnotateStatementElements(ext, env)}`;
325
+
326
+ else if (ext.returns)
334
327
  result += renderAnnotateReturns(ext, env);
335
- }
328
+
336
329
 
337
330
  if (ext.actions) { // Bound action annotations
338
331
  result += ' actions {\n';
@@ -362,17 +355,18 @@ function csnToCdl( csn, options, msg ) {
362
355
 
363
356
  /**
364
357
  * Render the elements-specific part of an 'annotate' statement for an element dictionary
365
- * 'elements' (assuming that the surrounding parent has just been rendered, without trailing newline).
358
+ * 'ext.elements' (assuming that the surrounding parent has just been rendered, without trailing newline).
366
359
  * Returns the resulting source string, ending without a trailing newline.
367
360
  *
368
- * @param {object} elements
361
+ * @param {object} ext
369
362
  * @param {CdlRenderEnvironment} env
370
363
  * @return {string}
371
364
  */
372
- function renderAnnotateStatementElements( elements, env ) {
365
+ function renderAnnotateStatementElements( ext, env ) {
366
+ const elements = ext.enum ? ext.enum : ext.elements;
373
367
  let result = '{\n';
374
368
  env.increaseIndent();
375
- env.path.push('');
369
+ env.path.push(ext.enum ? 'enum' : 'elements', '');
376
370
  for (const name in elements) {
377
371
  env.path[env.path.length - 1] = name;
378
372
  const elem = elements[name];
@@ -380,18 +374,18 @@ function csnToCdl( csn, options, msg ) {
380
374
  result += env.indent + quoteNonIdentifierOrKeyword(name, env);
381
375
  if (elem.elements) {
382
376
  env.path.push('elements');
383
- result += ` ${renderAnnotateStatementElements(elem.elements, env)}`;
377
+ result += ` ${renderAnnotateStatementElements(elem, env)}`;
384
378
  env.path.pop();
385
379
  }
386
380
  else if (elem.enum) {
387
381
  env.path.push('enum');
388
- result += ` ${renderAnnotateStatementElements(elem.enum, env)}`;
382
+ result += ` ${renderAnnotateStatementElements(elem, env)}`;
389
383
  env.path.pop();
390
384
  }
391
385
 
392
386
  result += ';\n';
393
387
  }
394
- env.path.length -= 1;
388
+ env.path.length -= 2;
395
389
  env.decreaseIndent();
396
390
  result += `${env.indent}}`;
397
391
  return result;
@@ -416,7 +410,7 @@ function csnToCdl( csn, options, msg ) {
416
410
  if (ext.returns.elements) {
417
411
  // Annotations are on separate lines: Have it aligned nicely
418
412
  result += returnAnnos ? `${env.indent}` : ' ';
419
- result += renderAnnotateStatementElements(ext.returns.elements, env);
413
+ result += renderAnnotateStatementElements(ext.returns, env);
420
414
  }
421
415
  return result;
422
416
  }
@@ -424,18 +418,20 @@ function csnToCdl( csn, options, msg ) {
424
418
  /**
425
419
  * Render a parameter list for `annotate` statements, in parentheses `()`.
426
420
  *
427
- * @param {CSN.Artifact} art
421
+ * @param {CSN.Artifact} ext
428
422
  * @param {CdlRenderEnvironment} env
429
423
  * @return {string}
430
424
  */
431
- function renderAnnotateParamsInParentheses( art, env ) {
425
+ function renderAnnotateParamsInParentheses( ext, env ) {
432
426
  const childEnv = env.withIncreasedIndent();
433
427
  let result = '(\n';
434
428
  const paramAnnotations = [];
435
- forEach(art.params, (paramName, param) => {
429
+ forEach(ext.params, (paramName, param) => {
436
430
  const annos = renderAnnotationAssignmentsAndDocComment(param, childEnv);
437
- const name = quoteNonIdentifierOrKeyword(paramName, env.withSubPath([ 'params', paramName ]));
438
- paramAnnotations.push( annos + childEnv.indent + name );
431
+ const name = quoteNonIdentifierOrKeyword(paramName, childEnv);
432
+ // Not supported, yet (#13052)
433
+ // const sub = (param.elements || param.enum) ? ` ${renderAnnotateStatementElements(param, childEnv)}` : '';
434
+ paramAnnotations.push( annos + childEnv.indent + name);
439
435
  });
440
436
  result += `${paramAnnotations.join(',\n')}\n${env.indent})`;
441
437
  return result;
@@ -686,7 +682,7 @@ function csnToCdl( csn, options, msg ) {
686
682
  let obj = annotate;
687
683
  for (let i = 2; i < env.path.length; ++i) {
688
684
  const key = env.path[i];
689
- if (key === 'elements' || key === 'actions') {
685
+ if (key === 'elements' || key === 'actions' || key === 'params') {
690
686
  obj[key] = Object.create(null);
691
687
  const elem = env.path[i + 1];
692
688
  obj[key][elem] = {};
@@ -1251,7 +1247,7 @@ function csnToCdl( csn, options, msg ) {
1251
1247
  */
1252
1248
  function renderParameters( art, env ) {
1253
1249
  const childEnv = env.withIncreasedIndent();
1254
- const parameters = Object.keys(art.params || {}).map(name => renderParameter(name, art.params[name], childEnv.withSubPath([ 'params', name ])));
1250
+ const parameters = Object.keys(art.params || {}).map(name => renderParameter(name, art.params[name], childEnv));
1255
1251
  if (parameters.length === 0)
1256
1252
  return '()';
1257
1253
  return `(\n${parameters.join(',\n')}\n${env.indent})`;
@@ -1335,6 +1331,9 @@ function csnToCdl( csn, options, msg ) {
1335
1331
  if (artifact.localized) // works even for type definitions
1336
1332
  result += 'localized ';
1337
1333
 
1334
+ // Some properties are always "top-level", even for "many", e.g. "default" or
1335
+ // "not null". Keep a reference to the outer artifact.
1336
+ const origArtifact = artifact;
1338
1337
  if (!artifact.type && artifact.items) {
1339
1338
  checkArrayedArtifact(artifact, env);
1340
1339
  result += 'many '; // alternative: 'array of'; but not used
@@ -1346,11 +1345,14 @@ function csnToCdl( csn, options, msg ) {
1346
1345
 
1347
1346
  if (!type && artifact.elements) {
1348
1347
  result += renderElements(artifact, env);
1349
- result += renderNullability(artifact);
1348
+ result += renderNullability(artifact.notNull);
1350
1349
  // structured default not possible at the moment
1351
1350
  return result;
1352
1351
  }
1353
1352
 
1353
+ const defaultValue = origArtifact.default ? origArtifact.default : artifact.default;
1354
+ const notNull = origArtifact.notNull ? origArtifact.notNull : artifact.notNull;
1355
+
1354
1356
  // Association type
1355
1357
  if (isDirectAssocOrComp(type)) {
1356
1358
  const isComp = type === 'cds.Composition';
@@ -1382,11 +1384,11 @@ function csnToCdl( csn, options, msg ) {
1382
1384
  if (artifact.keys && !artifact.on)
1383
1385
  result += ` ${ renderForeignKeys(artifact, env) }`;
1384
1386
 
1385
- if (artifact.notNull !== undefined && !artifact.on) // unmanaged associations can't be followed by "not null"
1386
- result += renderNullability(artifact);
1387
-
1388
- if (artifact.default && !artifact.on)
1389
- result += renderDefaultExpr(artifact, env);
1387
+ if (!artifact.on) {
1388
+ // unmanaged associations can't be followed by "not null" or "default"
1389
+ result += renderNullability(notNull);
1390
+ result += renderDefaultExpr(defaultValue, env.withSubPath([ 'default' ]));
1391
+ }
1390
1392
  return result;
1391
1393
  }
1392
1394
 
@@ -1413,13 +1415,12 @@ function csnToCdl( csn, options, msg ) {
1413
1415
 
1414
1416
  if (artifact.enum && !typeRefOnly)
1415
1417
  result += renderEnum(artifact.enum, env);
1416
- if (artifact.notNull !== undefined)
1417
- result += renderNullability(artifact);
1418
1418
 
1419
+ result += renderNullability(notNull);
1419
1420
  // If there is a default value, and it's a calculated element, do not
1420
1421
  // render the default (because it's not supported for calc elements).
1421
- if (artifact.default !== undefined && !artifact.value)
1422
- result += renderDefaultExpr(artifact, env);
1422
+ if (defaultValue !== undefined && !artifact.value)
1423
+ result += renderDefaultExpr(defaultValue, env.withSubPath([ 'default' ]));
1423
1424
 
1424
1425
  return result;
1425
1426
  }
@@ -1793,24 +1794,24 @@ function csnToCdl( csn, options, msg ) {
1793
1794
  return result;
1794
1795
  }
1795
1796
 
1796
- function renderDefaultExpr( art, env ) {
1797
- if (!art.default)
1797
+ function renderDefaultExpr( defaultValue, env ) {
1798
+ if (!defaultValue)
1798
1799
  return '';
1799
1800
  let result = ' default ';
1800
- if ( art.default.xpr && xprContainsCondition( art.default.xpr))
1801
- result += exprRenderer.renderSubExpr(withoutCast( art.default), env.withSubPath([ 'default' ]));
1801
+ if (defaultValue.xpr && xprContainsCondition( defaultValue.xpr))
1802
+ result += exprRenderer.renderSubExpr(withoutCast(defaultValue), env);
1802
1803
  else
1803
- result += exprRenderer.renderExpr(withoutCast( art.default), env.withSubPath([ 'default' ]));
1804
+ result += exprRenderer.renderExpr(withoutCast(defaultValue), env);
1804
1805
  return result;
1805
1806
  }
1806
1807
 
1807
1808
  // Render the nullability of an element or parameter (can be unset, true, or false)
1808
- function renderNullability( obj /* , env */) {
1809
- if (obj.notNull === undefined) {
1809
+ function renderNullability( notNull /* , env */) {
1810
+ if (notNull === undefined) {
1810
1811
  // Attribute not set at all
1811
1812
  return '';
1812
1813
  }
1813
- return obj.notNull ? ' not null' : ' null';
1814
+ return notNull ? ' not null' : ' null';
1814
1815
  }
1815
1816
 
1816
1817
  /**
@@ -415,7 +415,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
415
415
  const tableName = renderArtifactName(artifactName);
416
416
  deletionsDuplicateChecker.addArtifact(tableName, art.$location, artifactName);
417
417
 
418
- addDeletion(resultObj, artifactName, `DROP TABLE ${tableName}`);
418
+ addDeletion(resultObj, artifactName, `-- [WARNING] this statement is lossy\nDROP TABLE ${tableName}`);
419
419
  }
420
420
 
421
421
  // Render an artifact migration into the appropriate dictionary of 'resultObj'.
@@ -484,7 +484,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
484
484
 
485
485
  // Remove columns.
486
486
  if (removeCols.length)
487
- addMigration(resultObj, artifactName, true, render.dropColumns(artifactName, removeCols));
487
+ addMigration(resultObj, artifactName, true, render.dropColumns(artifactName, removeCols).map(s => (options.src !== 'hdi' ? `-- [WARNING] this statement is lossy\n${s}` : s)));
488
488
 
489
489
  // Remove associations.
490
490
  removeAssocs.forEach(assoc => addMigration(resultObj, artifactName, true, render.dropAssociation(artifactName, assoc)));
@@ -550,7 +550,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
550
550
  }
551
551
  }
552
552
 
553
- if (options.sqlChangeMode === 'drop' || def.old.target || def.new.target || reducesTypeSize(def)) {
553
+ if (options.sqlChangeMode === 'drop' || def.old.target || def.new.target || (reducesTypeSize(def) && options.src === 'hdi')) {
554
554
  // Lossy change because either an association is removed and/or added, or the type size is reduced.
555
555
  // Drop old element and re-add it in its new shape.
556
556
  const drop = def.old.target
@@ -559,10 +559,10 @@ function toSqlDdl( csn, options, messageFunctions ) {
559
559
  const add = def.new.target
560
560
  ? render.addAssociations(artifactName, { [eltName]: def.new }, env)
561
561
  : render.addColumnsFromElementsObj(artifactName, { [eltName]: def.new }, env);
562
- addMigration(resultObj, artifactName, true, render.concat(...drop, ...add));
562
+ addMigration(resultObj, artifactName, true, render.concat(...drop, ...add).map(s => (def.lossy && options.src !== 'hdi' ? `-- [WARNING] this statement could be lossy\n${s}` : s)));
563
563
  }
564
564
  else { // Lossless change: no associations directly affected, no size reduction.
565
- addMigration(resultObj, artifactName, false, render.alterColumns(artifactName, sqlId, def, eltStrNew, eltName, activateAlterMode(env, 'migration')));
565
+ addMigration(resultObj, artifactName, false, render.alterColumns(artifactName, sqlId, def, eltStrNew, eltName, activateAlterMode(env, 'migration')).map(s => (def.lossy ? `-- [WARNING] this statement could be lossy\n${s}` : s)));
566
566
  }
567
567
  }
568
568
  }
@@ -78,7 +78,7 @@ function addTenantFields( csn, options, messageFunctions ) {
78
78
  else if (!independent && independent != null) {
79
79
  error( 'tenant-invalid-anno-value', msgLocations( csnPath ),
80
80
  { anno: annoTenantIndep, value: independent },
81
- // eslint-disable-next-line max-len
81
+ // eslint-disable-next-line @stylistic/js/max-len
82
82
  'Can\'t add $(ANNO) with value $(VALUE) to a non-entity, which is always tenant-independent' );
83
83
  }
84
84
  else if (art.includes) {
@@ -119,9 +119,9 @@ function addTenantFields( csn, options, messageFunctions ) {
119
119
  .filter( name => isTenantDepEntity( csn.definitions[name] ) );
120
120
  if (names.length) {
121
121
  error( 'tenant-invalid-include', msgLocations( csnPath ), { names }, {
122
- // eslint-disable-next-line max-len
122
+ // eslint-disable-next-line @stylistic/js/max-len
123
123
  std: 'Can\'t include the tenant-dependent entities $(NAMES) into a tenant-independent definition',
124
- // eslint-disable-next-line max-len
124
+ // eslint-disable-next-line @stylistic/js/max-len
125
125
  one: 'Can\'t include the tenant-dependent entity $(NAMES) into a tenant-independent definition',
126
126
  } );
127
127
  }
@@ -206,7 +206,7 @@ function addTenantFields( csn, options, messageFunctions ) {
206
206
  if (art[annoTenantIndep]) {
207
207
  error( 'tenant-expecting-tenant-source', msgLocations( csnPath ), { art: query },
208
208
  // TODO: better the final entity name of assoc navigation in FROM
209
- // eslint-disable-next-line max-len
209
+ // eslint-disable-next-line @stylistic/js/max-len
210
210
  'Expecting the query source $(ART) to be tenant-dependent for a tenant-dependent query entity' );
211
211
  }
212
212
  return true;
@@ -306,45 +306,82 @@ function applyTransformationsOnDictionary( dictionary, customTransformers = {},
306
306
  * used primarily to transform annotation expressions.
307
307
  * If propName is undefined, all properties of parent are transformed.
308
308
  * @param {object} parent Start node
309
- * @param {string} propName Start at specific property of parent
309
+ * @param {string|number} parentName Start at specific property of parent
310
310
  * @param {object} transformers Map of callback functions
311
311
  * @param {CSN.Path} path Path to parent
312
+ * @param {object} ctx bucket to tunnel various info into the transformers
312
313
  * @returns {object} transformed node
313
314
  */
314
- function transformExpression( parent, propName, transformers, path = [] ) {
315
- const callT = (t, cpn, child) => {
316
- const ct = t[cpn];
315
+ function transformExpression( parent, parentName, transformers, path = [], ctx = undefined ) {
316
+ const callT = (t, childName, child) => {
317
+ const ct = t[childName];
317
318
  if (ct) {
318
- const ppn = propName;
319
319
  if (Array.isArray(ct))
320
- ct.forEach(cti => cti(child, cpn, child[cpn], path, parent, ppn));
320
+ ct.forEach(cti => cti(child, childName, child[childName], path, parent, parentName, ctx));
321
321
  else
322
- ct(child, cpn, child[cpn], path, parent, ppn);
322
+ ct(child, childName, child[childName], path, parent, parentName, ctx);
323
323
  }
324
324
  };
325
+ if (parentName != null) {
326
+ const child = parent[parentName];
327
+ if (!child || typeof child !== 'object' ||
328
+ !{}.propertyIsEnumerable.call( parent, parentName ))
329
+ return parent;
330
+
331
+ path = [ ...path, parentName ];
332
+ if (Array.isArray(child)) {
333
+ child.forEach( (n, i) => transformExpression( child, i, transformers, path, ctx ) );
334
+ }
335
+ else {
336
+ for (const childName of Object.getOwnPropertyNames( child )) {
337
+ if (Array.isArray(transformers))
338
+ transformers.forEach(t => callT(t, childName, child));
339
+ else
340
+ callT(transformers, childName, child);
341
+ transformExpression(child, childName, transformers, path, ctx);
342
+ }
343
+ }
344
+ }
345
+ else {
346
+ for (parentName of Object.getOwnPropertyNames( parent ))
347
+ transformExpression( parent, parentName, transformers, path, ctx );
348
+ }
349
+ return parent;
350
+ }
351
+
352
+ /**
353
+ * Drill into an annotation value and inspect each (sub-)object value if it is
354
+ * an annotation expression. If so, call the real transformExpression that will
355
+ * execute the callbacks (most likely reference rewriting), continue otherwise
356
+ *
357
+ * @param {object} parent Start node
358
+ * @param {string|number} propName Start at specific property of parent
359
+ * @param {object} transformers Map of callback functions
360
+ * @param {CSN.Path} path Path to parent
361
+ * @returns {object} transformed node
362
+ */
363
+ function transformAnnotationExpression( parent, propName, transformers, path = [] ) {
325
364
  if (propName != null) {
326
365
  const child = parent[propName];
327
366
  if (!child || typeof child !== 'object' ||
328
367
  !{}.propertyIsEnumerable.call( parent, propName ))
329
368
  return parent;
330
369
 
370
+ if (isAnnotationExpression(child))
371
+ return transformExpression(parent, propName, transformers, path, { annoExpr: child });
372
+
331
373
  path = [ ...path, propName ];
332
374
  if (Array.isArray(child)) {
333
- child.forEach( (n, i) => transformExpression( child, i, transformers, path ) );
375
+ child.forEach( (n, i) => transformAnnotationExpression( child, i, transformers, path ) );
334
376
  }
335
377
  else {
336
- for (const cpn of Object.getOwnPropertyNames( child )) {
337
- if (Array.isArray(transformers))
338
- transformers.forEach(t => callT(t, cpn, child));
339
- else
340
- callT(transformers, cpn, child);
341
- transformExpression(child, cpn, transformers, path);
342
- }
378
+ for (const cpn of Object.getOwnPropertyNames( child ))
379
+ transformAnnotationExpression(child, cpn, transformers, path);
343
380
  }
344
381
  }
345
382
  else {
346
383
  for (propName of Object.getOwnPropertyNames( parent ))
347
- transformExpression( parent, propName, transformers, path );
384
+ transformAnnotationExpression( parent, propName, transformers, path );
348
385
  }
349
386
  return parent;
350
387
  }
@@ -384,6 +421,7 @@ function mergeTransformers( transformers, that ) {
384
421
  module.exports = {
385
422
  mergeTransformers,
386
423
  transformExpression,
424
+ transformAnnotationExpression,
387
425
  applyTransformations,
388
426
  applyTransformationsOnNonDictionary,
389
427
  applyTransformationsOnDictionary,
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { forEachDefinition, forEachMemberRecursively,
4
4
  getServiceNames, applyAnnotationsFromExtensions,
5
- transformExpression } = require('../../model/csnUtils');
5
+ transformAnnotationExpression } = require('../../model/csnUtils');
6
6
  const { forEach } = require('../../utils/objectUtils');
7
7
  const { isArtifactInSomeService, getServiceOfArtifact } = require('../odata/utils');
8
8
  const { getTransformers } = require('../transformUtils');
@@ -231,17 +231,16 @@ function generateDrafts( csn, options, services, messageFunctions ) {
231
231
  function $draft2$self(member) {
232
232
  Object.keys(member).forEach(pn => {
233
233
  if(pn[0] === '@') {
234
- let refChanged = false;
235
- transformExpression(member, pn,{
236
- ref: (_parent, _prop, xpr, _path) => {
237
- if(xpr[0] === '$draft') {
238
- xpr[0] = '$self';
239
- refChanged = true;
234
+ transformAnnotationExpression(member, pn, {
235
+ ref: (_parent, _prop, xpr, _path, _p, _ppn, ctx) => {
236
+ if(xpr[0] === '$draft') {
237
+ xpr[0] = '$self';
238
+ if(ctx?.annoExpr?.['='])
239
+ ctx.annoExpr['='] = true;
240
+ }
240
241
  }
241
- }
242
- });
243
- if (refChanged)
244
- member[pn]['='] = true;
242
+ },
243
+ );
245
244
  }
246
245
  });
247
246
  }