2026-05-13 07:49:49 +00:00
import obsidianmd from "eslint-plugin-obsidianmd" ;
2026-05-14 08:12:52 +00:00
import eslintReact from "@eslint-react/eslint-plugin" ;
2026-05-13 07:49:49 +00:00
import reactHooks from "eslint-plugin-react-hooks" ;
import tailwind from "eslint-plugin-tailwindcss" ;
import globals from "globals" ;
export default [
{
ignores : [
"node_modules/**" ,
"main.js" ,
"styles.css" ,
"data.json" ,
"designdocs/**" ,
"docs/**" ,
] ,
} ,
// obsidianmd recommended brings:
// - eslint:recommended
// - typescript-eslint recommendedTypeChecked on .ts/.tsx (recommended on .js/.jsx)
// - obsidianmd plugin + all obsidianmd-namespaced rules
// - import / @microsoft/sdl / depend / no-unsanitized
// - Obsidian-injected globals (activeDocument, createDiv, etc.)
... obsidianmd . configs . recommended ,
// React + tailwind plugins ship flat configs with no `files` filter, so
// they'd cascade onto package.json (which uses the JSON parser) and crash.
2026-05-14 08:12:52 +00:00
// Constrain them to JSX/TSX sources where React/JSX rules actually apply.
2026-05-13 07:49:49 +00:00
{
2026-05-14 08:12:52 +00:00
files : [ "**/*.{jsx,tsx}" ] ,
... eslintReact . configs . recommended ,
2026-05-13 07:49:49 +00:00
} ,
chore(lint): fix no-array-index-key and other React lint warnings (#2453)
* chore(lint): fix no-array-index-key and other React lint warnings
Re-enable @eslint-react/no-array-index-key, replace index keys with stable
ids where possible (capability enum, action label, file paths, computed
coords), and add per-line eslint-disable with rationale where index is
intentional (diff parts, append-only steps, parsed markdown, duplicate-
tolerant context badges).
Also addresses adjacent React lint warnings: useMemo for context provider
values, stable module-scope defaults for empty array props, lazy useState
initializers, and explicit type="button" on non-submit buttons. Surface
no-direct-set-state-in-use-effect as a warning for follow-up triage.
* fix: dedupe selected image files
2026-05-14 09:19:38 +00:00
{
files : [ "**/*.{jsx,tsx}" ] ,
rules : {
// Deferred to follow-up PRs — these flag legitimate anti-patterns but
// each fix requires per-component intent analysis, and they're surfaced
// as warnings (not errors) so they don't block CI.
//
// no-direct-set-state-in-use-effect: ~50 violations. Common pattern is
// "sync local state with prop", which has no one-size-fits-all fix —
// some cases want render-time derivation, others want a `key` prop reset
// or `useSyncExternalStore`. Refactoring blindly risks behavior regressions
// in the chat UI's stateful components.
"@eslint-react/hooks-extra/no-direct-set-state-in-use-effect" : "warn" ,
} ,
} ,
2026-05-13 07:49:49 +00:00
{
files : [ "**/*.{js,jsx,mjs,cjs,ts,tsx}" ] ,
plugins : { "react-hooks" : reactHooks } ,
rules : {
"react-hooks/rules-of-hooks" : "error" ,
"react-hooks/exhaustive-deps" : "error" ,
} ,
} ,
... tailwind . configs [ "flat/recommended" ] . map ( ( cfg ) => ( {
files : [ "**/*.{js,jsx,mjs,cjs,ts,tsx}" ] ,
... cfg ,
} ) ) ,
{
files : [ "**/*.{js,jsx,mjs,cjs,ts,tsx}" ] ,
languageOptions : {
globals : {
// Obsidian plugin runtime injects `app` as a global (see CLAUDE.md).
app : "readonly" ,
} ,
} ,
settings : {
2026-05-14 08:12:52 +00:00
"react-x" : { version : "detect" } ,
2026-05-13 07:49:49 +00:00
tailwindcss : {
callees : [ "classnames" , "clsx" , "ctl" , "cn" , "cva" ] ,
config : "./tailwind.config.js" ,
cssFiles : [ "**/*.css" , "!**/node_modules" , "!**/.*" , "!**/dist" , "!**/build" ] ,
2026-05-14 08:43:39 +00:00
// Obsidian-provided utility classes used in JSX but not defined in our CSS.
whitelist : [ "clickable-icon" ] ,
2026-05-13 07:49:49 +00:00
} ,
} ,
rules : {
// Carry-over from legacy .eslintrc
"no-prototype-builtins" : "off" ,
"tailwindcss/classnames-order" : "error" ,
"tailwindcss/enforces-negative-arbitrary-values" : "error" ,
"tailwindcss/enforces-shorthand" : "error" ,
"tailwindcss/migration-from-tailwind-2" : "error" ,
"tailwindcss/no-arbitrary-value" : "off" ,
"tailwindcss/no-custom-classname" : "error" ,
"tailwindcss/no-contradicting-classname" : "error" ,
// obsidianmd: defer to follow-up PRs
"obsidianmd/ui/sentence-case" : "off" ,
// obsidianmd: disabled intentionally — Platform.isMacOS branching is on-purpose
"obsidianmd/platform" : "off" ,
// Bundled by obsidianmd/recommended via tseslint.configs.recommendedTypeChecked.
// Disabled here because the codebase intentionally uses `any` / dynamic typing
// around Obsidian's untyped APIs and LangChain message shapes — flipping these
// on would require refactoring thousands of call sites with no functional gain.
2026-05-13 08:52:09 +00:00
//
// Violation counts (src/**/*.{ts,tsx}) are noted inline. Rules with low counts
// are candidates to enable in small follow-up PRs.
// --- Heavy: any-flow through Obsidian/LangChain APIs ---
2026-05-16 01:26:40 +00:00
// no-unsafe-member-access: enabled globally; tests are exempted via the
// test-file override below.
2026-05-14 05:56:46 +00:00
"@typescript-eslint/no-unsafe-assignment" : "off" , // enabled for tests below; follow-up PR for production
2026-05-14 06:17:30 +00:00
"@typescript-eslint/no-unsafe-call" : "off" , // 107 violations
2026-05-13 08:52:09 +00:00
// --- Medium: promise / method ergonomics ---
2026-05-14 04:45:35 +00:00
// Enabled in the TS-only block below.
2026-05-13 08:52:09 +00:00
2026-05-13 07:49:49 +00:00
// no-deprecated: defer — surface the warnings, but don't fail CI yet
"@typescript-eslint/no-deprecated" : "off" ,
// SDL / import / no-unsanitized / depend: defer — review separately
"no-restricted-globals" : "off" ,
} ,
} ,
2026-05-15 18:49:03 +00:00
// Guardrail: every standalone React root in the plugin must go through
// `createPluginRoot` so descendants can rely on `useApp()` unconditionally
// (the bug class fixed in PR #2466). Forbid importing `createRoot` from
// `react-dom/client` anywhere except the helper itself.
{
files : [ "src/**/*.{ts,tsx}" ] ,
ignores : [ "src/utils/react/createPluginRoot.tsx" ] ,
rules : {
"no-restricted-syntax" : [
"error" ,
{
selector :
"ImportDeclaration[source.value='react-dom/client'] ImportSpecifier[imported.name='createRoot']" ,
message :
"Use createPluginRoot from '@/utils/react/createPluginRoot' instead. It wraps the root in <AppContext.Provider> so descendants can rely on useApp() unconditionally (see PR #2466)." ,
} ,
] ,
} ,
} ,
2026-05-13 07:49:49 +00:00
// Test files need Jest globals
{
files : [ "**/*.test.{js,jsx,ts,tsx}" , "jest.setup.js" , "__mocks__/**" ] ,
languageOptions : {
globals : {
... globals . jest ,
... globals . node ,
} ,
} ,
2026-05-13 09:01:32 +00:00
rules : {
"import/no-nodejs-modules" : "off" ,
2026-05-14 05:35:38 +00:00
// Tests use intentional `any` mocks; disable type-safety rules that flood
// the test suite without adding signal.
"@typescript-eslint/no-unsafe-member-access" : "off" ,
} ,
} ,
2026-05-14 05:56:46 +00:00
// Tests have been cleaned of unsafe `any` assignments. Production code
// (~499 violations) is a follow-up; keep tests enforced.
{
files : [ "**/*.test.{ts,tsx}" ] ,
rules : {
"@typescript-eslint/no-unsafe-assignment" : "error" ,
} ,
} ,
2026-05-13 09:13:27 +00:00
// Integration tests bootstrap jsdom fetch via `node-fetch` polyfill —
// allow the otherwise-banned import here only.
{
files : [ "src/integration_tests/**" ] ,
rules : {
"no-restricted-imports" : "off" ,
} ,
} ,
2026-05-13 07:49:49 +00:00
// Node-context files (build configs, scripts)
{
files : [
"*.{js,mjs,cjs}" ,
"scripts/**" ,
"esbuild.config.mjs" ,
"version-bump.mjs" ,
"wasmPlugin.mjs" ,
"nodeModuleShim.mjs" ,
"jest.config.js" ,
"tailwind.config.js" ,
] ,
languageOptions : {
globals : {
... globals . node ,
} ,
} ,
2026-05-13 09:01:32 +00:00
rules : {
"import/no-nodejs-modules" : "off" ,
} ,
2026-05-13 07:49:49 +00:00
} ,
// TypeScript-specific overrides (the @typescript-eslint plugin is registered
// by obsidianmd's recommended config only for .ts/.tsx files).
{
files : [ "**/*.ts" , "**/*.tsx" ] ,
languageOptions : {
parserOptions : {
project : "./tsconfig.json" ,
tsconfigRootDir : import . meta . dirname ,
} ,
} ,
rules : {
"@typescript-eslint/no-empty-function" : "off" ,
"@typescript-eslint/ban-ts-comment" : "off" ,
"@typescript-eslint/no-unused-vars" : [ "error" , { args : "none" } ] ,
2026-05-14 04:27:35 +00:00
// checksVoidReturn relaxed for:
// - attributes: async event handlers in JSX (onClick={async () => ...}) are
// the standard React pattern; React already handles them correctly.
// - inheritedMethods: Obsidian's Plugin.onload/onunload are commonly async.
"@typescript-eslint/no-misused-promises" : [
"error" ,
{ checksVoidReturn : { attributes : false , inheritedMethods : false } } ,
] ,
2026-05-14 04:45:35 +00:00
"@typescript-eslint/no-floating-promises" : "error" ,
2026-05-14 05:04:59 +00:00
"@typescript-eslint/no-unsafe-return" : "error" ,
2026-05-14 05:09:34 +00:00
"@typescript-eslint/unbound-method" : "error" ,
2026-05-13 07:49:49 +00:00
// TypeScript handles undefined-identifier detection (and does so cross-realm
// correctly); per typescript-eslint's own guidance, disable no-undef on TS.
"no-undef" : "off" ,
} ,
} ,
// Non-TS files aren't in tsconfig.json — disable type-aware rules that
2026-05-14 05:01:00 +00:00
// obsidianmd's recommended config enables globally. Most typed obsidianmd
// rules are already gated to **/*.ts(x); only no-plugin-as-component leaks
// out via recommendedPluginRulesConfig, and @typescript-eslint/no-deprecated
// is enabled globally.
2026-05-13 07:49:49 +00:00
{
files : [ "**/*.js" , "**/*.mjs" , "**/*.cjs" , "**/*.jsx" , "**/package.json" ] ,
rules : {
"@typescript-eslint/no-deprecated" : "off" ,
"obsidianmd/no-plugin-as-component" : "off" ,
} ,
} ,
2026-05-13 09:07:28 +00:00
// package.json: keep depend/ban-dependencies enabled (from obsidianmd
// recommended) but allow the deps we deliberately keep.
2026-05-13 07:49:49 +00:00
{
files : [ "**/package.json" ] ,
rules : {
2026-05-13 09:07:28 +00:00
"depend/ban-dependencies" : [
"error" ,
{
presets : [ "native" , "microutilities" , "preferred" ] ,
2026-05-14 08:12:52 +00:00
allowed : [ ] ,
2026-05-13 09:07:28 +00:00
} ,
] ,
2026-05-13 07:49:49 +00:00
} ,
} ,
2026-05-13 08:39:35 +00:00
// logger.ts is the central logging utility and must call console.* directly.
// scripts/** are CLI tools that print to stdout.
{
files : [ "src/logger.ts" , "scripts/**" ] ,
rules : {
"obsidianmd/rule-custom-message" : "off" ,
} ,
} ,
2026-05-14 05:09:34 +00:00
// Jest assertions like `expect(mock.method).toHaveBeenCalled()` reference
// methods unbound by design. The rule has no clean workaround for jest
// patterns (binding changes the reference identity and breaks the assertion),
// so disable it in tests. Scoped to .ts/.tsx because the @typescript-eslint
// plugin is only registered for those files. Placed last so it overrides the
// TS-only block above.
{
files : [ "**/*.test.{ts,tsx}" ] ,
rules : {
"@typescript-eslint/unbound-method" : "off" ,
} ,
} ,
2026-05-13 07:49:49 +00:00
] ;