Ctrl K
ComunidadGuías…

eslint.config.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { fileURLToPath } from "node:url";
import { includeIgnoreFile } from "@eslint/compat";
import { defineConfig } from "eslint/config";
import tsPlugin from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
import reactHooks from "eslint-plugin-react-hooks";
import core from "ultracite/eslint/core";
import next from "ultracite/eslint/next";
import noInternalMocks from "./scripts/eslint-rules/no-internal-mocks.mjs";
import { VERCEL_SANDBOX_SDK_IMPORTERS } from "./scripts/eslint-rules/vercel-sandbox-importers.mjs";

// Glob patterns read `[id]` as a character class; match route folders literally.
// Only `[` and `]` are escaped: allowlisted paths are source files, which never
// contain `*`, `?`, `{`, `}` or a leading `!`. Extend this if one ever does.
const escapeGlob = (path) => path.replaceAll(/[[\]]/g, String.raw`\$&`);

const sanitizedCore = core
  .map((config) => {
    if (!config || typeof config !== "object") return config;

    const nextConfig = { ...config };

    if ("files" in nextConfig && Array.isArray(nextConfig.files)) {
      nextConfig.files = nextConfig.files.filter(
        (pattern) => pattern !== "**/*.json" && pattern !== "**/*.html"
      );
    }

    if ("rules" in nextConfig && nextConfig.rules) {
      nextConfig.rules = {
        ...nextConfig.rules,
        "import-x/enforce-node-protocol-usage": "off",
        "import-x/no-unused-modules": "off",
        // Unbounded cycle detection walks the whole import graph per file
        // (~60% of total lint time). Shallow cycles are the real bugs.
        "import-x/no-cycle": ["error", { maxDepth: 3, ignoreExternal: true }],
      };
    }

    return nextConfig;
  })
  .filter(
    (config) =>
      !("files" in config) ||
      !Array.isArray(config.files) ||
      config.files.length > 0
  );

export default defineConfig([
  // Everything git ignores, eslint must ignore too. Local build artifacts
  // (.vercel/output alone holds ~500 minified bundles) are absent on CI, so
  // linting them turned an 86-second lint into 75 minutes locally.
  includeIgnoreFile(fileURLToPath(new URL(".gitignore", import.meta.url))),
  ...sanitizedCore,
  ...next,
  {
    files: ["**/*.{ts,tsx}"],
    languageOptions: {
      parser: tsParser,
      parserOptions: {
        ecmaVersion: "latest",
        sourceType: "module",
        ecmaFeatures: { jsx: true },
      },
    },
    plugins: {
      "@typescript-eslint": tsPlugin,
      "react-hooks": reactHooks,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      // Source files cap at 500 lines — split by concern instead of growing a
      // file. Pre-existing violators are grandfathered to "warn" in a block
      // near the end of this config; that list only shrinks.
      "max-lines": ["error", { max: 500 }],
      // Keep initial migration focused on formatting, imports, and low-risk fixes.
      "@typescript-eslint/array-type": "off",
      "@typescript-eslint/consistent-type-definitions": "off",
      "@typescript-eslint/consistent-type-imports": "off",
      "@typescript-eslint/dot-notation": "off",
      "@typescript-eslint/explicit-member-accessibility": "off",
      "@typescript-eslint/explicit-module-boundary-types": "off",
      "@typescript-eslint/init-declarations": "off",
      "@typescript-eslint/max-params": "off",
      "@typescript-eslint/naming-convention": "off",
      "@typescript-eslint/no-base-to-string": "off",
      "@typescript-eslint/no-empty-function": "off",
      "@typescript-eslint/no-empty-object-type": "off",
      "@typescript-eslint/no-explicit-any": "warn",
      "@typescript-eslint/no-floating-promises": "off",
      "@typescript-eslint/no-non-null-assertion": "off",
      "@typescript-eslint/no-require-imports": "off",
      "@typescript-eslint/no-shadow": "off",
      "@typescript-eslint/no-unnecessary-condition": "off",
      "@typescript-eslint/no-unnecessary-type-assertion": "off",
      "@typescript-eslint/no-unsafe-call": "off",
      "@typescript-eslint/no-unsafe-argument": "off",
      "@typescript-eslint/no-unsafe-assignment": "off",
      "@typescript-eslint/no-unsafe-member-access": "off",
      "@typescript-eslint/no-unsafe-return": "off",
      "@typescript-eslint/no-unsafe-type-assertion": "off",
      "@typescript-eslint/no-use-before-define": "off",
      "@typescript-eslint/promise-function-async": "off",
      "@typescript-eslint/prefer-destructuring": "off",
      "@typescript-eslint/prefer-nullish-coalescing": "off",
      "@typescript-eslint/prefer-regexp-exec": "off",
      "@typescript-eslint/restrict-template-expressions": "off",
      "@typescript-eslint/require-await": "off",
      "@typescript-eslint/return-await": "off",
      "@typescript-eslint/no-unused-vars": [
        "warn",
        { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
      ],
      "no-await-in-loop": "off",
      "no-continue": "off",
      "no-console": "off",
      "no-eq-null": "off",
      "no-void": "off",
      "no-nested-ternary": "off",
      eqeqeq: "off",
      "default-case": "off",
      "github/no-then": "off",
      "no-unused-vars": "off",
      "n/no-sync": "off",
      "no-magic-numbers": "off",
      "no-undef": "off",
      "compat/compat": "off",
      "func-style": "off",
      "id-length": "off",
      "import-x/consistent-type-specifier-style": "off",
      "import-x/dynamic-import-chunkname": "off",
      "import-x/no-nodejs-modules": "off",
      "import-x/order": "off",
      "prefer-destructuring": "off",
      "prefer-named-capture-group": "off",
      "prefer-template": "off",
      "promise/avoid-new": "off",
      "promise/prefer-await-to-callbacks": "off",
      "promise/prefer-await-to-then": "off",
      "require-atomic-updates": "off",
      "require-unicode-regexp": "off",
      "react-hooks/immutability": "warn",
      "react-hooks/invariant": "warn",
      "react-hooks/preserve-manual-memoization": "warn",
      "react-hooks/purity": "warn",
      "react-hooks/set-state-in-effect": "warn",
      "sonarjs/arrow-function-convention": "off",
      complexity: ["warn", { max: 10 }],
      "sonarjs/cognitive-complexity": "off",
      "sonarjs/cyclomatic-complexity": ["warn", { threshold: 10 }],
      "sonarjs/declarations-in-global-scope": "off",
      "sonarjs/expression-complexity": "off",
      "sonarjs/file-header": "off",
      "sonarjs/function-name": "off",
      "sonarjs/max-union-size": "off",
      "sonarjs/nested-control-flow": "off",
      "sonarjs/no-alphabetical-sort": "off",
      "sonarjs/no-duplicate-string": "off",
      "sonarjs/no-misleading-array-reverse": "off",
      "sonarjs/no-nested-conditional": "off",
      "sonarjs/no-reference-error": "off",
      "sonarjs/no-undefined-assignment": "off",
      "sonarjs/prefer-regexp-exec": "off",
      "sonarjs/shorthand-property-grouping": "off",
      "sonarjs/slow-regex": "off",
      "sonarjs/too-many-break-or-continue-in-loop": "off",
      "unused-imports/no-unused-vars": "off",
      "unicorn/better-regex": "off",
      "unicorn/catch-error-name": "off",
      "unicorn/consistent-destructuring": "off",
      "unicorn/no-array-sort": "off",
      "unicorn/no-nested-ternary": "off",
      "unicorn/no-useless-switch-case": "off",
      "unicorn/no-useless-undefined": "off",
      "unicorn/numeric-separators-style": "off",
      "unicorn/prefer-at": "off",
      "unicorn/prefer-abbreviations": "off",
      "unicorn/prevent-abbreviations": "off",
      "unicorn/prefer-global-this": "off",
      "unicorn/prefer-number-properties": "off",
      "unicorn/prefer-response-static-json": "off",
      "unicorn/prefer-spread": "off",
      "unicorn/prefer-string-raw": "off",
      "unicorn/prefer-string-replace-all": "off",
      "unicorn/switch-case-braces": "off",
      "unicorn/text-encoding-identifier-case": "off",
    },
  },
  {
    files: ["**/*.{js,jsx,ts,tsx,mjs,cjs}"],
    plugins: {
      "@typescript-eslint": tsPlugin,
    },
    rules: {
      "@typescript-eslint/await-thenable": "off",
      "@typescript-eslint/consistent-return": "off",
      "@typescript-eslint/no-dynamic-delete": "off",
      "@typescript-eslint/no-explicit-any": "warn",
      "@typescript-eslint/no-redundant-type-constituents": "off",
      "@typescript-eslint/no-unnecessary-type-conversion": "off",
      "@typescript-eslint/parameter-properties": "off",
      "@typescript-eslint/strict-void-return": "off",
      "@typescript-eslint/switch-exhaustiveness-check": "off",
      "@typescript-eslint/unbound-method": "off",
      "@typescript-eslint/use-unknown-in-catch-callback-variable": "off",
      "consistent-return": "off",
      "compat/compat": "off",
      "github/async-preventdefault": "off",
      "logical-assignment-operators": "off",
      "max-depth": "off",
      "new-cap": "off",
      "no-inline-comments": "off",
      "no-magic-numbers": "off",
      "no-plusplus": "off",
      "no-promise-executor-return": "off",
      "no-redeclare": "off",
      "no-underscore-dangle": "off",
      "no-warning-comments": "off",
      "promise/catch-or-return": "off",
      "promise/param-names": "off",
      radix: "off",
      "sonarjs/arrow-function-convention": "off",
      "sonarjs/declarations-in-global-scope": "off",
      "sonarjs/file-header": "off",
      "sonarjs/max-lines": "off",
      "sonarjs/max-lines-per-function": "off",
      "sonarjs/no-identical-functions": "off",
      "sonarjs/no-inconsistent-returns": "off",
      "sonarjs/no-nested-functions": "off",
      "sonarjs/no-unused-vars": "off",
      "sonarjs/no-wildcard-import": "off",
      "sonarjs/publicly-writable-directories": "off",
      "unicorn/consistent-function-scoping": "off",
      "unicorn/import-style": "off",
      "unicorn/no-array-reduce": "off",
      "unicorn/no-process-exit": "off",
      "unicorn/no-thenable": "off",
      "unicorn/prefer-code-point": "off",
      "unicorn/prefer-structured-clone": "off",
    },
  },
  {
    files: ["**/*.{js,jsx,ts,tsx,mjs,cjs}"],
    plugins: {
      "@typescript-eslint": tsPlugin,
    },
    rules: {
      "@next/next/no-img-element": "off",
      "@typescript-eslint/member-ordering": "off",
      "@typescript-eslint/no-deprecated": "off",
      "@typescript-eslint/no-invalid-void-type": "off",
      "@typescript-eslint/no-misused-spread": "off",
      "@typescript-eslint/no-unnecessary-type-parameters": "off",
      "@typescript-eslint/prefer-promise-reject-errors": "off",
      "@typescript-eslint/restrict-plus-operands": "off",
      "array-callback-return": "off",
      "func-style": "off",
      "github/no-then": "off",
      "import-x/no-deprecated": "off",
      "import-x/no-nodejs-modules": "off",
      "import-x/no-rename-default": "off",
      "n/no-process-exit": "off",
      "n/no-sync": "off",
      "no-bitwise": "off",
      "no-div-regex": "off",
      "no-await-in-loop": "off",
      "no-use-before-define": "off",
      "no-useless-return": "off",
      "no-void": "off",
      "prefer-const": "off",
      "prefer-named-capture-group": "off",
      "promise/always-return": "off",
      "promise/avoid-new": "off",
      "promise/no-multiple-resolved": "off",
      "promise/prefer-await-to-then": "off",
      "require-await": "off",
      "require-unicode-regexp": "off",
      "sonarjs/declarations-in-global-scope": "off",
      "sonarjs/deprecation": "off",
      complexity: ["warn", { max: 10 }],
      "sonarjs/cognitive-complexity": "off",
      "sonarjs/cyclomatic-complexity": "off",
      "sonarjs/file-header": "off",
      "sonarjs/file-name-differ-from-class": "off",
      "sonarjs/nested-control-flow": "off",
      "sonarjs/no-all-duplicated-branches": "off",
      "sonarjs/no-duplicated-branches": "off",
      "sonarjs/no-incorrect-string-concat": "off",
      "sonarjs/no-nested-assignment": "off",
      "sonarjs/no-nested-template-literals": "off",
      "sonarjs/no-os-command-from-path": "off",
      "sonarjs/no-unused-function-argument": "off",
      "sonarjs/no-nested-incdec": "off",
      "sonarjs/redundant-type-aliases": "off",
      "sonarjs/shorthand-property-grouping": "off",
      "sonarjs/single-char-in-character-classes": "off",
      "sonarjs/todo-tag": "off",
      "sonarjs/variable-name": "off",
      "sonarjs/void-use": "off",
      "unicorn/custom-error-definition": "off",
      "unicorn/no-array-reverse": "off",
      "unicorn/no-await-expression-member": "off",
      "unicorn/no-document-cookie": "off",
      "unicorn/no-for-loop": "off",
      "unicorn/prefer-default-parameters": "off",
      "unicorn/prefer-logical-operator-over-ternary": "off",
      "unicorn/prevent-abbreviations": "off",
      "unicorn/text-encoding-identifier-case": "off",
      "vars-on-top": "off",
    },
  },
  {
    // Sentry requires these exact filenames; exempt them from filename regex.
    files: ["sentry.server.config.ts", "sentry.edge.config.ts"],
    rules: {
      "github/filenames-match-regex": "off",
    },
  },
  {
    // components/ai-elements is vendored from the AI SDK Elements registry
    // (npx ai-elements). Registry updates regenerate these files wholesale, so
    // hand-splitting them to satisfy source-file rules would fight upstream.
    files: ["components/ai-elements/**/*.{jsx,tsx}"],
    rules: {
      "max-lines": "off",
      "react-hooks/refs": "off",
      "react-hooks/static-components": "off",
    },
  },
  {
    files: [
      "app/**/flows/**/*.{jsx,tsx}",
      "app/**/workflows/**/*.{jsx,tsx}",
      "components/control/**/*.{jsx,tsx}",
      "components/flows/**/*.{jsx,tsx}",
      "components/panes/{flow,workflow}*.{jsx,tsx}",
      "components/panes/{flow,workflow}*/**/*.{jsx,tsx}",
    ],
    rules: {
      "no-restricted-syntax": [
        "error",
        {
          selector: "JSXOpeningElement[name.name=/^(select|datalist)$/]",
          message:
            "Project and workflow selection controls must use app-styled components instead of browser-native select or datalist elements.",
        },
      ],
    },
  },
  {
    // Sandbox backends are pluggable (lib/sandbox/provider). Only the Vercel
    // adapter may use the Vercel SDK; the frozen list of older importers
    // shrinks as Track B migrates them.
    files: ["**/*.{ts,tsx,mjs}"],
    ignores: [
      "lib/sandbox/provider/vercel/**",
      "**/*.test.{ts,tsx}",
      "tests/**",
      ...VERCEL_SANDBOX_SDK_IMPORTERS.map(escapeGlob),
    ],
    rules: {
      "no-restricted-imports": [
        "error",
        {
          paths: [
            {
              name: "@vercel/sandbox",
              message:
                "Import sandbox types from @/lib/sandbox/provider/types and get a backend from @/lib/sandbox/provider. Only lib/sandbox/provider/vercel may use the Vercel SDK.",
            },
          ],
          patterns: [
            {
              group: ["@vercel/sandbox/*"],
              message:
                "Only lib/sandbox/provider/vercel may use the Vercel SDK.",
            },
          ],
        },
      ],
    },
  },
  {
    // TESTING.md: mock only at declared boundaries. The allowlist lives in
    // tests/support/mockable-boundaries.mjs and is owner-gated in CODEOWNERS.
    files: ["**/*.test.{ts,tsx}", "**/*.spec.{ts,tsx}"],
    plugins: {
      testing: { rules: { "no-internal-mocks": noInternalMocks } },
    },
    rules: {
      "testing/no-internal-mocks": "error",
    },
  },
  {
    ignores: [
      ".agents/",
      ".claude/",
      ".next/",
      "node_modules/",
      "playwright-report/",
      "supabase/functions/",
      "test-results/",
      "next-env.d.ts",
      "tsconfig.json",
      "vercel.json",
      "**/*.md",
      // pnpm requires this exact filename and CJS format.
      ".pnpmfile.cjs",
      // CJS require hook preloaded via `tsx --require` in test:unit; must be
      // CommonJS to patch require.extensions / Module._load.
      "tests/support/bundler-shims.cjs",
    ],
  },
]);