Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/astro-component-docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"scripts": {
"build": "tsc -b",
"dev": "tsc -b --watch",
"clean": "tsc -b --clean"
"clean": "tsc -b --clean",
"test": "bun test src/"
},
"dependencies": {
"esast-util-from-js": "^2.0.1",
Expand Down
121 changes: 121 additions & 0 deletions packages/astro-component-docs/src/props/extractor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { describe, it, expect } from 'bun:test';
import { extractAllProps } from './extractor.js';
import type { PackageConfig } from './types.js';
import { resolve } from 'node:path';

const REACT_NAVER_MAPS_CONFIG: PackageConfig = {
name: 'react-naver-maps',
tsconfig: resolve(
import.meta.dirname,
'../../../react-naver-maps/tsconfig.json',
),
};

describe('extractAllProps', () => {
const docs = extractAllProps(REACT_NAVER_MAPS_CONFIG);
const docMap = new Map(docs.map((d) => [d.displayName, d]));

it('.d.ts 파일에서 컴포넌트를 감지한다', () => {
expect(docs.length).toBeGreaterThan(0);
});

it('주요 컴포넌트가 모두 포함된다', () => {
const expected = [
'NaverMap',
'Marker',
'Container',
'Circle',
'Polygon',
'Polyline',
'InfoWindow',
];
for (const name of expected) {
expect(docMap.has(name)).toBe(true);
}
});

describe('NaverMap', () => {
const naverMap = docMap.get('NaverMap')!;

it('컴포넌트 문서가 존재한다', () => {
expect(naverMap).toBeDefined();
});

it('Props가 추출된다', () => {
expect(naverMap.props.length).toBeGreaterThan(10);
});

it('center prop의 타입이 정확하다', () => {
const center = naverMap.props.find((p) => p.name === 'center');
expect(center).toBeDefined();
expect(center!.required).toBe(false);
expect(center!.type).toContain('Coord');
});

it('onClick 이벤트 핸들러가 추출된다', () => {
const onClick = naverMap.props.find((p) => p.name === 'onClick');
expect(onClick).toBeDefined();
expect(onClick!.required).toBe(false);
expect(onClick!.type).toContain('PointerEvent');
});

it('ref, key는 제외된다', () => {
const names = naverMap.props.map((p) => p.name);
expect(names).not.toContain('ref');
expect(names).not.toContain('key');
});
});

describe('Marker', () => {
const marker = docMap.get('Marker')!;

it('position prop이 있다', () => {
const position = marker.props.find((p) => p.name === 'position');
expect(position).toBeDefined();
expect(position!.type).toContain('Coord');
});
});

describe('propsOverrides', () => {
it('hidden override가 적용된다', () => {
const config: PackageConfig = {
...REACT_NAVER_MAPS_CONFIG,
propsOverrides: {
NaverMap: {
center: { hidden: true },
},
},
};
const result = extractAllProps(config);
const naverMap = result.find((d) => d.displayName === 'NaverMap')!;
const names = naverMap.props.map((p) => p.name);
expect(names).not.toContain('center');
});

it('description override가 적용된다', () => {
const config: PackageConfig = {
...REACT_NAVER_MAPS_CONFIG,
propsOverrides: {
NaverMap: {
zoom: { description: '커스텀 설명' },
},
},
};
const result = extractAllProps(config);
const naverMap = result.find((d) => d.displayName === 'NaverMap')!;
const zoom = naverMap.props.find((p) => p.name === 'zoom');
expect(zoom!.description).toBe('커스텀 설명');
});
});

describe('에러 처리', () => {
it('잘못된 tsconfig 경로에서 예외를 던진다', () => {
expect(() =>
extractAllProps({
name: 'nonexistent',
tsconfig: '/nonexistent/tsconfig.json',
}),
).toThrow();
});
});
});
34 changes: 26 additions & 8 deletions packages/astro-component-docs/src/props/extractor.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { readFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { Project, Node, type Type, type Symbol as TsSymbol } from 'ts-morph';
import type {
ComponentDoc,
Expand All @@ -7,21 +9,37 @@ import type {
} from './types.js';

/**
* Extract props from a React component's type declarations using ts-morph.
* Extract props from a React component's .d.ts declarations using ts-morph.
*
* Strategy:
* 1. Load the project from the package's tsconfig
* 2. Find the component's exported function declaration
* 3. Extract the first parameter's type (Props)
* 4. For each property, extract name, type, required, description, defaultValue
* 5. Apply user overrides
* 1. Read the package's tsconfig to find outDir and compiler options
* 2. Load .d.ts files from outDir (not source files)
* 3. Find the component's exported function declaration
* 4. Extract the first parameter's type (Props)
* 5. For each property, extract name, type, required, description, defaultValue
* 6. Apply user overrides
*/
export function extractAllProps(pkg: PackageConfig): ComponentDoc[] {
const tsconfigPath = resolve(pkg.tsconfig!);
const tsconfigDir = dirname(tsconfigPath);
const tsconfig = JSON.parse(readFileSync(tsconfigPath, 'utf-8'));
const outDir = tsconfig.compilerOptions?.outDir ?? 'dist';
const dtsDir = resolve(tsconfigDir, outDir);

const project = new Project({
tsConfigFilePath: pkg.tsconfig,
skipAddingFilesFromTsConfig: false,
tsConfigFilePath: tsconfigPath,
skipAddingFilesFromTsConfig: true,
});

const added = project.addSourceFilesAtPaths(`${dtsDir}/**/*.d.ts`);
if (added.length === 0) {
console.warn(
`[astro-component-docs] No .d.ts files found in "${dtsDir}". ` +
`Build the package first (e.g. tsc -b).`,
);
return [];
}

const docs: ComponentDoc[] = [];

// Find exported components across all source files
Expand Down
1 change: 0 additions & 1 deletion packages/astro-component-docs/src/props/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export interface PropsOverride {
export interface PackageConfig {
name: string;
tsconfig?: string;
entrypoint?: string;
propsOverrides?: Record<string, Record<string, PropsOverride>>;
}

Expand Down
7 changes: 6 additions & 1 deletion packages/astro-component-docs/src/vite/vite-plugin-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ export function vitePluginProps(config: AstroComponentDocsConfig): VitePlugin {
},

handleHotUpdate({ file }: { file: string }) {
if (file.endsWith('tsconfig.json') || file.endsWith('.d.ts')) {
if (
file.endsWith('tsconfig.json') ||
file.endsWith('.d.ts') ||
file.endsWith('.ts') ||
file.endsWith('.tsx')
) {
propsCache = null;
}
},
Expand Down
2 changes: 1 addition & 1 deletion packages/astro-component-docs/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@
"types": []
},
"include": ["src"],
"exclude": ["node_modules", "dist", "src/content/schema.ts"]
"exclude": ["node_modules", "dist", "src/content/schema.ts", "**/*.test.ts"]
}
Loading