@rigour-labs/core 5.0.0 → 5.1.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/README.md +9 -1
- package/dist/gates/agent-team.d.ts +0 -1
- package/dist/gates/agent-team.js +0 -1
- package/dist/gates/checkpoint.d.ts +0 -2
- package/dist/gates/checkpoint.js +0 -2
- package/dist/gates/context-window-artifacts.d.ts +6 -2
- package/dist/gates/context-window-artifacts.js +107 -31
- package/dist/gates/deep-analysis.d.ts +2 -0
- package/dist/gates/deep-analysis.js +41 -11
- package/dist/gates/dependency.d.ts +0 -2
- package/dist/gates/dependency.js +23 -5
- package/dist/gates/deprecated-apis.d.ts +0 -2
- package/dist/gates/deprecated-apis.js +33 -20
- package/dist/gates/duplication-drift/index.d.ts +61 -0
- package/dist/gates/duplication-drift/index.js +240 -0
- package/dist/gates/duplication-drift/similarity.d.ts +68 -0
- package/dist/gates/duplication-drift/similarity.js +177 -0
- package/dist/gates/duplication-drift/tokenizer.d.ts +55 -0
- package/dist/gates/duplication-drift/tokenizer.js +195 -0
- package/dist/gates/frontend-secret-exposure.d.ts +0 -3
- package/dist/gates/frontend-secret-exposure.js +1 -114
- package/dist/gates/frontend-secret-patterns.d.ts +33 -0
- package/dist/gates/frontend-secret-patterns.js +119 -0
- package/dist/gates/{hallucinated-imports.d.ts → hallucinated-imports/index.d.ts} +2 -29
- package/dist/gates/hallucinated-imports/index.js +174 -0
- package/dist/gates/hallucinated-imports/js-resolver.d.ts +45 -0
- package/dist/gates/hallucinated-imports/js-resolver.js +320 -0
- package/dist/gates/hallucinated-imports/manifest-discovery.d.ts +28 -0
- package/dist/gates/hallucinated-imports/manifest-discovery.js +114 -0
- package/dist/gates/hallucinated-imports/python-resolver.d.ts +24 -0
- package/dist/gates/hallucinated-imports/python-resolver.js +306 -0
- package/dist/gates/hallucinated-imports-lang.d.ts +2 -2
- package/dist/gates/hallucinated-imports-lang.js +269 -34
- package/dist/gates/hallucinated-imports.test.js +1 -2
- package/dist/gates/inconsistent-error-handling.d.ts +0 -5
- package/dist/gates/inconsistent-error-handling.js +15 -144
- package/dist/gates/language-adapters/csharp-adapter.d.ts +16 -0
- package/dist/gates/language-adapters/csharp-adapter.js +211 -0
- package/dist/gates/language-adapters/go-adapter.d.ts +26 -0
- package/dist/gates/language-adapters/go-adapter.js +195 -0
- package/dist/gates/language-adapters/index.d.ts +15 -0
- package/dist/gates/language-adapters/index.js +16 -0
- package/dist/gates/language-adapters/java-adapter.d.ts +16 -0
- package/dist/gates/language-adapters/java-adapter.js +237 -0
- package/dist/gates/language-adapters/js-adapter.d.ts +26 -0
- package/dist/gates/language-adapters/js-adapter.js +279 -0
- package/dist/gates/language-adapters/python-adapter.d.ts +25 -0
- package/dist/gates/language-adapters/python-adapter.js +183 -0
- package/dist/gates/language-adapters/registry.d.ts +26 -0
- package/dist/gates/language-adapters/registry.js +65 -0
- package/dist/gates/language-adapters/ruby-adapter.d.ts +25 -0
- package/dist/gates/language-adapters/ruby-adapter.js +217 -0
- package/dist/gates/language-adapters/rust-adapter.d.ts +27 -0
- package/dist/gates/language-adapters/rust-adapter.js +235 -0
- package/dist/gates/language-adapters/types.d.ts +60 -0
- package/dist/gates/language-adapters/types.js +22 -0
- package/dist/gates/logic-drift-extractors.d.ts +15 -0
- package/dist/gates/logic-drift-extractors.js +34 -0
- package/dist/gates/logic-drift.d.ts +0 -30
- package/dist/gates/logic-drift.js +39 -129
- package/dist/gates/phantom-apis.d.ts +0 -2
- package/dist/gates/phantom-apis.js +49 -20
- package/dist/gates/promise-safety.d.ts +0 -1
- package/dist/gates/promise-safety.js +14 -2
- package/dist/gates/runner.js +51 -22
- package/dist/gates/security-patterns-data.d.ts +14 -0
- package/dist/gates/security-patterns-data.js +235 -0
- package/dist/gates/security-patterns.d.ts +17 -3
- package/dist/gates/security-patterns.js +80 -211
- package/dist/gates/side-effect-analysis/categorizer.d.ts +32 -0
- package/dist/gates/side-effect-analysis/categorizer.js +83 -0
- package/dist/gates/{side-effect-analysis.d.ts → side-effect-analysis/index.d.ts} +3 -5
- package/dist/gates/{side-effect-analysis.js → side-effect-analysis/index.js} +33 -45
- package/dist/gates/side-effect-analysis/scope-tracker.d.ts +37 -0
- package/dist/gates/side-effect-analysis/scope-tracker.js +40 -0
- package/dist/gates/side-effect-helpers/index.d.ts +4 -0
- package/dist/gates/side-effect-helpers/index.js +4 -0
- package/dist/gates/side-effect-helpers/pattern-detection.d.ts +123 -0
- package/dist/gates/{side-effect-helpers.js → side-effect-helpers/pattern-detection.js} +22 -468
- package/dist/gates/side-effect-helpers/resource-tracking.d.ts +80 -0
- package/dist/gates/side-effect-helpers/resource-tracking.js +281 -0
- package/dist/gates/side-effect-helpers/scope-analysis.d.ts +21 -0
- package/dist/gates/side-effect-helpers/scope-analysis.js +146 -0
- package/dist/gates/side-effect-helpers/types.d.ts +38 -0
- package/dist/gates/side-effect-helpers/types.js +41 -0
- package/dist/gates/side-effect-rules.d.ts +0 -1
- package/dist/gates/side-effect-rules.js +0 -1
- package/dist/gates/style-drift-rules.d.ts +86 -0
- package/dist/gates/style-drift-rules.js +103 -0
- package/dist/gates/style-drift.d.ts +7 -16
- package/dist/gates/style-drift.js +101 -119
- package/dist/gates/test-quality-matchers.d.ts +53 -0
- package/dist/gates/test-quality-matchers.js +86 -0
- package/dist/gates/test-quality.d.ts +0 -3
- package/dist/gates/test-quality.js +47 -44
- package/dist/hooks/checker.d.ts +0 -1
- package/dist/hooks/checker.js +1 -3
- package/dist/hooks/dlp-templates.d.ts +0 -1
- package/dist/hooks/dlp-templates.js +0 -4
- package/dist/hooks/index.d.ts +0 -2
- package/dist/hooks/index.js +0 -2
- package/dist/hooks/input-validator.d.ts +0 -1
- package/dist/hooks/input-validator.js +0 -1
- package/dist/hooks/input-validator.test.js +0 -1
- package/dist/hooks/standalone-checker.d.ts +0 -1
- package/dist/hooks/standalone-checker.js +0 -1
- package/dist/hooks/standalone-dlp-checker.d.ts +0 -1
- package/dist/hooks/standalone-dlp-checker.js +0 -1
- package/dist/hooks/templates.d.ts +6 -1
- package/dist/hooks/templates.js +6 -1
- package/dist/hooks/types.d.ts +1 -2
- package/dist/hooks/types.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/services/adaptive-thresholds.d.ts +0 -2
- package/dist/services/adaptive-thresholds.js +0 -2
- package/dist/services/filesystem-cache.d.ts +0 -1
- package/dist/services/filesystem-cache.js +0 -1
- package/dist/services/score-history.d.ts +0 -1
- package/dist/services/score-history.js +0 -1
- package/dist/services/temporal-drift.d.ts +1 -2
- package/dist/services/temporal-drift.js +7 -8
- package/dist/storage/db.d.ts +23 -7
- package/dist/storage/db.js +116 -55
- package/dist/storage/findings.d.ts +4 -3
- package/dist/storage/findings.js +13 -20
- package/dist/storage/local-memory.d.ts +4 -4
- package/dist/storage/local-memory.js +20 -22
- package/dist/storage/patterns.d.ts +5 -5
- package/dist/storage/patterns.js +20 -26
- package/dist/storage/scans.d.ts +6 -6
- package/dist/storage/scans.js +12 -21
- package/dist/types/index.d.ts +1 -0
- package/dist/utils/scanner.js +1 -1
- package/package.json +7 -8
- package/dist/gates/duplication-drift.d.ts +0 -128
- package/dist/gates/duplication-drift.js +0 -585
- package/dist/gates/hallucinated-imports.js +0 -641
- package/dist/gates/side-effect-helpers.d.ts +0 -260
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Style Drift Detection Gate — Naming Convention Rules and Regexes
|
|
3
|
+
*
|
|
4
|
+
* Contains per-language naming convention rules and naming pattern regexes.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Casing classification rules
|
|
8
|
+
*/
|
|
9
|
+
export function classifyCasing(name) {
|
|
10
|
+
if (name.startsWith('_') || name.length <= 1)
|
|
11
|
+
return null;
|
|
12
|
+
if (/^[A-Z][A-Z0-9_]+$/.test(name)) {
|
|
13
|
+
return 'SCREAMING_SNAKE';
|
|
14
|
+
}
|
|
15
|
+
else if (/^[A-Z]/.test(name)) {
|
|
16
|
+
return 'PascalCase';
|
|
17
|
+
}
|
|
18
|
+
else if (name.includes('_')) {
|
|
19
|
+
return 'snake_case';
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
return 'camelCase';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Function name pattern for JavaScript
|
|
27
|
+
*/
|
|
28
|
+
export const JS_FUNCTION_PATTERN = /(?:function|async\s+function)\s+(\w+)/;
|
|
29
|
+
/**
|
|
30
|
+
* Method definition pattern for all languages
|
|
31
|
+
*/
|
|
32
|
+
export const METHOD_PATTERN = /^\s+(?:async\s+)?(\w+)\s*\([^)]*\)\s*[{:]/;
|
|
33
|
+
/**
|
|
34
|
+
* Arrow function assignment pattern
|
|
35
|
+
*/
|
|
36
|
+
export const ARROW_FUNCTION_PATTERN = /(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|(\w+))\s*=>/;
|
|
37
|
+
/**
|
|
38
|
+
* Variable declaration pattern (non-function)
|
|
39
|
+
*/
|
|
40
|
+
export const VAR_DECLARATION_PATTERN = /(?:const|let|var)\s+(\w+)\s*=/;
|
|
41
|
+
/**
|
|
42
|
+
* Python function pattern
|
|
43
|
+
*/
|
|
44
|
+
export const PYTHON_FUNCTION_PATTERN = /def\s+(\w+)/;
|
|
45
|
+
/**
|
|
46
|
+
* Python variable pattern
|
|
47
|
+
*/
|
|
48
|
+
export const PYTHON_VAR_PATTERN = /^(\w+)\s*=/;
|
|
49
|
+
/**
|
|
50
|
+
* Go function pattern
|
|
51
|
+
*/
|
|
52
|
+
export const GO_FUNCTION_PATTERN = /^func\s+(?:\([^)]+\)\s+)?(\w+)/;
|
|
53
|
+
/**
|
|
54
|
+
* Go variable pattern
|
|
55
|
+
*/
|
|
56
|
+
export const GO_VAR_PATTERN = /^\s*(\w+)\s*:?=/;
|
|
57
|
+
/**
|
|
58
|
+
* Rust function pattern
|
|
59
|
+
*/
|
|
60
|
+
export const RUST_FUNCTION_PATTERN = /fn\s+(\w+)/;
|
|
61
|
+
/**
|
|
62
|
+
* Rust variable pattern
|
|
63
|
+
*/
|
|
64
|
+
export const RUST_VAR_PATTERN = /let\s+(?:mut\s+)?(\w+)/;
|
|
65
|
+
/**
|
|
66
|
+
* Ruby method pattern
|
|
67
|
+
*/
|
|
68
|
+
export const RUBY_METHOD_PATTERN = /def\s+(?:self\.)?(\w+)/;
|
|
69
|
+
/**
|
|
70
|
+
* Ruby variable pattern
|
|
71
|
+
*/
|
|
72
|
+
export const RUBY_VAR_PATTERN = /^\s*(\w+)\s*=/;
|
|
73
|
+
/**
|
|
74
|
+
* Java/Kotlin/C# method pattern
|
|
75
|
+
*/
|
|
76
|
+
export const JAVA_METHOD_PATTERN = /(?:public|private|protected|internal|static|override|virtual|abstract)\s+(?:\w+\s+)*(\w+)\s*\(/;
|
|
77
|
+
/**
|
|
78
|
+
* Java/Kotlin/C# variable pattern
|
|
79
|
+
*/
|
|
80
|
+
export const JAVA_VAR_PATTERN = /(?:var|val|final)?\s*\w+\s+(\w+)\s*[=;]/;
|
|
81
|
+
/**
|
|
82
|
+
* Error handling patterns
|
|
83
|
+
*/
|
|
84
|
+
export const TRY_CATCH_PATTERN = /\btry\s*\{|\btry\s*:/;
|
|
85
|
+
export const CATCH_PATTERN = /\.catch\s*\(|\bexcept\b|\brescue\b/;
|
|
86
|
+
export const RESULT_TYPE_PATTERN = /Result<|Result\[|Err\(|Ok\(|Either<|\bif\s+err\s*!=\s*nil\b/;
|
|
87
|
+
/**
|
|
88
|
+
* Import style patterns
|
|
89
|
+
*/
|
|
90
|
+
export const NAMED_IMPORT_PATTERN = /^import\s+\{/;
|
|
91
|
+
export const WILDCARD_IMPORT_PATTERN = /^import\s+\*\s+as/;
|
|
92
|
+
export const SIDE_EFFECT_IMPORT_PATTERN = /^import\s+['"]/;
|
|
93
|
+
export const DEFAULT_IMPORT_PATTERN = /^import\s+\w/;
|
|
94
|
+
/**
|
|
95
|
+
* Quote style detection
|
|
96
|
+
*/
|
|
97
|
+
export function countQuotes(line) {
|
|
98
|
+
return {
|
|
99
|
+
single: (line.match(/'/g) || []).length,
|
|
100
|
+
double: (line.match(/"/g) || []).length,
|
|
101
|
+
backtick: (line.match(/`/g) || []).length,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -7,19 +7,17 @@
|
|
|
7
7
|
*
|
|
8
8
|
* What it checks:
|
|
9
9
|
* 1. Naming conventions — camelCase vs snake_case vs PascalCase consistency
|
|
10
|
-
* 2. Error handling patterns — try-catch vs .catch()
|
|
10
|
+
* 2. Error handling patterns — try-catch vs .catch()/except/rescue consistency
|
|
11
11
|
* 3. Import style — named vs default vs wildcard import consistency
|
|
12
12
|
* 4. Quote style — single vs double quote consistency
|
|
13
13
|
*
|
|
14
14
|
* How it works:
|
|
15
|
-
* 1. First scan: sample source files → compute
|
|
16
|
-
* 2. Subsequent scans: compare new/changed files against baseline
|
|
15
|
+
* 1. First scan: sample source files → compute per-language style fingerprints → store baseline
|
|
16
|
+
* 2. Subsequent scans: compare new/changed files against their language's baseline
|
|
17
17
|
* 3. If a file deviates >25% on any dimension → flag as style drift
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* @since v5.0.0
|
|
19
|
+
* Baselines are per-language to avoid cross-language contamination
|
|
20
|
+
* (e.g., Python snake_case shouldn't flag JS camelCase).
|
|
23
21
|
*/
|
|
24
22
|
import { Gate, GateContext } from './base.js';
|
|
25
23
|
import { Failure, Provenance } from '../types/index.js';
|
|
@@ -34,20 +32,13 @@ export declare class StyleDriftGate extends Gate {
|
|
|
34
32
|
constructor(config?: StyleDriftConfig);
|
|
35
33
|
protected get provenance(): Provenance;
|
|
36
34
|
run(context: GateContext): Promise<Failure[]>;
|
|
35
|
+
private computePerLanguageBaseline;
|
|
37
36
|
private computeFingerprint;
|
|
37
|
+
private emptyFingerprint;
|
|
38
38
|
private analyzeFile;
|
|
39
|
-
private classifyCasing;
|
|
40
39
|
private mergeIntoFingerprint;
|
|
41
40
|
private compareToBaseline;
|
|
42
|
-
/**
|
|
43
|
-
* Compare two distributions and return a deviation score (0-1).
|
|
44
|
-
* 0 = perfect match, 1 = completely different predominant style.
|
|
45
|
-
*
|
|
46
|
-
* Method: find the predominant category in each distribution.
|
|
47
|
-
* If they differ, score = how far the file is from the baseline's predominant category.
|
|
48
|
-
*/
|
|
49
41
|
private distributionDeviation;
|
|
50
42
|
private hasSignificantData;
|
|
51
|
-
/** Convert a typed distribution to a generic Record for comparison */
|
|
52
43
|
private toRecord;
|
|
53
44
|
}
|
|
@@ -7,23 +7,23 @@
|
|
|
7
7
|
*
|
|
8
8
|
* What it checks:
|
|
9
9
|
* 1. Naming conventions — camelCase vs snake_case vs PascalCase consistency
|
|
10
|
-
* 2. Error handling patterns — try-catch vs .catch()
|
|
10
|
+
* 2. Error handling patterns — try-catch vs .catch()/except/rescue consistency
|
|
11
11
|
* 3. Import style — named vs default vs wildcard import consistency
|
|
12
12
|
* 4. Quote style — single vs double quote consistency
|
|
13
13
|
*
|
|
14
14
|
* How it works:
|
|
15
|
-
* 1. First scan: sample source files → compute
|
|
16
|
-
* 2. Subsequent scans: compare new/changed files against baseline
|
|
15
|
+
* 1. First scan: sample source files → compute per-language style fingerprints → store baseline
|
|
16
|
+
* 2. Subsequent scans: compare new/changed files against their language's baseline
|
|
17
17
|
* 3. If a file deviates >25% on any dimension → flag as style drift
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* @since v5.0.0
|
|
19
|
+
* Baselines are per-language to avoid cross-language contamination
|
|
20
|
+
* (e.g., Python snake_case shouldn't flag JS camelCase).
|
|
23
21
|
*/
|
|
24
22
|
import { Gate } from './base.js';
|
|
25
23
|
import { FileScanner } from '../utils/scanner.js';
|
|
26
24
|
import { Logger } from '../utils/logger.js';
|
|
25
|
+
import { languageAdapters } from './language-adapters/index.js';
|
|
26
|
+
import { TRY_CATCH_PATTERN, CATCH_PATTERN, RESULT_TYPE_PATTERN, NAMED_IMPORT_PATTERN, WILDCARD_IMPORT_PATTERN, SIDE_EFFECT_IMPORT_PATTERN, DEFAULT_IMPORT_PATTERN, countQuotes, } from './style-drift-rules.js';
|
|
27
27
|
import fs from 'fs-extra';
|
|
28
28
|
import path from 'path';
|
|
29
29
|
export class StyleDriftGate extends Gate {
|
|
@@ -33,7 +33,7 @@ export class StyleDriftGate extends Gate {
|
|
|
33
33
|
this.config = {
|
|
34
34
|
enabled: config.enabled ?? true,
|
|
35
35
|
deviation_threshold: config.deviation_threshold ?? 0.25,
|
|
36
|
-
sample_size: config.sample_size ??
|
|
36
|
+
sample_size: config.sample_size ?? 50,
|
|
37
37
|
baseline_path: config.baseline_path ?? '.rigour/style-baseline.json',
|
|
38
38
|
};
|
|
39
39
|
}
|
|
@@ -46,43 +46,63 @@ export class StyleDriftGate extends Gate {
|
|
|
46
46
|
// Find source files
|
|
47
47
|
const files = await FileScanner.findFiles({
|
|
48
48
|
cwd: context.cwd,
|
|
49
|
-
patterns: context.patterns ||
|
|
49
|
+
patterns: context.patterns || languageAdapters.getScanPatterns(),
|
|
50
50
|
ignore: [...(context.ignore || []), '**/node_modules/**', '**/dist/**', '**/*.test.*', '**/*.spec.*', '**/*.d.ts'],
|
|
51
51
|
});
|
|
52
52
|
if (files.length === 0)
|
|
53
53
|
return [];
|
|
54
|
+
// Group files by language
|
|
55
|
+
const filesByLang = new Map();
|
|
56
|
+
for (const file of files) {
|
|
57
|
+
const adapter = languageAdapters.getAdapter(file);
|
|
58
|
+
if (!adapter)
|
|
59
|
+
continue;
|
|
60
|
+
const langFiles = filesByLang.get(adapter.id) || [];
|
|
61
|
+
langFiles.push(file);
|
|
62
|
+
filesByLang.set(adapter.id, langFiles);
|
|
63
|
+
}
|
|
54
64
|
// Load or create baseline
|
|
55
65
|
let baseline = null;
|
|
56
66
|
if (await fs.pathExists(baselinePath)) {
|
|
57
67
|
try {
|
|
58
|
-
|
|
68
|
+
const raw = await fs.readJson(baselinePath);
|
|
69
|
+
// Handle migration from old single-fingerprint format
|
|
70
|
+
if (raw.version === 2) {
|
|
71
|
+
baseline = raw;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
Logger.debug('Old style baseline format detected, creating new per-language baseline');
|
|
75
|
+
}
|
|
59
76
|
}
|
|
60
77
|
catch {
|
|
61
78
|
Logger.debug('Failed to load style baseline, will create new one');
|
|
62
79
|
}
|
|
63
80
|
}
|
|
64
81
|
if (!baseline) {
|
|
65
|
-
// First scan: create baseline
|
|
66
|
-
|
|
67
|
-
baseline = await this.computeFingerprint(context, sampled);
|
|
68
|
-
baseline.createdAt = new Date().toISOString();
|
|
69
|
-
// Ensure directory exists and save baseline
|
|
82
|
+
// First scan: create per-language baseline
|
|
83
|
+
baseline = await this.computePerLanguageBaseline(context, filesByLang);
|
|
70
84
|
await fs.ensureDir(path.dirname(baselinePath));
|
|
71
85
|
await fs.writeJson(baselinePath, baseline, { spaces: 2 });
|
|
72
|
-
|
|
86
|
+
const langSummary = Object.entries(baseline.languages)
|
|
87
|
+
.map(([lang, fp]) => `${lang}:${fp.totalFilesAnalyzed}`)
|
|
88
|
+
.join(', ');
|
|
89
|
+
Logger.info(`Style Drift: Created baseline (${langSummary}) → ${baselinePath}`);
|
|
73
90
|
return []; // No failures on first scan
|
|
74
91
|
}
|
|
75
|
-
// Subsequent scan: compare each file against baseline
|
|
92
|
+
// Subsequent scan: compare each file against its own language's baseline
|
|
76
93
|
const contents = await FileScanner.readFiles(context.cwd, files, context.fileCache);
|
|
77
94
|
for (const [file, content] of contents) {
|
|
78
|
-
const
|
|
79
|
-
if (!
|
|
95
|
+
const adapter = languageAdapters.getAdapter(file);
|
|
96
|
+
if (!adapter)
|
|
80
97
|
continue;
|
|
81
|
-
const
|
|
82
|
-
|
|
98
|
+
const langBaseline = baseline.languages[adapter.id];
|
|
99
|
+
if (!langBaseline)
|
|
100
|
+
continue; // No baseline for this language yet
|
|
101
|
+
const fileFingerprint = this.analyzeFile(content, file);
|
|
102
|
+
const deviations = this.compareToBaseline(fileFingerprint, langBaseline);
|
|
83
103
|
for (const deviation of deviations) {
|
|
84
104
|
if (deviation.score > this.config.deviation_threshold) {
|
|
85
|
-
failures.push(this.createFailure(`Style drift in ${file}: ${deviation.dimension} deviates ${(deviation.score * 100).toFixed(0)}% from
|
|
105
|
+
failures.push(this.createFailure(`Style drift in ${file}: ${deviation.dimension} deviates ${(deviation.score * 100).toFixed(0)}% from ${adapter.id} baseline (${deviation.detail}).`, [file], `This file's ${deviation.dimension} doesn't match the ${adapter.id} project convention. ${deviation.suggestion}`, 'Style Drift', undefined, undefined, 'low'));
|
|
86
106
|
}
|
|
87
107
|
}
|
|
88
108
|
}
|
|
@@ -91,130 +111,105 @@ export class StyleDriftGate extends Gate {
|
|
|
91
111
|
}
|
|
92
112
|
return failures;
|
|
93
113
|
}
|
|
94
|
-
// ───
|
|
95
|
-
async
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
},
|
|
101
|
-
errorHandling: { tryCatch: 0, promiseCatch: 0, resultType: 0 },
|
|
102
|
-
importStyle: { named: 0, default: 0, wildcard: 0, sideEffect: 0 },
|
|
103
|
-
quoteStyle: { single: 0, double: 0, backtick: 0 },
|
|
104
|
-
totalFilesAnalyzed: 0,
|
|
105
|
-
createdAt: '',
|
|
114
|
+
// ─── Per-Language Baseline Computation ────────────────────────────
|
|
115
|
+
async computePerLanguageBaseline(context, filesByLang) {
|
|
116
|
+
const baseline = {
|
|
117
|
+
languages: {},
|
|
118
|
+
createdAt: new Date().toISOString(),
|
|
119
|
+
version: 2,
|
|
106
120
|
};
|
|
121
|
+
for (const [langId, langFiles] of filesByLang) {
|
|
122
|
+
// Sample up to sample_size files per language
|
|
123
|
+
const sampled = langFiles.slice(0, this.config.sample_size);
|
|
124
|
+
const fingerprint = await this.computeFingerprint(context, sampled);
|
|
125
|
+
fingerprint.createdAt = baseline.createdAt;
|
|
126
|
+
baseline.languages[langId] = fingerprint;
|
|
127
|
+
}
|
|
128
|
+
return baseline;
|
|
129
|
+
}
|
|
130
|
+
async computeFingerprint(context, files) {
|
|
131
|
+
const fingerprint = this.emptyFingerprint();
|
|
107
132
|
const contents = await FileScanner.readFiles(context.cwd, files, context.fileCache);
|
|
108
133
|
for (const [file, content] of contents) {
|
|
109
|
-
const
|
|
110
|
-
const fileAnalysis = this.analyzeFile(content, ext);
|
|
134
|
+
const fileAnalysis = this.analyzeFile(content, file);
|
|
111
135
|
this.mergeIntoFingerprint(fingerprint, fileAnalysis);
|
|
112
136
|
fingerprint.totalFilesAnalyzed++;
|
|
113
137
|
}
|
|
114
138
|
return fingerprint;
|
|
115
139
|
}
|
|
116
|
-
|
|
117
|
-
|
|
140
|
+
emptyFingerprint() {
|
|
141
|
+
return {
|
|
118
142
|
naming: {
|
|
119
|
-
functions: { camelCase: 0, snake_case: 0, PascalCase: 0, SCREAMING_SNAKE: 0 },
|
|
120
|
-
variables: { camelCase: 0, snake_case: 0, PascalCase: 0, SCREAMING_SNAKE: 0 },
|
|
143
|
+
functions: { camelCase: 0, snake_case: 0, PascalCase: 0, SCREAMING_SNAKE: 0, 'kebab-case': 0, other: 0 },
|
|
144
|
+
variables: { camelCase: 0, snake_case: 0, PascalCase: 0, SCREAMING_SNAKE: 0, 'kebab-case': 0, other: 0 },
|
|
121
145
|
},
|
|
122
146
|
errorHandling: { tryCatch: 0, promiseCatch: 0, resultType: 0 },
|
|
123
147
|
importStyle: { named: 0, default: 0, wildcard: 0, sideEffect: 0 },
|
|
124
148
|
quoteStyle: { single: 0, double: 0, backtick: 0 },
|
|
125
|
-
totalFilesAnalyzed:
|
|
149
|
+
totalFilesAnalyzed: 0,
|
|
126
150
|
createdAt: '',
|
|
127
151
|
};
|
|
152
|
+
}
|
|
153
|
+
analyzeFile(content, filePath) {
|
|
154
|
+
const fp = this.emptyFingerprint();
|
|
155
|
+
fp.totalFilesAnalyzed = 1;
|
|
128
156
|
const lines = content.split('\n');
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
// Arrow function assignments
|
|
141
|
-
const arrowMatch = line.match(/(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|(\w+))\s*=>/);
|
|
142
|
-
if (arrowMatch)
|
|
143
|
-
this.classifyCasing(arrowMatch[1], fp.naming.functions);
|
|
144
|
-
// Variable declarations (non-function)
|
|
145
|
-
const varMatch = line.match(/(?:const|let|var)\s+(\w+)\s*=/);
|
|
146
|
-
if (varMatch && !arrowMatch)
|
|
147
|
-
this.classifyCasing(varMatch[1], fp.naming.variables);
|
|
148
|
-
// Python function/variable
|
|
149
|
-
if (ext === '.py') {
|
|
150
|
-
const pyFn = line.match(/def\s+(\w+)/);
|
|
151
|
-
if (pyFn)
|
|
152
|
-
this.classifyCasing(pyFn[1], fp.naming.functions);
|
|
153
|
-
const pyVar = line.match(/^(\w+)\s*=/);
|
|
154
|
-
if (pyVar && !pyFn)
|
|
155
|
-
this.classifyCasing(pyVar[1], fp.naming.variables);
|
|
157
|
+
const adapter = languageAdapters.getAdapter(filePath);
|
|
158
|
+
// ── Naming conventions (via adapter) ──
|
|
159
|
+
if (adapter) {
|
|
160
|
+
const namingPatterns = adapter.extractNamingPatterns(content);
|
|
161
|
+
for (const pattern of namingPatterns) {
|
|
162
|
+
if (pattern.kind === 'function' || pattern.kind === 'method') {
|
|
163
|
+
fp.naming.functions[pattern.convention]++;
|
|
164
|
+
}
|
|
165
|
+
else if (pattern.kind === 'variable' || pattern.kind === 'constant') {
|
|
166
|
+
fp.naming.variables[pattern.convention]++;
|
|
167
|
+
}
|
|
156
168
|
}
|
|
169
|
+
}
|
|
170
|
+
for (const line of lines) {
|
|
157
171
|
// ── Error handling ──
|
|
158
|
-
if (
|
|
172
|
+
if (TRY_CATCH_PATTERN.test(line))
|
|
159
173
|
fp.errorHandling.tryCatch++;
|
|
160
|
-
if (
|
|
174
|
+
if (CATCH_PATTERN.test(line))
|
|
161
175
|
fp.errorHandling.promiseCatch++;
|
|
162
|
-
if (
|
|
176
|
+
if (RESULT_TYPE_PATTERN.test(line))
|
|
163
177
|
fp.errorHandling.resultType++;
|
|
164
178
|
// ── Import style ──
|
|
165
|
-
if (
|
|
179
|
+
if (NAMED_IMPORT_PATTERN.test(line.trim()))
|
|
166
180
|
fp.importStyle.named++;
|
|
167
|
-
else if (
|
|
181
|
+
else if (WILDCARD_IMPORT_PATTERN.test(line.trim()))
|
|
168
182
|
fp.importStyle.wildcard++;
|
|
169
|
-
else if (
|
|
183
|
+
else if (SIDE_EFFECT_IMPORT_PATTERN.test(line.trim()))
|
|
170
184
|
fp.importStyle.sideEffect++;
|
|
171
|
-
else if (
|
|
185
|
+
else if (DEFAULT_IMPORT_PATTERN.test(line.trim()))
|
|
172
186
|
fp.importStyle.default++;
|
|
173
187
|
// ── Quote style ──
|
|
174
|
-
// Count quotes in non-import lines (imports are already counted above)
|
|
175
188
|
if (!line.trim().startsWith('import')) {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
fp.quoteStyle.
|
|
180
|
-
fp.quoteStyle.double += doubles;
|
|
181
|
-
fp.quoteStyle.backtick += backticks;
|
|
189
|
+
const quotes = countQuotes(line);
|
|
190
|
+
fp.quoteStyle.single += quotes.single;
|
|
191
|
+
fp.quoteStyle.double += quotes.double;
|
|
192
|
+
fp.quoteStyle.backtick += quotes.backtick;
|
|
182
193
|
}
|
|
183
194
|
}
|
|
184
195
|
return fp;
|
|
185
196
|
}
|
|
186
|
-
classifyCasing(name, dist) {
|
|
187
|
-
if (name.startsWith('_') || name.length <= 1)
|
|
188
|
-
return; // Skip private/single char
|
|
189
|
-
if (/^[A-Z][A-Z0-9_]+$/.test(name)) {
|
|
190
|
-
dist.SCREAMING_SNAKE++;
|
|
191
|
-
}
|
|
192
|
-
else if (/^[A-Z]/.test(name)) {
|
|
193
|
-
dist.PascalCase++;
|
|
194
|
-
}
|
|
195
|
-
else if (name.includes('_')) {
|
|
196
|
-
dist.snake_case++;
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
dist.camelCase++;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
197
|
mergeIntoFingerprint(target, source) {
|
|
203
|
-
// Naming
|
|
204
198
|
for (const key of Object.keys(target.naming.functions)) {
|
|
205
|
-
target.naming.functions[key]
|
|
206
|
-
|
|
199
|
+
const targetVal = target.naming.functions[key] ?? 0;
|
|
200
|
+
const sourceVal = source.naming.functions[key] ?? 0;
|
|
201
|
+
target.naming.functions[key] = targetVal + sourceVal;
|
|
202
|
+
const targetVarVal = target.naming.variables[key] ?? 0;
|
|
203
|
+
const sourceVarVal = source.naming.variables[key] ?? 0;
|
|
204
|
+
target.naming.variables[key] = targetVarVal + sourceVarVal;
|
|
207
205
|
}
|
|
208
|
-
// Error handling
|
|
209
206
|
target.errorHandling.tryCatch += source.errorHandling.tryCatch;
|
|
210
207
|
target.errorHandling.promiseCatch += source.errorHandling.promiseCatch;
|
|
211
208
|
target.errorHandling.resultType += source.errorHandling.resultType;
|
|
212
|
-
// Import style
|
|
213
209
|
target.importStyle.named += source.importStyle.named;
|
|
214
210
|
target.importStyle.default += source.importStyle.default;
|
|
215
211
|
target.importStyle.wildcard += source.importStyle.wildcard;
|
|
216
212
|
target.importStyle.sideEffect += source.importStyle.sideEffect;
|
|
217
|
-
// Quote style
|
|
218
213
|
target.quoteStyle.single += source.quoteStyle.single;
|
|
219
214
|
target.quoteStyle.double += source.quoteStyle.double;
|
|
220
215
|
target.quoteStyle.backtick += source.quoteStyle.backtick;
|
|
@@ -222,7 +217,6 @@ export class StyleDriftGate extends Gate {
|
|
|
222
217
|
// ─── Baseline Comparison ─────────────────────────────────────────
|
|
223
218
|
compareToBaseline(file, baseline) {
|
|
224
219
|
const deviations = [];
|
|
225
|
-
// Compare function naming
|
|
226
220
|
const fnDev = this.distributionDeviation(this.toRecord(file.naming.functions), this.toRecord(baseline.naming.functions));
|
|
227
221
|
if (fnDev.score > 0) {
|
|
228
222
|
deviations.push({
|
|
@@ -232,7 +226,6 @@ export class StyleDriftGate extends Gate {
|
|
|
232
226
|
suggestion: `Use ${fnDev.baselinePredominant} for function names to match project conventions.`,
|
|
233
227
|
});
|
|
234
228
|
}
|
|
235
|
-
// Compare variable naming
|
|
236
229
|
const varDev = this.distributionDeviation(this.toRecord(file.naming.variables), this.toRecord(baseline.naming.variables));
|
|
237
230
|
if (varDev.score > 0) {
|
|
238
231
|
deviations.push({
|
|
@@ -242,7 +235,6 @@ export class StyleDriftGate extends Gate {
|
|
|
242
235
|
suggestion: `Use ${varDev.baselinePredominant} for variable names to match project conventions.`,
|
|
243
236
|
});
|
|
244
237
|
}
|
|
245
|
-
// Compare error handling
|
|
246
238
|
const errDev = this.distributionDeviation(file.errorHandling, baseline.errorHandling);
|
|
247
239
|
if (errDev.score > 0 && this.hasSignificantData(file.errorHandling)) {
|
|
248
240
|
deviations.push({
|
|
@@ -252,7 +244,6 @@ export class StyleDriftGate extends Gate {
|
|
|
252
244
|
suggestion: `Use ${errDev.baselinePredominant} error handling pattern to match project conventions.`,
|
|
253
245
|
});
|
|
254
246
|
}
|
|
255
|
-
// Compare import style
|
|
256
247
|
const impDev = this.distributionDeviation(file.importStyle, baseline.importStyle);
|
|
257
248
|
if (impDev.score > 0 && this.hasSignificantData(file.importStyle)) {
|
|
258
249
|
deviations.push({
|
|
@@ -264,42 +255,33 @@ export class StyleDriftGate extends Gate {
|
|
|
264
255
|
}
|
|
265
256
|
return deviations;
|
|
266
257
|
}
|
|
267
|
-
/**
|
|
268
|
-
* Compare two distributions and return a deviation score (0-1).
|
|
269
|
-
* 0 = perfect match, 1 = completely different predominant style.
|
|
270
|
-
*
|
|
271
|
-
* Method: find the predominant category in each distribution.
|
|
272
|
-
* If they differ, score = how far the file is from the baseline's predominant category.
|
|
273
|
-
*/
|
|
274
258
|
distributionDeviation(file, baseline) {
|
|
275
259
|
const fileTotal = Object.values(file).reduce((a, b) => a + b, 0);
|
|
276
260
|
const baselineTotal = Object.values(baseline).reduce((a, b) => a + b, 0);
|
|
277
261
|
if (fileTotal < 3 || baselineTotal < 5) {
|
|
278
262
|
return { score: 0, filePredominant: 'N/A', baselinePredominant: 'N/A' };
|
|
279
263
|
}
|
|
280
|
-
// Find predominant category
|
|
281
264
|
const filePredominant = Object.entries(file).sort((a, b) => b[1] - a[1])[0][0];
|
|
282
265
|
const baselinePredominant = Object.entries(baseline).sort((a, b) => b[1] - a[1])[0][0];
|
|
283
266
|
if (filePredominant === baselinePredominant) {
|
|
284
267
|
return { score: 0, filePredominant, baselinePredominant };
|
|
285
268
|
}
|
|
286
|
-
// Calculate how much the file uses the baseline's predominant style
|
|
287
269
|
const fileUseOfBaseline = (file[baselinePredominant] || 0) / fileTotal;
|
|
288
270
|
const baselineUseOfBaseline = (baseline[baselinePredominant] || 0) / baselineTotal;
|
|
289
|
-
// Score = how far the file deviates from baseline's predominant ratio
|
|
290
271
|
const deviation = Math.max(0, baselineUseOfBaseline - fileUseOfBaseline);
|
|
291
272
|
return { score: deviation, filePredominant, baselinePredominant };
|
|
292
273
|
}
|
|
293
274
|
hasSignificantData(obj) {
|
|
294
275
|
return Object.values(obj).reduce((a, b) => a + b, 0) >= 3;
|
|
295
276
|
}
|
|
296
|
-
/** Convert a typed distribution to a generic Record for comparison */
|
|
297
277
|
toRecord(dist) {
|
|
298
278
|
return {
|
|
299
279
|
camelCase: dist.camelCase,
|
|
300
280
|
snake_case: dist.snake_case,
|
|
301
281
|
PascalCase: dist.PascalCase,
|
|
302
282
|
SCREAMING_SNAKE: dist.SCREAMING_SNAKE,
|
|
283
|
+
'kebab-case': dist['kebab-case'] || 0,
|
|
284
|
+
other: dist.other || 0,
|
|
303
285
|
};
|
|
304
286
|
}
|
|
305
287
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Quality Gate — Assertion Pattern Matchers and Test Anti-Patterns
|
|
3
|
+
*
|
|
4
|
+
* Contains assertion pattern matchers and test anti-pattern regexes.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Regex patterns for detecting assertions in JavaScript/TypeScript tests
|
|
8
|
+
*/
|
|
9
|
+
export declare const JS_ASSERTION_PATTERNS: RegExp[];
|
|
10
|
+
/**
|
|
11
|
+
* Regex patterns for detecting mocks in JavaScript/TypeScript tests
|
|
12
|
+
*/
|
|
13
|
+
export declare const JS_MOCK_PATTERNS: RegExp[];
|
|
14
|
+
/**
|
|
15
|
+
* Regex patterns for detecting assertions in Python tests
|
|
16
|
+
*/
|
|
17
|
+
export declare const PYTHON_ASSERTION_PATTERNS: RegExp[];
|
|
18
|
+
/**
|
|
19
|
+
* Regex patterns for detecting mocks in Python tests
|
|
20
|
+
*/
|
|
21
|
+
export declare const PYTHON_MOCK_PATTERNS: RegExp[];
|
|
22
|
+
/**
|
|
23
|
+
* Test block pattern for JavaScript/TypeScript
|
|
24
|
+
*/
|
|
25
|
+
export declare const JS_TEST_START_PATTERN: RegExp;
|
|
26
|
+
/**
|
|
27
|
+
* Python test function pattern
|
|
28
|
+
*/
|
|
29
|
+
export declare const PYTHON_TEST_FUNC_PATTERN: RegExp;
|
|
30
|
+
/**
|
|
31
|
+
* Tautological assertion patterns for JavaScript
|
|
32
|
+
*/
|
|
33
|
+
export declare const JS_TAUTOLOGICAL_PATTERNS: RegExp[];
|
|
34
|
+
/**
|
|
35
|
+
* Variable tautology pattern for JavaScript
|
|
36
|
+
*/
|
|
37
|
+
export declare const JS_VAR_TAUTOLOGY_PATTERN: RegExp;
|
|
38
|
+
/**
|
|
39
|
+
* Snapshot test patterns
|
|
40
|
+
*/
|
|
41
|
+
export declare const SNAPSHOT_PATTERNS: RegExp[];
|
|
42
|
+
/**
|
|
43
|
+
* Python tautological patterns
|
|
44
|
+
*/
|
|
45
|
+
export declare const PYTHON_TAUTOLOGICAL_PATTERNS: RegExp[];
|
|
46
|
+
/**
|
|
47
|
+
* Python fixture decorator pattern
|
|
48
|
+
*/
|
|
49
|
+
export declare const PYTHON_FIXTURE_PATTERN: RegExp;
|
|
50
|
+
/**
|
|
51
|
+
* Python conftest file name
|
|
52
|
+
*/
|
|
53
|
+
export declare const PYTHON_CONFTEST_NAME = "conftest.py";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Quality Gate — Assertion Pattern Matchers and Test Anti-Patterns
|
|
3
|
+
*
|
|
4
|
+
* Contains assertion pattern matchers and test anti-pattern regexes.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Regex patterns for detecting assertions in JavaScript/TypeScript tests
|
|
8
|
+
*/
|
|
9
|
+
export const JS_ASSERTION_PATTERNS = [
|
|
10
|
+
/expect\s*\(/,
|
|
11
|
+
/assert\s*[.(]/,
|
|
12
|
+
/\.toEqual|\.toBe|\.toContain|\.toMatch|\.toThrow|\.toHaveBeenCalled|\.toHaveLength|\.toBeTruthy|\.toBeFalsy|\.toBeDefined|\.toBeNull|\.toBeUndefined|\.toBeGreaterThan|\.toBeLessThan|\.toHaveProperty|\.toStrictEqual|\.rejects|\.resolves/,
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Regex patterns for detecting mocks in JavaScript/TypeScript tests
|
|
16
|
+
*/
|
|
17
|
+
export const JS_MOCK_PATTERNS = [
|
|
18
|
+
/jest\.fn\(/,
|
|
19
|
+
/vi\.fn\(/,
|
|
20
|
+
/jest\.mock\(/,
|
|
21
|
+
/vi\.mock\(/,
|
|
22
|
+
/jest\.spyOn\(/,
|
|
23
|
+
/vi\.spyOn\(/,
|
|
24
|
+
/sinon\.(stub|mock|spy)\(/,
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* Regex patterns for detecting assertions in Python tests
|
|
28
|
+
*/
|
|
29
|
+
export const PYTHON_ASSERTION_PATTERNS = [
|
|
30
|
+
/\bassert\s+/,
|
|
31
|
+
/self\.assert\w+\s*\(/,
|
|
32
|
+
/pytest\.raises\s*\(/,
|
|
33
|
+
/\.assert_called|\.assert_any_call/,
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Regex patterns for detecting mocks in Python tests
|
|
37
|
+
*/
|
|
38
|
+
export const PYTHON_MOCK_PATTERNS = [
|
|
39
|
+
/mock\./,
|
|
40
|
+
/Mock\(/,
|
|
41
|
+
/patch\(/,
|
|
42
|
+
/MagicMock\(/,
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* Test block pattern for JavaScript/TypeScript
|
|
46
|
+
*/
|
|
47
|
+
export const JS_TEST_START_PATTERN = /^(?:it|test)\s*\(\s*['"`].*['"`]\s*,\s*(async\s+)?(?:\(\s*\)|function\s*\(\s*\)|\(\s*\{[^}]*\}\s*\))\s*(?:=>)?\s*\{/;
|
|
48
|
+
/**
|
|
49
|
+
* Python test function pattern
|
|
50
|
+
*/
|
|
51
|
+
export const PYTHON_TEST_FUNC_PATTERN = /^(\s*)(?:def|async\s+def)\s+(test_\w+)\s*\(/;
|
|
52
|
+
/**
|
|
53
|
+
* Tautological assertion patterns for JavaScript
|
|
54
|
+
*/
|
|
55
|
+
export const JS_TAUTOLOGICAL_PATTERNS = [
|
|
56
|
+
/expect\s*\(\s*true\s*\)\s*\.toBe\s*\(\s*true\s*\)/,
|
|
57
|
+
/expect\s*\(\s*false\s*\)\s*\.toBe\s*\(\s*false\s*\)/,
|
|
58
|
+
/expect\s*\(\s*1\s*\)\s*\.toBe\s*\(\s*1\s*\)/,
|
|
59
|
+
];
|
|
60
|
+
/**
|
|
61
|
+
* Variable tautology pattern for JavaScript
|
|
62
|
+
*/
|
|
63
|
+
export const JS_VAR_TAUTOLOGY_PATTERN = /expect\s*\(\s*(\w+)\s*\)\s*\.(?:toBe|toEqual|toStrictEqual)\s*\(\s*(\w+)\s*\)/;
|
|
64
|
+
/**
|
|
65
|
+
* Snapshot test patterns
|
|
66
|
+
*/
|
|
67
|
+
export const SNAPSHOT_PATTERNS = [
|
|
68
|
+
/\.toMatchSnapshot\s*\(/,
|
|
69
|
+
/\.toMatchInlineSnapshot\s*\(/,
|
|
70
|
+
];
|
|
71
|
+
/**
|
|
72
|
+
* Python tautological patterns
|
|
73
|
+
*/
|
|
74
|
+
export const PYTHON_TAUTOLOGICAL_PATTERNS = [
|
|
75
|
+
/\bassert\s+True\s*$/,
|
|
76
|
+
/\bassert\s+1\s*==\s*1/,
|
|
77
|
+
/self\.assertTrue\s*\(\s*True\s*\)/,
|
|
78
|
+
];
|
|
79
|
+
/**
|
|
80
|
+
* Python fixture decorator pattern
|
|
81
|
+
*/
|
|
82
|
+
export const PYTHON_FIXTURE_PATTERN = /^@pytest\.fixture/;
|
|
83
|
+
/**
|
|
84
|
+
* Python conftest file name
|
|
85
|
+
*/
|
|
86
|
+
export const PYTHON_CONFTEST_NAME = 'conftest.py';
|