@sap/cds-compiler 2.11.2 → 2.13.6

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 (140) hide show
  1. package/CHANGELOG.md +175 -2
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +23 -17
  5. package/bin/cdsse.js +2 -2
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +25 -6
  9. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  10. package/doc/NameResolution.md +21 -16
  11. package/lib/api/main.js +32 -79
  12. package/lib/api/options.js +3 -2
  13. package/lib/api/validate.js +2 -1
  14. package/lib/backends.js +16 -26
  15. package/lib/base/dictionaries.js +0 -8
  16. package/lib/base/error.js +26 -0
  17. package/lib/base/keywords.js +10 -19
  18. package/lib/base/location.js +9 -4
  19. package/lib/base/message-registry.js +75 -9
  20. package/lib/base/messages.js +31 -35
  21. package/lib/base/model.js +2 -62
  22. package/lib/base/optionProcessorHelper.js +246 -183
  23. package/lib/checks/.eslintrc.json +2 -0
  24. package/lib/checks/actionsFunctions.js +2 -1
  25. package/lib/checks/annotationsOData.js +1 -1
  26. package/lib/checks/cdsPersistence.js +2 -1
  27. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  28. package/lib/checks/enricher.js +17 -1
  29. package/lib/checks/foreignKeys.js +4 -4
  30. package/lib/checks/invalidTarget.js +3 -1
  31. package/lib/checks/managedInType.js +4 -4
  32. package/lib/checks/managedWithoutKeys.js +3 -1
  33. package/lib/checks/queryNoDbArtifacts.js +1 -3
  34. package/lib/checks/selectItems.js +4 -4
  35. package/lib/checks/sql-snippets.js +94 -0
  36. package/lib/checks/types.js +1 -1
  37. package/lib/checks/unknownMagic.js +1 -1
  38. package/lib/checks/validator.js +12 -7
  39. package/lib/compiler/assert-consistency.js +12 -8
  40. package/lib/compiler/base.js +0 -1
  41. package/lib/compiler/builtins.js +42 -21
  42. package/lib/compiler/checks.js +46 -12
  43. package/lib/compiler/cycle-detector.js +1 -1
  44. package/lib/compiler/define.js +1103 -0
  45. package/lib/compiler/extend.js +983 -0
  46. package/lib/compiler/finalize-parse-cdl.js +231 -0
  47. package/lib/compiler/index.js +46 -39
  48. package/lib/compiler/kick-start.js +190 -0
  49. package/lib/compiler/moduleLayers.js +4 -4
  50. package/lib/compiler/populate.js +1226 -0
  51. package/lib/compiler/propagator.js +113 -47
  52. package/lib/compiler/resolve.js +1433 -0
  53. package/lib/compiler/shared.js +100 -65
  54. package/lib/compiler/tweak-assocs.js +529 -0
  55. package/lib/compiler/utils.js +215 -33
  56. package/lib/edm/.eslintrc.json +5 -0
  57. package/lib/edm/annotations/genericTranslation.js +38 -25
  58. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  59. package/lib/edm/csn2edm.js +10 -9
  60. package/lib/edm/edm.js +19 -20
  61. package/lib/edm/edmPreprocessor.js +166 -95
  62. package/lib/edm/edmUtils.js +127 -34
  63. package/lib/gen/Dictionary.json +92 -43
  64. package/lib/gen/language.checksum +1 -1
  65. package/lib/gen/language.interp +11 -1
  66. package/lib/gen/language.tokens +86 -82
  67. package/lib/gen/languageLexer.interp +18 -1
  68. package/lib/gen/languageLexer.js +925 -847
  69. package/lib/gen/languageLexer.tokens +78 -74
  70. package/lib/gen/languageParser.js +5434 -4298
  71. package/lib/json/from-csn.js +59 -17
  72. package/lib/json/to-csn.js +189 -71
  73. package/lib/language/antlrParser.js +3 -3
  74. package/lib/language/docCommentParser.js +3 -3
  75. package/lib/language/errorStrategy.js +26 -8
  76. package/lib/language/genericAntlrParser.js +144 -53
  77. package/lib/language/language.g4 +424 -200
  78. package/lib/language/multiLineStringParser.js +536 -0
  79. package/lib/main.d.ts +550 -61
  80. package/lib/main.js +38 -11
  81. package/lib/model/api.js +3 -1
  82. package/lib/model/csnRefs.js +322 -198
  83. package/lib/model/csnUtils.js +226 -370
  84. package/lib/model/enrichCsn.js +124 -69
  85. package/lib/model/revealInternalProperties.js +29 -7
  86. package/lib/model/sortViews.js +10 -2
  87. package/lib/modelCompare/compare.js +17 -12
  88. package/lib/optionProcessor.js +8 -3
  89. package/lib/render/.eslintrc.json +1 -2
  90. package/lib/render/DuplicateChecker.js +1 -1
  91. package/lib/render/manageConstraints.js +36 -33
  92. package/lib/render/toCdl.js +174 -275
  93. package/lib/render/toHdbcds.js +203 -122
  94. package/lib/render/toRename.js +7 -10
  95. package/lib/render/toSql.js +161 -82
  96. package/lib/render/utils/common.js +22 -8
  97. package/lib/render/utils/sql.js +10 -7
  98. package/lib/render/utils/stringEscapes.js +111 -0
  99. package/lib/sql-identifier.js +1 -1
  100. package/lib/transform/.eslintrc.json +5 -0
  101. package/lib/transform/braceExpression.js +4 -2
  102. package/lib/transform/db/.eslintrc.json +2 -0
  103. package/lib/transform/db/applyTransformations.js +212 -0
  104. package/lib/transform/db/assertUnique.js +1 -1
  105. package/lib/transform/db/associations.js +187 -0
  106. package/lib/transform/db/cdsPersistence.js +150 -0
  107. package/lib/transform/db/constraints.js +61 -56
  108. package/lib/transform/db/expansion.js +50 -29
  109. package/lib/transform/db/flattening.js +556 -106
  110. package/lib/transform/db/groupByOrderBy.js +3 -1
  111. package/lib/transform/db/temporal.js +236 -0
  112. package/lib/transform/db/transformExists.js +103 -28
  113. package/lib/transform/db/views.js +92 -44
  114. package/lib/transform/draft/.eslintrc.json +38 -0
  115. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  116. package/lib/transform/draft/odata.js +227 -0
  117. package/lib/transform/forHanaNew.js +98 -783
  118. package/lib/transform/forOdataNew.js +22 -175
  119. package/lib/transform/localized.js +36 -32
  120. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  121. package/lib/transform/odata/referenceFlattener.js +95 -89
  122. package/lib/transform/odata/structureFlattener.js +1 -1
  123. package/lib/transform/odata/toFinalBaseType.js +86 -12
  124. package/lib/transform/odata/typesExposure.js +5 -5
  125. package/lib/transform/odata/utils.js +2 -2
  126. package/lib/transform/transformUtilsNew.js +47 -33
  127. package/lib/transform/translateAssocsToJoins.js +13 -30
  128. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  129. package/lib/transform/universalCsn/coreComputed.js +170 -0
  130. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  131. package/lib/transform/universalCsn/utils.js +63 -0
  132. package/lib/utils/file.js +8 -3
  133. package/lib/utils/objectUtils.js +30 -0
  134. package/lib/utils/timetrace.js +8 -2
  135. package/package.json +1 -1
  136. package/share/messages/README.md +26 -0
  137. package/lib/compiler/definer.js +0 -2349
  138. package/lib/compiler/resolver.js +0 -2922
  139. package/lib/transform/db/helpers.js +0 -58
  140. 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,12 +21,16 @@ 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
 
29
-
30
34
  const { emptyWeakLocation } = require('../base/location');
31
35
  const { createMessageFunctions, deduplicateMessages } = require('../base/messages');
32
36
  const { promiseAllDoNotRejectImmediately } = require('../base/node-helpers');
@@ -35,6 +39,8 @@ const { cdsFs } = require('../utils/file');
35
39
  const fs = require('fs');
36
40
  const path = require('path');
37
41
 
42
+ const csnExtensions = [ '.json', '.csn' ];
43
+ const cdlExtensions = [ '.cds', '.hdbcds', '.hdbdd', '.cdl' ];
38
44
 
39
45
  // Class for command invocation errors. Additional members:
40
46
  // `errors`: vector of errors (file IO or ArgumentError)
@@ -57,21 +63,21 @@ class ArgumentError extends Error {
57
63
 
58
64
  /**
59
65
  * Parse the given source with the correct parser based on the file name's
60
- * extension. For example use edm2csn for `.xml` files and the CDL parser
61
- * for `.cds` files.
66
+ * extension. For example uses CDL parser for `.cds` files.
67
+ * Respects the value of `options.fallbackParser`.
62
68
  *
63
69
  * @param {string} source Source code of the file.
64
70
  * @param {string} filename Filename including its extension, e.g. "file.cds"
65
71
  * @param {object} options Compile options
66
72
  * @param {object} messageFunctions If not provided, parse errors will not lead to an exception
67
73
  */
68
- function parseX( source, filename, options = {}, messageFunctions ) {
74
+ function parseX( source, filename, options = {}, messageFunctions = null ) {
69
75
  if (!messageFunctions)
70
76
  messageFunctions = createMessageFunctions( options, 'parse' );
71
77
  const ext = path.extname( filename ).toLowerCase();
72
- if ([ '.json', '.csn' ].includes(ext) || options.fallbackParser === 'csn!')
78
+ if (csnExtensions.includes(ext) || options.fallbackParser === 'csn!')
73
79
  return parseCsn.parse( source, filename, options, messageFunctions );
74
- if ([ '.cds', '.hdbcds', '.hdbdd', '.cdl' ].includes(ext))
80
+ if (cdlExtensions.includes(ext))
75
81
  return parseLanguage( source, filename, options, messageFunctions );
76
82
  if (options.fallbackParser === 'csn')
77
83
  return parseCsn.parse( source, filename, options, messageFunctions );
@@ -90,8 +96,8 @@ function parseX( source, filename, options = {}, messageFunctions ) {
90
96
  // Main function: Compile the sources from the files given by the array of
91
97
  // `filenames`. As usual with the `fs` library, relative file names are
92
98
  // relative to the working directory `process.cwd()`. With argument `dir`, the
93
- // file names are relative to `process.cwd()+dir`. Options can have the
94
- // 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:
95
101
  // - Truthy `parseOnly`: stop compilation after parsing.
96
102
  // - Truthy `lintMode`: do not do checks and propagation
97
103
  // - many others - TODO
@@ -103,7 +109,7 @@ function parseX( source, filename, options = {}, messageFunctions ) {
103
109
  //
104
110
  // The promise is fulfilled if all files could be read and processed without
105
111
  // errors. The fulfillment value is an augmented CSN (see
106
- // ./compiler/definer.js).
112
+ // ./compiler/define.js).
107
113
  //
108
114
  // If there are errors, the promise is rejected. If there was an invocation
109
115
  // error (repeated filenames or if the file could not be read), the rejection
@@ -144,36 +150,25 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
144
150
  });
145
151
 
146
152
  // Read file `filename` and parse its content, return messages
147
- function readAndParse( filename ) {
153
+ async function readAndParse( filename ) {
154
+ const { sources } = a;
148
155
  if ( filename === false ) // module which has not been found
149
156
  return [];
150
- const rel = a.sources[filename] || path.relative( dir, filename );
157
+ const rel = sources[filename] || path.relative( dir, filename );
151
158
  if (typeof rel === 'object') // already parsed
152
159
  return []; // no further dependency processing
153
160
  // no parallel readAndParse with same resolved filename should read the file,
154
- // also ensure deterministic sequence in a.sources:
155
- a.sources[filename] = { location: { file: rel } };
161
+ // also ensure deterministic sequence in sources:
162
+ sources[filename] = { location: { file: rel } };
156
163
 
157
- return new Promise( (fulfill, reject) => {
158
- cdsFs( fileCache, options.traceFs ).readFile( filename, 'utf8', (err, source) => {
159
- if (err) {
160
- reject(err);
161
- }
162
- else {
163
- try {
164
- const ast = parseX( source, rel, options, model.$messageFunctions );
165
- a.sources[filename] = ast;
166
- ast.location = { file: rel };
167
- ast.dirname = path.dirname( filename );
168
- assertConsistency( ast, options );
169
- fulfill( ast );
170
- }
171
- catch (e) {
172
- reject( e );
173
- }
174
- }
175
- });
176
- });
164
+ const source = await cdsFs( fileCache, options.traceFs ).readFileAsync( filename, 'utf8' );
165
+ const ast = parseX( source, rel, options, model.$messageFunctions );
166
+ sources[filename] = ast;
167
+ ast.location = { file: rel };
168
+ ast.dirname = path.dirname( filename );
169
+ assertConsistency( ast, options );
170
+
171
+ return ast;
177
172
  }
178
173
 
179
174
  // Combine the parse results (if there are not file IO errors)
@@ -338,9 +333,10 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
338
333
  /**
339
334
  * Promise-less main functions: compile the given sources.
340
335
  *
341
- * Argument `sourcesDict` is a dictionary (it could actually be a ordinary object)
342
- * mapping filenames to either source texts (string) or XSN objects (AST-like
343
- * 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
344
340
  * to be the source text of a file named `<stdin>.cds`.
345
341
  *
346
342
  * See function `compileX` for the meaning of the argument `options`. If there
@@ -368,9 +364,15 @@ function compileSourcesX( sourcesDict, options = {} ) {
368
364
  ast.location = { file: filename };
369
365
  assertConsistency( ast, options );
370
366
  }
371
- else { // source is a XSN object (CSN/CDL parser output)
367
+ else if (options.$xsnObjects) { // source is a XSN object with option $xsnObjects
372
368
  sources[filename] = source;
373
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
+ }
374
376
  }
375
377
  moduleLayers.setLayers( sources );
376
378
 
@@ -432,10 +434,15 @@ function compileDoX( model ) {
432
434
  // do not run the resolver in parse-cdl mode or we get duplicate annotations, etc.
433
435
  // TODO: do not use this function for parseCdl anyway…
434
436
  if (options.parseCdl) {
437
+ finalizeParseCdl( model );
435
438
  throwWithError();
436
439
  return model;
437
440
  }
441
+ extend( model );
442
+ kickStart( model );
443
+ populate( model );
438
444
  resolve( model );
445
+ tweakAssocs( model );
439
446
  assertConsistency( model );
440
447
  throwWithError();
441
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;