@sap/cds-compiler 2.5.2 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +235 -9
  2. package/bin/cdsc.js +44 -27
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +37 -3
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +37 -123
  7. package/lib/api/options.js +27 -15
  8. package/lib/api/validate.js +34 -9
  9. package/lib/backends.js +9 -89
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +73 -11
  13. package/lib/base/messages.js +86 -30
  14. package/lib/base/model.js +6 -6
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/defaultValues.js +27 -2
  17. package/lib/checks/elements.js +1 -6
  18. package/lib/checks/foreignKeys.js +0 -6
  19. package/lib/checks/managedWithoutKeys.js +17 -0
  20. package/lib/checks/nonexpandableStructured.js +38 -0
  21. package/lib/checks/onConditions.js +9 -45
  22. package/lib/checks/queryNoDbArtifacts.js +25 -7
  23. package/lib/checks/selectItems.js +29 -2
  24. package/lib/checks/types.js +26 -2
  25. package/lib/checks/unknownMagic.js +41 -0
  26. package/lib/checks/utils.js +61 -0
  27. package/lib/checks/validator.js +60 -7
  28. package/lib/compiler/assert-consistency.js +23 -7
  29. package/lib/compiler/base.js +65 -0
  30. package/lib/compiler/builtins.js +30 -1
  31. package/lib/compiler/checks.js +8 -5
  32. package/lib/compiler/definer.js +157 -133
  33. package/lib/compiler/index.js +89 -31
  34. package/lib/compiler/propagator.js +5 -2
  35. package/lib/compiler/resolver.js +375 -185
  36. package/lib/compiler/shared.js +49 -202
  37. package/lib/compiler/utils.js +173 -0
  38. package/lib/edm/annotations/genericTranslation.js +183 -187
  39. package/lib/edm/csn2edm.js +104 -108
  40. package/lib/edm/edm.js +18 -21
  41. package/lib/edm/edmPreprocessor.js +388 -146
  42. package/lib/edm/edmUtils.js +104 -34
  43. package/lib/gen/Dictionary.json +22 -0
  44. package/lib/gen/language.checksum +1 -1
  45. package/lib/gen/language.interp +28 -1
  46. package/lib/gen/language.tokens +79 -69
  47. package/lib/gen/languageLexer.interp +28 -1
  48. package/lib/gen/languageLexer.js +879 -805
  49. package/lib/gen/languageLexer.tokens +71 -62
  50. package/lib/gen/languageParser.js +5330 -4300
  51. package/lib/json/from-csn.js +110 -52
  52. package/lib/json/to-csn.js +434 -120
  53. package/lib/language/antlrParser.js +15 -3
  54. package/lib/language/errorStrategy.js +1 -0
  55. package/lib/language/genericAntlrParser.js +93 -26
  56. package/lib/language/language.g4 +172 -31
  57. package/lib/main.d.ts +216 -19
  58. package/lib/main.js +32 -7
  59. package/lib/model/api.js +78 -0
  60. package/lib/model/csnRefs.js +413 -149
  61. package/lib/model/csnUtils.js +286 -75
  62. package/lib/model/enrichCsn.js +50 -6
  63. package/lib/model/revealInternalProperties.js +22 -5
  64. package/lib/modelCompare/compare.js +39 -21
  65. package/lib/optionProcessor.js +35 -18
  66. package/lib/render/.eslintrc.json +4 -1
  67. package/lib/render/DuplicateChecker.js +9 -6
  68. package/lib/render/toCdl.js +121 -36
  69. package/lib/render/toHdbcds.js +148 -98
  70. package/lib/render/toSql.js +114 -43
  71. package/lib/render/utils/common.js +8 -13
  72. package/lib/render/utils/sql.js +3 -3
  73. package/lib/sql-identifier.js +6 -1
  74. package/lib/transform/db/assertUnique.js +5 -6
  75. package/lib/transform/db/constraints.js +281 -106
  76. package/lib/transform/db/draft.js +11 -8
  77. package/lib/transform/db/expansion.js +584 -0
  78. package/lib/transform/db/flattening.js +341 -0
  79. package/lib/transform/db/groupByOrderBy.js +2 -2
  80. package/lib/transform/db/transformExists.js +345 -65
  81. package/lib/transform/db/views.js +438 -0
  82. package/lib/transform/forHanaNew.js +131 -793
  83. package/lib/transform/forOdataNew.js +30 -24
  84. package/lib/transform/localized.js +39 -10
  85. package/lib/transform/odata/attachPath.js +19 -4
  86. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  87. package/lib/transform/odata/referenceFlattener.js +60 -39
  88. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  89. package/lib/transform/odata/structuralPath.js +72 -0
  90. package/lib/transform/odata/structureFlattener.js +19 -18
  91. package/lib/transform/odata/typesExposure.js +22 -12
  92. package/lib/transform/transformUtilsNew.js +144 -78
  93. package/lib/transform/translateAssocsToJoins.js +22 -27
  94. package/lib/transform/universalCsnEnricher.js +67 -0
  95. package/lib/utils/file.js +5 -14
  96. package/lib/utils/moduleResolve.js +6 -8
  97. package/lib/utils/term.js +65 -42
  98. package/lib/utils/timetrace.js +48 -26
  99. package/package.json +1 -1
  100. package/lib/json/walker.js +0 -26
  101. package/lib/transform/sqlite +0 -0
  102. package/lib/utils/string.js +0 -17
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
@@ -107,22 +208,92 @@ declare namespace compiler {
107
208
  constructor(messages: any, model: any, text: any, ...args);
108
209
  messages: any[];
109
210
  toString(): string;
211
+ /**
212
+ * If `options.attachValidNames` is set, this non-enumerable property holds the CSN model.
213
+ * @internal
214
+ */
215
+ model?: CSN;
216
+ /**
217
+ * Used by `cdsc` to indicate whether the message was already printed to stderr.
218
+ * @private
219
+ */
220
+ hasBeenReported: boolean;
110
221
  }
111
222
 
112
- export function sortMessages(messages: any[]): void;
113
- export function sortMessagesSeverityAware(messages: any[]): void;
223
+ /**
224
+ * Sort the given messages according to their location. Messages are sorted
225
+ * in ascending order according to their:
226
+ *
227
+ * - file name
228
+ * - start line
229
+ * - start column
230
+ * - end line
231
+ * - end column
232
+ * - semantic location (“home”)
233
+ * - message text
234
+ *
235
+ * If both messages do not have a location, they are sorted by their semantic
236
+ * location and then by their message text. If only one message has a file
237
+ * location, that message is sorted prior to those that don't have one.
238
+ *
239
+ * _Note_: Sorting is done in-place.
240
+ *
241
+ * Example of sorted messages:
242
+ * ```
243
+ * A.cds:1:11: Info id-3: First message text (in entity:“E”/element:“c”)
244
+ * A.cds:8:11: Error id-5: Another message text (in entity:“C”/element:“g”)
245
+ * B.cds:3:10: Debug id-7: First message text (in entity:“B”/element:“e”)
246
+ * B.cds:3:12: Warning id-4: Message text (in entity:“B”/element:“d”)
247
+ * B.cds:3:12: Error id-4: Message text (in entity:“B”/element:“e”)
248
+ * ```
249
+ *
250
+ * If you also want to sort according to message's severity,
251
+ * see {@link sortMessagesSeverityAware}.
252
+ *
253
+ * @returns The same messages array as the input parameter.
254
+ */
255
+ export function sortMessages(messages: CompileMessage[]): CompileMessage[];
114
256
 
115
- export function deduplicateMessages(messages: any[]): void;
257
+ /**
258
+ * Sort the given messages in severity aware order. Messages are sorted first
259
+ * by severity where 'Error' comes first, then 'Warning' and so forth.
260
+ * Messages of the same severity are sorted the same as by {@link sortMessages}.
261
+ *
262
+ * _Note_: Sorting is done in-place.
263
+ *
264
+ * @returns The same messages array as the input parameter.
265
+ */
266
+ export function sortMessagesSeverityAware(messages: CompileMessage[]): CompileMessage[];
267
+
268
+ /**
269
+ * Removes duplicate messages from the given messages array without destroying
270
+ * references to the array, i.e. removes them in-place.
271
+ *
272
+ * _Note_: Does NOT keep the original order!
273
+ *
274
+ * Two messages are the same if they have the same message hash (see below).
275
+ * If one of the two is more precise, then it replaces the other.
276
+ * A message is more precise if it is contained in the other or if
277
+ * the first does not have an `endLine`/`endCol`.
278
+ *
279
+ * A “message hash” is the string representation of the message. If the
280
+ * message does not have a semantic location (“home”), the message hash
281
+ * is the result of {@link messageString}. If the message has a semantic
282
+ * location, the file location is stripped before being passed to
283
+ * {@link messageString}.
284
+ */
285
+ export function deduplicateMessages(messages: CompileMessage[]): void;
116
286
 
117
287
  /**
118
288
  * Returns a message string with file- and semantic location if present in compact
119
289
  * form (i.e. one line)
120
290
  *
121
291
  * Example:
122
- * ```txt
292
+ * ```
123
293
  * <source>.cds:3:11: Error message-id: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
124
294
  * ```
125
295
  *
296
+ * @param msg Compiler message which shall be stringified.
126
297
  * @param normalizeFilename If true, the file path will be normalized to use `/` as the path separator.
127
298
  * @param noMessageId If true, the message ID will _not_ be part of the string.
128
299
  * @param noHome If true, the semantic location will _not_ be part of the string.
@@ -132,26 +303,30 @@ declare namespace compiler {
132
303
  /**
133
304
  * Returns a message string with file- and semantic location if present
134
305
  * in multiline form.
135
- * The error (+ message id) will be colored according to their severity if
136
- * run on a TTY.
306
+ * The error (+ message id) can colored according to their severity.
137
307
  *
138
308
  * Example:
139
- * ```txt
140
- * 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
141
311
  * |
142
- * <source>.cds:3:11, at entity:“E”
312
+ * <source>.cds:3:11, at entity:“E”/element:“e
143
313
  * ```
144
314
  *
145
315
  * @param config.normalizeFilename If true, the file path will be normalized to use `/` as the path separator.
146
316
  * @param config.noMessageId If true, no messages id (in brackets) will be shown.
147
317
  * @param config.hintExplanation If true, messages with explanations will get a "…" marker, see {@link hasMessageExplanation}.
148
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.
149
323
  */
150
324
  export function messageStringMultiline(msg: CompileMessage, config?: {
151
325
  normalizeFilename?: boolean
152
326
  noMessageId?: boolean
153
327
  hintExplanation?: boolean
154
328
  withLineSpacer?: boolean
329
+ color?: boolean | 'auto'
155
330
  }): string;
156
331
 
157
332
  /**
@@ -164,16 +339,24 @@ declare namespace compiler {
164
339
  * All lines are prepended by a pipe (`|`) and show the corresponding line number.
165
340
  *
166
341
  * Example Output:
167
- * ```txt
342
+ * ```
168
343
  * |
169
344
  * 13 | num * nu
170
345
  * | ^^
171
346
  * ```
172
347
  *
173
- * @param sourceLines The source code split up into lines, e.g. by `str.split(/\r\n?|\n/);`.
174
- * @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
+
175
356
  */
176
- export function messageContext(sourceLines: string[], msg: CompileMessage): string;
357
+ export function messageContext(sourceLines: string[], msg: CompileMessage, config?: {
358
+ color?: boolean | 'auto'
359
+ }): string;
177
360
 
178
361
  /**
179
362
  * Get an explanatory text for a complicated compiler message with ID
@@ -205,6 +388,15 @@ declare namespace compiler {
205
388
  export function preparedCsnToEdmx(csn: CSN, service: string, options: any): any;
206
389
 
207
390
  export namespace parse {
391
+ /**
392
+ * Parse the given CDL in parseCdl mode and return its corresponding CSN representation.
393
+ *
394
+ * @param cdl CDL source as string.
395
+ * @param filename Filename to be used in compiler messages.
396
+ * @param options Compiler options. Note that if `options.messages` is not set, messages will be printed to stderr.
397
+ */
398
+ function cdl(cdl: string, filename: string, options?: Options): any;
399
+
208
400
  /**
209
401
  * Parse the given CQL and return its corresponding CSN representation.
210
402
  *
@@ -238,21 +430,26 @@ declare namespace compiler {
238
430
  * @alias for
239
431
  */
240
432
  export namespace For {
241
- 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;
242
439
  }
243
440
 
244
441
  export namespace to {
245
442
  function cdl(csn: CSN, options: Options): object;
246
443
  function sql(csn: CSN, options: Options): any;
247
444
 
248
- function edm(csn: CSN, options: Options): any;
445
+ function edm(csn: CSN, options: ODataOptions): any;
249
446
  namespace edm {
250
- function all(csn: CSN, options: Options): any;
447
+ function all(csn: CSN, options: ODataOptions): any;
251
448
  }
252
449
 
253
- function edmx(csn: CSN, options: Options): any;
450
+ function edmx(csn: CSN, options: ODataOptions): any;
254
451
  namespace edmx {
255
- function all(csn: CSN, options: Options): any;
452
+ function all(csn: CSN, options: ODataOptions): any;
256
453
  }
257
454
 
258
455
  function hdbcds(csn: CSN, options: Options): any;
package/lib/main.js CHANGED
@@ -16,10 +16,13 @@
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 { sortMessages, sortMessagesSeverityAware, deduplicateMessages } = require('./base/messages');
19
+ const { traverseCsn } = require('./model/api');
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');
25
+ const { define } = require('./compiler/definer');
23
26
 
24
27
  // The compiler version (taken from package.json)
25
28
  function version() {
@@ -31,7 +34,6 @@ const {
31
34
  messageString,
32
35
  messageStringMultiline,
33
36
  messageContext,
34
- handleMessages,
35
37
  hasErrors,
36
38
  explainMessage,
37
39
  hasMessageExplanation
@@ -39,15 +41,35 @@ const {
39
41
 
40
42
  const { compactModel, compactQuery, compactExpr } = require('./json/to-csn')
41
43
 
44
+ function parseCdl( cdl, filename, options = {} ) {
45
+ options = Object.assign( {}, options, { parseCdl: true } );
46
+ const sources = Object.create(null);
47
+ const model = { sources, options, $functions: {}, $volatileFunctions: {} };
48
+ const messageFunctions = createMessageFunctions( options, 'parse', model );
49
+ model.$messageFunctions = messageFunctions;
50
+
51
+ const xsn = parseLanguage( cdl, filename, Object.assign( { parseOnly: true }, options ),
52
+ messageFunctions );
53
+ sources[filename] = xsn;
54
+ fns( model );
55
+ define( model );
56
+ messageFunctions.throwWithError();
57
+ return compactModel( model );
58
+ }
59
+
42
60
  function parseCql( cdl, filename = '<query>.cds', options = {} ) {
43
- let xsn = parseLanguage( cdl, filename, Object.assign( {parseOnly:true}, options ), 'query' );
44
- handleMessages( xsn, options );
61
+ const messageFunctions = createMessageFunctions( options, 'parse' );
62
+ const xsn = parseLanguage( cdl, filename, Object.assign( { parseOnly: true }, options ),
63
+ messageFunctions, 'query' );
64
+ messageFunctions.throwWithError();
45
65
  return compactQuery( xsn );
46
66
  }
47
67
 
48
68
  function parseExpr( cdl, filename = '<expr>.cds', options = {} ) {
49
- let xsn = parseLanguage( cdl, filename, Object.assign( {parseOnly:true}, options ), 'expr' );
50
- handleMessages( xsn, options );
69
+ const messageFunctions = createMessageFunctions( options, 'parse' );
70
+ const xsn = parseLanguage( cdl, filename, Object.assign( { parseOnly: true }, options ),
71
+ messageFunctions, 'expr' );
72
+ messageFunctions.throwWithError();
51
73
  return compactExpr( xsn );
52
74
  }
53
75
 
@@ -79,7 +101,7 @@ module.exports = {
79
101
  preparedCsnToEdm : (csn, service, options) => { return backends.preparedCsnToEdm(csn, service, options).edmj},
80
102
 
81
103
  // additional API:
82
- parse: { cql: parseCql, expr: parseExpr }, // preferred names
104
+ parse: { cdl: parseCdl, cql: parseCql, expr: parseExpr }, // preferred names
83
105
  /**
84
106
  * @deprecated Use parse.cql instead
85
107
  */
@@ -92,6 +114,9 @@ module.exports = {
92
114
  getArtifactCdsPersistenceName: getArtifactDatabaseNameOf,
93
115
  getElementCdsPersistenceName: getElementDatabaseNameOf,
94
116
 
117
+ // Other API functions:
118
+ traverseCsn,
119
+
95
120
  // INTERNAL functions for the cds-lsp package and friends - before you use
96
121
  // it, you MUST talk with us - there can be potential incompatibilities with
97
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
+ };