-
-
Notifications
You must be signed in to change notification settings - Fork 791
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1514 from Angelelz/feat-eslint-plugin
Feat: Added new eslint plugin
- Loading branch information
Showing
19 changed files
with
1,067 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# eslint-plugin-drizzle 0.1.0 | ||
|
||
- Initial release | ||
- 2 rules available |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules | ||
dist | ||
tsconfig.tsbuildinfo |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
{ | ||
"name": "eslint-plugin-drizzle", | ||
"version": "0.2.0", | ||
"description": "Eslint plugin for drizzle users to avoid common pitfalls", | ||
"main": "src/index.js", | ||
"scripts": { | ||
"test": "vitest run", | ||
"build": "tsc -b && pnpm cpy readme.md dist/", | ||
"pack": "(cd dist && npm pack --pack-destination ..) && rm -f package.tgz && mv *.tgz package.tgz", | ||
"publish": "npm publish package.tgz" | ||
}, | ||
"keywords": [ | ||
"eslint", | ||
"eslintplugin", | ||
"eslint-plugin", | ||
"drizzle" | ||
], | ||
"author": "Angelelz", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/drizzle-team/drizzle-orm.git" | ||
}, | ||
"license": "Apache-2.0", | ||
"devDependencies": { | ||
"@typescript-eslint/parser": "^6.10.0", | ||
"@typescript-eslint/rule-tester": "^6.10.0", | ||
"@typescript-eslint/utils": "^6.10.0", | ||
"cpy-cli": "^5.0.0", | ||
"eslint": "^8.53.0", | ||
"typescript": "^5.2.2", | ||
"vitest": "^0.34.6" | ||
}, | ||
"peerDependencies": { | ||
"eslint": ">=8.0.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# eslint-plugin-drizzle | ||
|
||
eslint plugin for drizzle users to avoid common pitfalls | ||
|
||
## Install | ||
|
||
```sh | ||
[ npm | yarn | pnpm | bun ] install eslint eslint-plugin-drizzle | ||
``` | ||
|
||
## Usage | ||
|
||
Use a [preset config](#preset-configs) or configure each rule in `package.json` or `eslint.config.js`. | ||
|
||
If you don't use the preset, ensure you use the same `env` and `parserOptions` config as below. | ||
|
||
```json | ||
{ | ||
"name": "my-awesome-project", | ||
"eslintConfig": { | ||
"env": { | ||
"es2024": true | ||
}, | ||
"parserOptions": { | ||
"ecmaVersion": "latest", | ||
"sourceType": "module" | ||
}, | ||
"plugins": ["drizzle"], | ||
"rules": { | ||
"drizzle/enforce-delete-with-where": "error", | ||
"drizzle/enforce-update-with-where": "error" | ||
} | ||
} | ||
} | ||
``` | ||
|
||
## Rules | ||
|
||
**enforce-delete-with-where**: Enforce using `delete` with `where` in `DELETE` statement | ||
Optionally, you can defined a `drizzleObjectName` in the plugin options that accepts a string or an array of strings. | ||
This is useful when you have object or classes with a delete method that's not from drizzle. Such delete method will trigger the eslint rule. | ||
To avoid that, you can define the name of the drizzle object that you use in your codebase (like `db`) so that the rule would only trigger if the delete method comes from this object: | ||
```json | ||
"rules": { | ||
"drizzle/enforce-delete-with-where": ["error", { "drizzleObjectName": ["db", "dataSource", "database"] }], | ||
} | ||
``` | ||
|
||
**enforce-update-with-where**: Enforce using `update` with `where` in `UPDATE` statement | ||
Similar to the delete rule, you can define the name of the drizzle object that you use in your codebase (like `db`) so that the rule would only trigger if the update method comes from this object: | ||
```json | ||
"rules": { | ||
"drizzle/enforce-update-with-where": ["error", { "drizzleObjectName": "db" }], | ||
} | ||
``` | ||
|
||
## Preset configs | ||
|
||
### Recommended config | ||
|
||
This plugin exports a [`recommended` config](src/configs/recommended.js) that enforces good practices. | ||
|
||
```json | ||
{ | ||
"name": "my-awesome-project", | ||
"eslintConfig": { | ||
"extends": "plugin:drizzle/recommended" | ||
} | ||
} | ||
``` | ||
|
||
### All config | ||
|
||
This plugin exports an [`all` config](src/configs/all.js) that makes use of all rules (except for deprecated ones). | ||
|
||
```json | ||
{ | ||
"name": "my-awesome-project", | ||
"eslintConfig": { | ||
"extends": "plugin:drizzle/all" | ||
} | ||
} | ||
``` | ||
|
||
At the moment, `all` is equivalent to `recommended`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
export default { | ||
env: { | ||
es2024: true, | ||
}, | ||
parserOptions: { | ||
ecmaVersion: 'latest', | ||
sourceType: 'module', | ||
}, | ||
plugins: ['drizzle'], | ||
rules: { | ||
'drizzle/enforce-delete-with-where': 'error', | ||
'drizzle/enforce-update-with-where': 'error', | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
export default { | ||
env: { | ||
es2024: true, | ||
}, | ||
parserOptions: { | ||
ecmaVersion: 'latest', | ||
sourceType: 'module', | ||
}, | ||
plugins: ['drizzle'], | ||
rules: { | ||
'drizzle/enforce-delete-with-where': 'error', | ||
'drizzle/enforce-update-with-where': 'error', | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { ESLintUtils } from '@typescript-eslint/utils'; | ||
import { isDrizzleObj, type Options } from './utils/options'; | ||
|
||
const createRule = ESLintUtils.RuleCreator(() => 'https://github.com/drizzle-team/eslint-plugin-drizzle'); | ||
|
||
type MessageIds = 'enforceDeleteWithWhere'; | ||
|
||
let lastNodeName: string = ''; | ||
|
||
const deleteRule = createRule<Options, MessageIds>({ | ||
defaultOptions: [{ drizzleObjectName: [] }], | ||
name: 'enforce-delete-with-where', | ||
meta: { | ||
type: 'problem', | ||
docs: { | ||
description: 'Enforce that `delete` method is used with `where` to avoid deleting all the rows in a table.', | ||
}, | ||
fixable: 'code', | ||
messages: { | ||
enforceDeleteWithWhere: 'Avoid deleting all the rows in a table. Use `db.delete(...).where(...)` instead.', | ||
}, | ||
schema: [{ | ||
type: 'object', | ||
properties: { | ||
drizzleObjectName: { | ||
type: ['string', 'array'], | ||
}, | ||
}, | ||
additionalProperties: false, | ||
}], | ||
}, | ||
create(context, options) { | ||
return { | ||
MemberExpression: (node) => { | ||
if (node.property.type === 'Identifier') { | ||
if (isDrizzleObj(node, options) && node.property.name === 'delete' && lastNodeName !== 'where') { | ||
context.report({ | ||
node, | ||
messageId: 'enforceDeleteWithWhere', | ||
}); | ||
} | ||
lastNodeName = node.property.name; | ||
} | ||
return; | ||
}, | ||
}; | ||
}, | ||
}); | ||
|
||
export default deleteRule; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { ESLintUtils } from '@typescript-eslint/utils'; | ||
import { isDrizzleObj, type Options } from './utils/options'; | ||
|
||
const createRule = ESLintUtils.RuleCreator(() => 'https://github.com/drizzle-team/eslint-plugin-drizzle'); | ||
type MessageIds = 'enforceUpdateWithWhere'; | ||
|
||
let lastNodeName: string = ''; | ||
|
||
const updateRule = createRule<Options, MessageIds>({ | ||
defaultOptions: [{ drizzleObjectName: [] }], | ||
name: 'enforce-update-with-where', | ||
meta: { | ||
type: 'problem', | ||
docs: { | ||
description: 'Enforce that `update` method is used with `where` to avoid deleting all the rows in a table.', | ||
}, | ||
fixable: 'code', | ||
messages: { | ||
enforceUpdateWithWhere: | ||
'Avoid updating all the rows in a table. Use `db.update(...).set(...).where(...)` instead.', | ||
}, | ||
schema: [{ | ||
type: 'object', | ||
properties: { | ||
drizzleObjectName: { | ||
type: ['string', 'array'], | ||
}, | ||
}, | ||
additionalProperties: false, | ||
}], | ||
}, | ||
create(context, options) { | ||
return { | ||
MemberExpression: (node) => { | ||
if (node.property.type === 'Identifier') { | ||
if ( | ||
lastNodeName !== 'where' | ||
&& node.property.name === 'set' | ||
&& node.object.type === 'CallExpression' | ||
&& node.object.callee.type === 'MemberExpression' | ||
&& node.object.callee.property.type === 'Identifier' | ||
&& node.object.callee.property.name === 'update' | ||
&& isDrizzleObj(node.object.callee, options) | ||
) { | ||
context.report({ | ||
node, | ||
messageId: 'enforceUpdateWithWhere', | ||
}); | ||
} | ||
lastNodeName = node.property.name; | ||
} | ||
return; | ||
}, | ||
}; | ||
}, | ||
}); | ||
|
||
export default updateRule; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import type { TSESLint } from '@typescript-eslint/utils'; | ||
import { name, version } from '../package.json'; | ||
import all from './configs/all'; | ||
import recommended from './configs/recommended'; | ||
import deleteRule from './enforce-delete-with-where'; | ||
import updateRule from './enforce-update-with-where'; | ||
import type { Options } from './utils/options'; | ||
|
||
export const rules = { | ||
'enforce-delete-with-where': deleteRule, | ||
'enforce-update-with-where': updateRule, | ||
} satisfies Record<string, TSESLint.RuleModule<string, Options>>; | ||
|
||
export const configs = { all, recommended }; | ||
|
||
export const meta = { name, version }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import type { TSESTree } from '@typescript-eslint/utils'; | ||
|
||
export type Options = readonly [{ | ||
drizzleObjectName: string[] | string; | ||
}]; | ||
|
||
export const isDrizzleObj = ( | ||
node: TSESTree.MemberExpression, | ||
options: Options, | ||
) => { | ||
const drizzleObjectName = options[0].drizzleObjectName; | ||
|
||
if ( | ||
node.object.type === 'Identifier' && typeof drizzleObjectName === 'string' | ||
&& node.object.name === drizzleObjectName | ||
) { | ||
return true; | ||
} | ||
|
||
if (Array.isArray(drizzleObjectName)) { | ||
if (drizzleObjectName.length === 0) { | ||
return true; | ||
} | ||
|
||
if (node.object.type === 'Identifier' && drizzleObjectName.includes(node.object.name)) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
}; |
Oops, something went wrong.