355 lines
6.8 KiB
Markdown
355 lines
6.8 KiB
Markdown
# @coze-arch/eslint-plugin
|
|
|
|
A comprehensive ESLint plugin designed for Flow applications, providing essential linting rules for code quality, import management, and Zustand state management best practices.
|
|
|
|
## Features
|
|
|
|
### Core Rules
|
|
- **Import Management**: Prevent deep relative imports and batch import/export issues
|
|
- **Code Quality**: Enforce function length limits, proper error handling, and catch block usage
|
|
- **Package Management**: Validate package.json structure and dependencies
|
|
- **React/TSX**: Prevent leaked renders and other React-specific issues
|
|
|
|
### Zustand Rules
|
|
- **State Management**: Enforce proper state mutation patterns and store conventions
|
|
- **Performance**: Optimize selector usage and prevent unnecessary re-renders
|
|
- **Best Practices**: Enforce naming conventions and proper store typing
|
|
|
|
### Processors
|
|
- **JSON Processor**: Custom processor for linting package.json files
|
|
|
|
## Get Started
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
# Install the package
|
|
rush update
|
|
|
|
# Or using pnpm in workspace
|
|
pnpm add @coze-arch/eslint-plugin@workspace:*
|
|
```
|
|
|
|
### Basic Usage
|
|
|
|
Add the plugin to your ESLint configuration:
|
|
|
|
```js
|
|
// eslint.config.js
|
|
import flowPlugin from '@coze-arch/eslint-plugin';
|
|
|
|
export default [
|
|
{
|
|
plugins: {
|
|
'@coze-arch': flowPlugin,
|
|
},
|
|
rules: {
|
|
'@coze-arch/no-deep-relative-import': ['error', { max: 4 }],
|
|
'@coze-arch/max-line-per-function': ['error', { max: 150 }],
|
|
'@coze-arch/tsx-no-leaked-render': 'warn',
|
|
},
|
|
},
|
|
];
|
|
```
|
|
|
|
### Using Recommended Configuration
|
|
|
|
```js
|
|
// eslint.config.js
|
|
import flowPlugin from '@coze-arch/eslint-plugin';
|
|
|
|
export default [
|
|
...flowPlugin.configs.recommended,
|
|
];
|
|
```
|
|
|
|
### Zustand Rules
|
|
|
|
```js
|
|
// eslint.config.js
|
|
import zustandPlugin from '@coze-arch/eslint-plugin/zustand';
|
|
|
|
export default [
|
|
{
|
|
plugins: {
|
|
'@coze-arch/zustand': zustandPlugin,
|
|
},
|
|
...zustandPlugin.configs.recommended,
|
|
},
|
|
];
|
|
```
|
|
|
|
## API Reference
|
|
|
|
### Core Rules
|
|
|
|
#### `no-deep-relative-import`
|
|
Prevents excessive relative import nesting.
|
|
|
|
```js
|
|
// ❌ Bad (default max: 3)
|
|
import something from '../../../deep/path';
|
|
|
|
// ✅ Good
|
|
import something from '../../shallow/path';
|
|
```
|
|
|
|
**Options:**
|
|
- `max` (number): Maximum allowed relative path depth (default: 3)
|
|
|
|
#### `max-line-per-function`
|
|
Enforces maximum lines per function.
|
|
|
|
```js
|
|
// ❌ Bad (exceeds limit)
|
|
function longFunction() {
|
|
// ... 200 lines of code
|
|
}
|
|
|
|
// ✅ Good
|
|
function shortFunction() {
|
|
// ... less than 150 lines
|
|
}
|
|
```
|
|
|
|
**Options:**
|
|
- `max` (number): Maximum lines per function (default: 150)
|
|
|
|
#### `tsx-no-leaked-render`
|
|
Prevents leaked renders in TSX components.
|
|
|
|
```js
|
|
// ❌ Bad
|
|
{count && <Component />} // count could be 0
|
|
|
|
// ✅ Good
|
|
{count > 0 && <Component />}
|
|
{Boolean(count) && <Component />}
|
|
```
|
|
|
|
#### `no-pkg-dir-import`
|
|
Prevents importing from package directories.
|
|
|
|
```js
|
|
// ❌ Bad
|
|
import something from 'package/src/internal';
|
|
|
|
// ✅ Good
|
|
import something from 'package';
|
|
```
|
|
|
|
#### `use-error-in-catch`
|
|
Enforces proper error handling in catch blocks.
|
|
|
|
```js
|
|
// ❌ Bad
|
|
try {
|
|
doSomething();
|
|
} catch (e) {
|
|
console.log('error occurred');
|
|
}
|
|
|
|
// ✅ Good
|
|
try {
|
|
doSomething();
|
|
} catch (error) {
|
|
console.error('error occurred:', error);
|
|
}
|
|
```
|
|
|
|
#### `no-empty-catch`
|
|
Prevents empty catch blocks.
|
|
|
|
```js
|
|
// ❌ Bad
|
|
try {
|
|
doSomething();
|
|
} catch (error) {
|
|
// empty
|
|
}
|
|
|
|
// ✅ Good
|
|
try {
|
|
doSomething();
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
```
|
|
|
|
#### `no-new-error`
|
|
Discourages creating new Error instances.
|
|
|
|
```js
|
|
// ❌ Bad
|
|
throw new Error('Something went wrong');
|
|
|
|
// ✅ Good (when configured)
|
|
throw createError('Something went wrong');
|
|
```
|
|
|
|
### Zustand Rules
|
|
|
|
#### `no-state-mutation`
|
|
Prevents direct state mutation in Zustand stores.
|
|
|
|
```js
|
|
// ❌ Bad
|
|
const state = useStore.getState();
|
|
state.count = 5;
|
|
|
|
// ✅ Good
|
|
useStore.setState({ count: 5 });
|
|
```
|
|
|
|
#### `prefer-selector`
|
|
Encourages using selectors for state access.
|
|
|
|
```js
|
|
// ❌ Bad
|
|
const { count, name } = useStore();
|
|
|
|
// ✅ Good
|
|
const count = useStore(state => state.count);
|
|
const name = useStore(state => state.name);
|
|
```
|
|
|
|
#### `store-name-convention`
|
|
Enforces naming conventions for stores.
|
|
|
|
```js
|
|
// ❌ Bad
|
|
const myStore = create(() => ({}));
|
|
|
|
// ✅ Good
|
|
const useMyStore = create(() => ({}));
|
|
```
|
|
|
|
#### `prefer-shallow`
|
|
Encourages using shallow equality for object selections.
|
|
|
|
```js
|
|
// ❌ Bad
|
|
const { user, settings } = useStore(state => ({
|
|
user: state.user,
|
|
settings: state.settings
|
|
}));
|
|
|
|
// ✅ Good
|
|
const { user, settings } = useStore(
|
|
state => ({ user: state.user, settings: state.settings }),
|
|
shallow
|
|
);
|
|
```
|
|
|
|
### Package.json Rules
|
|
|
|
#### `package-require-author`
|
|
Ensures package.json has an author field.
|
|
|
|
```json
|
|
{
|
|
"name": "my-package",
|
|
"author": "developer@example.com"
|
|
}
|
|
```
|
|
|
|
#### `package-disallow-deps`
|
|
Prevents usage of disallowed dependencies (configurable).
|
|
|
|
## Development
|
|
|
|
### Setup
|
|
|
|
```bash
|
|
# Install dependencies
|
|
rush update
|
|
|
|
# Run tests
|
|
rushx test
|
|
|
|
# Run with coverage
|
|
rushx test:cov
|
|
|
|
# Lint code
|
|
rushx lint
|
|
|
|
# Build (no-op for this package)
|
|
rushx build
|
|
```
|
|
|
|
### Project Structure
|
|
|
|
```
|
|
src/
|
|
├── index.ts # Main plugin entry
|
|
├── processors/
|
|
│ └── json.ts # JSON processor for package.json
|
|
├── rules/ # Core ESLint rules
|
|
│ ├── no-deep-relative-import/
|
|
│ ├── max-lines-per-function/
|
|
│ ├── tsx-no-leaked-render/
|
|
│ └── ...
|
|
└── zustand/ # Zustand-specific rules
|
|
├── index.ts # Zustand plugin entry
|
|
└── rules/
|
|
├── no-state-mutation/
|
|
├── prefer-selector/
|
|
└── ...
|
|
```
|
|
|
|
### Adding New Rules
|
|
|
|
1. Create a new directory under `src/rules/` or `src/zustand/rules/`
|
|
2. Implement the rule in `index.ts`
|
|
3. Add comprehensive tests in `index.test.ts`
|
|
4. Export the rule in the main plugin file
|
|
5. Add the rule to recommended configuration if appropriate
|
|
|
|
### Testing
|
|
|
|
Tests are written using ESLint's `RuleTester`:
|
|
|
|
```ts
|
|
import { RuleTester } from 'eslint';
|
|
import { myRule } from './index';
|
|
|
|
const ruleTester = new RuleTester();
|
|
|
|
ruleTester.run('my-rule', myRule, {
|
|
valid: [
|
|
// Valid code examples
|
|
],
|
|
invalid: [
|
|
// Invalid code examples with expected errors
|
|
],
|
|
});
|
|
```
|
|
|
|
## Dependencies
|
|
|
|
### Runtime Dependencies
|
|
- `@typescript-eslint/utils` - TypeScript ESLint utilities
|
|
- `eslint-module-utils` - ESLint module resolution utilities
|
|
- `eslint-rule-composer` - Rule composition utilities
|
|
- `eslint-traverse` - AST traversal utilities
|
|
- `eslint-utils` - General ESLint utilities
|
|
- `semver` - Semantic versioning utilities
|
|
|
|
### Development Dependencies
|
|
- `@typescript-eslint/rule-tester` - Rule testing utilities
|
|
- `vitest` - Test runner
|
|
- `eslint` - ESLint core
|
|
- TypeScript and various ESLint plugins for development
|
|
|
|
## License
|
|
|
|
Apache-2.0 License
|
|
|
|
## Author
|
|
|
|
fanwenjie.fe@bytedance.com
|
|
|
|
---
|
|
|
|
For more information about ESLint plugin development, see the [ESLint Plugin Developer Guide](https://eslint.org/docs/developer-guide/).
|