Skip to content

Commit a2d9dd8

Browse files
committed
fix: Pick up optional/readonly from mapped types
Resolves TypeStrong#1509
1 parent e3aa04f commit a2d9dd8

File tree

5 files changed

+855
-482
lines changed

5 files changed

+855
-482
lines changed

src/lib/converter/symbols.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ function convertFunctionOrMethod(
324324

325325
if (declarations.length && isMethod) {
326326
// All method signatures must have the same modifier flags.
327-
setModifiers(declarations[0], reflection);
327+
setModifiers(symbol, declarations[0], reflection);
328328

329329
assert(parentSymbol, "Tried to convert a method without a parent.");
330330
if (
@@ -375,7 +375,7 @@ function convertClassOrInterface(
375375
.getDeclarations()
376376
?.find((d) => ts.isClassDeclaration(d) || ts.isFunctionDeclaration(d));
377377
if (classDeclaration) {
378-
setModifiers(classDeclaration, reflection);
378+
setModifiers(symbol, classDeclaration, reflection);
379379

380380
// Classes can have static props
381381
const staticType = context.checker.getTypeOfSymbolAtLocation(
@@ -421,7 +421,7 @@ function convertClassOrInterface(
421421
.map((sig, i) => {
422422
// Modifiers are the same for all constructors
423423
if (sig.declaration && i === 0) {
424-
setModifiers(sig.declaration, constructMember);
424+
setModifiers(symbol, sig.declaration, constructMember);
425425
}
426426
const sigRef = createSignature(
427427
constructContext,
@@ -574,7 +574,7 @@ function convertProperty(
574574
ts.isParameter(declaration))
575575
) {
576576
parameterType = declaration.type;
577-
setModifiers(declaration, reflection);
577+
setModifiers(symbol, declaration, reflection);
578578
const parentSymbol = context.project.getSymbolFromReflection(
579579
context.scope
580580
);
@@ -614,7 +614,7 @@ function convertArrowAsMethod(
614614
symbol,
615615
exportSymbol
616616
);
617-
setModifiers(arrow.parent as ts.PropertyDeclaration, reflection);
617+
setModifiers(symbol, arrow.parent as ts.PropertyDeclaration, reflection);
618618
const rc = context.withScope(reflection);
619619

620620
const signature = context.checker.getSignatureFromDeclaration(arrow);
@@ -771,7 +771,7 @@ function convertVariable(
771771
typeNode ?? type
772772
);
773773

774-
setModifiers(declaration, reflection);
774+
setModifiers(symbol, declaration, reflection);
775775

776776
// Does anyone care about this? I doubt it...
777777
if (
@@ -806,7 +806,7 @@ function convertVariableAsFunction(
806806
symbol,
807807
exportSymbol
808808
);
809-
setModifiers(declaration ?? symbol.valueDeclaration, reflection);
809+
setModifiers(symbol, declaration ?? symbol.valueDeclaration, reflection);
810810
// Does anyone care about this? I doubt it...
811811
if (
812812
declaration &&
@@ -892,7 +892,11 @@ function isInherited(context: Context, symbol: ts.Symbol) {
892892
);
893893
}
894894

895-
function setModifiers(declaration: ts.Declaration, reflection: Reflection) {
895+
function setModifiers(
896+
symbol: ts.Symbol,
897+
declaration: ts.Declaration,
898+
reflection: Reflection
899+
) {
896900
const modifiers = ts.getCombinedModifierFlags(declaration);
897901
// Note: We only set this flag if the modifier is present because we allow
898902
// fake "private" or "protected" members via @private and @protected
@@ -907,11 +911,12 @@ function setModifiers(declaration: ts.Declaration, reflection: Reflection) {
907911
}
908912
reflection.setFlag(
909913
ReflectionFlag.Optional,
910-
!!(declaration as any).questionToken
914+
hasAllFlags(symbol.flags, ts.SymbolFlags.Optional)
911915
);
912916
reflection.setFlag(
913917
ReflectionFlag.Readonly,
914-
hasAllFlags(modifiers, ts.ModifierFlags.Readonly)
918+
hasAllFlags(symbol.checkFlags ?? 0, ts.CheckFlags.Readonly) ||
919+
hasAllFlags(modifiers, ts.ModifierFlags.Readonly)
915920
);
916921
reflection.setFlag(
917922
ReflectionFlag.Abstract,

src/lib/ts-internal.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,48 @@ import * as ts from "typescript";
55
*/
66
declare module "typescript" {
77
interface Node {
8-
// https://github.com/microsoft/TypeScript/blob/v4.1.3/src/compiler/types.ts#L847
8+
// https://github.com/microsoft/TypeScript/blob/v4.1.5/src/compiler/types.ts#L847
99
symbol?: ts.Symbol;
1010
}
1111

12+
interface Symbol {
13+
// https://github.com/microsoft/TypeScript/blob/v4.1.5/src/compiler/types.ts#L4734-L4737
14+
checkFlags?: CheckFlags;
15+
}
16+
1217
interface TypeChecker {
13-
// https://github.com/microsoft/TypeScript/blob/v4.1.3/src/compiler/types.ts#L4145
18+
// https://github.com/microsoft/TypeScript/blob/v4.1.5/src/compiler/types.ts#L4145
1419
// https://github.com/microsoft/TypeScript/issues/42118
1520
getTypePredicateOfSignature(
1621
signature: ts.Signature
1722
): ts.TypePredicate | undefined;
1823
}
24+
25+
// https://github.com/microsoft/TypeScript/blob/v4.1.5/src/compiler/types.ts#L4707-L4732
26+
/* @internal */
27+
export const enum CheckFlags {
28+
Instantiated = 1 << 0, // Instantiated symbol
29+
SyntheticProperty = 1 << 1, // Property in union or intersection type
30+
SyntheticMethod = 1 << 2, // Method in union or intersection type
31+
Readonly = 1 << 3, // Readonly transient symbol
32+
ReadPartial = 1 << 4, // Synthetic property present in some but not all constituents
33+
WritePartial = 1 << 5, // Synthetic property present in some but only satisfied by an index signature in others
34+
HasNonUniformType = 1 << 6, // Synthetic property with non-uniform type in constituents
35+
HasLiteralType = 1 << 7, // Synthetic property with at least one literal type in constituents
36+
ContainsPublic = 1 << 8, // Synthetic property with public constituent(s)
37+
ContainsProtected = 1 << 9, // Synthetic property with protected constituent(s)
38+
ContainsPrivate = 1 << 10, // Synthetic property with private constituent(s)
39+
ContainsStatic = 1 << 11, // Synthetic property with static constituent(s)
40+
Late = 1 << 12, // Late-bound symbol for a computed property with a dynamic name
41+
ReverseMapped = 1 << 13, // Property of reverse-inferred homomorphic mapped type
42+
OptionalParameter = 1 << 14, // Optional parameter
43+
RestParameter = 1 << 15, // Rest parameter
44+
DeferredType = 1 << 16, // Calculation of the type of this symbol is deferred due to processing costs, should be fetched with `getTypeOfSymbolWithDeferredType`
45+
HasNeverType = 1 << 17, // Synthetic property with at least one never type in constituents
46+
Mapped = 1 << 18, // Property of mapped type
47+
StripOptional = 1 << 19, // Strip optionality in mapped property
48+
Synthetic = SyntheticProperty | SyntheticMethod,
49+
Discriminant = HasNonUniformType | HasLiteralType,
50+
Partial = ReadPartial | WritePartial,
51+
}
1952
}

src/test/converter/class/class.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,12 @@ export class Ts38PrivateFields {
121121
/** Docs */
122122
#foo = 1;
123123
}
124+
125+
export namespace GH1509 {
126+
export interface Foo {
127+
foo: number;
128+
}
129+
130+
export interface PartialFoo extends Partial<Foo> {}
131+
export interface ReadonlyFoo extends Readonly<Partial<Foo>> {}
132+
}

0 commit comments

Comments
 (0)