@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.
- package/dist/index.js +13 -17
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +776 -489
- package/dist/index.mjs.map +1 -1
- package/dist/src/core/classes/classCheckExtendClass.d.ts +3 -0
- package/dist/src/core/classes/classCheckHaveMaxCyclomaticComplexity.d.ts +3 -0
- package/dist/src/core/classes/classCheckHaveModifier.d.ts +3 -0
- package/dist/src/core/classes/classCheckHaveNameMatchingFileName.d.ts +3 -0
- package/dist/src/core/classes/classCheckImplementInterface.d.ts +3 -0
- package/dist/src/core/classes/classCheckMatchNamePattern.d.ts +3 -0
- package/dist/src/core/classes/classCheckResideInFolder.d.ts +3 -0
- package/dist/src/core/classes/locateClasses.d.ts +3 -0
- package/dist/src/core/classes/types.d.ts +14 -0
- package/dist/src/core/files/checkDependOnFilesInFolder.d.ts +3 -0
- package/dist/src/core/files/fileCheckBeFreeOfCycles.d.ts +3 -0
- package/dist/src/core/files/fileCheckHaveMaxCyclomaticComplexity.d.ts +3 -0
- package/dist/src/core/files/fileCheckHaveMaxExportedFunctions.d.ts +3 -0
- package/dist/src/core/files/fileCheckHaveMinMaintainabilityIndex.d.ts +3 -0
- package/dist/src/core/files/fileCheckMatchNamePattern.d.ts +3 -0
- package/dist/src/core/files/getFileDependencies.d.ts +2 -0
- package/dist/src/core/files/locateFiles.d.ts +3 -0
- package/dist/src/core/files/types.d.ts +10 -0
- package/dist/src/core/functions/functionCheckHaveExplicitReturnType.d.ts +3 -0
- package/dist/src/core/functions/functionCheckHaveMaxCyclomaticComplexity.d.ts +3 -0
- package/dist/src/core/functions/functionCheckHaveMinMaintainabilityIndex.d.ts +3 -0
- package/dist/src/core/functions/functionCheckHaveModifier.d.ts +3 -0
- package/dist/src/core/functions/functionCheckHaveNameMatchingFileName.d.ts +3 -0
- package/dist/src/core/functions/functionCheckMatchNamePattern.d.ts +3 -0
- package/dist/src/core/functions/locateFunctions.d.ts +3 -0
- package/dist/src/core/functions/types.d.ts +11 -0
- package/dist/src/core/layers/checkLayeredArchitecture.d.ts +3 -0
- package/dist/src/core/layers/createLayeredArchitecture.d.ts +3 -0
- package/dist/src/core/layers/getLayerDependencies.d.ts +2 -0
- package/dist/src/core/layers/layer.d.ts +2 -0
- package/dist/src/core/layers/layerShouldNotBeAccessedByAnyLayer.d.ts +2 -0
- package/dist/src/core/layers/layerShouldOnlyBeAccessedBy.d.ts +2 -0
- package/dist/src/core/layers/types.d.ts +8 -0
- package/dist/src/core/metrics/calculateCyclomaticComplexity.d.ts +2 -0
- package/dist/src/core/metrics/calculateMaintainabilityIndex.d.ts +2 -0
- package/dist/src/core/project/parseProject.d.ts +22 -0
- package/dist/src/core/properties/locateProperties.d.ts +3 -0
- package/dist/src/core/properties/propertyCheckBeReadonly.d.ts +3 -0
- package/dist/src/core/properties/types.d.ts +10 -0
- package/dist/src/core/shared/sharedCheckHaveMaxCyclomaticComplexity.d.ts +1 -0
- package/dist/src/core/shared/sharedCheckHaveMinMaintainabilityIndex.d.ts +1 -0
- package/dist/src/core/shared/sharedCheckHaveModifier.d.ts +2 -0
- package/dist/src/core/shared/sharedCheckHaveNameMatchingFileName.d.ts +2 -0
- package/dist/src/core/shared/sharedCheckMatchNamePattern.d.ts +1 -0
- package/dist/src/core/slices/locateSlices.d.ts +3 -0
- package/dist/src/core/slices/sliceCheckBeFreeOfCycles.d.ts +3 -0
- package/dist/src/core/slices/sliceCheckHaveMaxDistanceFromMainSequence.d.ts +3 -0
- package/dist/src/core/slices/types.d.ts +8 -0
- package/dist/src/core/types.d.ts +7 -0
- package/dist/src/core/utils/ruleBuilder.d.ts +6 -0
- package/dist/src/index.d.ts +34 -8
- package/dist/src/matchers/index.d.ts +5 -0
- package/package.json +1 -1
- package/src/core/classes/classCheckExtendClass.test.ts +30 -0
- package/src/core/classes/classCheckExtendClass.ts +41 -0
- package/src/core/classes/classCheckHaveMaxCyclomaticComplexity.test.ts +8 -0
- package/src/core/classes/classCheckHaveMaxCyclomaticComplexity.ts +35 -0
- package/src/core/classes/classCheckHaveModifier.test.ts +30 -0
- package/src/core/classes/classCheckHaveModifier.ts +17 -0
- package/src/core/classes/classCheckHaveNameMatchingFileName.test.ts +30 -0
- package/src/core/classes/classCheckHaveNameMatchingFileName.ts +15 -0
- package/src/core/classes/classCheckImplementInterface.test.ts +30 -0
- package/src/core/classes/classCheckImplementInterface.ts +43 -0
- package/src/core/classes/classCheckMatchNamePattern.test.ts +8 -0
- package/src/core/classes/classCheckMatchNamePattern.ts +17 -0
- package/src/core/classes/classCheckResideInFolder.test.ts +8 -0
- package/src/core/classes/classCheckResideInFolder.ts +31 -0
- package/src/core/classes/locateClasses.test.ts +13 -0
- package/src/core/classes/locateClasses.ts +127 -0
- package/src/core/classes/types.ts +16 -0
- package/src/core/files/checkDependOnFilesInFolder.test.ts +7 -0
- package/src/core/files/checkDependOnFilesInFolder.ts +36 -0
- package/src/core/files/fileCheckBeFreeOfCycles.test.ts +7 -0
- package/src/core/files/fileCheckBeFreeOfCycles.ts +72 -0
- package/src/core/files/fileCheckHaveMaxCyclomaticComplexity.test.ts +8 -0
- package/src/core/files/fileCheckHaveMaxCyclomaticComplexity.ts +35 -0
- package/src/core/files/fileCheckHaveMaxExportedFunctions.test.ts +32 -0
- package/src/core/files/fileCheckHaveMaxExportedFunctions.ts +45 -0
- package/src/core/files/fileCheckHaveMinMaintainabilityIndex.test.ts +8 -0
- package/src/core/files/fileCheckHaveMinMaintainabilityIndex.ts +19 -0
- package/src/core/files/fileCheckMatchNamePattern.test.ts +30 -0
- package/src/core/files/fileCheckMatchNamePattern.ts +17 -0
- package/src/core/files/getFileDependencies.ts +43 -0
- package/src/core/files/locateFiles.test.ts +30 -0
- package/src/core/files/locateFiles.ts +31 -0
- package/src/core/files/types.ts +12 -0
- package/src/core/functions/functionCheckHaveExplicitReturnType.test.ts +30 -0
- package/src/core/functions/functionCheckHaveExplicitReturnType.ts +29 -0
- package/src/core/functions/functionCheckHaveMaxCyclomaticComplexity.test.ts +8 -0
- package/src/core/functions/functionCheckHaveMaxCyclomaticComplexity.ts +20 -0
- package/src/core/functions/functionCheckHaveMinMaintainabilityIndex.test.ts +8 -0
- package/src/core/functions/functionCheckHaveMinMaintainabilityIndex.ts +20 -0
- package/src/core/functions/functionCheckHaveModifier.test.ts +30 -0
- package/src/core/functions/functionCheckHaveModifier.ts +18 -0
- package/src/core/functions/functionCheckHaveNameMatchingFileName.test.ts +30 -0
- package/src/core/functions/functionCheckHaveNameMatchingFileName.ts +16 -0
- package/src/core/functions/functionCheckMatchNamePattern.test.ts +30 -0
- package/src/core/functions/functionCheckMatchNamePattern.ts +18 -0
- package/src/core/functions/locateFunctions.test.ts +16 -0
- package/src/core/functions/locateFunctions.ts +47 -0
- package/src/core/functions/types.ts +13 -0
- package/src/core/layers/checkLayeredArchitecture.test.ts +23 -0
- package/src/core/layers/checkLayeredArchitecture.ts +15 -0
- package/src/core/layers/createLayeredArchitecture.test.ts +13 -0
- package/src/core/layers/createLayeredArchitecture.ts +15 -0
- package/src/core/layers/getLayerDependencies.ts +45 -0
- package/src/core/layers/layer.test.ts +12 -0
- package/src/core/layers/layer.ts +10 -0
- package/src/core/layers/layerShouldNotBeAccessedByAnyLayer.test.ts +14 -0
- package/src/core/layers/layerShouldNotBeAccessedByAnyLayer.ts +40 -0
- package/src/core/layers/layerShouldOnlyBeAccessedBy.test.ts +15 -0
- package/src/core/layers/layerShouldOnlyBeAccessedBy.ts +48 -0
- package/src/core/layers/types.ts +9 -0
- package/src/core/metrics/calculateCyclomaticComplexity.test.ts +23 -0
- package/src/core/metrics/calculateCyclomaticComplexity.ts +36 -0
- package/src/core/metrics/calculateMaintainabilityIndex.test.ts +38 -0
- package/src/core/metrics/calculateMaintainabilityIndex.ts +47 -0
- package/src/core/project/parseProject.test.ts +19 -0
- package/src/core/project/parseProject.ts +216 -0
- package/src/core/properties/locateProperties.test.ts +19 -0
- package/src/core/properties/locateProperties.ts +41 -0
- package/src/core/properties/propertyCheckBeReadonly.test.ts +32 -0
- package/src/core/properties/propertyCheckBeReadonly.ts +27 -0
- package/src/core/properties/types.ts +12 -0
- package/src/core/shared/sharedCheckHaveMaxCyclomaticComplexity.test.ts +30 -0
- package/src/core/shared/sharedCheckHaveMaxCyclomaticComplexity.ts +23 -0
- package/src/core/shared/sharedCheckHaveMinMaintainabilityIndex.test.ts +30 -0
- package/src/core/shared/sharedCheckHaveMinMaintainabilityIndex.ts +23 -0
- package/src/core/shared/sharedCheckHaveModifier.test.ts +31 -0
- package/src/core/shared/sharedCheckHaveModifier.ts +51 -0
- package/src/core/shared/sharedCheckHaveNameMatchingFileName.test.ts +17 -0
- package/src/core/shared/sharedCheckHaveNameMatchingFileName.ts +36 -0
- package/src/core/shared/sharedCheckMatchNamePattern.test.ts +29 -0
- package/src/core/shared/sharedCheckMatchNamePattern.ts +22 -0
- package/src/core/slices/locateSlices.test.ts +22 -0
- package/src/core/slices/locateSlices.ts +35 -0
- package/src/core/slices/sliceCheckBeFreeOfCycles.test.ts +26 -0
- package/src/core/slices/sliceCheckBeFreeOfCycles.ts +91 -0
- package/src/core/slices/sliceCheckHaveMaxDistanceFromMainSequence.test.ts +27 -0
- package/src/core/slices/sliceCheckHaveMaxDistanceFromMainSequence.ts +110 -0
- package/src/core/slices/types.ts +9 -0
- package/src/core/testUtils.ts +61 -0
- package/src/core/types.ts +15 -0
- package/src/core/utils/ruleBuilder.test.ts +24 -0
- package/src/core/utils/ruleBuilder.ts +28 -0
- package/src/index.ts +34 -8
- package/src/matchers/index.ts +269 -51
- package/test/architecture.test.ts +109 -16
- package/tsconfig.json +2 -1
- package/vite.config.ts +2 -2
- package/dist/src/core/ClassLocator.d.ts +0 -20
- package/dist/src/core/FileLocator.d.ts +0 -15
- package/dist/src/core/FunctionLocator.d.ts +0 -15
- package/dist/src/core/LayerLocator.d.ts +0 -23
- package/dist/src/core/Project.d.ts +0 -20
- package/dist/src/core/PropertyLocator.d.ts +0 -12
- package/dist/src/core/SliceLocator.d.ts +0 -10
- package/src/core/ClassLocator.ts +0 -226
- package/src/core/FileLocator.ts +0 -168
- package/src/core/FunctionLocator.ts +0 -105
- package/src/core/LayerLocator.ts +0 -119
- package/src/core/Project.ts +0 -171
- package/src/core/PropertyLocator.ts +0 -50
- package/src/core/SliceLocator.ts +0 -92
package/src/matchers/index.ts
CHANGED
|
@@ -1,107 +1,320 @@
|
|
|
1
1
|
import { expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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
|
-
|
|
13
|
-
|
|
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:
|
|
24
|
-
|
|
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:
|
|
32
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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(
|
|
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,
|
|
2
|
-
import { parseProject } from '../src/core/
|
|
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('
|
|
23
|
-
it('
|
|
24
|
-
const
|
|
25
|
-
|
|
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('
|
|
29
|
-
const
|
|
30
|
-
|
|
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('
|
|
47
|
-
it('
|
|
48
|
-
const architecture = project
|
|
49
|
-
.
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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
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
|
-
}
|