diff --git a/site/scripts/api-docs-builder/src/data-attrs-handler.ts b/site/scripts/api-docs-builder/src/data-attrs-handler.ts index 8de8ec46..9233cc93 100644 --- a/site/scripts/api-docs-builder/src/data-attrs-handler.ts +++ b/site/scripts/api-docs-builder/src/data-attrs-handler.ts @@ -1,6 +1,26 @@ import * as ts from 'typescript'; import type { DataAttrsExtraction } from './types.js'; +function unwrapObjectLiteral(node: ts.Expression): ts.ObjectLiteralExpression | undefined { + if (ts.isObjectLiteralExpression(node)) { + return node; + } + + if (ts.isParenthesizedExpression(node)) { + return unwrapObjectLiteral(node.expression); + } + + if (ts.isAsExpression(node)) { + return unwrapObjectLiteral(node.expression); + } + + if (ts.isSatisfiesExpression(node)) { + return unwrapObjectLiteral(node.expression); + } + + return undefined; +} + /** * Extract data attributes from a data-attrs file. * @@ -37,15 +57,7 @@ export function extractDataAttrs( continue; } - // Handle `as const` assertions - let objLiteral: ts.ObjectLiteralExpression | undefined; - - if (ts.isObjectLiteralExpression(decl.initializer)) { - objLiteral = decl.initializer; - } else if (ts.isAsExpression(decl.initializer) && ts.isObjectLiteralExpression(decl.initializer.expression)) { - objLiteral = decl.initializer.expression; - } - + const objLiteral = unwrapObjectLiteral(decl.initializer); if (!objLiteral) continue; // Extract properties with their JSDoc comments diff --git a/site/scripts/api-docs-builder/src/tests/data-attrs-handler.test.ts b/site/scripts/api-docs-builder/src/tests/data-attrs-handler.test.ts index cce9e08a..c55a6e85 100644 --- a/site/scripts/api-docs-builder/src/tests/data-attrs-handler.test.ts +++ b/site/scripts/api-docs-builder/src/tests/data-attrs-handler.test.ts @@ -63,6 +63,58 @@ describe('extractDataAttrs', () => { expect(result!.attrs).toHaveLength(1); }); + it('extracts from {Name}DataAttrs with as const satisfies', () => { + const code = ` + type StateAttrMap = { [Key in keyof State]?: string }; + interface MockComponentState { + active: boolean; + } + + export const MockComponentDataAttrs = { + active: 'data-active', + } as const satisfies StateAttrMap; + `; + const program = createTestProgram(code); + const result = extractDataAttrs('test.ts', program, 'MockComponent'); + + expect(result).not.toBeNull(); + expect(result!.attrs).toHaveLength(1); + expect(result!.attrs[0]!.name).toBe('data-active'); + }); + + it('extracts from {Name}DataAttrs with satisfies expression', () => { + const code = ` + export const MockComponentDataAttrs = ({ + active: 'data-active', + }) satisfies Record; + `; + const program = createTestProgram(code); + const result = extractDataAttrs('test.ts', program, 'MockComponent'); + + expect(result).not.toBeNull(); + expect(result!.attrs).toHaveLength(1); + expect(result!.attrs[0]!.name).toBe('data-active'); + }); + + it('extracts JSDoc comments for properties wrapped with satisfies', () => { + const code = ` + type StateAttrMap = { [Key in keyof State]?: string }; + interface MockComponentState { + active: boolean; + } + + export const MockComponentDataAttrs = { + /** Present when the component is active. */ + active: 'data-active', + } as const satisfies StateAttrMap; + `; + const program = createTestProgram(code); + const result = extractDataAttrs('test.ts', program, 'MockComponent'); + + expect(result).not.toBeNull(); + expect(result!.attrs[0]!.description).toBe('Present when the component is active.'); + }); + it('returns null when constant not found', () => { const code = ` export const OtherConstant = {