Skip to content
This repository has been archived by the owner on Mar 10, 2024. It is now read-only.

refactor: use dgeni doc to fetch decorator data #30

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libs/ngx-doc-gen/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ngx-doc-gen",
"version": "2.0.1",
"version": "2.1.0",
"description": "Automagically generate your Angular documentation.",
"builders": "./executors.json",
"executors": "./executors.json",
Expand Down
38 changes: 17 additions & 21 deletions libs/ngx-doc-gen/src/common/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,36 +66,32 @@ export function isPrimaryExportDoc(doc: ApiDoc): boolean {
return hasJsDocTag(doc, 'docs-primary-export');
}

/**
* Gets the stored metadata value by name.
* @returns stored metadata or `undefined`
*/
function getMetadata(
classDoc: CategorizedClassDoc,
name: string
): string | undefined {
return classDoc.directiveMetadata.get(name)?.replace(/[\r\n']/g, '');
}

export function getDirectiveSelectors(
classDoc: CategorizedClassDoc
): string[] | undefined {
if (classDoc.directiveMetadata) {
const directiveSelectors: string =
classDoc.directiveMetadata.get('selector');

if (directiveSelectors) {
return directiveSelectors
.replace(/[\r\n]/g, '')
.split(/\s*,\s*/)
.filter((s) => s !== '');
}
}
return undefined;
return getMetadata(classDoc, 'selector')
?.split(/\s*,\s*/)
?.filter((s) => s !== '');
}

export function getPipeName(classDoc: CategorizedClassDoc): string | undefined {
if (classDoc.directiveMetadata) {
return classDoc.directiveMetadata.get('name');
}
return undefined;
return getMetadata(classDoc, 'name');
}

export function isStandalone(classDoc: CategorizedClassDoc): boolean {
if (classDoc.directiveMetadata) {
const standalone = classDoc.directiveMetadata.get('standalone');
return standalone != null && `${standalone}` !== 'false';
}
return false;
const standalone = getMetadata(classDoc, 'standalone');
return standalone !== undefined && `${standalone}` !== 'false';
}

export function hasMemberDecorator(
Expand Down
2 changes: 1 addition & 1 deletion libs/ngx-doc-gen/src/common/dgeni-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface CategorizedClassDoc
directiveExportAs?: string | null;
directiveSelectors?: string[];
pipeName?: string;
directiveMetadata: Map<string, any> | null;
directiveMetadata: Map<string, string> | null;
extendedDoc: ClassLikeExportDoc | undefined;
inheritedDocs: ClassLikeExportDoc[];
}
Expand Down
94 changes: 17 additions & 77 deletions libs/ngx-doc-gen/src/common/directive-metadata.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,9 @@
import {
ArrayLiteralExpression,
BooleanLiteral,
CallExpression,
getDecorators,
isCallExpression,
isClassDeclaration,
NodeArray,
ObjectLiteralExpression,
PropertyAssignment,
StringLiteral,
SyntaxKind,
} from 'typescript';
import { CategorizedClassDoc } from './dgeni-definitions';

/**
* Determines the component or directive metadata from the specified Dgeni class doc. The resolved
* directive metadata will be stored in a Map.
*
* Currently only string literal assignments and array literal assignments are supported. Other
* value types are not necessary because they are not needed for any user-facing documentation.
*
* ```ts
* @Component({
* inputs: ["red", "blue"],
Expand All @@ -30,76 +14,32 @@ import { CategorizedClassDoc } from './dgeni-definitions';
*/
export function getDirectiveMetadata(
classDoc: CategorizedClassDoc
): Map<string, any> | null {
const declaration = classDoc.symbol.valueDeclaration;
const decorators =
declaration && isClassDeclaration(declaration)
? getDecorators(declaration)
: null;
): Map<string, string> | null {
const decorators = classDoc.decorators || [];

if (!decorators?.length) {
return null;
}

const expression = decorators
const angularDecorators = decorators
.filter((decorator) => decorator.isCallExpression)
.filter(
(decorator) =>
decorator.expression && isCallExpression(decorator.expression)
)
.map((decorator) => decorator.expression as CallExpression)
.find(
(callExpression) =>
callExpression.expression.getText() === 'Component' ||
callExpression.expression.getText() === 'Directive' ||
callExpression.expression.getText() === 'Pipe'
decorator.name === 'Component' ||
decorator.name === 'Directive' ||
decorator.name === 'Pipe'
);

if (!expression) {
return null;
}

// The argument length of the CallExpression needs to be exactly one, because it's the single
// JSON object in the @Component/@Directive decorator.
if (expression.arguments.length !== 1) {
// a Component/Directive/Pipe should be decorated with only one decorator
if (angularDecorators.length !== 1) {
return null;
}

const objectExpression = expression.arguments[0] as ObjectLiteralExpression;
const resultMetadata = new Map<string, any>();

(objectExpression.properties as NodeArray<PropertyAssignment>).forEach(
(prop) => {
// Support ArrayLiteralExpression assignments in the directive metadata.
if (prop.initializer.kind === SyntaxKind.ArrayLiteralExpression) {
const arrayData = (
prop.initializer as ArrayLiteralExpression
).elements.map((literal) => (literal as StringLiteral).text);

resultMetadata.set(prop.name.getText(), arrayData);
}

// Support normal StringLiteral and NoSubstitutionTemplateLiteral assignments
if (
prop.initializer.kind === SyntaxKind.StringLiteral ||
prop.initializer.kind === SyntaxKind.NoSubstitutionTemplateLiteral
) {
resultMetadata.set(
prop.name.getText(),
(prop.initializer as StringLiteral).text
);
}
const processDecorator = angularDecorators[0];

// Support BooleanLiteral assignments
if (
prop.initializer.kind === SyntaxKind.TrueKeyword ||
prop.initializer.kind === SyntaxKind.FalseKeyword
) {
resultMetadata.set(
prop.name.getText(),
prop.initializer.kind === SyntaxKind.TrueKeyword
);
}
}
const resultMetadata = new Map<string, string>();
(processDecorator.argumentInfo || []).forEach((argumentInfo) =>
Object.entries(argumentInfo)
.filter(([, argumentInfo]) => typeof argumentInfo === 'string')
.forEach(([key, argumentInfo]: [string, string]) =>
resultMetadata.set(key, argumentInfo)
)
);

return resultMetadata;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Directive } from '@angular/core';

@Directive({
selector: '[ngxDocGenStandaloneApi]',
selector: '[ngxDocGenStandaloneApi], test[test]',
standalone: true,
})
export class StandaloneApiDirective {}