Skip to content

Commit ad86429

Browse files
committed
Add ban-untagged-deprecation rule
1 parent b722829 commit ad86429

File tree

5 files changed

+160
-0
lines changed

5 files changed

+160
-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: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
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+
for comment in context.all_comments() {
29+
extract_violated_deprecation_ranges(comment)
30+
.into_iter()
31+
.for_each(|range| {
32+
context.add_diagnostic_with_hint(range, CODE, MESSAGE, HINT)
33+
});
34+
}
35+
}
36+
37+
#[cfg(feature = "docs")]
38+
fn docs(&self) -> &'static str {
39+
include_str!("../../docs/rules/ban_untagged_deprecation.md")
40+
}
41+
}
42+
43+
/// Returns the ranges of invalid `@deprecated` comments in the given comment.
44+
fn extract_violated_deprecation_ranges(comment: &Comment) -> Vec<SourceRange> {
45+
if !is_jsdoc_comment(comment) {
46+
return Vec::new();
47+
}
48+
49+
static INVALID_DEPRECATION_REGEX: Lazy<Regex> = Lazy::new(|| {
50+
Regex::new(r"(?m)^(?:.*\s+|\s*\*\s*)(@deprecated\s*?)$").unwrap()
51+
});
52+
static BLOCK_COMMENT_OPEN_OFFSET: usize = 2; // Length of the "/*".
53+
54+
INVALID_DEPRECATION_REGEX
55+
.captures_iter(&comment.text)
56+
.filter_map(|caps| caps.get(1))
57+
.map(|mat| {
58+
let start = comment.start() + mat.start() + BLOCK_COMMENT_OPEN_OFFSET;
59+
let end = comment.start() + mat.end() + BLOCK_COMMENT_OPEN_OFFSET;
60+
SourceRange::new(start, end)
61+
})
62+
.collect()
63+
}
64+
65+
/// Checks if the given comment is a JSDoc-style comment.
66+
fn is_jsdoc_comment(comment: &Comment) -> bool {
67+
comment.kind == CommentKind::Block && comment.text.starts_with('*')
68+
}
69+
70+
#[cfg(test)]
71+
mod tests {
72+
use super::*;
73+
74+
#[test]
75+
fn ban_untagged_deprecation_valid() {
76+
assert_lint_ok! {
77+
BanUntaggedDeprecation,
78+
// @deprecated tag with additional context is valid.
79+
r#"/** @deprecated since v2.0 */"#,
80+
// @deprecated tag in the middle of comments with additional context is valid.
81+
r#"/**
82+
* @param foo - The input value.
83+
* @public @deprecated since v2.0
84+
* @returns The computed result.
85+
*/"#,
86+
// Line comments are not checked.
87+
r#"// @deprecated "#,
88+
// Non-JSDoc block comments are not checked.
89+
r#"/* @deprecated */"#,
90+
// More than two stars before @deprecated are not treated as JSDoc tag.
91+
r#"/***@deprecated
92+
**@deprecated
93+
***@deprecated
94+
/"#,
95+
// Invalid JSDoc tags are not treated as @deprecated.
96+
r#"/** @deprecatedtmp */"#,
97+
r#"/** tmp@deprecated */"#,
98+
};
99+
}
100+
101+
#[test]
102+
fn ban_untagged_deprecation_invalid() {
103+
assert_lint_err! {
104+
BanUntaggedDeprecation,
105+
// @deprecated tag without additional texts is invalid.
106+
r#"/** @deprecated */"#: [{ col: 4, line: 1, message: MESSAGE, hint: HINT }],
107+
r#"/**
108+
*@deprecated
109+
*/"#: [{ col: 2, line: 2, message: MESSAGE, hint: HINT }],
110+
r#"/**
111+
* @deprecated
112+
*/"#: [{ col: 3, line: 2, message: MESSAGE, hint: HINT }],
113+
r#"/**
114+
@deprecated
115+
*/"#: [{ col: 0, line: 2, message: MESSAGE, hint: HINT }],
116+
r#"/**
117+
@deprecated
118+
*/"#: [{ col: 3, line: 2, message: MESSAGE, hint: HINT }],
119+
r#"/**
120+
* This function is @deprecated
121+
*/"#: [{ col: 20, line: 2, message: MESSAGE, hint: HINT }],
122+
// Multiple violations in a single JSDoc comment.
123+
r#"/**
124+
* @deprecated
125+
* @deprecated
126+
*/"#: [
127+
{ col: 2, line: 2, message: MESSAGE, hint: HINT },
128+
{ col: 2, line: 3, message: MESSAGE, hint: HINT },
129+
],
130+
}
131+
}
132+
}

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\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)