From 011268d6e8d36779bf317de3750557d780694737 Mon Sep 17 00:00:00 2001 From: Jason Siefken Date: Thu, 11 Apr 2024 16:07:08 -0400 Subject: [PATCH] Fix invalid typescript generation for empy enums (#23) * Add e2e tests (with purposeful error) * Allow empty enums --- .github/workflows/on-pull-request.yml | 11 +++-- .gitignore | 1 + Cargo.toml | 7 +-- src/lib.rs | 2 + test.sh | 6 ++- tests-e2e/README.md | 6 +++ tests-e2e/build_all.sh | 23 ++++++++++ tests-e2e/reference_output/compare_output.sh | 45 +++++++++++++++++++ tests-e2e/reference_output/test1/test1.d.ts | 11 +++++ tests-e2e/reference_output/test2/test2.d.ts | 13 ++++++ tests-e2e/test1/Cargo.toml | 21 +++++++++ tests-e2e/test1/entry_point.rs | 15 +++++++ tests-e2e/test2/Cargo.toml | 21 +++++++++ tests-e2e/test2/entry_point.rs | 19 ++++++++ tests/enum.rs | 12 +++++ tsify-next-macros/Cargo.toml | 2 +- tsify-next-macros/src/decl.rs | 3 ++ tsify-next-macros/src/derive.rs | 1 + .../src/typescript/ts_type_display.rs | 34 +++++++------- 19 files changed, 230 insertions(+), 23 deletions(-) mode change 100644 => 100755 test.sh create mode 100644 tests-e2e/README.md create mode 100755 tests-e2e/build_all.sh create mode 100755 tests-e2e/reference_output/compare_output.sh create mode 100644 tests-e2e/reference_output/test1/test1.d.ts create mode 100644 tests-e2e/reference_output/test2/test2.d.ts create mode 100644 tests-e2e/test1/Cargo.toml create mode 100644 tests-e2e/test1/entry_point.rs create mode 100644 tests-e2e/test2/Cargo.toml create mode 100644 tests-e2e/test2/entry_point.rs diff --git a/.github/workflows/on-pull-request.yml b/.github/workflows/on-pull-request.yml index 9349faf..9da0d7b 100644 --- a/.github/workflows/on-pull-request.yml +++ b/.github/workflows/on-pull-request.yml @@ -47,13 +47,18 @@ jobs: override: true components: rustfmt + - name: Install wasm-pack + uses: jetli/wasm-pack-action@v0.4.0 + with: + # Optional version of wasm-pack to install(eg. 'v0.9.1', 'latest') + version: "latest" + - name: Add cargo-expand run: cargo install cargo-expand + # Run the ./test.sh script - name: Test - uses: actions-rs/cargo@v1 - with: - command: test + run: ./test.sh lint: name: Lint runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index c640ca5..c66efd2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target Cargo.lock .vscode +tests-e2e/*/pkg \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index a6731d7..4d8aecb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tsify-next" -version = "0.5.0" +version = "0.5.1" edition = "2021" authors = [ "Madono Haru ", @@ -14,7 +14,7 @@ keywords = ["wasm", "wasm-bindgen", "typescript"] categories = ["wasm"] [dependencies] -tsify-next-macros = { path = "tsify-next-macros", version = "^0.5" } +tsify-next-macros = { path = "tsify-next-macros", version = "0.5.1" } wasm-bindgen = { version = "0.2.86", optional = true } serde = { version = "1.0", optional = true } serde_json = { version = "1.0", optional = true } @@ -50,4 +50,5 @@ json = [ ] [workspace] -members = ["tsify-next-macros"] +members = ["tsify-next-macros", "tests-e2e/*"] +exclude = ["tests-e2e/reference_output"] diff --git a/src/lib.rs b/src/lib.rs index 08da67a..6c1a50a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,8 @@ pub struct SerializationConfig { pub large_number_types_as_bigints: bool, } +/// `Tsify` is a trait that allows you to convert a type to and from JavaScript. +/// Can be implemented manually if you need to customize the serialization or deserialization. pub trait Tsify { #[cfg(feature = "wasm-bindgen")] type JsType: JsCast; diff --git a/test.sh b/test.sh old mode 100644 new mode 100755 index 6e8d586..c5c562a --- a/test.sh +++ b/test.sh @@ -5,4 +5,8 @@ set -ex cargo test --all cargo test --all -F js wasm-pack test --node -wasm-pack test --node -F js \ No newline at end of file +wasm-pack test --node -F js + +# Test the end-to-end tests +./tests-e2e/build_all.sh +./tests-e2e/reference_output/compare_output.sh \ No newline at end of file diff --git a/tests-e2e/README.md b/tests-e2e/README.md new file mode 100644 index 0000000..377560f --- /dev/null +++ b/tests-e2e/README.md @@ -0,0 +1,6 @@ +# tests-e2e + +These tests test the actual `.d.ts` file output by `wasm-pack`. When `wasm-pack build` is run +on a project in one of the sub-folders, a `pkg/` directory will be created. Running `./reference_output/compare_output.sh` +will compare the reference output (stored in a directory match the test name) to that generated in the `pkg/` directory +output by `wasm-pack`. diff --git a/tests-e2e/build_all.sh b/tests-e2e/build_all.sh new file mode 100755 index 0000000..e654ade --- /dev/null +++ b/tests-e2e/build_all.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Define the root directory for the search +ROOT_DIR="tests-e2e" + +# Find all Cargo.toml files in the root directory and its direct subdirectories +FILES=$(find "$ROOT_DIR" -maxdepth 2 -name Cargo.toml) + +for FILE in $FILES; do + # Get the directory of the file + DIR=$(dirname "$FILE") + # Push the directory onto the stack and change to it + pushd "$DIR" > /dev/null || exit + + echo "" + echo "Building in $DIR" + echo "" + + # Run wasm-pack build + wasm-pack build + # Pop the directory from the stack and change back to the original directory + popd > /dev/null || exit +done \ No newline at end of file diff --git a/tests-e2e/reference_output/compare_output.sh b/tests-e2e/reference_output/compare_output.sh new file mode 100755 index 0000000..41da919 --- /dev/null +++ b/tests-e2e/reference_output/compare_output.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Given files / search for identically named files in `..//pkg/` +# and compare them. If the files differ, print the differences and return an error exit code. + +# Change the current directory to the directory where the script is located +cd "$(dirname "$0")" || exit + +DIFF_FOUND=0 + +# Find all directories in the current directory +for FOLDERNAME in $(find . -maxdepth 1 -type d); do + # Skip the current directory + if [ "$FOLDERNAME" = "." ]; then + continue + fi + + # Remove the leading "./" from the folder name + FOLDERNAME="${FOLDERNAME#./}" + + # Find all files in the current folder and its subdirectories + FILES=$(find "./${FOLDERNAME}/" -type f) + for FILE in $FILES; do + # Remove the leading ".//" from the file path + RELATIVE_PATH="${FILE#./${FOLDERNAME}/}" + # Construct the path to the corresponding file in ..//pkg/ + OTHER_FILE="../${FOLDERNAME}/pkg/${RELATIVE_PATH}" + # Compare the files + if [ -f "$OTHER_FILE" ]; then + echo "Comparing $FILE with $OTHER_FILE" + + # suppress the exit code if the files differ beause we want to print out all differences + DIFF_OUTPUT=$(diff --brief "$FILE" "$OTHER_FILE" || true) + if [ -n "$DIFF_OUTPUT" ]; then + echo " $DIFF_OUTPUT" + diff "$FILE" "$OTHER_FILE" + DIFF_FOUND=1 + else + echo " Files are identical" + fi + fi + done +done + +exit $DIFF_FOUND \ No newline at end of file diff --git a/tests-e2e/reference_output/test1/test1.d.ts b/tests-e2e/reference_output/test1/test1.d.ts new file mode 100644 index 0000000..7ac75a5 --- /dev/null +++ b/tests-e2e/reference_output/test1/test1.d.ts @@ -0,0 +1,11 @@ +/* tslint:disable */ +/* eslint-disable */ +/** +* @returns {Point} +*/ +export function into_js(): Point; +export interface Point { + x: number; + y: number; +} + diff --git a/tests-e2e/reference_output/test2/test2.d.ts b/tests-e2e/reference_output/test2/test2.d.ts new file mode 100644 index 0000000..4c996b0 --- /dev/null +++ b/tests-e2e/reference_output/test2/test2.d.ts @@ -0,0 +1,13 @@ +/* tslint:disable */ +/* eslint-disable */ +/** +* @returns {Point} +*/ +export function into_js(): Point; +export interface Point { + x: number; + y: number; +} + +export type NullPoint = void; + diff --git a/tests-e2e/test1/Cargo.toml b/tests-e2e/test1/Cargo.toml new file mode 100644 index 0000000..c5be93f --- /dev/null +++ b/tests-e2e/test1/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "test1" +publish = false +version = "0.1.0" +edition = "2021" + +[dependencies] +wasm-bindgen = "0.2" +tsify-next = { path = "../..", version = "*" } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +[dev-dependencies] +wasm-bindgen-test = "0.3" + +[lib] +path = "entry_point.rs" +crate-type = ["cdylib"] + +[build-dependencies] +wasm-bindgen-cli = "0.2" diff --git a/tests-e2e/test1/entry_point.rs b/tests-e2e/test1/entry_point.rs new file mode 100644 index 0000000..8c10c71 --- /dev/null +++ b/tests-e2e/test1/entry_point.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; +use tsify_next::Tsify; +use wasm_bindgen::prelude::*; + +#[derive(Tsify, Serialize, Deserialize)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct Point { + x: i32, + y: i32, +} + +#[wasm_bindgen] +pub fn into_js() -> Point { + Point { x: 0, y: 0 } +} diff --git a/tests-e2e/test2/Cargo.toml b/tests-e2e/test2/Cargo.toml new file mode 100644 index 0000000..177ffe8 --- /dev/null +++ b/tests-e2e/test2/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "test2" +publish = false +version = "0.1.0" +edition = "2021" + +[dependencies] +wasm-bindgen = "0.2" +tsify-next = { path = "../..", version = "*" } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +[dev-dependencies] +wasm-bindgen-test = "0.3" + +[lib] +path = "entry_point.rs" +crate-type = ["cdylib"] + +[build-dependencies] +wasm-bindgen-cli = "0.2" diff --git a/tests-e2e/test2/entry_point.rs b/tests-e2e/test2/entry_point.rs new file mode 100644 index 0000000..41993ff --- /dev/null +++ b/tests-e2e/test2/entry_point.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; +use tsify_next::Tsify; +use wasm_bindgen::prelude::*; + +#[derive(Tsify, Serialize, Deserialize)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct Point { + x: i32, + y: i32, +} + +#[derive(Tsify, Serialize, Deserialize)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub enum NullPoint {} + +#[wasm_bindgen] +pub fn into_js() -> Point { + Point { x: 0, y: 0 } +} diff --git a/tests/enum.rs b/tests/enum.rs index 83f3159..51a7b19 100644 --- a/tests/enum.rs +++ b/tests/enum.rs @@ -38,6 +38,18 @@ fn test_externally_tagged_enum() { assert_eq!(External::DECL, expected); } +#[test] +fn test_empty_enum() { + #[derive(Tsify)] + enum Empty {} + + let expected = indoc! {r#" + export type Empty = void;"# + }; + + assert_eq!(Empty::DECL, expected); +} + #[test] fn test_externally_tagged_enum_with_namespace() { /// Comment for External diff --git a/tsify-next-macros/Cargo.toml b/tsify-next-macros/Cargo.toml index 22a653b..b3f2fdd 100644 --- a/tsify-next-macros/Cargo.toml +++ b/tsify-next-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tsify-next-macros" -version = "0.5.0" +version = "0.5.1" edition = "2021" authors = [ "Madono Haru ", diff --git a/tsify-next-macros/src/decl.rs b/tsify-next-macros/src/decl.rs index de0c69d..26e1288 100644 --- a/tsify-next-macros/src/decl.rs +++ b/tsify-next-macros/src/decl.rs @@ -91,6 +91,7 @@ impl Display for TsInterfaceDecl { } } +/// A Typescript type resulting from an enum declaration. #[derive(Debug)] pub struct TsEnumDecl { pub id: String, @@ -278,6 +279,8 @@ impl Display for TsEnumDecl { } } +/// A typescript type declaration. For example `type Foo = string;` +/// or `interface Bar { baz: number; }` #[allow(clippy::enum_variant_names)] pub enum Decl { TsTypeAlias(TsTypeAliasDecl), diff --git a/tsify-next-macros/src/derive.rs b/tsify-next-macros/src/derive.rs index 85bf49d..624c776 100644 --- a/tsify-next-macros/src/derive.rs +++ b/tsify-next-macros/src/derive.rs @@ -35,6 +35,7 @@ pub fn expand(input: DeriveInput) -> syn::Result { Ok(tokens) } +/// Expand an `enum` or `struct` with `#[derive(Tsify)]`. pub fn expand_by_attr(args: TokenStream, input: DeriveInput) -> syn::Result { let mut cloned_input = input.clone(); let attr: syn::Attribute = parse_quote!(#[tsify(#args)]); diff --git a/tsify-next-macros/src/typescript/ts_type_display.rs b/tsify-next-macros/src/typescript/ts_type_display.rs index 64a6938..d0035c0 100644 --- a/tsify-next-macros/src/typescript/ts_type_display.rs +++ b/tsify-next-macros/src/typescript/ts_type_display.rs @@ -91,23 +91,27 @@ impl Display for TsType { write!(f, "{types}") } - TsType::Union(types) => { - if types.len() == 1 { + TsType::Union(types) => match types.len() { + 0 => { + write!(f, "void") + } + 1 => { let ty = &types[0]; - return write!(f, "{ty}"); + write!(f, "{ty}") } - - let types = types - .iter() - .map(|ty| match ty { - TsType::Intersection(_) => format!("({ty})"), - _ => ty.to_string(), - }) - .collect::>() - .join(" | "); - - write!(f, "{types}") - } + _ => { + let types = types + .iter() + .map(|ty| match ty { + TsType::Intersection(_) => format!("({ty})"), + _ => ty.to_string(), + }) + .collect::>() + .join(" | "); + + write!(f, "{types}") + } + }, TsType::Override { type_override, .. } => f.write_str(type_override), }