diff --git a/packages/eslint-config/README.stories.mdx b/packages/eslint-config/README.stories.mdx index 024bece4..e0799dc3 100644 --- a/packages/eslint-config/README.stories.mdx +++ b/packages/eslint-config/README.stories.mdx @@ -27,15 +27,16 @@ eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./ #### Specific Presets -- `@tablecheck/eslint-config/basic` - Rules for vanilla javascript only repositories. +- `@tablecheck/eslint-config/basic` - Rules for vanilla javascript only repositories. (Recommend using Typescript over this) - `@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 +- `@tablecheck/eslint-config/cypress` - Rules for Cypress projects in typescript 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 +- `@tablecheck/eslint-config/storybook` - This adds an override for story/stories files, use with typescript package For example, a react typescript project with cypress component testing should use the following; diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index d2a5cf44..233ba94d 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -44,6 +44,11 @@ "main": "./dist/presets/reactTs.js", "default": "./dist/presets/reactTs.js" }, + "./storybook": { + "types": "./dist/presets/storybook.d.ts", + "main": "./dist/presets/storybook.js", + "default": "./dist/presets/storybook.js" + }, "./package.json": "./package.json" }, "main": "./dist/index.js", @@ -51,9 +56,6 @@ "files": [ "dist" ], - "scripts": { - "build": "tsc -p ./tsconfig.build.json" - }, "dependencies": { "@emotion/eslint-plugin": "^11.11.0", "@nx/eslint-plugin": "^16.5.0", diff --git a/packages/eslint-config/project.json b/packages/eslint-config/project.json index 5892eb03..53983673 100644 --- a/packages/eslint-config/project.json +++ b/packages/eslint-config/project.json @@ -4,6 +4,26 @@ "sourceRoot": "packages/eslint-config/src", "projectType": "library", "targets": { + "build": { + "executor": "nx:run-commands", + "options": { + "command": "tsc -p packages/eslint-config/tsconfig.build.json" + } + }, + "test": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "dependsOn": ["build"], + "options": { + "noEslintrc": true, + "eslintConfig": "packages/eslint-config/tests/.eslintrc.cjs", + "reportUnusedDisableDirectives": "error", + "lintFilePatterns": [ + "packages/eslint-config/tests/**/*.ts", + "packages/eslint-config/tests/**/*.tsx" + ] + } + }, "quality": { "executor": "@tablecheck/nx:quality", "outputs": ["{options.outputFile}"], diff --git a/packages/eslint-config/src/overrides/buildBaseTypescript.ts b/packages/eslint-config/src/overrides/buildBaseTypescript.ts index 9fbd5a96..24eebad6 100644 --- a/packages/eslint-config/src/overrides/buildBaseTypescript.ts +++ b/packages/eslint-config/src/overrides/buildBaseTypescript.ts @@ -42,18 +42,13 @@ export const baseTypescriptRules: Linter.RulesRecord = { '@tablecheck/prefer-shortest-import': 'error', }; -export function buildBaseTypescript< - T extends Linter.RulesRecord, - TForced extends Linter.RulesRecord, ->({ +export function buildBaseTypescript({ files, rules, - forcedRules, ...options }: { files: Linter.ConfigOverride['files']; rules: T; - forcedRules?: TForced; } & Omit< Linter.ConfigOverride, 'parser' | 'extends' | 'plugins' | 'settings' | 'rules' | 'files' @@ -86,9 +81,8 @@ export function buildBaseTypescript< }, }, rules: { - ...rules, ...baseTypescriptRules, - ...forcedRules, + ...rules, }, }; } diff --git a/packages/eslint-config/src/overrides/storybook.ts b/packages/eslint-config/src/overrides/storybook.ts index 3969f04d..c7e92c0a 100644 --- a/packages/eslint-config/src/overrides/storybook.ts +++ b/packages/eslint-config/src/overrides/storybook.ts @@ -14,6 +14,10 @@ export const storybookOverrides: Linter.ConfigOverride = { 'react-hooks/exhaustive-deps': 'off', 'react/function-component-definition': 'off', 'react/jsx-no-constructed-context-values': 'off', + 'react/jsx-props-no-spreading': 'off', + 'react/react-in-jsx-scope': 'off', + 'react/prop-types': 'off', + 'react/require-default-props': 'off', 'react-refresh/only-export-components': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/naming-convention': 'off', diff --git a/packages/eslint-config/src/overrides/typescriptDefinitions.ts b/packages/eslint-config/src/overrides/typescriptDefinitions.ts index 3a396b3b..d6cc2861 100644 --- a/packages/eslint-config/src/overrides/typescriptDefinitions.ts +++ b/packages/eslint-config/src/overrides/typescriptDefinitions.ts @@ -12,8 +12,6 @@ export const typescriptDefinitionOverrides = buildBaseTypescript({ ...reactRules, ...promiseRules, ...emotionRules, - }, - forcedRules: { 'import/no-default-export': 'off', 'vars-on-top': 'off', 'no-unused-vars': 'off', diff --git a/packages/eslint-config/src/presets/basic.ts b/packages/eslint-config/src/presets/basic.ts index 40345d19..785948d6 100644 --- a/packages/eslint-config/src/presets/basic.ts +++ b/packages/eslint-config/src/presets/basic.ts @@ -2,7 +2,6 @@ import type { Linter } from 'eslint'; import { rootConfigsOverrides } from '../overrides/rootConfigs'; import { scriptsOverrides } from '../overrides/scripts'; -import { storybookOverrides } from '../overrides/storybook'; import { testOverrides } from '../overrides/tests'; import { emotionRules } from '../rules/emotion'; import { generalRules } from '../rules/general'; @@ -36,10 +35,5 @@ module.exports = { ...emotionRules, }, - overrides: [ - rootConfigsOverrides, - scriptsOverrides, - testOverrides, - storybookOverrides, - ], + overrides: [rootConfigsOverrides, scriptsOverrides, testOverrides], } satisfies Linter.Config; diff --git a/packages/eslint-config/src/presets/cypress.ts b/packages/eslint-config/src/presets/cypress.ts index 2afde0e2..57ee9f8f 100644 --- a/packages/eslint-config/src/presets/cypress.ts +++ b/packages/eslint-config/src/presets/cypress.ts @@ -1,43 +1,3 @@ -import type { Linter } from 'eslint'; +import { cypressPreset } from './cypressInternal'; -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; +module.exports = cypressPreset; diff --git a/packages/eslint-config/src/presets/cypressComponent.ts b/packages/eslint-config/src/presets/cypressComponent.ts index f9224a89..cd6bf9a5 100644 --- a/packages/eslint-config/src/presets/cypressComponent.ts +++ b/packages/eslint-config/src/presets/cypressComponent.ts @@ -1,15 +1,19 @@ import type { Linter } from 'eslint'; +import { cypressPreset } from './cypressInternal'; + if (!process.env.NODE_ENV) { // This check allows us to run linters inside IDE's process.env.NODE_ENV = 'development'; } +const { extends: extendPreset, ...config } = cypressPreset; + module.exports = { overrides: [ { files: ['**/cypress/**/*', '**/*.{cy,cypress}.{js,jsx,ts,tsx}'], - extends: ['@tablecheck/eslint-config/cypress'], + ...config, }, ], } satisfies Linter.Config; diff --git a/packages/eslint-config/src/presets/cypressInternal.ts b/packages/eslint-config/src/presets/cypressInternal.ts new file mode 100644 index 00000000..ced39bcf --- /dev/null +++ b/packages/eslint-config/src/presets/cypressInternal.ts @@ -0,0 +1,45 @@ +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' }), + {}, +); + +export const cypressPreset = { + plugins: ['cypress'], + extends: ['@tablecheck/eslint-config/typescript'], + 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; diff --git a/packages/eslint-config/src/presets/storybook.ts b/packages/eslint-config/src/presets/storybook.ts new file mode 100644 index 00000000..e3623aa6 --- /dev/null +++ b/packages/eslint-config/src/presets/storybook.ts @@ -0,0 +1,17 @@ +import type { Linter } from 'eslint'; + +import { storybookOverrides } from '../overrides/storybook'; + +if (!process.env.NODE_ENV) { + // This check allows us to run linters inside IDE's + process.env.NODE_ENV = 'development'; +} + +module.exports = { + overrides: [ + { + extends: ['@tablecheck/eslint-config/typescript'], + ...storybookOverrides, + }, + ], +} satisfies Linter.Config; diff --git a/packages/eslint-config/src/presets/typescript.ts b/packages/eslint-config/src/presets/typescript.ts index fb5abafb..6910c8e3 100644 --- a/packages/eslint-config/src/presets/typescript.ts +++ b/packages/eslint-config/src/presets/typescript.ts @@ -1,7 +1,6 @@ import type { Linter } from 'eslint'; import { buildBaseTypescript } from '../overrides/buildBaseTypescript'; -import { storybookOverrides } from '../overrides/storybook'; import { testOverrides } from '../overrides/tests'; import { emotionRules } from '../rules/emotion'; import { generalRules } from '../rules/general'; @@ -23,6 +22,7 @@ module.exports = { ...promiseRules, ...emotionRules, ...namingRules, + 'no-unused-vars': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', }, }), @@ -31,7 +31,6 @@ module.exports = { rules: { 'import/no-default-export': 'off', 'vars-on-top': 'off', - 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-empty-interface': 'warn', '@typescript-eslint/explicit-module-boundary-types': 'off', @@ -61,6 +60,5 @@ module.exports = { node: true, }, }), - buildBaseTypescript(storybookOverrides as never), ], } satisfies Linter.Config; diff --git a/packages/eslint-config/src/rules/namingConvention.ts b/packages/eslint-config/src/rules/namingConvention.ts index 943c4e5e..4c9dd012 100644 --- a/packages/eslint-config/src/rules/namingConvention.ts +++ b/packages/eslint-config/src/rules/namingConvention.ts @@ -1,16 +1,18 @@ import type { Linter } from 'eslint'; +const componentRegexpMatch = { + regex: '(? `${name[0].toUpperCase()}${name.substring(1)}`) .join('|')})`; })(), @@ -79,6 +83,11 @@ export const namingRules: Linter.RulesRecord = { modifiers: ['global'], format: ['camelCase', 'UPPER_CASE', 'PascalCase'], }, + { + selector: 'variable', + format: ['PascalCase'], + filter: componentRegexpMatch, + }, { selector: 'variable', format: ['camelCase', 'UPPER_CASE'], diff --git a/packages/eslint-config/tests/.eslintrc.cjs b/packages/eslint-config/tests/.eslintrc.cjs new file mode 100644 index 00000000..e65e3f5a --- /dev/null +++ b/packages/eslint-config/tests/.eslintrc.cjs @@ -0,0 +1,12 @@ +const { namingRules } = require('../dist/rules/namingConvention'); + +module.exports = { + plugins: ['@typescript-eslint'], + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: true, + }, + rules: namingRules, +}; diff --git a/packages/eslint-config/tests/namingConvention.ts/variables.tsx b/packages/eslint-config/tests/namingConvention.ts/variables.tsx new file mode 100644 index 00000000..420244cd --- /dev/null +++ b/packages/eslint-config/tests/namingConvention.ts/variables.tsx @@ -0,0 +1,13 @@ +/** Booleans */ +// eslint-disable-next-line @typescript-eslint/naming-convention +const badName = true; +const isBadName = true; +const disabled = true; + +/** Dereferencing */ +function SomeComponent({ icon: Icon, command }) { + const { icon: IconComponent } = command; + // eslint-disable-next-line @typescript-eslint/naming-convention + const { other: Other } = command; + const iconComponentRef = null; +} diff --git a/packages/eslint-config/tests/tsconfig.json b/packages/eslint-config/tests/tsconfig.json new file mode 100644 index 00000000..28d92157 --- /dev/null +++ b/packages/eslint-config/tests/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../tsconfig.json", + "include": ["**/*.ts", "**/*.tsx"], + "compilerOptions": { + "rootDir": ".", + "outDir": "dist", + "paths": {}, + "types": ["vitest"], + "module": "NodeNext", + "resolveJsonModule": true + }, + "exclude": ["node_modules"], + "files": [] +}