1 | import * as tsm from 'ts-morph';
|
2 | import {
|
3 | isClassDeclaration,
|
4 | isEnumDeclaration,
|
5 | isFunctionDeclaration,
|
6 | isInterfaceDeclaration,
|
7 | isNamespaceDeclaration,
|
8 | isTypeAliasDeclaration,
|
9 | isVariableDeclaration,
|
10 | } from '../types/declaration-type-guards';
|
11 | import { ModuleDeclarations } from '../types/module-declarations';
|
12 | import { log } from '../utils/log';
|
13 | import { isClass, newClass } from './classes';
|
14 | import { isEnum, newEnum } from './enums';
|
15 | import {
|
16 | isExpression,
|
17 | isVariableAssignmentExpression,
|
18 | newExpression,
|
19 | newVariableAssignmentExpression,
|
20 | } from './expression';
|
21 | import { isFileModule, newFileModule } from './file-modules';
|
22 | import {
|
23 | isFunction,
|
24 | isFunctionExpression,
|
25 | newFunction,
|
26 | newFunctionExpression,
|
27 | } from './functions';
|
28 | import { getDeclarationName } from './get-declaration-name';
|
29 | import { isInterface, newInterface } from './interfaces';
|
30 | import { isExportedDeclarations } from './is-exported-declarations';
|
31 | import { isGlobalDeclaration } from './is-global-declaration';
|
32 | import { isInternalDeclaration } from './is-internal-declaration';
|
33 | import { isNamespace, newNamespace } from './namespaces';
|
34 | import { sortByID } from './sort-by-id';
|
35 | import { SourceProvider } from './source-provider';
|
36 | import { toID } from './to-id';
|
37 | import { isTypeAlias, newTypeAlias } from './type-aliases';
|
38 | import { TypeChecker } from './type-checker';
|
39 | import { isVariable, newVariable } from './variables';
|
40 |
|
41 | type Module = tsm.SourceFile | tsm.ModuleDeclaration;
|
42 |
|
43 | interface ExportedDeclaration {
|
44 | readonly exportID: string;
|
45 | readonly exportName: string;
|
46 | readonly declarationID: string;
|
47 | readonly declarationName: string;
|
48 | readonly declaration: tsm.ExportedDeclarations;
|
49 | }
|
50 |
|
51 | export function getPackageDeclarations({
|
52 | project,
|
53 | indexFile,
|
54 | getSource,
|
55 | getType,
|
56 | maxDepth = 5,
|
57 | }: {
|
58 | project: tsm.Project;
|
59 | indexFile: tsm.SourceFile;
|
60 | getSource: SourceProvider;
|
61 | getType: TypeChecker;
|
62 | maxDepth?: number;
|
63 | }): ModuleDeclarations {
|
64 | return getModuleDeclarations({
|
65 | module: indexFile,
|
66 | moduleName: '',
|
67 | maxDepth,
|
68 | getSource,
|
69 | getType,
|
70 | project,
|
71 | });
|
72 | }
|
73 |
|
74 | /**
|
75 | * `getModuleDeclarations` extracts the public declarations from the given module.
|
76 | *
|
77 | * @param module - module (for example, a source file, node or namespace)
|
78 | * @param maxDepth - maximum extraction depth for inner modules
|
79 | * @param getSource - source provider
|
80 | * @param getType - type checker
|
81 | * @param moduleName - module's name, used to define IDs for declarations (optional)
|
82 | */
|
83 | export function getModuleDeclarations({
|
84 | module,
|
85 | moduleName,
|
86 | maxDepth,
|
87 | getSource,
|
88 | getType,
|
89 | project,
|
90 | }: {
|
91 | module: Module;
|
92 | moduleName: string;
|
93 | maxDepth: number;
|
94 | getSource: SourceProvider;
|
95 | getType: TypeChecker;
|
96 | project?: tsm.Project;
|
97 | }): ModuleDeclarations {
|
98 | log('getModuleDeclarations: extracting declarations: %O', {
|
99 | moduleName,
|
100 | maxDepth,
|
101 | module,
|
102 | });
|
103 |
|
104 | const normalExportDeclarations = getNormalExportDeclarations({
|
105 | module,
|
106 | moduleName,
|
107 | });
|
108 | log('getModuleDeclarations: got normal export declarations: %O', {
|
109 | moduleName,
|
110 | total: normalExportDeclarations.length,
|
111 | normalExportDeclarations,
|
112 | });
|
113 |
|
114 | const exportEqualsDeclarations = getExportEqualsDeclarations({
|
115 | module,
|
116 | moduleName,
|
117 | });
|
118 | log('getModuleDeclarations: got export equals declarations: %O', {
|
119 | moduleName,
|
120 | total: exportEqualsDeclarations.length,
|
121 | exportEqualsDeclarations,
|
122 | });
|
123 |
|
124 | const ambientModulesDeclarations = getAmbientModulesDeclarations({
|
125 | project,
|
126 | });
|
127 | log('getModuleDeclarations: got ambient modules declarations: %O', {
|
128 | moduleName,
|
129 | total: ambientModulesDeclarations.length,
|
130 | ambientModulesDeclarations,
|
131 | });
|
132 |
|
133 | const globalAmbientDeclarations = getGlobalAmbientDeclarations({
|
134 | module,
|
135 | moduleName,
|
136 | });
|
137 | log('getModuleDeclarations: got global ambient declarations: %O', {
|
138 | moduleName,
|
139 | total: globalAmbientDeclarations.length,
|
140 | globalAmbientDeclarations,
|
141 | });
|
142 |
|
143 | return extractModuleDeclarations({
|
144 | exportedDeclarations: [
|
145 | ...normalExportDeclarations,
|
146 | ...exportEqualsDeclarations,
|
147 | ...ambientModulesDeclarations,
|
148 | ...globalAmbientDeclarations,
|
149 | ],
|
150 | maxDepth,
|
151 | getSource,
|
152 | getType,
|
153 | });
|
154 | }
|
155 |
|
156 | function getNormalExportDeclarations({
|
157 | module,
|
158 | moduleName,
|
159 | }: {
|
160 | module: Module;
|
161 | moduleName: string;
|
162 | }): ExportedDeclaration[] {
|
163 | const namedExports = new Set<string>();
|
164 |
|
165 | return Array.from(module.getExportedDeclarations())
|
166 | .flatMap(([exportName, declarations]) => {
|
167 | return declarations.flatMap((declaration) => {
|
168 |
|
169 | if (isInternalDeclaration({ declaration, name: exportName })) {
|
170 | return [];
|
171 | }
|
172 |
|
173 | const exportID = toID(moduleName, exportName);
|
174 | const declarationName = getDeclarationName({
|
175 | exportName,
|
176 | declaration,
|
177 | });
|
178 | const declarationID = toID(moduleName, declarationName);
|
179 |
|
180 |
|
181 | if (declarationID === exportID) {
|
182 | namedExports.add(declarationID);
|
183 | }
|
184 |
|
185 | return {
|
186 | exportID,
|
187 | exportName,
|
188 | declarationID,
|
189 | declarationName,
|
190 | declaration,
|
191 | };
|
192 | });
|
193 | })
|
194 | .filter(({ exportID, declarationID }) => {
|
195 |
|
196 |
|
197 | return (
|
198 | declarationID === exportID || !namedExports.has(declarationID)
|
199 | );
|
200 | });
|
201 | }
|
202 |
|
203 | function getExportEqualsDeclarations({
|
204 | module,
|
205 | moduleName,
|
206 | }: {
|
207 | module: Module;
|
208 | moduleName: string;
|
209 | }): ExportedDeclaration[] {
|
210 |
|
211 |
|
212 | if (tsm.Node.isModuleDeclaration(module) && !module.hasBody()) {
|
213 | return [];
|
214 | }
|
215 |
|
216 | const exportIdentifier = module
|
217 | .getExportAssignment((ea) => ea.isExportEquals())
|
218 | ?.getLastChildByKind(tsm.SyntaxKind.Identifier);
|
219 | if (!exportIdentifier) {
|
220 | return [];
|
221 | }
|
222 |
|
223 | const exportName = exportIdentifier.getText();
|
224 |
|
225 | return exportIdentifier.getDefinitionNodes().flatMap((declaration) => {
|
226 |
|
227 | if (
|
228 | isInternalDeclaration({ declaration, name: exportName }) ||
|
229 | !isExportedDeclarations(declaration)
|
230 | ) {
|
231 | return [];
|
232 | }
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 | if (isNamespace(declaration)) {
|
239 | return [];
|
240 | }
|
241 |
|
242 | const exportID = toID(moduleName, exportName);
|
243 | const declarationName = getDeclarationName({
|
244 | exportName,
|
245 | declaration,
|
246 | });
|
247 | const declarationID = toID(moduleName, declarationName);
|
248 |
|
249 | return {
|
250 | exportID,
|
251 | exportName,
|
252 | declarationID,
|
253 | declarationName,
|
254 | declaration,
|
255 | };
|
256 | });
|
257 | }
|
258 |
|
259 | function getAmbientModulesDeclarations({
|
260 | project,
|
261 | }: {
|
262 | project?: tsm.Project;
|
263 | }): ExportedDeclaration[] {
|
264 | if (!project) {
|
265 | return [];
|
266 | }
|
267 |
|
268 | return project.getAmbientModules().flatMap((symbol) => {
|
269 | return symbol.getDeclarations().flatMap((declaration) => {
|
270 | const filepath = declaration.getSourceFile().getFilePath();
|
271 | if (
|
272 | !tsm.Node.isModuleDeclaration(declaration) ||
|
273 | filepath.startsWith('/node_modules')
|
274 | ) {
|
275 | return [];
|
276 | }
|
277 |
|
278 | const exportName = declaration.getName();
|
279 | const declarationName = exportName;
|
280 |
|
281 |
|
282 | const exportID = exportName
|
283 | .replace(/"|'/g, '')
|
284 | .replace(/\s/g, '_')
|
285 | .trim();
|
286 | const declarationID = exportID;
|
287 |
|
288 | return {
|
289 | exportID,
|
290 | exportName,
|
291 | declarationID,
|
292 | declarationName,
|
293 | declaration,
|
294 | };
|
295 | });
|
296 | });
|
297 | }
|
298 |
|
299 | function getGlobalAmbientDeclarations({
|
300 | module,
|
301 | moduleName,
|
302 | }: {
|
303 | module: Module;
|
304 | moduleName: string;
|
305 | }): ExportedDeclaration[] {
|
306 | if (!tsm.Node.isSourceFile(module)) {
|
307 | return [];
|
308 | }
|
309 |
|
310 |
|
311 | const globalCandidates = [
|
312 | ...module.getVariableDeclarations(),
|
313 | ...module.getFunctions(),
|
314 | ...module.getModules(),
|
315 | ];
|
316 |
|
317 | return globalCandidates.flatMap((declaration) => {
|
318 |
|
319 | const exportName = declaration.getName()!;
|
320 |
|
321 | if (
|
322 | !isGlobalDeclaration({ declaration }) ||
|
323 | isInternalDeclaration({ declaration, name: exportName })
|
324 | ) {
|
325 | return [];
|
326 | }
|
327 |
|
328 | const exportID = toID(moduleName, exportName);
|
329 | const declarationName = getDeclarationName({
|
330 | exportName,
|
331 | declaration,
|
332 | });
|
333 | const declarationID = toID(moduleName, declarationName);
|
334 |
|
335 | return {
|
336 | exportID,
|
337 | exportName,
|
338 | declarationID,
|
339 | declarationName,
|
340 | declaration,
|
341 | };
|
342 | });
|
343 | }
|
344 |
|
345 | function extractModuleDeclarations({
|
346 | exportedDeclarations,
|
347 | maxDepth,
|
348 | getSource,
|
349 | getType,
|
350 | }: {
|
351 | exportedDeclarations: ExportedDeclaration[];
|
352 | maxDepth: number;
|
353 | getSource: SourceProvider;
|
354 | getType: TypeChecker;
|
355 | }): ModuleDeclarations {
|
356 | const exportedFunctions = new Set<string>();
|
357 | const exportedNamespaces = new Set<string>();
|
358 |
|
359 | const declarations = exportedDeclarations
|
360 | .flatMap(
|
361 | ({
|
362 | exportID,
|
363 | declarationID: id,
|
364 | declarationName: name,
|
365 | declaration,
|
366 | }) => {
|
367 | if (isVariable(declaration)) {
|
368 | return newVariable({ id, name, declaration, getSource });
|
369 | }
|
370 |
|
371 | if (isVariableAssignmentExpression(declaration)) {
|
372 | return newVariableAssignmentExpression({
|
373 | id,
|
374 | name,
|
375 | declaration,
|
376 | getSource,
|
377 | });
|
378 | }
|
379 |
|
380 | if (isExpression(declaration)) {
|
381 | return newExpression({ id, name, declaration, getSource });
|
382 | }
|
383 |
|
384 | if (isFunction(declaration)) {
|
385 |
|
386 | if (exportedFunctions.has(exportID)) {
|
387 | return [];
|
388 | }
|
389 |
|
390 | exportedFunctions.add(exportID);
|
391 | return newFunction({
|
392 | id,
|
393 | name,
|
394 | declaration,
|
395 | getSource,
|
396 | getType,
|
397 | });
|
398 | }
|
399 |
|
400 | if (isFunctionExpression(declaration)) {
|
401 | return newFunctionExpression({
|
402 | id,
|
403 | name,
|
404 | declaration,
|
405 | getSource,
|
406 | getType,
|
407 | });
|
408 | }
|
409 |
|
410 | if (isClass(declaration)) {
|
411 | return newClass({
|
412 | id,
|
413 | name,
|
414 | declaration,
|
415 | getSource,
|
416 | getType,
|
417 | });
|
418 | }
|
419 |
|
420 | if (isInterface(declaration)) {
|
421 | return newInterface({
|
422 | id,
|
423 | name,
|
424 | declaration,
|
425 | getSource,
|
426 | getType,
|
427 | });
|
428 | }
|
429 |
|
430 | if (isEnum(declaration)) {
|
431 | return newEnum({ id, name, declaration, getSource });
|
432 | }
|
433 |
|
434 | if (isTypeAlias(declaration)) {
|
435 | return newTypeAlias({ id, name, declaration, getSource });
|
436 | }
|
437 |
|
438 | if (isNamespace(declaration) && maxDepth > 0) {
|
439 |
|
440 | if (exportedNamespaces.has(exportID)) {
|
441 | return [];
|
442 | }
|
443 |
|
444 | const declarations = getModuleDeclarations({
|
445 | module: declaration,
|
446 | moduleName: id,
|
447 | maxDepth: maxDepth - 1,
|
448 | getSource,
|
449 | getType,
|
450 | });
|
451 |
|
452 | exportedNamespaces.add(exportID);
|
453 | return newNamespace({
|
454 | id,
|
455 | name,
|
456 | declaration,
|
457 | declarations,
|
458 | getSource,
|
459 | });
|
460 | }
|
461 |
|
462 |
|
463 |
|
464 | if (isFileModule(declaration) && maxDepth > 0) {
|
465 | const declarations = getModuleDeclarations({
|
466 | module: declaration,
|
467 | moduleName: id,
|
468 | maxDepth: maxDepth - 1,
|
469 | getSource,
|
470 | getType,
|
471 | });
|
472 |
|
473 | return newFileModule({
|
474 | id,
|
475 | name,
|
476 | declaration,
|
477 | declarations,
|
478 | getSource,
|
479 | });
|
480 | }
|
481 |
|
482 | return [];
|
483 | }
|
484 | )
|
485 | .sort(sortByID);
|
486 |
|
487 | return {
|
488 | variables: declarations.filter(isVariableDeclaration),
|
489 | functions: declarations.filter(isFunctionDeclaration),
|
490 | classes: declarations.filter(isClassDeclaration),
|
491 | interfaces: declarations.filter(isInterfaceDeclaration),
|
492 | enums: declarations.filter(isEnumDeclaration),
|
493 | typeAliases: declarations.filter(isTypeAliasDeclaration),
|
494 | namespaces: declarations.filter(isNamespaceDeclaration),
|
495 | };
|
496 | }
|