@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,16 @@
1
+ import type * as ts from 'typescript';
2
+
3
+ export interface ClassLocatorData {
4
+ type: 'ClassLocator';
5
+ classes: ts.ClassDeclaration[];
6
+ program: ts.Program;
7
+ }
8
+
9
+ export interface ClassQueryOptions {
10
+ inFolder?: string;
11
+ matchNamePattern?: string | RegExp;
12
+ withDecorator?: string;
13
+ extending?: string;
14
+ implementing?: string;
15
+ havingModifier?: string;
16
+ }
@@ -0,0 +1,7 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ describe('checkDependOnFilesInFolder', () => {
4
+ it('is thoroughly integration tested in architecture.test.ts', () => {
5
+ expect(true).toBe(true);
6
+ });
7
+ });
@@ -0,0 +1,36 @@
1
+ import type { RuleResult } from '../types';
2
+ import { getFileDependencies } from './getFileDependencies';
3
+ import type { FileLocatorData } from './types';
4
+
5
+ export function checkDependOnFilesInFolder(
6
+ locator: FileLocatorData,
7
+ targetFolder: string,
8
+ isNot: boolean,
9
+ ): RuleResult {
10
+ const violations: string[] = [];
11
+
12
+ for (const file of locator.files) {
13
+ const dependencies = getFileDependencies(file, locator.program);
14
+
15
+ const dependsOnTarget = dependencies.some(
16
+ (depPath) =>
17
+ depPath.includes(`/${targetFolder}/`) ||
18
+ depPath.includes(`\\${targetFolder}\\`),
19
+ );
20
+
21
+ if (isNot && dependsOnTarget) {
22
+ violations.push(
23
+ `File ${file.fileName} depends on files in ${targetFolder}, but it shouldn't.`,
24
+ );
25
+ } else if (!isNot && !dependsOnTarget) {
26
+ violations.push(
27
+ `File ${file.fileName} does not depend on files in ${targetFolder}, but it should.`,
28
+ );
29
+ }
30
+ }
31
+
32
+ return {
33
+ pass: violations.length === 0,
34
+ message: () => violations.join('\n'),
35
+ };
36
+ }
@@ -0,0 +1,7 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ describe('fileCheckBeFreeOfCycles', () => {
4
+ it('is thoroughly integration tested in architecture.test.ts', () => {
5
+ expect(true).toBe(true);
6
+ });
7
+ });
@@ -0,0 +1,72 @@
1
+ import type { RuleResult } from '../types';
2
+ import { getFileDependencies } from './getFileDependencies';
3
+ import type { FileLocatorData } from './types';
4
+
5
+ export function fileCheckBeFreeOfCycles(
6
+ locator: FileLocatorData,
7
+ isNot: boolean,
8
+ ): RuleResult {
9
+ const violations: string[] = [];
10
+ const targetedFilePaths = new Set<string>(
11
+ locator.files.map((f) => f.fileName),
12
+ );
13
+ const graph = new Map<string, string[]>();
14
+
15
+ for (const file of locator.files) {
16
+ const filePath = file.fileName;
17
+ const dependencies = getFileDependencies(file, locator.program).filter(
18
+ (p) => targetedFilePaths.has(p),
19
+ );
20
+
21
+ graph.set(filePath, dependencies);
22
+ }
23
+
24
+ const visited = new Set<string>();
25
+ const recStack = new Set<string>();
26
+ const cycles: string[][] = [];
27
+
28
+ const isCyclicUtil = (node: string, path: string[]) => {
29
+ if (recStack.has(node)) {
30
+ const cycleStartIndex = path.indexOf(node);
31
+ const cycleStr = [...path.slice(cycleStartIndex), node].join('->');
32
+ if (!cycles.some((c) => c.join('->') === cycleStr)) {
33
+ cycles.push([...path.slice(cycleStartIndex), node]);
34
+ }
35
+ return true;
36
+ }
37
+
38
+ if (visited.has(node)) return false;
39
+
40
+ visited.add(node);
41
+ recStack.add(node);
42
+ path.push(node);
43
+
44
+ const neighbors = graph.get(node) || [];
45
+ for (const neighbor of neighbors) {
46
+ isCyclicUtil(neighbor, path);
47
+ }
48
+
49
+ recStack.delete(node);
50
+ path.pop();
51
+ return false;
52
+ };
53
+
54
+ for (const node of graph.keys()) {
55
+ if (!visited.has(node)) {
56
+ isCyclicUtil(node, []);
57
+ }
58
+ }
59
+
60
+ if (isNot && cycles.length === 0) {
61
+ violations.push('Expected cyclic dependencies, but found none.');
62
+ } else if (!isNot && cycles.length > 0) {
63
+ for (const cycle of cycles) {
64
+ violations.push(`Cycle detected: ${cycle.join(' -> ')}`);
65
+ }
66
+ }
67
+
68
+ return {
69
+ pass: violations.length === 0,
70
+ message: () => violations.join('\n'),
71
+ };
72
+ }
@@ -0,0 +1,8 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { fileCheckHaveMaxCyclomaticComplexity } from './fileCheckHaveMaxCyclomaticComplexity';
3
+
4
+ describe('fileCheckHaveMaxCyclomaticComplexity', () => {
5
+ it('should exist and be callable', () => {
6
+ expect(typeof fileCheckHaveMaxCyclomaticComplexity).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 { FileLocatorData } from './types';
6
+
7
+ export function fileCheckHaveMaxCyclomaticComplexity(
8
+ locator: FileLocatorData,
9
+ max: number,
10
+ isNot: boolean,
11
+ ): RuleResult {
12
+ return sharedCheckHaveMaxCyclomaticComplexity(
13
+ locator.files,
14
+ (f) => f.fileName,
15
+ (file) => {
16
+ let complexity = 0;
17
+ const visit = (node: ts.Node) => {
18
+ if (
19
+ ts.isFunctionDeclaration(node) ||
20
+ ts.isMethodDeclaration(node) ||
21
+ ts.isArrowFunction(node) ||
22
+ ts.isFunctionExpression(node)
23
+ ) {
24
+ complexity += calculateCyclomaticComplexity(node);
25
+ }
26
+ ts.forEachChild(node, visit);
27
+ };
28
+ visit(file);
29
+ return complexity;
30
+ },
31
+ 'File',
32
+ max,
33
+ isNot,
34
+ );
35
+ }
@@ -0,0 +1,32 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createSourceFile } from '../testUtils';
3
+ import { fileCheckHaveMaxExportedFunctions } from './fileCheckHaveMaxExportedFunctions';
4
+ import type { FileLocatorData } from './types';
5
+
6
+ describe('fileCheckHaveMaxExportedFunctions', () => {
7
+ it('should pass if under max exports', () => {
8
+ const sourceFile = createSourceFile(`export function a() {}`);
9
+ const data: FileLocatorData = {
10
+ type: 'FileLocator',
11
+ files: [sourceFile],
12
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking TS types for tests
13
+ program: {} as any,
14
+ };
15
+ const result = fileCheckHaveMaxExportedFunctions(data, 1, false);
16
+ expect(result.pass).toBe(true);
17
+ });
18
+
19
+ it('should fail if over max exports', () => {
20
+ const sourceFile = createSourceFile(
21
+ `export function a() {} export function b() {}`,
22
+ );
23
+ const data: FileLocatorData = {
24
+ type: 'FileLocator',
25
+ files: [sourceFile],
26
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking TS types for tests
27
+ program: {} as any,
28
+ };
29
+ const result = fileCheckHaveMaxExportedFunctions(data, 1, false);
30
+ expect(result.pass).toBe(false);
31
+ });
32
+ });
@@ -0,0 +1,45 @@
1
+ import * as ts from 'typescript';
2
+ import type { RuleResult } from '../types';
3
+ import type { FileLocatorData } from './types';
4
+
5
+ export function fileCheckHaveMaxExportedFunctions(
6
+ locator: FileLocatorData,
7
+ max: number,
8
+ isNot: boolean,
9
+ ): RuleResult {
10
+ const violations: string[] = [];
11
+ for (const file of locator.files) {
12
+ let exportedFuncCount = 0;
13
+
14
+ ts.forEachChild(file, (node) => {
15
+ if (ts.isFunctionDeclaration(node)) {
16
+ const modifiers = ts.canHaveModifiers(node)
17
+ ? ts.getModifiers(node)
18
+ : undefined;
19
+ if (modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {
20
+ exportedFuncCount++;
21
+ }
22
+ }
23
+ });
24
+
25
+ const exceeds = exportedFuncCount > max;
26
+
27
+ if (isNot && exceeds) {
28
+ violations.push(
29
+ `File ${file.fileName} has ${exportedFuncCount} exported functions, which exceeds the maximum of ${max}, but it shouldn't.`,
30
+ );
31
+ } else if (!isNot && exceeds) {
32
+ violations.push(
33
+ `File ${file.fileName} has ${exportedFuncCount} exported functions, which exceeds the maximum of ${max}.`,
34
+ );
35
+ }
36
+ }
37
+ return {
38
+ pass: isNot ? violations.length > 0 : violations.length === 0,
39
+ message: () =>
40
+ violations.join('\n') ||
41
+ (isNot
42
+ ? 'Expected some files to exceed maximum exported functions, but none did.'
43
+ : ''),
44
+ };
45
+ }
@@ -0,0 +1,8 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { fileCheckHaveMinMaintainabilityIndex } from './fileCheckHaveMinMaintainabilityIndex';
3
+
4
+ describe('fileCheckHaveMinMaintainabilityIndex', () => {
5
+ it('should exist and be callable', () => {
6
+ expect(typeof fileCheckHaveMinMaintainabilityIndex).toBe('function');
7
+ });
8
+ });
@@ -0,0 +1,19 @@
1
+ import { calculateMaintainabilityIndex } from '../metrics/calculateMaintainabilityIndex';
2
+ import { sharedCheckHaveMinMaintainabilityIndex } from '../shared/sharedCheckHaveMinMaintainabilityIndex';
3
+ import type { RuleResult } from '../types';
4
+ import type { FileLocatorData } from './types';
5
+
6
+ export function fileCheckHaveMinMaintainabilityIndex(
7
+ locator: FileLocatorData,
8
+ min: number,
9
+ isNot: boolean,
10
+ ): RuleResult {
11
+ return sharedCheckHaveMinMaintainabilityIndex(
12
+ locator.files,
13
+ (f) => f.fileName,
14
+ calculateMaintainabilityIndex,
15
+ 'File',
16
+ min,
17
+ isNot,
18
+ );
19
+ }
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createSourceFile } from '../testUtils';
3
+ import { fileCheckMatchNamePattern } from './fileCheckMatchNamePattern';
4
+ import type { FileLocatorData } from './types';
5
+
6
+ describe('fileCheckMatchNamePattern', () => {
7
+ it('should pass if filename matches', () => {
8
+ const sourceFile = createSourceFile(`// code`, 'src/core/test.ts');
9
+ const data: FileLocatorData = {
10
+ type: 'FileLocator',
11
+ files: [sourceFile],
12
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking TS types for tests
13
+ program: {} as any,
14
+ };
15
+ const result = fileCheckMatchNamePattern(data, /.*\.ts$/, false);
16
+ expect(result.pass).toBe(true);
17
+ });
18
+
19
+ it('should fail if filename does not match', () => {
20
+ const sourceFile = createSourceFile(`// code`, 'src/core/test.js');
21
+ const data: FileLocatorData = {
22
+ type: 'FileLocator',
23
+ files: [sourceFile],
24
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking TS types for tests
25
+ program: {} as any,
26
+ };
27
+ const result = fileCheckMatchNamePattern(data, /.*\.ts$/, false);
28
+ expect(result.pass).toBe(false);
29
+ });
30
+ });
@@ -0,0 +1,17 @@
1
+ import { sharedCheckMatchNamePattern } from '../shared/sharedCheckMatchNamePattern';
2
+ import type { RuleResult } from '../types';
3
+ import type { FileLocatorData } from './types';
4
+
5
+ export function fileCheckMatchNamePattern(
6
+ locator: FileLocatorData,
7
+ pattern: string | RegExp,
8
+ isNot: boolean,
9
+ ): RuleResult {
10
+ return sharedCheckMatchNamePattern(
11
+ locator.files,
12
+ (f) => f.fileName,
13
+ 'File',
14
+ pattern,
15
+ isNot,
16
+ );
17
+ }
@@ -0,0 +1,43 @@
1
+ import * as ts from 'typescript';
2
+
3
+ export function getFileDependencies(
4
+ sourceFile: ts.SourceFile,
5
+ program: ts.Program,
6
+ ): string[] {
7
+ const dependencies: string[] = [];
8
+ const compilerOptions = program.getCompilerOptions();
9
+
10
+ const visit = (node: ts.Node) => {
11
+ if (ts.isImportDeclaration(node)) {
12
+ const moduleSpecifier = (node.moduleSpecifier as ts.StringLiteral).text;
13
+ const resolved = ts.resolveModuleName(
14
+ moduleSpecifier,
15
+ sourceFile.fileName,
16
+ compilerOptions,
17
+ ts.sys,
18
+ );
19
+ if (resolved.resolvedModule?.resolvedFileName) {
20
+ if (!resolved.resolvedModule.isExternalLibraryImport) {
21
+ dependencies.push(resolved.resolvedModule.resolvedFileName);
22
+ }
23
+ }
24
+ } else if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
25
+ const moduleSpecifier = (node.moduleSpecifier as ts.StringLiteral).text;
26
+ const resolved = ts.resolveModuleName(
27
+ moduleSpecifier,
28
+ sourceFile.fileName,
29
+ compilerOptions,
30
+ ts.sys,
31
+ );
32
+ if (resolved.resolvedModule?.resolvedFileName) {
33
+ if (!resolved.resolvedModule.isExternalLibraryImport) {
34
+ dependencies.push(resolved.resolvedModule.resolvedFileName);
35
+ }
36
+ }
37
+ }
38
+ ts.forEachChild(node, visit);
39
+ };
40
+
41
+ visit(sourceFile);
42
+ return dependencies;
43
+ }
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createSourceFile } from '../testUtils';
3
+ import { locateFiles } from './locateFiles';
4
+
5
+ describe('locateFiles', () => {
6
+ it('should filter files by inFolder query', () => {
7
+ const file1 = createSourceFile('function a() {}', 'src/domain/file1.ts');
8
+ const file2 = createSourceFile('function b() {}', 'src/infra/file2.ts');
9
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking TS types for tests
10
+ const result = locateFiles([file1, file2], {} as any, {
11
+ inFolder: 'domain',
12
+ });
13
+ expect(result.files.length).toBe(1);
14
+ expect(result.files[0].fileName).toBe('src/domain/file1.ts');
15
+ });
16
+
17
+ it('should filter files by matchNamePattern query', () => {
18
+ const file1 = createSourceFile('function a() {}', 'src/domain/file1.ts');
19
+ const file2 = createSourceFile(
20
+ 'function b() {}',
21
+ 'src/domain/file2.spec.ts',
22
+ );
23
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking TS types for tests
24
+ const result = locateFiles([file1, file2], {} as any, {
25
+ matchNamePattern: /\.spec\.ts$/,
26
+ });
27
+ expect(result.files.length).toBe(1);
28
+ expect(result.files[0].fileName).toBe('src/domain/file2.spec.ts');
29
+ });
30
+ });
@@ -0,0 +1,31 @@
1
+ import type * as ts from 'typescript';
2
+ import type { FileLocatorData, FileQueryOptions } from './types';
3
+
4
+ export function locateFiles(
5
+ sourceFiles: ts.SourceFile[],
6
+ program: ts.Program,
7
+ options?: FileQueryOptions,
8
+ ): FileLocatorData {
9
+ let filtered = sourceFiles;
10
+
11
+ if (options?.inFolder) {
12
+ filtered = filtered.filter(
13
+ (file) =>
14
+ file.fileName.includes(`/${options.inFolder}/`) ||
15
+ file.fileName.includes(`\\${options.inFolder}\\`),
16
+ );
17
+ }
18
+ if (options?.matchNamePattern) {
19
+ const regex =
20
+ typeof options.matchNamePattern === 'string'
21
+ ? new RegExp(options.matchNamePattern)
22
+ : options.matchNamePattern;
23
+ filtered = filtered.filter((file) => regex.test(file.fileName));
24
+ }
25
+
26
+ return {
27
+ type: 'FileLocator',
28
+ files: filtered,
29
+ program,
30
+ };
31
+ }
@@ -0,0 +1,12 @@
1
+ import type * as ts from 'typescript';
2
+
3
+ export interface FileLocatorData {
4
+ type: 'FileLocator';
5
+ files: ts.SourceFile[];
6
+ program: ts.Program;
7
+ }
8
+
9
+ export interface FileQueryOptions {
10
+ inFolder?: string;
11
+ matchNamePattern?: string | RegExp;
12
+ }
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createSourceFile, getFunctions } from '../testUtils';
3
+ import { functionCheckHaveExplicitReturnType } from './functionCheckHaveExplicitReturnType';
4
+ import type { FunctionLocatorData } from './types';
5
+
6
+ describe('functionCheckHaveExplicitReturnType', () => {
7
+ it('should pass if function has explicit return type', () => {
8
+ const sourceFile = createSourceFile(`function myFunc(): void {}`);
9
+ const data: FunctionLocatorData = {
10
+ type: 'FunctionLocator',
11
+ functions: getFunctions(sourceFile),
12
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking TS types for tests
13
+ program: {} as any,
14
+ };
15
+ const result = functionCheckHaveExplicitReturnType(data, false);
16
+ expect(result.pass).toBe(true);
17
+ });
18
+
19
+ it('should fail if function lacks explicit return type', () => {
20
+ const sourceFile = createSourceFile(`function myFunc() {}`);
21
+ const data: FunctionLocatorData = {
22
+ type: 'FunctionLocator',
23
+ functions: getFunctions(sourceFile),
24
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking TS types for tests
25
+ program: {} as any,
26
+ };
27
+ const result = functionCheckHaveExplicitReturnType(data, false);
28
+ expect(result.pass).toBe(false);
29
+ });
30
+ });
@@ -0,0 +1,29 @@
1
+ import * as ts from 'typescript';
2
+ import type { RuleResult } from '../types';
3
+ import type { FunctionLocatorData } from './types';
4
+
5
+ export function functionCheckHaveExplicitReturnType(
6
+ locator: FunctionLocatorData,
7
+ isNot: boolean,
8
+ ): RuleResult {
9
+ const violations: string[] = [];
10
+ for (const f of locator.functions) {
11
+ const name =
12
+ f.name && ts.isIdentifier(f.name) ? f.name.text : 'Anonymous Function';
13
+ const hasType = !!f.type;
14
+
15
+ if (isNot && hasType) {
16
+ violations.push(
17
+ `Function ${name} has an explicit return type, but it shouldn't.`,
18
+ );
19
+ } else if (!isNot && !hasType) {
20
+ violations.push(
21
+ `Function ${name} does not have an explicit return type, but it should.`,
22
+ );
23
+ }
24
+ }
25
+ return {
26
+ pass: violations.length === 0,
27
+ message: () => violations.join('\n'),
28
+ };
29
+ }
@@ -0,0 +1,8 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { functionCheckHaveMaxCyclomaticComplexity } from './functionCheckHaveMaxCyclomaticComplexity';
3
+
4
+ describe('functionCheckHaveMaxCyclomaticComplexity', () => {
5
+ it('should exist and be callable', () => {
6
+ expect(typeof functionCheckHaveMaxCyclomaticComplexity).toBe('function');
7
+ });
8
+ });
@@ -0,0 +1,20 @@
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 { FunctionLocatorData } from './types';
6
+
7
+ export function functionCheckHaveMaxCyclomaticComplexity(
8
+ locator: FunctionLocatorData,
9
+ max: number,
10
+ isNot: boolean,
11
+ ): RuleResult {
12
+ return sharedCheckHaveMaxCyclomaticComplexity(
13
+ locator.functions,
14
+ (f) => (f.name && ts.isIdentifier(f.name) ? f.name.text : undefined),
15
+ calculateCyclomaticComplexity,
16
+ 'Function',
17
+ max,
18
+ isNot,
19
+ );
20
+ }
@@ -0,0 +1,8 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { functionCheckHaveMinMaintainabilityIndex } from './functionCheckHaveMinMaintainabilityIndex';
3
+
4
+ describe('functionCheckHaveMinMaintainabilityIndex', () => {
5
+ it('should exist and be callable', () => {
6
+ expect(typeof functionCheckHaveMinMaintainabilityIndex).toBe('function');
7
+ });
8
+ });
@@ -0,0 +1,20 @@
1
+ import * as ts from 'typescript';
2
+ import { calculateMaintainabilityIndex } from '../metrics/calculateMaintainabilityIndex';
3
+ import { sharedCheckHaveMinMaintainabilityIndex } from '../shared/sharedCheckHaveMinMaintainabilityIndex';
4
+ import type { RuleResult } from '../types';
5
+ import type { FunctionLocatorData } from './types';
6
+
7
+ export function functionCheckHaveMinMaintainabilityIndex(
8
+ locator: FunctionLocatorData,
9
+ min: number,
10
+ isNot: boolean,
11
+ ): RuleResult {
12
+ return sharedCheckHaveMinMaintainabilityIndex(
13
+ locator.functions,
14
+ (f) => (f.name && ts.isIdentifier(f.name) ? f.name.text : undefined),
15
+ calculateMaintainabilityIndex,
16
+ 'Function',
17
+ min,
18
+ isNot,
19
+ );
20
+ }
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createSourceFile, getFunctions } from '../testUtils';
3
+ import { functionCheckHaveModifier } from './functionCheckHaveModifier';
4
+ import type { FunctionLocatorData } from './types';
5
+
6
+ describe('functionCheckHaveModifier', () => {
7
+ it('should pass if function has modifier', () => {
8
+ const sourceFile = createSourceFile(`export function myFunc() {}`);
9
+ const data: FunctionLocatorData = {
10
+ type: 'FunctionLocator',
11
+ functions: getFunctions(sourceFile),
12
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking TS types for tests
13
+ program: {} as any,
14
+ };
15
+ const result = functionCheckHaveModifier(data, 'export', false);
16
+ expect(result.pass).toBe(true);
17
+ });
18
+
19
+ it('should fail if function lacks modifier', () => {
20
+ const sourceFile = createSourceFile(`function myFunc() {}`);
21
+ const data: FunctionLocatorData = {
22
+ type: 'FunctionLocator',
23
+ functions: getFunctions(sourceFile),
24
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking TS types for tests
25
+ program: {} as any,
26
+ };
27
+ const result = functionCheckHaveModifier(data, 'export', false);
28
+ expect(result.pass).toBe(false);
29
+ });
30
+ });
@@ -0,0 +1,18 @@
1
+ import * as ts from 'typescript';
2
+ import { sharedCheckHaveModifier } from '../shared/sharedCheckHaveModifier';
3
+ import type { RuleResult } from '../types';
4
+ import type { FunctionLocatorData } from './types';
5
+
6
+ export function functionCheckHaveModifier(
7
+ locator: FunctionLocatorData,
8
+ modifierStr: string,
9
+ isNot: boolean,
10
+ ): RuleResult {
11
+ return sharedCheckHaveModifier(
12
+ locator.functions,
13
+ (f) => (f.name && ts.isIdentifier(f.name) ? f.name.text : undefined),
14
+ 'Function',
15
+ modifierStr,
16
+ isNot,
17
+ );
18
+ }