-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/enforce custom provider type #25
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
Changes from 14 commits
728efed
061b85b
81f5303
a4009e9
ee1dadc
e84a24c
8583c37
e4608a0
0e767f0
a2b9f7d
93553a2
51a7e69
5167f01
6555624
b03a987
58647c7
a9b0473
509c9ba
c815a6a
fbad230
55a03f6
7d5cd24
02c2f7b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
v20.11.1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ | |
"LICENSE" | ||
], | ||
"engines": { | ||
"node": ">=20.9.0", | ||
"node": ">=20.11.1", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have to require such a high version number just for using this package? Because this will generate warnings for whoever is using the package. Nest is at |
||
"yarn": ">=4.0.2" | ||
}, | ||
"scripts": { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
import { | ||
ASTUtils, | ||
AST_NODE_TYPES, | ||
ESLintUtils, | ||
type TSESTree, | ||
} from '@typescript-eslint/utils'; | ||
|
||
const createRule = ESLintUtils.RuleCreator( | ||
(name) => `https://eslint.org/docs/latest/rules/${name}` | ||
); | ||
|
||
type ProviderType = 'class' | 'factory' | 'value' | 'existing' | 'unknown'; | ||
|
||
export type Options = [ | ||
{ | ||
prefer: ProviderType; | ||
}, | ||
]; | ||
|
||
const defaultOptions: Options = [ | ||
{ | ||
prefer: 'factory', | ||
}, | ||
]; | ||
|
||
export type MessageIds = 'providerTypeMismatch'; | ||
|
||
export default createRule<Options, MessageIds>({ | ||
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) { | ||
const options = context.options[0] || defaultOptions[0]; | ||
const preferredType = options.prefer; | ||
const providerTypesImported: string[] = []; | ||
return { | ||
'ImportDeclaration[source.value="@nestjs/common"]': ( | ||
node: TSESTree.ImportDeclaration | ||
) => { | ||
const specifiers = node.specifiers; | ||
|
||
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) => | ||
providerTypesImported.push(spec.local.name ?? spec.imported.name) | ||
); | ||
}, | ||
|
||
'Property[key.name="providers"] > ArrayExpression > ObjectExpression': ( | ||
node: TSESTree.ObjectExpression | ||
) => { | ||
for (const property of node.properties) { | ||
if (property.type === AST_NODE_TYPES.Property) { | ||
const providerType = providerTypeOfProperty(property); | ||
|
||
if (providerType && providerType !== preferredType) { | ||
context.report({ | ||
node: property, | ||
messageId: 'providerTypeMismatch', | ||
data: { | ||
preferred: preferredType, | ||
}, | ||
}); | ||
} | ||
} | ||
} | ||
}, | ||
|
||
'Identifier[typeAnnotation.typeAnnotation.type="TSTypeReference"]': ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this also capture providers inside There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question Rick, I don't think so haha I'll have to create another test for that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this is now fixed, @tuxmachine , could you look at the tests and see if I'm missing something? thanks! |
||
node: TSESTree.Identifier | ||
) => { | ||
const typeName = ( | ||
node.typeAnnotation?.typeAnnotation as TSESTree.TSTypeReference | ||
).typeName; | ||
|
||
if ( | ||
ASTUtils.isIdentifier(typeName) && | ||
providerTypesImported.includes(typeName.name) | ||
) { | ||
const providerType = providerTypeOfIdentifier(node); | ||
if (providerType && providerType !== preferredType) { | ||
context.report({ | ||
node, | ||
messageId: 'providerTypeMismatch', | ||
data: { | ||
preferred: preferredType, | ||
}, | ||
}); | ||
} | ||
} | ||
}, | ||
}; | ||
}, | ||
}); | ||
|
||
function providerTypeOfIdentifier( | ||
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) { | ||
type = providerTypeOfProperty(property); | ||
} | ||
} | ||
} | ||
|
||
return type; | ||
} | ||
} | ||
|
||
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; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we symlink to the
.node-version
file instead? nvm is pretty much the only version manager that won't support that filename. nvm-sh/nvm#794 (comment)