Skip to content

Commit

Permalink
New: Add generic-type-naming (typescript-eslint#111) (typescript-esli…
Browse files Browse the repository at this point in the history
  • Loading branch information
unlight authored and JamesHenry committed Jan 18, 2019
1 parent 2e54be1 commit f5e9c74
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/eslint-plugin-typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Then configure the rules you want to use under the rules section.
* [`typescript/class-name-casing`](./docs/rules/class-name-casing.md) — Require PascalCased class and interface names (`class-name` from TSLint)
* [`typescript/explicit-function-return-type`](./docs/rules/explicit-function-return-type.md) — Require explicit return types on functions and class methods
* [`typescript/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) — Require explicit accessibility modifiers on class properties and methods (`member-access` from TSLint)
* [`typescript/generic-type-naming`](./docs/rules/generic-type-naming.md) — Enforces naming of generic type variables
* [`typescript/interface-name-prefix`](./docs/rules/interface-name-prefix.md) — Require that interface names be prefixed with `I` (`interface-name` from TSLint)
* [`typescript/member-delimiter-style`](./docs/rules/member-delimiter-style.md) — Require a specific member delimiter style for interfaces and type literals
* [`typescript/member-naming`](./docs/rules/member-naming.md) — Enforces naming conventions for class members by visibility.
Expand Down
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 packages/eslint-plugin-typescript/lib/rules/generic-type-naming.js
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
};
}
};
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]+$" })]
}
]
});

0 comments on commit f5e9c74

Please sign in to comment.