Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(lint/noConfusingVoidType): add no-confusing-void-type #184

Merged
merged 7 commits into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/rome_diagnostics_categories/src/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ define_categories! {
"lint/correctness/useYield": "https://biomejs.dev/linter/rules/use-yield",

// nursery
"lint/nursery/noConfusingVoidType": "https://biomejs.dev/linter/rules/no-confusing-void-type",
"lint/nursery/noAccumulatingSpread": "https://biomejs.dev/linter/rules/no-accumulating-spread",
"lint/nursery/noAriaUnsupportedElements": "https://biomejs.dev/linter/rules/no-aria-unsupported-elements",
"lint/nursery/noBannedTypes": "https://biomejs.dev/linter/rules/no-banned-types",
Expand Down
2 changes: 2 additions & 0 deletions crates/rome_js_analyze/src/analyzers/nursery.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

135 changes: 135 additions & 0 deletions crates/rome_js_analyze/src/analyzers/nursery/no_confusing_void_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use biome_analyze::{context::RuleContext, declare_rule, Ast, Rule, RuleDiagnostic};
use rome_console::markup;
use rome_js_syntax::{AnyTsType, JsSyntaxKind};
use rome_rowan::{AstNode, SyntaxNode};

declare_rule! {
///
/// Disallow `void` type outside of generic or return types.
///
/// `void` in TypeScript refers to a function return that is meant to be ignored. Attempting to use a void type outside of a return type or generic type argument is often a sign of programmer error. void can also be misleading for other developers even if used correctly.
///
/// > The `void` type means cannot be mixed with any other types, other than `never`, which accepts all types.
/// > If you think you need this then you probably want the undefined type instead.
///
/// ## Examples
/// ### Invalid
///
/// ```ts,expect_diagnostic
/// type PossibleValues = number | void;
/// type MorePossibleValues = string | ((number & any) | (string | void));
/// ```
///
/// ```ts,expect_diagnostic
/// function logSomething(thing: void) {}
/// ```
///
/// ```ts,expect_diagnostic
/// interface Interface {
/// prop: void;
/// }
/// ```
///
/// ```ts,expect_diagnostic
/// let foo: void;
/// let bar = 1 as unknown as void;
/// let baz = 1 as unknown as void | string;
/// ```
///
/// ### Valid
///
/// ```ts
/// function foo(): void {};
/// function doSomething(this: void) {}
/// function printArg<T = void>(arg: T) {}
/// logAndReturn<void>(undefined);
/// let voidPromise: Promise<void> = new Promise<void>(() => { });
/// let voidMap: Map<string, void> = new Map<string, void>();
/// ```
///
pub(crate) NoConfusingVoidType {
version: "1.0.0",
name: "noConfusingVoidType",
recommended: false,
}
}

type Language = <AnyTsType as AstNode>::Language;

// We only focus on union type
pub enum VoidTypeIn {
Union,
Unknown,
}

impl Rule for NoConfusingVoidType {
type Query = Ast<AnyTsType>;
type State = VoidTypeIn;
type Signals = Option<Self::State>;
type Options = ();

fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let node = ctx.query();

if let AnyTsType::TsVoidType(node) = node {
let result = node_in(node.syntax());
return result;
}

None
}
fn diagnostic(ctx: &RuleContext<Self>, state: &Self::State) -> Option<RuleDiagnostic> {
let node = ctx.query();
return Some(RuleDiagnostic::new(
rule_category!(),
node.range(),
markup! {{match_message(state)}},
));
}
}

fn node_in(node: &SyntaxNode<Language>) -> Option<VoidTypeIn> {
for parent in node.parent()?.ancestors() {
match parent.kind() {
// (string | void)
// string | void
// string & void
// arg: void
// fn<T = void>() {}
JsSyntaxKind::TS_PARENTHESIZED_TYPE
| JsSyntaxKind::TS_UNION_TYPE_VARIANT_LIST
| JsSyntaxKind::TS_INTERSECTION_TYPE_ELEMENT_LIST
| JsSyntaxKind::TS_TYPE_ANNOTATION
| JsSyntaxKind::TS_DEFAULT_TYPE_CLAUSE => {
continue;
}

JsSyntaxKind::TS_UNION_TYPE => {
return Some(VoidTypeIn::Union);
}

// function fn(this: void) {}
// fn(): void;
// fn<T = void>() {}
// Promise<void>
JsSyntaxKind::TS_THIS_PARAMETER
| JsSyntaxKind::TS_RETURN_TYPE_ANNOTATION
| JsSyntaxKind::TS_TYPE_PARAMETER
| JsSyntaxKind::TS_TYPE_ARGUMENT_LIST => {
return None;
}

_ => return Some(VoidTypeIn::Unknown),
}
}

Some(VoidTypeIn::Unknown)
}

fn match_message(node: &VoidTypeIn) -> String {
if matches!(node, VoidTypeIn::Union) {
return "void is not valid as a constituent in a union type".into();
}

"void is only valid as a return type or a type argument in generic type".into()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
type PossibleValues = string | number | void;
type MorePossibleValues = string | ((number & any) | (string | void));

function logSomething(thing: void) {}
function printArg<T = void>(arg: T) {}
logAndReturn<void>(undefined);

let voidPromise: Promise<void> = new Promise<void>(() => { });
let voidMap: Map<string, void> = new Map<string, void>();

interface Interface {
prop: void;
}

class MyClass {
private readonly propName: void;
}

let foo: void;
let bar = 1 as unknown as void;
let baz = 1 as unknown as void | string;
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
---
source: crates/rome_js_analyze/tests/spec_tests.rs
assertion_line: 80
expression: invalid.ts
---
# Input
```js
type PossibleValues = string | number | void;
type MorePossibleValues = string | ((number & any) | (string | void));

function logSomething(thing: void) {}
function printArg<T = void>(arg: T) {}
logAndReturn<void>(undefined);

let voidPromise: Promise<void> = new Promise<void>(() => { });
let voidMap: Map<string, void> = new Map<string, void>();

interface Interface {
prop: void;
}

class MyClass {
private readonly propName: void;
}

let foo: void;
let bar = 1 as unknown as void;
let baz = 1 as unknown as void | string;

```

# Diagnostics
```
invalid.ts:1:41 lint/nursery/noConfusingVoidType ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

! void is not valid as a constituent in a union type

> 1 │ type PossibleValues = string | number | void;
│ ^^^^
2 │ type MorePossibleValues = string | ((number & any) | (string | void));
3 │


```

```
invalid.ts:2:64 lint/nursery/noConfusingVoidType ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

! void is not valid as a constituent in a union type

1 │ type PossibleValues = string | number | void;
> 2 │ type MorePossibleValues = string | ((number & any) | (string | void));
│ ^^^^
3 │
4 │ function logSomething(thing: void) {}


```

```
invalid.ts:4:30 lint/nursery/noConfusingVoidType ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

! void is only valid as a return type or a type argument in generic type

2 │ type MorePossibleValues = string | ((number & any) | (string | void));
3 │
> 4 │ function logSomething(thing: void) {}
│ ^^^^
5 │ function printArg<T = void>(arg: T) {}
6 │ logAndReturn<void>(undefined);


```

```
invalid.ts:12:8 lint/nursery/noConfusingVoidType ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

! void is only valid as a return type or a type argument in generic type

11 │ interface Interface {
> 12 │ prop: void;
│ ^^^^
13 │ }
14 │


```

```
invalid.ts:16:29 lint/nursery/noConfusingVoidType ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

! void is only valid as a return type or a type argument in generic type

15 │ class MyClass {
> 16 │ private readonly propName: void;
│ ^^^^
17 │ }
18 │


```

```
invalid.ts:19:10 lint/nursery/noConfusingVoidType ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

! void is only valid as a return type or a type argument in generic type

17 │ }
18 │
> 19 │ let foo: void;
│ ^^^^
20 │ let bar = 1 as unknown as void;
21 │ let baz = 1 as unknown as void | string;


```

```
invalid.ts:20:27 lint/nursery/noConfusingVoidType ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

! void is only valid as a return type or a type argument in generic type

19 │ let foo: void;
> 20 │ let bar = 1 as unknown as void;
│ ^^^^
21 │ let baz = 1 as unknown as void | string;
22 │


```

```
invalid.ts:21:27 lint/nursery/noConfusingVoidType ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

! void is not valid as a constituent in a union type

19 │ let foo: void;
20 │ let bar = 1 as unknown as void;
> 21 │ let baz = 1 as unknown as void | string;
│ ^^^^
22 │


```


Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
function Foo(): void {}
function doSomething(this: void) {}
function printArg<T = void>(arg: T) {}
logAndReturn<void>(undefined);

let voidPromise: Promise<void> = new Promise<void>(() => { });
let voidMap: Map<string, void> = new Map<string, void>();
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
source: crates/rome_js_analyze/tests/spec_tests.rs
assertion_line: 80
expression: valid.ts
---
# Input
```js
function Foo(): void {}
function doSomething(this: void) {}
function printArg<T = void>(arg: T) {}
logAndReturn<void>(undefined);

let voidPromise: Promise<void> = new Promise<void>(() => { });
let voidMap: Map<string, void> = new Map<string, void>();

```


Loading