Skip to content

Commit

Permalink
fix: not propagating Standard rules
Browse files Browse the repository at this point in the history
  • Loading branch information
6XGate committed Aug 29, 2022
1 parent d1b977a commit e1c3a25
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 46 deletions.
17 changes: 17 additions & 0 deletions src/index.test.ts
Expand Up @@ -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',
Expand All @@ -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',
Expand All @@ -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',
Expand Down Expand Up @@ -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',
Expand All @@ -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 }],
Expand All @@ -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', {
Expand Down
189 changes: 143 additions & 46 deletions 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<string, Method> = {
// ## 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 = <T>(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<T> (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>): 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<T> (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'],
Expand All @@ -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',
Expand Down

0 comments on commit e1c3a25

Please sign in to comment.