Skip to content

Commit

Permalink
feat: add more eslint config presets
Browse files Browse the repository at this point in the history
Allows us to use the eslint configs more flexibly. Upgrades the quality generator to allow selecting which preset - required for NX cypress project usage.
  • Loading branch information
SimeonC committed Nov 1, 2023
1 parent c237bdf commit 11ea9ba
Show file tree
Hide file tree
Showing 15 changed files with 438 additions and 46 deletions.
30 changes: 30 additions & 0 deletions packages/eslint-config/README.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Meta } from '@storybook/addon-docs/blocks';
## Introduction

This package is our standard rules for eslint in the form of a eslint preset package.
We also export sub presets for more specific use cases.

Recommended usage is;

Expand All @@ -19,3 +20,32 @@ module.exports = {
```shell static
eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./
```

### Presets

- `@tablecheck/eslint-config` - Our standard rules for eslint, used with legacy combined repos

#### Specific Presets

- `@tablecheck/eslint-config/basic` - Rules for vanilla javascript only repositories.
- `@tablecheck/eslint-config/typescript` - Rules for vanilla typescript projects
- `@tablecheck/eslint-config/react` - Rules for react javascript project
- `@tablecheck/eslint-config/react-typescript` - Rules for react typescript project

The following cypress presets should be used with one of the above Specific Presets

- `@tablecheck/eslint-config/cypress` - Rules for Cypress projects (applies to all files)
- `@tablecheck/eslint-config/cypress-component` - Use this when adding component testing to a project, it adds an override for `*.cypress.*` and `*.cy.*` files

For example, a react typescript project with cypress component testing should use the following;

`.eslintrc.js`

```js static
module.exports = {
extends: [
'@tablecheck/eslint-config/react-typescript',
'@tablecheck/eslint-config/cypress-component',
],
};
```
30 changes: 30 additions & 0 deletions packages/eslint-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,36 @@
"main": "./dist/index.js",
"default": "./dist/index.js"
},
"./basic": {
"types": "./dist/presets/basic.d.ts",
"main": "./dist/presets/basic.js",
"default": "./dist/presets/basic.js"
},
"./typescript": {
"types": "./dist/presets/typescript.d.ts",
"main": "./dist/presets/typescript.js",
"default": "./dist/presets/typescript.js"
},
"./cypress": {
"types": "./dist/presets/cypress.d.ts",
"main": "./dist/presets/cypress.js",
"default": "./dist/presets/cypress.js"
},
"./cypress-component": {
"types": "./dist/presets/cypressComponent.d.ts",
"main": "./dist/presets/cypressComponent.js",
"default": "./dist/presets/cypressComponent.js"
},
"./react": {
"types": "./dist/presets/react.d.ts",
"main": "./dist/presets/react.js",
"default": "./dist/presets/react.js"
},
"./react-typescript": {
"types": "./dist/presets/reactTs.d.ts",
"main": "./dist/presets/reactTs.js",
"default": "./dist/presets/reactTs.js"
},
"./package.json": "./package.json"
},
"main": "./dist/index.js",
Expand Down
63 changes: 34 additions & 29 deletions packages/eslint-config/src/overrides/buildBaseTypescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ if (!process.env.NODE_ENV) {
* typescript specific overrides for enabled eslint rules.
* Make sure to keep the typescript + eslint rules paired and commented.
*/
const eslintTypescriptRules: Linter.RulesRecord = {
export const baseTypescriptRules: Linter.RulesRecord = {
// unused variables
'@typescript-eslint/no-unused-vars': 'error',
'no-void': 'off',
Expand All @@ -25,29 +25,48 @@ const eslintTypescriptRules: Linter.RulesRecord = {
// see https://stackoverflow.com/a/67652059/1413689
'consistent-return': 'off',
'@typescript-eslint/no-unsafe-return': 'error',

'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unsafe-enum-comparison': 'off',
'@typescript-eslint/prefer-nullish-coalescing': [
'error',
{
ignoreConditionalTests: true,
ignoreMixedLogicalExpressions: true,
ignorePrimitives: {
string: true,
boolean: true,
},
},
],
'@tablecheck/prefer-shortest-import': 'error',
};

/**
*
* @param files - file globs
* @param rules - here should be the basic rules
* @param forcedRules - this is the place to override any ts rules
* @returns eslint-config
*/
export function buildBaseTypescript(
files: Linter.ConfigOverride['files'],
rules: Linter.RulesRecord,
forcedRules?: Linter.RulesRecord,
): Linter.ConfigOverride | undefined {
export function buildBaseTypescript<
T extends Linter.RulesRecord,
TForced extends Linter.RulesRecord,
>({
files,
rules,
forcedRules,
...options
}: {
files: Linter.ConfigOverride['files'];
rules: T;
forcedRules?: TForced;
} & Omit<
Linter.ConfigOverride,
'parser' | 'extends' | 'plugins' | 'settings' | 'rules' | 'files'
>): Linter.ConfigOverride {
return {
...options,
parser: '@typescript-eslint/parser',
extends: [
'airbnb-typescript',
'plugin:@typescript-eslint/recommended-type-checked',
'plugin:@typescript-eslint/stylistic-type-checked',
'plugin:eslint-comments/recommended',
'prettier',
'plugin:react-hooks/recommended',
],

plugins: [
Expand All @@ -68,21 +87,7 @@ export function buildBaseTypescript(
},
rules: {
...rules,
...eslintTypescriptRules,
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unsafe-enum-comparison': 'off',
'@typescript-eslint/prefer-nullish-coalescing': [
'error',
{
ignoreConditionalTests: true,
ignoreMixedLogicalExpressions: true,
ignorePrimitives: {
string: true,
boolean: true,
},
},
],
'@tablecheck/prefer-shortest-import': 'error',
...baseTypescriptRules,
...forcedRules,
},
};
Expand Down
6 changes: 3 additions & 3 deletions packages/eslint-config/src/overrides/documentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import type { Linter } from 'eslint';
export const documentationOverrides: Linter.ConfigOverride = {
files: [
'**/__fixtures__/**/*',
'**/*.fixture.{js,jsx}',
'**/*.{stories,story}.{js,jsx}',
'.storybook/**/*.{js,jsx}',
'**/*.fixture.{js,jsx,cjs,mjs}',
'**/*.{stories,story}.{js,jsx,cjs,mjs}',
'.storybook/**/*.{js,jsx,cjs,mjs}',
],
rules: {
'no-console': 'off',
Expand Down
8 changes: 4 additions & 4 deletions packages/eslint-config/src/overrides/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { reactRules } from '../rules/react';

import { buildBaseTypescript } from './buildBaseTypescript';

export const typescriptOverrides = buildBaseTypescript(
['**/*.ts', '**/*.tsx'],
{
export const typescriptOverrides = buildBaseTypescript({
files: ['**/*.ts', '**/*.tsx'],
rules: {
...generalRules,
...reactRules,
...promiseRules,
...emotionRules,
...namingRules,
},
);
});
10 changes: 5 additions & 5 deletions packages/eslint-config/src/overrides/typescriptDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import { reactRules } from '../rules/react';

import { buildBaseTypescript } from './buildBaseTypescript';

export const typescriptDefinitionOverrides = buildBaseTypescript(
['**/*.d.ts'],
{
export const typescriptDefinitionOverrides = buildBaseTypescript({
files: ['**/*.d.ts'],
rules: {
...generalRules,
...reactRules,
...promiseRules,
...emotionRules,
},
{
forcedRules: {
'import/no-default-export': 'off',
'vars-on-top': 'off',
'no-unused-vars': 'off',
Expand All @@ -22,4 +22,4 @@ export const typescriptDefinitionOverrides = buildBaseTypescript(
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
);
});
67 changes: 67 additions & 0 deletions packages/eslint-config/src/presets/basic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { Linter } from 'eslint';

import { rootConfigsOverrides } from '../overrides/rootConfigs';
import { scriptsOverrides } from '../overrides/scripts';
import { testOverrides } from '../overrides/tests';
import { emotionRules } from '../rules/emotion';
import { generalRules } from '../rules/general';
import { promiseRules } from '../rules/promise';

if (!process.env.NODE_ENV) {
// This check allows us to run linters inside IDE's
process.env.NODE_ENV = 'development';
}

module.exports = {
extends: ['airbnb', 'plugin:eslint-comments/recommended', 'prettier'],

plugins: ['eslint-comments', 'promise', '@tablecheck', '@nx', '@emotion'],

parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},

env: {
node: true,
browser: true,
commonjs: true,
es6: true,
},

rules: {
...generalRules,
...promiseRules,
...emotionRules,
},

overrides: [
rootConfigsOverrides,
scriptsOverrides,
testOverrides,
{
files: [
'**/__fixtures__/**/*',
'**/*.fixture.{ts,tsx,js,jsx,cts,mts,cjs,mjs}',
'**/*.{stories,story}.{ts,tsx,js,jsx}',
'.storybook/**/*.{ts,tsx,js,jsx}',
],
rules: {
'no-console': 'off',
'import/no-default-export': 'off',
'react-hooks/rules-of-hooks': 'off',
'react-hooks/exhaustive-deps': 'off',
'react/function-component-definition': 'off',
'react/jsx-no-constructed-context-values': 'off',
'react-refresh/only-export-components': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'consistent-return': 'error',
},
env: {
node: true,
},
},
],
} satisfies Linter.Config;
43 changes: 43 additions & 0 deletions packages/eslint-config/src/presets/cypress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { Linter } from 'eslint';

import { testOverrides as testRules } from '../overrides/tests';
import { namingRules } from '../rules/namingConvention';

if (!process.env.NODE_ENV) {
// This check allows us to run linters inside IDE's
process.env.NODE_ENV = 'development';
}

const testOverrides = Object.keys(testRules.rules ?? {}).reduce(
(result, ruleKey) => ({ ...result, [ruleKey]: 'off' }),
{},
);

module.exports = {
env: {
'cypress/globals': true,
},
rules: {
...testOverrides,
'promise/catch-or-return': 'off',
'promise/always-return': 'off',
'import/no-import-module-exports': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/naming-convention': (
['error'] as Linter.RuleLevelAndOptions
).concat(
(
namingRules[
'@typescript-eslint/naming-convention'
] as Linter.RuleLevelAndOptions
).slice(1),
[
{
selector: 'memberLike',
format: null,
},
],
) as Linter.RuleLevelAndOptions,
},
} satisfies Linter.Config;
15 changes: 15 additions & 0 deletions packages/eslint-config/src/presets/cypressComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { Linter } from 'eslint';

if (!process.env.NODE_ENV) {
// This check allows us to run linters inside IDE's
process.env.NODE_ENV = 'development';
}

module.exports = {
overrides: [
{
files: ['**/cypress/**/*', '**/*.{cy,cypress}.{js,jsx,ts,tsx}'],
extends: ['@tablecheck/eslint-config/preset-cypress'],
},
],
} satisfies Linter.Config;
41 changes: 41 additions & 0 deletions packages/eslint-config/src/presets/react.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as path from 'path';

import type { Linter } from 'eslint';
import * as fs from 'fs-extra';
import type { PackageJson } from 'type-fest';

import { reactRules } from '../rules/react';

if (!process.env.NODE_ENV) {
// This check allows us to run linters inside IDE's
process.env.NODE_ENV = 'development';
}

let reactVersion = '17'; // set to 17 for legacy reasons or to not error if react not present - should be able to detect below
const packageJsonPath = path.resolve(path.join(process.cwd(), 'package.json'));
if (fs.existsSync(packageJsonPath)) {
const pkg = fs.readJsonSync(packageJsonPath) as PackageJson;
if (pkg.dependencies?.react) {
const versionOnly = pkg.dependencies.react
.replace(/^[^0-9]+/gi, '')
.replace(/\..+$/gi, '');
if (versionOnly === '*')
reactVersion = '18'; // dumb hack, but using '*' is more dumb
else if (!Number.isNaN(parseFloat(versionOnly))) reactVersion = versionOnly;
}
}

module.exports = {
plugins: ['react-refresh'],
extends: ['plugin:react-hooks/recommended'],

settings: {
react: {
version: reactVersion,
},
},

rules: {
...reactRules,
},
} satisfies Linter.Config;
Loading

0 comments on commit 11ea9ba

Please sign in to comment.