-
-
Notifications
You must be signed in to change notification settings - Fork 512
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(linter): implement noRestrictedTypes (#3585)
- Loading branch information
Showing
15 changed files
with
682 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
204 changes: 112 additions & 92 deletions
204
crates/biome_configuration/src/analyzer/linter/rules.rs
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
130 changes: 130 additions & 0 deletions
130
crates/biome_js_analyze/src/lint/nursery/no_restricted_types.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
use crate::JsRuleAction; | ||
use ::serde::{Deserialize, Serialize}; | ||
use biome_analyze::context::RuleContext; | ||
use biome_analyze::{ | ||
declare_lint_rule, ActionCategory, Ast, FixKind, Rule, RuleDiagnostic, RuleSource, | ||
}; | ||
use biome_console::markup; | ||
use biome_deserialize_macros::Deserializable; | ||
use biome_js_factory::make; | ||
use biome_js_syntax::TsReferenceType; | ||
use biome_rowan::AstNode; | ||
use biome_rowan::BatchMutationExt; | ||
use biome_unicode_table::is_js_ident; | ||
use rustc_hash::FxHashMap; | ||
|
||
#[cfg(feature = "schemars")] | ||
use schemars::JsonSchema; | ||
|
||
declare_lint_rule! { | ||
/// Disallow user defined types. | ||
/// | ||
/// This rule allows you to specify type names that you don’t want to use in your application. | ||
/// | ||
/// To prevent use of commonly misleading types, you can refer to [noBannedTypes](https://biomejs.dev/linter/rules/no-banned-types/) | ||
/// | ||
/// ## Options | ||
/// | ||
/// Use the options to specify additional types that you want to restrict in your | ||
/// source code. | ||
/// | ||
/// ```json | ||
/// { | ||
/// "//": "...", | ||
/// "options": { | ||
/// "types": { | ||
/// "Foo": { | ||
/// "message": "Only bar is allowed", | ||
/// "use": "bar" | ||
/// }, | ||
/// "OldAPI": { | ||
/// "message": "Use NewAPI instead" | ||
/// } | ||
/// } | ||
/// } | ||
/// } | ||
/// ``` | ||
/// | ||
/// In the example above, the rule will emit a diagnostics if tried to use `Foo` or `OldAPI` are used. | ||
/// | ||
pub NoRestrictedTypes { | ||
version: "next", | ||
name: "noRestrictedTypes", | ||
language: "ts", | ||
sources: &[ | ||
RuleSource::EslintTypeScript("no-restricted-types"), | ||
], | ||
recommended: false, | ||
fix_kind: FixKind::Safe, | ||
} | ||
} | ||
|
||
impl Rule for NoRestrictedTypes { | ||
type Query = Ast<TsReferenceType>; | ||
type State = CustomRestrictedType; | ||
type Signals = Option<Self::State>; | ||
type Options = NoRestrictedTypesOptions; | ||
|
||
fn run(ctx: &RuleContext<Self>) -> Self::Signals { | ||
let ts_reference_type = ctx.query(); | ||
let options = ctx.options(); | ||
|
||
let ts_any_name = ts_reference_type.name().ok()?; | ||
let identifier = ts_any_name.as_js_reference_identifier()?; | ||
let identifier_token = identifier.value_token().ok()?; | ||
let token_name = identifier_token.text_trimmed(); | ||
|
||
let restricted_type = options.types.get(token_name)?.clone(); | ||
|
||
Some(restricted_type) | ||
} | ||
|
||
fn diagnostic(ctx: &RuleContext<Self>, state: &Self::State) -> Option<RuleDiagnostic> { | ||
Some(RuleDiagnostic::new( | ||
rule_category!(), | ||
ctx.query().range(), | ||
markup! { {state.message} }.to_owned(), | ||
)) | ||
} | ||
|
||
fn action(ctx: &RuleContext<Self>, state: &Self::State) -> Option<JsRuleAction> { | ||
let suggested_type = state.use_instead.as_ref()?; | ||
if !is_js_ident(suggested_type) { | ||
return None; | ||
} | ||
|
||
let mut mutation = ctx.root().begin(); | ||
|
||
let ts_reference_type = ctx.query(); | ||
let ts_any_name = ts_reference_type.name().ok()?; | ||
let identifier = ts_any_name.as_js_reference_identifier()?; | ||
let prev_token = identifier.value_token().ok()?; | ||
|
||
let new_token = make::ident(suggested_type); | ||
|
||
mutation.replace_element(prev_token.into(), new_token.into()); | ||
|
||
Some(JsRuleAction::new( | ||
ActionCategory::QuickFix, | ||
ctx.metadata().applicability(), | ||
markup! { "Use '"{suggested_type}"' instead" }.to_owned(), | ||
mutation, | ||
)) | ||
} | ||
} | ||
|
||
#[derive(Clone, Debug, Default, Deserializable, Deserialize, Serialize, Eq, PartialEq)] | ||
#[cfg_attr(feature = "schemars", derive(JsonSchema))] | ||
#[serde(rename_all = "camelCase", deny_unknown_fields)] | ||
pub struct NoRestrictedTypesOptions { | ||
types: FxHashMap<String, CustomRestrictedType>, | ||
} | ||
|
||
#[derive(Debug, Clone, Default, Deserializable, Deserialize, Serialize, Eq, PartialEq)] | ||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] | ||
#[serde(rename_all = "camelCase", deny_unknown_fields)] | ||
pub struct CustomRestrictedType { | ||
message: String, | ||
#[serde(rename = "use")] | ||
use_instead: Option<String>, | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
28 changes: 28 additions & 0 deletions
28
crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalidCustom.options.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
{ | ||
"$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", | ||
"linter": { | ||
"enabled": true, | ||
"rules": { | ||
"nursery": { | ||
"noRestrictedTypes": { | ||
"level": "error", | ||
"options": { | ||
"types": { | ||
"CustomType": { | ||
"message": "Only CustomType2 is allowed", | ||
"use": "CustomType2" | ||
}, | ||
"Bar": { | ||
"message": "Replace Bar with Foo" | ||
}, | ||
"InvalidUse": { | ||
"message": "Do not use this type", | ||
"use": "@" | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalidCustom.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
type CustomType = unknown | ||
function fn2(arg: CustomType) { | ||
return arg; | ||
} | ||
|
||
|
||
class Foo<T extends CustomType = String> extends Bar<CustomType> implements CustomType<Object> { | ||
constructor(foo: String | Object) {} | ||
|
||
exit(): CustomType<String> { | ||
const foo: String = 1 as CustomType; | ||
} | ||
} | ||
|
||
|
||
const foo: Bar = 1; | ||
|
||
const identifier: InvalidUse = 'foo'; |
Oops, something went wrong.