Skip to content

Commit

Permalink
Merge pull request #1586 from ngregrichardson/fix-eslint-callables
Browse files Browse the repository at this point in the history
[ESLint] Add support for functions and improve error messages
  • Loading branch information
AndriiSherman authored Dec 13, 2023
2 parents ac1dd05 + 1268722 commit 3efe430
Show file tree
Hide file tree
Showing 11 changed files with 438 additions and 71 deletions.
5 changes: 5 additions & 0 deletions changelogs/eslint-plugin-drizzle/0.2.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# eslint-plugin-drizzle 0.2.3

- Added better context to the suggestion in the error message
- fix: Correct detection of `drizzleObjectName` when it's retrieved from or is a function
- chore: Refactored duplicate code in `utils/options.ts` into `isDrizzleObjName` function
3 changes: 2 additions & 1 deletion eslint-plugin-drizzle/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-drizzle",
"version": "0.2.2",
"version": "0.2.3",
"description": "Eslint plugin for drizzle users to avoid common pitfalls",
"main": "src/index.js",
"scripts": {
Expand All @@ -22,6 +22,7 @@
},
"license": "Apache-2.0",
"devDependencies": {
"@types/node": "^20.10.1",
"@typescript-eslint/parser": "^6.10.0",
"@typescript-eslint/rule-tester": "^6.10.0",
"@typescript-eslint/utils": "^6.10.0",
Expand Down
8 changes: 8 additions & 0 deletions eslint-plugin-drizzle/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ For cases where it's impossible to perform type checks for specific scenarios, o
```sh
[ npm | yarn | pnpm | bun ] install eslint eslint-plugin-drizzle
```

You can install those packages for typescript support in your IDE

```sh
[ npm | yarn | pnpm | bun ] install @typescript-eslint/eslint-plugin @typescript-eslint/parser
```
Expand Down Expand Up @@ -65,6 +67,7 @@ plugins:
Optionally, you can define a `drizzleObjectName` in the plugin options that accept a `string` or `string[]`. This is useful when you have objects or classes with a delete method that's not from Drizzle. Such a `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:

Example, config 1:

```json
"rules": {
"drizzle/enforce-delete-with-where": ["error"]
Expand All @@ -89,11 +92,13 @@ db.delete()
```

Example, config 2:

```json
"rules": {
"drizzle/enforce-delete-with-where": ["error", { "drizzleObjectName": ["db"] }],
}
```

```ts
class MyClass {
public delete() {
Expand All @@ -116,6 +121,7 @@ db.delete()
Optionally, you can define a `drizzleObjectName` in the plugin options that accept a `string` or `string[]`. This is useful when you have objects or classes with a delete method that's not from Drizzle. Such as `update` 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:

Example, config 1:

```json
"rules": {
"drizzle/enforce-update-with-where": ["error"]
Expand All @@ -140,11 +146,13 @@ db.update()
```

Example, config 2:

```json
"rules": {
"drizzle/enforce-update-with-where": ["error", { "drizzleObjectName": ["db"] }],
}
```

```ts
class MyClass {
public update() {
Expand Down
6 changes: 5 additions & 1 deletion eslint-plugin-drizzle/src/enforce-delete-with-where.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ESLintUtils } from '@typescript-eslint/utils';
import { resolveMemberExpressionPath } from './utils/ast';
import { isDrizzleObj, type Options } from './utils/options';

const createRule = ESLintUtils.RuleCreator(() => 'https://github.com/drizzle-team/eslint-plugin-drizzle');
Expand All @@ -18,7 +19,7 @@ const deleteRule = createRule<Options, MessageIds>({
fixable: 'code',
messages: {
enforceDeleteWithWhere:
"Without `.where(...)` you will delete all the rows in a table. If you didn't want to do it, please use `db.delete(...).where(...)` instead. Otherwise you can ignore this rule here",
"Without `.where(...)` you will delete all the rows in a table. If you didn't want to do it, please use `{{ drizzleObjName }}.delete(...).where(...)` instead. Otherwise you can ignore this rule here",
},
schema: [{
type: 'object',
Expand All @@ -38,6 +39,9 @@ const deleteRule = createRule<Options, MessageIds>({
context.report({
node,
messageId: 'enforceDeleteWithWhere',
data: {
drizzleObjName: resolveMemberExpressionPath(node),
},
});
}
lastNodeName = node.property.name;
Expand Down
6 changes: 5 additions & 1 deletion eslint-plugin-drizzle/src/enforce-update-with-where.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ESLintUtils } from '@typescript-eslint/utils';
import { resolveMemberExpressionPath } from './utils/ast';
import { isDrizzleObj, type Options } from './utils/options';

const createRule = ESLintUtils.RuleCreator(() => 'https://github.com/drizzle-team/eslint-plugin-drizzle');
Expand All @@ -17,7 +18,7 @@ const updateRule = createRule<Options, MessageIds>({
fixable: 'code',
messages: {
enforceUpdateWithWhere:
'Without `.where(...)` you will update all the rows in a table. If you didn\'t want to do it, please use `db.update(...).set(...).where(...)` instead. Otherwise you can ignore this rule here'
"Without `.where(...)` you will update all the rows in a table. If you didn't want to do it, please use `{{ drizzleObjName }}.update(...).set(...).where(...)` instead. Otherwise you can ignore this rule here",
},
schema: [{
type: 'object',
Expand Down Expand Up @@ -45,6 +46,9 @@ const updateRule = createRule<Options, MessageIds>({
context.report({
node,
messageId: 'enforceUpdateWithWhere',
data: {
drizzleObjName: resolveMemberExpressionPath(node.object.callee),
},
});
}
lastNodeName = node.property.name;
Expand Down
38 changes: 38 additions & 0 deletions eslint-plugin-drizzle/src/utils/ast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { TSESTree } from '@typescript-eslint/utils';

export const resolveMemberExpressionPath = (node: TSESTree.MemberExpression) => {
let objectExpression = node.object;
let fullName = '';

const addToFullName = (name: string) => {
const prefix = fullName ? '.' : '';
fullName = `${name}${prefix}${fullName}`;
};

while (objectExpression) {
if (objectExpression.type === 'MemberExpression') {
if (objectExpression.property.type === 'Identifier') {
addToFullName(objectExpression.property.name);
}
objectExpression = objectExpression.object;
} else if (objectExpression.type === 'CallExpression' && objectExpression.callee.type === 'Identifier') {
addToFullName(`${objectExpression.callee.name}(...)`);
break;
} else if (objectExpression.type === 'CallExpression' && objectExpression.callee.type === 'MemberExpression') {
if (objectExpression.callee.property.type === 'Identifier') {
addToFullName(`${objectExpression.callee.property.name}(...)`);
}
objectExpression = objectExpression.callee.object;
} else if (objectExpression.type === 'Identifier') {
addToFullName(objectExpression.name);
break;
} else if (objectExpression.type === 'ThisExpression') {
addToFullName('this');
break;
} else {
break;
}
}

return fullName;
};
54 changes: 23 additions & 31 deletions eslint-plugin-drizzle/src/utils/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,37 @@ export type Options = readonly [{
drizzleObjectName: string[] | string;
}];

const isDrizzleObjName = (name: string, drizzleObjectName: string[] | string) => {
if (typeof drizzleObjectName === 'string') {
return name === drizzleObjectName;
}

if (Array.isArray(drizzleObjectName)) {
if (drizzleObjectName.length === 0) {
return true;
}

return drizzleObjectName.includes(name);
}

return false;
};

export const isDrizzleObj = (
node: TSESTree.MemberExpression,
options: Options,
) => {
const drizzleObjectName = options[0].drizzleObjectName;

if (node.object.type === 'Identifier') {
if (
typeof drizzleObjectName === 'string'
&& node.object.name === drizzleObjectName
) {
return true;
}

if (Array.isArray(drizzleObjectName)) {
if (drizzleObjectName.length === 0) {
return true;
}

if (drizzleObjectName.includes(node.object.name)) {
return true;
}
}
return isDrizzleObjName(node.object.name, drizzleObjectName);
} else if (node.object.type === 'MemberExpression' && node.object.property.type === 'Identifier') {
if (
typeof drizzleObjectName === 'string'
&& node.object.property.name === drizzleObjectName
) {
return true;
}

if (Array.isArray(drizzleObjectName)) {
if (drizzleObjectName.length === 0) {
return true;
}

if (drizzleObjectName.includes(node.object.property.name)) {
return true;
}
return isDrizzleObjName(node.object.property.name, drizzleObjectName);
} else if (node.object.type === 'CallExpression') {
if (node.object.callee.type === 'Identifier') {
return isDrizzleObjName(node.object.callee.name, drizzleObjectName);
} else if (node.object.callee.type === 'MemberExpression' && node.object.callee.property.type === 'Identifier') {
return isDrizzleObjName(node.object.callee.property.name, drizzleObjectName);
}
}

Expand Down
Loading

0 comments on commit 3efe430

Please sign in to comment.