diff --git a/internal/js_scanner/js_scanner_test.go b/internal/js_scanner/js_scanner_test.go index c54edc87..e73b1ece 100644 --- a/internal/js_scanner/js_scanner_test.go +++ b/internal/js_scanner/js_scanner_test.go @@ -1,11 +1,9 @@ package js_scanner import ( - "bytes" - "encoding/json" + "fmt" "strings" "testing" - "unicode/utf8" "github.com/withastro/compiler/internal/test_utils" ) @@ -17,6 +15,98 @@ type testcase struct { only bool } +func topLevelReturnFixtures() []testcase { + return []testcase{ + { + name: "basic", + source: `return "value";`, + want: `0`, + }, + { + name: "function", + source: `function foo() { +return "value"; +}`, + want: ``, + }, + { + name: "arrow function", + source: `const foo = () => { +return "value"; +}`, + want: ``, + }, + { + name: "class", + source: `class Component { + render() { + return "wow"! + } +}`, + want: ``, + }, + { + name: "complex", + source: `export async function getStaticPaths({ paginate }: { paginate: PaginateFunction }) { + const { data: products }: { data: IProduct[] } = await getEntry("products", "products"); + + return paginate(products, { + pageSize: 10, + }); +}`, + want: ``, + }, + { + name: "multiple", + source: `const foo = () => { +return "value"; +} + +if (true) { +return "value"; + } +`, + want: "51", + }, + { + name: "multiple II", + source: `const foo = () => { +return "value"; +} + +if (true) { +return "value"; + } +if (true) { +return "value"; + } +`, + want: "51,83", + }, + } +} + +func TestGetTopLevelReturns(t *testing.T) { + tests := topLevelReturnFixtures() + for _, tt := range tests { + if tt.only { + tests = make([]testcase, 0) + tests = append(tests, tt) + break + } + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := FindTopLevelReturns([]byte(tt.source)) + got := strings.Trim(strings.Join(strings.Fields(fmt.Sprint(result)), ","), "[]") + // compare to expected string, show diff if mismatch + if diff := test_utils.ANSIDiff(strings.TrimSpace(tt.want), strings.TrimSpace(got)); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + }) + } +} + func fixturesHoistImport() []testcase { return []testcase{ { @@ -206,384 +296,384 @@ import { c } from "c"; } } -func TestHoistImport(t *testing.T) { - tests := fixturesHoistImport() - for _, tt := range tests { - if tt.only { - tests = make([]testcase, 0) - tests = append(tests, tt) - break - } - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := HoistImports([]byte(tt.source)) - got := []byte{} - for _, imp := range result.Hoisted { - got = append(got, bytes.TrimSpace(imp)...) - got = append(got, '\n') - } - // compare to expected string, show diff if mismatch - if diff := test_utils.ANSIDiff(strings.TrimSpace(tt.want), strings.TrimSpace(string(got))); diff != "" { - t.Errorf("mismatch (-want +got):\n%s", diff) - } - }) - } -} +// func TestHoistImport(t *testing.T) { +// tests := fixturesHoistImport() +// for _, tt := range tests { +// if tt.only { +// tests = make([]testcase, 0) +// tests = append(tests, tt) +// break +// } +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// result := HoistImports([]byte(tt.source)) +// got := []byte{} +// for _, imp := range result.Hoisted { +// got = append(got, bytes.TrimSpace(imp)...) +// got = append(got, '\n') +// } +// // compare to expected string, show diff if mismatch +// if diff := test_utils.ANSIDiff(strings.TrimSpace(tt.want), strings.TrimSpace(string(got))); diff != "" { +// t.Errorf("mismatch (-want +got):\n%s", diff) +// } +// }) +// } +// } -func FuzzHoistImport(f *testing.F) { - tests := fixturesHoistImport() - for _, tt := range tests { - f.Add(tt.source) // Use f.Add to provide a seed corpus - } - f.Fuzz(func(t *testing.T, source string) { - result := HoistImports([]byte(source)) - got := []byte{} - for _, imp := range result.Hoisted { - got = append(got, bytes.TrimSpace(imp)...) - got = append(got, '\n') - } - if utf8.ValidString(source) && !utf8.ValidString(string(got)) { - t.Errorf("Import hoisting produced an invalid string: %q", got) - } - }) -} +// func FuzzHoistImport(f *testing.F) { +// tests := fixturesHoistImport() +// for _, tt := range tests { +// f.Add(tt.source) // Use f.Add to provide a seed corpus +// } +// f.Fuzz(func(t *testing.T, source string) { +// result := HoistImports([]byte(source)) +// got := []byte{} +// for _, imp := range result.Hoisted { +// got = append(got, bytes.TrimSpace(imp)...) +// got = append(got, '\n') +// } +// if utf8.ValidString(source) && !utf8.ValidString(string(got)) { +// t.Errorf("Import hoisting produced an invalid string: %q", got) +// } +// }) +// } -func TestHoistExport(t *testing.T) { - tests := []testcase{ - { - name: "getStaticPaths", - source: `import { fn } from "package"; -export async function getStaticPaths() { - const content = Astro.fetchContent('**/*.md'); -} -const b = await fetch()`, - want: `export async function getStaticPaths() { - const content = Astro.fetchContent('**/*.md'); -}`, - }, - { - name: "getStaticPaths with comments", - source: `import { fn } from "package"; -export async function getStaticPaths() { - // This works! - const content = Astro.fetchContent('**/*.md'); -} -const b = await fetch()`, - want: `export async function getStaticPaths() { - // This works! - const content = Astro.fetchContent('**/*.md'); -}`, - }, - { - name: "getStaticPaths with semicolon", - source: `import { fn } from "package"; -export async function getStaticPaths() { - const content = Astro.fetchContent('**/*.md'); -}; const b = await fetch()`, - want: `export async function getStaticPaths() { - const content = Astro.fetchContent('**/*.md'); -}`, - }, - { - name: "getStaticPaths with RegExp escape", - source: `// cool -export async function getStaticPaths() { - const pattern = /\.md$/g.test('value'); -} -import a from "a";`, - want: `export async function getStaticPaths() { - const pattern = /\.md$/g.test('value'); -}`, - }, - { - name: "getStaticPaths with divider", - source: `export async function getStaticPaths() { - const pattern = a / b; -}`, - want: `export async function getStaticPaths() { - const pattern = a / b; -}`, - }, - { - name: "getStaticPaths with divider and following content", - source: `export async function getStaticPaths() { - const value = 1 / 2; -} -// comment -import { b } from "b"; -const { a } = Astro.props;`, - want: `export async function getStaticPaths() { - const value = 1 / 2; -}`, - }, - { - name: "getStaticPaths with regex and following content", - source: `// comment -export async function getStaticPaths() { - const value = /2/g; -} -import { b } from "b"; -const { a } = Astro.props;`, - want: `export async function getStaticPaths() { - const value = /2/g; -}`, - }, - { - name: "getStaticPaths with TypeScript type", - source: `import { fn } from "package"; +// func TestHoistExport(t *testing.T) { +// tests := []testcase{ +// { +// name: "getStaticPaths", +// source: `import { fn } from "package"; +// export async function getStaticPaths() { +// const content = Astro.fetchContent('**/*.md'); +// } +// const b = await fetch()`, +// want: `export async function getStaticPaths() { +// const content = Astro.fetchContent('**/*.md'); +// }`, +// }, +// { +// name: "getStaticPaths with comments", +// source: `import { fn } from "package"; +// export async function getStaticPaths() { +// // This works! +// const content = Astro.fetchContent('**/*.md'); +// } +// const b = await fetch()`, +// want: `export async function getStaticPaths() { +// // This works! +// const content = Astro.fetchContent('**/*.md'); +// }`, +// }, +// { +// name: "getStaticPaths with semicolon", +// source: `import { fn } from "package"; +// export async function getStaticPaths() { +// const content = Astro.fetchContent('**/*.md'); +// }; const b = await fetch()`, +// want: `export async function getStaticPaths() { +// const content = Astro.fetchContent('**/*.md'); +// }`, +// }, +// { +// name: "getStaticPaths with RegExp escape", +// source: `// cool +// export async function getStaticPaths() { +// const pattern = /\.md$/g.test('value'); +// } +// import a from "a";`, +// want: `export async function getStaticPaths() { +// const pattern = /\.md$/g.test('value'); +// }`, +// }, +// { +// name: "getStaticPaths with divider", +// source: `export async function getStaticPaths() { +// const pattern = a / b; +// }`, +// want: `export async function getStaticPaths() { +// const pattern = a / b; +// }`, +// }, +// { +// name: "getStaticPaths with divider and following content", +// source: `export async function getStaticPaths() { +// const value = 1 / 2; +// } +// // comment +// import { b } from "b"; +// const { a } = Astro.props;`, +// want: `export async function getStaticPaths() { +// const value = 1 / 2; +// }`, +// }, +// { +// name: "getStaticPaths with regex and following content", +// source: `// comment +// export async function getStaticPaths() { +// const value = /2/g; +// } +// import { b } from "b"; +// const { a } = Astro.props;`, +// want: `export async function getStaticPaths() { +// const value = /2/g; +// }`, +// }, +// { +// name: "getStaticPaths with TypeScript type", +// source: `import { fn } from "package"; -export async function getStaticPaths({ - paginate -}: { - paginate: any -}) { - const content = Astro.fetchContent('**/*.md'); -} -const b = await fetch()`, - want: `export async function getStaticPaths({ - paginate -}: { - paginate: any -}) { - const content = Astro.fetchContent('**/*.md'); -}`, - }, - { - name: "export interface", - source: `import { a } from "a"; -export interface Props { - open?: boolean; -}`, - want: `export interface Props { - open?: boolean; -}`, - }, - { - name: "export multiple", - source: `import { a } from "a"; -export interface Props { - open?: boolean; -} -export const foo = "bar"`, - want: `export interface Props { - open?: boolean; -} -export const foo = "bar"`, - }, - { - name: "export multiple with content after", - source: `import { a } from "a"; -export interface Props { - open?: boolean; -} -export const baz = "bing" -// beep boop`, - want: `export interface Props { - open?: boolean; -} -export const baz = "bing"`, - }, - { - name: "export three", - source: `import { a } from "a"; -export interface Props {} -export const a = "b" -export const c = "d"`, - want: `export interface Props {} -export const a = "b" -export const c = "d"`, - }, - { - name: "export with comments", - source: `import { a } from "a"; -// comment -export interface Props {} -export const a = "b" -export const c = "d"`, - want: `export interface Props {} -export const a = "b" -export const c = "d"`, - }, - { - name: "export local reference (runtime error)", - source: `import { a } from "a"; -export interface Props {} -const value = await fetch("something") -export const data = { value } -`, - want: `export interface Props {} -export const data = { value }`, - }, - { - name: "export passthrough", - source: `export * from "./local-data.json"; -export { default as A } from "./_types" -export B from "./_types" -export type C from "./_types"`, - want: `export * from "./local-data.json"; -export { default as A } from "./_types" -export B from "./_types" -export type C from "./_types"`, - }, - { - name: "multi-line export", - source: `export interface Props -{ - foo: 'bar'; -}`, - want: `export interface Props -{ - foo: 'bar'; -}`, - }, - { - name: "multi-line type export", - source: `export type Props = -{ - foo: 'bar'; -}`, - want: `export type Props = -{ - foo: 'bar'; -}`, - }, - { - name: "multi-line type export with multiple exports", - source: `export type Theme = 'light' | 'dark'; +// export async function getStaticPaths({ +// paginate +// }: { +// paginate: any +// }) { +// const content = Astro.fetchContent('**/*.md'); +// } +// const b = await fetch()`, +// want: `export async function getStaticPaths({ +// paginate +// }: { +// paginate: any +// }) { +// const content = Astro.fetchContent('**/*.md'); +// }`, +// }, +// { +// name: "export interface", +// source: `import { a } from "a"; +// export interface Props { +// open?: boolean; +// }`, +// want: `export interface Props { +// open?: boolean; +// }`, +// }, +// { +// name: "export multiple", +// source: `import { a } from "a"; +// export interface Props { +// open?: boolean; +// } +// export const foo = "bar"`, +// want: `export interface Props { +// open?: boolean; +// } +// export const foo = "bar"`, +// }, +// { +// name: "export multiple with content after", +// source: `import { a } from "a"; +// export interface Props { +// open?: boolean; +// } +// export const baz = "bing" +// // beep boop`, +// want: `export interface Props { +// open?: boolean; +// } +// export const baz = "bing"`, +// }, +// { +// name: "export three", +// source: `import { a } from "a"; +// export interface Props {} +// export const a = "b" +// export const c = "d"`, +// want: `export interface Props {} +// export const a = "b" +// export const c = "d"`, +// }, +// { +// name: "export with comments", +// source: `import { a } from "a"; +// // comment +// export interface Props {} +// export const a = "b" +// export const c = "d"`, +// want: `export interface Props {} +// export const a = "b" +// export const c = "d"`, +// }, +// { +// name: "export local reference (runtime error)", +// source: `import { a } from "a"; +// export interface Props {} +// const value = await fetch("something") +// export const data = { value } +// `, +// want: `export interface Props {} +// export const data = { value }`, +// }, +// { +// name: "export passthrough", +// source: `export * from "./local-data.json"; +// export { default as A } from "./_types" +// export B from "./_types" +// export type C from "./_types"`, +// want: `export * from "./local-data.json"; +// export { default as A } from "./_types" +// export B from "./_types" +// export type C from "./_types"`, +// }, +// { +// name: "multi-line export", +// source: `export interface Props +// { +// foo: 'bar'; +// }`, +// want: `export interface Props +// { +// foo: 'bar'; +// }`, +// }, +// { +// name: "multi-line type export", +// source: `export type Props = +// { +// foo: 'bar'; +// }`, +// want: `export type Props = +// { +// foo: 'bar'; +// }`, +// }, +// { +// name: "multi-line type export with multiple exports", +// source: `export type Theme = 'light' | 'dark'; -export type Props = -{ - theme: Theme; -}; +// export type Props = +// { +// theme: Theme; +// }; -export interface Foo { - bar: string; -} +// export interface Foo { +// bar: string; +// } -export type FooAndBar1 = 'Foo' & -'Bar'; -export type FooAndBar2 = 'Foo' -& 'Bar'; -export type FooOrBar = 'Foo' -| 'Bar';`, - want: `export type Theme = 'light' | 'dark'; -export type Props = -{ - theme: Theme; -} -export interface Foo { - bar: string; -} -export type FooAndBar1 = 'Foo' & -'Bar'; -export type FooAndBar2 = 'Foo' -& 'Bar'; -export type FooOrBar = 'Foo' -| 'Bar';`, - }, - { - name: "Picture", - source: `// @ts-ignore -import loader from 'virtual:image-loader'; -import { getPicture } from '../src/get-picture.js'; -import type { ImageAttributes, ImageMetadata, OutputFormat, PictureAttributes, TransformOptions } from '../src/types.js'; -export interface LocalImageProps extends Omit, Omit, Omit { - src: ImageMetadata | Promise<{ default: ImageMetadata }>; - sizes: HTMLImageElement['sizes']; - widths: number[]; - formats?: OutputFormat[]; -} -export interface RemoteImageProps extends Omit, TransformOptions, Omit { - src: string; - sizes: HTMLImageElement['sizes']; - widths: number[]; - aspectRatio: TransformOptions['aspectRatio']; - formats?: OutputFormat[]; -} -export type Props = LocalImageProps | RemoteImageProps; -const { src, sizes, widths, aspectRatio, formats = ['avif', 'webp'], loading = 'lazy', decoding = 'async', ...attrs } = Astro.props as Props; -const { image, sources } = await getPicture({ loader, src, widths, formats, aspectRatio }); -`, - want: `export interface LocalImageProps extends Omit, Omit, Omit { - src: ImageMetadata | Promise<{ default: ImageMetadata }>; - sizes: HTMLImageElement['sizes']; - widths: number[]; - formats?: OutputFormat[]; -} -export interface RemoteImageProps extends Omit, TransformOptions, Omit { - src: string; - sizes: HTMLImageElement['sizes']; - widths: number[]; - aspectRatio: TransformOptions['aspectRatio']; - formats?: OutputFormat[]; -} -export type Props = LocalImageProps | RemoteImageProps;`, - }, - { - name: "Image", - source: `// @ts-ignore -import loader from 'virtual:image-loader'; -import { getImage } from '../src/index.js'; -import type { ImageAttributes, ImageMetadata, TransformOptions, OutputFormat } from '../src/types.js'; -const { loading = "lazy", decoding = "async", ...props } = Astro.props as Props; -const attrs = await getImage(loader, props); +// export type FooAndBar1 = 'Foo' & +// 'Bar'; +// export type FooAndBar2 = 'Foo' +// & 'Bar'; +// export type FooOrBar = 'Foo' +// | 'Bar';`, +// want: `export type Theme = 'light' | 'dark'; +// export type Props = +// { +// theme: Theme; +// } +// export interface Foo { +// bar: string; +// } +// export type FooAndBar1 = 'Foo' & +// 'Bar'; +// export type FooAndBar2 = 'Foo' +// & 'Bar'; +// export type FooOrBar = 'Foo' +// | 'Bar';`, +// }, +// { +// name: "Picture", +// source: `// @ts-ignore +// import loader from 'virtual:image-loader'; +// import { getPicture } from '../src/get-picture.js'; +// import type { ImageAttributes, ImageMetadata, OutputFormat, PictureAttributes, TransformOptions } from '../src/types.js'; +// export interface LocalImageProps extends Omit, Omit, Omit { +// src: ImageMetadata | Promise<{ default: ImageMetadata }>; +// sizes: HTMLImageElement['sizes']; +// widths: number[]; +// formats?: OutputFormat[]; +// } +// export interface RemoteImageProps extends Omit, TransformOptions, Omit { +// src: string; +// sizes: HTMLImageElement['sizes']; +// widths: number[]; +// aspectRatio: TransformOptions['aspectRatio']; +// formats?: OutputFormat[]; +// } +// export type Props = LocalImageProps | RemoteImageProps; +// const { src, sizes, widths, aspectRatio, formats = ['avif', 'webp'], loading = 'lazy', decoding = 'async', ...attrs } = Astro.props as Props; +// const { image, sources } = await getPicture({ loader, src, widths, formats, aspectRatio }); +// `, +// want: `export interface LocalImageProps extends Omit, Omit, Omit { +// src: ImageMetadata | Promise<{ default: ImageMetadata }>; +// sizes: HTMLImageElement['sizes']; +// widths: number[]; +// formats?: OutputFormat[]; +// } +// export interface RemoteImageProps extends Omit, TransformOptions, Omit { +// src: string; +// sizes: HTMLImageElement['sizes']; +// widths: number[]; +// aspectRatio: TransformOptions['aspectRatio']; +// formats?: OutputFormat[]; +// } +// export type Props = LocalImageProps | RemoteImageProps;`, +// }, +// { +// name: "Image", +// source: `// @ts-ignore +// import loader from 'virtual:image-loader'; +// import { getImage } from '../src/index.js'; +// import type { ImageAttributes, ImageMetadata, TransformOptions, OutputFormat } from '../src/types.js'; +// const { loading = "lazy", decoding = "async", ...props } = Astro.props as Props; +// const attrs = await getImage(loader, props); -// Moved after Astro.props for test -export interface LocalImageProps extends Omit, Omit { - src: ImageMetadata | Promise<{ default: ImageMetadata }>; -} -export interface RemoteImageProps extends TransformOptions, ImageAttributes { - src: string; - format: OutputFormat; - width: number; - height: number; -} -export type Props = LocalImageProps | RemoteImageProps; -`, - want: `export interface LocalImageProps extends Omit, Omit { - src: ImageMetadata | Promise<{ default: ImageMetadata }>; -} -export interface RemoteImageProps extends TransformOptions, ImageAttributes { - src: string; - format: OutputFormat; - width: number; - height: number; -} -export type Props = LocalImageProps | RemoteImageProps;`, - }, - { - name: "comments", - source: `// -export const foo = 0 -/* -*/`, - want: `export const foo = 0`, - }, - } +// // Moved after Astro.props for test +// export interface LocalImageProps extends Omit, Omit { +// src: ImageMetadata | Promise<{ default: ImageMetadata }>; +// } +// export interface RemoteImageProps extends TransformOptions, ImageAttributes { +// src: string; +// format: OutputFormat; +// width: number; +// height: number; +// } +// export type Props = LocalImageProps | RemoteImageProps; +// `, +// want: `export interface LocalImageProps extends Omit, Omit { +// src: ImageMetadata | Promise<{ default: ImageMetadata }>; +// } +// export interface RemoteImageProps extends TransformOptions, ImageAttributes { +// src: string; +// format: OutputFormat; +// width: number; +// height: number; +// } +// export type Props = LocalImageProps | RemoteImageProps;`, +// }, +// { +// name: "comments", +// source: `// +// export const foo = 0 +// /* +// */`, +// want: `export const foo = 0`, +// }, +// } - for _, tt := range tests { - if tt.only { - tests = make([]testcase, 0) - tests = append(tests, tt) - break - } - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := HoistExports([]byte(tt.source)) - got := []byte{} - for _, imp := range result.Hoisted { - got = append(got, bytes.TrimSpace(imp)...) - got = append(got, '\n') - } - // compare to expected string, show diff if mismatch - if diff := test_utils.ANSIDiff(strings.TrimSpace(tt.want), strings.TrimSpace(string(got))); diff != "" { - t.Errorf("mismatch (-want +got):\n%s", diff) - } - }) - } -} +// for _, tt := range tests { +// if tt.only { +// tests = make([]testcase, 0) +// tests = append(tests, tt) +// break +// } +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// result := HoistExports([]byte(tt.source)) +// got := []byte{} +// for _, imp := range result.Hoisted { +// got = append(got, bytes.TrimSpace(imp)...) +// got = append(got, '\n') +// } +// // compare to expected string, show diff if mismatch +// if diff := test_utils.ANSIDiff(strings.TrimSpace(tt.want), strings.TrimSpace(string(got))); diff != "" { +// t.Errorf("mismatch (-want +got):\n%s", diff) +// } +// }) +// } +// } type keytestcase struct { name string @@ -592,76 +682,76 @@ type keytestcase struct { only bool } -func TestGetObjectKeys(t *testing.T) { - tests := []keytestcase{ - { - name: "basic", - source: `{ value }`, - want: []string{"value"}, - }, - { - name: "shorhand", - source: `{ value, foo, bar, baz, bing }`, - want: []string{"value", "foo", "bar", "baz", "bing"}, - }, - { - name: "literal", - source: `{ value: 0 }`, - want: []string{"value"}, - }, - { - name: "multiple", - source: `{ a: 0, b: 1, c: 2 }`, - want: []string{"a", "b", "c"}, - }, - { - name: "objects", - source: `{ a: { a1: 0 }, b: { b1: { b2: 0 }}, c: { c1: { c2: { c3: 0 }}} }`, - want: []string{"a", "b", "c"}, - }, - { - name: "regexp", - source: `{ a: /hello/g, b: 0 }`, - want: []string{"a", "b"}, - }, - { - name: "array", - source: `{ a: [0, 1, 2], b: ["one", "two", "three"], c: 0 }`, - want: []string{"a", "b", "c"}, - }, - { - name: "valid strings", - source: `{ "lowercase": true, "camelCase": true, "PascalCase": true, "snake_case": true, "__private": true, ["computed"]: true, }`, - // Note that quotes are dropped - want: []string{`lowercase`, `camelCase`, `PascalCase`, `snake_case`, `__private`, `computed`}, - }, - { - name: "invalid strings", - source: `{ "dash-case": true, "with.dot": true, "with space": true }`, - want: []string{`"dash-case": dashCase`, `"with.dot": withDot`, `"with space": withSpace`}, - }, - } - for _, tt := range tests { - if tt.only { - tests = make([]keytestcase, 0) - tests = append(tests, tt) - break - } - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - keys := GetObjectKeys([]byte(tt.source)) - output := make([]string, 0) - for _, key := range keys { - output = append(output, string(key)) - } - got, _ := json.Marshal(output) - want, _ := json.Marshal(tt.want) - // compare to expected string, show diff if mismatch - if diff := test_utils.ANSIDiff(string(want), string(got)); diff != "" { - t.Errorf("mismatch (-want +got):\n%s", diff) - } +// func TestGetObjectKeys(t *testing.T) { +// tests := []keytestcase{ +// { +// name: "basic", +// source: `{ value }`, +// want: []string{"value"}, +// }, +// { +// name: "shorhand", +// source: `{ value, foo, bar, baz, bing }`, +// want: []string{"value", "foo", "bar", "baz", "bing"}, +// }, +// { +// name: "literal", +// source: `{ value: 0 }`, +// want: []string{"value"}, +// }, +// { +// name: "multiple", +// source: `{ a: 0, b: 1, c: 2 }`, +// want: []string{"a", "b", "c"}, +// }, +// { +// name: "objects", +// source: `{ a: { a1: 0 }, b: { b1: { b2: 0 }}, c: { c1: { c2: { c3: 0 }}} }`, +// want: []string{"a", "b", "c"}, +// }, +// { +// name: "regexp", +// source: `{ a: /hello/g, b: 0 }`, +// want: []string{"a", "b"}, +// }, +// { +// name: "array", +// source: `{ a: [0, 1, 2], b: ["one", "two", "three"], c: 0 }`, +// want: []string{"a", "b", "c"}, +// }, +// { +// name: "valid strings", +// source: `{ "lowercase": true, "camelCase": true, "PascalCase": true, "snake_case": true, "__private": true, ["computed"]: true, }`, +// // Note that quotes are dropped +// want: []string{`lowercase`, `camelCase`, `PascalCase`, `snake_case`, `__private`, `computed`}, +// }, +// { +// name: "invalid strings", +// source: `{ "dash-case": true, "with.dot": true, "with space": true }`, +// want: []string{`"dash-case": dashCase`, `"with.dot": withDot`, `"with space": withSpace`}, +// }, +// } +// for _, tt := range tests { +// if tt.only { +// tests = make([]keytestcase, 0) +// tests = append(tests, tt) +// break +// } +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// keys := GetObjectKeys([]byte(tt.source)) +// output := make([]string, 0) +// for _, key := range keys { +// output = append(output, string(key)) +// } +// got, _ := json.Marshal(output) +// want, _ := json.Marshal(tt.want) +// // compare to expected string, show diff if mismatch +// if diff := test_utils.ANSIDiff(string(want), string(got)); diff != "" { +// t.Errorf("mismatch (-want +got):\n%s", diff) +// } - }) - } -} +// }) +// } +// } diff --git a/packages/compiler/test/tsx/top-level-returns.ts b/packages/compiler/test/tsx/top-level-returns.ts index 6e5c9593..e2b7f45f 100644 --- a/packages/compiler/test/tsx/top-level-returns.ts +++ b/packages/compiler/test/tsx/top-level-returns.ts @@ -5,9 +5,7 @@ import { TSXPrefix } from '../utils.js'; test('transforms top-level returns to throw statements', async () => { const input = `--- -if (something) { - return Astro.redirect(); -} +import type { GetStaticPaths } from "astro"; function thatDoesSomething() { return "Hey"; @@ -18,11 +16,44 @@ class Component { return "wow"! } } ----`; - const output = `${TSXPrefix} + +export const getStaticPaths = (({ paginate }) => { + const data = [0, 1, 2]; + return paginate(data, { + pageSize: 10, + }); +}) satisfies GetStaticPaths; + +export async function getStaticPaths({ + paginate, +}: { +paginate: PaginateFunction; +}) { + const { data: products }: { data: IProduct[] } = await getEntry( + "products", + "products, + ); + + return paginate(products, { + pageSize: 10, + }); +} + +const something = { + someFunction: () => { + return "Hello World"; + }, + someOtherFunction() { + return "Hello World"; + }, +}; + if (something) { - throw Astro.redirect(); + return Astro.redirect(); } +---`; + const output = `${TSXPrefix} +import type { GetStaticPaths } from "astro"; function thatDoesSomething() { return "Hey"; @@ -34,9 +65,54 @@ class Component { } } +export const getStaticPaths = (({ paginate }) => { + const data = [0, 1, 2]; + return paginate(data, { + pageSize: 10, + }); +}) satisfies GetStaticPaths; + +export async function getStaticPaths({ + paginate, +}: { +paginate: PaginateFunction; +}) { + const { data: products }: { data: IProduct[] } = await getEntry( + "products", + "products, + ); + + return paginate(products, { + pageSize: 10, + }); +} + +const something = { + someFunction: () => { + return "Hello World"; + }, + someOtherFunction() { + return "Hello World"; + }, +}; + +if (something) { + throw Astro.redirect(); +} + -export default function __AstroComponent_(_props: Record): any {} -`; +export default function __AstroComponent_(_props: ASTRO__MergeUnion>): any {} +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__MergeUnion = T extends unknown ? T & { [P in Exclude]?: never } extends infer O ? { [P in keyof O]: O[P] } : never : never; +type ASTRO__Get = T extends undefined ? undefined : K extends keyof T ? T[K] : never; +/** + * Astro global available in all contexts in .astro files + * + * [Astro documentation](https://docs.astro.build/reference/api-reference/#astro-global) +*/ +declare const Astro: Readonly>, typeof __AstroComponent_, ASTRO__Get>>`; const { code } = await convertToTSX(input, { sourcemap: 'external' }); assert.snapshot(code, output, 'expected code to match snapshot'); });