create-appncy
Version:
Create projects as goncy would
202 lines (191 loc) • 7.74 kB
JavaScript
import js from "@eslint/js";
import next from "@next/eslint-plugin-next";
import stylistic from "@stylistic/eslint-plugin";
import jsxA11y from "eslint-plugin-jsx-a11y";
import perfectionist from "eslint-plugin-perfectionist";
import prettierRecommended from "eslint-plugin-prettier/recommended";
import react from "eslint-plugin-react";
import reactCompiler from "eslint-plugin-react-compiler";
import reactHooks from "eslint-plugin-react-hooks";
import globals from "globals";
import tseslint from "typescript-eslint";
// Flat config only processes `.js`/`.mjs`/`.cjs` by default. Any block's
// `files` field tells ESLint about additional extensions to walk; all blocks
// without `files` then apply to every file ESLint is processing.
//
// That's why only three blocks below declare `files`:
// · `appncy/javascript` — extends the walk to TS and JSX extensions.
// · `appncy/a11y` + `appncy/sort-jsx-props` — genuinely JSX-only rules.
export default tseslint.config(
// ── Ignores ─────────────────────────────────────────────────────────────
{
name: "appncy/ignores",
ignores: [
".next/**",
"out/**",
"build/**",
"dist/**",
"coverage/**",
"node_modules/**",
"next-env.d.ts",
],
},
// ── JavaScript / TypeScript ─────────────────────────────────────────────
// Non type-aware on purpose: no `parserOptions.project`, which would force
// a full TS program load on every lint pass.
{
name: "appncy/javascript",
files: ["**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx}"],
extends: [js.configs.recommended, tseslint.configs.recommended],
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
globals: {...globals.browser, ...globals.node, ...globals.serviceworker},
},
rules: {
"no-console": ["warn", {allow: ["error"]}],
"@typescript-eslint/no-unused-vars": [
"warn",
{
args: "after-used",
ignoreRestSiblings: false,
argsIgnorePattern: "^_.*?$",
caughtErrorsIgnorePattern: "^_.*?$",
},
],
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-confusing-void-expression": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-inferrable-types": "off",
// Optionals
// "@typescript-eslint/no-floating-promises": "off",
// "@typescript-eslint/no-non-null-assertion": "off",
},
},
// ── React / hooks / compiler ────────────────────────────────────────────
{
name: "appncy/react",
extends: [
react.configs.flat.recommended,
react.configs.flat["jsx-runtime"],
reactHooks.configs.flat.recommended,
reactCompiler.configs.recommended,
],
settings: {react: {version: "detect"}},
rules: {
"react/jsx-no-useless-fragment": "error",
"react/self-closing-comp": "warn",
"react/jsx-curly-brace-presence": ["error", {props: "never", children: "never"}],
},
},
// ── Accessibility (JSX only) ────────────────────────────────────────────
{
name: "appncy/a11y",
files: ["**/*.{jsx,tsx}"],
extends: [jsxA11y.flatConfigs.recommended],
rules: {
"jsx-a11y/click-events-have-key-events": "off",
},
},
// ── Next.js ─────────────────────────────────────────────────────────────
{
name: "appncy/next",
plugins: {"@next/next": next},
rules: {
...next.configs.recommended.rules,
...next.configs["core-web-vitals"].rules,
"@next/next/no-img-element": "off",
},
},
// ── Import sorting ──────────────────────────────────────────────────────
// Replaces eslint-plugin-import (`import/order`) with a faster, Map-based
// implementation.
{
name: "appncy/sort-imports",
plugins: {perfectionist},
rules: {
"perfectionist/sort-imports": [
"warn",
{
type: "natural",
order: "asc",
newlinesBetween: 1,
internalPattern: ["^@/.+"],
groups: [
"type",
"builtin",
"external",
"internal",
["parent", "sibling", "index"],
"side-effect",
"style",
"unknown",
],
},
],
"perfectionist/sort-named-imports": ["warn", {type: "natural", order: "asc"}],
},
},
// ── JSX prop sorting ────────────────────────────────────────────────────
// Replaces `react/jsx-sort-props` (perfectionist/react recommend disabling
// the react rule to avoid double-sorting).
{
name: "appncy/sort-jsx-props",
files: ["**/*.{jsx,tsx}"],
plugins: {perfectionist},
rules: {
"perfectionist/sort-jsx-props": [
"warn",
{
type: "natural",
order: "asc",
groups: ["shorthand-prop", "multiline-prop", "unknown", "callback"],
customGroups: [{groupName: "callback", elementNamePattern: "^on[A-Z].*"}],
},
],
},
},
// ── Stylistic ───────────────────────────────────────────────────────────
// `padding-line-between-statements` was removed from ESLint core's flat
// defaults; @stylistic is the supported home for it now.
{
name: "appncy/stylistic",
plugins: {"@stylistic": stylistic},
rules: {
"@stylistic/padding-line-between-statements": [
"warn",
{blankLine: "always", prev: "*", next: ["return", "export"]},
{blankLine: "always", prev: ["const", "let", "var"], next: "*"},
{blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]},
],
},
},
// ── Prettier ────────────────────────────────────────────────────────────
// Runs last so it overrides any stylistic rule still left on. Options are
// inlined with `usePrettierrc: false` so the plugin skips the per-file
// `prettier.resolveConfig` + `getFileInfo` calls that dominate its cost
// (see prettier/eslint-plugin-prettier#445).
{
...prettierRecommended,
name: "appncy/prettier",
rules: {
...prettierRecommended.rules,
"prettier/prettier": [
"warn",
{
printWidth: 100,
trailingComma: "all",
tabWidth: 2,
semi: true,
singleQuote: false,
bracketSpacing: false,
arrowParens: "always",
endOfLine: "auto",
plugins: ["prettier-plugin-tailwindcss"],
},
{usePrettierrc: false},
],
},
},
);