Skip to content

Commit 9238a69

Browse files
committed
feat: add ban-untagged-deprecation rule
1 parent b722829 commit 9238a69

File tree

5 files changed

+159
-0
lines changed

5 files changed

+159
-0
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
`@deprecated` tags must provide additional information, such as the reason for
2+
deprecation or suggested alternatives.
3+
4+
### Invalid:
5+
6+
```typescript
7+
/**
8+
* @deprecated
9+
*/
10+
export function oldFunction(): void {}
11+
```
12+
13+
### Valid:
14+
15+
```typescript
16+
/**
17+
* @deprecated since version 2.0. Use `newFunction` instead.
18+
*/
19+
export function oldFunction(): void {}
20+
```

schemas/rules.v1.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"ban-ts-comment",
66
"ban-types",
77
"ban-unknown-rule-code",
8+
"ban-untagged-deprecation",
89
"ban-untagged-ignore",
910
"ban-untagged-todo",
1011
"ban-unused-ignore",

src/rules.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub mod adjacent_overload_signatures;
1212
pub mod ban_ts_comment;
1313
pub mod ban_types;
1414
pub mod ban_unknown_rule_code;
15+
pub mod ban_untagged_deprecation;
1516
pub mod ban_untagged_ignore;
1617
pub mod ban_untagged_todo;
1718
pub mod ban_unused_ignore;
@@ -255,6 +256,7 @@ fn get_all_rules_raw() -> Vec<Box<dyn LintRule>> {
255256
Box::new(ban_ts_comment::BanTsComment),
256257
Box::new(ban_types::BanTypes),
257258
Box::new(ban_unknown_rule_code::BanUnknownRuleCode),
259+
Box::new(ban_untagged_deprecation::BanUntaggedDeprecation),
258260
Box::new(ban_untagged_ignore::BanUntaggedIgnore),
259261
Box::new(ban_untagged_todo::BanUntaggedTodo),
260262
Box::new(ban_unused_ignore::BanUnusedIgnore),
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2+
3+
use super::{Context, LintRule};
4+
use crate::Program;
5+
use deno_ast::swc::common::comments::{Comment, CommentKind};
6+
use deno_ast::{SourceRange, SourceRangedForSpanned};
7+
use once_cell::sync::Lazy;
8+
use regex::Regex;
9+
10+
#[derive(Debug)]
11+
pub struct BanUntaggedDeprecation;
12+
13+
const CODE: &str = "ban-untagged-deprecation";
14+
const MESSAGE: &str = "The @deprecated tag must include descriptive text";
15+
const HINT: &str =
16+
"Provide additional context for the @deprecated tag, e.g., '@deprecated since v2.0'";
17+
18+
impl LintRule for BanUntaggedDeprecation {
19+
fn code(&self) -> &'static str {
20+
CODE
21+
}
22+
23+
fn lint_program_with_ast_view(
24+
&self,
25+
context: &mut Context,
26+
_program: Program,
27+
) {
28+
context
29+
.all_comments()
30+
.flat_map(extract_violated_deprecation_ranges)
31+
.for_each(|range| {
32+
context.add_diagnostic_with_hint(range, CODE, MESSAGE, HINT)
33+
});
34+
}
35+
36+
#[cfg(feature = "docs")]
37+
fn docs(&self) -> &'static str {
38+
include_str!("../../docs/rules/ban_untagged_deprecation.md")
39+
}
40+
}
41+
42+
/// Returns the ranges of invalid `@deprecated` comments in the given comment.
43+
fn extract_violated_deprecation_ranges(comment: &Comment) -> Vec<SourceRange> {
44+
if !is_jsdoc_comment(comment) {
45+
return Vec::new();
46+
}
47+
48+
static INVALID_DEPRECATION_REGEX: Lazy<Regex> = Lazy::new(|| {
49+
Regex::new(r"(?m)^(?:.*\s+|\s*\*\s*)(@deprecated\s*?)$").unwrap()
50+
});
51+
static BLOCK_COMMENT_OPEN_OFFSET: usize = 2; // Length of the "/*".
52+
53+
INVALID_DEPRECATION_REGEX
54+
.captures_iter(&comment.text)
55+
.filter_map(|caps| caps.get(1))
56+
.map(|mat| {
57+
let start = comment.start() + mat.start() + BLOCK_COMMENT_OPEN_OFFSET;
58+
let end = comment.start() + mat.end() + BLOCK_COMMENT_OPEN_OFFSET;
59+
SourceRange::new(start, end)
60+
})
61+
.collect()
62+
}
63+
64+
/// Checks if the given comment is a JSDoc-style comment.
65+
fn is_jsdoc_comment(comment: &Comment) -> bool {
66+
comment.kind == CommentKind::Block && comment.text.starts_with('*')
67+
}
68+
69+
#[cfg(test)]
70+
mod tests {
71+
use super::*;
72+
73+
#[test]
74+
fn ban_untagged_deprecation_valid() {
75+
assert_lint_ok! {
76+
BanUntaggedDeprecation,
77+
// @deprecated tag with additional context is valid.
78+
r#"/** @deprecated since v2.0 */"#,
79+
// @deprecated tag in the middle of comments with additional context is valid.
80+
r#"/**
81+
* @param foo - The input value.
82+
* @public @deprecated since v2.0
83+
* @returns The computed result.
84+
*/"#,
85+
// Line comments are not checked.
86+
r#"// @deprecated "#,
87+
// Non-JSDoc block comments are not checked.
88+
r#"/* @deprecated */"#,
89+
// More than two stars before @deprecated are not treated as JSDoc tag.
90+
r#"/***@deprecated
91+
**@deprecated
92+
***@deprecated
93+
*/"#,
94+
// Invalid JSDoc tags are not treated as @deprecated.
95+
r#"/** @deprecatedtmp */"#,
96+
r#"/** tmp@deprecated */"#,
97+
};
98+
}
99+
100+
#[test]
101+
fn ban_untagged_deprecation_invalid() {
102+
assert_lint_err! {
103+
BanUntaggedDeprecation,
104+
// @deprecated tag without additional texts is invalid.
105+
r#"/** @deprecated */"#: [{ col: 4, line: 1, message: MESSAGE, hint: HINT }],
106+
r#"/**
107+
*@deprecated
108+
*/"#: [{ col: 2, line: 2, message: MESSAGE, hint: HINT }],
109+
r#"/**
110+
* @deprecated
111+
*/"#: [{ col: 3, line: 2, message: MESSAGE, hint: HINT }],
112+
r#"/**
113+
@deprecated
114+
*/"#: [{ col: 0, line: 2, message: MESSAGE, hint: HINT }],
115+
r#"/**
116+
@deprecated
117+
*/"#: [{ col: 3, line: 2, message: MESSAGE, hint: HINT }],
118+
r#"/**
119+
* This function is @deprecated
120+
*/"#: [{ col: 20, line: 2, message: MESSAGE, hint: HINT }],
121+
// Multiple violations in a single JSDoc comment.
122+
r#"/**
123+
* @deprecated
124+
* @deprecated
125+
*/"#: [
126+
{ col: 2, line: 2, message: MESSAGE, hint: HINT },
127+
{ col: 2, line: 3, message: MESSAGE, hint: HINT },
128+
],
129+
}
130+
}
131+
}

www/static/docs.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727
"recommended"
2828
]
2929
},
30+
{
31+
"code": "ban-untagged-deprecation",
32+
"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",
33+
"tags": []
34+
},
3035
{
3136
"code": "ban-untagged-ignore",
3237
"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",

0 commit comments

Comments
 (0)