Skip to content

Commit

Permalink
feat: support array and function types
Browse files Browse the repository at this point in the history
  • Loading branch information
bradzacher committed Dec 11, 2019
1 parent ac162f4 commit 278a25a
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 30 deletions.
47 changes: 35 additions & 12 deletions packages/eslint-plugin/src/rules/naming-convention.ts
Expand Up @@ -4,6 +4,7 @@ import {
TSESTree,
TSESLint,
} from '@typescript-eslint/experimental-utils';
import ts from 'typescript';
import * as util from '../util';

type MessageIds =
Expand Down Expand Up @@ -1138,31 +1139,39 @@ function isCorrectType(
const { esTreeNodeToTSNodeMap, program } = util.getParserServices(context);
const checker = program.getTypeChecker();
const tsNode = esTreeNodeToTSNodeMap.get(node);
const type = checker.getTypeAtLocation(tsNode);
const typeString = checker.typeToString(
// this will resolve things like true => boolean, 'a' => string and 1 => number
checker.getWidenedType(checker.getBaseTypeOfLiteralType(type)),
);
const type = checker
.getTypeAtLocation(tsNode)
// remove null and undefined from the type, as we don't care about it here
.getNonNullableType();

for (const allowedType of config.types) {
switch (allowedType) {
case TypeModifiers.array:
// TODO
if (
isAllTypesMatch(
type,
t => checker.isArrayType(t) || checker.isTupleType(t),
)
) {
return true;
}
break;

case TypeModifiers.function:
// TODO
if (isAllTypesMatch(type, t => t.getCallSignatures().length > 0)) {
return true;
}
break;

case TypeModifiers.boolean:
case TypeModifiers.number:
case TypeModifiers.string: {
const typeString = checker.typeToString(
// this will resolve things like true => boolean, 'a' => string and 1 => number
checker.getWidenedType(checker.getBaseTypeOfLiteralType(type)),
);
const allowedTypeString = TypeModifiers[allowedType];
if (
typeString === `${allowedTypeString}` ||
typeString === `${allowedTypeString} | null` ||
typeString === `${allowedTypeString} | null | undefined`
) {
if (typeString === allowedTypeString) {
return true;
}
break;
Expand All @@ -1173,6 +1182,20 @@ function isCorrectType(
return false;
}

/**
* @returns `true` if the type (or all union types) in the given type return true for the callback
*/
function isAllTypesMatch(
type: ts.Type,
cb: (type: ts.Type) => boolean,
): boolean {
if (type.isUnion()) {
return type.types.every(t => cb(t));
}

return cb(type);
}

export {
MessageIds,
Options,
Expand Down
77 changes: 59 additions & 18 deletions packages/eslint-plugin/tests/rules/naming-convention.test.ts
Expand Up @@ -652,24 +652,24 @@ ruleTester.run('naming-convention', rule, {
...createInvalidTestCases(cases),
{
code: `
declare const string_camelCase: string;
declare const string_camelCase: string | null;
declare const string_camelCase: string | null | undefined;
declare const string_camelCase: 'a' | null | undefined;
declare const string_camelCase: string | 'a' | null | undefined;
declare const number_camelCase: number;
declare const number_camelCase: number | null;
declare const number_camelCase: number | null | undefined;
declare const number_camelCase: 1 | null | undefined;
declare const number_camelCase: number | 2 | null | undefined;
declare const boolean_camelCase: boolean;
declare const boolean_camelCase: boolean | null;
declare const boolean_camelCase: boolean | null | undefined;
declare const boolean_camelCase: true | null | undefined;
declare const boolean_camelCase: false | null | undefined;
declare const boolean_camelCase: true | false | null | undefined;
declare const string_camelCase01: string;
declare const string_camelCase02: string | null;
declare const string_camelCase03: string | null | undefined;
declare const string_camelCase04: 'a' | null | undefined;
declare const string_camelCase05: string | 'a' | null | undefined;
declare const number_camelCase06: number;
declare const number_camelCase07: number | null;
declare const number_camelCase08: number | null | undefined;
declare const number_camelCase09: 1 | null | undefined;
declare const number_camelCase10: number | 2 | null | undefined;
declare const boolean_camelCase11: boolean;
declare const boolean_camelCase12: boolean | null;
declare const boolean_camelCase13: boolean | null | undefined;
declare const boolean_camelCase14: true | null | undefined;
declare const boolean_camelCase15: false | null | undefined;
declare const boolean_camelCase16: true | false | null | undefined;
`,
options: [
{
Expand All @@ -694,5 +694,46 @@ ruleTester.run('naming-convention', rule, {
parserOptions,
errors: Array(16).fill({ messageId: 'doesNotMatchFormat' }),
},
{
code: `
declare const function_camelCase1: (() => void);
declare const function_camelCase2: (() => void) | null;
declare const function_camelCase3: (() => void) | null | undefined;
declare const function_camelCase4: (() => void) | (() => string) | null | undefined;
`,
options: [
{
selector: 'variable',
types: ['function'],
format: ['snake_case'],
prefix: ['function_'],
},
],
parserOptions,
errors: Array(4).fill({ messageId: 'doesNotMatchFormat' }),
},
{
code: `
declare const array_camelCase1: Array<number>;
declare const array_camelCase2: ReadonlyArray<number> | null;
declare const array_camelCase3: number[] | null | undefined;
declare const array_camelCase4: readonly number[] | null | undefined;
declare const array_camelCase5: number[] | (number | string)[] | null | undefined;
declare const array_camelCase6: [] | null | undefined;
declare const array_camelCase7: [number] | null | undefined;
declare const array_camelCase8: readonly number[] | Array<string> | [boolean] | null | undefined;
`,
options: [
{
selector: 'variable',
types: ['array'],
format: ['snake_case'],
prefix: ['array_'],
},
],
parserOptions,
errors: Array(8).fill({ messageId: 'doesNotMatchFormat' }),
},
],
});
21 changes: 21 additions & 0 deletions packages/eslint-plugin/typings/typescript.d.ts
@@ -0,0 +1,21 @@
import { TypeChecker, Type } from 'typescript';

declare module 'typescript' {
interface TypeChecker {
// internal TS APIs

/**
* @returns `true` if the given type is an array type:
* - Array<foo>
* - ReadonlyArray<foo>
* - foo[]
* - readonly foo[]
*/
isArrayType(type: Type): boolean;
/**
* @returns `true` if the given type is a tuple type:
* - [foo]
*/
isTupleType(type: Type): boolean;
}
}

0 comments on commit 278a25a

Please sign in to comment.