Skip to content

Commit

Permalink
Handle bigint and decimal types
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel7grant committed Nov 25, 2024
1 parent f8ee43a commit 1b1d1e6
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 21 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## [Unreleased]

### Changed

- **Breaking**: `bigint` and `decimal` are now parsed correctly according to the driver ([#5](https://github.com/daniel7grant/eslint-plugin-typeorm-typescript/issues/5#issuecomment-2452988205))
- There is new option `driver` for `enforce-column-types`: can be 'postgres', 'mysql' or 'sqlite'
- If the driver is empty (default) or set to MySQL and PostgreSQL, bigint and decimal are parsed to be strings
- If the driver is set to SQLite, bigint and decimal are parsed to be numbers
- For more information, see [#5](https://github.com/daniel7grant/eslint-plugin-typeorm-typescript/issues/5#issuecomment-2455779084)

## [0.4.1] - 2024-11-24

### Added
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,10 @@ but columns aren't), it makes it easy to make mistakes. These ESLint rules will

### typeorm-typescript/enforce-column-types

TypeORM data types and TypeScript types should be consistent. It includes the primitive types (e.g. `VARCHAR` -> `string`)
and the nullability. By default columns are non-nullable, but if the `nullable: true` option is set, it should be unioned
with `null` in the TypeScript types too.
TypeORM data types and TypeScript types should be consistent. It checks the primitive types (e.g. `VARCHAR` -> `string`)
and driver-specific types. By most drivers, `bigint` and `decimal` are parsed as string, except in SQLite (set the `driver`
option, if you use SQLite). This rule checks the nullability too: by default columns are non-nullable, but if the `nullable: true`
option is set, it should be unioned with `null` in the TypeScript types as well.

It also handle primary columns (`number` by default), create and update columns (`date` by default) and delete columns
(`date` and nullable by default).
Expand All @@ -88,7 +89,9 @@ It also handle primary columns (`number` by default), create and update columns
```json
{
"rules": {
"typeorm-typescript/enforce-column-types": "error"
"typeorm-typescript/enforce-column-types": "error",
// If you are using SQLite, set the driver
"typeorm-typescript/enforce-relation-types": ["error", { "driver": "sqlite" }],
}
}
```
Expand Down
116 changes: 116 additions & 0 deletions src/rules/enforce-column-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,36 @@ ruleTester.run('enforce-column-types', enforceColumnTypes, {
num: number;
}`,
},
{
name: 'should allow matching decimal column types',
code: `class Entity {
@Column({ type: 'decimal' })
decimal: string;
}`,
},
{
name: 'should allow matching bigint column types',
code: `class Entity {
@Column({ type: 'bigint' })
big: string;
}`,
},
{
name: 'should allow matching decimal column types in SQLite',
code: `class Entity {
@Column({ type: 'decimal' })
decimal: number;
}`,
options: [{ driver: 'sqlite' }],
},
{
name: 'should allow matching bigint column types in SQLite',
code: `class Entity {
@Column({ type: 'bigint' })
big: number;
}`,
options: [{ driver: 'sqlite' }],
},
{
name: 'should allow matching bool column types',
code: `class Entity {
Expand Down Expand Up @@ -366,6 +396,92 @@ ruleTester.run('enforce-column-types', enforceColumnTypes, {
},
],
},
{
name: 'should fail on non-string TypeScript type with decimal type',
code: `class Entity {
@Column({ type: 'decimal' })
num: number;
}`,
errors: [
{
messageId: 'typescript_typeorm_column_mismatch',
suggestions: [
{
messageId: 'typescript_typeorm_column_suggestion',
output: `class Entity {
@Column({ type: 'decimal' })
num: string;
}`,
},
],
},
],
},
{
name: 'should fail on non-string TypeScript type with bigint type',
code: `class Entity {
@Column({ type: 'bigint' })
num: number;
}`,
errors: [
{
messageId: 'typescript_typeorm_column_mismatch',
suggestions: [
{
messageId: 'typescript_typeorm_column_suggestion',
output: `class Entity {
@Column({ type: 'bigint' })
num: string;
}`,
},
],
},
],
},
{
name: 'should fail on non-number TypeScript type with decimal type in SQLite',
code: `class Entity {
@Column({ type: 'decimal' })
num: string;
}`,
options: [{ driver: 'sqlite' }],
errors: [
{
messageId: 'typescript_typeorm_column_mismatch',
suggestions: [
{
messageId: 'typescript_typeorm_column_suggestion',
output: `class Entity {
@Column({ type: 'decimal' })
num: number;
}`,
},
],
},
],
},
{
name: 'should fail on non-number TypeScript type with bigint type in SQLite',
code: `class Entity {
@Column({ type: 'bigint' })
num: string;
}`,
options: [{ driver: 'sqlite' }],
errors: [
{
messageId: 'typescript_typeorm_column_mismatch',
suggestions: [
{
messageId: 'typescript_typeorm_column_suggestion',
output: `class Entity {
@Column({ type: 'bigint' })
num: number;
}`,
},
],
},
],
},
{
name: 'should fail on non-date TypeScript type',
code: `class Entity {
Expand Down
36 changes: 32 additions & 4 deletions src/rules/enforce-column-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,20 @@ const createRule = ESLintUtils.RuleCreator(
`https://github.com/daniel7grant/eslint-plugin-typeorm-typescript#typeorm-typescript${name}`,
);

const enforceColumnTypes = createRule({
type EnforceColumnMessages =
| 'typescript_typeorm_column_mismatch'
| 'typescript_typeorm_column_mismatch_weird'
| 'typescript_typeorm_column_suggestion';

type EnforceColumnOptions = [
{
driver?: 'postgres' | 'mysql' | 'sqlite';
},
];

const enforceColumnTypes = createRule<EnforceColumnOptions, EnforceColumnMessages>({
name: 'enforce-column-types',
defaultOptions: [],
defaultOptions: [{}],
meta: {
type: 'problem',
docs: {
Expand All @@ -31,10 +42,23 @@ const enforceColumnTypes = createRule({
messages: {
typescript_typeorm_column_mismatch:
'Type of {{ propertyName }}{{ className }} is not matching the TypeORM column type{{ expectedValue }}.',
typescript_typeorm_column_mismatch_weird:
'Type of {{ propertyName }}{{ className }} should be string, as decimals and bigints are encoded as strings by PostgreSQL and MySQL drivers. If you are using SQLite, change the driver option to sqlite.',
typescript_typeorm_column_suggestion:
'Change the type of {{ propertyName }} to {{ expectedValue }}.',
},
schema: [],
schema: [
{
type: 'object',
properties: {
driver: {
type: 'string',
enum: ['postgres', 'mysql', 'sqlite'],
},
},
additionalProperties: false,
},
],
},
create(context) {
return {
Expand All @@ -52,7 +76,11 @@ const enforceColumnTypes = createRule({
return;
}
const [column, colArguments] = columnArguments;
const typeormType = convertArgumentToColumnType(column, colArguments);
const typeormType = convertArgumentToColumnType(
column,
colArguments,
context.options?.[0]?.driver,
);

const { typeAnnotation } = node.typeAnnotation;
let typescriptType: ColumnType;
Expand Down
14 changes: 7 additions & 7 deletions src/rules/enforce-relation-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,21 @@ const createRule = ESLintUtils.RuleCreator(
`https://github.com/daniel7grant/eslint-plugin-typeorm-typescript#typeorm-typescript${name}`,
);

type EnforceColumnMessages =
type EnforceRelationMessages =
| 'typescript_typeorm_relation_missing'
| 'typescript_typeorm_relation_mismatch'
| 'typescript_typeorm_relation_array_to_many'
| 'typescript_typeorm_relation_suggestion'
| 'typescript_typeorm_relation_nullable_by_default'
| 'typescript_typeorm_relation_nullable_by_default_suggestion'
| 'typescript_typeorm_relation_specify_relation_always';
type Options = [
type EnforceRelationOptions = [
{
specifyRelation?: 'always';
},
];

const enforceColumnTypes = createRule<Options, EnforceColumnMessages>({
const enforceRelationTypes = createRule<EnforceRelationOptions, EnforceRelationMessages>({
name: 'enforce-relation-types',
defaultOptions: [{}],
meta: {
Expand Down Expand Up @@ -111,8 +111,8 @@ const enforceColumnTypes = createRule<Options, EnforceColumnMessages>({
const typescriptType = convertTypeToRelationType(typeAnnotation);

if (!isTypesEqual(typeormType, typescriptType)) {
let messageId: EnforceColumnMessages = 'typescript_typeorm_relation_mismatch';
const suggestions: ReportSuggestionArray<EnforceColumnMessages> = [];
let messageId: EnforceRelationMessages = 'typescript_typeorm_relation_mismatch';
const suggestions: ReportSuggestionArray<EnforceRelationMessages> = [];
const fixReplace = typeToString(typeormType, typescriptType);

// Construct strings for error message
Expand Down Expand Up @@ -181,7 +181,7 @@ const enforceColumnTypes = createRule<Options, EnforceColumnMessages>({
});
const expectedValue = fixReplace ? ` (expected type: ${fixReplace})` : '';

const suggestions: ReportSuggestionArray<EnforceColumnMessages> = [];
const suggestions: ReportSuggestionArray<EnforceRelationMessages> = [];
if (fixReplace) {
suggestions.push({
messageId: 'typescript_typeorm_relation_suggestion',
Expand Down Expand Up @@ -210,4 +210,4 @@ const enforceColumnTypes = createRule<Options, EnforceColumnMessages>({
},
});

export default enforceColumnTypes;
export default enforceRelationTypes;
15 changes: 9 additions & 6 deletions src/utils/columnType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ interface ColumnParameter {
// From: https://github.com/typeorm/typeorm/blob/master/src/driver/types/ColumnTypes.ts
const booleanLike = ['boolean', 'bool'];
const numberLike = [
'bigint',
'dec',
'decimal',
'fixed',
'int',
'int2',
Expand All @@ -54,7 +51,6 @@ const numberLike = [
'smallint',
'tinyint',
'dec',
'decimal',
'double precision',
'double',
'fixed',
Expand All @@ -64,6 +60,9 @@ const numberLike = [
'real',
'smalldecimal',
];
// These are numbers that depend on the driver if parsed to a string (MySQL, PostgreSQL) or number (SQLite)
// @see https://typeorm.io/entities#column-types, https://github.com/daniel7grant/eslint-plugin-typeorm-typescript/issues/5
const weirdNumberLike = ['bigint', 'dec', 'decimal'];
const stringLike = [
'character varying',
'varying character',
Expand Down Expand Up @@ -107,13 +106,16 @@ const dateLike = [
'year',
];

function convertTypeOrmToColumnType(arg: string): ColumnTypeString {
function convertTypeOrmToColumnType(arg: string, driver?: string): ColumnTypeString {
if (booleanLike.includes(arg)) {
return 'boolean';
}
if (numberLike.includes(arg)) {
return 'number';
}
if (weirdNumberLike.includes(arg)) {
return driver === 'sqlite' ? 'number' : 'string';
}
if (stringLike.includes(arg)) {
return 'string';
}
Expand Down Expand Up @@ -143,6 +145,7 @@ export function getDefaultColumnTypeForDecorator(column: Column): ColumnParamete
export function convertArgumentToColumnType(
column: Column,
args: TSESTree.CallExpressionArgument[],
driver?: string,
): ColumnType {
const parsed = args.reduce((prev, arg) => {
switch (arg.type) {
Expand All @@ -160,7 +163,7 @@ export function convertArgumentToColumnType(
return {
columnType:
parsed.type && !parsed.transformer
? convertTypeOrmToColumnType(parsed.type)
? convertTypeOrmToColumnType(parsed.type, driver)
: 'unknown',
nullable: parsed.nullable ?? false,
literal: false,
Expand Down

0 comments on commit 1b1d1e6

Please sign in to comment.