Skip to content
Open
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
148 changes: 67 additions & 81 deletions plugins/typedoc-plugin-react-components/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
type Context,
Converter,
DeclarationReflection,
IntersectionType,
IntrinsicType,
ParameterReflection,
PredicateType,
Expand All @@ -21,7 +20,15 @@ import {
} from 'typedoc';
import ts, { ModifierFlags, SymbolFlags } from 'typescript';

const excludeBaseTypes = ['LitElement', 'HTMLElement'];
const excludeBaseTypes = [
'LitElement',
'HTMLElement',
'FormRequiredInterface',
'FormAssociatedCheckboxElementInterface',
'FormAssociatedElementInterface',
'MaskBehaviorElementInterface',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this list might need an update since the mixins deff have changed.
Would be better if we can just access the ts.Type with all resolved properties directly and just have LitElement / HTMLElement as filter/cut-off point down the chain.

Although, even that is not entirely true, since we do actually re-use some of the native props (think input name, focus/validity methods - not necessary re-declaring them). Not sure how many, but might want to figure out a way to highlight important ones if needed.

];
const excludeProperties = ['emitEvent'];

export function load(app: Application) {
app.converter.on(
Expand Down Expand Up @@ -90,9 +97,6 @@ export function load(app: Application) {
// The 3rd element is for the `renderProps` prop of the `createComponent` method, but this is only declaration of them.
// Definition of them should be handled in the 1st part.

// Fill out "extendedTypes" section in TypeDoc with base types
extractBaseType(type.aliasTypeArguments[0], context, reflection);

// Save a reference to the wc component name for reference in case of deeper WC types being resolved.
reflection.implementationOf = ReferenceType.createResolvedReference(
type.aliasTypeArguments[0].symbol.name,
Expand Down Expand Up @@ -179,55 +183,80 @@ export function load(app: Application) {
}

function parseTypeProperties(type: any, context: Context) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type: any is no fun :)
Okay, so we do have types / possibly type checked access (at least some form of compile time running). Good to know.

if (excludeBaseTypes.includes(type.symbol?.name)) {
if (
type.symbol?.name.includes('EventEmitterInterface') ||
excludeBaseTypes.includes(type.symbol?.name)
) {
Comment on lines +186 to +189
return;
}
const props = type.declaredProperties || type.symbol?.members;
props?.forEach((value: ts.Symbol, key: ts.__String) => {
props?.forEach((value: ts.Symbol) => {
const memberDeclaration = value?.declarations?.length
? (value.declarations[0] as any)
: ((value.valueDeclaration as any) ?? null);
const modifiers = memberDeclaration
? ts.getCombinedModifierFlags(memberDeclaration)
: ModifierFlags.None;
const memberName = value.name;
if (
!key.toString().startsWith('_') &&
(modifiers === ModifierFlags.None ||
memberName.toString().startsWith('_') ||
excludeProperties.includes(memberName) ||
!(
modifiers === ModifierFlags.None ||
modifiers === ModifierFlags.Public ||
modifiers === ModifierFlags.Static)
modifiers === ModifierFlags.Static
)
) {
let reflectionKind = ReflectionKind.Property;
let category = 'Other';
switch (value.flags) {
case SymbolFlags.GetAccessor:
category = 'Accessors';
reflectionKind = ReflectionKind.Accessor;
break;
case SymbolFlags.Method:
category = 'Methods';
reflectionKind = ReflectionKind.Method;
break;
case SymbolFlags.Accessor:
case SymbolFlags.Property:
category = 'Properties';
}
if (value.flags.toString() === '16777220') {
// For some reason optional properties get flagged to this number, even though the optional is 16777216
category = 'Properties';
}
if (category === 'Other') {
// Other types we just ignore creating.
return;
}
return;
}

createMemberDeclaration(context, value, reflectionKind, category);
let reflectionKind = ReflectionKind.Property;
let category = 'Other';
switch (value.flags) {
case SymbolFlags.GetAccessor:
category = 'Accessors';
reflectionKind = ReflectionKind.Accessor;
break;
case SymbolFlags.Method:
category = 'Methods';
reflectionKind = ReflectionKind.Method;
break;
case SymbolFlags.Accessor:
case SymbolFlags.Property:
category = 'Properties';
}
if (value.flags.toString() === '16777220') {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Um yeah, 16777220 is still optional because 16777220 & 16777216 = 16777216, a.k.a how you check flags usually is:

Suggested change
if (value.flags.toString() === '16777220') {
if (value.flags & ts.SymbolFlags.Optional) {

The type checker sometime have some higher level API for such checks, but not all.

PS: 16777220 is pretty much 16777216 with additional flag flipped in the ^2 position.
My fave snippet for flags: Object.keys(ts.SymbolFlags).filter(x => !x.endsWith('Excludes') && ts.SymbolFlags[x] & <value>) (obv good for NodeFlags, TypeFlags, etc) which for 16777220 returns:

['Property', 'Optional', 'All', 'Value', 'PropertyOrAccessor', 'ClassMember']

a.k.a pretty much optional member - what you're looking :)

// For some reason optional properties get flagged to this number, even though the optional is 16777216
category = 'Properties';
}
if (category === 'Other') {
// Other types we just ignore creating.
return;
}

createMemberDeclaration(context, value, reflectionKind, category);
});

if (type.resolvedBaseTypes) {
for (const baseType of type.resolvedBaseTypes) {
parseTypeProperties(baseType, context);
}
resolveBaseTypeProps(type, context);
}

function resolveBaseTypeProps(type: any, context: Context) {
if (!type.baseTypesResolved || type.resolvedBaseTypes?.length === 0) {
return;
}
Comment on lines +244 to +246

let baseTypes = [];
if (type.resolvedBaseTypes[0].symbol) {
baseTypes = type.resolvedBaseTypes;
} else {
baseTypes = type.resolvedBaseTypes[0].types;
}
if (!baseTypes) {
return;
}

for (const baseType of baseTypes) {
parseTypeProperties(baseType, context);
}
}

Expand Down Expand Up @@ -354,49 +383,6 @@ function createEventDeclaration(context: Context, value: ts.Symbol) {
return reflection;
}

function extractBaseType(type: any, context: Context, reflection: SignatureReflection) {
if (!type.baseTypesResolved) {
return;
}
assert(context.scope instanceof DeclarationReflection);
// Expected only 1 item so far?
const resolvedBaseTypeString = context.checker.typeToString(type.resolvedBaseTypes[0]);
const intersectTypes = resolvedBaseTypeString.split('&').map((t) => t.trim());
if (intersectTypes.length > 1) {
for (const [index, typeName] of intersectTypes.entries()) {
if (typeName.includes('EventEmitterInterface') || excludeBaseTypes.includes(typeName)) {
continue;
}

const baseType = type.resolvedBaseTypes[0].types[index];
const refType = ReferenceType.createResolvedReference(
baseType.symbol.name,
reflection,
context.project,
);
if (context.scope.extendedTypes && context.scope.extendedTypes[0].type === 'intersection') {
(context.scope.extendedTypes[0] as IntersectionType).types.push(refType);
} else if (context.scope.extendedTypes) {
const intersectionType = new IntersectionType(context.scope.extendedTypes);
intersectionType.types.push(refType);
context.scope.extendedTypes = [intersectionType];
} else {
context.scope.extendedTypes = [refType];
}
}
} else if (!excludeBaseTypes.includes(resolvedBaseTypeString)) {
const baseType = type.resolvedBaseTypes[0];
const refType = ReferenceType.createResolvedReference(
baseType.symbol.name,
reflection,
context.project,
);
context.scope.extendedTypes = context.scope.extendedTypes
? [...context.scope.extendedTypes, refType]
: [refType];
}
}

//#region Taken from typedoc source for creating a generic signature of a Symbol.
function removeUndefined(type: SomeType): SomeType {
if (type instanceof UnionType) {
Expand Down