Skip to content
This repository was archived by the owner on Jan 19, 2019. It is now read-only.

Commit 26f2609

Browse files
j-f1JamesHenry
authored andcommitted
New: class-name-casing rule (class-name from TSLint) (#44)
1 parent 9773d50 commit 26f2609

File tree

4 files changed

+225
-0
lines changed

4 files changed

+225
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,6 @@ Then configure the rules you want to use under the rules section.
6262
* [`typescript/member-ordering`](./docs/rules/member-ordering.md) — enforces a standard member declaration order. (`member-ordering` from TSLint)
6363
* [`typescript/no-unused-vars`](./docs/rules/no-unused-vars.md) — prevents TypeScript-specific constructs from being erroneously flagged as unused
6464
* [`typescript/adjacent-overload-signatures`](./docs/rules/adjacent-overload-signatures.md) — enforces member overloads to be consecutive.
65+
* [`typescript/class-name-casing`](./docs/rules/adjacent-overload-signatures.md) - enforces PascalCased class and interface names. (`class-name` from TSLint)
6566
* [`typescript/member-delimiter-style`](./docs/rules/member-delimiter-style.md) - enforces a member delimiter style in interfaces and type literals.
6667
* [`typescript/no-empty-interface`](./docs/rules/no-empty-interface.md) - disallows the declaration of empty interfaces. (`no-empty-interface` from TSLint)

docs/rules/class-name-casing.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Enforces PascalCased class and interface names. (class-name-casing)
2+
3+
This rule enforces PascalCased names for classes and interfaces.
4+
5+
## Rule Details
6+
7+
This rule aims to make it easy to differentiate classes from regular variables at a glance.
8+
9+
Examples of **incorrect** code for this rule:
10+
11+
```ts
12+
13+
class invalidClassName {
14+
15+
}
16+
17+
class Another_Invalid_Class_Name {
18+
19+
}
20+
21+
var bar = class invalidName {}
22+
23+
interface someInterface {}
24+
25+
```
26+
27+
Examples of **correct** code for this rule:
28+
29+
```ts
30+
31+
class ValidClassName {
32+
33+
}
34+
35+
export default class {
36+
37+
}
38+
39+
var foo = class {};
40+
41+
interface SomeInterface {}
42+
43+
```
44+
45+
## When Not To Use It
46+
47+
You should turn off this rule if you do not care about class name casing, or if
48+
you use a different type of casing.
49+
50+
## Further Reading
51+
52+
* [`class-name`](https://palantir.github.io/tslint/rules/class-name/) in [TSLint](https://palantir.github.io/tslint/)

lib/rules/class-name-casing.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* @fileoverview Enforces PascalCased class and interface names.
3+
* @author Jed Fox
4+
*/
5+
"use strict";
6+
7+
//------------------------------------------------------------------------------
8+
// Rule Definition
9+
//------------------------------------------------------------------------------
10+
11+
module.exports = {
12+
meta: {
13+
docs: {
14+
description: "Enforces PascalCased class and interface names.",
15+
category: "Best Practices",
16+
recommended: true
17+
}
18+
},
19+
20+
create(context) {
21+
22+
// variables should be defined here
23+
24+
//----------------------------------------------------------------------
25+
// Helpers
26+
//----------------------------------------------------------------------
27+
28+
/**
29+
* Determine if the identifier name is PascalCased
30+
* @param {string} name The identifier name
31+
* @returns {boolean} Is the name PascalCased?
32+
*/
33+
function isPascalCase(name) {
34+
return name[0].toUpperCase() === name[0] && /^[A-Za-z]+$/.test(name);
35+
}
36+
37+
/**
38+
* Report a class declaration as invalid
39+
* @param {Node} decl The declaration
40+
* @param {Node} [id=classDecl.id] The name of the declaration
41+
* @returns {undefined}
42+
*/
43+
function report(decl, id) {
44+
id = id || decl.id;
45+
46+
let friendlyName;
47+
48+
switch (decl.type) {
49+
case "ClassDeclaration":
50+
case "ClassExpression":
51+
friendlyName = "Class";
52+
break;
53+
case "TSInterfaceDeclaration":
54+
friendlyName = "Interface";
55+
break;
56+
default:
57+
friendlyName = decl.type;
58+
}
59+
60+
context.report({
61+
node: id,
62+
message: `${friendlyName} '${id.name}' must be PascalCased`
63+
});
64+
}
65+
66+
//----------------------------------------------------------------------
67+
// Public
68+
//----------------------------------------------------------------------
69+
70+
return {
71+
72+
"ClassDeclaration, TSInterfaceDeclaration"(node) {
73+
74+
// class expressions (i.e. export default class {}) are OK
75+
if (node.id && !isPascalCase(node.id.name)) {
76+
report(node);
77+
}
78+
},
79+
VariableDeclarator(node) {
80+
if (node.init && node.init.type === "ClassExpression") {
81+
const id = node.id;
82+
83+
if (node.init.id && !isPascalCase(node.init.id.name)) {
84+
report(node.init);
85+
} else if (id && !isPascalCase(id.name)) {
86+
report(node.init, id);
87+
}
88+
}
89+
}
90+
91+
};
92+
}
93+
};

tests/lib/rules/class-name-casing.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* @fileoverview Enforces PascalCased class and interface names.
3+
* @author Jed Fox
4+
*/
5+
"use strict";
6+
7+
//------------------------------------------------------------------------------
8+
// Requirements
9+
//------------------------------------------------------------------------------
10+
11+
const rule = require("../../../lib/rules/class-name-casing"),
12+
RuleTester = require("eslint").RuleTester;
13+
14+
15+
//------------------------------------------------------------------------------
16+
// Tests
17+
//------------------------------------------------------------------------------
18+
19+
const ruleTester = new RuleTester({
20+
parser: "typescript-eslint-parser"
21+
});
22+
23+
ruleTester.run("class-name-casing", rule, {
24+
25+
valid: [
26+
"class ValidClassName {}",
27+
{
28+
code: "export default class {}",
29+
parserOptions: {
30+
sourceType: "module"
31+
}
32+
},
33+
"var Foo = class {};",
34+
"interface SomeInterface {}"
35+
],
36+
37+
invalid: [
38+
{
39+
code: "class invalidClassName {}",
40+
errors: [{
41+
message: "Class 'invalidClassName' must be PascalCased",
42+
line: 1,
43+
column: 7
44+
}]
45+
},
46+
{
47+
code: "class Another_Invalid_Class_Name {}",
48+
errors: [{
49+
message: "Class 'Another_Invalid_Class_Name' must be PascalCased",
50+
line: 1,
51+
column: 7
52+
}]
53+
},
54+
{
55+
code: "var foo = class {};",
56+
errors: [{
57+
message: "Class 'foo' must be PascalCased",
58+
line: 1,
59+
column: 5
60+
}]
61+
},
62+
{
63+
code: "var bar = class invalidName {}",
64+
errors: [{
65+
message: "Class 'invalidName' must be PascalCased",
66+
line: 1,
67+
column: 17
68+
}]
69+
},
70+
{
71+
code: "interface someInterface {}",
72+
errors: [{
73+
message: "Interface 'someInterface' must be PascalCased",
74+
line: 1,
75+
column: 11
76+
}]
77+
}
78+
]
79+
});

0 commit comments

Comments
 (0)