@sap/cds-compiler 3.0.2 → 3.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 +65 -0
- package/bin/.eslintrc.json +2 -1
- package/bin/cdsc.js +19 -0
- package/doc/API.md +11 -0
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +24 -2
- package/doc/CHANGELOG_DEPRECATED.md +21 -1
- package/lib/api/main.js +7 -7
- package/lib/api/options.js +2 -3
- package/lib/base/message-registry.js +17 -5
- package/lib/base/messages.js +18 -39
- package/lib/base/model.js +2 -0
- package/lib/checks/actionsFunctions.js +8 -7
- package/lib/checks/selectItems.js +96 -14
- package/lib/checks/types.js +5 -8
- package/lib/checks/validator.js +1 -2
- package/lib/compiler/assert-consistency.js +64 -12
- package/lib/compiler/base.js +6 -4
- package/lib/compiler/builtins.js +58 -8
- package/lib/compiler/checks.js +1 -1
- package/lib/compiler/define.js +25 -22
- package/lib/compiler/extend.js +16 -10
- package/lib/compiler/finalize-parse-cdl.js +5 -9
- package/lib/compiler/index.js +2 -0
- package/lib/compiler/populate.js +34 -31
- package/lib/compiler/propagator.js +11 -6
- package/lib/compiler/resolve.js +14 -15
- package/lib/compiler/shared.js +53 -26
- package/lib/compiler/tweak-assocs.js +5 -11
- package/lib/compiler/utils.js +13 -4
- package/lib/edm/annotations/preprocessAnnotations.js +8 -4
- package/lib/edm/csn2edm.js +3 -3
- package/lib/edm/edm.js +9 -1
- package/lib/edm/edmAnnoPreprocessor.js +349 -0
- package/lib/edm/edmInboundChecks.js +85 -0
- package/lib/edm/edmPreprocessor.js +295 -638
- package/lib/edm/edmUtils.js +85 -5
- package/lib/gen/Dictionary.json +29 -9
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -2
- package/lib/gen/languageLexer.js +3 -0
- package/lib/gen/languageParser.js +4344 -4530
- package/lib/inspect/.eslintrc.json +4 -0
- package/lib/inspect/index.js +14 -0
- package/lib/inspect/inspectModelStatistics.js +81 -0
- package/lib/inspect/inspectPropagation.js +189 -0
- package/lib/inspect/inspectUtils.js +44 -0
- package/lib/json/from-csn.js +3 -2
- package/lib/json/to-csn.js +8 -6
- package/lib/language/genericAntlrParser.js +121 -63
- package/lib/language/language.g4 +19 -57
- package/lib/main.d.ts +1 -0
- package/lib/model/api.js +1 -1
- package/lib/model/csnRefs.js +55 -29
- package/lib/model/csnUtils.js +11 -7
- package/lib/model/revealInternalProperties.js +2 -3
- package/lib/modelCompare/compare.js +3 -0
- package/lib/optionProcessor.js +27 -0
- package/lib/render/toCdl.js +57 -32
- package/lib/render/toSql.js +24 -8
- package/lib/render/utils/common.js +3 -4
- package/lib/transform/db/associations.js +43 -35
- package/lib/transform/db/cdsPersistence.js +0 -1
- package/lib/transform/db/flattening.js +3 -4
- package/lib/transform/db/transformExists.js +7 -5
- package/lib/transform/draft/db.js +1 -1
- package/lib/transform/forHanaNew.js +11 -2
- package/lib/transform/forOdataNew.js +1 -1
- package/lib/transform/odata/typesExposure.js +14 -5
- package/lib/utils/moduleResolve.js +0 -1
- package/package.json +2 -2
- package/lib/checks/unknownMagic.js +0 -41
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// cds-compiler Inspect Module
|
|
2
|
+
// Used by `cdsc inspect` to gather details about the model such as statistics, etc.
|
|
3
|
+
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const { inspectModelStatistics } = require('./inspectModelStatistics');
|
|
7
|
+
const { inspectPropagation } = require('./inspectPropagation');
|
|
8
|
+
const { stringRefToPath } = require('./inspectUtils');
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
inspectModelStatistics,
|
|
12
|
+
inspectPropagation,
|
|
13
|
+
stringRefToPath,
|
|
14
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { forEach } = require('../utils/objectUtils');
|
|
4
|
+
const { term } = require('../utils/term');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Return a string representation of the inspected results.
|
|
8
|
+
*
|
|
9
|
+
* @param {XSN.Model} xsn
|
|
10
|
+
* @param {CSN.Options} options
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
function inspectModelStatistics(xsn, options) {
|
|
14
|
+
let result = '';
|
|
15
|
+
|
|
16
|
+
// Default color mode is 'auto'
|
|
17
|
+
const color = term(options.color || 'auto');
|
|
18
|
+
|
|
19
|
+
const defCount = countDefinitionKinds(xsn);
|
|
20
|
+
const sources = {
|
|
21
|
+
cdl: Object.keys(xsn.sources).filter(name => xsn.sources[name].$frontend === 'cdl').length,
|
|
22
|
+
csn: Object.keys(xsn.sources).filter(name => xsn.sources[name].$frontend === 'csn').length,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
result += `cds-compiler model statistics:
|
|
26
|
+
|
|
27
|
+
${ color.underline('files') }: ${ Object.keys(xsn.sources).length }
|
|
28
|
+
cdl sources: ${ sources.cdl }
|
|
29
|
+
csn sources: ${ sources.csn }
|
|
30
|
+
|
|
31
|
+
${ color.underline('definitions') }: ${ defCount.definitions }
|
|
32
|
+
entities: ${ defCount.entity }
|
|
33
|
+
queries: ${ defCount.view }
|
|
34
|
+
aspects: ${ defCount.aspect }
|
|
35
|
+
events: ${ defCount.event }
|
|
36
|
+
types: ${ defCount.type }
|
|
37
|
+
services: ${ defCount.service }
|
|
38
|
+
context: ${ defCount.context }
|
|
39
|
+
actions: ${ defCount.action }
|
|
40
|
+
functions: ${ defCount.function }
|
|
41
|
+
namespaces: ${ defCount.namespace } (explicitly in CDL)
|
|
42
|
+
|
|
43
|
+
${ color.underline('vocabularies') }: ${ Object.keys(xsn.vocabularies || {}).length }
|
|
44
|
+
`;
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function countDefinitionKinds(xsn) {
|
|
49
|
+
const result = {
|
|
50
|
+
definitions: 0,
|
|
51
|
+
entity: 0,
|
|
52
|
+
action: 0,
|
|
53
|
+
function: 0,
|
|
54
|
+
aspect: 0,
|
|
55
|
+
event: 0,
|
|
56
|
+
type: 0,
|
|
57
|
+
service: 0,
|
|
58
|
+
context: 0,
|
|
59
|
+
namespace: 0,
|
|
60
|
+
// non-kind
|
|
61
|
+
view: 0,
|
|
62
|
+
};
|
|
63
|
+
forEach(xsn.definitions || {}, (name, def) => {
|
|
64
|
+
if (def.builtin)
|
|
65
|
+
return;
|
|
66
|
+
++result.definitions;
|
|
67
|
+
|
|
68
|
+
if (def.query || def.projection)
|
|
69
|
+
++result.view;
|
|
70
|
+
else if (result[def.kind] !== undefined)
|
|
71
|
+
++result[def.kind];
|
|
72
|
+
else
|
|
73
|
+
throw new Error(`Unhandled kind: ${ def.kind } for ${ name }`);
|
|
74
|
+
});
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
inspectModelStatistics,
|
|
81
|
+
};
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createMessageFunctions } = require('../base/messages');
|
|
4
|
+
const { locationString } = require('../base/location');
|
|
5
|
+
const { findArtifact, stringRefToPath } = require('./inspectUtils');
|
|
6
|
+
const { term } = require('../utils/term');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {XSN.Model} xsn
|
|
10
|
+
* @param {CSN.Options} options
|
|
11
|
+
* @param {string} artifactName
|
|
12
|
+
* @returns {string|null}
|
|
13
|
+
*/
|
|
14
|
+
function inspectPropagation(xsn, options, artifactName) {
|
|
15
|
+
const { error } = createMessageFunctions(options, 'inspect', xsn);
|
|
16
|
+
const result = [];
|
|
17
|
+
|
|
18
|
+
// Default color mode is 'auto'
|
|
19
|
+
const color = term(options.color || 'auto');
|
|
20
|
+
|
|
21
|
+
const path = stringRefToPath(artifactName);
|
|
22
|
+
if (!path) {
|
|
23
|
+
error(null, null, { name: artifactName },
|
|
24
|
+
'Artifact $(NAME) is not a valid path; expected format `<def>[:element]`');
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const artifactXsn = findArtifact(xsn, path);
|
|
29
|
+
|
|
30
|
+
if (!artifactXsn) {
|
|
31
|
+
error(null, null, { name: artifactName },
|
|
32
|
+
// eslint-disable-next-line max-len
|
|
33
|
+
'Artifact $(NAME) not found, only top-level artifacts and their elements are supported for now');
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
result.push(color.underline('analyzing propagation for artifact:'));
|
|
37
|
+
result.push(` name: ${ artifactXsn.name.id }`);
|
|
38
|
+
result.push(` kind: ${ artifactXsn.kind }`);
|
|
39
|
+
|
|
40
|
+
if (artifactXsn.$inferred)
|
|
41
|
+
result.push(` inferred: ${ artifactXsn.$inferred }`);
|
|
42
|
+
|
|
43
|
+
result.push('');
|
|
44
|
+
result.push(` ${ color.underline('annotation propagation:') }`);
|
|
45
|
+
result.push(..._indent(_inspectAnnotations(artifactXsn)));
|
|
46
|
+
|
|
47
|
+
result.push('');
|
|
48
|
+
result.push(` ${ color.underline('element propagation:') }`);
|
|
49
|
+
result.push(..._indent(_inspectElements(artifactXsn)));
|
|
50
|
+
|
|
51
|
+
return result.join('\n');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
@param {string[]} lines
|
|
56
|
+
@param {string} indent
|
|
57
|
+
* @returns {string[]}
|
|
58
|
+
*/
|
|
59
|
+
function _indent(lines, indent = ' ') {
|
|
60
|
+
return lines.map(str => `${ indent }${ str }`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {XSN.Artifact} artifactXsn
|
|
65
|
+
* @returns {string[]}
|
|
66
|
+
* @private
|
|
67
|
+
*/
|
|
68
|
+
function _inspectAnnotations(artifactXsn) {
|
|
69
|
+
const result = [];
|
|
70
|
+
const annos = Object.keys(artifactXsn).filter(str => str.startsWith('@')).sort();
|
|
71
|
+
|
|
72
|
+
if (annos.length === 0)
|
|
73
|
+
return [ 'no annotations' ];
|
|
74
|
+
|
|
75
|
+
let maxAnnoLength = 30; // chosen arbitrarily, hopefully average
|
|
76
|
+
for (const anno of annos) {
|
|
77
|
+
const annoXsn = artifactXsn[anno];
|
|
78
|
+
const loc = locationString(annoXsn.name.location);
|
|
79
|
+
let origin;
|
|
80
|
+
switch (annoXsn.$priority) {
|
|
81
|
+
case false:
|
|
82
|
+
if (annoXsn.$inferred)
|
|
83
|
+
origin = 'propagation';
|
|
84
|
+
else
|
|
85
|
+
origin = 'direct';
|
|
86
|
+
break;
|
|
87
|
+
|
|
88
|
+
case 'extend':
|
|
89
|
+
case 'annotate':
|
|
90
|
+
origin = annoXsn.$priority;
|
|
91
|
+
break;
|
|
92
|
+
|
|
93
|
+
case undefined:
|
|
94
|
+
if (annoXsn.$inferred === '$generated') {
|
|
95
|
+
origin = 'generated';
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
// fallthrough
|
|
99
|
+
default:
|
|
100
|
+
throw new Error(`inspect anno: Unhandled Case: ${ annoXsn.$priority }`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
maxAnnoLength = Math.max(maxAnnoLength, anno.length);
|
|
104
|
+
|
|
105
|
+
// origin: assume max length 11 of 'propagation'
|
|
106
|
+
// anno: use max length of all annotations till now
|
|
107
|
+
result.push([ origin.padStart(11), anno.padEnd(maxAnnoLength), loc ].join(' | '));
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {XSN.Artifact} artifactXsn
|
|
114
|
+
* @returns {string[]}
|
|
115
|
+
* @private
|
|
116
|
+
*/
|
|
117
|
+
function _inspectElements(artifactXsn) {
|
|
118
|
+
if (!artifactXsn.elements)
|
|
119
|
+
return [ 'does not have elements' ];
|
|
120
|
+
|
|
121
|
+
const result = [];
|
|
122
|
+
const elements = Object.keys(artifactXsn.elements);
|
|
123
|
+
|
|
124
|
+
let maxElemLength = 12;
|
|
125
|
+
|
|
126
|
+
const inferredNiceOutput = {
|
|
127
|
+
'*': 'wildcard',
|
|
128
|
+
'expand-element': 'expanded',
|
|
129
|
+
'expand-param': 'expanded',
|
|
130
|
+
'aspect-composition': 'composition',
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
for (const element of elements) {
|
|
134
|
+
const elementXsn = artifactXsn.elements[element];
|
|
135
|
+
const loc = locationString(_origin(elementXsn).name.location);
|
|
136
|
+
let origin;
|
|
137
|
+
|
|
138
|
+
if (elementXsn.$inferred) {
|
|
139
|
+
// Use nice(r) output for known $inferred
|
|
140
|
+
if (inferredNiceOutput[elementXsn.$inferred])
|
|
141
|
+
origin = inferredNiceOutput[elementXsn.$inferred];
|
|
142
|
+
else
|
|
143
|
+
origin = elementXsn.$inferred;
|
|
144
|
+
}
|
|
145
|
+
else if (!isContainedInParentLocation(elementXsn, artifactXsn)) {
|
|
146
|
+
// just a heuristic
|
|
147
|
+
origin = 'extend';
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
origin = 'direct';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
maxElemLength = Math.max(maxElemLength, element.length);
|
|
154
|
+
|
|
155
|
+
// origin: assume max length 11 of 'composition'
|
|
156
|
+
// element: assume average length of 30, chosen randomly
|
|
157
|
+
result.push([ origin.padStart(11), element.padEnd(maxElemLength), loc ].join(' | '));
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function _origin(elementXsn) {
|
|
163
|
+
while (elementXsn._origin)
|
|
164
|
+
elementXsn = elementXsn._origin;
|
|
165
|
+
return elementXsn;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Returns true if `art` is contained in `parent` according to its location.
|
|
170
|
+
*
|
|
171
|
+
* @param art
|
|
172
|
+
* @param parent
|
|
173
|
+
* @returns {boolean}
|
|
174
|
+
*/
|
|
175
|
+
function isContainedInParentLocation(art, parent) {
|
|
176
|
+
const artLoc = art.location;
|
|
177
|
+
const parentLoc = parent.location;
|
|
178
|
+
if (artLoc.file !== parentLoc.file)
|
|
179
|
+
return false;
|
|
180
|
+
if (artLoc.line < parentLoc.line || artLoc.line > parentLoc.endLine)
|
|
181
|
+
return false;
|
|
182
|
+
// Good enough for now
|
|
183
|
+
// TODO: Check columns
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
module.exports = {
|
|
188
|
+
inspectPropagation,
|
|
189
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reference (string) to path (array) that can be used to identify an artifact
|
|
5
|
+
* @param str
|
|
6
|
+
* @returns {*[]|*}
|
|
7
|
+
*/
|
|
8
|
+
function stringRefToPath(str) {
|
|
9
|
+
// e.g. `ns.service.E:sub.elem.structured`
|
|
10
|
+
const path = str.split(':');
|
|
11
|
+
if (path.length === 1)
|
|
12
|
+
return path;
|
|
13
|
+
if (path.length > 2)
|
|
14
|
+
return null;
|
|
15
|
+
return [ path[0], ...path[1].split('.') ];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {XSN.Model} xsn
|
|
20
|
+
* @param {string} path
|
|
21
|
+
* @private
|
|
22
|
+
*/
|
|
23
|
+
function findArtifact(xsn, path) {
|
|
24
|
+
const segments = [ ...path ];
|
|
25
|
+
const topLevelName = segments[0];
|
|
26
|
+
let art = (xsn.definitions && xsn.definitions[topLevelName]) ||
|
|
27
|
+
(xsn.vocabularies && xsn.vocabularies[topLevelName]);
|
|
28
|
+
if (!art)
|
|
29
|
+
return null;
|
|
30
|
+
segments.shift();
|
|
31
|
+
if (segments.length === 0)
|
|
32
|
+
return art;
|
|
33
|
+
while (segments.length && art) {
|
|
34
|
+
const segment = segments.shift();
|
|
35
|
+
art = (art.items?.elements || art.elements)?.[segment];
|
|
36
|
+
}
|
|
37
|
+
return art || null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
stringRefToPath,
|
|
43
|
+
findArtifact,
|
|
44
|
+
};
|
package/lib/json/from-csn.js
CHANGED
|
@@ -103,6 +103,7 @@ const ourpropsRegex = /^[_$]?[a-zA-Z]+[0-9]*$/;
|
|
|
103
103
|
const typeProperties = [
|
|
104
104
|
// do not include CSN v0.1.0 properties here:
|
|
105
105
|
'target', 'elements', 'enum', 'items',
|
|
106
|
+
'cardinality', // for association publishing in views
|
|
106
107
|
'type', 'length', 'precision', 'scale', 'srid', 'localized', 'notNull',
|
|
107
108
|
'keys', 'on', // only with 'target'
|
|
108
109
|
];
|
|
@@ -228,7 +229,7 @@ const schema = compileSchema( {
|
|
|
228
229
|
dictionaryOf: definition,
|
|
229
230
|
defaultKind: 'action',
|
|
230
231
|
validKinds: [ 'action', 'function' ],
|
|
231
|
-
inKind: [ 'entity', 'annotate', 'extend' ],
|
|
232
|
+
inKind: [ 'entity', 'aspect', 'annotate', 'extend' ],
|
|
232
233
|
},
|
|
233
234
|
params: {
|
|
234
235
|
dictionaryOf: definition,
|
|
@@ -1287,7 +1288,7 @@ function literal( lit, spec, xsn, csn ) {
|
|
|
1287
1288
|
return lit;
|
|
1288
1289
|
if (typeof lit === 'string' && quotedLiteralPatterns[lit]?.json_type === type) {
|
|
1289
1290
|
const p = quotedLiteralPatterns[lit];
|
|
1290
|
-
if (p &&
|
|
1291
|
+
if (p && p.test_fn && !p.test_fn(csn.val))
|
|
1291
1292
|
warning( 'syntax-invalid-literal', location(), { '#': p.test_variant } );
|
|
1292
1293
|
return lit;
|
|
1293
1294
|
}
|
package/lib/json/to-csn.js
CHANGED
|
@@ -127,9 +127,6 @@ const transformers = {
|
|
|
127
127
|
// location is not renamed to $location as the name is well established in
|
|
128
128
|
// XSN and too many places (also outside the compiler) had to be adapted
|
|
129
129
|
location, // non-enumerable $location in CSN
|
|
130
|
-
$a2j: (e, csn) => { // on artifact level
|
|
131
|
-
Object.assign( csn, e );
|
|
132
|
-
},
|
|
133
130
|
$extra: (e, csn) => {
|
|
134
131
|
Object.assign( csn, e );
|
|
135
132
|
},
|
|
@@ -185,9 +182,11 @@ const propertyOrder = (function orderPositions() {
|
|
|
185
182
|
}());
|
|
186
183
|
|
|
187
184
|
// sync with definition in from-csn.js:
|
|
185
|
+
// Note: Order here is also the property order in CSN.
|
|
188
186
|
const typeProperties = [
|
|
189
|
-
'target', 'elements', 'enum', 'items',
|
|
190
|
-
'
|
|
187
|
+
'target', 'elements', 'enum', 'items',
|
|
188
|
+
'cardinality', // for association publishing in views
|
|
189
|
+
'type', 'length', 'precision', 'scale', 'srid', 'localized', // TODO: notNull?
|
|
191
190
|
'foreignKeys', 'on', // for explicit ON/keys with REDIRECTED
|
|
192
191
|
];
|
|
193
192
|
|
|
@@ -1276,8 +1275,11 @@ function expression( node, dollarExtra ) {
|
|
|
1276
1275
|
return { ref: [ node.param.val ], param: true }; // CDL rule for runtimes
|
|
1277
1276
|
}
|
|
1278
1277
|
if (node.path) {
|
|
1278
|
+
const ref = node.path.map( pathItem );
|
|
1279
|
+
if (node.path.$prefix)
|
|
1280
|
+
ref.unshift( node.path.$prefix );
|
|
1279
1281
|
// we would need to consider node.global here if we introduce that
|
|
1280
|
-
return extra( { ref
|
|
1282
|
+
return extra( { ref }, dollarExtraNode );
|
|
1281
1283
|
}
|
|
1282
1284
|
if (node.literal) {
|
|
1283
1285
|
if (typeof node.val === node.literal || node.val === null)
|
|
@@ -63,10 +63,13 @@ Object.assign(GenericAntlrParser.prototype, {
|
|
|
63
63
|
info: function(...args) { return _message( this, 'info', ...args ); },
|
|
64
64
|
attachLocation,
|
|
65
65
|
assignAnnotation,
|
|
66
|
+
addAnnotation,
|
|
66
67
|
checkExtensionDict,
|
|
67
|
-
|
|
68
|
+
handleDuplicateExtension,
|
|
68
69
|
startLocation,
|
|
69
70
|
tokenLocation,
|
|
71
|
+
isMultiLineToken,
|
|
72
|
+
fixMultiLineTokenEndLocation,
|
|
70
73
|
valueWithTokenLocation,
|
|
71
74
|
previousTokenAtLocation,
|
|
72
75
|
combinedLocation,
|
|
@@ -190,12 +193,12 @@ function setLocalTokenIfBefore( string, tokenName, before, inSameLine ) {
|
|
|
190
193
|
}
|
|
191
194
|
|
|
192
195
|
function setLocalTokenForId( tokenNameMap ) {
|
|
196
|
+
const tokenName = tokenNameMap[ this._input.LT(2).text || '' ];
|
|
193
197
|
const ll1 = this.getCurrentToken();
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
198
|
+
if (tokenName &&
|
|
199
|
+
(ll1.type === this.constructor.Identifier || /^[a-zA-Z_]+$/.test( ll1.text )))
|
|
200
|
+
ll1.type = this.constructor[tokenName];
|
|
201
|
+
return !!tokenName;
|
|
199
202
|
}
|
|
200
203
|
|
|
201
204
|
// // Special function for rule `requiredSemi` before return $ctx
|
|
@@ -267,9 +270,6 @@ function prepareGenericKeywords( pathItem, expected = null) {
|
|
|
267
270
|
const func = pathItem?.id && specialFunctions[pathItem.id.toUpperCase()];
|
|
268
271
|
const spec = func && func[argPos] || specialFunctions[''][argPos ? 1 : 0];
|
|
269
272
|
this.$genericKeywords = spec;
|
|
270
|
-
// currently, we only have 'TODO', i.e. a keyword which is alternative to expression
|
|
271
|
-
// TODO: If not just at the beginning, we need a stack for $genericKeywords,
|
|
272
|
-
// as we can have nested special functions
|
|
273
273
|
// @ts-ignore
|
|
274
274
|
const token = this.getCurrentToken() || { text: '' };
|
|
275
275
|
const text = token.text.toUpperCase();
|
|
@@ -308,15 +308,26 @@ function reportErrorForGenericKeyword() {
|
|
|
308
308
|
function attachLocation( art ) {
|
|
309
309
|
if (!art || art.$parens)
|
|
310
310
|
return art;
|
|
311
|
-
if (!art.location)
|
|
312
|
-
art.location = this.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
311
|
+
if (!art.location) {
|
|
312
|
+
art.location = this.tokenLocation(this._ctx.start, this._ctx.stop);
|
|
313
|
+
return art;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// The last token (this._ctx.stop) may be a multi-line string literal, in which
|
|
317
|
+
// case we can't rely on `this._ctx.stop.line`.
|
|
318
|
+
if (this.isMultiLineToken(this._ctx.stop)) {
|
|
319
|
+
this.fixMultiLineTokenEndLocation(this._ctx.stop, art.location);
|
|
320
|
+
|
|
321
|
+
} else {
|
|
322
|
+
const { stop } = this._ctx;
|
|
323
|
+
art.location.endLine = stop.line;
|
|
324
|
+
art.location.endCol = stop.stop - stop.start + stop.column + 2; // after the last char (special for EOF?)
|
|
325
|
+
}
|
|
326
|
+
|
|
316
327
|
return art;
|
|
317
328
|
}
|
|
318
329
|
|
|
319
|
-
function assignAnnotation( art, anno, prefix = '', iHaveVariant ) {
|
|
330
|
+
function assignAnnotation( art, anno, prefix = '', iHaveVariant = false ) {
|
|
320
331
|
const { name, $flatten } = anno;
|
|
321
332
|
const { path } = name;
|
|
322
333
|
if (path.broken || !path[path.length - 1].id)
|
|
@@ -344,19 +355,9 @@ function assignAnnotation( art, anno, prefix = '', iHaveVariant ) {
|
|
|
344
355
|
}
|
|
345
356
|
else {
|
|
346
357
|
name.absolute = absolute;
|
|
347
|
-
|
|
348
|
-
const old = art[prop];
|
|
349
|
-
if (old && old.$inferred)
|
|
350
|
-
art[prop] = anno;
|
|
351
|
-
else
|
|
352
|
-
dictAddArray( art, prop, anno, (n, location, a) => {
|
|
353
|
-
this.error( 'syntax-duplicate-anno', [ location ], { anno: n },
|
|
354
|
-
'Duplicate assignment with $(ANNO)' );
|
|
355
|
-
a.$errorReported = 'syntax-duplicate-anno';
|
|
356
|
-
// do not report again later as anno-duplicate-xyz
|
|
357
|
-
} );
|
|
358
|
+
this.addAnnotation( art, '@' + absolute, anno );
|
|
358
359
|
}
|
|
359
|
-
if (!prefix) { // set deprecated $
|
|
360
|
+
if (!prefix) { // set deprecated $annotations for cds-lsp
|
|
360
361
|
if (!art.$annotations)
|
|
361
362
|
art.$annotations = [];
|
|
362
363
|
const location = locUtils.combinedLocation( anno.name, anno );
|
|
@@ -364,18 +365,53 @@ function assignAnnotation( art, anno, prefix = '', iHaveVariant ) {
|
|
|
364
365
|
}
|
|
365
366
|
}
|
|
366
367
|
|
|
368
|
+
function addAnnotation( art, prop, anno ) {
|
|
369
|
+
dictAddArray( art, prop, anno, (n, location, a) => {
|
|
370
|
+
// if we would make it a warning, we would still need to keep it an error
|
|
371
|
+
// with '...'; otherwise parse.cdl would have to split annotate statements
|
|
372
|
+
this.error( 'syntax-duplicate-anno', [ location ], { anno: n },
|
|
373
|
+
'Duplicate assignment with $(ANNO)' );
|
|
374
|
+
a.$errorReported = 'syntax-duplicate-anno';
|
|
375
|
+
// do not report again later as anno-duplicate-xyz
|
|
376
|
+
} );
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const extensionDicts = { elements: true, enum: true, params: true, returns: true };
|
|
380
|
+
|
|
367
381
|
function checkExtensionDict( dict ) {
|
|
368
382
|
for (const name in dict) {
|
|
369
383
|
const def = dict[name];
|
|
370
384
|
if (!def.$duplicates)
|
|
371
385
|
continue;
|
|
372
386
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
387
|
+
if (def.kind !== 'annotate') {
|
|
388
|
+
const numDefines =
|
|
389
|
+
def.$duplicates.reduce( addOneForDefinition, addOneForDefinition( 0, def ) );
|
|
390
|
+
this.handleDuplicateExtension( def, name, numDefines );
|
|
391
|
+
for (const dup of def.$duplicates)
|
|
392
|
+
this.handleDuplicateExtension( dup, name, numDefines );
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
// move annotations, 'doc' and 'elements' etc to main member
|
|
396
|
+
for (const dup of def.$duplicates) {
|
|
397
|
+
for (const prop of Object.keys( dup )) {
|
|
398
|
+
if (prop.charAt(0) === '@') {
|
|
399
|
+
this.addAnnotation( def, prop, dup[prop] )
|
|
400
|
+
}
|
|
401
|
+
else if (prop === 'doc') {
|
|
402
|
+
if (def.doc)
|
|
403
|
+
this.warning( 'syntax-duplicate-doc-comment', def.doc.location, {},
|
|
404
|
+
'Doc comment is overwritten by another one below' );
|
|
405
|
+
def.doc = dup.doc;
|
|
406
|
+
}
|
|
407
|
+
else if (extensionDicts[prop]) {
|
|
408
|
+
if (def[prop])
|
|
409
|
+
this.message( 'syntax-duplicate-annotate', [ def.name.location ], { name, prop } );
|
|
410
|
+
def[prop] = dup[prop]; // continuation semantics: last wins
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
def.$duplicates = null;
|
|
379
415
|
}
|
|
380
416
|
}
|
|
381
417
|
|
|
@@ -383,10 +419,15 @@ function addOneForDefinition( count, ext ) {
|
|
|
383
419
|
return (ext.kind === 'extend') ? count : count + 1;
|
|
384
420
|
}
|
|
385
421
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
422
|
+
/**
|
|
423
|
+
* Handle duplicate extensions. Does not handle `annotate`.
|
|
424
|
+
*
|
|
425
|
+
* @param {XSN.Extension} ext
|
|
426
|
+
* @param {string} name
|
|
427
|
+
* @param {number} numDefines
|
|
428
|
+
*/
|
|
429
|
+
function handleDuplicateExtension( ext, name, numDefines ) {
|
|
430
|
+
if (ext.kind === 'extend')
|
|
390
431
|
this.error( 'syntax-duplicate-extend', [ ext.name.location ],
|
|
391
432
|
{ name, '#': (numDefines ? 'define' : 'extend') } );
|
|
392
433
|
else if (numDefines === 1)
|
|
@@ -434,31 +475,49 @@ function tokenLocation( token, endToken = null ) {
|
|
|
434
475
|
|
|
435
476
|
// This check is done for performance reason. No need to access a token's
|
|
436
477
|
// data if we know that it spans only one single line.
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
478
|
+
if (this.isMultiLineToken(token))
|
|
479
|
+
this.fixMultiLineTokenEndLocation(token, loc);
|
|
480
|
+
|
|
481
|
+
return loc;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function isMultiLineToken(token) {
|
|
485
|
+
return (
|
|
486
|
+
token.type === this.constructor.DocComment ||
|
|
487
|
+
token.type === this.constructor.String ||
|
|
488
|
+
token.type === this.constructor.UnterminatedLiteral
|
|
441
489
|
);
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Adapt end location of `location` according to `token`, assuming that `token` is a multi-line
|
|
494
|
+
* token such as a multi-line string or doc comment.
|
|
495
|
+
*
|
|
496
|
+
* Sets `endLine`/`endCol`, respecting newline characters in the token.
|
|
497
|
+
*
|
|
498
|
+
* @param token
|
|
499
|
+
* @param {CSN.Location} location
|
|
500
|
+
*/
|
|
501
|
+
function fixMultiLineTokenEndLocation( token, location ) {
|
|
502
|
+
// Count the number of newlines in the token.
|
|
503
|
+
const source = token.source[1].data;
|
|
504
|
+
let newLineCount = 0;
|
|
505
|
+
let lastNewlineIndex = token.start;
|
|
506
|
+
for (let i = token.start; i < token.stop; i++) {
|
|
507
|
+
// Note: We do NOT check for CR, LS, and PS (/[\r\u2028\u2029]/)
|
|
508
|
+
// because ANTLR only uses LF for line break detection.
|
|
509
|
+
if (source[i] === 10) { // code point of '\n'
|
|
510
|
+
newLineCount++;
|
|
511
|
+
lastNewlineIndex = i;
|
|
458
512
|
}
|
|
459
513
|
}
|
|
460
|
-
|
|
461
|
-
|
|
514
|
+
if (newLineCount > 0) {
|
|
515
|
+
location.endLine = token.line + newLineCount;
|
|
516
|
+
location.endCol = token.stop - lastNewlineIndex + 1;
|
|
517
|
+
} else {
|
|
518
|
+
location.endLine = token.line;
|
|
519
|
+
location.endCol = token.stop - token.start + token.column + 2; // after the last char (special for EOF?)
|
|
520
|
+
}
|
|
462
521
|
}
|
|
463
522
|
|
|
464
523
|
/**
|
|
@@ -531,8 +590,8 @@ function docComment( node ) {
|
|
|
531
590
|
if (!this.options.docComment)
|
|
532
591
|
return;
|
|
533
592
|
if (node.doc) {
|
|
534
|
-
this.warning( 'syntax-duplicate-doc-comment',
|
|
535
|
-
'
|
|
593
|
+
this.warning( 'syntax-duplicate-doc-comment', node.doc.location, {},
|
|
594
|
+
'Doc comment is overwritten by another one below' );
|
|
536
595
|
}
|
|
537
596
|
node.doc = this.valueWithTokenLocation( parseDocComment( token.text ), token );
|
|
538
597
|
}
|
|
@@ -716,8 +775,7 @@ function quotedLiteral( token, literal ) {
|
|
|
716
775
|
literal = token.text.slice( 0, pos - 1 ).toLowerCase();
|
|
717
776
|
const p = quotedLiteralPatterns[literal] || {};
|
|
718
777
|
|
|
719
|
-
if (
|
|
720
|
-
!this.options.parseOnly)
|
|
778
|
+
if (p.test_fn && !p.test_fn(val) && !this.options.parseOnly)
|
|
721
779
|
this.warning( 'syntax-invalid-literal', location, { '#': p.test_variant } );
|
|
722
780
|
|
|
723
781
|
if (p.unexpected_char) {
|