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
This commit is contained in:
undergroundwires
2022-01-19 22:28:33 +01:00
parent 2354f0ba9f
commit 834ce8cf9e
12 changed files with 193 additions and 18 deletions

View File

@@ -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];
// }, []),
// },
// ],
};
}

View File

@@ -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<IApplication>;
protected constructor(costlyGetter: ApplicationGetter) {
protected constructor(costlyGetter: ApplicationGetterType) {
if (!costlyGetter) {
throw new Error('undefined getter');
}

View File

@@ -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

View File

@@ -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);
}
}