|
| 1 | +// tslint:disable:no-bitwise |
| 2 | + |
| 3 | +import {ClassExportDoc} from 'dgeni-packages/typescript/api-doc-types/ClassExportDoc'; |
1 | 4 | import {ClassLikeExportDoc} from 'dgeni-packages/typescript/api-doc-types/ClassLikeExportDoc';
|
| 5 | +import {InterfaceExportDoc} from 'dgeni-packages/typescript/api-doc-types/InterfaceExportDoc'; |
| 6 | +import * as ts from 'typescript'; |
2 | 7 |
|
3 | 8 | /** Gets all class like export documents which the given doc inherits from. */
|
4 |
| -export function getInheritedDocsOfClass(doc: ClassLikeExportDoc): ClassLikeExportDoc[] { |
5 |
| - const directBaseDocs = [ |
6 |
| - ...doc.implementsClauses.filter(clause => clause.doc).map(d => d.doc!), |
7 |
| - ...doc.extendsClauses.filter(clause => clause.doc).map(d => d.doc!), |
8 |
| - ]; |
| 9 | +export function getInheritedDocsOfClass( |
| 10 | + doc: ClassLikeExportDoc, |
| 11 | + exportSymbolsToDocsMap: Map<ts.Symbol, ClassLikeExportDoc>): ClassLikeExportDoc[] { |
| 12 | + const result: ClassLikeExportDoc[] = []; |
| 13 | + const typeChecker = doc.typeChecker; |
| 14 | + for (let info of doc.extendsClauses) { |
| 15 | + if (info.doc) { |
| 16 | + result.push(info.doc, ...getInheritedDocsOfClass(info.doc, exportSymbolsToDocsMap)); |
| 17 | + } else if (info.type) { |
| 18 | + // If the heritage info has not been resolved to a Dgeni API document, we try to |
| 19 | + // interpret the type expression and resolve/create corresponding Dgeni API documents. |
| 20 | + // An example is the use of mixins. Type-wise mixins are not like real classes, because |
| 21 | + // they are composed through an intersection type. In order to handle this pattern, we |
| 22 | + // need to handle intersection types manually and resolve them to Dgeni API documents. |
| 23 | + const resolvedType = typeChecker.getTypeAtLocation(info.type); |
| 24 | + const docs = getClassLikeDocsFromType(resolvedType, doc, exportSymbolsToDocsMap); |
| 25 | + // Add direct class-like types resolved from the expression. |
| 26 | + result.push(...docs); |
| 27 | + // Resolve inherited docs of the resolved documents. |
| 28 | + docs.forEach(d => result.push(...getInheritedDocsOfClass(d, exportSymbolsToDocsMap))); |
| 29 | + } |
| 30 | + } |
| 31 | + return result; |
| 32 | +} |
| 33 | + |
| 34 | +/** |
| 35 | + * Gets all class-like Dgeni documents from the given type. e.g. intersection types of |
| 36 | + * multiple classes will result in multiple Dgeni API documents for each class. |
| 37 | + */ |
| 38 | +function getClassLikeDocsFromType( |
| 39 | + type: ts.Type, baseDoc: ClassLikeExportDoc, |
| 40 | + exportSymbolsToDocsMap: Map<ts.Symbol, ClassLikeExportDoc>): ClassLikeExportDoc[] { |
| 41 | + let aliasSymbol: ts.Symbol|undefined = undefined; |
| 42 | + let symbol: ts.Symbol = type.symbol; |
| 43 | + const typeChecker = baseDoc.typeChecker; |
| 44 | + |
| 45 | + // Symbols can be aliases of the declaration symbol. e.g. in named import |
| 46 | + // specifiers. We need to resolve the aliased symbol back to the declaration symbol. |
| 47 | + if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0) { |
| 48 | + aliasSymbol = symbol; |
| 49 | + symbol = typeChecker.getAliasedSymbol(symbol); |
| 50 | + } |
9 | 51 |
|
10 |
| - return [ |
11 |
| - ...directBaseDocs, |
12 |
| - // recursively collect base documents of direct base documents. |
13 |
| - ...directBaseDocs.reduce( |
14 |
| - (res: ClassLikeExportDoc[], d) => res.concat(getInheritedDocsOfClass(d)), []), |
15 |
| - ]; |
| 52 | + // Intersection types are commonly used in TypeScript mixins to express the |
| 53 | + // class augmentation. e.g. "BaseClass & CanColor". |
| 54 | + if (type.isIntersection()) { |
| 55 | + return type.types.reduce( |
| 56 | + (res, t) => [...res, ...getClassLikeDocsFromType(t, baseDoc, exportSymbolsToDocsMap)], |
| 57 | + [] as ClassLikeExportDoc[]); |
| 58 | + } else if (symbol) { |
| 59 | + // If the given symbol has already been registered within Dgeni, we use the |
| 60 | + // existing symbol instead of creating a new one. The dgeni typescript package |
| 61 | + // keeps track of all exported symbols and their corresponding docs. See: |
| 62 | + // dgeni-packages/blob/master/typescript/src/processors/linkInheritedDocs.ts |
| 63 | + if (exportSymbolsToDocsMap.has(symbol)) { |
| 64 | + return [exportSymbolsToDocsMap.get(symbol)!]; |
| 65 | + } |
| 66 | + let createdDoc: ClassLikeExportDoc|null = null; |
| 67 | + if ((symbol.flags & ts.SymbolFlags.Class) !== 0) { |
| 68 | + createdDoc = new ClassExportDoc(baseDoc.host, baseDoc.moduleDoc, symbol, aliasSymbol); |
| 69 | + } else if ((symbol.flags & ts.SymbolFlags.Interface) !== 0) { |
| 70 | + createdDoc = new InterfaceExportDoc(baseDoc.host, baseDoc.moduleDoc, symbol, aliasSymbol); |
| 71 | + } |
| 72 | + // If a new document has been created, add it to the shared symbol |
| 73 | + // docs map and return it. |
| 74 | + if (createdDoc) { |
| 75 | + exportSymbolsToDocsMap.set(aliasSymbol || symbol, createdDoc); |
| 76 | + return [createdDoc]; |
| 77 | + } |
| 78 | + } |
| 79 | + return []; |
16 | 80 | }
|
0 commit comments