UNPKG

@xenon.js/configs

Version:

All common configuration related to JavaScript.

450 lines (409 loc) 17.5 kB
import comments from "@eslint-community/eslint-plugin-eslint-comments/configs"; import { convertIgnorePatternToMinimatch } from "@eslint/compat"; import js from "@eslint/js"; import html from "@html-eslint/eslint-plugin"; import jsdoc from "eslint-plugin-jsdoc"; import { getJsdocProcessorPlugin } from "eslint-plugin-jsdoc/getJsdocProcessorPlugin.js"; import node from "eslint-plugin-n"; import nodeSecurity from "eslint-plugin-security"; import { existsSync, readFileSync } from "fs"; import { isAbsolute, resolve } from "path"; import { cwd } from "process"; import { config as defineConfig, configs as tsConfigs, parser as tsParser, } from "typescript-eslint"; import { javascriptExt, scriptExt, typescriptExt } from "./glob.js"; import jsdocTagSequence from "./jsdoc-tag-sequence.js"; /** @import { Linter } from "eslint" */ /** * @typedef Config * * @property {string} [gitignorePath] .gitignore 路径,默认 `resolve(cwd(), ".gitignore")` * @property {boolean} [ignoreConfigFiles] 忽略所有 *.config 配置文件,比如 eslint.config.js,默认 `true` * @property {"none" | "loose" | "strict"} [jsdocLevel] JSDoc 检查级别,默认 `loose` * @property {string[]} [jsdocDefinedTags] 额外允许的 JSDoc 标签列表 */ /** * @param {Config} config * @returns {Linter.Config} */ export function config(config = {}) { const { gitignorePath = resolve(cwd(), ".gitignore"), ignoreConfigFiles = true, jsdocLevel = "loose", jsdocDefinedTags = [], } = config; return defineConfig( // 忽略文件 gitignorePath ? includeIgnoreFile(gitignorePath) : {}, ignoreConfigFiles ? { // 比如根目录的 eslint.config.js ignores: [`**/*.config.${scriptExt}`], } : {}, // 默认文件 { files: [`**/*.${scriptExt}`], }, // 基本配置 { languageOptions: { ecmaVersion: "latest", sourceType: "module", parserOptions: { projectService: true, ecmaFeatures: { jsx: true, }, }, }, linterOptions: { reportUnusedDisableDirectives: "error", }, }, // 推荐配置 js.configs.recommended, ...tsConfigs.strictTypeChecked, ...tsConfigs.stylisticTypeChecked, comments.recommended, // 偏好配置 { rules: { // 不在 @eslint/js 推荐规则内,自认为需要启用的规则 "array-callback-return": ["error", { checkForEach: false }], "no-constructor-return": "error", "no-promise-executor-return": "error", "no-self-compare": "error", "no-template-curly-in-string": "warn", "no-unmodified-loop-condition": "error", "default-case-last": "error", "eqeqeq": ["error", "smart"], "grouped-accessor-pairs": ["warn", "getBeforeSet"], // 本条已在 @typescript-eslint 推荐规则中 // "no-array-constructor": "off", // "@typescript-eslint/no-array-constructor": "error", "no-caller": "error", "no-iterator": "error", "no-label-var": "error", "no-new-wrappers": "error", "no-object-constructor": "error", "no-return-assign": "error", "no-useless-return": "warn", "no-var": "error", "prefer-object-has-own": "error", "prefer-regex-literals": "warn", "prefer-rest-params": "error", "prefer-template": "warn", "require-unicode-regexp": "warn", // 被 @typescript-eslint 启用,但错误级别太高 "prefer-const": "warn", // 由于其它规则要求必须处理 Promise,当显式不进行处理时则会使用 void "no-void": "off", // 有时候保留未使用的东西有用处,所以禁用该规则 "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": "off", // FUTURE 现在还有很多地方需要使用 namespace,未到禁用的时候 "@typescript-eslint/no-namespace": "off", // 现在有很多地方需要使用 ! ,未到禁用的时候 "@typescript-eslint/no-non-null-assertion": "off", // 有用处,并且暂时不会被滥用,不需要禁用 "@typescript-eslint/no-this-alias": "off", // FUTURE 未完全了解函数的协逆变规则,暂时还需要使用 Function 类型 "@typescript-eslint/no-unsafe-function-type": "off", // 在推荐规则内,但自认为没有必要启用的规则 "@typescript-eslint/dot-notation": "off", "@typescript-eslint/no-inferrable-types": "off", "@typescript-eslint/prefer-literal-enum-member": "off", "@typescript-eslint/no-unnecessary-type-parameters": "off", "@typescript-eslint/no-unnecessary-type-arguments": "off", "@typescript-eslint/consistent-type-assertions": "off", "@typescript-eslint/consistent-type-definitions": "off", "@typescript-eslint/no-extraneous-class": "off", "@typescript-eslint/class-literal-property-style": "off", "@typescript-eslint/no-unsafe-declaration-merging": "off", "@typescript-eslint/unified-signatures": "off", "@typescript-eslint/prefer-promise-reject-errors": "off", // 在推荐规则内,但需允许 (...args: any[]) 用法 "@typescript-eslint/no-explicit-any": [ "error", { ignoreRestArgs: true }, ], // 在推荐规则内,但需允许传入数值 "@typescript-eslint/restrict-template-expressions": [ "error", { allowAny: true, allowNumber: true, }, ], // 在推荐规则内,但需更宽松 "@typescript-eslint/no-confusing-void-expression": [ "error", { ignoreArrowShorthand: true, ignoreVoidOperator: true, }, ], // 在推荐规则内,但需允许 while (true) 用法 "@typescript-eslint/no-unnecessary-condition": [ "error", { allowConstantLoopConditions: true, checkTypePredicates: true, }, ], // 在推荐的规则内,但需更宽松 "@typescript-eslint/no-unused-expressions": [ "error", { allowShortCircuit: true, allowTernary: true, allowTaggedTemplates: true, enforceForJSX: true, }, ], // 在推荐的规则内,但需允许空的 interface // "@typescript-eslint/no-empty-object-type": [ // "error", // { // allowInterfaces: "always", // }, // ], // 某些类型好像必须使用 {} 才能正常运作,暂时关闭 "@typescript-eslint/no-empty-object-type": "off", // 在推荐规则内,但需允许 (this: void) 用法 // "@typescript-eslint/no-invalid-void-type": [ // "error", // { allowAsThisParameter: true }, // ], // FUTURE 存在误报的情况,暂时关闭 "@typescript-eslint/no-invalid-void-type": "off", // 在推荐规则内,但需更宽松 // "@typescript-eslint/only-throw-error": [ // "error", // { // allowThrowingAny: true, // allowThrowingUnknown: true, // }, // ], // FUTURE 误报 throw 泛型,暂时关闭 "@typescript-eslint/only-throw-error": "off", // 不在 @typescript-eslint 推荐规则内,自认为需要启用的规则 "@typescript-eslint/no-unnecessary-qualifier": "error", "@typescript-eslint/promise-function-async": "error", "@typescript-eslint/require-array-sort-compare": "error", "@typescript-eslint/strict-boolean-expressions": [ "error", { allowAny: true, allowNullableBoolean: true, allowNullableEnum: false, }, ], "@typescript-eslint/switch-exhaustiveness-check": [ "error", { requireDefaultForNonUnion: true, }, ], // 该规则可避免 verbatimModuleSyntax 导致意外留下的的副作用导入 "@typescript-eslint/no-import-type-side-effects": "error", // 允许整个文件的 disable 指令 "@eslint-community/eslint-comments/no-unlimited-disable": "off", "@eslint-community/eslint-comments/disable-enable-pair": [ "error", { "allowWholeFile": true }, ], // 如果禁用则必须进行注释 "@eslint-community/eslint-comments/require-description": "error", }, }, // JSDoc ...(jsdocLevel !== "none" ? defineConfig( // 推荐配置 { files: [`**/*.${javascriptExt}`], ...jsdoc.configs["flat/recommended"], }, { files: [`**/*.${typescriptExt}`], ...jsdoc.configs["flat/recommended-typescript"], }, { name: "jsdoc/examples-plugin", plugins: { examples: getJsdocProcessorPlugin({ checkDefaults: true, checkParams: true, checkProperties: true, parser: tsParser, }), }, processor: "examples/examples", }, // 根据源码得知数组第一位是规则配置 jsdoc.configs.examples[1], jsdoc.configs["default-expressions"][1], // FUTURE 代码块兼容 TypeScript 避免报错因为没有 Project { files: [ "**/*.md/*.js", "**/*.jsdoc-defaults", "**/*.jsdoc-params", "**/*.jsdoc-properties", ], ...tsConfigs.disableTypeChecked, }, // 补充一些代码块兼容规则 { files: [ "**/*.md/*.js", "**/*.jsdoc-defaults", "**/*.jsdoc-params", "**/*.jsdoc-properties", ], rules: { "@typescript-eslint/no-unused-expressions": "off", "@typescript-eslint/no-explicit-any": "off", }, }, // 偏好配置 { files: [`**/*.${scriptExt}`], rules: { "jsdoc/check-template-names": "warn", "jsdoc/check-tag-names": [ "warn", { definedTags: jsdocDefinedTags.concat([ // Xenon Reactive "val", "reactive", "shallow", "computed", ]), }, ], "jsdoc/no-bad-blocks": [ "warn", { preventAllMultiAsteriskBlocks: true, }, ], "jsdoc/no-defaults": "off", "jsdoc/no-blank-block-descriptions": "warn", "jsdoc/no-blank-blocks": "warn", "jsdoc/require-asterisk-prefix": "warn", "jsdoc/require-hyphen-before-param-description": [ "warn", "never", ], "jsdoc/require-jsdoc": "off", "jsdoc/tag-lines": [ "warn", "any", { startLines: 1, }, ], "jsdoc/sort-tags": [ "warn", { tagSequence: jsdocTagSequence, alphabetizeExtras: true, }, ], }, }, // 宽松配置 ...(jsdocLevel === "loose" ? defineConfig({ files: [`**/*.${scriptExt}`], rules: { "jsdoc/check-param-names": [ "warn", { disableMissingParamChecks: true, }, ], "jsdoc/require-param": "off", "jsdoc/require-returns": "off", "jsdoc/require-yields": "off", }, }) : []), ) : []), ); } /** * @typedef ConfigForNodeJS * * @property {boolean} [includeSecurityRules] 是否启用安全性规则,默认 `true` */ /** * 针对 Node.JS 的配置 * * 建议配置 `engines` 字段以提供准确的检查,详情请查看 [NPM 文档](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#engines) * * @param {ConfigForNodeJS} config * @returns {Linter.Config} */ export function configForNodeJS(config = {}) { const { includeSecurityRules = true } = config; return defineConfig( node.configs["flat/recommended"], includeSecurityRules ? nodeSecurity.configs.recommended : {}, ); } /** * @typedef ConfigForWeb */ /** * 针对 Web 的配置 * * 建议配置 `engines` 字段以提供准确的检查,详情请查看 [NPM 文档](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#engines) * * @param {ConfigForWeb} config * @returns {Linter.Config} */ export function configForWeb(config = {}) { const {} = config; return defineConfig({ files: ["**/*.html"], ...html.configs["flat/recommended"], }); } /** * The Code copied from @eslint/compat, but with a few modifications to handle it correctly. * * @param {string} ignoreFilePath The absolute path to the ignore file. * @returns {Linter.Config} An object with an `ignores` property that is an array of ignore patterns. * @throws {Error} If the ignore file path is not an absolute path. */ function includeIgnoreFile(ignoreFilePath) { if (!isAbsolute(ignoreFilePath)) { throw new Error("The ignore file location must be an absolute path."); } if (!existsSync(ignoreFilePath)) { return {}; } const ignoreFile = readFileSync(ignoreFilePath, "utf8"); const lines = ignoreFile.split(/\r?\n/u); return { name: "Imported .gitignore patterns", ignores: lines .map(line => line.trim()) .filter(line => line && !line.startsWith("#")) // no '/' then no dir .map(v => (v.at(-1) === "/" ? [v] : [v, v + "/"])) .flat(1) .map(convertIgnorePatternToMinimatch), }; }