@atlaskit/forge-react-types 0.42.7 → 0.42.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/types/components/__generated__/BadgeProps.codegen.d.ts +19 -4
  3. package/dist/types/components/__generated__/BoxProps.codegen.d.ts +2 -2
  4. package/dist/types/components/__generated__/CalendarProps.codegen.d.ts +128 -4
  5. package/dist/types/components/__generated__/CodeBlockProps.codegen.d.ts +8 -8
  6. package/dist/types/components/__generated__/CodeProps.codegen.d.ts +4 -4
  7. package/dist/types/components/__generated__/HeadingProps.codegen.d.ts +29 -3
  8. package/dist/types/components/__generated__/PressableProps.codegen.d.ts +2 -2
  9. package/dist/types/components/__generated__/RangeProps.codegen.d.ts +50 -5
  10. package/dist/types/components/__generated__/index.d.ts +0 -1
  11. package/dist/types/index.d.ts +1 -1
  12. package/dist/types-ts4.5/components/__generated__/BadgeProps.codegen.d.ts +19 -4
  13. package/dist/types-ts4.5/components/__generated__/BoxProps.codegen.d.ts +2 -2
  14. package/dist/types-ts4.5/components/__generated__/CalendarProps.codegen.d.ts +128 -4
  15. package/dist/types-ts4.5/components/__generated__/CodeBlockProps.codegen.d.ts +8 -8
  16. package/dist/types-ts4.5/components/__generated__/CodeProps.codegen.d.ts +4 -4
  17. package/dist/types-ts4.5/components/__generated__/HeadingProps.codegen.d.ts +29 -3
  18. package/dist/types-ts4.5/components/__generated__/PressableProps.codegen.d.ts +2 -2
  19. package/dist/types-ts4.5/components/__generated__/RangeProps.codegen.d.ts +50 -5
  20. package/dist/types-ts4.5/components/__generated__/index.d.ts +0 -1
  21. package/dist/types-ts4.5/index.d.ts +1 -1
  22. package/package.json +4 -6
  23. package/scripts/codegen/codeGenerator.ts +134 -30
  24. package/scripts/codegen/componentPropTypes.ts +11 -4
  25. package/scripts/codegen/typeSerializer.ts +158 -95
  26. package/scripts/codegen/utils.ts +71 -0
  27. package/scripts/codegen-runner.ts +17 -0
  28. package/scripts/typechecker.ts +35 -0
  29. package/src/components/__generated__/BadgeProps.codegen.tsx +22 -4
  30. package/src/components/__generated__/CalendarProps.codegen.tsx +119 -4
  31. package/src/components/__generated__/CodeBlockProps.codegen.tsx +9 -9
  32. package/src/components/__generated__/CodeProps.codegen.tsx +5 -5
  33. package/src/components/__generated__/HeadingProps.codegen.tsx +31 -3
  34. package/src/components/__generated__/RangeProps.codegen.tsx +54 -5
  35. package/src/components/__generated__/index.ts +1 -2
  36. package/src/index.ts +0 -2
  37. package/dist/cjs/components/__generated__/BleedProps.codegen.js +0 -5
  38. package/dist/es2019/components/__generated__/BleedProps.codegen.js +0 -1
  39. package/dist/esm/components/__generated__/BleedProps.codegen.js +0 -1
  40. package/dist/types/components/__generated__/BleedProps.codegen.d.ts +0 -15
  41. package/dist/types-ts4.5/components/__generated__/BleedProps.codegen.d.ts +0 -15
  42. package/src/components/__generated__/BleedProps.codegen.tsx +0 -22
@@ -5,10 +5,21 @@ import {
5
5
  type SourceFile,
6
6
  type TypeAliasDeclaration,
7
7
  type ImportDeclaration,
8
+ type TypeReferenceNode,
9
+ SyntaxKind,
8
10
  } from 'ts-morph';
9
11
  // eslint-disable-next-line import/no-extraneous-dependencies
10
12
  import kebabCase from 'lodash/kebabCase';
11
- import { serializeSymbolType } from './typeSerializer';
13
+ import {
14
+ serializeTypeReferenceWithPickType,
15
+ extractPickKeys,
16
+ extractOmitKeys,
17
+ } from './typeSerializer';
18
+ import {
19
+ findTypeReferenceFromUnionOrIntersect,
20
+ getTypeNodeFromSymbol,
21
+ makePickOrOmitPredicate,
22
+ } from './utils';
12
23
 
13
24
  const getNames = (symbol: Symbol) => {
14
25
  const name = symbol.getName();
@@ -200,8 +211,11 @@ class SimpleImportDeclaration implements IImportDeclaration {
200
211
 
201
212
  private namedImports = new Set<string>();
202
213
 
203
- constructor(packageName: string) {
214
+ private defaultImport?: string;
215
+
216
+ constructor(packageName: string, defaultImport?: string) {
204
217
  this.packageName = packageName;
218
+ this.defaultImport = defaultImport;
205
219
  }
206
220
 
207
221
  public addNamedImport(namedImport: string) {
@@ -213,14 +227,16 @@ class SimpleImportDeclaration implements IImportDeclaration {
213
227
  }
214
228
 
215
229
  public getText() {
216
- if (this.namedImports.size === 0) {
230
+ if (this.namedImports.size === 0 && !this.defaultImport) {
217
231
  return `import '${this.packageName}';`;
218
232
  }
219
- const importedNames = Array.from(this.namedImports)
233
+ const importedNamesList = Array.from(this.namedImports)
220
234
  .sort()
221
- .map((name) => `type ${name}`)
222
- .join(', ');
223
- return `import { ${importedNames} } from '${this.packageName}';`;
235
+ .map((name) => `type ${name}`);
236
+ const importedNames =
237
+ importedNamesList.length > 0 ? `{ ${importedNamesList.join(', ')} }` : null;
238
+ const defaultImport = this.defaultImport ? `type ${this.defaultImport}` : null;
239
+ return `import ${[defaultImport, importedNames].filter(Boolean).join(', ')} from '${this.packageName}';`;
224
240
  }
225
241
  }
226
242
 
@@ -559,15 +575,24 @@ const registeredExternalTypes: Record<
559
575
  string,
560
576
  {
561
577
  package: string;
562
- alias?: string;
578
+ defaultImport: string;
563
579
  }
564
580
  > = {
565
- 'React.ReactNode': {
581
+ '^React\..+$': {
566
582
  package: 'react',
567
- alias: 'ReactNode',
583
+ defaultImport: 'React',
568
584
  },
569
585
  };
570
586
 
587
+ const findExternalTypeInfo = (typeName: string) => {
588
+ return (
589
+ Object.entries(registeredExternalTypes).find(
590
+ ([externalTypePattern]) =>
591
+ externalTypePattern === typeName || new RegExp(externalTypePattern).test(typeName),
592
+ )?.[1] ?? null
593
+ );
594
+ };
595
+
571
596
  // consolidate external types into import declarations
572
597
  const consolidateImportDeclarations = (
573
598
  importDeclarations: ImportDeclarationProxy[],
@@ -575,7 +600,7 @@ const consolidateImportDeclarations = (
575
600
  ): IImportDeclaration[] => {
576
601
  const declarations: IImportDeclaration[] = [...importDeclarations];
577
602
  externalTypes.forEach((typeName) => {
578
- const typePackage = registeredExternalTypes[typeName];
603
+ const typePackage = findExternalTypeInfo(typeName);
579
604
  if (typePackage) {
580
605
  const existingImport = importDeclarations.find(
581
606
  (declaration) => declaration.getBasePackage() === typePackage.package,
@@ -583,8 +608,13 @@ const consolidateImportDeclarations = (
583
608
  if (existingImport) {
584
609
  existingImport.addNamedImport(typeName);
585
610
  } else {
586
- const newImport = new SimpleImportDeclaration(typePackage.package);
587
- newImport.addNamedImport(typePackage.alias ?? typeName);
611
+ const newImport = new SimpleImportDeclaration(
612
+ typePackage.package,
613
+ typePackage.defaultImport,
614
+ );
615
+ if (!typeName.startsWith(`${typePackage.defaultImport}.`)) {
616
+ newImport.addNamedImport(typeName);
617
+ }
588
618
  declarations.push(newImport);
589
619
  }
590
620
  }
@@ -592,6 +622,62 @@ const consolidateImportDeclarations = (
592
622
  return declarations;
593
623
  };
594
624
 
625
+ const extractPlatformPropsTypeDeclarationAndTargetPropertyKeys = (
626
+ rawDependentTypeDeclarations: TypeAliasDeclaration[],
627
+ baseComponentPropsSymbol: Symbol,
628
+ ): {
629
+ typeDeclaration: TypeAliasDeclaration;
630
+ baseComponentPropTypeReference: TypeReferenceNode;
631
+ omitKeys: string[];
632
+ pickKeys: string[];
633
+ } => {
634
+ // this pattern is used when there is special JSDoc comments override required to customize component documentation.
635
+ // e.g. Badge component has:
636
+ // PlatformBadgeProps = Omit<_PlatformBadgeProps, 'children'> & {}
637
+ // BadgeProps = Pick<PlatformBadgeProps, 'appearance' | 'children' | 'max' | 'testId'>
638
+ const specialPlatformPropsTypeDeclaration = rawDependentTypeDeclarations.find((declaration) =>
639
+ declaration.getName().startsWith('_Platform'),
640
+ );
641
+ const mainPlatformPropsTypeDeclaration = rawDependentTypeDeclarations.find((declaration) =>
642
+ declaration.getName().startsWith('Platform'),
643
+ );
644
+ if (!mainPlatformPropsTypeDeclaration && !specialPlatformPropsTypeDeclaration) {
645
+ throw new Error(
646
+ 'Could not find Platform props type declaration from the component prop type source code',
647
+ );
648
+ }
649
+ const platformPropsTypeDeclaration =
650
+ specialPlatformPropsTypeDeclaration ?? mainPlatformPropsTypeDeclaration;
651
+
652
+ const typeWhichPicksPlatformProps = findTypeReferenceFromUnionOrIntersect(
653
+ getTypeNodeFromSymbol(baseComponentPropsSymbol!)!,
654
+ makePickOrOmitPredicate('Pick', 'Platform'),
655
+ );
656
+ if (!typeWhichPicksPlatformProps) {
657
+ throw new Error(
658
+ `Could not find type which picks platform props from the component prop type source code for ${baseComponentPropsSymbol.getName()}`,
659
+ );
660
+ }
661
+ const pickKeys = extractPickKeys(typeWhichPicksPlatformProps);
662
+ let omitKeys: string[] = [];
663
+ if (specialPlatformPropsTypeDeclaration && mainPlatformPropsTypeDeclaration) {
664
+ const omitNode = mainPlatformPropsTypeDeclaration
665
+ .getTypeNodeOrThrow()
666
+ .asKindOrThrow(SyntaxKind.IntersectionType)
667
+ .getTypeNodes()[0]
668
+ .asKindOrThrow(SyntaxKind.TypeReference);
669
+ // if there is a special platform props type declaration, we need
670
+ omitKeys = extractOmitKeys(omitNode);
671
+ }
672
+
673
+ return {
674
+ typeDeclaration: platformPropsTypeDeclaration!,
675
+ baseComponentPropTypeReference: typeWhichPicksPlatformProps,
676
+ pickKeys,
677
+ omitKeys,
678
+ };
679
+ };
680
+
595
681
  /**
596
682
  * This function implements the new code generation logic for the component prop types.
597
683
  * Instead of referencing to the ADS component prop types, it generates the prop types
@@ -609,20 +695,20 @@ const generateComponentPropTypeSourceCodeWithSerializedType = (
609
695
  // 2) from the prop type code further extract other relevant types in the source file,
610
696
  // and separate the platform props type declaration from the rest of the dependent types.
611
697
  // as this will be used to generate the type code.
612
- const [dependentTypeDeclarations, platformPropsTypeDeclaration] = getDependentTypeDeclarations(
698
+ const rawDependentTypeDeclarations = getDependentTypeDeclarations(
613
699
  baseComponentPropSymbol,
614
700
  sourceFile,
615
- ).reduce(
616
- (agg, declarations) => {
617
- if (declarations.getName().startsWith('Platform')) {
618
- // this is the platform props type declaration, we will use it to generate the type code
619
- agg[1] = declarations;
620
- } else {
621
- agg[0].push(declarations);
622
- }
623
- return agg;
624
- },
625
- [[] as TypeAliasDeclaration[], null as TypeAliasDeclaration | null],
701
+ );
702
+ const {
703
+ omitKeys,
704
+ typeDeclaration: platformPropsTypeDeclaration,
705
+ baseComponentPropTypeReference,
706
+ } = extractPlatformPropsTypeDeclarationAndTargetPropertyKeys(
707
+ rawDependentTypeDeclarations,
708
+ baseComponentPropSymbol,
709
+ );
710
+ const dependentTypeDeclarations = rawDependentTypeDeclarations.filter(
711
+ (declaration) => declaration !== platformPropsTypeDeclaration,
626
712
  );
627
713
 
628
714
  // 3) extract the import statement
@@ -640,9 +726,20 @@ const generateComponentPropTypeSourceCodeWithSerializedType = (
640
726
  );
641
727
 
642
728
  // 5) serialize the prop type for the @atlaskit component (e.g. PlatformButtonProps)
643
- const [typeDefCode, usedExternalTypes] = serializeSymbolType(
644
- baseComponentPropSymbol,
645
- sourceFile.getProject().getTypeChecker(),
729
+ const [typeDefCode, usedExternalTypes] = serializeTypeReferenceWithPickType(
730
+ baseComponentPropTypeReference,
731
+ ({ jsDoc, typeCode, propertySignature }) => {
732
+ const propertyName = propertySignature.getName();
733
+ if (omitKeys.includes(propertyName)) {
734
+ return {
735
+ typeCode,
736
+ };
737
+ }
738
+ return {
739
+ jsDoc,
740
+ typeCode,
741
+ };
742
+ },
646
743
  );
647
744
 
648
745
  // 6) generate the source file
@@ -654,7 +751,7 @@ const generateComponentPropTypeSourceCodeWithSerializedType = (
654
751
  const dependentTypeCode = [
655
752
  ...dependentTypeDeclarations.map((typeAlias) => typeAlias.getText()),
656
753
  platformPropsTypeDeclarationName &&
657
- `\n// Serialized type\ntype ${platformPropsTypeDeclarationName} = ${typeDefCode}`,
754
+ `\n// Serialized type\ntype ${platformPropsTypeDeclarationName} = ${typeDefCode};`,
658
755
  ]
659
756
  .filter(Boolean)
660
757
  .join('\n');
@@ -725,7 +822,14 @@ const codeConsolidators: Record<string, CodeConsolidator> = {
725
822
  PressableProps: handleXCSSProp,
726
823
  };
727
824
 
728
- const typeSerializableComponentPropSymbols = ['CodeProps', 'CodeBlockProps'];
825
+ const typeSerializableComponentPropSymbols = [
826
+ 'CalendarProps',
827
+ 'CodeProps',
828
+ 'CodeBlockProps',
829
+ 'BadgeProps',
830
+ 'HeadingProps',
831
+ 'RangeProps',
832
+ ];
729
833
 
730
834
  const generateComponentPropTypeSourceCode = (
731
835
  componentPropSymbol: Symbol,
@@ -189,17 +189,24 @@ const generateSharedTypesFile = (componentOutputDir: string) => {
189
189
  fs.writeFileSync(typesFilePath, signedSourceCode);
190
190
  };
191
191
 
192
- const generateComponentPropTypes = (componentName?: string) => {
192
+ const generateComponentPropTypes = (componentNames?: string) => {
193
193
  const componentOutputDir = resolve(__dirname, '..', '..', 'src', 'components', '__generated__');
194
194
  const componentIndexSourceFile = forgeUIProject.addSourceFileAtPath(
195
195
  require.resolve('@atlassian/forge-ui/UIKit'),
196
196
  );
197
197
  try {
198
+ const componentNamesFilter = componentNames ? componentNames.split(',') : [];
198
199
  const componentPropTypeSymbols = componentIndexSourceFile
199
200
  .getExportSymbols()
200
- .filter((symbol) =>
201
- symbol.getName().endsWith(componentName ? `${componentName}Props` : 'Props'),
202
- )
201
+ .filter((symbol) => {
202
+ if (componentNamesFilter.length === 0) {
203
+ return symbol.getName().endsWith('Props');
204
+ }
205
+ const symbolName = symbol.getName();
206
+ return componentNamesFilter.some((componentName) => {
207
+ return symbolName === `${componentName}Props`;
208
+ });
209
+ })
203
210
  .sort((a, b) => a.getName().localeCompare(b.getName()));
204
211
 
205
212
  // generate share types file first
@@ -1,118 +1,137 @@
1
1
  import {
2
- type Symbol as TSSymbol,
3
2
  type Node,
4
- type TypeChecker,
5
3
  type PropertySignature,
6
4
  type Type as TSType,
5
+ type UnionTypeNode,
6
+ type TypeReferenceNode,
7
7
  SyntaxKind,
8
8
  } from 'ts-morph';
9
9
 
10
- export const serializeSymbolType = (
11
- symbol: TSSymbol,
12
- typeChecker: TypeChecker,
10
+ type PropertyCallback = ({
11
+ jsDoc,
12
+ typeCode,
13
+ propertySignature,
14
+ }: {
15
+ jsDoc?: string;
16
+ typeCode: string;
17
+ propertySignature: PropertySignature;
18
+ }) => {
19
+ jsDoc?: string;
20
+ typeCode: string;
21
+ } | null;
22
+
23
+ export const serializeTypeReferenceWithPickType = (
24
+ typeReference: TypeReferenceNode,
25
+ propertyCallback: PropertyCallback,
13
26
  ): [string, Set<string>] => {
14
- const declaration = symbol.getDeclarations()[0];
27
+ const usedExternalTypes = new Set<string>();
28
+ const serializedType = flattenPickType(typeReference, usedExternalTypes, propertyCallback);
29
+ return [serializedType, usedExternalTypes];
30
+ };
15
31
 
16
- if (!declaration) {
17
- throw new Error(`No declaration found for symbol: ${symbol.getName()}`);
32
+ // resolve single level type references (e.g. SupportedLanguages))
33
+ // type SupportedLanguages = 'text' | 'PHP' | 'Java' | 'CSharp' | ...;
34
+ const isSimpleTypeReferenceNode = (tsType: TSType): boolean => {
35
+ if (tsType.isUnion()) {
36
+ const unionTypes = tsType.getUnionTypes();
37
+ return unionTypes.every((t) => isBasicType(t));
18
38
  }
39
+ return false;
40
+ };
19
41
 
20
- const usedExternalTypes = new Set<string>();
21
- if (declaration.getKind() === SyntaxKind.TypeAliasDeclaration) {
22
- const typeAlias = declaration.asKindOrThrow(SyntaxKind.TypeAliasDeclaration);
23
- const typeNode = typeAlias.getTypeNode();
24
- if (typeNode && isCommonComponentPropType(typeNode)) {
25
- const serializedType = flattenPickType(
26
- typeNode.asKindOrThrow(SyntaxKind.TypeReference),
27
- typeChecker,
28
- usedExternalTypes,
29
- );
30
- return [serializedType, usedExternalTypes];
42
+ // event function type that is not a React event handler
43
+ const isCustomEventHandlerType = (tsType: TSType): boolean => {
44
+ const callSignatures = tsType.getCallSignatures();
45
+ // we already import React, hence we don't have to serialize it
46
+ if (
47
+ callSignatures.length > 0 &&
48
+ callSignatures[0].getParameters().length > 1 &&
49
+ !tsType.getText().startsWith('React.')
50
+ ) {
51
+ const analyticsEventSymbol = callSignatures[0].getParameters()[1];
52
+ if (analyticsEventSymbol) {
53
+ const eventTypeName = analyticsEventSymbol
54
+ .getDeclarations()[0]
55
+ .asKind(SyntaxKind.Parameter)
56
+ ?.getTypeNode()
57
+ ?.getText();
58
+ return eventTypeName === 'UIAnalyticsEvent';
31
59
  }
60
+ return true;
32
61
  }
62
+ return false;
63
+ };
33
64
 
34
- throw new Error(
35
- `Unsupported declaration kind: ${declaration.getKindName()} for symbol: ${symbol.getName()}`,
36
- );
65
+ const serializeSimpleEventType = (tsType: TSType): string => {
66
+ const propertyCode: string[] = tsType
67
+ .getProperties()
68
+ .map((prop) => {
69
+ const propertySignature = prop.getDeclarations()[0] as PropertySignature | undefined;
70
+ if (propertySignature) {
71
+ const typeCode = serializeSimpleTypeNode(resolveNonNullableType(propertySignature));
72
+ return `${prop.getName()}: ${typeCode}`;
73
+ }
74
+ return null;
75
+ })
76
+ .filter(Boolean) as string[];
77
+ return `{ ${propertyCode.join(', ')} }`;
37
78
  };
38
79
 
39
- /**
40
- * Checks if a node is a common component prop type. e.g.
41
- *
42
- * export type BleedProps = Pick<
43
- * PlatformBleedProps,
44
- * 'children' | 'all' | 'inline' | 'block' | 'testId' | 'role'
45
- * >;
46
- */
47
- const isCommonComponentPropType = (node: Node) => {
48
- if (node.getKind() === SyntaxKind.TypeReference) {
49
- const typeRef = node.asKindOrThrow(SyntaxKind.TypeReference);
50
- const typeName = typeRef.getTypeName().getText();
51
- return typeName === 'Pick';
52
- }
53
- return false;
80
+ const serializeCustomEventHandlerType = (tsType: TSType): string => {
81
+ const callSignature = tsType.getCallSignatures()[0];
82
+ const eventSymbol = callSignature.getParameters()[0];
83
+ const parameterDeclaration = eventSymbol.getDeclarations()[0].asKindOrThrow(SyntaxKind.Parameter);
84
+ const eventType = parameterDeclaration.getType();
85
+ const analyticsEventName = callSignature.getParameters()[1]?.getName() ?? 'analyticsEvent';
86
+ const returnType = callSignature.getReturnType().getText();
87
+ return `(${eventSymbol.getName()}: ${serializeSimpleEventType(eventType)}, ${analyticsEventName}: any) => ${returnType}`;
54
88
  };
55
89
 
56
- // resolve single level type references (e.g. SupportedLanguages))
57
- // type SupportedLanguages = 'text' | 'PHP' | 'Java' | 'CSharp' | ...;
58
- const isSimpleTypeReferenceNode = (node: Node): boolean => {
59
- const type = node.getType();
60
- if (type.isUnion()) {
61
- const unionTypes = type.getUnionTypes();
62
- return unionTypes.every((t) => {
63
- return t.isString() || t.isStringLiteral() || t.isNumber() || t.isBoolean();
64
- });
90
+ const serializeSimpleTypeNode = (tsType: TSType): string => {
91
+ if (tsType.isStringLiteral()) {
92
+ return `'${tsType.getLiteralValue()}'`;
93
+ } else if (isBasicType(tsType)) {
94
+ return tsType.getText();
95
+ } else if (isSimpleTypeReferenceNode(tsType)) {
96
+ const unionTypes = tsType.getUnionTypes();
97
+ const serializedTypes = unionTypes.map((t) => serializeSimpleTypeNode(t));
98
+ return serializedTypes.join(' | ');
99
+ } else if (isCustomEventHandlerType(tsType)) {
100
+ return serializeCustomEventHandlerType(tsType);
65
101
  }
66
- return false;
102
+ return tsType.getText();
67
103
  };
68
104
 
69
- const serializeSimpleTypeNode = (node: Node): string => {
70
- switch (node.getKind()) {
71
- case SyntaxKind.UnionType:
72
- const unionType = node.asKindOrThrow(SyntaxKind.UnionType);
73
- const unionTypes = unionType.getTypeNodes().map((t) => serializeSimpleTypeNode(t));
74
- return unionTypes.join(' | ');
75
-
76
- case SyntaxKind.StringLiteral:
77
- return `'${node.asKindOrThrow(SyntaxKind.StringLiteral).getLiteralValue()}'`;
78
- case SyntaxKind.TypeReference:
79
- // resolve single level type references (e.g. SupportedLanguages))
80
- if (isSimpleTypeReferenceNode(node)) {
81
- return node
82
- .getType()
83
- .getUnionTypes()
84
- .map((t) => t.getText())
85
- .join(' | ');
86
- }
105
+ const resolveNonNullableType = (propertySignature: PropertySignature): TSType => {
106
+ const hasOptionalHint = propertySignature.hasQuestionToken();
107
+ const type = propertySignature.getType();
108
+ if (!hasOptionalHint) {
109
+ return type;
87
110
  }
88
- return node.getText();
111
+ // there is a case where `children?: ReactNode` using getNonNullableType() don't return the original ReactNode type
112
+ if (type.getText().split(' | ').includes('undefined')) {
113
+ return type.getNonNullableType();
114
+ }
115
+ return type;
89
116
  };
90
117
 
91
118
  const serializePropertySignatureCode = (propertySignature: PropertySignature) => {
92
- return `${propertySignature.getName()}: ${serializeSimpleTypeNode(propertySignature.getTypeNode()!)};`;
119
+ const propertyName = propertySignature.getName();
120
+ const isOptional = propertySignature.hasQuestionToken();
121
+ const typeCode = serializeSimpleTypeNode(resolveNonNullableType(propertySignature));
122
+ return `${propertyName}${isOptional ? '?' : ''}: ${typeCode};`;
93
123
  };
94
124
 
95
125
  const flattenPickType = (
96
126
  typeRef: Node,
97
- typeChecker: TypeChecker,
98
127
  usedExternalTypesOutput: Set<string>,
128
+ propertyCallback: PropertyCallback,
99
129
  ): string => {
100
- const typeArgs = typeRef.asKindOrThrow(SyntaxKind.TypeReference).getTypeArguments();
101
-
102
- if (typeArgs.length < 2) {
103
- return typeRef.getText(); // Fallback if not a valid Pick
104
- }
105
-
106
- const keysNode = typeArgs[1];
107
-
108
- // Get the selected keys
109
- const selectedKeys = extractUnionKeys(keysNode);
110
-
111
- // Get the base type symbol
130
+ const pickKeys = extractPickKeys(typeRef.asKindOrThrow(SyntaxKind.TypeReference));
112
131
  const properties = typeRef
113
132
  .getType()
114
133
  .getProperties()
115
- .filter((prop) => selectedKeys.includes(prop.getName()));
134
+ .filter((prop) => pickKeys.includes(prop.getName()));
116
135
 
117
136
  if (properties.length === 0) {
118
137
  return '{}'; // If no properties match, return 'any'
@@ -124,19 +143,23 @@ const flattenPickType = (
124
143
  if (!propertySignature) {
125
144
  return null; // Skip if no declaration
126
145
  }
127
- const jsDoc = propertySignature.getJsDocs()?.[0]?.getText();
128
- const typeCode = `\t${serializePropertySignatureCode(propertySignature)}`;
146
+ const { jsDoc, typeCode } =
147
+ propertyCallback({
148
+ propertySignature,
149
+ jsDoc: propertySignature.getJsDocs()?.[0]?.getText(),
150
+ typeCode: serializePropertySignatureCode(propertySignature),
151
+ }) || {};
129
152
  getUnresolvableTypes(propertySignature.getType()).forEach((typeName) => {
130
153
  usedExternalTypesOutput.add(typeName);
131
154
  });
132
- return [jsDoc, typeCode].filter(Boolean).join('\n');
155
+ return `${jsDoc ?? ''}\n\t${typeCode}`;
133
156
  })
134
157
  .filter(Boolean);
135
158
 
136
159
  if (serializedProperties.length === 0) {
137
160
  return '{}'; // If no properties are serialized, return empty object
138
161
  }
139
- return `{\n ${serializedProperties.join('\n ')}\n}`;
162
+ return `{\n${serializedProperties.map((prop) => (!!prop?.trim() ? ` ${prop}` : '')).join('\n')}\n}`;
140
163
  };
141
164
 
142
165
  const getUnresolvableTypes = (tsType: TSType) => {
@@ -161,8 +184,11 @@ const getUnresolvableTypesBase = (tsType: TSType, unresolvableTypes: Set<string>
161
184
  const isBasicType = (tsType: TSType): boolean => {
162
185
  return (
163
186
  tsType.isString() ||
187
+ tsType.isStringLiteral() ||
164
188
  tsType.isNumber() ||
189
+ tsType.isNumberLiteral() ||
165
190
  tsType.isBoolean() ||
191
+ tsType.isBooleanLiteral() ||
166
192
  tsType.isNull() ||
167
193
  tsType.isUndefined() ||
168
194
  tsType.isAny() ||
@@ -173,10 +199,14 @@ const isBasicType = (tsType: TSType): boolean => {
173
199
 
174
200
  const isExternalType = (tsType: TSType): boolean => {
175
201
  const symbol = tsType.getSymbol() ?? tsType.getAliasSymbol();
176
- if (!symbol) {return false;}
202
+ if (!symbol) {
203
+ return false;
204
+ }
177
205
 
178
206
  const declaration = symbol.getDeclarations()?.[0];
179
- if (!declaration) {return false;}
207
+ if (!declaration) {
208
+ return false;
209
+ }
180
210
 
181
211
  const sourceFile = declaration.getSourceFile();
182
212
  const filePath = sourceFile.getFilePath();
@@ -184,18 +214,51 @@ const isExternalType = (tsType: TSType): boolean => {
184
214
  return filePath.includes('node_modules');
185
215
  };
186
216
 
217
+ export const extractUnionKeysFromTypeReferenceNode = (
218
+ typeReferenceNode: TypeReferenceNode,
219
+ targetTypeName: 'Pick' | 'Omit',
220
+ ): string[] => {
221
+ const typeName = typeReferenceNode.getTypeName().getText();
222
+ if (typeName !== targetTypeName) {
223
+ throw new Error(`Expected '${targetTypeName}' type, but found '${typeName}'`);
224
+ }
225
+ const typeArgs = typeReferenceNode.getTypeArguments();
226
+ if (typeArgs.length !== 2) {
227
+ throw new Error(
228
+ `Expected 2 type arguments for ${targetTypeName}, but found ${typeArgs.length}`,
229
+ );
230
+ }
231
+ const unionType = typeArgs[1];
232
+ return extractUnionKeys(unionType);
233
+ };
234
+
235
+ export const extractOmitKeys = (typeReferenceNode: TypeReferenceNode): string[] => {
236
+ return extractUnionKeysFromTypeReferenceNode(typeReferenceNode, 'Omit');
237
+ };
238
+
239
+ export const extractPickKeys = (typeReferenceNode: TypeReferenceNode): string[] => {
240
+ return extractUnionKeysFromTypeReferenceNode(typeReferenceNode, 'Pick');
241
+ };
242
+
243
+ const unwrapStringQuotes = (str: string): string => {
244
+ return str.replace(/['"]/g, '');
245
+ };
246
+
247
+ const extractUnionKeysFromUnionType = (unionType: UnionTypeNode): string[] => {
248
+ return unionType.getTypeNodes().map((node) => {
249
+ if (node.getKind() === SyntaxKind.StringLiteral) {
250
+ return node.asKindOrThrow(SyntaxKind.StringLiteral).getLiteralValue();
251
+ }
252
+ return unwrapStringQuotes(node.getText());
253
+ });
254
+ };
255
+
187
256
  const extractUnionKeys = (keysNode: Node): string[] => {
188
257
  if (keysNode.getKind() === SyntaxKind.UnionType) {
189
258
  const unionType = keysNode.asKindOrThrow(SyntaxKind.UnionType);
190
- return unionType.getTypeNodes().map((node) => {
191
- if (node.getKind() === SyntaxKind.StringLiteral) {
192
- return node.asKindOrThrow(SyntaxKind.StringLiteral).getLiteralValue();
193
- }
194
- return node.getText().replace(/['"]/g, '');
195
- });
259
+ return extractUnionKeysFromUnionType(unionType);
196
260
  } else if (keysNode.getKind() === SyntaxKind.StringLiteral) {
197
261
  return [keysNode.asKindOrThrow(SyntaxKind.StringLiteral).getLiteralValue()];
198
262
  }
199
-
200
- return [keysNode.getText().replace(/['"]/g, '')];
263
+ return [unwrapStringQuotes(keysNode.getText())];
201
264
  };