@typemf/generator 0.5.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/LICENSE +201 -0
- package/README.md +79 -0
- package/dist/cli.js +1612 -0
- package/dist/index.d.ts +741 -0
- package/dist/index.js +1678 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
- package/templates/typescript/eclass.njk +559 -0
- package/templates/typescript/eenum.njk +8 -0
- package/templates/typescript/efactory.njk +52 -0
- package/templates/typescript/epackage.njk +438 -0
- package/templates/typescript/epackageref.njk +46 -0
- package/templates/typescript/eswitch.njk +32 -0
- package/templates/typescript/etypeguards.njk +30 -0
- package/templates/typescript/main.njk +19 -0
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
{% set superClass = superType(eClass) %}
|
|
2
|
+
{% set ownFeatures = eClass.getEStructuralFeatures() %}
|
|
3
|
+
{% file "types/" + eClass.getName() + ".ts" %}
|
|
4
|
+
{% if superClass %}{% set _ = useImport(superClass.getName(), "types", {foundational: false}) %}{% endif %}
|
|
5
|
+
{% for typeName in referencedApiTypes(ownFeatures, eClass.getName(), superClass.getName() if superClass else '') %}
|
|
6
|
+
{% set _ = useImport(typeName, "types", {foundational: false}) %}
|
|
7
|
+
{% endfor %}
|
|
8
|
+
{% for typeName in referencedOperationTypes(eClass.getEOperations(), eClass.getName(), superClass.getName() if superClass else '') %}
|
|
9
|
+
{% set _ = useImport(typeName, "types", {foundational: false}) %}
|
|
10
|
+
{% endfor %}
|
|
11
|
+
|
|
12
|
+
{{ docComment(eClass) }}
|
|
13
|
+
export interface {{ eClass.getName() }} extends {{ superClass.getName() if superClass else useImport("EObject", "types", {foundational: true}) }} {
|
|
14
|
+
{% for feature in ownFeatures %}
|
|
15
|
+
{{ docComment(feature, " ") }}
|
|
16
|
+
{% if feature.isMany() %}
|
|
17
|
+
get{{ ucfirst(feature.getName()) }}(): {{ useImport("EList", "types", {foundational: true}) }}<{{ tsScalarType(feature) }}>;
|
|
18
|
+
{% else %}
|
|
19
|
+
{{ beanGetterName(feature) }}(): {{ tsOptionalScalarType(feature) }};
|
|
20
|
+
{% if not isReadOnlyFeature(feature) and not needsImplOnlySetter(feature) %}
|
|
21
|
+
set{{ ucfirst(feature.getName()) }}(value: {{ tsOptionalScalarType(feature) }}): void;
|
|
22
|
+
{% endif %}
|
|
23
|
+
{% endif %}
|
|
24
|
+
{% endfor %}
|
|
25
|
+
{% for op in eClass.getEOperations() %}
|
|
26
|
+
{{ docComment(op, " ") }}
|
|
27
|
+
{{ op.getName() }}({{ paramList(op) }}): {{ tsOperationReturnType(op) }};
|
|
28
|
+
{% endfor %}
|
|
29
|
+
}
|
|
30
|
+
{% endfile %}
|
|
31
|
+
{% file "impl/" + eClass.getName() + "Impl.ts" %}
|
|
32
|
+
{% set _ = useImport("EStructuralFeature", "types", {foundational: true}) %}
|
|
33
|
+
{% set _ = useImport(eClass.getName(), "types", {foundational: false}) %}
|
|
34
|
+
{% set _ = useImport("get" + packageClassName(package) + "Ref", "impl", {file: packageClassName(package) + "Ref", foundational: false}) %}
|
|
35
|
+
{% for typeName in referencedApiTypes(ownFeatures, eClass.getName()) %}
|
|
36
|
+
{% set _ = useImport(typeName, "types", {foundational: false}) %}
|
|
37
|
+
{% endfor %}
|
|
38
|
+
{% for typeName in referencedOperationTypes(eClass.getEOperations(), eClass.getName()) %}
|
|
39
|
+
{% set _ = useImport(typeName, "types", {foundational: false}) %}
|
|
40
|
+
{% endfor %}
|
|
41
|
+
{% if superClass %}
|
|
42
|
+
{% set _ = useImport(superClass.getName() + "Impl", "impl", {foundational: false}) %}
|
|
43
|
+
{% else %}
|
|
44
|
+
{% set _ = useImport("EObjectImpl", "impl", {foundational: true}) %}
|
|
45
|
+
{% endif %}
|
|
46
|
+
{% set _ = useImport("BasicEList", "impl", {foundational: true}) %}
|
|
47
|
+
{% for feature in ownFeatures %}
|
|
48
|
+
{% if feature.isMany() %}
|
|
49
|
+
{% set _ = useImport("EList", "types", {foundational: true}) %}
|
|
50
|
+
{% endif %}
|
|
51
|
+
{% endfor %}
|
|
52
|
+
|
|
53
|
+
{{ docComment(eClass) }}
|
|
54
|
+
export class {{ eClass.getName() }}Impl extends {{ (superClass.getName() + "Impl") if superClass else "EObjectImpl" }} implements {{ eClass.getName() }} {
|
|
55
|
+
{% for feature in ownFeatures %}
|
|
56
|
+
{% if feature.isMany() %}
|
|
57
|
+
{#- Lazy: the eager form ("private readonly _{name} = new BasicEList(this,
|
|
58
|
+
{Pkg}Impl.eINSTANCE.get{Class}_{Feature}())") looks up its own feature
|
|
59
|
+
the moment ANY instance of this class is constructed - including
|
|
60
|
+
instances built as part of {Pkg}PackageImpl's OWN construction (every
|
|
61
|
+
generated class's ancestor chain runs through EModelElementImpl,
|
|
62
|
+
whose own such field is built this way). For ordinary generated
|
|
63
|
+
packages this is harmless (the package singleton already exists by
|
|
64
|
+
the time anything else is constructed) - but for a self-hosted
|
|
65
|
+
package describing its own metamodel, the singleton doesn't exist
|
|
66
|
+
yet while it's still constructing itself, and eagerly resolving a
|
|
67
|
+
feature during that construction is a genuine bootstrap deadlock,
|
|
68
|
+
not merely wasted work. Deferring the lookup to first real access
|
|
69
|
+
(add/remove, iteration, non-empty check) means it's simply never
|
|
70
|
+
triggered during construction, self-hosted or not - fixing the
|
|
71
|
+
deadlock as a side effect of being correct about when the feature
|
|
72
|
+
is actually needed, rather than special-casing self-hosting. #}
|
|
73
|
+
{% if not featureGetter(feature) %}
|
|
74
|
+
private _{{ feature.getName() }}Cache: BasicEList<{{ tsScalarType(feature) }}> | undefined;
|
|
75
|
+
|
|
76
|
+
private get _{{ feature.getName() }}(): BasicEList<{{ tsScalarType(feature) }}> {
|
|
77
|
+
if (!this._{{ feature.getName() }}Cache) {
|
|
78
|
+
this._{{ feature.getName() }}Cache = new BasicEList<{{ tsScalarType(feature) }}>(this, get{{ packageClassName(package) }}Ref().get{{ eClass.getName() }}_{{ ucfirst(feature.getName()) }}());
|
|
79
|
+
}
|
|
80
|
+
return this._{{ feature.getName() }}Cache;
|
|
81
|
+
}
|
|
82
|
+
{% else %}
|
|
83
|
+
{#- A many-valued feature with a custom getter is computed, not stored - but recomputing its
|
|
84
|
+
full body on every call is wasteful for a traversal (eAllStructuralFeatures and kin), so
|
|
85
|
+
this caches the result, keyed by EObjectImpl.getModelGeneration() (see there for the
|
|
86
|
+
invalidation design: global, bumped whenever eStructuralFeatures/eOperations/eSuperTypes/
|
|
87
|
+
eGenericSuperTypes change anywhere) rather than never invalidating or requiring an explicit
|
|
88
|
+
recompute call. #}
|
|
89
|
+
private _{{ feature.getName() }}Cache: EList<{{ tsScalarType(feature) }}> | undefined;
|
|
90
|
+
private _{{ feature.getName() }}CacheGeneration = -1;
|
|
91
|
+
{% set _ = useImport("EObjectImpl", "impl", {foundational: true}) %}
|
|
92
|
+
{% endif %}
|
|
93
|
+
{% else %}
|
|
94
|
+
{% if not featureGetter(feature) or customBodyNeedsOwnField(feature) %}
|
|
95
|
+
{#- A feature with a custom getter whose body doesn't reference its own field (either no
|
|
96
|
+
setter at all, or a getter/setter pair that delegates entirely to a DIFFERENT feature -
|
|
97
|
+
see customBodyNeedsOwnField) is purely computed - there is nothing to declare a field FOR.
|
|
98
|
+
But a feature with a getter AND setter that DOES reference its own field (e.g.
|
|
99
|
+
EEnumLiteral.literal, whose getter falls back to getName() but whose setter writes a real,
|
|
100
|
+
explicit value to "this._literal" - found by running the swap, not predicted) needs a real
|
|
101
|
+
backing field: the setter has somewhere to write, and the getter reads it as part of its
|
|
102
|
+
own logic. A trivialDerivedFormula feature still gets one too
|
|
103
|
+
(pre-existing, unchanged shape); the field is unused by its getter but kept for
|
|
104
|
+
minimal change to that older mechanism. #}
|
|
105
|
+
private _{{ feature.getName() }}: {{ tsOptionalScalarType(feature) }}{% if isPrimitiveValueType(feature.getEType()) %} = {{ primitiveDefaultValue(feature) }}{% endif %};
|
|
106
|
+
{% else %}
|
|
107
|
+
{#- Same generation-keyed caching as the many-valued case above, for a single-valued custom getter.
|
|
108
|
+
Always "| undefined", regardless of whether the feature's OWN type already is - a required
|
|
109
|
+
feature's cache field is still genuinely undefined until first computed (this is a cache slot,
|
|
110
|
+
not the feature's own nullability); tsScalarType(feature) alone would leave "not yet computed"
|
|
111
|
+
with no way to represent, and TypeScript correctly refuses a field with no initializer and no
|
|
112
|
+
way to hold one at all - a real `tsc` error found this, not predicted. #}
|
|
113
|
+
private _{{ feature.getName() }}Cache: {{ tsScalarType(feature) }} | undefined;
|
|
114
|
+
private _{{ feature.getName() }}CacheGeneration = -1;
|
|
115
|
+
{% set _ = useImport("EObjectImpl", "impl", {foundational: true}) %}
|
|
116
|
+
{% endif %}
|
|
117
|
+
{% endif %}
|
|
118
|
+
{% endfor %}
|
|
119
|
+
|
|
120
|
+
{% if eClass.getName() == "EClassifier" %}
|
|
121
|
+
{#- classifierID is real, internal dispatch bookkeeping (matching
|
|
122
|
+
@typemf/core's own hand-written EClassifierImpl), not a modeled
|
|
123
|
+
Ecore feature - so it's declared here, once, on EClassifier itself
|
|
124
|
+
(inherited by every descendant: EClass, EDataType, EEnum, ...)
|
|
125
|
+
rather than emitted by the ordinary feature-driven generation
|
|
126
|
+
above, which has no feature to generate it from. Confirmed
|
|
127
|
+
necessary by actually running self-hosted bootstrap code (which
|
|
128
|
+
constructs real EClassifier-derived metaclass shells and needs to
|
|
129
|
+
set their classifierID), not assumed - see NOTES.md. #}
|
|
130
|
+
private classifierId = -1;
|
|
131
|
+
|
|
132
|
+
getClassifierID(): number {
|
|
133
|
+
return this.classifierId;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
setClassifierID(id: number): void {
|
|
137
|
+
this.classifierId = id;
|
|
138
|
+
}
|
|
139
|
+
{% endif %}
|
|
140
|
+
{% if eClass.getName() == "EStructuralFeature" %}
|
|
141
|
+
{#- featureID: the exact same real gap as classifierID above, one level
|
|
142
|
+
down the hierarchy - internal dispatch bookkeeping (matching
|
|
143
|
+
@typemf/core's own hand-written EStructuralFeatureImpl), not a
|
|
144
|
+
modeled Ecore feature, declared once here on EStructuralFeature
|
|
145
|
+
itself (inherited by EAttribute, EReference, ...). Found the same
|
|
146
|
+
way: running self-hosted bootstrap code, which constructs real
|
|
147
|
+
EStructuralFeature-derived metaclass shells (EAttributeImpl,
|
|
148
|
+
EReferenceImpl) and needs to set their featureID - see NOTES.md. #}
|
|
149
|
+
private featureId = -1;
|
|
150
|
+
|
|
151
|
+
getFeatureID(): number {
|
|
152
|
+
return this.featureId;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
setFeatureID(id: number): void {
|
|
156
|
+
this.featureId = id;
|
|
157
|
+
}
|
|
158
|
+
{#- containerClass: real EMF's own asymmetric design (see EStructuralFeature.getContainerClass()'s
|
|
159
|
+
doc comment in @typemf/core) - PUBLIC getter (a real, modeled, reflectively-callable operation),
|
|
160
|
+
IMPL-ONLY setter (never on the interface, set once during bootstrap - see epackage.njk). Not
|
|
161
|
+
internal bookkeeping like featureID (it IS a real declared operation), but shares the same
|
|
162
|
+
"special-cased field the normal per-operation loop must skip" shape, so isBookkeepingOperation
|
|
163
|
+
treats it the same way (see typescript-filters.ts). #}
|
|
164
|
+
private containerClass: {{ useImport("TypeScriptClass", "types", {foundational: true}) }}<unknown> | undefined;
|
|
165
|
+
|
|
166
|
+
getContainerClass(): {{ useImport("TypeScriptClass", "types", {foundational: true}) }}<unknown> | undefined {
|
|
167
|
+
return this.containerClass;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
setContainerClass(cls: {{ useImport("TypeScriptClass", "types", {foundational: true}) }}<unknown> | undefined): void {
|
|
171
|
+
this.containerClass = cls;
|
|
172
|
+
}
|
|
173
|
+
{% endif %}
|
|
174
|
+
{% if eClass.getName() == "EOperation" %}
|
|
175
|
+
{#- operationID: the exact same real gap as classifierID/featureID above - internal dispatch
|
|
176
|
+
bookkeeping (matching @typemf/core's own hand-written EOperationImpl), not a modeled Ecore
|
|
177
|
+
feature, declared once here on EOperation itself. Same discovery path: self-hosted bootstrap
|
|
178
|
+
code constructs real EOperationImpl instances and needs to set their operationID - see NOTES.md
|
|
179
|
+
(point 4). #}
|
|
180
|
+
private operationId = -1;
|
|
181
|
+
|
|
182
|
+
getOperationID(): number {
|
|
183
|
+
return this.operationId;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
setOperationID(id: number): void {
|
|
187
|
+
this.operationId = id;
|
|
188
|
+
}
|
|
189
|
+
{% endif %}
|
|
190
|
+
|
|
191
|
+
eClass(): {{ useImport("EClass", "types", {foundational: true}) }} {
|
|
192
|
+
return get{{ packageClassName(package) }}Ref().get{{ eClass.getName() }}();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
{% for feature in ownFeatures %}
|
|
196
|
+
{% set getter = featureGetter(feature) %}
|
|
197
|
+
{% set setter = featureSetter(feature) %}
|
|
198
|
+
{% if feature.isMany() %}
|
|
199
|
+
{% if getter %}
|
|
200
|
+
{% set _featureImports = registerBodyImports(feature) %}
|
|
201
|
+
get{{ ucfirst(feature.getName()) }}(): EList<{{ tsScalarType(feature) }}> {
|
|
202
|
+
if (this._{{ feature.getName() }}CacheGeneration !== EObjectImpl.getModelGeneration()) {
|
|
203
|
+
this._{{ feature.getName() }}Cache = (() => {
|
|
204
|
+
{{ getter | indent(8) }}
|
|
205
|
+
})();
|
|
206
|
+
this._{{ feature.getName() }}CacheGeneration = EObjectImpl.getModelGeneration();
|
|
207
|
+
}
|
|
208
|
+
return this._{{ feature.getName() }}Cache!;
|
|
209
|
+
}
|
|
210
|
+
{% else %}
|
|
211
|
+
get{{ ucfirst(feature.getName()) }}(): EList<{{ tsScalarType(feature) }}> {
|
|
212
|
+
return this._{{ feature.getName() }};
|
|
213
|
+
}
|
|
214
|
+
{% endif %}
|
|
215
|
+
{% else %}
|
|
216
|
+
{% set formula = trivialDerivedFormula(feature) %}
|
|
217
|
+
{% if getter and setter %}
|
|
218
|
+
{% set _featureImports = registerBodyImports(feature) %}
|
|
219
|
+
{#- A feature with BOTH a custom getter and a custom setter (e.g. EEnumLiteral.literal) has a
|
|
220
|
+
real backing field (see the field-declaration block above) that the setter writes to
|
|
221
|
+
directly - the getter's own body is cheap (reads that field, maybe augments it, as
|
|
222
|
+
literal's fallback-to-name does), not an expensive computation worth caching. Found by
|
|
223
|
+
running the swap: this used to always take the cached-computation branch below,
|
|
224
|
+
referencing a _{{ feature.getName() }}Cache field that was never declared for this case. #}
|
|
225
|
+
{{ beanGetterName(feature) }}(): {{ tsOptionalScalarType(feature) }} {
|
|
226
|
+
{{ getter | indent(4) }}
|
|
227
|
+
}
|
|
228
|
+
{% elif getter %}
|
|
229
|
+
{% set _featureImports = registerBodyImports(feature) %}
|
|
230
|
+
{#- A feature's own custom getter (see FEATURE_ANNOTATION_SOURCE) with no custom setter - it has
|
|
231
|
+
no stored field to read from (see the field-declaration block above), only a
|
|
232
|
+
generation-keyed cache of its own last-computed result (same design as the many-valued
|
|
233
|
+
case). #}
|
|
234
|
+
{{ beanGetterName(feature) }}(): {{ tsOptionalScalarType(feature) }} {
|
|
235
|
+
if (this._{{ feature.getName() }}CacheGeneration !== EObjectImpl.getModelGeneration()) {
|
|
236
|
+
this._{{ feature.getName() }}Cache = (() => {
|
|
237
|
+
{{ getter | indent(8) }}
|
|
238
|
+
})();
|
|
239
|
+
this._{{ feature.getName() }}CacheGeneration = EObjectImpl.getModelGeneration();
|
|
240
|
+
}
|
|
241
|
+
return this._{{ feature.getName() }}Cache!;
|
|
242
|
+
}
|
|
243
|
+
{% elif formula %}
|
|
244
|
+
{{ beanGetterName(feature) }}(): {{ tsOptionalScalarType(feature) }} {
|
|
245
|
+
return {{ formula }};
|
|
246
|
+
}
|
|
247
|
+
{% else %}
|
|
248
|
+
{{ beanGetterName(feature) }}(): {{ tsOptionalScalarType(feature) }} {
|
|
249
|
+
return this._{{ feature.getName() }};
|
|
250
|
+
}
|
|
251
|
+
{% endif %}
|
|
252
|
+
{% if not isReadOnlyFeature(feature) %}
|
|
253
|
+
{% if needsImplOnlySetter(feature) %}
|
|
254
|
+
{#- changeable=false, but not otherwise read-only (not derived, no custom getter) - real EMF's own
|
|
255
|
+
convention: a real setter still exists (bootstrap code and the loader both need one), but it's
|
|
256
|
+
impl-only (not on the public interface - see above) and bypasses eSet/eDidAdd/eDidRemove
|
|
257
|
+
entirely, a plain, unnotified field assignment. Matches this session's earlier containerClass
|
|
258
|
+
work exactly. #}
|
|
259
|
+
set{{ ucfirst(feature.getName()) }}(value: {{ tsOptionalScalarType(feature) }}): void {
|
|
260
|
+
this._{{ feature.getName() }} = value;
|
|
261
|
+
}
|
|
262
|
+
{% elif setter %}
|
|
263
|
+
{% set _featureImports = registerBodyImports(feature) %}
|
|
264
|
+
{#- A feature's own custom setter - replaces the default eSet(...) delegation entirely, since
|
|
265
|
+
the custom body defines the feature's real write semantics (including any notification -
|
|
266
|
+
that is the body author's responsibility, same as an operation body's is). #}
|
|
267
|
+
set{{ ucfirst(feature.getName()) }}(value: {{ tsOptionalScalarType(feature) }}): void {
|
|
268
|
+
{{ setter | indent(4) }}
|
|
269
|
+
}
|
|
270
|
+
{% else %}
|
|
271
|
+
set{{ ucfirst(feature.getName()) }}(value: {{ tsOptionalScalarType(feature) }}): void {
|
|
272
|
+
this.eSet(get{{ packageClassName(package) }}Ref().get{{ eClass.getName() }}_{{ ucfirst(feature.getName()) }}(), value);
|
|
273
|
+
}
|
|
274
|
+
{% endif %}
|
|
275
|
+
{% endif %}
|
|
276
|
+
{% endif %}
|
|
277
|
+
{% endfor %}
|
|
278
|
+
{% for group in groupOperationsByName(eClass.getEOperations()) %}
|
|
279
|
+
{% set op = group[0] %}
|
|
280
|
+
{% if group.length == 1 and isBookkeepingOperation(op, eClass) %}
|
|
281
|
+
{#- Skip entirely: real Ecore.ecore declares this operation for real
|
|
282
|
+
(getClassifierID/getFeatureID), but it collides by name with the
|
|
283
|
+
hand-added bookkeeping method above (needed for the self-hosting
|
|
284
|
+
bootstrap fix - see isBookkeepingOperation's own doc comment), which
|
|
285
|
+
already provides a correct, real implementation. Generating a second,
|
|
286
|
+
throwing-stub method of the same name here would just be the last
|
|
287
|
+
declaration silently winning at runtime, shadowing the real one. #}
|
|
288
|
+
{% elif group.length > 1 %}
|
|
289
|
+
{#- Overloaded (2+ EOperations sharing this name - real Ecore.ecore has this, e.g. EClass.getEStructuralFeature
|
|
290
|
+
by ID vs by name). Every overload signature is emitted with no body (a TS class can only have one),
|
|
291
|
+
followed by ONE implementation signature (see mergedParamList: the union of every overload's types at
|
|
292
|
+
each position, later positions optional when arities differ). Its body dispatches on the runtime
|
|
293
|
+
arguments to the overload's own body (see overloadDispatch), or, when that is not possible or no
|
|
294
|
+
overload has a body, is a single throwing stub saying why. #}
|
|
295
|
+
{% set dispatch = overloadDispatch(group, eClass.getName()) %}
|
|
296
|
+
{% for o in group %}
|
|
297
|
+
{{ o.getName() }}({{ paramList(o) }}): {{ tsOperationReturnType(o) }};
|
|
298
|
+
{% endfor %}
|
|
299
|
+
{{ op.getName() }}({{ mergedParamList(group) }}): {{ mergedReturnType(group) }} {
|
|
300
|
+
{% if dispatch.stubMessage %}
|
|
301
|
+
throw new Error('{{ dispatch.stubMessage }}');
|
|
302
|
+
{% else %}
|
|
303
|
+
{% for branch in dispatch.branches %}
|
|
304
|
+
{% if branch.body %}
|
|
305
|
+
{% set _bodyImports = registerBodyImports(branch.operation) %}
|
|
306
|
+
{% endif %}
|
|
307
|
+
if ({{ branch.condition }}) {
|
|
308
|
+
{% for alias in branch.aliases %}
|
|
309
|
+
{{ alias }}
|
|
310
|
+
{% endfor %}
|
|
311
|
+
{% if branch.body %}
|
|
312
|
+
{{ branch.body | indent(6) }}
|
|
313
|
+
{% else %}
|
|
314
|
+
throw new Error('{{ branch.label }} has no `body` annotation - nothing to generate.');
|
|
315
|
+
{% endif %}
|
|
316
|
+
}
|
|
317
|
+
{% endfor %}
|
|
318
|
+
throw new Error('{{ eClass.getName() }}.{{ op.getName() }}(): no overload matches the given arguments.');
|
|
319
|
+
{% endif %}
|
|
320
|
+
}
|
|
321
|
+
{% else %}
|
|
322
|
+
{% set body = operationBody(op) %}
|
|
323
|
+
{% if body %}
|
|
324
|
+
{% set _bodyImports = registerBodyImports(op) %}
|
|
325
|
+
{{ op.getName() }}({{ paramList(op) }}): {{ tsOperationReturnType(op) }} {
|
|
326
|
+
{{ body | indent(4) }}
|
|
327
|
+
}
|
|
328
|
+
{% else %}
|
|
329
|
+
{#- No `body` annotation (typemf generator or GenModel) - there is no further hand-editable
|
|
330
|
+
layer to fill this in anymore (Gen/Impl merged: this class is
|
|
331
|
+
instantiated directly via `new X Impl()` in the Factory, so it
|
|
332
|
+
cannot have any abstract members). A clear throwing stub is the
|
|
333
|
+
honest behavior for fully-generated code with nothing to base a
|
|
334
|
+
real implementation on, rather than silently failing to compile or
|
|
335
|
+
silently doing nothing. #}
|
|
336
|
+
{{ op.getName() }}({{ paramList(op) }}): {{ tsOperationReturnType(op) }} {
|
|
337
|
+
throw new Error('{{ eClass.getName() }}.{{ op.getName() }}() has no `body` annotation - nothing to generate.');
|
|
338
|
+
}
|
|
339
|
+
{% endif %}
|
|
340
|
+
{% endif %}
|
|
341
|
+
{% endfor %}
|
|
342
|
+
|
|
343
|
+
eGet(feature: EStructuralFeature): unknown {
|
|
344
|
+
switch (feature.getFeatureID()) {
|
|
345
|
+
{% for feature in ownFeatures %}
|
|
346
|
+
{% if featureGetter(feature) or trivialDerivedFormula(feature) %}
|
|
347
|
+
{#- Delegates to the real getter (custom body, or the pre-existing hard-coded formula)
|
|
348
|
+
rather than reading the stored field directly - the field is either absent (a
|
|
349
|
+
custom getter, computed, no backing store) or simply not what the getter itself
|
|
350
|
+
returns (a formula feature): reading it directly here was the exact bug found and
|
|
351
|
+
fixed for EClass.getEAllStructuralFeatures (getter said one thing, eGet said
|
|
352
|
+
another) - fixed uniformly for every computed feature, not just that one. #}
|
|
353
|
+
case {{ feature.getFeatureID() }}: return this.{{ beanGetterName(feature) if not feature.isMany() else 'get' + ucfirst(feature.getName()) }}();
|
|
354
|
+
{% else %}
|
|
355
|
+
case {{ feature.getFeatureID() }}: return this._{{ feature.getName() }};
|
|
356
|
+
{% endif %}
|
|
357
|
+
{% endfor %}
|
|
358
|
+
default:
|
|
359
|
+
{% if superClass %}
|
|
360
|
+
return super.eGet(feature);
|
|
361
|
+
{% else %}
|
|
362
|
+
throw new Error(`{{ eClass.getName() }} has no feature with id ${feature.getFeatureID()}`);
|
|
363
|
+
{% endif %}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
eSet(feature: EStructuralFeature, value: unknown): void {
|
|
368
|
+
switch (feature.getFeatureID()) {
|
|
369
|
+
{% for feature in ownFeatures %}
|
|
370
|
+
{% if feature.isMany() %}
|
|
371
|
+
{% if not featureGetter(feature) %}
|
|
372
|
+
case {{ feature.getFeatureID() }}: {
|
|
373
|
+
const list = this._{{ feature.getName() }};
|
|
374
|
+
const items = value === undefined || value === null ? [] : [...(value as Iterable<unknown>)];
|
|
375
|
+
list.clear();
|
|
376
|
+
for (const item of items) list.add(item as {{ tsScalarType(feature) }});
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
{% endif %}
|
|
380
|
+
{#- A many-valued feature with a custom getter has no field to list-replace into (see the
|
|
381
|
+
field-declaration block above) - omitted here entirely, falls to super/throw, same as
|
|
382
|
+
any other unknown feature id. A `set` annotation on one is flagged separately (see
|
|
383
|
+
findFeatureAnnotationProblems) since many-valued features never have a setter anyway. #}
|
|
384
|
+
{% elif featureSetter(feature) %}
|
|
385
|
+
case {{ feature.getFeatureID() }}: {
|
|
386
|
+
this.set{{ ucfirst(feature.getName()) }}(value as {{ tsOptionalScalarType(feature) }});
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
{% elif not isReadOnlyFeature(feature) and not needsImplOnlySetter(feature) %}
|
|
390
|
+
case {{ feature.getFeatureID() }}: {
|
|
391
|
+
const oldValue = this.eGet(feature);
|
|
392
|
+
if (oldValue === value) return;
|
|
393
|
+
const wasSet = this.eIsSet(feature);
|
|
394
|
+
this.eBasicSetValue(feature, value);
|
|
395
|
+
this.eDidRemove(feature, oldValue);
|
|
396
|
+
this.eDidAdd(feature, value);
|
|
397
|
+
this.eNotify({ eventType: 'SET', notifier: this, feature, oldValue, newValue: value, position: undefined, wasSet });
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
{% endif %}
|
|
401
|
+
{% endfor %}
|
|
402
|
+
default:
|
|
403
|
+
{% if superClass %}
|
|
404
|
+
super.eSet(feature, value);
|
|
405
|
+
return;
|
|
406
|
+
{% else %}
|
|
407
|
+
throw new Error(`{{ eClass.getName() }} has no feature with id ${feature.getFeatureID()}`);
|
|
408
|
+
{% endif %}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
{#- Accepts a plain feature-ID number as well as a real EStructuralFeature
|
|
413
|
+
- needed so bootstrap code (setting a classifier shell's own name
|
|
414
|
+
during self-hosted Ecore.ecore's Pass 1, before the "name" feature's
|
|
415
|
+
own metaclass object exists in Pass 2 - a real, confirmed chicken-
|
|
416
|
+
and-egg problem, not a hypothetical one) can bypass the metaclass
|
|
417
|
+
lookup entirely and dispatch on the feature ID directly, which the
|
|
418
|
+
generator already knows at codegen time (it's the literal case
|
|
419
|
+
label below) and never actually needed the feature OBJECT for in
|
|
420
|
+
the first place - only its ID. Ordinary callers (eSet, generated
|
|
421
|
+
bean setters) are unaffected; they still pass a real feature and
|
|
422
|
+
get identical behavior. #}
|
|
423
|
+
eBasicSetValue(feature: EStructuralFeature | number, value: unknown): void {
|
|
424
|
+
const featureId = typeof feature === 'number' ? feature : feature.getFeatureID();
|
|
425
|
+
switch (featureId) {
|
|
426
|
+
{% for feature in singleValuedFeatures(ownFeatures) %}
|
|
427
|
+
{% if not featureGetter(feature) %}
|
|
428
|
+
case {{ feature.getFeatureID() }}: this._{{ feature.getName() }} = value as {{ tsOptionalScalarType(feature) }}; return;
|
|
429
|
+
{% endif %}
|
|
430
|
+
{% endfor %}
|
|
431
|
+
default:
|
|
432
|
+
{% if superClass %}
|
|
433
|
+
super.eBasicSetValue(feature, value);
|
|
434
|
+
return;
|
|
435
|
+
{% else %}
|
|
436
|
+
void value;
|
|
437
|
+
throw new Error(`Feature ${featureId} on {{ eClass.getName() }} is many-valued or unknown.`);
|
|
438
|
+
{% endif %}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
{#- The many-valued analog of eBasicSetValue, for the identical reason:
|
|
443
|
+
bootstrap code needs to add to a list (eStructuralFeatures,
|
|
444
|
+
eAnnotations, eSuperTypes, ...) before the feature's own metaclass
|
|
445
|
+
exists to back the ordinary getter's lazy construction. Constructs
|
|
446
|
+
the cache with no feature reference when called this way - a real,
|
|
447
|
+
narrow, deliberate limitation: containment/opposite maintenance on
|
|
448
|
+
a list built this way is unavailable, since there's no feature to
|
|
449
|
+
derive it from. Acceptable specifically because only the metamodel's
|
|
450
|
+
own self-describing bootstrap instances (EClassImpl, EAttributeImpl,
|
|
451
|
+
etc., as constructed by {Pkg}PackageImpl) ever take this path -
|
|
452
|
+
ordinary domain instances always go through the normal, fully-
|
|
453
|
+
wired getter instead, never this one. #}
|
|
454
|
+
eBootstrapList(featureId: number): BasicEList<unknown> {
|
|
455
|
+
switch (featureId) {
|
|
456
|
+
{% for feature in ownFeatures %}
|
|
457
|
+
{% if feature.isMany() and not featureGetter(feature) %}
|
|
458
|
+
case {{ feature.getFeatureID() }}:
|
|
459
|
+
if (!this._{{ feature.getName() }}Cache) this._{{ feature.getName() }}Cache = new BasicEList<{{ tsScalarType(feature) }}>(this);
|
|
460
|
+
return this._{{ feature.getName() }}Cache;
|
|
461
|
+
{% endif %}
|
|
462
|
+
{% endfor %}
|
|
463
|
+
default:
|
|
464
|
+
{% if superClass %}
|
|
465
|
+
return super.eBootstrapList(featureId);
|
|
466
|
+
{% else %}
|
|
467
|
+
throw new Error(`Feature ${featureId} on {{ eClass.getName() }} is single-valued or unknown.`);
|
|
468
|
+
{% endif %}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
eIsSet(feature: EStructuralFeature): boolean {
|
|
473
|
+
switch (feature.getFeatureID()) {
|
|
474
|
+
{% for feature in ownFeatures %}
|
|
475
|
+
{% if feature.isMany() %}
|
|
476
|
+
{% if featureGetter(feature) %}
|
|
477
|
+
{#- No cache field to check (see the field-declaration block above) - call the getter
|
|
478
|
+
and apply the SAME "not empty" test the field-backed shape below uses. #}
|
|
479
|
+
case {{ feature.getFeatureID() }}: return this.get{{ ucfirst(feature.getName()) }}().size() !== 0;
|
|
480
|
+
{% else %}
|
|
481
|
+
case {{ feature.getFeatureID() }}: return this._{{ feature.getName() }}Cache !== undefined && this._{{ feature.getName() }}Cache.size() !== 0;
|
|
482
|
+
{% endif %}
|
|
483
|
+
{% elif customBodyNeedsOwnField(feature) %}
|
|
484
|
+
{#- A real backing field exists (see the field-declaration block above), but the getter may
|
|
485
|
+
augment it (literal's fallback-to-name) - "is set" means "explicitly given a value",
|
|
486
|
+
checked against the RAW field directly, not the getter's own (possibly fallback-masked)
|
|
487
|
+
result. Found by running the swap: EEnumLiteral.literal used to fall into the
|
|
488
|
+
no-field/call-the-getter branch below, where getLiteral()'s own fallback made eIsSet
|
|
489
|
+
always true, never false. Deliberately NOT "getter and setter" alone - a getter/setter
|
|
490
|
+
pair that delegates entirely to a DIFFERENT feature (no reference to its own field at
|
|
491
|
+
all) has no field here to check, and belongs in the call-the-getter branch below
|
|
492
|
+
instead; conflating the two broke exactly that case, found by running it. #}
|
|
493
|
+
{% if isPrimitiveValueType(feature.getEType()) %}
|
|
494
|
+
case {{ feature.getFeatureID() }}: return this._{{ feature.getName() }} !== {{ primitiveDefaultValue(feature) }};
|
|
495
|
+
{% else %}
|
|
496
|
+
case {{ feature.getFeatureID() }}: return this._{{ feature.getName() }} !== undefined;
|
|
497
|
+
{% endif %}
|
|
498
|
+
{% elif featureGetter(feature) %}
|
|
499
|
+
{#- No field to check - call the getter and apply the same default-value test below. #}
|
|
500
|
+
{% if isPrimitiveValueType(feature.getEType()) %}
|
|
501
|
+
case {{ feature.getFeatureID() }}: return this.{{ beanGetterName(feature) }}() !== {{ primitiveDefaultValue(feature) }};
|
|
502
|
+
{% else %}
|
|
503
|
+
case {{ feature.getFeatureID() }}: return this.{{ beanGetterName(feature) }}() !== undefined;
|
|
504
|
+
{% endif %}
|
|
505
|
+
{% else %}
|
|
506
|
+
{% if isPrimitiveValueType(feature.getEType()) %}
|
|
507
|
+
case {{ feature.getFeatureID() }}: return this._{{ feature.getName() }} !== {{ primitiveDefaultValue(feature) }};
|
|
508
|
+
{% else %}
|
|
509
|
+
case {{ feature.getFeatureID() }}: return this._{{ feature.getName() }} !== undefined;
|
|
510
|
+
{% endif %}
|
|
511
|
+
{% endif %}
|
|
512
|
+
{% endfor %}
|
|
513
|
+
default:
|
|
514
|
+
{% if superClass %}
|
|
515
|
+
return super.eIsSet(feature);
|
|
516
|
+
{% else %}
|
|
517
|
+
throw new Error(`{{ eClass.getName() }} has no feature with id ${feature.getFeatureID()}`);
|
|
518
|
+
{% endif %}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
eUnset(feature: EStructuralFeature): void {
|
|
523
|
+
switch (feature.getFeatureID()) {
|
|
524
|
+
{% for feature in ownFeatures %}
|
|
525
|
+
{% if feature.isMany() and not featureGetter(feature) %}
|
|
526
|
+
case {{ feature.getFeatureID() }}: this._{{ feature.getName() }}Cache?.clear(); return;
|
|
527
|
+
{% elif not feature.isMany() and (not featureGetter(feature) or customBodyNeedsOwnField(feature)) %}
|
|
528
|
+
{#- A feature with a getter+setter pair that references its own field (see the
|
|
529
|
+
field-declaration block above and customBodyNeedsOwnField) - unsettable the same way an
|
|
530
|
+
ordinary field-backed feature is, resetting the raw field. A getter+setter pair that
|
|
531
|
+
delegates entirely to a DIFFERENT feature has no field of its own here to reset - falls
|
|
532
|
+
through to super/throw below instead, same as a getter-only feature. #}
|
|
533
|
+
case {{ feature.getFeatureID() }}: {
|
|
534
|
+
const wasSet = this.eIsSet(feature);
|
|
535
|
+
const oldValue = this._{{ feature.getName() }};
|
|
536
|
+
{% if isPrimitiveValueType(feature.getEType()) %}
|
|
537
|
+
this._{{ feature.getName() }} = {{ primitiveDefaultValue(feature) }};
|
|
538
|
+
{% else %}
|
|
539
|
+
this._{{ feature.getName() }} = undefined;
|
|
540
|
+
{% endif %}
|
|
541
|
+
this.eDidRemove(feature, oldValue);
|
|
542
|
+
this.eNotify({ eventType: 'UNSET', notifier: this, feature, oldValue, newValue: undefined, position: undefined, wasSet });
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
{% endif %}
|
|
546
|
+
{#- A feature with ONLY a custom getter (no custom setter) has no field to reset - omitted
|
|
547
|
+
entirely, falls to super/throw. There is no `unset` key (only `get`/`set` were asked for). #}
|
|
548
|
+
{% endfor %}
|
|
549
|
+
default:
|
|
550
|
+
{% if superClass %}
|
|
551
|
+
super.eUnset(feature);
|
|
552
|
+
return;
|
|
553
|
+
{% else %}
|
|
554
|
+
throw new Error(`{{ eClass.getName() }} has no feature with id ${feature.getFeatureID()}`);
|
|
555
|
+
{% endif %}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
{% endfile %}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{% set eFactoryClassifier = findEClassByName(package, "EFactory") %}
|
|
2
|
+
{% file factoryClassName(package) + ".ts" %}
|
|
3
|
+
export interface {{ factoryClassName(package) }} extends {{ useImport("EFactory", "types", {foundational: true}) }} {
|
|
4
|
+
{% for classifier in concreteEClassesOf(package) %}
|
|
5
|
+
create{{ classifier.getName() }}(): {{ useImport(classifier.getName(), "types", {foundational: false}) }};
|
|
6
|
+
{% endfor %}
|
|
7
|
+
create(eClass: {{ useImport("EClass", "types", {foundational: true}) }}): {{ useImport("EObject", "types", {foundational: true}) }};
|
|
8
|
+
}
|
|
9
|
+
{% endfile %}
|
|
10
|
+
{% file "impl/" + factoryClassName(package) + "Impl.ts" %}
|
|
11
|
+
{#-
|
|
12
|
+
EFactoryImpl is requested with foundational: true unconditionally, even
|
|
13
|
+
in the real Ecore.ecore self-hosting case where a classifier literally
|
|
14
|
+
named "EFactory" also exists (see findEClassByName above, matching real
|
|
15
|
+
EMF's own EcoreFactoryImpl extends EFactoryImpl design) - this is
|
|
16
|
+
deliberate, not an oversight: when that's true, it can only be because
|
|
17
|
+
generate-ecore is also true (a metamodel only ever declares a
|
|
18
|
+
classifier named "EFactory" when it IS Ecore.ecore), so foundational's
|
|
19
|
+
own generate-ecore-based resolution already produces the correct
|
|
20
|
+
relative import to this same run's own generated impl/EFactoryImpl.ts
|
|
21
|
+
- no separate branch needed. And since every concrete classifier
|
|
22
|
+
(including "EFactory" itself, when present) is requested under the
|
|
23
|
+
same name/location/file via the loop below, ImportCollector's own
|
|
24
|
+
deduplication - not a hand-written exclusion - is what prevents the
|
|
25
|
+
double-import the old, pre-refactor version of this template had to
|
|
26
|
+
guard against explicitly. #}
|
|
27
|
+
export class {{ factoryClassName(package) }}Impl extends {{ useImport("EFactoryImpl", "impl", {foundational: true}) }} implements {{ useImport(factoryClassName(package), "root", {foundational: false}) }} {
|
|
28
|
+
{% for classifier in concreteEClassesOf(package) %}
|
|
29
|
+
create{{ classifier.getName() }}(): {{ useImport(classifier.getName(), "types", {foundational: false}) }} {
|
|
30
|
+
return new {{ useImport(classifier.getName() + "Impl", "impl", {foundational: false}) }}();
|
|
31
|
+
}
|
|
32
|
+
{% endfor %}
|
|
33
|
+
|
|
34
|
+
create(eClass: {{ useImport("EClass", "types", {foundational: true}) }}): {{ useImport("EObject", "types", {foundational: true}) }} {
|
|
35
|
+
switch (eClass.getClassifierID()) {
|
|
36
|
+
{% for classifier in concreteEClassesOf(package) %}
|
|
37
|
+
case {{ useImport("Ids", "root", {file: packageClassName(package), foundational: false}) }}.{{ classifier.getName() }}.self:
|
|
38
|
+
return this.create{{ classifier.getName() }}();
|
|
39
|
+
{% endfor %}
|
|
40
|
+
default:
|
|
41
|
+
{#- Not a locally-thrown error: delegates to whatever this factory's own
|
|
42
|
+
superclass considers unrecognized-classifier behavior (see
|
|
43
|
+
EFactoryImpl.create() in @typemf/core, or, when self-hosting, the
|
|
44
|
+
EFactory classifier's own `create` body in Ecore.ecore) - the base
|
|
45
|
+
EFactoryImpl.create() is the actual end of this chain, so the
|
|
46
|
+
"unknown classifier" error is thrown exactly once, in one place,
|
|
47
|
+
not duplicated per generated package. #}
|
|
48
|
+
return super.create(eClass);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
{% endfile %}
|