From 728efedc1c4bc584103f2f8048ca1b3e69611ba9 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Mon, 5 Feb 2024 09:38:52 -0300 Subject: [PATCH 01/22] feat: create enforce custom provider type rule --- .../enforce-custom-provider-type.rule.ts | 52 +++++++++++++++++++ .../enforce-custom-provider-type.rule.spec.ts | 37 +++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 src/rules/enforce-custom-provider-type.rule.ts create mode 100644 tests/rules/enforce-custom-provider-type.rule.spec.ts diff --git a/src/rules/enforce-custom-provider-type.rule.ts b/src/rules/enforce-custom-provider-type.rule.ts new file mode 100644 index 0000000..b094e86 --- /dev/null +++ b/src/rules/enforce-custom-provider-type.rule.ts @@ -0,0 +1,52 @@ +import { ESLintUtils } from '@typescript-eslint/utils'; + +const createRule = ESLintUtils.RuleCreator( + (name) => `https://eslint.org/docs/latest/rules/${name}` +); + +type ProviderType = 'class' | 'factory' | 'value'; + +export type Options = [ + { + prefer: ProviderType; + }, +]; + +const defaultOptions: Options = [ + { + prefer: 'factory', + }, +]; + +export type MessageIds = 'providerTypeMismatch'; + +export default createRule({ + name: 'enforce-custom-provider-type', + meta: { + type: 'suggestion', + docs: { + description: 'Ensure that custom providers are of the preferred type', + }, + fixable: undefined, + schema: [ + { + type: 'object', + properties: { + prefer: { + type: 'string', + enum: ['class', 'factory', 'value'], + }, + }, + }, + ], + messages: { + providerTypeMismatch: 'Provider is not of type {{ preferred }}', + }, + }, + defaultOptions, + create(context) { + return { + 'Program:exit': (node) => {}, + }; + }, +}); diff --git a/tests/rules/enforce-custom-provider-type.rule.spec.ts b/tests/rules/enforce-custom-provider-type.rule.spec.ts new file mode 100644 index 0000000..109ae70 --- /dev/null +++ b/tests/rules/enforce-custom-provider-type.rule.spec.ts @@ -0,0 +1,37 @@ +import { RuleTester } from '@typescript-eslint/rule-tester'; +import enforceCustomProviderTypeRule from '../../src/rules/enforce-custom-provider-type.rule'; + +// This test required changes to the tsconfig file to allow importing from the rule-tester package. +// See https://github.com/typescript-eslint/typescript-eslint/issues/7284 + +const ruleTester = new RuleTester({ + parserOptions: { + project: './tsconfig.json', + }, + parser: '@typescript-eslint/parser', + defaultFilenames: { + // We need to specify a filename that will be used by the rule parser. + // Since the test process starts at the root of the project, we need to point to the sub folder containing it. + ts: './tests/rules/file.ts', + tsx: '', + }, +}); + +ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { + valid: [ + { + code: ` + const factoryProvider: Provider = { + provide: 'TOKEN', + useFactory: () => 'some-value' + } + `, + options: [ + { + prefer: 'factory', + }, + ], + }, + ], + invalid: [], +}); From 061b85b69e73900c212af06afa74d64a74a936e6 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Mon, 5 Feb 2024 10:04:05 -0300 Subject: [PATCH 02/22] feat: ensure the provider type is factory --- .../enforce-custom-provider-type.rule.ts | 72 ++++++++++++++++++- .../enforce-custom-provider-type.rule.spec.ts | 22 +++++- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/src/rules/enforce-custom-provider-type.rule.ts b/src/rules/enforce-custom-provider-type.rule.ts index b094e86..3c68019 100644 --- a/src/rules/enforce-custom-provider-type.rule.ts +++ b/src/rules/enforce-custom-provider-type.rule.ts @@ -1,4 +1,9 @@ -import { ESLintUtils } from '@typescript-eslint/utils'; +import { + ASTUtils, + AST_NODE_TYPES, + ESLintUtils, + type TSESTree, +} from '@typescript-eslint/utils'; const createRule = ESLintUtils.RuleCreator( (name) => `https://eslint.org/docs/latest/rules/${name}` @@ -45,8 +50,71 @@ export default createRule({ }, defaultOptions, create(context) { + const options = context.options[0] || defaultOptions[0]; + const preferredType = options.prefer; return { - 'Program:exit': (node) => {}, + 'Identifier[typeAnnotation.typeAnnotation.type="TSTypeReference"]': ( + node: TSESTree.Identifier + ) => { + const typeName = ( + node.typeAnnotation?.typeAnnotation as TSESTree.TSTypeReference + ).typeName; + + if (ASTUtils.isIdentifier(typeName) && typeName.name === 'Provider') { + const providerType = getProviderType(node); + if (providerType && providerType !== preferredType) { + context.report({ + node, + messageId: 'providerTypeMismatch', + data: { + preferred: preferredType, + }, + }); + } + } + }, }; }, }); + +function getProviderType(node: TSESTree.Identifier): ProviderType | undefined { + const parent = node.parent; + + 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 && + ASTUtils.isIdentifier(property.key) && + property.key.name === 'useFactory' + ) { + type = 'factory'; + break; + } + + if ( + property.type === AST_NODE_TYPES.Property && + ASTUtils.isIdentifier(property.key) && + property.key.name === 'useClass' + ) { + type = 'class'; + break; + } + + if ( + property.type === AST_NODE_TYPES.Property && + ASTUtils.isIdentifier(property.key) && + property.key.name === 'useValue' + ) { + type = 'value'; + break; + } + } + } + + return type; + } +} diff --git a/tests/rules/enforce-custom-provider-type.rule.spec.ts b/tests/rules/enforce-custom-provider-type.rule.spec.ts index 109ae70..9e1b27b 100644 --- a/tests/rules/enforce-custom-provider-type.rule.spec.ts +++ b/tests/rules/enforce-custom-provider-type.rule.spec.ts @@ -33,5 +33,25 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { ], }, ], - invalid: [], + invalid: [ + { + code: ` + import { Provider } from '@nestjs/common'; + const customValueProvider: Provider = { + provide: 'TOKEN', + useValue: 'some-value' // ⚠️ provider is not of type "factory" + } + `, + options: [ + { + prefer: 'factory', + }, + ], + errors: [ + { + messageId: 'providerTypeMismatch', + }, + ], + }, + ], }); From 81f53036194d0fa498ef753e4f2558b135ef43b0 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Fri, 9 Feb 2024 09:48:32 -0300 Subject: [PATCH 03/22] test: provider type is class or useExisting --- .../enforce-custom-provider-type.rule.ts | 11 ++++- .../enforce-custom-provider-type.rule.spec.ts | 44 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/rules/enforce-custom-provider-type.rule.ts b/src/rules/enforce-custom-provider-type.rule.ts index 3c68019..3da2a5f 100644 --- a/src/rules/enforce-custom-provider-type.rule.ts +++ b/src/rules/enforce-custom-provider-type.rule.ts @@ -9,7 +9,7 @@ const createRule = ESLintUtils.RuleCreator( (name) => `https://eslint.org/docs/latest/rules/${name}` ); -type ProviderType = 'class' | 'factory' | 'value'; +type ProviderType = 'class' | 'factory' | 'value' | 'existing'; export type Options = [ { @@ -112,6 +112,15 @@ function getProviderType(node: TSESTree.Identifier): ProviderType | undefined { type = 'value'; break; } + + if ( + property.type === AST_NODE_TYPES.Property && + ASTUtils.isIdentifier(property.key) && + property.key.name === 'useExisting' + ) { + type = 'existing'; + break; + } } } diff --git a/tests/rules/enforce-custom-provider-type.rule.spec.ts b/tests/rules/enforce-custom-provider-type.rule.spec.ts index 9e1b27b..6e27866 100644 --- a/tests/rules/enforce-custom-provider-type.rule.spec.ts +++ b/tests/rules/enforce-custom-provider-type.rule.spec.ts @@ -53,5 +53,49 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { }, ], }, + { + code: ` + import { Provider } from '@nestjs/common'; + import { SomeClass } from './some-class'; + const customValueProvider: Provider = { + provide: 'TOKEN', + useClass: SomeClass // ⚠️ provider is not of type "factory" + } + `, + options: [ + { + prefer: 'factory', + }, + ], + errors: [ + { + messageId: 'providerTypeMismatch', + }, + ], + }, + { + code: ` + import { Provider } from '@nestjs/common'; + import { EXISTING_TOKEN } from './token'; + const customValueProvider: Provider = { + provide: 'TOKEN', + useExisting: EXISTING_TOKEN // ⚠️ provider is not of type "factory" + } + `, + options: [ + { + prefer: 'factory', + }, + ], + errors: [ + { + messageId: 'providerTypeMismatch', + }, + ], + }, + // TODO + // Test for when the Provider type is not imported from '@nestjs/common' + // Test for when the Provider type was renamed + // Test for when the Provider type is different from the one defined in the configuration ], }); From a4009e9479b85a11a3da88bc45c6c388e4e1c696 Mon Sep 17 00:00:00 2001 From: Tolga Paksoy Date: Sat, 10 Feb 2024 01:49:47 +0100 Subject: [PATCH 04/22] feat: support aliasing provider type --- .../enforce-custom-provider-type.rule.ts | 19 ++++++++++++++++- .../enforce-custom-provider-type.rule.spec.ts | 21 ++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/rules/enforce-custom-provider-type.rule.ts b/src/rules/enforce-custom-provider-type.rule.ts index 3da2a5f..9410fab 100644 --- a/src/rules/enforce-custom-provider-type.rule.ts +++ b/src/rules/enforce-custom-provider-type.rule.ts @@ -52,7 +52,21 @@ export default createRule({ create(context) { const options = context.options[0] || defaultOptions[0]; const preferredType = options.prefer; + let providerRefName = 'Provider'; + return { + 'ImportDeclaration > ImportSpecifier[imported.name="Provider"]': ( + node: TSESTree.ImportSpecifier & { + parent: TSESTree.ImportDeclaration; + imported: TSESTree.Identifier & { + source: TSESTree.Literal; + }; + } + ) => { + if (node.parent?.source.value === '@nestjs/common') { + providerRefName = node.local.name; + } + }, 'Identifier[typeAnnotation.typeAnnotation.type="TSTypeReference"]': ( node: TSESTree.Identifier ) => { @@ -60,7 +74,10 @@ export default createRule({ node.typeAnnotation?.typeAnnotation as TSESTree.TSTypeReference ).typeName; - if (ASTUtils.isIdentifier(typeName) && typeName.name === 'Provider') { + if ( + ASTUtils.isIdentifier(typeName) && + typeName.name === providerRefName + ) { const providerType = getProviderType(node); if (providerType && providerType !== preferredType) { context.report({ diff --git a/tests/rules/enforce-custom-provider-type.rule.spec.ts b/tests/rules/enforce-custom-provider-type.rule.spec.ts index 6e27866..6b0ca20 100644 --- a/tests/rules/enforce-custom-provider-type.rule.spec.ts +++ b/tests/rules/enforce-custom-provider-type.rule.spec.ts @@ -93,9 +93,28 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { }, ], }, + { + code: ` + import { Provider as NestProvider } from '@nestjs/common'; + import { EXISTING_TOKEN } from './token'; + const customValueProvider: NestProvider = { + provide: 'TOKEN', + useExisting: EXISTING_TOKEN // ⚠️ provider is not of type "factory" + } + `, + options: [ + { + prefer: 'factory', + }, + ], + errors: [ + { + messageId: 'providerTypeMismatch', + }, + ], + }, // TODO // Test for when the Provider type is not imported from '@nestjs/common' - // Test for when the Provider type was renamed // Test for when the Provider type is different from the one defined in the configuration ], }); From ee1dadcfe5728bd4cda8ad52bf6c1ba874835336 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Thu, 15 Feb 2024 09:54:32 -0300 Subject: [PATCH 05/22] feat: ensure we only check for Provider imported from nestjs package --- .../enforce-custom-provider-type.rule.ts | 47 ++++++++++++++++++- .../enforce-custom-provider-type.rule.spec.ts | 21 ++++++++- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/src/rules/enforce-custom-provider-type.rule.ts b/src/rules/enforce-custom-provider-type.rule.ts index 3da2a5f..f5e5bcf 100644 --- a/src/rules/enforce-custom-provider-type.rule.ts +++ b/src/rules/enforce-custom-provider-type.rule.ts @@ -9,7 +9,7 @@ const createRule = ESLintUtils.RuleCreator( (name) => `https://eslint.org/docs/latest/rules/${name}` ); -type ProviderType = 'class' | 'factory' | 'value' | 'existing'; +type ProviderType = 'class' | 'factory' | 'value' | 'existing' | 'unknown'; export type Options = [ { @@ -52,7 +52,32 @@ export default createRule({ create(context) { const options = context.options[0] || defaultOptions[0]; const preferredType = options.prefer; + const providerTypesImported: ProviderType[] = []; return { + 'ImportDeclaration[source.value="@nestjs/common"]': ( + node: TSESTree.ImportDeclaration + ) => { + const specifiers = node.specifiers; + for (const specifier of specifiers) { + if (specifier.type === AST_NODE_TYPES.ImportSpecifier) { + switch (specifier.imported.name) { + case 'Provider': + providerTypesImported.push('unknown'); + break; + case 'ClassProvider': + providerTypesImported.push('class'); + break; + case 'FactoryProvider': + providerTypesImported.push('factory'); + break; + case 'ValueProvider': + providerTypesImported.push('value'); + break; + } + } + } + }, + 'Identifier[typeAnnotation.typeAnnotation.type="TSTypeReference"]': ( node: TSESTree.Identifier ) => { @@ -60,7 +85,12 @@ export default createRule({ node.typeAnnotation?.typeAnnotation as TSESTree.TSTypeReference ).typeName; - if (ASTUtils.isIdentifier(typeName) && typeName.name === 'Provider') { + if ( + ASTUtils.isIdentifier(typeName) && + providerTypesImported.includes( + providerNameToType(typeName.name) as ProviderType + ) + ) { const providerType = getProviderType(node); if (providerType && providerType !== preferredType) { context.report({ @@ -127,3 +157,16 @@ function getProviderType(node: TSESTree.Identifier): ProviderType | undefined { return type; } } + +function providerNameToType(providerName: string): ProviderType | undefined { + switch (providerName) { + case 'ClassProvider': + return 'class'; + case 'FactoryProvider': + return 'factory'; + case 'ValueProvider': + return 'value'; + case 'Provider': + return 'unknown'; + } +} diff --git a/tests/rules/enforce-custom-provider-type.rule.spec.ts b/tests/rules/enforce-custom-provider-type.rule.spec.ts index 6e27866..5166928 100644 --- a/tests/rules/enforce-custom-provider-type.rule.spec.ts +++ b/tests/rules/enforce-custom-provider-type.rule.spec.ts @@ -21,6 +21,8 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { valid: [ { code: ` + import { Provider } from '@nestjs/common'; + const factoryProvider: Provider = { provide: 'TOKEN', useFactory: () => 'some-value' @@ -32,6 +34,22 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { }, ], }, + // Test for when the Provider type is not imported from '@nestjs/common' + { + code: ` + import { Provider } from 'some-other-package'; + const customValueProvider: Provider = { + useValue: 'some-value', + provide: 'TOKEN' + } + `, + options: [ + { + prefer: 'factory', + }, + ], + }, + // Test for when the Provider type was renamed ], invalid: [ { @@ -94,8 +112,7 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { ], }, // TODO - // Test for when the Provider type is not imported from '@nestjs/common' - // Test for when the Provider type was renamed + // Test for when the Provider type is different from the one defined in the configuration ], }); From 8583c37a29425595078a7844f56bc9d156b6f034 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Fri, 16 Feb 2024 11:06:30 -0300 Subject: [PATCH 06/22] refactor: use filter and forEach to get specifiers --- .../enforce-custom-provider-type.rule.ts | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/rules/enforce-custom-provider-type.rule.ts b/src/rules/enforce-custom-provider-type.rule.ts index 078ac18..2e97450 100644 --- a/src/rules/enforce-custom-provider-type.rule.ts +++ b/src/rules/enforce-custom-provider-type.rule.ts @@ -58,21 +58,26 @@ export default createRule({ node: TSESTree.ImportDeclaration ) => { const specifiers = node.specifiers; - for (const specifier of specifiers) { - if ( - specifier.type === AST_NODE_TYPES.ImportSpecifier && - [ - 'Provider', - 'ClassProvider', - 'FactoryProvider', - 'ValueProvider', - ].includes(specifier.imported.name) - ) { - providerTypesImported.push( - specifier.local.name ?? specifier.imported.name - ); - } - } + + const isImportSpecifier = ( + node: TSESTree.ImportClause + ): node is TSESTree.ImportSpecifier => + node.type === AST_NODE_TYPES.ImportSpecifier; + + const isProviderImport = (spec: TSESTree.ImportSpecifier) => + [ + 'Provider', + 'ClassProvider', + 'FactoryProvider', + 'ValueProvider', + ].includes(spec.imported.name); + + specifiers + .filter(isImportSpecifier) + .filter(isProviderImport) + .forEach((spec: TSESTree.ImportSpecifier) => + providerTypesImported.push(spec.local.name ?? spec.imported.name) + ); }, 'Identifier[typeAnnotation.typeAnnotation.type="TSTypeReference"]': ( From e4608a0db3e5183549fa9728806697c2d0f1ac42 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Fri, 16 Feb 2024 11:09:46 -0300 Subject: [PATCH 07/22] refactor: remove unnecessary type assertion --- src/rules/enforce-custom-provider-type.rule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rules/enforce-custom-provider-type.rule.ts b/src/rules/enforce-custom-provider-type.rule.ts index 2e97450..9f44a9d 100644 --- a/src/rules/enforce-custom-provider-type.rule.ts +++ b/src/rules/enforce-custom-provider-type.rule.ts @@ -75,7 +75,7 @@ export default createRule({ specifiers .filter(isImportSpecifier) .filter(isProviderImport) - .forEach((spec: TSESTree.ImportSpecifier) => + .forEach((spec) => providerTypesImported.push(spec.local.name ?? spec.imported.name) ); }, From 0e767f0baf6b0e731ba07358b37a42369f683772 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Fri, 16 Feb 2024 11:12:45 -0300 Subject: [PATCH 08/22] docs: added comments to tests --- tests/rules/enforce-custom-provider-type.rule.spec.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/rules/enforce-custom-provider-type.rule.spec.ts b/tests/rules/enforce-custom-provider-type.rule.spec.ts index bf8dd62..be7fbbe 100644 --- a/tests/rules/enforce-custom-provider-type.rule.spec.ts +++ b/tests/rules/enforce-custom-provider-type.rule.spec.ts @@ -19,6 +19,7 @@ const ruleTester = new RuleTester({ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { valid: [ + // Test for when the provider is of the preferred type { code: ` import { Provider } from '@nestjs/common'; @@ -49,9 +50,9 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { }, ], }, - // Test for when the Provider type was renamed ], invalid: [ + // Test for when the provider is not of the preferred type { code: ` import { Provider } from '@nestjs/common'; @@ -111,6 +112,7 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { }, ], }, + // Test for when the provider is not of the preferred type and was renamed { code: ` import { Provider as NestProvider } from '@nestjs/common'; @@ -131,8 +133,5 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { }, ], }, - // TODO - // Test for when the Provider type is not imported from '@nestjs/common' - // Test for when the Provider type is different from the one defined in the configuration ], }); From a2b9f7d2e574c2152d300049f1dc2b42065147a2 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Wed, 21 Feb 2024 12:48:55 -0300 Subject: [PATCH 09/22] feat: report error when provider type in providers array is invalid --- .../enforce-custom-provider-type.rule.ts | 30 +++++++++++++++++++ .../enforce-custom-provider-type.rule.spec.ts | 26 ++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/src/rules/enforce-custom-provider-type.rule.ts b/src/rules/enforce-custom-provider-type.rule.ts index 9f44a9d..cda26e7 100644 --- a/src/rules/enforce-custom-provider-type.rule.ts +++ b/src/rules/enforce-custom-provider-type.rule.ts @@ -80,6 +80,36 @@ export default createRule({ ); }, + 'Property[key.name="providers"] > ArrayExpression > ObjectExpression': ( + node: TSESTree.ObjectExpression + ) => { + for (const property of node.properties) { + if (property.type === AST_NODE_TYPES.Property) { + const propertyKey = (property.key as TSESTree.Identifier)?.name; + const providerType: ProviderType | undefined = + propertyKey === 'useClass' + ? 'class' + : propertyKey === 'useFactory' + ? 'factory' + : propertyKey === 'useValue' + ? 'value' + : propertyKey === 'useExisting' + ? 'existing' + : undefined; + + if (providerType && providerType !== preferredType) { + context.report({ + node: property, + messageId: 'providerTypeMismatch', + data: { + preferred: preferredType, + }, + }); + } + } + } + }, + 'Identifier[typeAnnotation.typeAnnotation.type="TSTypeReference"]': ( node: TSESTree.Identifier ) => { diff --git a/tests/rules/enforce-custom-provider-type.rule.spec.ts b/tests/rules/enforce-custom-provider-type.rule.spec.ts index be7fbbe..543e7a2 100644 --- a/tests/rules/enforce-custom-provider-type.rule.spec.ts +++ b/tests/rules/enforce-custom-provider-type.rule.spec.ts @@ -133,5 +133,31 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { }, ], }, + + // Test for when the provider (value) is not of the preferred type (factory) in the "providers" array + { + code: ` + import { Module } from '@nestjs/common'; + @Module({ + providers: [ + { + provide: 'TOKEN', + useValue: 'value-in-providers-array', + } + ] + }) + export class SomeModule {} + `, + errors: [ + { + messageId: 'providerTypeMismatch', + }, + ], + options: [ + { + prefer: 'factory', + }, + ], + }, ], }); From 93553a22010d5bf8eb5d65e095fe071e58c8df62 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Wed, 21 Feb 2024 12:53:22 -0300 Subject: [PATCH 10/22] test: add test for all types of providers --- .../enforce-custom-provider-type.rule.spec.ts | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/tests/rules/enforce-custom-provider-type.rule.spec.ts b/tests/rules/enforce-custom-provider-type.rule.spec.ts index 543e7a2..724bff3 100644 --- a/tests/rules/enforce-custom-provider-type.rule.spec.ts +++ b/tests/rules/enforce-custom-provider-type.rule.spec.ts @@ -159,5 +159,104 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { }, ], }, + // Test for when the provider (class) is not of the preferred type (factory) in the "providers" array + { + code: ` + import { Module } from '@nestjs/common'; + import { SomeClass } from './some-class'; + + @Module({ + providers: [ + { + provide: 'TOKEN', + useClass: SomeClass, + } + ] + }) + export class SomeModule {} + `, + errors: [ + { + messageId: 'providerTypeMismatch', + }, + ], + options: [ + { + prefer: 'factory', + }, + ], + }, + // Test for when the provider (existing) is not of the preferred type (factory) in the "providers" array + { + code: ` + import { Module } from '@nestjs/common'; + import { SomeClass } from './some-class'; + + @Module({ + providers: [ + { + provide: 'TOKEN', + useExisting: SomeClass, + } + ] + }) + export class SomeModule {} + `, + errors: [ + { + messageId: 'providerTypeMismatch', + }, + ], + options: [ + { + prefer: 'factory', + }, + ], + }, + // Test for when many provider types are not of the preferred type (factory) in the "providers" array + { + code: ` + import { Module } from '@nestjs/common'; + import { SomeClass } from './some-class'; + + @Module({ + providers: [ + { + provide: 'TOKEN', + useExisting: SomeClass, + }, + { + provide: 'TOKEN', + useClass: SomeClass, + }, + { + provide: 'TOKEN', + useValue: 'value-in-providers-array', + }, + { + provide: 'TOKEN', + useFactory: () => 'value-in-providers-array', + } + ] + }) + export class SomeModule {} + `, + errors: [ + { + messageId: 'providerTypeMismatch', + }, + { + messageId: 'providerTypeMismatch', + }, + { + messageId: 'providerTypeMismatch', + }, + ], + options: [ + { + prefer: 'factory', + }, + ], + }, ], }); From 51a7e69bd2d1eb0a782074bd9417283896e84f41 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Wed, 21 Feb 2024 12:55:25 -0300 Subject: [PATCH 11/22] refactor: simplify getProviderType code --- .../enforce-custom-provider-type.rule.ts | 46 +++++-------------- 1 file changed, 12 insertions(+), 34 deletions(-) diff --git a/src/rules/enforce-custom-provider-type.rule.ts b/src/rules/enforce-custom-provider-type.rule.ts index cda26e7..82fdf5a 100644 --- a/src/rules/enforce-custom-provider-type.rule.ts +++ b/src/rules/enforce-custom-provider-type.rule.ts @@ -146,40 +146,18 @@ function getProviderType(node: TSESTree.Identifier): 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 && - ASTUtils.isIdentifier(property.key) && - property.key.name === 'useFactory' - ) { - type = 'factory'; - break; - } - - if ( - property.type === AST_NODE_TYPES.Property && - ASTUtils.isIdentifier(property.key) && - property.key.name === 'useClass' - ) { - type = 'class'; - break; - } - - if ( - property.type === AST_NODE_TYPES.Property && - ASTUtils.isIdentifier(property.key) && - property.key.name === 'useValue' - ) { - type = 'value'; - break; - } - - if ( - property.type === AST_NODE_TYPES.Property && - ASTUtils.isIdentifier(property.key) && - property.key.name === 'useExisting' - ) { - type = 'existing'; - break; + if (property.type === AST_NODE_TYPES.Property) { + const propertyKey = (property.key as TSESTree.Identifier)?.name; + type = + propertyKey === 'useClass' + ? 'class' + : propertyKey === 'useFactory' + ? 'factory' + : propertyKey === 'useValue' + ? 'value' + : propertyKey === 'useExisting' + ? 'existing' + : undefined; } } } From 5167f0143035a27ac26740e5d4f874cec6c724a7 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Thu, 22 Feb 2024 09:53:58 -0300 Subject: [PATCH 12/22] refactor: extract common logic to method --- .../enforce-custom-provider-type.rule.ts | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/src/rules/enforce-custom-provider-type.rule.ts b/src/rules/enforce-custom-provider-type.rule.ts index 82fdf5a..687f43c 100644 --- a/src/rules/enforce-custom-provider-type.rule.ts +++ b/src/rules/enforce-custom-provider-type.rule.ts @@ -85,17 +85,7 @@ export default createRule({ ) => { for (const property of node.properties) { if (property.type === AST_NODE_TYPES.Property) { - const propertyKey = (property.key as TSESTree.Identifier)?.name; - const providerType: ProviderType | undefined = - propertyKey === 'useClass' - ? 'class' - : propertyKey === 'useFactory' - ? 'factory' - : propertyKey === 'useValue' - ? 'value' - : propertyKey === 'useExisting' - ? 'existing' - : undefined; + const providerType = providerTypeOfProperty(property); if (providerType && providerType !== preferredType) { context.report({ @@ -121,7 +111,7 @@ export default createRule({ ASTUtils.isIdentifier(typeName) && providerTypesImported.includes(typeName.name) ) { - const providerType = getProviderType(node); + const providerType = providerTypeOfIdentifier(node); if (providerType && providerType !== preferredType) { context.report({ node, @@ -137,7 +127,9 @@ export default createRule({ }, }); -function getProviderType(node: TSESTree.Identifier): ProviderType | undefined { +function providerTypeOfIdentifier( + node: TSESTree.Identifier +): ProviderType | undefined { const parent = node.parent; if (ASTUtils.isVariableDeclarator(parent)) { @@ -147,17 +139,7 @@ function getProviderType(node: TSESTree.Identifier): ProviderType | undefined { const properties = init.properties; for (const property of properties) { if (property.type === AST_NODE_TYPES.Property) { - const propertyKey = (property.key as TSESTree.Identifier)?.name; - type = - propertyKey === 'useClass' - ? 'class' - : propertyKey === 'useFactory' - ? 'factory' - : propertyKey === 'useValue' - ? 'value' - : propertyKey === 'useExisting' - ? 'existing' - : undefined; + type = providerTypeOfProperty(property); } } } @@ -166,3 +148,17 @@ function getProviderType(node: TSESTree.Identifier): ProviderType | undefined { } } +function providerTypeOfProperty( + node: TSESTree.Property +): ProviderType | undefined { + const propertyKey = (node.key as TSESTree.Identifier)?.name; + return propertyKey === 'useClass' + ? 'class' + : propertyKey === 'useFactory' + ? 'factory' + : propertyKey === 'useValue' + ? 'value' + : propertyKey === 'useExisting' + ? 'existing' + : undefined; +}; \ No newline at end of file From 655562442fdd13bf4e86cfd149690fd107546a13 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Thu, 22 Feb 2024 09:54:07 -0300 Subject: [PATCH 13/22] chore: update node version --- .nvmrc | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2efc7e1 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v20.11.1 \ No newline at end of file diff --git a/package.json b/package.json index e4bd7ec..db220df 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "LICENSE" ], "engines": { - "node": ">=20.9.0", + "node": ">=20.11.1", "yarn": ">=4.0.2" }, "scripts": { From b03a98735282dfb302cd8a4399106988a5fe94d8 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Mon, 26 Feb 2024 09:52:45 -0300 Subject: [PATCH 14/22] style: fix indentation --- .../enforce-custom-provider-type.rule.spec.ts | 100 +++++++++--------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/tests/rules/enforce-custom-provider-type.rule.spec.ts b/tests/rules/enforce-custom-provider-type.rule.spec.ts index 724bff3..c6f2bd3 100644 --- a/tests/rules/enforce-custom-provider-type.rule.spec.ts +++ b/tests/rules/enforce-custom-provider-type.rule.spec.ts @@ -162,19 +162,19 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { // Test for when the provider (class) is not of the preferred type (factory) in the "providers" array { code: ` - import { Module } from '@nestjs/common'; - import { SomeClass } from './some-class'; + import { Module } from '@nestjs/common'; + import { SomeClass } from './some-class'; - @Module({ - providers: [ - { - provide: 'TOKEN', - useClass: SomeClass, - } - ] - }) - export class SomeModule {} - `, + @Module({ + providers: [ + { + provide: 'TOKEN', + useClass: SomeClass, + } + ] + }) + export class SomeModule {} + `, errors: [ { messageId: 'providerTypeMismatch', @@ -189,19 +189,19 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { // Test for when the provider (existing) is not of the preferred type (factory) in the "providers" array { code: ` - import { Module } from '@nestjs/common'; - import { SomeClass } from './some-class'; - - @Module({ - providers: [ - { - provide: 'TOKEN', - useExisting: SomeClass, - } - ] - }) - export class SomeModule {} - `, + import { Module } from '@nestjs/common'; + import { SomeClass } from './some-class'; + + @Module({ + providers: [ + { + provide: 'TOKEN', + useExisting: SomeClass, + } + ] + }) + export class SomeModule {} + `, errors: [ { messageId: 'providerTypeMismatch', @@ -216,31 +216,31 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { // Test for when many provider types are not of the preferred type (factory) in the "providers" array { code: ` - import { Module } from '@nestjs/common'; - import { SomeClass } from './some-class'; - - @Module({ - providers: [ - { - provide: 'TOKEN', - useExisting: SomeClass, - }, - { - provide: 'TOKEN', - useClass: SomeClass, - }, - { - provide: 'TOKEN', - useValue: 'value-in-providers-array', - }, - { - provide: 'TOKEN', - useFactory: () => 'value-in-providers-array', - } - ] - }) - export class SomeModule {} - `, + import { Module } from '@nestjs/common'; + import { SomeClass } from './some-class'; + + @Module({ + providers: [ + { + provide: 'TOKEN', + useExisting: SomeClass, + }, + { + provide: 'TOKEN', + useClass: SomeClass, + }, + { + provide: 'TOKEN', + useValue: 'value-in-providers-array', + }, + { + provide: 'TOKEN', + useFactory: () => 'value-in-providers-array', + } + ] + }) + export class SomeModule {} + `, errors: [ { messageId: 'providerTypeMismatch', From 58647c753a3c61406bebec319695e2661d9ec51b Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Mon, 26 Feb 2024 10:04:23 -0300 Subject: [PATCH 15/22] docs: add docs for new rule and update README --- README.md | 9 ++-- docs/rules/enforce-custom-provider-type.md | 55 ++++++++++++++++++++++ src/index.ts | 10 ++++ 3 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 docs/rules/enforce-custom-provider-type.md diff --git a/README.md b/README.md index d09b5a7..52d8666 100644 --- a/README.md +++ b/README.md @@ -46,11 +46,12 @@ The "recommended" preset contains the rules listed below. If you need custom con ## Rules -| Rule | Description | Recommended | +| Rule | Description | Type | | ------------------------------------------------------------------------------------ | -------------------------------------------------------------- | ----------- | -| [`@trilon/enforce-close-testing-module`](docs/rules/enforce-close-testing-module.md) | Ensures NestJS testing modules are closed properly after tests | ✅ | -| [`@trilon/check-inject-decorator`](docs/rules/check-inject-decorator.md) | Detects incorrect usage of `@Inject(TOKEN)` decorator | ✅ | -| [`@trilon/detect-circular-reference`](docs/rules/detect-circular-reference.md) | Detects usage of `forwardRef()` method | ✅ | +| [`@trilon/enforce-close-testing-module`](docs/rules/enforce-close-testing-module.md) | Ensures NestJS testing modules are closed properly after tests | Recommended ✅ | +| [`@trilon/check-inject-decorator`](docs/rules/check-inject-decorator.md) | Detects incorrect usage of `@Inject(TOKEN)` decorator | Recommended ✅ | +| [`@trilon/detect-circular-reference`](docs/rules/detect-circular-reference.md) | Detects usage of `forwardRef()` method | Recommended ✅ | +| [`@trilon/enforce-custom-provider-type`](docs/rules/enforce-custom-provider-type.md) | Enforces a styleguide for provider types | Strict ⚠️ | --- # Trilon Consulting diff --git a/docs/rules/enforce-custom-provider-type.md b/docs/rules/enforce-custom-provider-type.md new file mode 100644 index 0000000..66ca8b3 --- /dev/null +++ b/docs/rules/enforce-custom-provider-type.md @@ -0,0 +1,55 @@ +--- +description: 'Enforces a styleguide for provider types' +--- + +Large teams can have the desire to limit or enforce a particular style of creating [custom providers](https://docs.nestjs.com/fundamentals/custom-providers); e.g. banning request-scoped providers to avoid potential circular dependencies, or [preferring factory providers over value providers to significantly increase performance](https://github.com/nestjs/nest/pull/12753). This rule enforces a particular type of provider to be used. + +## Options + +This rule accepts an object with the "prefer" property, which might be one of the following values: + +- `value`: Enforces the use of value providers. +- `factory`: Enforces the use of factory providers. +- `class`: Enforces the use of class providers. +- `existing`: Enforces the use of existing providers. + + +### Example of Options + +```json +"rules": { + "@trilon/enforce-custom-provider-type": [ + "warn", { + "prefer": "factory" + } + ] +} +``` + +## Examples +Considering the options above, the following examples will show how the rule behaves when the `prefer` option is set to `factory`. + +### ❌ Incorrect + +```ts +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" +} +``` + +### ✅ Correct + +const factoryProvider: Provider = { + provide: 'TOKEN', + useFactory: () => 'some-value' +} + +## When Not To Use It + +If you don't want to enforce a particular style of provider, you can disable this rule. diff --git a/src/index.ts b/src/index.ts index fe8ef49..b9637bb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import enforceCloseTestingModuleRule from './rules/enforce-close-testing-module.rule'; import checkInjectDecoratorRule from './rules/check-inject-decorator.rule'; import detectCircularReferenceRule from './rules/detect-circular-reference.rule'; +import enforceCustomProviderTypeRule from './rules/enforce-custom-provider-type.rule'; // TODO: we should type this as ESLint.Plugin but there's a type incompatibilities with the utils package const plugin = { configs: { @@ -11,11 +12,20 @@ const plugin = { '@trilon/detect-circular-reference': 'warn', }, }, + strict: { + rules: { + '@trilon/enforce-close-testing-module': 'error', + '@trilon/check-inject-decorator': 'error', + '@trilon/detect-circular-reference': 'error', + '@trilon/enforce-custom-provider-type': 'error', + }, + }, }, rules: { 'enforce-close-testing-module': enforceCloseTestingModuleRule, 'check-inject-decorator': checkInjectDecoratorRule, 'detect-circular-reference': detectCircularReferenceRule, + '@trilon/enforce-custom-provider-type': enforceCustomProviderTypeRule, }, }; From a9b0473eb55afe20c643fe0d410489f249791ad9 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Mon, 26 Feb 2024 10:06:24 -0300 Subject: [PATCH 16/22] chore: update node version hint --- .node-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.node-version b/.node-version index 9a2a0e2..2efc7e1 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -v20 +v20.11.1 \ No newline at end of file From 509c9ba71a25fb7278dff295ecd83f94b4dc307d Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Tue, 27 Feb 2024 09:46:18 -0300 Subject: [PATCH 17/22] refactor: make preferred type an array --- .../enforce-custom-provider-type.rule.ts | 21 +++++++++++-------- .../enforce-custom-provider-type.rule.spec.ts | 20 +++++++++--------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/rules/enforce-custom-provider-type.rule.ts b/src/rules/enforce-custom-provider-type.rule.ts index 687f43c..2ec0c82 100644 --- a/src/rules/enforce-custom-provider-type.rule.ts +++ b/src/rules/enforce-custom-provider-type.rule.ts @@ -13,13 +13,13 @@ type ProviderType = 'class' | 'factory' | 'value' | 'existing' | 'unknown'; export type Options = [ { - prefer: ProviderType; + prefer: ProviderType[]; }, ]; const defaultOptions: Options = [ { - prefer: 'factory', + prefer: [], }, ]; @@ -38,8 +38,11 @@ export default createRule({ type: 'object', properties: { prefer: { - type: 'string', - enum: ['class', 'factory', 'value'], + type: 'array', + items: { + type: 'string', + enum: ['class', 'factory', 'value', 'existing'], + }, }, }, }, @@ -51,7 +54,7 @@ export default createRule({ defaultOptions, create(context) { const options = context.options[0] || defaultOptions[0]; - const preferredType = options.prefer; + const preferredTypes = options.prefer; const providerTypesImported: string[] = []; return { 'ImportDeclaration[source.value="@nestjs/common"]': ( @@ -87,12 +90,12 @@ export default createRule({ if (property.type === AST_NODE_TYPES.Property) { const providerType = providerTypeOfProperty(property); - if (providerType && providerType !== preferredType) { + if (providerType && !preferredTypes.includes(providerType)) { context.report({ node: property, messageId: 'providerTypeMismatch', data: { - preferred: preferredType, + preferred: preferredTypes, }, }); } @@ -112,12 +115,12 @@ export default createRule({ providerTypesImported.includes(typeName.name) ) { const providerType = providerTypeOfIdentifier(node); - if (providerType && providerType !== preferredType) { + if (providerType && !preferredTypes.includes(providerType)) { context.report({ node, messageId: 'providerTypeMismatch', data: { - preferred: preferredType, + preferred: preferredTypes, }, }); } diff --git a/tests/rules/enforce-custom-provider-type.rule.spec.ts b/tests/rules/enforce-custom-provider-type.rule.spec.ts index c6f2bd3..2ea64ac 100644 --- a/tests/rules/enforce-custom-provider-type.rule.spec.ts +++ b/tests/rules/enforce-custom-provider-type.rule.spec.ts @@ -31,7 +31,7 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { `, options: [ { - prefer: 'factory', + prefer: ['factory'], }, ], }, @@ -46,7 +46,7 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { `, options: [ { - prefer: 'factory', + prefer: ['factory'], }, ], }, @@ -63,7 +63,7 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { `, options: [ { - prefer: 'factory', + prefer: ['factory'], }, ], errors: [ @@ -83,7 +83,7 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { `, options: [ { - prefer: 'factory', + prefer: ['factory'], }, ], errors: [ @@ -103,7 +103,7 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { `, options: [ { - prefer: 'factory', + prefer: ['factory'], }, ], errors: [ @@ -124,7 +124,7 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { `, options: [ { - prefer: 'factory', + prefer: ['factory'], }, ], errors: [ @@ -155,7 +155,7 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { ], options: [ { - prefer: 'factory', + prefer: ['factory'], }, ], }, @@ -182,7 +182,7 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { ], options: [ { - prefer: 'factory', + prefer: ['factory'], }, ], }, @@ -209,7 +209,7 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { ], options: [ { - prefer: 'factory', + prefer: ['factory'], }, ], }, @@ -254,7 +254,7 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { ], options: [ { - prefer: 'factory', + prefer: ['factory'], }, ], }, From c815a6a89c77a0e1525ad22f14089bca4766caee Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Tue, 27 Feb 2024 09:49:57 -0300 Subject: [PATCH 18/22] test: create tests for multiple preferred types --- .../enforce-custom-provider-type.rule.ts | 9 +++- .../enforce-custom-provider-type.rule.spec.ts | 48 +++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/rules/enforce-custom-provider-type.rule.ts b/src/rules/enforce-custom-provider-type.rule.ts index 2ec0c82..5b0bc59 100644 --- a/src/rules/enforce-custom-provider-type.rule.ts +++ b/src/rules/enforce-custom-provider-type.rule.ts @@ -90,7 +90,11 @@ export default createRule({ if (property.type === AST_NODE_TYPES.Property) { const providerType = providerTypeOfProperty(property); - if (providerType && !preferredTypes.includes(providerType)) { + if ( + providerType && + !preferredTypes.includes(providerType) && + preferredTypes.length > 0 + ) { context.report({ node: property, messageId: 'providerTypeMismatch', @@ -112,7 +116,8 @@ export default createRule({ if ( ASTUtils.isIdentifier(typeName) && - providerTypesImported.includes(typeName.name) + providerTypesImported.includes(typeName.name) && + preferredTypes.length > 0 ) { const providerType = providerTypeOfIdentifier(node); if (providerType && !preferredTypes.includes(providerType)) { diff --git a/tests/rules/enforce-custom-provider-type.rule.spec.ts b/tests/rules/enforce-custom-provider-type.rule.spec.ts index 2ea64ac..8dbc4cc 100644 --- a/tests/rules/enforce-custom-provider-type.rule.spec.ts +++ b/tests/rules/enforce-custom-provider-type.rule.spec.ts @@ -19,6 +19,17 @@ const ruleTester = new RuleTester({ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { valid: [ + // Test for when no options are provided + { + code: ` + import { Provider } from '@nestjs/common'; + + const factoryProvider: Provider = { + provide: 'TOKEN', + useFactory: () => 'some-value' + } + `, + }, // Test for when the provider is of the preferred type { code: ` @@ -35,6 +46,23 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { }, ], }, + // Test for when the provider is one of the preferred types + { + code: ` + import { Provider } from '@nestjs/common'; + import { SomeClass } from './some-class'; + + const factoryProvider: Provider = { + provide: 'TOKEN', + useClass: SomeClass + } + `, + options: [ + { + prefer: ['factory', 'class'], + }, + ], + }, // Test for when the Provider type is not imported from '@nestjs/common' { code: ` @@ -72,6 +100,26 @@ ruleTester.run('enforce-custom-provider-type', enforceCustomProviderTypeRule, { }, ], }, + // Test for when the provider is not one the preferred types + { + code: ` + import { Provider } from '@nestjs/common'; + const customValueProvider: Provider = { + provide: 'TOKEN', + useValue: 'some-value' // ⚠️ provider is not of type "factory" + } + `, + options: [ + { + prefer: ['factory', 'class', 'existing'], + }, + ], + errors: [ + { + messageId: 'providerTypeMismatch', + }, + ], + }, { code: ` import { Provider } from '@nestjs/common'; From fbad23057b5067cbfe2173f403ce14d410277f7f Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Tue, 27 Feb 2024 09:52:29 -0300 Subject: [PATCH 19/22] docs: update docs to include array of preferred types --- docs/rules/enforce-custom-provider-type.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/rules/enforce-custom-provider-type.md b/docs/rules/enforce-custom-provider-type.md index 66ca8b3..82213c7 100644 --- a/docs/rules/enforce-custom-provider-type.md +++ b/docs/rules/enforce-custom-provider-type.md @@ -6,7 +6,7 @@ Large teams can have the desire to limit or enforce a particular style of creati ## Options -This rule accepts an object with the "prefer" property, which might be one of the following values: +This rule accepts an object with the "prefer" property, which is an array containing one or more of the following values: - `value`: Enforces the use of value providers. - `factory`: Enforces the use of factory providers. @@ -20,7 +20,7 @@ This rule accepts an object with the "prefer" property, which might be one of th "rules": { "@trilon/enforce-custom-provider-type": [ "warn", { - "prefer": "factory" + "prefer": ["factory", "value"] } ] } @@ -34,12 +34,12 @@ Considering the options above, the following examples will show how the rule beh ```ts const customValueProvider: Provider = { provide: 'TOKEN', - useValue: 'some-value' // ⚠️ provider is not of type "factory" + useExisting: 'some-value' // ⚠️ provider is not of type ["factory", "value"] } const customClassProvider: Provider = { provide: AbstractClass, - useClass: SomeClass // ⚠️ provider is not of type "factory" + useClass: SomeClass // ⚠️ provider is not of type ["factory", "value"] } ``` From 55a03f686fbebffd54362d251eba59019a66fca2 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Tue, 27 Feb 2024 10:11:08 -0300 Subject: [PATCH 20/22] chore: change node version hint to 18 to make it less strict --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index db220df..cc7a17a 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "LICENSE" ], "engines": { - "node": ">=20.11.1", + "node": ">=18.*.*", "yarn": ">=4.0.2" }, "scripts": { From 7d5cd2444c6e25fa295064a4dc11bd02b0098203 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Wed, 28 Feb 2024 09:15:34 -0300 Subject: [PATCH 21/22] chore: delete nvmrc file --- .nvmrc | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index 2efc7e1..0000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v20.11.1 \ No newline at end of file From 02c2f7b5b06c81b3082c54c3aa2391179d52cf98 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Wed, 28 Feb 2024 09:15:55 -0300 Subject: [PATCH 22/22] chore: add symlink to .node-version --- .nvmrc | 1 + 1 file changed, 1 insertion(+) create mode 120000 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 120000 index 0000000..b711bfe --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +./.node-version \ No newline at end of file