Skip to content

Commit

Permalink
Merge pull request #93 from getlarge/feat-create-eslint-rules-library
Browse files Browse the repository at this point in the history
feat: create ESLint rules library
  • Loading branch information
getlarge authored Sep 13, 2024
2 parents fa83d8a + 858bbe7 commit 17c0a4b
Show file tree
Hide file tree
Showing 21 changed files with 1,579 additions and 152 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This repository contains several helpful packages for NestJS that I have develop
| [AMQP-Transport](./packages/amqp-transport/README.md) | A NestJS microservice adapter around [AMQPlib] supporting exchanges. | [![npm](https://img.shields.io/npm/v/@getlarge/nestjs-tools-amqp-transport.svg?style=flat)](https://npmjs.org/package/@getlarge/nestjs-tools-amqp-transport) |
| [Async-Local-Storage](./packages/async-local-storage/README.md) | A NestJS module to track context with [AsyncLocalStorage]. | [![npm](https://img.shields.io/npm/v/@getlarge/nestjs-tools-async-local-storage?style=flat)](https://npmjs.org/package/@getlarge/nestjs-tools-async-local-storage) |
| [Cluster](./packages/cluster/README.md) | A class to manage workers' lifecycle in a (Node.js) [cluster]. | [![npm](https://img.shields.io/npm/v/@getlarge/nestjs-tools-cluster?style=flat)](https://npmjs.org/package/@getlarge/nestjs-tools-cluster) |
| [ESLint-Rules](./packages/eslint-rules/README.md) | A set of ESLint rules for NestJS applications. | [![npm](https://img.shields.io/npm/v/@getlarge/nestjs-tools-eslint-rules?style=flat)](https://npmjs.org/package/@getlarge/nestjs-tools-eslint-rules) |
| [File-Storage](./packages/file-storage/README.md) | A NestJS module supporting [FS], [S3] and [GCP] strategies. | [![npm](https://img.shields.io/npm/v/@getlarge/nestjs-tools-file-storage?style=flat)](https://npmjs.org/package/@getlarge/nestjs-tools-file-storage) |
| [Lock](./packages/lock/README.md) | A NestJS module to provide a distributed lock for your application. | [![npm](https://img.shields.io/npm/v/@getlarge/nestjs-tools-lock?style=flat)](https://npmjs.org/package/@getlarge/nestjs-tools-lock) |
| [Fastify-Upload](./packages/fastify-upload/README.md) | A NestJS module to provide file upload support for Fastify. | [![npm](https://img.shields.io/npm/v/@getlarge/nestjs-tools-fastify-upload?style=flat)](https://npmjs.org/package/@getlarge/nestjs-tools-fastify-upload) |
Expand Down
1,230 changes: 1,085 additions & 145 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
"husky": "^9.0.11",
"ioredis-mock": "^8.9.0",
"jest": "29.7.0",
"jest-environment-node": "^29.4.1",
"jest-environment-node": "^29.7.0",
"lint-staged": "^15.0.3",
"mqtt": "^5.5.2",
"nx": "19.7.3",
Expand All @@ -89,6 +89,7 @@
"@nestjs/core": "^10.4.1",
"@nestjs/microservices": "^10.4.1",
"@nestjs/platform-fastify": "^10.4.1",
"@typescript-eslint/utils": "^8.5.0",
"amqp-connection-manager": "^4.1.14",
"amqplib": "^0.10.4",
"ioredis": "^5.3.2",
Expand Down
6 changes: 6 additions & 0 deletions packages/amqp-transport/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ A custom AMQP strategy for Nest microservice transport, which extends NestJS AMQ

By enabling exchanges you can broadcast messages to multiple queues, and by asserting reply queue you can ensure that reply queue with static name exists.

## Installation

```bash
npm install --save @getlarge/nestjs-tools-amqp-transport
```

## Example

The integration tests contain an [example consumer](./test/dummy-consumer.controller.mock.ts) and [example producer](./test/dummy-producer.service.mock.ts) that demonstrate how to use this library.
2 changes: 1 addition & 1 deletion packages/async-local-storage/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
## Installation

```bash
$ npm install --save @getlarge/nestjs-tools-async-local-storage
npm install --save @getlarge/nestjs-tools-async-local-storage
```

## Usage
Expand Down
2 changes: 1 addition & 1 deletion packages/cluster/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The `ClusterService` class is a wrapper around the native `cluster` module and p
## Installation

```bash
$ npm install --save @getlarge/nestjs-tools-cluster
npm install --save @getlarge/nestjs-tools-cluster
```

## Example
Expand Down
33 changes: 33 additions & 0 deletions packages/eslint-rules/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": [
"error",
{
"buildTargets": ["build"],
"checkMissingDependencies": true,
"checkObsoleteDependencies": true,
"checkVersionMismatches": true
}
]
}
}
]
}
29 changes: 29 additions & 0 deletions packages/eslint-rules/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# eslint-rules

[![npm][npm-image]][npm-url]

[npm-image]: https://img.shields.io/npm/v/@getlarge/nestjs-tools-eslint-rules.svg?style=flat
[npm-url]: https://npmjs.org/package/@getlarge/nestjs-tools-eslint-rules

This set of ESLint rules is provided to enforce a consistent patterns and practices across all NestJS projects.

## Installation

```bash
npm install --save @getlarge/nestjs-tools-eslint-rules
```

## Usage

### return-class-instance

This rule enforces that all public Service methods return a class instance of the same return type.
The purpose of this rule is to ensure you return class instances instead of plain objects, which is essentials when using the [`ClassSerializerInterceptor`](https://docs.nestjs.com/techniques/serialization#class-serializer-interceptor).

```json
{
"rules": {
"@getlarge/nestjs-tools-eslint-rules/return-class-instance": "error"
}
}
```
11 changes: 11 additions & 0 deletions packages/eslint-rules/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* eslint-disable */
export default {
displayName: 'eslint-rules',
preset: '../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/packages/eslint-rules',
};
26 changes: 26 additions & 0 deletions packages/eslint-rules/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@getlarge/nestjs-tools-eslint-rules",
"version": "0.0.0",
"description": "ESLint rules for NestJS applications",
"keywords": [
"eslint",
"nestjs",
"rules",
"typescript",
"lint",
"style"
],
"license": "Apache-2.0",
"author": "Edouard Maleix <[email protected]>",
"homepage": "https://github.com/getlarge/nestjs-tools/tree/main/packages/eslint-rules",
"publishConfig": {
"access": "public"
},
"dependencies": {},
"peerDependencies": {
"eslint": "7 || 8 || 9"
},
"type": "commonjs",
"main": "./src/index.js",
"typings": "./src/index.d.ts"
}
9 changes: 9 additions & 0 deletions packages/eslint-rules/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "nestjs-tools-eslint-rules",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/eslint-rules/src",
"projectType": "library",
"tags": [],
"// targets": "to see all targets run: nx show project eslint-rules --web",
"targets": {}
}
5 changes: 5 additions & 0 deletions packages/eslint-rules/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { rule as returnClassInstance, RULE_NAME as returnClassInstanceName } from './lib/return-class-instance';

module.exports = {
rules: { [returnClassInstanceName]: returnClassInstance },
};
162 changes: 162 additions & 0 deletions packages/eslint-rules/src/lib/return-class-instance.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { TSESLint } from '@typescript-eslint/utils';

import { rule, RULE_NAME } from './return-class-instance';

const ruleTester = new TSESLint.RuleTester({
parser: require.resolve('@typescript-eslint/parser'),
});

ruleTester.run(RULE_NAME, rule, {
valid: [
// basic test case
`import { Injectable } from '@nestjs/common';
class Test {
constructor(params) {
Object.assign(this, params);
}
message: string;
}
@Injectable()
export class AppService {
getData(): Test {
return new Test({ message: 'Hello API' });
}
}`,
// promise test case
`import { Injectable } from '@nestjs/common';
class Test {
constructor(params) {
Object.assign(this, params);
}
message: string;
}
@Injectable()
export class AppService {
getData(): Promise<Test> {
return Promise.resolve(new Test({ message: 'Hello API' }));
}
}`,
// constructor are not checked
`import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
constructor() {}
}`,
// check when return statement is coming from another service method
`import { Injectable } from '@nestjs/common';
class Test {
constructor(params) {
Object.assign(this, params);
}
message: string;
}
@Injectable()
export class InternalService {
getData(): Test {
return new Test({ message: 'Hello API' });
}
}
@Injectable()
export class AppService {
constructor(private readonly internalService: InternalService = new InternalService()) {}
getData(): Test {
return this.internalService.getData();
}
}`,
// lifecycle methods are ignored
`import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
onModuleInit() {
console.log('Module initialized.');
}
}`,
// getter return type
`import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
get data() {
return { message: 'Hello API' };
}
}`,
// primitive return type
`import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getData(): string {
return 'Hello API';
}
}`,
// primitive union return type
`import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getData(): 'OK' | 'ERROR' {
return 'OK';
}
}`,
// union with null return type
`import { Injectable } from '@nestjs/common';
class Test {
constructor(params) {
Object.assign(this, params);
}
message: string;
}
@Injectable()
export class AppService {
getData(doIt: boolean): Test | null {
if (doIt) {
return new Test({ message: 'Hello API' });
}
return null;
}
}`,
// TODO: class instance is modified before being returned
// `import { Injectable } from '@nestjs/common';
// class Test {
// constructor(params) {
// Object.assign(this, params);
// }
// message: string;
// }
// @Injectable()
// export class AppService {
// getData(): Test {
// const test = new Test({ message: 'Hello API' });
// test.message = 'Hello API';
// return test;
// }
// }`,
],
invalid: [
{
errors: [{ messageId: 'missingReturnType' }],
code: `
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getData() {
return { message: 'Hello API' };
}
}`,
},
{
errors: [{ messageId: 'returnClassInstance' }],
code: `
import { Injectable } from '@nestjs/common';
class Test {
constructor(params) {
Object.assign(this, params);
}
message: string;
}
@Injectable()
export class AppService {
getData(): Test {
return { message: 'Hello API' };
}
}`,
},
],
});
Loading

0 comments on commit 17c0a4b

Please sign in to comment.