@sap/cds-compiler 4.9.6 → 5.1.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.
- package/CHANGELOG.md +92 -0
- package/bin/cds_remove_invalid_whitespace.js +2 -1
- package/bin/cdsc.js +49 -19
- package/bin/cdshi.js +3 -1
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/main.js +16 -19
- package/lib/api/options.js +5 -14
- package/lib/api/trace.js +0 -1
- package/lib/base/builtins.js +1 -0
- package/lib/base/location.js +4 -1
- package/lib/base/message-registry.js +43 -29
- package/lib/base/messages.js +23 -26
- package/lib/base/meta.js +10 -0
- package/lib/base/model.js +0 -2
- package/lib/base/node-helpers.js +0 -1
- package/lib/base/optionProcessorHelper.js +11 -0
- package/lib/checks/dbFeatureFlags.js +5 -0
- package/lib/checks/enricher.js +1 -5
- package/lib/checks/structuredAnnoExpressions.js +30 -0
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +5 -1
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/builtins.js +18 -2
- package/lib/compiler/checks.js +2 -5
- package/lib/compiler/define.js +8 -8
- package/lib/compiler/extend.js +108 -37
- package/lib/compiler/generate.js +1 -1
- package/lib/compiler/index.js +27 -10
- package/lib/compiler/lsp-api.js +501 -2
- package/lib/compiler/populate.js +60 -13
- package/lib/compiler/propagator.js +10 -8
- package/lib/compiler/resolve.js +117 -94
- package/lib/compiler/shared.js +114 -32
- package/lib/compiler/tweak-assocs.js +31 -21
- package/lib/compiler/utils.js +2 -1
- package/lib/compiler/xsn-model.js +4 -0
- package/lib/edm/annotations/genericTranslation.js +69 -35
- package/lib/edm/csn2edm.js +16 -4
- package/lib/edm/edm.js +10 -3
- package/lib/edm/edmAnnoPreprocessor.js +1 -2
- package/lib/edm/edmPreprocessor.js +8 -10
- package/lib/gen/Dictionary.json +66 -2
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +2 -1
- package/lib/gen/languageParser.js +4995 -4817
- package/lib/json/csnVersion.js +1 -1
- package/lib/json/from-csn.js +4 -7
- package/lib/json/to-csn.js +25 -12
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/errorStrategy.js +0 -1
- package/lib/language/genericAntlrParser.js +35 -12
- package/lib/language/multiLineStringParser.js +3 -2
- package/lib/language/textUtils.js +1 -0
- package/lib/main.d.ts +28 -9
- package/lib/main.js +9 -9
- package/lib/model/cloneCsn.js +1 -0
- package/lib/model/csnRefs.js +22 -5
- package/lib/model/csnUtils.js +0 -14
- package/lib/model/revealInternalProperties.js +1 -2
- package/lib/modelCompare/compare.js +13 -11
- package/lib/optionProcessor.js +30 -9
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +44 -14
- package/lib/render/toHdbcds.js +1 -2
- package/lib/render/toSql.js +45 -8
- package/lib/render/utils/common.js +12 -9
- package/lib/render/utils/stringEscapes.js +1 -0
- package/lib/transform/db/applyTransformations.js +13 -8
- package/lib/transform/db/associations.js +62 -54
- package/lib/transform/db/backlinks.js +20 -5
- package/lib/transform/db/expansion.js +1 -6
- package/lib/transform/db/flattening.js +86 -109
- package/lib/transform/db/killAnnotations.js +3 -0
- package/lib/transform/db/processSqlServices.js +63 -0
- package/lib/transform/db/temporal.js +3 -4
- package/lib/transform/db/views.js +0 -1
- package/lib/transform/draft/odata.js +56 -3
- package/lib/transform/effective/annotations.js +3 -2
- package/lib/transform/effective/flattening.js +135 -0
- package/lib/transform/effective/main.js +6 -4
- package/lib/transform/effective/types.js +13 -9
- package/lib/transform/forOdata.js +0 -2
- package/lib/transform/forRelationalDB.js +9 -19
- package/lib/transform/localized.js +7 -8
- package/lib/transform/odata/flattening.js +39 -31
- package/lib/transform/odata/typesExposure.js +5 -17
- package/lib/transform/transformUtils.js +1 -1
- package/lib/transform/translateAssocsToJoins.js +0 -1
- package/lib/utils/file.js +87 -8
- package/lib/utils/moduleResolve.js +59 -8
- package/lib/utils/term.js +3 -2
- package/package.json +7 -3
- package/share/messages/message-explanations.json +2 -0
- package/share/messages/type-unexpected-foreign-keys.md +52 -0
- package/share/messages/type-unexpected-on-condition.md +52 -0
package/lib/compiler/lsp-api.js
CHANGED
|
@@ -1,5 +1,504 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// API for `@sap/cds-lsp`.
|
|
4
|
+
//
|
|
5
|
+
// THIS FILE IS CONSIDERED INTERNAL!
|
|
6
|
+
// We do not guarantee stability for any project besides the CAP LSP server.
|
|
7
|
+
//
|
|
8
|
+
// This files includes an iterator over "semantic tokens" in an XSN model.
|
|
9
|
+
// "Semantic tokens" are identifiers, but also the "return" parameter.
|
|
10
|
+
// See `internalDoc/lsp/IdentifierCrawling.md` for details.
|
|
4
11
|
|
|
5
|
-
|
|
12
|
+
const { CompilerAssertion } = require('../base/error');
|
|
13
|
+
const $inferred = Symbol.for( 'cds.$inferred' );
|
|
14
|
+
|
|
15
|
+
// TODO: Remove hints; they should not be necessary in the best case
|
|
16
|
+
const HINTS = {
|
|
17
|
+
USING_ALIAS: 'using-alias',
|
|
18
|
+
DEFINITION_NAME: 'definition',
|
|
19
|
+
NAMESPACE_STATEMENT: 'namespace-statement',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// eslint-disable-next-line no-unused-vars
|
|
23
|
+
class LspSemanticTokenEvent {
|
|
24
|
+
event; // 'reference' | 'definition',
|
|
25
|
+
semanticToken;
|
|
26
|
+
node;
|
|
27
|
+
hint; // TODO: Remove
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* All actions to report semantic tokens in a model.
|
|
32
|
+
*/
|
|
33
|
+
const artifactActions = {
|
|
34
|
+
__proto__: null,
|
|
35
|
+
|
|
36
|
+
// e.g. sources or services
|
|
37
|
+
artifacts: dictOf( artifactTokens ),
|
|
38
|
+
extensions: arrayOf( extensionTokens ),
|
|
39
|
+
namespace: namespaceTokens,
|
|
40
|
+
// e.g. via CSN input
|
|
41
|
+
vocabularies: dictOf( artifactTokens ),
|
|
42
|
+
definitions: dictOf( artifactTokens ),
|
|
43
|
+
|
|
44
|
+
extern: artifactTokens,
|
|
45
|
+
name: definitionNameTokens,
|
|
46
|
+
path: pathReferenceTokens,
|
|
47
|
+
|
|
48
|
+
type: artifactTokens,
|
|
49
|
+
target: artifactTokens,
|
|
50
|
+
targetAspect: artifactTokens,
|
|
51
|
+
targetElement: artifactTokens,
|
|
52
|
+
returns: returnsTokens,
|
|
53
|
+
items: artifactTokens,
|
|
54
|
+
elements: elementsTokens,
|
|
55
|
+
|
|
56
|
+
enum: dictOf( artifactTokens ),
|
|
57
|
+
foreignKeys: dictOf( artifactTokens ),
|
|
58
|
+
actions: dictOf( artifactTokens ),
|
|
59
|
+
params: dictOf( artifactTokens ),
|
|
60
|
+
mixin: dictOf( artifactTokens ),
|
|
61
|
+
excludingDict: dictOf( nameAsReference ),
|
|
62
|
+
|
|
63
|
+
// Don't crawl `$tableAliases`, as they are set multiple times in queries
|
|
64
|
+
// via different `$tableAliases`.
|
|
65
|
+
// $tableAliases: null,
|
|
66
|
+
|
|
67
|
+
// NOT $queries, as that doesn't cover UNIONs (e.g. `orderBy` vs `$orderBy`)
|
|
68
|
+
query: artifactTokens,
|
|
69
|
+
|
|
70
|
+
from: artifactTokens,
|
|
71
|
+
includes: arrayOf( artifactTokens ),
|
|
72
|
+
columns: arrayOf( artifactTokens ),
|
|
73
|
+
expand: arrayOf( artifactTokens ),
|
|
74
|
+
inline: arrayOf( artifactTokens ),
|
|
75
|
+
|
|
76
|
+
args: argsTokens,
|
|
77
|
+
on: artifactTokens,
|
|
78
|
+
default: artifactTokens,
|
|
79
|
+
value: artifactTokens,
|
|
80
|
+
sym: enumSymToken,
|
|
81
|
+
where: artifactTokens,
|
|
82
|
+
groupBy: artifactTokens,
|
|
83
|
+
orderBy: artifactTokens,
|
|
84
|
+
having: artifactTokens,
|
|
85
|
+
suffix: artifactTokens,
|
|
86
|
+
limit: artifactTokens,
|
|
87
|
+
rows: artifactTokens,
|
|
88
|
+
offset: artifactTokens,
|
|
89
|
+
|
|
90
|
+
'@': annotationTokens,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/** Returns a generator that applies the given function on all entries and yields the result. */
|
|
94
|
+
function dictOf( func ) {
|
|
95
|
+
return function* dictionary( dict ) {
|
|
96
|
+
for (const [ item ] of iterateGeneric({ dict }, 'dict'))
|
|
97
|
+
yield* func( item );
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Returns a generator that applies the given function on all entries and yields the result. */
|
|
102
|
+
function arrayOf( func ) {
|
|
103
|
+
return function* array( arr ) {
|
|
104
|
+
if (!Array.isArray(arr))
|
|
105
|
+
return;
|
|
106
|
+
for (const item of arr)
|
|
107
|
+
yield* func( item );
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Generator equivalent of iterateGeneric of forEachGeneric() */
|
|
112
|
+
function* iterateGeneric( obj, prop ) {
|
|
113
|
+
const dict = obj[prop];
|
|
114
|
+
if (!dict)
|
|
115
|
+
return;
|
|
116
|
+
|
|
117
|
+
for (const name in dict) {
|
|
118
|
+
obj = dict[name];
|
|
119
|
+
if (Array.isArray( obj )) {
|
|
120
|
+
for (const item of obj)
|
|
121
|
+
yield [ item, name, prop ]; // parser or source duplicates (e.g. USING vs definition)
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
yield [ obj, name, prop ];
|
|
125
|
+
if (Array.isArray( obj.$duplicates )) { // redefinitions
|
|
126
|
+
for (const dup of obj.$duplicates)
|
|
127
|
+
yield [ dup, name, prop ];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* A generator that yields all semantic tokens in an XSN model.
|
|
135
|
+
* Semantic tokens include identifiers (references/definitions) and the "returns" parameter.
|
|
136
|
+
*
|
|
137
|
+
* @param {XSN.Model} xsn
|
|
138
|
+
* @param {CSN.Options} options
|
|
139
|
+
* @returns {Generator<LspSemanticTokenEvent>}
|
|
140
|
+
*/
|
|
141
|
+
function* traverseSemanticTokens( xsn, options ) {
|
|
142
|
+
if (!xsn)
|
|
143
|
+
throw new CompilerAssertion('Expected valid XSN model for traverseSemanticTokens(…)');
|
|
144
|
+
if (!options)
|
|
145
|
+
throw new CompilerAssertion('Expected valid options for traverseSemanticTokens(…)');
|
|
146
|
+
|
|
147
|
+
if (xsn.sources)
|
|
148
|
+
yield* dictOf( artifactTokens )( xsn.sources );
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Report semantic tokens in artifacts, including definitions, elements, params, etc.
|
|
153
|
+
*
|
|
154
|
+
* @param {XSN.Artifact} art
|
|
155
|
+
* @returns {Generator<LspSemanticTokenEvent>}
|
|
156
|
+
*/
|
|
157
|
+
function* artifactTokens( art ) {
|
|
158
|
+
if (!art || art.builtin || art.$inferred)
|
|
159
|
+
return null;
|
|
160
|
+
|
|
161
|
+
if (Array.isArray( art )) {
|
|
162
|
+
for (const entry of art)
|
|
163
|
+
yield* artifactTokens( entry );
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (const prop in art) {
|
|
168
|
+
if (artifactActions[prop])
|
|
169
|
+
yield* artifactActions[prop](art[prop], art);
|
|
170
|
+
else if (prop.charAt(0) === '@')
|
|
171
|
+
yield* artifactActions['@'](art[prop], art);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* For an extension, yield all semantic tokens.
|
|
179
|
+
* We don't use `artifactTokens` for it, because extensions are a special case:
|
|
180
|
+
* - they have a name, but actually refer to some other artifact.
|
|
181
|
+
* - their artifacts such as elements may overlap with existing definitions, because
|
|
182
|
+
* extensions are applied; if they were applied, `_parent` does not point to the
|
|
183
|
+
* extension, which means we can't use it to skip them in `artifactTokens`.
|
|
184
|
+
* - we only need to handle `annotate` and `extend` kinds specifically:
|
|
185
|
+
* if an extension was not applied, pass it to `artifactTokens`;
|
|
186
|
+
* if an extension was applied, we only need to report its name (i.e. reference)
|
|
187
|
+
* and traverse over all artifacts
|
|
188
|
+
*
|
|
189
|
+
* @param {XSN.Extension} ext
|
|
190
|
+
* @returns {Generator<LspSemanticTokenEvent>}
|
|
191
|
+
*/
|
|
192
|
+
function* extensionTokens( ext ) {
|
|
193
|
+
if (ext.kind !== 'extend' && ext.kind !== 'annotate')
|
|
194
|
+
return null;
|
|
195
|
+
|
|
196
|
+
const wasApplied = ext.name._artifact && !ext.name._artifact.$inferred;
|
|
197
|
+
if (!wasApplied) {
|
|
198
|
+
yield* artifactTokens( ext );
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
yield* nameAsReference( ext );
|
|
203
|
+
|
|
204
|
+
// We need to traverse all dictionaries that could themselves contain
|
|
205
|
+
// extensions. Enum extensions or columns don't need to be traversed,
|
|
206
|
+
// for example, because there can't be inner extensions.
|
|
207
|
+
yield* dictOf( extensionTokens )( ext.params );
|
|
208
|
+
yield* dictOf( extensionTokens )( ext.actions );
|
|
209
|
+
yield* dictOf( extensionTokens )( ext.elements );
|
|
210
|
+
|
|
211
|
+
if (ext.returns)
|
|
212
|
+
yield* extensionTokens( ext.returns );
|
|
213
|
+
|
|
214
|
+
// Artifact extensions are always definitions, and can't have nested `extend`s,
|
|
215
|
+
// hence no need to traverse them with `extensionTokens`.
|
|
216
|
+
yield* dictOf( artifactTokens )( ext.artifacts );
|
|
217
|
+
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Report all semantic tokens in an annotation assignment.
|
|
223
|
+
*
|
|
224
|
+
* @param {XSN.Artifact} anno
|
|
225
|
+
* @returns {Generator<LspSemanticTokenEvent>}
|
|
226
|
+
*/
|
|
227
|
+
function* annotationTokens( anno ) {
|
|
228
|
+
// TODO: Also report annotation names
|
|
229
|
+
if (anno.kind === '$annotation')
|
|
230
|
+
yield* annotationValueTokens( anno );
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function* argsTokens( args, art ) {
|
|
234
|
+
if (Array.isArray(args)) {
|
|
235
|
+
// e.g. unnamed function arguments
|
|
236
|
+
yield* arrayOf( artifactTokens )( args );
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
// e.g. named arguments
|
|
240
|
+
for (const [ param ] of iterateGeneric( art, 'args' )) {
|
|
241
|
+
yield* nameAsReference( param );
|
|
242
|
+
yield* artifactTokens( param );
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function* enumSymToken( sym, expr ) {
|
|
248
|
+
yield {
|
|
249
|
+
event: 'reference',
|
|
250
|
+
semanticToken: expr.sym,
|
|
251
|
+
node: expr,
|
|
252
|
+
hint: undefined,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* A namespace is always considered a reference and not a definition.
|
|
258
|
+
*
|
|
259
|
+
* @param {XSN.Artifact} def
|
|
260
|
+
* @returns {Generator<LspSemanticTokenEvent>}
|
|
261
|
+
*/
|
|
262
|
+
function* namespaceTokens( def ) {
|
|
263
|
+
if (!def.name)
|
|
264
|
+
return null;
|
|
265
|
+
|
|
266
|
+
for (let i = 0; i < def.name.path.length; ++i) {
|
|
267
|
+
yield {
|
|
268
|
+
event: 'reference',
|
|
269
|
+
semanticToken: def.name.path[i],
|
|
270
|
+
node: def,
|
|
271
|
+
hint: (i === def.name.path.length - 1) ? HINTS.NAMESPACE_STATEMENT : null,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* An annotation value may contain expressions which we need to report.
|
|
280
|
+
*
|
|
281
|
+
* @param {object} anno
|
|
282
|
+
* @returns {Generator<LspSemanticTokenEvent>}
|
|
283
|
+
*/
|
|
284
|
+
function* annotationValueTokens( anno ) {
|
|
285
|
+
if (Array.isArray(anno)) {
|
|
286
|
+
for (const entry of anno)
|
|
287
|
+
yield* annotationValueTokens( entry );
|
|
288
|
+
}
|
|
289
|
+
else if (anno.$tokenTexts) {
|
|
290
|
+
yield* artifactTokens( anno );
|
|
291
|
+
}
|
|
292
|
+
else if (Array.isArray(anno.val)) {
|
|
293
|
+
yield* annotationValueTokens( anno.val );
|
|
294
|
+
}
|
|
295
|
+
else if (anno.struct) {
|
|
296
|
+
for (const [ struct ] of iterateGeneric( anno, 'struct' ))
|
|
297
|
+
yield* annotationValueTokens( struct );
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* A `returns` structure may contain sub-elements. But we report the `returns`
|
|
303
|
+
* token as well, as it is considered a token with semantic value.
|
|
304
|
+
*
|
|
305
|
+
* @param {XSN.Artifact} art
|
|
306
|
+
* @returns {Generator<LspSemanticTokenEvent>}
|
|
307
|
+
*/
|
|
308
|
+
function* returnsTokens( art ) {
|
|
309
|
+
if (art.kind === 'param') {
|
|
310
|
+
// report the `returns` parameter
|
|
311
|
+
yield {
|
|
312
|
+
event: 'definition',
|
|
313
|
+
semanticToken: art.name,
|
|
314
|
+
node: art,
|
|
315
|
+
hint: undefined,
|
|
316
|
+
};
|
|
317
|
+
yield* artifactTokens( art );
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Report elements if they should be traversed. They are not always traversed
|
|
323
|
+
* to avoid duplication due to `expand` and `columns` also being traversed.
|
|
324
|
+
*
|
|
325
|
+
* @param {Record<string, XSN.Artifact>} elements
|
|
326
|
+
* @param {XSN.Artifact} art
|
|
327
|
+
* @returns {Generator<LspSemanticTokenEvent>}
|
|
328
|
+
*/
|
|
329
|
+
function* elementsTokens( elements, art ) {
|
|
330
|
+
if (shouldTraverseElements( art ))
|
|
331
|
+
yield* dictOf( artifactTokens )( elements );
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Report all references in `ref`.
|
|
336
|
+
*
|
|
337
|
+
* @returns {Generator<LspSemanticTokenEvent>}
|
|
338
|
+
*/
|
|
339
|
+
function* pathReferenceTokens( path, ref, user = ref, hint = null ) {
|
|
340
|
+
if (!path)
|
|
341
|
+
return null;
|
|
342
|
+
|
|
343
|
+
// don't report cds.Association/cds.Composition
|
|
344
|
+
// TODO: Or report the `Association` keyword, similar to `returns`?
|
|
345
|
+
if (path.length === 1 && ref._artifact?.category === 'relation')
|
|
346
|
+
return null;
|
|
347
|
+
|
|
348
|
+
yield* artifactTokens( path );
|
|
349
|
+
|
|
350
|
+
// parser prepends a fake `type of` segment, which we need to skip
|
|
351
|
+
const root = ref.scope === 'typeOf' ? 1 : 0;
|
|
352
|
+
for (let i = root; i < path.length; ++i) {
|
|
353
|
+
if (!path[i].$inferred) { // e.g. `id` when expanded from `$user`
|
|
354
|
+
yield {
|
|
355
|
+
event: 'reference',
|
|
356
|
+
semanticToken: path[i],
|
|
357
|
+
node: user,
|
|
358
|
+
hint,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Some XSN nodes such as entries in `excludingDict` or named arguments are references
|
|
368
|
+
* but don't have a `path` property, only a `name` property. Report such names
|
|
369
|
+
* as references.
|
|
370
|
+
*
|
|
371
|
+
* @returns {Generator<LspSemanticTokenEvent>}
|
|
372
|
+
*/
|
|
373
|
+
function* nameAsReference( ref, hint = null ) {
|
|
374
|
+
if (!ref.name || ref.name.$inferred)
|
|
375
|
+
return null;
|
|
376
|
+
|
|
377
|
+
if (ref.name.path) {
|
|
378
|
+
yield* pathReferenceTokens( ref.name.path, ref.name, ref, hint );
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
yield {
|
|
382
|
+
event: 'reference',
|
|
383
|
+
semanticToken: ref.name,
|
|
384
|
+
node: ref,
|
|
385
|
+
hint,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Traverse the name of a definition, and report N-1 path steps as references
|
|
393
|
+
* and of course the definition itself.
|
|
394
|
+
*
|
|
395
|
+
* @returns {Generator<LspSemanticTokenEvent>}
|
|
396
|
+
*/
|
|
397
|
+
function* definitionNameTokens( name, art ) {
|
|
398
|
+
if (!art.kind)
|
|
399
|
+
return null; // e.g. parameter references
|
|
400
|
+
if (art.kind === '$annotation')
|
|
401
|
+
return null; // annotation name, e.g. in `@anno: (elem)`
|
|
402
|
+
|
|
403
|
+
if ((name.$inferred && name.$inferred !== 'as') ||
|
|
404
|
+
art.kind === 'select' || art.kind === '$join') {
|
|
405
|
+
// Internal names such as numbers for SELECTs or the `$internal` names must
|
|
406
|
+
// not be reported.
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (art.kind === 'extend' || art.kind === 'annotate') {
|
|
411
|
+
yield* nameAsReference( art );
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Report references in a name (N-1 path steps).
|
|
416
|
+
for (let i = 0; i < name.path?.length - 1; ++i) {
|
|
417
|
+
yield {
|
|
418
|
+
event: 'reference',
|
|
419
|
+
semanticToken: name.path[i],
|
|
420
|
+
node: art,
|
|
421
|
+
hint: HINTS.DEFINITION_NAME,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const hint = art.kind === 'using' ? HINTS.USING_ALIAS : null;
|
|
426
|
+
|
|
427
|
+
if (name.path) {
|
|
428
|
+
// Only take the last path step; all others are considered references.
|
|
429
|
+
const implicitName = name.path[name.path.length - 1];
|
|
430
|
+
yield {
|
|
431
|
+
event: 'definition',
|
|
432
|
+
semanticToken: implicitName,
|
|
433
|
+
node: art,
|
|
434
|
+
hint,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
else if (name.id) {
|
|
438
|
+
// Not all names have a path; some (e.g. parameters) only have an ID.
|
|
439
|
+
yield {
|
|
440
|
+
event: 'definition',
|
|
441
|
+
semanticToken: name,
|
|
442
|
+
node: art,
|
|
443
|
+
hint,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Returns true if `elements` of the given `art` should be traversed.
|
|
452
|
+
* Elements are _not_ traversed, e.g. for `expand`, to avoid duplicates.
|
|
453
|
+
*
|
|
454
|
+
* @returns {boolean}
|
|
455
|
+
*/
|
|
456
|
+
function shouldTraverseElements( art ) {
|
|
457
|
+
return (
|
|
458
|
+
// $expand: 'origin' -> normal expansion
|
|
459
|
+
// $expand: 'annotate' -> additional annotation (needs to traverse annotation expressions)
|
|
460
|
+
art.$expand !== 'origin' && !art.elements[$inferred] && (
|
|
461
|
+
// sub-elements are always traversed except for `expand`, which is handled on its own.
|
|
462
|
+
art.kind === 'element' && !art.expand ||
|
|
463
|
+
// all non-query elements are traversed; because `_main` on bound actions may point
|
|
464
|
+
// to a query, we handle parameters explicitly.
|
|
465
|
+
art.kind === 'param' || !(art._main || art).$queries
|
|
466
|
+
)
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Given a LspSemanticTokenEvent, returns a generator that yields the referenced
|
|
472
|
+
* object and its origin's until the deepest entry is found.
|
|
473
|
+
*
|
|
474
|
+
* @param obj
|
|
475
|
+
* @returns {Generator<*, void, *>}
|
|
476
|
+
*/
|
|
477
|
+
function* getSemanticTokenOrigin( obj ) {
|
|
478
|
+
let ref = obj.semanticToken;
|
|
479
|
+
if (obj.event === 'definition') {
|
|
480
|
+
ref = obj.node;
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
if (!ref?._artifact)
|
|
484
|
+
return; // unknown -> abort
|
|
485
|
+
// take first artifact for duplicates (best effort)
|
|
486
|
+
ref = Array.isArray(ref._artifact) ? ref._artifact[0] : ref._artifact;
|
|
487
|
+
yield ref;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (!ref._effectiveType)
|
|
491
|
+
return; // abort for unresolved references and cyclic ones
|
|
492
|
+
|
|
493
|
+
while (ref._origin) {
|
|
494
|
+
yield ref._origin;
|
|
495
|
+
ref = ref._origin;
|
|
496
|
+
if (!ref || typeof ref === 'string')
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
module.exports = {
|
|
502
|
+
traverseSemanticTokens,
|
|
503
|
+
getSemanticTokenOrigin,
|
|
504
|
+
};
|
package/lib/compiler/populate.js
CHANGED
|
@@ -83,6 +83,7 @@ function populate( model ) {
|
|
|
83
83
|
effectiveType,
|
|
84
84
|
getOrigin,
|
|
85
85
|
getInheritedProp,
|
|
86
|
+
mergeSpecifiedForeignKeys,
|
|
86
87
|
} );
|
|
87
88
|
// let depth = 100;
|
|
88
89
|
|
|
@@ -115,7 +116,10 @@ function populate( model ) {
|
|
|
115
116
|
|
|
116
117
|
/** Make sure that effectiveType() is called on all members and items */
|
|
117
118
|
function traverseElementEnvironments( art ) {
|
|
118
|
-
//
|
|
119
|
+
// We leave out foreign keys (as they are traversed via forEachMember).
|
|
120
|
+
// Keys are handled in tweak-assocs.js
|
|
121
|
+
if (art.kind === 'key')
|
|
122
|
+
return;
|
|
119
123
|
let type = effectiveType( art );
|
|
120
124
|
while (type?.items)
|
|
121
125
|
type = effectiveType( type.items );
|
|
@@ -529,9 +533,6 @@ function populate( model ) {
|
|
|
529
533
|
if (!selem) {
|
|
530
534
|
info( 'query-missing-element', [ ielem.name.location, art ], {
|
|
531
535
|
'#': ielem.kind === 'enum' ? 'enum' : 'std', id,
|
|
532
|
-
}, {
|
|
533
|
-
std: 'Element $(ID) is missing in specified elements',
|
|
534
|
-
enum: 'Enum $(ID) is missing in specified enum values',
|
|
535
536
|
} );
|
|
536
537
|
}
|
|
537
538
|
else {
|
|
@@ -560,6 +561,8 @@ function populate( model ) {
|
|
|
560
561
|
setLink( ielem, 'elements$', selem.elements );
|
|
561
562
|
if (selem.enum)
|
|
562
563
|
setLink( ielem, 'enum$', selem.enum );
|
|
564
|
+
if (selem.foreignKeys)
|
|
565
|
+
setLink( ielem, 'foreignKeys$', selem.foreignKeys );
|
|
563
566
|
}
|
|
564
567
|
}
|
|
565
568
|
|
|
@@ -574,8 +577,57 @@ function populate( model ) {
|
|
|
574
577
|
specifiedElement.$isSpecifiedElement = true;
|
|
575
578
|
if (!specifiedElement.$replacement) {
|
|
576
579
|
const loc = [ specifiedElement.name.location, specifiedElement ];
|
|
577
|
-
error( 'query-unspecified-element', loc, { id }
|
|
578
|
-
|
|
580
|
+
error( 'query-unspecified-element', loc, { id } );
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Merge _specified_ foreign keys with _inferred_ foreign keys in the given view/element,
|
|
587
|
+
* where specified elements can appear through CSN.
|
|
588
|
+
*
|
|
589
|
+
* We only copy annotations.
|
|
590
|
+
*
|
|
591
|
+
* This is important to ensure re-compilability.
|
|
592
|
+
*
|
|
593
|
+
* TODO: make this part of a revamped on-demand 'extend' functionality.
|
|
594
|
+
*
|
|
595
|
+
* @param art
|
|
596
|
+
*/
|
|
597
|
+
function mergeSpecifiedForeignKeys( art ) {
|
|
598
|
+
if (!art.foreignKeys)
|
|
599
|
+
return; // TODO: Warn if there are no foreign keys?
|
|
600
|
+
|
|
601
|
+
let wasAnnotated = false;
|
|
602
|
+
|
|
603
|
+
for (const id in art.foreignKeys) {
|
|
604
|
+
const ielem = art.foreignKeys[id]; // inferred element
|
|
605
|
+
const selem = art.foreignKeys$[id]; // specified element
|
|
606
|
+
if (!selem) {
|
|
607
|
+
info( 'query-missing-element', [ ielem.name.location, art ], { '#': 'foreignKeys', id } );
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
for (const prop in selem) {
|
|
611
|
+
// just annotation assignments and doc comments for foreign keys
|
|
612
|
+
if (prop.charAt(0) === '@' || prop === 'doc') {
|
|
613
|
+
ielem[prop] = selem[prop];
|
|
614
|
+
// required for gensrc mode of to-csn.js, otherwise the annotation
|
|
615
|
+
// may be lost during recompilation.
|
|
616
|
+
ielem[prop].$priority = 'annotate';
|
|
617
|
+
wasAnnotated = true;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
selem.$replacement = true;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (wasAnnotated)
|
|
624
|
+
setExpandStatusAnnotate( art, 'annotate' );
|
|
625
|
+
|
|
626
|
+
for (const id in art.foreignKeys$) {
|
|
627
|
+
const specifiedElement = art.foreignKeys$[id];
|
|
628
|
+
if (!specifiedElement.$replacement) {
|
|
629
|
+
const loc = [ specifiedElement.name.location, specifiedElement ];
|
|
630
|
+
error( 'query-unspecified-element', loc, { '#': 'foreignKeys', id } );
|
|
579
631
|
}
|
|
580
632
|
}
|
|
581
633
|
}
|
|
@@ -816,15 +868,10 @@ function populate( model ) {
|
|
|
816
868
|
const excludingDict = (colParent || query).excludingDict || Object.create( null );
|
|
817
869
|
|
|
818
870
|
const envParent = wildcard._pathHead; // TODO: rename _pathHead to _columnParent
|
|
819
|
-
// console.log('S1:',location.line,location.col,
|
|
820
|
-
// envParent&&!!envParent._origin&&envParent._origin.name)
|
|
821
871
|
const env = wildcardColumnEnv( wildcard, query );
|
|
822
872
|
if (!env)
|
|
823
873
|
return;
|
|
824
874
|
|
|
825
|
-
// if (envParent) console.log('S2:',location.line,location.col,
|
|
826
|
-
// envParent?.name,envParent?._origin?.name,
|
|
827
|
-
// Object.keys(env),Object.keys(elements))
|
|
828
875
|
for (const name in env) {
|
|
829
876
|
const navElem = env[name];
|
|
830
877
|
// TODO: remove all access to masked (use 'grep')
|
|
@@ -1069,10 +1116,10 @@ function populate( model ) {
|
|
|
1069
1116
|
else {
|
|
1070
1117
|
// referred (and probably inferred) assoc (without a user-provided target at that place)
|
|
1071
1118
|
// HINT: consider bin/cdsv2m.js when changing the following message text
|
|
1072
|
-
// No grouped and sub messages yet (TODO
|
|
1119
|
+
// No grouped and sub messages yet (TODO v6): mention at all target places with all assocs
|
|
1073
1120
|
const withAnno = annotationVal( exposed[0]['@cds.redirection.target'] );
|
|
1074
1121
|
for (const proj of exposed) {
|
|
1075
|
-
// TODO: def-ambiguous-target (just
|
|
1122
|
+
// TODO: def-ambiguous-target (just v6, as the current is infamous and used in options),
|
|
1076
1123
|
message( 'redirected-implicitly-ambiguous',
|
|
1077
1124
|
[ weakLocation( proj.name.location ), proj ],
|
|
1078
1125
|
{
|
|
@@ -13,7 +13,6 @@ const {
|
|
|
13
13
|
forEachMember,
|
|
14
14
|
forEachGeneric,
|
|
15
15
|
isDeprecatedEnabled,
|
|
16
|
-
isBetaEnabled,
|
|
17
16
|
} = require( '../base/model');
|
|
18
17
|
const {
|
|
19
18
|
setLink,
|
|
@@ -50,13 +49,17 @@ function propagate( model ) {
|
|
|
50
49
|
targetAspect,
|
|
51
50
|
cardinality: notWithExpand,
|
|
52
51
|
on: notWithExpand,
|
|
53
|
-
|
|
52
|
+
// "expensive" includes "notWithExpand"
|
|
53
|
+
// required for places where we don't handle associations, such as in parameters;
|
|
54
|
+
// otherwise already expanded and rewritten.
|
|
55
|
+
foreignKeys: expensive,
|
|
54
56
|
items,
|
|
57
|
+
// required for propagation in targetAspect; otherwise already expanded
|
|
55
58
|
elements: expensive,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
// already expanded if necessary
|
|
60
|
+
// enum: expensive,
|
|
61
|
+
// params: expensive, // actually only with parent action
|
|
62
|
+
// returns,
|
|
60
63
|
$enclosed: annotation,
|
|
61
64
|
};
|
|
62
65
|
const ruleToFunction = {
|
|
@@ -75,8 +78,7 @@ function propagate( model ) {
|
|
|
75
78
|
const { warning, throwWithError } = model.$messageFunctions;
|
|
76
79
|
|
|
77
80
|
forEachDefinition( model, run );
|
|
78
|
-
|
|
79
|
-
forEachGeneric( model, 'vocabularies', run );
|
|
81
|
+
forEachGeneric( model, 'vocabularies', run );
|
|
80
82
|
|
|
81
83
|
// TODO: move 'virtual' handling/checks to resolver if
|
|
82
84
|
// 'deprecated.oldVirtualNotNullPropagation' is gone
|