Skip to content

Commit cc08711

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

File tree

5 files changed

+165
-0
lines changed

5 files changed

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

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)