Files
Yishen Tu 024d48a1d7 fix: resolve reported lint violations (#1126)
* fix: resolve reported lint violations

* chore: align local quality checks with CI

* fix: migrate settings tab lifecycle
2026-08-21 00:38:49 +08:00

227 lines
8.7 KiB
JavaScript

import js from '@eslint/js';
import eslintComments from '@eslint-community/eslint-plugin-eslint-comments';
import tseslint from '@typescript-eslint/eslint-plugin';
import jestPlugin from 'eslint-plugin-jest';
import obsidianmd from 'eslint-plugin-obsidianmd';
import { DEFAULT_ACRONYMS } from 'eslint-plugin-obsidianmd/dist/lib/rules/ui/acronyms.js';
import { DEFAULT_BRANDS } from 'eslint-plugin-obsidianmd/dist/lib/rules/ui/brands.js';
import simpleImportSort from 'eslint-plugin-simple-import-sort';
import { defineConfig } from 'eslint/config';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const jestRecommended = jestPlugin.configs['flat/recommended'];
const tsconfigRootDir = dirname(fileURLToPath(import.meta.url));
const obsidianRuleSeverity = 'error';
// Enforces the file naming conventions from AGENTS.md without extra dependencies.
// Exported so scripts/check-eslint-config.test.mjs can exercise it directly.
export const fileNamingRule = {
meta: {
type: 'suggestion',
docs: { description: 'Enforce the file naming conventions from AGENTS.md' },
messages: {
invalidCase:
"Filename '{{name}}' must use camelCase, PascalCase, or kebab-case (see AGENTS.md naming conventions).",
conceptMismatch:
"File '{{name}}' exports '{{concept}}'; modules with a primary named concept use a PascalCase filename ('{{concept}}.ts').",
},
},
create(context) {
const filename = context.physicalFilename ?? context.filename;
const base = filename.split(/[\\/]/).pop() ?? '';
if (!base.endsWith('.ts')) return {};
const first = base.split('.')[0];
if (first === 'index' || first === 'types') return {};
const isCamel = /^[a-z][a-zA-Z0-9]*$/.test(first);
const isPascal = /^[A-Z][a-zA-Z0-9]*$/.test(first);
const isKebab = /^[a-z0-9]+(-[a-z0-9]+)*$/.test(first);
return {
Program(node) {
if (!isCamel && !isPascal && !isKebab) {
context.report({ node, messageId: 'invalidCase', data: { name: base } });
return;
}
if (!isCamel) return;
const concept = first.charAt(0).toUpperCase() + first.slice(1);
for (const statement of node.body) {
if (statement.type !== 'ExportNamedDeclaration' || !statement.declaration) continue;
const declaration = statement.declaration;
const declared =
declaration.type === 'VariableDeclaration'
? declaration.declarations.map((d) => d.id).filter((id) => id.type === 'Identifier')
: declaration.id
? [declaration.id]
: [];
if (declared.some((id) => id.name === concept)) {
context.report({ node: statement, messageId: 'conceptMismatch', data: { name: base, concept } });
return;
}
}
},
};
},
};
const localPlugin = { rules: { 'file-naming': fileNamingRule } };
const stagedObsidianRules = {
'obsidianmd/commands/no-command-in-command-id': obsidianRuleSeverity,
'obsidianmd/commands/no-command-in-command-name': obsidianRuleSeverity,
'obsidianmd/commands/no-default-hotkeys': obsidianRuleSeverity,
'obsidianmd/commands/no-plugin-id-in-command-id': obsidianRuleSeverity,
'obsidianmd/commands/no-plugin-name-in-command-name': obsidianRuleSeverity,
'obsidianmd/detach-leaves': obsidianRuleSeverity,
'obsidianmd/editor-drop-paste': obsidianRuleSeverity,
'obsidianmd/hardcoded-config-path': obsidianRuleSeverity,
'obsidianmd/no-forbidden-elements': obsidianRuleSeverity,
'obsidianmd/no-global-this': obsidianRuleSeverity,
'obsidianmd/no-plugin-as-component': obsidianRuleSeverity,
'obsidianmd/no-sample-code': obsidianRuleSeverity,
'obsidianmd/no-static-styles-assignment': obsidianRuleSeverity,
'obsidianmd/no-tfile-tfolder-cast': obsidianRuleSeverity,
'obsidianmd/no-unsupported-api': obsidianRuleSeverity,
'obsidianmd/no-view-references-in-plugin': obsidianRuleSeverity,
'obsidianmd/object-assign': obsidianRuleSeverity,
'obsidianmd/platform': obsidianRuleSeverity,
'obsidianmd/prefer-abstract-input-suggest': obsidianRuleSeverity,
'obsidianmd/prefer-active-doc': obsidianRuleSeverity,
'obsidianmd/prefer-file-manager-trash-file': obsidianRuleSeverity,
'obsidianmd/prefer-get-language': obsidianRuleSeverity,
'obsidianmd/prefer-instanceof': obsidianRuleSeverity,
'obsidianmd/prefer-window-timers': obsidianRuleSeverity,
'obsidianmd/regex-lookbehind': obsidianRuleSeverity,
'obsidianmd/sample-names': obsidianRuleSeverity,
'obsidianmd/settings-tab/no-deprecated-display': obsidianRuleSeverity,
'obsidianmd/settings-tab/no-manual-html-headings': obsidianRuleSeverity,
'obsidianmd/settings-tab/no-problematic-settings-headings': obsidianRuleSeverity,
'obsidianmd/ui/sentence-case': [
obsidianRuleSeverity,
{
ignoreWords: ['Claudian', 'Codex', 'OpenCode', 'Pi', 'WSL'],
brands: [...DEFAULT_BRANDS, 'Claudian', 'Codex', 'OpenCode', 'Pi'],
acronyms: [...DEFAULT_ACRONYMS, 'TOML', 'WSL'],
ignoreRegex: ['\\.(?:claude|codex|opencode)/'],
enforceCamelCaseLower: true,
},
],
'obsidianmd/vault/iterate': obsidianRuleSeverity,
};
const strictTypeAwareRules = {
'prefer-promise-reject-errors': 'off',
'@typescript-eslint/await-thenable': 'error',
'@typescript-eslint/no-deprecated': 'error',
'@typescript-eslint/no-duplicate-type-constituents': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/no-redundant-type-constituents': 'error',
'@typescript-eslint/no-unsafe-argument': 'error',
'@typescript-eslint/no-unsafe-assignment': 'error',
'@typescript-eslint/no-unsafe-call': 'error',
'@typescript-eslint/no-unsafe-member-access': 'error',
'@typescript-eslint/no-unsafe-return': 'error',
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
'@typescript-eslint/only-throw-error': 'error',
'@typescript-eslint/prefer-promise-reject-errors': 'error',
'@typescript-eslint/unbound-method': 'error',
};
export default defineConfig([
{
ignores: ['dist/**', 'node_modules/**', 'coverage/**', 'main.js'],
},
js.configs.recommended,
{
files: ['esbuild.config.mjs', 'scripts/**/*.js', 'scripts/**/*.mjs'],
languageOptions: {
globals: {
console: 'readonly',
module: 'readonly',
process: 'readonly',
},
},
},
...tseslint.configs['flat/recommended'],
{
files: ['src/**/*.ts', 'tests/**/*.ts'],
plugins: {
'simple-import-sort': simpleImportSort,
local: localPlugin,
},
rules: {
'local/file-naming': 'error',
'@typescript-eslint/consistent-type-imports': [
'error',
{ prefer: 'type-imports', fixStyle: 'separate-type-imports' },
],
'@typescript-eslint/no-unused-vars': [
'error',
{ args: 'none', ignoreRestSiblings: true },
],
'@typescript-eslint/no-explicit-any': 'off',
'prefer-promise-reject-errors': 'error',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
},
},
{
files: ['src/**/*.ts'],
languageOptions: {
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir,
},
},
plugins: {
'eslint-comments': eslintComments,
obsidianmd,
},
linterOptions: {
reportUnusedDisableDirectives: 'error',
reportUnusedInlineConfigs: 'error',
},
rules: {
...stagedObsidianRules,
...strictTypeAwareRules,
'eslint-comments/no-restricted-disable': [
'error',
'obsidianmd/*',
],
'eslint-comments/require-description': 'error',
'obsidianmd/prefer-create-el': 'error',
'@typescript-eslint/naming-convention': [
'error',
{ selector: 'default', format: ['camelCase'], leadingUnderscore: 'allow', trailingUnderscore: 'allow' },
{ selector: 'variable', format: ['camelCase', 'UPPER_CASE', 'PascalCase'], leadingUnderscore: 'allow' },
{ selector: 'typeLike', format: ['PascalCase'] },
{ selector: 'enumMember', format: ['PascalCase'] },
{ selector: 'classProperty', format: ['camelCase', 'UPPER_CASE'], leadingUnderscore: 'allow', trailingUnderscore: 'allow' },
{ selector: 'import', format: ['camelCase', 'PascalCase'] },
{ selector: 'objectLiteralProperty', format: null },
{ selector: 'typeProperty', format: null },
],
},
},
{
files: ['packages/collab-protocol/src/**/*.ts'],
languageOptions: {
parserOptions: {
project: './packages/collab-protocol/tsconfig.json',
tsconfigRootDir,
},
},
rules: {
...strictTypeAwareRules,
},
},
{
files: ['tests/**/*.ts'],
...jestRecommended,
rules: {
...jestRecommended.rules,
'@typescript-eslint/no-explicit-any': 'off',
},
},
]);