@sap/cds-compiler 2.11.4 → 2.13.8

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 (133) hide show
  1. package/CHANGELOG.md +159 -1
  2. package/bin/cds_update_identifiers.js +7 -7
  3. package/bin/cdsc.js +22 -23
  4. package/bin/cdsse.js +2 -2
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +25 -6
  7. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  8. package/doc/NameResolution.md +21 -16
  9. package/lib/api/main.js +30 -63
  10. package/lib/api/options.js +5 -5
  11. package/lib/api/validate.js +0 -5
  12. package/lib/backends.js +15 -23
  13. package/lib/base/dictionaries.js +0 -8
  14. package/lib/base/error.js +26 -0
  15. package/lib/base/keywords.js +7 -17
  16. package/lib/base/location.js +9 -4
  17. package/lib/base/message-registry.js +52 -2
  18. package/lib/base/messages.js +16 -26
  19. package/lib/base/model.js +2 -62
  20. package/lib/base/optionProcessorHelper.js +246 -183
  21. package/lib/checks/.eslintrc.json +2 -0
  22. package/lib/checks/actionsFunctions.js +2 -1
  23. package/lib/checks/annotationsOData.js +1 -1
  24. package/lib/checks/cdsPersistence.js +2 -1
  25. package/lib/checks/enricher.js +17 -1
  26. package/lib/checks/foreignKeys.js +4 -4
  27. package/lib/checks/invalidTarget.js +3 -1
  28. package/lib/checks/managedInType.js +4 -4
  29. package/lib/checks/managedWithoutKeys.js +3 -1
  30. package/lib/checks/queryNoDbArtifacts.js +1 -3
  31. package/lib/checks/selectItems.js +4 -4
  32. package/lib/checks/sql-snippets.js +94 -0
  33. package/lib/checks/types.js +1 -1
  34. package/lib/checks/validator.js +12 -7
  35. package/lib/compiler/assert-consistency.js +10 -6
  36. package/lib/compiler/base.js +0 -1
  37. package/lib/compiler/builtins.js +8 -6
  38. package/lib/compiler/checks.js +46 -12
  39. package/lib/compiler/cycle-detector.js +1 -1
  40. package/lib/compiler/define.js +1103 -0
  41. package/lib/compiler/extend.js +983 -0
  42. package/lib/compiler/finalize-parse-cdl.js +231 -0
  43. package/lib/compiler/index.js +33 -14
  44. package/lib/compiler/kick-start.js +190 -0
  45. package/lib/compiler/moduleLayers.js +4 -4
  46. package/lib/compiler/populate.js +1226 -0
  47. package/lib/compiler/propagator.js +113 -47
  48. package/lib/compiler/resolve.js +1433 -0
  49. package/lib/compiler/shared.js +76 -38
  50. package/lib/compiler/tweak-assocs.js +529 -0
  51. package/lib/compiler/utils.js +204 -33
  52. package/lib/edm/.eslintrc.json +5 -0
  53. package/lib/edm/annotations/genericTranslation.js +38 -25
  54. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  55. package/lib/edm/csn2edm.js +10 -9
  56. package/lib/edm/edm.js +19 -20
  57. package/lib/edm/edmPreprocessor.js +166 -95
  58. package/lib/edm/edmUtils.js +127 -34
  59. package/lib/gen/Dictionary.json +92 -43
  60. package/lib/gen/language.checksum +1 -1
  61. package/lib/gen/language.interp +11 -1
  62. package/lib/gen/language.tokens +86 -82
  63. package/lib/gen/languageLexer.interp +18 -1
  64. package/lib/gen/languageLexer.js +925 -847
  65. package/lib/gen/languageLexer.tokens +78 -74
  66. package/lib/gen/languageParser.js +5434 -4298
  67. package/lib/json/from-csn.js +59 -17
  68. package/lib/json/to-csn.js +143 -71
  69. package/lib/language/antlrParser.js +3 -3
  70. package/lib/language/docCommentParser.js +3 -3
  71. package/lib/language/genericAntlrParser.js +144 -54
  72. package/lib/language/language.g4 +424 -203
  73. package/lib/language/multiLineStringParser.js +536 -0
  74. package/lib/main.d.ts +472 -61
  75. package/lib/main.js +38 -11
  76. package/lib/model/api.js +3 -1
  77. package/lib/model/csnRefs.js +321 -204
  78. package/lib/model/csnUtils.js +224 -263
  79. package/lib/model/enrichCsn.js +97 -40
  80. package/lib/model/revealInternalProperties.js +27 -6
  81. package/lib/model/sortViews.js +2 -1
  82. package/lib/modelCompare/compare.js +17 -12
  83. package/lib/optionProcessor.js +7 -6
  84. package/lib/render/DuplicateChecker.js +1 -1
  85. package/lib/render/manageConstraints.js +36 -33
  86. package/lib/render/toCdl.js +174 -275
  87. package/lib/render/toHdbcds.js +201 -115
  88. package/lib/render/toRename.js +7 -10
  89. package/lib/render/toSql.js +149 -75
  90. package/lib/render/utils/common.js +22 -8
  91. package/lib/render/utils/sql.js +10 -7
  92. package/lib/render/utils/stringEscapes.js +111 -0
  93. package/lib/sql-identifier.js +1 -1
  94. package/lib/transform/.eslintrc.json +5 -0
  95. package/lib/transform/braceExpression.js +4 -2
  96. package/lib/transform/db/.eslintrc.json +2 -0
  97. package/lib/transform/db/applyTransformations.js +35 -12
  98. package/lib/transform/db/assertUnique.js +1 -1
  99. package/lib/transform/db/associations.js +187 -0
  100. package/lib/transform/db/cdsPersistence.js +150 -0
  101. package/lib/transform/db/constraints.js +61 -56
  102. package/lib/transform/db/expansion.js +50 -29
  103. package/lib/transform/db/flattening.js +552 -105
  104. package/lib/transform/db/groupByOrderBy.js +3 -1
  105. package/lib/transform/db/temporal.js +236 -0
  106. package/lib/transform/db/transformExists.js +94 -28
  107. package/lib/transform/db/views.js +5 -4
  108. package/lib/transform/draft/.eslintrc.json +38 -0
  109. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  110. package/lib/transform/draft/odata.js +227 -0
  111. package/lib/transform/forHanaNew.js +94 -801
  112. package/lib/transform/forOdataNew.js +22 -175
  113. package/lib/transform/localized.js +36 -32
  114. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  115. package/lib/transform/odata/referenceFlattener.js +95 -89
  116. package/lib/transform/odata/structureFlattener.js +1 -1
  117. package/lib/transform/odata/toFinalBaseType.js +86 -12
  118. package/lib/transform/odata/typesExposure.js +5 -5
  119. package/lib/transform/odata/utils.js +2 -2
  120. package/lib/transform/transformUtilsNew.js +47 -33
  121. package/lib/transform/translateAssocsToJoins.js +10 -27
  122. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  123. package/lib/transform/universalCsn/coreComputed.js +170 -0
  124. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  125. package/lib/transform/universalCsn/utils.js +63 -0
  126. package/lib/utils/file.js +2 -1
  127. package/lib/utils/objectUtils.js +30 -0
  128. package/lib/utils/timetrace.js +8 -2
  129. package/package.json +1 -1
  130. package/share/messages/README.md +26 -0
  131. package/lib/compiler/definer.js +0 -2340
  132. package/lib/compiler/resolver.js +0 -2988
  133. package/lib/transform/universalCsnEnricher.js +0 -67
@@ -0,0 +1,231 @@
1
+ // Things which needs to done for parse.cdl after define()
2
+
3
+ 'use strict';
4
+
5
+ const { forEachGeneric, forEachMember } = require('../base/model');
6
+ const { setLink, setArtifactLink } = require('./utils');
7
+
8
+
9
+ // Used for resolving types in parseCdl mode.
10
+ // See resolveTypesForParseCdl().
11
+ const parseCdlSpeciallyHandledXsnProps = [ '$queries', 'mixin', 'columns', 'args', 'returns' ];
12
+ const parseCdlIgnoredXsnProps = [ 'location', 'query', '$tableAliases' ];
13
+
14
+ function finalizeParseCdl( model ) {
15
+ // Get simplified "resolve" functionality and the message function:
16
+ const { message, error } = model.$messageFunctions;
17
+ const {
18
+ resolveUncheckedPath,
19
+ resolveTypeArguments,
20
+ defineAnnotations,
21
+ initMembers,
22
+ extensionsDict,
23
+ } = model.$functions;
24
+
25
+ resolveTypesAndExtensionsForParseCdl();
26
+ return;
27
+
28
+ function resolveTypesAndExtensionsForParseCdl() {
29
+ if (!model.extensions)
30
+ model.extensions = [];
31
+
32
+ // TODO: probably better to loop over extensions of all sources (there is just one)
33
+ for (const name in extensionsDict) {
34
+ for (const ext of extensionsDict[name]) {
35
+ ext.name.absolute = resolveUncheckedPath( ext.name, 'extend', ext );
36
+ // Define annotations of this top-level extension
37
+ defineAnnotations( ext, ext, ext._block );
38
+ mergeAnnotatesForSameArtifact( ext );
39
+ // Initialize members and define annotations in sub-elements.
40
+ initMembers( ext, ext, ext._block, true );
41
+ model.extensions.push( ext );
42
+ }
43
+ }
44
+
45
+ forEachGeneric(model, 'definitions', art => resolveTypesForParseCdl(art, art));
46
+ forEachGeneric(model, 'vocabularies', art => resolveTypesForParseCdl(art, art));
47
+ if (model.extensions)
48
+ model.extensions.forEach(ext => resolveTypesForParseCdl(ext, ext));
49
+ }
50
+
51
+ /**
52
+ * Resolve all types in parseCdl mode for the given artifact.
53
+ * `main` refers to the current user/scope, e.g. top-level definition, element, function, etc.
54
+ *
55
+ * @param {*} artifact
56
+ * @param {XSN.Artifact} main
57
+ */
58
+ function resolveTypesForParseCdl(artifact, main) {
59
+ if (!artifact || typeof artifact !== 'object')
60
+ return;
61
+
62
+ if (Array.isArray(artifact)) {
63
+ // e.g. `args` array
64
+ artifact.forEach(art => resolveTypesForParseCdl(art, main));
65
+ return;
66
+ }
67
+
68
+ if (artifact.kind === 'namespace' || artifact.kind === 'service' || artifact.kind === 'context')
69
+ // Services and context artifacts don't have any types.
70
+ return;
71
+
72
+ if (artifact.type)
73
+ resolveTypeUnchecked(artifact, main);
74
+
75
+ for (const include of artifact.includes || [])
76
+ resolveUncheckedPath(include, 'include', main);
77
+
78
+ if (artifact.target)
79
+ resolveUncheckedPath(artifact.target, 'target', main);
80
+
81
+ if (artifact.from) {
82
+ const { from } = artifact;
83
+ // Note: `_from` only contains sources necessary for calculating elements,
84
+ // not e.g. those from the `where exists` clause.
85
+ resolveUncheckedPath(from, 'from', main);
86
+ resolveTypesForParseCdl(from, main);
87
+ }
88
+
89
+ if (artifact.targetAspect) {
90
+ if (artifact.targetAspect.path)
91
+ resolveUncheckedPath(artifact.targetAspect, 'target', main);
92
+ resolveTypesForParseCdl(artifact.targetAspect, main);
93
+ }
94
+
95
+ // Recursively go through all XSN properties. There are a few that need to be
96
+ // handled specifically. Refer to the code below this loop for details.
97
+ for (const prop in artifact) {
98
+ if (parseCdlSpeciallyHandledXsnProps.includes(prop) || parseCdlIgnoredXsnProps.includes(prop))
99
+ continue;
100
+
101
+ // define.js (and initMembers()) initializes annotations. If there is a duplicate, the
102
+ // annotation is an array in XSN.
103
+ if (prop[0] === '@' && Array.isArray(artifact[prop]))
104
+ chooseAndReportDuplicateAnnotation(artifact, prop);
105
+
106
+ if (artifact[prop] && Object.getPrototypeOf(artifact[prop]) === null)
107
+ // Dictionary in XSN
108
+ forEachGeneric(artifact, prop, art => resolveTypesForParseCdl(art, art));
109
+ else
110
+ resolveTypesForParseCdl(artifact[prop], main);
111
+ }
112
+
113
+ // `$queries` has a flat structure that we can use instead of going through all `query`.
114
+ // For these query-related properties, we need to keep the reference to the artifact
115
+ // containing it. Otherwise some type's aren't properly resolved.
116
+ // TODO: If resolveTypeUnchecked is reworked, we may be able to simplify this coding.
117
+ (artifact.$queries || []).forEach( art => resolveTypesForParseCdl(art, artifact) );
118
+ (artifact.columns || []).forEach( art => resolveTypesForParseCdl(art, artifact) );
119
+ forEachGeneric( artifact, 'mixin', art => resolveTypesForParseCdl(art, artifact) );
120
+
121
+ // For better error messages for `type of`s in `returns`, we pass the object as the new main.
122
+ resolveTypesForParseCdl(artifact.returns, artifact.returns);
123
+
124
+ // Because `args` can be used in different contexts with different semantics,
125
+ // it needs to be handled specifically.
126
+ if (artifact.args && typeof artifact.args === 'object') {
127
+ if (Array.isArray(artifact.args)) {
128
+ // `args` may either be an array (e.g. query 'from' args) ...
129
+ artifact.args.forEach((from) => {
130
+ // ... and could be either inside a `from` ...
131
+ if (from && from.kind === '$tableAlias')
132
+ resolveUncheckedPath(from, 'from', from._main);
133
+
134
+ // ... or only params ...
135
+ resolveTypesForParseCdl(from, main);
136
+ });
137
+ }
138
+ else {
139
+ // ... or dictionary (e.g. params)
140
+ forEachGeneric(artifact, 'args', obj => resolveTypesForParseCdl(obj, obj));
141
+ }
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Resolves `artWithType.type` in an unchecked manner. Handles `type of` cases.
147
+ *
148
+ * @param {object} artWithType
149
+ * @param {XSN.Artifact} artifact
150
+ */
151
+ function resolveTypeUnchecked(artWithType, artifact) {
152
+ if (!artWithType.type)
153
+ return;
154
+ const root = artWithType.type.path && artWithType.type.path[0];
155
+ if (!root) // parse error
156
+ return;
157
+ // `scope` is only `typeOf` for `type of element` and not
158
+ // `type of Entity:element`. For the latter we can resolve the path
159
+ // without special treatment.
160
+ if (artWithType.type.scope !== 'typeOf') {
161
+ // elem: Type or elem: type of Artifact:elem
162
+ const name = resolveUncheckedPath(artWithType.type, 'type', artifact);
163
+ const def = name && model.definitions[name];
164
+ if (def)
165
+ resolveTypeArguments( artWithType, def, artifact );
166
+ return;
167
+ }
168
+ else if (!artifact._main) {
169
+ error( 'ref-undefined-typeof', [ artWithType.type.location, artifact ], {},
170
+ 'Current artifact has no element to refer to as type' );
171
+ return;
172
+ }
173
+ else if (root.id === '$self' || root.id === '$projection') {
174
+ setArtifactLink( root, artifact._main );
175
+ }
176
+ else {
177
+ // For better error messages, check for invalid TYPE OFs similarly
178
+ // to how `resolveType()` does.
179
+ let struct = artWithType;
180
+ while (struct.kind === 'element')
181
+ struct = struct._parent;
182
+ if (struct.kind === 'select' || struct !== artifact._main) {
183
+ message( 'type-unexpected-typeof', [ artWithType.type.location, artifact ],
184
+ { keyword: 'type of', '#': struct.kind } );
185
+ return;
186
+ }
187
+
188
+ const fake = { name: { absolute: artifact.name.absolute } };
189
+ // to-csn just needs a fake element whose absolute name and _parent/_main links are correct
190
+ setLink( fake, '_parent', artifact._parent );
191
+ setLink( fake, '_main', artifact._main ); // value does not matter...
192
+ setArtifactLink( root, fake );
193
+ }
194
+ resolveTypeArguments( artifact, {}, artifact ); // issue error for type args
195
+ }
196
+
197
+ function chooseAndReportDuplicateAnnotation(artifact, annoName) {
198
+ for (const anno of artifact[annoName])
199
+ message( 'anno-duplicate', [ anno.name.location, artifact ], { anno: annoName } );
200
+
201
+ // Choose any annotation, doesn't matter because of the error above.
202
+ artifact[annoName] = artifact[annoName][0];
203
+ }
204
+
205
+ /**
206
+ * For duplicate entries in `annotate {}` blocks, we de-duplicate the entries and merge them.
207
+ * Since it is allowed in "normal" compilations to have e.g.
208
+ * `annotate E with { @anno1 id, @anno2 id }`.
209
+ *
210
+ * @param {object} ext
211
+ */
212
+ function mergeAnnotatesForSameArtifact( ext ) {
213
+ if (!ext || typeof ext !== 'object')
214
+ return;
215
+
216
+ forEachMember(ext, sub => mergeAnnotatesForSameArtifact(sub));
217
+
218
+ if (ext.$annotations && Array.isArray(ext.$duplicates)) {
219
+ const annotates = ext.$duplicates.filter(val => (val.kind === 'annotate'));
220
+ for (const dup of annotates) {
221
+ ext.$annotations.push(...dup.$annotations);
222
+ delete dup.$annotations;
223
+ }
224
+ ext.$duplicates = ext.$duplicates.filter(val => (val.kind !== 'annotate'));
225
+ if (ext.$duplicates.length === 0)
226
+ delete ext.$duplicates;
227
+ }
228
+ }
229
+ }
230
+
231
+ module.exports = finalizeParseCdl;
@@ -21,8 +21,13 @@ const parseCsn = require('../json/from-csn');
21
21
  const assertConsistency = require('./assert-consistency');
22
22
  const moduleLayers = require('./moduleLayers');
23
23
  const { fns } = require('./shared');
24
- const { define } = require('./definer');
25
- const resolve = require('./resolver');
24
+ const define = require('./define');
25
+ const finalizeParseCdl = require('./finalize-parse-cdl');
26
+ const extend = require('./extend');
27
+ const kickStart = require('./kick-start');
28
+ const populate = require('./populate');
29
+ const resolve = require('./resolve');
30
+ const tweakAssocs = require('./tweak-assocs');
26
31
  const propagator = require('./propagator');
27
32
  const check = require('./checks');
28
33
 
@@ -34,6 +39,8 @@ const { cdsFs } = require('../utils/file');
34
39
  const fs = require('fs');
35
40
  const path = require('path');
36
41
 
42
+ const csnExtensions = [ '.json', '.csn' ];
43
+ const cdlExtensions = [ '.cds', '.hdbcds', '.hdbdd', '.cdl' ];
37
44
 
38
45
  // Class for command invocation errors. Additional members:
39
46
  // `errors`: vector of errors (file IO or ArgumentError)
@@ -56,21 +63,21 @@ class ArgumentError extends Error {
56
63
 
57
64
  /**
58
65
  * Parse the given source with the correct parser based on the file name's
59
- * extension. For example use edm2csn for `.xml` files and the CDL parser
60
- * for `.cds` files.
66
+ * extension. For example uses CDL parser for `.cds` files.
67
+ * Respects the value of `options.fallbackParser`.
61
68
  *
62
69
  * @param {string} source Source code of the file.
63
70
  * @param {string} filename Filename including its extension, e.g. "file.cds"
64
71
  * @param {object} options Compile options
65
72
  * @param {object} messageFunctions If not provided, parse errors will not lead to an exception
66
73
  */
67
- function parseX( source, filename, options = {}, messageFunctions ) {
74
+ function parseX( source, filename, options = {}, messageFunctions = null ) {
68
75
  if (!messageFunctions)
69
76
  messageFunctions = createMessageFunctions( options, 'parse' );
70
77
  const ext = path.extname( filename ).toLowerCase();
71
- if ([ '.json', '.csn' ].includes(ext) || options.fallbackParser === 'csn!')
78
+ if (csnExtensions.includes(ext) || options.fallbackParser === 'csn!')
72
79
  return parseCsn.parse( source, filename, options, messageFunctions );
73
- if ([ '.cds', '.hdbcds', '.hdbdd', '.cdl' ].includes(ext))
80
+ if (cdlExtensions.includes(ext))
74
81
  return parseLanguage( source, filename, options, messageFunctions );
75
82
  if (options.fallbackParser === 'csn')
76
83
  return parseCsn.parse( source, filename, options, messageFunctions );
@@ -89,8 +96,8 @@ function parseX( source, filename, options = {}, messageFunctions ) {
89
96
  // Main function: Compile the sources from the files given by the array of
90
97
  // `filenames`. As usual with the `fs` library, relative file names are
91
98
  // relative to the working directory `process.cwd()`. With argument `dir`, the
92
- // file names are relative to `process.cwd()+dir`. Options can have the
93
- // following properties:
99
+ // file names are relative to `process.cwd()+dir` (or just `dir` if it is absolute).
100
+ // Options can have the following properties:
94
101
  // - Truthy `parseOnly`: stop compilation after parsing.
95
102
  // - Truthy `lintMode`: do not do checks and propagation
96
103
  // - many others - TODO
@@ -102,7 +109,7 @@ function parseX( source, filename, options = {}, messageFunctions ) {
102
109
  //
103
110
  // The promise is fulfilled if all files could be read and processed without
104
111
  // errors. The fulfillment value is an augmented CSN (see
105
- // ./compiler/definer.js).
112
+ // ./compiler/define.js).
106
113
  //
107
114
  // If there are errors, the promise is rejected. If there was an invocation
108
115
  // error (repeated filenames or if the file could not be read), the rejection
@@ -326,9 +333,10 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
326
333
  /**
327
334
  * Promise-less main functions: compile the given sources.
328
335
  *
329
- * Argument `sourcesDict` is a dictionary (it could actually be a ordinary object)
330
- * mapping filenames to either source texts (string) or XSN objects (AST-like
331
- * augmented CSNs). It could also be a simple string, which is then considered
336
+ * Argument `sourcesDict` is a dictionary (it could actually be an ordinary object)
337
+ * mapping filenames to either source texts (string) or JS objects; the objects
338
+ * are usually CSNs, or XSNs (AST-like augmented CSNs) with option `$xsnObjects`.
339
+ * It could also be a simple string, which is then considered
332
340
  * to be the source text of a file named `<stdin>.cds`.
333
341
  *
334
342
  * See function `compileX` for the meaning of the argument `options`. If there
@@ -356,9 +364,15 @@ function compileSourcesX( sourcesDict, options = {} ) {
356
364
  ast.location = { file: filename };
357
365
  assertConsistency( ast, options );
358
366
  }
359
- else { // source is a XSN object (CSN/CDL parser output)
367
+ else if (options.$xsnObjects) { // source is a XSN object with option $xsnObjects
360
368
  sources[filename] = source;
361
369
  }
370
+ else { // source is a CSN object
371
+ const ast = parseCsn.augment( source, filename, options, model.$messageFunctions );
372
+ sources[filename] = ast;
373
+ ast.location = { file: filename };
374
+ assertConsistency( ast, options );
375
+ }
362
376
  }
363
377
  moduleLayers.setLayers( sources );
364
378
 
@@ -420,10 +434,15 @@ function compileDoX( model ) {
420
434
  // do not run the resolver in parse-cdl mode or we get duplicate annotations, etc.
421
435
  // TODO: do not use this function for parseCdl anyway…
422
436
  if (options.parseCdl) {
437
+ finalizeParseCdl( model );
423
438
  throwWithError();
424
439
  return model;
425
440
  }
441
+ extend( model );
442
+ kickStart( model );
443
+ populate( model );
426
444
  resolve( model );
445
+ tweakAssocs( model );
427
446
  assertConsistency( model );
428
447
  throwWithError();
429
448
  if (options.lintMode)
@@ -0,0 +1,190 @@
1
+ // Kick-start: prepare to resolve all references
2
+
3
+ 'use strict';
4
+
5
+ const { isBetaEnabled, forEachGeneric } = require('../base/model');
6
+ const { setLink, annotationVal, annotationIsFalse } = require('./utils');
7
+
8
+ function kickStart( model ) {
9
+ const { options } = model;
10
+ const { message } = model.$messageFunctions;
11
+
12
+ const { resolveUncheckedPath, resolvePath } = model.$functions;
13
+ Object.assign( model.$functions, { projectionAncestor } );
14
+
15
+ // Set _service link (sorted to set it on parent first). Could be set
16
+ // directly, but beware a namespace becoming a service later.
17
+ Object.keys( model.definitions ).sort().forEach( setAncestorsAndService );
18
+ forEachGeneric( model, 'definitions', postProcessArtifact );
19
+
20
+ forEachGeneric( model, 'sources', resolveUsings );
21
+ return;
22
+
23
+
24
+ /**
25
+ * Set projection ancestors, and _service link for artifact with absolute name 'name':
26
+ * - not set: internal artifact
27
+ * - null: not within service
28
+ * - service: the artifact of the embedding service
29
+ * This function must be called ordered: parent first
30
+ *
31
+ * @param {string} name Artifact name
32
+ */
33
+ function setAncestorsAndService( name ) {
34
+ const art = model.definitions[name];
35
+ if (!('_parent' in art))
36
+ return; // nothing to do for builtins and redefinitions
37
+ if (art._from && !('_ancestors' in art))
38
+ setProjectionAncestors( art );
39
+
40
+ let parent = art._parent;
41
+ if (parent === model.definitions.localized)
42
+ parent = model.definitions[name.substring( 'localized.'.length )];
43
+ const service = parent && (parent._service || parent.kind === 'service' && parent);
44
+ setLink( art, '_service', service );
45
+ if (!parent || !service)
46
+ return;
47
+ // To be removed when nested services are allowed
48
+ if (!isBetaEnabled(options, 'nestedServices') && art.kind === 'service') {
49
+ while (parent.kind !== 'service')
50
+ parent = parent._parent;
51
+ message( 'service-nested-service', [ art.name.location, art ], { art: parent },
52
+ 'A service can\'t be nested within a service $(ART)' );
53
+ }
54
+ else if (art.kind === 'context') {
55
+ while (parent.kind !== 'service')
56
+ parent = parent._parent;
57
+ // TODO: remove this error
58
+ message( 'service-nested-context', [ art.name.location, art ], { art: parent },
59
+ 'A context can\'t be nested within a service $(ART)' );
60
+ }
61
+ }
62
+
63
+ function setProjectionAncestors( art ) {
64
+ // Must be run after processLocalizedData() as we could have a projection
65
+ // on a generated entity.
66
+
67
+ // TODO: do not do implicit redirection across services, i.e. Service2.E is
68
+ // no redirection target for E if Service2.E = projection on Service1.E and
69
+ // Service1.E = projection on E
70
+ const chain = [];
71
+ const autoexposed = art.$generated || annotationVal( art['@cds.autoexposed'] );
72
+ const preferredRedirectionTarget = annotationVal( art['@cds.redirection.target'] );
73
+ // no need to set preferredRedirectionTarget in the while loop as we would
74
+ // use the projection having @cds.redirection.target anyhow instead of
75
+ // `art` anyway (if we do the no-x-service-implicit-redirection TODO above)
76
+ while (art && !('_ancestors' in art) &&
77
+ art._from && art._from.length === 1 &&
78
+ (preferredRedirectionTarget || !annotationIsFalse( art['@cds.redirection.target'] ) ) &&
79
+ art.query.op && art.query.op.val === 'SELECT') {
80
+ chain.push( art );
81
+ setLink( art, '_ancestors', null ); // avoid infloop with cyclic from
82
+ const name = resolveUncheckedPath( art._from[0], 'include', art ); // TODO: 'include'?
83
+ art = name && projectionAncestor( model.definitions[name], art.params );
84
+ if (autoexposed)
85
+ break; // only direct projection for auto-exposed
86
+ }
87
+ let ancestors = art && (!autoexposed && art._ancestors || []);
88
+ chain.reverse();
89
+ for (const a of chain) {
90
+ ancestors = (ancestors ? [ ...ancestors, art ] : []);
91
+ setLink( a, '_ancestors', ancestors );
92
+ art = a;
93
+ }
94
+ }
95
+
96
+ // Return argument `source` if entity `source` has parameters like `params`
97
+ // - same parameters, although `params` can contain a new optional one (with DEFAULT)
98
+ // - a parameter in `params` can be optional which is not in `source.params`, but not vice versa
99
+ // - exactly the same types (type argument do not matter)
100
+ function projectionAncestor( source, params ) {
101
+ if (!source)
102
+ return source;
103
+ if (!params) // proj has no params => ok if source has no params
104
+ return !source.params && source;
105
+ const sourceParams = source.params || Object.create(null);
106
+ for (const n in sourceParams) {
107
+ if (!(n in params)) // source param is not projection param
108
+ return null; // -> can't be used as implicit redirection target
109
+ }
110
+ for (const n in params) {
111
+ const pp = params[n];
112
+ const sp = sourceParams[n];
113
+ if (sp) {
114
+ if (sp.default && !pp.default) // param DEFAULT clause not supported yet
115
+ return null; // param is not optional anymore
116
+ const pt = pp.type && resolveUncheckedPath( pp.type, 'type', pp );
117
+ const st = sp.type && resolveUncheckedPath( sp.type, 'type', sp );
118
+ if ((pt || null) !== (st || null))
119
+ return null; // params have different type
120
+ }
121
+ else if (!pp.default) {
122
+ return null;
123
+ } // non-optional param in projection, but not source
124
+ }
125
+ return source;
126
+ }
127
+
128
+ function postProcessArtifact( art ) {
129
+ tagCompositionTargets( art );
130
+ if (art.$queries) {
131
+ for (const query of art.$queries) {
132
+ if (query.mixin)
133
+ forEachGeneric( query, 'mixin', tagCompositionTargets );
134
+ }
135
+ }
136
+ if (!art._ancestors || art.kind !== 'entity')
137
+ return; // redirections only to entities
138
+ const service = art._service;
139
+ if (!service)
140
+ return;
141
+ const sname = service.name.absolute;
142
+ art._ancestors.forEach( expose );
143
+ return;
144
+
145
+ function expose( ancestor ) {
146
+ if (ancestor._service === service)
147
+ return;
148
+ const desc = ancestor._descendants ||
149
+ setLink( ancestor, '_descendants', Object.create(null) );
150
+ if (!desc[sname])
151
+ desc[sname] = [ art ];
152
+ else
153
+ desc[sname].push( art );
154
+ }
155
+ }
156
+
157
+ function tagCompositionTargets( elem ) {
158
+ const type = elem.type && elem.type.path;
159
+ if (elem.target && type && type[0] && type[0].id === 'cds.Composition') {
160
+ // A target aspect would have already moved to property `targetAspect` in
161
+ // define.js (hm... more something for kick-start.js...)
162
+ const target = resolvePath( elem.target, 'target', elem );
163
+ if (target)
164
+ model.$compositionTargets[target.name.absolute] = true;
165
+ }
166
+ forEachGeneric( elem, 'elements', tagCompositionTargets );
167
+ }
168
+
169
+ // Resolve the using declarations in `using`. Issue
170
+ // error message if the referenced artifact does not exist.
171
+ function resolveUsings( src, topLevel ) {
172
+ if (!src.usings)
173
+ return;
174
+ for (const def of src.usings) {
175
+ if (def.usings) // using {...}
176
+ resolveUsings( def );
177
+ if (!def.name || !def.name.absolute)
178
+ continue; // using {...}, parse error
179
+ const art = model.definitions[def.name.absolute];
180
+ if (art && art.$duplicates)
181
+ continue;
182
+ const ref = def.extern;
183
+ const from = (topLevel ? def : src).fileDep;
184
+ if (art || !from || from.realname) // no error for non-existing ref with non-existing module
185
+ resolvePath( ref, 'global', def ); // TODO: consider FROM for validNames
186
+ }
187
+ }
188
+ }
189
+
190
+ module.exports = kickStart;
@@ -3,14 +3,14 @@
3
3
  'use strict';
4
4
 
5
5
  const detectCycles = require('./cycle-detector');
6
- const { setProp } = require('../base/model');
6
+ const { setLink } = require('./utils');
7
7
 
8
8
  function setLayers( sources ) {
9
9
  // set dependencies
10
10
  for (const name in sources) {
11
11
  const ast = sources[name];
12
12
  ast.realname = name;
13
- setProp( ast, '_deps', [] );
13
+ setLink( ast, '_deps', [] );
14
14
  for (const d of ast.dependencies || []) {
15
15
  const art = sources[d.realname];
16
16
  if (art)
@@ -24,7 +24,7 @@ function setLayers( sources ) {
24
24
  // It is ensured that the representative is called last in SCC and that
25
25
  // dependent SCCs are called first
26
26
  function setExtends( node, representative, sccDeps = Object.create(null) ) {
27
- setProp( node, '_layerRepresentative', representative );
27
+ setLink( node, '_layerRepresentative', representative );
28
28
  if (layerRepresentative !== representative) {
29
29
  layerRepresentative = representative;
30
30
  ++layerNumber;
@@ -40,7 +40,7 @@ function setLayers( sources ) {
40
40
  if (node === representative) {
41
41
  const exts = Object.keys( sccDeps ).map( name => sccDeps[name]._layerExtends );
42
42
  Object.assign( sccDeps, ...exts );
43
- setProp( representative, '_layerExtends', sccDeps );
43
+ setLink( representative, '_layerExtends', sccDeps );
44
44
  // console.log ('SCC:', node.realname)
45
45
  }
46
46
  return sccDeps;