UNPKG

@szum-tech/eslint-config

Version:

ESLint configuration for TypeScript projects

470 lines (467 loc) 15.8 kB
import { readFileSync, existsSync } from 'fs'; import { join, resolve } from 'path'; import * as importPlugin from 'eslint-plugin-import'; import jestDomPlugin from 'eslint-plugin-jest-dom'; import playwrightPlugin from 'eslint-plugin-playwright'; import reactPlugin from 'eslint-plugin-react'; import * as reactHooksPlugin from 'eslint-plugin-react-hooks'; import storybookPlugin from 'eslint-plugin-storybook'; import tailwindcssPlugin from 'eslint-plugin-tailwindcss'; import testingLibraryPlugin from 'eslint-plugin-testing-library'; import globals from 'globals'; import tsEslint from 'typescript-eslint'; import nextPlugin from '@next/eslint-plugin-next'; import vitestPlugin from '@vitest/eslint-plugin'; // src/index.js var logger = console; function isPackageInstalled(packageName) { const currentDir = process.cwd(); const packageJsonPath = findPackageJson(currentDir); try { const packageJson = readFileSync(packageJsonPath, "utf-8"); const parsedPackageJson = JSON.parse(packageJson); const dependencies = parsedPackageJson.dependencies || {}; const devDependencies = parsedPackageJson.devDependencies || {}; return dependencies.hasOwnProperty(packageName) || devDependencies.hasOwnProperty(packageName); } catch (error) { logger.error("Error reading package.json file:", error); process.exit(1); } } var PACKAGE_JSON = "package.json"; function findPackageJson(startDir) { let currentDir = startDir; while (true) { const packageJsonPath = join(currentDir, PACKAGE_JSON); if (existsSync(packageJsonPath)) { return packageJsonPath; } const parentDir = resolve(currentDir, ".."); if (parentDir === currentDir) { break; } currentDir = parentDir; } return null; } var hasTypeScript = isPackageInstalled("typescript"); var hasTailwindcss = isPackageInstalled("tailwindcss"); var hasReact = isPackageInstalled("react"); var hasNext = isPackageInstalled("next"); var hasTestingLibrary = isPackageInstalled("@testing-library/dom"); var hasJestDom = isPackageInstalled("@testing-library/jest-dom"); var hasVitest = isPackageInstalled("vitest"); var hasPlaywright = isPackageInstalled("@playwright/test"); var hasStorybook = isPackageInstalled("storybook"); var typeScriptExtensions = [".ts", ".cts", ".mts", ".tsx"]; var allExtensions = [...typeScriptExtensions, ".js", ".jsx", ".mjs", ".cjs"]; var vitestFiles = ["**/__tests__/**/*", "**/*.test.*"]; var testFiles = ["**/tests/**", ...vitestFiles]; var playwrightFiles = ["**/e2e/**", "**/*.e2e.*"]; function showFeaturesTable() { const tableData = [ { Name: "TypeScript", Status: hasTypeScript ? "\u2714\uFE0F" : "\u274C" }, { Name: "React", Status: hasReact ? "\u2714\uFE0F" : "\u274C" }, { Name: "Next", Status: hasNext ? "\u2714\uFE0F" : "\u274C" }, { Name: "Testing Library", Status: hasTestingLibrary ? "\u2714\uFE0F" : "\u274C" }, { Name: "Jest Dom", Status: hasJestDom ? "\u2714\uFE0F" : "\u274C" }, { Name: "Vitest", Status: hasVitest ? "\u2714\uFE0F" : "\u274C" }, { Name: "Playwright", Status: hasPlaywright ? "\u2714\uFE0F" : "\u274C" }, { Name: "Storybook", Status: hasStorybook ? "\u2714\uFE0F" : "\u274C" } ].sort((a, b) => { const aHasCheck = a.Status.includes("\u2714\uFE0F"); const bHasCheck = b.Status.includes("\u2714\uFE0F"); if (aHasCheck && !bHasCheck) { return -1; } else if (!aHasCheck && bHasCheck) { return 1; } else { return 0; } }); logger.log("Hello There!\nHere are the features detected in your project:"); logger.table(tableData); logger.log(`Dear Developer `); logger.log("Thanks a lot for using '@szum-tech/eslint-config'"); logger.log("If you like it, leave a star \u2B50 \u{1F449} https://github.com/JanSzewczyk/handy-szumrak"); logger.log("And recommend to others\n"); logger.log(`May the SZUMRAK be with You \u{1F680}\u{1F680}\u{1F680}`); } showFeaturesTable(); var ERROR = "error"; var WARN = "warn"; var OFF = "off"; var config = [ { name: "eslint/ignores", ignores: [ "**/.cache/**", "**/node_modules/**", "**/build/**", "**/public/build/**", "**/playwright-report/**", "**/server-build/**", "**/dist/**", "**/.next/**", "**/storybook-static/**" ] }, { name: "eslint/config/base&import", plugins: { import: importPlugin }, languageOptions: { ecmaVersion: "latest", sourceType: "module", globals: { ...globals.browser, ...globals.node }, parserOptions: { warnOnUnsupportedTypeScriptVersion: false } }, settings: hasTypeScript ? { "import/extensions": allExtensions, "import/external-module-folders": ["node_modules", "node_modules/@types"], "import/parsers": { "@typescript-eslint/parser": typeScriptExtensions }, "import/resolver": { node: { extensions: allExtensions } } } : {}, rules: { "no-unexpected-multiline": ERROR, "no-warning-comments": [ERROR, { terms: ["FIXME"], location: "anywhere" }], "no-console": WARN, "no-unused-vars": [ WARN, { args: "all", argsIgnorePattern: "^_", ignoreRestSiblings: true, varsIgnorePattern: "^ignored" } ], // analysis/correctness "import/default": ERROR, "import/namespace": ERROR, "import/export": ERROR, "import/no-unresolved": OFF, "import/named": OFF, // red flags (thus, warnings) "import/consistent-type-specifier-style": [WARN, "prefer-inline"], "import/no-named-as-default": WARN, "import/no-named-as-default-member": WARN, "import/no-duplicates": [WARN, { "prefer-inline": true }], "import/order": [ WARN, { groups: ["builtin", "external", "internal", "parent", "sibling", "index"], pathGroups: [ { pattern: "react", group: "external", position: "before" }, { pattern: "*/**", group: "internal" } ], pathGroupsExcludedImportTypes: ["react"], "newlines-between": "always", alphabetize: { order: "asc", caseInsensitive: true } } ] } }, hasReact ? { name: "eslint/config/react&react-hooks", files: ["**/*.tsx", "**/*.jsx"], plugins: { react: reactPlugin, "react-hooks": reactHooksPlugin }, languageOptions: { parser: tsEslint.parser, parserOptions: { jsx: true } }, settings: { react: { version: "detect" } }, rules: { "react/display-name": ERROR, "react/jsx-no-comment-textnodes": ERROR, "react/jsx-no-duplicate-props": ERROR, "react/jsx-no-target-blank": ERROR, "react/jsx-no-undef": ERROR, "react/jsx-uses-react": ERROR, "react/jsx-uses-vars": ERROR, "react/no-children-prop": ERROR, "react/no-danger-with-children": ERROR, "react/no-deprecated": ERROR, "react/no-direct-mutation-state": ERROR, "react/no-find-dom-node": ERROR, "react/no-is-mounted": ERROR, "react/no-render-return-value": ERROR, "react/no-unescaped-entities": ERROR, "react/no-unknown-property": ERROR, "react/require-render-return": ERROR, "react/jsx-key": WARN, "react/react-in-jsx-scope": OFF, "react/no-unsafe": OFF, "react-hooks/rules-of-hooks": ERROR, "react-hooks/exhaustive-deps": WARN } } : null, hasTailwindcss ? { name: "eslint/config/tailwindcss", plugins: { tailwindcss: tailwindcssPlugin }, languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } }, rules: { "tailwindcss/no-contradicting-classname": ERROR, "tailwindcss/classnames-order": WARN, "tailwindcss/enforces-negative-arbitrary-values": WARN, "tailwindcss/enforces-shorthand": WARN, "tailwindcss/migration-from-tailwind-2": WARN, "tailwindcss/no-custom-classname": WARN, "tailwindcss/no-unnecessary-arbitrary-value": WARN, "tailwindcss/no-arbitrary-value": OFF } } : null, hasNext ? { name: "eslint/config/next", files: ["**/*.ts?(x)", "**/*.js?(x)"], plugins: { "@next/next": nextPlugin }, rules: { "@next/next/inline-script-id": ERROR, "@next/next/no-assign-module-variable": ERROR, "@next/next/no-document-import-in-page": ERROR, "@next/next/no-duplicate-head": ERROR, "@next/next/no-head-import-in-document": ERROR, "@next/next/no-script-component-in-head": ERROR, "@next/next/google-font-display": WARN, "@next/next/google-font-preconnect": WARN, "@next/next/next-script-for-ga": WARN, "@next/next/no-async-client-component": WARN, "@next/next/no-before-interactive-script-outside-document": WARN, "@next/next/no-css-tags": WARN, "@next/next/no-head-element": WARN, "@next/next/no-html-link-for-pages": WARN, "@next/next/no-img-element": WARN, "@next/next/no-page-custom-font": WARN, "@next/next/no-styled-jsx-in-document": WARN, "@next/next/no-sync-scripts": WARN, "@next/next/no-title-in-document-head": WARN, "@next/next/no-typos": WARN, "@next/next/no-unwanted-polyfillio": WARN } } : null, hasTypeScript ? { name: "eslint/config/typescript", files: ["**/*.ts?(x)"], languageOptions: { parser: tsEslint.parser, parserOptions: { projectService: true }, sourceType: "module" }, plugins: { "@typescript-eslint": tsEslint.plugin }, rules: { "no-unused-expressions": OFF, "no-array-constructor": OFF, "no-unused-vars": OFF, "@typescript-eslint/no-misused-promises": [ERROR, { checksVoidReturn: false }], "@typescript-eslint/no-floating-promises": ERROR, "@typescript-eslint/ban-ts-comment": ERROR, "@typescript-eslint/no-array-constructor": ERROR, "@typescript-eslint/no-duplicate-enum-values": ERROR, "@typescript-eslint/no-explicit-any": ERROR, "@typescript-eslint/no-extra-non-null-assertion": ERROR, "@typescript-eslint/no-misused-new": ERROR, "@typescript-eslint/no-namespace": ERROR, "@typescript-eslint/no-non-null-asserted-optional-chain": ERROR, "@typescript-eslint/no-require-imports": ERROR, "@typescript-eslint/no-this-alias": ERROR, "@typescript-eslint/no-unnecessary-type-constraint": ERROR, "@typescript-eslint/no-unsafe-declaration-merging": ERROR, "@typescript-eslint/no-unsafe-function-type": ERROR, "@typescript-eslint/no-wrapper-object-types": ERROR, "@typescript-eslint/prefer-as-const": "error", "@typescript-eslint/prefer-namespace-keyword": ERROR, "@typescript-eslint/triple-slash-reference": ERROR, "@typescript-eslint/no-empty-object-type": WARN, "@typescript-eslint/no-unused-vars": [ WARN, { args: "all", argsIgnorePattern: "^_", caughtErrors: "all", caughtErrorsIgnorePattern: "^_", destructuredArrayIgnorePattern: "^_", varsIgnorePattern: "^_", ignoreRestSiblings: true } ], "@typescript-eslint/consistent-type-imports": [ WARN, { prefer: "type-imports", disallowTypeAnnotations: true, fixStyle: "inline-type-imports" } ] } } : null, hasTestingLibrary ? { name: "eslint/config/testing-library", files: testFiles, ignores: playwrightFiles, plugins: { "testing-library": testingLibraryPlugin }, rules: { "testing-library/no-unnecessary-act": [ERROR, { isStrict: false }], "testing-library/no-wait-for-side-effects": ERROR, "testing-library/prefer-find-by": ERROR } } : null, hasJestDom ? { name: "eslint/config/jest-dom", files: testFiles, ignores: playwrightFiles, plugins: { "jest-dom": jestDomPlugin }, rules: { "jest-dom/prefer-checked": ERROR, "jest-dom/prefer-enabled-disabled": ERROR, "jest-dom/prefer-focus": ERROR, "jest-dom/prefer-empty": ERROR, "jest-dom/prefer-to-have-value": ERROR, "jest-dom/prefer-to-have-text-content": ERROR, "jest-dom/prefer-required": ERROR } } : null, hasVitest ? { name: "eslint/config/vitest", files: testFiles, ignores: playwrightFiles, plugins: { vitest: vitestPlugin }, settings: { vitest: { typecheck: hasTypeScript } }, languageOptions: { globals: { ...vitestPlugin.environments.env.globals } }, rules: { "vitest/expect-expect": ERROR, "vitest/no-identical-title": ERROR, "vitest/no-commented-out-tests": ERROR, "vitest/valid-title": ERROR, "vitest/valid-expect": ERROR, "vitest/valid-describe-callback": ERROR, "vitest/require-local-test-context-for-concurrent-snapshots": ERROR, "vitest/no-import-node-test": ERROR, "vitest/no-focused-tests": [WARN, { fixable: false }] } } : null, hasPlaywright ? { name: "eslint/config/playwright", files: playwrightFiles, plugins: { playwright: playwrightPlugin }, languageOptions: { globals: globals["shared-node-browser"] }, rules: { "no-empty-pattern": OFF, "playwright/missing-playwright-await": ERROR, "playwright/no-focused-test": ERROR, "playwright/no-networkidle": ERROR, "playwright/no-unsafe-references": ERROR, "playwright/valid-describe-callback": ERROR, "playwright/valid-expect": ERROR, "playwright/valid-expect-in-promise": ERROR, "playwright/valid-title": ERROR, "playwright/prefer-web-first-assertions": ERROR, "playwright/no-standalone-expect": ERROR, "playwright/expect-expect": WARN, "playwright/max-nested-describe": WARN, "playwright/no-conditional-expect": WARN, "playwright/no-conditional-in-test": WARN, "playwright/no-element-handle": WARN, "playwright/no-eval": WARN, "playwright/no-force-option": WARN, "playwright/no-nested-step": WARN, "playwright/no-page-pause": WARN, "playwright/no-skipped-test": WARN, "playwright/no-useless-await": WARN, "playwright/no-useless-not": WARN, "playwright/no-wait-for-selector": WARN, "playwright/no-wait-for-timeout": WARN } } : null, hasStorybook ? { name: "eslint/config/storybook", plugins: { storybook: storybookPlugin } } : null, hasStorybook ? { name: "eslint/config/storybook/stories", files: ["**/*.stories.@(ts|tsx|js|jsx|mjs|cjs)", "**/*.story.@(ts|tsx|js|jsx|mjs|cjs)"], rules: { "storybook/await-interactions": ERROR, "storybook/context-in-play-function": ERROR, "storybook/default-exports": ERROR, "storybook/story-exports": ERROR, "storybook/use-storybook-expect": ERROR, "storybook/use-storybook-testing-library": ERROR, "storybook/no-redundant-story-name": WARN, "storybook/prefer-pascal-case": WARN, "storybook/hierarchy-separator": WARN, "react-hooks/rules-of-hooks": OFF, "import/no-anonymous-default-export": OFF } } : null, hasStorybook ? { name: "eslint/config/storybook/main", files: [".storybook/main.@(js|cjs|mjs|ts|tsx)"], rules: { "storybook/no-uninstalled-addons": ERROR } } : null ].filter(Boolean); var index_default = config; export { index_default as default };