This repository was archived by the owner on Jan 19, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FEAT] [no-unnecessary-class] Add rule (#234)
* Add `no-unnecessary-class` rule * Add metadata * ⬆️ eslint-docs
- Loading branch information
Showing
6 changed files
with
298 additions
and
5 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
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,64 @@ | ||
# Forbids the use of classes as namespaces (no-extraneous-class) | ||
|
||
This rule warns when a class is accidentally used as a namespace. | ||
|
||
## Rule Details | ||
|
||
From TSLint’s docs: | ||
|
||
> Users who come from a Java-style OO language may wrap their utility functions in an extra class, | ||
> instead of putting them at the top level. | ||
Examples of **incorrect** code for this rule: | ||
|
||
```ts | ||
class EmptyClass {} | ||
|
||
class ConstructorOnly { | ||
constructor() { | ||
foo(); | ||
} | ||
} | ||
|
||
// Use an object instead: | ||
class StaticOnly { | ||
static version = 42; | ||
static hello() { | ||
console.log("Hello, world!"); | ||
} | ||
} | ||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```ts | ||
class EmptyClass extends SuperClass {} | ||
|
||
class ParameterProperties { | ||
constructor(public name: string) {} | ||
} | ||
|
||
const StaticOnly = { | ||
version: 42, | ||
hello() { | ||
console.log("Hello, world!"); | ||
}, | ||
}; | ||
``` | ||
|
||
### Options | ||
|
||
This rule accepts a single object option. | ||
|
||
- `constructorOnly: true` will silence warnings about classes containing only a constructor. | ||
- `allowEmpty: true` will silence warnings about empty classes. | ||
- `staticOnly: true` will silence warnings about classes containing only static members. | ||
|
||
## When Not To Use It | ||
|
||
You can disable this rule if you don’t have anyone who would make these kinds of mistakes on your | ||
team or if you use classes as namespaces. | ||
|
||
## Compatibility | ||
|
||
[`no-unnecessary-class`](https://palantir.github.io/tslint/rules/no-unnecessary-class/) from TSLint |
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,100 @@ | ||
/** | ||
* @fileoverview Forbids the use of classes as namespaces | ||
* @author Jed Fox | ||
*/ | ||
"use strict"; | ||
|
||
const util = require("../util"); | ||
|
||
//------------------------------------------------------------------------------ | ||
// Rule Definition | ||
//------------------------------------------------------------------------------ | ||
|
||
module.exports = { | ||
meta: { | ||
docs: { | ||
description: "Forbids the use of classes as namespaces", | ||
extraDescription: [util.tslintRule("no-unnecessary-class")], | ||
category: "Best Practices", | ||
recommended: false, | ||
url: util.metaDocsUrl("no-extraneous-class"), | ||
}, | ||
fixable: null, | ||
schema: [ | ||
{ | ||
type: "object", | ||
additionalProperties: false, | ||
properties: { | ||
allowConstructorOnly: { | ||
type: "boolean", | ||
}, | ||
allowEmpty: { | ||
type: "boolean", | ||
}, | ||
allowStaticOnly: { | ||
type: "boolean", | ||
}, | ||
}, | ||
}, | ||
], | ||
messages: { | ||
empty: "Unexpected empty class.", | ||
onlyStatic: "Unexpected class with only static properties.", | ||
onlyConstructor: "Unexpected class with only a constructor.", | ||
}, | ||
}, | ||
|
||
create(context) { | ||
const { allowConstructorOnly, allowEmpty, allowStaticOnly } = | ||
context.options[0] || {}; | ||
|
||
return { | ||
ClassBody(node) { | ||
const { id, superClass } = node.parent; | ||
|
||
if (superClass) return; | ||
|
||
if (node.body.length === 0) { | ||
if (allowEmpty) return; | ||
context.report({ node: id, messageId: "empty" }); | ||
return; | ||
} | ||
|
||
let onlyStatic = true; | ||
let onlyConstructor = true; | ||
|
||
for (const prop of node.body) { | ||
if (prop.kind === "constructor") { | ||
if ( | ||
prop.value.params.some( | ||
param => param.type === "TSParameterProperty" | ||
) | ||
) { | ||
onlyConstructor = false; | ||
onlyStatic = false; | ||
} | ||
} else { | ||
onlyConstructor = false; | ||
if (!prop.static) { | ||
onlyStatic = false; | ||
} | ||
} | ||
if (!(onlyStatic || onlyConstructor)) break; | ||
} | ||
|
||
if (onlyConstructor) { | ||
if (!allowConstructorOnly) { | ||
context.report({ | ||
node: id, | ||
messageId: "onlyConstructor", | ||
}); | ||
} | ||
return; | ||
} | ||
if (onlyStatic && !allowStaticOnly) { | ||
context.report({ node: id, messageId: "onlyStatic" }); | ||
} | ||
}, | ||
}; | ||
}, | ||
}; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
/** | ||
* @fileoverview Forbids the use of classes as namespaces | ||
* Some tests adapted from https://github.com/palantir/tslint/tree/c7fc99b5/test/rules/no-unnecessary-class | ||
* @author Jed Fox | ||
*/ | ||
"use strict"; | ||
|
||
//------------------------------------------------------------------------------ | ||
// Requirements | ||
//------------------------------------------------------------------------------ | ||
|
||
const rule = require("../../../lib/rules/no-extraneous-class"), | ||
RuleTester = require("eslint").RuleTester; | ||
|
||
const empty = { messageId: "empty", type: "Identifier" }; | ||
const onlyStatic = { messageId: "onlyStatic", type: "Identifier" }; | ||
const onlyConstructor = { messageId: "onlyConstructor", type: "Identifier" }; | ||
|
||
//------------------------------------------------------------------------------ | ||
// Tests | ||
//------------------------------------------------------------------------------ | ||
|
||
const ruleTester = new RuleTester({ | ||
parser: "typescript-eslint-parser", | ||
}); | ||
|
||
ruleTester.run("no-extraneous-class", rule, { | ||
valid: [ | ||
` | ||
class Foo { | ||
public prop = 1; | ||
constructor() {} | ||
} | ||
`.trim(), | ||
` | ||
export class CClass extends BaseClass { | ||
public static helper(): void {} | ||
private static privateHelper(): boolean { | ||
return true; | ||
} | ||
constructor() {} | ||
} | ||
`.trim(), | ||
` | ||
class Foo { | ||
constructor( | ||
public bar: string | ||
) {} | ||
} | ||
`.trim(), | ||
{ | ||
code: "class Foo {}", | ||
options: [{ allowEmpty: true }], | ||
}, | ||
{ | ||
code: ` | ||
class Foo { | ||
constructor() {} | ||
} | ||
`.trim(), | ||
options: [{ allowConstructorOnly: true }], | ||
}, | ||
{ | ||
code: ` | ||
export class Bar { | ||
public static helper(): void {} | ||
private static privateHelper(): boolean { | ||
return true; | ||
} | ||
} | ||
`.trim(), | ||
options: [{ allowStaticOnly: true }], | ||
}, | ||
], | ||
|
||
invalid: [ | ||
{ | ||
code: "class Foo {}", | ||
errors: [empty], | ||
}, | ||
{ | ||
code: ` | ||
class Foo { | ||
public prop = 1; | ||
constructor() { | ||
class Bar { | ||
static PROP = 2; | ||
} | ||
} | ||
} | ||
export class Bar { | ||
public static helper(): void {} | ||
private static privateHelper(): boolean { | ||
return true; | ||
} | ||
} | ||
`.trim(), | ||
errors: [onlyStatic, onlyStatic], | ||
}, | ||
{ | ||
code: ` | ||
class Foo { | ||
constructor() {} | ||
} | ||
`.trim(), | ||
errors: [onlyConstructor], | ||
}, | ||
{ | ||
code: ` | ||
export class AClass { | ||
public static helper(): void {} | ||
private static privateHelper(): boolean { | ||
return true; | ||
} | ||
constructor() { | ||
class nestedClass { | ||
} | ||
} | ||
} | ||
`.trim(), | ||
errors: [onlyStatic, empty], | ||
}, | ||
], | ||
}); |
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