eslint-plugin-conventions 4.2.6 → 4.2.7
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/package.json +2 -2
- package/src/index.js +1 -45
- package/src/lib/eslint-plugin-conventions.js +1 -6
- package/src/oxlint.js +1 -3
- package/src/rules/conventions/analytics-event-naming.js +1 -93
- package/src/rules/conventions/consistent-existence-index-check.js +1 -130
- package/src/rules/conventions/expiring-todo-comments.js +1 -283
- package/src/rules/conventions/filename-case.js +1 -234
- package/src/rules/conventions/no-commented-code.js +1 -237
- package/src/rules/conventions/no-console-spaces.js +1 -113
- package/src/rules/conventions/no-json-schema-tags.js +1 -159
- package/src/rules/conventions/no-magic-numbers.js +2 -185
- package/src/rules/conventions/no-raw-cross-property-href.js +1 -76
- package/src/rules/conventions/prefer-code-point.js +1 -80
- package/src/rules/conventions/prefer-dependency-version-strategy.js +1 -201
- package/src/rules/conventions/prefer-dom-node-text-content.js +1 -127
- package/src/rules/conventions/require-data-testid.js +1 -179
- package/src/rules/conventions/utm-taxonomy.js +1 -106
- package/src/rules/deprecation/no-deprecated-api.js +1 -161
|
@@ -1,234 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.filenameCase = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
6
|
-
const eslint_devkit_3 = require("@interlace/eslint-devkit");
|
|
7
|
-
const DEFAULT_ALLOWED_UPPERCASE_FILES = [
|
|
8
|
-
'README',
|
|
9
|
-
'LICENSE',
|
|
10
|
-
'CHANGELOG',
|
|
11
|
-
'CONTRIBUTING',
|
|
12
|
-
'AUTHORS',
|
|
13
|
-
'COPYING',
|
|
14
|
-
'SECURITY',
|
|
15
|
-
'NOTICE',
|
|
16
|
-
'PATENTS',
|
|
17
|
-
'VERSION',
|
|
18
|
-
];
|
|
19
|
-
exports.filenameCase = (0, eslint_devkit_1.createRule)({
|
|
20
|
-
name: 'filename-case',
|
|
21
|
-
meta: {
|
|
22
|
-
type: 'problem',
|
|
23
|
-
docs: {
|
|
24
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/filename-case.md',
|
|
25
|
-
description: 'Enforce filename case conventions for consistency',
|
|
26
|
-
},
|
|
27
|
-
hasSuggestions: true,
|
|
28
|
-
messages: {
|
|
29
|
-
filenameCase: (0, eslint_devkit_2.formatLLMMessage)({
|
|
30
|
-
icon: eslint_devkit_2.MessageIcons.WARNING,
|
|
31
|
-
issueName: 'Filename Case Convention',
|
|
32
|
-
description: 'Filename "{{current}}" violates {{case}} naming convention',
|
|
33
|
-
severity: 'MEDIUM',
|
|
34
|
-
fix: 'Rename file from "{{current}}" to "{{suggested}}" following {{case}} convention',
|
|
35
|
-
documentationLink: 'https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/filename-case.md',
|
|
36
|
-
}),
|
|
37
|
-
},
|
|
38
|
-
schema: [
|
|
39
|
-
{
|
|
40
|
-
type: 'object',
|
|
41
|
-
properties: {
|
|
42
|
-
case: {
|
|
43
|
-
type: 'string',
|
|
44
|
-
enum: ['camelCase', 'kebabCase', 'pascalCase', 'snakeCase'],
|
|
45
|
-
default: 'kebabCase',
|
|
46
|
-
},
|
|
47
|
-
ignore: {
|
|
48
|
-
type: 'array',
|
|
49
|
-
items: {
|
|
50
|
-
anyOf: [
|
|
51
|
-
{ type: 'string' },
|
|
52
|
-
{ type: 'object' },
|
|
53
|
-
],
|
|
54
|
-
},
|
|
55
|
-
default: [],
|
|
56
|
-
},
|
|
57
|
-
allowedUppercaseFiles: {
|
|
58
|
-
type: 'array',
|
|
59
|
-
items: { type: 'string' },
|
|
60
|
-
description: 'List of uppercase filenames to allow (without extension). Defaults to common files like README, LICENSE, etc.',
|
|
61
|
-
},
|
|
62
|
-
allowedKebabCase: {
|
|
63
|
-
type: 'array',
|
|
64
|
-
items: { type: 'string' },
|
|
65
|
-
description: 'List of filenames allowed to use kebab-case regardless of the global case setting.',
|
|
66
|
-
default: [],
|
|
67
|
-
},
|
|
68
|
-
allowedSnakeCase: {
|
|
69
|
-
type: 'array',
|
|
70
|
-
items: { type: 'string' },
|
|
71
|
-
description: 'List of filenames allowed to use snake_case regardless of the global case setting.',
|
|
72
|
-
default: [],
|
|
73
|
-
},
|
|
74
|
-
allowedCamelCase: {
|
|
75
|
-
type: 'array',
|
|
76
|
-
items: { type: 'string' },
|
|
77
|
-
description: 'List of filenames allowed to use camelCase regardless of the global case setting.',
|
|
78
|
-
default: [],
|
|
79
|
-
},
|
|
80
|
-
allowedPascalCase: {
|
|
81
|
-
type: 'array',
|
|
82
|
-
items: { type: 'string' },
|
|
83
|
-
description: 'List of filenames allowed to use PascalCase regardless of the global case setting.',
|
|
84
|
-
default: [],
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
additionalProperties: false,
|
|
88
|
-
},
|
|
89
|
-
],
|
|
90
|
-
},
|
|
91
|
-
defaultOptions: [{
|
|
92
|
-
case: 'kebabCase',
|
|
93
|
-
ignore: [],
|
|
94
|
-
allowedUppercaseFiles: DEFAULT_ALLOWED_UPPERCASE_FILES,
|
|
95
|
-
allowedKebabCase: [],
|
|
96
|
-
allowedSnakeCase: [],
|
|
97
|
-
allowedCamelCase: [],
|
|
98
|
-
allowedPascalCase: [],
|
|
99
|
-
}],
|
|
100
|
-
create(context) {
|
|
101
|
-
const [options] = context.options;
|
|
102
|
-
const { case: caseType = 'kebabCase', ignore = [], allowedUppercaseFiles = DEFAULT_ALLOWED_UPPERCASE_FILES, allowedKebabCase = [], allowedSnakeCase = [], allowedCamelCase = [], allowedPascalCase = [], } = options || {};
|
|
103
|
-
function toCamelCase(str) {
|
|
104
|
-
return str.replace(/[-_](.)/g, (_, letter) => letter.toUpperCase());
|
|
105
|
-
}
|
|
106
|
-
function toKebabCase(str) {
|
|
107
|
-
return str
|
|
108
|
-
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
109
|
-
.replace(/[\s_]+/g, '-')
|
|
110
|
-
.toLowerCase();
|
|
111
|
-
}
|
|
112
|
-
function toPascalCase(str) {
|
|
113
|
-
const camel = toCamelCase(str);
|
|
114
|
-
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
115
|
-
}
|
|
116
|
-
function toSnakeCase(str) {
|
|
117
|
-
return str
|
|
118
|
-
.replace(/([a-z])([A-Z])/g, '$1_$2')
|
|
119
|
-
.replace(/[\s-]+/g, '_')
|
|
120
|
-
.toLowerCase();
|
|
121
|
-
}
|
|
122
|
-
function isCamelCase(str) {
|
|
123
|
-
return /^[a-z][a-zA-Z0-9]*$/.test(str);
|
|
124
|
-
}
|
|
125
|
-
function isKebabCase(str) {
|
|
126
|
-
return /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(str);
|
|
127
|
-
}
|
|
128
|
-
function isPascalCase(str) {
|
|
129
|
-
return /^[A-Z][a-zA-Z0-9]*$/.test(str);
|
|
130
|
-
}
|
|
131
|
-
function isSnakeCase(str) {
|
|
132
|
-
return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(str);
|
|
133
|
-
}
|
|
134
|
-
function matchesCase(str, expectedCase) {
|
|
135
|
-
switch (expectedCase) {
|
|
136
|
-
case 'camelCase':
|
|
137
|
-
return isCamelCase(str);
|
|
138
|
-
case 'kebabCase':
|
|
139
|
-
return isKebabCase(str);
|
|
140
|
-
case 'pascalCase':
|
|
141
|
-
return isPascalCase(str);
|
|
142
|
-
case 'snakeCase':
|
|
143
|
-
return isSnakeCase(str);
|
|
144
|
-
default:
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
function getSuggestedName(basename, nameWithoutExt, targetCase) {
|
|
149
|
-
let transformed = nameWithoutExt;
|
|
150
|
-
switch (targetCase) {
|
|
151
|
-
case 'camelCase':
|
|
152
|
-
transformed = toCamelCase(nameWithoutExt);
|
|
153
|
-
break;
|
|
154
|
-
case 'kebabCase':
|
|
155
|
-
transformed = toKebabCase(nameWithoutExt);
|
|
156
|
-
break;
|
|
157
|
-
case 'pascalCase':
|
|
158
|
-
transformed = toPascalCase(nameWithoutExt);
|
|
159
|
-
break;
|
|
160
|
-
case 'snakeCase':
|
|
161
|
-
transformed = toSnakeCase(nameWithoutExt);
|
|
162
|
-
break;
|
|
163
|
-
}
|
|
164
|
-
const ext = basename.slice(nameWithoutExt.length);
|
|
165
|
-
return transformed + ext;
|
|
166
|
-
}
|
|
167
|
-
return {
|
|
168
|
-
Program(node) {
|
|
169
|
-
const filename = context.filename;
|
|
170
|
-
if (!filename) {
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
const basename = (0, eslint_devkit_3.getBasename)(filename);
|
|
174
|
-
const nameWithoutExt = basename.replace(/(\.(spec|test|stories|story|e2e|d))*\.[^.]+$/, '');
|
|
175
|
-
if (!nameWithoutExt) {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
for (const ignorePattern of ignore) {
|
|
179
|
-
if (typeof ignorePattern === 'string') {
|
|
180
|
-
if (basename === ignorePattern) {
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
else if (ignorePattern instanceof RegExp) {
|
|
185
|
-
if (ignorePattern.test(basename)) {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
if (allowedUppercaseFiles.includes(nameWithoutExt)) {
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
if (basename.startsWith('.')) {
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
if (allowedKebabCase.includes(nameWithoutExt) && matchesCase(nameWithoutExt, 'kebabCase')) {
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
if (allowedSnakeCase.includes(nameWithoutExt) && matchesCase(nameWithoutExt, 'snakeCase')) {
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
if (allowedCamelCase.includes(nameWithoutExt) && matchesCase(nameWithoutExt, 'camelCase')) {
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (allowedPascalCase.includes(nameWithoutExt) && matchesCase(nameWithoutExt, 'pascalCase')) {
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (!matchesCase(nameWithoutExt, caseType)) {
|
|
209
|
-
const suggestedName = getSuggestedName(basename, nameWithoutExt, caseType);
|
|
210
|
-
context.report({
|
|
211
|
-
node,
|
|
212
|
-
messageId: 'filenameCase',
|
|
213
|
-
data: {
|
|
214
|
-
case: caseType,
|
|
215
|
-
current: basename,
|
|
216
|
-
suggested: suggestedName,
|
|
217
|
-
},
|
|
218
|
-
suggest: [
|
|
219
|
-
{
|
|
220
|
-
messageId: 'filenameCase',
|
|
221
|
-
data: {
|
|
222
|
-
suggestedName,
|
|
223
|
-
},
|
|
224
|
-
fix() {
|
|
225
|
-
return null;
|
|
226
|
-
},
|
|
227
|
-
},
|
|
228
|
-
],
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
},
|
|
232
|
-
};
|
|
233
|
-
},
|
|
234
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.filenameCase=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const eslint_devkit_3=require("@interlace/eslint-devkit");const DEFAULT_ALLOWED_UPPERCASE_FILES=["README","LICENSE","CHANGELOG","CONTRIBUTING","AUTHORS","COPYING","SECURITY","NOTICE","PATENTS","VERSION"];exports.filenameCase=(0,eslint_devkit_1.createRule)({name:"filename-case",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/filename-case.md",description:"Enforce filename case conventions for consistency"},hasSuggestions:true,messages:{filenameCase:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Filename Case Convention",description:'Filename "{{current}}" violates {{case}} naming convention',severity:"MEDIUM",fix:'Rename file from "{{current}}" to "{{suggested}}" following {{case}} convention',documentationLink:"https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/filename-case.md"})},schema:[{type:"object",properties:{case:{type:"string",enum:["camelCase","kebabCase","pascalCase","snakeCase"],default:"kebabCase"},ignore:{type:"array",items:{anyOf:[{type:"string"},{type:"object"}]},default:[]},allowedUppercaseFiles:{type:"array",items:{type:"string"},description:"List of uppercase filenames to allow (without extension). Defaults to common files like README, LICENSE, etc."},allowedKebabCase:{type:"array",items:{type:"string"},description:"List of filenames allowed to use kebab-case regardless of the global case setting.",default:[]},allowedSnakeCase:{type:"array",items:{type:"string"},description:"List of filenames allowed to use snake_case regardless of the global case setting.",default:[]},allowedCamelCase:{type:"array",items:{type:"string"},description:"List of filenames allowed to use camelCase regardless of the global case setting.",default:[]},allowedPascalCase:{type:"array",items:{type:"string"},description:"List of filenames allowed to use PascalCase regardless of the global case setting.",default:[]}},additionalProperties:false}]},defaultOptions:[{case:"kebabCase",ignore:[],allowedUppercaseFiles:DEFAULT_ALLOWED_UPPERCASE_FILES,allowedKebabCase:[],allowedSnakeCase:[],allowedCamelCase:[],allowedPascalCase:[]}],create(context){const[options]=context.options;const{case:caseType="kebabCase",ignore=[],allowedUppercaseFiles=DEFAULT_ALLOWED_UPPERCASE_FILES,allowedKebabCase=[],allowedSnakeCase=[],allowedCamelCase=[],allowedPascalCase=[]}=options||{};function toCamelCase(str){return str.replace(/[-_](.)/g,(_,letter)=>letter.toUpperCase())}function toKebabCase(str){return str.replace(/([a-z])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase()}function toPascalCase(str){const camel=toCamelCase(str);return camel.charAt(0).toUpperCase()+camel.slice(1)}function toSnakeCase(str){return str.replace(/([a-z])([A-Z])/g,"$1_$2").replace(/[\s-]+/g,"_").toLowerCase()}function isCamelCase(str){return/^[a-z][a-zA-Z0-9]*$/.test(str)}function isKebabCase(str){return/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(str)}function isPascalCase(str){return/^[A-Z][a-zA-Z0-9]*$/.test(str)}function isSnakeCase(str){return/^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(str)}function matchesCase(str,expectedCase){switch(expectedCase){case"camelCase":return isCamelCase(str);case"kebabCase":return isKebabCase(str);case"pascalCase":return isPascalCase(str);case"snakeCase":return isSnakeCase(str);default:return true}}function getSuggestedName(basename,nameWithoutExt,targetCase){let transformed=nameWithoutExt;switch(targetCase){case"camelCase":transformed=toCamelCase(nameWithoutExt);break;case"kebabCase":transformed=toKebabCase(nameWithoutExt);break;case"pascalCase":transformed=toPascalCase(nameWithoutExt);break;case"snakeCase":transformed=toSnakeCase(nameWithoutExt);break}const ext=basename.slice(nameWithoutExt.length);return transformed+ext}return{Program(node){const filename=context.filename;if(!filename){return}const basename=(0,eslint_devkit_3.getBasename)(filename);const nameWithoutExt=basename.replace(/(\.(spec|test|stories|story|e2e|d))*\.[^.]+$/,"");if(!nameWithoutExt){return}for(const ignorePattern of ignore){if(typeof ignorePattern==="string"){if(basename===ignorePattern){return}}else if(ignorePattern instanceof RegExp){if(ignorePattern.test(basename)){return}}}if(allowedUppercaseFiles.includes(nameWithoutExt)){return}if(basename.startsWith(".")){return}if(allowedKebabCase.includes(nameWithoutExt)&&matchesCase(nameWithoutExt,"kebabCase")){return}if(allowedSnakeCase.includes(nameWithoutExt)&&matchesCase(nameWithoutExt,"snakeCase")){return}if(allowedCamelCase.includes(nameWithoutExt)&&matchesCase(nameWithoutExt,"camelCase")){return}if(allowedPascalCase.includes(nameWithoutExt)&&matchesCase(nameWithoutExt,"pascalCase")){return}if(!matchesCase(nameWithoutExt,caseType)){const suggestedName=getSuggestedName(basename,nameWithoutExt,caseType);context.report({node,messageId:"filenameCase",data:{case:caseType,current:basename,suggested:suggestedName},suggest:[{messageId:"filenameCase",data:{suggestedName},fix(){return null}}]})}}}}});
|
|
@@ -1,237 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.noCommentedCode = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
6
|
-
function looksLikeCode(comment, isBlockComment) {
|
|
7
|
-
let text = comment;
|
|
8
|
-
if (isBlockComment) {
|
|
9
|
-
text = text.replace(/^\s*\/\*+/, '').replace(/\*+\/\s*$/, '');
|
|
10
|
-
text = text
|
|
11
|
-
.split('\n')
|
|
12
|
-
.map((line) => line.replace(/^\s*\*+\s*/, ''))
|
|
13
|
-
.join('\n');
|
|
14
|
-
}
|
|
15
|
-
else {
|
|
16
|
-
text = text
|
|
17
|
-
.split('\n')
|
|
18
|
-
.map((line) => line.replace(/^\s*\/\/+\s*/, ''))
|
|
19
|
-
.join('\n');
|
|
20
|
-
}
|
|
21
|
-
const lines = text
|
|
22
|
-
.split('\n')
|
|
23
|
-
.map((line) => line.trim())
|
|
24
|
-
.filter((line) => line.length > 0);
|
|
25
|
-
if (lines.length === 0) {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
const codePatterns = [
|
|
29
|
-
/^(const|let|var|function|class|if|for|while|return|import|export)\s+/,
|
|
30
|
-
/^[a-zA-Z_$][a-zA-Z0-9_$]*\s*[=:]\s*/,
|
|
31
|
-
/^[a-zA-Z_$][a-zA-Z0-9_$]*\s*\(/,
|
|
32
|
-
/^[{}[\]]/,
|
|
33
|
-
];
|
|
34
|
-
let codeLikeLines = 0;
|
|
35
|
-
for (const line of lines) {
|
|
36
|
-
if (/^(TODO|FIXME|HACK|XXX)/i.test(line)) {
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
for (const pattern of codePatterns) {
|
|
40
|
-
if (pattern.test(line)) {
|
|
41
|
-
codeLikeLines++;
|
|
42
|
-
break;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return codeLikeLines > 0;
|
|
47
|
-
}
|
|
48
|
-
function countCommentLines(comment) {
|
|
49
|
-
if (!comment)
|
|
50
|
-
return 0;
|
|
51
|
-
const newlineCount = (comment.match(/\n/g) || []).length;
|
|
52
|
-
return newlineCount > 0 ? newlineCount + 1 : 1;
|
|
53
|
-
}
|
|
54
|
-
exports.noCommentedCode = (0, eslint_devkit_2.createRule)({
|
|
55
|
-
name: 'no-commented-code',
|
|
56
|
-
meta: {
|
|
57
|
-
type: 'suggestion',
|
|
58
|
-
docs: {
|
|
59
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/no-commented-code.md',
|
|
60
|
-
description: 'Detects commented-out code blocks',
|
|
61
|
-
},
|
|
62
|
-
hasSuggestions: true,
|
|
63
|
-
messages: {
|
|
64
|
-
commentedCode: (0, eslint_devkit_1.formatLLMMessage)({
|
|
65
|
-
icon: eslint_devkit_1.MessageIcons.WARNING,
|
|
66
|
-
issueName: 'Commented code',
|
|
67
|
-
description: 'Commented-out code detected ({{lines}} lines)',
|
|
68
|
-
severity: 'LOW',
|
|
69
|
-
fix: 'Remove commented code or use version control for history',
|
|
70
|
-
documentationLink: 'https://rules.sonarsource.com/javascript/RSPEC-125/',
|
|
71
|
-
}),
|
|
72
|
-
removeCode: (0, eslint_devkit_1.formatLLMMessage)({
|
|
73
|
-
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
74
|
-
issueName: 'Remove Code',
|
|
75
|
-
description: 'Remove commented code block',
|
|
76
|
-
severity: 'LOW',
|
|
77
|
-
fix: 'Delete the commented code block',
|
|
78
|
-
documentationLink: 'https://rules.sonarsource.com/javascript/RSPEC-125/',
|
|
79
|
-
}),
|
|
80
|
-
useVersionControl: (0, eslint_devkit_1.formatLLMMessage)({
|
|
81
|
-
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
82
|
-
issueName: 'Use Git',
|
|
83
|
-
description: 'Use version control for code history',
|
|
84
|
-
severity: 'LOW',
|
|
85
|
-
fix: 'Delete commented code and use git history instead',
|
|
86
|
-
documentationLink: 'https://git-scm.com/docs/git-log',
|
|
87
|
-
}),
|
|
88
|
-
},
|
|
89
|
-
schema: [
|
|
90
|
-
{
|
|
91
|
-
type: 'object',
|
|
92
|
-
properties: {
|
|
93
|
-
ignoreSingleLine: {
|
|
94
|
-
type: 'boolean',
|
|
95
|
-
default: false,
|
|
96
|
-
description: 'Ignore single-line comments',
|
|
97
|
-
},
|
|
98
|
-
ignoreInTests: {
|
|
99
|
-
type: 'boolean',
|
|
100
|
-
default: true,
|
|
101
|
-
description: 'Ignore comments in test files',
|
|
102
|
-
},
|
|
103
|
-
minLines: {
|
|
104
|
-
type: 'number',
|
|
105
|
-
default: 1,
|
|
106
|
-
minimum: 1,
|
|
107
|
-
description: 'Minimum lines of commented code to trigger',
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
additionalProperties: false,
|
|
111
|
-
},
|
|
112
|
-
],
|
|
113
|
-
},
|
|
114
|
-
defaultOptions: [
|
|
115
|
-
{
|
|
116
|
-
ignoreSingleLine: false,
|
|
117
|
-
ignoreInTests: true,
|
|
118
|
-
minLines: 1,
|
|
119
|
-
},
|
|
120
|
-
],
|
|
121
|
-
create(context, [options = {}]) {
|
|
122
|
-
const { ignoreSingleLine = false, ignoreInTests = true, minLines = 1, } = options || {};
|
|
123
|
-
const filename = context.filename;
|
|
124
|
-
const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
|
|
125
|
-
if (isTestFile) {
|
|
126
|
-
return {};
|
|
127
|
-
}
|
|
128
|
-
const sourceCode = context.sourceCode;
|
|
129
|
-
function checkComment(node) {
|
|
130
|
-
try {
|
|
131
|
-
const commentText = node.value || '';
|
|
132
|
-
const isBlockComment = node.type === 'Block';
|
|
133
|
-
const lines = countCommentLines(commentText);
|
|
134
|
-
if (ignoreSingleLine && lines === 1 && !isBlockComment) {
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
if (lines < minLines) {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
if (looksLikeCode(commentText, isBlockComment)) {
|
|
141
|
-
context.report({
|
|
142
|
-
node,
|
|
143
|
-
messageId: 'commentedCode',
|
|
144
|
-
data: {
|
|
145
|
-
lines: String(lines),
|
|
146
|
-
},
|
|
147
|
-
suggest: [
|
|
148
|
-
{
|
|
149
|
-
messageId: 'removeCode',
|
|
150
|
-
fix: (fixer) => {
|
|
151
|
-
try {
|
|
152
|
-
return fixer.remove(node);
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
messageId: 'useVersionControl',
|
|
161
|
-
fix: () => null,
|
|
162
|
-
},
|
|
163
|
-
],
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
catch {
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return {
|
|
172
|
-
Program() {
|
|
173
|
-
const comments = sourceCode.getAllComments();
|
|
174
|
-
const groupedComments = [];
|
|
175
|
-
let currentGroup = [];
|
|
176
|
-
for (let i = 0; i < comments.length; i++) {
|
|
177
|
-
const comment = comments[i];
|
|
178
|
-
const commentText = comment.value || '';
|
|
179
|
-
const isBlockComment = comment.type === 'Block';
|
|
180
|
-
if (looksLikeCode(commentText, isBlockComment)) {
|
|
181
|
-
currentGroup.push(comment);
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
if (currentGroup.length > 0) {
|
|
185
|
-
groupedComments.push([...currentGroup]);
|
|
186
|
-
currentGroup = [];
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
if (currentGroup.length > 0) {
|
|
191
|
-
groupedComments.push(currentGroup);
|
|
192
|
-
}
|
|
193
|
-
groupedComments.forEach((group) => {
|
|
194
|
-
if (group.length === 1) {
|
|
195
|
-
checkComment(group[0]);
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
const firstComment = group[0];
|
|
199
|
-
const totalLines = group.reduce((sum, comment) => {
|
|
200
|
-
const commentText = comment.value || '';
|
|
201
|
-
return sum + countCommentLines(commentText);
|
|
202
|
-
}, 0);
|
|
203
|
-
if (totalLines >= minLines) {
|
|
204
|
-
context.report({
|
|
205
|
-
node: firstComment,
|
|
206
|
-
messageId: 'commentedCode',
|
|
207
|
-
data: {
|
|
208
|
-
lines: String(totalLines),
|
|
209
|
-
},
|
|
210
|
-
suggest: [
|
|
211
|
-
{
|
|
212
|
-
messageId: 'removeCode',
|
|
213
|
-
fix: (fixer) => {
|
|
214
|
-
try {
|
|
215
|
-
return fixer.removeRange([
|
|
216
|
-
group[0].range[0],
|
|
217
|
-
group[group.length - 1].range[1],
|
|
218
|
-
]);
|
|
219
|
-
}
|
|
220
|
-
catch {
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
},
|
|
224
|
-
},
|
|
225
|
-
{
|
|
226
|
-
messageId: 'useVersionControl',
|
|
227
|
-
fix: () => null,
|
|
228
|
-
},
|
|
229
|
-
],
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
},
|
|
235
|
-
};
|
|
236
|
-
},
|
|
237
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noCommentedCode=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");function looksLikeCode(comment,isBlockComment){let text=comment;if(isBlockComment){text=text.replace(/^\s*\/\*+/,"").replace(/\*+\/\s*$/,"");text=text.split("\n").map(line=>line.replace(/^\s*\*+\s*/,"")).join("\n")}else{text=text.split("\n").map(line=>line.replace(/^\s*\/\/+\s*/,"")).join("\n")}const lines=text.split("\n").map(line=>line.trim()).filter(line=>line.length>0);if(lines.length===0){return false}const codePatterns=[/^(const|let|var|function|class|if|for|while|return|import|export)\s+/,/^[a-zA-Z_$][a-zA-Z0-9_$]*\s*[=:]\s*/,/^[a-zA-Z_$][a-zA-Z0-9_$]*\s*\(/,/^[{}[\]]/];let codeLikeLines=0;for(const line of lines){if(/^(TODO|FIXME|HACK|XXX)/i.test(line)){continue}for(const pattern of codePatterns){if(pattern.test(line)){codeLikeLines++;break}}}return codeLikeLines>0}function countCommentLines(comment){if(!comment)return 0;const newlineCount=(comment.match(/\n/g)||[]).length;return newlineCount>0?newlineCount+1:1}exports.noCommentedCode=(0,eslint_devkit_2.createRule)({name:"no-commented-code",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/no-commented-code.md",description:"Detects commented-out code blocks"},hasSuggestions:true,messages:{commentedCode:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Commented code",description:"Commented-out code detected ({{lines}} lines)",severity:"LOW",fix:"Remove commented code or use version control for history",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-125/"}),removeCode:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Remove Code",description:"Remove commented code block",severity:"LOW",fix:"Delete the commented code block",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-125/"}),useVersionControl:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Git",description:"Use version control for code history",severity:"LOW",fix:"Delete commented code and use git history instead",documentationLink:"https://git-scm.com/docs/git-log"})},schema:[{type:"object",properties:{ignoreSingleLine:{type:"boolean",default:false,description:"Ignore single-line comments"},ignoreInTests:{type:"boolean",default:true,description:"Ignore comments in test files"},minLines:{type:"number",default:1,minimum:1,description:"Minimum lines of commented code to trigger"}},additionalProperties:false}]},defaultOptions:[{ignoreSingleLine:false,ignoreInTests:true,minLines:1}],create(context,[options={}]){const{ignoreSingleLine=false,ignoreInTests=true,minLines=1}=options||{};const filename=context.filename;const isTestFile=ignoreInTests&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);if(isTestFile){return{}}const sourceCode=context.sourceCode;function checkComment(node){try{const commentText=node.value||"";const isBlockComment=node.type==="Block";const lines=countCommentLines(commentText);if(ignoreSingleLine&&lines===1&&!isBlockComment){return}if(lines<minLines){return}if(looksLikeCode(commentText,isBlockComment)){context.report({node,messageId:"commentedCode",data:{lines:String(lines)},suggest:[{messageId:"removeCode",fix:fixer=>{try{return fixer.remove(node)}catch{return null}}},{messageId:"useVersionControl",fix:()=>null}]})}}catch{return}}return{Program(){const comments=sourceCode.getAllComments();const groupedComments=[];let currentGroup=[];for(let i=0;i<comments.length;i++){const comment=comments[i];const commentText=comment.value||"";const isBlockComment=comment.type==="Block";if(looksLikeCode(commentText,isBlockComment)){currentGroup.push(comment)}else{if(currentGroup.length>0){groupedComments.push([...currentGroup]);currentGroup=[]}}}if(currentGroup.length>0){groupedComments.push(currentGroup)}groupedComments.forEach(group=>{if(group.length===1){checkComment(group[0])}else{const firstComment=group[0];const totalLines=group.reduce((sum,comment)=>{const commentText=comment.value||"";return sum+countCommentLines(commentText)},0);if(totalLines>=minLines){context.report({node:firstComment,messageId:"commentedCode",data:{lines:String(totalLines)},suggest:[{messageId:"removeCode",fix:fixer=>{try{return fixer.removeRange([group[0].range[0],group[group.length-1].range[1]])}catch{return null}}},{messageId:"useVersionControl",fix:()=>null}]})}}})}}}});
|
|
@@ -1,113 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.noConsoleSpaces = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
6
|
-
exports.noConsoleSpaces = (0, eslint_devkit_1.createRule)({
|
|
7
|
-
name: 'no-console-spaces',
|
|
8
|
-
meta: {
|
|
9
|
-
type: 'problem',
|
|
10
|
-
docs: {
|
|
11
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/no-console-spaces.md',
|
|
12
|
-
description: 'Prevent leading/trailing space between console.log parameters',
|
|
13
|
-
},
|
|
14
|
-
fixable: 'code',
|
|
15
|
-
hasSuggestions: true,
|
|
16
|
-
messages: {
|
|
17
|
-
noConsoleSpaces: (0, eslint_devkit_2.formatLLMMessage)({
|
|
18
|
-
icon: eslint_devkit_2.MessageIcons.WARNING,
|
|
19
|
-
issueName: 'Console Spaces',
|
|
20
|
-
description: 'Remove leading/trailing spaces in console method parameters',
|
|
21
|
-
severity: 'MEDIUM',
|
|
22
|
-
fix: 'Remove spaces from console method arguments',
|
|
23
|
-
documentationLink: 'https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-console-spaces.md',
|
|
24
|
-
}),
|
|
25
|
-
},
|
|
26
|
-
schema: [],
|
|
27
|
-
},
|
|
28
|
-
defaultOptions: [],
|
|
29
|
-
create(context) {
|
|
30
|
-
const consoleMethods = new Set([
|
|
31
|
-
'log',
|
|
32
|
-
'debug',
|
|
33
|
-
'info',
|
|
34
|
-
'warn',
|
|
35
|
-
'error',
|
|
36
|
-
'table',
|
|
37
|
-
'trace',
|
|
38
|
-
'group',
|
|
39
|
-
'groupCollapsed',
|
|
40
|
-
]);
|
|
41
|
-
function isInAllowedContext() {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
function isConsoleMethodCall(node) {
|
|
45
|
-
if (node.callee.type === 'MemberExpression' &&
|
|
46
|
-
node.callee.object.type === 'Identifier' &&
|
|
47
|
-
node.callee.object.name === 'console' &&
|
|
48
|
-
node.callee.property.type === 'Identifier' &&
|
|
49
|
-
consoleMethods.has(node.callee.property.name)) {
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
function getConsoleMethodName(node) {
|
|
55
|
-
if (node.callee.type === 'MemberExpression' &&
|
|
56
|
-
node.callee.property.type === 'Identifier') {
|
|
57
|
-
return node.callee.property.name;
|
|
58
|
-
}
|
|
59
|
-
return 'console';
|
|
60
|
-
}
|
|
61
|
-
function hasLeadingOrTrailingSpaces(text) {
|
|
62
|
-
const trimmed = text.trim();
|
|
63
|
-
return trimmed.length > 0 && /^\s|\s$/.test(text);
|
|
64
|
-
}
|
|
65
|
-
function hasLeadingOrTrailingSpacesInTemplate(text) {
|
|
66
|
-
return /^\s|\s$/.test(text);
|
|
67
|
-
}
|
|
68
|
-
return {
|
|
69
|
-
CallExpression(node) {
|
|
70
|
-
if (isConsoleMethodCall(node) && !isInAllowedContext()) {
|
|
71
|
-
for (const arg of node.arguments) {
|
|
72
|
-
if (arg.type === 'Literal' && typeof arg.value === 'string') {
|
|
73
|
-
if (hasLeadingOrTrailingSpaces(arg.value)) {
|
|
74
|
-
context.report({
|
|
75
|
-
node: arg,
|
|
76
|
-
messageId: 'noConsoleSpaces',
|
|
77
|
-
data: {
|
|
78
|
-
method: getConsoleMethodName(node),
|
|
79
|
-
arg: arg.value,
|
|
80
|
-
},
|
|
81
|
-
fix(fixer) {
|
|
82
|
-
const trimmed = arg.value.trim();
|
|
83
|
-
return fixer.replaceText(arg, `'${trimmed}'`);
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
else if (arg.type === 'TemplateLiteral') {
|
|
89
|
-
let hasSpacesInTemplate = false;
|
|
90
|
-
for (const quasi of arg.quasis) {
|
|
91
|
-
if (hasLeadingOrTrailingSpacesInTemplate(quasi.value.raw)) {
|
|
92
|
-
hasSpacesInTemplate = true;
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
if (hasSpacesInTemplate) {
|
|
97
|
-
context.report({
|
|
98
|
-
node: arg,
|
|
99
|
-
messageId: 'noConsoleSpaces',
|
|
100
|
-
data: {
|
|
101
|
-
method: getConsoleMethodName(node),
|
|
102
|
-
arg: 'template literal with spaces',
|
|
103
|
-
},
|
|
104
|
-
fix: null,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
},
|
|
113
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noConsoleSpaces=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.noConsoleSpaces=(0,eslint_devkit_1.createRule)({name:"no-console-spaces",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/no-console-spaces.md",description:"Prevent leading/trailing space between console.log parameters"},fixable:"code",hasSuggestions:true,messages:{noConsoleSpaces:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Console Spaces",description:"Remove leading/trailing spaces in console method parameters",severity:"MEDIUM",fix:"Remove spaces from console method arguments",documentationLink:"https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-console-spaces.md"})},schema:[]},defaultOptions:[],create(context){const consoleMethods=new Set(["log","debug","info","warn","error","table","trace","group","groupCollapsed"]);function isInAllowedContext(){return false}function isConsoleMethodCall(node){if(node.callee.type==="MemberExpression"&&node.callee.object.type==="Identifier"&&node.callee.object.name==="console"&&node.callee.property.type==="Identifier"&&consoleMethods.has(node.callee.property.name)){return true}return false}function getConsoleMethodName(node){if(node.callee.type==="MemberExpression"&&node.callee.property.type==="Identifier"){return node.callee.property.name}return"console"}function hasLeadingOrTrailingSpaces(text){const trimmed=text.trim();return trimmed.length>0&&/^\s|\s$/.test(text)}function hasLeadingOrTrailingSpacesInTemplate(text){return/^\s|\s$/.test(text)}return{CallExpression(node){if(isConsoleMethodCall(node)&&!isInAllowedContext()){for(const arg of node.arguments){if(arg.type==="Literal"&&typeof arg.value==="string"){if(hasLeadingOrTrailingSpaces(arg.value)){context.report({node:arg,messageId:"noConsoleSpaces",data:{method:getConsoleMethodName(node),arg:arg.value},fix(fixer){const trimmed=arg.value.trim();return fixer.replaceText(arg,`'${trimmed}'`)}})}}else if(arg.type==="TemplateLiteral"){let hasSpacesInTemplate=false;for(const quasi of arg.quasis){if(hasLeadingOrTrailingSpacesInTemplate(quasi.value.raw)){hasSpacesInTemplate=true;break}}if(hasSpacesInTemplate){context.report({node:arg,messageId:"noConsoleSpaces",data:{method:getConsoleMethodName(node),arg:"template literal with spaces"},fix:null})}}}}}}}});
|