mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
This new processor, named `mergeOverriddenImplementation`, allows Dgeni to produce correct documentation for symbols with overridden exported constructors. For example, in the following example, the implementation documentation will be used, including the constructor signature: ``` export const Foo: FooCtor = FooImpl as FooCtor; ``` This is a major improvement over the current situation, in which no constructor signature is documented whatsoever. PR Close #44689
103 lines
4.4 KiB
JavaScript
103 lines
4.4 KiB
JavaScript
/**
|
|
* In some cases it is desirable to override the exported implementation and constructor for a symbol.
|
|
* This is useful for a few reasons:
|
|
* - A symbol could have multiple constructors
|
|
* - It is possible to disambiguate multiple different signatures from a single polymorphic constructor
|
|
* - The return type of the overridden constructor can differ (e.g. `Foo<T|null>` vs `Foo<T>`)
|
|
*
|
|
* This looks like the following:
|
|
*
|
|
* ```
|
|
* export interface Foo {
|
|
* bar();
|
|
* }
|
|
*
|
|
* export class FooImpl {
|
|
* bar() {}
|
|
* }
|
|
*
|
|
* export interface FooCtor {
|
|
* new(): Foo;
|
|
* }
|
|
*
|
|
* export const Foo: FooCtor = FooImpl as FooCtor;
|
|
* ```
|
|
*
|
|
* This processor will correct the docs for symbol `Foo` by copying them over from `FooImpl`
|
|
* to the exported symbol `Foo`. The processor will also copy all documented constructor overrides from `FooCtor`.
|
|
*
|
|
* In order to use this processor, annotate the exported constant with `@overriddenImplementation`,
|
|
* and mark the implementation and constructor types as `@internal`. Place the desired
|
|
* documentation on the implementation class.
|
|
*/
|
|
module.exports = function mergeOverriddenImplementation(getDocFromAlias, log) {
|
|
return {
|
|
$runAfter: ['tags-extracted', 'ids-computed'],
|
|
$runBefore: ['filterPrivateDocs'],
|
|
propertiesToKeep: [
|
|
'name', 'id', 'aliases', 'fileInfo', 'startingLine', 'endingLine',
|
|
'path', 'originalModule', 'outputPath', 'privateExport', 'moduleDoc'
|
|
],
|
|
$process(docs) {
|
|
docs.forEach(doc => {
|
|
if (doc.overriddenImplementation) {
|
|
// Check the AST is of the expected expression shape, and extract the identifiers.
|
|
const symbolAstObjects = [doc.declaration?.name, doc.declaration?.type, doc.declaration?.initializer?.expression];
|
|
if (symbolAstObjects.some(symbol => symbol === undefined)) {
|
|
throw new Error('@overriddenImplementation must have format `export const Foo: FooCtor = FooImpl as FooCtor;`');
|
|
}
|
|
|
|
// Convert the AST nodes into docs.
|
|
const symbolNames = symbolAstObjects.map(s => s.getText());
|
|
const symbolDocArrays = symbolNames.map(symbol => getDocFromAlias(symbol));
|
|
for (let i = 0; i < symbolDocArrays.length; i++) {
|
|
if (symbolDocArrays[i].length === 0) {
|
|
throw new Error(`@overriddenImplementation failed to find a doc for ${symbolNames[i]}. Are you sure this symbol is documented and exported?`);
|
|
}
|
|
if (symbolDocArrays[i].length >= 2) {
|
|
throw new Error(`@overriddenImplementation found multiple docs for ${symbolNames[i]}. You may only have one documented symbol for each.`);
|
|
}
|
|
}
|
|
const symbolDocs = symbolDocArrays.map(a => a[0]);
|
|
const exportedNameDoc = symbolDocs[0];
|
|
const ctorDoc = symbolDocs[1];
|
|
const implDoc = symbolDocs[2];
|
|
|
|
// Clean out the unwanted properties from the exported doc.
|
|
Object.keys(doc).forEach(key => {
|
|
if (!this.propertiesToKeep.includes(key)) {
|
|
delete doc[key];
|
|
}
|
|
});
|
|
|
|
// Copy over all the properties from the implementation doc.
|
|
Object.keys(implDoc).forEach(key => {
|
|
if (!this.propertiesToKeep.includes(key)) {
|
|
exportedNameDoc[key] = implDoc[key];
|
|
}
|
|
});
|
|
|
|
// Copy the constructor overrides from the constructor doc, if any are present.
|
|
if (!ctorDoc.members || ctorDoc.members.length !== 1 || !ctorDoc.members[0].name.includes('new')) {
|
|
throw new Error(`@overriddenImplementation requires that the provided constructor ${symbolNames[1]} have exactly one member called "new", possibly with multiple overrides.`);
|
|
}
|
|
exportedNameDoc.constructorDoc = ctorDoc.members[0];
|
|
|
|
// Mark symbols other than the exported name as internal.
|
|
if (!ctorDoc.internal) {
|
|
log.warn(`Constructor doc ${symbolNames[1]} was not marked '@internal'; adding this annotation.`);
|
|
ctorDoc.internal = true;
|
|
}
|
|
if (!implDoc.internal) {
|
|
log.warn(`Implementation doc ${symbolNames[2]} was not marked '@internal'; adding this annotation.`);
|
|
implDoc.internal = true;
|
|
}
|
|
|
|
// The exported doc should not be private, unlike the implementation doc.
|
|
exportedNameDoc.privateExport = false;
|
|
exportedNameDoc.internal = false;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
};
|