Skip to content

fix: unexpected unknown type for api docs format type#1663

Merged
danielroe merged 3 commits intonpmx-dev:mainfrom
RYGRIT:fix/unexpected-unknown-type
Feb 26, 2026
Merged

fix: unexpected unknown type for api docs format type#1663
danielroe merged 3 commits intonpmx-dev:mainfrom
RYGRIT:fix/unexpected-unknown-type

Conversation

@RYGRIT
Copy link
Contributor

@RYGRIT RYGRIT commented Feb 26, 2026

🔗 Linked issue

resolves #1411

🧭 Context

formatType() only handled 4 type kinds (keyword, typeRef, array, union), but @deno/doc produces 21 kinds. When encountering unhandled kinds with an empty repr, the function returned "unknown".

📚 Description

  • Added TYPE_FORMATTERS for missing type kinds: fnOrConstructor, indexedAccess, typeOperator, typeLiteral, literal, this
  • Extended TsType interface with corresponding fields
  • Added fixture-based test cases from linkdave@0.0.2 covering all 5 reported cases

Recently, deno_doc is undergoing some changes; please see https://discord.com/channels/1464542801676206113/1464544758020968448/1476439287468654733. I'm not sure if these changes will cause any other problems.

before after
image image

@vercel
Copy link

vercel bot commented Feb 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Feb 26, 2026 10:55am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Feb 26, 2026 10:55am
npmx-lunaria Ignored Ignored Feb 26, 2026 10:55am

Request Review

@codecov
Copy link

codecov bot commented Feb 26, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@RYGRIT RYGRIT changed the title fix: unexpected unknown type for docs format type fix: unexpected unknown type for api docs format type Feb 26, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 2026

📝 Walkthrough

Walkthrough

This pull request refactors the type formatting system in server/utils/docs/format.ts by introducing a dispatcher-based approach using a TYPE_FORMATTERS mapping that handles various TypeScript type kinds. The shared/types/deno-doc.ts file is extended with new type representations including fnOrConstructor, indexedAccess, typeOperator, typeLiteral, and this to support more complex type structures. The class extends field is changed from TsType to string. Multiple test fixtures are added for real-world type examples, and a comprehensive test suite validates the formatting and signature generation for these advanced type representations.

Suggested labels

front

Suggested reviewers

  • danielroe
  • alexdln
  • serhalp
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The PR description clearly explains the problem and solution related to handling unhandled type kinds in formatType().
Linked Issues check ✅ Passed All coding requirements from issue #1411 are addressed: added formatters for fnOrConstructor, indexedAccess, typeOperator, typeLiteral, literal, and this types with corresponding TsType interface extensions.
Out of Scope Changes check ✅ Passed All changes are directly related to resolving #1411: type formatter implementations, TsType interface extensions, test fixtures, and corresponding test cases.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
server/utils/docs/format.ts (1)

124-137: typeLiteral output is incomplete for callable/indexable object types.

The current formatter only emits properties and methods. callSignatures and indexSignatures are silently dropped, which can still produce inaccurate docs for valid type literals.

Proposed enhancement
 function formatTypeLiteralType(lit: NonNullable<TsType['typeLiteral']>): string {
   const parts: string[] = []
   for (const prop of lit.properties) {
     const opt = prop.optional ? '?' : ''
     const ro = prop.readonly ? 'readonly ' : ''
     parts.push(`${ro}${prop.name}${opt}: ${formatType(prop.tsType) || 'unknown'}`)
   }
   for (const method of lit.methods) {
     const params = method.params?.map(p => formatParam(p)).join(', ') || ''
     const ret = formatType(method.returnType) || 'void'
     parts.push(`${method.name}(${params}): ${ret}`)
   }
+  for (const callSig of lit.callSignatures) {
+    const params = callSig.params?.map(p => formatParam(p)).join(', ') || ''
+    const ret = formatType(callSig.tsType) || 'void'
+    parts.push(`(${params}): ${ret}`)
+  }
+  for (const indexSig of lit.indexSignatures) {
+    const params = indexSig.params.map(p => formatParam(p)).join(', ')
+    const ret = formatType(indexSig.tsType) || 'unknown'
+    parts.push(`[${params}]: ${ret}`)
+  }
   return `{ ${parts.join('; ')} }`
 }

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 976070d and 789bcd3.

📒 Files selected for processing (8)
  • server/utils/docs/format.ts
  • shared/types/deno-doc.ts
  • test/fixtures/esm-sh/doc-nodes/linkdave@0.0.2-client-message.json
  • test/fixtures/esm-sh/doc-nodes/linkdave@0.0.2-client.json
  • test/fixtures/esm-sh/doc-nodes/linkdave@0.0.2-manager-events.json
  • test/fixtures/esm-sh/doc-nodes/linkdave@0.0.2-pick-type.json
  • test/fixtures/esm-sh/doc-nodes/linkdave@0.0.2-send-to-shard.json
  • test/unit/server/utils/docs/format.spec.ts

Comment on lines +116 to +122
function formatFnOrConstructorType(fn: NonNullable<TsType['fnOrConstructor']>): string {
const typeParams = fn.typeParams?.map(t => t.name).join(', ')
const typeParamsStr = typeParams ? `<${typeParams}>` : ''
const params = fn.params.map(p => formatParam(p)).join(', ')
const ret = formatType(fn.tsType) || 'void'
return `${typeParamsStr}(${params}) => ${ret}`
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n server/utils/docs/format.ts | head -150 | tail -50

Repository: npmx-dev/npmx.dev

Length of output: 2493


🏁 Script executed:

rg -n "fnOrConstructor|TsType" server/utils/docs/format.ts | head -20

Repository: npmx-dev/npmx.dev

Length of output: 581


🏁 Script executed:

rg -n "interface.*TsType|type.*TsType" server/utils/docs/ -A 30

Repository: npmx-dev/npmx.dev

Length of output: 5460


🏁 Script executed:

fd -t f "types" --type f -path "*shared/types*" | head -20

Repository: npmx-dev/npmx.dev

Length of output: 229


🏁 Script executed:

find . -type f -name "*deno-doc*" -o -name "*types*" | grep -i "shared\|types" | head -20

Repository: npmx-dev/npmx.dev

Length of output: 340


🏁 Script executed:

cat -n shared/types/deno-doc.ts | grep -A 100 "fnOrConstructor\|interface.*TsType"

Repository: npmx-dev/npmx.dev

Length of output: 3442


🏁 Script executed:

cat -n shared/types/deno-doc.ts | sed -n '38,43p'

Repository: npmx-dev/npmx.dev

Length of output: 262


🏁 Script executed:

cat -n shared/types/deno-doc.ts | sed -n '38,43p'

Repository: npmx-dev/npmx.dev

Length of output: 262


🏁 Script executed:

cat -n server/utils/docs/format.ts | sed -n '116,122p'

Repository: npmx-dev/npmx.dev

Length of output: 482


fnOrConstructor formatting drops generic constraints and constructor semantics.

Line 117 ignores the constraint field of type parameters (e.g. K extends keyof ...), and line 121 does not check the constructor boolean flag, so constructor signatures are rendered as plain function arrows instead of constructor syntax.

Proposed fix
 function formatFnOrConstructorType(fn: NonNullable<TsType['fnOrConstructor']>): string {
-  const typeParams = fn.typeParams?.map(t => t.name).join(', ')
+  const typeParams = fn.typeParams
+    ?.map(t => (t.constraint ? `${t.name} extends ${formatType(t.constraint)}` : t.name))
+    .join(', ')
   const typeParamsStr = typeParams ? `<${typeParams}>` : ''
   const params = fn.params.map(p => formatParam(p)).join(', ')
   const ret = formatType(fn.tsType) || 'void'
-  return `${typeParamsStr}(${params}) => ${ret}`
+  const ctorPrefix = fn.constructor ? 'new ' : ''
+  return `${ctorPrefix}${typeParamsStr}(${params}) => ${ret}`
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function formatFnOrConstructorType(fn: NonNullable<TsType['fnOrConstructor']>): string {
const typeParams = fn.typeParams?.map(t => t.name).join(', ')
const typeParamsStr = typeParams ? `<${typeParams}>` : ''
const params = fn.params.map(p => formatParam(p)).join(', ')
const ret = formatType(fn.tsType) || 'void'
return `${typeParamsStr}(${params}) => ${ret}`
}
function formatFnOrConstructorType(fn: NonNullable<TsType['fnOrConstructor']>): string {
const typeParams = fn.typeParams
?.map(t => (t.constraint ? `${t.name} extends ${formatType(t.constraint)}` : t.name))
.join(', ')
const typeParamsStr = typeParams ? `<${typeParams}>` : ''
const params = fn.params.map(p => formatParam(p)).join(', ')
const ret = formatType(fn.tsType) || 'void'
const ctorPrefix = fn.constructor ? 'new ' : ''
return `${ctorPrefix}${typeParamsStr}(${params}) => ${ret}`
}

Comment on lines +20 to +21
const onProp = node.interfaceDef!.properties![0]!
const emitProp = node.interfaceDef!.properties![1]!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and read the test file mentioned
find . -type f -name "format.spec.ts" | head -5

Repository: npmx-dev/npmx.dev

Length of output: 105


🏁 Script executed:

# Get the file content to examine lines 20-21 and 42
cat -n test/unit/server/utils/docs/format.spec.ts | head -80

Repository: npmx-dev/npmx.dev

Length of output: 3770


🏁 Script executed:

# Check the fixture file referenced in the test to understand array guarantees
find . -type f -name "linkdave@0.0.2-client.json" 2>/dev/null | head -3

Repository: npmx-dev/npmx.dev

Length of output: 120


🏁 Script executed:

# Look for similar patterns of unchecked array access in the same directory
rg -n "properties!\[" test/unit/server/utils/docs/ -A 2 -B 2

Repository: npmx-dev/npmx.dev

Length of output: 1024


🏁 Script executed:

# Examine the fixture file to see if the array has the required elements
cat test/fixtures/esm-sh/doc-nodes/linkdave@0.0.2-client.json | jq '.interfaceDef.properties | length'

Repository: npmx-dev/npmx.dev

Length of output: 62


🏁 Script executed:

# Also check the other fixture used in line 42
cat test/fixtures/esm-sh/doc-nodes/linkdave@0.0.2-manager-events.json | jq '.interfaceDef.properties | length'

Repository: npmx-dev/npmx.dev

Length of output: 62


🏁 Script executed:

# Look at the structure of the properties to verify they exist
cat test/fixtures/esm-sh/doc-nodes/linkdave@0.0.2-client.json | jq '.interfaceDef.properties[0:2]' | head -30

Repository: npmx-dev/npmx.dev

Length of output: 718


🏁 Script executed:

# Check the TypeScript types for DenoDocNode to understand the property types
find . -type f -name "*.ts" -path "*/types/*" | xargs grep -l "DenoDocNode\|interfaceDef\|properties" | head -5

Repository: npmx-dev/npmx.dev

Length of output: 87


🏁 Script executed:

# Read the DenoDocNode type definition to understand property types
cat -n ./shared/types/deno-doc.ts | head -100

Repository: npmx-dev/npmx.dev

Length of output: 3094


🏁 Script executed:

# Search for interfaceDef definition specifically
rg -n "interfaceDef\|properties" ./shared/types/deno-doc.ts -A 3 -B 1 | head -50

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

# Continue searching for interfaceDef definition
cat -n ./shared/types/deno-doc.ts | tail -80

Repository: npmx-dev/npmx.dev

Length of output: 2263


🏁 Script executed:

# Search more specifically for interfaceDef
rg -n "interfaceDef" ./shared/types/deno-doc.ts -A 5

Repository: npmx-dev/npmx.dev

Length of output: 219


Ensure strict array bounds checking instead of relying on non-null assertions.

Lines 20–21 and 42 use non-null assertions (!) to bypass TypeScript's array bounds checking. Although the fixtures happen to contain the required properties, code should explicitly verify array access rather than asserting safety. Consider adding guards before indexed access to comply with type-safety guidelines.

it('event listener: interface properties with fnOrConstructor type (client.d.ts)', () => {
  const node = loadFixture('linkdave@0.0.2-client.json')
-  const onProp = node.interfaceDef!.properties![0]!
-  const emitProp = node.interfaceDef!.properties![1]!
+  const properties = node.interfaceDef?.properties
+  if (!properties?.[0] || !properties[1]) {
+    throw new Error('Fixture is missing expected interface properties')
+  }
+  const onProp = properties[0]
+  const emitProp = properties[1]

Line 42 should be similarly guarded before access.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const onProp = node.interfaceDef!.properties![0]!
const emitProp = node.interfaceDef!.properties![1]!
it('event listener: interface properties with fnOrConstructor type (client.d.ts)', () => {
const node = loadFixture('linkdave@0.0.2-client.json')
const properties = node.interfaceDef?.properties
if (!properties?.[0] || !properties[1]) {
throw new Error('Fixture is missing expected interface properties')
}
const onProp = properties[0]
const emitProp = properties[1]

Copy link
Member

@danielroe danielroe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

@danielroe danielroe added this pull request to the merge queue Feb 26, 2026
Merged via the queue into npmx-dev:main with commit dd3c186 Feb 26, 2026
18 checks passed
@RYGRIT RYGRIT deleted the fix/unexpected-unknown-type branch February 27, 2026 02:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: wrong unknown types in package api docs

2 participants