@nfq/eslint-config 4.0.0-beta.14 → 4.0.0-beta.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -18,7 +18,7 @@ const hexagonalDependencyDirection = createRule$8({
|
|
|
18
18
|
missingServiceClass: 'Client services must export a class implementing an adapter interface.',
|
|
19
19
|
missingServiceImplements: 'Client services classes must implement an adapter interface.',
|
|
20
20
|
serverDirection: 'Server dependency direction is domain → errors → services → controllers.',
|
|
21
|
-
uiApplicationImport: 'UI may only import from
|
|
21
|
+
uiApplicationImport: 'UI may only import from UI, shared, domain, or application useCases/configs/utils.'
|
|
22
22
|
},
|
|
23
23
|
schema: [],
|
|
24
24
|
type: 'problem'
|
|
@@ -153,6 +153,9 @@ const hexagonalDependencyDirection = createRule$8({
|
|
|
153
153
|
return;
|
|
154
154
|
}
|
|
155
155
|
if (currentLayer === 'client-application' || currentLayer === 'client-application-configs' || currentLayer === 'client-application-services' || currentLayer === 'client-application-usecases' || currentLayer === 'client-application-utils') {
|
|
156
|
+
if (targetLayer === 'client-shared') {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
156
159
|
if (targetLayer === 'client-ui') {
|
|
157
160
|
context.report({
|
|
158
161
|
messageId: 'crossBoundary',
|
|
@@ -162,7 +165,7 @@ const hexagonalDependencyDirection = createRule$8({
|
|
|
162
165
|
return;
|
|
163
166
|
}
|
|
164
167
|
if (currentLayer === 'client-ui') {
|
|
165
|
-
const allowed = targetLayer === 'client-ui' || targetLayer === 'client-shared' || targetLayer === 'client-application-configs' || targetLayer === 'client-application-usecases' || targetLayer === 'client-application-utils';
|
|
168
|
+
const allowed = targetLayer === 'client-ui' || targetLayer === 'client-shared' || targetLayer === 'client-domain' || targetLayer === 'client-application-configs' || targetLayer === 'client-application-usecases' || targetLayer === 'client-application-utils';
|
|
166
169
|
if (!allowed) {
|
|
167
170
|
context.report({
|
|
168
171
|
messageId: 'uiApplicationImport',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hexagonal-dependency-direction.js","sources":["../../../../src/rules/custom/hexagonal-dependency-direction.ts"],"sourcesContent":["import {ESLintUtils} from '@typescript-eslint/utils';\n\nimport {\n buildAliasMap,\n getLayer,\n isClientLayer,\n isClientUtils,\n isConfigLayer,\n isDomainLayer,\n isMobxStoreFile,\n isServerLayer,\n isServerUtils,\n normalizePath,\n readAliasesFromSettings,\n resolveAliasImport,\n resolveRelativeImport\n} from './utils/hexagonal-dependency-direction-utils';\n\nimport type {TSESLint, TSESTree} from '@typescript-eslint/utils';\n\nconst createRule = ESLintUtils.RuleCreator(\n name => `https://github.com/nfqde/eslint-config-nfq/blob/master/docs/rules/${name}.md`\n);\n\ntype RuleOptions = [];\n\ntype MessageIds = 'apiImport' | 'clientApiImport' | 'crossBoundary' | 'domainImport' | 'domainUtilsImport'\n | 'missingServiceClass' | 'missingServiceImplements' | 'serverDirection' | 'uiApplicationImport';\n\nconst requiredAliases = [\n 'Domain',\n 'Application',\n 'UI',\n 'ApiRoutes',\n 'Controllers',\n 'ServerDomain',\n 'Services'\n];\n\nexport const hexagonalDependencyDirection = createRule<RuleOptions, MessageIds>({\n defaultOptions: [],\n meta: {\n docs: {description: 'Enforce hexagonal dependency direction and boundary imports.'},\n messages: {\n apiImport: 'API routes may only import controllers, middleware, or server utils.',\n clientApiImport: 'Only client application services may import API routes.',\n crossBoundary: 'Client and server layers must not import from each other.',\n domainImport: 'Domain layers must not import from other layers.',\n domainUtilsImport: 'Domain layers must not import from utils.',\n missingServiceClass: 'Client services must export a class implementing an adapter interface.',\n missingServiceImplements: 'Client services classes must implement an adapter interface.',\n serverDirection: 'Server dependency direction is domain → errors → services → controllers.',\n // eslint-disable-next-line @stylistic/max-len\n uiApplicationImport: 'UI may only import from application/useCases, application/configs, or application/utils.'\n },\n schema: [],\n type: 'problem'\n },\n name: 'hexagonal-dependency-direction',\n /**\n * This function creates the ESLint rule listeners for validating hexagonal dependency direction.\n * It initializes alias resolution, determines the current file layer, and wires up import checks.\n * It also validates service class exports and adapter implementations for client services.\n *\n * @param context The rule context used for reporting and settings.\n * @returns A rule listener map for import declarations and program validation.\n *\n * @example\n * ```tsx\n * const rule = hexagonalDependencyDirection.create(context);\n * ```\n */\n // eslint-disable-next-line max-lines-per-function\n create(context) {\n const aliases = readAliasesFromSettings(context.settings);\n const aliasMap = buildAliasMap(aliases);\n\n const missing = requiredAliases.filter(alias => !aliasMap.has(alias));\n\n if (missing.length > 0) {\n return {};\n }\n\n const {filename} = context;\n const normalizedFile = normalizePath(filename);\n const currentLayer = getLayer(normalizedFile);\n\n if (currentLayer === 'unknown') {\n return {};\n }\n\n const isClient = isClientLayer(currentLayer);\n const isServer = isServerLayer(currentLayer);\n\n /**\n * This function validates that client application services export at least one class and that the class\n * implements an adapter interface. It scans the program body for class declarations and named exports\n * containing classes. It reports rule violations when no class is found or when a class lacks implemented\n * interfaces.\n *\n * @param program The program node to analyze for service class exports.\n *\n * @example\n * ```tsx\n * reportInvalidService(program);\n * ```\n */\n const reportInvalidService = (program: TSESTree.Program) => {\n if (currentLayer !== 'client-application-services') {\n return;\n }\n\n if (isMobxStoreFile(program)) {\n return;\n }\n\n const classes: (TSESTree.ClassDeclaration | TSESTree.ClassExpression)[] = [];\n\n for (const node of program.body) {\n // @ts-expect-error\n if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {\n classes.push(node);\n continue;\n }\n\n if (node.type === 'ExportNamedDeclaration' && node.declaration) {\n const {declaration} = node;\n\n // @ts-expect-error\n if (declaration.type === 'ClassDeclaration' || declaration.type === 'ClassExpression') {\n classes.push(declaration);\n }\n }\n }\n\n if (classes.length === 0) {\n context.report({\n messageId: 'missingServiceClass',\n node: program\n });\n\n return;\n }\n\n for (const classNode of classes) {\n if (classNode.implements.length === 0) {\n context.report({\n messageId: 'missingServiceImplements',\n node: classNode\n });\n }\n }\n };\n\n /**\n * Resolves an import source to a normalized path using aliases and relative paths. It first attempts to match\n * the source against configured aliases to ensure project-wide consistency. If no alias applies, it falls back\n * to resolving a relative import against the current file for local references.\n *\n * @param source The raw import source string from the import declaration.\n * @returns The resolved path string when resolution succeeds, or undefined when it cannot be resolved.\n *\n * @example\n * ```tsx\n * const resolved = resolveImport('@app/domain');\n * ```\n */\n const resolveImport = (source: string) => {\n const aliasResolved = resolveAliasImport(source, aliasMap);\n\n if (aliasResolved) {\n return aliasResolved;\n }\n\n return resolveRelativeImport(source, filename);\n };\n\n /**\n * Validates an import declaration against the hexagonal dependency rules for the current file.\n * It resolves the import source into a target layer and checks for forbidden cross-boundary or layer-specific dependencies.\n * It reports violations using the configured ESLint messages when a rule is broken.\n *\n * @param node The import declaration node to validate.\n *\n * @example\n * ```tsx\n * checkImport(node);\n * ```\n */\n // eslint-disable-next-line complexity\n const checkImport = (node: TSESTree.ImportDeclaration) => {\n const source = node.source.value;\n\n if (typeof source !== 'string') {\n return;\n }\n\n const resolved = resolveImport(source);\n\n if (!resolved) {\n return;\n }\n\n const targetLayer = getLayer(resolved);\n\n if (targetLayer === 'unknown') {\n return;\n }\n\n if (isClient && isServerLayer(targetLayer)) {\n context.report({\n messageId: 'crossBoundary',\n node\n });\n\n return;\n }\n\n if (isServer && isClientLayer(targetLayer)) {\n context.report({\n messageId: 'crossBoundary',\n node\n });\n\n return;\n }\n\n if (currentLayer === 'api') {\n const allowed = targetLayer === 'server-controllers'\n || targetLayer === 'server-middleware'\n || targetLayer === 'server-utils';\n\n if (!allowed) {\n context.report({\n messageId: 'apiImport',\n node\n });\n }\n\n return;\n }\n\n if (targetLayer === 'api' && currentLayer !== 'client-application-services') {\n context.report({\n messageId: 'clientApiImport',\n node\n });\n\n return;\n }\n\n if (isDomainLayer(currentLayer)) {\n if (targetLayer === 'client-shared' || targetLayer === 'server-configs') {\n return;\n }\n\n if (isConfigLayer(targetLayer)) {\n return;\n }\n\n if (isClientUtils(targetLayer) || isServerUtils(targetLayer)) {\n context.report({\n messageId: 'domainUtilsImport',\n node\n });\n\n return;\n }\n\n if (targetLayer !== currentLayer) {\n context.report({\n messageId: 'domainImport',\n node\n });\n }\n\n return;\n }\n\n if (currentLayer === 'client-application' || currentLayer === 'client-application-configs'\n || currentLayer === 'client-application-services'\n || currentLayer === 'client-application-usecases'\n || currentLayer === 'client-application-utils') {\n if (targetLayer === 'client-ui') {\n context.report({\n messageId: 'crossBoundary',\n node\n });\n }\n\n return;\n }\n\n if (currentLayer === 'client-ui') {\n const allowed = targetLayer === 'client-ui'\n || targetLayer === 'client-shared'\n || targetLayer === 'client-application-configs'\n || targetLayer === 'client-application-usecases'\n || targetLayer === 'client-application-utils';\n\n if (!allowed) {\n context.report({\n messageId: 'uiApplicationImport',\n node\n });\n }\n\n return;\n }\n\n if (currentLayer === 'server-errors') {\n const allowed = targetLayer === 'server-errors'\n || targetLayer === 'server-domain'\n || targetLayer === 'server-configs';\n\n if (!allowed) {\n context.report({\n messageId: 'serverDirection',\n node\n });\n }\n\n return;\n }\n\n if (currentLayer === 'server-services') {\n const allowed = targetLayer === 'server-services'\n || targetLayer === 'server-domain'\n || targetLayer === 'server-errors'\n || targetLayer === 'server-utils'\n || targetLayer === 'server-configs';\n\n if (!allowed) {\n context.report({\n messageId: 'serverDirection',\n node\n });\n }\n\n return;\n }\n\n if (currentLayer === 'server-controllers') {\n const allowed = targetLayer === 'server-controllers'\n || targetLayer === 'server-domain'\n || targetLayer === 'server-errors'\n || targetLayer === 'server-services'\n || targetLayer === 'server-utils'\n || targetLayer === 'server-middleware'\n || targetLayer === 'server-configs';\n\n if (!allowed) {\n context.report({\n messageId: 'serverDirection',\n node\n });\n }\n }\n };\n\n return {\n ImportDeclaration: checkImport,\n /**\n * Inspects constructor method definitions to detect assignments where a class method\n * is bound to `this` via `.bind(this)`, and reports such bindings for further handling.\n * This listener is triggered for the entire program to validate service class exports and adapter implementations\n * for client application services. It ensures that the hexagonal architecture rules are enforced at the program level.\n *\n * @param program The program node to analyze for service class export and implementation validation.\n */\n Program(program) {\n reportInvalidService(program);\n }\n } satisfies TSESLint.RuleListener;\n }\n});"],"names":["createRule","ESLintUtils","RuleCreator","name","requiredAliases","hexagonalDependencyDirection","defaultOptions","meta","docs","description","messages","apiImport","clientApiImport","crossBoundary","domainImport","domainUtilsImport","missingServiceClass","missingServiceImplements","serverDirection","uiApplicationImport","schema","type","create","context","aliases","readAliasesFromSettings","settings","aliasMap","buildAliasMap","missing","filter","alias","has","length","filename","normalizedFile","normalizePath","currentLayer","getLayer","isClient","isClientLayer","isServer","isServerLayer","reportInvalidService","program","isMobxStoreFile","classes","node","body","push","declaration","report","messageId","classNode","implements","resolveImport","source","aliasResolved","resolveAliasImport","resolveRelativeImport","checkImport","value","resolved","targetLayer","allowed","isDomainLayer","isConfigLayer","isClientUtils","isServerUtils","ImportDeclaration","Program"],"mappings":";;;AAoBA,MAAMA,YAAU,GAAGC,WAAW,CAACC,WAAW,CACtCC,IAAI,IAAI,CAAA,kEAAA,EAAqEA,IAAI,CAAA,GAAA,CACrF,CAAC;AAOD,MAAMC,eAAe,GAAG,CACpB,QAAQ,EACR,aAAa,EACb,IAAI,EACJ,WAAW,EACX,aAAa,EACb,cAAc,EACd,UAAU,CACb;AAEM,MAAMC,4BAA4B,GAAGL,YAAU,CAA0B;AAC5EM,EAAAA,cAAc,EAAE,EAAE;AAClBC,EAAAA,IAAI,EAAE;AACFC,IAAAA,IAAI,EAAE;AAACC,MAAAA,WAAW,EAAE;KAA+D;AACnFC,IAAAA,QAAQ,EAAE;AACNC,MAAAA,SAAS,EAAE,sEAAsE;AACjFC,MAAAA,eAAe,EAAE,yDAAyD;AAC1EC,MAAAA,aAAa,EAAE,2DAA2D;AAC1EC,MAAAA,YAAY,EAAE,kDAAkD;AAChEC,MAAAA,iBAAiB,EAAE,2CAA2C;AAC9DC,MAAAA,mBAAmB,EAAE,wEAAwE;AAC7FC,MAAAA,wBAAwB,EAAE,8DAA8D;AACxFC,MAAAA,eAAe,EAAE,0EAA0E;AAE3FC,MAAAA,mBAAmB,EAAE;KACxB;AACDC,IAAAA,MAAM,EAAE,EAAE;AACVC,IAAAA,IAAI,EAAE;GACT;AACDlB,EAAAA,IAAI,EAAE,gCAAgC;EAetCmB,MAAMA,CAACC,OAAO,EAAE;AACZ,IAAA,MAAMC,OAAO,GAAGC,uBAAuB,CAACF,OAAO,CAACG,QAAQ,CAAC;AACzD,IAAA,MAAMC,QAAQ,GAAGC,aAAa,CAACJ,OAAO,CAAC;AAEvC,IAAA,MAAMK,OAAO,GAAGzB,eAAe,CAAC0B,MAAM,CAACC,KAAK,IAAI,CAACJ,QAAQ,CAACK,GAAG,CAACD,KAAK,CAAC,CAAC;AAErE,IAAA,IAAIF,OAAO,CAACI,MAAM,GAAG,CAAC,EAAE;AACpB,MAAA,OAAO,EAAE;AACb,IAAA;IAEA,MAAM;AAACC,MAAAA;AAAQ,KAAC,GAAGX,OAAO;AAC1B,IAAA,MAAMY,cAAc,GAAGC,aAAa,CAACF,QAAQ,CAAC;AAC9C,IAAA,MAAMG,YAAY,GAAGC,QAAQ,CAACH,cAAc,CAAC;IAE7C,IAAIE,YAAY,KAAK,SAAS,EAAE;AAC5B,MAAA,OAAO,EAAE;AACb,IAAA;AAEA,IAAA,MAAME,QAAQ,GAAGC,aAAa,CAACH,YAAY,CAAC;AAC5C,IAAA,MAAMI,QAAQ,GAAGC,aAAa,CAACL,YAAY,CAAC;IAe5C,MAAMM,oBAAoB,GAAIC,OAAyB,IAAK;MACxD,IAAIP,YAAY,KAAK,6BAA6B,EAAE;AAChD,QAAA;AACJ,MAAA;AAEA,MAAA,IAAIQ,eAAe,CAACD,OAAO,CAAC,EAAE;AAC1B,QAAA;AACJ,MAAA;MAEA,MAAME,OAAiE,GAAG,EAAE;AAE5E,MAAA,KAAK,MAAMC,IAAI,IAAIH,OAAO,CAACI,IAAI,EAAE;QAE7B,IAAID,IAAI,CAAC1B,IAAI,KAAK,kBAAkB,IAAI0B,IAAI,CAAC1B,IAAI,KAAK,iBAAiB,EAAE;AACrEyB,UAAAA,OAAO,CAACG,IAAI,CAACF,IAAI,CAAC;AAClB,UAAA;AACJ,QAAA;QAEA,IAAIA,IAAI,CAAC1B,IAAI,KAAK,wBAAwB,IAAI0B,IAAI,CAACG,WAAW,EAAE;UAC5D,MAAM;AAACA,YAAAA;AAAW,WAAC,GAAGH,IAAI;UAG1B,IAAIG,WAAW,CAAC7B,IAAI,KAAK,kBAAkB,IAAI6B,WAAW,CAAC7B,IAAI,KAAK,iBAAiB,EAAE;AACnFyB,YAAAA,OAAO,CAACG,IAAI,CAACC,WAAW,CAAC;AAC7B,UAAA;AACJ,QAAA;AACJ,MAAA;AAEA,MAAA,IAAIJ,OAAO,CAACb,MAAM,KAAK,CAAC,EAAE;QACtBV,OAAO,CAAC4B,MAAM,CAAC;AACXC,UAAAA,SAAS,EAAE,qBAAqB;AAChCL,UAAAA,IAAI,EAAEH;AACV,SAAC,CAAC;AAEF,QAAA;AACJ,MAAA;AAEA,MAAA,KAAK,MAAMS,SAAS,IAAIP,OAAO,EAAE;AAC7B,QAAA,IAAIO,SAAS,CAACC,UAAU,CAACrB,MAAM,KAAK,CAAC,EAAE;UACnCV,OAAO,CAAC4B,MAAM,CAAC;AACXC,YAAAA,SAAS,EAAE,0BAA0B;AACrCL,YAAAA,IAAI,EAAEM;AACV,WAAC,CAAC;AACN,QAAA;AACJ,MAAA;IACJ,CAAC;IAeD,MAAME,aAAa,GAAIC,MAAc,IAAK;AACtC,MAAA,MAAMC,aAAa,GAAGC,kBAAkB,CAACF,MAAM,EAAE7B,QAAQ,CAAC;AAE1D,MAAA,IAAI8B,aAAa,EAAE;AACf,QAAA,OAAOA,aAAa;AACxB,MAAA;AAEA,MAAA,OAAOE,qBAAqB,CAACH,MAAM,EAAEtB,QAAQ,CAAC;IAClD,CAAC;IAeD,MAAM0B,WAAW,GAAIb,IAAgC,IAAK;AACtD,MAAA,MAAMS,MAAM,GAAGT,IAAI,CAACS,MAAM,CAACK,KAAK;AAEhC,MAAA,IAAI,OAAOL,MAAM,KAAK,QAAQ,EAAE;AAC5B,QAAA;AACJ,MAAA;AAEA,MAAA,MAAMM,QAAQ,GAAGP,aAAa,CAACC,MAAM,CAAC;MAEtC,IAAI,CAACM,QAAQ,EAAE;AACX,QAAA;AACJ,MAAA;AAEA,MAAA,MAAMC,WAAW,GAAGzB,QAAQ,CAACwB,QAAQ,CAAC;MAEtC,IAAIC,WAAW,KAAK,SAAS,EAAE;AAC3B,QAAA;AACJ,MAAA;AAEA,MAAA,IAAIxB,QAAQ,IAAIG,aAAa,CAACqB,WAAW,CAAC,EAAE;QACxCxC,OAAO,CAAC4B,MAAM,CAAC;AACXC,UAAAA,SAAS,EAAE,eAAe;AAC1BL,UAAAA;AACJ,SAAC,CAAC;AAEF,QAAA;AACJ,MAAA;AAEA,MAAA,IAAIN,QAAQ,IAAID,aAAa,CAACuB,WAAW,CAAC,EAAE;QACxCxC,OAAO,CAAC4B,MAAM,CAAC;AACXC,UAAAA,SAAS,EAAE,eAAe;AAC1BL,UAAAA;AACJ,SAAC,CAAC;AAEF,QAAA;AACJ,MAAA;MAEA,IAAIV,YAAY,KAAK,KAAK,EAAE;AACxB,QAAA,MAAM2B,OAAO,GAAGD,WAAW,KAAK,oBAAoB,IAC7CA,WAAW,KAAK,mBAAmB,IACnCA,WAAW,KAAK,cAAc;QAErC,IAAI,CAACC,OAAO,EAAE;UACVzC,OAAO,CAAC4B,MAAM,CAAC;AACXC,YAAAA,SAAS,EAAE,WAAW;AACtBL,YAAAA;AACJ,WAAC,CAAC;AACN,QAAA;AAEA,QAAA;AACJ,MAAA;AAEA,MAAA,IAAIgB,WAAW,KAAK,KAAK,IAAI1B,YAAY,KAAK,6BAA6B,EAAE;QACzEd,OAAO,CAAC4B,MAAM,CAAC;AACXC,UAAAA,SAAS,EAAE,iBAAiB;AAC5BL,UAAAA;AACJ,SAAC,CAAC;AAEF,QAAA;AACJ,MAAA;AAEA,MAAA,IAAIkB,aAAa,CAAC5B,YAAY,CAAC,EAAE;AAC7B,QAAA,IAAI0B,WAAW,KAAK,eAAe,IAAIA,WAAW,KAAK,gBAAgB,EAAE;AACrE,UAAA;AACJ,QAAA;AAEA,QAAA,IAAIG,aAAa,CAACH,WAAW,CAAC,EAAE;AAC5B,UAAA;AACJ,QAAA;QAEA,IAAII,aAAa,CAACJ,WAAW,CAAC,IAAIK,aAAa,CAACL,WAAW,CAAC,EAAE;UAC1DxC,OAAO,CAAC4B,MAAM,CAAC;AACXC,YAAAA,SAAS,EAAE,mBAAmB;AAC9BL,YAAAA;AACJ,WAAC,CAAC;AAEF,UAAA;AACJ,QAAA;QAEA,IAAIgB,WAAW,KAAK1B,YAAY,EAAE;UAC9Bd,OAAO,CAAC4B,MAAM,CAAC;AACXC,YAAAA,SAAS,EAAE,cAAc;AACzBL,YAAAA;AACJ,WAAC,CAAC;AACN,QAAA;AAEA,QAAA;AACJ,MAAA;AAEA,MAAA,IAAIV,YAAY,KAAK,oBAAoB,IAAIA,YAAY,KAAK,4BAA4B,IACnFA,YAAY,KAAK,6BAA6B,IAC9CA,YAAY,KAAK,6BAA6B,IAC9CA,YAAY,KAAK,0BAA0B,EAAE;QAChD,IAAI0B,WAAW,KAAK,WAAW,EAAE;UAC7BxC,OAAO,CAAC4B,MAAM,CAAC;AACXC,YAAAA,SAAS,EAAE,eAAe;AAC1BL,YAAAA;AACJ,WAAC,CAAC;AACN,QAAA;AAEA,QAAA;AACJ,MAAA;MAEA,IAAIV,YAAY,KAAK,WAAW,EAAE;QAC9B,MAAM2B,OAAO,GAAGD,WAAW,KAAK,WAAW,IACpCA,WAAW,KAAK,eAAe,IAC/BA,WAAW,KAAK,4BAA4B,IAC5CA,WAAW,KAAK,6BAA6B,IAC7CA,WAAW,KAAK,0BAA0B;QAEjD,IAAI,CAACC,OAAO,EAAE;UACVzC,OAAO,CAAC4B,MAAM,CAAC;AACXC,YAAAA,SAAS,EAAE,qBAAqB;AAChCL,YAAAA;AACJ,WAAC,CAAC;AACN,QAAA;AAEA,QAAA;AACJ,MAAA;MAEA,IAAIV,YAAY,KAAK,eAAe,EAAE;AAClC,QAAA,MAAM2B,OAAO,GAAGD,WAAW,KAAK,eAAe,IACxCA,WAAW,KAAK,eAAe,IAC/BA,WAAW,KAAK,gBAAgB;QAEvC,IAAI,CAACC,OAAO,EAAE;UACVzC,OAAO,CAAC4B,MAAM,CAAC;AACXC,YAAAA,SAAS,EAAE,iBAAiB;AAC5BL,YAAAA;AACJ,WAAC,CAAC;AACN,QAAA;AAEA,QAAA;AACJ,MAAA;MAEA,IAAIV,YAAY,KAAK,iBAAiB,EAAE;QACpC,MAAM2B,OAAO,GAAGD,WAAW,KAAK,iBAAiB,IAC1CA,WAAW,KAAK,eAAe,IAC/BA,WAAW,KAAK,eAAe,IAC/BA,WAAW,KAAK,cAAc,IAC9BA,WAAW,KAAK,gBAAgB;QAEvC,IAAI,CAACC,OAAO,EAAE;UACVzC,OAAO,CAAC4B,MAAM,CAAC;AACXC,YAAAA,SAAS,EAAE,iBAAiB;AAC5BL,YAAAA;AACJ,WAAC,CAAC;AACN,QAAA;AAEA,QAAA;AACJ,MAAA;MAEA,IAAIV,YAAY,KAAK,oBAAoB,EAAE;AACvC,QAAA,MAAM2B,OAAO,GAAGD,WAAW,KAAK,oBAAoB,IAC7CA,WAAW,KAAK,eAAe,IAC/BA,WAAW,KAAK,eAAe,IAC/BA,WAAW,KAAK,iBAAiB,IACjCA,WAAW,KAAK,cAAc,IAC9BA,WAAW,KAAK,mBAAmB,IACnCA,WAAW,KAAK,gBAAgB;QAEvC,IAAI,CAACC,OAAO,EAAE;UACVzC,OAAO,CAAC4B,MAAM,CAAC;AACXC,YAAAA,SAAS,EAAE,iBAAiB;AAC5BL,YAAAA;AACJ,WAAC,CAAC;AACN,QAAA;AACJ,MAAA;IACJ,CAAC;IAED,OAAO;AACHsB,MAAAA,iBAAiB,EAAET,WAAW;MAS9BU,OAAOA,CAAC1B,OAAO,EAAE;QACbD,oBAAoB,CAACC,OAAO,CAAC;AACjC,MAAA;KACH;AACL,EAAA;AACJ,CAAC;;;;"}
|
|
1
|
+
{"version":3,"file":"hexagonal-dependency-direction.js","sources":["../../../../src/rules/custom/hexagonal-dependency-direction.ts"],"sourcesContent":["import {ESLintUtils} from '@typescript-eslint/utils';\n\nimport {\n buildAliasMap,\n getLayer,\n isClientLayer,\n isClientUtils,\n isConfigLayer,\n isDomainLayer,\n isMobxStoreFile,\n isServerLayer,\n isServerUtils,\n normalizePath,\n readAliasesFromSettings,\n resolveAliasImport,\n resolveRelativeImport\n} from './utils/hexagonal-dependency-direction-utils';\n\nimport type {TSESLint, TSESTree} from '@typescript-eslint/utils';\n\nconst createRule = ESLintUtils.RuleCreator(\n name => `https://github.com/nfqde/eslint-config-nfq/blob/master/docs/rules/${name}.md`\n);\n\ntype RuleOptions = [];\n\ntype MessageIds = 'apiImport' | 'clientApiImport' | 'crossBoundary' | 'domainImport' | 'domainUtilsImport'\n | 'missingServiceClass' | 'missingServiceImplements' | 'serverDirection' | 'uiApplicationImport';\n\nconst requiredAliases = [\n 'Domain',\n 'Application',\n 'UI',\n 'ApiRoutes',\n 'Controllers',\n 'ServerDomain',\n 'Services'\n];\n\nexport const hexagonalDependencyDirection = createRule<RuleOptions, MessageIds>({\n defaultOptions: [],\n meta: {\n docs: {description: 'Enforce hexagonal dependency direction and boundary imports.'},\n messages: {\n apiImport: 'API routes may only import controllers, middleware, or server utils.',\n clientApiImport: 'Only client application services may import API routes.',\n crossBoundary: 'Client and server layers must not import from each other.',\n domainImport: 'Domain layers must not import from other layers.',\n domainUtilsImport: 'Domain layers must not import from utils.',\n missingServiceClass: 'Client services must export a class implementing an adapter interface.',\n missingServiceImplements: 'Client services classes must implement an adapter interface.',\n serverDirection: 'Server dependency direction is domain → errors → services → controllers.',\n // eslint-disable-next-line @stylistic/max-len\n uiApplicationImport: 'UI may only import from UI, shared, domain, or application useCases/configs/utils.'\n },\n schema: [],\n type: 'problem'\n },\n name: 'hexagonal-dependency-direction',\n /**\n * This function creates the ESLint rule listeners for validating hexagonal dependency direction.\n * It initializes alias resolution, determines the current file layer, and wires up import checks.\n * It also validates service class exports and adapter implementations for client services.\n *\n * @param context The rule context used for reporting and settings.\n * @returns A rule listener map for import declarations and program validation.\n *\n * @example\n * ```tsx\n * const rule = hexagonalDependencyDirection.create(context);\n * ```\n */\n // eslint-disable-next-line max-lines-per-function\n create(context) {\n const aliases = readAliasesFromSettings(context.settings);\n const aliasMap = buildAliasMap(aliases);\n\n const missing = requiredAliases.filter(alias => !aliasMap.has(alias));\n\n if (missing.length > 0) {\n return {};\n }\n\n const {filename} = context;\n const normalizedFile = normalizePath(filename);\n const currentLayer = getLayer(normalizedFile);\n\n if (currentLayer === 'unknown') {\n return {};\n }\n\n const isClient = isClientLayer(currentLayer);\n const isServer = isServerLayer(currentLayer);\n\n /**\n * This function validates that client application services export at least one class and that the class\n * implements an adapter interface. It scans the program body for class declarations and named exports\n * containing classes. It reports rule violations when no class is found or when a class lacks implemented\n * interfaces.\n *\n * @param program The program node to analyze for service class exports.\n *\n * @example\n * ```tsx\n * reportInvalidService(program);\n * ```\n */\n const reportInvalidService = (program: TSESTree.Program) => {\n if (currentLayer !== 'client-application-services') {\n return;\n }\n\n if (isMobxStoreFile(program)) {\n return;\n }\n\n const classes: (TSESTree.ClassDeclaration | TSESTree.ClassExpression)[] = [];\n\n for (const node of program.body) {\n // @ts-expect-error\n if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {\n classes.push(node);\n continue;\n }\n\n if (node.type === 'ExportNamedDeclaration' && node.declaration) {\n const {declaration} = node;\n\n // @ts-expect-error\n if (declaration.type === 'ClassDeclaration' || declaration.type === 'ClassExpression') {\n classes.push(declaration);\n }\n }\n }\n\n if (classes.length === 0) {\n context.report({\n messageId: 'missingServiceClass',\n node: program\n });\n\n return;\n }\n\n for (const classNode of classes) {\n if (classNode.implements.length === 0) {\n context.report({\n messageId: 'missingServiceImplements',\n node: classNode\n });\n }\n }\n };\n\n /**\n * Resolves an import source to a normalized path using aliases and relative paths. It first attempts to match\n * the source against configured aliases to ensure project-wide consistency. If no alias applies, it falls back\n * to resolving a relative import against the current file for local references.\n *\n * @param source The raw import source string from the import declaration.\n * @returns The resolved path string when resolution succeeds, or undefined when it cannot be resolved.\n *\n * @example\n * ```tsx\n * const resolved = resolveImport('@app/domain');\n * ```\n */\n const resolveImport = (source: string) => {\n const aliasResolved = resolveAliasImport(source, aliasMap);\n\n if (aliasResolved) {\n return aliasResolved;\n }\n\n return resolveRelativeImport(source, filename);\n };\n\n /**\n * Validates an import declaration against the hexagonal dependency rules for the current file.\n * It resolves the import source into a target layer and checks for forbidden cross-boundary or layer-specific dependencies.\n * It reports violations using the configured ESLint messages when a rule is broken.\n *\n * @param node The import declaration node to validate.\n *\n * @example\n * ```tsx\n * checkImport(node);\n * ```\n */\n // eslint-disable-next-line complexity\n const checkImport = (node: TSESTree.ImportDeclaration) => {\n const source = node.source.value;\n\n if (typeof source !== 'string') {\n return;\n }\n\n const resolved = resolveImport(source);\n\n if (!resolved) {\n return;\n }\n\n const targetLayer = getLayer(resolved);\n\n if (targetLayer === 'unknown') {\n return;\n }\n\n if (isClient && isServerLayer(targetLayer)) {\n context.report({\n messageId: 'crossBoundary',\n node\n });\n\n return;\n }\n\n if (isServer && isClientLayer(targetLayer)) {\n context.report({\n messageId: 'crossBoundary',\n node\n });\n\n return;\n }\n\n if (currentLayer === 'api') {\n const allowed = targetLayer === 'server-controllers'\n || targetLayer === 'server-middleware'\n || targetLayer === 'server-utils';\n\n if (!allowed) {\n context.report({\n messageId: 'apiImport',\n node\n });\n }\n\n return;\n }\n\n if (targetLayer === 'api' && currentLayer !== 'client-application-services') {\n context.report({\n messageId: 'clientApiImport',\n node\n });\n\n return;\n }\n\n if (isDomainLayer(currentLayer)) {\n if (targetLayer === 'client-shared' || targetLayer === 'server-configs') {\n return;\n }\n\n if (isConfigLayer(targetLayer)) {\n return;\n }\n\n if (isClientUtils(targetLayer) || isServerUtils(targetLayer)) {\n context.report({\n messageId: 'domainUtilsImport',\n node\n });\n\n return;\n }\n\n if (targetLayer !== currentLayer) {\n context.report({\n messageId: 'domainImport',\n node\n });\n }\n\n return;\n }\n\n if (currentLayer === 'client-application' || currentLayer === 'client-application-configs'\n || currentLayer === 'client-application-services'\n || currentLayer === 'client-application-usecases'\n || currentLayer === 'client-application-utils') {\n if (targetLayer === 'client-shared') {\n return;\n }\n\n if (targetLayer === 'client-ui') {\n context.report({\n messageId: 'crossBoundary',\n node\n });\n }\n\n return;\n }\n\n if (currentLayer === 'client-ui') {\n const allowed = targetLayer === 'client-ui'\n || targetLayer === 'client-shared'\n || targetLayer === 'client-domain'\n || targetLayer === 'client-application-configs'\n || targetLayer === 'client-application-usecases'\n || targetLayer === 'client-application-utils';\n\n if (!allowed) {\n context.report({\n messageId: 'uiApplicationImport',\n node\n });\n }\n\n return;\n }\n\n if (currentLayer === 'server-errors') {\n const allowed = targetLayer === 'server-errors'\n || targetLayer === 'server-domain'\n || targetLayer === 'server-configs';\n\n if (!allowed) {\n context.report({\n messageId: 'serverDirection',\n node\n });\n }\n\n return;\n }\n\n if (currentLayer === 'server-services') {\n const allowed = targetLayer === 'server-services'\n || targetLayer === 'server-domain'\n || targetLayer === 'server-errors'\n || targetLayer === 'server-utils'\n || targetLayer === 'server-configs';\n\n if (!allowed) {\n context.report({\n messageId: 'serverDirection',\n node\n });\n }\n\n return;\n }\n\n if (currentLayer === 'server-controllers') {\n const allowed = targetLayer === 'server-controllers'\n || targetLayer === 'server-domain'\n || targetLayer === 'server-errors'\n || targetLayer === 'server-services'\n || targetLayer === 'server-utils'\n || targetLayer === 'server-middleware'\n || targetLayer === 'server-configs';\n\n if (!allowed) {\n context.report({\n messageId: 'serverDirection',\n node\n });\n }\n }\n };\n\n return {\n ImportDeclaration: checkImport,\n /**\n * Inspects constructor method definitions to detect assignments where a class method\n * is bound to `this` via `.bind(this)`, and reports such bindings for further handling.\n * This listener is triggered for the entire program to validate service class exports and adapter implementations\n * for client application services. It ensures that the hexagonal architecture rules are enforced at the program level.\n *\n * @param program The program node to analyze for service class export and implementation validation.\n */\n Program(program) {\n reportInvalidService(program);\n }\n } satisfies TSESLint.RuleListener;\n }\n});"],"names":["createRule","ESLintUtils","RuleCreator","name","requiredAliases","hexagonalDependencyDirection","defaultOptions","meta","docs","description","messages","apiImport","clientApiImport","crossBoundary","domainImport","domainUtilsImport","missingServiceClass","missingServiceImplements","serverDirection","uiApplicationImport","schema","type","create","context","aliases","readAliasesFromSettings","settings","aliasMap","buildAliasMap","missing","filter","alias","has","length","filename","normalizedFile","normalizePath","currentLayer","getLayer","isClient","isClientLayer","isServer","isServerLayer","reportInvalidService","program","isMobxStoreFile","classes","node","body","push","declaration","report","messageId","classNode","implements","resolveImport","source","aliasResolved","resolveAliasImport","resolveRelativeImport","checkImport","value","resolved","targetLayer","allowed","isDomainLayer","isConfigLayer","isClientUtils","isServerUtils","ImportDeclaration","Program"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -3117,7 +3117,7 @@ const hexagonalDependencyDirection = createRule$8({
|
|
|
3117
3117
|
missingServiceClass: 'Client services must export a class implementing an adapter interface.',
|
|
3118
3118
|
missingServiceImplements: 'Client services classes must implement an adapter interface.',
|
|
3119
3119
|
serverDirection: 'Server dependency direction is domain → errors → services → controllers.',
|
|
3120
|
-
uiApplicationImport: 'UI may only import from
|
|
3120
|
+
uiApplicationImport: 'UI may only import from UI, shared, domain, or application useCases/configs/utils.'
|
|
3121
3121
|
},
|
|
3122
3122
|
schema: [],
|
|
3123
3123
|
type: 'problem'
|
|
@@ -3252,6 +3252,9 @@ const hexagonalDependencyDirection = createRule$8({
|
|
|
3252
3252
|
return;
|
|
3253
3253
|
}
|
|
3254
3254
|
if (currentLayer === 'client-application' || currentLayer === 'client-application-configs' || currentLayer === 'client-application-services' || currentLayer === 'client-application-usecases' || currentLayer === 'client-application-utils') {
|
|
3255
|
+
if (targetLayer === 'client-shared') {
|
|
3256
|
+
return;
|
|
3257
|
+
}
|
|
3255
3258
|
if (targetLayer === 'client-ui') {
|
|
3256
3259
|
context.report({
|
|
3257
3260
|
messageId: 'crossBoundary',
|
|
@@ -3261,7 +3264,7 @@ const hexagonalDependencyDirection = createRule$8({
|
|
|
3261
3264
|
return;
|
|
3262
3265
|
}
|
|
3263
3266
|
if (currentLayer === 'client-ui') {
|
|
3264
|
-
const allowed = targetLayer === 'client-ui' || targetLayer === 'client-shared' || targetLayer === 'client-application-configs' || targetLayer === 'client-application-usecases' || targetLayer === 'client-application-utils';
|
|
3267
|
+
const allowed = targetLayer === 'client-ui' || targetLayer === 'client-shared' || targetLayer === 'client-domain' || targetLayer === 'client-application-configs' || targetLayer === 'client-application-usecases' || targetLayer === 'client-application-utils';
|
|
3265
3268
|
if (!allowed) {
|
|
3266
3269
|
context.report({
|
|
3267
3270
|
messageId: 'uiApplicationImport',
|