@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,741 @@
1
+ import { EPackage, EObject, EPackageImpl, EClassImpl, EOperation, EStructuralFeature, EClass, EAnnotation, EModelElement, EGenericType, EClassifier, EDataType, EEnum, ETypedElement } from '@typemf/core';
2
+ import nunjucks from 'nunjucks';
3
+
4
+ /**
5
+ * One config, one template set, one .ecore file, one outputDir - per the
6
+ * design discussion. Multi-language generation is achieved by running the
7
+ * CLI multiple times with different config files (e.g.
8
+ * typescript.typemf-generator.json, java.typemf-generator.json), not by
9
+ * one config handling multiple sets.
10
+ */
11
+ interface GeneratorConfig {
12
+ ecoreFile: string;
13
+ outputDir: string;
14
+ templateSet: string;
15
+ options: Record<string, unknown>;
16
+ }
17
+ declare function loadConfig(configPath: string): Promise<GeneratorConfig>;
18
+
19
+ /**
20
+ * Loads a real .ecore file into a typed EPackage. A .ecore file is an
21
+ * ordinary XMI instance document whose root happens to be an EPackage -
22
+ * read here by @typemf/xmi's own generic XmiSerializer, with no separate
23
+ * parsing logic of its own. Interpreting it correctly needs a registered
24
+ * meta-schema describing what "EClass"/"EAttribute"/etc mean (see
25
+ * ecore-meta-schema.ts) - this produces a DYNAMIC EObject graph, which
26
+ * gets converted (see ecore-dynamic-to-typed.ts) into the real, typed
27
+ * EPackage generate() actually needs as input. See NOTES.md for the full
28
+ * bootstrapping story and why this two-step shape is necessary - NOT
29
+ * because hand-written classes lack eSet() support (they support it fully
30
+ * for every real, modeled feature now, confirmed directly), but because
31
+ * bookkeeping fields (classifierID, featureID, containerClass,
32
+ * operationID) are deliberately excluded from eSet() by design, matching
33
+ * real EMF, and generate()'s own internals need those set too (via plain
34
+ * methods and id-assignment.ts, not eSet) - so a pure eSet-driven read
35
+ * could never be sufficient on its own regardless.
36
+ */
37
+ declare function loadEcorePackage(ecoreFilePath: string): Promise<EPackage>;
38
+
39
+ /**
40
+ * Converts a dynamic EObject graph - produced by parsing a real .ecore
41
+ * file against the meta-schema (see ecore-meta-schema.ts) - into a real,
42
+ * typed EPackage built from @typemf/core's own hand-written classes. This
43
+ * is the piece generate() actually needs as input; a dynamic graph, even
44
+ * though it fully supports eGet/eSet reflectively, doesn't have the plain
45
+ * convenience methods (setClassifierID(), recomputeAllLists(), ...)
46
+ * generate()'s own internals call directly - only the real typed classes
47
+ * do. This conversion writes via the real classes' own plain methods
48
+ * throughout, not because eSet() itself is unsupported there (it is, for
49
+ * every real, modeled feature - confirmed directly, this comment used to
50
+ * claim otherwise), but because bookkeeping (classifierID, featureID,
51
+ * containerClass, operationID) is deliberately unreachable via eSet by
52
+ * design, matching real EMF, and this conversion needs to set exactly
53
+ * that bookkeeping too - so writing everything through the SAME plain
54
+ * methods uniformly is simpler than splitting eSet-for-real-features from
55
+ * plain-calls-for-bookkeeping. It reads reflectively throughout (eGet, by
56
+ * name, off whatever meta-schema classifier each dynamic object happens
57
+ * to be an instance of).
58
+ *
59
+ * A classifier named "EObject" in the source graph (present only so
60
+ * "#//EObject" fragments resolve during parsing - see
61
+ * ecore-meta-schema.ts) is deliberately NOT converted into a real
62
+ * classifier here: any reference to it becomes `undefined` on the real
63
+ * side, matching the generator's own existing convention for a classifier
64
+ * with no explicit type (which already means "real @typemf/core EObject").
65
+ */
66
+ declare function convertDynamicEcoreToTyped(dynamicPkg: EObject): EPackage;
67
+
68
+ /**
69
+ * A hand-authored graph mirroring real Ecore.ecore's own full structure -
70
+ * every EClass, every EDataType, every declared feature (including
71
+ * derived/transient ones - these ARE present in the real file as ordinary
72
+ * <eStructuralFeatures> descriptions of what features a class HAS; what's
73
+ * never serialized is a VALUE for a derived feature on some downstream
74
+ * instance, which never comes up while parsing Ecore.ecore itself, since
75
+ * this file describes structure, not instances-of-that-structure).
76
+ *
77
+ * Two distinct roles, same shape:
78
+ * 1. Registered as the meta-schema the generic XmiSerializer needs to
79
+ * interpret the real Ecore.ecore file at all (a .ecore file is an
80
+ * ordinary XMI instance document whose root is an EPackage - reading
81
+ * it needs SOME registered EPackage describing what "EClass"/
82
+ * "EAttribute"/etc mean, or there's nothing to dispatch xsi:type
83
+ * against).
84
+ * 2. The target shape the dynamic-graph -> typed-EPackage conversion step
85
+ * produces after parsing - what actually gets fed to generate().
86
+ *
87
+ * EObject gets a real classifier shell here (needed so "#//EObject"
88
+ * fragments - e.g. EAnnotation.contents: EObject[] - resolve during
89
+ * parsing) but is deliberately excluded from what the GENERATOR treats as
90
+ * a real classifier to produce files for: it's the one name the generator
91
+ * special-cases exactly the way it already handles "no declared supertype"
92
+ * (see eclass.njk) - any reference to a classifier named "EObject"
93
+ * resolves to @typemf/core's own real EObject instead of generating a
94
+ * redundant, shadowing one.
95
+ *
96
+ * Does NOT model EString/EInt/EBoolean/etc as anything other than bare
97
+ * EDataType shells - no instanceClassName is set here beyond a name;
98
+ * `serializable`/`defaultValueLiteral` on real Ecore.ecore's own
99
+ * EDataType declarations are read during conversion where present, not
100
+ * hardcoded into this meta-schema (the meta-schema only needs to recognize
101
+ * these as valid attributes to parse, not carry their real values itself).
102
+ */
103
+ declare function buildEcoreMetaSchema(): {
104
+ pkg: EPackageImpl;
105
+ eObject: EClassImpl;
106
+ eModelElement: EClassImpl;
107
+ eAnnotation: EClassImpl;
108
+ eNamedElement: EClassImpl;
109
+ eTypedElement: EClassImpl;
110
+ eClassifier: EClassImpl;
111
+ eStructuralFeature: EClassImpl;
112
+ eAttribute: EClassImpl;
113
+ eReference: EClassImpl;
114
+ eClass: EClassImpl;
115
+ ePackage: EClassImpl;
116
+ eDataType: EClassImpl;
117
+ eEnum: EClassImpl;
118
+ eEnumLiteral: EClassImpl;
119
+ eFactory: EClassImpl;
120
+ eOperation: EClassImpl;
121
+ eParameter: EClassImpl;
122
+ eStringToStringMapEntry: EClassImpl;
123
+ eGenericType: EClassImpl;
124
+ eTypeParameter: EClassImpl;
125
+ };
126
+
127
+ /** One output file from a generation run. `path` is always relative - see generate.ts. */
128
+ interface GeneratedFile {
129
+ path: string;
130
+ content: string;
131
+ }
132
+
133
+ /**
134
+ * An isolated, self-contained template contribution - the generator's
135
+ * extension point. `baseFolder` contains a `main.njk` entry point and is
136
+ * ALSO the private include/import resolution root for that set: two
137
+ * different sets can each have their own e.g. `eclass.ts.njk` inside their
138
+ * own folder and never know the other exists. There is no cross-set
139
+ * override or search-path merging - isolation is structural, not a policy
140
+ * decision to make at render time.
141
+ *
142
+ * `main.njk` is invoked exactly once per generation run, over the whole
143
+ * EPackage - it is the template-set author's own job to iterate whatever
144
+ * they need to (classifiers, features, ...) and call {% file %} as many
145
+ * times as they want. The generator's own TypeScript orchestration does
146
+ * not impose a fixed per-class loop on every language.
147
+ */
148
+ interface TemplateSet {
149
+ name: string;
150
+ baseFolder: string;
151
+ /**
152
+ * Optional hook to reject a package before any template renders, by
153
+ * returning a non-empty list of problems (rendered together into one
154
+ * thrown error) - e.g. this set's own generated member names
155
+ * colliding in a way it doesn't know how to resolve automatically.
156
+ * Runs once, after ID assignment, before configureEnvironment/main.njk.
157
+ */
158
+ validate?(pkg: EPackage): string[];
159
+ /**
160
+ * Optional hook to register set-specific filters/globals (e.g. a
161
+ * `tsType` filter mapping EDataType names to TypeScript type names) on
162
+ * this set's own Nunjucks Environment, before `main.njk` renders. Only
163
+ * this set's templates ever see these - a "java" set registering its
164
+ * own `javaType` filter neither conflicts with nor is visible to this
165
+ * one. Also receives the package being generated and the generation
166
+ * options, for a set that needs them outside the templates themselves.
167
+ */
168
+ configureEnvironment?(env: nunjucks.Environment, context: {
169
+ pkg: EPackage;
170
+ options: Record<string, unknown>;
171
+ }): void;
172
+ /**
173
+ * Optional hook to transform a file's fully-rendered body before it's
174
+ * recorded as output - called once per {% file %} block, right after
175
+ * its body string is complete, before anything else sees it. The one
176
+ * real use today: prepending a computed, deduplicated import header
177
+ * that a set's own `useType()`-style mechanism collected while the
178
+ * body rendered (see ImportCollector/typescript-template-set.ts) -
179
+ * this is the only point in the pipeline where the whole body is
180
+ * available as a string but nothing has been finalized yet, since
181
+ * nunjucks renders top-to-bottom in one linear pass and there's no way
182
+ * to "go back" and insert a header once the body below it has already
183
+ * streamed out.
184
+ */
185
+ postProcessFile?(path: string, content: string, options: Record<string, unknown>): string;
186
+ }
187
+
188
+ /**
189
+ * Renders `templateSet`'s main.njk once, over the whole `pkg`. Pure - no
190
+ * disk I/O beyond reading the template files themselves; writing the
191
+ * returned files anywhere is a separate, later step the caller owns. See
192
+ * the design discussion for why `outputDir` deliberately isn't a
193
+ * parameter here: every returned path is relative, so relative imports
194
+ * between generated files are computable without knowing an eventual
195
+ * absolute location.
196
+ */
197
+ declare function generate(pkg: EPackage, templateSet: TemplateSet, options?: Record<string, unknown>): GeneratedFile[];
198
+
199
+ /**
200
+ * Assigns classifierID/featureID freshly, in declaration order, mutating
201
+ * the EClass/EStructuralFeature objects directly - matches real EMF's own
202
+ * behaviour (these are recomputed-every-build implementation details of
203
+ * the generated code, not values meant to persist anywhere). See the
204
+ * design discussion: our wire formats (json/xmi) address by name, never by
205
+ * numeric ID, so this never threatens file compatibility - the only
206
+ * observable effect is generated-code diff noise across unrelated
207
+ * metamodel edits, a cosmetic cost judged acceptable given it matches
208
+ * upstream EMF precedent.
209
+ *
210
+ * featureID is assigned over getEAllStructuralFeatures() (inherited-then-
211
+ * own, matching real EMF), NOT a per-class restart at 0 - a subclass's
212
+ * generated eGet/eSet needs one flat ID space covering both its inherited
213
+ * and its own features (a generated subclass typically delegates any
214
+ * featureID it doesn't recognize to `super.eGet()`/`super.eSet()`), so an
215
+ * inherited feature and a subclass's own new feature must never collide.
216
+ * Reassigning via any subclass's getEAllStructuralFeatures() is safe to
217
+ * repeat for the same feature across multiple classes in the hierarchy -
218
+ * the computed index is the same regardless of which class's perspective
219
+ * computes it, since supertype features always come first in the same
220
+ * relative order.
221
+ *
222
+ * operationID follows the exact same shape and rationale, over
223
+ * getEAllOperations() instead - added for point 4 (EOperation.
224
+ * getOperationID(), which just exposes this).
225
+ */
226
+ declare function assignFreshIds(pkg: EPackage): void;
227
+
228
+ interface RunGenerationResult {
229
+ writtenPaths: string[];
230
+ outputRoot: string;
231
+ }
232
+ /**
233
+ * `configDir` is the directory the config file itself lives in -
234
+ * `config.outputDir` resolves relative to THAT, not the process's current
235
+ * working directory, so a config file behaves identically regardless of
236
+ * where the CLI happens to be invoked from.
237
+ */
238
+ declare function runGeneration(config: GeneratorConfig, configDir: string, pkg: EPackage): Promise<RunGenerationResult>;
239
+
240
+ /**
241
+ * Resolves a config file's "templateSet" name string to a real
242
+ * TemplateSet. Only built-in sets are known here for now - a third-party
243
+ * set (e.g. a Java one) would need its own registration mechanism, not yet
244
+ * built; see NOTES.md.
245
+ */
246
+ declare function resolveTemplateSet(name: string): TemplateSet;
247
+
248
+ /** The generator's own annotation source. */
249
+ declare const TYPEMF_GENERATOR_ANNOTATION_SOURCE = "https://typemf.dev/generator";
250
+ /** Real EMF's own GenModel annotation source - where its GenModel keeps an operation's `body`. */
251
+ declare const ECLIPSE_GENMODEL_ANNOTATION_SOURCE = "http://www.eclipse.org/emf/2002/GenModel";
252
+ /** Real EMF's Ecore annotation source - the documentation fallback (real Ecore.ecore uses it for `constraints`). */
253
+ declare const ECLIPSE_ECORE_ANNOTATION_SOURCE = "http://www.eclipse.org/emf/2002/Ecore";
254
+ /**
255
+ * The layering rule shared by everything that is read from an element's
256
+ * annotations under a plain key: the `key` detail of the annotation with
257
+ * source https://typemf.dev/generator first, then the same key in the
258
+ * annotation with source `fallbackSource`. Which fallback source applies
259
+ * is decided per thing being read (see documentationOf, operationBody),
260
+ * not globally. Used as written - not checked, not interpreted; an empty
261
+ * value counts as absent (so it falls through to the next layer).
262
+ */
263
+ declare function layeredAnnotationDetail(element: EModelElement, key: string, fallbackSource: string): string | undefined;
264
+ /**
265
+ * An element's documentation: the `documentation` detail, layered - the
266
+ * typemf generator annotation first, the annotation with source
267
+ * http://www.eclipse.org/emf/2002/Ecore as the fallback.
268
+ *
269
+ * NOTE: real EMF keeps `documentation` in the GenModel source
270
+ * (http://www.eclipse.org/emf/2002/GenModel), not the Ecore one - so that
271
+ * is deliberately NOT read here: documentation an EMF-authored .ecore
272
+ * carries under GenModel is ignored unless it is also supplied under one
273
+ * of the two sources above.
274
+ */
275
+ declare function documentationOf(element: EModelElement): string | undefined;
276
+ /**
277
+ * Renders a JSDoc block from documentationOf(), or '' if there is none.
278
+ * Deliberately WITHOUT a trailing newline: every template writes
279
+ * `{{ docComment(x) }}` on its own line and puts the declaration on the
280
+ * next one, so the template's own newline is what separates them - a
281
+ * trailing newline here as well used to leave a blank line between every
282
+ * doc comment and the declaration it documents.
283
+ */
284
+ declare function docComment(element: EModelElement, indent?: string): string;
285
+ /**
286
+ * An operation's body, in strict priority order:
287
+ *
288
+ * 1. the `body` detail of the annotation with source
289
+ * https://typemf.dev/generator
290
+ * 2. the `body` detail of the annotation with source
291
+ * http://www.eclipse.org/emf/2002/GenModel
292
+ *
293
+ * Used as written - not checked, not interpreted; an empty value counts
294
+ * as absent (the operation then gets the throwing stub). Note the second
295
+ * layer is real EMF's own key, where the value is Java source: a real
296
+ * EMF-authored .ecore that carries Java bodies there will have that Java
297
+ * emitted verbatim unless a layer-1 body overrides it - which is the
298
+ * point of the ordering, and the reason a layer-1 body should be added
299
+ * to any operation whose GenModel body isn't valid TypeScript.
300
+ *
301
+ * (Earlier versions read a per-template-set key, `body:typescript`, on
302
+ * the reasoning that one shared key would be ambiguous across multiple
303
+ * target languages. Replaced on request by these two layers.)
304
+ */
305
+ declare function operationBody(operation: EOperation): string | undefined;
306
+ /**
307
+ * The annotation source for a structural feature's own custom getter/setter
308
+ * bodies: `get` and `set` details of the annotation with source
309
+ * https://typemf.dev/generator/feature. Deliberately NOT layered with a
310
+ * GenModel fallback (unlike documentationOf/operationBody) - this source
311
+ * was specified on its own, with no second layer requested.
312
+ */
313
+ declare const FEATURE_ANNOTATION_SOURCE = "https://typemf.dev/generator/feature";
314
+ /**
315
+ * A feature's custom getter body (`get` detail), used verbatim as the body
316
+ * of its bean getter method - and, so reflective access agrees with the
317
+ * real implementation (the bug found and fixed for
318
+ * EClass.getEAllStructuralFeatures: the generated eGet used to read the
319
+ * STORED FIELD directly, bypassing any computed getter entirely), as what
320
+ * `eGet` calls too. A feature with a custom getter has NO stored field at
321
+ * all - there is nothing for the old field-reading shape to read.
322
+ */
323
+ declare function featureGetter(feature: EStructuralFeature): string | undefined;
324
+ /**
325
+ * A feature's custom setter body (`set` detail), used verbatim as the body
326
+ * of its bean setter method, and what `eSet` delegates to reflectively -
327
+ * replacing the default oldValue/eBasicSetValue/eDidAdd/eDidRemove
328
+ * machinery entirely, since a custom setter defines the feature's true
329
+ * write semantics itself. Only meaningful for a SINGLE-valued feature: a
330
+ * many-valued feature never has a setter method to begin with (real EMF
331
+ * convention - the getter's EList is mutated directly), so a `set` on one
332
+ * is a no-op, flagged by findFeatureAnnotationProblems.
333
+ */
334
+ declare function featureSetter(feature: EStructuralFeature): string | undefined;
335
+ /**
336
+ * Whether a feature with both a custom getter and a custom setter genuinely needs its own backing
337
+ * field - true only when at least one of the two bodies actually references it (`this._name`).
338
+ * EEnumLiteral.literal's getter/setter do ("this._literal ?? this.getName()", "this._literal =
339
+ * value") - it needs a field. A feature whose getter/setter delegate entirely to a DIFFERENT
340
+ * feature (e.g. a "shout" getter/setter that only ever reads/writes "raw") does not - declaring an
341
+ * unused field for it would be dead weight, and checking `eIsSet`/`eUnset` against that field would
342
+ * be flatly wrong (the field never changes, so "is set" would never reflect reality). Found by
343
+ * running this exact case: an earlier version of this rule declared a field for EVERY getter+setter
344
+ * pair unconditionally, breaking any feature that delegates to another one entirely.
345
+ */
346
+ declare function customBodyNeedsOwnField(feature: EStructuralFeature): boolean;
347
+ /**
348
+ * Whether a single-valued feature has no setter at all: either a
349
+ * trivialDerivedFormula (pre-existing, hard-coded), or a custom getter
350
+ * with no custom setter (a feature computed by featureGetter, and nothing
351
+ * says how to write it back). A many-valued feature is unaffected by this
352
+ * (it never has a setter regardless).
353
+ */
354
+ declare function isReadOnlyFeature(feature: EStructuralFeature): boolean;
355
+ /**
356
+ * A changeable=false feature that isn't otherwise read-only (not derived, no custom getter) still
357
+ * needs a real setter SOMEWHERE - real EMF's own convention (matches this session's earlier
358
+ * containerClass work): a plain, impl-only setter that bypasses eSet/eDidAdd/eDidRemove entirely
359
+ * (direct field assignment, no notification), reachable only by callers with the concrete impl type
360
+ * (bootstrap code, the loader), never through the public interface or reflective eSet.
361
+ */
362
+ declare function needsImplOnlySetter(feature: EStructuralFeature): boolean;
363
+ /** findDataTypeAnnotationProblems's counterpart for feature get/set annotations: a `set` on a many-valued feature is a no-op (see featureSetter), reported rather than silently ignored. */
364
+ declare function findFeatureAnnotationProblems(pkg: EPackage): string[];
365
+ /** The real formula body for a trivially-computable derived feature, or undefined if `feature` isn't one of the four. */
366
+ declare function trivialDerivedFormula(feature: EStructuralFeature): string | undefined;
367
+ /**
368
+ * The TypeScript type name for a classifier's scalar form (no EList
369
+ * wrapping). `undefined` specifically means "this feature's type is
370
+ * Ecore's own EObject classifier" - the generator never produces its own
371
+ * api/gen/impl files for a classifier named "EObject" (matches the
372
+ * existing no-declared-supertype convention, which already falls back to
373
+ * the real @typemf/core EObject rather than generating a redundant,
374
+ * shadowing one - see eclass.njk / main.njk). The dynamic-to-typed
375
+ * conversion step that builds a real metamodel from a parsed .ecore file
376
+ * deliberately leaves such a feature's eType unset for exactly this
377
+ * reason, so `undefined` reaching here always means EObject.
378
+ */
379
+ declare function tsScalarType(type: EClassifier | ETypedElement | undefined): string;
380
+ /**
381
+ * Every classifier bound anywhere inside `genericType`'s type arguments,
382
+ * recursively (not `genericType`'s own classifier - that is the element's
383
+ * eType, handled separately) - needed so the templates can register the
384
+ * imports the emitted type arguments require.
385
+ */
386
+ declare function genericArgumentClassifiers(genericType: EGenericType | undefined): EClassifier[];
387
+ interface ResolvedDataTypeText {
388
+ /** The TypeScript type text to emit wherever this datatype is used. */
389
+ text: string;
390
+ /** The import this use needs, if the datatype has import information that applies (see resolveDataTypeTs): the symbol and the module it comes from (see ImportEntry.from). Independent of where `text` came from. */
391
+ importName?: string;
392
+ importFrom?: string;
393
+ }
394
+ /**
395
+ * The layered resolution of what TypeScript type text an EDataType
396
+ * stands for, wherever it occurs (attribute/reference type, operation
397
+ * return type, parameter type, ...), in strict priority order - and with
398
+ * NO built-in knowledge of any datatype: every mapping comes from the
399
+ * metamodel (or the mapping supplied alongside it), and a missing one is
400
+ * a fix there, not something the generator papers over.
401
+ *
402
+ * 1. The `type` detail of the datatype's annotation with source
403
+ * https://typemf.dev/generator - the text, as written.
404
+ * 2. The datatype's `instanceClassName`, as written, if set.
405
+ * 3. The datatype's name, as written.
406
+ *
407
+ * Separately from which layer supplied the text, the datatype's IMPORT
408
+ * information (TypeImportMapping: the explicit mapping entry if there is
409
+ * one, else its annotation with source https://typemf.dev/generator/import)
410
+ * says what to import when it is used: its `type` symbol, from `from` if
411
+ * the datatype is EXTERNAL (belongs to a package other than the one being
412
+ * generated) or from `internal-from` (falling back to `from`) if INTERNAL.
413
+ * If the key that applies is absent, no import is registered - the text is
414
+ * still emitted, so a missing import shows up as a compile error naming
415
+ * the type.
416
+ *
417
+ * Nothing is checked or interpreted.
418
+ */
419
+ declare function resolveDataTypeTs(dataType: EDataType): ResolvedDataTypeText;
420
+ /**
421
+ * Registers the imports a body declares - an operation's, or (see
422
+ * FEATURE_ANNOTATION_SOURCE) a feature's custom getter/setter: one
423
+ * annotation with source https://typemf.dev/generator/import per import
424
+ * (`type` = the symbol; `from` / `internal-from` reuse a datatype's detail
425
+ * names, but NOT its external/internal MEANING - see below).
426
+ *
427
+ * A body is always internal: it is source code that becomes part of the
428
+ * package being generated, in every generation, never a reference to a
429
+ * type belonging to some other package. So there is no isExternal()
430
+ * check here (contrast resolveDataTypeTs, where a datatype genuinely can
431
+ * belong to a different package) - `internal-from` applies, falling back
432
+ * to `from` only as an alternate spelling of the same (internal) path,
433
+ * not as a distinct external case. Returns '' - it is called for its
434
+ * effect, from the template that emits the body.
435
+ */
436
+ declare function registerBodyImports(element: EModelElement): string;
437
+ declare function isPrimitiveValueType(classifier: EClassifier | undefined): boolean;
438
+ /**
439
+ * The feature's own declared `defaultValueLiteral` when it has one (e.g. `changeable`'s real
440
+ * declared default is "true", not the generic zero-default) - otherwise 'false' for EBoolean, '0' for
441
+ * every other primitive numeric EDataType (the real Java-primitive zero-default). Used as a stored
442
+ * field's initial value instead of `undefined`.
443
+ */
444
+ declare function primitiveDefaultValue(feature: ETypedElement): string;
445
+ /**
446
+ * A feature's scalar TypeScript type, with " | undefined" appended UNLESS
447
+ * the type is one of the real JS-primitive-with-a-zero-value EDataTypes
448
+ * (see isPrimitiveValueType) - those never need the union, since a
449
+ * generated field for one always has a real value (the zero-default),
450
+ * never a genuine absence. Prefer this over manually appending
451
+ * " | undefined" to tsScalarType()'s result in a template, so the
452
+ * primitive-type exception lives in one place.
453
+ */
454
+ declare function tsOptionalScalarType(type: EClassifier | ETypedElement | undefined): string;
455
+ /**
456
+ * "getTitle" or, for a single-valued EBoolean attribute specifically,
457
+ * "isPublished" - matching the common convention (real EMF does this too,
458
+ * gated on the same isMany()===false && type===EBoolean condition).
459
+ * Deliberately does NOT affect many-valued features (an EList<boolean>
460
+ * has no sensible "isX" reading) or the setter (which stays "setX"
461
+ * regardless - only asked for the getter to change).
462
+ */
463
+ declare function beanGetterName(feature: EStructuralFeature): string;
464
+ /** The full TypeScript type for a feature's getter/setter, including EList<T> for many-valued features. */
465
+ declare function tsFeatureType(feature: ETypedElement): string;
466
+ /** Whether a single-valued feature's getter/setter type should be nullable (unset is a real possibility). */
467
+ declare function isOptional(feature: EStructuralFeature): boolean;
468
+ declare function isEClass(classifier: EClassifier): classifier is EClass;
469
+ declare function isEEnum(classifier: EClassifier): classifier is EEnum;
470
+ declare function isEDataType(classifier: EClassifier): classifier is EDataType;
471
+ declare function isEReference(feature: EStructuralFeature): boolean;
472
+ /** "name: Type, name2: Type2" for an operation's parameter list. */
473
+ declare function paramList(operation: EOperation): string;
474
+ /** Like paramList, but every parameter is optional ("name?: type") - for the merged implementation signature of an auto-resolved overload set (see metaclassAccessorCollision). */
475
+ declare function optionalParamList(operation: EOperation): string;
476
+ /** "name, name2" - just the argument names, e.g. for a super-call passthrough. */
477
+ declare function argList(operation: EOperation): string;
478
+ /**
479
+ * Groups a class's own operations by name, preserving first-seen order -
480
+ * needed because real Ecore.ecore has genuine overloaded operations
481
+ * (EEnum.getEEnumLiteral, by name and by value) and a TS class can only
482
+ * have ONE method body per name, unlike an interface, which supports
483
+ * overloads natively (so only the impl side needs this grouping, not the
484
+ * types/ interface side - see the loop in eclass.njk). Scoped to
485
+ * same-arity overloads only, matching what real Ecore.ecore actually
486
+ * has - a genuine arity mismatch within one name is not handled (see
487
+ * NOTES.md).
488
+ */
489
+ declare function groupOperationsByName(operations: Iterable<EOperation>): EOperation[][];
490
+ /**
491
+ * The merged implementation signature's parameter list for a group of
492
+ * same-named operations (see mergedParams; different arities included).
493
+ * For a group of exactly one operation, this is identical to paramList().
494
+ */
495
+ declare function mergedParamList(group: EOperation[]): string;
496
+ interface OverloadBranch {
497
+ operation: EOperation;
498
+ /** The TypeScript condition that selects this overload. */
499
+ condition: string;
500
+ /** The overload's body (layered, see operationBody), or undefined -> the branch throws its own "no body" error. */
501
+ body: string | undefined;
502
+ /** `const <own name> = <merged name> as <own type>;` for each of the overload's own parameter names its body actually uses. */
503
+ aliases: string[];
504
+ /** e.g. "EClass.getEStructuralFeature(featureID)", for error messages. */
505
+ label: string;
506
+ }
507
+ interface OverloadDispatch {
508
+ /** Set when the group is NOT dispatched: the whole implementation is one throwing stub with this message. */
509
+ stubMessage?: string;
510
+ branches: OverloadBranch[];
511
+ }
512
+ /**
513
+ * How the implementation of an overload group selects the overload to
514
+ * run. Per overload, in order:
515
+ *
516
+ * 1. an explicit `dispatch` detail on the overload's annotation with
517
+ * source https://typemf.dev/generator, used as written (a condition
518
+ * over the MERGED parameter names) - for cases the derivation cannot
519
+ * handle;
520
+ * 2. otherwise a condition derived from the parameters, combined with &&,
521
+ * for each position: a position the overload does not have must be
522
+ * `=== undefined`; where the overloads having the position differ in
523
+ * type, the overload's own type must be a runtime-testable primitive
524
+ * (`typeof x === 'string'`); where the type is the same everywhere but
525
+ * the position is optional, `!== undefined`.
526
+ *
527
+ * The group is NOT dispatched - the implementation stays a single
528
+ * throwing stub whose message says why - when no overload has a body,
529
+ * when a needed test cannot be derived and no explicit condition was
530
+ * given, or when two overloads end up with the same condition (they could
531
+ * never be told apart, so the second would be unreachable).
532
+ */
533
+ declare function overloadDispatch(group: EOperation[], className: string): OverloadDispatch;
534
+ /**
535
+ * An operation's return type, with " | undefined" appended only if its
536
+ * OWN declared multiplicity says the result may genuinely be absent
537
+ * (not required - lowerBound < 1) - unlike tsOptionalScalarType() (used
538
+ * for structural features' getters), which appends it unconditionally
539
+ * for every non-primitive type. Operations need this per-operation check
540
+ * instead of a blanket rule: most operations (isSuperTypeOf(): boolean,
541
+ * getClassifierID(): number, ...) are genuinely never-null by their own
542
+ * declared multiplicity, and blanket-appending | undefined to every one
543
+ * of them would be wrong - unlike structural features, where treating
544
+ * every single-valued reference as "may be unset" is the reasonable
545
+ * default.
546
+ *
547
+ * This is the fix for a real, confirmed gap: real Ecore.ecore's own
548
+ * EModelElement.getEAnnotation(source) has no declared lowerBound (real
549
+ * EMF's default there is 0 - not required), meaning the operation is
550
+ * genuinely allowed to return nothing when no annotation with that
551
+ * source is attached - but the generator previously always emitted the
552
+ * bare, non-optional return type for every operation regardless.
553
+ */
554
+ declare function tsOperationReturnType(op: EOperation): string;
555
+ /** The merged implementation signature's return type for a group - the union of every overload's OWN (possibly optional) return type via tsOperationReturnType(), deduplicated (so an all-identical group collapses to just that one type, not a redundant self-union). */
556
+ declare function mergedReturnType(group: EOperation[]): string;
557
+ /**
558
+ * Uppercases only the first character, leaving the rest untouched -
559
+ * NOT the same as Nunjucks' built-in `capitalize` filter, which mirrors
560
+ * Jinja2's and lowercases everything after the first letter (breaking any
561
+ * camelCase name: "pageCount" -> "Pagecount"). Used to build bean-style
562
+ * accessor names ("pageCount" -> "getPageCount") without corrupting them.
563
+ */
564
+ declare function ucfirst(s: string): string;
565
+ /** The generated Package singleton class name for a package, e.g. "library" -> "LibraryPackage". */
566
+ declare function packageClassName(pkg: EPackage): string;
567
+ declare function factoryClassName(pkg: EPackage): string;
568
+ declare function switchClassName(pkg: EPackage): string;
569
+ declare function typeGuardsClassName(pkg: EPackage): string;
570
+ /** The EClass's single supertype for TS `extends` purposes, or undefined - see NOTES.md on multiple inheritance. */
571
+ declare function superType(eClass: EClass): EClass | undefined;
572
+ /** eClass itself, then each ancestor in turn (via superType()), nearest first. */
573
+ declare function superTypeChain(eClass: EClass): EClass[];
574
+ /**
575
+ * Whether this class descends from (or is) "EClassifier" - needed
576
+ * specifically for self-hosting Ecore.ecore: classifierID is internal
577
+ * dispatch bookkeeping, not a real modeled Ecore feature, so it's never
578
+ * emitted by the ordinary feature-driven getter/setter generation - but
579
+ * bootstrap code constructing the metamodel's own classifier shells
580
+ * (which ARE real EClassifier-derived instances, e.g. EClassImpl,
581
+ * EDataTypeImpl) genuinely needs to set it. Confirmed as a real,
582
+ * necessary gap by actually running self-hosted bootstrap code, not
583
+ * assumed - see NOTES.md.
584
+ */
585
+ declare function isClassifierDerived(eClass: EClass): boolean;
586
+ /** The featureID analog of isClassifierDerived() - same reasoning, same real gap found the same way (see NOTES.md). */
587
+ declare function isStructuralFeatureDerived(eClass: EClass): boolean;
588
+ /** The operationID analog of isClassifierDerived()/isStructuralFeatureDerived() - added for point 4. */
589
+ declare function isOperationDerived(eClass: EClass): boolean;
590
+ /**
591
+ * Whether a real EOperation's name collides with a hand-added bookkeeping
592
+ * method (classifierID/featureID - see isClassifierDerived/
593
+ * isStructuralFeatureDerived's own doc comments). Real Ecore.ecore
594
+ * genuinely declares both "EClassifier.getClassifierID(): EInt" and
595
+ * "EStructuralFeature.getFeatureID(): EInt" as real, zero-arg operations
596
+ * - colliding, by name, with exactly the bookkeeping getters added for
597
+ * the self-hosting bootstrap fix. Confirmed directly against the real
598
+ * file before excluding these operations from the generic,
599
+ * throwing-stub-generating operation loop, not assumed - see NOTES.md.
600
+ */
601
+ declare function isBookkeepingOperation(op: EOperation, eClass: EClass): boolean;
602
+ /**
603
+ * Whether classifier `classifierName`'s zero-arg metaclass accessor
604
+ * (get{ClassifierName}(), generated directly on the package class for
605
+ * every classifier the package declares) collides with a real EOperation
606
+ * inherited by the package class from one of ITS OWN ancestors. Only
607
+ * relevant when self-hosting - "EPackage" (the package class's own
608
+ * superclass) is itself one of the metamodel's declared classifiers only
609
+ * in that case; an ordinary metamodel never has this situation at all,
610
+ * so this always returns undefined for one.
611
+ *
612
+ * General by design, not hardcoded to specific names: real Ecore.ecore
613
+ * happens to have exactly two instances of this shape
614
+ * (EModelElement.getEAnnotation(source), EPackage.getEClassifier(name)),
615
+ * found by a systematic scan across every classifier's own+inherited
616
+ * generated members, not by inspection - confirmed nothing else in the
617
+ * real file collides this way (or any other way) - see NOTES.md. This
618
+ * function exists so the SAME resolution automatically covers any future
619
+ * metamodel with a classifier sharing a name with one of the package
620
+ * class's own inherited operations, without needing another hardcoded
621
+ * exclusion added by hand each time one is found.
622
+ */
623
+ declare function metaclassAccessorCollision(classifierName: string, pkg: EPackage): EOperation | undefined;
624
+ /**
625
+ * Distinct, non-primitive classifier names referenced by any metaclass-
626
+ * accessor collision's own signature (return type or parameter types) in
627
+ * `pkg` - needed for {Pkg}Package.ts's own import line, general rather
628
+ * than hardcoding "EAnnotation" (real Ecore.ecore's one actual instance
629
+ * today): a future metamodel's own colliding operation could return or
630
+ * accept any classifier at all.
631
+ */
632
+ declare function metaclassAccessorCollisionTypeNames(pkg: EPackage): string[];
633
+ /**
634
+ * A general validation pass, meant to run during generation (not just as
635
+ * a one-off diagnostic): for every classifier in `pkg`, checks every name
636
+ * that would be generated as one of its members - own or inherited bean
637
+ * accessors, real operations, bookkeeping methods (classifierID/
638
+ * featureID), and (for the package class specifically) metaclass
639
+ * accessors - for a collision with something else generated under the
640
+ * same name, EXCLUDING the two patterns the generator already resolves
641
+ * correctly on its own (isBookkeepingOperation's exclusion, and
642
+ * metaclassAccessorCollision's automatic overload-set generation).
643
+ * Anything left over is a genuine, unhandled collision that would
644
+ * silently produce wrong runtime behaviour (the later declaration wins,
645
+ * no compile error) - generation should refuse to proceed rather than
646
+ * emit it silently.
647
+ *
648
+ * Deliberately reuses the same real helpers the templates themselves
649
+ * use (isBookkeepingOperation, groupOperationsByName, beanGetterName,
650
+ * metaclassAccessorCollision) rather than a parallel reimplementation,
651
+ * so this can never silently drift out of sync with what actually gets
652
+ * generated.
653
+ */
654
+ interface MemberCollision {
655
+ classifierName: string;
656
+ memberName: string;
657
+ sources: string[];
658
+ }
659
+ /**
660
+ * Every classifier, feature, operation, parameter, and enum literal in `pkg` genuinely needs a name -
661
+ * code generation cannot produce a meaningful `class {name} {}`/`get{name}()`/etc. for one that has
662
+ * none (real Ecore.ecore's own `name` attribute is optional, matching real EMF, so this is a real,
663
+ * reachable state, not a hypothetical one). Checked once, up front, via the same validate() hook every
664
+ * other model-level problem goes through - so every OTHER function in this file can safely assert a
665
+ * name is present (`!`) rather than re-checking it at every one of the many call sites that need one,
666
+ * confident generate() would have already refused to run otherwise.
667
+ */
668
+ declare function findUnnamedElements(pkg: EPackage): string[];
669
+ declare function findUnresolvedCollisions(pkg: EPackage): MemberCollision[];
670
+ /**
671
+ * Distinct EClass/EEnum type names referenced by `features` (an EClass or
672
+ * EEnum's own attribute/reference types) that need their own import
673
+ * statement - EDataType primitives (string/number/boolean/Date) never do.
674
+ * A TS helper rather than a Nunjucks loop with manual dedup, since
675
+ * template-level array mutation is awkward without extra Nunjucks
676
+ * extensions - matches the "templates handle structure, TS computes"
677
+ * principle.
678
+ */
679
+ declare function referencedApiTypes(features: Iterable<EStructuralFeature>, ...excludeTypeNames: string[]): string[];
680
+ /**
681
+ * The operation-scoped analog of referencedApiTypes, for the identical
682
+ * "needs its own import statement" reason - but operations weren't
683
+ * scanned by anything at all before this existed, a genuine gap: an
684
+ * operation's return type or any parameter's type can be any classifier,
685
+ * including a non-primitive EDataType (real Ecore.ecore's own
686
+ * EClassifier.getContainerClass(): EJavaClass, for one) - unlike
687
+ * features, where only EClass/EEnum ever need an import (a plain
688
+ * EDataType-typed feature is always one of the handful of TS-primitive-
689
+ * mapped names). Confirmed as a real, previously-missing import by
690
+ * actually generating real Ecore.ecore and type-checking the merged
691
+ * output, not assumed - see NOTES.md.
692
+ */
693
+ declare function referencedOperationTypes(operations: Iterable<EOperation>, ...excludeTypeNames: string[]): string[];
694
+ /**
695
+ * Nunjucks 3.2.4 silently does not support Jinja2's inline `{% for x in y
696
+ * if cond %}` for-loop filter syntax (it parses without error but produces
697
+ * wrong output - verified empirically, not assumed) - these pre-filtered
698
+ * helpers replace every case a template would otherwise want that syntax
699
+ * for, consistent with keeping templates dumb.
700
+ */
701
+ /**
702
+ * Safely emits a metamodel-supplied string as a TypeScript string literal,
703
+ * via JSON.stringify() - handles quotes, apostrophes, newlines, backslashes,
704
+ * anything. Every place a template interpolates a name/label/annotation
705
+ * value from the metamodel into generated source needs this, not naive
706
+ * single-quote wrapping - annotation `details` values in particular are
707
+ * often free-form prose (GenModel documentation, validation constraint
708
+ * text) and are genuinely likely to contain apostrophes or multi-line
709
+ * text, which naive interpolation would turn into broken generated code.
710
+ */
711
+ declare function jsString(value: string | undefined): string;
712
+ /**
713
+ * Nunjucks' {% for %} reliably iterates plain objects and arrays, but not
714
+ * necessarily a real ES6 Map instance the same way (its object-iteration
715
+ * path likely uses Object.keys(), which is empty for a Map's own
716
+ * enumerable properties - the entries live internally, not as own
717
+ * properties). Converting to a plain array here avoids relying on
718
+ * behavior that was never actually verified.
719
+ */
720
+ declare function detailsEntries(annotation: EAnnotation): {
721
+ key: string;
722
+ value: string;
723
+ }[];
724
+ declare function eClassesOf(pkg: EPackage): EClass[];
725
+ declare function concreteEClassesOf(pkg: EPackage): EClass[];
726
+ /**
727
+ * Finds an EClass by exact name within the package, or undefined if none
728
+ * matches - used specifically for the EFactory/{Pkg}Factory unification
729
+ * special case (see efactory.njk): when a metamodel models a classifier
730
+ * literally named "EFactory" (as real Ecore.ecore does), the package-level
731
+ * generated Factory extends ITS generated impl instead of the generic
732
+ * @typemf/core EFactoryImpl base, matching real EMF's own
733
+ * EcoreFactoryImpl-extends-EFactoryImpl design. Ordinary metamodels (no
734
+ * "EFactory" classifier) are unaffected.
735
+ */
736
+ declare function findEClassByName(pkg: EPackage, name: string): EClass | undefined;
737
+ declare function singleValuedFeatures(features: Iterable<EStructuralFeature>): EStructuralFeature[];
738
+
739
+ declare const typescriptTemplateSet: TemplateSet;
740
+
741
+ export { ECLIPSE_ECORE_ANNOTATION_SOURCE, ECLIPSE_GENMODEL_ANNOTATION_SOURCE, FEATURE_ANNOTATION_SOURCE, type GeneratedFile, type GeneratorConfig, type MemberCollision, type OverloadBranch, type OverloadDispatch, type ResolvedDataTypeText, type RunGenerationResult, TYPEMF_GENERATOR_ANNOTATION_SOURCE, type TemplateSet, argList, assignFreshIds, beanGetterName, buildEcoreMetaSchema, concreteEClassesOf, convertDynamicEcoreToTyped, customBodyNeedsOwnField, detailsEntries, docComment, documentationOf, eClassesOf, factoryClassName, featureGetter, featureSetter, findEClassByName, findFeatureAnnotationProblems, findUnnamedElements, findUnresolvedCollisions, generate, genericArgumentClassifiers, groupOperationsByName, isBookkeepingOperation, isClassifierDerived, isEClass, isEDataType, isEEnum, isEReference, isOperationDerived, isOptional, isPrimitiveValueType, isReadOnlyFeature, isStructuralFeatureDerived, jsString, layeredAnnotationDetail, loadConfig, loadEcorePackage, mergedParamList, mergedReturnType, metaclassAccessorCollision, metaclassAccessorCollisionTypeNames, needsImplOnlySetter, operationBody, optionalParamList, overloadDispatch, packageClassName, paramList, primitiveDefaultValue, referencedApiTypes, referencedOperationTypes, registerBodyImports, resolveDataTypeTs, resolveTemplateSet, runGeneration, singleValuedFeatures, superType, superTypeChain, switchClassName, trivialDerivedFormula, tsFeatureType, tsOperationReturnType, tsOptionalScalarType, tsScalarType, typeGuardsClassName, typescriptTemplateSet, ucfirst };