diff --git a/.changeset/grumpy-spoons-rescue.md b/.changeset/grumpy-spoons-rescue.md new file mode 100644 index 000000000..52fd44c0c --- /dev/null +++ b/.changeset/grumpy-spoons-rescue.md @@ -0,0 +1,5 @@ +--- +'@astrojs/compiler': minor +--- + +Adds ability for TSX output to automatically infer `Astro.props` and `Astro.params` when `getStaticPaths` is used diff --git a/internal/js_scanner/js_scanner.go b/internal/js_scanner/js_scanner.go index 1c03f4521..1e1a6fbad 100644 --- a/internal/js_scanner/js_scanner.go +++ b/internal/js_scanner/js_scanner.go @@ -238,6 +238,21 @@ func HoistImports(source []byte) HoistedScripts { return HoistedScripts{Hoisted: imports, HoistedLocs: importLocs, Body: body, BodyLocs: bodyLocs} } +func HasGetStaticPaths(source []byte) bool { + ident := []byte("getStaticPaths") + if !bytes.Contains(source, ident) { + return false + } + + exports := HoistExports(source) + for _, statement := range exports.Hoisted { + if bytes.Contains(statement, ident) { + return true + } + } + return false +} + type Props struct { Ident string Statement string diff --git a/internal/printer/print-to-tsx.go b/internal/printer/print-to-tsx.go index 155d7e741..5d5001dc9 100644 --- a/internal/printer/print-to-tsx.go +++ b/internal/printer/print-to-tsx.go @@ -97,7 +97,9 @@ func getTextType(n *astro.Node) TextType { func renderTsx(p *printer, n *Node) { // Root of the document, print all children if n.Type == DocumentNode { - props := js_scanner.GetPropsType([]byte(p.sourcetext)) + source := []byte(p.sourcetext) + props := js_scanner.GetPropsType(source) + hasGetStaticPaths := js_scanner.HasGetStaticPaths(source) hasChildren := false for c := n.FirstChild; c != nil; c = c.NextSibling { // This checks for the first node that comes *after* the frontmatter @@ -134,15 +136,34 @@ func renderTsx(p *printer, n *Node) { p.print("\n") } componentName := getTSXComponentName(p.opts.Filename) + propsIdent := props.Ident + paramsIdent := "" + if hasGetStaticPaths { + paramsIdent = "ASTRO__Get" + if propsIdent == "Record" { + propsIdent = "ASTRO__Get" + } + } - p.print(fmt.Sprintf("export default function %s%s(_props: %s%s): any {}\n", componentName, props.Statement, props.Ident, props.Generics)) - if props.Ident != "Record" { + p.print(fmt.Sprintf("export default function %s%s(_props: %s%s): any {}\n", componentName, props.Statement, propsIdent, props.Generics)) + if hasGetStaticPaths { + p.printf(`type ASTRO__ArrayElement = ArrayType extends readonly (infer ElementType)[] ? ElementType : never; +type ASTRO__Flattened = T extends Array ? ASTRO__Flattened : T; +type ASTRO__InferredGetStaticPath = ASTRO__Flattened>>>; +type ASTRO__Get = T extends undefined ? undefined : K extends keyof T ? T[K] : never;%s`, "\n") + } + + if propsIdent != "Record" { p.printf(`/** * Astro global available in all contexts in .astro files * * [Astro documentation](https://docs.astro.build/reference/api-reference/#astro-global) */ -declare const Astro: Readonly>`, props.Ident, componentName) +declare const Astro: Readonly>") } return } diff --git a/packages/compiler/test/tsx/props-and-getStaticPaths.ts b/packages/compiler/test/tsx/props-and-getStaticPaths.ts new file mode 100644 index 000000000..34ca32715 --- /dev/null +++ b/packages/compiler/test/tsx/props-and-getStaticPaths.ts @@ -0,0 +1,79 @@ +import { convertToTSX } from '@astrojs/compiler'; +import { test } from 'uvu'; +import * as assert from 'uvu/assert'; + +function getPrefix({ + props = `ASTRO__Get`, + component = '__AstroComponent_', + params = `ASTRO__Get`, +}: { + props?: string; + component?: string; + params?: string; +} = {}) { + return `/** + * Astro global available in all contexts in .astro files + * + * [Astro documentation](https://docs.astro.build/reference/api-reference/#astro-global) +*/ +declare const Astro: Readonly>`; +} + +function getSuffix() { + return `type ASTRO__ArrayElement = ArrayType extends readonly (infer ElementType)[] ? ElementType : never; +type ASTRO__Flattened = T extends Array ? ASTRO__Flattened : T; +type ASTRO__InferredGetStaticPath = ASTRO__Flattened>>>; +type ASTRO__Get = T extends undefined ? undefined : K extends keyof T ? T[K] : never;`; +} + +test('explicit props definition', async () => { + const input = `--- +interface Props {}; +export function getStaticPaths() { + return {}; +} +--- + +
`; + const output = + '\n' + + `interface Props {}; +export function getStaticPaths() { + return {}; +} + +""; +
+
+export default function __AstroComponent_(_props: Props): any {} +${getSuffix()} +${getPrefix({ props: 'Props' })}`; + const { code } = await convertToTSX(input, { sourcemap: 'external' }); + assert.snapshot(code, output, `expected code to match snapshot`); +}); + +test('inferred props', async () => { + const input = `--- +export function getStaticPaths() { + return {}; +} +--- + +
`; + const output = + '\n' + + `export function getStaticPaths() { + return {}; +} + +""; +
+
+export default function __AstroComponent_(_props: ASTRO__Get): any {} +${getSuffix()} +${getPrefix()}`; + const { code } = await convertToTSX(input, { sourcemap: 'external' }); + assert.snapshot(code, output, `expected code to match snapshot`); +}); + +test.run(); diff --git a/packages/compiler/test/tsx/props-and-staticPaths.ts b/packages/compiler/test/tsx/props-and-staticPaths.ts deleted file mode 100644 index dd9cbb818..000000000 --- a/packages/compiler/test/tsx/props-and-staticPaths.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { convertToTSX } from '@astrojs/compiler'; -import { test } from 'uvu'; -import * as assert from 'uvu/assert'; - -const PREFIX = (component: string = '__AstroComponent_') => `/** - * Astro global available in all contexts in .astro files - * - * [Astro documentation](https://docs.astro.build/reference/api-reference/#astro-global) -*/ -declare const Astro: Readonly>`; - -test('no props', async () => { - const input = `--- -type Props = Record; -export function getStaticProps() { - return {}; -} ---- - -
`; - const output = - '\n' + - `type Props = Record; -export function getStaticProps() { - return {}; -} - -""; -
-
-export default function __AstroComponent_(_props: Props): any {} -${PREFIX()}`; - const { code } = await convertToTSX(input, { sourcemap: 'external' }); - assert.snapshot(code, output, `expected code to match snapshot`); -}); - -test.run();