@rigour-labs/core 2.19.1 → 2.19.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/context.test.js +154 -0
- package/dist/gates/context.js +18 -5
- package/package.json +1 -1
- package/src/context.test.ts +169 -0
- package/src/gates/context.ts +19 -5
package/dist/context.test.js
CHANGED
|
@@ -72,4 +72,158 @@ describe('Context Awareness Engine', () => {
|
|
|
72
72
|
const specificFailures = driftFailures.filter(f => f.files?.includes('valid.js'));
|
|
73
73
|
expect(specificFailures.length).toBe(0);
|
|
74
74
|
});
|
|
75
|
+
it('should classify arrow function exports as camelCase, not unknown', async () => {
|
|
76
|
+
// Create files with arrow function patterns that previously returned 'unknown'
|
|
77
|
+
await fs.writeFile(path.join(TEST_CWD, 'api.ts'), `
|
|
78
|
+
export const fetchData = async () => { return []; };
|
|
79
|
+
export const getUserProfile = async (id: string) => { return {}; };
|
|
80
|
+
export const use = () => {};
|
|
81
|
+
export const get = async () => {};
|
|
82
|
+
const handleClick = (e: Event) => {};
|
|
83
|
+
let processItem = async (item: any) => {};
|
|
84
|
+
`);
|
|
85
|
+
// Create a second file with consistent arrow function naming
|
|
86
|
+
await fs.writeFile(path.join(TEST_CWD, 'service.ts'), `
|
|
87
|
+
export const createUser = async (data: any) => {};
|
|
88
|
+
export const deleteUser = async (id: string) => {};
|
|
89
|
+
export const updateUser = async (id: string, data: any) => {};
|
|
90
|
+
`);
|
|
91
|
+
const config = {
|
|
92
|
+
version: 1,
|
|
93
|
+
commands: {},
|
|
94
|
+
gates: {
|
|
95
|
+
context: {
|
|
96
|
+
enabled: true,
|
|
97
|
+
sensitivity: 0.8,
|
|
98
|
+
mining_depth: 10,
|
|
99
|
+
ignored_patterns: [],
|
|
100
|
+
cross_file_patterns: true,
|
|
101
|
+
naming_consistency: true,
|
|
102
|
+
import_relationships: true,
|
|
103
|
+
max_cross_file_depth: 50,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
output: { report_path: 'rigour-report.json' }
|
|
107
|
+
};
|
|
108
|
+
const runner = new GateRunner(config);
|
|
109
|
+
const report = await runner.run(TEST_CWD);
|
|
110
|
+
// Should NOT have any "unknown" naming convention failures
|
|
111
|
+
const namingFailures = report.failures.filter(f => f.id === 'context-drift' && f.details?.includes('unknown'));
|
|
112
|
+
expect(namingFailures.length).toBe(0);
|
|
113
|
+
});
|
|
114
|
+
it('should not classify plain variable declarations as function patterns', async () => {
|
|
115
|
+
// Create file with non-function const declarations
|
|
116
|
+
await fs.writeFile(path.join(TEST_CWD, 'constants.ts'), `
|
|
117
|
+
export const API_URL = 'https://api.example.com';
|
|
118
|
+
export const MAX_RETRIES = 3;
|
|
119
|
+
const config = { timeout: 5000 };
|
|
120
|
+
let count = 0;
|
|
121
|
+
`);
|
|
122
|
+
// Create file with actual functions for a dominant pattern
|
|
123
|
+
await fs.writeFile(path.join(TEST_CWD, 'utils.ts'), `
|
|
124
|
+
function getData() { return []; }
|
|
125
|
+
function setData(d: any) { return d; }
|
|
126
|
+
function processRequest(req: any) { return req; }
|
|
127
|
+
`);
|
|
128
|
+
const config = {
|
|
129
|
+
version: 1,
|
|
130
|
+
commands: {},
|
|
131
|
+
gates: {
|
|
132
|
+
context: {
|
|
133
|
+
enabled: true,
|
|
134
|
+
sensitivity: 0.8,
|
|
135
|
+
mining_depth: 10,
|
|
136
|
+
ignored_patterns: [],
|
|
137
|
+
cross_file_patterns: true,
|
|
138
|
+
naming_consistency: true,
|
|
139
|
+
import_relationships: true,
|
|
140
|
+
max_cross_file_depth: 50,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
output: { report_path: 'rigour-report.json' }
|
|
144
|
+
};
|
|
145
|
+
const runner = new GateRunner(config);
|
|
146
|
+
const report = await runner.run(TEST_CWD);
|
|
147
|
+
// SCREAMING_SNAKE constants should NOT create naming drift failures
|
|
148
|
+
// because they should not be in the 'function' pattern bucket at all
|
|
149
|
+
const namingFailures = report.failures.filter(f => f.id === 'context-drift' && f.details?.includes('SCREAMING_SNAKE'));
|
|
150
|
+
expect(namingFailures.length).toBe(0);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
/**
|
|
154
|
+
* Direct unit tests for detectCasing logic
|
|
155
|
+
*/
|
|
156
|
+
describe('detectCasing classification', () => {
|
|
157
|
+
// We test the regex rules directly since detectCasing is private
|
|
158
|
+
function detectCasing(name) {
|
|
159
|
+
if (/^[A-Z][a-z]/.test(name) && /[a-z][A-Z]/.test(name))
|
|
160
|
+
return 'PascalCase';
|
|
161
|
+
if (/^[a-z]/.test(name) && /[a-z][A-Z]/.test(name))
|
|
162
|
+
return 'camelCase';
|
|
163
|
+
if (/^[a-z][a-zA-Z0-9]*$/.test(name))
|
|
164
|
+
return 'camelCase'; // single-word lowercase
|
|
165
|
+
if (/^[a-z]+(_[a-z]+)+$/.test(name))
|
|
166
|
+
return 'snake_case';
|
|
167
|
+
if (/^[A-Z]+(_[A-Z]+)*$/.test(name))
|
|
168
|
+
return 'SCREAMING_SNAKE';
|
|
169
|
+
if (/^[A-Z][a-zA-Z]*$/.test(name))
|
|
170
|
+
return 'PascalCase';
|
|
171
|
+
return 'unknown';
|
|
172
|
+
}
|
|
173
|
+
// Multi-word camelCase
|
|
174
|
+
it('classifies multi-word camelCase', () => {
|
|
175
|
+
expect(detectCasing('fetchData')).toBe('camelCase');
|
|
176
|
+
expect(detectCasing('getUserProfile')).toBe('camelCase');
|
|
177
|
+
expect(detectCasing('handleClick')).toBe('camelCase');
|
|
178
|
+
expect(detectCasing('processItem')).toBe('camelCase');
|
|
179
|
+
expect(detectCasing('createNewUser')).toBe('camelCase');
|
|
180
|
+
});
|
|
181
|
+
// Single-word lowercase (the bug fix)
|
|
182
|
+
it('classifies single-word lowercase as camelCase', () => {
|
|
183
|
+
expect(detectCasing('fetch')).toBe('camelCase');
|
|
184
|
+
expect(detectCasing('use')).toBe('camelCase');
|
|
185
|
+
expect(detectCasing('get')).toBe('camelCase');
|
|
186
|
+
expect(detectCasing('set')).toBe('camelCase');
|
|
187
|
+
expect(detectCasing('run')).toBe('camelCase');
|
|
188
|
+
expect(detectCasing('a')).toBe('camelCase');
|
|
189
|
+
expect(detectCasing('x')).toBe('camelCase');
|
|
190
|
+
expect(detectCasing('id')).toBe('camelCase');
|
|
191
|
+
});
|
|
192
|
+
// Single-word lowercase with digits
|
|
193
|
+
it('classifies lowercase with digits as camelCase', () => {
|
|
194
|
+
expect(detectCasing('handler2')).toBe('camelCase');
|
|
195
|
+
expect(detectCasing('config3')).toBe('camelCase');
|
|
196
|
+
expect(detectCasing('v2')).toBe('camelCase');
|
|
197
|
+
});
|
|
198
|
+
// PascalCase
|
|
199
|
+
it('classifies PascalCase', () => {
|
|
200
|
+
expect(detectCasing('MyComponent')).toBe('PascalCase');
|
|
201
|
+
expect(detectCasing('UserService')).toBe('PascalCase');
|
|
202
|
+
expect(detectCasing('App')).toBe('PascalCase');
|
|
203
|
+
expect(detectCasing('A')).toBe('SCREAMING_SNAKE'); // single uppercase letter
|
|
204
|
+
});
|
|
205
|
+
// snake_case
|
|
206
|
+
it('classifies snake_case', () => {
|
|
207
|
+
expect(detectCasing('my_func')).toBe('snake_case');
|
|
208
|
+
expect(detectCasing('get_data')).toBe('snake_case');
|
|
209
|
+
expect(detectCasing('process_all_items')).toBe('snake_case');
|
|
210
|
+
});
|
|
211
|
+
// SCREAMING_SNAKE
|
|
212
|
+
it('classifies SCREAMING_SNAKE_CASE', () => {
|
|
213
|
+
expect(detectCasing('API_URL')).toBe('SCREAMING_SNAKE');
|
|
214
|
+
expect(detectCasing('MAX_RETRIES')).toBe('SCREAMING_SNAKE');
|
|
215
|
+
expect(detectCasing('A')).toBe('SCREAMING_SNAKE');
|
|
216
|
+
expect(detectCasing('DB')).toBe('SCREAMING_SNAKE');
|
|
217
|
+
});
|
|
218
|
+
// Edge cases that should NOT be unknown
|
|
219
|
+
it('does not return unknown for valid identifiers', () => {
|
|
220
|
+
const validIdentifiers = [
|
|
221
|
+
'fetch', 'getData', 'MyClass', 'my_func', 'API_KEY',
|
|
222
|
+
'use', 'run', 'a', 'x', 'id', 'App', 'handler2',
|
|
223
|
+
'processItem', 'UserProfile', 'get_all_data', 'MAX_SIZE',
|
|
224
|
+
];
|
|
225
|
+
for (const name of validIdentifiers) {
|
|
226
|
+
expect(detectCasing(name)).not.toBe('unknown');
|
|
227
|
+
}
|
|
228
|
+
});
|
|
75
229
|
});
|
package/dist/gates/context.js
CHANGED
|
@@ -70,11 +70,22 @@ export class ContextGate extends Gate {
|
|
|
70
70
|
* Collect naming patterns (function names, class names, variable names)
|
|
71
71
|
*/
|
|
72
72
|
collectNamingPatterns(content, file, patterns) {
|
|
73
|
-
//
|
|
74
|
-
const
|
|
75
|
-
for (const match of
|
|
76
|
-
const
|
|
77
|
-
|
|
73
|
+
// Named function declarations: function fetchData() { ... }
|
|
74
|
+
const namedFuncMatches = content.matchAll(/function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g);
|
|
75
|
+
for (const match of namedFuncMatches) {
|
|
76
|
+
const casing = this.detectCasing(match[1]);
|
|
77
|
+
this.addPattern(patterns, 'function', { casing, file, count: 1 });
|
|
78
|
+
}
|
|
79
|
+
// Arrow function expressions: (export) const fetchData = (async) (...) => { ... }
|
|
80
|
+
const arrowFuncMatches = content.matchAll(/(?:export\s+)?(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>/g);
|
|
81
|
+
for (const match of arrowFuncMatches) {
|
|
82
|
+
const casing = this.detectCasing(match[1]);
|
|
83
|
+
this.addPattern(patterns, 'function', { casing, file, count: 1 });
|
|
84
|
+
}
|
|
85
|
+
// Function expressions: (export) const fetchData = (async) function(...) { ... }
|
|
86
|
+
const funcExprMatches = content.matchAll(/(?:export\s+)?(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s+)?function\s*\(/g);
|
|
87
|
+
for (const match of funcExprMatches) {
|
|
88
|
+
const casing = this.detectCasing(match[1]);
|
|
78
89
|
this.addPattern(patterns, 'function', { casing, file, count: 1 });
|
|
79
90
|
}
|
|
80
91
|
// Class declarations
|
|
@@ -175,6 +186,8 @@ export class ContextGate extends Gate {
|
|
|
175
186
|
return 'PascalCase';
|
|
176
187
|
if (/^[a-z]/.test(name) && /[a-z][A-Z]/.test(name))
|
|
177
188
|
return 'camelCase';
|
|
189
|
+
if (/^[a-z][a-zA-Z0-9]*$/.test(name))
|
|
190
|
+
return 'camelCase'; // single-word lowercase (e.g. fetch, use, get)
|
|
178
191
|
if (/^[a-z]+(_[a-z]+)+$/.test(name))
|
|
179
192
|
return 'snake_case';
|
|
180
193
|
if (/^[A-Z]+(_[A-Z]+)*$/.test(name))
|
package/package.json
CHANGED
package/src/context.test.ts
CHANGED
|
@@ -84,4 +84,173 @@ describe('Context Awareness Engine', () => {
|
|
|
84
84
|
const specificFailures = driftFailures.filter(f => f.files?.includes('valid.js'));
|
|
85
85
|
expect(specificFailures.length).toBe(0);
|
|
86
86
|
});
|
|
87
|
+
it('should classify arrow function exports as camelCase, not unknown', async () => {
|
|
88
|
+
// Create files with arrow function patterns that previously returned 'unknown'
|
|
89
|
+
await fs.writeFile(path.join(TEST_CWD, 'api.ts'), `
|
|
90
|
+
export const fetchData = async () => { return []; };
|
|
91
|
+
export const getUserProfile = async (id: string) => { return {}; };
|
|
92
|
+
export const use = () => {};
|
|
93
|
+
export const get = async () => {};
|
|
94
|
+
const handleClick = (e: Event) => {};
|
|
95
|
+
let processItem = async (item: any) => {};
|
|
96
|
+
`);
|
|
97
|
+
|
|
98
|
+
// Create a second file with consistent arrow function naming
|
|
99
|
+
await fs.writeFile(path.join(TEST_CWD, 'service.ts'), `
|
|
100
|
+
export const createUser = async (data: any) => {};
|
|
101
|
+
export const deleteUser = async (id: string) => {};
|
|
102
|
+
export const updateUser = async (id: string, data: any) => {};
|
|
103
|
+
`);
|
|
104
|
+
|
|
105
|
+
const config = {
|
|
106
|
+
version: 1,
|
|
107
|
+
commands: {},
|
|
108
|
+
gates: {
|
|
109
|
+
context: {
|
|
110
|
+
enabled: true,
|
|
111
|
+
sensitivity: 0.8,
|
|
112
|
+
mining_depth: 10,
|
|
113
|
+
ignored_patterns: [],
|
|
114
|
+
cross_file_patterns: true,
|
|
115
|
+
naming_consistency: true,
|
|
116
|
+
import_relationships: true,
|
|
117
|
+
max_cross_file_depth: 50,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
output: { report_path: 'rigour-report.json' }
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const runner = new GateRunner(config as any);
|
|
124
|
+
const report = await runner.run(TEST_CWD);
|
|
125
|
+
|
|
126
|
+
// Should NOT have any "unknown" naming convention failures
|
|
127
|
+
const namingFailures = report.failures.filter(f =>
|
|
128
|
+
f.id === 'context-drift' && f.details?.includes('unknown')
|
|
129
|
+
);
|
|
130
|
+
expect(namingFailures.length).toBe(0);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should not classify plain variable declarations as function patterns', async () => {
|
|
134
|
+
// Create file with non-function const declarations
|
|
135
|
+
await fs.writeFile(path.join(TEST_CWD, 'constants.ts'), `
|
|
136
|
+
export const API_URL = 'https://api.example.com';
|
|
137
|
+
export const MAX_RETRIES = 3;
|
|
138
|
+
const config = { timeout: 5000 };
|
|
139
|
+
let count = 0;
|
|
140
|
+
`);
|
|
141
|
+
|
|
142
|
+
// Create file with actual functions for a dominant pattern
|
|
143
|
+
await fs.writeFile(path.join(TEST_CWD, 'utils.ts'), `
|
|
144
|
+
function getData() { return []; }
|
|
145
|
+
function setData(d: any) { return d; }
|
|
146
|
+
function processRequest(req: any) { return req; }
|
|
147
|
+
`);
|
|
148
|
+
|
|
149
|
+
const config = {
|
|
150
|
+
version: 1,
|
|
151
|
+
commands: {},
|
|
152
|
+
gates: {
|
|
153
|
+
context: {
|
|
154
|
+
enabled: true,
|
|
155
|
+
sensitivity: 0.8,
|
|
156
|
+
mining_depth: 10,
|
|
157
|
+
ignored_patterns: [],
|
|
158
|
+
cross_file_patterns: true,
|
|
159
|
+
naming_consistency: true,
|
|
160
|
+
import_relationships: true,
|
|
161
|
+
max_cross_file_depth: 50,
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
output: { report_path: 'rigour-report.json' }
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const runner = new GateRunner(config as any);
|
|
168
|
+
const report = await runner.run(TEST_CWD);
|
|
169
|
+
|
|
170
|
+
// SCREAMING_SNAKE constants should NOT create naming drift failures
|
|
171
|
+
// because they should not be in the 'function' pattern bucket at all
|
|
172
|
+
const namingFailures = report.failures.filter(f =>
|
|
173
|
+
f.id === 'context-drift' && f.details?.includes('SCREAMING_SNAKE')
|
|
174
|
+
);
|
|
175
|
+
expect(namingFailures.length).toBe(0);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Direct unit tests for detectCasing logic
|
|
181
|
+
*/
|
|
182
|
+
describe('detectCasing classification', () => {
|
|
183
|
+
// We test the regex rules directly since detectCasing is private
|
|
184
|
+
function detectCasing(name: string): string {
|
|
185
|
+
if (/^[A-Z][a-z]/.test(name) && /[a-z][A-Z]/.test(name)) return 'PascalCase';
|
|
186
|
+
if (/^[a-z]/.test(name) && /[a-z][A-Z]/.test(name)) return 'camelCase';
|
|
187
|
+
if (/^[a-z][a-zA-Z0-9]*$/.test(name)) return 'camelCase'; // single-word lowercase
|
|
188
|
+
if (/^[a-z]+(_[a-z]+)+$/.test(name)) return 'snake_case';
|
|
189
|
+
if (/^[A-Z]+(_[A-Z]+)*$/.test(name)) return 'SCREAMING_SNAKE';
|
|
190
|
+
if (/^[A-Z][a-zA-Z]*$/.test(name)) return 'PascalCase';
|
|
191
|
+
return 'unknown';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Multi-word camelCase
|
|
195
|
+
it('classifies multi-word camelCase', () => {
|
|
196
|
+
expect(detectCasing('fetchData')).toBe('camelCase');
|
|
197
|
+
expect(detectCasing('getUserProfile')).toBe('camelCase');
|
|
198
|
+
expect(detectCasing('handleClick')).toBe('camelCase');
|
|
199
|
+
expect(detectCasing('processItem')).toBe('camelCase');
|
|
200
|
+
expect(detectCasing('createNewUser')).toBe('camelCase');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Single-word lowercase (the bug fix)
|
|
204
|
+
it('classifies single-word lowercase as camelCase', () => {
|
|
205
|
+
expect(detectCasing('fetch')).toBe('camelCase');
|
|
206
|
+
expect(detectCasing('use')).toBe('camelCase');
|
|
207
|
+
expect(detectCasing('get')).toBe('camelCase');
|
|
208
|
+
expect(detectCasing('set')).toBe('camelCase');
|
|
209
|
+
expect(detectCasing('run')).toBe('camelCase');
|
|
210
|
+
expect(detectCasing('a')).toBe('camelCase');
|
|
211
|
+
expect(detectCasing('x')).toBe('camelCase');
|
|
212
|
+
expect(detectCasing('id')).toBe('camelCase');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Single-word lowercase with digits
|
|
216
|
+
it('classifies lowercase with digits as camelCase', () => {
|
|
217
|
+
expect(detectCasing('handler2')).toBe('camelCase');
|
|
218
|
+
expect(detectCasing('config3')).toBe('camelCase');
|
|
219
|
+
expect(detectCasing('v2')).toBe('camelCase');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// PascalCase
|
|
223
|
+
it('classifies PascalCase', () => {
|
|
224
|
+
expect(detectCasing('MyComponent')).toBe('PascalCase');
|
|
225
|
+
expect(detectCasing('UserService')).toBe('PascalCase');
|
|
226
|
+
expect(detectCasing('App')).toBe('PascalCase');
|
|
227
|
+
expect(detectCasing('A')).toBe('SCREAMING_SNAKE'); // single uppercase letter
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// snake_case
|
|
231
|
+
it('classifies snake_case', () => {
|
|
232
|
+
expect(detectCasing('my_func')).toBe('snake_case');
|
|
233
|
+
expect(detectCasing('get_data')).toBe('snake_case');
|
|
234
|
+
expect(detectCasing('process_all_items')).toBe('snake_case');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// SCREAMING_SNAKE
|
|
238
|
+
it('classifies SCREAMING_SNAKE_CASE', () => {
|
|
239
|
+
expect(detectCasing('API_URL')).toBe('SCREAMING_SNAKE');
|
|
240
|
+
expect(detectCasing('MAX_RETRIES')).toBe('SCREAMING_SNAKE');
|
|
241
|
+
expect(detectCasing('A')).toBe('SCREAMING_SNAKE');
|
|
242
|
+
expect(detectCasing('DB')).toBe('SCREAMING_SNAKE');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Edge cases that should NOT be unknown
|
|
246
|
+
it('does not return unknown for valid identifiers', () => {
|
|
247
|
+
const validIdentifiers = [
|
|
248
|
+
'fetch', 'getData', 'MyClass', 'my_func', 'API_KEY',
|
|
249
|
+
'use', 'run', 'a', 'x', 'id', 'App', 'handler2',
|
|
250
|
+
'processItem', 'UserProfile', 'get_all_data', 'MAX_SIZE',
|
|
251
|
+
];
|
|
252
|
+
for (const name of validIdentifiers) {
|
|
253
|
+
expect(detectCasing(name)).not.toBe('unknown');
|
|
254
|
+
}
|
|
255
|
+
});
|
|
87
256
|
});
|
package/src/gates/context.ts
CHANGED
|
@@ -107,11 +107,24 @@ export class ContextGate extends Gate {
|
|
|
107
107
|
file: string,
|
|
108
108
|
patterns: Map<string, { casing: string; file: string; count: number }[]>
|
|
109
109
|
) {
|
|
110
|
-
//
|
|
111
|
-
const
|
|
112
|
-
for (const match of
|
|
113
|
-
const
|
|
114
|
-
|
|
110
|
+
// Named function declarations: function fetchData() { ... }
|
|
111
|
+
const namedFuncMatches = content.matchAll(/function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g);
|
|
112
|
+
for (const match of namedFuncMatches) {
|
|
113
|
+
const casing = this.detectCasing(match[1]);
|
|
114
|
+
this.addPattern(patterns, 'function', { casing, file, count: 1 });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Arrow function expressions: (export) const fetchData = (async) (...) => { ... }
|
|
118
|
+
const arrowFuncMatches = content.matchAll(/(?:export\s+)?(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>/g);
|
|
119
|
+
for (const match of arrowFuncMatches) {
|
|
120
|
+
const casing = this.detectCasing(match[1]);
|
|
121
|
+
this.addPattern(patterns, 'function', { casing, file, count: 1 });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Function expressions: (export) const fetchData = (async) function(...) { ... }
|
|
125
|
+
const funcExprMatches = content.matchAll(/(?:export\s+)?(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s+)?function\s*\(/g);
|
|
126
|
+
for (const match of funcExprMatches) {
|
|
127
|
+
const casing = this.detectCasing(match[1]);
|
|
115
128
|
this.addPattern(patterns, 'function', { casing, file, count: 1 });
|
|
116
129
|
}
|
|
117
130
|
|
|
@@ -234,6 +247,7 @@ export class ContextGate extends Gate {
|
|
|
234
247
|
private detectCasing(name: string): string {
|
|
235
248
|
if (/^[A-Z][a-z]/.test(name) && /[a-z][A-Z]/.test(name)) return 'PascalCase';
|
|
236
249
|
if (/^[a-z]/.test(name) && /[a-z][A-Z]/.test(name)) return 'camelCase';
|
|
250
|
+
if (/^[a-z][a-zA-Z0-9]*$/.test(name)) return 'camelCase'; // single-word lowercase (e.g. fetch, use, get)
|
|
237
251
|
if (/^[a-z]+(_[a-z]+)+$/.test(name)) return 'snake_case';
|
|
238
252
|
if (/^[A-Z]+(_[A-Z]+)*$/.test(name)) return 'SCREAMING_SNAKE';
|
|
239
253
|
if (/^[A-Z][a-zA-Z]*$/.test(name)) return 'PascalCase';
|