@pequity/eslint-config
Version:
Pequity's ESLint config
259 lines (254 loc) • 7.61 kB
JavaScript
import js from '@eslint/js';
import pluginVitest from '@vitest/eslint-plugin';
import eslintConfigPrettier from 'eslint-config-prettier';
import pluginHtml from 'eslint-plugin-html';
import pluginPrettier from 'eslint-plugin-prettier/recommended';
import pluginSimpleImportSort from 'eslint-plugin-simple-import-sort';
import neostandard, { plugins, resolveIgnoresFromGitignore } from 'neostandard';
export { plugins } from 'neostandard';
import pluginVue from 'eslint-plugin-vue';
const typescript = [
...plugins["typescript-eslint"].configs.recommended,
{
name: "pequity/typescript",
rules: {
// Prefer T[] instead of Array<T>
"@typescript-eslint/array-type": ["error", { default: "array" }],
// Prefer type over interface for objects
"@typescript-eslint/consistent-type-definitions": ["error", "type"],
// Fix type imports
"@typescript-eslint/consistent-type-imports": [
"error",
{
prefer: "type-imports",
fixStyle: "inline-type-imports"
}
],
// Allow _ for unused variables
"@typescript-eslint/no-unused-vars": [
"error",
{
args: "none",
caughtErrors: "none",
ignoreRestSiblings: true,
vars: "all",
argsIgnorePattern: "^_"
}
],
// Allow optional chaining and nullish coalescing in expressions
"@typescript-eslint/no-unused-expressions": [
"error",
{
allowShortCircuit: true,
allowTernary: true
}
],
// Disallow non-null assertions using the ! postfix operator
"@typescript-eslint/no-non-null-assertion": "error",
// Disallow enums
"no-restricted-syntax": [
"error",
{
selector: "TSEnumDeclaration",
message: `Enums introduce unexpected runtime behavior, break TypeScript's structural typing, and add unnecessary complexity. Use union types or 'as const' objects instead.`
}
]
}
}
];
const vue = (options) => {
const opts = {
version: 3,
ts: true,
...options
};
const vueConfig = opts.version === 3 ? pluginVue.configs["flat/recommended"] : pluginVue.configs["flat/vue2-recommended"];
const languageOptions = opts.ts ? {
languageOptions: {
parserOptions: {
parser: plugins["typescript-eslint"].parser
}
}
} : {};
const config = [
...vueConfig,
{
name: "pequity/vue",
files: ["*.vue", "**/*.vue"],
...languageOptions,
rules: {
// Overrides for vue/(vue3-)recommended preset
"vue/max-attributes-per-line": "off",
"vue/singleline-html-element-content-newline": "off",
// Strengthen vue/(vue3-)recommended preset for autofix
// https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/configs/recommended.js
"vue/attributes-order": "error",
"vue/block-order": "error",
"vue/no-lone-template": "error",
"vue/no-multiple-slot-args": "error",
"vue/no-v-html": "error",
"vue/order-in-components": "error",
"vue/this-in-template": "error",
"vue/require-prop-types": "error",
// Enforce PascalCase for Vue components
"vue/component-name-in-template-casing": [
"error",
"PascalCase",
{ registeredComponentsOnly: false, ignores: [] }
],
// Do not allow inline styles
"vue/no-static-inline-styles": ["error", { allowBinding: false }],
// Require explicit emits
"vue/require-explicit-emits": "error",
// Require component to have a name property
"vue/require-name-property": "error",
// Require components that don't have any content to self-close
"vue/html-self-closing": [
"error",
{
html: {
void: "always",
normal: "never",
component: "always"
},
svg: "always",
math: "always"
}
],
// Enforce dot notation whenever possible in `<template>`
"vue/dot-notation": ["error"]
}
}
];
return config;
};
const config = (options) => {
const opts = {
noJsx: true,
noStyle: true,
semi: true,
ts: true,
vue: true,
vueVersion: 3,
vitest: true,
prettier: true,
...options
};
const linterConfig = [
{
name: "pequity/ignores",
ignores: [
"**/node_modules/**",
"{tmp,temp}/**",
"**/*.min.js",
"vendor/**",
"dist/**",
"public/**",
...resolveIgnoresFromGitignore()
]
},
// JavaScript
{
name: "pequity/javascript",
...js.configs.recommended
},
// TypeScript
...opts.ts ? typescript : [],
// Neostandard
...neostandard(opts),
{
name: "pequity/neostandard-overrides",
rules: {
// Console and debugger settings depending whether we're on production or not
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
// Enforce consistent brace style for all control statements
curly: ["error", "all"],
// Disable relative imports - https://stackoverflow.com/questions/65670432/eslint-only-allow-absolute-import-paths-not-relative
"no-restricted-imports": [
"error",
{
patterns: [
{
group: ["./", "../"],
message: "Relative imports are not allowed."
}
]
}
],
// Enforce dot notation
"dot-notation": ["error"],
// Import rules
// TypeScript compilation already ensures that named imports exist in the referenced module
// and has its own version of no-unused-vars
...opts.ts ? {
"import-x/named": "off",
"no-unused-vars": "off"
} : {},
"import-x/no-mutable-exports": "error",
"import-x/newline-after-import": ["error", { count: 1 }],
"import-x/no-self-import": "error",
"import-x/no-relative-packages": "error"
}
},
// Sort imports
{
name: "pequity/sort-imports",
plugins: {
"simple-import-sort": pluginSimpleImportSort
},
rules: {
"simple-import-sort/imports": [
"error",
// Remove all blank lines between imports
{
groups: [["^\\u0000", "^node:", "^@?\\w", "^", "^\\."]]
}
],
"simple-import-sort/exports": "error"
}
},
// HTML
{
name: "pequity/html",
files: ["**/*.html"],
plugins: {
html: pluginHtml
}
},
// Vue
...opts.vue ? vue({ version: opts.vueVersion, ts: opts.ts }) : [],
// Vitest
...opts.vitest ? [
{
name: "pequity/vitest",
files: ["test/**", "tests/**", "**/*.test.{js,ts}*", "**/*.spec.{js,ts}"],
plugins: {
vitest: pluginVitest
},
rules: {
...pluginVitest.configs.recommended.rules
},
languageOptions: {
globals: {
...pluginVitest.environments.env.globals
}
}
}
] : [],
// Prettier
...opts.prettier ? [
{
name: "pequity/plugin-prettier",
...pluginPrettier
}
] : [
{
name: "pequity/config-prettier",
...eslintConfigPrettier
}
]
];
return linterConfig;
};
export { config };