@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
package/dist/index.d.ts
ADDED
|
@@ -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 };
|