Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle bigint and decimal types #19

Merged
merged 1 commit into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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