From 5e9477cd21a5e0a2a15b1d81f8f95d6922f723c7 Mon Sep 17 00:00:00 2001 From: imfozilbek Date: Sun, 23 Nov 2025 21:19:34 +0500 Subject: [PATCH] chore: configure code quality tools Add EditorConfig for consistent editor settings, Prettier for formatting (4-space indentation), ESLint with TypeScript rules, and linting documentation --- .editorconfig | 37 ++++++++ .prettierrc | 15 ++++ LINTING.md | 216 ++++++++++++++++++++++++++++++++++++++++++++++ eslint.config.mjs | 134 ++++++++++++++++++++++++++++ 4 files changed, 402 insertions(+) create mode 100644 .editorconfig create mode 100644 .prettierrc create mode 100644 LINTING.md create mode 100644 eslint.config.mjs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..21f25d4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,37 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +# TypeScript, JavaScript files +[*.{ts,tsx,js,jsx,mjs,cjs}] +indent_style = space +indent_size = 4 +max_line_length = 160 + +# JSON, YAML files +[*.{json,yaml,yml}] +indent_style = space +indent_size = 4 + +# Markdown files +[*.md] +trim_trailing_whitespace = false +max_line_length = off + +# Package.json +[package.json] +indent_style = space +indent_size = 4 + +# Shell scripts +[*.sh] +indent_style = space +indent_size = 4 diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..e766451 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,15 @@ +{ + "printWidth": 100, + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true, + "quoteProps": "as-needed", + "jsxSingleQuote": false, + "trailingComma": "all", + "bracketSpacing": true, + "bracketSameLine": false, + "arrowParens": "always", + "endOfLine": "lf", + "embeddedLanguageFormatting": "auto" +} diff --git a/LINTING.md b/LINTING.md new file mode 100644 index 0000000..c5ec75b --- /dev/null +++ b/LINTING.md @@ -0,0 +1,216 @@ +# Linting & Code Style Guide + +## Overview + +This project uses **ESLint** + **Prettier** with strict TypeScript rules and **4-space indentation**. + +## Quick Start + +```bash +# Format all code +pnpm format + +# Lint and auto-fix +pnpm lint + +# Check only (no fix) +pnpm eslint "packages/**/*.ts" +``` + +## Configuration Files + +- **`.prettierrc`** - Prettier code formatting (4 spaces, single quotes, etc.) +- **`eslint.config.mjs`** - ESLint rules (TypeScript strict mode + best practices) +- **`.editorconfig`** - Editor settings (consistent across IDEs) + +## Key Rules + +### TypeScript Strictness + +| Rule | Level | Description | +|------|-------|-------------| +| `@typescript-eslint/no-explicit-any` | warn | Avoid `any` type - use proper typing | +| `@typescript-eslint/explicit-function-return-type` | warn | Functions must declare return types | +| `@typescript-eslint/no-floating-promises` | error | Always handle promises | +| `@typescript-eslint/no-unsafe-*` | warn | Strict type safety checks | + +### Code Quality + +| Rule | Level | Description | +|------|-------|-------------| +| `no-console` | warn | Use logger instead (except `console.warn/error`) | +| `prefer-const` | error | Use `const` for non-reassigned variables | +| `eqeqeq` | error | Always use `===` instead of `==` | +| `curly` | error | Always use curly braces for if/for/while | +| `complexity` | warn | Max cyclomatic complexity: 15 | +| `max-params` | warn | Max function parameters: 5 | +| `max-lines-per-function` | warn | Max 100 lines per function | + +### Code Style + +- **Indentation:** 4 spaces (not tabs) +- **Line Length:** 100 characters max +- **Quotes:** Single quotes `'string'` +- **Semicolons:** Always required `;` +- **Trailing Commas:** Always in multiline +- **Arrow Functions:** Always use parentheses `(x) => x` + +## Handling Warnings + +### `any` Type Usage + +**Before:** +```typescript +function process(data: any) { + return data.value; +} +``` + +**After:** +```typescript +interface ProcessData { + value: string; +} + +function process(data: ProcessData): string { + return data.value; +} +``` + +### Missing Return Types + +**Before:** +```typescript +async function fetchData() { + return await api.get('/data'); +} +``` + +**After:** +```typescript +async function fetchData(): Promise { + return await api.get('/data'); +} +``` + +### Floating Promises + +**Before:** +```typescript +async function init() { + startServer(); // ❌ Promise not handled +} +``` + +**After:** +```typescript +async function init(): Promise { + await startServer(); // ✅ Promise awaited +} +``` + +## Ignoring Rules (Use Sparingly) + +```typescript +// Disable specific rule for next line +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const legacyData: any = getLegacyApi(); + +// Disable for entire file (add at top) +/* eslint-disable @typescript-eslint/no-explicit-any */ +``` + +**Note:** Only use `eslint-disable` for legacy code or when type safety is genuinely impossible. + +## Pre-commit Hooks (Optional) + +To automatically format on commit, install Husky: + +```bash +pnpm add -D husky lint-staged +npx husky init + +# Add to .husky/pre-commit: +pnpm lint-staged +``` + +Create `lint-staged` config in `package.json`: + +```json +{ + "lint-staged": { + "*.{ts,tsx}": [ + "prettier --write", + "eslint --fix" + ] + } +} +``` + +## IDE Setup + +### VS Code + +Install extensions: +- **ESLint** (`dbaeumer.vscode-eslint`) +- **Prettier** (`esbenp.prettier-vscode`) + +Add to `.vscode/settings.json`: + +```json +{ + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, + "eslint.validate": ["typescript", "javascript"] +} +``` + +### WebStorm / IntelliJ IDEA + +1. **Settings → Languages & Frameworks → JavaScript → Prettier** + - Enable "On save" + - Set "Run for files": `{**/*,*}.{ts,js,tsx,jsx}` + +2. **Settings → Languages & Frameworks → JavaScript → Code Quality Tools → ESLint** + - Enable "Automatic ESLint configuration" + - Enable "Run eslint --fix on save" + +## Common Issues + +### "Parsing error: ESLint was configured to run..." + +**Solution:** Make sure `tsconfig.json` exists in the project root. + +### "Rule 'indent' conflicts with Prettier" + +**Solution:** Already handled - ESLint `indent` rule is disabled in favor of Prettier. + +### Too many warnings after upgrade + +**Solution:** Run auto-fix first, then address remaining issues: + +```bash +pnpm lint +# Review warnings, fix manually +``` + +## Best Practices + +1. **Run `pnpm format` before committing** - ensures consistent formatting +2. **Fix warnings incrementally** - don't disable rules without reason +3. **Add types instead of `any`** - improves code quality and IDE support +4. **Use ESLint ignore comments sparingly** - only for edge cases + +## Resources + +- [TypeScript ESLint Rules](https://typescript-eslint.io/rules/) +- [Prettier Options](https://prettier.io/docs/en/options.html) +- [ESLint Rules](https://eslint.org/docs/latest/rules/) +- [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) + +--- + +**Last Updated:** 2025-01-19 diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..1e894c5 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,134 @@ +// @ts-check +import eslint from '@eslint/js'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { + ignores: [ + 'eslint.config.mjs', + '**/dist/**', + '**/node_modules/**', + '**/coverage/**', + '**/.puaros/**', + '**/build/**', + ], + }, + eslint.configs.recommended, + ...tseslint.configs.strictTypeChecked, + ...tseslint.configs.stylisticTypeChecked, + eslintPluginPrettierRecommended, + { + languageOptions: { + globals: { + ...globals.node, + ...globals.jest, + ...globals.es2021, + }, + sourceType: 'commonjs', + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + rules: { + // ======================================== + // TypeScript Best Practices + // ======================================== + '@typescript-eslint/no-explicit-any': 'warn', // Warn about 'any' usage + '@typescript-eslint/no-unsafe-assignment': 'warn', + '@typescript-eslint/no-unsafe-member-access': 'warn', + '@typescript-eslint/no-unsafe-call': 'warn', + '@typescript-eslint/no-unsafe-return': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + '@typescript-eslint/explicit-function-return-type': [ + 'warn', + { + allowExpressions: true, + allowTypedFunctionExpressions: true, + allowHigherOrderFunctions: true, + }, + ], + '@typescript-eslint/explicit-module-boundary-types': 'warn', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }, + ], + '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/await-thenable': 'error', + '@typescript-eslint/no-misused-promises': 'error', + '@typescript-eslint/prefer-nullish-coalescing': 'warn', + '@typescript-eslint/prefer-optional-chain': 'warn', + '@typescript-eslint/prefer-readonly': 'warn', + '@typescript-eslint/promise-function-async': 'warn', + '@typescript-eslint/require-await': 'warn', + '@typescript-eslint/no-unnecessary-condition': 'warn', + '@typescript-eslint/no-non-null-assertion': 'warn', + + // ======================================== + // Code Quality & Best Practices + // ======================================== + 'no-console': ['warn', { allow: ['warn', 'error'] }], + 'no-debugger': 'error', + 'no-alert': 'error', + 'no-var': 'error', + 'prefer-const': 'error', + 'prefer-arrow-callback': 'warn', + 'prefer-template': 'warn', + 'no-nested-ternary': 'warn', + 'no-unneeded-ternary': 'error', + 'no-else-return': 'warn', + eqeqeq: ['error', 'always'], + curly: ['error', 'all'], + 'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }], + 'no-trailing-spaces': 'error', + 'comma-dangle': ['error', 'always-multiline'], + + // ======================================== + // Code Style (handled by Prettier mostly) + // ======================================== + indent: ['error', 4, { SwitchCase: 1 }], + '@typescript-eslint/indent': 'off', // Let Prettier handle this + quotes: ['error', 'single', { avoidEscape: true }], + semi: ['error', 'always'], + + // ======================================== + // Prettier Integration + // ======================================== + 'prettier/prettier': [ + 'error', + { + tabWidth: 4, + endOfLine: 'auto', + }, + ], + + // ======================================== + // Complexity & Maintainability + // ======================================== + complexity: ['warn', 15], + 'max-depth': ['warn', 4], + 'max-lines-per-function': ['warn', { max: 100, skipBlankLines: true, skipComments: true }], + 'max-params': ['warn', 5], + + // ======================================== + // Imports & Dependencies + // ======================================== + 'no-duplicate-imports': 'error', + 'sort-imports': [ + 'warn', + { + ignoreCase: true, + ignoreDeclarationSort: true, + }, + ], + }, + }, +);