@aiready/consistency 0.3.4 → 0.3.5
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/.turbo/turbo-build.log +24 -0
- package/.turbo/turbo-test.log +81 -0
- package/README.md +55 -7
- package/dist/chunk-2BTBNG6X.mjs +814 -0
- package/dist/cli.js +134 -91
- package/dist/cli.mjs +1 -1
- package/dist/index.js +131 -88
- package/dist/index.mjs +1 -1
- package/package.json +12 -13
- package/src/__tests__/analyzer.test.ts +27 -0
- package/src/analyzers/naming.ts +208 -137
|
@@ -95,6 +95,33 @@ const api = new ApiClient();
|
|
|
95
95
|
// Should not flag these as issues
|
|
96
96
|
expect(true).toBe(true);
|
|
97
97
|
});
|
|
98
|
+
|
|
99
|
+
it('should NOT flag multi-line arrow function parameters (Phase 3)', () => {
|
|
100
|
+
// Multi-line arrow functions should not trigger single-letter warnings
|
|
101
|
+
const multiLineArrowCode = `
|
|
102
|
+
items.map(
|
|
103
|
+
s => s.value
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
items.filter(
|
|
107
|
+
item =>
|
|
108
|
+
item.valid
|
|
109
|
+
)
|
|
110
|
+
`;
|
|
111
|
+
// 's' and 'item' should not be flagged as poor naming
|
|
112
|
+
expect(true).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should NOT flag short-lived comparison variables (Phase 3)', () => {
|
|
116
|
+
// Variables used only within 3-5 lines for comparisons
|
|
117
|
+
const shortLivedCode = `
|
|
118
|
+
const a = obj1;
|
|
119
|
+
const b = obj2;
|
|
120
|
+
return compare(a, b);
|
|
121
|
+
`;
|
|
122
|
+
// 'a' and 'b' should not be flagged as they're short-lived
|
|
123
|
+
expect(true).toBe(true);
|
|
124
|
+
});
|
|
98
125
|
});
|
|
99
126
|
|
|
100
127
|
describe('analyzePatterns', () => {
|
package/src/analyzers/naming.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { readFileContent } from '@aiready/core';
|
|
1
|
+
import { readFileContent, loadConfig } from '@aiready/core';
|
|
2
2
|
import type { NamingIssue } from '../types';
|
|
3
|
+
import { dirname } from 'path';
|
|
3
4
|
|
|
4
5
|
// Common short English words that are NOT abbreviations (full, valid words)
|
|
5
6
|
const COMMON_SHORT_WORDS = new Set([
|
|
@@ -72,16 +73,32 @@ const ACCEPTABLE_ABBREVIATIONS = new Set([
|
|
|
72
73
|
export async function analyzeNaming(files: string[]): Promise<NamingIssue[]> {
|
|
73
74
|
const issues: NamingIssue[] = [];
|
|
74
75
|
|
|
76
|
+
// Load config from the first file's directory (or project root)
|
|
77
|
+
const rootDir = files.length > 0 ? dirname(files[0]) : process.cwd();
|
|
78
|
+
const config = loadConfig(rootDir);
|
|
79
|
+
const consistencyConfig = config?.tools?.['consistency'];
|
|
80
|
+
|
|
81
|
+
// Merge custom abbreviations and short words with defaults
|
|
82
|
+
const customAbbreviations = new Set(consistencyConfig?.acceptedAbbreviations || []);
|
|
83
|
+
const customShortWords = new Set(consistencyConfig?.shortWords || []);
|
|
84
|
+
const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
|
|
85
|
+
|
|
75
86
|
for (const file of files) {
|
|
76
87
|
const content = await readFileContent(file);
|
|
77
|
-
const fileIssues = analyzeFileNaming(file, content);
|
|
88
|
+
const fileIssues = analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks);
|
|
78
89
|
issues.push(...fileIssues);
|
|
79
90
|
}
|
|
80
91
|
|
|
81
92
|
return issues;
|
|
82
93
|
}
|
|
83
94
|
|
|
84
|
-
function analyzeFileNaming(
|
|
95
|
+
function analyzeFileNaming(
|
|
96
|
+
file: string,
|
|
97
|
+
content: string,
|
|
98
|
+
customAbbreviations: Set<string>,
|
|
99
|
+
customShortWords: Set<string>,
|
|
100
|
+
disabledChecks: Set<string>
|
|
101
|
+
): NamingIssue[] {
|
|
85
102
|
const issues: NamingIssue[] = [];
|
|
86
103
|
|
|
87
104
|
// Check if this is a test file (more lenient rules)
|
|
@@ -90,100 +107,150 @@ function analyzeFileNaming(file: string, content: string): NamingIssue[] {
|
|
|
90
107
|
// Split into lines for line number tracking
|
|
91
108
|
const lines = content.split('\n');
|
|
92
109
|
|
|
110
|
+
// Merge custom sets with defaults
|
|
111
|
+
const allAbbreviations = new Set([...ACCEPTABLE_ABBREVIATIONS, ...customAbbreviations]);
|
|
112
|
+
const allShortWords = new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Helper: Get context window around a line (for multi-line pattern detection)
|
|
116
|
+
*/
|
|
117
|
+
const getContextWindow = (index: number, windowSize: number = 3): string => {
|
|
118
|
+
const start = Math.max(0, index - windowSize);
|
|
119
|
+
const end = Math.min(lines.length, index + windowSize + 1);
|
|
120
|
+
return lines.slice(start, end).join('\n');
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Helper: Check if a variable is short-lived (used only within 3-5 lines)
|
|
125
|
+
*/
|
|
126
|
+
const isShortLivedVariable = (varName: string, declarationIndex: number): boolean => {
|
|
127
|
+
const searchRange = 5; // Check 5 lines after declaration
|
|
128
|
+
const endIndex = Math.min(lines.length, declarationIndex + searchRange + 1);
|
|
129
|
+
|
|
130
|
+
let usageCount = 0;
|
|
131
|
+
for (let i = declarationIndex; i < endIndex; i++) {
|
|
132
|
+
// Match variable name as whole word
|
|
133
|
+
const regex = new RegExp(`\\b${varName}\\b`, 'g');
|
|
134
|
+
const matches = lines[i].match(regex);
|
|
135
|
+
if (matches) {
|
|
136
|
+
usageCount += matches.length;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// If variable is only used 2-3 times within 5 lines, it's short-lived
|
|
141
|
+
// (1 = declaration, 1-2 = actual usage)
|
|
142
|
+
return usageCount >= 2 && usageCount <= 3;
|
|
143
|
+
};
|
|
144
|
+
|
|
93
145
|
// Check for naming patterns
|
|
94
146
|
lines.forEach((line, index) => {
|
|
95
147
|
const lineNumber = index + 1;
|
|
148
|
+
const contextWindow = getContextWindow(index);
|
|
96
149
|
|
|
97
150
|
// Check for single letter variables (except i, j, k, l in loops/common contexts)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
// Enhanced loop/iterator context detection
|
|
103
|
-
const isInLoopContext =
|
|
104
|
-
line.includes('for') ||
|
|
105
|
-
/\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) ||
|
|
106
|
-
line.includes('=>') || // Arrow function
|
|
107
|
-
/\w+\s*=>\s*/.test(line); // Callback pattern
|
|
108
|
-
|
|
109
|
-
// Check for i18n/translation context
|
|
110
|
-
const isI18nContext =
|
|
111
|
-
line.includes('useTranslation') ||
|
|
112
|
-
line.includes('i18n.t') ||
|
|
113
|
-
/\bt\s*\(['"]/.test(line); // t('key') pattern
|
|
114
|
-
|
|
115
|
-
// Check for arrow function parameter (improved detection)
|
|
116
|
-
const isArrowFunctionParam =
|
|
117
|
-
/\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
|
|
118
|
-
/[a-z]\s*=>/.test(line); // s =>
|
|
119
|
-
|
|
120
|
-
if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !['x', 'y', 'z', 'i', 'j', 'k', 'l', 'n', 'm'].includes(letter)) {
|
|
121
|
-
// Skip in test files unless it's really unclear
|
|
122
|
-
if (isTestFile && ['a', 'b', 'c', 'd', 'e', 'f', 's'].includes(letter)) {
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
151
|
+
if (!disabledChecks.has('single-letter')) {
|
|
152
|
+
const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
|
|
153
|
+
for (const match of singleLetterMatches) {
|
|
154
|
+
const letter = match[1].toLowerCase();
|
|
125
155
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
line
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
156
|
+
// Enhanced loop/iterator context detection
|
|
157
|
+
const isInLoopContext =
|
|
158
|
+
line.includes('for') ||
|
|
159
|
+
/\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) ||
|
|
160
|
+
line.includes('=>') || // Arrow function
|
|
161
|
+
/\w+\s*=>\s*/.test(line); // Callback pattern
|
|
162
|
+
|
|
163
|
+
// Check for i18n/translation context
|
|
164
|
+
const isI18nContext =
|
|
165
|
+
line.includes('useTranslation') ||
|
|
166
|
+
line.includes('i18n.t') ||
|
|
167
|
+
/\bt\s*\(['"]/.test(line); // t('key') pattern
|
|
168
|
+
|
|
169
|
+
// Check for arrow function parameter (improved detection with context window)
|
|
170
|
+
const isArrowFunctionParam =
|
|
171
|
+
/\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
|
|
172
|
+
/[a-z]\s*=>/.test(line) || // s => on same line
|
|
173
|
+
// Multi-line arrow function detection: look for pattern in context window
|
|
174
|
+
new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
|
|
175
|
+
new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || '') && /=>/.test(contextWindow); // .map(\n s =>
|
|
176
|
+
|
|
177
|
+
// Check if variable is short-lived (comparison/temporary contexts)
|
|
178
|
+
const isShortLived = isShortLivedVariable(letter, index);
|
|
179
|
+
|
|
180
|
+
if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !isShortLived && !['x', 'y', 'z', 'i', 'j', 'k', 'l', 'n', 'm'].includes(letter)) {
|
|
181
|
+
// Skip in test files unless it's really unclear
|
|
182
|
+
if (isTestFile && ['a', 'b', 'c', 'd', 'e', 'f', 's'].includes(letter)) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
issues.push({
|
|
187
|
+
file,
|
|
188
|
+
line: lineNumber,
|
|
189
|
+
type: 'poor-naming',
|
|
190
|
+
identifier: match[1],
|
|
191
|
+
severity: 'minor',
|
|
192
|
+
suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
|
|
193
|
+
});
|
|
194
|
+
}
|
|
134
195
|
}
|
|
135
196
|
}
|
|
136
197
|
|
|
137
198
|
// Check for overly abbreviated variables
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Check for arrow function parameter context
|
|
153
|
-
const isArrowFunctionParam =
|
|
154
|
-
/\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
|
|
155
|
-
new RegExp(`\\b${abbrev}\\s*=>`).test(line); // s =>
|
|
156
|
-
|
|
157
|
-
if (isArrowFunctionParam) {
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// For very short names (1-2 letters), check for date/time context
|
|
162
|
-
if (abbrev.length <= 2) {
|
|
163
|
-
const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
|
|
164
|
-
if (isDateTimeContext && ['d', 't', 'dt'].includes(abbrev)) {
|
|
199
|
+
if (!disabledChecks.has('abbreviation')) {
|
|
200
|
+
const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
|
|
201
|
+
for (const match of abbreviationMatches) {
|
|
202
|
+
const abbrev = match[1].toLowerCase();
|
|
203
|
+
|
|
204
|
+
// Skip if it's a common short English word (full word, not abbreviation)
|
|
205
|
+
if (allShortWords.has(abbrev)) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Skip acceptable abbreviations (including custom ones)
|
|
210
|
+
if (allAbbreviations.has(abbrev)) {
|
|
165
211
|
continue;
|
|
166
212
|
}
|
|
167
213
|
|
|
168
|
-
// Check for
|
|
169
|
-
const
|
|
170
|
-
|
|
214
|
+
// Check for arrow function parameter context (with multi-line detection)
|
|
215
|
+
const isArrowFunctionParam =
|
|
216
|
+
/\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
|
|
217
|
+
new RegExp(`\\b${abbrev}\\s*=>`).test(line) || // s => on same line
|
|
218
|
+
// Multi-line arrow function: check context window
|
|
219
|
+
(new RegExp(`\\b${abbrev}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow)) || // (s)\n =>
|
|
220
|
+
(new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || '') &&
|
|
221
|
+
new RegExp(`^\\s*${abbrev}\\s*=>`).test(line)); // .map(\n s =>
|
|
222
|
+
|
|
223
|
+
if (isArrowFunctionParam) {
|
|
171
224
|
continue;
|
|
172
225
|
}
|
|
226
|
+
|
|
227
|
+
// For very short names (1-2 letters), check for date/time context
|
|
228
|
+
if (abbrev.length <= 2) {
|
|
229
|
+
const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
|
|
230
|
+
if (isDateTimeContext && ['d', 't', 'dt'].includes(abbrev)) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Check for user/auth context
|
|
235
|
+
const isUserContext = /user|auth|account/i.test(line);
|
|
236
|
+
if (isUserContext && abbrev === 'u') {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
issues.push({
|
|
242
|
+
file,
|
|
243
|
+
line: lineNumber,
|
|
244
|
+
type: 'abbreviation',
|
|
245
|
+
identifier: match[1],
|
|
246
|
+
severity: 'info',
|
|
247
|
+
suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
|
|
248
|
+
});
|
|
173
249
|
}
|
|
174
|
-
|
|
175
|
-
issues.push({
|
|
176
|
-
file,
|
|
177
|
-
line: lineNumber,
|
|
178
|
-
type: 'abbreviation',
|
|
179
|
-
identifier: match[1],
|
|
180
|
-
severity: 'info',
|
|
181
|
-
suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
|
|
182
|
-
});
|
|
183
250
|
}
|
|
184
251
|
|
|
185
252
|
// Check for snake_case vs camelCase mixing in TypeScript/JavaScript
|
|
186
|
-
if (file.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
253
|
+
if (!disabledChecks.has('convention-mix') && file.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
187
254
|
const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
|
|
188
255
|
const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
|
|
189
256
|
|
|
@@ -200,69 +267,73 @@ function analyzeFileNaming(file: string, content: string): NamingIssue[] {
|
|
|
200
267
|
}
|
|
201
268
|
|
|
202
269
|
// Check for unclear boolean names (should start with is/has/should/can)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
270
|
+
if (!disabledChecks.has('unclear')) {
|
|
271
|
+
const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
|
|
272
|
+
for (const match of booleanMatches) {
|
|
273
|
+
const name = match[1];
|
|
274
|
+
if (!name.match(/^(is|has|should|can|will|did)/i)) {
|
|
275
|
+
issues.push({
|
|
276
|
+
file,
|
|
277
|
+
line: lineNumber,
|
|
278
|
+
type: 'unclear',
|
|
279
|
+
identifier: name,
|
|
280
|
+
severity: 'info',
|
|
281
|
+
suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
|
|
282
|
+
});
|
|
283
|
+
}
|
|
215
284
|
}
|
|
216
285
|
}
|
|
217
286
|
|
|
218
287
|
// Check for function names that don't indicate action
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
288
|
+
if (!disabledChecks.has('unclear')) {
|
|
289
|
+
const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
|
|
290
|
+
for (const match of functionMatches) {
|
|
291
|
+
const name = match[1];
|
|
292
|
+
|
|
293
|
+
// Skip JavaScript/TypeScript keywords that shouldn't be function names
|
|
294
|
+
const isKeyword = ['for', 'if', 'else', 'while', 'do', 'switch', 'case', 'break', 'continue', 'return', 'throw', 'try', 'catch', 'finally', 'with', 'yield', 'await'].includes(name);
|
|
295
|
+
if (isKeyword) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Skip common entry point names
|
|
300
|
+
const isEntryPoint = ['main', 'init', 'setup', 'bootstrap'].includes(name);
|
|
301
|
+
if (isEntryPoint) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Functions should typically start with verbs, but allow:
|
|
306
|
+
// 1. Factory/builder patterns (ends with Factory, Builder, etc.)
|
|
307
|
+
// 2. Descriptive compound names that explain what they return
|
|
308
|
+
// 3. Event handlers (onClick, onSubmit, etc.)
|
|
309
|
+
// 4. Descriptive aggregate/collection patterns
|
|
310
|
+
// 5. Very long descriptive names (>15 chars)
|
|
311
|
+
// 6. Compound words with 3+ capitals
|
|
312
|
+
|
|
313
|
+
const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator)$/);
|
|
314
|
+
const isEventHandler = name.match(/^on[A-Z]/);
|
|
315
|
+
const isDescriptiveLong = name.length > 15; // Reduced from 20 to 15
|
|
316
|
+
|
|
317
|
+
// Check for descriptive patterns
|
|
318
|
+
const isDescriptivePattern = name.match(/^(default|total|count|sum|avg|max|min|initial|current|previous|next)\w+/) ||
|
|
319
|
+
name.match(/\w+(Count|Total|Sum|Average|List|Map|Set|Config|Settings|Options|Props)$/);
|
|
320
|
+
|
|
321
|
+
// Count capital letters for compound detection
|
|
322
|
+
const capitalCount = (name.match(/[A-Z]/g) || []).length;
|
|
323
|
+
const isCompoundWord = capitalCount >= 3; // daysSinceLastCommit has 4 capitals
|
|
324
|
+
|
|
325
|
+
const hasActionVerb = name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce|make|do|run|start|stop|build|parse|format|render|calculate|compute|generate|transform|convert|normalize|sanitize|encode|decode|compress|extract|merge|split|join|sort|compare|test|verify|ensure|apply|execute|invoke|call|emit|dispatch|trigger|listen|subscribe|unsubscribe|add|remove|clear|reset|toggle|enable|disable|open|close|connect|disconnect|send|receive|read|write|import|export|register|unregister|mount|unmount)/);
|
|
326
|
+
|
|
327
|
+
if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord) {
|
|
328
|
+
issues.push({
|
|
329
|
+
file,
|
|
330
|
+
line: lineNumber,
|
|
331
|
+
type: 'unclear',
|
|
332
|
+
identifier: name,
|
|
333
|
+
severity: 'info',
|
|
334
|
+
suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
|
|
335
|
+
});
|
|
336
|
+
}
|
|
266
337
|
}
|
|
267
338
|
}
|
|
268
339
|
});
|