Skip to content

Commit

Permalink
Merge pull request #1514 from Angelelz/feat-eslint-plugin
Browse files Browse the repository at this point in the history
Feat: Added new eslint plugin
  • Loading branch information
AndriiSherman authored Nov 28, 2023
2 parents 44e2467 + 1edfc39 commit 89a2300
Show file tree
Hide file tree
Showing 19 changed files with 1,067 additions and 16 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/release-feature-branch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
- drizzle-zod
- drizzle-typebox
- drizzle-valibot
- eslint-plugin-drizzle
runs-on: ubuntu-20.04
permissions:
contents: read
Expand Down Expand Up @@ -91,8 +92,8 @@ jobs:
is_version_published="$(npm view ${{ matrix.package }} versions --json | jq -r '.[] | select(. == "'$version'") | . == "'$version'"')"
if [[ "$is_version_published" == "true" ]]; then
echo "\`${{ matrix.package }}@$version\` already published, adding tag \`$tag\`" >> $GITHUB_STEP_SUMMARY
npm dist-tag add ${{ matrix.package }}@$version $tag
echo "\`${{ matrix.package }}@ $version\` already published, adding tag \`$tag\`" >> $GITHUB_STEP_SUMMARY
npm dist-tag add ${{ matrix.package }}@ $version $tag
else
{
echo "version=$version"
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/release-latest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ jobs:
- drizzle-zod
- drizzle-typebox
- drizzle-valibot
- eslint-plugin-drizzle
runs-on: ubuntu-20.04
services:
postgres:
Expand Down Expand Up @@ -82,8 +83,8 @@ jobs:
is_version_published="$(npm view ${{ matrix.package }} versions --json | jq -r '.[] | select(. == "'$version'") | . == "'$version'"')"
if [[ "$is_version_published" == "true" ]]; then
echo "\`${{ matrix.package }}@$version\` already published, adding tag \`latest\`" >> $GITHUB_STEP_SUMMARY
npm dist-tag add ${{ matrix.package }}@$version latest
echo "\`${{ matrix.package }}@ $version\` already published, adding tag \`latest\`" >> $GITHUB_STEP_SUMMARY
npm dist-tag add ${{ matrix.package }}@ $version latest
elif [[ "$latest" != "$version" ]]; then
echo "Latest: $latest"
echo "Current: $version"
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/unpublish-release-feature-branch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ jobs:
- drizzle-zod
- drizzle-typebox
- drizzle-valibot
- eslint-plugin-drizzle
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
Expand Down
4 changes: 4 additions & 0 deletions changelogs/eslint-plugin-drizzle/0.2.0.md
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
3 changes: 3 additions & 0 deletions eslint-plugin-drizzle/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
dist
tsconfig.tsbuildinfo
36 changes: 36 additions & 0 deletions eslint-plugin-drizzle/package.json
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"
}
}
85 changes: 85 additions & 0 deletions eslint-plugin-drizzle/readme.md
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`.
14 changes: 14 additions & 0 deletions eslint-plugin-drizzle/src/configs/all.ts
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',
},
};
14 changes: 14 additions & 0 deletions eslint-plugin-drizzle/src/configs/recommended.ts
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',
},
};
50 changes: 50 additions & 0 deletions eslint-plugin-drizzle/src/enforce-delete-with-where.ts
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;
58 changes: 58 additions & 0 deletions eslint-plugin-drizzle/src/enforce-update-with-where.ts
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;
16 changes: 16 additions & 0 deletions eslint-plugin-drizzle/src/index.ts
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 };
31 changes: 31 additions & 0 deletions eslint-plugin-drizzle/src/utils/options.ts
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;
};
Loading

0 comments on commit 89a2300

Please sign in to comment.