@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.
@@ -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,8 @@
1
+ {% file "types/" + eEnum.getName() + ".ts" %}
2
+ {{ docComment(eEnum) }}
3
+ export enum {{ eEnum.getName() }} {
4
+ {% for literal in eEnum.getELiterals() %}
5
+ {{ literal.getName() }} = {{ jsString(literal.getLiteral()) }},
6
+ {% endfor %}
7
+ }
8
+ {% 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 %}