@kouts/eslint-config
Version:
Kouts's ESLint config
294 lines (286 loc) • 8.94 kB
JavaScript
;
var js = require('@eslint/js');
var pluginVitest = require('@vitest/eslint-plugin');
var pluginHtml = require('eslint-plugin-html');
var pluginPrettier = require('eslint-plugin-prettier/recommended');
var pluginSimpleImportSort = require('eslint-plugin-simple-import-sort');
var neostandard = require('neostandard');
var pluginVue = require('eslint-plugin-vue');
const rule = {
meta: {
type: "suggestion",
docs: {
description: "Require defineOptions with name attribute in script setup Vue components",
category: "Best Practices",
recommended: true
},
schema: []
},
create(context) {
let hasDefineOptionsWithName = false;
return {
// We'll assume every Vue file with script setup needs a defineOptions name
// Check for defineOptions function calls
'CallExpression[callee.name="defineOptions"]'(node) {
if (node.arguments?.length && node.arguments[0]?.type === "ObjectExpression") {
const properties = node.arguments[0].properties || [];
for (const prop of properties) {
if (prop.type === "Property" && prop.key?.type === "Identifier" && prop.key?.name === "name") {
hasDefineOptionsWithName = true;
break;
}
}
}
},
// At the end of the file, report if we don't have defineOptions with name
"Program:exit"(node) {
const filename = context.physicalFilename;
const isVueFile = filename.endsWith(".vue");
if (isVueFile && !hasDefineOptionsWithName) {
const sourceCode = context.sourceCode.getText();
const hasScriptSetup = /<script\s+setup/.test(sourceCode);
if (hasScriptSetup) {
context.report({
node,
message: 'Component name is required. Add defineOptions({ name: "YourComponentName" }) to your <script setup>'
});
}
}
}
};
}
};
var customRules = {
"vue-require-name-in-setup": rule
};
const typescript = [
...neostandard.plugins["typescript-eslint"].configs.recommended,
{
name: "kouts/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: "^_"
}
],
// 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: neostandard.plugins["typescript-eslint"].parser
}
}
} : {};
const config = [
...vueConfig,
{
name: "kouts/vue",
files: ["*.vue", "**/*.vue"],
...languageOptions,
rules: {
// Custom rules
"kouts/vue-require-name-in-setup": "error",
// 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 customRulesPlugin = { name: "kouts", rules: customRules };
const config = (options) => {
const opts = {
noJsx: true,
noStyle: true,
semi: false,
ts: true,
vue: true,
vueVersion: 3,
vitest: true,
...options
};
const linterConfig = [
// Custom rules plugin
{
name: "kouts/custom-rules",
plugins: {
kouts: customRulesPlugin
}
},
{
name: "kouts/ignores",
ignores: [
"**/node_modules/**",
"{tmp,temp}/**",
"**/*.min.js",
"vendor/**",
"dist/**",
"public/**",
...neostandard.resolveIgnoresFromGitignore()
]
},
// JavaScript
{
name: "kouts/javascript",
...js.configs.recommended
},
// TypeScript
...opts.ts ? typescript : [],
// Neostandard
...neostandard(opts),
{
name: "kouts/neostandard-overrides",
rules: {
// Enforce blank lines between the given 2 kinds of statements
"padding-line-between-statements": [
"error",
{ blankLine: "always", prev: "*", next: "return" },
{ blankLine: "always", prev: ["const", "let", "var"], next: "*" },
{ blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"] },
{ blankLine: "always", prev: "directive", next: "*" },
{ blankLine: "any", prev: "directive", next: "directive" }
],
// 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 dot notation
"dot-notation": ["error"],
// Import rules
...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"
}
},
// Sort imports
{
name: "kouts/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: "kouts/html",
files: ["**/*.html"],
plugins: {
html: pluginHtml
}
},
// Vue
...opts.vue ? vue({ version: opts.vueVersion, ts: opts.ts }) : [],
// Vitest
...opts.vitest ? [
{
name: "kouts/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
{
name: "kouts/prettier",
...pluginPrettier
}
];
return linterConfig;
};
Object.defineProperty(exports, "plugins", {
enumerable: true,
get: function () { return neostandard.plugins; }
});
exports.config = config;