@sap/cds-compiler 5.8.2 → 5.9.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 (87) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/bin/cds_remove_invalid_whitespace.js +5 -3
  3. package/bin/cds_update_identifiers.js +9 -6
  4. package/bin/cdsc.js +79 -59
  5. package/bin/cdsse.js +14 -10
  6. package/bin/cdsv2m.js +3 -1
  7. package/lib/api/options.js +28 -6
  8. package/lib/base/message-registry.js +15 -4
  9. package/lib/checks/validator.js +3 -0
  10. package/lib/compiler/base.js +1 -1
  11. package/lib/compiler/checks.js +70 -50
  12. package/lib/compiler/extend.js +1 -1
  13. package/lib/compiler/generate.js +8 -2
  14. package/lib/compiler/index.js +1 -1
  15. package/lib/compiler/lsp-api.js +1 -1
  16. package/lib/compiler/propagator.js +2 -2
  17. package/lib/compiler/resolve.js +78 -31
  18. package/lib/compiler/shared.js +3 -3
  19. package/lib/compiler/tweak-assocs.js +1 -1
  20. package/lib/compiler/utils.js +10 -0
  21. package/lib/compiler/xpr-rewrite.js +1 -1
  22. package/lib/edm/annotations/edmJson.js +42 -39
  23. package/lib/edm/annotations/genericTranslation.js +55 -55
  24. package/lib/edm/annotations/preprocessAnnotations.js +5 -5
  25. package/lib/edm/csn2edm.js +21 -16
  26. package/lib/edm/edm.js +62 -62
  27. package/lib/edm/edmAnnoPreprocessor.js +2 -2
  28. package/lib/edm/edmInboundChecks.js +1 -1
  29. package/lib/edm/edmPreprocessor.js +32 -32
  30. package/lib/edm/edmUtils.js +8 -8
  31. package/lib/gen/CdlGrammar.checksum +1 -1
  32. package/lib/gen/CdlParser.js +77 -81
  33. package/lib/gen/Dictionary.json +3062 -3072
  34. package/lib/gen/language.checksum +1 -1
  35. package/lib/gen/language.interp +1 -1
  36. package/lib/gen/languageParser.js +1238 -1236
  37. package/lib/json/from-csn.js +1 -1
  38. package/lib/json/to-csn.js +30 -3
  39. package/lib/language/genericAntlrParser.js +16 -0
  40. package/lib/main.d.ts +79 -1
  41. package/lib/model/csnRefs.js +12 -5
  42. package/lib/model/xprAsTree.js +71 -0
  43. package/lib/modelCompare/utils/filter.js +1 -1
  44. package/lib/optionProcessor.js +46 -32
  45. package/lib/parsers/CdlGrammar.g4 +33 -28
  46. package/lib/parsers/Lexer.js +1 -1
  47. package/lib/parsers/XprTree.js +25 -16
  48. package/lib/render/toCdl.js +902 -414
  49. package/lib/render/toHdbcds.js +1 -1
  50. package/lib/render/toSql.js +8 -0
  51. package/lib/render/utils/common.js +2 -2
  52. package/lib/render/utils/operators.js +160 -0
  53. package/lib/render/utils/pretty.js +337 -0
  54. package/lib/sql-identifier.js +7 -9
  55. package/lib/transform/addTenantFields.js +39 -41
  56. package/lib/transform/db/applyTransformations.js +4 -4
  57. package/lib/transform/db/assertUnique.js +6 -5
  58. package/lib/transform/db/associations.js +3 -3
  59. package/lib/transform/db/assocsToQueries/transformExists.js +13 -13
  60. package/lib/transform/db/assocsToQueries/utils.js +8 -0
  61. package/lib/transform/db/backlinks.js +19 -14
  62. package/lib/transform/db/constraints.js +6 -6
  63. package/lib/transform/db/expansion.js +1 -1
  64. package/lib/transform/db/flattening.js +2 -2
  65. package/lib/transform/db/groupByOrderBy.js +1 -1
  66. package/lib/transform/db/processSqlServices.js +3 -3
  67. package/lib/transform/db/rewriteCalculatedElements.js +2 -2
  68. package/lib/transform/db/temporal.js +7 -9
  69. package/lib/transform/db/views.js +6 -6
  70. package/lib/transform/draft/odata.js +2 -0
  71. package/lib/transform/effective/annotations.js +1 -1
  72. package/lib/transform/effective/associations.js +1 -1
  73. package/lib/transform/effective/main.js +1 -0
  74. package/lib/transform/effective/service.js +2 -2
  75. package/lib/transform/forRelationalDB.js +11 -5
  76. package/lib/transform/localized.js +2 -0
  77. package/lib/transform/odata/adaptAnnotationRefs.js +10 -9
  78. package/lib/transform/parseExpr.js +2 -2
  79. package/lib/transform/transformUtils.js +9 -7
  80. package/lib/transform/translateAssocsToJoins.js +0 -2
  81. package/lib/transform/universalCsn/coreComputed.js +2 -2
  82. package/lib/utils/moduleResolve.js +7 -5
  83. package/package.json +1 -1
  84. package/share/messages/def-upcoming-virtual-change.md +55 -0
  85. package/share/messages/file-unexpected-case-mismatch.md +61 -0
  86. package/share/messages/message-explanations.json +2 -0
  87. package/lib/transform/braceExpression.js +0 -77
@@ -20,6 +20,7 @@ const publicOptionsNewAPI = [
20
20
  'defaultBinaryLength',
21
21
  'defaultStringLength',
22
22
  'csnFlavor',
23
+ 'compositionIncludes',
23
24
  // DB
24
25
  'sqlDialect',
25
26
  'sqlMapping',
@@ -33,6 +34,7 @@ const publicOptionsNewAPI = [
33
34
  'fewerLocalizedViews',
34
35
  'withHanaAssociations',
35
36
  'standardDatabaseFunctions',
37
+ 'booleanEquality',
36
38
  // ODATA
37
39
  'odataOpenapiHints',
38
40
  'edm4OpenAPI',
@@ -48,6 +50,9 @@ const publicOptionsNewAPI = [
48
50
  'odataNoCreator',
49
51
  'service',
50
52
  'serviceNames',
53
+ // to.cdl
54
+ 'renderCdlDefinitionNesting',
55
+ 'renderCdlCommonNamespace',
51
56
  //
52
57
  'dictionaryPrototype',
53
58
  // for.effective
@@ -66,7 +71,7 @@ const publicOptionsNewAPI = [
66
71
  const privateOptions = [
67
72
  // Not callable via cdsc, keep private for now until we are sure that we want this
68
73
  'filterCsn',
69
- 'lintMode',
74
+ 'lintMode', // for cdsse only
70
75
  'traceFs',
71
76
  'traceParser',
72
77
  'traceParserAmb',
@@ -134,7 +139,7 @@ function translateOptions( input = {}, defaults = {}, hardRequire = {},
134
139
  reclassifyErrorsForOpenApi( options );
135
140
 
136
141
  // Convenience for $user -> $user.id replacement
137
- if (options.variableReplacements && options.variableReplacements.$user && typeof options.variableReplacements.$user === 'string')
142
+ if (options.variableReplacements?.$user && typeof options.variableReplacements?.$user === 'string')
138
143
  options.variableReplacements.$user = { id: options.variableReplacements.$user };
139
144
 
140
145
  return options;
@@ -158,11 +163,21 @@ function reclassifyErrorsForOpenApi( options ) {
158
163
 
159
164
  module.exports = {
160
165
  to: {
161
- cdl: options => translateOptions(options, undefined, undefined, undefined, undefined, 'to.cdl'),
166
+ cdl: (options) => {
167
+ // TODO(v6): Change defaults to 'true'
168
+ const defaultOptions = {
169
+ renderCdlDefinitionNesting: false,
170
+ renderCdlCommonNamespace: false,
171
+ };
172
+ return translateOptions(options, defaultOptions, undefined, undefined, undefined, 'to.cdl');
173
+ },
162
174
  sql: (options) => {
163
175
  const hardOptions = { src: 'sql', toSql: true, forHana: true };
164
176
  const defaultOptions = {
165
- sqlMapping: 'plain', sqlDialect: 'plain', withHanaAssociations: true,
177
+ sqlMapping: 'plain',
178
+ sqlDialect: 'plain',
179
+ withHanaAssociations: true,
180
+ booleanEquality: false,
166
181
  };
167
182
  const processed = translateOptions(options, defaultOptions, hardOptions, undefined, [ 'sql-dialect-and-naming' ], 'to.sql');
168
183
 
@@ -172,14 +187,21 @@ module.exports = {
172
187
  const hardOptions = { src: 'hdi', toSql: true, forHana: true };
173
188
  // TODO: sqlDialect should be a hard option!
174
189
  const defaultOptions = {
175
- sqlMapping: 'plain', sqlDialect: 'hana', withHanaAssociations: true,
190
+ sqlMapping: 'plain',
191
+ sqlDialect: 'hana',
192
+ withHanaAssociations: true,
193
+ booleanEquality: false,
176
194
  };
177
195
  return translateOptions(options, defaultOptions, hardOptions, { sqlDialect: generateStringValidator([ 'hana' ]) }, undefined, 'to.hdi');
178
196
  },
179
197
  hdbcds: (options) => {
180
198
  const hardOptions = { forHana: true };
181
199
  // TODO: sqlDialect should be a hard option!
182
- const defaultOptions = { sqlMapping: 'plain', sqlDialect: 'hana' };
200
+ const defaultOptions = {
201
+ sqlMapping: 'plain',
202
+ sqlDialect: 'hana',
203
+ booleanEquality: false,
204
+ };
183
205
  return translateOptions(options, defaultOptions, hardOptions, { sqlDialect: generateStringValidator([ 'hana' ]) }, undefined, 'to.hdbcds');
184
206
  },
185
207
  edm: (options) => {
@@ -76,6 +76,8 @@ const centralMessages = {
76
76
 
77
77
  'type-invalid-items': { severity: 'Error' }, // not supported yet
78
78
  'assoc-as-type': { severity: 'Error' }, // TODO: allow more, but not all
79
+ // the following is not really an error in v6, but gets a different semantics:
80
+ 'assoc-incomplete-to-many': { severity: 'Warning', errorFor: [ 'v6' ] },
79
81
  'def-unexpected-nested-proj': { severity: 'Error', configurableFor: 'v4' },
80
82
  'def-unexpected-paramview-assoc': { severity: 'Error' },
81
83
  'def-unexpected-calcview-assoc': { severity: 'Error' },
@@ -93,7 +95,7 @@ const centralMessages = {
93
95
 
94
96
  'ref-deprecated-orderby': { severity: 'Error', configurableFor: true },
95
97
  'ref-deprecated-self-element': { severity: 'Error', configurableFor: true },
96
- 'ref-deprecated-variable': { severity: 'Warning', errorFor: [ 'v6' ] },
98
+ 'ref-deprecated-variable': { severity: 'Warning' },
97
99
  'ref-invalid-type': { severity: 'Error' },
98
100
  'ref-unexpected-self': { severity: 'Error' },
99
101
  'ref-invalid-include': { severity: 'Error' },
@@ -163,15 +165,16 @@ const centralMessages = {
163
165
  // fallback, but then parse.cdl is not supposed to work correctly (it can
164
166
  // then either issue an error or produce a CSN missing some annotations):
165
167
  'syntax-duplicate-annotate': { severity: 'Error' },
166
- 'syntax-duplicate-clause': { severity: 'Error', configurableFor: 'v6' },
168
+ 'syntax-duplicate-clause': { severity: 'Error', configurableFor: [ 'v6' ] },
167
169
  // remark: a hard syntax error in new parser for `null` together with `not null`
168
170
  'syntax-duplicate-equal-clause': { severity: 'Warning', errorFor: [ 'v6' ] },
169
171
  'syntax-invalid-name': { severity: 'Error', configurableFor: 'deprecated' },
170
172
  'syntax-missing-as': { severity: 'Error', configurableFor: true },
171
- 'syntax-missing-proj-semicolon': { severity: 'Warning', errorFor: [ 'v6' ] },
173
+ 'syntax-missing-proj-semicolon': { severity: 'Warning' },
172
174
  'syntax-unexpected-after': { severity: 'Warning', errorFor: [ 'v6' ] },
173
175
  'syntax-unexpected-filter': { severity: 'Warning', errorFor: [ 'v6' ] },
174
176
  'syntax-unexpected-many-one': { severity: 'Error', configurableFor: true }, // TODO: remove `configurableFor` soon, latest v6
177
+ 'syntax-deprecated-ref-virtual': { severity: 'Warning', errorFor: [ 'v6' ] },
175
178
  'syntax-unexpected-reserved-word': { severity: 'Error', configurableFor: true },
176
179
  'syntax-unknown-escape': { severity: 'Error', configurableFor: true },
177
180
  'syntax-unsupported-masked': { severity: 'Error', configurableFor: 'deprecated' },
@@ -268,6 +271,9 @@ const centralMessageTexts = {
268
271
  type: 'Expected option $(OPTION) to be of type $(VALUE). Found: $(OTHERVALUE)',
269
272
  forbidden: 'Option $(OPTION) can\'t be used with API function $(MODULE)',
270
273
  },
274
+ 'def-upcoming-virtual-change': {
275
+ std: 'This select item will be a new element in cds-compiler v6, without referencing $(NAME); prepend a table alias if you want to keep the virtual element as a reference; see https://cap.cloud.sap/docs/cds/compiler/messages#def-upcoming-virtual-change for details',
276
+ },
271
277
 
272
278
  'api-invalid-variable-replacement': {
273
279
  std: 'Option $(OPTION) does not support $(NAME)',
@@ -385,6 +391,11 @@ const centralMessageTexts = {
385
391
  },
386
392
  'syntax-unexpected-filter': 'Unexpected filter on the result of a function call',
387
393
  'syntax-unexpected-many-one': 'Replace $(CODE) with $(DELIMITED) to avoid an ambiguity with managed compositions of anonymous aspects',
394
+ 'syntax-deprecated-ref-virtual': {
395
+ std: 'Use $(DELIMITED) at the beginning of the column expression',
396
+ ref: 'Use $(DELIMITED) to refer to the element $(NAME) at the beginning of the column expression',
397
+ func: 'Use $(DELIMITED) when calling the function $(NAME) at the beginning of the column expression',
398
+ },
388
399
  'syntax-invalid-name': {
389
400
  std: 'Identifier must consist of at least one character', // only via delimited id
390
401
  // as: 'String in property $(PROP) must not be empty', // expecting non-empty string is ok
@@ -900,12 +911,12 @@ const centralMessageTexts = {
900
911
  },
901
912
  'def-unexpected-localized': {
902
913
  std: 'Unexpected $(KEYWORD)',
903
- struct: 'Unexpected $(KEYWORD) for structured type',
904
914
  map: 'Unexpected $(KEYWORD) for map type',
905
915
  elements: '$(ART) can\'t have localized elements',
906
916
  // TODO: Better message?
907
917
  include: '$(ART) can\'t have localized elements (through include)',
908
918
  },
919
+ 'def-unexpected-localized-struct': '$(KEYWORD) is not fully supported for structures',
909
920
  'def-unexpected-localized-anno': 'Annotations can\'t have localized elements',
910
921
  'type-unexpected-structure': {
911
922
  std: 'Unexpected structured type', // unused variant
@@ -52,6 +52,7 @@ const {
52
52
  } = require('./sql-snippets');
53
53
  const assertNoAssocUsageOutsideOfService = require('./assocOutsideService');
54
54
  const featureFlags = require('./featureFlags');
55
+ const { timetrace } = require('../utils/timetrace');
55
56
 
56
57
  const forRelationalDBMemberValidators
57
58
  = [
@@ -156,7 +157,9 @@ function _validate( csn, that,
156
157
  artifactValidators = [],
157
158
  queryValidators = [],
158
159
  iterateOptions = {} ) {
160
+ timetrace.start('Enrich CSN');
159
161
  const { cleanup } = enrichCsn(csn, that.options);
162
+ timetrace.stop('Enrich CSN');
160
163
  // TODO: Don't know if that's feasible? Do we really need to enrich annotations always?
161
164
  // const { cleanup } = enrich(csn, { processAnnotations: that.options.tranformation === 'odata' });
162
165
 
@@ -75,7 +75,7 @@ function propExists( prop, parent ) {
75
75
  * `element`. The following code makes use of the fact that only member extensions
76
76
  * have a "sparse" name structure.
77
77
  *
78
- * @param {XSN.Artifact} art
78
+ * @param {XSN.Artifact & XSN.Using} art
79
79
  * @returns {XSN.Name}
80
80
  */
81
81
  function getArtifactName( art ) {
@@ -21,6 +21,7 @@ const {
21
21
  const { typeParameters } = require('./builtins');
22
22
  const { propagationRules } = require('../base/builtins');
23
23
  const { annotationVal } = require('./utils');
24
+ const { functionsWithoutParentheses } = require('../parsers/identifiers');
24
25
 
25
26
  const $location = Symbol.for( 'cds.$location' );
26
27
 
@@ -131,7 +132,6 @@ function check( model ) {
131
132
  }
132
133
 
133
134
  function checkElement( elem, parentProps ) {
134
- checkLocalizedSubElement( elem );
135
135
  checkKey( elem, parentProps );
136
136
  checkLocalizedElement( elem );
137
137
 
@@ -274,10 +274,13 @@ function check( model ) {
274
274
  function checkLocalizedElement( elem ) {
275
275
  if (elem.localized?.val) {
276
276
  const type = elem._effectiveType;
277
- if (type?.category === 'map' ||
278
- (type?.elements && isBetaEnabled( model.options, 'v6preview' ))) {
277
+ if (type?.category === 'map') {
279
278
  error( 'def-unexpected-localized', [ elem.localized.location, elem ],
280
- { keyword: 'localized', '#': type.category === 'map' ? 'map' : 'struct' } );
279
+ { keyword: 'localized', '#': 'map' } );
280
+ }
281
+ else if (type?.elements) { // warning only, as we want to support it in the future
282
+ warning( 'def-unexpected-localized-struct', [ elem.localized.location, elem ],
283
+ { keyword: 'localized' } );
281
284
  }
282
285
  else if (!type || !type.builtin || type.category !== 'string') {
283
286
  // See discussion issue #6520: should we allow all scalar types?
@@ -287,9 +290,10 @@ function check( model ) {
287
290
  }
288
291
  }
289
292
 
290
- // TODO: This check should be moved to localized.js
293
+ // TODO: This check should be moved to localized.js - WHY?
291
294
  // "key" keyword at localized element in SELECT list.
292
295
  // TODO: not in inferred elements, but also inside aspects
296
+ // TODO: `localized` is not necessarily at _origin, but the _origin chain
293
297
  if (elem.key?.val && elem._main?.query) {
294
298
  // either the element was casted to localized (no `_origin`) or
295
299
  // original element is localized but not key, as that would have
@@ -472,47 +476,6 @@ function check( model ) {
472
476
  }
473
477
  }
474
478
 
475
- /**
476
- * TODO: check inside compiler as it is a compiler restriction - improve
477
- * TODO: Recursive check; use "parentProps" as other member checks
478
- *
479
- * Non-recursive check if sub-elements have a "localized" keyword since this is
480
- * not yet supported.
481
- *
482
- * This check is not recursive to avoid a runtime overhead. Because of this it fails
483
- * to detect scenarios with indirections, e.g.
484
- * ```cds
485
- * type L : localized String;
486
- * type L1 : L;
487
- * type L2 : L1;
488
- *
489
- * entity E {
490
- * struct : {
491
- * subElement : L2;
492
- * }
493
- * }
494
- * ```
495
- *
496
- * @param {XSN.Artifact} element
497
- */
498
- function checkLocalizedSubElement( element ) {
499
- if (element._parent?.kind !== 'element')
500
- return;
501
-
502
- const isLocalizedSubElement = element.localized?.val;
503
- if (isLocalizedSubElement || element.type?._artifact?.localized?.val) {
504
- const loc = isLocalizedSubElement ? element.localized.location : element.type.location;
505
- warning( 'localized-sub-element', [ loc, element ], {
506
- type: element.type?._artifact || '',
507
- '#': isLocalizedSubElement ? 'std' : 'type',
508
- keyword: 'localized',
509
- }, {
510
- std: 'Keyword $(KEYWORD) is ignored for sub elements',
511
- type: 'Keyword $(KEYWORD) in type $(TYPE) is ignored for sub elements',
512
- } );
513
- }
514
- }
515
-
516
479
  /**
517
480
  * Check that min and max cardinalities of 'art' have legal values
518
481
  *
@@ -615,13 +578,22 @@ function check( model ) {
615
578
  }
616
579
  }
617
580
 
618
- // TODO(v6): Also reject default for structures (#13154), i.e. add `|| !!art.….elements`
619
- const isStructured = art._effectiveType?.name.id === 'cds.Map';
620
- if (isStructured) {
581
+ const isMap = art._effectiveType?.name.id === 'cds.Map';
582
+ if (isMap) {
621
583
  error( 'type-unexpected-default', [ defaultValue.location, art ], {
622
584
  '#': 'map', keyword: 'default', type: 'cds.Map',
623
585
  } );
624
586
  }
587
+ else if (art._effectiveType?.elements) {
588
+ warning( 'type-unexpected-default-struct', [ defaultValue.location, art ], {
589
+ '#': art.kind, keyword: 'default',
590
+ }, {
591
+ std: 'Unexpected $(KEYWORD) for a structure',
592
+ param: 'Unexpected $(KEYWORD) for a structured parameter',
593
+ type: 'Unexpected $(KEYWORD) for a structured type definition',
594
+ element: 'Unexpected $(KEYWORD) for a structured element',
595
+ } );
596
+ }
625
597
  }
626
598
 
627
599
  function getBinaryOp( cond ) {
@@ -673,7 +645,7 @@ function check( model ) {
673
645
  const element = def.elements[name];
674
646
  // Element is new in `art`, not expanded; we can't check for !element._origin, due
675
647
  // to calculated elements such as `a = b`.
676
- if (element.$inferred !== 'include') {
648
+ if (element.$inferred !== 'include' && element.$inferred !== 'aspect-composition') {
677
649
  for (const include of def.includes) {
678
650
  if (include._artifact?.elements?.[name] !== undefined)
679
651
  checkElementOverride( element, include._artifact.elements[name] );
@@ -780,6 +752,7 @@ function check( model ) {
780
752
 
781
753
  function checkSelectItemValue( elem ) {
782
754
  checkExpressionAssociationUsage( elem.value, elem, false );
755
+ checkVirtualSelectItemChangeForV6( elem );
783
756
  // To avoid duplicate messages, only run this check if the type wasn't inferred from
784
757
  // the cast, as otherwise we will check it twice (once here, once via element).
785
758
  if (elem.value?.op?.val === 'cast' && elem.type?.$inferred !== 'cast') {
@@ -792,6 +765,40 @@ function check( model ) {
792
765
  } );
793
766
  }
794
767
 
768
+ /**
769
+ * In v6, there will be a change in semantics for following example:
770
+ *
771
+ * ```cds
772
+ * view V as select from E {
773
+ * virtual b, // -> warning: will be new element, not reference in v6
774
+ * }
775
+ * ```
776
+ *
777
+ * We allow users to define _new_ elements using `virtual`. But all such references
778
+ * in v5 are valid, resolvable references. Hence, the semantics will change in v6.
779
+ * Let users know about it.
780
+ *
781
+ * @param elem
782
+ */
783
+ function checkVirtualSelectItemChangeForV6( elem ) {
784
+ if (
785
+ !elem.virtual?.val || elem.virtual.$inferred || // not explicitly marked virtual
786
+ elem.type && !elem.type.$inferred || // has explicit type
787
+ elem.value.path?.length > 1 || // multi-path step, i.e. no new definition
788
+ !elem.name.$inferred // has explicit alias
789
+ )
790
+ return;
791
+
792
+ if ((!elem.value.path || !elem.value._artifact) &&
793
+ !isFunctionWithoutParentheses( elem ))
794
+ return; // not a reference nor function without parentheses
795
+
796
+ if (elem.value.path?.some(ps => ps.args || ps.where))
797
+ return; // not a simple reference
798
+
799
+ warning('def-upcoming-virtual-change', [ elem.virtual.location, elem ], { name: elem.name.id });
800
+ }
801
+
795
802
  function checkCalculatedElementValue( elem ) {
796
803
  const isStored = elem.value.stored?.val;
797
804
  visitExpression( elem.value, elem, (xpr, user) => {
@@ -1378,4 +1385,17 @@ function isComplexView( art ) {
1378
1385
  return (!art.query.from?._artifact || art.query.from._artifact.kind === 'element');
1379
1386
  }
1380
1387
 
1388
+ /**
1389
+ * @param {XSN.Element} elem
1390
+ * @returns {boolean}
1391
+ */
1392
+ function isFunctionWithoutParentheses( elem ) {
1393
+ const func = elem.value?.func;
1394
+ if (!func?.path || elem.value.args)
1395
+ return false;
1396
+ if (func.path.length !== 1)
1397
+ return false;
1398
+ return functionsWithoutParentheses.includes(func.path[0].id.toUpperCase());
1399
+ }
1400
+
1381
1401
  module.exports = check;
@@ -1400,7 +1400,7 @@ function layeredExtensions( extensions ) {
1400
1400
  * at least two files are involved
1401
1401
  *
1402
1402
  * @param {Record<string, object>} layered Structure as returned by layeredExtensions()
1403
- * @returns {{assignments, issue: boolean|string}}
1403
+ * @returns {{highest, issue: boolean|string}}
1404
1404
  */
1405
1405
  function extensionsOfHighestLayers( layered ) {
1406
1406
  const layerNames = Object.keys( layered );
@@ -35,6 +35,7 @@ function generate( model ) {
35
35
  extendArtifactBefore,
36
36
  applyIncludes,
37
37
  } = model.$functions;
38
+ model.$functions.hasTruthyProp = hasTruthyProp;
38
39
 
39
40
  const addTextsLanguageAssoc = checkTextsLanguageAssocOption( model, options );
40
41
  const useTextsAspect = checkTextsAspect();
@@ -530,7 +531,7 @@ function generate( model ) {
530
531
  return false;
531
532
  name = (art._main || art)?.name?.id;
532
533
  }
533
- else if (art.type && art._block && art.type.scope !== 'typeOf') {
534
+ else if (art.type) {
534
535
  // TODO: also do something special for TYPE OF inside `art`s own elements
535
536
  // TODO: check for own - add test case with Type:elem (not TYPE OF elem)
536
537
  name = resolveUncheckedPath( art.type, 'type', art );
@@ -563,7 +564,7 @@ function generate( model ) {
563
564
  const elem = base.elements[name];
564
565
  if (elem.$duplicates)
565
566
  return false; // no composition-of-type unfold with redefined elems
566
- if (elem.key && elem.key.val)
567
+ if (elem.key?.val)
567
568
  k[name] = elem;
568
569
  }
569
570
  return k;
@@ -695,6 +696,8 @@ function generate( model ) {
695
696
  $inferred: 'composition-entity',
696
697
  };
697
698
  if (target.name) { // named target aspect
699
+ if (options.compositionIncludes !== false) // default is 'true' since v5.9
700
+ art.includes = [ createInclude( target.name.id, location ) ];
698
701
  setLink( art, '_origin', target );
699
702
  setLink( art, '_upperAspects', [ target, ...(elem._main._upperAspects || []) ] );
700
703
  }
@@ -749,6 +752,9 @@ function generate( model ) {
749
752
  extendArtifactBefore( art );
750
753
  // Copy persistence annotations from aspect.
751
754
  copyPersistenceAnnotations( art, target ); // after extendArtifactBefore()
755
+
756
+ if (options.compositionIncludes !== false && art.includes)
757
+ applyIncludes( art, art ); // for actions
752
758
  return art;
753
759
  }
754
760
 
@@ -124,7 +124,7 @@ function parserForFile( source, ext, options ) {
124
124
  // file names are relative to `process.cwd()+dir` (or just `dir` if it is absolute).
125
125
  // Options can have the following properties:
126
126
  // - Truthy `parseOnly`: stop compilation after parsing.
127
- // - Truthy `lintMode`: do not do checks and propagation
127
+ // - Truthy `lintMode`: do not do propagation
128
128
  // - many others - TODO
129
129
 
130
130
  // This function returns a Promise and can be used with `await`. For an example
@@ -168,7 +168,7 @@ function* artifactTokens( art ) {
168
168
  if (artifactActions[prop])
169
169
  yield* artifactActions[prop](art[prop], art);
170
170
  else if (prop.charAt(0) === '@')
171
- yield* artifactActions['@'](art[prop], art);
171
+ yield* artifactActions['@'](art[prop]);
172
172
  }
173
173
 
174
174
  return null;
@@ -366,10 +366,10 @@ function propagate( model ) {
366
366
  return;
367
367
  for (const item of path) {
368
368
  const art = item && item._artifact;
369
- if (art && art.virtual && art.virtual.val) {
369
+ if (art?.virtual?.val) {
370
370
  warning( 'def-missing-virtual', [ item.location, elem ], { art, keyword: 'virtual' },
371
371
  // eslint-disable-next-line @stylistic/js/max-len
372
- 'Prepend $(KEYWORD) to current select item - referred element $(ART) is virtual which is not inherited' );
372
+ 'Prepend $(KEYWORD) to current select item - containing element $(ART) is virtual' );
373
373
  return;
374
374
  }
375
375
  }
@@ -64,6 +64,7 @@ const {
64
64
  linkToOrigin,
65
65
  compositionTextVariant,
66
66
  targetCantBeAspect,
67
+ userParam,
67
68
  } = require('./utils');
68
69
 
69
70
  const detectCycles = require('./cycle-detector');
@@ -96,6 +97,7 @@ function resolve( model ) {
96
97
  effectiveType,
97
98
  getOrigin,
98
99
  getInheritedProp,
100
+ hasTruthyProp, // limited inheritance
99
101
  resolveTypeArgumentsUnchecked,
100
102
  } = model.$functions;
101
103
  Object.assign( model.$functions, {
@@ -509,7 +511,7 @@ function resolve( model ) {
509
511
 
510
512
  // Check if relational type is missing its target or if it's used directly.
511
513
  if (elemtype.category === 'relation' &&
512
- !obj.target && !obj.targetAspect) {
514
+ !obj.target && !obj.targetAspect) {
513
515
  const isCsn = (obj._block && obj._block.$frontend === 'json');
514
516
  error( 'type-missing-target', [ obj.type.location, obj ],
515
517
  { '#': isCsn ? 'csn' : 'std', type: elemtype }, {
@@ -582,6 +584,35 @@ function resolve( model ) {
582
584
  }
583
585
  }
584
586
 
587
+ // Set '@Core.Computed' in the Core Compiler to have it propagated...
588
+ if (art.kind !== 'element' || art['@Core.Computed'])
589
+ return;
590
+
591
+ // For events and types, elements can't be @Core.Computed, as values are only used
592
+ // to infer the element signature. For virtual, we keep @Core.Computed, as it's
593
+ // always been that way, even before type projections.
594
+ const elementsCanBeComputed = art._main?.kind !== 'type' && art._main?.kind !== 'event';
595
+
596
+ if (art.virtual?.val ||
597
+ elementsCanBeComputed && art.value &&
598
+ (!art.value._artifact || !art.value.path || // in localization view: _artifact, but no path
599
+ art.value.stored?.val || // calculated elements on-write are always computed
600
+ art.value._artifact.kind === 'builtin' ||
601
+ art.value._artifact.kind === 'param' ||
602
+ art.value.scope === 'param' )) {
603
+ art['@Core.Computed'] = {
604
+ name: {
605
+ path: [ { id: 'Core.Computed', location: art.location } ],
606
+ location: art.location,
607
+ },
608
+ $inferred: '$generated',
609
+ };
610
+ }
611
+
612
+ if (art.kind === 'element' && art._effectiveType)
613
+ checkLocalizedElement( art );
614
+ return;
615
+
585
616
  /**
586
617
  * Check whether the signature of the specified element matches that of the inferred one.
587
618
  *
@@ -619,11 +650,11 @@ function resolve( model ) {
619
650
  }
620
651
  // If specified type is `null`, type could not be resolved.
621
652
  else if (!compToAssoc && sType && sType !== iType &&
622
- // Special case for $recompilation: allow one level of type indirection. See #12113.
623
- (!options.$recompile || sType !== iType.type?._artifact)) {
653
+ // Special case for $recompilation: allow one level of type indirection. See #12113.
654
+ (!options.$recompile || sType !== iType.type?._artifact)) {
624
655
  const typeName = !iTypeArt && 'typeExtra' || // no inferred type prop
625
- iType?.name && sType?.name && 'typeName' || // both types are named
626
- 'type'; // unknown type names
656
+ iType?.name && sType?.name && 'typeName' || // both types are named
657
+ 'type'; // unknown type names
627
658
  const othertype = typeName !== 'type' && iType || '';
628
659
  error( 'query-mismatched-element', [
629
660
  specifiedElement.type.location || specifiedElement.location, user,
@@ -639,7 +670,7 @@ function resolve( model ) {
639
670
  // This relies on (element) expansion! Check that both sides have the following properties.
640
671
  // On the inferred side, they are likely expanded.
641
672
  if (!hasXorPropMismatch( 'elements' ) && !hasXorPropMismatch( 'items' ) &&
642
- !hasXorPropMismatch( 'target' ) && !hasXorPropMismatch( 'enum' )) {
673
+ !hasXorPropMismatch( 'target' ) && !hasXorPropMismatch( 'enum' )) {
643
674
  // Element are already traversed via elements$ merging.
644
675
 
645
676
  // only check items, if the specified one is not expanded/inferred
@@ -789,7 +820,7 @@ function resolve( model ) {
789
820
  // This property is directly in the `value` property, but not part of `inferredElement`
790
821
  // which has a `type` property, but no `enum`.
791
822
  if (!inferredElement[prop] !== !specifiedElement[prop] &&
792
- !inferredElement.value?.[prop] !== !specifiedElement[prop]) {
823
+ !inferredElement.value?.[prop] !== !specifiedElement[prop]) {
793
824
  error( 'query-mismatched-element', [ specifiedElement.location, specifiedElement ], {
794
825
  '#': specifiedElement[prop] ? 'extra' : 'missing', name: user.name.id, prop,
795
826
  } );
@@ -812,31 +843,41 @@ function resolve( model ) {
812
843
  return element.cardinality || ref?.[ref.length - 1]?.cardinality;
813
844
  }
814
845
  }
846
+ }
815
847
 
816
-
817
- // Set '@Core.Computed' in the Core Compiler to have it propagated...
818
- if (art.kind !== 'element' || art['@Core.Computed'])
819
- return;
820
-
821
- // For events and types, elements can't be @Core.Computed, as values are only used
822
- // to infer the element signature. For virtual, we keep @Core.Computed, as it's
823
- // always been that way, even before type projections.
824
- const elementsCanBeComputed = art._main?.kind !== 'type' && art._main?.kind !== 'event';
825
-
826
- if (art.virtual?.val ||
827
- elementsCanBeComputed && art.value &&
828
- (!art.value._artifact || !art.value.path || // in localization view: _artifact, but no path
829
- art.value.stored?.val || // calculated elements on-write are always computed
830
- art.value._artifact.kind === 'builtin' ||
831
- art.value._artifact.kind === 'param' ||
832
- art.value.scope === 'param' )) {
833
- art['@Core.Computed'] = {
834
- name: {
835
- path: [ { id: 'Core.Computed', location: art.location } ],
836
- location: art.location,
837
- },
838
- $inferred: '$generated',
839
- };
848
+ /**
849
+ * Issue warnings for restrictions concerning `localized`, i.e. for situations
850
+ * where a (later) inherited `localized` does not lead to the texts entity
851
+ * (element) being created, because the inherited info was not available then,
852
+ * or would involve more work (localized sub elements).
853
+ */
854
+ function checkLocalizedElement( art ) {
855
+ const parent = art._parent;
856
+ if (!parent)
857
+ return; // with duplicate defs
858
+ const isSubElem = (parent.kind === 'element' || art._outer);
859
+ if (isSubElem) { // sub element or in MANY
860
+ // Localized sub elements in types, aspects, parameters and non-query
861
+ // entities are not problematic. They are just not really useful there
862
+ // just report direct (not inherited) `localized` usage in non-inferred
863
+ // elements then. For non-query entities, always report.
864
+ if (art._main?.kind !== 'entity' || art._main?.query || userParam( parent )
865
+ ? !art.$inferred && art.localized?.val && art._main?.kind !== 'annotation'
866
+ : getInheritedProp( art, 'localized' )) {
867
+ const loc = (art.localized || art.type || art.value)?.location || art.location;
868
+ warning( 'type-unsupported-localized', [ loc, art ], {},
869
+ 'Localized sub elements are not supported' );
870
+ }
871
+ }
872
+ else if (parent.kind === 'entity' && !art._main?.query &&
873
+ art.$syntax !== 'calc' &&
874
+ getInheritedProp( art, 'localized' )?.val &&
875
+ // no inherited `localized` which wasn't known in generate.js
876
+ // TODO: should we set `localized` to null otherwise?
877
+ !hasTruthyProp( art, 'localized' )) {
878
+ const loc = (art.localized || art.type)?.location || art.location;
879
+ warning( 'type-missing-localized', [ loc, art ], { keyword: 'localized' },
880
+ 'Add keyword $(KEYWORD), can\'t derive early enough that the element is localized' );
840
881
  }
841
882
  }
842
883
 
@@ -1132,6 +1173,12 @@ function resolve( model ) {
1132
1173
  }
1133
1174
 
1134
1175
  function addImplicitForeignKeys( art, obj, target ) {
1176
+ const max = art.cardinality?.targetMax;
1177
+ if (max && (typeof max.val !== 'number' || max.val > 1) &&
1178
+ !art.$inferred && !art.virtual?.val) {
1179
+ warning( 'assoc-incomplete-to-many', [ max.location, art ], null,
1180
+ 'Provide an ON-condition or foreign keys to this to-many association' );
1181
+ }
1135
1182
  obj.foreignKeys = Object.create( null );
1136
1183
  forEachInOrder( target, 'elements', ( elem, name ) => {
1137
1184
  if (elem.key?.val) {