mirror of
https://github.com/danbulant/oxc
synced 2026-05-22 21:58:36 +00:00
feat(semantic/jsdoc): Handle optional type syntax for type name part (#2960)
It seems `JSDocTypeNamePart` can contain whitespace like... ```js /** @property [cfg.n12="default value"] Config... */ ```
This commit is contained in:
parent
ec1ca3ac98
commit
40af2b1662
3 changed files with 121 additions and 16 deletions
|
|
@ -124,7 +124,7 @@ impl<'a> JSDocTagTypePart<'a> {
|
|||
/// Returns the type content without `{` and `}`.
|
||||
pub fn parsed(&self) -> &'a str {
|
||||
// +1 for `{`, -1 for `}`
|
||||
&self.raw[1..self.raw.len() - 1]
|
||||
self.raw[1..self.raw.len() - 1].trim()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +141,13 @@ impl<'a> JSDocTagTypeNamePart<'a> {
|
|||
}
|
||||
|
||||
/// Returns the type name itself.
|
||||
/// `.raw` may be like `[foo = var]`, so extract the name
|
||||
pub fn parsed(&self) -> &'a str {
|
||||
if self.raw.starts_with('[') {
|
||||
let inner = self.raw.trim_start_matches('[').trim_end_matches(']').trim();
|
||||
return inner.split_once('=').map_or(inner, |(v, _)| v.trim());
|
||||
}
|
||||
|
||||
self.raw
|
||||
}
|
||||
}
|
||||
|
|
@ -269,8 +275,8 @@ mod test {
|
|||
("{}", ""),
|
||||
("{-}", "-"),
|
||||
("{string}", "string"),
|
||||
("{ string}", " string"),
|
||||
("{ bool }", " bool "),
|
||||
("{ string}", "string"),
|
||||
("{ bool }", "bool"),
|
||||
("{{x:1}}", "{x:1}"),
|
||||
("{[1,2,3]}", "[1,2,3]"),
|
||||
] {
|
||||
|
|
@ -282,7 +288,15 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn type_name_part_parsed() {
|
||||
for (actual, expect) in [("foo", "foo"), ("Bar", "Bar"), ("変数", "変数")] {
|
||||
for (actual, expect) in [
|
||||
("foo", "foo"),
|
||||
("Bar", "Bar"),
|
||||
("変数", "変数"),
|
||||
("[opt]", "opt"),
|
||||
("[ opt2 ]", "opt2"),
|
||||
("[def1 = [ 1 ]]", "def1"),
|
||||
(r#"[def2 = "foo bar"]"#, "def2"),
|
||||
] {
|
||||
// `Span` is not used in this test
|
||||
let type_name_part = JSDocTagTypeNamePart::new(actual, SPAN);
|
||||
assert_eq!(type_name_part.parsed(), expect);
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ impl<'a> JSDocTag<'a> {
|
|||
None => (None, self.body_raw, self.body_span.start),
|
||||
};
|
||||
|
||||
let (name_part, comment_part) = match utils::find_token_range(name_comment_content) {
|
||||
let (name_part, comment_part) = match utils::find_type_name_range(name_comment_content) {
|
||||
Some((n_start, n_end)) => {
|
||||
// Include whitespace for comment trimming
|
||||
let c_start = n_end;
|
||||
|
|
@ -327,7 +327,7 @@ mod test {
|
|||
{t2} */",
|
||||
Some(("t2", "{t2}")),
|
||||
),
|
||||
("/** @k3 { t3 } */", Some((" t3 ", "{ t3 }"))),
|
||||
("/** @k3 { t3 } */", Some(("t3", "{ t3 }"))),
|
||||
("/** @k4 x{t4}y */", Some(("t4", "{t4}"))),
|
||||
("/** @k5 {t5}} */", Some(("t5", "{t5}"))),
|
||||
("/** @k6 */", None),
|
||||
|
|
@ -403,10 +403,10 @@ c5 */",
|
|||
("c4\n...", " c4\n..."),
|
||||
),
|
||||
(
|
||||
"/** @k5 {t5} n5 - c5 */",
|
||||
"/** @k5 {t5} n5 - c5 */",
|
||||
Some(("t5", "{t5}")),
|
||||
Some(("n5", "n5")),
|
||||
("- c5", " - c5 "),
|
||||
("- c5", " - c5 "),
|
||||
),
|
||||
(
|
||||
"/** @k6
|
||||
|
|
@ -430,7 +430,27 @@ c7 */",
|
|||
("c7", "\n\nc7 "),
|
||||
),
|
||||
("/** @k8 {t8} */", Some(("t8", "{t8}")), None, ("", "")),
|
||||
("/** @k8 n8 */", None, Some(("n8", "n8")), ("", " ")),
|
||||
("/** @k9 n9 */", None, Some(("n9", "n9")), ("", " ")),
|
||||
("/** @property n[].n10 */", None, Some(("n[].n10", "n[].n10")), ("", " ")),
|
||||
("/** @property n.n11 */", None, Some(("n.n11", "n.n11")), ("", " ")),
|
||||
(
|
||||
r#"/** @property [cfg.n12="default value"] */"#,
|
||||
None,
|
||||
Some(("cfg.n12", r#"[cfg.n12="default value"]"#)),
|
||||
("", " "),
|
||||
),
|
||||
(
|
||||
"/** @property {t13} [n = 13] c13 */",
|
||||
Some(("t13", "{t13}")),
|
||||
Some(("n", "[n = 13]")),
|
||||
("c13", " c13 "),
|
||||
),
|
||||
(
|
||||
"/** @param {t14} [n14] - opt */",
|
||||
Some(("t14", "{t14}")),
|
||||
Some(("n14", "[n14]")),
|
||||
("- opt", " - opt "),
|
||||
),
|
||||
] {
|
||||
let allocator = Allocator::default();
|
||||
let semantic = build_semantic(&allocator, source_text);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,47 @@ pub fn find_type_range(s: &str) -> Option<(usize, usize)> {
|
|||
None
|
||||
}
|
||||
|
||||
// Like a token but whitespace may appear inside of optional type syntax
|
||||
// e.g. `[foo = 1]`, `[bar="here inside of string"]`, `[ baz = [ "a b", "c" ] ]`
|
||||
pub fn find_type_name_range(s: &str) -> Option<(usize, usize)> {
|
||||
// Not optional type syntax
|
||||
if !s.trim_start().starts_with('[') {
|
||||
return find_token_range(s);
|
||||
}
|
||||
|
||||
let mut bracket = 0;
|
||||
let mut start = None;
|
||||
for (idx, ch) in s.char_indices() {
|
||||
if ch.is_whitespace() {
|
||||
if bracket != 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(start) = start {
|
||||
return Some((start, idx));
|
||||
}
|
||||
} else {
|
||||
if ch == '[' {
|
||||
bracket += 1;
|
||||
}
|
||||
if ch == ']' {
|
||||
bracket -= 1;
|
||||
}
|
||||
|
||||
if start.is_none() {
|
||||
start = Some(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Everything is a token
|
||||
if let Some(start) = start {
|
||||
return Some((start, s.len()));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// Find inline token string as range
|
||||
pub fn find_token_range(s: &str) -> Option<(usize, usize)> {
|
||||
let mut start = None;
|
||||
|
|
@ -49,7 +90,7 @@ pub fn find_token_range(s: &str) -> Option<(usize, usize)> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{find_token_range, find_type_range};
|
||||
use super::{find_token_range, find_type_name_range, find_type_range};
|
||||
|
||||
#[test]
|
||||
fn extract_type_part_range() {
|
||||
|
|
@ -64,21 +105,51 @@ mod test {
|
|||
("{{t8}", None),
|
||||
("", None),
|
||||
("{[ true, false ]}", Some("{[ true, false ]}")),
|
||||
(
|
||||
"{{
|
||||
t9a: string;
|
||||
t9b: number;
|
||||
}}",
|
||||
Some("{{\nt9a: string;\nt9b: number;\n}}"),
|
||||
),
|
||||
] {
|
||||
assert_eq!(find_type_range(actual).map(|(s, e)| &actual[s..e]), expect);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_type_name_part_range() {
|
||||
for (actual, expect) in [
|
||||
("", None),
|
||||
("n1", Some("n1")),
|
||||
(" n2 ", Some("n2")),
|
||||
(" n3 n3", Some("n3")),
|
||||
("[n4]\n", Some("[n4]")),
|
||||
("[n5 = 1]", Some("[n5 = 1]")),
|
||||
(" [n6 = [1,[2, [3]]]] ", Some("[n6 = [1,[2, [3]]]]")),
|
||||
(r#"[n7 = "foo bar"]"#, Some(r#"[n7 = "foo bar"]"#)),
|
||||
("n.n8", Some("n.n8")),
|
||||
("n[].n9", Some("n[].n9")),
|
||||
(r#"[ n10 = ["{}", "[]"] ]"#, Some(r#"[ n10 = ["{}", "[]"] ]"#)),
|
||||
("[n11... c11", Some("[n11... c11")),
|
||||
("[n12[]\nc12", Some("[n12[]\nc12")),
|
||||
("n12.n12", Some("n12.n12")),
|
||||
("n13[].n13", Some("n13[].n13")),
|
||||
] {
|
||||
assert_eq!(find_type_name_range(actual).map(|(s, e)| &actual[s..e]), expect);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_token_part_range() {
|
||||
for (actual, expect) in [
|
||||
("n1", Some("n1")),
|
||||
("n2 x", Some("n2")),
|
||||
(" n3 ", Some("n3")),
|
||||
("n4\ny", Some("n4")),
|
||||
("k1", Some("k1")),
|
||||
("k2 x", Some("k2")),
|
||||
(" k3 ", Some("k3")),
|
||||
("k4\ny", Some("k4")),
|
||||
("", None),
|
||||
(" 名前5\n", Some("名前5")),
|
||||
("\nn6\nx", Some("n6")),
|
||||
(" トークン5\n", Some("トークン5")),
|
||||
("\nk6\nx", Some("k6")),
|
||||
] {
|
||||
assert_eq!(find_token_range(actual).map(|(s, e)| &actual[s..e]), expect);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue