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:
Yuji Sugiura 2024-04-24 01:32:14 +09:00 committed by GitHub
parent 45649db86e
commit d109767330
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 1441 additions and 35 deletions

View file

@ -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())
);
}
}

View file

@ -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,

File diff suppressed because it is too large Load diff

View 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.