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",