@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.
- package/CHANGELOG.md +16 -0
- package/dist/types/components/__generated__/BadgeProps.codegen.d.ts +19 -4
- package/dist/types/components/__generated__/BoxProps.codegen.d.ts +2 -2
- package/dist/types/components/__generated__/CalendarProps.codegen.d.ts +128 -4
- package/dist/types/components/__generated__/CodeBlockProps.codegen.d.ts +8 -8
- package/dist/types/components/__generated__/CodeProps.codegen.d.ts +4 -4
- package/dist/types/components/__generated__/HeadingProps.codegen.d.ts +29 -3
- package/dist/types/components/__generated__/PressableProps.codegen.d.ts +2 -2
- package/dist/types/components/__generated__/RangeProps.codegen.d.ts +50 -5
- package/dist/types/components/__generated__/index.d.ts +0 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types-ts4.5/components/__generated__/BadgeProps.codegen.d.ts +19 -4
- package/dist/types-ts4.5/components/__generated__/BoxProps.codegen.d.ts +2 -2
- package/dist/types-ts4.5/components/__generated__/CalendarProps.codegen.d.ts +128 -4
- package/dist/types-ts4.5/components/__generated__/CodeBlockProps.codegen.d.ts +8 -8
- package/dist/types-ts4.5/components/__generated__/CodeProps.codegen.d.ts +4 -4
- package/dist/types-ts4.5/components/__generated__/HeadingProps.codegen.d.ts +29 -3
- package/dist/types-ts4.5/components/__generated__/PressableProps.codegen.d.ts +2 -2
- package/dist/types-ts4.5/components/__generated__/RangeProps.codegen.d.ts +50 -5
- package/dist/types-ts4.5/components/__generated__/index.d.ts +0 -1
- package/dist/types-ts4.5/index.d.ts +1 -1
- package/package.json +4 -6
- package/scripts/codegen/codeGenerator.ts +134 -30
- package/scripts/codegen/componentPropTypes.ts +11 -4
- package/scripts/codegen/typeSerializer.ts +158 -95
- package/scripts/codegen/utils.ts +71 -0
- package/scripts/codegen-runner.ts +17 -0
- package/scripts/typechecker.ts +35 -0
- package/src/components/__generated__/BadgeProps.codegen.tsx +22 -4
- package/src/components/__generated__/CalendarProps.codegen.tsx +119 -4
- package/src/components/__generated__/CodeBlockProps.codegen.tsx +9 -9
- package/src/components/__generated__/CodeProps.codegen.tsx +5 -5
- package/src/components/__generated__/HeadingProps.codegen.tsx +31 -3
- package/src/components/__generated__/RangeProps.codegen.tsx +54 -5
- package/src/components/__generated__/index.ts +1 -2
- package/src/index.ts +0 -2
- package/dist/cjs/components/__generated__/BleedProps.codegen.js +0 -5
- package/dist/es2019/components/__generated__/BleedProps.codegen.js +0 -1
- package/dist/esm/components/__generated__/BleedProps.codegen.js +0 -1
- package/dist/types/components/__generated__/BleedProps.codegen.d.ts +0 -15
- package/dist/types-ts4.5/components/__generated__/BleedProps.codegen.d.ts +0 -15
- 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 {
|
|
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
|
-
|
|
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
|
|
233
|
+
const importedNamesList = Array.from(this.namedImports)
|
|
220
234
|
.sort()
|
|
221
|
-
.map((name) => `type ${name}`)
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
578
|
+
defaultImport: string;
|
|
563
579
|
}
|
|
564
580
|
> = {
|
|
565
|
-
'React
|
|
581
|
+
'^React\..+$': {
|
|
566
582
|
package: 'react',
|
|
567
|
-
|
|
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 =
|
|
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(
|
|
587
|
-
|
|
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
|
|
698
|
+
const rawDependentTypeDeclarations = getDependentTypeDeclarations(
|
|
613
699
|
baseComponentPropSymbol,
|
|
614
700
|
sourceFile,
|
|
615
|
-
)
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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] =
|
|
644
|
-
|
|
645
|
-
|
|
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 = [
|
|
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 = (
|
|
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
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
27
|
+
const usedExternalTypes = new Set<string>();
|
|
28
|
+
const serializedType = flattenPickType(typeReference, usedExternalTypes, propertyCallback);
|
|
29
|
+
return [serializedType, usedExternalTypes];
|
|
30
|
+
};
|
|
15
31
|
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
102
|
+
return tsType.getText();
|
|
67
103
|
};
|
|
68
104
|
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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) =>
|
|
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 =
|
|
128
|
-
|
|
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
|
|
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
|
|
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) {
|
|
202
|
+
if (!symbol) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
177
205
|
|
|
178
206
|
const declaration = symbol.getDeclarations()?.[0];
|
|
179
|
-
if (!declaration) {
|
|
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
|
|
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
|
};
|