mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
refactor(core): Lint to enforce lazyloading in modules (#16843)
Co-authored-by: Juuso Tapaninen <juuso@n8n.io>
This commit is contained in:
@@ -10,6 +10,8 @@ import { NoDynamicImportTemplateRule } from './no-dynamic-import-template.js';
|
||||
import { MisplacedN8nTypeormImportRule } from './misplaced-n8n-typeorm-import.js';
|
||||
import { NoTypeUnsafeEventEmitterRule } from './no-type-unsafe-event-emitter.js';
|
||||
import { NoUntypedConfigClassFieldRule } from './no-untyped-config-class-field.js';
|
||||
import { NoTopLevelRelativeImportsInBackendModuleRule } from './no-top-level-relative-imports-in-backend-module.js';
|
||||
import { NoConstructorInBackendModuleRule } from './no-constructor-in-backend-module.js';
|
||||
import type { AnyRuleModule } from '@typescript-eslint/utils/ts-eslint';
|
||||
|
||||
export const rules = {
|
||||
@@ -25,4 +27,6 @@ export const rules = {
|
||||
'misplaced-n8n-typeorm-import': MisplacedN8nTypeormImportRule,
|
||||
'no-type-unsafe-event-emitter': NoTypeUnsafeEventEmitterRule,
|
||||
'no-untyped-config-class-field': NoUntypedConfigClassFieldRule,
|
||||
'no-top-level-relative-imports-in-backend-module': NoTopLevelRelativeImportsInBackendModuleRule,
|
||||
'no-constructor-in-backend-module': NoConstructorInBackendModuleRule,
|
||||
} satisfies Record<string, AnyRuleModule>;
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { RuleTester } from '@typescript-eslint/rule-tester';
|
||||
import { NoConstructorInBackendModuleRule } from './no-constructor-in-backend-module.js';
|
||||
|
||||
const ruleTester = new RuleTester();
|
||||
|
||||
ruleTester.run('no-constructor-in-backend-module', NoConstructorInBackendModuleRule, {
|
||||
valid: [
|
||||
{
|
||||
code: `
|
||||
@BackendModule({ name: 'test' })
|
||||
export class TestModule {
|
||||
async init() {
|
||||
// initialization code
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
code: `
|
||||
export class RegularClass {
|
||||
constructor() {
|
||||
// this is fine in regular classes
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
code: `
|
||||
@SomeOtherDecorator()
|
||||
export class OtherModule {
|
||||
constructor() {
|
||||
// this is fine with other decorators
|
||||
}
|
||||
}`,
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: `
|
||||
@BackendModule({ name: 'test' })
|
||||
export class TestModule {
|
||||
constructor() {
|
||||
// this should be removed
|
||||
}
|
||||
}`,
|
||||
errors: [{ messageId: 'noConstructorInBackendModule' }],
|
||||
output: `
|
||||
@BackendModule({ name: 'test' })
|
||||
export class TestModule {
|
||||
|
||||
}`,
|
||||
},
|
||||
{
|
||||
code: `
|
||||
@BackendModule({ name: 'insights' })
|
||||
export class InsightsModule {
|
||||
constructor(private service: SomeService) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
async init() {
|
||||
// other code
|
||||
}
|
||||
}`,
|
||||
errors: [{ messageId: 'noConstructorInBackendModule' }],
|
||||
output: `
|
||||
@BackendModule({ name: 'insights' })
|
||||
export class InsightsModule {
|
||||
|
||||
|
||||
async init() {
|
||||
// other code
|
||||
}
|
||||
}`,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';
|
||||
|
||||
export const NoConstructorInBackendModuleRule = ESLintUtils.RuleCreator.withoutDocs({
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description:
|
||||
'A class decorated with `@BackendModule` must not have a constructor. This ensures that module dependencies are loaded only when the module is used.',
|
||||
},
|
||||
messages: {
|
||||
noConstructorInBackendModule:
|
||||
'Remove the constructor from the class decorated with `@BackendModule`.',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [],
|
||||
},
|
||||
defaultOptions: [],
|
||||
create(context) {
|
||||
return {
|
||||
'ClassDeclaration MethodDefinition[kind="constructor"]'(node: TSESTree.MethodDefinition) {
|
||||
const classDeclaration = node.parent?.parent as TSESTree.ClassDeclaration;
|
||||
|
||||
const isBackendModule =
|
||||
classDeclaration.decorators?.some(
|
||||
(d) =>
|
||||
d.expression.type === 'CallExpression' &&
|
||||
d.expression.callee.type === 'Identifier' &&
|
||||
d.expression.callee.name === 'BackendModule',
|
||||
) ?? false;
|
||||
|
||||
if (isBackendModule) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'noConstructorInBackendModule',
|
||||
fix: (fixer) => fixer.remove(node),
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import { RuleTester } from '@typescript-eslint/rule-tester';
|
||||
import { NoTopLevelRelativeImportsInBackendModuleRule } from './no-top-level-relative-imports-in-backend-module.js';
|
||||
|
||||
const ruleTester = new RuleTester();
|
||||
|
||||
ruleTester.run(
|
||||
'no-top-level-relative-imports-in-backend-module',
|
||||
NoTopLevelRelativeImportsInBackendModuleRule,
|
||||
{
|
||||
valid: [
|
||||
{
|
||||
code: `
|
||||
import { Container } from '@n8n/di';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
|
||||
@BackendModule({ name: 'test' })
|
||||
export class TestModule {
|
||||
async init() {
|
||||
const { LocalService } = await import('./local.service');
|
||||
}
|
||||
}`,
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: `
|
||||
import { Container } from '@n8n/di';
|
||||
import { LocalService } from './local.service';
|
||||
|
||||
@BackendModule({ name: 'test' })
|
||||
export class TestModule {
|
||||
async init() {
|
||||
// code
|
||||
}
|
||||
}`,
|
||||
errors: [{ messageId: 'placeInsideInit' }],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
import { BackendModule } from '@n8n/decorators';
|
||||
import { helper } from './helper';
|
||||
import { config } from './config';
|
||||
|
||||
@BackendModule({ name: 'test' })
|
||||
export class TestModule {
|
||||
async init() {
|
||||
// code
|
||||
}
|
||||
}`,
|
||||
errors: [{ messageId: 'placeInsideInit' }, { messageId: 'placeInsideInit' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';
|
||||
|
||||
export const NoTopLevelRelativeImportsInBackendModuleRule = ESLintUtils.RuleCreator.withoutDocs({
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description:
|
||||
'Relative imports in `.module.ts` files must be placed inside the `init` method. This ensures that module imports are loaded only when the module is used.',
|
||||
},
|
||||
messages: {
|
||||
placeInsideInit:
|
||||
"Place this relative import inside the `init` method, using `await import('./path')` syntax.",
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
defaultOptions: [],
|
||||
create(context) {
|
||||
return {
|
||||
'Program > ImportDeclaration'(node: TSESTree.ImportDeclaration) {
|
||||
if (node.source.value.startsWith('.')) {
|
||||
context.report({ node, messageId: 'placeInsideInit' });
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user