From d0123e55ec4e7ba7338b9cfe59efdbd202ae555d Mon Sep 17 00:00:00 2001 From: Luke Chu <37006668+lukechu10@users.noreply.github.com> Date: Sat, 11 Sep 2021 12:14:56 -0700 Subject: [PATCH] Fix router parser not parsing identifiers (#234) * Fix router parser not parsing identifiers * Update trybuild --- packages/sycamore-reactive/src/effect.rs | 3 +- packages/sycamore-router-macro/Cargo.toml | 5 +- packages/sycamore-router-macro/src/parser.rs | 77 +++++++++++++++++-- packages/sycamore-router-macro/src/route.rs | 15 +++- .../tests/router/router-fail.stderr | 14 ++-- packages/sycamore-router/src/lib.rs | 22 ++++++ website/src/index.rs | 6 +- 7 files changed, 119 insertions(+), 23 deletions(-) diff --git a/packages/sycamore-reactive/src/effect.rs b/packages/sycamore-reactive/src/effect.rs index 442b5a906..8191b6db1 100644 --- a/packages/sycamore-reactive/src/effect.rs +++ b/packages/sycamore-reactive/src/effect.rs @@ -374,7 +374,8 @@ where /// An alternative to [`Signal::new`] that uses a reducer to get the next value. /// -/// It uses a reducer function that takes the previous value and a message and returns the next value. +/// It uses a reducer function that takes the previous value and a message and returns the next +/// value. /// /// Returns a [`StateHandle`] and a dispatch function to send messages to the reducer. /// diff --git a/packages/sycamore-router-macro/Cargo.toml b/packages/sycamore-router-macro/Cargo.toml index 7a7e2ffcc..b40bf2b52 100644 --- a/packages/sycamore-router-macro/Cargo.toml +++ b/packages/sycamore-router-macro/Cargo.toml @@ -17,12 +17,13 @@ proc-macro = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nom = "6.2.1" +nom = "7.0.0" proc-macro2 = "1.0.28" quote = "1.0.9" syn = { version = "1.0.74", features = ["full"] } +unicode-xid = "0.2.2" [dev-dependencies] expect-test = "1.1.0" -sycamore-router = {path = "../sycamore-router"} +sycamore-router = { path = "../sycamore-router" } trybuild = "1.0.45" diff --git a/packages/sycamore-router-macro/src/parser.rs b/packages/sycamore-router-macro/src/parser.rs index 113fbfb13..4f19a55d1 100644 --- a/packages/sycamore-router-macro/src/parser.rs +++ b/packages/sycamore-router-macro/src/parser.rs @@ -1,8 +1,8 @@ use nom::branch::alt; -use nom::bytes::complete::{tag, take_till, take_until}; -use nom::combinator::map; -use nom::multi::separated_list0; -use nom::sequence::delimited; +use nom::bytes::complete::{tag, take, take_till}; +use nom::combinator::{map, recognize, verify}; +use nom::multi::{many0, separated_list0}; +use nom::sequence::{delimited, pair}; use nom::IResult; #[derive(Debug, Clone)] @@ -31,12 +31,30 @@ fn param(i: &str) -> IResult<&str, &str> { take_till(|c| c == '/')(i) } +pub fn ident_start(s: &str) -> IResult<&str, &str> { + verify(take(1usize), |c: &str| { + let c = c.chars().next().unwrap(); + c == '_' || unicode_xid::UnicodeXID::is_xid_start(c) + })(s) +} + +pub fn ident_continue(s: &str) -> IResult<&str, &str> { + verify(take(1usize), |c: &str| { + unicode_xid::UnicodeXID::is_xid_continue(c.chars().next().unwrap()) + })(s) +} + +/// Parse a Rust identifier. Reference: https://doc.rust-lang.org/reference/identifiers.html +fn ident(i: &str) -> IResult<&str, &str> { + recognize(pair(ident_start, many0(ident_continue)))(i) +} + fn dyn_param(i: &str) -> IResult<&str, &str> { - delimited(tag("<"), take_until(">"), tag(">"))(i) + delimited(tag("<"), ident, tag(">"))(i) } fn dyn_segments(i: &str) -> IResult<&str, &str> { - delimited(tag("<"), take_until("..>"), tag("..>"))(i) + delimited(tag("<"), ident, tag("..>"))(i) } fn segment(i: &str) -> IResult<&str, SegmentAst> { @@ -208,6 +226,27 @@ mod tests { ); } + #[test] + fn unnamed_dyn_param() { + check( + "/id/<_>", + expect![[r#" + ( + "", + RoutePathAst { + segments: [ + Param( + "id", + ), + DynParam( + "_", + ), + ], + }, + )"#]], + ); + } + #[test] fn dyn_segments() { check( @@ -233,13 +272,37 @@ mod tests { fn dyn_should_eat_slash_character() { check( "//", + expect![[r#" + ( + "", + RoutePathAst { + segments: [ + Param( + "", + ), + ], + }, + )"#]], + ); + } + + #[test] + fn dyn_param_before_dyn_segment() { + check( + "//", expect![[r#" ( "", RoutePathAst { segments: [ DynParam( - "a/b", + "param", + ), + DynSegments( + "segments", ), ], }, diff --git a/packages/sycamore-router-macro/src/route.rs b/packages/sycamore-router-macro/src/route.rs index 286e92334..960e58b41 100644 --- a/packages/sycamore-router-macro/src/route.rs +++ b/packages/sycamore-router-macro/src/route.rs @@ -118,7 +118,8 @@ fn impl_to( if expected_fields_len != variant.fields.len() { return Err(syn::Error::new( variant.fields.span(), - "mismatch between number of capture fields and variant fields", + format!("mismatch between number of capture fields and variant fields (found {} capture field(s) and {} variant field(s))", + expected_fields_len, variant.fields.len()), )); } @@ -134,7 +135,11 @@ fn impl_to( if param != &field.ident.as_ref().unwrap().to_string() { return Err(syn::Error::new( field.ident.span(), - "capture field name mismatch", + format!( + "capture field name mismatch (expected `{}`, found `{}`)", + param, + field.ident.as_ref().unwrap() + ), )); } let param_id: Ident = syn::parse_str(param)?; @@ -151,7 +156,11 @@ fn impl_to( if param != &field.ident.as_ref().unwrap().to_string() { return Err(syn::Error::new( field.ident.span(), - "capture field name mismatch", + format!( + "capture field name mismatch (expected `{}`, found `{}`)", + param, + field.ident.as_ref().unwrap() + ), )); } let param_id: Ident = syn::parse_str(param)?; diff --git a/packages/sycamore-router-macro/tests/router/router-fail.stderr b/packages/sycamore-router-macro/tests/router/router-fail.stderr index 46e4bcda3..85640a09c 100644 --- a/packages/sycamore-router-macro/tests/router/router-fail.stderr +++ b/packages/sycamore-router-macro/tests/router/router-fail.stderr @@ -16,7 +16,7 @@ error: not found route cannot have any fields 13 | NotFound(i32), // Cannot have field | ^^^^^ -error: mismatch between number of capture fields and variant fields +error: mismatch between number of capture fields and variant fields (found 1 capture field(s) and 0 variant field(s)) --> $DIR/router-fail.rs:16:10 | 16 | #[derive(Route)] @@ -24,26 +24,26 @@ error: mismatch between number of capture fields and variant fields | = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) -error: mismatch between number of capture fields and variant fields +error: mismatch between number of capture fields and variant fields (found 1 capture field(s) and 0 variant field(s)) --> $DIR/router-fail.rs:27:10 | 27 | Path {}, // Missing capture field | ^^ -error: capture field name mismatch +error: capture field name mismatch (expected `capture`, found `not_capture`) --> $DIR/router-fail.rs:35:12 | 35 | Path { not_capture: u32 }, // Wrong capture field name | ^^^^^^^^^^^ -error: capture field name mismatch +error: capture field name mismatch (expected `a`, found `b`) --> $DIR/router-fail.rs:43:12 | 43 | Path { b: u32, a: u32 }, // Wrong order | ^ -error: capture field name mismatch - --> $DIR/router-fail.rs:51:12 +error: mismatch between number of capture fields and variant fields (found 0 capture field(s) and 1 variant field(s)) + --> $DIR/router-fail.rs:51:10 | 51 | Path { a: u32 }, - | ^ + | ^^^^^^^^^^ diff --git a/packages/sycamore-router/src/lib.rs b/packages/sycamore-router/src/lib.rs index 65a99f337..630f307a9 100644 --- a/packages/sycamore-router/src/lib.rs +++ b/packages/sycamore-router/src/lib.rs @@ -452,5 +452,27 @@ mod tests { Routes::NotFound ); } + + #[test] + fn router_dyn_param_before_dyn_segment() { + #[derive(Debug, PartialEq, Eq, Route)] + enum Routes { + #[to("//")] + Path { + param: String, + segments: Vec, + }, + #[not_found] + NotFound, + } + + assert_eq!( + Routes::match_route(&["path", "1", "2"]), + Routes::Path { + param: "path".to_string(), + segments: vec!["1".to_string(), "2".to_string()] + } + ); + } } } diff --git a/website/src/index.rs b/website/src/index.rs index 67b25e663..c4ca321dd 100644 --- a/website/src/index.rs +++ b/website/src/index.rs @@ -14,11 +14,11 @@ pub fn index() -> Template { h1(class="text-5xl font-bold mt-20 mb-5") { "Sycamore" } - + p(class="mb-5 text-center") { "A reactive library for creating web apps in Rust and WebAssembly" } - + // region: badges div(class="mb-7 flex flex-row flex-wrap justify-center gap-1") { a( @@ -43,7 +43,7 @@ pub fn index() -> Template { ) { img(src="https://img.shields.io/discord/820400041332179004?label=discord", alt="Discord") } } // endregion - + a( href="/docs/getting_started/installation", class="py-2 px-3 text-white bg-yellow-600 rounded font-medium transition",