@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
@@ -1,107 +1,320 @@
1
1
  import { expect } from 'vitest';
2
- import { RuleResult } from '../core/types';
3
- import { ClassLocator } from '../core/ClassLocator';
4
- import { FunctionLocator } from '../core/FunctionLocator';
5
- import { PropertyLocator } from '../core/PropertyLocator';
6
- import { FileLocator } from '../core/FileLocator';
7
- import { SliceLocator } from '../core/SliceLocator';
8
- import { LayeredArchitectureBuilder } from '../core/LayerLocator';
2
+ import { classCheckExtendClass } from '../core/classes/classCheckExtendClass';
3
+ import { classCheckHaveMaxCyclomaticComplexity } from '../core/classes/classCheckHaveMaxCyclomaticComplexity';
4
+ import { classCheckHaveModifier } from '../core/classes/classCheckHaveModifier';
5
+ import { classCheckHaveNameMatchingFileName } from '../core/classes/classCheckHaveNameMatchingFileName';
6
+ import { classCheckImplementInterface } from '../core/classes/classCheckImplementInterface';
7
+ import { classCheckMatchNamePattern } from '../core/classes/classCheckMatchNamePattern';
8
+ import { classCheckResideInFolder } from '../core/classes/classCheckResideInFolder';
9
+ import { checkDependOnFilesInFolder } from '../core/files/checkDependOnFilesInFolder';
10
+ import { fileCheckBeFreeOfCycles } from '../core/files/fileCheckBeFreeOfCycles';
11
+ import { fileCheckHaveMaxCyclomaticComplexity } from '../core/files/fileCheckHaveMaxCyclomaticComplexity';
12
+ import { fileCheckHaveMaxExportedFunctions } from '../core/files/fileCheckHaveMaxExportedFunctions';
13
+ import { fileCheckHaveMinMaintainabilityIndex } from '../core/files/fileCheckHaveMinMaintainabilityIndex';
14
+ import { fileCheckMatchNamePattern } from '../core/files/fileCheckMatchNamePattern';
15
+ import { functionCheckHaveExplicitReturnType } from '../core/functions/functionCheckHaveExplicitReturnType';
16
+ import { functionCheckHaveMaxCyclomaticComplexity } from '../core/functions/functionCheckHaveMaxCyclomaticComplexity';
17
+ import { functionCheckHaveMinMaintainabilityIndex } from '../core/functions/functionCheckHaveMinMaintainabilityIndex';
18
+ import { functionCheckHaveModifier } from '../core/functions/functionCheckHaveModifier';
19
+ import { functionCheckHaveNameMatchingFileName } from '../core/functions/functionCheckHaveNameMatchingFileName';
20
+ import { functionCheckMatchNamePattern } from '../core/functions/functionCheckMatchNamePattern';
21
+ import { checkLayeredArchitecture } from '../core/layers/checkLayeredArchitecture';
22
+
23
+ import { propertyCheckBeReadonly } from '../core/properties/propertyCheckBeReadonly';
24
+
25
+ import { sliceCheckBeFreeOfCycles } from '../core/slices/sliceCheckBeFreeOfCycles';
26
+ import { sliceCheckHaveMaxDistanceFromMainSequence } from '../core/slices/sliceCheckHaveMaxDistanceFromMainSequence';
27
+ import type { LocatorData, RuleResult } from '../core/types';
9
28
 
10
29
  export function setupMatchers() {
11
30
  expect.extend({
12
- toPass(received: RuleResult | LayeredArchitectureBuilder) {
13
- const result = received instanceof LayeredArchitectureBuilder ? received.check() : received;
31
+ // biome-ignore lint/suspicious/noExplicitAny: Matcher signature
32
+ toPass(received: any) {
33
+ let result: RuleResult;
34
+
35
+ if (received?.data && received.data.type === 'LayeredArchitecture') {
36
+ result = checkLayeredArchitecture(received.data);
37
+ } else {
38
+ result = received as RuleResult;
39
+ }
40
+
14
41
  const { pass, message } = result;
15
42
  return {
16
43
  pass: this.isNot ? !pass : pass,
17
- message: pass
18
- ? () => 'Expected rule not to pass'
19
- : () => message()
44
+ message: pass ? () => 'Expected rule not to pass' : () => message(),
20
45
  };
21
46
  },
22
47
 
23
- toResideInFolder(received: ClassLocator, folder: string) {
24
- const { pass, message } = received.checkResideInFolder(folder, this.isNot);
48
+ toResideInFolder(received: LocatorData, folder: string) {
49
+ let result: RuleResult;
50
+ if (received.type === 'ClassLocator') {
51
+ result = classCheckResideInFolder(received, folder, this.isNot);
52
+ } else {
53
+ throw new Error(
54
+ `toResideInFolder matcher does not support ${received.type}`,
55
+ );
56
+ }
25
57
  return {
26
- pass: this.isNot ? !pass : pass,
27
- message
58
+ pass: this.isNot ? !result.pass : result.pass,
59
+ message: result.message,
28
60
  };
29
61
  },
30
62
 
31
- toHaveModifier(received: ClassLocator | FunctionLocator, modifier: string) {
32
- const { pass, message } = received.checkHaveModifier(modifier, this.isNot);
63
+ toHaveModifier(received: LocatorData, modifier: string) {
64
+ let result: RuleResult;
65
+ if (received.type === 'ClassLocator') {
66
+ result = classCheckHaveModifier(received, modifier, this.isNot);
67
+ } else if (received.type === 'FunctionLocator') {
68
+ result = functionCheckHaveModifier(received, modifier, this.isNot);
69
+ } else {
70
+ throw new Error(
71
+ `toHaveModifier matcher does not support ${received.type}`,
72
+ );
73
+ }
33
74
  return {
34
- pass: this.isNot ? !pass : pass,
35
- message
75
+ pass: this.isNot ? !result.pass : result.pass,
76
+ message: result.message,
36
77
  };
37
78
  },
38
79
 
80
+ toExtendClass(received: LocatorData, className: string) {
81
+ let result: RuleResult;
82
+ if (received.type === 'ClassLocator') {
83
+ result = classCheckExtendClass(received, className, this.isNot);
84
+ } else {
85
+ throw new Error(
86
+ `toExtendClass matcher does not support ${received.type}`,
87
+ );
88
+ }
89
+ return {
90
+ pass: this.isNot ? !result.pass : result.pass,
91
+ message: result.message,
92
+ };
93
+ },
39
94
 
95
+ toImplementInterface(received: LocatorData, interfaceName: string) {
96
+ let result: RuleResult;
97
+ if (received.type === 'ClassLocator') {
98
+ result = classCheckImplementInterface(
99
+ received,
100
+ interfaceName,
101
+ this.isNot,
102
+ );
103
+ } else {
104
+ throw new Error(
105
+ `toImplementInterface matcher does not support ${received.type}`,
106
+ );
107
+ }
108
+ return {
109
+ pass: this.isNot ? !result.pass : result.pass,
110
+ message: result.message,
111
+ };
112
+ },
40
113
 
41
- toExtendClass(received: ClassLocator, className: string) {
42
- const { pass, message } = received.checkExtendClass(className, this.isNot);
114
+ toHaveExplicitReturnType(received: LocatorData) {
115
+ let result: RuleResult;
116
+ if (received.type === 'FunctionLocator') {
117
+ result = functionCheckHaveExplicitReturnType(received, this.isNot);
118
+ } else {
119
+ throw new Error(
120
+ `toHaveExplicitReturnType matcher does not support ${received.type}`,
121
+ );
122
+ }
43
123
  return {
44
- pass: this.isNot ? !pass : pass,
45
- message
124
+ pass: this.isNot ? !result.pass : result.pass,
125
+ message: result.message,
46
126
  };
47
127
  },
48
128
 
49
- toImplementInterface(received: ClassLocator, interfaceName: string) {
50
- const { pass, message } = received.checkImplementInterface(interfaceName, this.isNot);
129
+ toBeReadonly(received: LocatorData) {
130
+ let result: RuleResult;
131
+ if (received.type === 'PropertyLocator') {
132
+ result = propertyCheckBeReadonly(received, this.isNot);
133
+ } else {
134
+ throw new Error(
135
+ `toBeReadonly matcher does not support ${received.type}`,
136
+ );
137
+ }
51
138
  return {
52
- pass: this.isNot ? !pass : pass,
53
- message
139
+ pass: this.isNot ? !result.pass : result.pass,
140
+ message: result.message,
54
141
  };
55
142
  },
56
143
 
57
- toHaveExplicitReturnType(received: FunctionLocator) {
58
- const { pass, message } = received.checkHaveExplicitReturnType(this.isNot);
144
+ toDependOnFilesInFolder(received: LocatorData, folder: string) {
145
+ let result: RuleResult;
146
+ if (received.type === 'FileLocator') {
147
+ result = checkDependOnFilesInFolder(received, folder, this.isNot);
148
+ } else {
149
+ throw new Error(
150
+ `toDependOnFilesInFolder matcher does not support ${received.type}`,
151
+ );
152
+ }
59
153
  return {
60
- pass: this.isNot ? !pass : pass,
61
- message
154
+ pass: this.isNot ? !result.pass : result.pass,
155
+ message: result.message,
62
156
  };
63
157
  },
64
158
 
65
- toBeReadonly(received: PropertyLocator) {
66
- const { pass, message } = received.checkBeReadonly(this.isNot);
159
+ toBeFreeOfCycles(received: LocatorData) {
160
+ let result: RuleResult;
161
+ if (received.type === 'FileLocator') {
162
+ result = fileCheckBeFreeOfCycles(received, this.isNot);
163
+ } else if (received.type === 'SliceLocator') {
164
+ result = sliceCheckBeFreeOfCycles(received, this.isNot);
165
+ } else {
166
+ throw new Error(
167
+ `toBeFreeOfCycles matcher does not support ${received.type}`,
168
+ );
169
+ }
67
170
  return {
68
- pass: this.isNot ? !pass : pass,
69
- message
171
+ pass: this.isNot ? !result.pass : result.pass,
172
+ message: result.message,
70
173
  };
71
174
  },
72
175
 
73
- toDependOnFilesInFolder(received: FileLocator, folder: string) {
74
- const { pass, message } = received.checkDependOnFilesInFolder(folder, this.isNot);
176
+ toMatchNamePattern(received: LocatorData, pattern: string | RegExp) {
177
+ let result: RuleResult;
178
+ if (received.type === 'FileLocator') {
179
+ result = fileCheckMatchNamePattern(received, pattern, this.isNot);
180
+ } else if (received.type === 'ClassLocator') {
181
+ result = classCheckMatchNamePattern(received, pattern, this.isNot);
182
+ } else if (received.type === 'FunctionLocator') {
183
+ result = functionCheckMatchNamePattern(received, pattern, this.isNot);
184
+ } else {
185
+ throw new Error(
186
+ `toMatchNamePattern matcher does not support ${received.type}`,
187
+ );
188
+ }
75
189
  return {
76
- pass: this.isNot ? !pass : pass,
77
- message
190
+ pass: this.isNot ? !result.pass : result.pass,
191
+ message: result.message,
78
192
  };
79
193
  },
80
194
 
81
- toBeFreeOfCycles(received: FileLocator | SliceLocator) {
82
- const { pass, message } = received.checkBeFreeOfCycles(this.isNot);
195
+ toHaveMaxCyclomaticComplexity(received: LocatorData, max: number) {
196
+ let result: RuleResult;
197
+ if (received.type === 'FileLocator') {
198
+ result = fileCheckHaveMaxCyclomaticComplexity(
199
+ received,
200
+ max,
201
+ this.isNot,
202
+ );
203
+ } else if (received.type === 'ClassLocator') {
204
+ result = classCheckHaveMaxCyclomaticComplexity(
205
+ received,
206
+ max,
207
+ this.isNot,
208
+ );
209
+ } else if (received.type === 'FunctionLocator') {
210
+ result = functionCheckHaveMaxCyclomaticComplexity(
211
+ received,
212
+ max,
213
+ this.isNot,
214
+ );
215
+ } else {
216
+ throw new Error(
217
+ `toHaveMaxCyclomaticComplexity matcher does not support ${received.type}`,
218
+ );
219
+ }
83
220
  return {
84
- pass: this.isNot ? !pass : pass,
85
- message
221
+ pass: this.isNot ? !result.pass : result.pass,
222
+ message: result.message,
86
223
  };
87
224
  },
88
225
 
89
- toMatchNamePattern(received: FileLocator | ClassLocator | FunctionLocator, pattern: string | RegExp) {
90
- const { pass, message } = received.checkMatchNamePattern(pattern, this.isNot);
226
+ toHaveMinMaintainabilityIndex(received: LocatorData, min: number) {
227
+ let result: RuleResult;
228
+ if (received.type === 'FileLocator') {
229
+ result = fileCheckHaveMinMaintainabilityIndex(
230
+ received,
231
+ min,
232
+ this.isNot,
233
+ );
234
+ } else if (received.type === 'FunctionLocator') {
235
+ result = functionCheckHaveMinMaintainabilityIndex(
236
+ received,
237
+ min,
238
+ this.isNot,
239
+ );
240
+ } else {
241
+ throw new Error(
242
+ `toHaveMinMaintainabilityIndex matcher does not support ${received.type}`,
243
+ );
244
+ }
91
245
  return {
92
- pass: this.isNot ? !pass : pass,
93
- message
246
+ pass: this.isNot ? !result.pass : result.pass,
247
+ message: result.message,
248
+ };
249
+ },
250
+
251
+ toHaveMaxDistanceFromMainSequence(received: LocatorData, max: number) {
252
+ let result: RuleResult;
253
+ if (received.type === 'SliceLocator') {
254
+ result = sliceCheckHaveMaxDistanceFromMainSequence(
255
+ received,
256
+ max,
257
+ this.isNot,
258
+ );
259
+ } else {
260
+ throw new Error(
261
+ `toHaveMaxDistanceFromMainSequence matcher does not support ${received.type}`,
262
+ );
263
+ }
264
+ return {
265
+ pass: this.isNot ? !result.pass : result.pass,
266
+ message: result.message,
267
+ };
268
+ },
269
+
270
+ toHaveNameMatchingFileName(received: LocatorData) {
271
+ let result: RuleResult;
272
+ if (received.type === 'FunctionLocator') {
273
+ result = functionCheckHaveNameMatchingFileName(received, this.isNot);
274
+ } else if (received.type === 'ClassLocator') {
275
+ result = classCheckHaveNameMatchingFileName(received, this.isNot);
276
+ } else {
277
+ throw new Error(
278
+ `toHaveNameMatchingFileName matcher does not support ${received.type}`,
279
+ );
280
+ }
281
+ return {
282
+ pass: this.isNot ? !result.pass : result.pass,
283
+ message: result.message,
284
+ };
285
+ },
286
+
287
+ toHaveMaxExportedFunctions(received: LocatorData, max: number) {
288
+ let result: RuleResult;
289
+ if (received.type === 'FileLocator') {
290
+ result = fileCheckHaveMaxExportedFunctions(received, max, this.isNot);
291
+ } else {
292
+ throw new Error(
293
+ `toHaveMaxExportedFunctions matcher does not support ${received.type}`,
294
+ );
295
+ }
296
+ return {
297
+ pass: this.isNot ? !result.pass : result.pass,
298
+ message: result.message,
94
299
  };
95
- }
300
+ },
96
301
  });
97
302
  }
98
303
 
99
304
  declare module 'vitest' {
305
+ // biome-ignore lint/suspicious/noExplicitAny: Matcher signature
100
306
  interface Assertion<T = any> {
101
307
  toPass(): void;
102
308
  toResideInFolder(folder: string): void;
103
- toHaveModifier(modifier: 'export' | 'default' | 'abstract' | 'async' | 'private' | 'public'): void;
104
-
309
+ toHaveModifier(
310
+ modifier:
311
+ | 'export'
312
+ | 'default'
313
+ | 'abstract'
314
+ | 'async'
315
+ | 'private'
316
+ | 'public',
317
+ ): void;
105
318
  toExtendClass(className: string): void;
106
319
  toImplementInterface(interfaceName: string): void;
107
320
  toHaveExplicitReturnType(): void;
@@ -109,5 +322,10 @@ declare module 'vitest' {
109
322
  toDependOnFilesInFolder(folder: string): void;
110
323
  toBeFreeOfCycles(): void;
111
324
  toMatchNamePattern(pattern: string | RegExp): void;
325
+ toHaveMaxCyclomaticComplexity(max: number): void;
326
+ toHaveMinMaintainabilityIndex(min: number): void;
327
+ toHaveMaxDistanceFromMainSequence(max: number): void;
328
+ toHaveNameMatchingFileName(): void;
329
+ toHaveMaxExportedFunctions(max: number): void;
112
330
  }
113
331
  }
@@ -1,5 +1,5 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { parseProject } from '../src/core/Project';
1
+ import { describe, expect, it } from 'vitest';
2
+ import { parseProject } from '../src/core/project/parseProject';
3
3
  import { setupMatchers } from '../src/matchers';
4
4
 
5
5
  setupMatchers();
@@ -19,15 +19,20 @@ describe('@archest/vitest', () => {
19
19
  });
20
20
  });
21
21
 
22
- describe('ClassLocator (Declaration Checks)', () => {
23
- it('locators should have export modifier', () => {
24
- const locators = project.getClasses({ inFolder: 'core', matchNamePattern: /Locator$/ });
25
- expect(locators).toHaveModifier('export');
22
+ describe('FunctionLocator (Declaration Checks)', () => {
23
+ it('check functions should have export modifier', () => {
24
+ const checkFunctions = project.getFunctions({
25
+ inFolder: 'core',
26
+ matchNamePattern: /check/i,
27
+ });
28
+ expect(checkFunctions).toHaveModifier('export');
26
29
  });
27
30
 
28
- it('Project class should be exported', () => {
29
- const projectClasses = project.getClasses({ matchNamePattern: /Project$/ });
30
- expect(projectClasses).toHaveModifier('export');
31
+ it('parseProject function should be exported', () => {
32
+ const projectFunctions = project.getFunctions({
33
+ matchNamePattern: /^parseProject$/,
34
+ });
35
+ expect(projectFunctions).toHaveModifier('export');
31
36
  });
32
37
  });
33
38
 
@@ -43,17 +48,105 @@ describe('@archest/vitest', () => {
43
48
  });
44
49
  });
45
50
 
46
- describe('LayeredArchitectureBuilder', () => {
47
- it('core should not depend on matchers layer', () => {
48
- const architecture = project.layeredArchitecture()
49
- .layer('Core', 'core')
51
+ describe('Layered Architecture', () => {
52
+ it('should enforce strict internal layer dependencies', () => {
53
+ const architecture = project
54
+ .layeredArchitecture()
55
+ .layer('Utils', 'core/utils')
56
+ .layer('Shared', 'core/shared')
57
+ .layer(
58
+ 'Domains',
59
+ 'core/(classes|files|functions|properties|slices|layers|metrics|project)',
60
+ )
50
61
  .layer('Matchers', 'matchers')
51
62
  .layer('Root', 'src'); // src/index.ts is just under src
52
63
 
53
- const rule = architecture
54
- .whereLayer('Matchers').shouldOnlyBeAccessedBy('Core', 'Root');
64
+ // Utils should only be accessed by higher layers
65
+ expect(
66
+ architecture
67
+ .whereLayer('Utils')
68
+ .shouldOnlyBeAccessedBy('Shared', 'Domains', 'Matchers', 'Root'),
69
+ ).toPass();
55
70
 
56
- expect(rule).toPass();
71
+ // Shared should only be accessed by Domains and above
72
+ expect(
73
+ architecture
74
+ .whereLayer('Shared')
75
+ .shouldOnlyBeAccessedBy('Domains', 'Matchers', 'Root'),
76
+ ).toPass();
77
+
78
+ // Domains should only be accessed by Matchers and Root
79
+ expect(
80
+ architecture
81
+ .whereLayer('Domains')
82
+ .shouldOnlyBeAccessedBy('Matchers', 'Root'),
83
+ ).toPass();
84
+
85
+ // Matchers should only be accessed by Root
86
+ expect(
87
+ architecture.whereLayer('Matchers').shouldOnlyBeAccessedBy('Root'),
88
+ ).toPass();
89
+ });
90
+ });
91
+
92
+ describe('Structural Metrics', () => {
93
+ it('core files should have a reasonable cyclomatic complexity', () => {
94
+ const coreFiles = project.getFiles({ inFolder: 'core' });
95
+ // The max complexity is arbitrary for this test, but 200 should pass
96
+ expect(coreFiles).toHaveMaxCyclomaticComplexity(200);
97
+ });
98
+
99
+ it('core files should have a minimum maintainability index', () => {
100
+ const coreFiles = project.getFiles({ inFolder: 'core' });
101
+ // A minimum index of 5 should pass for our small files
102
+ expect(coreFiles).toHaveMinMaintainabilityIndex(5);
103
+ });
104
+
105
+ it('slices should maintain a reasonable distance from the main sequence', () => {
106
+ const slices = project.getSlices('src/(.*)');
107
+ // Distance is between 0 and 1, we expect a reasonable balance < 0.8
108
+ expect(slices).toHaveMaxDistanceFromMainSequence(0.8);
109
+ });
110
+ });
111
+
112
+ describe('Granular Folder Structure', () => {
113
+ it('functions should have a name matching their filename', () => {
114
+ const coreFunctions = project.getFunctions({
115
+ inFolder: 'core',
116
+ isTopLevel: true,
117
+ });
118
+ expect(coreFunctions).toHaveNameMatchingFileName();
119
+ });
120
+
121
+ it('classes should have a name matching their filename', () => {
122
+ const coreClasses = project.getClasses({ inFolder: 'core' });
123
+ expect(coreClasses).toHaveNameMatchingFileName();
124
+ });
125
+
126
+ it('files should have a maximum of 1 exported function', () => {
127
+ // Exclude index.ts, types.ts, and testUtils.ts which have different export rules
128
+ const implementationFiles = project.getFiles({
129
+ inFolder: 'core',
130
+ matchNamePattern: /^(?!.*(types|index|testUtils)\.ts$).*\.ts$/,
131
+ });
132
+ expect(implementationFiles).toHaveMaxExportedFunctions(1);
133
+ });
134
+ });
135
+
136
+ describe('Shared Abstraction Layer', () => {
137
+ it('shared abstraction functions should be prefixed with sharedCheck', () => {
138
+ const sharedFunctions = project.getFunctions({
139
+ inFolder: 'core/shared',
140
+ isTopLevel: true,
141
+ });
142
+ expect(sharedFunctions).toMatchNamePattern(/^sharedCheck/);
143
+ });
144
+
145
+ it('shared abstraction files should not depend on domain logic', () => {
146
+ const sharedFiles = project.getFiles({ inFolder: 'core/shared' });
147
+ expect(sharedFiles).not.toDependOnFilesInFolder('classes');
148
+ expect(sharedFiles).not.toDependOnFilesInFolder('functions');
149
+ expect(sharedFiles).not.toDependOnFilesInFolder('files');
57
150
  });
58
151
  });
59
152
  });
package/tsconfig.json CHANGED
@@ -13,5 +13,6 @@
13
13
  "forceConsistentCasingInFileNames": true,
14
14
  "outDir": "dist"
15
15
  },
16
- "include": ["src/**/*"]
16
+ "include": ["src/**/*"],
17
+ "exclude": ["src/**/*.test.ts", "src/core/testUtils.ts"]
17
18
  }
package/vite.config.ts CHANGED
@@ -1,6 +1,6 @@
1
+ import { resolve } from 'node:path';
1
2
  import { defineConfig } from 'vite';
2
3
  import dts from 'vite-plugin-dts';
3
- import { resolve } from 'path';
4
4
 
5
5
  export default defineConfig({
6
6
  build: {
@@ -10,7 +10,7 @@ export default defineConfig({
10
10
  fileName: (format) => `index.${format === 'es' ? 'mjs' : 'js'}`,
11
11
  },
12
12
  rollupOptions: {
13
- external: ['typescript', 'vitest', 'path', 'fs'],
13
+ external: ['typescript', 'vitest', 'path', 'fs', 'node:path', 'node:fs'],
14
14
  },
15
15
  sourcemap: true,
16
16
  },
@@ -1,20 +0,0 @@
1
- import { RuleResult } from './types';
2
- import * as ts from 'typescript';
3
- export interface ClassQueryOptions {
4
- inFolder?: string;
5
- matchNamePattern?: string | RegExp;
6
- withDecorator?: string;
7
- extending?: string;
8
- implementing?: string;
9
- havingModifier?: string;
10
- }
11
- export declare class ClassLocator {
12
- private program;
13
- readonly classes: ts.ClassDeclaration[];
14
- constructor(classes: ts.ClassDeclaration[], program: ts.Program, options?: ClassQueryOptions);
15
- checkResideInFolder(targetFolder: string, isNot: boolean): RuleResult;
16
- checkMatchNamePattern(pattern: string | RegExp, isNot: boolean): RuleResult;
17
- checkExtendClass(className: string, isNot: boolean): RuleResult;
18
- checkImplementInterface(interfaceName: string, isNot: boolean): RuleResult;
19
- checkHaveModifier(modifierStr: string, isNot: boolean): RuleResult;
20
- }
@@ -1,15 +0,0 @@
1
- import { RuleResult } from './types';
2
- import * as ts from 'typescript';
3
- export interface FileQueryOptions {
4
- inFolder?: string;
5
- matchNamePattern?: string | RegExp;
6
- }
7
- export declare class FileLocator {
8
- private program;
9
- readonly files: ts.SourceFile[];
10
- constructor(sourceFiles: ts.SourceFile[], program: ts.Program, options?: FileQueryOptions);
11
- private getFileDependencies;
12
- checkDependOnFilesInFolder(targetFolder: string, isNot: boolean): RuleResult;
13
- checkBeFreeOfCycles(isNot: boolean): RuleResult;
14
- checkMatchNamePattern(pattern: string | RegExp, isNot: boolean): RuleResult;
15
- }
@@ -1,15 +0,0 @@
1
- import { RuleResult } from './types';
2
- import * as ts from 'typescript';
3
- export interface FunctionQueryOptions {
4
- inFolder?: string;
5
- matchNamePattern?: string | RegExp;
6
- isTopLevel?: boolean;
7
- }
8
- export declare class FunctionLocator {
9
- private program;
10
- readonly functions: ts.FunctionLikeDeclaration[];
11
- constructor(functions: ts.FunctionLikeDeclaration[], program: ts.Program, options?: FunctionQueryOptions);
12
- checkHaveModifier(modifierStr: string, isNot: boolean): RuleResult;
13
- checkHaveExplicitReturnType(isNot: boolean): RuleResult;
14
- checkMatchNamePattern(pattern: string | RegExp, isNot: boolean): RuleResult;
15
- }
@@ -1,23 +0,0 @@
1
- import { RuleResult } from './types';
2
- import * as ts from 'typescript';
3
- export declare class LayeredArchitectureBuilder {
4
- program: ts.Program;
5
- private files;
6
- private layers;
7
- private assertions;
8
- constructor(files: ts.SourceFile[], program: ts.Program);
9
- layer(name: string, folderPattern: string): this;
10
- whereLayer(name: string): LayerAssertionBuilder;
11
- addAssertion(assertion: (files: ts.SourceFile[]) => string[]): void;
12
- getFiles(): ts.SourceFile[];
13
- getLayerPattern(name: string): string;
14
- getFileDependencies(sourceFile: ts.SourceFile): string[];
15
- check(): RuleResult;
16
- }
17
- export declare class LayerAssertionBuilder {
18
- private parent;
19
- private targetLayer;
20
- constructor(parent: LayeredArchitectureBuilder, targetLayer: string);
21
- shouldNotBeAccessedByAnyLayer(): LayeredArchitectureBuilder;
22
- shouldOnlyBeAccessedBy(...allowedLayers: string[]): LayeredArchitectureBuilder;
23
- }
@@ -1,20 +0,0 @@
1
- import { FileLocator, FileQueryOptions } from './FileLocator';
2
- import { ClassLocator, ClassQueryOptions } from './ClassLocator';
3
- import { LayeredArchitectureBuilder } from './LayerLocator';
4
- import { FunctionLocator, FunctionQueryOptions } from './FunctionLocator';
5
- import { PropertyLocator, PropertyQueryOptions } from './PropertyLocator';
6
- import { SliceLocator } from './SliceLocator';
7
- export declare class Project {
8
- private program;
9
- private typeChecker;
10
- constructor(options?: {
11
- tsConfigFilePath?: string;
12
- });
13
- getFiles(options?: FileQueryOptions): FileLocator;
14
- getClasses(options?: ClassQueryOptions): ClassLocator;
15
- layeredArchitecture(): LayeredArchitectureBuilder;
16
- getFunctions(options?: FunctionQueryOptions): FunctionLocator;
17
- getProperties(options?: PropertyQueryOptions): PropertyLocator;
18
- getSlices(pattern: string): SliceLocator;
19
- }
20
- export declare function parseProject(tsConfigFilePath?: string): Project;
@@ -1,12 +0,0 @@
1
- import { RuleResult } from './types';
2
- import * as ts from 'typescript';
3
- export interface PropertyQueryOptions {
4
- inFolder?: string;
5
- matchNamePattern?: string | RegExp;
6
- }
7
- export declare class PropertyLocator {
8
- private program;
9
- readonly properties: ts.PropertyDeclaration[];
10
- constructor(properties: ts.PropertyDeclaration[], program: ts.Program, options?: PropertyQueryOptions);
11
- checkBeReadonly(isNot: boolean): RuleResult;
12
- }