1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
use crate::Resolver;

#[derive(Clone, Debug)]
pub struct Request {
    pub target: String,
    pub query: String,
    pub fragment: String,
}

enum ParseStats {
    Request,
    Query,
    Fragment,
    Start,
}

impl Resolver {
    fn parse_identifier(ident: &str) -> (String, String, String) {
        // maybe we should use regexp: https://github.com/webpack/enhanced-resolve/blob/main/lib/util/identifier.js#L8
        let mut target = String::new();
        let mut query = String::new();
        let mut fragment = String::new();
        let mut stats = ParseStats::Start;
        for c in ident.chars() {
            match c {
                '#' => {
                    matches!(stats, ParseStats::Request | ParseStats::Query).then(|| {
                        stats = ParseStats::Fragment;
                    });
                    matches!(stats, ParseStats::Start).then(|| {
                        stats = ParseStats::Request;
                    });
                }
                '?' => {
                    (!matches!(stats, ParseStats::Fragment)).then(|| {
                        stats = ParseStats::Query;
                    });
                }
                _ => {
                    matches!(stats, ParseStats::Start).then(|| {
                        stats = ParseStats::Request;
                    });
                }
            };
            match stats {
                ParseStats::Request => target.push(c),
                ParseStats::Query => query.push(c),
                ParseStats::Fragment => fragment.push(c),
                _ => unreachable!(),
            };
        }
        (target, query, fragment)
    }

    pub fn parse(target: &str) -> Request {
        let (target, query, fragment) = Self::parse_identifier(target);
        Request {
            target,
            query,
            fragment,
        }
    }
}

#[test]
fn parse_identifier_test() {
    macro_rules! should_parsed {
        ($ident: expr; $r: expr, $q: expr, $f: expr) => {
            assert_eq!(
                Resolver::parse_identifier(&String::from($ident)),
                (
                    ($r).chars().collect(),
                    ($q).chars().collect(),
                    ($f).chars().collect()
                )
            );
        };
    }

    should_parsed!("path/#"; "path/", "", "#");
    should_parsed!("path/as/?"; "path/as/", "?", "");
    should_parsed!("path/#/?"; "path/", "", "#/?");
    should_parsed!("path/#repo#hash"; "path/", "", "#repo#hash");
    should_parsed!("path/#r#hash"; "path/", "", "#r#hash");
    should_parsed!("path/#repo/#repo2#hash"; "path/", "", "#repo/#repo2#hash");
    should_parsed!("path/#r/#r#hash"; "path/", "", "#r/#r#hash");
    should_parsed!("path/#/not/a/hash?not-a-query"; "path/", "", "#/not/a/hash?not-a-query");
    should_parsed!("#a?b#c?d"; "#a", "?b", "#c?d");

    // windows like
    should_parsed!("path\\#"; "path\\", "", "#");
    should_parsed!("C:path\\as\\?"; "C:path\\as\\", "?", "");
    should_parsed!("path\\#\\?"; "path\\", "", "#\\?");
    should_parsed!("path\\#repo#hash"; "path\\", "", "#repo#hash");
    should_parsed!("path\\#r#hash"; "path\\", "", "#r#hash");
    should_parsed!("path\\#/not/a/hash?not-a-query"; "path\\", "", "#/not/a/hash?not-a-query");
}