Skip to content

Commit

Permalink
Fix router parser not parsing identifiers (#234)
Browse files Browse the repository at this point in the history
* Fix router parser not parsing identifiers

* Update trybuild
  • Loading branch information
lukechu10 authored Sep 11, 2021
1 parent c2c0e22 commit d0123e5
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 23 deletions.
3 changes: 2 additions & 1 deletion packages/sycamore-reactive/src/effect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down
5 changes: 3 additions & 2 deletions packages/sycamore-router-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
77 changes: 70 additions & 7 deletions packages/sycamore-router-macro/src/parser.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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(
Expand All @@ -233,13 +272,37 @@ mod tests {
fn dyn_should_eat_slash_character() {
check(
"/<a/b>/",
expect![[r#"
(
"",
RoutePathAst {
segments: [
Param(
"<a",
),
Param(
"b>",
),
],
},
)"#]],
);
}

#[test]
fn dyn_param_before_dyn_segment() {
check(
"/<param>/<segments..>",
expect![[r#"
(
"",
RoutePathAst {
segments: [
DynParam(
"a/b",
"param",
),
DynSegments(
"segments",
),
],
},
Expand Down
15 changes: 12 additions & 3 deletions packages/sycamore-router-macro/src/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
));
}

Expand All @@ -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)?;
Expand All @@ -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)?;
Expand Down
14 changes: 7 additions & 7 deletions packages/sycamore-router-macro/tests/router/router-fail.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,34 @@ 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)]
| ^^^^^
|
= 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 },
| ^
| ^^^^^^^^^^
22 changes: 22 additions & 0 deletions packages/sycamore-router/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,5 +452,27 @@ mod tests {
Routes::NotFound
);
}

#[test]
fn router_dyn_param_before_dyn_segment() {
#[derive(Debug, PartialEq, Eq, Route)]
enum Routes {
#[to("/<param>/<segments..>")]
Path {
param: String,
segments: Vec<String>,
},
#[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()]
}
);
}
}
}
6 changes: 3 additions & 3 deletions website/src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ pub fn index() -> Template<G> {
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(
Expand All @@ -43,7 +43,7 @@ pub fn index() -> Template<G> {
) { 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",
Expand Down

0 comments on commit d0123e5

Please sign in to comment.