Skip to content
This repository has been archived by the owner on Mar 25, 2021. It is now read-only.

Commit

Permalink
[new-rule] Added new invalid-void rule (#4736)
Browse files Browse the repository at this point in the history
* [new-rule] Added new no-invalid-void rule

Fixes #4732

* [no-invalid-void] Fixed lint error

* [no-invalid-void] Changed copyright year

* [no-invalid-void] Removed unnecessary checking parent of the node

* [no-invalid-void] Fixed a long string

* [invalid-void] Renamed no-invalid-void rule to invalid-void

* [invalid-void] Added rationale

* [invalid-void] Changed failure string

* [invalid-void] Changed failedKinds type from Array to Set

* [invalid-void] Added new kinds to fail

* [invalid-void] Fixed build for TypeScript < 2.3

https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#generic-parameter-defaults

* [invalid-void] Replaced default generic type of WalkContext and AbstractWalker to undefined from {}
  • Loading branch information
timocov authored and Josh Goldberg committed Jul 6, 2019
1 parent 1d5d624 commit aa2af99
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/configs/all.ts
Expand Up @@ -163,6 +163,7 @@ export const rules = {
"cyclomatic-complexity": true,
eofline: true,
indent: { options: ["spaces"] },
"invalid-void": true,
"linebreak-style": { options: "LF" },
"max-classes-per-file": { options: 1 },
"max-file-line-count": { options: 1000 },
Expand Down
2 changes: 1 addition & 1 deletion src/language/rule/abstractRule.ts
Expand Up @@ -67,7 +67,7 @@ export abstract class AbstractRule implements IRule {
): RuleFailure[];
protected applyWithFunction<T, U>(
sourceFile: ts.SourceFile,
walkFn: (ctx: WalkContext<T | void>, programOrChecker?: U) => void,
walkFn: (ctx: WalkContext<T | undefined>, programOrChecker?: U) => void,
options?: T,
programOrChecker?: U,
): RuleFailure[] {
Expand Down
2 changes: 1 addition & 1 deletion src/language/walker/walkContext.ts
Expand Up @@ -19,7 +19,7 @@ import * as ts from "typescript";

import { Fix, RuleFailure } from "../rule/rule";

export class WalkContext<T = void> {
export class WalkContext<T = undefined> {
public readonly failures: RuleFailure[] = [];

constructor(
Expand Down
2 changes: 1 addition & 1 deletion src/language/walker/walker.ts
Expand Up @@ -27,7 +27,7 @@ export interface IWalker {
getFailures(): RuleFailure[];
}

export abstract class AbstractWalker<T = void> extends WalkContext<T> implements IWalker {
export abstract class AbstractWalker<T = undefined> extends WalkContext<T> implements IWalker {
public abstract walk(sourceFile: ts.SourceFile): void;

public getSourceFile() {
Expand Down
86 changes: 86 additions & 0 deletions src/rules/invalidVoidRule.ts
@@ -0,0 +1,86 @@
/**
* @license
* Copyright 2019 Palantir Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as ts from "typescript";

import * as Lint from "../index";

export class Rule extends Lint.Rules.AbstractRule {
/* tslint:disable:object-literal-sort-keys */
public static metadata: Lint.IRuleMetadata = {
ruleName: "invalid-void",
description: Lint.Utils.dedent`
Disallows usage of \`void\` type outside of return type.
If \`void\` is used as return type, it shouldn't be a part of intersection/union type.`,
rationale: Lint.Utils.dedent`
The \`void\` type means "nothing" or that a function does not return any value,
in contra with implicit \`undefined\` type which means that a function returns a value \`undefined\`.
So "nothing" cannot be mixed with any other types.
If you need this - use \`undefined\` type instead.`,
hasFix: false,
optionsDescription: "Not configurable.",
options: null,
optionExamples: [true],
type: "maintainability",
typescriptOnly: true,
};
/* tslint:enable:object-literal-sort-keys */

public static FAILURE_STRING = "void is not a valid type other than return types";

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, walk);
}
}

const failedKinds = new Set([
ts.SyntaxKind.PropertySignature,
ts.SyntaxKind.PropertyDeclaration,

ts.SyntaxKind.VariableDeclaration,
ts.SyntaxKind.TypeAliasDeclaration,

ts.SyntaxKind.IntersectionType,
ts.SyntaxKind.UnionType,

ts.SyntaxKind.Parameter,
ts.SyntaxKind.TypeParameter,

ts.SyntaxKind.AsExpression,
ts.SyntaxKind.TypeAssertionExpression,

ts.SyntaxKind.TypeOperator,
ts.SyntaxKind.ArrayType,

ts.SyntaxKind.MappedType,
ts.SyntaxKind.ConditionalType,

ts.SyntaxKind.TypeReference,

ts.SyntaxKind.NewExpression,
ts.SyntaxKind.CallExpression,
]);

function walk(ctx: Lint.WalkContext): void {
ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node) {
if (node.kind === ts.SyntaxKind.VoidKeyword && failedKinds.has(node.parent.kind)) {
ctx.addFailureAtNode(node, Rule.FAILURE_STRING);
}

ts.forEachChild(node, cb);
});
}
121 changes: 121 additions & 0 deletions test/rules/invalid-void/test.ts.lint
@@ -0,0 +1,121 @@
function func(): void {}

type NormalType = () => void;

let normalArrow = (): void => { }

let ughThisThing = void 0;

function takeThing(thing: undefined) { }
takeThing(void 0);

function takeVoid(thing: void) { }
~~~~ [0]


const arrowGeneric = <T extends void>(arg: T) => { }
~~~~ [0]
#if typescript >= 2.3.0
const arrowGeneric1 = <T = void>(arg: T) => { }
~~~~ [0]
const arrowGeneric2 = <T extends void = void>(arg: T) => { }
~~~~ [0]
~~~~ [0]
#endif

#if typescript >= 2.3.0
function functionGeneric<T extends void>(arg: T) {}
~~~~ [0]

function functionGeneric1<T = void>(arg: T) {}
~~~~ [0]
function functionGeneric2<T extends void = void>(arg: T) {}
~~~~ [0]
~~~~ [0]
#endif

declare function functionDeclaration<T extends void>(arg: T): void;
~~~~ [0]
#if typescript >= 2.3.0
declare function functionDeclaration1<T = void>(arg: T): void;
~~~~ [0]
declare function functionDeclaration2<T extends void = void>(arg: T): void;
~~~~ [0]
~~~~ [0]
#endif


functionGeneric<void>(undefined);
~~~~ [0]

declare function voidArray(args: void[]): void[];
~~~~ [0]
~~~~ [0]

let value = undefined as void;
~~~~ [0]

let value = <void>undefined;
~~~~ [0]

function takesThings(...things: void[]): void { }
~~~~ [0]

type KeyofVoid = keyof void;
~~~~[0]

interface Interface {
lambda: () => void;
voidProp: void;
~~~~ [0]
}

class ClassName {
private readonly propName: void;
~~~~ [0]
}

let invalidMap: Map<string, void> = new Map<string, void>();
~~~~ [0]
~~~~ [0]

let letVoid: void;
~~~~ [0]

type VoidType = void;
~~~~ [0]

class OtherClassName {
private propName: VoidType;
}

type UnionType = string | number;
type UnionType2 = string | number | void;
~~~~ [0]

type UnionType3 = string | (number & any | (string | void));
~~~~ [0]

type IntersectionType = string & number & void;
~~~~ [0]

#if typescript >= 2.8.0
type MappedType<T> = {
[K in keyof T]: void;
~~~~ [0]
}
type ConditionalType<T> = {
[K in keyof T]: T[K] extends string ? void : string;
~~~~ [0]
}
#endif

#if typescript >= 3.4.0
type ManyVoid = readonly void[];
~~~~ [0]

function foo(arr: readonly void[]) { }
~~~~ [0]
#endif

[0]: void is not a valid type other than return types
5 changes: 5 additions & 0 deletions test/rules/invalid-void/tslint.json
@@ -0,0 +1,5 @@
{
"rules": {
"invalid-void": true
}
}

0 comments on commit aa2af99

Please sign in to comment.