Skip to content

Commit

Permalink
Merge from drizzle-team/beta
Browse files Browse the repository at this point in the history
Beta
  • Loading branch information
Angelelz authored Dec 20, 2023
2 parents d53698f + a4d758d commit 2431a1c
Show file tree
Hide file tree
Showing 19 changed files with 459 additions and 78 deletions.
Binary file added .DS_Store
Binary file not shown.
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
18 changes: 15 additions & 3 deletions drizzle-orm/src/aws-data-api/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,28 @@ export function toValueParam(value: any, typings?: QueryTypingsValue): { value:
if (value === null) {
response.value = { isNull: true };
} else if (typeof value === 'string') {
response.value = response.typeHint === 'DATE'
? { stringValue: value.split('T')[0]! }
: { stringValue: value };
switch (response.typeHint) {
case TypeHint.DATE: {
response.value = { stringValue: value.split('T')[0]! };
break;
}
case TypeHint.TIMESTAMP: {
response.value = { stringValue: value.replace('T', ' ').replace('Z', '') };
break;
}
default: {
response.value = { stringValue: value };
break;
}
}
} else if (typeof value === 'number' && Number.isInteger(value)) {
response.value = { longValue: value };
} else if (typeof value === 'number' && !Number.isInteger(value)) {
response.value = { doubleValue: value };
} else if (typeof value === 'boolean') {
response.value = { booleanValue: value };
} else if (value instanceof Date) { // eslint-disable-line no-instanceof/no-instanceof
// TODO: check if this clause is needed? Seems like date value always comes as string
response.value = { stringValue: value.toISOString().replace('T', ' ').replace('Z', '') };
} else {
throw new Error(`Unknown type for ${value}`);
Expand Down
2 changes: 2 additions & 0 deletions drizzle-orm/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ export type ValueOrArray<T> = T | T[];
export function applyMixins(baseClass: any, extendedClasses: any[]) {
for (const extendedClass of extendedClasses) {
for (const name of Object.getOwnPropertyNames(extendedClass.prototype)) {
if (name === 'constructor') continue;

Object.defineProperty(
baseClass.prototype,
name,
Expand Down
2 changes: 1 addition & 1 deletion drizzle-typebox/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"test:types": "cd tests && tsc",
"pack": "(cd dist && npm pack --pack-destination ..) && rm -f package.tgz && mv *.tgz package.tgz",
"publish": "npm publish package.tgz",
"test": "ava tests"
"test": "NODE_OPTIONS='--loader=tsx --no-warnings' ava"
},
"exports": {
".": {
Expand Down
2 changes: 1 addition & 1 deletion drizzle-valibot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"test:types": "cd tests && tsc",
"pack": "(cd dist && npm pack --pack-destination ..) && rm -f package.tgz && mv *.tgz package.tgz",
"publish": "npm publish package.tgz",
"test": "ava tests"
"test": "NODE_OPTIONS='--loader=tsx --no-warnings' ava"
},
"exports": {
".": {
Expand Down
2 changes: 1 addition & 1 deletion drizzle-zod/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"test:types": "cd tests && tsc",
"pack": "(cd dist && npm pack --pack-destination ..) && rm -f package.tgz && mv *.tgz package.tgz",
"publish": "npm publish package.tgz",
"test": "ava tests"
"test": "NODE_OPTIONS='--loader=tsx --no-warnings' ava"
},
"exports": {
".": {
Expand Down
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 2431a1c

Please sign in to comment.