Skip to content
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