@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.
- package/CHANGELOG.md +221 -15
- package/bin/cdsc.js +125 -50
- package/bin/cdsse.js +2 -2
- package/doc/CHANGELOG_BETA.md +13 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +47 -84
- package/lib/api/options.js +5 -6
- package/lib/api/validate.js +6 -11
- package/lib/backends.js +15 -23
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +7 -17
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +114 -18
- package/lib/base/messages.js +101 -90
- package/lib/base/model.js +2 -63
- package/lib/base/optionProcessorHelper.js +177 -123
- package/lib/checks/annotationsOData.js +12 -33
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +27 -26
- package/lib/checks/types.js +1 -1
- package/lib/checks/validator.js +6 -11
- package/lib/compiler/assert-consistency.js +6 -3
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +19 -6
- package/lib/compiler/checks.js +23 -60
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1151 -0
- package/lib/compiler/extend.js +1000 -0
- package/lib/compiler/finalize-parse-cdl.js +237 -0
- package/lib/compiler/index.js +107 -39
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1227 -0
- package/lib/compiler/propagator.js +114 -46
- package/lib/compiler/resolve.js +1521 -0
- package/lib/compiler/shared.js +126 -65
- package/lib/compiler/tweak-assocs.js +535 -0
- package/lib/compiler/utils.js +197 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -24
- package/lib/edm/annotations/preprocessAnnotations.js +2 -2
- package/lib/edm/csn2edm.js +219 -100
- package/lib/edm/edm.js +302 -230
- package/lib/edm/edmPreprocessor.js +554 -419
- package/lib/edm/edmUtils.js +138 -44
- package/lib/gen/Dictionary.json +100 -19
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -83
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +860 -833
- package/lib/gen/languageLexer.tokens +78 -75
- package/lib/gen/languageParser.js +5765 -4480
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +15 -3
- package/lib/json/to-csn.js +126 -68
- package/lib/language/docCommentParser.js +4 -4
- package/lib/language/genericAntlrParser.js +123 -5
- package/lib/language/language.g4 +355 -156
- package/lib/language/multiLineStringParser.js +5 -5
- package/lib/main.d.ts +486 -59
- package/lib/main.js +41 -9
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +252 -156
- package/lib/model/csnUtils.js +384 -297
- package/lib/model/enrichCsn.js +71 -29
- package/lib/model/revealInternalProperties.js +29 -8
- package/lib/model/sortViews.js +2 -1
- package/lib/modelCompare/compare.js +23 -18
- package/lib/optionProcessor.js +63 -26
- package/lib/render/manageConstraints.js +35 -32
- package/lib/render/toCdl.js +897 -947
- package/lib/render/toHdbcds.js +205 -257
- package/lib/render/toSql.js +264 -225
- package/lib/render/utils/common.js +136 -25
- package/lib/render/utils/sql.js +4 -3
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/db/.eslintrc.json +3 -1
- package/lib/transform/db/applyTransformations.js +35 -12
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +104 -306
- package/lib/transform/db/cdsPersistence.js +2 -2
- package/lib/transform/db/constraints.js +58 -53
- package/lib/transform/db/expansion.js +60 -33
- package/lib/transform/db/flattening.js +582 -104
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/transformExists.js +66 -13
- package/lib/transform/db/views.js +11 -7
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +6 -5
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +109 -208
- package/lib/transform/forOdataNew.js +59 -212
- package/lib/transform/localized.js +46 -26
- package/lib/transform/odata/toFinalBaseType.js +85 -11
- package/lib/transform/odata/typesExposure.js +147 -199
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +44 -33
- package/lib/transform/translateAssocsToJoins.js +3 -20
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +172 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +737 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/moduleResolve.js +13 -6
- package/lib/utils/objectUtils.js +30 -0
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/lib/compiler/definer.js +0 -2361
- package/lib/compiler/resolver.js +0 -3079
- package/lib/transform/odata/attachPath.js +0 -96
- package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
- package/lib/transform/odata/generateForeignKeyElements.js +0 -261
- package/lib/transform/odata/referenceFlattener.js +0 -290
- package/lib/transform/odata/sortByAssociationDependency.js +0 -105
- package/lib/transform/odata/structuralPath.js +0 -72
- package/lib/transform/odata/structureFlattener.js +0 -171
- 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;
|
package/lib/compiler/index.js
CHANGED
|
@@ -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
|
|
25
|
-
const
|
|
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
|
|
60
|
-
*
|
|
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 (
|
|
78
|
+
if (csnExtensions.includes(ext) || options.fallbackParser === 'csn!')
|
|
72
79
|
return parseCsn.parse( source, filename, options, messageFunctions );
|
|
73
|
-
if (
|
|
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
|
|
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/
|
|
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
|
|
125
|
-
|
|
126
|
-
const model = { sources: a.sources, options };
|
|
131
|
+
const model = { sources: null, options };
|
|
127
132
|
model.$messageFunctions = createMessageFunctions( options, 'compile', model );
|
|
128
|
-
let
|
|
129
|
-
|
|
130
|
-
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( [ ...
|
|
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(
|
|
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 } =
|
|
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 (
|
|
181
|
+
if (input.repeated.length)
|
|
170
182
|
// repeated file names in invocation => just report these
|
|
171
|
-
return Promise.reject( new InvocationError(
|
|
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 =
|
|
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
|
|
330
|
-
* mapping filenames to either source texts (string) or
|
|
331
|
-
*
|
|
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 {
|
|
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
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const
|
|
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
|
-
|
|
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,
|