Skip to content

Commit 9f06f7f

Browse files
committed
fix: separate class getters from methods in docs rendering (#1943)
1 parent bf12be0 commit 9f06f7f

3 files changed

Lines changed: 157 additions & 2 deletions

File tree

server/utils/docs/render.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,11 +293,32 @@ function renderClassMembers(def: NonNullable<DenoDocNode['classDef']>): string {
293293
lines.push(`</div>`)
294294
}
295295

296-
if (methods && methods.length > 0) {
296+
const getters = methods?.filter(m => m.kind === 'getter') || []
297+
const regularMethods = methods?.filter(m => m.kind !== 'getter') || []
298+
299+
if (getters.length > 0) {
300+
lines.push(`<div class="docs-members">`)
301+
lines.push(`<h4>Getters</h4>`)
302+
lines.push(`<dl>`)
303+
for (const getter of getters) {
304+
const ret = formatType(getter.functionDef?.returnType) || 'unknown'
305+
const staticStr = getter.isStatic ? 'static ' : ''
306+
lines.push(
307+
`<dt><code>${escapeHtml(staticStr)}get ${escapeHtml(getter.name)}: ${escapeHtml(ret)}</code></dt>`,
308+
)
309+
if (getter.jsDoc?.doc) {
310+
lines.push(`<dd>${escapeHtml(getter.jsDoc.doc.split('\n')[0] ?? '')}</dd>`)
311+
}
312+
}
313+
lines.push(`</dl>`)
314+
lines.push(`</div>`)
315+
}
316+
317+
if (regularMethods.length > 0) {
297318
lines.push(`<div class="docs-members">`)
298319
lines.push(`<h4>Methods</h4>`)
299320
lines.push(`<dl>`)
300-
for (const method of methods) {
321+
for (const method of regularMethods) {
301322
const params = method.functionDef?.params?.map(p => formatParam(p)).join(', ') || ''
302323
const ret = formatType(method.functionDef?.returnType) || 'void'
303324
const staticStr = method.isStatic ? 'static ' : ''

shared/types/deno-doc.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export interface DenoDocNode {
100100
}>
101101
methods?: Array<{
102102
name: string
103+
kind?: string
103104
isStatic?: boolean
104105
functionDef?: {
105106
params?: FunctionParam[]
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { renderDocNodes } from '../../../../../server/utils/docs/render'
3+
import type { DenoDocNode } from '#shared/types/deno-doc'
4+
import type { MergedSymbol } from '../../../../../server/utils/docs/types'
5+
6+
// =============================================================================
7+
// Issue #1943: class getters shown as methods
8+
// https://github.com/npmx-dev/npmx.dev/issues/1943
9+
// =============================================================================
10+
11+
function createClassSymbol(classDef: DenoDocNode['classDef']): MergedSymbol {
12+
const node: DenoDocNode = {
13+
name: 'TestClass',
14+
kind: 'class',
15+
classDef,
16+
}
17+
return {
18+
name: 'TestClass',
19+
kind: 'class',
20+
nodes: [node],
21+
}
22+
}
23+
24+
describe('issue #1943 - class getters separated from methods', () => {
25+
it('renders getters under a "Getters" heading, not "Methods"', async () => {
26+
const symbol = createClassSymbol({
27+
methods: [
28+
{
29+
name: 'clientId',
30+
kind: 'getter',
31+
functionDef: {
32+
returnType: { repr: 'string', kind: 'keyword', keyword: 'string' },
33+
},
34+
},
35+
],
36+
})
37+
38+
const html = await renderDocNodes([symbol], new Map())
39+
40+
expect(html).toContain('<h4>Getters</h4>')
41+
expect(html).toContain('get clientId')
42+
expect(html).not.toContain('<h4>Methods</h4>')
43+
})
44+
45+
it('renders regular methods under "Methods" heading', async () => {
46+
const symbol = createClassSymbol({
47+
methods: [
48+
{
49+
name: 'connect',
50+
kind: 'method',
51+
functionDef: {
52+
params: [],
53+
returnType: { repr: 'void', kind: 'keyword', keyword: 'void' },
54+
},
55+
},
56+
],
57+
})
58+
59+
const html = await renderDocNodes([symbol], new Map())
60+
61+
expect(html).toContain('<h4>Methods</h4>')
62+
expect(html).toContain('connect(')
63+
expect(html).not.toContain('<h4>Getters</h4>')
64+
})
65+
66+
it('renders both getters and methods in separate sections', async () => {
67+
const symbol = createClassSymbol({
68+
methods: [
69+
{
70+
name: 'clientId',
71+
kind: 'getter',
72+
functionDef: {
73+
returnType: { repr: 'string', kind: 'keyword', keyword: 'string' },
74+
},
75+
jsDoc: { doc: 'The client ID' },
76+
},
77+
{
78+
name: 'connect',
79+
kind: 'method',
80+
functionDef: {
81+
params: [
82+
{
83+
kind: 'identifier',
84+
name: 'url',
85+
tsType: { repr: 'string', kind: 'keyword', keyword: 'string' },
86+
},
87+
],
88+
returnType: { repr: 'void', kind: 'keyword', keyword: 'void' },
89+
},
90+
jsDoc: { doc: 'Connect to server' },
91+
},
92+
],
93+
})
94+
95+
const html = await renderDocNodes([symbol], new Map())
96+
97+
// Both sections should exist
98+
expect(html).toContain('<h4>Getters</h4>')
99+
expect(html).toContain('<h4>Methods</h4>')
100+
101+
// Getter should use "get" prefix without parentheses
102+
expect(html).toContain('get clientId')
103+
expect(html).toContain('The client ID')
104+
105+
// Method should have parentheses
106+
expect(html).toContain('connect(')
107+
expect(html).toContain('Connect to server')
108+
109+
// Getters section should appear before Methods section
110+
const gettersIndex = html.indexOf('<h4>Getters</h4>')
111+
const methodsIndex = html.indexOf('<h4>Methods</h4>')
112+
expect(gettersIndex).toBeLessThan(methodsIndex)
113+
})
114+
115+
it('renders static getter correctly', async () => {
116+
const symbol = createClassSymbol({
117+
methods: [
118+
{
119+
name: 'instance',
120+
kind: 'getter',
121+
isStatic: true,
122+
functionDef: {
123+
returnType: { repr: 'TestClass', kind: 'typeRef', typeRef: { typeName: 'TestClass' } },
124+
},
125+
},
126+
],
127+
})
128+
129+
const html = await renderDocNodes([symbol], new Map())
130+
131+
expect(html).toContain('static get instance')
132+
})
133+
})

0 commit comments

Comments
 (0)