From 8460ef8b5702db13716ea3a11a850cc9fa7a51b9 Mon Sep 17 00:00:00 2001 From: typicode Date: Wed, 12 Jun 2024 22:17:04 +0200 Subject: [PATCH] Add TSDoc support (#55) * Add comment support * Update docs --- docs/src/content/docs/component.mdx | 2 +- src/parser.test.ts | 4 +++- src/parser.ts | 16 +++++++++++++-- .../__snapshots__/react.test.ts.snap | 12 +++++++++++ src/renderers/__snapshots__/vue.test.ts.snap | 9 +++++++++ src/renderers/react.test.ts | 6 +++++- src/renderers/react.ts | 5 ++++- src/renderers/vue.test.ts | 3 +++ src/renderers/vue.ts | 20 ++++--------------- 9 files changed, 55 insertions(+), 22 deletions(-) diff --git a/docs/src/content/docs/component.mdx b/docs/src/content/docs/component.mdx index 9b1167a..0a6c1ed 100644 --- a/docs/src/content/docs/component.mdx +++ b/docs/src/content/docs/component.mdx @@ -7,7 +7,7 @@ title: Writing Components Here's the basic structure of a MistCSS component. See below for details. ```css title="Button.mist.css" copy -/* Tag and component name */ +/* This comment will be used as TSDoc for the generated component. */ @scope (button.custom-button) { :scope { /* CSS variables */ diff --git a/src/parser.test.ts b/src/parser.test.ts index 7d1ccfc..e2d4a84 100644 --- a/src/parser.test.ts +++ b/src/parser.test.ts @@ -2,8 +2,9 @@ import { test, expect } from 'vitest' import { parse } from './parser.js' - test('parse', () => { +test('parse', () => { const css = ` + /* comment */ @scope (div.foo) { :scope { --foo: green; @@ -26,6 +27,7 @@ import { parse } from './parser.js' const actual = parse(css) const expected = [ { + comment: 'comment', tag: 'div', className: 'foo', attributes: { 'data-foo': new Set(['one', 'two']) }, diff --git a/src/parser.ts b/src/parser.ts index 0adb803..216eebe 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -4,6 +4,7 @@ import { Element, Middleware, middleware, + COMMENT, RULESET, // @ts-ignore SCOPE, @@ -11,6 +12,7 @@ import { } from 'stylis' export interface Data { + comment: string className: string // foo tag: string // div attributes: Record> // data-foo: ['bar', 'baz'] @@ -18,6 +20,15 @@ export interface Data { properties: Set // --foo, --bar } +function getComment(element: Element): string { + // @ts-ignore + const prev = element.siblings[element.siblings.indexOf(element) - 1] as Element + if (prev.type === COMMENT) { + return (prev.children as string).trim() + } + return '' +} + // (div.foo) -> { tag: 'div', className: 'foo' } function parseScopeSelector(str: string): { tag: string; className: string } { const [tag = '', className = ''] = str.slice(1, -1).split('.') @@ -38,7 +49,7 @@ function parseAttribute(str: string): { attribute: string; value?: string } { } function update(data: Data): Middleware { - return function (element, _index, _children, callback) { + return function(element, _index, _children, callback) { switch (element.type) { case DECLARATION: // Custom properties @@ -48,7 +59,7 @@ function update(data: Data): Middleware { break case RULESET: - ;(element.props as string[]) + ; (element.props as string[]) .filter(isAttribute) .map(parseAttribute) .forEach(({ attribute, value }) => { @@ -81,6 +92,7 @@ export function parse(css: string): Data[] { const { tag, className } = parseScopeSelector(prop) const data: Data = { + comment: getComment(element), tag, className, attributes: {}, diff --git a/src/renderers/__snapshots__/react.test.ts.snap b/src/renderers/__snapshots__/react.test.ts.snap index 2e6abf2..be560ba 100644 --- a/src/renderers/__snapshots__/react.test.ts.snap +++ b/src/renderers/__snapshots__/react.test.ts.snap @@ -8,6 +8,9 @@ import type { PropsWithChildren } from 'hono/jsx' type Props = { attr?: 'a' | 'b', attrFooBar?: 'foo-bar', isFoo?: boolean, propFoo?: string, propBar?: string } & JSX.IntrinsicElements['div'] +/** +* comment +*/ export function Foo({ children, attr, attrFooBar, isFoo, propFoo, propBar, ...props }: PropsWithChildren) { return (
{children}
) } @@ -22,6 +25,9 @@ import type { JSX, PropsWithChildren } from 'react' type Props = { attr?: 'a' | 'b', attrFooBar?: 'foo-bar', isFoo?: boolean, propFoo?: string, propBar?: string } & JSX.IntrinsicElements['div'] +/** +* comment +*/ export function Foo({ children, attr, attrFooBar, isFoo, propFoo, propBar, ...props }: PropsWithChildren) { return (
{children}
) } @@ -36,6 +42,9 @@ import type { JSX, PropsWithChildren } from 'react' type Props = { } & JSX.IntrinsicElements['div'] +/** +* comment +*/ export function Foo({ children, ...props }: PropsWithChildren) { return (
{children}
) } @@ -50,6 +59,9 @@ import type { JSX } from 'react' type Props = { } & JSX.IntrinsicElements['hr'] +/** +* comment +*/ export function Foo({ ...props }: Props) { return (
) } diff --git a/src/renderers/__snapshots__/vue.test.ts.snap b/src/renderers/__snapshots__/vue.test.ts.snap index 3498246..6daa179 100644 --- a/src/renderers/__snapshots__/vue.test.ts.snap +++ b/src/renderers/__snapshots__/vue.test.ts.snap @@ -10,6 +10,9 @@ import type { JSX } from 'vue/jsx-runtime' type Props = { attr?: 'a' | 'b', attrFooBar?: 'foo-bar', isFoo?: boolean, propFoo?: string, propBar?: string } & JSX.IntrinsicElements['div'] +/** +* comment +*/ export function Foo({ attr, attrFooBar, isFoo, propFoo, propBar, ...props }: Props, { slots }: SetupContext) { return (
{slots.default?.()}
) } @@ -26,6 +29,9 @@ import type { JSX } from 'vue/jsx-runtime' type Props = { } & JSX.IntrinsicElements['div'] +/** +* comment +*/ export function Foo({ ...props }: Props, { slots }: SetupContext) { return (
{slots.default?.()}
) } @@ -42,6 +48,9 @@ import type { JSX } from 'vue/jsx-runtime' type Props = { } & JSX.IntrinsicElements['hr'] +/** +* comment +*/ export function Foo({ ...props }: Props) { return (
) } diff --git a/src/renderers/react.test.ts b/src/renderers/react.test.ts index 595f867..d8ebbc7 100644 --- a/src/renderers/react.test.ts +++ b/src/renderers/react.test.ts @@ -6,6 +6,7 @@ import { render } from './react.js' describe('render', () => { it('renders React component (full)', () => { const data: Data = { + comment: 'comment', tag: 'div', className: 'foo', attributes: { @@ -22,6 +23,7 @@ describe('render', () => { it('renders React component (minimal)', () => { const data: Data = { + comment: 'comment', tag: 'div', className: 'foo', attributes: {}, @@ -35,6 +37,7 @@ describe('render', () => { it('renders React component (void element)', () => { const data: Data = { + comment: 'comment', tag: 'hr', // hr is a void element and should not have children className: 'foo', attributes: {}, @@ -48,6 +51,7 @@ describe('render', () => { it('renders Hono component (full)', () => { const data: Data = { + comment: 'comment', tag: 'div', className: 'foo', attributes: { @@ -62,4 +66,4 @@ describe('render', () => { const result = render('component', data, isHono) expect(result).toMatchSnapshot() }) -}) \ No newline at end of file +}) diff --git a/src/renderers/react.ts b/src/renderers/react.ts index ad6f9ad..9ea79ea 100644 --- a/src/renderers/react.ts +++ b/src/renderers/react.ts @@ -31,7 +31,10 @@ function renderFunction(data: Data, isClass: boolean): string { '...props', ].join(', ') - return `export function ${pascalCase(data.className)}({ ${args} }: ${hasChildren(data.tag) ? `PropsWithChildren` : `Props`}) { + return `/** +* ${data.comment} +*/ +export function ${pascalCase(data.className)}({ ${args} }: ${hasChildren(data.tag) ? `PropsWithChildren` : `Props`}) { return (${renderTag(data, '{children}', isClass ? 'class' : 'className')}) }` } diff --git a/src/renderers/vue.test.ts b/src/renderers/vue.test.ts index f421e02..e8f5d5e 100644 --- a/src/renderers/vue.test.ts +++ b/src/renderers/vue.test.ts @@ -6,6 +6,7 @@ import { render } from './vue.js' describe('render', () => { it('renders Vue component (full)', () => { const data: Data = { + comment: 'comment', tag: 'div', className: 'foo', attributes: { @@ -22,6 +23,7 @@ describe('render', () => { it('renders Vue component (minimal)', () => { const data: Data = { + comment: 'comment', tag: 'div', className: 'foo', attributes: {}, @@ -35,6 +37,7 @@ describe('render', () => { it('renders Vue component (void element)', () => { const data: Data = { + comment: 'comment', tag: 'hr', // hr is a void element and should not have children className: 'foo', attributes: {}, diff --git a/src/renderers/vue.ts b/src/renderers/vue.ts index 370a196..8193a88 100644 --- a/src/renderers/vue.ts +++ b/src/renderers/vue.ts @@ -1,18 +1,3 @@ -// import type { SetupContext } from 'vue' -// import type { JSX } from 'vue/jsx-runtime' - -// type FComponentProps = { -// message?: string -// } & JSX.IntrinsicElements['img'] - -// export default function FComponent(props: FComponentProps, context: SetupContext) { -// return ( -// -// {context.slots.default?.()} -// -// ) -// } - import { attributeToCamelCase, pascalCase, propertyToCamelCase } from './_case.js' import { Data } from '../parser.js' import { renderTag, renderPropsInterface, hasChildren } from './_common.js' @@ -25,7 +10,10 @@ function renderFunction(data: Data): string { '...props', ].join(', ') - return `export function ${pascalCase(data.className)}({ ${args} }: Props${hasChildren(data.tag) ? ', { slots }: SetupContext' : ''}) { + return `/** +* ${data.comment} +*/ +export function ${pascalCase(data.className)}({ ${args} }: Props${hasChildren(data.tag) ? ', { slots }: SetupContext' : ''}) { return (${renderTag(data, '{slots.default?.()}', 'class')}) }` }