@sap/cds-compiler 2.7.0 → 2.11.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 (87) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/bin/cdsc.js +42 -25
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +10 -0
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +17 -33
  7. package/lib/api/options.js +25 -13
  8. package/lib/api/validate.js +33 -9
  9. package/lib/backends.js +9 -8
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +26 -2
  13. package/lib/base/messages.js +25 -9
  14. package/lib/base/model.js +5 -3
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/onConditions.js +5 -0
  17. package/lib/checks/selectItems.js +4 -0
  18. package/lib/checks/types.js +26 -2
  19. package/lib/checks/unknownMagic.js +41 -0
  20. package/lib/checks/validator.js +7 -2
  21. package/lib/compiler/assert-consistency.js +18 -5
  22. package/lib/compiler/base.js +65 -0
  23. package/lib/compiler/builtins.js +30 -1
  24. package/lib/compiler/checks.js +5 -2
  25. package/lib/compiler/definer.js +145 -120
  26. package/lib/compiler/index.js +16 -4
  27. package/lib/compiler/propagator.js +5 -2
  28. package/lib/compiler/resolver.js +207 -47
  29. package/lib/compiler/shared.js +47 -200
  30. package/lib/compiler/utils.js +173 -0
  31. package/lib/edm/annotations/genericTranslation.js +183 -187
  32. package/lib/edm/csn2edm.js +94 -98
  33. package/lib/edm/edm.js +16 -20
  34. package/lib/edm/edmPreprocessor.js +302 -115
  35. package/lib/edm/edmUtils.js +31 -12
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +28 -1
  38. package/lib/gen/language.tokens +79 -69
  39. package/lib/gen/languageLexer.interp +28 -1
  40. package/lib/gen/languageLexer.js +879 -805
  41. package/lib/gen/languageLexer.tokens +71 -62
  42. package/lib/gen/languageParser.js +5308 -4308
  43. package/lib/json/from-csn.js +59 -30
  44. package/lib/json/to-csn.js +354 -105
  45. package/lib/language/antlrParser.js +11 -0
  46. package/lib/language/errorStrategy.js +1 -0
  47. package/lib/language/genericAntlrParser.js +81 -14
  48. package/lib/language/language.g4 +163 -31
  49. package/lib/main.d.ts +136 -17
  50. package/lib/main.js +7 -1
  51. package/lib/model/api.js +78 -0
  52. package/lib/model/csnRefs.js +115 -32
  53. package/lib/model/csnUtils.js +71 -33
  54. package/lib/model/enrichCsn.js +36 -9
  55. package/lib/model/revealInternalProperties.js +20 -4
  56. package/lib/modelCompare/compare.js +2 -1
  57. package/lib/optionProcessor.js +33 -16
  58. package/lib/render/.eslintrc.json +3 -1
  59. package/lib/render/DuplicateChecker.js +1 -1
  60. package/lib/render/toCdl.js +60 -17
  61. package/lib/render/toHdbcds.js +122 -74
  62. package/lib/render/toSql.js +57 -32
  63. package/lib/render/utils/common.js +6 -10
  64. package/lib/sql-identifier.js +6 -1
  65. package/lib/transform/db/constraints.js +273 -119
  66. package/lib/transform/db/draft.js +9 -6
  67. package/lib/transform/db/expansion.js +19 -7
  68. package/lib/transform/db/flattening.js +31 -7
  69. package/lib/transform/db/transformExists.js +344 -66
  70. package/lib/transform/db/views.js +438 -0
  71. package/lib/transform/forHanaNew.js +65 -436
  72. package/lib/transform/forOdataNew.js +21 -10
  73. package/lib/transform/localized.js +2 -0
  74. package/lib/transform/odata/attachPath.js +19 -4
  75. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  76. package/lib/transform/odata/referenceFlattener.js +44 -38
  77. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  78. package/lib/transform/odata/structuralPath.js +72 -0
  79. package/lib/transform/odata/structureFlattener.js +13 -10
  80. package/lib/transform/odata/typesExposure.js +22 -12
  81. package/lib/transform/transformUtilsNew.js +55 -9
  82. package/lib/transform/translateAssocsToJoins.js +11 -17
  83. package/lib/transform/universalCsnEnricher.js +67 -0
  84. package/lib/utils/file.js +5 -3
  85. package/lib/utils/term.js +65 -42
  86. package/lib/utils/timetrace.js +48 -26
  87. package/package.json +1 -1
package/lib/main.d.ts CHANGED
@@ -4,6 +4,10 @@
4
4
  //
5
5
  // These types are improved step by step and use a lot any types at the moment.
6
6
 
7
+ // Author's note: All "options" interfaces should actually be types. However, due to
8
+ // https://github.com/TypeStrong/typedoc/issues/1519 we can't use
9
+ // intersection types at the moment.
10
+
7
11
  export = compiler;
8
12
 
9
13
  declare namespace compiler {
@@ -11,7 +15,7 @@ declare namespace compiler {
11
15
  /**
12
16
  * Options used by the core compiler and all backends.
13
17
  */
14
- export type Options = {
18
+ export interface Options {
15
19
  [option: string]: any,
16
20
 
17
21
  /**
@@ -57,6 +61,102 @@ declare namespace compiler {
57
61
  dictionaryPrototype?: any
58
62
  }
59
63
 
64
+ /**
65
+ * Options used by OData backends. Includes options for the OData
66
+ * transformer as well as for rendering EDM and EDMX.
67
+ */
68
+ export interface ODataOptions extends Options {
69
+ /**
70
+ * OData version for output files. Either 'v4' or 'v2'.
71
+ *
72
+ * @default 'v4'
73
+ */
74
+ odataVersion?: string | 'v4' | 'v2'
75
+ /**
76
+ * Whether to generate OData as flat or as structured.
77
+ * Structured is only supported for OData v4.
78
+ *
79
+ * @default 'flat'
80
+ */
81
+ odataFormat?: string | 'flat' | 'structured'
82
+ /**
83
+ * Naming mode used by the corresponding SQL.
84
+ *
85
+ * @default 'plain'
86
+ */
87
+ sqlMapping?: string | 'plain' | 'quoted' | 'hdbcds'
88
+ /**
89
+ * If `true`, `cds.Compositions` are rendered as `edm:NavigationProperty` with the additional
90
+ * attribute `ContainsTarget="true"` and all contained entities (composition targets) have no
91
+ * `edm.EntitySet`.
92
+ *
93
+ * @note Only available for OData v4 EDM(X) rendering.
94
+ * @default false
95
+ */
96
+ odataContainment?: boolean
97
+ /**
98
+ * If `true`, render generated foreign keys for managed associations.
99
+ * By default foreign keys are never visible in structured OData APIs.
100
+ *
101
+ * @note Only available for structured OData v4 EDM(X) rendering.
102
+ * @default false
103
+ */
104
+ odataForeignKeys?: boolean
105
+ /**
106
+ * If `true`, association targets outside of the current service are added as
107
+ * `edm.EntityType` that only exposes their primary keys and have no `edm.EntitySet`.
108
+ * If the original association target is a service member, a corresponding `edm.Schema`
109
+ * representing the namespace of that service is added to `edm.Services`. All association
110
+ * targets that are no service members are collected in an `edm.Schema` with namespace `root`.
111
+ *
112
+ * @note Only valid for structured OData v4 EDM(X) rendering.
113
+ * @default false
114
+ * @since v2.1.0
115
+ */
116
+ odataProxies?: boolean
117
+ /**
118
+ * This option is an extension to `odataProxies`.
119
+ * If `true`, an `edm:Reference` instead of a proxy `edm.EntityType` is rendered for each
120
+ * association target that is a service member outside the current service instead of proxies.
121
+ *
122
+ * @note Only valid for structured OData v4 EDM(X) rendering.
123
+ * @default false
124
+ * @since v2.1.0
125
+ */
126
+ odataXServiceRefs?: boolean
127
+ /**
128
+ * The OData specification requires that all primary keys of the principal must be used as
129
+ * referential constraints. If an association is modelled with only a partial key, no
130
+ * referential constraints are added. If `true`, partial constraints are rendered for
131
+ * backwards compatibility and mocking scenarios. A spec violation warning is raised for
132
+ * each incomplete constraint.
133
+ *
134
+ * @note Only valid for OData v2 CSN transformation.
135
+ * @default false
136
+ * @since v2.2.6
137
+ */
138
+ odataV2PartialConstr?: boolean
139
+ /**
140
+ * Service name for which EDMX or EDM shall be rendered.
141
+ *
142
+ * @note Only available for `to.edmx()` and `to.edm()`. For `to.edmx.all()`
143
+ * and `to.edm.all()`, use `serviceNames` instead.
144
+ *
145
+ * @see serviceNames
146
+ */
147
+ service?: string
148
+ /**
149
+ * Array of service names for which EDMX or EDM shall be rendered.
150
+ * If unspecified, all services are rendered.
151
+ *
152
+ * @note Only available for `to.edmx.all()` and `to.edm.all()`. For `to.edmx()`
153
+ * and `to.edm()`, use `service` instead.
154
+ *
155
+ * @see service
156
+ */
157
+ serviceNames?: string[]
158
+ }
159
+
60
160
  /**
61
161
  * The compiler's package version.
62
162
  * For more details on versioning and SemVer, see `doc/Versioning.md`
@@ -83,6 +183,7 @@ declare namespace compiler {
83
183
  * {@link CompilationError} containing a vector of individual errors.
84
184
  *
85
185
  * @param filenames Array of files that should be compiled.
186
+ * @param dir Working directory. Relative paths in `filenames` will be resolved relatively to this directory.
86
187
  * @param options Compiler options. If you do not set `messages`, they will be printed to console.
87
188
  * @param fileCache A dictionary of absolute file names to the file content with values:
88
189
  * - false: the file does not exist
@@ -138,7 +239,7 @@ declare namespace compiler {
138
239
  * _Note_: Sorting is done in-place.
139
240
  *
140
241
  * Example of sorted messages:
141
- * ```txt
242
+ * ```
142
243
  * A.cds:1:11: Info id-3: First message text (in entity:“E”/element:“c”)
143
244
  * A.cds:8:11: Error id-5: Another message text (in entity:“C”/element:“g”)
144
245
  * B.cds:3:10: Debug id-7: First message text (in entity:“B”/element:“e”)
@@ -188,10 +289,11 @@ declare namespace compiler {
188
289
  * form (i.e. one line)
189
290
  *
190
291
  * Example:
191
- * ```txt
292
+ * ```
192
293
  * <source>.cds:3:11: Error message-id: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
193
294
  * ```
194
295
  *
296
+ * @param msg Compiler message which shall be stringified.
195
297
  * @param normalizeFilename If true, the file path will be normalized to use `/` as the path separator.
196
298
  * @param noMessageId If true, the message ID will _not_ be part of the string.
197
299
  * @param noHome If true, the semantic location will _not_ be part of the string.
@@ -201,26 +303,30 @@ declare namespace compiler {
201
303
  /**
202
304
  * Returns a message string with file- and semantic location if present
203
305
  * in multiline form.
204
- * The error (+ message id) will be colored according to their severity if
205
- * run on a TTY.
306
+ * The error (+ message id) can colored according to their severity.
206
307
  *
207
308
  * Example:
208
- * ```txt
209
- * Error[message-id]: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
309
+ * ```
310
+ * Error[message-id]: Can't find type `nu` in this scope
210
311
  * |
211
- * <source>.cds:3:11, at entity:“E”
312
+ * <source>.cds:3:11, at entity:“E”/element:“e
212
313
  * ```
213
314
  *
214
315
  * @param config.normalizeFilename If true, the file path will be normalized to use `/` as the path separator.
215
316
  * @param config.noMessageId If true, no messages id (in brackets) will be shown.
216
317
  * @param config.hintExplanation If true, messages with explanations will get a "…" marker, see {@link hasMessageExplanation}.
217
318
  * @param config.withLineSpacer If true, an additional line (with `|`) will be inserted between message and location.
319
+ * @param config.color If true, ANSI escape codes will be used for coloring the severity. If false, no
320
+ * coloring will be used. If 'auto', we will decide based on certain factors such
321
+ * as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
322
+ * unset.
218
323
  */
219
324
  export function messageStringMultiline(msg: CompileMessage, config?: {
220
325
  normalizeFilename?: boolean
221
326
  noMessageId?: boolean
222
327
  hintExplanation?: boolean
223
328
  withLineSpacer?: boolean
329
+ color?: boolean | 'auto'
224
330
  }): string;
225
331
 
226
332
  /**
@@ -233,16 +339,24 @@ declare namespace compiler {
233
339
  * All lines are prepended by a pipe (`|`) and show the corresponding line number.
234
340
  *
235
341
  * Example Output:
236
- * ```txt
342
+ * ```
237
343
  * |
238
344
  * 13 | num * nu
239
345
  * | ^^
240
346
  * ```
241
347
  *
242
- * @param sourceLines The source code split up into lines, e.g. by `str.split(/\r\n?|\n/);`.
243
- * @param msg Message whose location is used to print the message context.
348
+ * @param sourceLines The source code split up into lines, e.g. by `str.split(/\r\n?|\n/);`.
349
+ * @param msg Message whose location is used to print the message context.
350
+ * @param config Configuration for the message context.
351
+ * @param config.color If true, ANSI escape codes will be used for coloring the severity. If false, no
352
+ * coloring will be used. If 'auto', we will decide based on certain factors such
353
+ * as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
354
+ * unset.
355
+
244
356
  */
245
- export function messageContext(sourceLines: string[], msg: CompileMessage): string;
357
+ export function messageContext(sourceLines: string[], msg: CompileMessage, config?: {
358
+ color?: boolean | 'auto'
359
+ }): string;
246
360
 
247
361
  /**
248
362
  * Get an explanatory text for a complicated compiler message with ID
@@ -316,21 +430,26 @@ declare namespace compiler {
316
430
  * @alias for
317
431
  */
318
432
  export namespace For {
319
- function odata(): any;
433
+ /**
434
+ * Transform the given (generic) CSN into one that is used for OData.
435
+ * Changes include flattening, type resolution and more, according to
436
+ * the provided options.
437
+ */
438
+ function odata(csn: CSN, options: ODataOptions): any;
320
439
  }
321
440
 
322
441
  export namespace to {
323
442
  function cdl(csn: CSN, options: Options): object;
324
443
  function sql(csn: CSN, options: Options): any;
325
444
 
326
- function edm(csn: CSN, options: Options): any;
445
+ function edm(csn: CSN, options: ODataOptions): any;
327
446
  namespace edm {
328
- function all(csn: CSN, options: Options): any;
447
+ function all(csn: CSN, options: ODataOptions): any;
329
448
  }
330
449
 
331
- function edmx(csn: CSN, options: Options): any;
450
+ function edmx(csn: CSN, options: ODataOptions): any;
332
451
  namespace edmx {
333
- function all(csn: CSN, options: Options): any;
452
+ function all(csn: CSN, options: ODataOptions): any;
334
453
  }
335
454
 
336
455
  function hdbcds(csn: CSN, options: Options): any;
package/lib/main.js CHANGED
@@ -16,10 +16,12 @@
16
16
  const backends = require('./backends');
17
17
  const { odata, cdl, sql, hdi, hdbcds, edm, edmx } = require('./api/main');
18
18
  const { getArtifactDatabaseNameOf, getElementDatabaseNameOf } = require('./model/csnUtils');
19
+ const { traverseCsn } = require('./model/api');
19
20
  const { createMessageFunctions, sortMessages, sortMessagesSeverityAware, deduplicateMessages } = require('./base/messages');
20
21
 
21
22
  const parseLanguage = require('./language/antlrParser');
22
23
  const { parseX, compileX, compileSyncX, compileSourcesX, InvocationError } = require('./compiler');
24
+ const { fns } = require('./compiler/shared');
23
25
  const { define } = require('./compiler/definer');
24
26
 
25
27
  // The compiler version (taken from package.json)
@@ -42,13 +44,14 @@ const { compactModel, compactQuery, compactExpr } = require('./json/to-csn')
42
44
  function parseCdl( cdl, filename, options = {} ) {
43
45
  options = Object.assign( {}, options, { parseCdl: true } );
44
46
  const sources = Object.create(null);
45
- const model = { sources, options };
47
+ const model = { sources, options, $functions: {}, $volatileFunctions: {} };
46
48
  const messageFunctions = createMessageFunctions( options, 'parse', model );
47
49
  model.$messageFunctions = messageFunctions;
48
50
 
49
51
  const xsn = parseLanguage( cdl, filename, Object.assign( { parseOnly: true }, options ),
50
52
  messageFunctions );
51
53
  sources[filename] = xsn;
54
+ fns( model );
52
55
  define( model );
53
56
  messageFunctions.throwWithError();
54
57
  return compactModel( model );
@@ -111,6 +114,9 @@ module.exports = {
111
114
  getArtifactCdsPersistenceName: getArtifactDatabaseNameOf,
112
115
  getElementCdsPersistenceName: getElementDatabaseNameOf,
113
116
 
117
+ // Other API functions:
118
+ traverseCsn,
119
+
114
120
  // INTERNAL functions for the cds-lsp package and friends - before you use
115
121
  // it, you MUST talk with us - there can be potential incompatibilities with
116
122
  // new releases (even having the same major version):
@@ -0,0 +1,78 @@
1
+ // Miscellaneous CSN functions we put into our compiler API
2
+
3
+ // Do not change at will - they are in the compiler API!
4
+
5
+ /**
6
+ * Dictionary of default traversal functions for function `traverseCsn`.
7
+ * It maps CSN property names to functions which are used by default
8
+ * to traverse the CSN node which is the value of the corresponding property.
9
+ * Users specify their own traversal function via argument `userFunctions`.
10
+ *
11
+ * Each function in `userFunctions` and `defaultFunctions` is called with:
12
+ * - `userFunctions`
13
+ * - the current CSN node, i.e. ‹parent node›.‹property name›
14
+ * - the the ‹parent node›
15
+ * - the ‹property name› (might be useful if the same function is used for several props)
16
+ */
17
+ const defaultFunctions = {
18
+ '@': () => { /* do not traverse annotation assignments */ },
19
+ args: dictionary,
20
+ elements: dictionary,
21
+ enum: dictionary,
22
+ params: dictionary,
23
+ actions: dictionary,
24
+ mixin: dictionary,
25
+ definitions: dictionary,
26
+ '$': () => { /* do not traverse properties starting with '$' */},
27
+ }
28
+
29
+ /**
30
+ * Traverse the CSN node `csn`.
31
+ * If `csn` is an array, call it recursively on each array item.
32
+ * If `csn` is an(other) object, call a function on each property:
33
+ * - The property name is a used as key in argument `userFunctions` and the
34
+ * constant `defaultFunctions` above to get the function which is called on
35
+ * the property value, see `defaultFunctions` for details.
36
+ * - If no function is found with the property name, try to find one with the first char.
37
+ * - If still not found, call `traverseCsn` recursively.
38
+ *
39
+ * The functions in `userFunctions` are usually transformer functions, which
40
+ * change the input CSN destructively.
41
+ */
42
+ function traverseCsn( userFunctions, csn ) {
43
+ if (!csn || typeof csn !== 'object')
44
+ return;
45
+ if (Array.isArray( csn )) {
46
+ csn.forEach( node => traverseCsn( userFunctions, node ) );
47
+ }
48
+ else {
49
+ for (const prop of Object.keys( csn )) {
50
+ const func = userFunctions[prop] || defaultFunctions[prop] ||
51
+ userFunctions[prop.charAt(0)] || defaultFunctions[prop.charAt(0)] ||
52
+ traverseCsn;
53
+ func( userFunctions, csn[prop], csn, prop );
54
+ }
55
+ }
56
+ }
57
+ // people might want to have their own traversal function for `elements`, etc:
58
+ traverseCsn.dictionary = dictionary;
59
+
60
+ /**
61
+ * Traverse the CSN dictionary node `csn`.
62
+ * Call `traverseCsn` on each property value in `csn`, passing down `userFunctions`.
63
+ */
64
+ function dictionary( userFunctions, csn ) {
65
+ if (!csn || typeof csn !== 'object')
66
+ return;
67
+ if (Array.isArray( csn )) { // args can be both array and dictionary
68
+ csn.forEach( node => traverseCsn( userFunctions, node ) );
69
+ }
70
+ else {
71
+ for (const name of Object.keys( csn ))
72
+ traverseCsn( userFunctions, csn[name] );
73
+ }
74
+ }
75
+
76
+ module.exports = {
77
+ traverseCsn,
78
+ };
@@ -175,6 +175,7 @@ const referenceSemantics = {
175
175
  type: { lexical: false, dynamic: 'global' },
176
176
  includes: { lexical: false, dynamic: 'global' },
177
177
  target: { lexical: false, dynamic: 'global' },
178
+ targetAspect: { lexical: false, dynamic: 'global' },
178
179
  from: { lexical: false, dynamic: 'global' },
179
180
  keys: { lexical: false, dynamic: 'target' },
180
181
  excluding: { lexical: false, dynamic: 'source' },
@@ -224,17 +225,19 @@ function csnRefs( csn ) {
224
225
  const cachedType = getCache( art, '_effectiveType' );
225
226
  if (cachedType !== undefined)
226
227
  return cachedType;
227
- else if (!art.type && !art.$origin ||
228
- art.elements || art.target || art.targetAspect || art.enum)
229
- return setCache( art, '_effectiveType', art );
230
228
 
231
229
  const chain = [];
232
- while (getCache( art, '_effectiveType' ) === undefined && (art.type || art.$origin) &&
230
+ let origin;
231
+ while (getCache( art, '_effectiveType' ) === undefined &&
232
+ (origin = cached( art, '_origin', getOriginRaw )) &&
233
233
  !art.elements && !art.target && !art.targetAspect && !art.enum && !art.items) {
234
234
  chain.push( art );
235
235
  setCache( art, '_effectiveType', 0 ); // initial setting in case of cycles
236
- art = (art.$origin) ? getOrigin( art ) : artifactRef( art.type, BUILTIN_TYPE );
236
+ art = origin;
237
237
  }
238
+ if (!chain.length)
239
+ return setCache( art, '_effectiveType', art );
240
+
238
241
  if (getCache( art, '_effectiveType' ) === 0)
239
242
  throw new Error( 'Circular type reference');
240
243
  const type = getCache( art, '_effectiveType' ) || art;
@@ -250,10 +253,16 @@ function csnRefs( csn ) {
250
253
  // here, we do not care whether it is semantically ok to navigate into sub
251
254
  // elements of array items (that is the task of the core compiler /
252
255
  // semantic check)
253
- while (type.items)
256
+ while (type.items) {
257
+ cached( type, '$origin', _a => setImplicitOrigin( type, origin ) );
254
258
  type = effectiveType( type.items );
259
+ }
255
260
  // cannot navigate along targetAspect!
256
- return (type.target) ? csn.definitions[type.target] : type;
261
+ const env = (type.target) ? csn.definitions[type.target] : type;
262
+ const origin = cached( env, '_origin', getOriginRaw );
263
+ if (origin && origin !== BUILTIN_TYPE)
264
+ cached( env, '$origin', _a => setImplicitOrigin( env, origin ) );
265
+ return env;
257
266
  }
258
267
 
259
268
  /**
@@ -278,38 +287,102 @@ function csnRefs( csn ) {
278
287
  function artifactPathRef( ref ) {
279
288
  const [ head, ...tail ] = ref.ref;
280
289
  let art = csn.definitions[pathId( head )];
281
- for (const elem of tail)
282
- art = navigationEnv( art ).elements[pathId( elem )];
290
+ for (const elem of tail) {
291
+ const env = navigationEnv( art );
292
+ art = env.elements[pathId( elem )];
293
+ }
283
294
  return art;
284
295
  }
285
296
 
286
- function getOrigin( def ) {
287
- const art = cached( def, '_origin', originPathRef );
288
- if (art)
289
- return art;
290
- throw new Error( 'Undefined origin reference' );
297
+ function getOrigin( art, alsoType ) {
298
+ const origin = cached( art, '_origin', getOriginRaw );
299
+ if (origin && origin !== BUILTIN_TYPE)
300
+ cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
301
+ return art.type && !alsoType ? undefined : origin;
291
302
  }
292
303
 
293
- function originPathRef( def ) {
294
- const [ head, ...tail ] = def.$origin;
295
- let art = csn.definitions[head];
296
- for (const elem of tail)
297
- art = originNavigation( art, elem );
298
- return art;
304
+ function getOriginRaw( art ) {
305
+ if (art.type) // TODO: make robust against "linked" = only direct
306
+ return artifactRef( art.type, BUILTIN_TYPE );
307
+ if (!art.$origin) // implicit $origin should have been set
308
+ return null;
309
+ // art.$origin must not be a string here - shortened refs should already
310
+ // have been used to set the _origin cache
311
+ if (!Array.isArray( art.$origin )) // anonymous prototype in $origin
312
+ return cached( art.$origin, '_origin', getOriginRaw );
313
+ const [ head, ...tail ] = art.$origin;
314
+ let origin = csn.definitions[head];
315
+ // allow shorter $origin ref for actions/functions, just using a string:
316
+ let isAction = art.kind === 'action' || art.kind === 'function';
317
+ for (const elem of tail) {
318
+ origin = originNavigation( origin, elem, isAction );
319
+ isAction = false;
320
+ }
321
+ return origin;
299
322
  }
300
323
 
301
- function originNavigation( art, elem ) {
324
+ function originNavigation( art, elem, isAction ) {
302
325
  if (typeof elem !== 'string') {
303
326
  if (elem.action)
304
- return art.actions[elem.action]
327
+ return art.actions[elem.action];
305
328
  if (elem.param)
306
- return (elem.param ? art.params[elem.param] : art.returns);
329
+ return art.params[elem.param];
330
+ if (elem.returns)
331
+ return art.returns;
307
332
  }
308
- if (art.returns)
333
+ if (isAction)
334
+ return art.actions[elem];
335
+ // TODO: if we use effectiveType(), we might have more implicit prototypes
336
+ // we might then need a function like effectiveArtifact,
337
+ // which cares about actions/params
338
+ // let origin = cached( art, '_origin', getOriginRaw );
339
+ // while (art.items) {
340
+ // cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
341
+ // art = art.items;
342
+ // origin = cached( art, '_origin', getOriginRaw );
343
+ // }
344
+ // if (origin)
345
+ // cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
346
+ return (art.elements || art.enum || (art.targetAspect || art.target).elements)[elem];
347
+ }
348
+
349
+ // From the current CSN object, set implicit origin for the next navigation step
350
+ // Currently (TODO: ?) `elements` only, i.e. what is needed for name resolution.
351
+ function setImplicitOrigin( art, origin ) {
352
+ setMembersImplicit( art.actions, origin.actions );
353
+ setMembersImplicit( art.params, origin.params );
354
+ if (art.returns) {
309
355
  art = art.returns;
310
- while (art.items)
356
+ if (art.type || typeof art.$origin === 'object') // null, […], {…}
357
+ return true; // not implicit or shortened
358
+ origin = effectiveType( origin.returns );
359
+ setCache( art, '_origin', origin );
360
+ return true;
361
+ }
362
+ while (art.items) {
311
363
  art = art.items;
312
- return (art.elements || art.enum || (art.targetAspect || art.target).elements)[elem];
364
+ if (art.type || typeof art.$origin === 'object') // null, [], {…}
365
+ return true; // not implicit or shortened
366
+ origin = effectiveType( origin.items );
367
+ setCache( art, '_origin', origin );
368
+ }
369
+ setMembersImplicit( art.elements, origin.elements );
370
+ // The enum base type is _not_ where we find the origins of the enum symbols.
371
+ // A derived type of an enum type with individual annotations on a symbol
372
+ // has both 'type' and '$origin'.
373
+ if (!art.type || art.$origin)
374
+ setMembersImplicit( art.enum, origin.enum );
375
+ return true;
376
+ }
377
+
378
+ function setMembersImplicit( members, originMembers ) {
379
+ if (!members)
380
+ return;
381
+ for (const name in members) {
382
+ const elem = members[name];
383
+ if (!elem.type && typeof elem.$origin !== 'object') // undefined or string
384
+ setCache( elem, '_origin', originMembers[elem.$origin || name] || false );
385
+ }
313
386
  }
314
387
 
315
388
  /**
@@ -370,6 +443,9 @@ function csnRefs( csn ) {
370
443
  if (semantics.dynamic === 'global' || ref.global)
371
444
  return resolvePath( path, csn.definitions[head], 'global', refCtx === 'from' );
372
445
 
446
+ const origin = cached( main, '_origin', getOriginRaw )
447
+ if (origin)
448
+ cached( main, '$origin', _a => setImplicitOrigin( main, origin ) );
373
449
  cached( main, '$queries', allQueries );
374
450
  let qcache = query && cache.get( query.projection || query );
375
451
  // BACKEND ISSUE: you cannot call csnRefs(), inspect some refs, change the
@@ -386,7 +462,7 @@ function csnRefs( csn ) {
386
462
  while (cache) {
387
463
  const alias = tryAlias && cache.$aliases[head];
388
464
  if (alias)
389
- return resolvePath( path, alias._select || alias, 'alias', cache.$queryNumber );
465
+ return resolvePath( path, alias._select || alias._ref, 'alias', cache.$queryNumber );
390
466
  const mixin = cache._select.mixin && cache._select.mixin[head];
391
467
  if (mixin && {}.hasOwnProperty.call( cache._select.mixin, head ))
392
468
  return resolvePath( path, mixin, 'mixin', cache.$queryNumber );
@@ -480,7 +556,8 @@ function csnRefs( csn ) {
480
556
  if (query.ref) { // ref in from
481
557
  // console.log('SQ:',query,cache.get(query))
482
558
  const as = query.as || implicitAs( query.ref );
483
- getCache( fromSelect, '$aliases' )[as] = fromRef( query );
559
+ const _ref = fromRef( query );
560
+ getCache( fromSelect, '$aliases' )[as] = { _ref, elements: _ref.elements };
484
561
  }
485
562
  else {
486
563
  const qcache = getQueryCache( parentQuery );
@@ -606,7 +683,7 @@ function queryOrMain( query, main ) {
606
683
  *
607
684
  * @param {CSN.Query} query
608
685
  * @param {CSN.QuerySelect} fromSelect
609
- * @param {CSN.Query} parentQuery
686
+ * @param {CSN.Query} parentQuery
610
687
  * @param {(query: CSN.Query&CSN.QueryFrom, select: CSN.QuerySelectEnriched) => void} callback
611
688
  */
612
689
  function traverseQuery( query, fromSelect, parentQuery, callback ) {
@@ -631,8 +708,9 @@ function traverseQuery( query, fromSelect, parentQuery, callback ) {
631
708
 
632
709
  /**
633
710
  * @param {CSN.QueryFrom} from
634
- * @param {CSN.QuerySelect} select
635
- * @param {(from: CSN.QueryFrom, select: CSN.QuerySelect) => void} callback
711
+ * @param {CSN.QuerySelect} fromSelect
712
+ * @param {CSN.Query} parentQuery
713
+ * @param {(from: CSN.QueryFrom, select: CSN.QuerySelect, parentQuery: CSN.Query) => void} callback
636
714
  */
637
715
  function traverseFrom( from, fromSelect, parentQuery, callback ) {
638
716
  if (from.ref) {
@@ -688,6 +766,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
688
766
  let isName = false;
689
767
  let baseRef = null;
690
768
  let baseEnv = null;
769
+ let main = csn.definitions[csnPath[1]];
691
770
 
692
771
  for (let index = 0; index < csnPath.length; index++) {
693
772
  const prop = csnPath[index];
@@ -700,6 +779,10 @@ function analyseCsnPath( csnPath, csn, resolve ) {
700
779
  isName = false;
701
780
  }
702
781
  else if (artifactProperties.includes( String(prop) )) {
782
+ if (refCtx === 'target' || refCtx === 'targetAspect') { // with 'elements'
783
+ main = art = obj; // $self refers to the anonymous aspect
784
+ parent = null;
785
+ }
703
786
  isName = prop;
704
787
  refCtx = prop;
705
788
  }
@@ -758,7 +841,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
758
841
  // console.log( 'CPATH:', csnPath, refCtx, obj, parent.$location );
759
842
  if (!resolve)
760
843
  return { query }; // for constructSemanticLocationFromCsnPath
761
- return resolve( obj, refCtx, csn.definitions[csnPath[1]], query, parent, baseEnv );
844
+ return resolve( obj, refCtx, main, query, parent, baseEnv );
762
845
  }
763
846
 
764
847
  module.exports = {