Skip to content

Commit

Permalink
Add TSDoc support (#55)
Browse files Browse the repository at this point in the history
* Add comment support

* Update docs
  • Loading branch information
typicode committed Jun 12, 2024
1 parent edd5ad7 commit 8460ef8
Show file tree
Hide file tree
Showing 9 changed files with 55 additions and 22 deletions.
2 changes: 1 addition & 1 deletion docs/src/content/docs/component.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
4 changes: 3 additions & 1 deletion src/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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']) },
Expand Down
16 changes: 14 additions & 2 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,31 @@ import {
Element,
Middleware,
middleware,
COMMENT,
RULESET,
// @ts-ignore
SCOPE,
serialize,
} from 'stylis'

export interface Data {
comment: string
className: string // foo
tag: string // div
attributes: Record<string, Set<string>> // data-foo: ['bar', 'baz']
booleanAttributes: Set<string> // data-foo, data-bar
properties: Set<string> // --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('.')
Expand All @@ -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
Expand All @@ -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 }) => {
Expand Down Expand Up @@ -81,6 +92,7 @@ export function parse(css: string): Data[] {
const { tag, className } = parseScopeSelector(prop)

const data: Data = {
comment: getComment(element),
tag,
className,
attributes: {},
Expand Down
12 changes: 12 additions & 0 deletions src/renderers/__snapshots__/react.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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<Props>) {
return (<div {...props} data-attr={attr} data-attr-foo-bar={attrFooBar} data-is-foo={isFoo} style={{ '--prop-foo': propFoo, '--prop-bar': propBar }} class="foo" >{children}</div>)
}
Expand All @@ -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<Props>) {
return (<div {...props} data-attr={attr} data-attr-foo-bar={attrFooBar} data-is-foo={isFoo} style={{ '--prop-foo': propFoo, '--prop-bar': propBar }} className="foo" >{children}</div>)
}
Expand All @@ -36,6 +42,9 @@ import type { JSX, PropsWithChildren } from 'react'
type Props = { } & JSX.IntrinsicElements['div']
/**
* comment
*/
export function Foo({ children, ...props }: PropsWithChildren<Props>) {
return (<div {...props} className="foo" >{children}</div>)
}
Expand All @@ -50,6 +59,9 @@ import type { JSX } from 'react'
type Props = { } & JSX.IntrinsicElements['hr']
/**
* comment
*/
export function Foo({ ...props }: Props) {
return (<hr {...props} className="foo" />)
}
Expand Down
9 changes: 9 additions & 0 deletions src/renderers/__snapshots__/vue.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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 (<div {...props} data-attr={attr} data-attr-foo-bar={attrFooBar} data-is-foo={isFoo} style={{ '--prop-foo': propFoo, '--prop-bar': propBar }} class="foo" >{slots.default?.()}</div>)
}
Expand All @@ -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 (<div {...props} class="foo" >{slots.default?.()}</div>)
}
Expand All @@ -42,6 +48,9 @@ import type { JSX } from 'vue/jsx-runtime'
type Props = { } & JSX.IntrinsicElements['hr']
/**
* comment
*/
export function Foo({ ...props }: Props) {
return (<hr {...props} class="foo" />)
}
Expand Down
6 changes: 5 additions & 1 deletion src/renderers/react.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -22,6 +23,7 @@ describe('render', () => {

it('renders React component (minimal)', () => {
const data: Data = {
comment: 'comment',
tag: 'div',
className: 'foo',
attributes: {},
Expand All @@ -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: {},
Expand All @@ -48,6 +51,7 @@ describe('render', () => {

it('renders Hono component (full)', () => {
const data: Data = {
comment: 'comment',
tag: 'div',
className: 'foo',
attributes: {
Expand All @@ -62,4 +66,4 @@ describe('render', () => {
const result = render('component', data, isHono)
expect(result).toMatchSnapshot()
})
})
})
5 changes: 4 additions & 1 deletion src/renderers/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>` : `Props`}) {
return `/**
* ${data.comment}
*/
export function ${pascalCase(data.className)}({ ${args} }: ${hasChildren(data.tag) ? `PropsWithChildren<Props>` : `Props`}) {
return (${renderTag(data, '{children}', isClass ? 'class' : 'className')})
}`
}
Expand Down
3 changes: 3 additions & 0 deletions src/renderers/vue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -22,6 +23,7 @@ describe('render', () => {

it('renders Vue component (minimal)', () => {
const data: Data = {
comment: 'comment',
tag: 'div',
className: 'foo',
attributes: {},
Expand All @@ -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: {},
Expand Down
20 changes: 4 additions & 16 deletions src/renderers/vue.ts
Original file line number Diff line number Diff line change
@@ -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 (
// <img {...props} class="foo">
// {context.slots.default?.()}
// </img>
// )
// }

import { attributeToCamelCase, pascalCase, propertyToCamelCase } from './_case.js'
import { Data } from '../parser.js'
import { renderTag, renderPropsInterface, hasChildren } from './_common.js'
Expand All @@ -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')})
}`
}
Expand Down

0 comments on commit 8460ef8

Please sign in to comment.