UNPKG

@ntnyq/eslint-config

Version:

An opinionated ESLint config preset of ntnyq

1,849 lines (1,820 loc) 96.5 kB
import { FlatConfigComposer } from "eslint-flat-config-utils"; import { mergeProcessors, mergeProcessors as mergeProcessors$1, processorPassThrough } from "eslint-merge-processors"; import { configs as configsTypeScript, parser as parserTypeScript, plugin as pluginTypeScript } from "typescript-eslint"; import * as parserVue from "vue-eslint-parser"; import * as parserToml from "toml-eslint-parser"; import * as parserYaml from "yaml-eslint-parser"; import * as parserPlain from "eslint-parser-plain"; import * as parserJsonc from "jsonc-eslint-parser"; import * as pluginRegexp from "eslint-plugin-regexp"; import pluginNode from "eslint-plugin-n"; import pluginVue from "eslint-plugin-vue"; import pluginYml from "eslint-plugin-yml"; import pluginSvgo from "eslint-plugin-svgo"; import pluginToml from "eslint-plugin-toml"; import pluginMarkdown from "@eslint/markdown"; import pluginAntfu from "eslint-plugin-antfu"; import pluginJsdoc from "eslint-plugin-jsdoc"; import pluginJsonc from "eslint-plugin-jsonc"; import pluginNtnyq from "eslint-plugin-ntnyq"; import pluginPinia from "eslint-plugin-pinia"; import pluginDepend from "eslint-plugin-depend"; import pluginUnoCSS from "@unocss/eslint-plugin"; import pluginVitest from "@vitest/eslint-plugin"; import pluginUnicorn from "eslint-plugin-unicorn"; import pluginImportX from "eslint-plugin-import-x"; import pluginPrettier from "eslint-plugin-prettier"; import pluginDeMorgan from "eslint-plugin-de-morgan"; import pluginNoOnlyTests from "eslint-plugin-no-only-tests"; import pluginGitHubAction from "eslint-plugin-github-action"; import pluginPerfectionist from "eslint-plugin-perfectionist"; import pluginComments from "@eslint-community/eslint-plugin-eslint-comments"; import processorVueBlocks from "eslint-processor-vue-blocks"; import { resolve } from "node:path"; import process from "node:process"; import { isPackageExists } from "local-pkg"; import { builtinCommands, defineCommand } from "eslint-plugin-command/commands"; import createCommandConfig from "eslint-plugin-command/config"; import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript"; import globals from "globals"; import createGitIgnoreConfig from "eslint-config-flat-gitignore"; import jsConfig from "@eslint/js"; //#region src/globs.ts /** * @file globs constants */ const GLOB_SRC_EXT = "?([cm])[jt]s?(x)"; const GLOB_SRC = `**/*.${GLOB_SRC_EXT}`; const GLOB_JS = "**/*.?([cm])js"; const GLOB_JSX = `${GLOB_JS}x`; const GLOB_JSX_ONLY = "**/*.jsx"; const GLOB_TS = "**/*.?([cm])ts"; const GLOB_TSX = `${GLOB_TS}x`; const GLOB_TSX_ONLY = "**/*.tsx"; const GLOB_DTS = "**/*.d.?([cm])ts"; const GLOB_TYPES = [ GLOB_DTS, `**/types/${GLOB_TS}`, `**/types.ts` ]; const GLOB_TYPE_TEST = [`**/*.test-d.${GLOB_SRC_EXT}`, `**/*.spec-d.${GLOB_SRC_EXT}`]; const GLOB_TEST = [ `**/*.test.${GLOB_SRC_EXT}`, `**/*.spec.${GLOB_SRC_EXT}`, `**/*.bench.${GLOB_SRC_EXT}`, `**/*.benchmark.${GLOB_SRC_EXT}`, ...GLOB_TYPE_TEST ]; const GLOB_STYLE = "**/*.{c,le,sc}ss"; const GLOB_CSS = "**/*.css"; const GLOB_LESS = "**/*.less"; const GLOB_SCSS = "**/*.scss"; const GLOB_POSTCSS = "**/*.{p,post}css"; const GLOB_JSON = "**/*.json"; const GLOB_JSON5 = "**/*.json5"; const GLOB_JSONC = "**/*.jsonc"; const GLOB_PACKAGE_JSON = "**/package.json"; const GLOB_JSON_SCHEMA = ["**/*.schema.json", "**/schemas/*.json"]; const GLOB_TSCONFIG_JSON = ["**/tsconfig.json", "**/tsconfig.*.json"]; const GLOB_YAML = "**/*.y?(a)ml"; const GLOB_PNPM_WORKSPACE_YAML = "**/pnpm-workspace.yaml"; const GLOB_SVG = "**/*.svg"; const GLOB_VUE = "**/*.vue"; const GLOB_SVELTE = "**/*.svelte?(.{js,ts})"; const GLOB_TOML = "**/*.toml"; const GLOB_HTML = "**/*.htm?(l)"; const GLOB_ASTRO = "**/*.astro"; const GLOB_ASTRO_TS = "**/*.astro/*.ts"; const GLOB_MARKDOWN = "**/*.md"; const GLOB_MARKDOWN_CODE = `${GLOB_MARKDOWN}/${GLOB_SRC}`; const GLOB_MARKDOWN_NESTED = `${GLOB_MARKDOWN}/*.md`; const GLOB_ALL_SRC = [ GLOB_SRC, GLOB_STYLE, GLOB_JSON, GLOB_JSON5, GLOB_VUE, GLOB_YAML, GLOB_TOML, GLOB_HTML, GLOB_MARKDOWN ]; const GLOB_PINIA_STORE = `**/store?(s)/*.${GLOB_SRC_EXT}`; const GLOB_GITHUB_ACTION = "**/.github/workflows/*.y?(a)ml"; const GLOB_NODE_MODULES = "**/node_modules/**"; const GLOB_DIST = "**/dist/**"; const GLOB_LOCKFILE = [ "**/package-lock.json", "**/yarn.lock", "**/pnpm-lock.yaml", "**/bun.lock?(b)", "**/deno.lock" ]; const GLOB_EXCLUDE = [ GLOB_NODE_MODULES, GLOB_DIST, ...GLOB_LOCKFILE, "**/.pnpm-store/**", "!.github", "!.vitepress", "!.vuepress", "!.vscode", "**/CHANGELOG*.md", "**/*.min.*", "**/LICENSE*", "**/__snapshots__", "**/auto-import?(s).d.ts", "**/components.d.ts", "**/typed-router.d.ts", "**/uni-pages.d.ts", "**/coverage", "**/fixtures", "**/output", "**/public", "**/static", "**/?(.)temp", "**/?(.)cache", "**/.eslintcache", "**/.stylelintcache", "**/.eslint-config-inspector", "**/.vite-inspect", "**/.nuxt", "**/.wxt", "**/.output", "**/.tsup", "**/.nitro", "**/.vercel", "**/.wrangler", "**/.changeset", "**/.npmrc", "**/.yarnrc", "**/.husky", "**/src-tauri/gen", "**/src-tauri/target" ]; //#endregion //#region src/configs/vue.ts const sharedRules = { ...pluginVue.configs.base.rules, ...pluginVue.configs.essential.rules, ...pluginVue.configs["strongly-recommended"].rules, ...pluginVue.configs.recommended.rules }; const disabledRules = { "vue/multi-word-component-names": "off", "vue/no-setup-props-reactivity-loss": "off", "vue/no-v-html": "off", "vue/no-v-text-v-html-on-component": "off", "vue/require-default-prop": "off", "vue/require-prop-types": "off" }; const extensionRules = { "vue/array-bracket-spacing": ["error", "never"], "vue/arrow-spacing": ["error", { after: true, before: true }], "vue/block-spacing": ["error", "always"], "vue/brace-style": [ "error", "stroustrup", { allowSingleLine: true } ], "vue/comma-dangle": ["error", "always-multiline"], "vue/comma-spacing": ["error", { after: true, before: false }], "vue/comma-style": ["error", "last"], "vue/dot-location": ["error", "property"], "vue/dot-notation": ["error", { allowKeywords: true }], "vue/eqeqeq": ["error", "smart"], "vue/key-spacing": ["error", { afterColon: true, beforeColon: false }], "vue/keyword-spacing": ["error", { after: true, before: true }], "vue/no-constant-condition": "error", "vue/no-empty-pattern": "error", "vue/no-extra-parens": ["error", "functions"], "vue/no-loss-of-precision": "error", "vue/no-negated-condition": "error", "vue/no-restricted-syntax": [ "error", "DebuggerStatement", "LabeledStatement", "WithStatement" ], "vue/no-sparse-arrays": "error", "vue/object-curly-newline": ["error", { consistent: true, multiline: true }], "vue/object-curly-spacing": ["error", "always"], "vue/object-property-newline": ["error", { allowMultiplePropertiesPerLine: true }], "vue/object-shorthand": [ "error", "always", { avoidQuotes: true, ignoreConstructors: false } ], "vue/operator-linebreak": ["error", "before"], "vue/prefer-template": "error", "vue/quote-props": ["error", "consistent-as-needed"], "vue/space-in-parens": ["error", "never"], "vue/space-infix-ops": "error", "vue/space-unary-ops": ["error", { nonwords: false, words: true }], "vue/template-curly-spacing": "error" }; const unCategorizedRules = { "vue/block-order": ["error", { order: [ "script", "template", "style" ] }], "vue/block-tag-newline": ["error", { multiline: "always", singleline: "always" }], "vue/component-api-style": ["error", ["script-setup", "composition"]], "vue/component-name-in-template-casing": [ "error", "PascalCase", { ignores: ["slot", "component"], registeredComponentsOnly: false } ], "vue/component-options-name-casing": ["error", "PascalCase"], "vue/custom-event-name-casing": ["error", "camelCase"], "vue/define-emits-declaration": ["error", "type-literal"], "vue/define-macros-order": ["error", { defineExposeLast: true, order: [ "defineProps", "defineEmits", "defineOptions", "defineSlots", "defineModel" ] }], "vue/define-props-declaration": ["error", "type-based"], "vue/define-props-destructuring": ["error", { destructure: "never" }], "vue/enforce-style-attribute": ["error", { allow: ["scoped", "plain"] }], "vue/html-button-has-type": ["error", { button: true, reset: true, submit: true }], "vue/html-comment-content-newline": [ "error", { multiline: "always", singleline: "ignore" }, { exceptions: ["*"] } ], "vue/html-comment-content-spacing": [ "error", "always", { exceptions: ["-"] } ], "vue/html-comment-indent": ["error", 2], "vue/next-tick-style": ["error", "promise"], "vue/no-deprecated-delete-set": "error", "vue/no-deprecated-model-definition": ["error", { allowVue3Compat: true }], "vue/no-duplicate-attr-inheritance": ["error", { checkMultiRootNodes: true }], "vue/no-empty-component-block": "error", "vue/no-irregular-whitespace": "error", "vue/no-multiple-objects-in-class": "error", "vue/no-negated-v-if-condition": "error", "vue/no-ref-object-reactivity-loss": "error", "vue/no-required-prop-with-default": ["error", { autofix: true }], "vue/no-restricted-v-bind": ["error", "/^v-/"], "vue/no-static-inline-styles": ["error", { allowBinding: true }], "vue/no-unused-emit-declarations": "error", "vue/no-use-v-else-with-v-for": "error", "vue/no-useless-v-bind": "error", "vue/no-v-text": "error", "vue/padding-line-between-blocks": "error", "vue/prefer-define-options": "error", "vue/prefer-prop-type-boolean-first": "error", "vue/prefer-separate-static-class": "error", "vue/prefer-true-attribute-shorthand": ["error", "always"], "vue/prefer-use-template-ref": "error", "vue/require-macro-variable-name": ["error", { defineEmits: "emits", defineProps: "props", defineSlots: "slots", useAttrs: "attrs", useSlots: "slots" }], "vue/require-typed-object-prop": "error", "vue/slot-name-casing": ["error", "kebab-case"], "vue/v-for-delimiter-style": ["error", "in"], "vue/valid-define-options": "error" }; /** * Config for vue files * * @see {@link https://github.com/vuejs/eslint-plugin-vue} * * @param options - {@link ConfigVueOptions} * @returns ESLint configs */ const configVue = (options = {}) => { const { files = [GLOB_VUE], ecmaVersion = "latest", extraFileExtensions = [] } = options; const sfcBlocks = options.sfcBlocks === true ? {} : options.sfcBlocks ?? {}; function getVueProcessor() { const processorVueSFC = pluginVue.processors[".vue"]; if (!sfcBlocks) return processorVueSFC; return mergeProcessors$1([processorVueSFC, processorVueBlocks({ ...sfcBlocks, blocks: { styles: true, ...sfcBlocks.blocks } })]); } return [{ name: "ntnyq/vue/setup", plugins: { "@typescript-eslint": pluginTypeScript, vue: pluginVue } }, { name: "ntnyq/vue/rules", files, processor: getVueProcessor(), languageOptions: { parser: parserVue, parserOptions: { ecmaVersion, extraFileExtensions, parser: parserTypeScript, sourceType: "module", ecmaFeatures: { jsx: true } } }, rules: { ...sharedRules, "vue/attributes-order": ["error", { alphabetical: false, order: [ "EVENTS", "TWO_WAY_BINDING", "OTHER_DIRECTIVES", "LIST_RENDERING", "CONDITIONALS", "CONTENT", "SLOT", "UNIQUE", "DEFINITION", "ATTR_DYNAMIC", "RENDER_MODIFIERS", "GLOBAL", "ATTR_STATIC", "ATTR_SHORTHAND_BOOL" ] }], "vue/html-self-closing": ["error", { math: "always", svg: "always", html: { component: "always", normal: "always", void: "always" } }], "vue/max-attributes-per-line": ["error", { multiline: 1, singleline: 1 }], "vue/order-in-components": ["error", { order: [ "el", "name", "key", "parent", "functional", ["provide", "inject"], ["delimiters", "comments"], [ "components", "directives", "filters" ], "extends", "mixins", "layout", "middleware", "validate", "scrollToTop", "transition", "loading", "inheritAttrs", "model", ["props", "propsData"], "slots", "expose", "emits", "setup", "asyncData", "computed", "data", "fetch", "head", "methods", ["template", "render"], "watch", "watchQuery", "LIFECYCLE_HOOKS", "renderError", "ROUTER_GUARDS" ] }], "vue/prop-name-casing": ["error", "camelCase"], "vue/this-in-template": ["error", "never"], "vue/v-bind-style": [ "error", "shorthand", { sameNameShorthand: "always" } ], ...disabledRules, ...extensionRules, ...unCategorizedRules, ...options.overrides } }]; }; //#endregion //#region src/configs/yml.ts /** * @see {@link https://github.com/ota-meshi/eslint-plugin-yml/blob/master/src/configs/base.ts} */ const disabledCoreRules$1 = { "no-irregular-whitespace": "off", "no-unused-vars": "off", "spaced-comment": "off" }; /** * Config for yml, yaml files * * @see {@link https://ota-meshi.github.io/eslint-plugin-yml} * * @param options - {@link ConfigYmlOptions} * @returns ESLint configs */ const configYml = (options = {}) => { const { files = [GLOB_YAML] } = options; return [{ name: "ntnyq/yml", files, plugins: { yml: pluginYml }, languageOptions: { parser: parserYaml }, rules: { "yml/no-empty-mapping-value": "off", "yml/block-mapping": "error", "yml/block-mapping-question-indicator-newline": "error", "yml/block-sequence": "error", "yml/block-sequence-hyphen-indicator-newline": "error", "yml/flow-mapping-curly-newline": "error", "yml/flow-mapping-curly-spacing": "error", "yml/flow-sequence-bracket-newline": "error", "yml/flow-sequence-bracket-spacing": "error", "yml/indent": "error", "yml/key-spacing": "error", "yml/no-empty-document": "error", "yml/no-empty-key": "error", "yml/no-empty-sequence-entry": "error", "yml/no-irregular-whitespace": "error", "yml/no-tab-indent": "error", "yml/plain-scalar": "error", "yml/quotes": ["error", { avoidEscape: false, prefer: "single" }], "yml/spaced-comment": "error", "yml/vue-custom-block/no-parsing-error": "error", ...disabledCoreRules$1, ...options.prettier ? { "yml/block-mapping-colon-indicator-newline": "off", "yml/block-mapping-question-indicator-newline": "off", "yml/block-sequence-hyphen-indicator-newline": "off", "yml/flow-mapping-curly-newline": "off", "yml/flow-mapping-curly-spacing": "off", "yml/flow-sequence-bracket-newline": "off", "yml/flow-sequence-bracket-spacing": "off", "yml/indent": "off", "yml/key-spacing": "off", "yml/no-multiple-empty-lines": "off", "yml/no-trailing-zeros": "off", "yml/quotes": "off" } : {}, ...options.overrides } }]; }; //#endregion //#region src/utils/env.ts const hasPinia = () => isPackageExists("pinia"); const hasVitest = () => isPackageExists("vitest"); const hasTypeScript = () => isPackageExists("typescript"); const hasShadcnVue = () => (isPackageExists("radix-vue") || isPackageExists("reka-ui")) && isPackageExists("class-variance-authority") && isPackageExists("clsx"); const hasUnoCSS = () => isPackageExists("unocss") || isPackageExists("@unocss/postcss") || isPackageExists("@unocss/webpack") || isPackageExists("@unocss/nuxt"); const hasVue = () => isPackageExists("vue") || isPackageExists("nuxt") || isPackageExists("vitepress") || isPackageExists("vuepress") || isPackageExists("@slidev/cli") || isPackageExists("vue", { paths: [resolve(process.cwd(), "playground"), resolve(process.cwd(), "docs")] }); //#endregion //#region src/utils/resolveSubOptions.ts function resolveSubOptions(options, key) { return typeof options[key] === "boolean" ? {} : options[key] || {}; } //#endregion //#region src/utils/getOverrides.ts function getOverrides(options, key) { const subOptions = resolveSubOptions(options, key); return "overrides" in subOptions && subOptions.overrides ? subOptions.overrides : {}; } //#endregion //#region src/utils/combineConfigs.ts async function combineConfigs(...configs) { return (await Promise.all(configs)).flat(); } //#endregion //#region src/utils/isInGitHooksOrRunBySpecifyPackages.ts const CHECKED_RUNNER_PACKAGES = [ "nano-staged", "lint-staged", "lefthook", "tsx" ]; function isInGitHooksOrRunBySpecifyPackages() { return !!(process.env.GIT_PARAMS || process.env.VSCODE_GIT_COMMAND || CHECKED_RUNNER_PACKAGES.some((packageName) => process.env.npm_lifecycle_script?.startsWith(packageName))); } //#endregion //#region src/utils/ensurePackages.ts const isCwdInScope = isPackageExists("@ntnyq/eslint-config"); function isPackageInScope(name) { return isPackageExists(name, { paths: [import.meta.dirname] }); } async function ensurePackages(packages) { if (process.env.CI || !process.stdout.isTTY || isInGitHooksOrRunBySpecifyPackages() || !isCwdInScope) return; const nonExistingPackages = packages.filter((pkg) => !!pkg && !isPackageInScope(pkg)); if (nonExistingPackages.length === 0) return; const { confirm } = await import("@clack/prompts"); if (await confirm({ message: `${nonExistingPackages.length === 1 ? "Package is" : "Packages are"} required for this config: ${nonExistingPackages.join(", ")}. Do you want to install them?` })) try { const { installPackage } = await import("@antfu/install-pkg"); await installPackage(nonExistingPackages, { dev: true }); } catch (err) { console.log(err); } } //#endregion //#region src/utils/interopDefault.ts /** * Interop default export from a module * @param mod - The module * @returns The default export */ async function interopDefault(mod) { const resolved = await mod; return resolved.default || resolved; } //#endregion //#region src/utils/mergePrettierOptions.ts function mergePrettierOptions(options = {}, overrides = {}) { return { ...options, ...overrides, plugins: [...options.plugins || [], ...overrides.plugins || []] }; } //#endregion //#region src/configs/html.ts /** * Config for html files * * @see {@link https://github.com/yeonjuan/html-eslint} * * @param options - {@link ConfigHtmlOptions} * @returns ESLint configs */ const configHtml = async (options = {}) => { await ensurePackages(["@html-eslint/parser", "@html-eslint/eslint-plugin"]); const [parserHtml, pluginHtml] = await Promise.all([interopDefault(import("@html-eslint/parser")), interopDefault(import("@html-eslint/eslint-plugin"))]); const { files = [GLOB_HTML] } = options; return [{ name: "ntnyq/html", files, plugins: { "@html-eslint": pluginHtml }, languageOptions: { parser: parserHtml }, rules: { "@html-eslint/attrs-newline": "error", "@html-eslint/element-newline": ["error", { inline: [`$inline`] }], "@html-eslint/indent": "error", "@html-eslint/no-duplicate-attrs": "error", "@html-eslint/no-duplicate-id": "error", "@html-eslint/no-extra-spacing-attrs": "error", "@html-eslint/no-multiple-h1": "error", "@html-eslint/no-obsolete-tags": "error", "@html-eslint/quotes": "error", "@html-eslint/require-closing-tags": "error", "@html-eslint/require-doctype": "error", "@html-eslint/require-img-alt": "error", "@html-eslint/require-lang": "error", "@html-eslint/require-li-container": "error", "@html-eslint/require-title": "error", "@html-eslint/use-baseline": "error", ...options.overrides } }]; }; //#endregion //#region src/configs/node.ts /** * Config for common files * * @see {@link https://github.com/eslint-community/eslint-plugin-n} * * @param options - {@link ConfigNodeOptions} * @returns ESLint configs */ const configNode = (options = {}) => [{ name: "ntnyq/node", plugins: { node: pluginNode }, rules: { "node/handle-callback-err": ["error", "^(err|error)$"], "node/no-deprecated-api": "error", "node/no-exports-assign": "error", "node/no-new-require": "error", "node/no-path-concat": "error", "node/prefer-global/buffer": ["error", "never"], "node/prefer-global/process": ["error", "never"], "node/process-exit-as-throw": "error", ...options.overrides } }]; //#endregion //#region src/configs/pnpm.ts /** * Config for pnpm package manager * * @see {@link https://github.com/antfu/pnpm-workspace-utils/tree/main/packages/eslint-plugin-pnpm} * * @param options - {@link ConfigPnpmOptions} * @returns ESLint configs */ const configPnpm = async (options = {}) => { await ensurePackages(["eslint-plugin-pnpm"]); const pluginPnpm = await interopDefault(import("eslint-plugin-pnpm")); const { filesJson = [GLOB_PACKAGE_JSON], filesYaml = [GLOB_PNPM_WORKSPACE_YAML] } = options; return [{ name: "ntnyq/pnpm/package-json", files: filesJson, plugins: { pnpm: pluginPnpm }, languageOptions: { parser: parserJsonc }, rules: { "pnpm/json-enforce-catalog": ["error", { autofix: true }], "pnpm/json-valid-catalog": "error", ...options.overridesJsonRules } }, { name: "ntnyq/pnpm/pnpm-workspace-yaml", files: filesYaml, plugins: { pnpm: pluginPnpm }, languageOptions: { parser: parserYaml }, rules: { "pnpm/yaml-no-duplicate-catalog-item": "error", "pnpm/yaml-no-unused-catalog-item": "error", ...options.overridesYamlRules } }]; }; //#endregion //#region src/configs/sort.ts /** * Config for sort keys and values * * @param options - {@link ConfigSortOptions} * @returns ESLint configs */ const configSort = (options = {}) => { const configs = []; const { additionalJsonFiles = [], additionalYamlFiles = [], i18nLocale: enableSortI18nLocale = true, jsonSchema: enableSortJsonSchema = true, packageJson: enableSortPackageJson = true, pnpmWorkspace: enableSortPnpmWorkspace = true, tsconfig: enableSortTsconfig = true } = options; if (enableSortTsconfig) configs.push({ name: "ntnyq/sort/tsconfig", files: [...GLOB_TSCONFIG_JSON], rules: { "jsonc/sort-keys": [ "error", { pathPattern: "^$", order: [ "extends", "compilerOptions", "references", "files", "include", "exclude", "vueCompilerOptions", { order: { type: "asc" } } ] }, { pathPattern: "^compilerOptions$", order: [ "incremental", "composite", "tsBuildInfoFile", "disableSourceOfProjectReferenceRedirect", "disableSolutionSearching", "disableReferencedProjectLoad", "target", "lib", "jsx", "experimentalDecorators", "emitDecoratorMetadata", "jsxFactory", "jsxFragmentFactory", "jsxImportSource", "reactNamespace", "noLib", "useDefineForClassFields", "moduleDetection", "module", "rootDir", "moduleResolution", "baseUrl", "paths", "rootDirs", "typeRoots", "types", "allowUmdGlobalAccess", "moduleSuffixes", "allowImportingTsExtensions", "resolvePackageJsonExports", "resolvePackageJsonImports", "customConditions", "resolveJsonModule", "allowArbitraryExtensions", "noResolve", "erasableSyntaxOnly", "libReplacement", "allowJs", "checkJs", "maxNodeModuleJsDepth", "declaration", "declarationMap", "emitDeclarationOnly", "sourceMap", "inlineSourceMap", "outFile", "outDir", "removeComments", "noEmit", "importHelpers", "importsNotUsedAsValues", "downlevelIteration", "sourceRoot", "mapRoot", "inlineSources", "emitBOM", "newLine", "stripInternal", "noEmitHelpers", "noEmitOnError", "preserveConstEnums", "declarationDir", "preserveValueImports", "isolatedDeclarations", "isolatedModules", "verbatimModuleSyntax", "allowSyntheticDefaultImports", "esModuleInterop", "preserveSymlinks", "forceConsistentCasingInFileNames", "strict", "strictBindCallApply", "strictFunctionTypes", "strictNullChecks", "strictPropertyInitialization", "allowUnreachableCode", "allowUnusedLabels", "alwaysStrict", "exactOptionalPropertyTypes", "noFallthroughCasesInSwitch", "noImplicitAny", "noImplicitOverride", "noImplicitReturns", "noImplicitThis", "noPropertyAccessFromIndexSignature", "noUncheckedIndexedAccess", "noUnusedLocals", "noUnusedParameters", "useUnknownInCatchVariables", "skipDefaultLibCheck", "skipLibCheck", { order: { type: "asc" } } ] }, { order: { type: "asc" }, pathPattern: "^vueCompilerOptions$" } ] } }); if (enableSortPackageJson) configs.push({ name: "ntnyq/sort/package-json", files: [GLOB_PACKAGE_JSON], rules: { "jsonc/sort-array-values": ["error", { order: { type: "asc" }, pathPattern: "^(?:files|keywords|activationEvents|contributes.*)$" }], "jsonc/sort-keys": [ "error", { pathPattern: "^$", order: [ "publisher", "name", "displayName", "preview", "type", "config", "version", "private", "packageManager", "workspaces", "description", "keywords", "license", "licenses", "author", "contributors", "maintainers", "homepage", "repository", "bugs", "funding", "exports", "imports", "main", "module", "unpkg", "jsdelivr", "types", "typesVersions", "bin", "icon", "files", "directories", "publishConfig", "sideEffects", "scripts", "peerDependencies", "peerDependenciesMeta", "bundledDependencies", "bundleDependencies", "dependencies", "optionalDependencies", "devDependencies", "activationEvents", "contributes", "categories", "galleryBanner", "badges", "markdown", "qna", "sponsor", "extensionPack", "extensionDependencies", "extensionKind", "pricing", "capabilities", "engines", "pnpm", "overrides", "resolutions", "husky", "prettier", "nano-staged", "lint-staged", "eslintConfig", { order: { type: "asc" } } ] }, { order: { type: "asc" }, pathPattern: "^(?:dev|peer|optional|bundled)?[Dd]ependencies(Meta)?$" }, { order: { type: "asc" }, pathPattern: "^(?:resolutions|overrides|pnpm.overrides)$" }, { pathPattern: "^exports.*$", order: [ "./package.json", "types", "import", "require", "default", { order: { type: "asc" } } ] }, { order: { type: "asc" }, pathPattern: "^contributes.*$" }, ( /** * pnpm publish config * @see {@link https://pnpm.io/package_json#publishconfig} */ { pathPattern: "^publishConfig.*$", order: [ "./package.json", "types", "import", "require", "default", { order: { type: "asc" } } ] }), { order: { type: "asc" }, pathPattern: "^scripts$" }, { pathPattern: "^(?:gitHooks|husky|simple-git-hooks)$", order: [ "pre-commit", "prepare-commit-msg", "commit-msg", "post-commit", "pre-rebase", "post-rewrite", "post-checkout", "post-merge", "pre-push", "pre-auto-gc", { order: { type: "asc" } } ] } ] } }); if (enableSortI18nLocale) configs.push({ name: "ntnyq/sort/i18n-locale/json", files: ["**/{i18n,langs,locales}/*.json"], rules: { "jsonc/sort-keys": ["error", { order: { type: "asc" }, pathPattern: ".*" }] } }, { name: "ntnyq/sort/i18n-locale/yaml", files: ["**/{i18n,langs,locales}/*.y?(a)ml"], rules: { "yml/sort-keys": ["error", { order: { type: "asc" }, pathPattern: ".*" }] } }); /** * @see {@link https://json-schema.org/draft-07/schema} */ if (enableSortJsonSchema) configs.push({ name: "ntnyq/sort/json-schema", files: [...GLOB_JSON_SCHEMA], ignores: [GLOB_PACKAGE_JSON], rules: { "jsonc/sort-array-values": ["error", { order: { type: "asc" }, pathPattern: "^(?:required)$" }], "jsonc/sort-keys": [ "error", { pathPattern: "^$", order: [ "$schema", "$comment", "$id", "$ref", "title", "description", "version", "type", "definitions", "properties", "required", "additionalProperties", { order: { type: "asc" } } ] }, { order: { type: "asc" }, pathPattern: "^(?:definitions|properties)$" } ] } }); if (enableSortPnpmWorkspace) configs.push({ name: "ntnyq/sort/pnpm-workspace", files: [GLOB_PNPM_WORKSPACE_YAML], rules: { "yml/sort-keys": [ "error", { pathPattern: "^$", order: [ "packages", "catalog", "catalogs", "allowedDeprecatedVersions", "overrides", "onlyBuiltDependencies", "patchedDependencies", "peerDependencyRules", "allowNonAppliedPatches", "auditConfig", "configDependencies", "executionEnv", "ignoredBuiltDependencies", "ignoredOptionalDependencies", "neverBuiltDependencies", "onlyBuiltDependenciesFile", "packageExtensions", "requiredScripts", "supportedArchitectures", "updateConfig", { order: { type: "asc" } } ] }, { order: { type: "asc" }, pathPattern: "^(?:catalog|overrides|patchedDependencies|peerDependencyRules)$" }, { allowLineSeparatedGroups: true, order: { type: "asc" }, pathPattern: "^catalogs$" } ], "yml/sort-sequence-values": ["error", { order: [".", { order: { type: "asc" } }], pathPattern: "^(?:packages|onlyBuiltDependencies|peerDependencyRules.ignoreMissing)$" }] } }); if (additionalJsonFiles.length) configs.push({ name: "ntnyq/sort/additional-json", files: additionalJsonFiles, rules: { "jsonc/sort-keys": ["error", { pathPattern: ".*", order: ["$schema", { order: { type: "asc" } }] }] } }); if (additionalYamlFiles.length) configs.push({ name: "ntnyq/sort/additional-yaml", files: additionalYamlFiles, rules: { "yml/sort-keys": ["error", { order: { type: "asc" }, pathPattern: ".*" }] } }); return configs; }; //#endregion //#region src/configs/svgo.ts /** * Config for svg files * * @see {@link https://github.com/ntnyq/eslint-plugin-svgo} * * @param options - {@link ConfigSVGOOptions} * @returns ESLint configs */ const configSVGO = (options = {}) => { const { files = [GLOB_SVG], ignores = [] } = options; return [{ name: "ntnyq/svgo", files, ignores, plugins: { svgo: pluginSvgo }, languageOptions: { parser: parserPlain }, rules: { "svgo/svgo": ["error", { plugins: ["preset-default"], js2svg: { indent: 2, pretty: true } }], ...options.overrides } }]; }; //#endregion //#region src/configs/test.ts /** * Config for test files * * @see {@link https://github.com/vitest-dev/eslint-plugin-vitest} * * @param options - {@link ConfigTestOptions} * @returns ESLint configs */ const configTest = (options = {}) => { const { files = [...GLOB_TEST], vitest: enableVitest = hasVitest() } = options; const configs = [{ name: "ntnyq/test/setup", plugins: { "no-only-tests": pluginNoOnlyTests } }, { name: "ntnyq/test/base", files, rules: { "max-lines-per-function": "off", "no-unused-expressions": "off", "no-only-tests/no-only-tests": "error", ...options.overrides } }]; if (enableVitest) configs.push({ name: "ntnyq/test/vitest", files, plugins: { vitest: pluginVitest }, settings: {}, rules: { ...pluginVitest.configs.recommended.rules, "vitest/expect-expect": ["error", { assertFunctionNames: [ "expect", "assert", "expectTypeOf", "assertType" ] }], ...options.overridesVitestRules } }); return configs; }; //#endregion //#region src/configs/toml.ts /** * Config for toml files * * @see {@link https://ota-meshi.github.io/eslint-plugin-toml} * * @param options - {@link ConfigTomlOptions} * @returns ESLint configs */ const configToml = (options = {}) => { const { files = [GLOB_TOML] } = options; return [{ name: "ntnyq/toml", files, plugins: { toml: pluginToml }, languageOptions: { parser: parserToml }, rules: { "toml/array-bracket-newline": "error", "toml/array-bracket-spacing": ["error", "never"], "toml/array-element-newline": ["error", "never"], "toml/comma-style": "error", "toml/indent": ["error", 2], "toml/inline-table-curly-spacing": "error", "toml/key-spacing": "error", "toml/keys-order": "error", "toml/no-space-dots": "error", "toml/no-unreadable-number-separator": "error", "toml/padding-line-between-pairs": "error", "toml/padding-line-between-tables": "error", "toml/precision-of-fractional-seconds": "error", "toml/precision-of-integer": "error", "toml/quoted-keys": "error", "toml/spaced-comment": "error", "toml/table-bracket-spacing": "error", "toml/tables-order": "error", "toml/vue-custom-block/no-parsing-error": "error", ...options.overrides } }]; }; //#endregion //#region src/configs/antfu.ts /** * Config for common files * * @see {@link https://github.com/antfu/eslint-plugin-antfu} * * @param options - {@link ConfigAntfuOptions} * @returns ESLint configs */ const configAntfu = (options = {}) => [{ name: "ntnyq/antfu", plugins: { antfu: pluginAntfu }, rules: { "antfu/import-dedupe": "error", "antfu/indent-unindent": "error", "antfu/no-import-dist": "error", "antfu/no-import-node-modules-by-path": "error", ...options.overrides } }]; //#endregion //#region src/configs/astro.ts /** * Config for astro files * * @see {@link https://github.com/ota-meshi/eslint-plugin-astro} * * @param options - {@link ConfigAstroOptions} * @returns ESLint configs */ const configAstro = async (options = {}) => { await ensurePackages(["astro-eslint-parser", "eslint-plugin-astro"]); const [parserAstro, pluginAstro] = await Promise.all([interopDefault(import("astro-eslint-parser")), interopDefault(import("eslint-plugin-astro"))]); const { files = [GLOB_ASTRO], extraFileExtensions = [] } = options; return [{ name: "ntnyq/astro", files, plugins: { astro: pluginAstro }, processor: pluginAstro.processors["client-side-ts"], languageOptions: { parser: parserAstro, sourceType: "module", globals: { ...pluginAstro.environments.astro.globals }, parserOptions: { extraFileExtensions, parser: parserTypeScript } }, rules: { "astro/missing-client-only-directive-value": "error", "astro/no-conflict-set-directives": "error", "astro/no-deprecated-astro-canonicalurl": "error", "astro/no-deprecated-astro-fetchcontent": "error", "astro/no-deprecated-astro-resolve": "error", "astro/no-deprecated-getentrybyslug": "error", "astro/no-unused-define-vars-in-style": "error", "astro/valid-compile": "error", ...options.overrides } }]; }; //#endregion //#region src/configs/jsdoc.ts const SPECIAL_CHAR = { emptyString: "", singleSpace: " " }; /** * JavaScript specific rules */ const javscriptRules = { "jsdoc/no-types": "off", "jsdoc/no-undefined-types": "error", "jsdoc/require-param-type": "error", "jsdoc/require-property-type": "error", "jsdoc/require-returns-type": "error" }; /** * TypeScript specific rules */ const typescriptRules = { "jsdoc/no-undefined-types": "off", "jsdoc/require-param-type": "off", "jsdoc/require-property-type": "off", "jsdoc/require-returns-type": "off", "jsdoc/no-types": "error" }; /** * Config for jsdoc * * @see {@link https://github.com/gajus/eslint-plugin-jsdoc} * * @param options - {@link ConfigJsdocOptions} * @returns ESLint configs */ const configJsdoc = (options = {}) => [{ name: "ntnyq/jsdoc", plugins: { jsdoc: pluginJsdoc }, rules: { "jsdoc/prefer-import-tag": "off", "jsdoc/require-tags": "off", "jsdoc/tag-lines": "off", "jsdoc/text-escaping": "off", "jsdoc/check-access": "warn", "jsdoc/implements-on-classes": "warn", "jsdoc/require-param-name": "warn", "jsdoc/require-property": "warn", "jsdoc/require-property-description": "warn", "jsdoc/require-property-name": "warn", "jsdoc/require-returns-check": "warn", "jsdoc/require-returns-description": "warn", "jsdoc/require-template-description": "warn", "jsdoc/require-yields-check": "warn", "jsdoc/require-next-description": "warn", "jsdoc/require-next-type": "warn", "jsdoc/require-throws-description": "warn", "jsdoc/require-throws-type": "warn", "jsdoc/require-yields-description": "warn", "jsdoc/require-yields-type": "warn", "jsdoc/ts-method-signature-style": "warn", "jsdoc/ts-no-empty-object-type": "warn", "jsdoc/ts-no-unnecessary-template-expression": "warn", "jsdoc/check-alignment": "error", "jsdoc/check-line-alignment": "error", "jsdoc/check-param-names": "error", "jsdoc/check-property-names": "error", "jsdoc/check-tag-names": ["error", { definedTags: [ "vite-ignore", "unocss-include", "pg", "perfectionist-group", "regex101", "compatibility", "category", "experimental", "internal" ] }], "jsdoc/check-types": "error", "jsdoc/empty-tags": "error", "jsdoc/multiline-blocks": "error", "jsdoc/no-bad-blocks": ["error", { ignore: [ "ts-check", "ts-expect-error", "ts-ignore", "ts-nocheck", "vite-ignore" ] }], "jsdoc/no-blank-block-descriptions": "error", "jsdoc/no-blank-blocks": "error", "jsdoc/no-defaults": "error", "jsdoc/no-multi-asterisks": "error", "jsdoc/reject-any-type": "error", "jsdoc/reject-function-type": "error", "jsdoc/require-asterisk-prefix": "error", "jsdoc/require-hyphen-before-param-description": "error", "jsdoc/type-formatting": ["error", { arrayBrackets: "square", enableFixer: true, genericDot: false, keyValuePostColonSpacing: SPECIAL_CHAR.singleSpace, keyValuePostKeySpacing: SPECIAL_CHAR.emptyString, keyValuePostOptionalSpacing: SPECIAL_CHAR.emptyString, keyValuePostVariadicSpacing: SPECIAL_CHAR.emptyString, objectFieldIndent: SPECIAL_CHAR.emptyString, objectFieldQuote: null, objectFieldSeparator: "comma", stringQuotes: "single", typeBracketSpacing: SPECIAL_CHAR.emptyString, unionSpacing: SPECIAL_CHAR.singleSpace }], ...options.typescript ? typescriptRules : javscriptRules, ...options.overrides } }]; //#endregion //#region src/configs/jsonc.ts /** * @see {@link https://github.com/ota-meshi/eslint-plugin-jsonc/blob/master/lib/configs/base.ts} */ const disabledCoreRules = { "no-unused-expressions": "off", "no-unused-vars": "off", strict: "off" }; /** * Config for json, jsonc and json5 files * * @see {@link https://ota-meshi.github.io/eslint-plugin-jsonc} * * @param options - {@link ConfigJsoncOptions} * @returns ESLint configs */ const configJsonc = (options = {}) => { const { files = [ GLOB_JSON, GLOB_JSON5, GLOB_JSONC ] } = options; return [{ name: "ntnyq/jsonc", files, plugins: { jsonc: pluginJsonc }, languageOptions: { parser: parserJsonc }, rules: { "jsonc/array-bracket-spacing": ["error", "never"], "jsonc/comma-dangle": ["error", "never"], "jsonc/comma-style": ["error", "last"], "jsonc/indent": ["error", 2], "jsonc/key-spacing": ["error", { afterColon: true, beforeColon: false }], "jsonc/no-bigint-literals": "error", "jsonc/no-binary-expression": "error", "jsonc/no-binary-numeric-literals": "error", "jsonc/no-dupe-keys": "error", "jsonc/no-escape-sequence-in-identifier": "error", "jsonc/no-floating-decimal": "error", "jsonc/no-hexadecimal-numeric-literals": "error", "jsonc/no-infinity": "error", "jsonc/no-multi-str": "error", "jsonc/no-nan": "error", "jsonc/no-number-props": "error", "jsonc/no-numeric-separators": "error", "jsonc/no-octal": "error", "jsonc/no-octal-escape": "error", "jsonc/no-octal-numeric-literals": "error", "jsonc/no-parenthesized": "error", "jsonc/no-plus-sign": "error", "jsonc/no-regexp-literals": "error", "jsonc/no-sparse-arrays": "error", "jsonc/no-template-literals": "error", "jsonc/no-undefined-value": "error", "jsonc/no-unicode-codepoint-escapes": "error", "jsonc/no-useless-escape": "error", "jsonc/object-curly-newline": ["error", { consistent: true, multiline: true }], "jsonc/object-curly-spacing": ["error", "always"], "jsonc/object-property-newline": ["error", { allowMultiplePropertiesPerLine: true }], "jsonc/quote-props": "error", "jsonc/quotes": "error", "jsonc/space-unary-ops": "error", "jsonc/valid-json-number": "error", "jsonc/vue-custom-block/no-parsing-error": "error", ...disabledCoreRules, ...options.prettier ? { "jsonc/array-bracket-newline": "off", "jsonc/array-bracket-spacing": "off", "jsonc/array-element-newline": "off", "jsonc/comma-dangle": "off", "jsonc/comma-style": "off", "jsonc/indent": "off", "jsonc/key-spacing": "off", "jsonc/no-floating-decimal": "off", "jsonc/object-curly-newline": "off", "jsonc/object-curly-spacing": "off", "jsonc/object-property-newline": "off", "jsonc/quote-props": "off", "jsonc/quotes": "off", "jsonc/space-unary-ops": "off" } : {}, ...options.overrides } }]; }; //#endregion //#region src/configs/ntnyq.ts /** * Config for common files * * @see {@link https://github.com/ntnyq/eslint-plugin-ntnyq} * * @param options - {@link ConfigNtnyqOptions} * @returns ESLint configs */ const configNtnyq = (options = {}) => [{ name: "ntnyq/ntnyq", plugins: { ntnyq: pluginNtnyq }, rules: { "ntnyq/no-duplicate-exports": "error", "ntnyq/prefer-newline-after-file-header": "error", ...options.overrides } }]; //#endregion //#region src/configs/pinia.ts /** * Config for pinia store * * @see {@link https://github.com/lisilinhart/eslint-plugin-pinia} * * @param options - {@link ConfigPiniaOptions} * @returns ESLint configs */ const configPinia = (options = {}) => { const { files = [GLOB_PINIA_STORE] } = options; return [{ name: "ntnyq/pinia", files, plugins: { pinia: pluginPinia }, rules: { "pinia/never-export-initialized-store": "error", "pinia/no-duplicate-store-ids": "error", "pinia/no-return-global-properties": "error", "pinia/no-store-to-refs-in-store": "error", "pinia/prefer-single-store-per-file": "error", "pinia/prefer-use-store-naming-convention": ["error", { checkStoreNameMismatch: true, storeSuffix: "Store" }], "pinia/require-setup-store-properties-export": "error", ...options.overrides } }]; }; //#endregion //#region src/configs/depend.ts /** * Config for optimisations dependency * * @see {@link https://github.com/es-tooling/eslint-plugin-depend} * @see {@link https://github.com/es-tooling/module-replacements} * * @param options - {@link ConfigDependOptions} * @returns ESLint configs */ const configDepend = (options = {}) => { const { files = [GLOB_SRC], allowed = [], modules = [], packageJson: enableCheckPackageJson = true, presets = [ "native", "microutilities", "preferred" ] } = options; const rules = { "depend/ban-dependencies": ["error", { allowed, modules, presets }], ...options.overrides }; const configs = [{ name: "ntnyq/depend", files, plugins: { depend: pluginDepend }, rules }]; if (enableCheckPackageJson) configs.push({ name: "ntnyq/depend/package-json", files: [GLOB_PACKAGE_JSON], plugins: { depend: pluginDepend }, languageOptions: { parser: parserJsonc }, rules }); return configs; }; //#endregion //#region src/constants/prettier.ts /** * Options from `@ntnyq/prettier-config` * * @see {@link https://github.com/ntnyq/configs/blob/main/packages/prettier-config/index.js} */ const PRETTIER_DEFAULT_OPTIONS = { arrowParens: "avoid", bracketSameLine: false, bracketSpacing: true, embeddedLanguageFormatting: "auto", endOfLine: "lf", experimentalOperatorPosition: "start", experimentalTernaries: false, htmlWhitespaceSensitivity: "css", insertPragma: false, jsxSingleQuote: true, objectWrap: "preserve", printWidth: 80, proseWrap: "preserve", quoteProps: "as-needed", rangeEnd: Number.POSITIVE_INFINITY, rangeStart: 0, requirePragma: false, semi: false, singleAttributePerLine: true, singleQuote: true, tabWidth: 2, trailingComma: "all", useTabs: false, vueIndentScriptAndStyle: false }; //#endregion //#region src/constants/perfectionist.ts /** * Shared perfectionist plugin settings */ const pluginSettings = { fallbackSort: { order: "asc", type: "alphabetical" }, ignoreCase: true, order: "asc", partitionByNewLine: false, specialCharacters: "keep", type: "alphabetical" }; /** * Shared perfectionist rule options for some rules */ const partialRuleOptions = { newlinesBetween: "ignore", partitionByComment: ["@pg", "@perfectionist-group"] }; /** * Shared option `groups` for rule `sort-objects` * * @see {@link https://perfectionist.dev/rules/sort-objects} */ const sortObjectsGroups = [ "property", "multiline-property", "method", "multiline-method", "unknown" ]; /** * Shared option `groups` for rules * - `sort-interfaces` * - `sort-object-types` * * @see {@link https://perfectionist.dev/rules/sort-interfaces} * @see {@link https://perfectionist.dev/rules/sort-object-types} */ const sortInterfacesOrObjectTypesGroups = [ "required-property", "optional-property", "required-method", "optional-method", "required-multiline-property", "optional-multiline-property", "required-multiline-method", "optional-multiline-method", "unknown", "index-signature", "multiline-index-signature" ]; /** * Shared option `groups` for rules: * - `sort-intersection-types` * - `sort-union-types` * * Philosophy: keep simple thing first except null & undefined * * @see {@link https://perfectionist.dev/rules/sort-intersection-types} * @see {@link https://perfectionist.dev/rules/sort-union-types} */ const sortIntersectionTypesOrUnionTypesGroups = [ "literal", "keyword", "named", "intersection", "conditional", "function", "import", "object", "operator", "tuple", "union", "nullish" ]; /** * Shared option `groups` for rule `sort-imports` * * @see {@link https://perfectionist.dev/rules/sort-imports} */ const sortImportsTypes = [ "side-effect-style", "value-style", "value-builtin", "value-external", "value-subpath", "value-internal", "value-parent", "value-sibling", "value-index", "ts-equals-import", "side-effect", "type-builtin", "type-external", "type-subpath", "type-internal", "type-parent", "type-sibling", "type-index", "unknown" ]; /** * Shared option `groups` for rule `sort-exports` * * @see {@link https://perfectionist.dev/rules/sort-exports} */ const sortExportsGroups = [ "value-export", "type-export", "unknown" ]; /** * Shared option `groups` for rule `sort-named-exports` * * @see {@link https://perfectionist.dev/rules/sort-named-exports} */ const sortNamedExportsGroups = [ "value-export", "type-export", "unknown" ]; /** * Shared option `groups` for rule `sort-named-imports` * * @see {@link https://perfectionist.dev/rules/sort-named-imports} */ const sortNamedImportsGroups = [ "value-import", "type-import", "unknown" ]; /** * Shared option `groups` for rule `sort-classes` * * // TODO: implement this * * @see {@link https://perfectionist.dev/rules/sort-classes} */ const sortClassesGroups = ["unknown"]; /** * Shared constants about eslint-plugin-perfectionist */ const PERFECTIONIST = Object.freeze({ partialRuleOptions, pluginSettings, sortClassesGroups, sortExportsGroups, sortImportsTypes, sortInterfacesOrObjectTypesGroups, sortIntersectionTypesOrUnionTypesGroups, sortNamedExportsGroups, sortNamedImportsGroups, sortObjectsGroups }); //#endregion //#region src/configs/format.ts /** * Config to use a formatter * * @see {@link https://github.com/antfu/eslint-plugin-format} * * @param options - {@link ConfigFormatOptions} * @returns ESLint configs */ const configFormat = async (options = {}) => { await ensurePackages(["eslint-plugin-format"]); const pluginFormat = await interopDefault(import("eslint-plugin-format")); const { css: enableCSS = true, html: enableHTML = true, prettierOptions = {} } = options; const sharedPrettierOptions = { ...PRETTIER_DEFAULT_OPTIONS, ...prettierOptions }; const configs = [{ name: "ntnyq/format/setup", plugins: { format: pluginFormat } }]; if (enableCSS) configs.push({ name: "ntnyq/format/css", files: [GLOB_CSS, GLOB_POSTCSS], languageOptions: { parser: parserPlain }, rules: { "format/prettier": ["error", mergePrettierOptions(sharedPrettierOptions, { parser: "css" })] } }, { name: "ntnyq/format/scss", files: [GLOB_SCSS], languageOptions: { parser: parserPlain }, rules: { "format/prettier": ["error", mergePrettierOptions(sharedPrettierOptions, { parser: "scss" })] } }, { name: "ntnyq/format/less", files: [GLOB_LESS], languageOptions: { parser: parserPlain }, rules: { "format/prettier": ["error", mergePrettierOptions(sharedPrettierOptions, { parser: "less" })] } }); if (enableHTML) configs.push({ name: "ntnyq/format/html", files: [GLOB_HTML], languageOptions: { parser: parserPlain }, rules: { "format/prettier": ["error", mergePrettierOptions(sharedPrettierOptions, { parser: "html" })] } }); return configs; }; //#endregion //#region src/configs/regexp.ts /** * Config for regexp * * @see {@link https://github.com/ota-meshi/eslint-plugin-regexp} * * @param options - {@link ConfigRegexpOptions} * @returns ESLint configs */ const configRegexp = (options = {}) => { const recommendedConfig = pluginRegexp.configs["flat/recommended"]; const recommendedRules$1 = { ...recommendedConfig.rules }; if (options.severity === "warn") { for (const key in recommendedRules$1) if (recommendedRules$1[key] === "error") recommendedRules$1[key] = "warn"; } return [{ ...recommendedC