UNPKG

@hannoeru/eslint-plugin

Version:
805 lines (789 loc) 25.3 kB
import config from 'eslint-config-standard/.eslintrc.json'; const TSEquivalents = [ "comma-spacing", "brace-style", "func-call-spacing", "indent", "keyword-spacing", "lines-between-class-members", "no-array-constructor", "no-dupe-class-members", "no-redeclare", "no-unused-vars", "no-unused-expressions", "no-useless-constructor", "no-use-before-define", "quotes", "semi", "space-before-function-paren", "space-infix-ops", "object-curly-spacing" ]; const VueEquivalents = [ // Extension rules "array-bracket-spacing", "arrow-spacing", "block-spacing", "brace-style", "camelcase", "comma-dangle", "comma-spacing", "comma-style", "dot-location", "dot-notation", "eqeqeq", "func-call-spacing", "key-spacing", "keyword-spacing", "no-constant-condition", "no-empty-pattern", "no-extra-parens", "no-irregular-whitespace", "no-loss-of-precision", "no-sparse-arrays", "object-curly-newline", "object-curly-spacing", "object-property-newline", "object-shorthand", "operator-linebreak", "quote-props", "space-in-parens", "space-infix-ops", "space-unary-ops", "template-curly-spacing" ]; const configStandard = config; const resolverExtensions = [".js", ".jsx", ".mjs", ".cjs", ".json"]; const resolverExtensionsWithTS = [".ts", ".tsx", ...resolverExtensions]; function defineConfig(config) { return config; } function definePlugin(plugin) { return plugin; } const ruleFromStandard = (name) => { if (configStandard.rules === void 0) throw new Error("rules can not be undefined"); const rule = configStandard.rules[name]; if (rule === void 0) throw new Error("rule can not be undefined"); if (typeof rule !== "object") return rule; return JSON.parse(JSON.stringify(rule)); }; function fromEntries(iterable) { return [...iterable].reduce((obj, [key, val]) => { obj[key] = val; return obj; }, {}); } const ignorePatterns = [ "*.min.*", "CHANGELOG.md", "dist", "LICENSE*", "output", "coverage", "public", "temp", "packages-lock.json", "pnpm-lock.yaml", "yarn.lock", "__snapshots__", "!.github", "!.vitepress", "!.vscode" ]; const core = defineConfig({ plugins: ["@hannoeru", "html", "eslint-comments"], extends: ["standard"], ignorePatterns, rules: { // Common "curly": ["error", "multi-line"], "quote-props": ["error", "consistent-as-needed"], "no-unused-vars": ["error", { argsIgnorePattern: "^_" }], "no-param-reassign": "off", "camelcase": "off", "comma-dangle": ["error", "always-multiline"], "no-console": "error", "no-cond-assign": ["error", "always"], "no-return-await": "error", "operator-linebreak": ["error", "before"], "space-before-function-paren": ["error", "never"], "no-use-before-define": ["error", { functions: false, classes: false, variables: true }], "no-restricted-syntax": [ "error", "DebuggerStatement", "LabeledStatement", "WithStatement" ], // formatting "spaced-comment": ["error", "always", { line: { markers: ["/"], exceptions: ["/", "#"] }, block: { markers: ["!"], exceptions: ["*"], balanced: true } }], "sort-imports": [ "error", { ignoreCase: false, ignoreDeclarationSort: true, ignoreMemberSort: false, memberSyntaxSortOrder: ["none", "all", "multiple", "single"], allowSeparatedGroups: false } ], // eslint-comments // Require a eslint-enable comment for every eslint-disable comment "eslint-comments/disable-enable-pair": ["error", { allowWholeFile: true }], // Disallow a eslint-enable comment for multiple eslint-disable comments "eslint-comments/no-aggregating-enable": "error", // Disallow duplicate eslint-disable comments "eslint-comments/no-duplicate-disable": "error", // Disallow eslint-disable comments without rule names "eslint-comments/no-unlimited-disable": "error", // Disallow unused eslint-disable comments "eslint-comments/no-unused-disable": "error", // Disallow unused eslint-enable comments "eslint-comments/no-unused-enable": "error", // Disallow eslint-disable comments about specific rules "eslint-comments/no-restricted-disable": "off", // Disallow ESLint directive-comments entirely "eslint-comments/no-use": "off", // best-practice "block-scoped-var": "error", "eqeqeq": ["error", "smart"], "no-alert": "warn", "vars-on-top": "error" } }); const esnext = defineConfig({ env: { es6: true, browser: true, node: true, es2021: true }, parserOptions: { ecmaVersion: 2021, sourceType: "module" }, extends: [ "plugin:@hannoeru/core", "plugin:import/recommended", "plugin:promise/recommended" ], plugins: ["unicorn"], settings: { "import/resolver": { node: { extensions: resolverExtensions } } }, overrides: [ { files: ["scripts/**/*"], rules: { "no-console": "off" } }, { files: ["*.test.ts", "*.test.js", "*.spec.ts", "*.spec.js"], rules: { "no-unused-expressions": "off" } } ], rules: { "no-var": "error", "prefer-const": [ "error", { destructuring: "any", ignoreReadBeforeAssign: true } ], "prefer-arrow-callback": [ "error", { allowNamedFunctions: false, allowUnboundThis: true } ], "object-shorthand": [ "error", "always", { ignoreConstructors: false, avoidQuotes: true } ], "prefer-exponentiation-operator": "error", "prefer-rest-params": "error", "prefer-spread": "error", "prefer-template": "error", "arrow-parens": ["error", "as-needed", { requireForBlockBody: true }], "generator-star-spacing": ["error", { before: true, after: false }], // import "import/order": [ "error", { groups: [ "builtin", "external", "internal", "parent", "sibling", "index", "type" ], pathGroups: [ { pattern: "@/**", group: "external", position: "after" }, { pattern: "~/**", group: "external", position: "after" } ], pathGroupsExcludedImportTypes: ["type"] } ], "import/first": "error", "import/no-mutable-exports": "error", "import/no-unresolved": "off", "import/no-absolute-path": "off", "import/no-named-as-default-member": "off", // promise "promise/always-return": "off", "promise/catch-or-return": "off", // unicorns // Pass error message when throwing errors "unicorn/error-message": "error", // Uppercase regex escapes "unicorn/escape-case": "error", // Array.isArray instead of instanceof "unicorn/no-instanceof-array": "error", // Prevent deprecated `new Buffer()` "unicorn/no-new-buffer": "error", // Keep regex literals safe! "unicorn/no-unsafe-regex": "off", // Lowercase number formatting for octal, hex, binary (0x1'error' instead of 0X1'error') "unicorn/number-literal-case": "error", // includes over indexOf when checking for existence "unicorn/prefer-includes": "error", // String methods startsWith/endsWith instead of more complicated stuff "unicorn/prefer-starts-ends-with": "error", // textContent instead of innerText "unicorn/prefer-text-content": "error", // Enforce throwing type error when throwing error while checking typeof "unicorn/prefer-type-error": "error", // Use new when throwing error "unicorn/throw-new-error": "error" } }); const react = defineConfig({ overrides: [ { files: ["*.jsx", "*.tsx"], extends: [ "plugin:react/recommended", "plugin:react/jsx-runtime", "plugin:react-hooks/recommended" ], settings: { react: { version: "17" } }, rules: { "jsx-quotes": [ "error", "prefer-double" ], // off "react/prop-types": "off", "react/no-unknown-property": "off", // Prevent usage of button elements without an explicit type attribute "react/button-has-type": "off", // Prevent missing displayName in a React component definition "react/display-name": ["error", { ignoreTranspilerName: false }], // Prevent extraneous defaultProps on components "react/default-props-match-prop-types": "error", // Forbid foreign propTypes; forbids using another component's prop types unless they are explicitly imported/exported "react/forbid-foreign-prop-types": "error", // Forbid certain propTypes "react/forbid-prop-types": ["error", { forbid: ["any", "array"] }], // Standardize the way function component get defined "react/function-component-definition": [ "error", { namedComponents: "arrow-function", unnamedComponents: "arrow-function" } ], // Enforce using <> instead of <React.Fragment> for fragments "react/jsx-fragments": ["error", "syntax"], // Prevent using this.state within a this.setState "react/no-access-state-in-setstate": "error", // Prevent using Array index in key prop "react/no-array-index-key": "error", // Prevent usage of setState in componentDidUpdate "react/no-did-update-set-state": "error", // Prevent usage of shouldComponentUpdate when extending React.PureComponent "react/no-redundant-should-component-update": "error", // Prevent this from being used in stateless functional components "react/no-this-in-sfc": "error", // Prevent common casing typos "react/no-typos": "error", // Prevent usage of UNSAFE_ methods "react/no-unsafe": ["error", { checkAliases: true }], // Prevent definitions of unused prop types "react/no-unused-prop-types": "error", // Attempts to discover all state fields in a React component and warn if any of them are never read. "react/no-unused-state": "error", // Prevent usage of setState in componentWillUpdate "react/no-will-update-set-state": "error", // Enforce ES5 or ES6 class for React Components "react/prefer-es6-class": "error", // Enforce stateless React Components to be written as a pure function "react/prefer-stateless-function": ["error", { ignorePureComponents: true }], // Prevent extra closing tags for components without children "react/self-closing-comp": "error", // Enforce state initialization style "react/state-in-constructor": ["error", "never"], // Enforce style prop value being an object "react/style-prop-object": "error", // Prevent void DOM elements (e.g. <img />, <br />) from receiving children "react/void-dom-elements-no-children": "error", // JSX // Enforce a new line after jsx elements and expressions "react/jsx-newline": "off", // Prevent usage of javascript: in URLs "react/jsx-no-script-url": "error", // Prevent react contexts from taking non-stable values "react/jsx-no-constructed-context-values": "error", // Enforce boolean attributes notation in JSX "react/jsx-boolean-value": "error", // Enforce or disallow spaces inside of curly braces in JSX attributes and expressions "react/jsx-child-element-spacing": "error", // Validate closing bracket location in JSX "react/jsx-closing-bracket-location": ["error", { location: "tag-aligned" }], // Validate closing tag location in JSX "react/jsx-closing-tag-location": "error", // Enforce curly braces or disallow unnecessary curly braces in JSX props and/or children "react/jsx-curly-brace-presence": ["error", { propElementValues: "always" }], // Enforce or disallow spaces inside of curly braces in JSX attributes "react/jsx-curly-spacing": ["error", "never", { allowMultiline: true }], // Enforce or disallow spaces around equal signs in JSX attributes "react/jsx-equals-spacing": ["error", "never"], // Restrict file extensions that may contain JSX "react/jsx-filename-extension": ["error", { extensions: [".jsx", ".tsx"] }], // Validate props indentation in JSX "react/jsx-indent-props": ["error", 2], // Validate JSX indentation "react/jsx-indent": ["error", 2], "react/jsx-key": [ "error", { checkFragmentShorthand: true, checkKeyMustBeforeSpread: true } ], // Enforce position of the first prop in JSX "react/jsx-first-prop-new-line": ["error", "multiline"], // Limit maximum of props on a single line in JSX "react/jsx-max-props-per-line": ["error", { maximum: { single: 5, multi: 1 } }], // Disallow unnecessary fragments "react/jsx-no-useless-fragment": ["error", { allowExpressions: true }], // Limits every line in JSX to one expression each "react/jsx-one-expression-per-line": "off", // Enforce PascalCase for user-defined JSX components "react/jsx-pascal-case": [ "error", { allowNamespace: true, allowLeadingUnderscore: true } ], // Disallow multiple spaces between inline JSX props (fixable) "react/jsx-props-no-multi-spaces": "error", // Validate whitespace in and around the JSX opening and closing brackets "react/jsx-tag-spacing": "error", // Prevent missing parentheses around multilines JSX "react/jsx-wrap-multilines": "error" } } ] }); const next = defineConfig({ extends: [ "plugin:@hannoeru/typescript", "plugin:@hannoeru/react", "plugin:@hannoeru/json", "plugin:@hannoeru/yml", "plugin:@next/next/recommended" ], plugins: ["jsx-a11y"], rules: { "import/no-anonymous-default-export": "warn", "jsx-a11y/alt-text": [ "warn", { elements: ["img"], img: ["Image"] } ], "jsx-a11y/aria-props": "warn", "jsx-a11y/aria-proptypes": "warn", "jsx-a11y/aria-unsupported-elements": "warn", "jsx-a11y/role-has-required-aria-props": "warn", "jsx-a11y/role-supports-aria-props": "warn" } }); const json = defineConfig({ extends: [ "plugin:jsonc/recommended-with-jsonc" ], overrides: [ { files: ["*.json", "*.json5", "*.jsonc"], parser: "jsonc-eslint-parser", rules: { "jsonc/array-bracket-spacing": "error", "jsonc/comma-style": ["error", "last"], "jsonc/indent": ["error", 2], "jsonc/key-spacing": ["error", { beforeColon: false, afterColon: true }], "jsonc/no-octal-escape": "error", "jsonc/object-curly-newline": ["error", { multiline: true, consistent: true }], "jsonc/object-curly-spacing": ["error", "always"], "jsonc/object-property-newline": ["error", { allowMultiplePropertiesPerLine: true }] } }, { files: ["package.json"], parser: "jsonc-eslint-parser", rules: { "jsonc/sort-keys": [ "error", { pathPattern: "^$", order: [ "name", "type", "version", "private", "packageManager", "description", "keywords", "license", "author", "repository", "funding", "main", "module", "types", "unpkg", "jsdelivr", "exports", "files", "bin", "sideEffects", "scripts", "peerDependencies", "peerDependenciesMeta", "dependencies", "optionalDependencies", "devDependencies", "husky", "lint-staged", "eslintConfig" ] }, { pathPattern: "^(?:dev|peer|optional|bundled)?[Dd]ependencies$", order: { type: "asc" } } ] } } ] }); const jest = defineConfig({ extends: [ "plugin:jest/style", "plugin:jest/recommended", "plugin:jest-formatting/recommended" ], settings: { jest: { version: 26 } }, rules: { "jest/prefer-lowercase-title": ["error", { ignore: ["describe"] }], "jest/no-restricted-matchers": [ "error", { toBeTruthy: "Avoid `toBeTruthy`", toBeFalsy: "Avoid `toBeFalsy`", resolves: "Use `expect(await promise)` instead." } ], "jest/valid-title": [ "error", { disallowedWords: [ "correct", "appropriate", "properly", "should", "every", "descriptive" ] } ] } }); const vue = defineConfig({ overrides: [ { files: ["*.vue"], parser: "vue-eslint-parser", parserOptions: { parser: "@typescript-eslint/parser" }, extends: ["plugin:vue/vue3-recommended"], rules: { "no-undef": "off", "no-unused-vars": "off", // Override Recommended "vue/max-attributes-per-line": ["warn", { singleline: 5 }], "vue/no-v-html": "off", "vue/require-default-prop": "off", "vue/multi-word-component-names": "off", // Vue versions of Standard.js rules: ...fromEntries(VueEquivalents.map((name) => [`vue/${name}`, ruleFromStandard(name)])), // Override Standard.js rules "vue/quote-props": ["error", "consistent-as-needed"], "vue/no-unused-vars": ["error", { ignorePattern: "^_" }], "vue/camelcase": "off", "vue/comma-dangle": ["error", "always-multiline"], // Uncategorized rules "vue/block-tag-newline": ["error", { singleline: "always", multiline: "always" }], "vue/component-name-in-template-casing": ["error", "PascalCase"], "vue/component-options-name-casing": ["error", "PascalCase"], "vue/custom-event-name-casing": ["error", "camelCase"], "vue/define-macros-order": ["error", { order: ["defineProps", "defineEmits"] }], "vue/html-comment-content-spacing": ["error", "always"], "vue/html-comment-indent": ["error", 2], "vue/no-restricted-v-bind": ["error", "/^v-/"], "vue/no-useless-v-bind": "error", "vue/padding-line-between-blocks": ["error", "always"], "vue/prefer-separate-static-class": "error" } } ] }); const yml = defineConfig({ extends: [ "plugin:yml/standard" ], rules: { "yml/quotes": ["error", { prefer: "single", avoidEscape: false }], "yml/no-empty-document": "off", "yml/no-empty-mapping-value": "off" } }); const markdown = defineConfig({ extends: [ "plugin:markdown/recommended" ], overrides: [ { files: ["**/*.md"], rules: { indent: "off" } }, { // Code blocks in markdown file files: ["**/*.md/*.*"], rules: { "no-alert": "off", "no-console": "off", "no-restricted-imports": "off", "no-undef": "off", "no-unused-vars": "off", "no-unused-expressions": "off", "react/jsx-no-undef": "off", "import/no-unresolved": "off", "import/namespace": "off", "import/default": "off", "import/no-named-as-default": "off", "@typescript-eslint/no-redeclare": "off", "@typescript-eslint/no-unused-vars": "off", "@typescript-eslint/no-use-before-define": "off", "@typescript-eslint/no-var-requires": "off" } } ] }); const typescript = defineConfig({ extends: [ "plugin:@hannoeru/esnext", "plugin:import/typescript" ], settings: { "import/extensions": resolverExtensionsWithTS, "import/external-module-folders": ["node_modules", "node_modules/@types"], "import/parsers": { "@typescript-eslint/parser": [".ts", ".tsx"] }, "import/resolver": { node: { extensions: resolverExtensionsWithTS }, typescript: { alwaysTryTypes: true } } }, overrides: [ { files: ["*.d.ts"], rules: { "import/no-duplicates": "off" } }, { files: ["*.ts", "*.tsx"], extends: ["plugin:@typescript-eslint/recommended"], rules: { // Rules replaced by @typescript-eslint versions: ...fromEntries(TSEquivalents.map((name) => [name, "off"])), // @typescript-eslint versions of Standard.js rules: ...fromEntries(TSEquivalents.map((name) => [`@typescript-eslint/${name}`, ruleFromStandard(name)])), // Override custom JS rules "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], "@typescript-eslint/no-use-before-define": ["error", { functions: false, classes: false, enums: false, variables: true, typedefs: false // Only the TypeScript rule has this option. }], "@typescript-eslint/comma-dangle": ["error", "always-multiline"], "@typescript-eslint/space-before-function-paren": ["error", "never"], // TS "@typescript-eslint/ban-ts-comment": ["error", { "ts-ignore": "allow-with-description" }], "@typescript-eslint/member-delimiter-style": [ "error", { multiline: { delimiter: "none" }, singleline: { delimiter: "comma", requireLast: false } } ], "@typescript-eslint/type-annotation-spacing": "error", "@typescript-eslint/consistent-type-imports": ["error", { prefer: "type-imports", disallowTypeAnnotations: false }], "@typescript-eslint/consistent-indexed-object-style": ["error", "record"], "@typescript-eslint/consistent-type-definitions": ["error", "interface"], "@typescript-eslint/prefer-ts-expect-error": "error", // off "@typescript-eslint/naming-convention": "off", "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-member-accessibility": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/parameter-properties": "off", "@typescript-eslint/no-empty-interface": "off", "@typescript-eslint/ban-ts-ignore": "off", "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/ban-types": "off", "@typescript-eslint/no-var-requires": "off", "@typescript-eslint/no-namespace": "off" } } ] }); const typescriptTypeChecking = defineConfig({ overrides: [ { files: ["*.ts", "*.tsx"], rules: { "@typescript-eslint/await-thenable": "error", "@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/no-for-in-array": "error", "no-implied-eval": "off", "@typescript-eslint/no-implied-eval": "error", "@typescript-eslint/no-misused-promises": "error", "@typescript-eslint/no-unsafe-argument": "error", "@typescript-eslint/no-unsafe-assignment": "error", "@typescript-eslint/no-unsafe-call": "error", "@typescript-eslint/no-unsafe-member-access": "error", "@typescript-eslint/no-unsafe-return": "error", "require-await": "off", "@typescript-eslint/require-await": "error", "@typescript-eslint/restrict-plus-operands": "error", "@typescript-eslint/restrict-template-expressions": "error", "@typescript-eslint/unbound-method": "error", "@typescript-eslint/no-unnecessary-condition": ["error"], "@typescript-eslint/no-unnecessary-type-assertion": "error", "@typescript-eslint/no-unnecessary-type-arguments": "error", "@typescript-eslint/prefer-includes": "error", "@typescript-eslint/prefer-readonly": [ "error", { onlyInlineLambdas: false } ], "@typescript-eslint/promise-function-async": "error", "dot-notation": "off", "@typescript-eslint/dot-notation": "error", "no-throw-literal": "off", "@typescript-eslint/no-throw-literal": "error" } } ] }); const plugin = definePlugin({ rules: {}, configs: { // basic core, esnext, // frameworks vue, react, next, // ts typescript, typescriptTypeChecking, // config format yml, json, // test jest, // docs markdown } }); const { rules, configs } = plugin; export { configs, rules };