Skip to content

Commit

Permalink
feat: add ban-untagged-deprecation rule
Browse files Browse the repository at this point in the history
  • Loading branch information
takaebato committed Dec 7, 2024
1 parent b722829 commit 9238a69
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 0 deletions.
20 changes: 20 additions & 0 deletions docs/rules/ban_untagged_deprecation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
`@deprecated` tags must provide additional information, such as the reason for
deprecation or suggested alternatives.

### Invalid:

```typescript
/**
* @deprecated
*/
export function oldFunction(): void {}
```

### Valid:

```typescript
/**
* @deprecated since version 2.0. Use `newFunction` instead.
*/
export function oldFunction(): void {}
```
1 change: 1 addition & 0 deletions schemas/rules.v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"ban-ts-comment",
"ban-types",
"ban-unknown-rule-code",
"ban-untagged-deprecation",
"ban-untagged-ignore",
"ban-untagged-todo",
"ban-unused-ignore",
Expand Down
2 changes: 2 additions & 0 deletions src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod adjacent_overload_signatures;
pub mod ban_ts_comment;
pub mod ban_types;
pub mod ban_unknown_rule_code;
pub mod ban_untagged_deprecation;
pub mod ban_untagged_ignore;
pub mod ban_untagged_todo;
pub mod ban_unused_ignore;
Expand Down Expand Up @@ -255,6 +256,7 @@ fn get_all_rules_raw() -> Vec<Box<dyn LintRule>> {
Box::new(ban_ts_comment::BanTsComment),
Box::new(ban_types::BanTypes),
Box::new(ban_unknown_rule_code::BanUnknownRuleCode),
Box::new(ban_untagged_deprecation::BanUntaggedDeprecation),
Box::new(ban_untagged_ignore::BanUntaggedIgnore),
Box::new(ban_untagged_todo::BanUntaggedTodo),
Box::new(ban_unused_ignore::BanUnusedIgnore),
Expand Down
131 changes: 131 additions & 0 deletions src/rules/ban_untagged_deprecation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

use super::{Context, LintRule};
use crate::Program;
use deno_ast::swc::common::comments::{Comment, CommentKind};
use deno_ast::{SourceRange, SourceRangedForSpanned};
use once_cell::sync::Lazy;
use regex::Regex;

#[derive(Debug)]
pub struct BanUntaggedDeprecation;

const CODE: &str = "ban-untagged-deprecation";
const MESSAGE: &str = "The @deprecated tag must include descriptive text";
const HINT: &str =
"Provide additional context for the @deprecated tag, e.g., '@deprecated since v2.0'";

impl LintRule for BanUntaggedDeprecation {
fn code(&self) -> &'static str {
CODE
}

fn lint_program_with_ast_view(
&self,
context: &mut Context,
_program: Program,
) {
context
.all_comments()
.flat_map(extract_violated_deprecation_ranges)
.for_each(|range| {
context.add_diagnostic_with_hint(range, CODE, MESSAGE, HINT)
});
}

#[cfg(feature = "docs")]
fn docs(&self) -> &'static str {
include_str!("../../docs/rules/ban_untagged_deprecation.md")
}
}

/// Returns the ranges of invalid `@deprecated` comments in the given comment.
fn extract_violated_deprecation_ranges(comment: &Comment) -> Vec<SourceRange> {
if !is_jsdoc_comment(comment) {
return Vec::new();
}

static INVALID_DEPRECATION_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?m)^(?:.*\s+|\s*\*\s*)(@deprecated\s*?)$").unwrap()
});
static BLOCK_COMMENT_OPEN_OFFSET: usize = 2; // Length of the "/*".

INVALID_DEPRECATION_REGEX
.captures_iter(&comment.text)
.filter_map(|caps| caps.get(1))
.map(|mat| {
let start = comment.start() + mat.start() + BLOCK_COMMENT_OPEN_OFFSET;
let end = comment.start() + mat.end() + BLOCK_COMMENT_OPEN_OFFSET;
SourceRange::new(start, end)
})
.collect()
}

/// Checks if the given comment is a JSDoc-style comment.
fn is_jsdoc_comment(comment: &Comment) -> bool {
comment.kind == CommentKind::Block && comment.text.starts_with('*')
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn ban_untagged_deprecation_valid() {
assert_lint_ok! {
BanUntaggedDeprecation,
// @deprecated tag with additional context is valid.
r#"/** @deprecated since v2.0 */"#,
// @deprecated tag in the middle of comments with additional context is valid.
r#"/**
* @param foo - The input value.
* @public @deprecated since v2.0
* @returns The computed result.
*/"#,
// Line comments are not checked.
r#"// @deprecated "#,
// Non-JSDoc block comments are not checked.
r#"/* @deprecated */"#,
// More than two stars before @deprecated are not treated as JSDoc tag.
r#"/***@deprecated
**@deprecated
***@deprecated
*/"#,
// Invalid JSDoc tags are not treated as @deprecated.
r#"/** @deprecatedtmp */"#,
r#"/** tmp@deprecated */"#,
};
}

#[test]
fn ban_untagged_deprecation_invalid() {
assert_lint_err! {
BanUntaggedDeprecation,
// @deprecated tag without additional texts is invalid.
r#"/** @deprecated */"#: [{ col: 4, line: 1, message: MESSAGE, hint: HINT }],
r#"/**
*@deprecated
*/"#: [{ col: 2, line: 2, message: MESSAGE, hint: HINT }],
r#"/**
* @deprecated
*/"#: [{ col: 3, line: 2, message: MESSAGE, hint: HINT }],
r#"/**
@deprecated
*/"#: [{ col: 0, line: 2, message: MESSAGE, hint: HINT }],
r#"/**
@deprecated
*/"#: [{ col: 3, line: 2, message: MESSAGE, hint: HINT }],
r#"/**
* This function is @deprecated
*/"#: [{ col: 20, line: 2, message: MESSAGE, hint: HINT }],
// Multiple violations in a single JSDoc comment.
r#"/**
* @deprecated
* @deprecated
*/"#: [
{ col: 2, line: 2, message: MESSAGE, hint: HINT },
{ col: 2, line: 3, message: MESSAGE, hint: HINT },
],
}
}
}
5 changes: 5 additions & 0 deletions www/static/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
"recommended"
]
},
{
"code": "ban-untagged-deprecation",
"docs": "`@deprecated` tags must provide additional information, such as the reason for\ndeprecation or suggested alternatives.\n\n### Invalid:\n\n```typescript\n/**\n * @deprecated\n */\nexport function oldFunction(): void {}\n```\n\n### Valid:\n\n```typescript\n/**\n * @deprecated since version 2.0. Use `newFunction` instead.\n */\nexport function oldFunction(): void {}\n```\n",
"tags": []
},
{
"code": "ban-untagged-ignore",
"docs": "Requires `deno-lint-ignore` to be annotated with one or more rule names.\n\nIgnoring all rules can mask unexpected or future problems. Therefore you need to\nexplicitly specify which rule(s) are to be ignored.\n\n### Invalid:\n\n```typescript\n// deno-lint-ignore\nexport function duplicateArgumentsFn(a, b, a) {}\n```\n\n### Valid:\n\n```typescript\n// deno-lint-ignore no-dupe-args\nexport function duplicateArgumentsFn(a, b, a) {}\n```\n",
Expand Down

0 comments on commit 9238a69

Please sign in to comment.