Skip to content

Add JSX support in type definition #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Dec 9, 2020
19 changes: 19 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,21 @@ var x = require('xastscript')
console.log(<music />)
```

For [TypeScript][], this can be done by setting `"jsx": "react"`,
`"jsxFactory": "x"`, and `"jsxFragmentFactory": "null"` in the compiler options.
For more details on configuring JSX for TypeScript, see the
[TypeScript JSX handbook page][].

TypeScript also lets you configure this in a script:

```tsx
/** @jsx x */
/** @jsxFrag null */
import * as x from 'xastscript'

console.log(<music />)
```

## Security

XML can be a dangerous language: don’t trust user-provided data.
Expand Down Expand Up @@ -327,3 +342,7 @@ abide by its terms.
[babel]: https://github.com/babel/babel

[babel-jsx]: https://github.com/babel/babel/tree/main/packages/babel-plugin-transform-react-jsx

[typescript]: https://www.typescriptlang.org

[typescript jsx handbook page]: https://www.typescriptlang.org/docs/handbook/jsx.html
64 changes: 58 additions & 6 deletions types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// TypeScript Version: 3.7
// TypeScript Version: 4.0

import {Element, Node, Root} from 'xast'
import * as xast from 'xast'

type Children = string | Node | number | Children[]
type Children = string | xast.Node | number | Children[]

type Primitive = null | undefined | string | number

Expand All @@ -17,15 +17,15 @@ type Attributes = Record<string, Primitive>
* @param name Qualified name. Case sensitive and can contain a namespace prefix (such as rdf:RDF).
* @param children (Lists of) child nodes. When strings are encountered, they are mapped to Text nodes.
*/
declare function xastscript(name: string, ...children: Children[]): Element
declare function xastscript(name: string, ...children: Children[]): xast.Element

/**
* Create XML trees in xast.
*
* @param name Qualified name. Case sensitive and can contain a namespace prefix (such as rdf:RDF).
* @param children (Lists of) child nodes. When strings are encountered, they are mapped to Text nodes.
*/
declare function xastscript(name: null, ...children: Children[]): Root
declare function xastscript(name: null, ...children: Children[]): xast.Root

/**
* Create XML trees in xast.
Expand All @@ -38,6 +38,58 @@ declare function xastscript(
name: string,
attributes?: Attributes,
...children: Children[]
): Element
): xast.Element

/**
* This unique symbol is declared to specify the key on which JSX children are passed, without conflicting
* with the Attributes type.
*/
declare const children: unique symbol

/**
* This namespace allows to use `xastscript` as a JSX implementation.
*
* This namespace is only used to support the use as JSX. It’s **not** intended for direct usage.
*/
declare namespace xastscript.JSX {
/**
* This defines the return value of JSX syntax.
*/
type Element = xast.Element | xast.Root

/**
* This disallows the use of functional components.
*/
type IntrinsicAttributes = never

/**
* This defines the prop types for known elements.
*
* For `xastscript` this defines any string may be used in combination with `xast` `Attributes`.
*
* This **must** be an interface.
*/
// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
interface IntrinsicElements {
[tagName: string]:
| Attributes
| {
/**
* The prop that matches `ElementChildrenAttribute` key defines the type of JSX children, defines the children type.
*/
[children]?: Children
}
}

/**
* The key of this interface defines as what prop children are passed.
*/
interface ElementChildrenAttribute {
/**
* Only the key matters, not the value.
*/
[children]?: never
}
}

export = xastscript
44 changes: 44 additions & 0 deletions types/test-jsx.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {Element, Root} from 'xast'
import * as x from 'xastscript'

const xmlns = 'http://www.sitemaps.org/schemas/sitemap/0.9'

let jsx: Element | Root
jsx = <urlset />
jsx = <urlset xmlns={xmlns} />
jsx = <urlset>string</urlset>
jsx = <urlset>{['string', 'string']}</urlset>
jsx = (
<urlset xmlns={xmlns}>
<child />
</urlset>
)
jsx = (
<urlset>
<loc />
string
</urlset>
)
jsx = (
<urlset>
<loc />
</urlset>
)
jsx = (
<urlset>
<loc />
<loc />
</urlset>
)
jsx = <urlset>{[<loc />, <loc />]}</urlset>
jsx = <urlset>{[]}</urlset>
jsx = <></>

jsx = <foo invalid={{}}></foo> // $ExpectError
jsx = <foo>{{invalid: 'child'}}</foo> // $ExpectError

const element: Element = <foo /> // $ExpectError
const root: Root = <></> // $ExpectError

declare function Bar(props?: Record<string, unknown>): Element
const bar = <Bar /> // $ExpectError
2 changes: 1 addition & 1 deletion types/test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import x = require('xastscript')
import * as x from 'xastscript'

x('urlset') // $ExpectType Element
x('urlset', 'string') // $ExpectType Element
Expand Down
3 changes: 3 additions & 0 deletions types/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"compilerOptions": {
"module": "commonjs",
"jsx": "react",
"jsxFactory": "x",
"jsxFragmentFactory": "null",
"lib": ["es2015"],
"noImplicitAny": true,
"noImplicitThis": true,
Expand Down