@sap/cds-compiler 5.1.2 → 5.2.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 (51) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/bin/cdsc.js +2 -2
  3. package/bin/cdshi.js +24 -17
  4. package/bin/cdsse.js +17 -18
  5. package/lib/api/main.js +19 -2
  6. package/lib/api/options.js +4 -1
  7. package/lib/base/builtins.js +1 -0
  8. package/lib/base/message-registry.js +16 -3
  9. package/lib/base/model.js +0 -10
  10. package/lib/checks/actionsFunctions.js +0 -12
  11. package/lib/checks/structuredAnnoExpressions.js +10 -14
  12. package/lib/compiler/assert-consistency.js +19 -11
  13. package/lib/compiler/builtins.js +1 -1
  14. package/lib/compiler/define.js +3 -3
  15. package/lib/compiler/extend.js +5 -5
  16. package/lib/compiler/populate.js +9 -9
  17. package/lib/compiler/propagator.js +1 -0
  18. package/lib/compiler/resolve.js +29 -34
  19. package/lib/compiler/shared.js +7 -8
  20. package/lib/compiler/tweak-assocs.js +155 -64
  21. package/lib/compiler/utils.js +1 -1
  22. package/lib/compiler/xpr-rewrite.js +4 -3
  23. package/lib/edm/annotations/genericTranslation.js +13 -9
  24. package/lib/edm/csn2edm.js +26 -2
  25. package/lib/edm/edm.js +23 -8
  26. package/lib/edm/edmInboundChecks.js +5 -7
  27. package/lib/edm/edmPreprocessor.js +43 -30
  28. package/lib/gen/BaseParser.js +720 -0
  29. package/lib/gen/CdlParser.js +4421 -0
  30. package/lib/gen/language.checksum +1 -1
  31. package/lib/gen/language.interp +1 -1
  32. package/lib/gen/languageParser.js +4006 -4001
  33. package/lib/language/antlrParser.js +62 -0
  34. package/lib/language/genericAntlrParser.js +28 -0
  35. package/lib/model/csnUtils.js +2 -0
  36. package/lib/model/revealInternalProperties.js +2 -0
  37. package/lib/modelCompare/utils/filter.js +70 -42
  38. package/lib/optionProcessor.js +9 -3
  39. package/lib/parsers/AstBuildingParser.js +1172 -0
  40. package/lib/parsers/CdlGrammar.g4 +1940 -0
  41. package/lib/parsers/Lexer.js +239 -0
  42. package/lib/render/toCdl.js +23 -27
  43. package/lib/render/toSql.js +5 -5
  44. package/lib/transform/db/applyTransformations.js +54 -16
  45. package/lib/transform/draft/odata.js +10 -11
  46. package/lib/transform/effective/flattening.js +10 -14
  47. package/lib/transform/odata/flattening.js +42 -31
  48. package/lib/transform/odata/toFinalBaseType.js +7 -6
  49. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  50. package/package.json +2 -2
  51. package/share/messages/redirected-to-ambiguous.md +5 -4
package/CHANGELOG.md CHANGED
@@ -7,12 +7,31 @@
7
7
  Note: `beta` fixes, changes and features are usually not listed in this ChangeLog but [here](doc/CHANGELOG_BETA.md).
8
8
  The compiler behavior concerning `beta` features can change at any time without notice.
9
9
 
10
+ ## Version 5.2.0 - 2024-08-27
11
+
12
+ ### Added
13
+
14
+ - to.edm(x): Add `@Core.Links { rel: 'author', href: 'https://cap.cloud.sap' };` as watermark to lead schema.
15
+ - to.sql.migration: Introduce option `script` to aid in generation of manual migration scripts by not aborting when encountering lossy changes.
16
+
17
+ ### Changed
18
+
19
+ - for.odata: No longer reject default values on action/function parameters.
20
+ - to.edm(x): Raise warning for default values on action/function parameters that they are ignored.
21
+
22
+ ### Fixed
23
+
24
+ - compiler: Fix extensions with bound actions using an explicit binding parameter in `parseCdl` CSN.
25
+ - for.odata, to.edm(x): Fix some issues with resolving annotation expressions in nested objects and
26
+ reliably replace value of `=` attribute with `true` after processing.
27
+
10
28
  ## Version 5.1.2 - 2024-08-05
11
29
 
12
30
  ### Fixed
13
31
 
14
32
  - compiler: In parseCdl mode, bound actions specifying the binding parameter with `$self` did not work.
15
33
 
34
+
16
35
  ## Version 5.1.0 - 2024-07-25
17
36
 
18
37
  ### Added
@@ -105,6 +124,13 @@ This is a preview version for the major release and contains breaking changes. I
105
124
  Use `to.edm(x)` instead.
106
125
 
107
126
 
127
+ ## Version 4.9.8 - 2024-07-29
128
+
129
+ ### Fixed
130
+
131
+ - compiler: Fix extensions with bound actions using an explicit binding parameter in `parseCdl` CSN.
132
+ - to.edm(x): No `Nullable` attribute for `$ReturnType` of `Collection(<entity type>)` [OData V4 CSDL, section 12.8 Return Type](https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_ReturnType)
133
+
108
134
  ## Version 4.9.6 - 2024-07-15
109
135
 
110
136
  ### Fixed
package/bin/cdsc.js CHANGED
@@ -306,7 +306,7 @@ async function executeCommandLine( command, options, args ) {
306
306
  manageConstraints,
307
307
  toSql,
308
308
  inspect,
309
- toEffectiveCsn,
309
+ forEffective,
310
310
  forSeal,
311
311
  };
312
312
  const commandsWithoutCompilation = {
@@ -352,7 +352,7 @@ async function executeCommandLine( command, options, args ) {
352
352
  return model;
353
353
  }
354
354
 
355
- function toEffectiveCsn( model ) {
355
+ function forEffective( model ) {
356
356
  const features = [ 'resolveSimpleTypes', 'resolveProjections', 'remapOdataAnnotations', 'keepLocalized' ];
357
357
  for (const feature of features) {
358
358
  if (options[feature]) // map to boolean equivalent
package/bin/cdshi.js CHANGED
@@ -34,40 +34,47 @@ const categoryChars = { // default: first char of category name
34
34
  // ExtElement: 'E', // using the first letter is the default
35
35
  ExtBoundAction: 'B', // highlight like bound action definition
36
36
  ExtParam: 'P', // highlight like entity/action parameter definition
37
+ FromImplicit: 'W',
37
38
  Event: 'Y',
38
39
  KeyImplicit: 'r', // handle as normal ref
39
40
  // Remark: do not use `x`/`X` (hex literal `x'1e3d'`)
40
41
  };
41
42
 
43
+ const options = { newParser: true, attachTokens: true, messages: [] };
44
+
42
45
  function highlight( err, buf ) {
43
46
  if (err) {
44
47
  console.error( 'ERROR:', err.toString() );
45
48
  return;
46
49
  }
47
- const ts = compiler.parseX( buf, 'hi.cds', { attachTokens: true, messages: [] } ).tokenStream;
48
- if (!buf.length || !ts.tokens || !ts.tokens.length)
50
+ const parser = compiler.parseX( buf, 'hi.cds', options ).tokenStream;
51
+ // ts is parser with new parser
52
+ const { tokens, lexer } = parser;
53
+ if (!buf.length || !tokens || !tokens.length)
49
54
  return;
50
55
  const chars = [ ...buf ];
51
- for (const tok of ts.tokens) {
52
- if (tok.start < 0)
56
+ for (const tok of tokens) {
57
+ const { location } = tok;
58
+ const start = lexer.characterPos( location.line, location.col );
59
+ if (start < 0)
53
60
  continue;
54
- if (tok.$isSkipped) {
55
- if (tok.stop > tok.start) {
56
- chars[tok.start] = (tok.$isSkipped === true ? '\x0f' : '\x16'); // ^O / ^V
57
- chars[tok.stop] = '\x17'; // ^W
61
+ const stop = lexer.characterPos( location.endLine, location.endCol ) - 1;
62
+ const cat = tok.parsed;
63
+ // console.log(tok.location.toString(),tok.text,tok.parsed,stop > start)
64
+ if (!cat) {
65
+ if (stop > start) {
66
+ chars[start] = (cat !== 0 ? '\x0f' : '\x16'); // ^O / ^V (ERROR)
67
+ chars[stop] = '\x17'; // ^W
58
68
  }
59
69
  else {
60
- chars[tok.start] = (tok.$isSkipped === true ? '\x0e' : '\x15'); // ^N / ^U
70
+ chars[start] = (cat !== 0 ? '\x0e' : '\x15'); // ^N / ^U (ERROR)
61
71
  }
62
72
  }
63
- else {
64
- const cat = tok.isIdentifier;
65
- if (cat) {
66
- if (cat !== 'ref' || chars[tok.start] !== '$')
67
- chars[tok.start] = categoryChars[cat] || cat.charAt(0);
68
- if (tok.stop > tok.start) // stop in ANTLR at last char, not behind
69
- chars[tok.start + 1] = '_';
70
- }
73
+ else if (cat !== 'keyword' && cat !== 'token') {
74
+ if (cat !== 'ref' || chars[start] !== '$')
75
+ chars[start] = categoryChars[cat] || cat.charAt(0);
76
+ if (stop > start) // stop in ANTLR at last char, not behind
77
+ chars[start + 1] = '_';
71
78
  }
72
79
  }
73
80
  for (const c of chars)
package/bin/cdsse.js CHANGED
@@ -7,8 +7,8 @@
7
7
  // corrupted, incomplete or erroneous CDL sources.
8
8
  //
9
9
  // The output could be used directly by some editors, e.g. Emacs. The
10
- // capabilities supported at the moments is: complete - work in progress.
11
- // Planned are: gotoDefinition, highlight (for syntax highlighting).
10
+ // capabilities supported at the moments is: complete, find, lint.
11
+ // Syntax highlighting is supported by ./cdshi.js.
12
12
 
13
13
  /* eslint no-console:off */
14
14
  // @ts-nocheck
@@ -52,6 +52,7 @@ function usage( err ) {
52
52
  }
53
53
 
54
54
  function complete( err, buf ) {
55
+ const messages = [];
55
56
  if (err)
56
57
  return usage( err );
57
58
  const off = offset( buf );
@@ -74,16 +75,13 @@ function complete( err, buf ) {
74
75
  const src = `${buf.substring( 0, off.prefix )}__NO_SUCH_ID__${buf.substring( off.cursor )}`;
75
76
  const fname = path.resolve( '', file );
76
77
  compiler.compileX( [ file ], '', {
77
- attachValidNames: true, lintMode: true, beta, messages: [],
78
- }, { [fname]: src } )
78
+ newParser: true, attachValidNames: true, lintMode: true, beta, messages }, { [fname]: src } )
79
79
  .then( ident, ident );
80
80
  }
81
81
  return true;
82
82
 
83
- function ident( xsnOrErr ) {
84
- if (!(xsnOrErr.messages || xsnOrErr.options && xsnOrErr.options.messages))
85
- return usage( xsnOrErr );
86
- const vn = messageAt( xsnOrErr, 'validNames', off.col ) || Object.create(null);
83
+ function ident() {
84
+ const vn = messageAt( messages, 'validNames', off.col ) || Object.create(null);
87
85
  // TODO: if there is no such message, use console.log( 'arbitrary identifier' )
88
86
  // if we want to avoid that the editor switches to fuzzy completion match
89
87
  // against the prefix (not yet done anyway)
@@ -107,18 +105,17 @@ function find( err, buf ) {
107
105
  return usage();
108
106
  if (off.prefix === off.cursor) // not at name
109
107
  return true;
108
+ const messages = [];
110
109
  const src = `${buf.substring( 0, off.prefix )}__NO_SUCH_ID__${buf.substring( off.cursor )}`;
111
110
  const fname = path.resolve( '', file );
112
111
  compiler.compileX( [ file ], '', {
113
- attachValidNames: true, lintMode: true, beta, messages: [],
112
+ newParser: true, attachValidNames: true, lintMode: true, beta, messages,
114
113
  }, { [fname]: src } )
115
114
  .then( show, show );
116
115
  return true;
117
116
 
118
- function show( xsnOrErr ) {
119
- if (!(xsnOrErr.messages || xsnOrErr.options && xsnOrErr.options.messages))
120
- return usage( xsnOrErr );
121
- const vn = messageAt( xsnOrErr, 'validNames', off.col ) || Object.create(null);
117
+ function show() {
118
+ const vn = messageAt( messages, 'validNames', off.col ) || Object.create(null);
122
119
  const art = vn[buf.substring( off.prefix, off.cursor )];
123
120
  if (art)
124
121
  console.log( `${locationString( art.name.location || art.location )}: Definition` );
@@ -129,13 +126,13 @@ function find( err, buf ) {
129
126
  function lint( err, buf ) {
130
127
  if (err)
131
128
  return usage( err );
129
+ const messages = [];
132
130
  const fname = path.resolve( '', file );
133
- compiler.compileX( [ file ], '', { lintMode: true, beta, messages: [] }, { [fname]: buf } )
131
+ compiler.compileX( [ file ], '', { newParser: true, lintMode: true, beta, messages }, { [fname]: buf } )
134
132
  .then( display, display );
135
133
  return true;
136
134
 
137
135
  function display( xsnOrErr ) {
138
- const messages = xsnOrErr.messages || xsnOrErr.options && xsnOrErr.options.messages;
139
136
  if (!messages)
140
137
  return usage( xsnOrErr );
141
138
  for (const msg of messages)
@@ -145,8 +142,10 @@ function lint( err, buf ) {
145
142
  }
146
143
 
147
144
  function tokensAt( buf, _offset, col, symbol ) {
145
+ const messages = [];
148
146
  const src = `${buf.substring( 0, _offset )}≠${buf.substring( _offset )}`;
149
- const et = messageAt( compiler.parseX( src, frel, { messages: [] } ), 'expectedTokens', col ) || [];
147
+ compiler.parseX( src, frel, { newParser: true, messages } );
148
+ const et = messageAt( messages, 'expectedTokens', col ) || [];
150
149
  for (const n of et) {
151
150
  if (typeof symbol === 'string') {
152
151
  if (n.length > 3 && n.charAt(0) === "'" && n.charAt(1) === symbol)
@@ -166,8 +165,8 @@ function tokensAt( buf, _offset, col, symbol ) {
166
165
  return et.includes( 'Identifier' );
167
166
  }
168
167
 
169
- function messageAt( model, prop, col ) {
170
- const msg = (model.messages || model.options && model.options.messages).find(
168
+ function messageAt( messages, prop, col ) {
169
+ const msg = messages.find(
171
170
  m => m[prop] && m.$location.line === line && m.$location.col === col && m.$location.file === frel
172
171
  );
173
172
  return msg && msg[prop];
package/lib/api/main.js CHANGED
@@ -468,8 +468,19 @@ function remapName( key, csn, filter = () => true ) {
468
468
  */
469
469
  function sqlMigration( csn, options, messageFunctions, beforeImage ) {
470
470
  const internalOptions = prepareOptions.to.sql(options);
471
+
471
472
  messageFunctions.setOptions( internalOptions );
472
473
  handleTenantDiscriminator(options, internalOptions, messageFunctions);
474
+ if (!options.dry && internalOptions.script) {
475
+ messageFunctions.error('api-invalid-combination', null, { '#': 'dry-and-script', value: options.dry || 'undefined' });
476
+ messageFunctions.throwWithError();
477
+ }
478
+
479
+ if (internalOptions.script && !internalOptions.severities?.['type-unsupported-key-change']) {
480
+ internalOptions.severities ??= {};
481
+ internalOptions.severities['type-unsupported-key-change'] = 'warning';
482
+ }
483
+
473
484
  const { error, throwWithError } = messageFunctions;
474
485
 
475
486
  // Prepare after-image.
@@ -479,7 +490,7 @@ function sqlMigration( csn, options, messageFunctions, beforeImage ) {
479
490
  // Compare both images.
480
491
  const diff = modelCompare.compareModels(beforeImage || afterImage, afterImage, internalOptions);
481
492
  messageFunctions.setModel(diff);
482
- const diffFilterObj = diffFilter[internalOptions.sqlDialect];
493
+ const diffFilterObj = diffFilter.getFilter(internalOptions);
483
494
 
484
495
  if (diffFilterObj) {
485
496
  diff.extensions = diff.extensions.filter(ex => diffFilterObj.extension(ex, messageFunctions));
@@ -487,6 +498,9 @@ function sqlMigration( csn, options, messageFunctions, beforeImage ) {
487
498
  Object.entries(diff.deletions).forEach(entry => diffFilterObj.deletion(entry, error));
488
499
  diff.changedPrimaryKeys = diff.changedPrimaryKeys
489
500
  .filter(an => diffFilterObj.changedPrimaryKeys(an));
501
+
502
+ if (internalOptions.script && diffFilterObj.hasLossyChanges())
503
+ messageFunctions.warning('def-unsupported-changes', null, null, 'Found potentially lossy changes - check generated SQL statements');
490
504
  }
491
505
 
492
506
  const identifierUtils = sqlUtils.getIdentifierUtils(csn, internalOptions);
@@ -494,7 +508,10 @@ function sqlMigration( csn, options, messageFunctions, beforeImage ) {
494
508
  const drops = {
495
509
  creates: {},
496
510
  final: Object.entries(diff.deletions).reduce((previous, [ name, artifact ]) => {
497
- previous[name] = `DROP ${ (artifact.query || artifact.projection) ? 'VIEW' : 'TABLE' } ${ identifierUtils.renderArtifactName(name) };`;
511
+ if (artifact.query || artifact.projection)
512
+ previous[name] = `DROP VIEW ${ identifierUtils.renderArtifactName(name) };`;
513
+ else
514
+ previous[name] = `-- [WARNING] this statement is lossy\nDROP TABLE ${ identifierUtils.renderArtifactName(name) };`;
498
515
  return previous;
499
516
  }, {}),
500
517
  };
@@ -44,6 +44,7 @@ const publicOptionsNewAPI = [
44
44
  'odataXServiceRefs',
45
45
  'odataV2PartialConstr',
46
46
  'odataVocabularies',
47
+ 'odataNoCreator',
47
48
  'service',
48
49
  'serviceNames',
49
50
  //
@@ -53,6 +54,8 @@ const publicOptionsNewAPI = [
53
54
  'resolveProjections',
54
55
  'remapOdataAnnotations',
55
56
  'keepLocalized',
57
+ // to.sql.migration
58
+ 'script',
56
59
  ];
57
60
 
58
61
  // Internal options used for testing/debugging etc.
@@ -195,7 +198,7 @@ module.exports = {
195
198
  return translateOptions(options, defaultOptions, hardOptions, undefined, [ 'valid-structured' ], 'to.odata');
196
199
  },
197
200
  },
198
- for: { // TODO: Rename version to oDataVersion
201
+ for: {
199
202
  odata: (options) => {
200
203
  const hardOptions = { toOdata: true };
201
204
  const defaultOptions = { odataVersion: 'v4', odataFormat: 'flat' };
@@ -17,6 +17,7 @@ const propagationRules = {
17
17
  '@cds.autoexpose': 'onlyViaArtifact',
18
18
  '@cds.autoexposed': 'never',
19
19
  '@cds.external': 'never',
20
+ '@cds.java.this.name': 'onlyViaParent',
20
21
  '@cds.persistence.calcview': 'never',
21
22
  '@cds.persistence.exists': 'never',
22
23
  '@cds.persistence.skip': 'notWithPersistenceTable',
@@ -157,6 +157,8 @@ const centralMessages = {
157
157
  'syntax-duplicate-equal-clause': { severity: 'Warning' },
158
158
  'syntax-invalid-name': { severity: 'Error', configurableFor: 'deprecated' },
159
159
  'syntax-missing-as': { severity: 'Error', configurableFor: true },
160
+ 'syntax-missing-proj-semicolon': { severity: 'Warning', errorFor: [ 'v6' ] },
161
+ 'syntax-unexpected-many-one': { severity: 'Error', configurableFor: true }, // TODO: remove `configurableFor` soon, latest v6
160
162
  'syntax-unexpected-null': { severity: 'Error', configurableFor: true },
161
163
  'syntax-unexpected-reserved-word': { severity: 'Error', configurableFor: true },
162
164
  'syntax-unknown-escape': { severity: 'Error', configurableFor: true },
@@ -260,7 +262,8 @@ const centralMessageTexts = {
260
262
  std: 'Invalid option combination found: $(OPTION) and $(PROP)', // unused
261
263
  'valid-structured': 'Structured OData is only supported with OData version v4',
262
264
  'sql-dialect-and-naming': 'sqlDialect $(NAME) can\'t be combined with sqlMapping $(PROP)',
263
- 'tenant-and-naming': 'Option $(OPTION) can\'t be combined with sqlMapping $(PROP) - expected sqlMapping $(VALUE)'
265
+ 'tenant-and-naming': 'Option $(OPTION) can\'t be combined with sqlMapping $(PROP) - expected sqlMapping $(VALUE)',
266
+ 'dry-and-script': 'script:true must be combined with dry:true, found $(VALUE)',
264
267
  },
265
268
  'api-unexpected-combination': {
266
269
  std: 'Unexpected option combination: $(OPTION) and $(PROP)', // unused
@@ -356,6 +359,7 @@ const centralMessageTexts = {
356
359
  std: 'Annotations can\'t be used in a column with $(CODE)',
357
360
  doc: 'Doc comments can\'t be used in a column with $(CODE)',
358
361
  },
362
+ 'syntax-unexpected-many-one': 'Replace $(CODE) with $(DELIMITED) to avoid an ambiguity with managed compositions of anonymous aspects',
359
363
  'syntax-invalid-name': {
360
364
  std: 'Identifier must consist of at least one character', // only via delimited id
361
365
  // as: 'String in property $(PROP) must not be empty', // expecting non-empty string is ok
@@ -694,8 +698,12 @@ const centralMessageTexts = {
694
698
  'annotation': 'Variable $(NAME) can only be used in annotation values',
695
699
  },
696
700
 
697
- // TODO: Better text ?
698
- 'rewrite-not-supported': 'The ON-condition is not rewritten here - provide an explicit ON-condition',
701
+ 'rewrite-not-supported': {
702
+ // TODO: Better text ?
703
+ std: 'The ON-condition is not rewritten here - provide an explicit ON-condition',
704
+ 'inline-expand': 'The ON-condition is not rewritten in nested projections - provide an explicit ON-condition',
705
+ 'secondary': 'The ON-condition is not rewritten due to multiple associations in this path - provide an explicit ON-condition',
706
+ },
699
707
  'type-unsupported-rewrite': {
700
708
  std: 'Rewriting the ON-condition not supported here', // unused: merge with 'rewrite-not-supported'
701
709
  'sub-element': 'Rewriting the ON-condition of unmanaged association in sub element is not supported'
@@ -1109,6 +1117,11 @@ const centralMessageTexts = {
1109
1117
  'odata-parameter-order': 'Unexpected mandatory after optional parameter',
1110
1118
  'odata-key-recursive': 'Unexpected recursive key $(NAME)',
1111
1119
  'odata-key-uuid-default-anno': 'Expected element of type $(TYPE) to be annotated with $(ANNO) when used as primary key in $(ID)',
1120
+ 'odata-ignoring-param-default': {
1121
+ 'std': 'Ignoring default value on parameter',
1122
+ 'xpr': 'Ignoring unexpected expression as default value',
1123
+ 'colitem': 'Ignoring unexpected default value for a structured or collection like parameter',
1124
+ },
1112
1125
  // -----------------------------------------------------------------------------------
1113
1126
  // All odata-anno MUST have a '$(ANNO)' parameter to indicate error location
1114
1127
  // -----------------------------------------------------------------------------------
package/lib/base/model.js CHANGED
@@ -7,15 +7,6 @@
7
7
 
8
8
  const { forEach } = require('../utils/objectUtils');
9
9
 
10
- const queryOps = {
11
- query: 'select', // TODO: rename to SELECT
12
- union: 'union',
13
- intersect: 'union',
14
- except: 'union',
15
- minus: 'union',
16
- subquery: 'union', // for (subquery) with ORDER BY or LIMIT/OFFSET
17
- };
18
-
19
10
  /**
20
11
  * Object of all available beta flags that will be enabled/disabled by `--beta-mode`
21
12
  * through cdsc. Only intended for INTERNAL USE.
@@ -224,7 +215,6 @@ module.exports = {
224
215
  availableBetaFlags,
225
216
  isDeprecatedEnabled,
226
217
  checkRemovedDeprecatedFlags,
227
- queryOps,
228
218
  forEachDefinition,
229
219
  forEachMember,
230
220
  forEachMemberRecursively,
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const { isBuiltinType } = require('../base/builtins');
4
- const { isBetaEnabled } = require('../base/model');
5
4
 
6
5
  // Only to be used with validator.js - a correct this value needs to be provided!
7
6
 
@@ -21,8 +20,6 @@ function checkActionOrFunction( art, artName, prop, path ) {
21
20
  // (this.options.odataProxies || this.options.odataXServiceRefs);
22
21
 
23
22
  const serviceName = this.csnUtils.getServiceName(artName);
24
- if (!serviceName && art.kind !== 'aspect')
25
- this.warning(null, path, {}, 'Functions and actions must be declared in a service');
26
23
 
27
24
  if (art.kind === 'entity') {
28
25
  for (const [ actName, act ] of Object.entries(art.actions)) {
@@ -71,15 +68,6 @@ function checkActionOrFunction( art, artName, prop, path ) {
71
68
  if (!paramType)
72
69
  return; // no type could be resolved
73
70
 
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
- });
81
- }
82
-
83
71
  if (param.type && this.csnUtils.isAssocOrComposition(param)) {
84
72
  this.error(null, currPath, { '#': actKind }, {
85
73
  std: 'An association is not allowed as this artifact\'s parameter type', // Not used
@@ -1,27 +1,23 @@
1
1
  'use strict';
2
2
 
3
3
  const { isBuiltinType } = require('../base/builtins');
4
- const { transformExpression, applyTransformationsOnNonDictionary } = require('../model/csnUtils');
4
+ const { transformAnnotationExpression } = require('../model/csnUtils');
5
5
  /**
6
6
  *
7
7
  * @param {object} member
8
8
  */
9
9
  function checkAnnotationExpression( member, _memberName, _prop, path ) {
10
10
  Object.keys(member).filter(pn => pn[0] === '@').forEach((anno) => {
11
- applyTransformationsOnNonDictionary(member, anno, {
12
- xpr: (parent, prop, _xpr, xprPath) => {
13
- transformExpression(parent, prop, {
14
- ref: (elemref, __prop, ref, refPath) => {
15
- const { art, scope } = this.csnUtils.inspectRef(refPath);
16
- if (scope !== '$magic' && art) {
17
- const ft = this.csnUtils.getFinalTypeInfo(art.type);
18
- if (!isBuiltinType(ft?.type))
19
- this.error('odata-anno-xpr-ref', refPath, { anno, elemref, '#': 'flatten_builtin_type' });
20
- }
21
- },
22
- }, xprPath);
11
+ transformAnnotationExpression(member, anno, {
12
+ ref: (elemref, __prop, _ref, refPath) => {
13
+ const { art, scope } = this.csnUtils.inspectRef(refPath);
14
+ if (scope !== '$magic' && art) {
15
+ const ft = this.csnUtils.getFinalTypeInfo(art.type);
16
+ if (!isBuiltinType(ft?.type))
17
+ this.error('odata-anno-xpr-ref', refPath, { anno, elemref, '#': 'flatten_builtin_type' });
18
+ }
23
19
  },
24
- }, {}, path);
20
+ }, path);
25
21
  });
26
22
  }
27
23
 
@@ -289,7 +289,8 @@ function assertConsistency( model, stage ) {
289
289
  '$calcDepElement',
290
290
  'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit', '$limit',
291
291
  '_origin', '_block', '$contains',
292
- '_projections', '_parent', '_main', '_effectiveType', '$effectiveSeqNo', '$expand',
292
+ '_projections', '_complexProjections',
293
+ '_parent', '_main', '_effectiveType', '$effectiveSeqNo', '$expand',
293
294
  '$tableAliases', 'kind', '_$next', '_combined', '$inlines', '_status',
294
295
  ],
295
296
  },
@@ -313,7 +314,7 @@ function assertConsistency( model, stage ) {
313
314
  'kind', 'name', '$syntax', '_block', '_parent', '_main',
314
315
  'elements', '_origin', '_joinParent', '$joinArgsIndex', '$syntax',
315
316
  '$parens', '_status', // TODO: only in from
316
- 'scope', '_artifact', '$inferred', 'kind',
317
+ 'scope', '_artifact', '_originalArtifact', '$inferred', 'kind',
317
318
  '_effectiveType', '$effectiveSeqNo', // TODO:check this
318
319
  '$duplicates', // In JOIN if both sides are the same.
319
320
  ],
@@ -370,7 +371,8 @@ function assertConsistency( model, stage ) {
370
371
  requires: [ 'location' ],
371
372
  optional: [
372
373
  'path', 'elements', '_outer', '_parent', '_main', '_block', 'kind',
373
- 'scope', '_artifact', '$inferred', '$expand', '$inCycle', '$tableAliases', '_$next',
374
+ 'scope', '_artifact', '$inferred', '$expand', '$inCycle',
375
+ '$tableAliases', '_$next',
374
376
  '_origin', '_effectiveType', '$effectiveSeqNo', '_extensions', '$contains',
375
377
  ],
376
378
  },
@@ -390,7 +392,8 @@ function assertConsistency( model, stage ) {
390
392
  'args', '$syntax',
391
393
  'where', 'groupBy', 'limit', 'orderBy', 'having',
392
394
  'cardinality',
393
- '_artifact', '_navigation', '_user',
395
+ '_artifact', '_originalArtifact',
396
+ '_navigation', '_user',
394
397
  '$inferred',
395
398
  ],
396
399
  },
@@ -443,7 +446,10 @@ function assertConsistency( model, stage ) {
443
446
  test: expression, // properties below are "sub specifications"
444
447
  ref: {
445
448
  requires: [ 'location', 'path' ],
446
- optional: [ 'scope', 'variant', '_artifact', '$inferred', '$parens', 'sort', 'nulls' ],
449
+ optional: [
450
+ 'scope', 'variant', '_artifact', '_originalArtifact',
451
+ '$inferred', '$parens', 'sort', 'nulls',
452
+ ],
447
453
  },
448
454
  none: { optional: () => true }, // parse error
449
455
  // TODO: why optional / enough in name?
@@ -488,7 +494,7 @@ function assertConsistency( model, stage ) {
488
494
  'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicates', 'upTo',
489
495
  // expressions as annotation values
490
496
  '$tokenTexts', 'op', 'args', 'func', '_artifact', 'type', '$typeArgs',
491
- 'scale', 'srid', 'length', 'precision', 'scope',
497
+ 'scale', 'srid', 'length', 'precision', 'scope', '$parens',
492
498
  ],
493
499
  // TODO: restrict path to #simplePath
494
500
  },
@@ -524,7 +530,7 @@ function assertConsistency( model, stage ) {
524
530
  '_effectiveType', '$effectiveSeqNo', '_origin', '_deps',
525
531
  // CSN parser may let these properties slip through to XSN, even if input is invalid.
526
532
  'args', 'op', 'func', 'suffix',
527
- '$invalidPaths',
533
+ '$invalidPaths', '$parens',
528
534
  ],
529
535
  // TODO: name requires if not in parser?
530
536
  },
@@ -538,7 +544,7 @@ function assertConsistency( model, stage ) {
538
544
  schema: {
539
545
  id: { test: isStringOrNumber },
540
546
  select: { test: TODO }, // TODO: remove
541
- }, // TODO: rename query prop in name
547
+ },
542
548
  requires: [ 'location' ],
543
549
  optional: [
544
550
  'path', 'id', '$delimited', 'variant', // TODO: req path, opt id for main, req id for member
@@ -607,6 +613,7 @@ function assertConsistency( model, stage ) {
607
613
  // - on a path item with a filter condition to the user of the ref (not nested)
608
614
  // - on a JOIN node to the query (TODO: _outer?)
609
615
  _artifact: { test: TODO },
616
+ _originalArtifact: { test: TODO },
610
617
  _navigation: { test: TODO },
611
618
  _effectiveType: { kind: true, test: TODO },
612
619
  $effectiveSeqNo: { kind: true, test: isNumber },
@@ -641,7 +648,7 @@ function assertConsistency( model, stage ) {
641
648
  $replacement: { kind: true, test: TODO }, // for smart * in queries
642
649
  _origin: { kind: true, test: TODO },
643
650
  _calcOrigin: { kind: true, test: TODO },
644
- _pathHead: { kind: [ 'element', undefined ], test: TODO }, // column or * (wildcard)
651
+ _columnParent: { kind: [ 'element', undefined ], test: TODO }, // column or * (wildcard)
645
652
  _from: { kind: true, test: TODO }, // TODO: not necessary anymore ?
646
653
  // array of $tableAlias (or includes) for explicit and implicit redirection:
647
654
  _redirected: { kind: true, test: TODO },
@@ -658,7 +665,8 @@ function assertConsistency( model, stage ) {
658
665
  _scc: { kind: true, test: TODO }, // for cyclic calculation
659
666
  _sccCaller: { kind: true, test: TODO }, // for cyclic calculation
660
667
  _status: { kind: true, test: TODO }, // TODO: $status
661
- _projections: { kind: true, test: TODO }, // for mixin definitions
668
+ _projections: { kind: true, test: TODO },
669
+ _complexProjections: { kind: true, test: TODO }, // for projected paths with filters
662
670
  $entity: { kind: true, test: TODO },
663
671
  _entities: { test: TODO },
664
672
  $compositionTargets: { test: isDictionary( isBoolean ) },
@@ -972,7 +980,7 @@ function assertConsistency( model, stage ) {
972
980
  return function valWithLocation( node, parent, prop, spec, name ) {
973
981
  const valSchema = { val: Object.assign( {}, spec, { test: func } ) };
974
982
  const requires = [ 'val', 'location' ];
975
- const optional = [ 'literal', '$inferred', '$priority', '_pathHead' ];
983
+ const optional = [ 'literal', '$inferred', '$priority', '_columnParent' ];
976
984
  standard( node, parent, prop, {
977
985
  schema: valSchema, requires, optional, instanceOf: spec.instanceOf,
978
986
  }, name );
@@ -77,6 +77,7 @@ typeParameters.list = Object.keys( typeParameters.expectedLiteralsFor );
77
77
 
78
78
 
79
79
  const specialFunctions = compileFunctions( {
80
+ // TODO: use lower-case
80
81
  '': [ // the default
81
82
  {
82
83
  intro: [ 'ALL', 'DISTINCT' ],
@@ -231,7 +232,6 @@ const numberRegEx = /^[ \t]*[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]?\d+)?[ \t]*$/i;
231
232
  * - `unexpected_msg`: error message which is issued if `unexpected_char` matches
232
233
  * - `unexpected_char`: regular expression matching an illegal character in `value`,
233
234
  * the error location is only correct for a literal <prefix>'<value>'
234
- * - `literal`: the value which is used instead of `prefix` in the AST
235
235
  * TODO: we might do a range check (consider leap seconds, i.e. max value 60),
236
236
  * but always allow Feb 29 (no leap year computation)
237
237
  * Notes:
@@ -903,9 +903,9 @@ function define( model ) {
903
903
  hasItems = true;
904
904
  if (!columns) { // expand or inline
905
905
  if (parent.value)
906
- setLink( col, '_pathHead', parent ); // also set for '*' in expand/inline
907
- else if (parent._pathHead)
908
- setLink( col, '_pathHead', parent._pathHead );
906
+ setLink( col, '_columnParent', parent ); // also set for '*' in expand/inline
907
+ else if (parent._columnParent)
908
+ setLink( col, '_columnParent', parent._columnParent );
909
909
  }
910
910
  if (col.val === '*') {
911
911
  if (!wildcard) {
@@ -441,8 +441,8 @@ function extend( model ) {
441
441
  function applySingleExtension( art, ext, prop ) {
442
442
  if (prop === 'includes') {
443
443
  if (ext.kind === 'extend' && art.$inferred) {
444
- error( 'extend-for-generated', [ ext.name.location, ext ], { art },
445
- 'You can\'t use EXTEND on the generated $(ART)' );
444
+ error( 'extend-for-generated', [ ext.name.location, ext ], { art, keyword: 'extend' },
445
+ 'You can\'t use $(KEYWORD) on the generated $(ART)' );
446
446
  }
447
447
  else if (art.kind !== 'annotate' && !art._outer) { // not with elem extension in targetAspect
448
448
  const { id } = art.name;
@@ -797,7 +797,7 @@ function extend( model ) {
797
797
  const dict = parent[prop];
798
798
  if (!dict) {
799
799
  // TODO: check - for each name? - better locations
800
- const location = ext._parent[prop]?.[$location] || ext.name.location;
800
+ const location = ext._parent?.[prop]?.[$location] || ext.name.location;
801
801
  // Remark: no `elements` dict location with `annotate Main:elem`
802
802
  switch (prop) {
803
803
  // TODO: change texts, somehow similar to checkDefinitions() ?
@@ -1052,8 +1052,8 @@ function extend( model ) {
1052
1052
  if (ext.name._artifact === undefined) { // not already applied
1053
1053
  setArtifactLink( ext.name, art );
1054
1054
  if (noExtend && ext.kind === 'extend') {
1055
- error( 'extend-for-generated', [ ext.name.location, ext ], { art },
1056
- 'You can\'t use EXTEND on the generated $(ART)' );
1055
+ error( 'extend-for-generated', [ ext.name.location, ext ], { art, keyword: 'extend' },
1056
+ 'You can\'t use $(KEYWORD) on the generated $(ART)' );
1057
1057
  continue;
1058
1058
  }
1059
1059
  if (ext.includes) {