x-fidelity 3.3.1 → 3.4.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 +14 -0
- package/dist/demoConfig/node-fullstack.json +4 -2
- package/dist/demoConfig/rules/regexMatch-example-iterative-rule.json +28 -0
- package/dist/operators/index.js +3 -1
- package/dist/operators/regexMatch.d.ts +10 -0
- package/dist/operators/regexMatch.js +46 -0
- package/dist/operators/regexMatch.test.d.ts +1 -0
- package/dist/operators/regexMatch.test.js +52 -0
- package/package.json +1 -1
- package/src/demoConfig/node-fullstack.json +4 -2
- package/src/demoConfig/rules/regexMatch-example-iterative-rule.json +28 -0
- package/src/operators/index.ts +2 -0
- package/src/operators/regexMatch.test.ts +59 -0
- package/src/operators/regexMatch.ts +51 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [3.4.0](https://github.com/zotoio/x-fidelity/compare/v3.3.1...v3.4.0) (2025-02-26)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* add support for all modern regex flags in pattern matching ([847a320](https://github.com/zotoio/x-fidelity/commit/847a32068b68db66c1a9d84090bae4bc918f36d0))
|
|
7
|
+
* correct regex test case to use case-insensitive flag ([96d906c](https://github.com/zotoio/x-fidelity/commit/96d906c23833f482d649df866ca36f28f609bbd6))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* add regex pattern matching operator with tests ([8ac216f](https://github.com/zotoio/x-fidelity/commit/8ac216f222a28367eba971f0f187fad4173167b3))
|
|
13
|
+
* **operators:** add regexMatch general purpose operator ([8270696](https://github.com/zotoio/x-fidelity/commit/82706966e9a52c089848f785007f415e614b67da))
|
|
14
|
+
|
|
1
15
|
## [3.3.1](https://github.com/zotoio/x-fidelity/compare/v3.3.0...v3.3.1) (2025-02-26)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"openaiAnalysisTop5-global",
|
|
9
9
|
"openaiAnalysisA11y-global",
|
|
10
10
|
"invalidSystemIdConfigured-iterative",
|
|
11
|
-
"missingRequiredFiles-global"
|
|
11
|
+
"missingRequiredFiles-global",
|
|
12
|
+
"regexMatch-example-iterative"
|
|
12
13
|
],
|
|
13
14
|
"operators": [
|
|
14
15
|
"fileContains",
|
|
@@ -16,7 +17,8 @@
|
|
|
16
17
|
"nonStandardDirectoryStructure",
|
|
17
18
|
"openaiAnalysisHighSeverity",
|
|
18
19
|
"invalidRemoteValidation",
|
|
19
|
-
"missingRequiredFiles"
|
|
20
|
+
"missingRequiredFiles",
|
|
21
|
+
"regexMatch"
|
|
20
22
|
],
|
|
21
23
|
"facts": [
|
|
22
24
|
"repoFilesystemFacts",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "regexMatch-example",
|
|
3
|
+
"conditions": {
|
|
4
|
+
"all": [
|
|
5
|
+
{
|
|
6
|
+
"fact": "fileData",
|
|
7
|
+
"path": "$.filePath",
|
|
8
|
+
"operator": "regexMatch",
|
|
9
|
+
"value": ".*/facts/.*"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"fact": "fileData",
|
|
13
|
+
"path": "$.fileContent",
|
|
14
|
+
"operator": "regexMatch",
|
|
15
|
+
"value": "OpenAI"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"event": {
|
|
20
|
+
"type": "warning",
|
|
21
|
+
"params": {
|
|
22
|
+
"message": "I'm seeing a pattern here..",
|
|
23
|
+
"details": {
|
|
24
|
+
"suggestion": "wake up and smell the coffee"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/operators/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const outdatedFramework_1 = require("./outdatedFramework");
|
|
|
14
14
|
const fileContains_1 = require("./fileContains");
|
|
15
15
|
const nonStandardDirectoryStructure_1 = require("./nonStandardDirectoryStructure");
|
|
16
16
|
const openaiAnalysisHighSeverity_1 = require("./openaiAnalysisHighSeverity");
|
|
17
|
+
const regexMatch_1 = require("./regexMatch");
|
|
17
18
|
const openaiUtils_1 = require("../utils/openaiUtils");
|
|
18
19
|
const logger_1 = require("../utils/logger");
|
|
19
20
|
const pluginRegistry_1 = require("../core/pluginRegistry");
|
|
@@ -23,7 +24,8 @@ function loadOperators(operatorNames) {
|
|
|
23
24
|
const allAvailableOperators = Object.assign({ outdatedFramework: outdatedFramework_1.outdatedFramework,
|
|
24
25
|
fileContains: fileContains_1.fileContains,
|
|
25
26
|
nonStandardDirectoryStructure: nonStandardDirectoryStructure_1.nonStandardDirectoryStructure,
|
|
26
|
-
openaiAnalysisHighSeverity: openaiAnalysisHighSeverity_1.openaiAnalysisHighSeverity
|
|
27
|
+
openaiAnalysisHighSeverity: openaiAnalysisHighSeverity_1.openaiAnalysisHighSeverity,
|
|
28
|
+
regexMatch: regexMatch_1.regexMatch }, Object.fromEntries(pluginRegistry_1.pluginRegistry.getPluginOperators().map(op => [op.name, op])));
|
|
27
29
|
const openAIStatus = (0, openaiUtils_1.getOpenAIStatus)();
|
|
28
30
|
logger_1.logger.info(`Loading operators: ${operatorNames.join(', ')}`);
|
|
29
31
|
const loadedOperators = [];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { OperatorDefn } from '../types/typeDefs';
|
|
2
|
+
/**
|
|
3
|
+
* Operator that tests if a string matches a regular expression pattern
|
|
4
|
+
*
|
|
5
|
+
* @param factValue - The string to test against the regex pattern
|
|
6
|
+
* @param regexPattern - The regex pattern to test against (as a string)
|
|
7
|
+
* @returns true if the string matches the pattern, false otherwise
|
|
8
|
+
*/
|
|
9
|
+
declare const regexMatch: OperatorDefn;
|
|
10
|
+
export { regexMatch };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.regexMatch = void 0;
|
|
4
|
+
const logger_1 = require("../utils/logger");
|
|
5
|
+
/**
|
|
6
|
+
* Operator that tests if a string matches a regular expression pattern
|
|
7
|
+
*
|
|
8
|
+
* @param factValue - The string to test against the regex pattern
|
|
9
|
+
* @param regexPattern - The regex pattern to test against (as a string)
|
|
10
|
+
* @returns true if the string matches the pattern, false otherwise
|
|
11
|
+
*/
|
|
12
|
+
const regexMatch = {
|
|
13
|
+
'name': 'regexMatch',
|
|
14
|
+
'fn': (factValue, regexPattern) => {
|
|
15
|
+
try {
|
|
16
|
+
logger_1.logger.debug(`regexMatch: testing ${factValue} against pattern ${regexPattern}`);
|
|
17
|
+
if (factValue === undefined || factValue === null) {
|
|
18
|
+
logger_1.logger.debug('regexMatch: factValue is undefined or null');
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (typeof regexPattern !== 'string') {
|
|
22
|
+
logger_1.logger.debug(`regexMatch: regexPattern is not a string: ${typeof regexPattern}`);
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
// Extract flags if they exist (pattern/flags format)
|
|
26
|
+
let flags = '';
|
|
27
|
+
let pattern = regexPattern;
|
|
28
|
+
// Check if the pattern is in /pattern/flags format
|
|
29
|
+
const regexFormatMatch = /^\/(.+)\/([gimsuyd]*)$/.exec(regexPattern);
|
|
30
|
+
if (regexFormatMatch) {
|
|
31
|
+
pattern = regexFormatMatch[1];
|
|
32
|
+
flags = regexFormatMatch[2];
|
|
33
|
+
logger_1.logger.debug(`regexMatch: extracted pattern "${pattern}" with flags "${flags}"`);
|
|
34
|
+
}
|
|
35
|
+
const regex = new RegExp(pattern, flags);
|
|
36
|
+
const result = regex.test(String(factValue));
|
|
37
|
+
logger_1.logger.debug(`regexMatch: result is ${result}`);
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
logger_1.logger.error(`regexMatch error: ${e}`);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
exports.regexMatch = regexMatch;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const regexMatch_1 = require("./regexMatch");
|
|
4
|
+
const logger_1 = require("../utils/logger");
|
|
5
|
+
jest.mock('../utils/logger', () => ({
|
|
6
|
+
logger: {
|
|
7
|
+
debug: jest.fn(),
|
|
8
|
+
error: jest.fn()
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
describe('regexMatch', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
it('should return true when the string matches the pattern', () => {
|
|
16
|
+
expect(regexMatch_1.regexMatch.fn('hello world', 'hello')).toBe(true);
|
|
17
|
+
expect(regexMatch_1.regexMatch.fn('hello world', 'world$')).toBe(true);
|
|
18
|
+
expect(regexMatch_1.regexMatch.fn('hello world', '^hello')).toBe(true);
|
|
19
|
+
expect(logger_1.logger.debug).toHaveBeenCalledWith(expect.stringContaining('result is true'));
|
|
20
|
+
});
|
|
21
|
+
it('should return false when the string does not match the pattern', () => {
|
|
22
|
+
expect(regexMatch_1.regexMatch.fn('hello world', 'goodbye')).toBe(false);
|
|
23
|
+
expect(regexMatch_1.regexMatch.fn('hello world', '^world')).toBe(false);
|
|
24
|
+
expect(regexMatch_1.regexMatch.fn('hello world', 'hello$')).toBe(false);
|
|
25
|
+
expect(logger_1.logger.debug).toHaveBeenCalledWith(expect.stringContaining('result is false'));
|
|
26
|
+
});
|
|
27
|
+
it('should handle regex flags correctly', () => {
|
|
28
|
+
expect(regexMatch_1.regexMatch.fn('Hello World', '/hello/i')).toBe(true);
|
|
29
|
+
expect(regexMatch_1.regexMatch.fn('Hello\nWorld', '/hello.*world/is')).toBe(true);
|
|
30
|
+
expect(regexMatch_1.regexMatch.fn('Hello World', '/hello world/i')).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
it('should handle undefined or null factValue', () => {
|
|
33
|
+
expect(regexMatch_1.regexMatch.fn(undefined, 'test')).toBe(false);
|
|
34
|
+
expect(regexMatch_1.regexMatch.fn(null, 'test')).toBe(false);
|
|
35
|
+
expect(logger_1.logger.debug).toHaveBeenCalledWith('regexMatch: factValue is undefined or null');
|
|
36
|
+
});
|
|
37
|
+
it('should handle non-string regexPattern', () => {
|
|
38
|
+
expect(regexMatch_1.regexMatch.fn('test', null)).toBe(false);
|
|
39
|
+
expect(regexMatch_1.regexMatch.fn('test', undefined)).toBe(false);
|
|
40
|
+
expect(regexMatch_1.regexMatch.fn('test', 123)).toBe(false);
|
|
41
|
+
expect(logger_1.logger.debug).toHaveBeenCalledWith(expect.stringContaining('regexPattern is not a string'));
|
|
42
|
+
});
|
|
43
|
+
it('should handle invalid regex patterns gracefully', () => {
|
|
44
|
+
expect(regexMatch_1.regexMatch.fn('test', '[')).toBe(false);
|
|
45
|
+
expect(logger_1.logger.error).toHaveBeenCalledWith(expect.stringContaining('regexMatch error'));
|
|
46
|
+
});
|
|
47
|
+
it('should handle non-string factValues by converting them to strings', () => {
|
|
48
|
+
expect(regexMatch_1.regexMatch.fn(123, '123')).toBe(true);
|
|
49
|
+
expect(regexMatch_1.regexMatch.fn(true, 'true')).toBe(true);
|
|
50
|
+
expect(regexMatch_1.regexMatch.fn({ toString: () => 'custom' }, 'custom')).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
});
|
package/package.json
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"openaiAnalysisTop5-global",
|
|
9
9
|
"openaiAnalysisA11y-global",
|
|
10
10
|
"invalidSystemIdConfigured-iterative",
|
|
11
|
-
"missingRequiredFiles-global"
|
|
11
|
+
"missingRequiredFiles-global",
|
|
12
|
+
"regexMatch-example-iterative"
|
|
12
13
|
],
|
|
13
14
|
"operators": [
|
|
14
15
|
"fileContains",
|
|
@@ -16,7 +17,8 @@
|
|
|
16
17
|
"nonStandardDirectoryStructure",
|
|
17
18
|
"openaiAnalysisHighSeverity",
|
|
18
19
|
"invalidRemoteValidation",
|
|
19
|
-
"missingRequiredFiles"
|
|
20
|
+
"missingRequiredFiles",
|
|
21
|
+
"regexMatch"
|
|
20
22
|
],
|
|
21
23
|
"facts": [
|
|
22
24
|
"repoFilesystemFacts",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "regexMatch-example",
|
|
3
|
+
"conditions": {
|
|
4
|
+
"all": [
|
|
5
|
+
{
|
|
6
|
+
"fact": "fileData",
|
|
7
|
+
"path": "$.filePath",
|
|
8
|
+
"operator": "regexMatch",
|
|
9
|
+
"value": ".*/facts/.*"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"fact": "fileData",
|
|
13
|
+
"path": "$.fileContent",
|
|
14
|
+
"operator": "regexMatch",
|
|
15
|
+
"value": "OpenAI"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"event": {
|
|
20
|
+
"type": "warning",
|
|
21
|
+
"params": {
|
|
22
|
+
"message": "I'm seeing a pattern here..",
|
|
23
|
+
"details": {
|
|
24
|
+
"suggestion": "wake up and smell the coffee"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/operators/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { outdatedFramework } from './outdatedFramework';
|
|
|
3
3
|
import { fileContains } from './fileContains';
|
|
4
4
|
import { nonStandardDirectoryStructure } from './nonStandardDirectoryStructure';
|
|
5
5
|
import { openaiAnalysisHighSeverity } from './openaiAnalysisHighSeverity';
|
|
6
|
+
import { regexMatch } from './regexMatch';
|
|
6
7
|
import { getOpenAIStatus } from '../utils/openaiUtils';
|
|
7
8
|
import { logger } from '../utils/logger';
|
|
8
9
|
import { pluginRegistry } from '../core/pluginRegistry';
|
|
@@ -14,6 +15,7 @@ async function loadOperators(operatorNames: string[]): Promise<OperatorDefn[]> {
|
|
|
14
15
|
fileContains,
|
|
15
16
|
nonStandardDirectoryStructure,
|
|
16
17
|
openaiAnalysisHighSeverity,
|
|
18
|
+
regexMatch,
|
|
17
19
|
...Object.fromEntries(
|
|
18
20
|
pluginRegistry.getPluginOperators().map(op => [op.name, op])
|
|
19
21
|
)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { regexMatch } from './regexMatch';
|
|
2
|
+
import { logger } from '../utils/logger';
|
|
3
|
+
|
|
4
|
+
jest.mock('../utils/logger', () => ({
|
|
5
|
+
logger: {
|
|
6
|
+
debug: jest.fn(),
|
|
7
|
+
error: jest.fn()
|
|
8
|
+
},
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
describe('regexMatch', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should return true when the string matches the pattern', () => {
|
|
17
|
+
expect(regexMatch.fn('hello world', 'hello')).toBe(true);
|
|
18
|
+
expect(regexMatch.fn('hello world', 'world$')).toBe(true);
|
|
19
|
+
expect(regexMatch.fn('hello world', '^hello')).toBe(true);
|
|
20
|
+
expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining('result is true'));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should return false when the string does not match the pattern', () => {
|
|
24
|
+
expect(regexMatch.fn('hello world', 'goodbye')).toBe(false);
|
|
25
|
+
expect(regexMatch.fn('hello world', '^world')).toBe(false);
|
|
26
|
+
expect(regexMatch.fn('hello world', 'hello$')).toBe(false);
|
|
27
|
+
expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining('result is false'));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should handle regex flags correctly', () => {
|
|
31
|
+
expect(regexMatch.fn('Hello World', '/hello/i')).toBe(true);
|
|
32
|
+
expect(regexMatch.fn('Hello\nWorld', '/hello.*world/is')).toBe(true);
|
|
33
|
+
expect(regexMatch.fn('Hello World', '/hello world/i')).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should handle undefined or null factValue', () => {
|
|
37
|
+
expect(regexMatch.fn(undefined, 'test')).toBe(false);
|
|
38
|
+
expect(regexMatch.fn(null, 'test')).toBe(false);
|
|
39
|
+
expect(logger.debug).toHaveBeenCalledWith('regexMatch: factValue is undefined or null');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should handle non-string regexPattern', () => {
|
|
43
|
+
expect(regexMatch.fn('test', null as any)).toBe(false);
|
|
44
|
+
expect(regexMatch.fn('test', undefined as any)).toBe(false);
|
|
45
|
+
expect(regexMatch.fn('test', 123 as any)).toBe(false);
|
|
46
|
+
expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining('regexPattern is not a string'));
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should handle invalid regex patterns gracefully', () => {
|
|
50
|
+
expect(regexMatch.fn('test', '[')).toBe(false);
|
|
51
|
+
expect(logger.error).toHaveBeenCalledWith(expect.stringContaining('regexMatch error'));
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should handle non-string factValues by converting them to strings', () => {
|
|
55
|
+
expect(regexMatch.fn(123, '123')).toBe(true);
|
|
56
|
+
expect(regexMatch.fn(true, 'true')).toBe(true);
|
|
57
|
+
expect(regexMatch.fn({ toString: () => 'custom' }, 'custom')).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { logger } from '../utils/logger';
|
|
2
|
+
import { OperatorDefn } from '../types/typeDefs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Operator that tests if a string matches a regular expression pattern
|
|
6
|
+
*
|
|
7
|
+
* @param factValue - The string to test against the regex pattern
|
|
8
|
+
* @param regexPattern - The regex pattern to test against (as a string)
|
|
9
|
+
* @returns true if the string matches the pattern, false otherwise
|
|
10
|
+
*/
|
|
11
|
+
const regexMatch: OperatorDefn = {
|
|
12
|
+
'name': 'regexMatch',
|
|
13
|
+
'fn': (factValue: any, regexPattern: string) => {
|
|
14
|
+
try {
|
|
15
|
+
logger.debug(`regexMatch: testing ${factValue} against pattern ${regexPattern}`);
|
|
16
|
+
|
|
17
|
+
if (factValue === undefined || factValue === null) {
|
|
18
|
+
logger.debug('regexMatch: factValue is undefined or null');
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (typeof regexPattern !== 'string') {
|
|
23
|
+
logger.debug(`regexMatch: regexPattern is not a string: ${typeof regexPattern}`);
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Extract flags if they exist (pattern/flags format)
|
|
28
|
+
let flags = '';
|
|
29
|
+
let pattern = regexPattern;
|
|
30
|
+
|
|
31
|
+
// Check if the pattern is in /pattern/flags format
|
|
32
|
+
const regexFormatMatch = /^\/(.+)\/([gimsuyd]*)$/.exec(regexPattern);
|
|
33
|
+
if (regexFormatMatch) {
|
|
34
|
+
pattern = regexFormatMatch[1];
|
|
35
|
+
flags = regexFormatMatch[2];
|
|
36
|
+
logger.debug(`regexMatch: extracted pattern "${pattern}" with flags "${flags}"`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const regex = new RegExp(pattern, flags);
|
|
40
|
+
const result = regex.test(String(factValue));
|
|
41
|
+
|
|
42
|
+
logger.debug(`regexMatch: result is ${result}`);
|
|
43
|
+
return result;
|
|
44
|
+
} catch (e) {
|
|
45
|
+
logger.error(`regexMatch error: ${e}`);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export { regexMatch };
|