@sap/cds-compiler 4.0.2 → 4.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.md +100 -5
  2. package/bin/cdsc.js +12 -12
  3. package/doc/CHANGELOG_BETA.md +11 -0
  4. package/lib/api/main.js +31 -11
  5. package/lib/api/validate.js +1 -1
  6. package/lib/base/location.js +6 -7
  7. package/lib/base/message-registry.js +84 -38
  8. package/lib/base/messages.js +11 -10
  9. package/lib/base/model.js +6 -2
  10. package/lib/checks/defaultValues.js +6 -6
  11. package/lib/checks/foreignKeys.js +0 -5
  12. package/lib/checks/onConditions.js +17 -12
  13. package/lib/checks/queryNoDbArtifacts.js +132 -72
  14. package/lib/checks/sql-snippets.js +15 -4
  15. package/lib/checks/types.js +3 -3
  16. package/lib/checks/utils.js +1 -1
  17. package/lib/compiler/assert-consistency.js +44 -16
  18. package/lib/compiler/base.js +1 -0
  19. package/lib/compiler/builtins.js +7 -8
  20. package/lib/compiler/checks.js +274 -197
  21. package/lib/compiler/classes.js +62 -0
  22. package/lib/compiler/cycle-detector.js +3 -3
  23. package/lib/compiler/define.js +63 -50
  24. package/lib/compiler/extend.js +38 -20
  25. package/lib/compiler/finalize-parse-cdl.js +2 -1
  26. package/lib/compiler/generate.js +0 -8
  27. package/lib/compiler/index.js +9 -7
  28. package/lib/compiler/kick-start.js +2 -0
  29. package/lib/compiler/populate.js +139 -110
  30. package/lib/compiler/propagator.js +4 -3
  31. package/lib/compiler/resolve.js +157 -126
  32. package/lib/compiler/shared.js +706 -404
  33. package/lib/compiler/tweak-assocs.js +21 -10
  34. package/lib/compiler/utils.js +228 -36
  35. package/lib/edm/annotations/genericTranslation.js +1 -1
  36. package/lib/edm/edm.js +4 -1
  37. package/lib/edm/edmPreprocessor.js +5 -4
  38. package/lib/edm/edmUtils.js +2 -4
  39. package/lib/gen/Dictionary.json +34 -10
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +1 -1
  42. package/lib/gen/languageParser.js +3987 -3963
  43. package/lib/json/from-csn.js +43 -47
  44. package/lib/json/to-csn.js +11 -11
  45. package/lib/language/antlrParser.js +2 -1
  46. package/lib/language/genericAntlrParser.js +52 -43
  47. package/lib/language/language.g4 +59 -59
  48. package/lib/language/multiLineStringParser.js +2 -0
  49. package/lib/main.d.ts +5 -0
  50. package/lib/model/csnRefs.js +37 -19
  51. package/lib/model/csnUtils.js +20 -16
  52. package/lib/model/revealInternalProperties.js +29 -21
  53. package/lib/modelCompare/compare.js +112 -39
  54. package/lib/modelCompare/utils/filter.js +54 -24
  55. package/lib/optionProcessor.js +6 -6
  56. package/lib/render/manageConstraints.js +20 -17
  57. package/lib/render/toCdl.js +34 -20
  58. package/lib/render/toHdbcds.js +2 -2
  59. package/lib/render/toRename.js +4 -9
  60. package/lib/render/toSql.js +77 -26
  61. package/lib/render/utils/common.js +3 -3
  62. package/lib/render/utils/unique.js +52 -0
  63. package/lib/transform/db/applyTransformations.js +61 -20
  64. package/lib/transform/db/assertUnique.js +7 -8
  65. package/lib/transform/db/associations.js +2 -2
  66. package/lib/transform/db/cdsPersistence.js +8 -8
  67. package/lib/transform/db/expansion.js +17 -21
  68. package/lib/transform/db/flattening.js +23 -23
  69. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  70. package/lib/transform/db/temporal.js +1 -1
  71. package/lib/transform/db/transformExists.js +8 -7
  72. package/lib/transform/db/views.js +73 -33
  73. package/lib/transform/draft/db.js +11 -9
  74. package/lib/transform/draft/odata.js +1 -1
  75. package/lib/transform/{forOdataNew.js → forOdata.js} +6 -6
  76. package/lib/transform/forRelationalDB.js +69 -75
  77. package/lib/transform/localized.js +6 -5
  78. package/lib/transform/odata/toFinalBaseType.js +3 -3
  79. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +4 -101
  80. package/lib/transform/translateAssocsToJoins.js +14 -28
  81. package/package.json +1 -1
  82. package/share/messages/check-proper-type-of.md +1 -1
  83. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  84. package/share/messages/message-explanations.json +1 -1
@@ -0,0 +1,62 @@
1
+ // Base classes used as prototypes for XSN definitions, elements, etc.
2
+ // The goal is to have named classes that can be seen in performance analyses, e.g.
3
+ // by using the [DeOpt Explorer][1].
4
+ // All classes should also be constructible using `{ __proto__: Class, …}`, i.e.
5
+ // their constructors must not do anything besides assigning properties.
6
+ //
7
+ // Refer to these resources:
8
+ // - <https://mathiasbynens.be/notes/shapes-ics>
9
+ // - <https://v8.dev/blog/fast-properties>
10
+ //
11
+ // Before adding new properties, evaluate whether it has any performance
12
+ // impact. Too many properties that are rarely used could reduce performance,
13
+ // but too few could lead to inconsistent object shapes for commonly
14
+ // used properties.
15
+ //
16
+ // Use [DeOpt Explorer][1] to see the different object Maps by v8.
17
+ //
18
+ // [1]: https://devblogs.microsoft.com/typescript/introducing-deopt-explorer/
19
+
20
+ 'use strict';
21
+
22
+ class XsnSource {
23
+ kind = 'source';
24
+ location;
25
+ usings = [];
26
+ dependencies = [];
27
+ artifacts = Object.create(null);
28
+ vocabularies = Object.create(null);
29
+ extensions = [];
30
+ }
31
+
32
+ class XsnArtifact {
33
+ location;
34
+ name;
35
+ kind;
36
+ }
37
+
38
+ class XsnName {
39
+ location;
40
+ }
41
+
42
+ class CsnLocation {
43
+ file;
44
+ line;
45
+ col;
46
+ endLine;
47
+ endCol;
48
+ constructor(file, line, col, endLine, endCol) {
49
+ this.file = file;
50
+ this.line = line;
51
+ this.col = col;
52
+ this.endLine = endLine;
53
+ this.endCol = endCol;
54
+ }
55
+ }
56
+
57
+ module.exports = {
58
+ XsnSource,
59
+ XsnArtifact,
60
+ XsnName,
61
+ CsnLocation,
62
+ };
@@ -64,14 +64,14 @@ function detectCycles( definitions, reportCycle, cbScc ) {
64
64
  // console.log('PUSH: ', v.kind,v.name)
65
65
  }
66
66
  if (!v._deps) // builtins, otherwise forgotten (TODO: assert in --test-mode)
67
- setProp(v, '_deps', []);
67
+ setProp( v, '_deps', [] );
68
68
  // assert( v._scc.onStack );
69
69
 
70
70
  // Now consider successors of v (called w):
71
71
  while (v._scc.depIndex < v._deps.length) {
72
72
  const w = v._deps[v._scc.depIndex++].art;
73
73
  if (!w._scc) { // node has not yet been visited
74
- setProp(w, '_sccCaller', v);
74
+ setProp( w, '_sccCaller', v );
75
75
  // console.log('CALL: ', v._scc.depIndex )
76
76
  return w; // recursive call with w in recursive algorithm
77
77
  }
@@ -112,7 +112,7 @@ function detectCycles( definitions, reportCycle, cbScc ) {
112
112
  function defaultCb( w, v, r ) {
113
113
  for (const dep of w._deps) {
114
114
  if (dep.art._scc.lowlink === w._scc.lowlink) // in same SCC
115
- reportCycle( w, dep.art, dep.location );
115
+ reportCycle( w, dep.art, dep.location, dep.semanticLoc );
116
116
  }
117
117
  return r;
118
118
  }
@@ -142,6 +142,7 @@ const { compareLayer } = require('./moduleLayers');
142
142
  const { initBuiltins, isInReservedNamespace } = require('./builtins');
143
143
 
144
144
  const $location = Symbol.for('cds.$location');
145
+ const $inferred = Symbol.for('cds.$inferred');
145
146
 
146
147
  /**
147
148
  * Export function of this file. Transform argument `sources` = dictionary of
@@ -172,6 +173,7 @@ function define( model ) {
172
173
  initArtifact,
173
174
  initMembers,
174
175
  checkDefinitions, // TODO: remove
176
+ initSelectItems,
175
177
  } );
176
178
 
177
179
  let boundSelfParamType = true; // special `$self` for binding param must still be initialised
@@ -252,7 +254,7 @@ function define( model ) {
252
254
  }
253
255
  else if (src.definitions) { // CSN input
254
256
  prefix = '';
255
- dictForEach( shuffleDict( src.definitions ), v => addDefinition( v, src ) );
257
+ dictForEach( shuffleDict( src.definitions ), def => addDefinition( def, src, prefix ));
256
258
  }
257
259
  if (src.vocabularies) {
258
260
  if (!model.vocabularies)
@@ -264,7 +266,11 @@ function define( model ) {
264
266
  }
265
267
  }
266
268
 
267
- function addDefinition( art, block ) {
269
+ function addDefinition( art, block, prefix ) {
270
+ if (!art.name.absolute) {
271
+ // TODO: art.name.absolute = art.name.id || …
272
+ art.name.absolute = (!art.name.path) ? art.name.id : prefix + pathName(art.name.path);
273
+ }
268
274
  const { absolute } = art.name;
269
275
  // TODO: check reserved, see checkName()/checkLocalizedObjects() of checks.js
270
276
  if (absolute === 'cds' || isInReservedNamespace(absolute)) {
@@ -373,8 +379,7 @@ function define( model ) {
373
379
  function addArtifact( art, block, prefix ) {
374
380
  if (art.kind === 'using')
375
381
  return;
376
- art.name.absolute = prefix + pathName( art.name.path );
377
- addDefinition( art, block );
382
+ addDefinition( art, block, prefix );
378
383
  if (art.artifacts) {
379
384
  const p = `${ art.name.absolute }.`;
380
385
  // path prefixes (usings) must be added before extensions in artifacts:
@@ -444,8 +449,10 @@ function define( model ) {
444
449
  function addVocabulary( vocab, block, prefix ) {
445
450
  setLink( vocab, '_block', block );
446
451
  const { name } = vocab;
447
- if (!name.absolute)
448
- name.absolute = prefix + pathName( name.path );
452
+ if (!name.absolute) {
453
+ // TODO: art.name.absolute = vocab.name.id || …
454
+ vocab.name.absolute = (!vocab.name.path) ? vocab.name.id : prefix + pathName(vocab.name.path);
455
+ }
449
456
  dictAdd( model.vocabularies, name.absolute, vocab );
450
457
  }
451
458
 
@@ -574,7 +581,7 @@ function define( model ) {
574
581
  if (!art.query)
575
582
  return;
576
583
  art.$queries = [];
577
- setLink( art, '_from', [] ); // for sequence of resolve steps
584
+ setLink( art, '_from', [] ); // for sequence of resolve steps - TODO: remove
578
585
  if (!setLink( art, '_leadingQuery', initQueryExpression( art.query, art ) ) )
579
586
  return; // null or undefined in case of parse error
580
587
  // if (art._leadingQuery !== art.$queries [0]) throw Error('FOO');
@@ -880,9 +887,9 @@ function define( model ) {
880
887
  }
881
888
  }
882
889
 
883
- function initSelectItems( parent, columns, user ) {
884
- // TODO: forbid expand/inline with :param, global:true, in ref-where, outside queries (CSN), ...
885
- let wildcard = null;
890
+ function initSelectItems( parent, columns, user, inExtension = false ) {
891
+ let wildcard = !!inExtension; // no `extend with columns { * }`
892
+ // TODO: forbid expand/inline in ref-where, outside queries (CSN), ...
886
893
  let hasItems = false;
887
894
  for (const col of columns || parent.expand || parent.inline || []) {
888
895
  if (!col) // parse error
@@ -898,6 +905,11 @@ function define( model ) {
898
905
  if (!wildcard) {
899
906
  wildcard = col;
900
907
  }
908
+ else if (wildcard === true) { // in `extend … with columns {…}`
909
+ error( 'ext-unexpected-wildcard', [ col.location, parent ], { code: '*' },
910
+ 'Unexpected $(CODE) (wildcard) in an extension' );
911
+ col.val = null; // do not consider it for expandWildcard()
912
+ }
901
913
  else {
902
914
  // a late syntax error (this code also runs with parse-cdl), i.e.
903
915
  // no semantic loc (wouldn't be available for expand/inline anyway)
@@ -917,7 +929,8 @@ function define( model ) {
917
929
  }
918
930
  // Either expression (value), expand or new association (target && type)
919
931
  else if (col.value || col.expand || (col.target && col.type)) {
920
- setLink( col, '_block', parent._block );
932
+ if (!col._block)
933
+ setLink( col, '_block', parent._block );
921
934
  if (col.inline) { // `@anno elem.{ * }` does not work
922
935
  if (col.doc) {
923
936
  warning( 'syntax-ignoring-anno', [ col.doc.location, col ],
@@ -932,23 +945,18 @@ function define( model ) {
932
945
  }
933
946
  }
934
947
  // TODO: allow sub queries? at least in top-level expand without parallel ref
935
- if (columns)
948
+ if (columns && !inExtension) // not (yet) in `extend … with columns {…}`
936
949
  initExprForQuery( col.value, parent );
937
- initSelectItems( col, null, user );
950
+ initSelectItems( col, null, user ); // TODO: use col as user (i.e. remove param)
938
951
  }
939
952
  }
940
953
 
941
- if (hasItems && !wildcard && parent.excludingDict) {
942
- // TODO: Better way to get source file?
943
- let block = parent;
944
- while (block._block)
945
- block = block._block;
946
-
947
- if (block.$frontend === 'cdl') {
948
- warning('query-ignoring-exclude', [ parent.excludingDict[$location], user ],
949
- { prop: '*' },
950
- 'Excluding elements without wildcard $(PROP) has no effect');
951
- }
954
+ if (hasItems && !wildcard && parent.excludingDict && !options.$recompile) {
955
+ // TODO: the SQL backend should probably delete `excluding` when expanding `*`
956
+ // TODO: use `parent` for semantic location, use `-excluding`
957
+ warning( 'query-ignoring-exclude', [ parent.excludingDict[$location], user ],
958
+ { prop: '*' },
959
+ 'Excluding elements without wildcard $(PROP) has no effect');
952
960
  }
953
961
  }
954
962
 
@@ -1016,13 +1024,13 @@ function define( model ) {
1016
1024
  const { targetAspect } = obj;
1017
1025
  if (targetAspect) {
1018
1026
  if (obj.foreignKeys) {
1019
- error( 'unexpected-keys-for-composition', [ obj.foreignKeys[$location], construct ],
1027
+ error( 'type-unexpected-foreign-keys', [ obj.foreignKeys[$location], construct ],
1020
1028
  {},
1021
1029
  'A managed aspect composition can\'t have a foreign keys specification' );
1022
1030
  delete obj.foreignKeys; // continuation semantics: not specified
1023
1031
  }
1024
1032
  if (obj.on && !obj.target) {
1025
- error( 'unexpected-on-for-composition', [ obj.on.location, construct ],
1033
+ error( 'type-unexpected-on-condition', [ obj.on.location, construct ],
1026
1034
  {},
1027
1035
  'A managed aspect composition can\'t have a specified ON-condition' );
1028
1036
  delete obj.on; // continuation semantics: not specified
@@ -1056,30 +1064,33 @@ function define( model ) {
1056
1064
 
1057
1065
  function initElementsAsEnum() {
1058
1066
  // in extensions, extended enums are represented as elements
1059
- let firstEnum = null;
1067
+ let hasElement = false;
1060
1068
  for (const n in obj.elements) {
1061
1069
  const e = obj.elements[n];
1062
- if (e.kind === 'element') {
1063
- // An "element" has `$syntax: 'enum'` if it could also be an enum
1064
- if (e.$syntax === 'enum') { // TODO: what about "just name"? (current forbidden)
1065
- e.kind = 'enum';
1066
- firstEnum = firstEnum || e;
1067
- }
1068
- else {
1069
- // We do not want to complain separately about all element properties:
1070
- error( 'ext-unexpected-element', [ e.location, construct ],
1071
- { name: e.name.id, code: 'extend … with enum' },
1072
- // eslint-disable-next-line max-len
1073
- 'Unexpected elements like $(NAME) in an extension for an enum. Additionally, use $(CODE) when extending enums' );
1074
- return;
1075
- }
1070
+ if (e.kind === 'extend')
1071
+ continue;
1072
+ const noVal = e.value?.val === undefined && e.value?.sym === undefined;
1073
+ // TODO: forbid #symbol as enum value
1074
+ if (e.$syntax === 'element' || // `extend … with elements` or `extend with { element … }`
1075
+ noVal && e.$syntax !== 'enum' || // no value in CDL input
1076
+ e.virtual || e.key || e.masked || e.type || e.elements || e.items || e.stored) {
1077
+ // We do not want to complain separately about all element properties:
1078
+ error( 'ext-unexpected-element', [ e.location, construct ],
1079
+ { name: e.name.id, code: 'extend … with enum' },
1080
+ // eslint-disable-next-line max-len
1081
+ 'Unexpected elements like $(NAME) in an extension for an enum. Additionally, use $(CODE) when extending enums' );
1082
+ // Don't emit 'ext-expecting-enum' if this error is emitted.
1083
+ return;
1076
1084
  }
1085
+ e.kind = 'enum';
1086
+ if (noVal || e.$syntax !== 'enum')
1087
+ hasElement = true; // warning with CDL input or `name: {}` in CSN input
1077
1088
  }
1078
- if (firstEnum && block.$frontend !== 'json') {
1079
- // Don't emit this message if `ext-unexpected-element` was already emitted.
1080
- // This message is similar to the one above. In v5/6, we could probably remove the warning
1081
- // and always emit the error.
1082
- warning( 'ext-expecting-enum', [ firstEnum.location, construct ],
1089
+ if (hasElement) {
1090
+ // This message is similar to the one above. In v5/6, we could probably
1091
+ // turn this warning into an error, remove `$syntax: 'element' (also in
1092
+ // language.g4), and use the above `ext-unexpected-element` only for CSN input.
1093
+ warning( 'ext-expecting-enum', [ obj.elements[$location], construct ],
1083
1094
  { code: 'extend … with enum' }, 'Use $(CODE) when extending enums' );
1084
1095
  }
1085
1096
  forEachGeneric( { enum: obj.elements }, 'enum', init );
@@ -1231,7 +1242,7 @@ function define( model ) {
1231
1242
  if (!feature) {
1232
1243
  // Note: This error can't be triggered at the moment. But as we likely want to
1233
1244
  // allow extensions with params in the future, we keep the code.
1234
- error( 'unexpected-params', [ location, construct ], {},
1245
+ error( 'def-unexpected-params', [ location, construct ], {},
1235
1246
  'Parameters only exist for entities, actions or functions' );
1236
1247
  }
1237
1248
  else {
@@ -1252,7 +1263,7 @@ function define( model ) {
1252
1263
  }
1253
1264
  }
1254
1265
  else if (prop === 'elements') {
1255
- error( 'unexpected-elements', [ location, construct ], {},
1266
+ error( 'def-unexpected-elements', [ location, construct ], {},
1256
1267
  'Elements only exist in entities, types or typed constructs' );
1257
1268
  }
1258
1269
  else if (prop === 'columns') {
@@ -1270,14 +1281,16 @@ function define( model ) {
1270
1281
  function targetIsTargetAspect( elem ) {
1271
1282
  const { target } = elem;
1272
1283
  if (target.elements) {
1273
- // TODO: error if CSN has both target.elements and targetAspect.elements -> delete target
1284
+ // TODO: error if CSN has both target.elements and targetAspect.elements
1285
+ // -> delete target
1274
1286
  return true;
1275
1287
  }
1276
1288
  if (elem.targetAspect || options.parseCdl || !isDirectComposition( elem ))
1277
1289
  return false;
1278
1290
  const name = resolveUncheckedPath( target, 'target', elem );
1279
1291
  const aspect = name && model.definitions[name];
1280
- return aspect && (aspect.kind === 'aspect' || aspect.kind === 'type'); // type is sloppy
1292
+ return (aspect?.kind === 'aspect' || aspect?.kind === 'type') && // type is sloppy
1293
+ aspect.elements && !aspect.elements[$inferred];
1281
1294
  }
1282
1295
  }
1283
1296
 
@@ -23,10 +23,12 @@ const {
23
23
  } = require('./utils');
24
24
  const layers = require('./moduleLayers');
25
25
  const { CompilerAssertion } = require('../base/error');
26
+ const { CsnLocation } = require('./classes');
26
27
 
27
28
  const $location = Symbol.for('cds.$location');
28
29
 
29
- const genLocation = { file: '' }; // attach stupid location - TODO: remove in v4
30
+ // attach stupid location - TODO: remove in v5
31
+ const genLocation = new CsnLocation( '' );
30
32
 
31
33
  // Array.prototype.spread = 42; // prototype-polluted JS classes
32
34
 
@@ -41,6 +43,7 @@ function extend( model ) {
41
43
  resolveTypeArgumentsUnchecked,
42
44
  attachAndEmitValidNames,
43
45
  initMembers,
46
+ initSelectItems,
44
47
  } = model.$functions;
45
48
 
46
49
  Object.assign( model.$functions, {
@@ -383,12 +386,16 @@ function extend( model ) {
383
386
  const { query } = art;
384
387
  for (const col of ext.columns)
385
388
  col.$extended = true;
386
- if (!query?.from?.path)
389
+
390
+ if (!query?.from?.path) {
387
391
  error( 'extend-columns', [ ext.columns[$location], ext ], { art } );
388
- else if (!query.columns)
392
+ return;
393
+ }
394
+ if (!query.columns)
389
395
  query.columns = [ { location: query.from.location, val: '*' }, ...ext.columns ];
390
396
  else
391
397
  query.columns.push( ...ext.columns );
398
+ initSelectItems( query, ext.columns, query, true );
392
399
  }
393
400
  else if ([ 'length', 'precision', 'scale', 'srid' ].includes( prop )) {
394
401
  const typeExts = art.$typeExts || (art.$typeExts = {});
@@ -719,6 +726,11 @@ function extend( model ) {
719
726
  warning( 'anno-unexpected-params', [ location, ext._parent ], {},
720
727
  'Parameters only exist for actions or functions' );
721
728
  break;
729
+ case 'actions':
730
+ // TODO: check if artifact can have actions, similar to `anno-unexpected-actions`
731
+ notFound( 'anno-undefined-action', ext.name.location, ext,
732
+ { '#': 'action', art: parent, name } );
733
+ break;
722
734
  default:
723
735
  if (model.options.testMode)
724
736
  throw new CompilerAssertion(`Missing case for prop: ${ prop }`);
@@ -887,8 +899,15 @@ function extend( model ) {
887
899
  model._entities.push( art ); // add structure with includes in dep order
888
900
  art.$entity = ++model.$entity;
889
901
  }
890
- if (!noIncludes && art.includes)
891
- applyIncludes( art, art );
902
+ if (art.includes) {
903
+ if (!noIncludes) {
904
+ applyIncludes( art, art );
905
+ }
906
+ else {
907
+ for (const ref of art.includes)
908
+ resolvePath( ref, 'include', art );
909
+ }
910
+ }
892
911
  // checkExtensionsKind( extensions, art );
893
912
  extendMembers( extensions, art, noIncludes === 'gen' );
894
913
  if (!noIncludes && art.includes) {
@@ -1055,7 +1074,6 @@ function extend( model ) {
1055
1074
  function canApplyIncludes( art, target, justResolveCyclic ) {
1056
1075
  if (!art.includes)
1057
1076
  return true;
1058
- const isView = !!target.query;
1059
1077
  for (const ref of art.includes) {
1060
1078
  const name = resolveUncheckedPath( ref, 'include', art );
1061
1079
  // console.log('CAI:',justResolveCyclic, name, ref.path, Object.keys(extensionsDict))
@@ -1071,7 +1089,6 @@ function extend( model ) {
1071
1089
  else if (ref._artifact) {
1072
1090
  delete ref._artifact;
1073
1091
  }
1074
- resolvePath( ref, isView ? 'viewInclude' : 'include', art );
1075
1092
  }
1076
1093
  return true;
1077
1094
  }
@@ -1096,20 +1113,18 @@ function extend( model ) {
1096
1113
  return;
1097
1114
  }
1098
1115
 
1099
- if (!art.query) {
1100
- if (!art._ancestors)
1101
- setLink( art, '_ancestors', [] ); // recursive array of includes
1102
- for (const ref of ext.includes) {
1103
- const template = ref._artifact;
1104
- // !template -> non-includable, e.g. scalar type, or cyclic
1105
- if (template) {
1106
- if (template._ancestors)
1107
- art._ancestors.push( ...template._ancestors );
1108
- art._ancestors.push( template );
1109
- }
1116
+ if (!art._ancestors && !art.query)
1117
+ setLink( art, '_ancestors', [] ); // recursive array of includes
1118
+ for (const ref of ext.includes) {
1119
+ const template = resolvePath( ref, 'include', art );
1120
+ // !template -> non-includable, e.g. scalar type, or cyclic
1121
+ if (template && !art.query) {
1122
+ if (template._ancestors)
1123
+ art._ancestors.push( ...template._ancestors );
1124
+ art._ancestors.push( template );
1110
1125
  }
1111
1126
  }
1112
- if (!art.query) // do not set art.elements and art.enums with query entity!
1127
+ if (!art.query && art.elements) // do not set art.elements and art.enums with query entity!
1113
1128
  includeMembers( ext, art, 'elements' );
1114
1129
  includeMembers( ext, art, 'actions' );
1115
1130
  }
@@ -1128,12 +1143,15 @@ function extend( model ) {
1128
1143
  // Warning 'Overwrites definition from include "I" (at elem def)
1129
1144
  const parent = ext === art && art;
1130
1145
  const members = ext[prop];
1131
- ext[prop] = Object.create(null); // TODO: do not set actions property if there are none
1146
+ if (members || art[prop])
1147
+ ext[prop] = Object.create(null);
1132
1148
  let hasNewElement = false;
1133
1149
 
1134
1150
  for (const ref of ext.includes) {
1135
1151
  const template = ref._artifact; // already resolved
1136
1152
  if (template) { // be robust
1153
+ if (template[prop] && !ext[prop])
1154
+ ext[prop] = Object.create(null);
1137
1155
  // eslint-disable-next-line no-loop-func
1138
1156
  forEachInOrder( template, prop, ( origin, name ) => {
1139
1157
  if (members && members[name]) {
@@ -101,7 +101,8 @@ function finalizeParseCdl( model ) {
101
101
  // Recursively go through all XSN properties. There are a few that need to be
102
102
  // handled specifically. Refer to the code below this loop for details.
103
103
  for (const prop in artifact) {
104
- if (parseCdlSpeciallyHandledXsnProps.includes(prop) || parseCdlIgnoredXsnProps.includes(prop))
104
+ if (artifact[prop] === undefined || parseCdlSpeciallyHandledXsnProps.includes(prop) ||
105
+ parseCdlIgnoredXsnProps.includes(prop))
105
106
  continue;
106
107
 
107
108
  if (artifact[prop] && Object.getPrototypeOf(artifact[prop]) === null)
@@ -214,12 +214,6 @@ function generate( model ) {
214
214
  else if (isLocalized) {
215
215
  textElems.push( elem );
216
216
  }
217
-
218
- if (isKey && isLocalized) { // key with localized is wrong - ignore localized
219
- const errpos = elem.localized || elem.type || elem.name;
220
- warning( 'def-ignoring-localized-key', [ errpos.location, elem ], { keyword: 'localized' },
221
- 'Keyword $(KEYWORD) is ignored for primary keys' );
222
- }
223
217
  }
224
218
  if (textElems.length <= keys)
225
219
  return false;
@@ -386,7 +380,6 @@ function generate( model ) {
386
380
 
387
381
  if (!fioriEnabled) {
388
382
  // To be compatible, we switch off draft without @fiori.draft.enabled
389
- // TODO (next major version): remove?
390
383
  setAnnotation( art, '@odata.draft.enabled', art.location, false );
391
384
  }
392
385
  else {
@@ -446,7 +439,6 @@ function generate( model ) {
446
439
  if (!fioriEnabled) {
447
440
  locale.key = { val: true, location };
448
441
  // To be compatible, we switch off draft without @fiori.draft.enabled
449
- // TODO (next major version): remove?
450
442
  setAnnotation( art, '@odata.draft.enabled', art.location, false );
451
443
  }
452
444
  else {
@@ -38,6 +38,7 @@ const { cdsFs } = require('../utils/file');
38
38
 
39
39
  const fs = require('fs');
40
40
  const path = require('path');
41
+ const { CsnLocation, XsnSource } = require('./classes');
41
42
 
42
43
  const extensionParsers = {
43
44
  csn: parseCsn.parse,
@@ -91,7 +92,8 @@ function parseX( source, filename, options = {}, messageFunctions = null ) {
91
92
  if (parser)
92
93
  return parser( source, filename, options, messageFunctions );
93
94
 
94
- const model = { location: { file: filename } };
95
+ const model = new XsnSource();
96
+ model.location = new CsnLocation( filename );
95
97
  messageFunctions.error( 'file-unknown-ext', emptyWeakLocation(filename),
96
98
  { file: ext, '#': !ext && 'none' }, {
97
99
  std: 'Unknown file extension $(FILE)',
@@ -171,12 +173,12 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
171
173
  return []; // no further dependency processing
172
174
  // no parallel readAndParse with same resolved filename should read the file,
173
175
  // also ensure deterministic sequence in sources:
174
- sources[filename] = { location: { file: rel } };
176
+ sources[filename] = { location: new CsnLocation( rel ) };
175
177
 
176
178
  const source = await cdsFs( fileCache, options.traceFs ).readFileAsync( filename, 'utf8' );
177
179
  const ast = parseX( source, rel, options, model.$messageFunctions );
178
180
  sources[filename] = ast;
179
- ast.location = { file: rel };
181
+ ast.location = new CsnLocation( rel );
180
182
  ast.dirname = path.dirname( filename );
181
183
  assertConsistency( ast, options );
182
184
 
@@ -290,7 +292,7 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
290
292
  }
291
293
  // no parallel readAndParse with same resolved filename should read the file,
292
294
  // also ensure deterministic sequence in a.sources:
293
- a.sources[filename] = { location: { file: rel } };
295
+ a.sources[filename] = { location: new CsnLocation( rel ) };
294
296
 
295
297
  cdsFs( fileCache, options.traceFs ).readFileSync( filename, 'utf8', (err, source) => {
296
298
  if (err) {
@@ -300,7 +302,7 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
300
302
  try {
301
303
  const ast = parseX( source, rel, options, model.$messageFunctions );
302
304
  a.sources[filename] = ast;
303
- ast.location = { file: rel };
305
+ ast.location = new CsnLocation( rel );
304
306
  ast.dirname = path.dirname( filename );
305
307
  assertConsistency( ast, options );
306
308
  cb( null, ast );
@@ -373,7 +375,7 @@ function compileSourcesX( sourcesDict, options = {} ) {
373
375
  if (typeof source === 'string') {
374
376
  const ast = parseX( source, filename, options, model.$messageFunctions );
375
377
  sources[filename] = ast;
376
- ast.location = { file: filename };
378
+ ast.location = new CsnLocation( filename );
377
379
  assertConsistency( ast, options );
378
380
  }
379
381
  else if (options.$xsnObjects) { // source is a XSN object with option $xsnObjects
@@ -382,7 +384,7 @@ function compileSourcesX( sourcesDict, options = {} ) {
382
384
  else { // source is a CSN object
383
385
  const ast = parseCsn.augment( source, filename, options, model.$messageFunctions );
384
386
  sources[filename] = ast;
385
- ast.location = { file: filename };
387
+ ast.location = new CsnLocation( filename );
386
388
  assertConsistency( ast, options );
387
389
  }
388
390
 
@@ -125,6 +125,7 @@ function kickStart( model ) {
125
125
  if (elem.target && type && type[0] && type[0].id === 'cds.Composition') {
126
126
  // A target aspect would have already moved to property `targetAspect` in
127
127
  // define.js (hm... more something for kick-start.js...)
128
+ // TODO: for safety, just use resolveUncheckedPath()
128
129
  const target = resolvePath( elem.target, 'target', elem );
129
130
  if (target)
130
131
  model.$compositionTargets[target.name.absolute] = true;
@@ -134,6 +135,7 @@ function kickStart( model ) {
134
135
 
135
136
  // Resolve the using declarations in `using`. Issue
136
137
  // error message if the referenced artifact does not exist.
138
+ // TODO: think of moving this to resolve.js
137
139
  function resolveUsings( src, topLevel ) {
138
140
  if (!src.usings)
139
141
  return;