From e1c3a25422f38a562557ffa46fea46220c62f6c7 Mon Sep 17 00:00:00 2001 From: Matthew Holder Date: Mon, 29 Aug 2022 11:53:18 -0500 Subject: [PATCH] fix: not propagating Standard rules --- src/index.test.ts | 17 +++++ src/index.ts | 189 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 160 insertions(+), 46 deletions(-) diff --git a/src/index.test.ts b/src/index.test.ts index 8909caca..4ba32611 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -43,6 +43,7 @@ test('export', (t): void => { rules: { 'brace-style': 'off', camelcase: 'off', + 'comma-dangle': 'off', 'comma-spacing': 'off', 'dot-notation': 'off', 'func-call-spacing': 'off', @@ -51,6 +52,9 @@ test('export', (t): void => { 'lines-between-class-members': 'off', 'no-array-constructor': 'off', 'no-dupe-class-members': 'off', + 'no-extra-parens': 'off', + 'no-implied-eval': 'off', + 'no-loss-of-precision': 'off', 'no-redeclare': 'off', 'no-throw-literal': 'off', 'no-undef': 'off', @@ -59,13 +63,22 @@ test('export', (t): void => { 'no-unused-expressions': 'off', 'no-useless-constructor': 'off', 'no-void': ['error', { allowAsStatement: true }], + 'object-curly-spacing': 'off', quotes: 'off', semi: 'off', + 'space-before-blocks': 'off', 'space-before-function-paren': 'off', 'space-infix-ops': 'off', '@typescript-eslint/adjacent-overload-signatures': 'error', '@typescript-eslint/array-type': ['error', { default: 'array-simple' }], '@typescript-eslint/brace-style': ['error', '1tbs', { allowSingleLine: true }], + '@typescript-eslint/comma-dangle': ['error', { + arrays: 'never', + objects: 'never', + imports: 'never', + exports: 'never', + functions: 'never' + }], '@typescript-eslint/comma-spacing': ['error', { before: false, after: true }], '@typescript-eslint/consistent-type-assertions': [ 'error', @@ -121,11 +134,13 @@ test('export', (t): void => { '@typescript-eslint/no-dynamic-delete': 'error', '@typescript-eslint/no-empty-interface': ['error', { allowSingleExtends: true }], '@typescript-eslint/no-extra-non-null-assertion': 'error', + '@typescript-eslint/no-extra-parens': ['error', 'functions'], '@typescript-eslint/no-extraneous-class': ['error', { allowWithDecorator: true }], '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/no-for-in-array': 'error', '@typescript-eslint/no-implied-eval': 'error', '@typescript-eslint/no-invalid-void-type': 'error', + '@typescript-eslint/no-loss-of-precision': 'error', '@typescript-eslint/no-misused-new': 'error', '@typescript-eslint/no-misused-promises': 'error', '@typescript-eslint/no-namespace': 'error', @@ -141,6 +156,7 @@ test('export', (t): void => { '@typescript-eslint/no-unused-expressions': ['error', { allowShortCircuit: true, allowTaggedTemplates: true, allowTernary: true }], '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-var-requires': 'error', + '@typescript-eslint/object-curly-spacing': ['error', 'always'], '@typescript-eslint/prefer-function-type': 'error', '@typescript-eslint/prefer-includes': 'error', '@typescript-eslint/prefer-nullish-coalescing': ['error', { ignoreConditionalTests: false, ignoreMixedLogicalExpressions: false }], @@ -155,6 +171,7 @@ test('export', (t): void => { '@typescript-eslint/restrict-template-expressions': ['error', { allowNumber: true }], '@typescript-eslint/return-await': ['error', 'always'], '@typescript-eslint/semi': ['error', 'never'], + '@typescript-eslint/space-before-blocks': ['error', 'always'], '@typescript-eslint/space-before-function-paren': ['error', 'always'], '@typescript-eslint/space-infix-ops': 'error', '@typescript-eslint/strict-boolean-expressions': ['error', { diff --git a/src/index.ts b/src/index.ts index 3a2cddfd..e192b2e3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,42 +1,136 @@ import configStandard from './eslint-config-standard' import { Linter } from 'eslint' -const equivalents = [ - 'comma-spacing', - 'dot-notation', - 'brace-style', - 'func-call-spacing', - 'indent', - 'keyword-spacing', - 'lines-between-class-members', - 'no-array-constructor', - 'no-dupe-class-members', - 'no-redeclare', - 'no-throw-literal', - 'no-unused-vars', - 'no-unused-expressions', - 'no-useless-constructor', - 'quotes', - 'semi', - 'space-before-function-paren', - 'space-infix-ops' -] as const - -const ruleFromStandard = (name: string): Linter.RuleEntry => { - if (configStandard.rules === undefined) throw new Error() - const rule = configStandard.rules[name] - if (rule === undefined) throw new Error() - if (typeof rule !== 'object') return rule - return JSON.parse(JSON.stringify(rule)) +const isOff = (value: unknown): value is 'off' | 0 => value === 'off' || value === 0 + +type Converter = (name: string, level: Linter.RuleLevel, options: any[]) => null | [string, Linter.RuleLevelAndOptions] +type Method = 'passthru' | 'exclude' | Converter + +const equivalents: Record = { + // ## Rules to pass through + 'brace-style': 'passthru', + 'comma-dangle': 'passthru', + 'comma-spacing': 'passthru', + 'default-param-last': 'passthru', + 'dot-notation': 'passthru', + 'func-call-spacing': 'passthru', + indent: 'passthru', + 'init-declarations': 'passthru', + 'keyword-spacing': 'passthru', + 'lines-between-class-members': 'passthru', + 'no-array-constructor': 'passthru', + 'no-dupe-class-members': 'passthru', + 'no-empty-function': 'passthru', + 'no-extra-parens': 'passthru', + 'no-extra-semi': 'passthru', + 'no-implied-eval': 'passthru', + 'no-invalid-this': 'passthru', + 'no-loop-func': 'passthru', + 'no-loss-of-precision': 'passthru', + 'no-magic-numbers': 'passthru', + 'no-redeclare': 'passthru', + 'no-restricted-imports': 'passthru', + 'no-shadow': 'passthru', + 'no-throw-literal': 'passthru', + 'no-unused-expressions': 'passthru', + 'no-unused-vars': 'passthru', + 'no-useless-constructor': 'passthru', + 'object-curly-spacing': 'passthru', + 'padding-line-between-statements': 'passthru', + quotes: 'passthru', + 'require-await': 'passthru', + semi: 'passthru', + 'space-before-blocks': 'passthru', + 'space-before-function-paren': 'passthru', + 'space-infix-ops': 'passthru', + // ## Rules that require additional conversion + // TS Standard adds typedefs and lets TypeScript handle some other kinds. + 'no-use-before-define': (name, level, options) => { + if (isOff(level)) { + return null + } + + const original = typeof options?.[0] === 'object' ? (options[0] ?? { }) : { } + const converted = { + ...original, + functions: false, + classes: false, + enums: false, + variables: false, + // Only the TypeScript rule has this option. + typedefs: false + } + + return [`@typescript-eslint/${name}`, [level, converted]] + }, + // `no-return-await` is replaced by `@typescript-eslint/return-await` + 'no-return-await': (_, level) => { + // `no-return-await` acts like the `@typescript-eslint` version with the options of `in-try-catch` + return isOff(level) ? null : ['@typescript-eslint/return-await', [level, 'in-try-catch']] + }, + // ## Rules that should be excluded + // TypeScript plug-in replaces this with `@typescript/naming-convention` + camelcase: 'exclude', + // TypeScript has this functionality by default: + 'no-undef': 'exclude' +} as const + +const jsonClone = (value: T): T => JSON.parse(JSON.stringify(value)) as T + +const parseRule = (name: string, config: Linter.RuleEntry): [name: string, level: Linter.RuleLevel, options: any] => { + if (typeof config === 'string' || typeof config === 'number') { + return [name, config, null] + } + + const [level, ...options] = jsonClone(config) + + return [name, level, options] } -function fromEntries (iterable: Array<[string, T]>): { [key: string]: T } { - return [...iterable].reduce<{ [key: string]: T }>((obj, [key, val]) => { - obj[key] = val - return obj - }, {}) +const propagate = (rules: Partial): Linter.RulesRecord => { + const result: Linter.RulesRecord = { } + + for (const [name, method] of Object.entries(equivalents)) { + const original = rules[name] + if (original != null) { + if (method === 'passthru') { + result[name] = 'off' + result[`@typescript-eslint/${name}`] = jsonClone(original) + } + + if (method === 'exclude') { + result[name] = 'off' + } + + if (typeof method === 'function') { + const args = parseRule(name, original) + const converted = method(...args) + if (converted != null) { + result[name] = 'off' + result[converted[0]] = converted[1] + } + } + } + } + + return result } +// const ruleFromStandard = (name: string): Linter.RuleEntry => { +// if (configStandard.rules === undefined) throw new Error() +// const rule = configStandard.rules[name] +// if (rule === undefined) throw new Error() +// if (typeof rule !== 'object') return rule +// return JSON.parse(JSON.stringify(rule)) +// } + +// function fromEntries (iterable: Array<[string, T]>): { [key: string]: T } { +// return [...iterable].reduce<{ [key: string]: T }>((obj, [key, val]) => { +// obj[key] = val +// return obj +// }, {}) +// } + const config: Linter.Config = { extends: 'eslint-config-standard', plugins: ['@typescript-eslint'], @@ -45,23 +139,26 @@ const config: Linter.Config = { files: ['*.ts', '*.tsx'], parser: '@typescript-eslint/parser', rules: { - // TypeScript has this functionality by default: - 'no-undef': 'off', + // Propagate rules from Standard that have TypeScript equivalents + ...propagate(configStandard.rules ?? { }), - // Rules replaced by @typescript-eslint versions: - ...fromEntries(equivalents.map((name) => [name, 'off'])), - camelcase: 'off', - 'no-use-before-define': 'off', + // // TypeScript has this functionality by default: + // 'no-undef': 'off', + + // // Rules replaced by @typescript-eslint versions: + // ...fromEntries(equivalents.map((name) => [name, 'off'])), + // camelcase: 'off', + // 'no-use-before-define': 'off', // @typescript-eslint versions of Standard.js rules: - ...fromEntries(equivalents.map((name) => [`@typescript-eslint/${name}`, ruleFromStandard(name)])), - '@typescript-eslint/no-use-before-define': ['error', { - functions: false, - classes: false, - enums: false, - variables: false, - typedefs: false // Only the TypeScript rule has this option. - }], + // ...fromEntries(equivalents.map((name) => [`@typescript-eslint/${name}`, ruleFromStandard(name)])), + // '@typescript-eslint/no-use-before-define': ['error', { + // functions: false, + // classes: false, + // enums: false, + // variables: false, + // typedefs: false // Only the TypeScript rule has this option. + // }], // Rules exclusive to Standard TypeScript: '@typescript-eslint/adjacent-overload-signatures': 'error',