Skip to content

Commit

Permalink
Improve handling of function-namespaces
Browse files Browse the repository at this point in the history
  • Loading branch information
Gerrit0 committed Dec 27, 2023
1 parent 56aef14 commit aaa5f35
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 248 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@
- Added special cases for converting methods which are documented as returning `this` or accepting `this` as a parameter, #2458.
Note: This will only happen if a method is declared as `method(): this`, it will not happen if the method implicitly returns `this`
as the compiler strips that information when creating types for a class instance.
- Improved handling of functions with properties. Previous TypeDoc versions would always create a separate
namespace for properties, now, TypeDoc will create a separate namespace if the function is declaration merged
with a namespace. If the properties are added via `Object.assign` or via property assignment on the function
TypeDoc will now instead add the properties to the function's page, #2461.

### Bug Fixes

- Navigation triangle markers should no longer display on a separate line with some font settings, #2457.
- `@group` and `@category` organization is now applied later to allow inherited comments to create groups/categories, #2459.
- Keyword syntax highlighting introduced in 0.25.4 was not always applied to keywords.
- If all members in a group are hidden from the page, the group will be hidden in the page index on page load.

## v0.25.4 (2023-11-26)

Expand Down
66 changes: 39 additions & 27 deletions src/lib/converter/symbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const conversionOrder = [
ts.SymbolFlags.ExportValue,

ts.SymbolFlags.TypeAlias,
ts.SymbolFlags.Function,
ts.SymbolFlags.Function, // Before NamespaceModule
ts.SymbolFlags.Method,
ts.SymbolFlags.Interface,
ts.SymbolFlags.Property,
Expand Down Expand Up @@ -161,6 +161,10 @@ export function convertSymbol(

if (hasAllFlags(symbol.flags, ts.SymbolFlags.NamespaceModule)) {
// This might be here if a namespace is declared several times.
// Or if it's a namespace-like thing defined on a function
// In the function case, it's important to remove ValueModule so that
// if we convert the children as properties of the function rather than as
// a separate namespace, we skip creating the namespace.
flags = removeFlag(flags, ts.SymbolFlags.ValueModule);
}

Expand Down Expand Up @@ -460,11 +464,7 @@ function convertFunctionOrMethod(
const signatures = type.getNonNullableType().getCallSignatures();

const reflection = context.createDeclarationReflection(
context.scope.kindOf(
ReflectionKind.ClassOrInterface |
ReflectionKind.VariableOrProperty |
ReflectionKind.TypeLiteral,
)
context.scope.kindOf(ReflectionKind.MethodContainer)
? ReflectionKind.Method
: ReflectionKind.Function,
symbol,
Expand All @@ -485,6 +485,10 @@ function convertFunctionOrMethod(
for (const sig of signatures) {
createSignature(scope, ReflectionKind.CallSignature, sig, symbol);
}

convertFunctionProperties(scope, symbol, type);

return ts.SymbolFlags.NamespaceModule;
}

// getDeclaredTypeOfSymbol gets the INSTANCE type
Expand Down Expand Up @@ -635,9 +639,7 @@ function convertProperty(
) {
// This might happen if we're converting a function-module created with Object.assign
// or `export default () => {}`
if (
context.scope.kindOf(ReflectionKind.SomeModule | ReflectionKind.Project)
) {
if (context.scope.kindOf(ReflectionKind.VariableContainer)) {
return convertVariable(context, symbol, exportSymbol);
}

Expand Down Expand Up @@ -687,7 +689,7 @@ function convertProperty(
}

const reflection = context.createDeclarationReflection(
context.scope.kindOf(ReflectionKind.Namespace)
context.scope.kindOf(ReflectionKind.VariableContainer)
? ReflectionKind.Variable
: ReflectionKind.Property,
symbol,
Expand Down Expand Up @@ -878,7 +880,9 @@ function convertVariable(
}

const reflection = context.createDeclarationReflection(
ReflectionKind.Variable,
context.scope.kindOf(ReflectionKind.VariableContainer)
? ReflectionKind.Variable
: ReflectionKind.Property,
symbol,
exportSymbol,
);
Expand Down Expand Up @@ -1000,7 +1004,9 @@ function convertVariableAsFunction(
: context.checker.getDeclaredTypeOfSymbol(symbol);

const reflection = context.createDeclarationReflection(
ReflectionKind.Function,
context.scope.kindOf(ReflectionKind.MethodContainer)
? ReflectionKind.Method
: ReflectionKind.Function,
symbol,
exportSymbol,
);
Expand All @@ -1020,25 +1026,31 @@ function convertVariableAsFunction(
);
}

// #2436: Functions created with Object.assign on a function won't have a namespace flag
// but likely have properties that we should put into a namespace.
convertFunctionProperties(context.withScope(reflection), symbol, type);

return ts.SymbolFlags.Property | ts.SymbolFlags.NamespaceModule;
}

function convertFunctionProperties(
context: Context,
symbol: ts.Symbol,
type: ts.Type,
) {
// #2436/#2461: Functions created with Object.assign on a function likely have properties
// that we should document. We also add properties to the function if they are defined like:
// function f() {}
// f.x = 123;
// rather than creating a separate namespace.
// In the expando case, we'll have both namespace flags.
// In the Object.assign case, we'll have no namespace flags.
const nsFlags = ts.SymbolFlags.ValueModule | ts.SymbolFlags.NamespaceModule;
if (
type.getProperties().length &&
!hasAnyFlag(
symbol.flags,
ts.SymbolFlags.NamespaceModule | ts.SymbolFlags.ValueModule,
)
(hasAllFlags(symbol.flags, nsFlags) ||
!hasAnyFlag(symbol.flags, nsFlags))
) {
const ns = context.createDeclarationReflection(
ReflectionKind.Namespace,
symbol,
exportSymbol,
);
context.finalizeDeclarationReflection(ns);
convertSymbols(context.withScope(ns), type.getProperties());
convertSymbols(context, type.getProperties());
}

return ts.SymbolFlags.Property;
}

function convertAccessor(
Expand Down
8 changes: 8 additions & 0 deletions src/lib/models/reflections/kind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ export namespace ReflectionKind {
export const SignatureContainer =
ContainsCallSignatures | ReflectionKind.Accessor;

export const VariableContainer = SomeModule | ReflectionKind.Project;

export const MethodContainer =
ClassOrInterface |
VariableOrProperty |
FunctionOrMethod |
ReflectionKind.TypeLiteral;

const SINGULARS = {
[ReflectionKind.Enum]: "Enumeration",
[ReflectionKind.EnumMember]: "Enumeration Member",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export class Filter extends Component {
this.setLocalStorage(this.fromLocalStorage());

style.innerHTML += `html:not(.${this.key}) .tsd-is-${this.el.name} { display: none; }\n`;
this.handleValueChange();
}

/**
Expand Down
6 changes: 6 additions & 0 deletions src/lib/utils/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ export function hasAnyFlag(flags: number, check: number): boolean {
return (flags & check) !== 0;
}

export function debugFlags(Enum: {}, flags: number): string[] {
return getEnumKeys(Enum).filter(
(key) => ((Enum as any)[key] & flags) === (Enum as any)[key],
);
}

// Note: String enums are not handled.
export function getEnumKeys(Enum: {}): string[] {
const E = Enum as any;
Expand Down

0 comments on commit aaa5f35

Please sign in to comment.