@kubb/plugin-ts 5.0.0-alpha.2 → 5.0.0-alpha.20

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 (57) hide show
  1. package/dist/Type-B70QnSzH.cjs +688 -0
  2. package/dist/Type-B70QnSzH.cjs.map +1 -0
  3. package/dist/Type-CMC7L-38.js +671 -0
  4. package/dist/Type-CMC7L-38.js.map +1 -0
  5. package/dist/casing-Cp-jbC_k.js +84 -0
  6. package/dist/casing-Cp-jbC_k.js.map +1 -0
  7. package/dist/casing-D2uQKLWS.cjs +144 -0
  8. package/dist/casing-D2uQKLWS.cjs.map +1 -0
  9. package/dist/components.cjs +3 -2
  10. package/dist/components.d.ts +41 -11
  11. package/dist/components.js +2 -2
  12. package/dist/generators-BFkr7ecU.js +556 -0
  13. package/dist/generators-BFkr7ecU.js.map +1 -0
  14. package/dist/generators-xHWQCNd9.cjs +560 -0
  15. package/dist/generators-xHWQCNd9.cjs.map +1 -0
  16. package/dist/generators.cjs +2 -2
  17. package/dist/generators.d.ts +3 -491
  18. package/dist/generators.js +1 -1
  19. package/dist/index.cjs +146 -3
  20. package/dist/index.cjs.map +1 -0
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.js +145 -1
  23. package/dist/index.js.map +1 -0
  24. package/dist/resolvers-DsKabI0F.js +184 -0
  25. package/dist/resolvers-DsKabI0F.js.map +1 -0
  26. package/dist/resolvers-YIpeP5YD.cjs +194 -0
  27. package/dist/resolvers-YIpeP5YD.cjs.map +1 -0
  28. package/dist/resolvers.cjs +4 -0
  29. package/dist/resolvers.d.ts +52 -0
  30. package/dist/resolvers.js +2 -0
  31. package/dist/types-zqLMbIqZ.d.ts +340 -0
  32. package/package.json +15 -8
  33. package/src/components/Enum.tsx +83 -0
  34. package/src/components/Type.tsx +25 -144
  35. package/src/components/index.ts +1 -0
  36. package/src/constants.ts +29 -0
  37. package/src/factory.ts +14 -16
  38. package/src/generators/typeGenerator.tsx +221 -414
  39. package/src/generators/utils.ts +308 -0
  40. package/src/index.ts +1 -1
  41. package/src/plugin.ts +74 -87
  42. package/src/presets.ts +23 -0
  43. package/src/printer.ts +256 -92
  44. package/src/resolvers/index.ts +2 -0
  45. package/src/resolvers/resolverTs.ts +104 -0
  46. package/src/resolvers/resolverTsLegacy.ts +87 -0
  47. package/src/types.ts +234 -63
  48. package/dist/components-9wydyqUx.cjs +0 -848
  49. package/dist/components-9wydyqUx.cjs.map +0 -1
  50. package/dist/components-LmqJfxMv.js +0 -721
  51. package/dist/components-LmqJfxMv.js.map +0 -1
  52. package/dist/plugin-CNkzbtpl.cjs +0 -508
  53. package/dist/plugin-CNkzbtpl.cjs.map +0 -1
  54. package/dist/plugin-DoLrDl9P.js +0 -476
  55. package/dist/plugin-DoLrDl9P.js.map +0 -1
  56. package/dist/types-BpeKGgCn.d.ts +0 -170
  57. package/src/parser.ts +0 -396
@@ -1,25 +1,23 @@
1
- import { camelCase, jsStringEscape, pascalCase, trimQuotes } from '@internals/utils'
1
+ import { collect, narrowSchema, schemaTypes } from '@kubb/ast'
2
+ import type { EnumSchemaNode, SchemaNode } from '@kubb/ast/types'
2
3
  import { safePrint } from '@kubb/fabric-core/parsers/typescript'
3
- import type { SchemaObject } from '@kubb/oas'
4
- import { isKeyword, type Schema, SchemaGenerator, schemaKeywords } from '@kubb/plugin-oas'
5
4
  import { File } from '@kubb/react-fabric'
6
5
  import type { FabricReactNode } from '@kubb/react-fabric/types'
7
- import type ts from 'typescript'
8
- import * as factory from '../factory.ts'
9
- import { parse, typeKeywordMapper } from '../parser.ts'
6
+ import { printerTs } from '../printer.ts'
10
7
  import type { PluginTs } from '../types.ts'
8
+ import { Enum, getEnumNames } from './Enum.tsx'
11
9
 
12
10
  type Props = {
13
11
  name: string
14
12
  typedName: string
15
- schema: SchemaObject
16
- tree: Array<Schema>
13
+ node: SchemaNode
17
14
  optionalType: PluginTs['resolvedOptions']['optionalType']
18
15
  arrayType: PluginTs['resolvedOptions']['arrayType']
19
16
  enumType: PluginTs['resolvedOptions']['enumType']
20
17
  enumKeyCasing: PluginTs['resolvedOptions']['enumKeyCasing']
21
- mapper: PluginTs['resolvedOptions']['mapper']
22
18
  syntaxType: PluginTs['resolvedOptions']['syntaxType']
19
+ resolver: PluginTs['resolvedOptions']['resolver']
20
+ legacy?: boolean
23
21
  description?: string
24
22
  keysToOmit?: string[]
25
23
  }
@@ -27,133 +25,35 @@ type Props = {
27
25
  export function Type({
28
26
  name,
29
27
  typedName,
30
- tree,
28
+ node,
31
29
  keysToOmit,
32
- schema,
33
30
  optionalType,
34
31
  arrayType,
35
32
  syntaxType,
36
33
  enumType,
37
34
  enumKeyCasing,
38
- mapper,
39
35
  description,
36
+ resolver,
40
37
  }: Props): FabricReactNode {
41
- const typeNodes: ts.Node[] = []
42
-
43
- if (!tree.length) {
44
- return ''
45
- }
46
-
47
- const schemaFromTree = tree.find((item) => item.keyword === schemaKeywords.schema)
48
- const enumSchemas = SchemaGenerator.deepSearch(tree, schemaKeywords.enum)
49
-
50
- let type =
51
- (tree
52
- .map((current, _index, siblings) =>
53
- parse(
54
- { name, schema, parent: undefined, current, siblings },
55
- {
56
- optionalType,
57
- arrayType,
58
- enumType,
59
- mapper,
60
- },
61
- ),
62
- )
63
- .filter(Boolean)
64
- .at(0) as ts.TypeNode) || typeKeywordMapper.undefined()
65
-
66
- // Add a "Key" suffix to avoid collisions where necessary
67
- if (['asConst', 'asPascalConst'].includes(enumType) && enumSchemas.length > 0) {
68
- const isDirectEnum = schema.type === 'array' && schema.items !== undefined
69
- const isEnumOnly = 'enum' in schema && schema.enum
70
-
71
- if (isDirectEnum || isEnumOnly) {
72
- const enumSchema = enumSchemas[0]!
73
- const typeNameWithKey = `${enumSchema.args.typeName}Key`
74
-
75
- type = factory.createTypeReferenceNode(typeNameWithKey)
76
-
77
- if (schema.type === 'array') {
78
- if (arrayType === 'generic') {
79
- type = factory.createTypeReferenceNode(factory.createIdentifier('Array'), [type])
80
- } else {
81
- type = factory.createArrayTypeNode(type)
82
- }
83
- }
84
- }
85
- }
86
-
87
- if (schemaFromTree && isKeyword(schemaFromTree, schemaKeywords.schema)) {
88
- const isNullish = tree.some((item) => item.keyword === schemaKeywords.nullish)
89
- const isNullable = tree.some((item) => item.keyword === schemaKeywords.nullable)
90
- const isOptional = tree.some((item) => item.keyword === schemaKeywords.optional)
38
+ const resolvedDescription = description || node?.description
39
+ const enumSchemaNodes = collect<EnumSchemaNode>(node, {
40
+ schema(n): EnumSchemaNode | undefined {
41
+ const enumNode = narrowSchema(n, schemaTypes.enum)
42
+ if (enumNode?.name) return enumNode
43
+ },
44
+ })
91
45
 
92
- if (isNullable) {
93
- type = factory.createUnionDeclaration({
94
- nodes: [type, factory.keywordTypeNodes.null],
95
- }) as ts.TypeNode
96
- }
46
+ const printer = printerTs({ optionalType, arrayType, enumType, typeName: name, syntaxType, description: resolvedDescription, keysToOmit, resolver })
47
+ const typeNode = printer.print(node)
97
48
 
98
- if (isNullish && ['undefined', 'questionTokenAndUndefined'].includes(optionalType as string)) {
99
- type = factory.createUnionDeclaration({
100
- nodes: [type, factory.keywordTypeNodes.undefined],
101
- }) as ts.TypeNode
102
- }
103
-
104
- if (isOptional && ['undefined', 'questionTokenAndUndefined'].includes(optionalType as string)) {
105
- type = factory.createUnionDeclaration({
106
- nodes: [type, factory.keywordTypeNodes.undefined],
107
- }) as ts.TypeNode
108
- }
49
+ if (!typeNode) {
50
+ return
109
51
  }
110
52
 
111
- const useTypeGeneration = syntaxType === 'type' || [factory.syntaxKind.union].includes(type.kind as typeof factory.syntaxKind.union) || !!keysToOmit?.length
112
-
113
- typeNodes.push(
114
- factory.createTypeDeclaration({
115
- name,
116
- isExportable: true,
117
- type: keysToOmit?.length
118
- ? factory.createOmitDeclaration({
119
- keys: keysToOmit,
120
- type,
121
- nonNullable: true,
122
- })
123
- : type,
124
- syntax: useTypeGeneration ? 'type' : 'interface',
125
- comments: [
126
- schema.title ? `${jsStringEscape(schema.title)}` : undefined,
127
- description ? `@description ${jsStringEscape(description)}` : undefined,
128
- schema.deprecated ? '@deprecated' : undefined,
129
- schema.minLength ? `@minLength ${schema.minLength}` : undefined,
130
- schema.maxLength ? `@maxLength ${schema.maxLength}` : undefined,
131
- schema.pattern ? `@pattern ${schema.pattern}` : undefined,
132
- schema.default ? `@default ${schema.default}` : undefined,
133
- schema.example ? `@example ${schema.example}` : undefined,
134
- ],
135
- }),
136
- )
137
-
138
- const enums = [...new Set(enumSchemas)].map((enumSchema) => {
139
- const name = enumType === 'asPascalConst' ? pascalCase(enumSchema.args.name) : camelCase(enumSchema.args.name)
140
- const typeName = ['asConst', 'asPascalConst'].includes(enumType) ? `${enumSchema.args.typeName}Key` : enumSchema.args.typeName
141
-
142
- const [nameNode, typeNode] = factory.createEnumDeclaration({
143
- name,
144
- typeName,
145
- enums: enumSchema.args.items
146
- .map((item) => (item.value === undefined ? undefined : [trimQuotes(item.name?.toString()), item.value]))
147
- .filter(Boolean) as unknown as Array<[string, string]>,
148
- type: enumType,
149
- enumKeyCasing,
150
- })
151
-
53
+ const enums = [...new Map(enumSchemaNodes.map((n) => [n.name, n])).values()].map((node) => {
152
54
  return {
153
- nameNode,
154
- typeNode,
155
- name,
156
- typeName,
55
+ node,
56
+ ...getEnumNames({ node, enumType, resolver }),
157
57
  }
158
58
  })
159
59
 
@@ -163,29 +63,10 @@ export function Type({
163
63
 
164
64
  return (
165
65
  <>
166
- {shouldExportEnums &&
167
- enums.map(({ name, nameNode, typeName, typeNode }) => (
168
- <>
169
- {nameNode && (
170
- <File.Source name={name} isExportable isIndexable isTypeOnly={false}>
171
- {safePrint(nameNode)}
172
- </File.Source>
173
- )}
174
- {
175
- <File.Source
176
- name={typeName}
177
- isIndexable
178
- isExportable={['enum', 'asConst', 'asPascalConst', 'constEnum', 'literal', undefined].includes(enumType)}
179
- isTypeOnly={['asConst', 'asPascalConst', 'literal', undefined].includes(enumType)}
180
- >
181
- {safePrint(typeNode)}
182
- </File.Source>
183
- }
184
- </>
185
- ))}
66
+ {shouldExportEnums && enums.map(({ node }) => <Enum node={node} enumType={enumType} enumKeyCasing={enumKeyCasing} resolver={resolver} />)}
186
67
  {shouldExportType && (
187
68
  <File.Source name={typedName} isTypeOnly isExportable isIndexable>
188
- {safePrint(...typeNodes)}
69
+ {safePrint(typeNode)}
189
70
  </File.Source>
190
71
  )}
191
72
  </>
@@ -1 +1,2 @@
1
+ export { Enum } from './Enum.tsx'
1
2
  export { Type } from './Type.tsx'
@@ -0,0 +1,29 @@
1
+ import type { PluginTs } from './types.ts'
2
+
3
+ type OptionalType = PluginTs['resolvedOptions']['optionalType']
4
+ type EnumType = PluginTs['resolvedOptions']['enumType']
5
+
6
+ /**
7
+ * `optionalType` values that cause a property's type to include `| undefined`.
8
+ */
9
+ export const OPTIONAL_ADDS_UNDEFINED = new Set<OptionalType>(['undefined', 'questionTokenAndUndefined'] as const)
10
+
11
+ /**
12
+ * `optionalType` values that render the property key with a `?` token.
13
+ */
14
+ export const OPTIONAL_ADDS_QUESTION_TOKEN = new Set<OptionalType>(['questionToken', 'questionTokenAndUndefined'] as const)
15
+
16
+ /**
17
+ * `enumType` values that append a `Key` suffix to the generated enum type alias.
18
+ */
19
+ export const ENUM_TYPES_WITH_KEY_SUFFIX = new Set<EnumType>(['asConst', 'asPascalConst'] as const)
20
+
21
+ /**
22
+ * `enumType` values that require a runtime value declaration (object, enum, or literal).
23
+ */
24
+ export const ENUM_TYPES_WITH_RUNTIME_VALUE = new Set<EnumType | undefined>(['enum', 'asConst', 'asPascalConst', 'constEnum', 'literal', undefined] as const)
25
+
26
+ /**
27
+ * `enumType` values whose type declaration is type-only (no runtime value emitted for the type alias).
28
+ */
29
+ export const ENUM_TYPES_WITH_TYPE_ONLY = new Set<EnumType | undefined>(['asConst', 'asPascalConst', 'literal', undefined] as const)
package/src/factory.ts CHANGED
@@ -15,6 +15,8 @@ export const modifiers = {
15
15
 
16
16
  export const syntaxKind = {
17
17
  union: SyntaxKind.UnionType as 192,
18
+ literalType: SyntaxKind.LiteralType,
19
+ stringLiteral: SyntaxKind.StringLiteral,
18
20
  } as const
19
21
 
20
22
  export function getUnknownType(unknownType: 'any' | 'unknown' | 'void' | undefined) {
@@ -660,43 +662,39 @@ export const keywordTypeNodes = {
660
662
  * Converts a path like '/pet/{petId}/uploadImage' to a template literal type
661
663
  * like `/pet/${string}/uploadImage`
662
664
  */
665
+ /**
666
+ * Converts an OAS-style path (e.g. `/pets/{petId}`) or an Express-style path
667
+ * (e.g. `/pets/:petId`) to a TypeScript template literal type
668
+ * like `` `/pets/${string}` ``.
669
+ */
663
670
  export function createUrlTemplateType(path: string): ts.TypeNode {
664
- // If no parameters, return literal string type
665
- if (!path.includes('{')) {
666
- return factory.createLiteralTypeNode(factory.createStringLiteral(path))
667
- }
671
+ // normalized Express `:param` OAS `{param}` so a single regex handles both.
672
+ const normalized = path.replace(/:([^/]+)/g, '{$1}')
668
673
 
669
- // Split path by parameter placeholders, e.g. '/pet/{petId}/upload' -> ['/pet/', 'petId', '/upload']
670
- const segments = path.split(/(\{[^}]+\})/)
674
+ if (!normalized.includes('{')) {
675
+ return factory.createLiteralTypeNode(factory.createStringLiteral(normalized))
676
+ }
671
677
 
672
- // Separate static parts from parameter placeholders
678
+ const segments = normalized.split(/(\{[^}]+\})/)
673
679
  const parts: string[] = []
674
680
  const parameterIndices: number[] = []
675
681
 
676
682
  segments.forEach((segment) => {
677
683
  if (segment.startsWith('{') && segment.endsWith('}')) {
678
- // This is a parameter placeholder
679
684
  parameterIndices.push(parts.length)
680
- parts.push(segment) // Will be replaced with ${string}
685
+ parts.push(segment)
681
686
  } else if (segment) {
682
- // This is a static part
683
687
  parts.push(segment)
684
688
  }
685
689
  })
686
690
 
687
- // Build template literal type
688
- // Template literal structure: head + templateSpans[]
689
- // For '/pet/{petId}/upload': head = '/pet/', spans = [{ type: string, literal: '/upload' }]
690
-
691
691
  const head = ts.factory.createTemplateHead(parts[0] || '')
692
692
  const templateSpans: ts.TemplateLiteralTypeSpan[] = []
693
693
 
694
694
  parameterIndices.forEach((paramIndex, i) => {
695
695
  const isLast = i === parameterIndices.length - 1
696
696
  const nextPart = parts[paramIndex + 1] || ''
697
-
698
697
  const literal = isLast ? ts.factory.createTemplateTail(nextPart) : ts.factory.createTemplateMiddle(nextPart)
699
-
700
698
  templateSpans.push(ts.factory.createTemplateLiteralTypeSpan(keywordTypeNodes.string, literal))
701
699
  })
702
700