From 834ce8cf9e8e46934dfa604526360870d109765b Mon Sep 17 00:00:00 2001 From: undergroundwires Date: Wed, 19 Jan 2022 22:28:33 +0100 Subject: [PATCH] Add AirBnb TypeScript overrides for linting AirBnb only imports JavaScript rules and some fail for TypeScript files. This commit overrides those rules with TypeScript equivalents. Changes here can be mostly replaced when Vue natively support TypeScript for Airbnb (vuejs/eslint-config-airbnb#23). Enables @typescript-eslint/indent even though it's broken and it will not be fixed typescript-eslint/typescript-eslint#1824 until prettifier is used, because it is still useful. Change broken rules with TypeScript variants: - `no-useless-constructor` eslint/eslint#14118 typescript-eslint/typescript-eslint#873 - `no-shadow` eslint/eslint#13044 typescript-eslint/typescript-eslint#2483 typescript-eslint/typescript-eslint#325 typescript-eslint/typescript-eslint#2552 typescript-eslint/typescript-eslint#2484 typescript-eslint/typescript-eslint#2466 --- .eslintrc.js | 175 ++++++++++++++++++ src/application/ApplicationFactory.ts | 6 +- .../Context/IApplicationContext.ts | 4 +- .../Expressions/Expression/Expression.ts | 2 +- src/presentation/electron/main.ts | 2 +- .../application/ApplicationFactory.spec.ts | 8 +- .../Parser/ApplicationParser.spec.ts | 4 +- .../Threading/AsyncSleep.spec.ts | 2 +- tests/unit/stubs/CodeSubstituterStub.ts | 2 +- tests/unit/stubs/EnumParserStub.ts | 2 +- tests/unit/stubs/ExpressionsCompilerStub.ts | 2 +- tests/unit/stubs/UserSelectionStub.ts | 2 +- 12 files changed, 193 insertions(+), 18 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 2dc335df..a4cbeb7e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,4 +1,9 @@ +const { rules: baseBestPracticesRules } = require('eslint-config-airbnb-base/rules/best-practices'); +const { rules: baseErrorsRules } = require('eslint-config-airbnb-base/rules/errors'); +const { rules: baseES6Rules } = require('eslint-config-airbnb-base/rules/es6'); +const { rules: baseImportsRules } = require('eslint-config-airbnb-base/rules/imports'); const { rules: baseStyleRules } = require('eslint-config-airbnb-base/rules/style'); +const { rules: baseVariablesRules } = require('eslint-config-airbnb-base/rules/variables'); module.exports = { root: true, @@ -39,6 +44,18 @@ module.exports = { mocha: true, }, }, + { + files: ['**/*.ts?(x)', '**/*.d.ts'], + parserOptions: { + // Setting project is required for some rules such as @typescript-eslint/dot-notation, + // @typescript-eslint/return-await and @typescript-eslint/no-throw-literal. + // If this property is missing they fail due to missing parser. + project: ['./tsconfig.json'], + }, + rules: { + ...getTypeScriptOverrides(), + }, + }, { files: ['**/tests/**/*.{j,t}s?(x)'], rules: { @@ -113,3 +130,161 @@ function getOpinionatedRuleOverrides() { ], }; } + +function getTypeScriptOverrides() { + /* + Here until Vue supports AirBnb Typescript overrides (vuejs/eslint-config-airbnb#23). + Based on `eslint-config-airbnb-typescript`. + Source: https://github.com/iamturns/eslint-config-airbnb-typescript/blob/v16.1.0/lib/shared.js + It cannot be used directly due to compilation errors. + */ + return { + 'brace-style': 'off', + '@typescript-eslint/brace-style': baseStyleRules['brace-style'], + + camelcase: 'off', + '@typescript-eslint/naming-convention': [ + 'error', + { selector: 'variable', format: ['camelCase', 'PascalCase', 'UPPER_CASE'] }, + { selector: 'function', format: ['camelCase', 'PascalCase'] }, + { selector: 'typeLike', format: ['PascalCase'] }, + ], + + 'comma-dangle': 'off', + '@typescript-eslint/comma-dangle': [ + baseStyleRules['comma-dangle'][0], + { + ...baseStyleRules['comma-dangle'][1], + enums: baseStyleRules['comma-dangle'][1].arrays, + generics: baseStyleRules['comma-dangle'][1].arrays, + tuples: baseStyleRules['comma-dangle'][1].arrays, + }, + ], + + 'comma-spacing': 'off', + '@typescript-eslint/comma-spacing': baseStyleRules['comma-spacing'], + + 'default-param-last': 'off', + '@typescript-eslint/default-param-last': baseBestPracticesRules['default-param-last'], + + 'dot-notation': 'off', + '@typescript-eslint/dot-notation': baseBestPracticesRules['dot-notation'], + + 'func-call-spacing': 'off', + '@typescript-eslint/func-call-spacing': baseStyleRules['func-call-spacing'], + + // ❌ Broken for some cases, but still useful. + // Here until Prettifier is used. + indent: 'off', + '@typescript-eslint/indent': baseStyleRules.indent, + + 'keyword-spacing': 'off', + '@typescript-eslint/keyword-spacing': baseStyleRules['keyword-spacing'], + + 'lines-between-class-members': 'off', + '@typescript-eslint/lines-between-class-members': baseStyleRules['lines-between-class-members'], + + 'no-array-constructor': 'off', + '@typescript-eslint/no-array-constructor': baseStyleRules['no-array-constructor'], + + 'no-dupe-class-members': 'off', + '@typescript-eslint/no-dupe-class-members': baseES6Rules['no-dupe-class-members'], + + 'no-empty-function': 'off', + '@typescript-eslint/no-empty-function': baseBestPracticesRules['no-empty-function'], + + 'no-extra-parens': 'off', + '@typescript-eslint/no-extra-parens': baseErrorsRules['no-extra-parens'], + + 'no-extra-semi': 'off', + '@typescript-eslint/no-extra-semi': baseErrorsRules['no-extra-semi'], + + // ❌ Fails due to missing parser + // 'no-implied-eval': 'off', + // 'no-new-func': 'off', + // '@typescript-eslint/no-implied-eval': baseBestPracticesRules['no-implied-eval'], + + 'no-loss-of-precision': 'off', + '@typescript-eslint/no-loss-of-precision': baseErrorsRules['no-loss-of-precision'], + + 'no-loop-func': 'off', + '@typescript-eslint/no-loop-func': baseBestPracticesRules['no-loop-func'], + + 'no-magic-numbers': 'off', + '@typescript-eslint/no-magic-numbers': baseBestPracticesRules['no-magic-numbers'], + + 'no-redeclare': 'off', + '@typescript-eslint/no-redeclare': baseBestPracticesRules['no-redeclare'], + + // ESLint variant does not work with TypeScript enums. + 'no-shadow': 'off', + '@typescript-eslint/no-shadow': baseVariablesRules['no-shadow'], + + 'no-throw-literal': 'off', + '@typescript-eslint/no-throw-literal': baseBestPracticesRules['no-throw-literal'], + + 'no-unused-expressions': 'off', + '@typescript-eslint/no-unused-expressions': baseBestPracticesRules['no-unused-expressions'], + + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': baseVariablesRules['no-unused-vars'], + + // https://erkinekici.com/articles/linting-trap#no-use-before-define + // 'no-use-before-define': 'off', + // '@typescript-eslint/no-use-before-define': baseVariablesRules['no-use-before-define'], + + // ESLint variant does not understand TypeScript constructors. + // eslint/eslint/#14118, typescript-eslint/typescript-eslint#873 + 'no-useless-constructor': 'off', + '@typescript-eslint/no-useless-constructor': baseES6Rules['no-useless-constructor'], + + quotes: 'off', + '@typescript-eslint/quotes': baseStyleRules.quotes, + + semi: 'off', + '@typescript-eslint/semi': baseStyleRules.semi, + + 'space-before-function-paren': 'off', + '@typescript-eslint/space-before-function-paren': baseStyleRules['space-before-function-paren'], + + 'require-await': 'off', + '@typescript-eslint/require-await': baseBestPracticesRules['require-await'], + + 'no-return-await': 'off', + '@typescript-eslint/return-await': baseBestPracticesRules['no-return-await'], + + 'space-infix-ops': 'off', + '@typescript-eslint/space-infix-ops': baseStyleRules['space-infix-ops'], + + 'object-curly-spacing': 'off', + '@typescript-eslint/object-curly-spacing': baseStyleRules['object-curly-spacing'], + + 'import/extensions': [ + baseImportsRules['import/extensions'][0], + baseImportsRules['import/extensions'][1], + { + ...baseImportsRules['import/extensions'][2], + ts: 'never', + tsx: 'never', + }, + ], + + // Changes required is not yet implemented: + // 'import/no-extraneous-dependencies': [ + // baseImportsRules['import/no-extraneous-dependencies'][0], + // { + // ...baseImportsRules['import/no-extraneous-dependencies'][1], + // devDependencies: baseImportsRules[ + // 'import/no-extraneous-dependencies' + // ][1].devDependencies.reduce((result, devDep) => { + // const toAppend = [devDep]; + // const devDepWithTs = devDep.replace(/\bjs(x?)\b/g, 'ts$1'); + // if (devDepWithTs !== devDep) { + // toAppend.push(devDepWithTs); + // } + // return [...result, ...toAppend]; + // }, []), + // }, + // ], + }; +} diff --git a/src/application/ApplicationFactory.ts b/src/application/ApplicationFactory.ts index edfc0657..0ab31304 100644 --- a/src/application/ApplicationFactory.ts +++ b/src/application/ApplicationFactory.ts @@ -3,15 +3,15 @@ import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy'; import { IApplicationFactory } from './IApplicationFactory'; import { parseApplication } from './Parser/ApplicationParser'; -export type ApplicationGetter = () => IApplication; -const ApplicationGetter: ApplicationGetter = parseApplication; +export type ApplicationGetterType = () => IApplication; +const ApplicationGetter: ApplicationGetterType = parseApplication; export class ApplicationFactory implements IApplicationFactory { public static readonly Current: IApplicationFactory = new ApplicationFactory(ApplicationGetter); private readonly getter: AsyncLazy; - protected constructor(costlyGetter: ApplicationGetter) { + protected constructor(costlyGetter: ApplicationGetterType) { if (!costlyGetter) { throw new Error('undefined getter'); } diff --git a/src/application/Context/IApplicationContext.ts b/src/application/Context/IApplicationContext.ts index 59bf5892..bd1b2c32 100644 --- a/src/application/Context/IApplicationContext.ts +++ b/src/application/Context/IApplicationContext.ts @@ -10,8 +10,8 @@ export interface IReadOnlyApplicationContext { } export interface IApplicationContext extends IReadOnlyApplicationContext { - readonly state: ICategoryCollectionState; - changeContext(os: OperatingSystem): void; + readonly state: ICategoryCollectionState; + changeContext(os: OperatingSystem): void; } export interface IApplicationContextChangedEvent { diff --git a/src/application/Parser/Script/Compiler/Expressions/Expression/Expression.ts b/src/application/Parser/Script/Compiler/Expressions/Expression/Expression.ts index fdc2566a..9142bace 100644 --- a/src/application/Parser/Script/Compiler/Expressions/Expression/Expression.ts +++ b/src/application/Parser/Script/Compiler/Expressions/Expression/Expression.ts @@ -12,7 +12,7 @@ export class Expression implements IExpression { public readonly position: ExpressionPosition, public readonly evaluator: ExpressionEvaluator, public readonly parameters - : IReadOnlyFunctionParameterCollection = new FunctionParameterCollection(), + : IReadOnlyFunctionParameterCollection = new FunctionParameterCollection(), ) { if (!position) { throw new Error('undefined position'); diff --git a/src/presentation/electron/main.ts b/src/presentation/electron/main.ts index 813c31b6..58698ffa 100644 --- a/src/presentation/electron/main.ts +++ b/src/presentation/electron/main.ts @@ -14,7 +14,7 @@ import { setupAutoUpdater } from './Update/Updater'; const isDevelopment = process.env.NODE_ENV !== 'production'; // Path of static assets, magic variable populated by electron -// eslint-disable-next-line no-underscore-dangle +// eslint-disable-next-line @typescript-eslint/naming-convention, no-underscore-dangle declare const __static: string; // https://github.com/electron-userland/electron-webpack/issues/172 // Keep a global reference of the window object, if you don't, the window will diff --git a/tests/unit/application/ApplicationFactory.spec.ts b/tests/unit/application/ApplicationFactory.spec.ts index 40a34746..751c542e 100644 --- a/tests/unit/application/ApplicationFactory.spec.ts +++ b/tests/unit/application/ApplicationFactory.spec.ts @@ -1,6 +1,6 @@ import 'mocha'; import { expect } from 'chai'; -import { ApplicationFactory, ApplicationGetter } from '@/application/ApplicationFactory'; +import { ApplicationFactory, ApplicationGetterType } from '@/application/ApplicationFactory'; import { ApplicationStub } from '@tests/unit/stubs/ApplicationStub'; describe('ApplicationFactory', () => { @@ -19,7 +19,7 @@ describe('ApplicationFactory', () => { it('returns result from the getter', async () => { // arrange const expected = new ApplicationStub(); - const getter: ApplicationGetter = () => expected; + const getter: ApplicationGetterType = () => expected; const sut = new SystemUnderTest(getter); // act const actual = await Promise.all([ @@ -35,7 +35,7 @@ describe('ApplicationFactory', () => { // arrange let totalExecution = 0; const expected = new ApplicationStub(); - const getter: ApplicationGetter = () => { + const getter: ApplicationGetterType = () => { totalExecution++; return expected; }; @@ -54,7 +54,7 @@ describe('ApplicationFactory', () => { }); class SystemUnderTest extends ApplicationFactory { - public constructor(costlyGetter: ApplicationGetter) { + public constructor(costlyGetter: ApplicationGetterType) { super(costlyGetter); } } diff --git a/tests/unit/application/Parser/ApplicationParser.spec.ts b/tests/unit/application/Parser/ApplicationParser.spec.ts index cf3cb5a2..bd40ac36 100644 --- a/tests/unit/application/Parser/ApplicationParser.spec.ts +++ b/tests/unit/application/Parser/ApplicationParser.spec.ts @@ -138,8 +138,8 @@ describe('ApplicationParser', () => { class CategoryCollectionParserSpy { public arguments = new Array<{ - data: CollectionData, - info: ProjectInformation, + data: CollectionData, + info: ProjectInformation, }>(); private returnValues = new Map(); diff --git a/tests/unit/infrastructure/Threading/AsyncSleep.spec.ts b/tests/unit/infrastructure/Threading/AsyncSleep.spec.ts index 09e9d931..978a8b91 100644 --- a/tests/unit/infrastructure/Threading/AsyncSleep.spec.ts +++ b/tests/unit/infrastructure/Threading/AsyncSleep.spec.ts @@ -42,7 +42,7 @@ class SchedulerMock { private currentTime = 0; - private scheduledActions = new Array<{time: number, action: SchedulerCallbackType}>(); + private scheduledActions = new Array<{ time: number, action: SchedulerCallbackType }>(); constructor() { this.mock = (callback: SchedulerCallbackType, ms: number) => { diff --git a/tests/unit/stubs/CodeSubstituterStub.ts b/tests/unit/stubs/CodeSubstituterStub.ts index bf7da7c3..90d43315 100644 --- a/tests/unit/stubs/CodeSubstituterStub.ts +++ b/tests/unit/stubs/CodeSubstituterStub.ts @@ -3,7 +3,7 @@ import { ICodeSubstituter } from '@/application/Parser/ScriptingDefinition/ICode export class CodeSubstituterStub implements ICodeSubstituter { private readonly scenarios = - new Array<{ code: string, info: IProjectInformation, result: string}>(); + new Array<{ code: string, info: IProjectInformation, result: string }>(); public substitute(code: string, info: IProjectInformation): string { const scenario = this.scenarios.find((s) => s.code === code && s.info === info); diff --git a/tests/unit/stubs/EnumParserStub.ts b/tests/unit/stubs/EnumParserStub.ts index f77deb2a..bdf7705b 100644 --- a/tests/unit/stubs/EnumParserStub.ts +++ b/tests/unit/stubs/EnumParserStub.ts @@ -2,7 +2,7 @@ import { IEnumParser } from '@/application/Common/Enum'; export class EnumParserStub implements IEnumParser { private readonly scenarios = - new Array<{ inputName: string, inputValue: string, outputValue: T }>(); + new Array<{ inputName: string, inputValue: string, outputValue: T }>(); private defaultValue: T; diff --git a/tests/unit/stubs/ExpressionsCompilerStub.ts b/tests/unit/stubs/ExpressionsCompilerStub.ts index a91fb7f4..80c3d1a9 100644 --- a/tests/unit/stubs/ExpressionsCompilerStub.ts +++ b/tests/unit/stubs/ExpressionsCompilerStub.ts @@ -6,7 +6,7 @@ import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCa export class ExpressionsCompilerStub implements IExpressionsCompiler { public readonly callHistory = - new Array<{ code: string, parameters: IReadOnlyFunctionCallArgumentCollection }>(); + new Array<{ code: string, parameters: IReadOnlyFunctionCallArgumentCollection }>(); private readonly scenarios = new Array(); diff --git a/tests/unit/stubs/UserSelectionStub.ts b/tests/unit/stubs/UserSelectionStub.ts index 10f67adb..b0019a9b 100644 --- a/tests/unit/stubs/UserSelectionStub.ts +++ b/tests/unit/stubs/UserSelectionStub.ts @@ -6,7 +6,7 @@ import { EventSource } from '@/infrastructure/Events/EventSource'; export class UserSelectionStub implements IUserSelection { public readonly changed: IEventSource = - new EventSource(); + new EventSource(); public selectedScripts: readonly SelectedScript[] = [];