Giter Site home page Giter Site logo

eslint-plugin's Introduction

Trilon ESLint Plugin

Apache-2.0 license


Trilon.io - Angular Universal, NestJS, JavaScript Application Consulting Development and Training

Made with ❤️ by Trilon.io


Node.js CI

At Trilon, our goal is to help elevate teams - giving them the push they need to continuously succeed in today's ever-changing tech world.

As part of that, we focus on developing tools that make your dev experience easier, enjoyable, and safer.

The official Trilon ESLint Plugin is part of that toolbelt to help your team to thrive, applying best practices for NestJS, curated by our key contributors and core team.

Installation

npm install @trilon/eslint-plugin

And add these the plugin to your .eslintrc:

{
  plugins: ['@trilon/eslint-plugin'],
  extends: ['plugin:@trilon/recommended'],
}

The "recommended" preset contains the rules listed below. If you need custom configuration, please refer to the documentation of the individual linting rules.

Rules

Rule Description Type
@trilon/enforce-close-testing-module Ensures NestJS testing modules are closed properly after tests Recommended ✅
@trilon/check-inject-decorator Detects incorrect usage of @Inject(TOKEN) decorator Recommended ✅
@trilon/detect-circular-reference Detects usage of forwardRef() method Recommended ✅
@trilon/enforce-custom-provider-type Enforces a styleguide for provider types Strict ⚠️

Trilon Consulting

JavaScript, Node, NestJS Consulting from Open-Source Fanatics and Key Contributors!

Check out Trilon.io for more info!

Contact us at [email protected], and let's talk about your projects needs.



Trilon.io - Angular Universal, NestJS, JavaScript Application Consulting Development and Training

Made with ❤️ by Trilon.io

eslint-plugin's People

Contributors

thiagomini avatar tuxmachine avatar tolgap avatar

Stargazers

Igor Makowski avatar Daniel Marczydło avatar Hien Nguyen Minh avatar Alireza Naghdipour avatar Ilya Moroz avatar  avatar Davide Gheri avatar  avatar  avatar 曾明健 avatar

Watchers

 avatar  avatar Mark Pieszak avatar Guilherme Luchesi avatar Henrique Weiand avatar Ilya Moroz avatar Camila Nery avatar Mirsad Halilcevic avatar  avatar

eslint-plugin's Issues

Feat: Enforce custom provider types

Description

We want to allow users to prefer one type of custom provider over the others. For instance, factory providers over value providers can significantly increase performance.

Acceptance Criteria

Given the options below:

// ...
rules: {
  '@trilon/enforce-custom-provider-type': [ 
   'warn', {  
     prefer: 'factory'
   }
  ]
}

Then, the following code will be considered Invalid:

const customValueProvider: Provider = {
  provide: 'TOKEN',
  useValue: 'some-value' // ⚠️ provider is not of type "factory"
}

const customClassProvider: Provider = {
  provide: AbstractClass,
  useClass: SomeClass  // ⚠️ provider is not of type "factory"
}

And the following code is considered Valid:

const factoryProvider: Provider = {
  provide: 'TOKEN',
  useFactory: () => 'some-value'
}

Feat: Add custom method name to enforce-close-testing-module

Description

The enforce-close-testing-module rule doesn't support custom method names that could replace the testingModule.close() and Test.createTestingModule() calls. We need to add that option so users can create utility methods that are proxies for those calls.

Acceptance Criteria

The user can define an option with the enforce-close-testing-module rule so that ESLint recognizes custom functions:

// ...
rules: {
  '@trilon/enforce-close-testing-module': [ 
   'warn', {  
     closeAliases: [ 
       { kind: 'function', name: 'close' } 
     ],
     createAliases: [ 
       { kind: 'function', name: 'setupTest' } 
     ]  
   }
  ]
}

The configuration above defines:

  • close() as a custom function that should be interpreted the same as testingModule.close()
  • setupTest() as a custom function that should be interpreted the same as Test.createTestingModule()

Feat: warn about usage of `forwardRef`

Description

Circular dependencies are a clear sign of code that should be refactored because e.g. a class has grown too big or separation of concerns is violated.

Acceptance Criteria

Invalid code:

import { forwardRef } from '@nestjs/common';

@Injectable()
export class CatsService {
  constructor(
    @Inject(forwardRef(() => CommonService)) // ⚠️ Circular-dependency detected
    private commonService: CommonService,
  ) {}
}


@Module({
  imports: [forwardRef(() => CatsModule)], // ⚠️ Circular-dependency detected
})
export class CommonModule {}

Would we also be able to detect this attempt to dodge detection? 🤔

import { forwardRef as silentForwardRef } from '@nestjs/common';

Feat: Create custom ESLint Rule to avoid lingering modules

Description

We need a Rule that enforces closing a TestingModule. When we create a module in tests and don't close it we potentially leak memory and/or database connections.

Acceptance Criteria

The ESLint rule should warn when:

  • A Test.createTestingModule() is executed in the beforeEach() hook, but then the module isn’t closed in the afterEach()
  • A Test.createTestingModule() is executed in the beforeAll() hook, but isn’t closed in the afterAll() one.
  • A Test.createTestingModule() is executed within a test, or it block, but isn't closed.

The ESLint rule should consider valid:

  • When a Test.createTestingModule() is executed in the beforeEach() and the module is then closed in the afterEach() hook
  • When a Test.createTestingModule() is executed in the beforeAll() and the module is then closed in the afterAll() hook
  • When a Test.createTestingModule() is executed, followed by a module.createNestApp(), and then the application is closed in the correct hook

Feat: Add lint rule to enforce modules to have `register/registerAsync` and/or `forRoot/forRootAsync`

Description

We should detect whether module classes that return a DynamicModule are named register, registerAsync, forRoot or forRootAsync.

Acceptance Criteria

Incorrect:

@Module({})
export class ProductsModule {
  static create(options): DynamicModule { // ⚠️ `Dynamic module methods must be named `register/registerAsync` or `forRoot/forRootAsync`
    return {
      module: ProductsModule,
      imports: [TypeOrmModule.forFeature([Product]),
      providers: [/* ... */],
    }
  }

  static registerAsync(options): DynamicModule { // ✅ 
    return {
      module: ProductsModule,
      imports: [TypeOrmModule.forFeature([Product]),
      providers: [/* ... */],
    }
  }
}

Correct:

@Module({})
export class ProductsModule {
  static register(options): DynamicModule { // ✅ 
    return {
      module: ProductsModule,
      imports: [TypeOrmModule.forFeature([Product]),
      providers: [/* ... */],
    }
  }

  static registerAsync(options): DynamicModule { // ✅ 
    return {
      module: ProductsModule,
      imports: [TypeOrmModule.forFeature([Product]),
      providers: [/* ... */],
    }
  }
}

Define bundler tool

Description

We should define which bundler tool we want to use to publish this package. A few things to take into consideration:

  • How do we configure that bundler to map the tsconfig.paths so we can use path aliases?
  • Is it possible to bundle/compile our code for both CJS and ESM? (Example: pkgroll)

Acceptance Criteria

A bundler and its configuration are created so that the requirements above are answered/met.

ESLint couldn't find the plugin "@trilon/eslint-plugin".

ESLint: 8.56.0

ESLint couldn't find the plugin "@trilon/eslint-plugin".

(The package "@trilon/eslint-plugin" was not found when loaded as a Node module from the directory "/Users/igormakowski/Documents/repositories/rigtch/rigtch-music-api".)

It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:

    npm install @trilon/eslint-plugin@latest --save-dev

The plugin "@trilon/eslint-plugin" was referenced from the config file in ".eslintrc.cjs".

If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.

.eslintrc.cjs

{
  plugins: [
    '@typescript-eslint',
    '@trilon/eslint-plugin',
    'import',
    'nestjs',
    'eslint-plugin-import-helpers',
    'prettier',
  ],
  extends: [
    'plugin:@typescript-eslint/strict-type-checked',
    'plugin:@typescript-eslint/stylistic-type-checked',
    'plugin:@trilon/recommended',
    'plugin:sonarjs/recommended',
    'plugin:unicorn/recommended',
    'plugin:nestjs/recommended',
    'plugin:eslint-comments/recommended',
    'prettier',
  ],
}

package.json

{
  "devDependencies": {
    "@trilon/eslint-plugin": "^0.2.0",
  }
}

Create documentation for encorce-close-testing-module

Description

We need to create the documentation for our first rule #1. It has to present the valid and invalid codes, along with any additional options.

Acceptance Criteria

A public page / link describing the rule following the template below:

---
description: '<Description from rule metadata here>'
---

> 🛑 This file is source code, not the primary documentation location! 🛑
>
> See **https://typescript-eslint.io/rules/RULE_NAME_REPLACEME** for documentation.

## Examples

To fill out: tell us more about this rule.

<!--tabs-->

### ❌ Incorrect

```ts
// To fill out: incorrect code

✅ Correct

// To fill out: correct code

When Not To Use It

To fill out: why wouldn't you want to use this rule?
For example if this rule requires a feature released in a certain TS version.

Feat: create styleguide rule for providers

Description

Large teams can have the desire to limit or enforce a particular style of creating providers; e.g. banning request-scoped providers.

Acceptance Criteria

tbd needs more refinement

Create rule for correct usage of @Inject

Description

We should create a rule for code that detects incorrect usage of @Inject(TOKEN)

Acceptance criteria

The following code would be considered invalid:

class FooBarController {
	private readonly fooService: FooService; // ⚠️ Did you want to `@Inject(FooService)`?

	constructor(
		// 🚫 Type is an interface and cannot be injected
		private readonly barService: IService<Bar>,

		@Inject(BazService) // ⚠️ Token duplicates type
		private readonly bazService: BazService,
	) {}
}

The following code would be considered valid:

class FooBarController {
	@Inject(FooService) // ✅ Token duplicates type, but class properties don't have type metadata
	private readonly fooService: FooService;

	constructor(
		@Inject('BAR_SERVICE') // ✅ Token differs from type
		private readonly barService: IService<Bar>,

		// ✅ Type metadata is sufficient to inject
		private readonly bazService: BazService,
	) {}
}

Feat: Add lint rule to enforce module naming

Description

A rule to ensure that any class decorated with @Module() should have their class name end with Module.

Acceptance criteria

Incorrect:

@Module({
  imports: [TypeOrmModule.forFeature([Product]),
})
export class Products {} // ⚠️ `Module classes should have a name that ends with the word `Module`

Correct:

@Module({
  imports: [TypeOrmModule.forFeature([Product]),
})
export class ProductsModule {}

Refactor: Create a matcher utility to facilitate rule creation

Description

Manipulating and querying ASTs is inherently a complex problem. Often, we need to perform consecutive checks for children's properties, including their type and value. This leads to repetitive, not-so-readable code. Here's an example of such code:

if (ASTUtils.isVariableDeclarator(parent)) {
    const init = parent.init;
    let type: ProviderType | undefined;
    if (init?.type === AST_NODE_TYPES.ObjectExpression) {
      const properties = init.properties;
      for (const property of properties) {
        if (property.type === AST_NODE_TYPES.Property) {
          type = providerTypeOfProperty(property);
        }
      }
    }

    return type;
  }

And here's how this could be replaced with TS-Pattern

  return match(node)
    .with(
      {
        parent: {
          init: {
            type: AST_NODE_TYPES.ObjectExpression,
            properties: P.select(),
          },
          type: P.when(ASTUtils.isVariableDeclarator),
        },
      },
      (properties) => {
        for (const property of properties) {
          if (property.type === AST_NODE_TYPES.Property) {
            const type = providerTypeOfProperty(property);
            if (type) {
              return type;
            }
          }
        }
      }
    )
    .otherwise(() => undefined);

This second approach is more declarative and makes it easier to grasp the expected node structure. Moreover, we can select parts of the matched node with the P.select() function and make use of it in the following callback function.

Acceptance Criteria

Initially, it would suffice to implement a match(node) function that accepts two additional methods:

with(structure, callback | value) - matches with given object structure, and returns either the value or the callback's returned value
when(function, callback | value) - matches with the given matching function, and returns either the value or the callback's returned value

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.