@archest/vitest 1.0.0 → 1.0.1

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.
Files changed (168) hide show
  1. package/dist/index.js +13 -17
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +776 -489
  4. package/dist/index.mjs.map +1 -1
  5. package/dist/src/core/classes/classCheckExtendClass.d.ts +3 -0
  6. package/dist/src/core/classes/classCheckHaveMaxCyclomaticComplexity.d.ts +3 -0
  7. package/dist/src/core/classes/classCheckHaveModifier.d.ts +3 -0
  8. package/dist/src/core/classes/classCheckHaveNameMatchingFileName.d.ts +3 -0
  9. package/dist/src/core/classes/classCheckImplementInterface.d.ts +3 -0
  10. package/dist/src/core/classes/classCheckMatchNamePattern.d.ts +3 -0
  11. package/dist/src/core/classes/classCheckResideInFolder.d.ts +3 -0
  12. package/dist/src/core/classes/locateClasses.d.ts +3 -0
  13. package/dist/src/core/classes/types.d.ts +14 -0
  14. package/dist/src/core/files/checkDependOnFilesInFolder.d.ts +3 -0
  15. package/dist/src/core/files/fileCheckBeFreeOfCycles.d.ts +3 -0
  16. package/dist/src/core/files/fileCheckHaveMaxCyclomaticComplexity.d.ts +3 -0
  17. package/dist/src/core/files/fileCheckHaveMaxExportedFunctions.d.ts +3 -0
  18. package/dist/src/core/files/fileCheckHaveMinMaintainabilityIndex.d.ts +3 -0
  19. package/dist/src/core/files/fileCheckMatchNamePattern.d.ts +3 -0
  20. package/dist/src/core/files/getFileDependencies.d.ts +2 -0
  21. package/dist/src/core/files/locateFiles.d.ts +3 -0
  22. package/dist/src/core/files/types.d.ts +10 -0
  23. package/dist/src/core/functions/functionCheckHaveExplicitReturnType.d.ts +3 -0
  24. package/dist/src/core/functions/functionCheckHaveMaxCyclomaticComplexity.d.ts +3 -0
  25. package/dist/src/core/functions/functionCheckHaveMinMaintainabilityIndex.d.ts +3 -0
  26. package/dist/src/core/functions/functionCheckHaveModifier.d.ts +3 -0
  27. package/dist/src/core/functions/functionCheckHaveNameMatchingFileName.d.ts +3 -0
  28. package/dist/src/core/functions/functionCheckMatchNamePattern.d.ts +3 -0
  29. package/dist/src/core/functions/locateFunctions.d.ts +3 -0
  30. package/dist/src/core/functions/types.d.ts +11 -0
  31. package/dist/src/core/layers/checkLayeredArchitecture.d.ts +3 -0
  32. package/dist/src/core/layers/createLayeredArchitecture.d.ts +3 -0
  33. package/dist/src/core/layers/getLayerDependencies.d.ts +2 -0
  34. package/dist/src/core/layers/layer.d.ts +2 -0
  35. package/dist/src/core/layers/layerShouldNotBeAccessedByAnyLayer.d.ts +2 -0
  36. package/dist/src/core/layers/layerShouldOnlyBeAccessedBy.d.ts +2 -0
  37. package/dist/src/core/layers/types.d.ts +8 -0
  38. package/dist/src/core/metrics/calculateCyclomaticComplexity.d.ts +2 -0
  39. package/dist/src/core/metrics/calculateMaintainabilityIndex.d.ts +2 -0
  40. package/dist/src/core/project/parseProject.d.ts +22 -0
  41. package/dist/src/core/properties/locateProperties.d.ts +3 -0
  42. package/dist/src/core/properties/propertyCheckBeReadonly.d.ts +3 -0
  43. package/dist/src/core/properties/types.d.ts +10 -0
  44. package/dist/src/core/shared/sharedCheckHaveMaxCyclomaticComplexity.d.ts +1 -0
  45. package/dist/src/core/shared/sharedCheckHaveMinMaintainabilityIndex.d.ts +1 -0
  46. package/dist/src/core/shared/sharedCheckHaveModifier.d.ts +2 -0
  47. package/dist/src/core/shared/sharedCheckHaveNameMatchingFileName.d.ts +2 -0
  48. package/dist/src/core/shared/sharedCheckMatchNamePattern.d.ts +1 -0
  49. package/dist/src/core/slices/locateSlices.d.ts +3 -0
  50. package/dist/src/core/slices/sliceCheckBeFreeOfCycles.d.ts +3 -0
  51. package/dist/src/core/slices/sliceCheckHaveMaxDistanceFromMainSequence.d.ts +3 -0
  52. package/dist/src/core/slices/types.d.ts +8 -0
  53. package/dist/src/core/types.d.ts +7 -0
  54. package/dist/src/core/utils/ruleBuilder.d.ts +6 -0
  55. package/dist/src/index.d.ts +34 -8
  56. package/dist/src/matchers/index.d.ts +5 -0
  57. package/package.json +1 -1
  58. package/src/core/classes/classCheckExtendClass.test.ts +30 -0
  59. package/src/core/classes/classCheckExtendClass.ts +41 -0
  60. package/src/core/classes/classCheckHaveMaxCyclomaticComplexity.test.ts +8 -0
  61. package/src/core/classes/classCheckHaveMaxCyclomaticComplexity.ts +35 -0
  62. package/src/core/classes/classCheckHaveModifier.test.ts +30 -0
  63. package/src/core/classes/classCheckHaveModifier.ts +17 -0
  64. package/src/core/classes/classCheckHaveNameMatchingFileName.test.ts +30 -0
  65. package/src/core/classes/classCheckHaveNameMatchingFileName.ts +15 -0
  66. package/src/core/classes/classCheckImplementInterface.test.ts +30 -0
  67. package/src/core/classes/classCheckImplementInterface.ts +43 -0
  68. package/src/core/classes/classCheckMatchNamePattern.test.ts +8 -0
  69. package/src/core/classes/classCheckMatchNamePattern.ts +17 -0
  70. package/src/core/classes/classCheckResideInFolder.test.ts +8 -0
  71. package/src/core/classes/classCheckResideInFolder.ts +31 -0
  72. package/src/core/classes/locateClasses.test.ts +13 -0
  73. package/src/core/classes/locateClasses.ts +127 -0
  74. package/src/core/classes/types.ts +16 -0
  75. package/src/core/files/checkDependOnFilesInFolder.test.ts +7 -0
  76. package/src/core/files/checkDependOnFilesInFolder.ts +36 -0
  77. package/src/core/files/fileCheckBeFreeOfCycles.test.ts +7 -0
  78. package/src/core/files/fileCheckBeFreeOfCycles.ts +72 -0
  79. package/src/core/files/fileCheckHaveMaxCyclomaticComplexity.test.ts +8 -0
  80. package/src/core/files/fileCheckHaveMaxCyclomaticComplexity.ts +35 -0
  81. package/src/core/files/fileCheckHaveMaxExportedFunctions.test.ts +32 -0
  82. package/src/core/files/fileCheckHaveMaxExportedFunctions.ts +45 -0
  83. package/src/core/files/fileCheckHaveMinMaintainabilityIndex.test.ts +8 -0
  84. package/src/core/files/fileCheckHaveMinMaintainabilityIndex.ts +19 -0
  85. package/src/core/files/fileCheckMatchNamePattern.test.ts +30 -0
  86. package/src/core/files/fileCheckMatchNamePattern.ts +17 -0
  87. package/src/core/files/getFileDependencies.ts +43 -0
  88. package/src/core/files/locateFiles.test.ts +30 -0
  89. package/src/core/files/locateFiles.ts +31 -0
  90. package/src/core/files/types.ts +12 -0
  91. package/src/core/functions/functionCheckHaveExplicitReturnType.test.ts +30 -0
  92. package/src/core/functions/functionCheckHaveExplicitReturnType.ts +29 -0
  93. package/src/core/functions/functionCheckHaveMaxCyclomaticComplexity.test.ts +8 -0
  94. package/src/core/functions/functionCheckHaveMaxCyclomaticComplexity.ts +20 -0
  95. package/src/core/functions/functionCheckHaveMinMaintainabilityIndex.test.ts +8 -0
  96. package/src/core/functions/functionCheckHaveMinMaintainabilityIndex.ts +20 -0
  97. package/src/core/functions/functionCheckHaveModifier.test.ts +30 -0
  98. package/src/core/functions/functionCheckHaveModifier.ts +18 -0
  99. package/src/core/functions/functionCheckHaveNameMatchingFileName.test.ts +30 -0
  100. package/src/core/functions/functionCheckHaveNameMatchingFileName.ts +16 -0
  101. package/src/core/functions/functionCheckMatchNamePattern.test.ts +30 -0
  102. package/src/core/functions/functionCheckMatchNamePattern.ts +18 -0
  103. package/src/core/functions/locateFunctions.test.ts +16 -0
  104. package/src/core/functions/locateFunctions.ts +47 -0
  105. package/src/core/functions/types.ts +13 -0
  106. package/src/core/layers/checkLayeredArchitecture.test.ts +23 -0
  107. package/src/core/layers/checkLayeredArchitecture.ts +15 -0
  108. package/src/core/layers/createLayeredArchitecture.test.ts +13 -0
  109. package/src/core/layers/createLayeredArchitecture.ts +15 -0
  110. package/src/core/layers/getLayerDependencies.ts +45 -0
  111. package/src/core/layers/layer.test.ts +12 -0
  112. package/src/core/layers/layer.ts +10 -0
  113. package/src/core/layers/layerShouldNotBeAccessedByAnyLayer.test.ts +14 -0
  114. package/src/core/layers/layerShouldNotBeAccessedByAnyLayer.ts +40 -0
  115. package/src/core/layers/layerShouldOnlyBeAccessedBy.test.ts +15 -0
  116. package/src/core/layers/layerShouldOnlyBeAccessedBy.ts +48 -0
  117. package/src/core/layers/types.ts +9 -0
  118. package/src/core/metrics/calculateCyclomaticComplexity.test.ts +23 -0
  119. package/src/core/metrics/calculateCyclomaticComplexity.ts +36 -0
  120. package/src/core/metrics/calculateMaintainabilityIndex.test.ts +38 -0
  121. package/src/core/metrics/calculateMaintainabilityIndex.ts +47 -0
  122. package/src/core/project/parseProject.test.ts +19 -0
  123. package/src/core/project/parseProject.ts +216 -0
  124. package/src/core/properties/locateProperties.test.ts +19 -0
  125. package/src/core/properties/locateProperties.ts +41 -0
  126. package/src/core/properties/propertyCheckBeReadonly.test.ts +32 -0
  127. package/src/core/properties/propertyCheckBeReadonly.ts +27 -0
  128. package/src/core/properties/types.ts +12 -0
  129. package/src/core/shared/sharedCheckHaveMaxCyclomaticComplexity.test.ts +30 -0
  130. package/src/core/shared/sharedCheckHaveMaxCyclomaticComplexity.ts +23 -0
  131. package/src/core/shared/sharedCheckHaveMinMaintainabilityIndex.test.ts +30 -0
  132. package/src/core/shared/sharedCheckHaveMinMaintainabilityIndex.ts +23 -0
  133. package/src/core/shared/sharedCheckHaveModifier.test.ts +31 -0
  134. package/src/core/shared/sharedCheckHaveModifier.ts +51 -0
  135. package/src/core/shared/sharedCheckHaveNameMatchingFileName.test.ts +17 -0
  136. package/src/core/shared/sharedCheckHaveNameMatchingFileName.ts +36 -0
  137. package/src/core/shared/sharedCheckMatchNamePattern.test.ts +29 -0
  138. package/src/core/shared/sharedCheckMatchNamePattern.ts +22 -0
  139. package/src/core/slices/locateSlices.test.ts +22 -0
  140. package/src/core/slices/locateSlices.ts +35 -0
  141. package/src/core/slices/sliceCheckBeFreeOfCycles.test.ts +26 -0
  142. package/src/core/slices/sliceCheckBeFreeOfCycles.ts +91 -0
  143. package/src/core/slices/sliceCheckHaveMaxDistanceFromMainSequence.test.ts +27 -0
  144. package/src/core/slices/sliceCheckHaveMaxDistanceFromMainSequence.ts +110 -0
  145. package/src/core/slices/types.ts +9 -0
  146. package/src/core/testUtils.ts +61 -0
  147. package/src/core/types.ts +15 -0
  148. package/src/core/utils/ruleBuilder.test.ts +24 -0
  149. package/src/core/utils/ruleBuilder.ts +28 -0
  150. package/src/index.ts +34 -8
  151. package/src/matchers/index.ts +269 -51
  152. package/test/architecture.test.ts +109 -16
  153. package/tsconfig.json +2 -1
  154. package/vite.config.ts +2 -2
  155. package/dist/src/core/ClassLocator.d.ts +0 -20
  156. package/dist/src/core/FileLocator.d.ts +0 -15
  157. package/dist/src/core/FunctionLocator.d.ts +0 -15
  158. package/dist/src/core/LayerLocator.d.ts +0 -23
  159. package/dist/src/core/Project.d.ts +0 -20
  160. package/dist/src/core/PropertyLocator.d.ts +0 -12
  161. package/dist/src/core/SliceLocator.d.ts +0 -10
  162. package/src/core/ClassLocator.ts +0 -226
  163. package/src/core/FileLocator.ts +0 -168
  164. package/src/core/FunctionLocator.ts +0 -105
  165. package/src/core/LayerLocator.ts +0 -119
  166. package/src/core/Project.ts +0 -171
  167. package/src/core/PropertyLocator.ts +0 -50
  168. package/src/core/SliceLocator.ts +0 -92
@@ -0,0 +1,3 @@
1
+ import { PropertyLocatorData, PropertyQueryOptions } from './types';
2
+ import * as ts from 'typescript';
3
+ export declare function locateProperties(properties: ts.PropertyDeclaration[], program: ts.Program, options?: PropertyQueryOptions): PropertyLocatorData;
@@ -0,0 +1,3 @@
1
+ import { RuleResult } from '../types';
2
+ import { PropertyLocatorData } from './types';
3
+ export declare function propertyCheckBeReadonly(locator: PropertyLocatorData, isNot: boolean): RuleResult;
@@ -0,0 +1,10 @@
1
+ import type * as ts from 'typescript';
2
+ export interface PropertyLocatorData {
3
+ type: 'PropertyLocator';
4
+ properties: ts.PropertyDeclaration[];
5
+ program: ts.Program;
6
+ }
7
+ export interface PropertyQueryOptions {
8
+ inFolder?: string;
9
+ matchNamePattern?: string | RegExp;
10
+ }
@@ -0,0 +1 @@
1
+ export declare function sharedCheckHaveMaxCyclomaticComplexity<T>(items: T[], getName: (item: T) => string | undefined, getComplexity: (item: T) => number, label: string, max: number, isNot: boolean): import('../types').RuleResult;
@@ -0,0 +1 @@
1
+ export declare function sharedCheckHaveMinMaintainabilityIndex<T>(items: T[], getName: (item: T) => string | undefined, getMi: (item: T) => number, label: string, min: number, isNot: boolean): import('../types').RuleResult;
@@ -0,0 +1,2 @@
1
+ import * as ts from 'typescript';
2
+ export declare function sharedCheckHaveModifier<T extends ts.Node>(items: T[], getName: (item: T) => string | undefined, label: string, modifierStr: string, isNot: boolean): import('../types').RuleResult;
@@ -0,0 +1,2 @@
1
+ import * as ts from 'typescript';
2
+ export declare function sharedCheckHaveNameMatchingFileName<T extends ts.Node>(items: T[], getName: (item: T) => string | undefined, label: string, isNot: boolean): import('../types').RuleResult;
@@ -0,0 +1 @@
1
+ export declare function sharedCheckMatchNamePattern<T>(items: T[], getName: (item: T) => string | undefined, label: string, pattern: string | RegExp, isNot: boolean): import('../types').RuleResult;
@@ -0,0 +1,3 @@
1
+ import { SliceLocatorData } from './types';
2
+ import type * as ts from 'typescript';
3
+ export declare function locateSlices(sourceFiles: ts.SourceFile[], program: ts.Program, pattern: string): SliceLocatorData;
@@ -0,0 +1,3 @@
1
+ import { RuleResult } from '../types';
2
+ import { SliceLocatorData } from './types';
3
+ export declare function sliceCheckBeFreeOfCycles(locator: SliceLocatorData, isNot: boolean): RuleResult;
@@ -0,0 +1,3 @@
1
+ import { RuleResult } from '../types';
2
+ import { SliceLocatorData } from './types';
3
+ export declare function sliceCheckHaveMaxDistanceFromMainSequence(locator: SliceLocatorData, max: number, isNot: boolean): RuleResult;
@@ -0,0 +1,8 @@
1
+ import type * as ts from 'typescript';
2
+ export interface SliceLocatorData {
3
+ type: 'SliceLocator';
4
+ slicePattern: RegExp;
5
+ sliceIds: Set<string>;
6
+ sliceFiles: Map<string, ts.SourceFile[]>;
7
+ program: ts.Program;
8
+ }
@@ -1,4 +1,11 @@
1
+ import { ClassLocatorData } from './classes/types';
2
+ import { FileLocatorData } from './files/types';
3
+ import { FunctionLocatorData } from './functions/types';
4
+ import { LayeredArchitectureData } from './layers/types';
5
+ import { PropertyLocatorData } from './properties/types';
6
+ import { SliceLocatorData } from './slices/types';
1
7
  export interface RuleResult {
2
8
  pass: boolean;
3
9
  message: () => string;
4
10
  }
11
+ export type LocatorData = FileLocatorData | ClassLocatorData | FunctionLocatorData | PropertyLocatorData | SliceLocatorData | LayeredArchitectureData;
@@ -0,0 +1,6 @@
1
+ import { RuleResult } from '../types';
2
+ export declare function ruleBuilder<T>(items: T[], isNot: boolean, evaluate: (item: T) => {
3
+ passes: boolean;
4
+ failMessage: string;
5
+ failNotMessage: string;
6
+ }): RuleResult;
@@ -1,9 +1,35 @@
1
- export { Project, parseProject } from './core/Project';
2
- export { setupMatchers } from './matchers';
3
- export * from './core/FileLocator';
4
- export * from './core/ClassLocator';
5
- export * from './core/FunctionLocator';
6
- export * from './core/PropertyLocator';
7
- export * from './core/SliceLocator';
8
- export * from './core/LayerLocator';
1
+ export * from './core/classes/classCheckExtendClass';
2
+ export * from './core/classes/classCheckHaveMaxCyclomaticComplexity';
3
+ export * from './core/classes/classCheckHaveModifier';
4
+ export * from './core/classes/classCheckHaveNameMatchingFileName';
5
+ export * from './core/classes/classCheckImplementInterface';
6
+ export * from './core/classes/classCheckMatchNamePattern';
7
+ export * from './core/classes/classCheckResideInFolder';
8
+ export * from './core/classes/locateClasses';
9
+ export * from './core/files/checkDependOnFilesInFolder';
10
+ export * from './core/files/fileCheckBeFreeOfCycles';
11
+ export * from './core/files/fileCheckHaveMaxCyclomaticComplexity';
12
+ export * from './core/files/fileCheckHaveMaxExportedFunctions';
13
+ export * from './core/files/fileCheckHaveMinMaintainabilityIndex';
14
+ export * from './core/files/fileCheckMatchNamePattern';
15
+ export * from './core/files/locateFiles';
16
+ export * from './core/functions/functionCheckHaveExplicitReturnType';
17
+ export * from './core/functions/functionCheckHaveMaxCyclomaticComplexity';
18
+ export * from './core/functions/functionCheckHaveMinMaintainabilityIndex';
19
+ export * from './core/functions/functionCheckHaveModifier';
20
+ export * from './core/functions/functionCheckHaveNameMatchingFileName';
21
+ export * from './core/functions/functionCheckMatchNamePattern';
22
+ export * from './core/functions/locateFunctions';
23
+ export * from './core/layers/checkLayeredArchitecture';
24
+ export * from './core/layers/createLayeredArchitecture';
25
+ export * from './core/layers/layer';
26
+ export * from './core/layers/layerShouldNotBeAccessedByAnyLayer';
27
+ export * from './core/layers/layerShouldOnlyBeAccessedBy';
28
+ export { parseProject } from './core/project/parseProject';
29
+ export * from './core/properties/locateProperties';
30
+ export * from './core/properties/propertyCheckBeReadonly';
31
+ export * from './core/slices/locateSlices';
32
+ export * from './core/slices/sliceCheckBeFreeOfCycles';
33
+ export * from './core/slices/sliceCheckHaveMaxDistanceFromMainSequence';
9
34
  export * from './core/types';
35
+ export { setupMatchers } from './matchers';
@@ -11,5 +11,10 @@ declare module 'vitest' {
11
11
  toDependOnFilesInFolder(folder: string): void;
12
12
  toBeFreeOfCycles(): void;
13
13
  toMatchNamePattern(pattern: string | RegExp): void;
14
+ toHaveMaxCyclomaticComplexity(max: number): void;
15
+ toHaveMinMaintainabilityIndex(min: number): void;
16
+ toHaveMaxDistanceFromMainSequence(max: number): void;
17
+ toHaveNameMatchingFileName(): void;
18
+ toHaveMaxExportedFunctions(max: number): void;
14
19
  }
15
20
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archest/vitest",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/hinterdupfinger/archest.git"
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createSourceFile, getClasses } from '../testUtils';
3
+ import { classCheckExtendClass } from './classCheckExtendClass';
4
+ import type { ClassLocatorData } from './types';
5
+
6
+ describe('classCheckExtendClass', () => {
7
+ it('should pass if class extends correctly', () => {
8
+ const sourceFile = createSourceFile(`class Test extends Base {}`);
9
+ const data: ClassLocatorData = {
10
+ type: 'ClassLocator',
11
+ classes: getClasses(sourceFile),
12
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking TS types for tests
13
+ program: {} as any,
14
+ };
15
+ const result = classCheckExtendClass(data, 'Base', false);
16
+ expect(result.pass).toBe(true);
17
+ });
18
+
19
+ it('should fail if class does not extend correctly', () => {
20
+ const sourceFile = createSourceFile(`class Test extends Other {}`);
21
+ const data: ClassLocatorData = {
22
+ type: 'ClassLocator',
23
+ classes: getClasses(sourceFile),
24
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking TS types for tests
25
+ program: {} as any,
26
+ };
27
+ const result = classCheckExtendClass(data, 'Base', false);
28
+ expect(result.pass).toBe(false);
29
+ });
30
+ });
@@ -0,0 +1,41 @@
1
+ import * as ts from 'typescript';
2
+ import type { RuleResult } from '../types';
3
+ import type { ClassLocatorData } from './types';
4
+
5
+ export function classCheckExtendClass(
6
+ locator: ClassLocatorData,
7
+ className: string,
8
+ isNot: boolean,
9
+ ): RuleResult {
10
+ const violations: string[] = [];
11
+ for (const c of locator.classes) {
12
+ const name = c.name?.text || 'Anonymous';
13
+ let matches = false;
14
+ if (c.heritageClauses) {
15
+ for (const clause of c.heritageClauses) {
16
+ if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
17
+ for (const type of clause.types) {
18
+ if (
19
+ ts.isIdentifier(type.expression) &&
20
+ type.expression.text === className
21
+ ) {
22
+ matches = true;
23
+ break;
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
29
+ if (isNot && matches) {
30
+ violations.push(`Class ${name} extends ${className}, but it shouldn't.`);
31
+ } else if (!isNot && !matches) {
32
+ violations.push(
33
+ `Class ${name} does not extend ${className}, but it should.`,
34
+ );
35
+ }
36
+ }
37
+ return {
38
+ pass: violations.length === 0,
39
+ message: () => violations.join('\n'),
40
+ };
41
+ }
@@ -0,0 +1,8 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { classCheckHaveMaxCyclomaticComplexity } from './classCheckHaveMaxCyclomaticComplexity';
3
+
4
+ describe('classCheckHaveMaxCyclomaticComplexity', () => {
5
+ it('should exist and be callable', () => {
6
+ expect(typeof classCheckHaveMaxCyclomaticComplexity).toBe('function');
7
+ });
8
+ });
@@ -0,0 +1,35 @@
1
+ import * as ts from 'typescript';
2
+ import { calculateCyclomaticComplexity } from '../metrics/calculateCyclomaticComplexity';
3
+ import { sharedCheckHaveMaxCyclomaticComplexity } from '../shared/sharedCheckHaveMaxCyclomaticComplexity';
4
+ import type { RuleResult } from '../types';
5
+ import type { ClassLocatorData } from './types';
6
+
7
+ export function classCheckHaveMaxCyclomaticComplexity(
8
+ locator: ClassLocatorData,
9
+ max: number,
10
+ isNot: boolean,
11
+ ): RuleResult {
12
+ return sharedCheckHaveMaxCyclomaticComplexity(
13
+ locator.classes,
14
+ (c) => c.name?.text,
15
+ (c) => {
16
+ let complexity = 0;
17
+ const visit = (node: ts.Node) => {
18
+ if (
19
+ ts.isMethodDeclaration(node) ||
20
+ ts.isConstructorDeclaration(node) ||
21
+ ts.isGetAccessor(node) ||
22
+ ts.isSetAccessor(node)
23
+ ) {
24
+ complexity += calculateCyclomaticComplexity(node);
25
+ }
26
+ ts.forEachChild(node, visit);
27
+ };
28
+ visit(c);
29
+ return complexity;
30
+ },
31
+ 'Class',
32
+ max,
33
+ isNot,
34
+ );
35
+ }
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createSourceFile, getClasses } from '../testUtils';
3
+ import { classCheckHaveModifier } from './classCheckHaveModifier';
4
+ import type { ClassLocatorData } from './types';
5
+
6
+ describe('classCheckHaveModifier', () => {
7
+ it('should pass if class has modifier', () => {
8
+ const sourceFile = createSourceFile(`export class Test {}`);
9
+ const data: ClassLocatorData = {
10
+ type: 'ClassLocator',
11
+ classes: getClasses(sourceFile),
12
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking TS types for tests
13
+ program: {} as any,
14
+ };
15
+ const result = classCheckHaveModifier(data, 'export', false);
16
+ expect(result.pass).toBe(true);
17
+ });
18
+
19
+ it('should fail if class does not have modifier', () => {
20
+ const sourceFile = createSourceFile(`class Test {}`);
21
+ const data: ClassLocatorData = {
22
+ type: 'ClassLocator',
23
+ classes: getClasses(sourceFile),
24
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking TS types for tests
25
+ program: {} as any,
26
+ };
27
+ const result = classCheckHaveModifier(data, 'export', false);
28
+ expect(result.pass).toBe(false);
29
+ });
30
+ });
@@ -0,0 +1,17 @@
1
+ import { sharedCheckHaveModifier } from '../shared/sharedCheckHaveModifier';
2
+ import type { RuleResult } from '../types';
3
+ import type { ClassLocatorData } from './types';
4
+
5
+ export function classCheckHaveModifier(
6
+ locator: ClassLocatorData,
7
+ modifierStr: string,
8
+ isNot: boolean,
9
+ ): RuleResult {
10
+ return sharedCheckHaveModifier(
11
+ locator.classes,
12
+ (c) => c.name?.text,
13
+ 'Class',
14
+ modifierStr,
15
+ isNot,
16
+ );
17
+ }
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createSourceFile, getClasses } from '../testUtils';
3
+ import { classCheckHaveNameMatchingFileName } from './classCheckHaveNameMatchingFileName';
4
+ import type { ClassLocatorData } from './types';
5
+
6
+ describe('classCheckHaveNameMatchingFileName', () => {
7
+ it('should pass if class matches filename', () => {
8
+ const sourceFile = createSourceFile(`class Test {}`, 'Test.ts');
9
+ const data: ClassLocatorData = {
10
+ type: 'ClassLocator',
11
+ classes: getClasses(sourceFile),
12
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking TS types for tests
13
+ program: {} as any,
14
+ };
15
+ const result = classCheckHaveNameMatchingFileName(data, false);
16
+ expect(result.pass).toBe(true);
17
+ });
18
+
19
+ it('should fail if class does not match filename', () => {
20
+ const sourceFile = createSourceFile(`class Test {}`, 'Other.ts');
21
+ const data: ClassLocatorData = {
22
+ type: 'ClassLocator',
23
+ classes: getClasses(sourceFile),
24
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking TS types for tests
25
+ program: {} as any,
26
+ };
27
+ const result = classCheckHaveNameMatchingFileName(data, false);
28
+ expect(result.pass).toBe(false);
29
+ });
30
+ });
@@ -0,0 +1,15 @@
1
+ import { sharedCheckHaveNameMatchingFileName } from '../shared/sharedCheckHaveNameMatchingFileName';
2
+ import type { RuleResult } from '../types';
3
+ import type { ClassLocatorData } from './types';
4
+
5
+ export function classCheckHaveNameMatchingFileName(
6
+ locator: ClassLocatorData,
7
+ isNot: boolean,
8
+ ): RuleResult {
9
+ return sharedCheckHaveNameMatchingFileName(
10
+ locator.classes,
11
+ (c) => c.name?.text,
12
+ 'Class',
13
+ isNot,
14
+ );
15
+ }
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createSourceFile, getClasses } from '../testUtils';
3
+ import { classCheckImplementInterface } from './classCheckImplementInterface';
4
+ import type { ClassLocatorData } from './types';
5
+
6
+ describe('classCheckImplementInterface', () => {
7
+ it('should pass if class implements interface', () => {
8
+ const sourceFile = createSourceFile(`class Test implements IBase {}`);
9
+ const data: ClassLocatorData = {
10
+ type: 'ClassLocator',
11
+ classes: getClasses(sourceFile),
12
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking TS types for tests
13
+ program: {} as any,
14
+ };
15
+ const result = classCheckImplementInterface(data, 'IBase', false);
16
+ expect(result.pass).toBe(true);
17
+ });
18
+
19
+ it('should fail if class does not implement interface', () => {
20
+ const sourceFile = createSourceFile(`class Test {}`);
21
+ const data: ClassLocatorData = {
22
+ type: 'ClassLocator',
23
+ classes: getClasses(sourceFile),
24
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking TS types for tests
25
+ program: {} as any,
26
+ };
27
+ const result = classCheckImplementInterface(data, 'IBase', false);
28
+ expect(result.pass).toBe(false);
29
+ });
30
+ });
@@ -0,0 +1,43 @@
1
+ import * as ts from 'typescript';
2
+ import type { RuleResult } from '../types';
3
+ import type { ClassLocatorData } from './types';
4
+
5
+ export function classCheckImplementInterface(
6
+ locator: ClassLocatorData,
7
+ interfaceName: string,
8
+ isNot: boolean,
9
+ ): RuleResult {
10
+ const violations: string[] = [];
11
+ for (const c of locator.classes) {
12
+ const name = c.name?.text || 'Anonymous';
13
+ let matches = false;
14
+ if (c.heritageClauses) {
15
+ for (const clause of c.heritageClauses) {
16
+ if (clause.token === ts.SyntaxKind.ImplementsKeyword) {
17
+ for (const type of clause.types) {
18
+ if (
19
+ ts.isIdentifier(type.expression) &&
20
+ type.expression.text === interfaceName
21
+ ) {
22
+ matches = true;
23
+ break;
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
29
+ if (isNot && matches) {
30
+ violations.push(
31
+ `Class ${name} implements ${interfaceName}, but it shouldn't.`,
32
+ );
33
+ } else if (!isNot && !matches) {
34
+ violations.push(
35
+ `Class ${name} does not implement ${interfaceName}, but it should.`,
36
+ );
37
+ }
38
+ }
39
+ return {
40
+ pass: violations.length === 0,
41
+ message: () => violations.join('\n'),
42
+ };
43
+ }
@@ -0,0 +1,8 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { classCheckMatchNamePattern } from './classCheckMatchNamePattern';
3
+
4
+ describe('classCheckMatchNamePattern', () => {
5
+ it('should exist and be callable', () => {
6
+ expect(typeof classCheckMatchNamePattern).toBe('function');
7
+ });
8
+ });
@@ -0,0 +1,17 @@
1
+ import { sharedCheckMatchNamePattern } from '../shared/sharedCheckMatchNamePattern';
2
+ import type { RuleResult } from '../types';
3
+ import type { ClassLocatorData } from './types';
4
+
5
+ export function classCheckMatchNamePattern(
6
+ locator: ClassLocatorData,
7
+ pattern: string | RegExp,
8
+ isNot: boolean,
9
+ ): RuleResult {
10
+ return sharedCheckMatchNamePattern(
11
+ locator.classes,
12
+ (c) => c.name?.text,
13
+ 'Class',
14
+ pattern,
15
+ isNot,
16
+ );
17
+ }
@@ -0,0 +1,8 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { classCheckResideInFolder } from './classCheckResideInFolder';
3
+
4
+ describe('classCheckResideInFolder', () => {
5
+ it('should exist and be callable', () => {
6
+ expect(typeof classCheckResideInFolder).toBe('function');
7
+ });
8
+ });
@@ -0,0 +1,31 @@
1
+ import type { RuleResult } from '../types';
2
+ import type { ClassLocatorData } from './types';
3
+
4
+ export function classCheckResideInFolder(
5
+ locator: ClassLocatorData,
6
+ targetFolder: string,
7
+ isNot: boolean,
8
+ ): RuleResult {
9
+ const violations: string[] = [];
10
+ for (const c of locator.classes) {
11
+ const className = c.name?.text || 'Anonymous Class';
12
+ const sourceFile = c.getSourceFile();
13
+ const inTargetFolder =
14
+ sourceFile.fileName.includes(`/${targetFolder}/`) ||
15
+ sourceFile.fileName.includes(`\\${targetFolder}\\`);
16
+
17
+ if (isNot && inTargetFolder) {
18
+ violations.push(
19
+ `Class ${className} resides in ${targetFolder}, but it shouldn't.`,
20
+ );
21
+ } else if (!isNot && !inTargetFolder) {
22
+ violations.push(
23
+ `Class ${className} does not reside in ${targetFolder}, but it should.`,
24
+ );
25
+ }
26
+ }
27
+ return {
28
+ pass: violations.length === 0,
29
+ message: () => violations.join('\n'),
30
+ };
31
+ }
@@ -0,0 +1,13 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createSourceFile, getClasses } from '../testUtils';
3
+ import { locateClasses } from './locateClasses';
4
+
5
+ describe('locateClasses', () => {
6
+ it('should filter classes by inFolder', () => {
7
+ const file1 = createSourceFile('class A {}', 'src/domain/A.ts');
8
+ const classes = getClasses(file1);
9
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking TS types for tests
10
+ const result = locateClasses(classes, {} as any, { inFolder: 'infra' });
11
+ expect(result.classes.length).toBe(0);
12
+ });
13
+ });
@@ -0,0 +1,127 @@
1
+ import * as ts from 'typescript';
2
+ import type { ClassLocatorData, ClassQueryOptions } from './types';
3
+
4
+ export function locateClasses(
5
+ classes: ts.ClassDeclaration[],
6
+ program: ts.Program,
7
+ options?: ClassQueryOptions,
8
+ ): ClassLocatorData {
9
+ let filtered = classes;
10
+
11
+ if (options?.inFolder) {
12
+ filtered = filtered.filter((c) => {
13
+ const sourceFile = c.getSourceFile
14
+ ? c.getSourceFile()
15
+ : c.parent
16
+ ? ts.getSourceFileOfNode(c)
17
+ : undefined;
18
+ if (!sourceFile) return false;
19
+ return (
20
+ sourceFile.fileName.includes(`/${options.inFolder}/`) ||
21
+ sourceFile.fileName.includes(`\\${options.inFolder}\\`)
22
+ );
23
+ });
24
+ }
25
+ if (options?.matchNamePattern) {
26
+ const regex =
27
+ typeof options.matchNamePattern === 'string'
28
+ ? new RegExp(options.matchNamePattern)
29
+ : options.matchNamePattern;
30
+ filtered = filtered.filter((c) => {
31
+ const name = c.name?.text;
32
+ return name && regex.test(name);
33
+ });
34
+ }
35
+ if (options?.withDecorator) {
36
+ filtered = filtered.filter((c) => {
37
+ const decorators = ts.canHaveDecorators(c)
38
+ ? ts.getDecorators(c)
39
+ : undefined;
40
+ let hasDecorator = false;
41
+ if (decorators) {
42
+ for (const dec of decorators) {
43
+ const exp = dec.expression;
44
+ let name = '';
45
+ if (ts.isIdentifier(exp)) {
46
+ name = exp.text;
47
+ } else if (
48
+ ts.isCallExpression(exp) &&
49
+ ts.isIdentifier(exp.expression)
50
+ ) {
51
+ name = exp.expression.text;
52
+ }
53
+ if (name === options.withDecorator) {
54
+ hasDecorator = true;
55
+ }
56
+ }
57
+ }
58
+ return hasDecorator;
59
+ });
60
+ }
61
+ if (options?.extending) {
62
+ filtered = filtered.filter((c) => {
63
+ if (c.heritageClauses) {
64
+ for (const clause of c.heritageClauses) {
65
+ if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
66
+ for (const type of clause.types) {
67
+ if (
68
+ ts.isIdentifier(type.expression) &&
69
+ type.expression.text === options.extending
70
+ ) {
71
+ return true;
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }
77
+ return false;
78
+ });
79
+ }
80
+ if (options?.implementing) {
81
+ filtered = filtered.filter((c) => {
82
+ if (c.heritageClauses) {
83
+ for (const clause of c.heritageClauses) {
84
+ if (clause.token === ts.SyntaxKind.ImplementsKeyword) {
85
+ for (const type of clause.types) {
86
+ if (
87
+ ts.isIdentifier(type.expression) &&
88
+ type.expression.text === options.implementing
89
+ ) {
90
+ return true;
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }
96
+ return false;
97
+ });
98
+ }
99
+ if (options?.havingModifier) {
100
+ let targetKind: ts.SyntaxKind;
101
+ switch (options.havingModifier) {
102
+ case 'export':
103
+ targetKind = ts.SyntaxKind.ExportKeyword;
104
+ break;
105
+ case 'default':
106
+ targetKind = ts.SyntaxKind.DefaultKeyword;
107
+ break;
108
+ case 'abstract':
109
+ targetKind = ts.SyntaxKind.AbstractKeyword;
110
+ break;
111
+ default:
112
+ throw new Error(
113
+ `Modifier ${options.havingModifier} is not fully supported.`,
114
+ );
115
+ }
116
+ filtered = filtered.filter((c) => {
117
+ const modifiers = ts.canHaveModifiers(c) ? ts.getModifiers(c) : undefined;
118
+ return modifiers?.some((m: ts.Modifier) => m.kind === targetKind);
119
+ });
120
+ }
121
+
122
+ return {
123
+ type: 'ClassLocator',
124
+ classes: filtered,
125
+ program,
126
+ };
127
+ }