@valkyrjaio/sindri 26.6.65 → 26.6.67
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/dist/bin/sindri.cjs +496 -124
- package/package.json +1 -1
- package/src/Sindri/Ast/Abstract/AstReader.ts +71 -5
- package/src/Sindri/Ast/Data/Result/ListenerAttributeResult.ts +8 -1
- package/src/Sindri/Ast/Data/Result/ListenerProviderResult.ts +6 -0
- package/src/Sindri/Ast/ListenerAttributeReader.ts +42 -4
- package/src/Sindri/Ast/ListenerProviderReader.ts +70 -7
- package/src/Sindri/Ast/RouteProviderReader.ts +41 -7
- package/src/Sindri/Ast/Throwable/Exception/AstUnreadableListenerException.ts +11 -0
- package/src/Sindri/Ast/Throwable/Exception/AstUnreadableRouteException.ts +11 -0
- package/src/Sindri/Constant/SindriInfo.ts +2 -2
- package/src/Sindri/Generate/Abstract/GenerateDataFromAst.ts +23 -10
- package/src/Sindri/Generator/Ast/Event/AstEventDataFileGenerator.ts +288 -13
- package/src/Sindri/Generator/Event/Contract/EventDataFileGeneratorContract.ts +16 -2
- package/src/Sindri/Generator/Throwable/Exception/GeneratorUnreadableListenerException.ts +11 -0
package/package.json
CHANGED
|
@@ -285,21 +285,87 @@ export abstract class AstReader {
|
|
|
285
285
|
return names;
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
-
if (ts.
|
|
289
|
-
|
|
288
|
+
if (ts.isBindingElement(node)) {
|
|
289
|
+
// The `config` of `{ config: c }` names a property of the value, where the `CliA` of
|
|
290
|
+
// `{ [CliA.KEY]: c }` is an expression the generated file evaluates.
|
|
291
|
+
if (node.propertyName !== undefined && ts.isComputedPropertyName(node.propertyName)) {
|
|
292
|
+
this.collectReferencedIdentifiers(node.propertyName.expression, names);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
this.collectReferencedIdentifiers(node.name, names);
|
|
296
|
+
|
|
297
|
+
return node.initializer === undefined ? names : this.collectReferencedIdentifiers(node.initializer, names);
|
|
290
298
|
}
|
|
291
299
|
|
|
292
|
-
|
|
293
|
-
|
|
300
|
+
// An identifier a `name` slot carries declares or keys the name, such as the `HELP` of
|
|
301
|
+
// `CommandName.HELP`, and only a shorthand property references the name it carries.
|
|
302
|
+
const slotName: ts.Node | undefined =
|
|
303
|
+
'name' in node &&
|
|
304
|
+
node.name !== undefined &&
|
|
305
|
+
ts.isIdentifier(node.name as ts.Node) &&
|
|
306
|
+
!ts.isShorthandPropertyAssignment(node)
|
|
307
|
+
? (node.name as ts.Node)
|
|
308
|
+
: undefined;
|
|
309
|
+
|
|
310
|
+
ts.forEachChild(node, (child: ts.Node): void => {
|
|
311
|
+
if (child !== slotName) {
|
|
312
|
+
this.collectReferencedIdentifiers(child, names);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
return names;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* The names the provider file declares anywhere in itself, less the file's own base name, which
|
|
321
|
+
* is the name the generated data file imports the provider by.
|
|
322
|
+
*/
|
|
323
|
+
protected collectFileNames(sourceFile: SourceFile, filePath: string): Set<string> {
|
|
324
|
+
const names = this.collectDeclaredNames(sourceFile.compilerNode);
|
|
325
|
+
|
|
326
|
+
names.delete(path.basename(filePath, '.ts'));
|
|
327
|
+
|
|
328
|
+
return names;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* The names a node declares, such as the parameters of an inline handler.
|
|
333
|
+
*
|
|
334
|
+
* A name any part of an expression declares counts as declared throughout it, which reads a
|
|
335
|
+
* shadowed name as declared rather than stopping on it.
|
|
336
|
+
*/
|
|
337
|
+
protected collectDeclaredNames(node: ts.Node, names: Set<string> = new Set<string>()): Set<string> {
|
|
338
|
+
if (AstReader.declaresName(node) && node.name !== undefined && ts.isIdentifier(node.name)) {
|
|
339
|
+
names.add(node.name.text);
|
|
294
340
|
}
|
|
295
341
|
|
|
296
342
|
ts.forEachChild(node, (child: ts.Node): void => {
|
|
297
|
-
this.
|
|
343
|
+
this.collectDeclaredNames(child, names);
|
|
298
344
|
});
|
|
299
345
|
|
|
300
346
|
return names;
|
|
301
347
|
}
|
|
302
348
|
|
|
349
|
+
/**
|
|
350
|
+
* Whether a node's `name` declares a binding rather than naming one.
|
|
351
|
+
*
|
|
352
|
+
* A property access, a shorthand property and an object-literal key all carry a `name` that
|
|
353
|
+
* references or keys instead of declaring, so the kind decides rather than the slot.
|
|
354
|
+
*/
|
|
355
|
+
protected static declaresName(node: ts.Node): node is ts.NamedDeclaration {
|
|
356
|
+
return (
|
|
357
|
+
ts.isParameter(node) ||
|
|
358
|
+
ts.isVariableDeclaration(node) ||
|
|
359
|
+
ts.isBindingElement(node) ||
|
|
360
|
+
ts.isFunctionDeclaration(node) ||
|
|
361
|
+
ts.isFunctionExpression(node) ||
|
|
362
|
+
ts.isClassDeclaration(node) ||
|
|
363
|
+
ts.isClassExpression(node) ||
|
|
364
|
+
ts.isEnumDeclaration(node) ||
|
|
365
|
+
ts.isModuleDeclaration(node)
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
303
369
|
/**
|
|
304
370
|
* Extract service ID strings from keys of an object-returning method.
|
|
305
371
|
*
|
|
@@ -9,5 +9,12 @@
|
|
|
9
9
|
import type { ts } from 'ts-morph';
|
|
10
10
|
|
|
11
11
|
export class ListenerAttributeResult {
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* @param listeners A Map, because an object reorders a key that reads as an integer
|
|
14
|
+
* @param importMap Class short name → absolute file path for the handler imports
|
|
15
|
+
*/
|
|
16
|
+
constructor(
|
|
17
|
+
readonly listeners: ReadonlyMap<string, ts.Expression> = new Map(),
|
|
18
|
+
readonly importMap: Record<string, string> = {},
|
|
19
|
+
) {}
|
|
13
20
|
}
|
|
@@ -9,15 +9,21 @@
|
|
|
9
9
|
import type { ts } from 'ts-morph';
|
|
10
10
|
|
|
11
11
|
export class ListenerProviderResult {
|
|
12
|
+
/**
|
|
13
|
+
* @param listenerImports Local name → absolute file path for every class the `listeners`
|
|
14
|
+
* expressions reference
|
|
15
|
+
*/
|
|
12
16
|
constructor(
|
|
13
17
|
readonly listenerClasses: readonly string[] = [],
|
|
14
18
|
readonly listeners: readonly ts.Expression[] = [],
|
|
19
|
+
readonly listenerImports: Readonly<Record<string, string>> = {},
|
|
15
20
|
) {}
|
|
16
21
|
|
|
17
22
|
merge(other: ListenerProviderResult): ListenerProviderResult {
|
|
18
23
|
return new ListenerProviderResult(
|
|
19
24
|
[...new Set([...this.listenerClasses, ...other.listenerClasses])],
|
|
20
25
|
[...this.listeners, ...other.listeners],
|
|
26
|
+
{ ...this.listenerImports, ...other.listenerImports },
|
|
21
27
|
);
|
|
22
28
|
}
|
|
23
29
|
}
|
|
@@ -35,13 +35,15 @@ export class ListenerAttributeReader extends AstReader implements ListenerAttrib
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
const { classDecl, useMap, currentClass } = context;
|
|
38
|
-
const listeners
|
|
38
|
+
const listeners = new Map<string, ts.Expression>();
|
|
39
|
+
const listenerData: ListenerData[] = [];
|
|
39
40
|
|
|
40
41
|
for (const decorator of this.findDecoratorsOnNode(classDecl, 'Listener', useMap, filePath)) {
|
|
41
42
|
const data = this.buildListenerData(decorator, useMap, filePath, currentClass, classDecl, undefined);
|
|
42
43
|
|
|
43
44
|
if (data !== null) {
|
|
44
|
-
listeners
|
|
45
|
+
listeners.set(data.name, this.buildListenerExpr(data));
|
|
46
|
+
listenerData.push(data);
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
49
|
|
|
@@ -50,12 +52,48 @@ export class ListenerAttributeReader extends AstReader implements ListenerAttrib
|
|
|
50
52
|
const data = this.buildListenerData(decorator, useMap, filePath, currentClass, classDecl, method);
|
|
51
53
|
|
|
52
54
|
if (data !== null) {
|
|
53
|
-
listeners
|
|
55
|
+
listeners.set(data.name, this.buildListenerExpr(data));
|
|
56
|
+
listenerData.push(data);
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
return new ListenerAttributeResult(
|
|
61
|
+
return new ListenerAttributeResult(
|
|
62
|
+
listeners,
|
|
63
|
+
this.buildImportMap(listenerData, useMap, filePath, currentClass),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Build the class-name → absolute-file-path map for every handler class the generated data
|
|
69
|
+
* cache references.
|
|
70
|
+
*/
|
|
71
|
+
protected buildImportMap(
|
|
72
|
+
listenerData: readonly ListenerData[],
|
|
73
|
+
useMap: Record<string, string>,
|
|
74
|
+
currentFilePath: string,
|
|
75
|
+
currentClass: string,
|
|
76
|
+
): Record<string, string> {
|
|
77
|
+
const importMap: Record<string, string> = {};
|
|
78
|
+
|
|
79
|
+
for (const data of listenerData) {
|
|
80
|
+
if (data.handler === null) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const shortName = data.handler.class.slice(data.handler.class.lastIndexOf('\\') + 1);
|
|
85
|
+
|
|
86
|
+
const filePath =
|
|
87
|
+
shortName === currentClass
|
|
88
|
+
? currentFilePath
|
|
89
|
+
: this.resolveClassToFilePath(shortName, useMap, currentFilePath);
|
|
90
|
+
|
|
91
|
+
if (filePath !== '') {
|
|
92
|
+
importMap[shortName] = filePath;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return importMap;
|
|
59
97
|
}
|
|
60
98
|
|
|
61
99
|
protected buildListenerData(
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
* Released under the MIT License. See LICENSE.md for details.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import
|
|
9
|
+
import { ts } from 'ts-morph';
|
|
10
10
|
|
|
11
11
|
import { AstReader } from './Abstract/AstReader.ts';
|
|
12
12
|
import { ListenerProviderResult } from './Data/Result/ListenerProviderResult.ts';
|
|
13
|
+
import { AstUnreadableListenerException } from './Throwable/Exception/AstUnreadableListenerException.ts';
|
|
13
14
|
|
|
14
15
|
import type { ListenerProviderReaderContract } from './Contract/ListenerProviderReaderContract.ts';
|
|
15
16
|
|
|
@@ -27,18 +28,80 @@ export class ListenerProviderReader extends AstReader implements ListenerProvide
|
|
|
27
28
|
|
|
28
29
|
const useMap = this.buildUseMap(sourceFile);
|
|
29
30
|
const methods = this.indexMethods(classDecl);
|
|
31
|
+
const listeners = this.extractListeners(methods[ListenerProviderReader.METHOD_LISTENERS]);
|
|
30
32
|
|
|
31
33
|
return new ListenerProviderResult(
|
|
32
34
|
this.extractClassListFromValues(methods[ListenerProviderReader.METHOD_LISTENER_CLASSES], useMap, filePath),
|
|
33
|
-
|
|
35
|
+
listeners,
|
|
36
|
+
this.extractListenerImports(listeners, useMap, filePath, this.collectFileNames(sourceFile, filePath)),
|
|
34
37
|
);
|
|
35
38
|
}
|
|
36
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Extract the listeners a provider's `getListeners()` method returns, which the generator
|
|
42
|
+
* emits verbatim.
|
|
43
|
+
*/
|
|
37
44
|
protected extractListeners(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
method: ReturnType<typeof this.indexMethods>[string] | undefined,
|
|
46
|
+
): readonly ts.Expression[] {
|
|
47
|
+
if (method === undefined) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const array = this.findReturnedArray(method);
|
|
52
|
+
|
|
53
|
+
if (array === undefined) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return array.elements.filter((element) => this.isConstructedListener(element));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Whether an expression declares a listener through a construction, with or without a chain. */
|
|
61
|
+
protected isConstructedListener(expression: ts.Expression): boolean {
|
|
62
|
+
let current = this.unwrapTypeAssertions(expression);
|
|
63
|
+
|
|
64
|
+
while (ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
|
|
65
|
+
current = this.unwrapTypeAssertions(current.expression.expression);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return ts.isNewExpression(current);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolve every class the listener expressions reference to its absolute file path, so the
|
|
73
|
+
* generated data cache can import them.
|
|
74
|
+
*/
|
|
75
|
+
protected extractListenerImports(
|
|
76
|
+
listeners: readonly ts.Expression[],
|
|
77
|
+
useMap: Record<string, string>,
|
|
78
|
+
filePath: string,
|
|
79
|
+
fileNames: ReadonlySet<string>,
|
|
80
|
+
): Record<string, string> {
|
|
81
|
+
const imports: Record<string, string> = {};
|
|
82
|
+
|
|
83
|
+
for (const listener of listeners) {
|
|
84
|
+
const declaredNames = this.collectDeclaredNames(listener);
|
|
85
|
+
|
|
86
|
+
for (const name of this.collectReferencedIdentifiers(listener)) {
|
|
87
|
+
const resolved = this.resolveImportToFilePath(name, useMap, filePath);
|
|
88
|
+
|
|
89
|
+
if (resolved !== '') {
|
|
90
|
+
imports[name] = resolved;
|
|
91
|
+
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// A name the provider file declares for itself is one the generated data file, in
|
|
96
|
+
// another module, cannot reach. Every other unresolved name is a global.
|
|
97
|
+
if (fileNames.has(name) && !declaredNames.has(name)) {
|
|
98
|
+
throw new AstUnreadableListenerException(
|
|
99
|
+
`Cannot read the listener '${listener.getText()}': the generated data cannot import '${name}', which the provider declares for itself.`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return imports;
|
|
43
106
|
}
|
|
44
107
|
}
|
|
@@ -10,6 +10,7 @@ import { ts } from 'ts-morph';
|
|
|
10
10
|
|
|
11
11
|
import { AstReader } from './Abstract/AstReader.ts';
|
|
12
12
|
import { RouteProviderResult } from './Data/Result/RouteProviderResult.ts';
|
|
13
|
+
import { AstUnreadableRouteException } from './Throwable/Exception/AstUnreadableRouteException.ts';
|
|
13
14
|
|
|
14
15
|
import type { RouteProviderReaderContract } from './Contract/RouteProviderReaderContract.ts';
|
|
15
16
|
|
|
@@ -28,21 +29,22 @@ export class RouteProviderReader extends AstReader implements RouteProviderReade
|
|
|
28
29
|
const useMap = this.buildUseMap(sourceFile);
|
|
29
30
|
const methods = this.indexMethods(classDecl);
|
|
30
31
|
const routes = this.extractRoutes(methods[RouteProviderReader.METHOD_ROUTES], useMap, filePath);
|
|
32
|
+
const routeImports = this.extractRouteImports(routes, useMap, filePath);
|
|
33
|
+
|
|
34
|
+
// A further list of route expressions needs a call of its own, because a name is validated
|
|
35
|
+
// against the expression that references it.
|
|
36
|
+
this.validateRouteImports(routes, routeImports, useMap, this.collectFileNames(sourceFile, filePath));
|
|
31
37
|
|
|
32
38
|
return new RouteProviderResult(
|
|
33
39
|
this.extractClassListFromValues(methods[RouteProviderReader.METHOD_CONTROLLER_CLASSES], useMap, filePath),
|
|
34
40
|
routes,
|
|
35
|
-
|
|
41
|
+
routeImports,
|
|
36
42
|
);
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
/**
|
|
40
|
-
* Resolve every class the imperative route expressions reference to its
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* Names that are not imported by the provider file — locals, globals, and
|
|
44
|
-
* the provider's own class (which the generator imports separately) — have
|
|
45
|
-
* no entry in the use map and are skipped.
|
|
46
|
+
* Resolve every class the imperative route expressions reference to its absolute file path, so
|
|
47
|
+
* the generated data cache can import them.
|
|
46
48
|
*/
|
|
47
49
|
protected extractRouteImports(
|
|
48
50
|
routes: readonly ts.Expression[],
|
|
@@ -64,6 +66,38 @@ export class RouteProviderReader extends AstReader implements RouteProviderReade
|
|
|
64
66
|
return imports;
|
|
65
67
|
}
|
|
66
68
|
|
|
69
|
+
/** Stop on a name a route references that the generated data file, in another module, cannot reach. */
|
|
70
|
+
protected validateRouteImports(
|
|
71
|
+
routes: readonly ts.Expression[],
|
|
72
|
+
routeImports: Readonly<Record<string, string>>,
|
|
73
|
+
useMap: Record<string, string>,
|
|
74
|
+
fileNames: ReadonlySet<string>,
|
|
75
|
+
): void {
|
|
76
|
+
for (const route of routes) {
|
|
77
|
+
const declaredNames = this.collectDeclaredNames(route);
|
|
78
|
+
|
|
79
|
+
for (const name of this.collectReferencedIdentifiers(route)) {
|
|
80
|
+
if (routeImports[name] !== undefined || declaredNames.has(name)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (fileNames.has(name)) {
|
|
85
|
+
throw new AstUnreadableRouteException(
|
|
86
|
+
`Cannot read the route '${route.getText()}': the generated data cannot import '${name}', which the provider declares for itself.`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// An import the reader resolves to no TypeScript file leaves the generated data no
|
|
91
|
+
// path to import the name from. Every name left is a global.
|
|
92
|
+
if (useMap[name] !== undefined) {
|
|
93
|
+
throw new AstUnreadableRouteException(
|
|
94
|
+
`Cannot read the route '${route.getText()}': the import of '${name}' resolves to no TypeScript file.`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
67
101
|
/**
|
|
68
102
|
* Extract the route objects returned by a provider's `getRoutes()` method.
|
|
69
103
|
*
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Sindri package.
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2016-present Melech Mizrachi
|
|
5
|
+
*
|
|
6
|
+
* Released under the MIT License. See LICENSE.md for details.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { AstInvalidArgumentException } from './Abstract/AstInvalidArgumentException.ts';
|
|
10
|
+
|
|
11
|
+
export class AstUnreadableListenerException extends AstInvalidArgumentException {}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Sindri package.
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2016-present Melech Mizrachi
|
|
5
|
+
*
|
|
6
|
+
* Released under the MIT License. See LICENSE.md for details.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { AstInvalidArgumentException } from './Abstract/AstInvalidArgumentException.ts';
|
|
10
|
+
|
|
11
|
+
export class AstUnreadableRouteException extends AstInvalidArgumentException {}
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
export class SindriInfo {
|
|
10
|
-
static readonly VERSION = '26.6.
|
|
11
|
-
static readonly VERSION_BUILD_DATE_TIME = 'October
|
|
10
|
+
static readonly VERSION = '26.6.67' as const;
|
|
11
|
+
static readonly VERSION_BUILD_DATE_TIME = 'October 13 2026 12:46:02 MST' as const;
|
|
12
12
|
static readonly ICON = `\
|
|
13
13
|
██████████
|
|
14
14
|
████ ████
|
|
@@ -264,7 +264,10 @@ export abstract class GenerateDataFromAst extends GenerateFromAst {
|
|
|
264
264
|
.withAddedMessages(new Message('Generating Event Data..........................'))
|
|
265
265
|
.writeMessages();
|
|
266
266
|
|
|
267
|
-
|
|
267
|
+
// A Map, because an object reorders a key that reads as an integer.
|
|
268
|
+
const allListeners = new Map<string, ts.Expression>();
|
|
269
|
+
const imperativeListeners: ts.Expression[] = [];
|
|
270
|
+
const importMap: Record<string, string> = {};
|
|
268
271
|
|
|
269
272
|
for (const providerClass of listenerProviders) {
|
|
270
273
|
const filePath = this.fqnToFilePath(providerClass, config.namespace, config.dir);
|
|
@@ -275,6 +278,13 @@ export abstract class GenerateDataFromAst extends GenerateFromAst {
|
|
|
275
278
|
|
|
276
279
|
const providerResult = this.listenerProviderReader.readFile(filePath);
|
|
277
280
|
|
|
281
|
+
if (providerResult.listeners.length > 0) {
|
|
282
|
+
// The expressions are emitted verbatim, so every class they name is imported.
|
|
283
|
+
imperativeListeners.push(...providerResult.listeners);
|
|
284
|
+
importMap[path.basename(filePath, '.ts')] = this.importSpecifier(config.dataPath, filePath);
|
|
285
|
+
this.mergeReaderImports(importMap, providerResult.listenerImports, config);
|
|
286
|
+
}
|
|
287
|
+
|
|
278
288
|
for (const listenerClass of providerResult.listenerClasses) {
|
|
279
289
|
const listenerPath = this.fqnToFilePath(listenerClass, config.namespace, config.dir);
|
|
280
290
|
|
|
@@ -284,15 +294,22 @@ export abstract class GenerateDataFromAst extends GenerateFromAst {
|
|
|
284
294
|
|
|
285
295
|
const attrResult = this.listenerAttributeReader.readFile(listenerPath);
|
|
286
296
|
|
|
287
|
-
|
|
297
|
+
for (const [name, listener] of attrResult.listeners) {
|
|
298
|
+
allListeners.set(name, listener);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
this.mergeReaderImports(importMap, attrResult.importMap, config);
|
|
288
302
|
}
|
|
289
303
|
}
|
|
290
304
|
|
|
291
|
-
|
|
305
|
+
this.eventGenerator.classImportMap = importMap;
|
|
306
|
+
|
|
307
|
+
const status = this.eventGenerator.generateMergedFile(
|
|
292
308
|
config.dataPath,
|
|
293
309
|
'AppEventData',
|
|
294
310
|
config.dataNamespace,
|
|
295
311
|
allListeners,
|
|
312
|
+
imperativeListeners,
|
|
296
313
|
);
|
|
297
314
|
|
|
298
315
|
return this.addMessagesForGenerateStatus(output, status).withAddedMessages(new NewLine()).writeMessages();
|
|
@@ -428,16 +445,12 @@ export abstract class GenerateDataFromAst extends GenerateFromAst {
|
|
|
428
445
|
}
|
|
429
446
|
|
|
430
447
|
/**
|
|
431
|
-
* Merge the class-name → absolute-file-path imports a
|
|
432
|
-
*
|
|
433
|
-
* file's import map, converting each absolute path to the specifier the
|
|
434
|
-
* generated file imports it by — relative to the output directory for
|
|
435
|
-
* application source, or the owning package's own specifier for a class an
|
|
436
|
-
* installed package exposes.
|
|
448
|
+
* Merge the class-name → absolute-file-path imports a reader resolved into the generated data
|
|
449
|
+
* file's import map, by the specifier that file imports each one by.
|
|
437
450
|
*/
|
|
438
451
|
protected mergeReaderImports(
|
|
439
452
|
importMap: Record<string, string>,
|
|
440
|
-
readerImportMap: Record<string, string
|
|
453
|
+
readerImportMap: Readonly<Record<string, string>>,
|
|
441
454
|
config: ConfigResult,
|
|
442
455
|
): void {
|
|
443
456
|
for (const [name, absolutePath] of Object.entries(readerImportMap)) {
|