From 32abef3f7421fb9e3f9850198a849b3bb8f6daac Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Tue, 15 Nov 2022 16:47:37 +0100 Subject: [PATCH] Add rules that require type information (#250) * Add rules that require type information * Run lint:fix * Disable no-confusing-void-expression * Fix rule typo * Allow camelCase, PascalCase and UPPER_CASE for object literal properties * Allow camelCase, PascalCase and UPPER_CASE for object literal methods * Disable unbound-method * Allow leading underscore in front of variables * Enable no-throw-literal * Enable restrict-template-expression * Run lint:fix * Fix tests * Remove allowAny from restrict-template-expressions * Re-enable unbound-method * Fix rebase issue --- packages/typescript/rules-snapshot.json | 74 ++++++++++++++++++++++ packages/typescript/src/__test__/dummy.ts | 2 + packages/typescript/src/index.js | 75 ++++++++++++++++++++++- packages/typescript/src/index.test.js | 12 +++- packages/typescript/tsconfig.json | 3 + 5 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 packages/typescript/src/__test__/dummy.ts create mode 100644 packages/typescript/tsconfig.json diff --git a/packages/typescript/rules-snapshot.json b/packages/typescript/rules-snapshot.json index 7679ba03..2bcb8e08 100644 --- a/packages/typescript/rules-snapshot.json +++ b/packages/typescript/rules-snapshot.json @@ -1,11 +1,51 @@ { "@typescript-eslint/adjacent-overload-signatures": "error", "@typescript-eslint/array-type": "error", + "@typescript-eslint/await-thenable": "error", "@typescript-eslint/ban-ts-comment": "error", "@typescript-eslint/ban-types": "error", "@typescript-eslint/consistent-type-assertions": "error", "@typescript-eslint/consistent-type-definitions": ["error", "type"], + "@typescript-eslint/consistent-type-exports": "error", "@typescript-eslint/default-param-last": "error", + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": "default", + "format": ["camelCase"], + "leadingUnderscore": "allow", + "trailingUnderscore": "forbid" + }, + { + "selector": "enumMember", + "format": ["PascalCase"] + }, + { + "selector": "interface", + "format": ["PascalCase"], + "custom": { + "regex": "^I[A-Z]", + "match": false + } + }, + { + "selector": "objectLiteralMethod", + "format": ["camelCase", "PascalCase", "UPPER_CASE"] + }, + { + "selector": "objectLiteralProperty", + "format": ["camelCase", "PascalCase", "UPPER_CASE"] + }, + { + "selector": "typeLike", + "format": ["PascalCase"] + }, + { + "selector": "variable", + "format": ["camelCase", "UPPER_CASE", "PascalCase"], + "leadingUnderscore": "allow" + } + ], "@typescript-eslint/no-array-constructor": "error", "@typescript-eslint/no-dupe-class-members": "error", "@typescript-eslint/no-empty-function": "error", @@ -13,9 +53,14 @@ "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-extra-non-null-assertion": "error", "@typescript-eslint/no-extra-semi": "off", + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-for-in-array": "error", + "@typescript-eslint/no-implied-eval": "error", "@typescript-eslint/no-inferrable-types": "error", "@typescript-eslint/no-loss-of-precision": "error", + "@typescript-eslint/no-meaningless-void-operator": "error", "@typescript-eslint/no-misused-new": "error", + "@typescript-eslint/no-misused-promises": "error", "@typescript-eslint/no-namespace": [ "error", { @@ -33,7 +78,17 @@ } ], "@typescript-eslint/no-this-alias": "error", + "@typescript-eslint/no-throw-literal": "error", + "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", + "@typescript-eslint/no-unnecessary-qualifier": "error", + "@typescript-eslint/no-unnecessary-type-arguments": "error", + "@typescript-eslint/no-unnecessary-type-assertion": "error", "@typescript-eslint/no-unnecessary-type-constraint": "error", + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-call": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-return": "off", "@typescript-eslint/no-unused-expressions": [ "error", { @@ -61,9 +116,26 @@ "@typescript-eslint/prefer-as-const": "error", "@typescript-eslint/prefer-for-of": "error", "@typescript-eslint/prefer-function-type": "error", + "@typescript-eslint/prefer-includes": "error", "@typescript-eslint/prefer-namespace-keyword": "error", + "@typescript-eslint/prefer-nullish-coalescing": "error", "@typescript-eslint/prefer-optional-chain": "error", + "@typescript-eslint/prefer-readonly": "error", + "@typescript-eslint/prefer-reduce-type-parameter": "error", + "@typescript-eslint/prefer-string-starts-ends-with": "error", + "@typescript-eslint/promise-function-async": "error", + "@typescript-eslint/require-await": "error", + "@typescript-eslint/restrict-plus-operands": "error", + "@typescript-eslint/restrict-template-expressions": [ + "error", + { + "allowBoolean": true, + "allowNumber": true + } + ], + "@typescript-eslint/switch-exhaustiveness-check": "error", "@typescript-eslint/triple-slash-reference": "error", + "@typescript-eslint/unbound-method": "error", "@typescript-eslint/unified-signatures": "error", "constructor-super": "off", "default-param-last": "off", @@ -85,6 +157,7 @@ "no-empty-function": "off", "no-extra-semi": "off", "no-func-assign": "off", + "no-implied-eval": "off", "no-import-assign": "off", "no-loss-of-precision": "off", "no-new-symbol": "off", @@ -112,5 +185,6 @@ "prefer-const": "error", "prefer-rest-params": "error", "prefer-spread": "error", + "require-await": "off", "valid-typeof": "off" } diff --git a/packages/typescript/src/__test__/dummy.ts b/packages/typescript/src/__test__/dummy.ts new file mode 100644 index 00000000..8072504a --- /dev/null +++ b/packages/typescript/src/__test__/dummy.ts @@ -0,0 +1,2 @@ +// This file is only used to test the config. +console.log('Hello, world!'); diff --git a/packages/typescript/src/index.js b/packages/typescript/src/index.js index 3cf47ef1..643500d6 100644 --- a/packages/typescript/src/index.js +++ b/packages/typescript/src/index.js @@ -15,12 +15,19 @@ module.exports = { // (not pre-release) here: https://github.com/tc39/ecma262/releases ecmaVersion: 2020, sourceType: 'module', + + // This enables support for linting rules that require type information. We + // assume that the project has a `tsconfig.json` file in the directory where + // ESLint is being run. + tsconfigRootDir: process.cwd(), + project: ['./tsconfig.json'], }, plugins: ['@typescript-eslint', 'jsdoc'], extends: [ 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', 'plugin:import/typescript', ], @@ -59,6 +66,72 @@ module.exports = { }, ], + // Recommended rules that require type information + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + + // Our rules that require type information + '@typescript-eslint/consistent-type-exports': 'error', + '@typescript-eslint/naming-convention': [ + 'error', + { + selector: 'default', + format: ['camelCase'], + leadingUnderscore: 'allow', + trailingUnderscore: 'forbid', + }, + { + selector: 'enumMember', + format: ['PascalCase'], + }, + { + selector: 'interface', + format: ['PascalCase'], + custom: { + regex: '^I[A-Z]', + match: false, + }, + }, + { + selector: 'objectLiteralMethod', + format: ['camelCase', 'PascalCase', 'UPPER_CASE'], + }, + { + selector: 'objectLiteralProperty', + format: ['camelCase', 'PascalCase', 'UPPER_CASE'], + }, + { + selector: 'typeLike', + format: ['PascalCase'], + }, + { + selector: 'variable', + format: ['camelCase', 'UPPER_CASE', 'PascalCase'], + leadingUnderscore: 'allow', + }, + ], + '@typescript-eslint/no-meaningless-void-operator': 'error', + '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error', + '@typescript-eslint/no-unnecessary-qualifier': 'error', + '@typescript-eslint/no-unnecessary-type-arguments': 'error', + '@typescript-eslint/prefer-includes': 'error', + '@typescript-eslint/prefer-nullish-coalescing': 'error', + '@typescript-eslint/prefer-readonly': 'error', + '@typescript-eslint/prefer-reduce-type-parameter': 'error', + '@typescript-eslint/prefer-string-starts-ends-with': 'error', + '@typescript-eslint/promise-function-async': 'error', + '@typescript-eslint/restrict-template-expressions': [ + 'error', + { + allowBoolean: true, + allowNumber: true, + }, + ], + '@typescript-eslint/switch-exhaustiveness-check': 'error', + 'default-param-last': 'off', '@typescript-eslint/default-param-last': 'error', @@ -66,7 +139,7 @@ module.exports = { '@typescript-eslint/no-shadow': ['error', { builtinGlobals: true }], 'no-throw-literal': 'off', - // '@typescript-eslint/no-throw-literal' is left disabled because it requires type information + '@typescript-eslint/no-throw-literal': 'error', 'no-unused-expressions': 'off', '@typescript-eslint/no-unused-expressions': [ diff --git a/packages/typescript/src/index.test.js b/packages/typescript/src/index.test.js index 1635b752..562e8cdd 100644 --- a/packages/typescript/src/index.test.js +++ b/packages/typescript/src/index.test.js @@ -1,4 +1,5 @@ const { ESLint } = require('eslint'); +const { resolve } = require('path'); const config = require('.'); @@ -6,15 +7,22 @@ describe('index', () => { it('is a valid ESLint config', async () => { const api = new ESLint({ baseConfig: config, + useEslintrc: false, overrideConfig: { env: { node: true, }, + parserOptions: { + tsconfigRootDir: resolve(__dirname, '..'), + project: 'tsconfig.json', + }, }, - useEslintrc: false, }); - const result = await api.lintText(`console.log('Hello, world!');\n`); + // In order to test rules that require type information, we need to actually + // compile the file with TypeScript, so rather than using `api.lintText()`, + // we use `api.lintFiles()` and pass in a file that we know will pass. + const result = await api.lintFiles(resolve(__dirname, '__test__/dummy.ts')); expect(result[0].messages).toStrictEqual([]); expect(result[0].warningCount).toBe(0); diff --git a/packages/typescript/tsconfig.json b/packages/typescript/tsconfig.json new file mode 100644 index 00000000..bcd8df0e --- /dev/null +++ b/packages/typescript/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["src"] +}