@sap/cds-compiler 2.12.0 → 2.15.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 (128) hide show
  1. package/CHANGELOG.md +221 -15
  2. package/bin/cdsc.js +125 -50
  3. package/bin/cdsse.js +2 -2
  4. package/doc/CHANGELOG_BETA.md +13 -6
  5. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  6. package/doc/NameResolution.md +21 -16
  7. package/lib/api/main.js +47 -84
  8. package/lib/api/options.js +5 -6
  9. package/lib/api/validate.js +6 -11
  10. package/lib/backends.js +15 -23
  11. package/lib/base/dictionaries.js +0 -8
  12. package/lib/base/error.js +26 -0
  13. package/lib/base/keywords.js +7 -17
  14. package/lib/base/location.js +9 -4
  15. package/lib/base/message-registry.js +114 -18
  16. package/lib/base/messages.js +101 -90
  17. package/lib/base/model.js +2 -63
  18. package/lib/base/optionProcessorHelper.js +177 -123
  19. package/lib/checks/annotationsOData.js +12 -33
  20. package/lib/checks/arrayOfs.js +1 -34
  21. package/lib/checks/cdsPersistence.js +2 -1
  22. package/lib/checks/enricher.js +17 -1
  23. package/lib/checks/invalidTarget.js +3 -1
  24. package/lib/checks/managedWithoutKeys.js +3 -1
  25. package/lib/checks/selectItems.js +4 -4
  26. package/lib/checks/sql-snippets.js +27 -26
  27. package/lib/checks/types.js +1 -1
  28. package/lib/checks/validator.js +6 -11
  29. package/lib/compiler/assert-consistency.js +6 -3
  30. package/lib/compiler/base.js +1 -0
  31. package/lib/compiler/builtins.js +19 -6
  32. package/lib/compiler/checks.js +23 -60
  33. package/lib/compiler/cycle-detector.js +1 -1
  34. package/lib/compiler/define.js +1151 -0
  35. package/lib/compiler/extend.js +1000 -0
  36. package/lib/compiler/finalize-parse-cdl.js +237 -0
  37. package/lib/compiler/index.js +107 -39
  38. package/lib/compiler/kick-start.js +190 -0
  39. package/lib/compiler/moduleLayers.js +4 -4
  40. package/lib/compiler/populate.js +1227 -0
  41. package/lib/compiler/propagator.js +114 -46
  42. package/lib/compiler/resolve.js +1521 -0
  43. package/lib/compiler/shared.js +126 -65
  44. package/lib/compiler/tweak-assocs.js +535 -0
  45. package/lib/compiler/utils.js +197 -33
  46. package/lib/edm/.eslintrc.json +5 -0
  47. package/lib/edm/annotations/genericTranslation.js +38 -24
  48. package/lib/edm/annotations/preprocessAnnotations.js +2 -2
  49. package/lib/edm/csn2edm.js +219 -100
  50. package/lib/edm/edm.js +302 -230
  51. package/lib/edm/edmPreprocessor.js +554 -419
  52. package/lib/edm/edmUtils.js +138 -44
  53. package/lib/gen/Dictionary.json +100 -19
  54. package/lib/gen/language.checksum +1 -1
  55. package/lib/gen/language.interp +11 -1
  56. package/lib/gen/language.tokens +86 -83
  57. package/lib/gen/languageLexer.interp +10 -1
  58. package/lib/gen/languageLexer.js +860 -833
  59. package/lib/gen/languageLexer.tokens +78 -75
  60. package/lib/gen/languageParser.js +5765 -4480
  61. package/lib/json/csnVersion.js +10 -11
  62. package/lib/json/from-csn.js +15 -3
  63. package/lib/json/to-csn.js +126 -68
  64. package/lib/language/docCommentParser.js +4 -4
  65. package/lib/language/genericAntlrParser.js +123 -5
  66. package/lib/language/language.g4 +355 -156
  67. package/lib/language/multiLineStringParser.js +5 -5
  68. package/lib/main.d.ts +486 -59
  69. package/lib/main.js +41 -9
  70. package/lib/model/api.js +3 -1
  71. package/lib/model/csnRefs.js +252 -156
  72. package/lib/model/csnUtils.js +384 -297
  73. package/lib/model/enrichCsn.js +71 -29
  74. package/lib/model/revealInternalProperties.js +29 -8
  75. package/lib/model/sortViews.js +2 -1
  76. package/lib/modelCompare/compare.js +23 -18
  77. package/lib/optionProcessor.js +63 -26
  78. package/lib/render/manageConstraints.js +35 -32
  79. package/lib/render/toCdl.js +897 -947
  80. package/lib/render/toHdbcds.js +205 -257
  81. package/lib/render/toSql.js +264 -225
  82. package/lib/render/utils/common.js +136 -25
  83. package/lib/render/utils/sql.js +4 -3
  84. package/lib/render/utils/stringEscapes.js +111 -0
  85. package/lib/sql-identifier.js +1 -1
  86. package/lib/transform/.eslintrc.json +5 -0
  87. package/lib/transform/db/.eslintrc.json +3 -1
  88. package/lib/transform/db/applyTransformations.js +35 -12
  89. package/lib/transform/db/assertUnique.js +1 -1
  90. package/lib/transform/db/associations.js +104 -306
  91. package/lib/transform/db/cdsPersistence.js +2 -2
  92. package/lib/transform/db/constraints.js +58 -53
  93. package/lib/transform/db/expansion.js +60 -33
  94. package/lib/transform/db/flattening.js +582 -104
  95. package/lib/transform/db/groupByOrderBy.js +3 -1
  96. package/lib/transform/db/transformExists.js +66 -13
  97. package/lib/transform/db/views.js +11 -7
  98. package/lib/transform/draft/.eslintrc.json +38 -0
  99. package/lib/transform/{db/draft.js → draft/db.js} +6 -5
  100. package/lib/transform/draft/odata.js +227 -0
  101. package/lib/transform/forHanaNew.js +109 -208
  102. package/lib/transform/forOdataNew.js +59 -212
  103. package/lib/transform/localized.js +46 -26
  104. package/lib/transform/odata/toFinalBaseType.js +85 -11
  105. package/lib/transform/odata/typesExposure.js +147 -199
  106. package/lib/transform/odata/utils.js +2 -2
  107. package/lib/transform/transformUtilsNew.js +44 -33
  108. package/lib/transform/translateAssocsToJoins.js +3 -20
  109. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  110. package/lib/transform/universalCsn/coreComputed.js +172 -0
  111. package/lib/transform/universalCsn/universalCsnEnricher.js +737 -0
  112. package/lib/transform/universalCsn/utils.js +63 -0
  113. package/lib/utils/moduleResolve.js +13 -6
  114. package/lib/utils/objectUtils.js +30 -0
  115. package/package.json +1 -1
  116. package/share/messages/README.md +26 -0
  117. package/share/messages/message-explanations.json +2 -1
  118. package/share/messages/syntax-expected-integer.md +37 -0
  119. package/lib/compiler/definer.js +0 -2361
  120. package/lib/compiler/resolver.js +0 -3079
  121. package/lib/transform/odata/attachPath.js +0 -96
  122. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  123. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  124. package/lib/transform/odata/referenceFlattener.js +0 -290
  125. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  126. package/lib/transform/odata/structuralPath.js +0 -72
  127. package/lib/transform/odata/structureFlattener.js +0 -171
  128. package/lib/transform/universalCsnEnricher.js +0 -237
@@ -0,0 +1,237 @@
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
+ resolveTypeArgumentsUnchecked,
20
+ defineAnnotations,
21
+ initMembers,
22
+ extensionsDict,
23
+ } = model.$functions;
24
+
25
+ resolveTypesAndExtensionsForParseCdl();
26
+ return;
27
+
28
+ function resolveTypesAndExtensionsForParseCdl() {
29
+ const extensions = [];
30
+
31
+ // TODO: probably better to loop over extensions of all sources (there is just one)
32
+ for (const name in extensionsDict) {
33
+ for (const ext of extensionsDict[name]) {
34
+ ext.name.absolute = resolveUncheckedPath( ext.name, 'extend', ext );
35
+ // Define annotations of this top-level extension
36
+ defineAnnotations( ext, ext, ext._block );
37
+ mergeAnnotatesForSameArtifact( ext );
38
+ // Initialize members and define annotations in sub-elements.
39
+ initMembers( ext, ext, ext._block, true );
40
+ extensions.push( ext );
41
+ for (const col of ext.columns || []) {
42
+ // Note, no `priority` argument, since we don't apply the extension in the end.
43
+ defineAnnotations( col, col, ext._block );
44
+ }
45
+ }
46
+ }
47
+
48
+ forEachGeneric(model, 'definitions', art => resolveTypesForParseCdl(art, art));
49
+ forEachGeneric(model, 'vocabularies', art => resolveTypesForParseCdl(art, art));
50
+
51
+ if (extensions.length > 0) {
52
+ model.extensions = extensions;
53
+ model.extensions.forEach(ext => resolveTypesForParseCdl(ext, ext));
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Resolve all types in parseCdl mode for the given artifact.
59
+ * `main` refers to the current user/scope, e.g. top-level definition, element, function, etc.
60
+ *
61
+ * @param {*} artifact
62
+ * @param {XSN.Artifact} main
63
+ */
64
+ function resolveTypesForParseCdl(artifact, main) {
65
+ if (!artifact || typeof artifact !== 'object')
66
+ return;
67
+
68
+ if (Array.isArray(artifact)) {
69
+ // e.g. `args` array
70
+ artifact.forEach(art => resolveTypesForParseCdl(art, main));
71
+ return;
72
+ }
73
+
74
+ if (artifact.kind === 'namespace' || artifact.kind === 'service' || artifact.kind === 'context')
75
+ // Services and context artifacts don't have any types.
76
+ return;
77
+
78
+ if (artifact.type)
79
+ resolveTypeUnchecked(artifact, main);
80
+
81
+ for (const include of artifact.includes || [])
82
+ resolveUncheckedPath(include, 'include', main);
83
+
84
+ if (artifact.target)
85
+ resolveUncheckedPath(artifact.target, 'target', main);
86
+
87
+ if (artifact.from) {
88
+ const { from } = artifact;
89
+ // Note: `_from` only contains sources necessary for calculating elements,
90
+ // not e.g. those from the `where exists` clause.
91
+ resolveUncheckedPath(from, 'from', main);
92
+ resolveTypesForParseCdl(from, main);
93
+ }
94
+
95
+ if (artifact.targetAspect) {
96
+ if (artifact.targetAspect.path)
97
+ resolveUncheckedPath(artifact.targetAspect, 'target', main);
98
+ resolveTypesForParseCdl(artifact.targetAspect, main);
99
+ }
100
+
101
+ // Recursively go through all XSN properties. There are a few that need to be
102
+ // handled specifically. Refer to the code below this loop for details.
103
+ for (const prop in artifact) {
104
+ if (parseCdlSpeciallyHandledXsnProps.includes(prop) || parseCdlIgnoredXsnProps.includes(prop))
105
+ continue;
106
+
107
+ // define.js (and initMembers()) initializes annotations. If there is a duplicate, the
108
+ // annotation is an array in XSN.
109
+ if (prop[0] === '@' && Array.isArray(artifact[prop]))
110
+ chooseAndReportDuplicateAnnotation(artifact, prop);
111
+
112
+ if (artifact[prop] && Object.getPrototypeOf(artifact[prop]) === null)
113
+ // Dictionary in XSN
114
+ forEachGeneric(artifact, prop, art => resolveTypesForParseCdl(art, art));
115
+ else
116
+ resolveTypesForParseCdl(artifact[prop], main);
117
+ }
118
+
119
+ // `$queries` has a flat structure that we can use instead of going through all `query`.
120
+ // For these query-related properties, we need to keep the reference to the artifact
121
+ // containing it. Otherwise some type's aren't properly resolved.
122
+ // TODO: If resolveTypeUnchecked is reworked, we may be able to simplify this coding.
123
+ (artifact.$queries || []).forEach( art => resolveTypesForParseCdl(art, artifact) );
124
+ (artifact.columns || []).forEach( art => resolveTypesForParseCdl(art, artifact) );
125
+ forEachGeneric( artifact, 'mixin', art => resolveTypesForParseCdl(art, artifact) );
126
+
127
+ // For better error messages for `type of`s in `returns`, we pass the object as the new main.
128
+ resolveTypesForParseCdl(artifact.returns, artifact.returns);
129
+
130
+ // Because `args` can be used in different contexts with different semantics,
131
+ // it needs to be handled specifically.
132
+ if (artifact.args && typeof artifact.args === 'object') {
133
+ if (Array.isArray(artifact.args)) {
134
+ // `args` may either be an array (e.g. query 'from' args) ...
135
+ artifact.args.forEach((from) => {
136
+ // ... and could be either inside a `from` ...
137
+ if (from && from.kind === '$tableAlias')
138
+ resolveUncheckedPath(from, 'from', from._main);
139
+
140
+ // ... or only params ...
141
+ resolveTypesForParseCdl(from, main);
142
+ });
143
+ }
144
+ else {
145
+ // ... or dictionary (e.g. params)
146
+ forEachGeneric(artifact, 'args', obj => resolveTypesForParseCdl(obj, obj));
147
+ }
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Resolves `artWithType.type` in an unchecked manner. Handles `type of` cases.
153
+ * `artWithType` has the `type` property, i.e. it could be an `items` object.
154
+ * `user` is the actual artifact, e.g. entity or element.
155
+ *
156
+ * @param {object} artWithType
157
+ * @param {XSN.Artifact} user
158
+ */
159
+ function resolveTypeUnchecked(artWithType, user) {
160
+ if (!artWithType.type)
161
+ return;
162
+ const root = artWithType.type.path && artWithType.type.path[0];
163
+ if (!root) // parse error
164
+ return;
165
+ // `scope` is only `typeOf` for `type of element` and not
166
+ // `type of Entity:element`. For the latter we can resolve the path
167
+ // without special treatment.
168
+ if (artWithType.type.scope !== 'typeOf') {
169
+ // elem: Type or elem: type of Artifact:elem
170
+ const name = resolveUncheckedPath( artWithType.type, 'type', user );
171
+ const type = name && model.definitions[name] || { name: { absolute: name } };
172
+ resolveTypeArgumentsUnchecked( artWithType, type, user );
173
+ return;
174
+ }
175
+ else if (!user._main) {
176
+ error( 'ref-undefined-typeof', [ artWithType.type.location, user ], {},
177
+ 'Current artifact has no element to refer to as type' );
178
+ return;
179
+ }
180
+ else if (root.id === '$self' || root.id === '$projection') {
181
+ setArtifactLink( root, user._main );
182
+ }
183
+ else {
184
+ // For better error messages, check for invalid TYPE OFs similarly
185
+ // to how `resolveType()` does.
186
+ let struct = artWithType;
187
+ while (struct.kind === 'element')
188
+ struct = struct._parent;
189
+ if (struct.kind === 'select' || struct !== user._main) {
190
+ message( 'type-unexpected-typeof', [ artWithType.type.location, user ],
191
+ { keyword: 'type of', '#': struct.kind } );
192
+ return;
193
+ }
194
+
195
+ const fake = { name: { absolute: user.name.absolute } };
196
+ // to-csn just needs a fake element whose absolute name and _parent/_main links are correct
197
+ setLink( fake, '_parent', user._parent );
198
+ setLink( fake, '_main', user._main ); // value does not matter...
199
+ setArtifactLink( root, fake );
200
+ }
201
+ }
202
+
203
+ function chooseAndReportDuplicateAnnotation(artifact, annoName) {
204
+ for (const anno of artifact[annoName])
205
+ message( 'anno-duplicate', [ anno.name.location, artifact ], { anno: annoName } );
206
+
207
+ // Choose any annotation, doesn't matter because of the error above.
208
+ artifact[annoName] = artifact[annoName][0];
209
+ }
210
+
211
+ /**
212
+ * For duplicate entries in `annotate {}` blocks, we de-duplicate the entries and merge them.
213
+ * Since it is allowed in "normal" compilations to have e.g.
214
+ * `annotate E with { @anno1 id, @anno2 id }`.
215
+ *
216
+ * @param {object} ext
217
+ */
218
+ function mergeAnnotatesForSameArtifact( ext ) {
219
+ if (!ext || typeof ext !== 'object')
220
+ return;
221
+
222
+ forEachMember(ext, sub => mergeAnnotatesForSameArtifact(sub));
223
+
224
+ if (ext.$annotations && Array.isArray(ext.$duplicates)) {
225
+ const annotates = ext.$duplicates.filter(val => (val.kind === 'annotate'));
226
+ for (const dup of annotates) {
227
+ ext.$annotations.push(...dup.$annotations);
228
+ delete dup.$annotations;
229
+ }
230
+ ext.$duplicates = ext.$duplicates.filter(val => (val.kind !== 'annotate'));
231
+ if (ext.$duplicates.length === 0)
232
+ delete ext.$duplicates;
233
+ }
234
+ }
235
+ }
236
+
237
+ 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,8 +63,8 @@ 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"
@@ -68,9 +75,9 @@ 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 = null ) {
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 = null ) {
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
@@ -121,30 +128,35 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
121
128
  // if (Object.getPrototypeOf( fileCache ))
122
129
  // fileCache = Object.assign( Object.create(null), fileCache );
123
130
  dir = path.resolve(dir);
124
- const a = processFilenames( filenames, dir );
125
-
126
- const model = { sources: a.sources, options };
131
+ const model = { sources: null, options };
127
132
  model.$messageFunctions = createMessageFunctions( options, 'compile', model );
128
- let all = promiseAllDoNotRejectImmediately( a.files.map(readAndParse) );
129
-
130
- all = all
133
+ let input = null;
134
+
135
+ let all = processFilenames( filenames, dir )
136
+ .then((processedInput) => {
137
+ input = processedInput;
138
+ model.sources = input.sources;
139
+ })
140
+ .then(() => promiseAllDoNotRejectImmediately( input.files.map(readAndParse) ))
131
141
  .then( testInvocation, (reason) => {
132
142
  // do not reject with PromiseAllError, use InvocationError:
133
143
  const errs = reason.valuesOrErrors.filter( e => e instanceof Error );
134
144
  // internal error if no file IO error (has property `path`)
135
145
  return Promise.reject( errs.find( e => !e.path ) ||
136
- new InvocationError( [ ...a.repeated, ...errs ]) );
146
+ new InvocationError( [ ...input.repeated, ...errs ]) );
137
147
  });
148
+
138
149
  if (!options.parseOnly && !options.parseCdl)
139
150
  all = all.then( readDependencies );
151
+
140
152
  return all.then( () => {
141
- moduleLayers.setLayers( a.sources );
153
+ moduleLayers.setLayers( input.sources );
142
154
  return compileDoX( model );
143
155
  });
144
156
 
145
157
  // Read file `filename` and parse its content, return messages
146
158
  async function readAndParse( filename ) {
147
- const { sources } = a;
159
+ const { sources } = input;
148
160
  if ( filename === false ) // module which has not been found
149
161
  return [];
150
162
  const rel = sources[filename] || path.relative( dir, filename );
@@ -166,9 +178,9 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
166
178
 
167
179
  // Combine the parse results (if there are not file IO errors)
168
180
  function testInvocation( values ) {
169
- if (a.repeated.length)
181
+ if (input.repeated.length)
170
182
  // repeated file names in invocation => just report these
171
- return Promise.reject( new InvocationError(a.repeated) );
183
+ return Promise.reject( new InvocationError(input.repeated) );
172
184
  return values;
173
185
  }
174
186
 
@@ -218,7 +230,7 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
218
230
  // A non-proper dictionary (i.e. with prototype) is safe if the keys are
219
231
  // absolute file names - they start with `/` or `\` or similar
220
232
  dir = path.resolve(dir);
221
- const a = processFilenames( filenames, dir );
233
+ const a = processFilenamesSync( filenames, dir );
222
234
 
223
235
  const model = { sources: a.sources, options };
224
236
  model.$messageFunctions = createMessageFunctions( options, 'compile', model );
@@ -326,9 +338,10 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
326
338
  /**
327
339
  * Promise-less main functions: compile the given sources.
328
340
  *
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
341
+ * Argument `sourcesDict` is a dictionary (it could actually be an ordinary object)
342
+ * mapping filenames to either source texts (string) or JS objects; the objects
343
+ * are usually CSNs, or XSNs (AST-like augmented CSNs) with option `$xsnObjects`.
344
+ * It could also be a simple string, which is then considered
332
345
  * to be the source text of a file named `<stdin>.cds`.
333
346
  *
334
347
  * See function `compileX` for the meaning of the argument `options`. If there
@@ -356,9 +369,15 @@ function compileSourcesX( sourcesDict, options = {} ) {
356
369
  ast.location = { file: filename };
357
370
  assertConsistency( ast, options );
358
371
  }
359
- else { // source is a XSN object (CSN/CDL parser output)
372
+ else if (options.$xsnObjects) { // source is a XSN object with option $xsnObjects
360
373
  sources[filename] = source;
361
374
  }
375
+ else { // source is a CSN object
376
+ const ast = parseCsn.augment( source, filename, options, model.$messageFunctions );
377
+ sources[filename] = ast;
378
+ ast.location = { file: filename };
379
+ assertConsistency( ast, options );
380
+ }
362
381
  }
363
382
  moduleLayers.setLayers( sources );
364
383
 
@@ -420,10 +439,15 @@ function compileDoX( model ) {
420
439
  // do not run the resolver in parse-cdl mode or we get duplicate annotations, etc.
421
440
  // TODO: do not use this function for parseCdl anyway…
422
441
  if (options.parseCdl) {
442
+ finalizeParseCdl( model );
423
443
  throwWithError();
424
444
  return model;
425
445
  }
446
+ extend( model );
447
+ kickStart( model );
448
+ populate( model );
426
449
  resolve( model );
450
+ tweakAssocs( model );
427
451
  assertConsistency( model );
428
452
  throwWithError();
429
453
  if (options.lintMode)
@@ -434,22 +458,44 @@ function compileDoX( model ) {
434
458
  return propagator.propagate( model );
435
459
  }
436
460
 
437
- // Process an array of `filenames`. Returns an object with properties:
438
- // - `sources`: dictionary which has a filename as key (value is irrelevant)
439
- // - `files`: the argument array without repeating the same name
440
- // - `repeated`: array of filenames which have been repeatedly listed
441
- // (listed only once here even if listed thrice)
442
- //
443
- // Note: there is nothing file-specific about the filenames, the filenames are
444
- // not normalized - any strings work
445
- function processFilenames( filenames, dir ) {
446
- const sources = Object.create(null); // not {} = no [[Prototype]]
447
- const files = [];
448
- const repeated = [];
461
+ /**
462
+ * Process an array of `filenames`. Returns an object with properties:
463
+ * - `sources`: dictionary which has a filename as key (value is irrelevant)
464
+ * - `files`: the argument array without repeating the same name
465
+ * - `repeated`: array of filenames which have been repeatedly listed
466
+ * (listed only once here even if listed thrice)
467
+ *
468
+ * Note: there is nothing file-specific about the filenames, the filenames are
469
+ * not normalized - any strings work
470
+ */
471
+ async function processFilenames( filenames, dir ) {
472
+ const filenameMap = Object.create(null);
449
473
 
474
+ const promises = [];
450
475
  for (const originalName of filenames) {
451
- let name = path.resolve(dir, originalName);
476
+ const setName = (name) => {
477
+ filenameMap[originalName] = name;
478
+ };
479
+ // Resolve possible symbolic link; if the file does not exist
480
+ // we just continue using the original name because readFile()
481
+ // already handles non-existent files.
482
+ const promise = fs.promises.realpath(path.resolve(dir, originalName))
483
+ .then(setName, () => setName(originalName));
484
+ promises.push(promise);
485
+ }
486
+
487
+ await Promise.all(promises);
488
+ return createSourcesDict( filenames, filenameMap, dir );
489
+ }
452
490
 
491
+ /**
492
+ * Synchronous version of processFilenames().
493
+ */
494
+ function processFilenamesSync( filenames, dir ) {
495
+ const filenameMap = Object.create(null);
496
+
497
+ for (const originalName of filenames) {
498
+ let name = path.resolve(dir, originalName);
453
499
  try {
454
500
  // Resolve possible symbolic link; if the file does not exist
455
501
  // we just continue using the original name because readFile()
@@ -459,7 +505,29 @@ function processFilenames( filenames, dir ) {
459
505
  catch (e) {
460
506
  // Ignore the not-found (ENOENT) error
461
507
  }
508
+ filenameMap[originalName] = name;
509
+ }
510
+
511
+ return createSourcesDict( filenames, filenameMap, dir );
512
+ }
513
+
514
+ /**
515
+ * Creates the sources dictionary as well as a list of absolute filenames.
516
+ * If files are repeated, `repeated` will contain ArgumentErrors for it.
517
+ *
518
+ * @param {string[]} filenames List of (possibly relative) filenames. Defines the file order.
519
+ * @param {Record<string, string>} filenameMap Map from original name to actual filename
520
+ * (e.g. from symlink to underlying path)
521
+ * @param {string} dir "Current working directory"
522
+ * @return {{sources: object, files: string[], repeated: ArgumentError[]}}
523
+ */
524
+ function createSourcesDict( filenames, filenameMap, dir ) {
525
+ const sources = Object.create(null);
526
+ const files = [];
527
+ const repeated = [];
462
528
 
529
+ for (const originalName of filenames) {
530
+ const name = filenameMap[originalName];
463
531
  if (!sources[name]) {
464
532
  sources[name] = path.relative( dir, name );
465
533
  files.push(name);
@@ -469,10 +537,10 @@ function processFilenames( filenames, dir ) {
469
537
  repeated.push( new ArgumentError( name, msg ) );
470
538
  }
471
539
  }
540
+
472
541
  return { sources, files, repeated };
473
542
  }
474
543
 
475
-
476
544
  module.exports = {
477
545
  parseX,
478
546
  compileX,