x-fidelity 3.12.0 → 3.13.0
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/CHANGELOG.md +33 -0
- package/dist/demoConfig/node-fullstack.json +17 -7
- package/dist/demoConfig/rules/functionComplexity-iterative-rule.json +31 -0
- package/dist/demoConfig/rules/functionCount-iterative-rule.json +30 -0
- package/dist/demoConfig/rules/mutuallyExclusivePackages-global-rule.json +38 -0
- package/dist/demoConfig/rules/newSdkFeatureNotAdoped-global-rule.json +1 -1
- package/dist/demoConfig/rules/openaiAnalysisTestCriticality-global-rule.json +31 -0
- package/dist/facts/globalFileAnalysisFacts.js +1 -1
- package/dist/facts/openaiAnalysisFacts.js +2 -2
- package/dist/plugins/xfiPluginAst/facts/astFact.d.ts +2 -0
- package/dist/plugins/xfiPluginAst/facts/astFact.js +33 -0
- package/dist/plugins/xfiPluginAst/facts/functionComplexityFact.d.ts +2 -0
- package/dist/plugins/xfiPluginAst/facts/functionComplexityFact.js +136 -0
- package/dist/plugins/xfiPluginAst/facts/functionCountFact.d.ts +2 -0
- package/dist/plugins/xfiPluginAst/facts/functionCountFact.js +69 -0
- package/dist/plugins/xfiPluginAst/index.d.ts +1 -0
- package/dist/plugins/xfiPluginAst/index.js +5 -0
- package/dist/plugins/xfiPluginAst/operators/astComplexity.d.ts +2 -0
- package/dist/plugins/xfiPluginAst/operators/astComplexity.js +33 -0
- package/dist/plugins/xfiPluginAst/operators/functionCount.d.ts +2 -0
- package/dist/plugins/xfiPluginAst/operators/functionCount.js +28 -0
- package/dist/plugins/xfiPluginAst/xfiPluginAst.d.ts +3 -0
- package/dist/plugins/xfiPluginAst/xfiPluginAst.js +20 -0
- package/dist/utils/astUtils.d.ts +7 -0
- package/dist/utils/astUtils.js +78 -0
- package/package.json +5 -1
- package/src/demoConfig/node-fullstack.json +17 -7
- package/src/demoConfig/rules/functionComplexity-iterative-rule.json +31 -0
- package/src/demoConfig/rules/functionCount-iterative-rule.json +30 -0
- package/src/demoConfig/rules/mutuallyExclusivePackages-global-rule.json +38 -0
- package/src/demoConfig/rules/newSdkFeatureNotAdoped-global-rule.json +1 -1
- package/src/demoConfig/rules/openaiAnalysisTestCriticality-global-rule.json +31 -0
- package/src/exampleTriggerFiles/mixedUIComponents.tsx +11 -0
- package/src/facts/globalFileAnalysisFacts.ts +1 -1
- package/src/facts/openaiAnalysisFacts.ts +2 -2
- package/src/plugins/xfiPluginAst/facts/astFact.ts +24 -0
- package/src/plugins/xfiPluginAst/facts/functionComplexityFact.ts +146 -0
- package/src/plugins/xfiPluginAst/facts/functionCountFact.ts +63 -0
- package/src/plugins/xfiPluginAst/index.ts +1 -0
- package/src/plugins/xfiPluginAst/operators/astComplexity.ts +33 -0
- package/src/plugins/xfiPluginAst/operators/functionCount.ts +29 -0
- package/src/plugins/xfiPluginAst/sampleRules/functionComplexity-iterative-rule.json +31 -0
- package/src/plugins/xfiPluginAst/sampleRules/functionCount-iterative-rule.json +30 -0
- package/src/plugins/xfiPluginAst/xfiPluginAst.ts +20 -0
- package/src/types/tree-sitter-typescript.d.ts +4 -0
- package/src/utils/astUtils.ts +47 -0
- package/tsconfig.json +9 -3
- package/website/docs/getting-started.md +4 -0
- package/website/docs/local-configuration.md +22 -0
- package/website/docs/notifications.md +136 -0
- package/website/sidebars.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
# [3.13.0](https://github.com/zotoio/x-fidelity/compare/v3.12.1...v3.13.0) (2025-03-10)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* add proper TypeScript types for tree-sitter nodes and imports ([9f9d23f](https://github.com/zotoio/x-fidelity/commit/9f9d23ff8b7a08fae53e3d7512c368d187a71976))
|
|
7
|
+
* correct JavaScript parser language initialization ([9ea4ba0](https://github.com/zotoio/x-fidelity/commit/9ea4ba0077cf69f4fa04f3fa0bff65b6b67974a0))
|
|
8
|
+
* correct TypeScript import declaration for tree-sitter-typescript ([a0c3355](https://github.com/zotoio/x-fidelity/commit/a0c3355bd6774d579ac14c5bf944c89e7a1151db))
|
|
9
|
+
* handle empty array case for maxComplexity calculation ([301e5c3](https://github.com/zotoio/x-fidelity/commit/301e5c3e53e37e0976b81a0f6f1c0c1748c3666c))
|
|
10
|
+
* replace cursor traversal with recursive AST traversal for complexity analysis ([4d54473](https://github.com/zotoio/x-fidelity/commit/4d5447324be6d36cd25b9b752e647ec971ffaf95))
|
|
11
|
+
* resolve maxComplexity variable redeclaration in functionComplexityFact ([0d35ea8](https://github.com/zotoio/x-fidelity/commit/0d35ea82a88bca3b6b5c9d9b8a6456e1ee083af4))
|
|
12
|
+
* Update fact value key from 'ast' to 'astResult' ([e930b53](https://github.com/zotoio/x-fidelity/commit/e930b53d7bbc9414d66906903c0b01a1f2058405))
|
|
13
|
+
* update tree-sitter TypeScript import path and usage ([8bf7ce6](https://github.com/zotoio/x-fidelity/commit/8bf7ce6b34be567216dc331f99b87be67fd5d8f3))
|
|
14
|
+
* update tree-sitter walk API usage to use cursor-based traversal ([f92f2c0](https://github.com/zotoio/x-fidelity/commit/f92f2c0abd14d233d43ec377c4f783bbbae35da5))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* add AST analysis capabilities with function complexity detection ([9d01df0](https://github.com/zotoio/x-fidelity/commit/9d01df0bc3c49e92b6b870c87313a0936704b7b2))
|
|
20
|
+
* add debug logging to AST plugin operations ([cd7ea16](https://github.com/zotoio/x-fidelity/commit/cd7ea16680d4c8d24fea1d14442ed8c3448b159b))
|
|
21
|
+
* add function count rule with AST analysis ([3311d85](https://github.com/zotoio/x-fidelity/commit/3311d8528ff369cac21bee346dce7c2c109208fe))
|
|
22
|
+
* add minimum complexity threshold filter for function analysis ([a67279c](https://github.com/zotoio/x-fidelity/commit/a67279c134d9dbdd711d9782389d6ee8629d929f))
|
|
23
|
+
* add type declarations for tree-sitter-typescript module ([afd1553](https://github.com/zotoio/x-fidelity/commit/afd1553895f33eca6a016e194d70f504d3fb0e03))
|
|
24
|
+
* create AST plugin with facts and operators ([0be5fa5](https://github.com/zotoio/x-fidelity/commit/0be5fa59d76ad2b5fa261545b0c1254ae1441c6c))
|
|
25
|
+
* **plugin:** ast based facts and rule examples ([b734111](https://github.com/zotoio/x-fidelity/commit/b7341118645627967f0adfbe89664c4ffc5752a2))
|
|
26
|
+
|
|
27
|
+
## [3.12.1](https://github.com/zotoio/x-fidelity/compare/v3.12.0...v3.12.1) (2025-03-10)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Bug Fixes
|
|
31
|
+
|
|
32
|
+
* **examples:** simple rules to check code evolution ([46547a6](https://github.com/zotoio/x-fidelity/commit/46547a6d5127976178b57010dbc33ad16d0cbdbc))
|
|
33
|
+
|
|
1
34
|
# [3.12.0](https://github.com/zotoio/x-fidelity/compare/v3.11.0...v3.12.0) (2025-03-10)
|
|
2
35
|
|
|
3
36
|
|
|
@@ -3,37 +3,47 @@
|
|
|
3
3
|
"rules": [
|
|
4
4
|
"sensitiveLogging-iterative",
|
|
5
5
|
"outdatedFramework-global",
|
|
6
|
-
"noDatabases-iterative",
|
|
6
|
+
"noDatabases-iterative",
|
|
7
7
|
"nonStandardDirectoryStructure-global",
|
|
8
8
|
"openaiAnalysisTop5-global",
|
|
9
9
|
"openaiAnalysisA11y-global",
|
|
10
|
+
"openaiAnalysisTestCriticality-global",
|
|
10
11
|
"invalidSystemIdConfigured-iterative",
|
|
11
12
|
"missingRequiredFiles-global",
|
|
12
13
|
"factDoesNotAddResultToAlmanac-iterative",
|
|
13
|
-
"newSdkFeatureNotAdoped-global"
|
|
14
|
+
"newSdkFeatureNotAdoped-global",
|
|
15
|
+
"mutuallyExclusivePackages-global",
|
|
16
|
+
"functionComplexity-iterative",
|
|
17
|
+
"functionCount-iterative"
|
|
14
18
|
],
|
|
15
19
|
"operators": [
|
|
16
20
|
"fileContains",
|
|
17
|
-
"outdatedFramework",
|
|
21
|
+
"outdatedFramework",
|
|
18
22
|
"nonStandardDirectoryStructure",
|
|
19
23
|
"openaiAnalysisHighSeverity",
|
|
20
24
|
"invalidRemoteValidation",
|
|
21
25
|
"missingRequiredFiles",
|
|
22
26
|
"regexMatch",
|
|
23
27
|
"globalPatternRatio",
|
|
24
|
-
"globalPatternCount"
|
|
28
|
+
"globalPatternCount",
|
|
29
|
+
"astComplexity",
|
|
30
|
+
"functionCount"
|
|
25
31
|
],
|
|
26
32
|
"facts": [
|
|
27
33
|
"repoFilesystemFacts",
|
|
28
34
|
"repoDependencyFacts",
|
|
29
35
|
"openaiAnalysisFacts",
|
|
30
|
-
"remoteSubstringValidation",
|
|
36
|
+
"remoteSubstringValidation",
|
|
31
37
|
"missingRequiredFiles",
|
|
32
|
-
"globalFileAnalysisFacts"
|
|
38
|
+
"globalFileAnalysisFacts",
|
|
39
|
+
"ast",
|
|
40
|
+
"functionComplexity",
|
|
41
|
+
"functionCount"
|
|
33
42
|
],
|
|
34
43
|
"plugins": [
|
|
35
44
|
"xfiPluginRequiredFiles",
|
|
36
|
-
"xfiPluginRemoteStringValidator"
|
|
45
|
+
"xfiPluginRemoteStringValidator",
|
|
46
|
+
"xfiPluginAst"
|
|
37
47
|
],
|
|
38
48
|
"config": {
|
|
39
49
|
"minimumDependencyVersions": {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "functionComplexity-iterative",
|
|
3
|
+
"conditions": {
|
|
4
|
+
"all": [
|
|
5
|
+
{
|
|
6
|
+
"fact": "fileData",
|
|
7
|
+
"path": "$.fileName",
|
|
8
|
+
"operator": "notEqual",
|
|
9
|
+
"value": "REPO_GLOBAL_CHECK"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"fact": "functionComplexity",
|
|
13
|
+
"params": {
|
|
14
|
+
"resultFact": "complexityResult",
|
|
15
|
+
"minimumComplexityLogged": 8
|
|
16
|
+
},
|
|
17
|
+
"operator": "astComplexity",
|
|
18
|
+
"value": 10
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"event": {
|
|
23
|
+
"type": "warning",
|
|
24
|
+
"params": {
|
|
25
|
+
"message": "Functions detected with high cyclomatic complexity (10+). Consider refactoring.",
|
|
26
|
+
"details": {
|
|
27
|
+
"fact": "complexityResult"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "functionCount-iterative",
|
|
3
|
+
"conditions": {
|
|
4
|
+
"all": [
|
|
5
|
+
{
|
|
6
|
+
"fact": "fileData",
|
|
7
|
+
"path": "$.filePath",
|
|
8
|
+
"operator": "regexMatch",
|
|
9
|
+
"value": "^.*\\/facts\\/(?!.*\\.test).*\\.ts$"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"fact": "functionCount",
|
|
13
|
+
"params": {
|
|
14
|
+
"resultFact": "functionCountResult"
|
|
15
|
+
},
|
|
16
|
+
"operator": "functionCount",
|
|
17
|
+
"value": 20
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"event": {
|
|
22
|
+
"type": "warning",
|
|
23
|
+
"params": {
|
|
24
|
+
"message": "File contains too many functions (>20). Consider splitting into multiple files.",
|
|
25
|
+
"details": {
|
|
26
|
+
"fact": "functionCountResult"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mutuallyExclusivePackages-global",
|
|
3
|
+
"conditions": {
|
|
4
|
+
"all": [
|
|
5
|
+
{
|
|
6
|
+
"fact": "fileData",
|
|
7
|
+
"path": "$.fileName",
|
|
8
|
+
"operator": "equal",
|
|
9
|
+
"value": "REPO_GLOBAL_CHECK"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"fact": "globalFileAnalysis",
|
|
13
|
+
"params": {
|
|
14
|
+
"patterns": [
|
|
15
|
+
"import.*from\\s+['\"](@mui/material.*)['\"]",
|
|
16
|
+
"import.*from\\s+['\"](antd)['\"]",
|
|
17
|
+
"require\\(['\"](@mui/material.*)['\"]\\)",
|
|
18
|
+
"require\\(['\"](antd)['\"]\\)"
|
|
19
|
+
],
|
|
20
|
+
"fileFilter": "\\.(js|jsx|ts|tsx)$",
|
|
21
|
+
"resultFact": "packageUsageAnalysis"
|
|
22
|
+
},
|
|
23
|
+
"operator": "globalPatternCount",
|
|
24
|
+
"value": 0
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"event": {
|
|
29
|
+
"type": "fatality",
|
|
30
|
+
"params": {
|
|
31
|
+
"message": "Mutually exclusive packages detected. MUI and AntDesign should not be used together.",
|
|
32
|
+
"details": {
|
|
33
|
+
"fact": "packageUsageAnalysis",
|
|
34
|
+
"recommendation": "Choose migrate to MUI."
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openaiAnalysisTestCriticality-global",
|
|
3
|
+
"conditions": {
|
|
4
|
+
"all": [
|
|
5
|
+
{
|
|
6
|
+
"fact": "fileData",
|
|
7
|
+
"path": "$.fileName",
|
|
8
|
+
"operator": "equal",
|
|
9
|
+
"value": "REPO_GLOBAL_CHECK"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"fact": "openaiAnalysis",
|
|
13
|
+
"params": {
|
|
14
|
+
"prompt": "what are the files and features in the codebase that are most likely to require unit test uplift in order to be able to trust pull-request checks?",
|
|
15
|
+
"resultFact": "openaiAnalysisTestCriticality"
|
|
16
|
+
},
|
|
17
|
+
"operator": "openaiAnalysisHighSeverity",
|
|
18
|
+
"value": 8
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"event": {
|
|
23
|
+
"type": "warning",
|
|
24
|
+
"params": {
|
|
25
|
+
"message": "OpenAI suggestions for the supplied prompt.",
|
|
26
|
+
"details": {
|
|
27
|
+
"fact": "openaiAnalysisTestCriticality"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -24,7 +24,7 @@ if ((0, openaiUtils_1.isOpenAIEnabled)()) {
|
|
|
24
24
|
const openaiAnalysis = function (params, almanac) {
|
|
25
25
|
return __awaiter(this, void 0, void 0, function* () {
|
|
26
26
|
let result = { 'result': [] };
|
|
27
|
-
const model = process.env.OPENAI_MODEL || '
|
|
27
|
+
const model = process.env.OPENAI_MODEL || 'gpt-4o';
|
|
28
28
|
try {
|
|
29
29
|
if (!openai) {
|
|
30
30
|
throw new Error('OpenAI client is not initialized');
|
|
@@ -40,7 +40,7 @@ const openaiAnalysis = function (params, almanac) {
|
|
|
40
40
|
};
|
|
41
41
|
const payload = {
|
|
42
42
|
model,
|
|
43
|
-
reasoning_effort: 'high',
|
|
43
|
+
//reasoning_effort: 'high',
|
|
44
44
|
messages: [
|
|
45
45
|
{ role: 'system', content: openaiSystemPrompt },
|
|
46
46
|
{ role: 'user', content: `${params.prompt}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.astFact = void 0;
|
|
13
|
+
const logger_1 = require("../../../utils/logger");
|
|
14
|
+
const astUtils_1 = require("../../../utils/astUtils");
|
|
15
|
+
exports.astFact = {
|
|
16
|
+
name: 'ast',
|
|
17
|
+
fn: (params, almanac) => __awaiter(void 0, void 0, void 0, function* () {
|
|
18
|
+
try {
|
|
19
|
+
const fileData = yield almanac.factValue('fileData');
|
|
20
|
+
const result = (0, astUtils_1.generateAst)(fileData);
|
|
21
|
+
// Add the AST to the almanac for other facts/operators to use
|
|
22
|
+
if (params === null || params === void 0 ? void 0 : params.resultFact) {
|
|
23
|
+
logger_1.logger.trace({ resultFact: params.resultFact }, 'Adding AST to almanac');
|
|
24
|
+
almanac.addRuntimeFact(params.resultFact, result);
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
logger_1.logger.error(`Error in AST fact: ${error}`);
|
|
30
|
+
return { tree: null };
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.functionComplexityFact = void 0;
|
|
13
|
+
const logger_1 = require("../../../utils/logger");
|
|
14
|
+
const astUtils_1 = require("../../../utils/astUtils");
|
|
15
|
+
exports.functionComplexityFact = {
|
|
16
|
+
name: 'functionComplexity',
|
|
17
|
+
fn: (params, almanac) => __awaiter(void 0, void 0, void 0, function* () {
|
|
18
|
+
try {
|
|
19
|
+
const fileData = yield almanac.factValue('fileData');
|
|
20
|
+
const { tree } = (0, astUtils_1.generateAst)(fileData);
|
|
21
|
+
if (!tree) {
|
|
22
|
+
logger_1.logger.debug('No AST available for complexity analysis');
|
|
23
|
+
return { complexity: 0 };
|
|
24
|
+
}
|
|
25
|
+
const complexities = [];
|
|
26
|
+
logger_1.logger.debug('Starting function complexity analysis');
|
|
27
|
+
// Visit each function declaration/expression
|
|
28
|
+
// Traverse the AST to find all function nodes
|
|
29
|
+
const functionNodes = [];
|
|
30
|
+
function visit(node) {
|
|
31
|
+
if (node.type === 'function_declaration' ||
|
|
32
|
+
node.type === 'function_expression' ||
|
|
33
|
+
node.type === 'arrow_function') {
|
|
34
|
+
functionNodes.push(node);
|
|
35
|
+
}
|
|
36
|
+
for (let child of node.children || []) {
|
|
37
|
+
visit(child);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
visit(tree.rootNode);
|
|
41
|
+
// Get minimum complexity threshold from params
|
|
42
|
+
const minimumComplexityLogged = (params === null || params === void 0 ? void 0 : params.minimumComplexityLogged) || 0;
|
|
43
|
+
// Analyze each function node
|
|
44
|
+
for (const node of functionNodes) {
|
|
45
|
+
const name = getFunctionName(node);
|
|
46
|
+
const complexity = analyzeFunctionComplexity(node);
|
|
47
|
+
// Only include functions that exceed the minimum complexity threshold
|
|
48
|
+
if (complexity >= minimumComplexityLogged) {
|
|
49
|
+
logger_1.logger.debug({
|
|
50
|
+
functionName: name,
|
|
51
|
+
nodeType: node.type,
|
|
52
|
+
complexity,
|
|
53
|
+
startLine: node.startPosition.row + 1,
|
|
54
|
+
endLine: node.endPosition.row + 1,
|
|
55
|
+
threshold: minimumComplexityLogged
|
|
56
|
+
}, 'Function exceeds complexity threshold');
|
|
57
|
+
complexities.push({
|
|
58
|
+
name,
|
|
59
|
+
complexity,
|
|
60
|
+
location: {
|
|
61
|
+
start: node.startPosition,
|
|
62
|
+
end: node.endPosition
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
logger_1.logger.debug({
|
|
68
|
+
functionName: name,
|
|
69
|
+
complexity,
|
|
70
|
+
threshold: minimumComplexityLogged
|
|
71
|
+
}, 'Function below complexity threshold - skipping');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Calculate max complexity from values
|
|
75
|
+
const complexityValues = complexities.map(c => c.complexity);
|
|
76
|
+
const maxComplexity = complexityValues.length > 0 ? Math.max(...complexityValues) : 0;
|
|
77
|
+
logger_1.logger.debug({
|
|
78
|
+
functionCount: complexities.length,
|
|
79
|
+
maxComplexity,
|
|
80
|
+
complexityBreakdown: complexities.map(c => ({
|
|
81
|
+
name: c.name,
|
|
82
|
+
complexity: c.complexity
|
|
83
|
+
}))
|
|
84
|
+
}, 'Completed complexity analysis');
|
|
85
|
+
const result = {
|
|
86
|
+
complexities,
|
|
87
|
+
maxComplexity
|
|
88
|
+
};
|
|
89
|
+
if (params === null || params === void 0 ? void 0 : params.resultFact) {
|
|
90
|
+
logger_1.logger.debug({ resultFact: params.resultFact }, 'Adding complexity results to almanac');
|
|
91
|
+
almanac.addRuntimeFact(params.resultFact, result);
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
logger_1.logger.error(`Error analyzing function complexity: ${error}`);
|
|
97
|
+
return { complexities: [], maxComplexity: 0 };
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
};
|
|
101
|
+
function analyzeFunctionComplexity(node) {
|
|
102
|
+
let complexity = 1; // Base complexity
|
|
103
|
+
// Walk the AST and count:
|
|
104
|
+
// - Conditional statements (if, else, switch cases)
|
|
105
|
+
// - Loops (for, while, do-while)
|
|
106
|
+
// - Logical operators (&&, ||)
|
|
107
|
+
// - Try/catch blocks
|
|
108
|
+
function visit(node) {
|
|
109
|
+
switch (node.type) {
|
|
110
|
+
case 'if_statement':
|
|
111
|
+
case 'switch_case':
|
|
112
|
+
case 'for_statement':
|
|
113
|
+
case 'while_statement':
|
|
114
|
+
case 'do_statement':
|
|
115
|
+
case 'try_statement':
|
|
116
|
+
complexity++;
|
|
117
|
+
break;
|
|
118
|
+
case '&&':
|
|
119
|
+
case '||':
|
|
120
|
+
complexity += 0.5;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
for (let child of node.children || []) {
|
|
124
|
+
visit(child);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
visit(node);
|
|
128
|
+
return complexity;
|
|
129
|
+
}
|
|
130
|
+
function getFunctionName(node) {
|
|
131
|
+
if (node.type === 'function_declaration') {
|
|
132
|
+
const nameNode = node.descendantsOfType('identifier')[0];
|
|
133
|
+
return nameNode ? nameNode.text : 'anonymous';
|
|
134
|
+
}
|
|
135
|
+
return 'anonymous';
|
|
136
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.functionCountFact = void 0;
|
|
13
|
+
const logger_1 = require("../../../utils/logger");
|
|
14
|
+
const astUtils_1 = require("../../../utils/astUtils");
|
|
15
|
+
exports.functionCountFact = {
|
|
16
|
+
name: 'functionCount',
|
|
17
|
+
fn: (params, almanac) => __awaiter(void 0, void 0, void 0, function* () {
|
|
18
|
+
try {
|
|
19
|
+
const fileData = yield almanac.factValue('fileData');
|
|
20
|
+
const { tree } = (0, astUtils_1.generateAst)(fileData);
|
|
21
|
+
if (!tree) {
|
|
22
|
+
logger_1.logger.debug('No AST available for function count analysis');
|
|
23
|
+
return { count: 0 };
|
|
24
|
+
}
|
|
25
|
+
// Find all function nodes
|
|
26
|
+
const functionNodes = [];
|
|
27
|
+
function visit(node) {
|
|
28
|
+
if (node.type === 'function_declaration' ||
|
|
29
|
+
node.type === 'function_expression' ||
|
|
30
|
+
node.type === 'arrow_function') {
|
|
31
|
+
functionNodes.push(node);
|
|
32
|
+
}
|
|
33
|
+
for (let child of node.children || []) {
|
|
34
|
+
visit(child);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
visit(tree.rootNode);
|
|
38
|
+
const result = {
|
|
39
|
+
count: functionNodes.length,
|
|
40
|
+
functions: functionNodes.map(node => {
|
|
41
|
+
var _a;
|
|
42
|
+
return ({
|
|
43
|
+
name: node.type === 'function_declaration' ?
|
|
44
|
+
(((_a = node.descendantsOfType('identifier')[0]) === null || _a === void 0 ? void 0 : _a.text) || 'anonymous') :
|
|
45
|
+
'anonymous',
|
|
46
|
+
location: {
|
|
47
|
+
start: node.startPosition,
|
|
48
|
+
end: node.endPosition
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
})
|
|
52
|
+
};
|
|
53
|
+
logger_1.logger.debug({
|
|
54
|
+
filePath: fileData.filePath,
|
|
55
|
+
functionCount: result.count,
|
|
56
|
+
functions: result.functions
|
|
57
|
+
}, 'Completed function count analysis');
|
|
58
|
+
// Add result to almanac if resultFact param provided
|
|
59
|
+
if (params === null || params === void 0 ? void 0 : params.resultFact) {
|
|
60
|
+
almanac.addRuntimeFact(params.resultFact, result);
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
logger_1.logger.error(`Error in function count analysis: ${error}`);
|
|
66
|
+
return { count: 0, functions: [] };
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { plugin } from './xfiPluginAst';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.plugin = void 0;
|
|
4
|
+
var xfiPluginAst_1 = require("./xfiPluginAst");
|
|
5
|
+
Object.defineProperty(exports, "plugin", { enumerable: true, get: function () { return xfiPluginAst_1.plugin; } });
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.astComplexity = void 0;
|
|
4
|
+
const logger_1 = require("../../../utils/logger");
|
|
5
|
+
exports.astComplexity = {
|
|
6
|
+
name: 'astComplexity',
|
|
7
|
+
fn: (factValue, threshold) => {
|
|
8
|
+
var _a;
|
|
9
|
+
try {
|
|
10
|
+
logger_1.logger.debug({ threshold }, 'Checking AST complexity against threshold');
|
|
11
|
+
if (!factValue || !factValue.maxComplexity) {
|
|
12
|
+
logger_1.logger.debug('No complexity data available');
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
const result = factValue.maxComplexity >= threshold;
|
|
16
|
+
logger_1.logger.trace({
|
|
17
|
+
maxComplexity: factValue.maxComplexity,
|
|
18
|
+
threshold,
|
|
19
|
+
exceedsThreshold: result,
|
|
20
|
+
complexityDetails: (_a = factValue.complexities) === null || _a === void 0 ? void 0 : _a.map((c) => ({
|
|
21
|
+
name: c.name,
|
|
22
|
+
complexity: c.complexity,
|
|
23
|
+
exceedsThreshold: c.complexity >= threshold
|
|
24
|
+
}))
|
|
25
|
+
}, 'Completed complexity threshold check');
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
logger_1.logger.error(`Error in astComplexity operator: ${error}`);
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.functionCount = void 0;
|
|
4
|
+
const logger_1 = require("../../../utils/logger");
|
|
5
|
+
exports.functionCount = {
|
|
6
|
+
name: 'functionCount',
|
|
7
|
+
fn: (factValue, threshold) => {
|
|
8
|
+
try {
|
|
9
|
+
logger_1.logger.debug({ threshold }, 'Checking function count against threshold');
|
|
10
|
+
if (!factValue || typeof factValue.count !== 'number') {
|
|
11
|
+
logger_1.logger.debug('No valid function count data available');
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
const result = factValue.count > threshold;
|
|
15
|
+
logger_1.logger.debug({
|
|
16
|
+
count: factValue.count,
|
|
17
|
+
threshold,
|
|
18
|
+
exceedsThreshold: result,
|
|
19
|
+
functions: factValue.functions
|
|
20
|
+
}, 'Completed function count threshold check');
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
logger_1.logger.error(`Error in functionCount operator: ${error}`);
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.plugin = void 0;
|
|
4
|
+
const astFact_1 = require("./facts/astFact");
|
|
5
|
+
const functionComplexityFact_1 = require("./facts/functionComplexityFact");
|
|
6
|
+
const functionCountFact_1 = require("./facts/functionCountFact");
|
|
7
|
+
const astComplexity_1 = require("./operators/astComplexity");
|
|
8
|
+
const functionCount_1 = require("./operators/functionCount");
|
|
9
|
+
const plugin = {
|
|
10
|
+
name: 'xfiPluginAst',
|
|
11
|
+
version: '1.0.0',
|
|
12
|
+
facts: [astFact_1.astFact, functionComplexityFact_1.functionComplexityFact, functionCountFact_1.functionCountFact],
|
|
13
|
+
operators: [astComplexity_1.astComplexity, functionCount_1.functionCount],
|
|
14
|
+
onError: (error) => ({
|
|
15
|
+
message: `AST analysis error: ${error.message}`,
|
|
16
|
+
level: 'warning',
|
|
17
|
+
details: error.stack
|
|
18
|
+
})
|
|
19
|
+
};
|
|
20
|
+
exports.plugin = plugin;
|