forked from typescript-eslint/typescript-eslint
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New: Add generic-type-naming (typescript-eslint#111) (typescript-esli…
- Loading branch information
1 parent
2e54be1
commit f5e9c74
Showing
4 changed files
with
188 additions
and
0 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
42 changes: 42 additions & 0 deletions
42
packages/eslint-plugin-typescript/docs/rules/generic-type-naming.md
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,42 @@ | ||
# Enforces naming of generic type variables (generic-type-naming) | ||
|
||
It can be helpful to enforce a consistent naming style for generic type variables used within a type. | ||
For example, prefixing them with `T` and ensuring a somewhat descriptive name, or enforcing Hungarian notation. | ||
|
||
## Rule Details | ||
|
||
This rule allows you to enforce conventions over type variables. By default, it does nothing. | ||
|
||
## Options | ||
|
||
The rule takes a single string option, which is a regular expression that type variables should match. | ||
|
||
Examples of **correct** code with a configuration of `'^T[A-Z][a-zA-Z]+$'`: | ||
|
||
```typescript | ||
type ReadOnly<TType extends object> = { | ||
readonly [TKey in keyof TType]: TType[TKey]; | ||
} | ||
|
||
interface SimpleMap<TValue> { | ||
[key: string]: TValue; | ||
} | ||
``` | ||
|
||
Examples of **incorrect** code with a configuration of `'^T[A-Z][a-zA-Z]+$'`: | ||
|
||
```typescript | ||
type ReadOnly<T extends object> = { | ||
readonly [Key in keyof T]: T[Key]; | ||
} | ||
|
||
interface SimpleMap<T> { | ||
[key: string]: T; | ||
} | ||
``` | ||
|
||
## When Not To Use It | ||
If you do not want to enforce a naming convention for type variables. | ||
|
||
## Further Reading | ||
- [TypeScript Generics](https://www.typescriptlang.org/docs/handbook/generics.html) |
71 changes: 71 additions & 0 deletions
71
packages/eslint-plugin-typescript/lib/rules/generic-type-naming.js
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,71 @@ | ||
/** | ||
* @fileoverview Enforces naming of generic type variables. | ||
*/ | ||
"use strict"; | ||
|
||
/** | ||
* | ||
* @param {any} context ESLint context | ||
* @param {string} rule Option | ||
* @returns {Function} Node's visitor function | ||
*/ | ||
function createTypeParameterChecker(context, rule) { | ||
const regex = new RegExp(rule); | ||
|
||
return function checkTypeParameters(pnode) { | ||
const params = pnode.typeParameters && pnode.typeParameters.params; | ||
|
||
if (!Array.isArray(params) || params.length === 0) { | ||
return; | ||
} | ||
params.forEach(node => { | ||
const type = node.type; | ||
|
||
if (type === "TSTypeParameter" || type === "TypeParameter") { | ||
const name = node.name; | ||
|
||
if (name && !regex.test(name)) { | ||
const data = { name, rule }; | ||
|
||
context.report({ | ||
node, | ||
message: | ||
"Type parameter {{name}} does not match rule {{rule}}", | ||
data | ||
}); | ||
} | ||
} | ||
}); | ||
}; | ||
} | ||
|
||
module.exports = { | ||
meta: { | ||
docs: { | ||
description: "Enforces naming of generic type variables", | ||
category: "TypeScript", | ||
url: | ||
"https://github.com/nzakas/eslint-plugin-typescript/blob/master/docs/rules/generic-type-naming.md" | ||
} | ||
}, | ||
|
||
create(context) { | ||
const rule = context.options[0]; | ||
|
||
if (!rule) { | ||
return {}; | ||
} | ||
|
||
const checkTypeParameters = createTypeParameterChecker(context, rule); | ||
|
||
return { | ||
VariableDeclarator: checkTypeParameters, | ||
ClassDeclaration: checkTypeParameters, | ||
InterfaceDeclaration: checkTypeParameters, | ||
TSInterfaceDeclaration: checkTypeParameters, | ||
FunctionDeclaration: checkTypeParameters, | ||
TSCallSignature: checkTypeParameters, | ||
CallSignature: checkTypeParameters | ||
}; | ||
} | ||
}; |
74 changes: 74 additions & 0 deletions
74
packages/eslint-plugin-typescript/tests/lib/rules/generic-type-naming.js
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,74 @@ | ||
"use strict"; | ||
|
||
//------------------------------------------------------------------------------ | ||
// Requirements | ||
//------------------------------------------------------------------------------ | ||
|
||
const rule = require("../../../lib/rules/generic-type-naming"), | ||
RuleTester = require("eslint").RuleTester; | ||
|
||
//------------------------------------------------------------------------------ | ||
// Tests | ||
//------------------------------------------------------------------------------ | ||
|
||
const ruleTester = new RuleTester({ | ||
parser: "typescript-eslint-parser" | ||
}); | ||
|
||
const messagePattern = "Type parameter {{name}} does not match rule {{rule}}"; | ||
|
||
/** | ||
* Creates error object | ||
* @param {Object} data Data | ||
* @returns {Object} Object with message property | ||
*/ | ||
function error(data) { | ||
let message = messagePattern; | ||
|
||
Object.keys(data).forEach(key => { | ||
message = message.replace(new RegExp(`{{${key}}}`, "g"), data[key]); | ||
}); | ||
return { message }; | ||
} | ||
|
||
ruleTester.run("generic-type-naming", rule, { | ||
valid: [ | ||
{ code: "class<T,U,V> { }", options: [] }, | ||
{ code: "type ReadOnly<T extends object> = {}", options: [] }, | ||
{ code: "interface SimpleMap<V> { }", options: [] }, | ||
{ code: "function get<T>() {}", options: [] }, | ||
{ code: "interface GenericIdentityFn { <T>(arg: T): T }", options: [] }, | ||
{ code: "class<x> { }", options: ["^x+$"] }, | ||
{ | ||
code: "class CounterContainer extends Container<Counter> { }", | ||
options: ["^T$"] | ||
} | ||
], | ||
invalid: [ | ||
{ | ||
code: "class<x> { }", | ||
options: ["^[A-Z]+$"], | ||
errors: [error({ name: "x", rule: "^[A-Z]+$" })] | ||
}, | ||
{ | ||
code: "interface SimpleMap<x> { }", | ||
options: ["^[A-Z]+$"], | ||
errors: [error({ name: "x", rule: "^[A-Z]+$" })] | ||
}, | ||
{ | ||
code: "type R<x> = {}", | ||
options: ["^[A-Z]+$"], | ||
errors: [error({ name: "x", rule: "^[A-Z]+$" })] | ||
}, | ||
{ | ||
code: "function get<x>() {}", | ||
options: ["^[A-Z]+$"], | ||
errors: [error({ name: "x", rule: "^[A-Z]+$" })] | ||
}, | ||
{ | ||
code: "interface GenericIdentityFn { <x>(arg: x): x }", | ||
options: ["^[A-Z]+$"], | ||
errors: [error({ name: "x", rule: "^[A-Z]+$" })] | ||
} | ||
] | ||
}); |