mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 20:32:10 +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 `}`.
|
/// Returns the type content without `{` and `}`.
|
||||||
pub fn parsed(&self) -> &'a str {
|
pub fn parsed(&self) -> &'a str {
|
||||||
// +1 for `{`, -1 for `}`
|
// +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.
|
/// Returns the type name itself.
|
||||||
|
/// `.raw` may be like `[foo = var]`, so extract the name
|
||||||
pub fn parsed(&self) -> &'a str {
|
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
|
self.raw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -269,8 +275,8 @@ mod test {
|
||||||
("{}", ""),
|
("{}", ""),
|
||||||
("{-}", "-"),
|
("{-}", "-"),
|
||||||
("{string}", "string"),
|
("{string}", "string"),
|
||||||
("{ string}", " string"),
|
("{ string}", "string"),
|
||||||
("{ bool }", " bool "),
|
("{ bool }", "bool"),
|
||||||
("{{x:1}}", "{x:1}"),
|
("{{x:1}}", "{x:1}"),
|
||||||
("{[1,2,3]}", "[1,2,3]"),
|
("{[1,2,3]}", "[1,2,3]"),
|
||||||
] {
|
] {
|
||||||
|
|
@ -282,7 +288,15 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn type_name_part_parsed() {
|
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
|
// `Span` is not used in this test
|
||||||
let type_name_part = JSDocTagTypeNamePart::new(actual, SPAN);
|
let type_name_part = JSDocTagTypeNamePart::new(actual, SPAN);
|
||||||
assert_eq!(type_name_part.parsed(), expect);
|
assert_eq!(type_name_part.parsed(), expect);
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ impl<'a> JSDocTag<'a> {
|
||||||
None => (None, self.body_raw, self.body_span.start),
|
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)) => {
|
Some((n_start, n_end)) => {
|
||||||
// Include whitespace for comment trimming
|
// Include whitespace for comment trimming
|
||||||
let c_start = n_end;
|
let c_start = n_end;
|
||||||
|
|
@ -327,7 +327,7 @@ mod test {
|
||||||
{t2} */",
|
{t2} */",
|
||||||
Some(("t2", "{t2}")),
|
Some(("t2", "{t2}")),
|
||||||
),
|
),
|
||||||
("/** @k3 { t3 } */", Some((" t3 ", "{ t3 }"))),
|
("/** @k3 { t3 } */", Some(("t3", "{ t3 }"))),
|
||||||
("/** @k4 x{t4}y */", Some(("t4", "{t4}"))),
|
("/** @k4 x{t4}y */", Some(("t4", "{t4}"))),
|
||||||
("/** @k5 {t5}} */", Some(("t5", "{t5}"))),
|
("/** @k5 {t5}} */", Some(("t5", "{t5}"))),
|
||||||
("/** @k6 */", None),
|
("/** @k6 */", None),
|
||||||
|
|
@ -403,10 +403,10 @@ c5 */",
|
||||||
("c4\n...", " c4\n..."),
|
("c4\n...", " c4\n..."),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"/** @k5 {t5} n5 - c5 */",
|
"/** @k5 {t5} n5 - c5 */",
|
||||||
Some(("t5", "{t5}")),
|
Some(("t5", "{t5}")),
|
||||||
Some(("n5", "n5")),
|
Some(("n5", "n5")),
|
||||||
("- c5", " - c5 "),
|
("- c5", " - c5 "),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"/** @k6
|
"/** @k6
|
||||||
|
|
@ -430,7 +430,27 @@ c7 */",
|
||||||
("c7", "\n\nc7 "),
|
("c7", "\n\nc7 "),
|
||||||
),
|
),
|
||||||
("/** @k8 {t8} */", Some(("t8", "{t8}")), None, ("", "")),
|
("/** @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 allocator = Allocator::default();
|
||||||
let semantic = build_semantic(&allocator, source_text);
|
let semantic = build_semantic(&allocator, source_text);
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,47 @@ pub fn find_type_range(s: &str) -> Option<(usize, usize)> {
|
||||||
None
|
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
|
// Find inline token string as range
|
||||||
pub fn find_token_range(s: &str) -> Option<(usize, usize)> {
|
pub fn find_token_range(s: &str) -> Option<(usize, usize)> {
|
||||||
let mut start = None;
|
let mut start = None;
|
||||||
|
|
@ -49,7 +90,7 @@ pub fn find_token_range(s: &str) -> Option<(usize, usize)> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::{find_token_range, find_type_range};
|
use super::{find_token_range, find_type_name_range, find_type_range};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn extract_type_part_range() {
|
fn extract_type_part_range() {
|
||||||
|
|
@ -64,21 +105,51 @@ mod test {
|
||||||
("{{t8}", None),
|
("{{t8}", None),
|
||||||
("", None),
|
("", None),
|
||||||
("{[ true, false ]}", Some("{[ true, false ]}")),
|
("{[ 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);
|
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]
|
#[test]
|
||||||
fn extract_token_part_range() {
|
fn extract_token_part_range() {
|
||||||
for (actual, expect) in [
|
for (actual, expect) in [
|
||||||
("n1", Some("n1")),
|
("k1", Some("k1")),
|
||||||
("n2 x", Some("n2")),
|
("k2 x", Some("k2")),
|
||||||
(" n3 ", Some("n3")),
|
(" k3 ", Some("k3")),
|
||||||
("n4\ny", Some("n4")),
|
("k4\ny", Some("k4")),
|
||||||
("", None),
|
("", None),
|
||||||
(" 名前5\n", Some("名前5")),
|
(" トークン5\n", Some("トークン5")),
|
||||||
("\nn6\nx", Some("n6")),
|
("\nk6\nx", Some("k6")),
|
||||||
] {
|
] {
|
||||||
assert_eq!(find_token_range(actual).map(|(s, e)| &actual[s..e]), expect);
|
assert_eq!(find_token_range(actual).map(|(s, e)| &actual[s..e]), expect);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue