@fougere/compiler 0.11.0-alpha.0 → 0.12.1-alpha.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/dist/boot.d.ts +8 -3
- package/dist/boot.d.ts.map +1 -1
- package/dist/boot.js +14 -4
- package/dist/boot.js.map +1 -1
- package/dist/scan/Implemented.d.ts +14 -0
- package/dist/scan/Implemented.d.ts.map +1 -0
- package/dist/scan/Implemented.js +34 -0
- package/dist/scan/Implemented.js.map +1 -0
- package/dist/scan/TypeProgram.d.ts +21 -0
- package/dist/scan/TypeProgram.d.ts.map +1 -0
- package/dist/scan/TypeProgram.js +177 -0
- package/dist/scan/TypeProgram.js.map +1 -0
- package/dist/scan/emit.d.ts.map +1 -1
- package/dist/scan/emit.js +37 -28
- package/dist/scan/emit.js.map +1 -1
- package/dist/scan/handler-parser.d.ts +0 -3
- package/dist/scan/handler-parser.d.ts.map +1 -1
- package/dist/scan/handler-parser.js +209 -279
- package/dist/scan/handler-parser.js.map +1 -1
- package/dist/scan/refusals.d.ts +0 -7
- package/dist/scan/refusals.d.ts.map +1 -1
- package/dist/scan/refusals.js +11 -2
- package/dist/scan/refusals.js.map +1 -1
- package/dist/scan/scanner.d.ts.map +1 -1
- package/dist/scan/scanner.js +118 -82
- package/dist/scan/scanner.js.map +1 -1
- package/package.json +6 -6
- package/src/boot.ts +23 -7
- package/src/scan/Implemented.ts +41 -0
- package/src/scan/TypeProgram.ts +199 -0
- package/src/scan/emit.ts +32 -20
- package/src/scan/handler-parser.ts +235 -307
- package/src/scan/refusals.ts +13 -2
- package/src/scan/scanner.ts +158 -94
package/src/boot.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Logger, applyConfig, createApp, type App, type CreateAppOptions, type Extension, type FougereConfig, type Transport } from '@fougere/core';
|
|
2
|
-
import { loadConfig } from '@fougere/core/node';
|
|
2
|
+
import { loadConfig, remotesOf, statedModules } from '@fougere/core/node';
|
|
3
3
|
import { scanProject } from './scan/scanner.js';
|
|
4
4
|
|
|
5
5
|
import type { Container } from '@fougere/container';
|
|
@@ -9,9 +9,14 @@ interface BootOptions {
|
|
|
9
9
|
root?: string;
|
|
10
10
|
/** Override config (merged with fougere.config.ts). */
|
|
11
11
|
config?: Partial<FougereConfig>;
|
|
12
|
-
/**
|
|
13
|
-
createContainer
|
|
14
|
-
/**
|
|
12
|
+
/** The container to resolve through. Absent, core builds its own. */
|
|
13
|
+
createContainer?: () => Container;
|
|
14
|
+
/**
|
|
15
|
+
* Boot only these fronds, by name — absent, every one found. Not `fronds:`, which says what
|
|
16
|
+
* an app is MADE of: this narrows the same list to what one process carries.
|
|
17
|
+
*/
|
|
18
|
+
only?: string[];
|
|
19
|
+
/** @deprecated The name `only:` now carries. */
|
|
15
20
|
fronds?: string[];
|
|
16
21
|
/**
|
|
17
22
|
* Remote fronds — label → address. A declared remote wins over local
|
|
@@ -64,16 +69,27 @@ export async function boot(options: BootOptions): Promise<App> {
|
|
|
64
69
|
log.info('database initialized');
|
|
65
70
|
}
|
|
66
71
|
|
|
72
|
+
// What the config names by module — imported here, because core resolves no specifier.
|
|
73
|
+
const stated = await statedModules(config.fronds);
|
|
74
|
+
|
|
67
75
|
log.debug('creating app (scan + container)');
|
|
68
76
|
const app = await createApp({
|
|
77
|
+
...(stated.fronds.length > 0 ? { fronds: stated.fronds } : {}),
|
|
69
78
|
// boot() lives on the Node entry, so boot() is what reads the disk. `createApp` is
|
|
70
79
|
// handed the answer and reaches for nothing.
|
|
71
|
-
scan: await scanProject(root, options.fronds, config.conventions),
|
|
80
|
+
scan: await scanProject(root, options.only ?? options.fronds, config.conventions),
|
|
72
81
|
createContainer: options.createContainer,
|
|
73
82
|
storageFactory: dbSetup?.storageFactory,
|
|
74
83
|
db: dbSetup?.db,
|
|
75
84
|
auth: config.auth,
|
|
76
|
-
remotes
|
|
85
|
+
// The tree's string leaves say the same thing `remotes:` does, and this boot read
|
|
86
|
+
// neither of them from the file until now — a project stating its topology got a
|
|
87
|
+
// process that believed every frond was local.
|
|
88
|
+
remotes: options.remotes ?? remotesOf(config),
|
|
89
|
+
// Who inherits code from whom — handed over whole, because a refusal names where an
|
|
90
|
+
// entry sits in the tree.
|
|
91
|
+
under: config.fronds,
|
|
92
|
+
narrowed: (options.only ?? options.fronds) !== undefined,
|
|
77
93
|
ports: config.ports,
|
|
78
94
|
// Read from the config for the same reason `ports` is, one line up: it is a fact the
|
|
79
95
|
// project states, not one the caller passes. Absent here, `serveRest` and
|
|
@@ -87,7 +103,7 @@ export async function boot(options: BootOptions): Promise<App> {
|
|
|
87
103
|
// Its own gesture, handed over whole. The ORDER — tables, then rows, then whatever this
|
|
88
104
|
// host takes on — is `createApp`'s, and was written out by four hosts before.
|
|
89
105
|
migrate: dbSetup?.migrate,
|
|
90
|
-
extensions: options.extensions ?? [],
|
|
106
|
+
extensions: [...stated.extensions, ...(options.extensions ?? [])],
|
|
91
107
|
onEmit: options.onEmit,
|
|
92
108
|
remoteTransport: options.remoteTransport,
|
|
93
109
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** What a scanned class declares it implements. */
|
|
2
|
+
import { loadTS, checkedSourceOf, findDefaultClass } from './TypeProgram.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* What a scanned class says it IMPLEMENTS, and whether each name is a class.
|
|
6
|
+
*
|
|
7
|
+
* A class is the interesting case: `implements` is erased, so `Object.getPrototypeOf(ctor).name`
|
|
8
|
+
* is empty and the boot binds no port. An interface is the ordinary one — and one of them,
|
|
9
|
+
* `AsyncDisposable`, is how a provider says its scope keeps it and closes it.
|
|
10
|
+
*/
|
|
11
|
+
export async function parseImplements(filePath: string, projectRoot?: string): Promise<Implemented[]> {
|
|
12
|
+
const ts = await loadTS();
|
|
13
|
+
const { source, checker } = checkedSourceOf(filePath, projectRoot);
|
|
14
|
+
const cls = findDefaultClass(source);
|
|
15
|
+
if (!cls?.heritageClauses) return [];
|
|
16
|
+
|
|
17
|
+
const found: Implemented[] = [];
|
|
18
|
+
for (const clause of cls.heritageClauses) {
|
|
19
|
+
if (clause.token !== ts.SyntaxKind.ImplementsKeyword) continue;
|
|
20
|
+
|
|
21
|
+
for (const base of clause.types) {
|
|
22
|
+
if (!ts.isIdentifier(base.expression)) continue;
|
|
23
|
+
|
|
24
|
+
let symbol = checker.getSymbolAtLocation(base.expression);
|
|
25
|
+
if (symbol && symbol.flags & ts.SymbolFlags.Alias) symbol = checker.getAliasedSymbol(symbol);
|
|
26
|
+
|
|
27
|
+
found.push({
|
|
28
|
+
name: base.expression.text,
|
|
29
|
+
isClass: symbol?.declarations?.some((one) => ts.isClassDeclaration(one)) ?? false,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return found;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** One `implements` clause, and what the checker says its name is. */
|
|
38
|
+
export interface Implemented {
|
|
39
|
+
name: string;
|
|
40
|
+
isClass: boolean;
|
|
41
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/** The TypeScript program the scan reads through, and the mutable state it keeps. */
|
|
2
|
+
import type ts from '@typescript/typescript6';
|
|
3
|
+
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
4
|
+
import { join, dirname, resolve as resolvePath } from 'node:path';
|
|
5
|
+
|
|
6
|
+
/** Lazy-loaded TypeScript module — avoids bundling the 9MB compiler. */
|
|
7
|
+
let _ts: typeof ts | undefined;
|
|
8
|
+
export async function loadTS(): Promise<typeof ts> {
|
|
9
|
+
if (!_ts) _ts = (await import('@typescript/typescript6')).default;
|
|
10
|
+
return _ts;
|
|
11
|
+
}
|
|
12
|
+
export function getTS(): typeof ts {
|
|
13
|
+
if (!_ts) throw new Error('TypeScript not loaded — call an async parse function first');
|
|
14
|
+
return _ts;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface TypeProject {
|
|
18
|
+
roots: Set<string>;
|
|
19
|
+
options: ts.CompilerOptions;
|
|
20
|
+
program: ts.Program;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** One checked program per project/configuration during a scan. */
|
|
24
|
+
const typeProjects = new Map<string, TypeProject>();
|
|
25
|
+
const compilerProjects = new Map<string, { key: string; roots: string[]; options: ts.CompilerOptions }>();
|
|
26
|
+
|
|
27
|
+
/** What survives a run. */
|
|
28
|
+
const sourceFiles = new Map<string, { mtime: number; file: ts.SourceFile }>();
|
|
29
|
+
const retained = new Map<string, { host: ts.CompilerHost; program: ts.Program }>();
|
|
30
|
+
|
|
31
|
+
export function resetTypePrograms(): void {
|
|
32
|
+
typeProjects.clear();
|
|
33
|
+
compilerProjects.clear();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function keptHost(key: string, options: ts.CompilerOptions): ts.CompilerHost {
|
|
37
|
+
const cached = retained.get(key);
|
|
38
|
+
if (cached) return cached.host;
|
|
39
|
+
const typescript = getTS();
|
|
40
|
+
const base = typescript.createCompilerHost(options);
|
|
41
|
+
const host: ts.CompilerHost = {
|
|
42
|
+
...base,
|
|
43
|
+
getSourceFile(fileName, languageVersion, onError, shouldCreate) {
|
|
44
|
+
const path = resolvePath(fileName);
|
|
45
|
+
const mtime = statSync(path, { throwIfNoEntry: false })?.mtimeMs ?? -1;
|
|
46
|
+
const cached = sourceFiles.get(path);
|
|
47
|
+
if (cached && cached.mtime === mtime) return cached.file;
|
|
48
|
+
const file = base.getSourceFile(fileName, languageVersion, onError, shouldCreate);
|
|
49
|
+
if (file && mtime >= 0) sourceFiles.set(path, { mtime, file });
|
|
50
|
+
return file;
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
retained.set(key, { host, program: undefined as unknown as ts.Program });
|
|
54
|
+
return host;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function builtProgram(key: string, roots: readonly string[], options: ts.CompilerOptions): ts.Program {
|
|
58
|
+
const typescript = getTS();
|
|
59
|
+
const host = keptHost(key, options);
|
|
60
|
+
const program = typescript.createProgram({
|
|
61
|
+
rootNames: [...roots], options, host, oldProgram: retained.get(key)?.program,
|
|
62
|
+
});
|
|
63
|
+
retained.set(key, { host, program });
|
|
64
|
+
return program;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function compilerProjectOf(filePath: string, projectRoot?: string): { key: string; roots: string[]; options: ts.CompilerOptions } {
|
|
68
|
+
const typescript = getTS();
|
|
69
|
+
const absolute = resolvePath(filePath);
|
|
70
|
+
const configPath = typescript.findConfigFile(dirname(absolute), typescript.sys.fileExists);
|
|
71
|
+
|
|
72
|
+
if (configPath) {
|
|
73
|
+
const key = `${configPath}:${projectRoot ?? ''}`;
|
|
74
|
+
const cached = compilerProjects.get(key);
|
|
75
|
+
if (cached) return cached;
|
|
76
|
+
|
|
77
|
+
const read = typescript.readConfigFile(configPath, typescript.sys.readFile);
|
|
78
|
+
if (read.error) throw new Error(typescript.flattenDiagnosticMessageText(read.error.messageText, '\n'));
|
|
79
|
+
const parsed = typescript.parseJsonConfigFileContent(read.config, typescript.sys, dirname(configPath));
|
|
80
|
+
const configured = {
|
|
81
|
+
key,
|
|
82
|
+
// Compiler options belong to the project; its entire include glob does not belong
|
|
83
|
+
// to this scan. Each declaration inspected below becomes a root and TypeScript
|
|
84
|
+
// follows its imports. Seeding the monorepo here made a one-file scan compile it all.
|
|
85
|
+
roots: [],
|
|
86
|
+
// Handlers may be authored or emitted as JavaScript. They still need to belong to
|
|
87
|
+
// the checked program so constructor parsing does not fail on the first cold scan.
|
|
88
|
+
options: { ...parsed.options, allowJs: true, noEmit: true },
|
|
89
|
+
};
|
|
90
|
+
compilerProjects.set(key, configured);
|
|
91
|
+
return configured;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const key = projectRoot ?? dirname(absolute);
|
|
95
|
+
const cached = compilerProjects.get(key);
|
|
96
|
+
if (cached) return cached;
|
|
97
|
+
const configured = {
|
|
98
|
+
key,
|
|
99
|
+
roots: [],
|
|
100
|
+
options: {
|
|
101
|
+
target: typescript.ScriptTarget.ES2022,
|
|
102
|
+
module: typescript.ModuleKind.Node16,
|
|
103
|
+
moduleResolution: typescript.ModuleResolutionKind.Node16,
|
|
104
|
+
strict: true,
|
|
105
|
+
skipLibCheck: true,
|
|
106
|
+
allowJs: true,
|
|
107
|
+
noEmit: true,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
compilerProjects.set(key, configured);
|
|
111
|
+
return configured;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Declare every file a run will read, so one program covers it. */
|
|
115
|
+
export async function seedTypeProgram(filePaths: readonly string[], projectRoot?: string): Promise<void> {
|
|
116
|
+
const typescript = await loadTS();
|
|
117
|
+
const grouped = new Map<string, { options: ts.CompilerOptions; paths: string[] }>();
|
|
118
|
+
|
|
119
|
+
for (const filePath of filePaths) {
|
|
120
|
+
const absolute = resolvePath(filePath);
|
|
121
|
+
const typescript = getTS();
|
|
122
|
+
const configured = compilerProjectOf(absolute, projectRoot);
|
|
123
|
+
const group = grouped.get(configured.key) ?? { options: configured.options, paths: [] };
|
|
124
|
+
group.paths.push(absolute);
|
|
125
|
+
grouped.set(configured.key, group);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (const [key, { options, paths }] of grouped) {
|
|
129
|
+
const roots = new Set(typeProjects.get(key)?.roots ?? []);
|
|
130
|
+
for (const path of paths) roots.add(path);
|
|
131
|
+
typeProjects.set(key, { roots, options, program: builtProgram(key, [...roots], options) });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The program a file belongs to, built once and widened as more files are asked for. */
|
|
136
|
+
export function projectOf(filePath: string, projectRoot?: string): { program: ts.Program; absolute: string } {
|
|
137
|
+
const absolute = resolvePath(filePath);
|
|
138
|
+
const configured = compilerProjectOf(absolute, projectRoot);
|
|
139
|
+
let project = typeProjects.get(configured.key);
|
|
140
|
+
|
|
141
|
+
if (!project) {
|
|
142
|
+
// `path.resolve` is variadic, so handing it directly to `map` also passed the
|
|
143
|
+
// index and the whole roots array as path segments. A fixture without a warm scan
|
|
144
|
+
// cache exposed that first-run-only failure.
|
|
145
|
+
const roots = new Set(configured.roots.map((root) => resolvePath(root)));
|
|
146
|
+
roots.add(absolute);
|
|
147
|
+
const program = builtProgram(configured.key, [...roots], configured.options);
|
|
148
|
+
project = { roots, options: configured.options, program };
|
|
149
|
+
typeProjects.set(configured.key, project);
|
|
150
|
+
} else if (!project.roots.has(absolute)) {
|
|
151
|
+
project.roots.add(absolute);
|
|
152
|
+
project.program = builtProgram(configured.key, [...project.roots], project.options);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { program: project.program, absolute };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function checkedSourceOf(filePath: string, projectRoot?: string): { source: ts.SourceFile; checker: ts.TypeChecker } {
|
|
159
|
+
const { program, absolute } = projectOf(filePath, projectRoot);
|
|
160
|
+
const source = program.getSourceFile(absolute);
|
|
161
|
+
if (!source) throw new Error(`TypeScript did not include '${absolute}' in its program.`);
|
|
162
|
+
|
|
163
|
+
return { source, checker: program.getTypeChecker() };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** A file, opened. Five places read and parsed one, each spelling the same two calls. */
|
|
167
|
+
export function sourceOf(filePath: string): ts.SourceFile {
|
|
168
|
+
const ts = getTS();
|
|
169
|
+
return ts.createSourceFile(filePath, readFileSync(filePath, 'utf-8'), ts.ScriptTarget.Latest, true);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── Class finding ────────────────────────────
|
|
173
|
+
|
|
174
|
+
/** Find the default exported class in a source file. */
|
|
175
|
+
export function findDefaultClass(source: ts.SourceFile): ts.ClassDeclaration | undefined {
|
|
176
|
+
const ts = getTS();
|
|
177
|
+
for (const stmt of source.statements) {
|
|
178
|
+
// export default class Foo { ... }
|
|
179
|
+
if (ts.isClassDeclaration(stmt) && stmt.modifiers?.some(
|
|
180
|
+
(m) => m.kind === ts.SyntaxKind.ExportKeyword,
|
|
181
|
+
) && stmt.modifiers?.some(
|
|
182
|
+
(m) => m.kind === ts.SyntaxKind.DefaultKeyword,
|
|
183
|
+
)) {
|
|
184
|
+
return stmt;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// export default Foo (separate statement) — find the class it points to
|
|
189
|
+
for (const stmt of source.statements) {
|
|
190
|
+
if (ts.isExportAssignment(stmt) && !stmt.isExportEquals && ts.isIdentifier(stmt.expression)) {
|
|
191
|
+
const name = stmt.expression.text;
|
|
192
|
+
for (const s of source.statements) {
|
|
193
|
+
if (ts.isClassDeclaration(s) && s.name?.text === name) return s;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
package/src/scan/emit.ts
CHANGED
|
@@ -86,13 +86,16 @@ function collectorOf(c: CollectorEntry, imports: Imports): string {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
function providerOf(p: ProviderEntry, imports: Imports): string {
|
|
89
|
+
// `kept` travels like every other member: a provider that loses it on disk is registered
|
|
90
|
+
// transient, so the same code would answer differently depending on how it was booted.
|
|
89
91
|
return `{ name: ${lit(nameOf(p))}, ctor: ${imports.aliasOf(p.ctor as Live)}, `
|
|
90
|
-
+ `deps: ${lit(p.deps)}, filePath: ${lit(p.filePath)}
|
|
92
|
+
+ `deps: ${lit(p.deps)}, filePath: ${lit(p.filePath)}`
|
|
93
|
+
+ `${p.kept ? ', kept: true' : ''} }`;
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
function middlewareOf(m: MiddlewareEntry, imports: Imports): string {
|
|
94
97
|
return `{ name: ${lit(m.name)}, ctor: ${imports.aliasOf(m.ctor as Live)}, `
|
|
95
|
-
+ `
|
|
98
|
+
+ `deps: ${lit(m.deps)}, filePath: ${lit(m.filePath)} }`;
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
function seedOf(s: SeedEntry, imports: Imports): string {
|
|
@@ -125,25 +128,11 @@ export function emitScan(result: ScanResult, options: EmitOptions): string {
|
|
|
125
128
|
const imports = new Imports(options.outFile);
|
|
126
129
|
const core = options.core ?? '@fougere/core';
|
|
127
130
|
|
|
128
|
-
|
|
129
|
-
// them then finds it in the table instead of being imported a second time.
|
|
130
|
-
for (const f of result.fronds) {
|
|
131
|
-
for (const e of f.entities) {
|
|
132
|
-
imports.default(e.entityClass as Live, e.filePath);
|
|
133
|
-
const name = (e.entityClass as { name?: string }).name;
|
|
134
|
-
if (name) imports.byClassName.set(name, e.entityClass as Live);
|
|
135
|
-
}
|
|
136
|
-
for (const p of f.providers) imports.default(p.ctor as Live, p.filePath);
|
|
137
|
-
for (const h of f.handlers) imports.default(h.ctor as Live, h.filePath);
|
|
138
|
-
for (const p of f.presenters) imports.default(p.ctor as Live, p.filePath);
|
|
139
|
-
for (const c of f.collectors) imports.default(c.ctor as Live, c.filePath);
|
|
140
|
-
for (const m of f.middlewares) imports.default(m.ctor as Live, m.filePath);
|
|
141
|
-
for (const s of f.seeds) if (typeof s.data === 'function') imports.default(s.data as Live, s.filePath);
|
|
142
|
-
}
|
|
131
|
+
collectImports(result, imports);
|
|
143
132
|
|
|
144
|
-
const fronds = result.fronds.map((
|
|
145
|
-
// `cause` is dropped: it is an Error, and the build that produced this already reported
|
|
146
|
-
//
|
|
133
|
+
const fronds = result.fronds.map((one) => frondOf(one, imports)).join(',\n');
|
|
134
|
+
// `cause` is dropped: it is an Error, and the build that produced this already reported it.
|
|
135
|
+
// What a boot logs is the message, and that travels.
|
|
147
136
|
const diagnostics = result.diagnostics.map(({ cause: _cause, ...rest }) => lit(rest));
|
|
148
137
|
|
|
149
138
|
// The annotation is a convenience for a file a human's tsc will read; it is never what
|
|
@@ -166,3 +155,26 @@ ${fronds}
|
|
|
166
155
|
};
|
|
167
156
|
`;
|
|
168
157
|
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Every class the descriptor names by a file, imported first: a schema slot holding one of them
|
|
161
|
+
* then finds it in the table instead of being imported a second time.
|
|
162
|
+
*/
|
|
163
|
+
function collectImports(result: ScanResult, imports: Imports): void {
|
|
164
|
+
for (const frond of result.fronds) {
|
|
165
|
+
for (const entity of frond.entities) {
|
|
166
|
+
imports.default(entity.entityClass as Live, entity.filePath);
|
|
167
|
+
const name = (entity.entityClass as { name?: string }).name;
|
|
168
|
+
if (name) imports.byClassName.set(name, entity.entityClass as Live);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for (const one of frond.providers) imports.default(one.ctor as Live, one.filePath);
|
|
172
|
+
for (const one of frond.handlers) imports.default(one.ctor as Live, one.filePath);
|
|
173
|
+
for (const one of frond.presenters) imports.default(one.ctor as Live, one.filePath);
|
|
174
|
+
for (const one of frond.collectors) imports.default(one.ctor as Live, one.filePath);
|
|
175
|
+
for (const one of frond.middlewares) imports.default(one.ctor as Live, one.filePath);
|
|
176
|
+
for (const seed of frond.seeds) {
|
|
177
|
+
if (typeof seed.data === 'function') imports.default(seed.data as Live, seed.filePath);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|