Skip to content

Commit 1a10f96

Browse files
authored
feat: Add PHPDoc to SDK (#456)
1 parent 863f1c6 commit 1a10f96

41 files changed

Lines changed: 7834 additions & 28 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

codegen/layouts/partials/route-method.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{{{methodPhpDoc this}}}
12
public function {{methodName}}({{{signatureParams}}}): {{returnType}} {
23
{{#if hasParams}}
34
$request_payload = [];

codegen/layouts/resource.hbs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
namespace Seam\Resources;
44

55
{{#each classes}}
6+
{{#if (hasPhpDoc this)}}
7+
{{{resourcePhpDoc this}}}
8+
{{/if}}
69
class {{className}}
710
{
811
public static function from_json(mixed $json): {{className}}|null
@@ -19,7 +22,10 @@ class {{className}}
1922

2023
public function __construct(
2124
{{#each constructorParams}}
22-
{{{this}}}
25+
{{#if (hasPhpDoc this)}}
26+
{{{propertyPhpDoc this}}}
27+
{{/if}}
28+
{{{declaration}}}
2329
{{/each}}
2430
) {
2531
}

codegen/lib/class-model.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@
44
export interface PhpClientMethodParameter {
55
name: string
66
type: string
7+
description: string
78
required?: boolean | undefined
89
position?: number | undefined
910
}
1011

1112
export interface PhpClientMethod {
1213
methodName: string
1314
path: string
15+
description: string
16+
responseDescription: string
17+
isDeprecated: boolean
18+
deprecationMessage: string
1419
parameters: PhpClientMethodParameter[]
1520
returnResource: string
1621
returnPath: string

codegen/lib/handlebars-helpers.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,72 @@
11
export const identity = (x: unknown): unknown => x
2+
3+
export interface DeprecatedPhpDocContext {
4+
description: string
5+
isDeprecated: boolean
6+
deprecationMessage: string
7+
}
8+
9+
export interface MethodPhpDocContext extends DeprecatedPhpDocContext {
10+
returnType: string
11+
responseDescription: string
12+
parameters: Array<{ name: string; type: string; description: string }>
13+
}
14+
15+
export const resourcePhpDoc = (context: DeprecatedPhpDocContext): string =>
16+
createPhpDoc(context.description, deprecatedTag(context))
17+
18+
export const hasPhpDoc = (context: DeprecatedPhpDocContext): boolean =>
19+
context.description.trim() !== '' || context.isDeprecated
20+
21+
export const propertyPhpDoc = (context: DeprecatedPhpDocContext): string =>
22+
createPhpDoc(context.description, deprecatedTag(context), ' ')
23+
24+
export const methodPhpDoc = (context: MethodPhpDocContext): string =>
25+
createPhpDoc(
26+
context.description,
27+
[
28+
...context.parameters.map(
29+
(parameter) =>
30+
`@param ${parameter.type} $${parameter.name}${parameter.description === '' ? '' : ` ${parameter.description}`}`,
31+
),
32+
`@return ${context.returnType}${context.responseDescription === '' ? '' : ` ${context.responseDescription}`}`,
33+
...deprecatedTag(context),
34+
],
35+
' ',
36+
)
37+
38+
const deprecatedTag = (context: DeprecatedPhpDocContext): string[] =>
39+
context.isDeprecated
40+
? [
41+
`@deprecated${context.deprecationMessage === '' ? '' : ` ${context.deprecationMessage}`}`,
42+
]
43+
: []
44+
45+
const createPhpDoc = (
46+
description: string,
47+
tags: string[],
48+
indentation = '',
49+
): string => {
50+
const descriptionLines = description
51+
.trim()
52+
.split(/\r?\n/)
53+
.map(sanitizePhpDocLine)
54+
const populatedDescription = description.trim() === '' ? [] : descriptionLines
55+
const populatedTags = tags.filter((tag) => tag.trim() !== '').map(sanitizePhpDocLine)
56+
const lines = [
57+
...populatedDescription,
58+
...(populatedDescription.length > 0 && populatedTags.length > 0 ? [''] : []),
59+
...populatedTags,
60+
]
61+
62+
if (lines.length === 0) return ''
63+
64+
return [
65+
`${indentation}/**`,
66+
...lines.map((line) => `${indentation} *${line === '' ? '' : ` ${line}`}`),
67+
`${indentation} */`,
68+
].join('\n')
69+
}
70+
71+
const sanitizePhpDocLine = (line: string): string =>
72+
line.replaceAll('*/', '* /').trimEnd()

codegen/lib/layouts/resource.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,18 @@ import type {
1515

1616
export interface ClassLayoutContext {
1717
className: string
18+
description: string
19+
isDeprecated: boolean
20+
deprecationMessage: string
1821
fromJsonProps: string[]
19-
constructorParams: string[]
22+
constructorParams: ConstructorParamLayoutContext[]
23+
}
24+
25+
export interface ConstructorParamLayoutContext {
26+
declaration: string
27+
description: string
28+
isDeprecated: boolean
29+
deprecationMessage: string
2030
}
2131

2232
export interface ResourceLayoutContext {
@@ -38,20 +48,33 @@ const generateFromJsonProp = (property: ResourceClassProperty): string => {
3848
}
3949
}
4050

41-
const generateConstructorParam = (property: ResourceClassProperty): string => {
51+
const generateConstructorParam = (
52+
property: ResourceClassProperty,
53+
): ConstructorParamLayoutContext => {
54+
let declaration: string
4255
switch (property.kind) {
4356
case 'objectReference':
44-
return `public ${property.referenceName}|null $${property.name},`
57+
declaration = `public ${property.referenceName}|null $${property.name},`
58+
break
4559

4660
case 'listReference':
47-
return `public array $${property.name},`
61+
declaration = `public array $${property.name},`
62+
break
4863

4964
case 'value': {
5065
const { phpType } = property
5166
const nullSuffix = phpType === 'mixed' ? '' : '|null'
52-
return `public ${phpType}${nullSuffix} $${property.name},`
67+
declaration = `public ${phpType}${nullSuffix} $${property.name},`
68+
break
5369
}
5470
}
71+
72+
return {
73+
declaration,
74+
description: property.description,
75+
isDeprecated: property.isDeprecated,
76+
deprecationMessage: property.deprecationMessage,
77+
}
5578
}
5679

5780
const getClassLayoutContext = (
@@ -63,6 +86,9 @@ const getClassLayoutContext = (
6386

6487
return {
6588
className: schema.name,
89+
description: schema.description,
90+
isDeprecated: schema.isDeprecated,
91+
deprecationMessage: schema.deprecationMessage,
6692
fromJsonProps: sorted.map(generateFromJsonProp),
6793
constructorParams: sorted.map(generateConstructorParam),
6894
}

codegen/lib/layouts/seam-client.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ import {
1010

1111
export interface MethodLayoutContext {
1212
methodName: string
13+
description: string
14+
responseDescription: string
15+
isDeprecated: boolean
16+
deprecationMessage: string
17+
parameters: Array<{ name: string; type: string; description: string }>
1318
path: string
1419
returnType: string
1520
hasParams: boolean
@@ -67,6 +72,15 @@ const getMethodLayoutContext = (
6772

6873
return {
6974
methodName,
75+
description: method.description,
76+
responseDescription: method.responseDescription,
77+
isDeprecated: method.isDeprecated,
78+
deprecationMessage: method.deprecationMessage,
79+
parameters: sortedParameters.map(({ name, type, description }) => ({
80+
name,
81+
type,
82+
description,
83+
})),
7084
path,
7185
returnType,
7286
hasParams: parameters.length > 0,

codegen/lib/resource-model.ts

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,22 @@ import { pascalCase } from 'change-case'
1515
import { getPhpType } from './map-php-type.js'
1616

1717
export type ResourceClassProperty =
18-
| { name: string; kind: 'value'; phpType: string }
19-
| { name: string; kind: 'objectReference'; referenceName: string }
20-
| { name: string; kind: 'listReference'; referenceName: string }
18+
| ({ kind: 'value'; phpType: string } & ResourceClassPropertyMetadata)
19+
| ({ kind: 'objectReference'; referenceName: string } & ResourceClassPropertyMetadata)
20+
| ({ kind: 'listReference'; referenceName: string } & ResourceClassPropertyMetadata)
21+
22+
interface ResourceClassPropertyMetadata {
23+
name: string
24+
description: string
25+
isDeprecated: boolean
26+
deprecationMessage: string
27+
}
2128

2229
export interface ResourceClassSchema {
2330
name: string
31+
description: string
32+
isDeprecated: boolean
33+
deprecationMessage: string
2434
properties: ResourceClassProperty[]
2535
}
2636

@@ -72,9 +82,18 @@ export const createResourceModel = (blueprint: Blueprint): ResourceModel => {
7282
name: string,
7383
properties: Property[],
7484
baseName: string,
85+
description = '',
86+
isDeprecated = false,
87+
deprecationMessage = '',
7588
): void => {
7689
if (classes.has(name)) return
77-
const schema: ResourceClassSchema = { name, properties: [] }
90+
const schema: ResourceClassSchema = {
91+
name,
92+
description,
93+
isDeprecated,
94+
deprecationMessage,
95+
properties: [],
96+
}
7897
classes.set(name, schema)
7998
if (name !== currentResourceName) {
8099
localClassNames.get(currentResourceName)?.push(name)
@@ -89,7 +108,17 @@ export const createResourceModel = (blueprint: Blueprint): ResourceModel => {
89108
const name = pascalCase(resourceType)
90109
currentResourceName = name
91110
localClassNames.set(name, [])
92-
addClass(name, baseResources.get(resourceType) ?? [], resourceType)
111+
const sourceResource =
112+
blueprint.resources.find((resource) => resource.resourceType === resourceType) ??
113+
(resourceType === 'event' ? blueprint.events[0] : blueprint.actionAttempts[0])
114+
addClass(
115+
name,
116+
baseResources.get(resourceType) ?? [],
117+
resourceType,
118+
sourceResource?.description,
119+
sourceResource?.isDeprecated,
120+
sourceResource?.deprecationMessage,
121+
)
93122

94123
const resourceClass = classes.get(name)
95124
if (resourceClass == null) {
@@ -122,16 +151,29 @@ export const createResourceModel = (blueprint: Blueprint): ResourceModel => {
122151
const createResourceClassProperty = (
123152
property: Property,
124153
baseName: string,
125-
addClass: (name: string, properties: Property[], baseName: string) => void,
154+
addClass: (
155+
name: string,
156+
properties: Property[],
157+
baseName: string,
158+
description?: string,
159+
isDeprecated?: boolean,
160+
deprecationMessage?: string,
161+
) => void,
126162
): ResourceClassProperty => {
127163
const referenceName = pascalCase(`${baseName}_${property.name}`)
164+
const metadata = {
165+
name: property.name,
166+
description: property.description,
167+
isDeprecated: property.isDeprecated,
168+
deprecationMessage: property.deprecationMessage,
169+
}
128170

129171
if (property.format === 'object') {
130172
const { properties } = property
131173

132174
if (properties.length > 0) {
133-
addClass(referenceName, properties, baseName)
134-
return { name: property.name, kind: 'objectReference', referenceName }
175+
addClass(referenceName, properties, baseName, property.description)
176+
return { ...metadata, kind: 'objectReference', referenceName }
135177
}
136178
}
137179

@@ -146,13 +188,13 @@ const createResourceClassProperty = (
146188
: []
147189

148190
if (itemProperties.length > 0) {
149-
addClass(referenceName, itemProperties, baseName)
150-
return { name: property.name, kind: 'listReference', referenceName }
191+
addClass(referenceName, itemProperties, baseName, property.description)
192+
return { ...metadata, kind: 'listReference', referenceName }
151193
}
152194
}
153195

154196
return {
155-
name: property.name,
197+
...metadata,
156198
kind: 'value',
157199
phpType: getPhpType(property),
158200
}

codegen/lib/routes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,14 @@ const createClientMethod = (endpoint: Endpoint): PhpClientMethod => {
110110
return {
111111
methodName: endpoint.name,
112112
path: endpoint.path,
113+
description: endpoint.description,
114+
responseDescription: response.description,
115+
isDeprecated: endpoint.isDeprecated,
116+
deprecationMessage: endpoint.deprecationMessage,
113117
parameters: endpoint.request.parameters.map((parameter) => ({
114118
name: parameter.name,
115119
type: getPhpType(parameter),
120+
description: parameter.description,
116121
required: parameter.isRequired,
117122
// The primary identifier of a get endpoint always sorts first in the
118123
// method signature.

0 commit comments

Comments
 (0)