Skip to content

Instantly share code, notes, and snippets.

@ShayanTheNerd
Last active August 17, 2024 16:40
Show Gist options
  • Save ShayanTheNerd/6c1ba67117b4bd9115fd48036316820b to your computer and use it in GitHub Desktop.
Save ShayanTheNerd/6c1ba67117b4bd9115fd48036316820b to your computer and use it in GitHub Desktop.
Exhaustive linting and formatting configs for HTML, JS, TS, Astro, Vue, Tailwind, Vitest, Cypress, and Playwright.
import eslintPluginAstro from 'eslint-plugin-astro';
export default {
...eslintPluginAstro.configs.recommended.rules,
'astro/no-set-html-directive': 'error',
'astro/no-set-text-directive': 'error',
'astro/no-unused-css-selector': 'error',
'astro/prefer-object-class-list': 'error',
'astro/no-exports-from-components': 'error',
};
const cypressRules = {
'cypress/no-pause': 'error',
'cypress/no-force': 'error',
'cypress/no-async-tests': 'off',
'cypress/assertion-before-screenshot': 'error',
};
import eslintAntfuConfig from '@antfu/eslint-config';
import eslintHTMLParser from '@html-eslint/parser';
import eslintPluginVitest from 'eslint-plugin-vitest';
import eslintTSParser from '@typescript-eslint/parser';
import eslintPLuginHTML from '@html-eslint/eslint-plugin';
import eslintPluginCypress from 'eslint-plugin-cypress/flat';
import eslintPluginPlaywright from 'eslint-plugin-playwright';
export default eslintAntfuConfig(
{
files: ['index.html'],
plugins: { '@html-eslint': eslintPLuginHTML },
languageOptions: { parser: eslintHTMLParser },
rules: { ...htmlRules, ...tailwindRules },
},
{
files: ['**/*.{js,ts}', 'src/components/**/*.vue', 'src/**/*.astro'],
vue: true,
astro: true,
typescript: { tsconfigPath: 'tsconfig.json' },
rules: { ...jsRules, ...tsRules, ...stylisticRules, ...tailwindRules },
languageOptions: {
parser: eslintTSParser,
parserOptions: {
project: './tsconfig.json',
extraFileExtensions: ['.astro'],
},
},
},
{
files: ['src/**/*.astro'],
rules: astroRules,
},
{
files: ['src/components/**/*.vue'],
rules: { ...vueRules, 'no-useless-assignment': 'off' },
},
{
files: ['tests/unit/**/*.test.ts'],
plugins: { eslintPluginVitest },
settings: {
vitest: {
typecheck: true,
},
},
languageOptions: {
globals: {
...eslintPluginVitest.environments.env.globals,
},
},
rules: { ...vitestRules, 'no-magic-numbers': 'off' },
},
{
files: ['tests/cypress/e2e/**/*.cy.ts'],
...eslintPluginCypress.configs.recommended,
rules: cypressRules,
},
{
files: ['tests/e2e/**/*.test.ts'],
...eslintPluginPlaywright.configs['flat/recommended'],
rules: {
...playwrightRules,
'dot-notation': 'off',
'no-magic-numbers': 'off',
},
},
{ rules: generalRules },
{
settings: {
tailwindcss: {
config: "tailwind.config.js",
},
},
},
{ ignores: ['**/*.json', '**/env.d.ts'] },
);
export default {
'import/order': 'off',
'style/no-tabs': 'off',
'antfu/if-newline': 'off',
'unused-imports/no-unused-vars': 'off',
/* Sorting */
'perfectionist/sort-maps': ['error', { type: 'line-length' }],
'perfectionist/sort-exports': ['error', { type: 'line-length' }],
'perfectionist/sort-union-types': ['error', { type: 'line-length' }],
'perfectionist/sort-array-includes': ['error', { type: 'line-length' }],
'perfectionist/sort-intersection-types': ['error', { type: 'line-length' }],
'perfectionist/sort-named-imports': ['error', { type: 'line-length', groupKind: 'types-first' }],
'perfectionist/sort-named-exports': ['error', { type: 'line-length', groupKind: 'types-first' }],
'perfectionist/sort-imports': ['error', {
'type': 'line-length',
'internal-pattern': ['@ts/**', '@styles/**', '@components/**'],
'custom-groups': {
value: {
'astro-components': '@components/**/*.astro',
'vue-components': '@components/**/*.vue',
}
},
'groups': [
['side-effect-style', 'side-effect'],
['index-type', 'builtin-type', 'external-type', 'internal-type', 'parent-type', 'sibling-type'],
'astro-components',
'vue-components',
['index', 'builtin', 'external', 'internal', 'parent', 'sibling'],
['object', 'unknown'],
],
}],
};
export default {
/* Best Practices */
'@html-eslint/no-duplicate-id': 'error',
'@html-eslint/require-doctype': 'error',
'@html-eslint/no-obsolete-tags': 'error',
'@html-eslint/no-duplicate-attrs': 'error',
'@html-eslint/require-li-container': 'error',
'@html-eslint/require-button-type': 'error',
'@html-eslint/no-script-style-type': 'error',
'@html-eslint/require-meta-charset': 'error',
'@html-eslint/require-closing-tags': ['error', { selfClosing: 'always' }],
/* SEO */
'@html-eslint/require-lang': 'error',
'@html-eslint/require-title': 'error',
'@html-eslint/no-multiple-h1': 'error',
'@html-eslint/require-meta-description': 'error',
'@html-eslint/require-open-graph-protocol': 'error',
/* Accessibility */
'@html-eslint/require-img-alt': 'error',
'@html-eslint/no-abstract-roles': 'error',
'@html-eslint/no-accesskey-attrs': 'error',
'@html-eslint/require-frame-title': 'error',
'@html-eslint/no-aria-hidden-body': 'error',
'@html-eslint/no-positive-tabindex': 'error',
'@html-eslint/no-skip-heading-levels': 'error',
'@html-eslint/require-meta-viewport': 'error',
/* Style */
'@html-eslint/quotes': 'error',
'@html-eslint/lowercase': 'error',
'@html-eslint/indent': ['error', 'tab'],
'@html-eslint/no-trailing-spaces': 'error',
'@html-eslint/id-naming-convention': 'error',
'@html-eslint/no-multiple-empty-lines': 'error',
'@html-eslint/element-newline': ['error', { skip: ['pre', 'code'] }],
'@html-eslint/no-extra-spacing-attrs': ['error', { enforceBeforeSelfClose: true }],
};
import eslintJS from '@eslint/js';
/* eslint-disable no-magic-numbers -- Improve SNR */
export default {
...eslintJS.configs.recommended.rules,
/* Possible Problems */
'no-await-in-loop': 'error',
'no-self-compare': 'error',
'no-unreachable-loop': 'error',
'no-inner-declarations': 'error',
'array-callback-return': 'error',
'no-useless-assignment': 'error',
'no-constructor-return': 'error',
'require-atomic-updates': 'error',
'no-async-promise-executor': 'error',
'no-template-curly-in-string': 'error',
'no-promise-executor-return': 'error',
'no-unmodified-loop-condition': 'error',
'no-use-before-define': ['error', 'nofunc'],
'use-isnan': ['error', { enforceForIndexOf: true }],
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'no-unsafe-negation': ['error', { enforceForOrderingRelations: true }],
'no-unsafe-optional-chaining': ['error', { disallowArithmeticOperators: true }],
/* Suggestions */
'yoda': 'error',
'strict': 'error',
'no-var': 'error',
'eqeqeq': 'error',
'no-new': 'error',
'no-eval': 'error',
'no-void': 'error',
'new-cap': 'error',
'no-proto': 'error',
'no-caller': 'error',
'no-empty': 'error',
'no-eq-null': 'error',
'camelcase': 'error',
'sort-imports': 'off',
'no-redeclare': 'off',
'complexity': 'error',
'no-iterator': 'error',
'no-continue': 'error',
'no-lonely-if': 'error',
'max-params': 'error',
'no-shadow': ['error'],
'no-label-var': 'error',
'no-new-func': 'error',
'no-multi-str': 'error',
'guard-for-in': 'error',
'no-loop-func': 'error',
'default-case': 'error',
'no-script-url': 'error',
'prefer-const': 'error',
'no-undefined': 'error',
'no-undef-init': 'error',
'require-await': 'error',
'no-extra-bind': 'error',
'prefer-spread': 'error',
// 'no-invalid-this': 'error', // Only in Vanilla JS projects
'no-lone-blocks': 'error',
'no-extra-label': 'error',
'accessor-pairs': 'error',
'no-useless-call': 'error',
'max-depth': ['error', 3],
'no-implied-eval': 'error',
'consistent-this': 'error',
'no-octal-escape': 'error',
'no-throw-literal': 'error',
'prefer-template': 'error',
'init-declarations': 'error',
'no-new-wrappers': 'error',
'block-scoped-var': 'error',
'no-extend-native': 'error',
'default-case-last': 'error',
'object-shorthand': 'error',
'no-useless-return': 'error',
'consistent-return': 'error',
'no-useless-concat': 'error',
'no-nested-ternary': 'error',
'no-useless-rename': 'error',
'no-implicit-globals': 'error',
'default-param-last': 'error',
'symbol-description': 'error',
'curly': ['error', 'multi-line'],
'prefer-rest-params': 'error',
'no-implicit-coercion': 'error',
'radix': ['error', 'as-needed'],
'func-name-matching': 'error',
'operator-assignment': 'error',
'no-unneeded-ternary': 'error',
'no-array-constructor': 'error',
'prefer-destructuring': 'error',
'no-underscore-dangle': 'error',
'prefer-object-spread': 'error',
'no-object-constructor': 'error',
'prefer-object-has-own': 'error',
'no-useless-constructor': 'error',
'class-methods-use-this': 'error',
'require-unicode-regexp': 'error',
'prefer-numeric-literals': 'error',
'no-useless-computed-key': 'error',
'max-nested-callbacks': ['error', 3],
'no-return-assign': ['error', 'always'],
'prefer-named-capture-group': 'error',
'no-bitwise': ['error', { int32Hint: true }],
'prefer-exponentiation-operator': 'error',
'import/extensions': ['error', 'ignorePackages'],
'dot-notation': ['error', { allowKeywords: false }],
'logical-assignment-operators': ['error', 'always'],
'no-sequences': ['error', { allowInParentheses: false }],
'no-multi-assign': ['error', { ignoreNonDeclaration: true }],
'no-empty-function': ['error', { allow: ['arrowFunctions'] }],
'no-plusplus': ['error', { allowForLoopAfterthoughts: true }],
'prefer-arrow-callback': ['error', { allowUnboundThis: true }],
'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
'prefer-promise-reject-errors': ['error', { allowEmptyReject: true }],
'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }],
'no-extra-boolean-cast': ['error', { enforceForInnerExpressions: true }],
'no-unused-expressions': ['error', { allowShortCircuit: true, allowTernary: true }],
'arrow-body-style': ['error', 'as-needed', { requireReturnForObjectLiteral: true }],
'no-restricted-exports': ['error', {
restrictDefaultExports: {
named: true,
namedFrom: true,
defaultFrom: true,
namespaceFrom: true,
},
}],
'no-magic-numbers': ['error', {
enforceConst: true,
ignoreDefaultValues: true,
ignoreClassFieldInitialValues: true,
ignore: [-100, -1, 0, 1, 2, 100],
}],
};
{
"type": "module",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"format": "prettier --write '**/*.{json,css}' --config=prettier.config.js --cache",
"lint": "eslint --fix --ignore-pattern='.vscode/*.json' --config=eslint.config.js --cache-location='/node_modules/.eslintcache/",
"test:unit": "vitest --config=vitest.config.ts",
"test:e2e": "concurrently \"pnpm dev\" \"cypress open\" --kill-others",
"test:e2e:ci": "concurrently \"pnpm preview\" \"cypress run\" --kill-others",
"all": "pnpm format && pnpm lint && pnpm test:unit --run && pnpm build && pnpm test:e2e:ci"
},
"devDependencies": {
"@antfu/eslint-config": "^2.23.2",
"@eslint/js": "^9.8.0",
"@html-eslint/eslint-plugin": "^0.25.0",
"@html-eslint/parser": "^0.25.0",
"@stylistic/eslint-plugin": "2.3.0",
"@types/eslint__js": "^8.42.3",
"@typescript-eslint/parser": "^7.17.0",
"concurrently": "^8.2.2",
"cypress": "^13.13.2",
"cypress-vite": "^1.5.0",
"eslint": "^9.8.0",
"eslint-plugin-astro": "^1.2.3",
"eslint-plugin-tailwindcss": "^3.17.4",
"eslint-plugin-cypress": "^3.4.0",
"eslint-plugin-vitest": "^0.5.4",
"eslint-plugin-vue": "^9.26.0",
"prettier": "^3.3.3"
}
}
export default {
'playwright/max-expects': 'off',
'playwright/prefer-to-be': 'error',
'playwright/no-get-by-title': 'error',
'playwright/prefer-to-contain': 'error',
'playwright/no-wait-for-timeout': 'off',
'playwright/no-duplicate-hooks': 'error',
'playwright/prefer-strict-equal': 'error',
'playwright/prefer-hooks-on-top': 'error',
'playwright/prefer-to-have-count': 'error',
'playwright/prefer-to-have-length': 'error',
'playwright/prefer-hooks-in-order': 'error',
'playwright/prefer-equality-matcher': 'error',
'playwright/no-commented-out-tests': 'error',
'playwright/require-to-throw-message': 'error',
'playwright/prefer-comparison-matcher': 'error',
'playwright/max-nested-describe': ['error', { max: 1 }],
'playwright/prefer-lowercase-title': ['error', { ignoreTopLevelDescribe: true }],
};
export default {
useTabs: true,
printWidth: 150,
singleQuote: true,
trailingComma: 'all',
quoteProps: 'consistent',
};
{
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never" // Prevent conflicts with “perfectionist/sort-imports” rule.
},
/* Silent stylistic rules in the editor, but still auto fix them. */
"eslint.rules.customizations": [
/* General */
{ "rule": "*sort*", "severity": "off" },
{ "rule": "*style*", "severity": "off" },
{ "rule": "*indent", "severity": "off" },
{ "rule": "*quotes", "severity": "off" },
{ "rule": "import-*", "severity": "off" },
{ "rule": "*console", "severity": "off" },
{ "rule": "*-spaces", "severity": "off" },
{ "rule": "*-spacing", "severity": "off" },
{ "rule": "*newline*", "severity": "off" },
{ "rule": "attribute*", "severity": "off" },
{ "rule": "import/first", "severity": "off" },
{ "rule": "format/prettier", "severity": "off" },
/* Vue */
{ "rule": "vue/comma-dangle", "severity": "off" },
{ "rule": "vue/space-in-parens", "severity": "off" },
{ "rule": "vue/attributes-order", "severity": "off" },
{ "rule": "vue/define-macros-order", "severity": "off" },
{ "rule": "vue/max-attributes-per-line", "severity": "off" },
{ "rule": "vue/first-attribute-linebreak", "severity": "off" },
{ "rule": "vue/prefer-true-attribute-shorthand", "severity": "off" }
],
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"yaml",
"toml",
"gql",
"graphql"
],
}
import eslintPluginStylistic from '@stylistic/eslint-plugin';
export default {
...eslintPluginStylistic.configs['recommended-flat'].rules,
'style/semi-style': 'error',
'style/no-extra-semi': 'error',
'style/spaced-comment': 'off',
'style/indent': ['error', 'tab'],
'style/arrow-parens': ['error', 'always'],
'style/indent-binary-ops': ['error', 'tab'],
'style/operator-linebreak': ['error', 'none'],
'style/implicit-arrow-linebreak': ['error', 'beside'],
'style/no-mixed-spaces-and-tabs': ['error', 'smart-tabs'],
'style/brace-style': ['error', '1tbs', { allowSingleLine: true }],
'style/semi': ['error', 'always', { omitLastInOneLineBlock: true }],
'style/lines-around-comment': ['error', {
allowTypeStart: true,
allowEnumStart: true,
allowClassStart: true,
allowBlockStart: true,
allowArrayStart: true,
allowModuleStart: true,
allowObjectStart: true,
allowInterfaceStart: true,
}],
'style/member-delimiter-style': ['error', {
multilineDetection: 'brackets',
multiline: { delimiter: 'semi', requireLast: true },
singleline: { delimiter: 'semi', requireLast: false },
}],
'style/padding-line-between-statements': [
'error',
{
prev: '*',
next: [
'do',
'try',
'for',
'iife',
'with',
'class',
'block',
'while',
'throw',
'return',
'switch',
'export',
'function',
'directive',
'block-like',
'cjs-export',
'multiline-block-like',
],
blankLine: 'always',
},
{
prev: 'export',
next: 'export',
blankLine: 'any',
},
{
prev: ['const', 'let', 'var'],
next: 'block-like',
blankLine: 'any',
},
{
prev: 'block-like',
next: '*',
blankLine: 'always',
},
{
prev: 'function-overload',
next: 'function',
blankLine: 'never',
},
],
};
import eslintPluginTailwind from "eslint-plugin-tailwindcss";
export default {
...eslintPluginTailwind.configs["flat/recommended"].rules,
}
export default {
'ts/no-misused-promises': 'off',
'ts/no-unsafe-assignment': 'off',
'ts/member-delimiter-style': 'error',
'ts/type-annotation-spacing': 'error',
'ts/strict-boolean-expressions': 'off',
'ts/consistent-type-definitions': ['error', 'type'],
};
import eslintPluginVitest from 'eslint-plugin-vitest';
export default {
...eslintPluginVitest.configs.recommended.rules,
'vitest/prefer-todo': 'error',
'vitest/require-hook': 'error',
'vitest/prefer-to-be': 'error',
'vitest/prefer-spy-on': 'error',
'vitest/no-mocks-import': 'error',
'vitest/no-test-prefixes': 'error',
'vitest/no-alias-methods': 'error',
'vitest/no-focused-tests': 'error',
'vitest/no-disabled-tests': 'error',
'vitest/prefer-to-contain': 'error',
'vitest/prefer-called-with': 'error',
'vitest/prefer-to-be-falsy': 'error',
'vitest/no-duplicate-hooks': 'error',
'vitest/prefer-strict-equal': 'error',
'vitest/no-conditional-tests': 'error',
'vitest/prefer-to-be-truthy': 'error',
'vitest/prefer-to-be-object': 'error',
'vitest/no-standalone-expect': 'error',
'vitest/no-conditional-expect': 'error',
'vitest/no-conditional-in-test': 'error',
'vitest/prefer-to-have-length': 'error',
'vitest/prefer-hooks-in-order': 'error',
'vitest/prefer-lowercase-title': 'error',
'vitest/prefer-equality-matcher': 'error',
'vitest/consistent-test-filename': 'error',
'vitest/no-test-return-statement': 'error',
'vitest/require-to-throw-message': 'error',
'vitest/prefer-comparison-matcher': 'error',
'vitest/no-interpolation-in-snapshots': 'error',
'vitest/prefer-mock-promise-shorthand': 'error',
'vitest/prefer-snapshot-hint': ['error', 'always'],
'vitest/max-nested-describe': ['error', { max: 1 }],
'vitest/consistent-test-it': ['error', { fn: 'test', withinDescribe: 'test' }],
};
import { version as vueVersion } from 'vue';
import eslintPluginVue from 'eslint-plugin-vue';
export default {
...eslintPluginVue.configs.recommended.rules,
/* Base */
'vue/jsx-uses-vars': 'error',
'vue/comment-directive': ['error', { reportUnusedDisableDirectives: true }],
/* Priority B: Strongly Recommended (Improving Readability) */
'vue/singleline-html-element-content-newline': 'off',
'vue/max-attributes-per-line': ['error', { singleline: 3 }],
'vue/v-slot-style': ['error', { atComponent: 'shorthand' }],
'vue/html-self-closing': ['error', { html: { void: 'always' } }],
'vue/first-attribute-linebreak': ['error', { singleline: 'beside' }],
'vue/html-indent': ['error', 'tab', { attribute: 1, baseIndent: 1 }],
'vue/v-on-event-hyphenation': ['error', 'always', { autofix: true }],
'vue/v-bind-style': ['error', 'shorthand', { sameNameShorthand: 'always' }],
/* Priority C: Recommended (Potentially Dangerous Patterns) */
'vue/attributes-order': ['error', {
order: [
'RENDER_MODIFIERS',
'DEFINITION',
'CONDITIONALS',
'LIST_RENDERING',
'UNIQUE',
'TWO_WAY_BINDING',
'OTHER_DIRECTIVES',
'OTHER_ATTR',
'SLOT',
'CONTENT',
'GLOBAL',
],
}],
/* Miscellaneous */
'vue/no-root-v-if': ['error'],
'vue/require-expose': ['error'],
'vue/no-useless-v-bind': ['error'],
'vue/require-typed-ref': ['error'],
'vue/valid-define-options': ['error'],
'vue/require-explicit-slots': ['error'],
'vue/prefer-define-options': ['error'],
'vue/require-emit-validator': ['error'],
'vue/no-use-v-else-with-v-for': ['error'],
'vue/v-for-delimiter-style': ['error', 'in'],
'vue/no-empty-component-block': ['error'],
'vue/html-comment-indent': ['error', 'tab'],
'vue/no-multiple-objects-in-class': ['error'],
'vue/prefer-separate-static-class': ['error'],
'vue/no-ref-object-reactivity-loss': ['error'],
'vue/no-duplicate-attr-inheritance': ['error'],
'vue/no-this-in-before-route-enter': ['error'],
'vue/no-setup-props-reactivity-loss': ['error'],
'vue/block-lang': ['error', { script: { lang: 'ts' } }],
'vue/component-api-style': ['error', ['script-setup']],
'vue/padding-line-between-blocks': ['error', 'always'],
'vue/define-props-declaration': ['error', 'type-based'],
'vue/custom-event-name-casing': ['error', 'camelCase'],
'vue/no-unsupported-features': ['error', { vueVersion }],
'vue/define-emits-declaration': ['error', 'type-literal'],
'vue/html-comment-content-spacing': ['error', 'always'],
'vue/prefer-true-attribute-shorthand': ['error', 'always'],
'vue/no-static-inline-styles': ['error', { allowBinding: true }],
'vue/enforce-style-attribute': ['error', { allow: ['scoped'] }],
'vue/component-options-name-casing': ['error', 'PascalCase'],
'vue/no-required-prop-with-default': ['error', { autofix: true }],
'vue/v-on-handler-style': ['error', ['method', 'inline-function']],
'vue/no-deprecated-model-definition': ['error', { allowVue3Compat: true }],
'vue/html-button-has-type': ['error', { button: true, submit: true, reset: true }],
'vue/block-order': ['error', { order: ['script[setup]', 'template', 'style[scoped]'] }],
'vue/html-comment-content-newline': ['error', { singleline: 'never', multiline: 'always' }],
'vue/match-component-file-name': ['error', { extensions: ['vue'], shouldMatchCase: true }],
'vue/block-tag-newline': ['error', { maxEmptyLines: 0, multiline: 'always', singleline: 'consistent' }],
'vue/component-name-in-template-casing': ['error', 'PascalCase', { registeredComponentsOnly: false }],
'vue/define-macros-order': ['error', {
order: [
'defineOptions',
'defineProps',
],
}],
'vue/require-macro-variable-name': ['error', {
useSlots: 'slots',
useAttrs: 'attrs',
defineSlots: 'slots',
defineProps: 'props',
defineEmits: 'emits',
}],
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment