mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(linter/jsdoc): Implement check-tag-names rule (#3029)
Part of #1170 - https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-tag-names.md#user-content-check-tag-names-options-definedtags (This time, I'm less confident that I have implemented correctly. 😓 )
This commit is contained in:
parent
45649db86e
commit
d109767330
4 changed files with 1441 additions and 35 deletions
|
|
@ -71,41 +71,30 @@ pub struct JSDocPluginSettings {
|
|||
}
|
||||
|
||||
impl JSDocPluginSettings {
|
||||
/// Only for `check-tag-names` rule
|
||||
/// Return `Some(reason)` if blocked
|
||||
pub fn check_blocked_tag_name(&self, tag_name: &str) -> Option<String> {
|
||||
match self.tag_name_preference.get(tag_name) {
|
||||
Some(TagNamePreference::FalseOnly(_)) => Some(format!("Unexpected tag `@{tag_name}`")),
|
||||
Some(
|
||||
TagNamePreference::ObjectWithMessage { message }
|
||||
| TagNamePreference::ObjectWithMessageAndReplacement { message, .. },
|
||||
) => Some(message.to_string()),
|
||||
Some(TagNamePreference::FalseOnly(_)) => Some(format!("Unexpected tag `@{tag_name}`.")),
|
||||
Some(TagNamePreference::ObjectWithMessage { message }) => Some(message.to_string()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
/// Only for `check-tag-names` rule
|
||||
/// Return `Some(reason)` if replacement found or default aliased
|
||||
pub fn check_preferred_tag_name(&self, original_name: &str) -> Option<String> {
|
||||
let reason = |preferred_name: &str| -> String {
|
||||
format!("Replace tag `@{original_name}` with `@{preferred_name}`.")
|
||||
};
|
||||
|
||||
pub fn list_preferred_tag_names(&self) -> Vec<String> {
|
||||
self.tag_name_preference
|
||||
.iter()
|
||||
.filter_map(|(_, pref)| match pref {
|
||||
TagNamePreference::TagNameOnly(replacement)
|
||||
| TagNamePreference::ObjectWithMessageAndReplacement { replacement, .. } => {
|
||||
Some(replacement.to_string())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Resolve original, known tag name to user preferred name
|
||||
/// If not defined, return original name
|
||||
pub fn resolve_tag_name(&self, original_name: &str) -> String {
|
||||
match self.tag_name_preference.get(original_name) {
|
||||
Some(
|
||||
TagNamePreference::TagNameOnly(replacement)
|
||||
| TagNamePreference::ObjectWithMessageAndReplacement { replacement, .. },
|
||||
) => replacement.to_string(),
|
||||
Some(TagNamePreference::TagNameOnly(preferred_name)) => Some(reason(preferred_name)),
|
||||
Some(TagNamePreference::ObjectWithMessageAndReplacement { message, .. }) => {
|
||||
Some(message.to_string())
|
||||
}
|
||||
_ => {
|
||||
// https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/settings.md#default-preferred-aliases
|
||||
match original_name {
|
||||
let aliased_name = match original_name {
|
||||
"virtual" => "abstract",
|
||||
"extends" => "augments",
|
||||
"constructor" => "class",
|
||||
|
|
@ -123,11 +112,42 @@ impl JSDocPluginSettings {
|
|||
"exception" => "throws",
|
||||
"yield" => "yields",
|
||||
_ => original_name,
|
||||
};
|
||||
|
||||
if aliased_name != original_name {
|
||||
return Some(reason(aliased_name));
|
||||
}
|
||||
.to_string()
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Only for `check-tag-names` rule
|
||||
/// Return all user replacement tag names
|
||||
pub fn list_user_defined_tag_names(&self) -> Vec<&str> {
|
||||
self.tag_name_preference
|
||||
.iter()
|
||||
.filter_map(|(_, pref)| match pref {
|
||||
TagNamePreference::TagNameOnly(replacement)
|
||||
| TagNamePreference::ObjectWithMessageAndReplacement { replacement, .. } => {
|
||||
Some(replacement.as_str())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Resolve original, known tag name to user preferred name
|
||||
/// If not defined, return original name
|
||||
pub fn resolve_tag_name(&self, original_name: &str) -> String {
|
||||
match self.tag_name_preference.get(original_name) {
|
||||
Some(
|
||||
TagNamePreference::TagNameOnly(replacement)
|
||||
| TagNamePreference::ObjectWithMessageAndReplacement { replacement, .. },
|
||||
) => replacement.to_string(),
|
||||
_ => original_name.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deserialize helper types
|
||||
|
|
@ -186,9 +206,6 @@ mod test {
|
|||
fn resolve_tag_name() {
|
||||
let settings = JSDocPluginSettings::deserialize(&serde_json::json!({})).unwrap();
|
||||
assert_eq!(settings.resolve_tag_name("foo"), "foo".to_string());
|
||||
assert_eq!(settings.resolve_tag_name("virtual"), "abstract".to_string());
|
||||
assert_eq!(settings.resolve_tag_name("fileoverview"), "file".to_string());
|
||||
assert_eq!(settings.resolve_tag_name("overview"), "file".to_string());
|
||||
|
||||
let settings = JSDocPluginSettings::deserialize(&serde_json::json!({
|
||||
"tagNamePreference": {
|
||||
|
|
@ -208,9 +225,9 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn list_preferred_tag_names() {
|
||||
fn list_user_defined_tag_names() {
|
||||
let settings = JSDocPluginSettings::deserialize(&serde_json::json!({})).unwrap();
|
||||
assert_eq!(settings.list_preferred_tag_names().len(), 0);
|
||||
assert_eq!(settings.list_user_defined_tag_names().len(), 0);
|
||||
|
||||
let settings = JSDocPluginSettings::deserialize(&serde_json::json!({
|
||||
"tagNamePreference": {
|
||||
|
|
@ -222,7 +239,7 @@ mod test {
|
|||
}
|
||||
}))
|
||||
.unwrap();
|
||||
let mut preferred = settings.list_preferred_tag_names();
|
||||
let mut preferred = settings.list_user_defined_tag_names();
|
||||
preferred.sort_unstable();
|
||||
assert_eq!(preferred, vec!["bar", "noop", "overridedefault"]);
|
||||
}
|
||||
|
|
@ -242,9 +259,32 @@ mod test {
|
|||
.unwrap();
|
||||
assert_eq!(
|
||||
settings.check_blocked_tag_name("foo"),
|
||||
Some("Unexpected tag `@foo`".to_string())
|
||||
Some("Unexpected tag `@foo`.".to_string())
|
||||
);
|
||||
assert_eq!(settings.check_blocked_tag_name("bar"), Some("do not use bar".to_string()));
|
||||
assert_eq!(settings.check_blocked_tag_name("baz"), Some("baz is noop now".to_string()));
|
||||
assert_eq!(settings.check_blocked_tag_name("baz"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_preferred_tag_name() {
|
||||
let settings = JSDocPluginSettings::deserialize(&serde_json::json!({})).unwrap();
|
||||
assert_eq!(settings.check_preferred_tag_name("foo"), None);
|
||||
|
||||
let settings = JSDocPluginSettings::deserialize(&serde_json::json!({
|
||||
"tagNamePreference": {
|
||||
"foo": false,
|
||||
"bar": { "message": "do not use bar" },
|
||||
"baz": { "message": "baz is noop now", "replacement": "noop" },
|
||||
"qux": "quux"
|
||||
}
|
||||
}))
|
||||
.unwrap();
|
||||
assert_eq!(settings.check_preferred_tag_name("foo"), None,);
|
||||
assert_eq!(settings.check_preferred_tag_name("bar"), None);
|
||||
assert_eq!(settings.check_preferred_tag_name("baz"), Some("baz is noop now".to_string()));
|
||||
assert_eq!(
|
||||
settings.check_preferred_tag_name("qux"),
|
||||
Some("Replace tag `@qux` with `@quux`.".to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -361,6 +361,7 @@ mod nextjs {
|
|||
mod jsdoc {
|
||||
pub mod check_access;
|
||||
pub mod check_property_names;
|
||||
pub mod check_tag_names;
|
||||
pub mod empty_tags;
|
||||
pub mod require_property;
|
||||
pub mod require_property_description;
|
||||
|
|
@ -693,6 +694,7 @@ oxc_macros::declare_all_lint_rules! {
|
|||
nextjs::no_before_interactive_script_outside_document,
|
||||
jsdoc::check_access,
|
||||
jsdoc::check_property_names,
|
||||
jsdoc::check_tag_names,
|
||||
jsdoc::empty_tags,
|
||||
jsdoc::require_property,
|
||||
jsdoc::require_property_type,
|
||||
|
|
|
|||
1047
crates/oxc_linter/src/rules/jsdoc/check_tag_names.rs
Normal file
1047
crates/oxc_linter/src/rules/jsdoc/check_tag_names.rs
Normal file
File diff suppressed because it is too large
Load diff
317
crates/oxc_linter/src/snapshots/check_tag_names.snap
Normal file
317
crates/oxc_linter/src/snapshots/check_tag_names.snap
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
---
|
||||
source: crates/oxc_linter/src/tester.rs
|
||||
expression: check_tag_names
|
||||
---
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:2:24]
|
||||
1 │
|
||||
2 │ /** @typoo {string} (fail: invalid name) */
|
||||
· ──────
|
||||
3 │ let a;
|
||||
╰────
|
||||
help: `@typoo` is invalid tag name.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:25]
|
||||
2 │ /**
|
||||
3 │ * @Param (fail: invalid name)
|
||||
· ──────
|
||||
4 │ */
|
||||
╰────
|
||||
help: `@Param` is invalid tag name.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:25]
|
||||
2 │ /**
|
||||
3 │ * @foo (fail: invalid name)
|
||||
· ────
|
||||
4 │ */
|
||||
╰────
|
||||
help: `@foo` is invalid tag name.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:25]
|
||||
2 │ /**
|
||||
3 │ * @arg foo (fail: invalid name, default aliased)
|
||||
· ────
|
||||
4 │ */
|
||||
╰────
|
||||
help: Replace tag `@arg` with `@param`.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:25]
|
||||
2 │ /**
|
||||
3 │ * @param foo (fail: valid name but user preferred)
|
||||
· ──────
|
||||
4 │ */
|
||||
╰────
|
||||
help: Replace tag `@param` with `@arg`.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:25]
|
||||
2 │ /**
|
||||
3 │ * @constructor foo (fail: invalid name and user preferred)
|
||||
· ────────────
|
||||
4 │ */
|
||||
╰────
|
||||
help: Replace tag `@constructor` with `@cons`.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:34]
|
||||
2 │ /**
|
||||
3 │ * @arg foo (fail: invalid name and user preferred)
|
||||
· ────
|
||||
4 │ */
|
||||
╰────
|
||||
help: Replace tag `@arg` with `@somethingDifferent`.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:25]
|
||||
2 │ /**
|
||||
3 │ * @param foo (fail: valid name but user preferred)
|
||||
· ──────
|
||||
4 │ */
|
||||
╰────
|
||||
help: Replace tag `@param` with `@parameter`.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:25]
|
||||
2 │ /**
|
||||
3 │ * @bar foo (fail: invalid name)
|
||||
· ────
|
||||
4 │ */
|
||||
╰────
|
||||
help: `@bar` is invalid tag name.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:25]
|
||||
2 │ /**
|
||||
3 │ * @baz @bar foo (fail: invalid name)
|
||||
· ────
|
||||
4 │ */
|
||||
╰────
|
||||
help: `@baz` is invalid tag name.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:4:27]
|
||||
3 │ * @bar
|
||||
4 │ * @baz (fail: invalid name)
|
||||
· ────
|
||||
5 │ */
|
||||
╰────
|
||||
help: `@baz` is invalid tag name.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:25]
|
||||
2 │ /**
|
||||
3 │ * @todo (fail: valid name but blocked)
|
||||
· ─────
|
||||
4 │ */
|
||||
╰────
|
||||
help: Unexpected tag `@todo`.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:25]
|
||||
2 │ /**
|
||||
3 │ * @todo (fail: valid name but blocked)
|
||||
· ─────
|
||||
4 │ */
|
||||
╰────
|
||||
help: Please resolve to-dos or add to the tracker
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:25]
|
||||
2 │ /**
|
||||
3 │ * @todo (fail: valid name but blocked)
|
||||
· ─────
|
||||
4 │ */
|
||||
╰────
|
||||
help: Please use x-todo instead of todo
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:4:25]
|
||||
3 │ * @property {object} a
|
||||
4 │ * @prop {boolean} b (fail: invalid name, default aliased)
|
||||
· ─────
|
||||
5 │ */
|
||||
╰────
|
||||
help: Replace tag `@prop` with `@property`.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:25]
|
||||
2 │ /**
|
||||
3 │ * @abc foo (fail: invalid name and user preferred)
|
||||
· ────
|
||||
4 │ * @abcd bar
|
||||
╰────
|
||||
help: Replace tag `@abc` with `@abcd`.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:34]
|
||||
2 │ /**
|
||||
3 │ * @abc (fail: invalid name and user preferred)
|
||||
· ────
|
||||
4 │ * @abcd
|
||||
╰────
|
||||
help: Replace tag `@abc` with `@abcd`.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:2:24]
|
||||
1 │
|
||||
2 │ /** @jsx h */
|
||||
· ────
|
||||
3 │ /** @jsxFrag Fragment */
|
||||
╰────
|
||||
help: `@jsx` is invalid tag name.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:24]
|
||||
2 │ /** @jsx h */
|
||||
3 │ /** @jsxFrag Fragment */
|
||||
· ────────
|
||||
4 │ /** @jsxImportSource preact */
|
||||
╰────
|
||||
help: `@jsxFrag` is invalid tag name.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:4:24]
|
||||
3 │ /** @jsxFrag Fragment */
|
||||
4 │ /** @jsxImportSource preact */
|
||||
· ────────────────
|
||||
5 │ /** @jsxRuntime automatic */
|
||||
╰────
|
||||
help: `@jsxImportSource` is invalid tag name.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:5:24]
|
||||
4 │ /** @jsxImportSource preact */
|
||||
5 │ /** @jsxRuntime automatic */
|
||||
· ───────────
|
||||
6 │
|
||||
╰────
|
||||
help: `@jsxRuntime` is invalid tag name.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:21]
|
||||
2 │ /**
|
||||
3 │ * @constructor (fail: invalid name)
|
||||
· ────────────
|
||||
4 │ */
|
||||
╰────
|
||||
help: Replace tag `@constructor` with `@class`.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:25]
|
||||
2 │ /**
|
||||
3 │ * @todo (fail: valid name but blocked)
|
||||
· ─────
|
||||
4 │ */
|
||||
╰────
|
||||
help: Please don't use todo
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:15]
|
||||
2 │ /**
|
||||
3 │ * @module
|
||||
· ───────
|
||||
4 │ * A comment related to the module
|
||||
╰────
|
||||
help: `@module` is redundant outside of ambient(`declare` or `.d.ts`) contexts when using a type system.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:1:5]
|
||||
1 │ /** @type {string} */let a;
|
||||
· ─────
|
||||
2 │
|
||||
╰────
|
||||
help: `@type` is redundant when using a type system.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:4:24]
|
||||
3 │ * Existing comment.
|
||||
4 │ * @type {string}
|
||||
· ─────
|
||||
5 │ */
|
||||
╰────
|
||||
help: `@type` is redundant when using a type system.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:2:22]
|
||||
1 │
|
||||
2 │ /** @typedef {Object} MyObject
|
||||
· ────────
|
||||
3 │ * @property {string} id - my id
|
||||
╰────
|
||||
help: `@typedef` is redundant when using a type system.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:21]
|
||||
2 │ /** @typedef {Object} MyObject
|
||||
3 │ * @property {string} id - my id
|
||||
· ─────────
|
||||
4 │ */
|
||||
╰────
|
||||
help: `@property` is redundant when using a type system.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:21]
|
||||
2 │ /**
|
||||
3 │ * @property {string} id - my id
|
||||
· ─────────
|
||||
4 │ */
|
||||
╰────
|
||||
help: `@property` is redundant when using a type system.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:2:22]
|
||||
1 │
|
||||
2 │ /** @typedef {Object} MyObject */
|
||||
· ────────
|
||||
3 │
|
||||
╰────
|
||||
help: `@typedef` is redundant when using a type system.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:2:22]
|
||||
1 │
|
||||
2 │ /** @typedef {Object} MyObject
|
||||
· ────────
|
||||
3 │ */
|
||||
╰────
|
||||
help: `@typedef` is redundant when using a type system.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:2:24]
|
||||
1 │
|
||||
2 │ /** @abstract */
|
||||
· ─────────
|
||||
3 │ let a;
|
||||
╰────
|
||||
help: `@abstract` is redundant outside of ambient(`declare` or `.d.ts`) contexts when using a type system.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:3:26]
|
||||
2 │ const a = {
|
||||
3 │ /** @abstract */
|
||||
· ─────────
|
||||
4 │ b: true,
|
||||
╰────
|
||||
help: `@abstract` is redundant outside of ambient(`declare` or `.d.ts`) contexts when using a type system.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:2:24]
|
||||
1 │
|
||||
2 │ /** @template */
|
||||
· ─────────
|
||||
3 │ let a;
|
||||
╰────
|
||||
help: `@template` without a name is redundant when using a type system.
|
||||
|
||||
⚠ eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.
|
||||
╭─[check_tag_names.tsx:5:23]
|
||||
4 │ *
|
||||
5 │ * @template
|
||||
· ─────────
|
||||
6 │ */
|
||||
╰────
|
||||
help: `@template` without a name is redundant when using a type system.
|
||||
Loading…
Reference in a new issue