Skip to content

Commit 687ae53

Browse files
committed
fix: Static methods added to the class manually in JS
Closes #1481
1 parent b2a88ba commit 687ae53

File tree

6 files changed

+309
-99
lines changed

6 files changed

+309
-99
lines changed

src/lib/converter/converter.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { relative } from "path";
1616
import { getCommonDirectory } from "../utils/fs";
1717
import { createMinimatch } from "../utils/paths";
1818
import { IMinimatch } from "minimatch";
19-
import { hasFlag } from "../utils/enum";
19+
import { hasAllFlags, hasAnyFlag } from "../utils/enum";
2020
import { resolveAliasedSymbol } from "./utils/symbols";
2121

2222
/**
@@ -369,7 +369,7 @@ export class Converter extends ChildableComponent<
369369
if (
370370
this.excludeNotDocumented &&
371371
// If the enum is included, we should include members even if not documented.
372-
!hasFlag(symbol.flags, ts.SymbolFlags.EnumMember) &&
372+
!hasAllFlags(symbol.flags, ts.SymbolFlags.EnumMember) &&
373373
resolveAliasedSymbol(symbol, checker).getDocumentationComment(
374374
checker
375375
).length === 0
@@ -449,21 +449,30 @@ function getExports(
449449
node: ts.SourceFile | ts.ModuleBlock,
450450
symbol: ts.Symbol | undefined
451451
): ts.Symbol[] {
452-
const exports: ts.Symbol[] = [];
453-
454452
// The generated docs aren't great, but you really ought not be using
455453
// this in the first place... so it's better than nothing.
456454
const exportEq = symbol?.exports?.get("export=" as ts.__String);
457455
if (exportEq) {
458-
exports.push(exportEq);
456+
// JS users might also have exported types here.
457+
// We need to filter for types because otherwise static methods can show up as both
458+
// members of the export= class and as functions if a class is directly exported.
459+
return [exportEq].concat(
460+
context.checker
461+
.getExportsOfModule(symbol!)
462+
.filter(
463+
(s) =>
464+
!hasAnyFlag(
465+
s.flags,
466+
ts.SymbolFlags.Prototype | ts.SymbolFlags.Value
467+
)
468+
)
469+
);
459470
}
460471

461472
if (symbol) {
462-
return exports.concat(
463-
context.checker
464-
.getExportsOfModule(symbol)
465-
.filter((s) => !hasFlag(s.flags, ts.SymbolFlags.Prototype))
466-
);
473+
return context.checker
474+
.getExportsOfModule(symbol)
475+
.filter((s) => !hasAllFlags(s.flags, ts.SymbolFlags.Prototype));
467476
}
468477

469478
// Global file with no inferred top level symbol, get all symbols declared in this file.

src/lib/converter/symbols.ts

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import {
88
ReflectionKind,
99
TypeParameterReflection,
1010
} from "../models";
11-
import { flatMap, uniqueByEquals, zip } from "../utils/array";
12-
import { getEnumFlags, hasFlag, removeFlag } from "../utils/enum";
11+
import { flatMap, uniqueByEquals } from "../utils/array";
12+
import { getEnumFlags, hasAllFlags, removeFlag } from "../utils/enum";
1313
import { Context } from "./context";
1414
import { convertDefaultValue } from "./convert-expression";
1515
import { ConverterEvents } from "./converter-events";
@@ -86,21 +86,21 @@ export function convertSymbol(
8686
// Declaration merging - the only type (excluding enum/enum, ns/ns, etc)
8787
// that TD supports is merging a class and interface. All others are
8888
// represented as multiple reflections
89-
if (hasFlag(symbol.flags, ts.SymbolFlags.Class)) {
89+
if (hasAllFlags(symbol.flags, ts.SymbolFlags.Class)) {
9090
flags = removeFlag(flags, ts.SymbolFlags.Interface);
9191
}
9292

9393
// Kind of declaration merging... we treat this as a property with get/set signatures.
94-
if (hasFlag(symbol.flags, ts.SymbolFlags.GetAccessor)) {
94+
if (hasAllFlags(symbol.flags, ts.SymbolFlags.GetAccessor)) {
9595
flags = removeFlag(flags, ts.SymbolFlags.SetAccessor);
9696
}
9797

98-
if (hasFlag(symbol.flags, ts.SymbolFlags.NamespaceModule)) {
98+
if (hasAllFlags(symbol.flags, ts.SymbolFlags.NamespaceModule)) {
9999
// This might be here if a namespace is declared several times.
100100
flags = removeFlag(flags, ts.SymbolFlags.ValueModule);
101101
}
102102

103-
if (hasFlag(symbol.flags, ts.SymbolFlags.Method)) {
103+
if (hasAllFlags(symbol.flags, ts.SymbolFlags.Method)) {
104104
// This happens when someone declares an object with methods:
105105
// { methodProperty() {} }
106106
flags = removeFlag(flags, ts.SymbolFlags.Property);
@@ -166,6 +166,17 @@ function convertNamespace(
166166
symbol: ts.Symbol,
167167
nameOverride?: string
168168
) {
169+
// This can happen in JS land where a user defines a class using a mixture
170+
// of ES6 class syntax and adding properties to the class manually.
171+
if (
172+
symbol
173+
.getDeclarations()
174+
?.some((d) => ts.isModuleDeclaration(d) || ts.isSourceFile(d)) ===
175+
false
176+
) {
177+
return;
178+
}
179+
169180
const reflection = context.createDeclarationReflection(
170181
ReflectionKind.Namespace,
171182
symbol,
@@ -266,7 +277,7 @@ function convertFunctionOrMethod(
266277
isMethod &&
267278
isInherited(context, symbol) &&
268279
declarations.length > 0 &&
269-
hasFlag(
280+
hasAllFlags(
270281
ts.getCombinedModifierFlags(declarations[0]),
271282
ts.ModifierFlags.Private
272283
)
@@ -314,12 +325,15 @@ function convertFunctionOrMethod(
314325

315326
const scope = context.withScope(reflection);
316327
reflection.signatures ??= [];
317-
for (const [signature, declaration] of zip(signatures, declarations)) {
328+
329+
// Can't use zip here. We might have less declarations than signatures
330+
// or less signatures than declarations.
331+
for (let i = 0; i < signatures.length; i++) {
318332
const converted = createSignature(
319333
scope,
320334
ReflectionKind.CallSignature,
321-
signature,
322-
declaration
335+
signatures[i],
336+
declarations[i]
323337
);
324338
reflection.signatures.push(converted);
325339
}
@@ -364,6 +378,12 @@ function convertClassOrInterface(
364378
)
365379
continue;
366380
convertSymbol(reflectionContext, prop);
381+
382+
// We need to do this because of JS users. See GH1481
383+
const refl = context.project.getReflectionFromSymbol(prop);
384+
if (refl) {
385+
refl.setFlag(ReflectionFlag.Static);
386+
}
367387
}
368388

369389
const constructMember = new DeclarationReflection(
@@ -487,7 +507,7 @@ function convertProperty(
487507
if (
488508
isInherited(context, symbol) &&
489509
declarations.length > 0 &&
490-
hasFlag(
510+
hasAllFlags(
491511
ts.getCombinedModifierFlags(declarations[0]),
492512
ts.ModifierFlags.Private
493513
)
@@ -740,11 +760,11 @@ function convertVariable(
740760
// Does anyone care about this? I doubt it...
741761
if (
742762
ts.isVariableDeclaration(declaration) &&
743-
hasFlag(symbol.flags, ts.SymbolFlags.BlockScopedVariable)
763+
hasAllFlags(symbol.flags, ts.SymbolFlags.BlockScopedVariable)
744764
) {
745765
reflection.setFlag(
746766
ReflectionFlag.Const,
747-
hasFlag(declaration.parent.flags, ts.NodeFlags.Const)
767+
hasAllFlags(declaration.parent.flags, ts.NodeFlags.Const)
748768
);
749769
}
750770

@@ -774,11 +794,11 @@ function convertVariableAsFunction(
774794
// Does anyone care about this? I doubt it...
775795
if (
776796
declaration &&
777-
hasFlag(symbol.flags, ts.SymbolFlags.BlockScopedVariable)
797+
hasAllFlags(symbol.flags, ts.SymbolFlags.BlockScopedVariable)
778798
) {
779799
reflection.setFlag(
780800
ReflectionFlag.Const,
781-
hasFlag(
801+
hasAllFlags(
782802
(declaration || symbol.valueDeclaration).parent.flags,
783803
ts.NodeFlags.Const
784804
)
@@ -860,13 +880,13 @@ function setModifiers(declaration: ts.Declaration, reflection: Reflection) {
860880
const modifiers = ts.getCombinedModifierFlags(declaration);
861881
// Note: We only set this flag if the modifier is present because we allow
862882
// fake "private" or "protected" members via @private and @protected
863-
if (hasFlag(modifiers, ts.ModifierFlags.Private)) {
883+
if (hasAllFlags(modifiers, ts.ModifierFlags.Private)) {
864884
reflection.setFlag(ReflectionFlag.Private);
865885
}
866-
if (hasFlag(modifiers, ts.ModifierFlags.Protected)) {
886+
if (hasAllFlags(modifiers, ts.ModifierFlags.Protected)) {
867887
reflection.setFlag(ReflectionFlag.Protected);
868888
}
869-
if (hasFlag(modifiers, ts.ModifierFlags.Public)) {
889+
if (hasAllFlags(modifiers, ts.ModifierFlags.Public)) {
870890
reflection.setFlag(ReflectionFlag.Public);
871891
}
872892
reflection.setFlag(
@@ -875,14 +895,14 @@ function setModifiers(declaration: ts.Declaration, reflection: Reflection) {
875895
);
876896
reflection.setFlag(
877897
ReflectionFlag.Readonly,
878-
hasFlag(modifiers, ts.ModifierFlags.Readonly)
898+
hasAllFlags(modifiers, ts.ModifierFlags.Readonly)
879899
);
880900
reflection.setFlag(
881901
ReflectionFlag.Abstract,
882-
hasFlag(modifiers, ts.ModifierFlags.Abstract)
902+
hasAllFlags(modifiers, ts.ModifierFlags.Abstract)
883903
);
884904
reflection.setFlag(
885905
ReflectionFlag.Static,
886-
hasFlag(modifiers, ts.ModifierFlags.Static)
906+
hasAllFlags(modifiers, ts.ModifierFlags.Static)
887907
);
888908
}

src/lib/utils/enum.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export function removeFlag<T extends number>(flag: T, remove: T & {}): T {
1414
return ((flag ^ remove) & flag) as T;
1515
}
1616

17-
export function hasFlag(flags: number, check: number): boolean {
17+
export function hasAllFlags(flags: number, check: number): boolean {
1818
return (flags & check) === check;
1919
}
20+
21+
export function hasAnyFlag(flags: number, check: number): boolean {
22+
return (flags & check) !== 0;
23+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class GH1481 {}
2+
/** static docs */
3+
GH1481.static = function () {};
4+
5+
module.exports = GH1481;

src/test/converter/js/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,7 @@ export const ColumnType = {
5959
STRING: "string",
6060
NUMBER: "number",
6161
};
62+
63+
export class GH1481 {}
64+
/** static docs */
65+
GH1481.static = function () {};

0 commit comments

Comments
 (0)