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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-conventions",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.7",
|
|
4
4
|
"description": "ESLint plugin for team code conventions — enforces filename case, magic-number bans, commented-out code, expiring TODOs, and deprecated-API usage.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"node": ">=18.0.0"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@interlace/eslint-devkit": "^1.
|
|
53
|
+
"@interlace/eslint-devkit": "^1.10.0"
|
|
54
54
|
},
|
|
55
55
|
"repository": {
|
|
56
56
|
"type": "git",
|
package/src/index.js
CHANGED
|
@@ -1,45 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.configs = exports.plugin = exports.rules = void 0;
|
|
4
|
-
exports.rules = {
|
|
5
|
-
get 'no-commented-code'() { return require("./rules/conventions/no-commented-code").noCommentedCode; },
|
|
6
|
-
get 'expiring-todo-comments'() { return require("./rules/conventions/expiring-todo-comments").expiringTodoComments; },
|
|
7
|
-
get 'prefer-code-point'() { return require("./rules/conventions/prefer-code-point").preferCodePoint; },
|
|
8
|
-
get 'prefer-dom-node-text-content'() { return require("./rules/conventions/prefer-dom-node-text-content").preferDomNodeTextContent; },
|
|
9
|
-
get 'no-console-spaces'() { return require("./rules/conventions/no-console-spaces").noConsoleSpaces; },
|
|
10
|
-
get 'no-deprecated-api'() { return require("./rules/deprecation/no-deprecated-api").noDeprecatedApi; },
|
|
11
|
-
get 'prefer-dependency-version-strategy'() { return require("./rules/conventions/prefer-dependency-version-strategy").preferDependencyVersionStrategy; },
|
|
12
|
-
get 'filename-case'() { return require("./rules/conventions/filename-case").filenameCase; },
|
|
13
|
-
get 'consistent-existence-index-check'() { return require("./rules/conventions/consistent-existence-index-check").consistentExistenceIndexCheck; },
|
|
14
|
-
get 'no-json-schema-tags'() { return require("./rules/conventions/no-json-schema-tags").noJsonSchemaTags; },
|
|
15
|
-
get 'require-data-testid'() { return require("./rules/conventions/require-data-testid").requireDataTestId; },
|
|
16
|
-
get 'utm-taxonomy'() { return require("./rules/conventions/utm-taxonomy").utmTaxonomy; },
|
|
17
|
-
get 'no-raw-cross-property-href'() { return require("./rules/conventions/no-raw-cross-property-href").noRawCrossPropertyHref; },
|
|
18
|
-
get 'analytics-event-naming'() { return require("./rules/conventions/analytics-event-naming").analyticsEventNaming; },
|
|
19
|
-
get 'no-magic-numbers'() { return require("./rules/conventions/no-magic-numbers").noMagicNumbers; },
|
|
20
|
-
|
|
21
|
-
};
|
|
22
|
-
exports.plugin = {
|
|
23
|
-
meta: {
|
|
24
|
-
name: 'eslint-plugin-conventions',
|
|
25
|
-
version: '4.2.6',
|
|
26
|
-
},
|
|
27
|
-
rules: exports.rules,
|
|
28
|
-
};
|
|
29
|
-
exports.configs = {
|
|
30
|
-
recommended: {
|
|
31
|
-
plugins: {
|
|
32
|
-
conventions: exports.plugin,
|
|
33
|
-
},
|
|
34
|
-
rules: {
|
|
35
|
-
'conventions/no-commented-code': 'warn',
|
|
36
|
-
'conventions/expiring-todo-comments': 'warn',
|
|
37
|
-
'conventions/no-deprecated-api': 'warn',
|
|
38
|
-
'conventions/utm-taxonomy': 'error',
|
|
39
|
-
'conventions/no-raw-cross-property-href': 'error',
|
|
40
|
-
'conventions/analytics-event-naming': 'error',
|
|
41
|
-
'conventions/no-magic-numbers': 'warn',
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
exports.default = exports.plugin;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.configs=exports.plugin=exports.rules=void 0;exports.rules={get"no-commented-code"(){return require("./rules/conventions/no-commented-code").noCommentedCode},get"expiring-todo-comments"(){return require("./rules/conventions/expiring-todo-comments").expiringTodoComments},get"prefer-code-point"(){return require("./rules/conventions/prefer-code-point").preferCodePoint},get"prefer-dom-node-text-content"(){return require("./rules/conventions/prefer-dom-node-text-content").preferDomNodeTextContent},get"no-console-spaces"(){return require("./rules/conventions/no-console-spaces").noConsoleSpaces},get"no-deprecated-api"(){return require("./rules/deprecation/no-deprecated-api").noDeprecatedApi},get"prefer-dependency-version-strategy"(){return require("./rules/conventions/prefer-dependency-version-strategy").preferDependencyVersionStrategy},get"filename-case"(){return require("./rules/conventions/filename-case").filenameCase},get"consistent-existence-index-check"(){return require("./rules/conventions/consistent-existence-index-check").consistentExistenceIndexCheck},get"no-json-schema-tags"(){return require("./rules/conventions/no-json-schema-tags").noJsonSchemaTags},get"require-data-testid"(){return require("./rules/conventions/require-data-testid").requireDataTestId},get"utm-taxonomy"(){return require("./rules/conventions/utm-taxonomy").utmTaxonomy},get"no-raw-cross-property-href"(){return require("./rules/conventions/no-raw-cross-property-href").noRawCrossPropertyHref},get"analytics-event-naming"(){return require("./rules/conventions/analytics-event-naming").analyticsEventNaming},get"no-magic-numbers"(){return require("./rules/conventions/no-magic-numbers").noMagicNumbers}};exports.plugin={meta:{name:"eslint-plugin-conventions",version:"4.2.7"},rules:exports.rules};exports.configs={recommended:{plugins:{conventions:exports.plugin},rules:{"conventions/no-commented-code":"warn","conventions/expiring-todo-comments":"warn","conventions/no-deprecated-api":"warn","conventions/utm-taxonomy":"error","conventions/no-raw-cross-property-href":"error","conventions/analytics-event-naming":"error","conventions/no-magic-numbers":"warn"}}};exports.default=exports.plugin;
|
|
@@ -1,6 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.eslintPluginConventions = eslintPluginConventions;
|
|
4
|
-
function eslintPluginConventions() {
|
|
5
|
-
return 'eslint-plugin-conventions';
|
|
6
|
-
}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.eslintPluginConventions=eslintPluginConventions;function eslintPluginConventions(){return"eslint-plugin-conventions"}
|
package/src/oxlint.js
CHANGED
|
@@ -1,93 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.analyticsEventNaming = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
6
|
-
const ACTION_VERBS = [
|
|
7
|
-
'click',
|
|
8
|
-
'submit',
|
|
9
|
-
'view',
|
|
10
|
-
'add',
|
|
11
|
-
'remove',
|
|
12
|
-
'start',
|
|
13
|
-
'end',
|
|
14
|
-
'generate',
|
|
15
|
-
'send',
|
|
16
|
-
'cancel',
|
|
17
|
-
'fail',
|
|
18
|
-
'create',
|
|
19
|
-
'delete',
|
|
20
|
-
'update',
|
|
21
|
-
'invite',
|
|
22
|
-
].join('|');
|
|
23
|
-
const EVENT_NAME_RE = new RegExp(`^[a-z][a-z0-9_]*:[a-z][a-z0-9_]*_(${ACTION_VERBS})$|^\\$[a-z_]+$`);
|
|
24
|
-
exports.analyticsEventNaming = (0, eslint_devkit_1.createRule)({
|
|
25
|
-
name: 'analytics-event-naming',
|
|
26
|
-
meta: {
|
|
27
|
-
type: 'problem',
|
|
28
|
-
docs: {
|
|
29
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/analytics-event-naming.md',
|
|
30
|
-
description: 'Analytics event names (track/capture) must follow ANALYTICS_PHILOSOPHY.md principle 4: lowercase snake_case, category:object_action, fixed verb list',
|
|
31
|
-
},
|
|
32
|
-
fixable: undefined,
|
|
33
|
-
hasSuggestions: false,
|
|
34
|
-
messages: {
|
|
35
|
-
invalidEventName: (0, eslint_devkit_2.formatLLMMessage)({
|
|
36
|
-
icon: eslint_devkit_2.MessageIcons.WARNING,
|
|
37
|
-
issueName: 'Invalid analytics event name',
|
|
38
|
-
description: 'Event "{{name}}" does not match category:object_action grammar (lowercase snake_case, action from fixed verb list).',
|
|
39
|
-
severity: 'HIGH',
|
|
40
|
-
fix: 'Rename to match `<category>:<object>_<verb>` where verb ∈ {click, submit, view, add, remove, start, end, generate, send, cancel, fail, create, delete, update, invite}. Vendor reserved events ($pageview, $set, …) are exempt.',
|
|
41
|
-
documentationLink: 'https://github.com/ofri-peretz/eslint/blob/main/ANALYTICS_PHILOSOPHY.md',
|
|
42
|
-
}),
|
|
43
|
-
interpolatedEventName: (0, eslint_devkit_2.formatLLMMessage)({
|
|
44
|
-
icon: eslint_devkit_2.MessageIcons.WARNING,
|
|
45
|
-
issueName: 'Interpolated analytics event name',
|
|
46
|
-
description: 'Event names must be static string literals. Template literals create an unbounded catalog (ANALYTICS_PHILOSOPHY.md principle 3).',
|
|
47
|
-
severity: 'HIGH',
|
|
48
|
-
fix: 'Move the dynamic part into an event property: track(\'category:object_view\', { slug }).',
|
|
49
|
-
documentationLink: 'https://github.com/ofri-peretz/eslint/blob/main/ANALYTICS_PHILOSOPHY.md',
|
|
50
|
-
}),
|
|
51
|
-
},
|
|
52
|
-
schema: [],
|
|
53
|
-
},
|
|
54
|
-
defaultOptions: [],
|
|
55
|
-
create(context) {
|
|
56
|
-
function isCaptureLike(node) {
|
|
57
|
-
if (node.callee.type === 'MemberExpression' &&
|
|
58
|
-
node.callee.property.type === 'Identifier' &&
|
|
59
|
-
(node.callee.property.name === 'capture' ||
|
|
60
|
-
node.callee.property.name === 'track')) {
|
|
61
|
-
return true;
|
|
62
|
-
}
|
|
63
|
-
if (node.callee.type === 'Identifier' &&
|
|
64
|
-
node.callee.name === 'track') {
|
|
65
|
-
return true;
|
|
66
|
-
}
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
return {
|
|
70
|
-
CallExpression(node) {
|
|
71
|
-
if (!isCaptureLike(node) || node.arguments.length === 0)
|
|
72
|
-
return;
|
|
73
|
-
const first = node.arguments[0];
|
|
74
|
-
if (first.type === 'Literal' && typeof first.value === 'string') {
|
|
75
|
-
if (!EVENT_NAME_RE.test(first.value)) {
|
|
76
|
-
context.report({
|
|
77
|
-
node: first,
|
|
78
|
-
messageId: 'invalidEventName',
|
|
79
|
-
data: { name: first.value },
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
if (first.type === 'TemplateLiteral' && first.expressions.length > 0) {
|
|
85
|
-
context.report({
|
|
86
|
-
node: first,
|
|
87
|
-
messageId: 'interpolatedEventName',
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
},
|
|
93
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.analyticsEventNaming=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const ACTION_VERBS=["click","submit","view","add","remove","start","end","generate","send","cancel","fail","create","delete","update","invite"].join("|");const EVENT_NAME_RE=new RegExp(`^[a-z][a-z0-9_]*:[a-z][a-z0-9_]*_(${ACTION_VERBS})$|^\\$[a-z_]+$`);exports.analyticsEventNaming=(0,eslint_devkit_1.createRule)({name:"analytics-event-naming",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/analytics-event-naming.md",description:"Analytics event names (track/capture) must follow ANALYTICS_PHILOSOPHY.md principle 4: lowercase snake_case, category:object_action, fixed verb list"},fixable:void 0,hasSuggestions:false,messages:{invalidEventName:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Invalid analytics event name",description:'Event "{{name}}" does not match category:object_action grammar (lowercase snake_case, action from fixed verb list).',severity:"HIGH",fix:"Rename to match `<category>:<object>_<verb>` where verb \u2208 {click, submit, view, add, remove, start, end, generate, send, cancel, fail, create, delete, update, invite}. Vendor reserved events ($pageview, $set, \u2026) are exempt.",documentationLink:"https://github.com/ofri-peretz/eslint/blob/main/ANALYTICS_PHILOSOPHY.md"}),interpolatedEventName:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Interpolated analytics event name",description:"Event names must be static string literals. Template literals create an unbounded catalog (ANALYTICS_PHILOSOPHY.md principle 3).",severity:"HIGH",fix:"Move the dynamic part into an event property: track('category:object_view', { slug }).",documentationLink:"https://github.com/ofri-peretz/eslint/blob/main/ANALYTICS_PHILOSOPHY.md"})},schema:[]},defaultOptions:[],create(context){function isCaptureLike(node){if(node.callee.type==="MemberExpression"&&node.callee.property.type==="Identifier"&&(node.callee.property.name==="capture"||node.callee.property.name==="track")){return true}if(node.callee.type==="Identifier"&&node.callee.name==="track"){return true}return false}return{CallExpression(node){if(!isCaptureLike(node)||node.arguments.length===0)return;const first=node.arguments[0];if(first.type==="Literal"&&typeof first.value==="string"){if(!EVENT_NAME_RE.test(first.value)){context.report({node:first,messageId:"invalidEventName",data:{name:first.value}})}return}if(first.type==="TemplateLiteral"&&first.expressions.length>0){context.report({node:first,messageId:"interpolatedEventName"})}}}}});
|
|
@@ -1,130 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.consistentExistenceIndexCheck = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
6
|
-
exports.consistentExistenceIndexCheck = (0, eslint_devkit_1.createRule)({
|
|
7
|
-
name: 'consistent-existence-index-check',
|
|
8
|
-
meta: {
|
|
9
|
-
type: 'suggestion',
|
|
10
|
-
docs: {
|
|
11
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/consistent-existence-index-check.md',
|
|
12
|
-
description: 'Enforce consistent style for checking object property existence',
|
|
13
|
-
},
|
|
14
|
-
fixable: 'code',
|
|
15
|
-
hasSuggestions: true,
|
|
16
|
-
messages: {
|
|
17
|
-
consistentExistenceCheck: (0, eslint_devkit_2.formatLLMMessage)({
|
|
18
|
-
icon: eslint_devkit_2.MessageIcons.WARNING,
|
|
19
|
-
issueName: 'Inconsistent Property Check',
|
|
20
|
-
description: 'Use consistent method for property existence checks',
|
|
21
|
-
severity: 'MEDIUM',
|
|
22
|
-
fix: 'Use "{{preferred}}" instead of "{{current}}" for property checks',
|
|
23
|
-
documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in',
|
|
24
|
-
}),
|
|
25
|
-
},
|
|
26
|
-
schema: [
|
|
27
|
-
{
|
|
28
|
-
type: 'object',
|
|
29
|
-
properties: {
|
|
30
|
-
preferred: {
|
|
31
|
-
type: 'string',
|
|
32
|
-
enum: ['in', 'hasOwnProperty', 'Object.hasOwn'],
|
|
33
|
-
default: 'in',
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
additionalProperties: false,
|
|
37
|
-
},
|
|
38
|
-
],
|
|
39
|
-
},
|
|
40
|
-
defaultOptions: [{ preferred: 'in' }],
|
|
41
|
-
create(context) {
|
|
42
|
-
const [options] = context.options;
|
|
43
|
-
const { preferred = 'in' } = options || {};
|
|
44
|
-
function reportInconsistentCheck(node, currentMethod, object, property) {
|
|
45
|
-
let fix;
|
|
46
|
-
const parent = node.parent;
|
|
47
|
-
const isStandaloneExpression = !parent ||
|
|
48
|
-
parent.type === 'ExpressionStatement' ||
|
|
49
|
-
(parent.type === 'VariableDeclarator' && parent.init === node) ||
|
|
50
|
-
(parent.type === 'AssignmentExpression' && parent.right === node) ||
|
|
51
|
-
(parent.type === 'ReturnStatement' && parent.argument === node) ||
|
|
52
|
-
(parent.type === 'ArrowFunctionExpression' && parent.body === node) ||
|
|
53
|
-
(parent.type === 'IfStatement' && parent.test === node) ||
|
|
54
|
-
(parent.type === 'WhileStatement' && parent.test === node) ||
|
|
55
|
-
(parent.type === 'DoWhileStatement' && parent.test === node) ||
|
|
56
|
-
(parent.type === 'ForStatement' && parent.test === node) ||
|
|
57
|
-
(parent.type === 'ConditionalExpression' && parent.test === node);
|
|
58
|
-
if (preferred === 'in' && isStandaloneExpression) {
|
|
59
|
-
fix = function (fixer) {
|
|
60
|
-
const objectText = context.sourceCode.getText(object);
|
|
61
|
-
const propertyText = context.sourceCode.getText(property);
|
|
62
|
-
return fixer.replaceText(node, `${propertyText} in ${objectText}`);
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
else if (preferred === 'hasOwnProperty' && isStandaloneExpression) {
|
|
66
|
-
fix = function (fixer) {
|
|
67
|
-
const objectText = context.sourceCode.getText(object);
|
|
68
|
-
const propertyText = context.sourceCode.getText(property);
|
|
69
|
-
return fixer.replaceText(node, `${objectText}.hasOwnProperty(${propertyText})`);
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
else if (preferred === 'Object.hasOwn' && isStandaloneExpression) {
|
|
73
|
-
fix = function (fixer) {
|
|
74
|
-
const objectText = context.sourceCode.getText(object);
|
|
75
|
-
const propertyText = context.sourceCode.getText(property);
|
|
76
|
-
return fixer.replaceText(node, `Object.hasOwn(${objectText}, ${propertyText})`);
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
context.report({
|
|
80
|
-
node,
|
|
81
|
-
messageId: 'consistentExistenceCheck',
|
|
82
|
-
data: {
|
|
83
|
-
current: currentMethod,
|
|
84
|
-
preferred,
|
|
85
|
-
},
|
|
86
|
-
fix,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
return {
|
|
90
|
-
CallExpression(node) {
|
|
91
|
-
if (node.callee.type === 'MemberExpression' &&
|
|
92
|
-
node.callee.property.type === 'Identifier' &&
|
|
93
|
-
node.callee.property.name === 'hasOwnProperty' &&
|
|
94
|
-
node.arguments.length === 1 &&
|
|
95
|
-
preferred !== 'hasOwnProperty') {
|
|
96
|
-
reportInconsistentCheck(node, 'hasOwnProperty', node.callee.object, node.arguments[0]);
|
|
97
|
-
}
|
|
98
|
-
if (node.callee.type === 'MemberExpression' &&
|
|
99
|
-
node.callee.object.type === 'MemberExpression' &&
|
|
100
|
-
node.callee.object.object.type === 'MemberExpression' &&
|
|
101
|
-
node.callee.object.object.object.type === 'Identifier' &&
|
|
102
|
-
node.callee.object.object.object.name === 'Object' &&
|
|
103
|
-
node.callee.object.object.property.type === 'Identifier' &&
|
|
104
|
-
node.callee.object.object.property.name === 'prototype' &&
|
|
105
|
-
node.callee.object.property.type === 'Identifier' &&
|
|
106
|
-
node.callee.object.property.name === 'hasOwnProperty' &&
|
|
107
|
-
node.callee.property.type === 'Identifier' &&
|
|
108
|
-
node.callee.property.name === 'call' &&
|
|
109
|
-
node.arguments.length >= 2 &&
|
|
110
|
-
preferred !== 'hasOwnProperty') {
|
|
111
|
-
reportInconsistentCheck(node, 'Object.prototype.hasOwnProperty.call', node.arguments[0], node.arguments[1]);
|
|
112
|
-
}
|
|
113
|
-
if (node.callee.type === 'MemberExpression' &&
|
|
114
|
-
node.callee.object.type === 'Identifier' &&
|
|
115
|
-
node.callee.object.name === 'Object' &&
|
|
116
|
-
node.callee.property.type === 'Identifier' &&
|
|
117
|
-
node.callee.property.name === 'hasOwn' &&
|
|
118
|
-
node.arguments.length === 2 &&
|
|
119
|
-
preferred !== 'Object.hasOwn') {
|
|
120
|
-
reportInconsistentCheck(node, 'Object.hasOwn', node.arguments[0], node.arguments[1]);
|
|
121
|
-
}
|
|
122
|
-
},
|
|
123
|
-
BinaryExpression(node) {
|
|
124
|
-
if (node.operator === 'in' && preferred !== 'in') {
|
|
125
|
-
reportInconsistentCheck(node, 'in', node.right, node.left);
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
};
|
|
129
|
-
},
|
|
130
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.consistentExistenceIndexCheck=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.consistentExistenceIndexCheck=(0,eslint_devkit_1.createRule)({name:"consistent-existence-index-check",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/consistent-existence-index-check.md",description:"Enforce consistent style for checking object property existence"},fixable:"code",hasSuggestions:true,messages:{consistentExistenceCheck:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Inconsistent Property Check",description:"Use consistent method for property existence checks",severity:"MEDIUM",fix:'Use "{{preferred}}" instead of "{{current}}" for property checks',documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in"})},schema:[{type:"object",properties:{preferred:{type:"string",enum:["in","hasOwnProperty","Object.hasOwn"],default:"in"}},additionalProperties:false}]},defaultOptions:[{preferred:"in"}],create(context){const[options]=context.options;const{preferred="in"}=options||{};function reportInconsistentCheck(node,currentMethod,object,property){let fix;const parent=node.parent;const isStandaloneExpression=!parent||parent.type==="ExpressionStatement"||parent.type==="VariableDeclarator"&&parent.init===node||parent.type==="AssignmentExpression"&&parent.right===node||parent.type==="ReturnStatement"&&parent.argument===node||parent.type==="ArrowFunctionExpression"&&parent.body===node||parent.type==="IfStatement"&&parent.test===node||parent.type==="WhileStatement"&&parent.test===node||parent.type==="DoWhileStatement"&&parent.test===node||parent.type==="ForStatement"&&parent.test===node||parent.type==="ConditionalExpression"&&parent.test===node;if(preferred==="in"&&isStandaloneExpression){fix=function(fixer){const objectText=context.sourceCode.getText(object);const propertyText=context.sourceCode.getText(property);return fixer.replaceText(node,`${propertyText} in ${objectText}`)}}else if(preferred==="hasOwnProperty"&&isStandaloneExpression){fix=function(fixer){const objectText=context.sourceCode.getText(object);const propertyText=context.sourceCode.getText(property);return fixer.replaceText(node,`${objectText}.hasOwnProperty(${propertyText})`)}}else if(preferred==="Object.hasOwn"&&isStandaloneExpression){fix=function(fixer){const objectText=context.sourceCode.getText(object);const propertyText=context.sourceCode.getText(property);return fixer.replaceText(node,`Object.hasOwn(${objectText}, ${propertyText})`)}}context.report({node,messageId:"consistentExistenceCheck",data:{current:currentMethod,preferred},fix})}return{CallExpression(node){if(node.callee.type==="MemberExpression"&&node.callee.property.type==="Identifier"&&node.callee.property.name==="hasOwnProperty"&&node.arguments.length===1&&preferred!=="hasOwnProperty"){reportInconsistentCheck(node,"hasOwnProperty",node.callee.object,node.arguments[0])}if(node.callee.type==="MemberExpression"&&node.callee.object.type==="MemberExpression"&&node.callee.object.object.type==="MemberExpression"&&node.callee.object.object.object.type==="Identifier"&&node.callee.object.object.object.name==="Object"&&node.callee.object.object.property.type==="Identifier"&&node.callee.object.object.property.name==="prototype"&&node.callee.object.property.type==="Identifier"&&node.callee.object.property.name==="hasOwnProperty"&&node.callee.property.type==="Identifier"&&node.callee.property.name==="call"&&node.arguments.length>=2&&preferred!=="hasOwnProperty"){reportInconsistentCheck(node,"Object.prototype.hasOwnProperty.call",node.arguments[0],node.arguments[1])}if(node.callee.type==="MemberExpression"&&node.callee.object.type==="Identifier"&&node.callee.object.name==="Object"&&node.callee.property.type==="Identifier"&&node.callee.property.name==="hasOwn"&&node.arguments.length===2&&preferred!=="Object.hasOwn"){reportInconsistentCheck(node,"Object.hasOwn",node.arguments[0],node.arguments[1])}},BinaryExpression(node){if(node.operator==="in"&&preferred!=="in"){reportInconsistentCheck(node,"in",node.right,node.left)}}}}});
|
|
@@ -1,283 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.expiringTodoComments = void 0;
|
|
4
|
-
exports.parseVersionParts = parseVersionParts;
|
|
5
|
-
exports.compareVersions = compareVersions;
|
|
6
|
-
exports.checkDateCondition = checkDateCondition;
|
|
7
|
-
exports.checkPackageVersionCondition = checkPackageVersionCondition;
|
|
8
|
-
exports.checkEngineVersionCondition = checkEngineVersionCondition;
|
|
9
|
-
exports.checkDependencyCondition = checkDependencyCondition;
|
|
10
|
-
exports.parseTodoCondition = parseTodoCondition;
|
|
11
|
-
exports.validateCondition = validateCondition;
|
|
12
|
-
exports.checkParsedCondition = checkParsedCondition;
|
|
13
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
14
|
-
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
15
|
-
const eslint_devkit_3 = require("@interlace/eslint-devkit");
|
|
16
|
-
const eslint_devkit_4 = require("@interlace/eslint-devkit");
|
|
17
|
-
function parseVersionParts(version) {
|
|
18
|
-
const cleaned = version.replace(/^[\^~>=<]+/, '').trim();
|
|
19
|
-
const parts = cleaned
|
|
20
|
-
.split('.')
|
|
21
|
-
.map((p) => (/^\d+$/.test(p) ? parseInt(p, 10) : 0));
|
|
22
|
-
while (parts.length < 3)
|
|
23
|
-
parts.push(0);
|
|
24
|
-
return { major: parts[0], minor: parts[1], patch: parts[2] };
|
|
25
|
-
}
|
|
26
|
-
function compareVersions(version1, version2) {
|
|
27
|
-
const v1 = parseVersionParts(version1);
|
|
28
|
-
const v2 = parseVersionParts(version2);
|
|
29
|
-
if (v1.major !== v2.major)
|
|
30
|
-
return v1.major - v2.major;
|
|
31
|
-
if (v1.minor !== v2.minor)
|
|
32
|
-
return v1.minor - v2.minor;
|
|
33
|
-
return v1.patch - v2.patch;
|
|
34
|
-
}
|
|
35
|
-
function checkDateCondition(dateStr) {
|
|
36
|
-
const conditionDate = new Date(dateStr + 'T00:00:00Z');
|
|
37
|
-
const now = new Date();
|
|
38
|
-
return now >= conditionDate;
|
|
39
|
-
}
|
|
40
|
-
function checkPackageVersionCondition(pkg, operator, targetVersion) {
|
|
41
|
-
if (!pkg?.version)
|
|
42
|
-
return false;
|
|
43
|
-
const comparison = compareVersions(pkg.version, targetVersion);
|
|
44
|
-
switch (operator) {
|
|
45
|
-
case '>':
|
|
46
|
-
return comparison > 0;
|
|
47
|
-
case '>=':
|
|
48
|
-
return comparison >= 0;
|
|
49
|
-
case '<':
|
|
50
|
-
return comparison < 0;
|
|
51
|
-
case '<=':
|
|
52
|
-
return comparison <= 0;
|
|
53
|
-
case '=':
|
|
54
|
-
case '==':
|
|
55
|
-
return comparison === 0;
|
|
56
|
-
default:
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
function checkEngineVersionCondition(pkg, engine, operator, targetVersion) {
|
|
61
|
-
if (engine !== 'node')
|
|
62
|
-
return false;
|
|
63
|
-
const nodeVersion = pkg?.engines?.node;
|
|
64
|
-
if (!nodeVersion)
|
|
65
|
-
return false;
|
|
66
|
-
const currentVersion = nodeVersion.replace(/^[\^~>=<]+/, '');
|
|
67
|
-
const comparison = compareVersions(currentVersion, targetVersion);
|
|
68
|
-
switch (operator) {
|
|
69
|
-
case '>':
|
|
70
|
-
return comparison > 0;
|
|
71
|
-
case '>=':
|
|
72
|
-
return comparison >= 0;
|
|
73
|
-
case '<':
|
|
74
|
-
return comparison < 0;
|
|
75
|
-
case '<=':
|
|
76
|
-
return comparison <= 0;
|
|
77
|
-
default:
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
function checkDependencyCondition(pkg, packageName, present) {
|
|
82
|
-
if (!pkg)
|
|
83
|
-
return false;
|
|
84
|
-
const allDeps = {
|
|
85
|
-
...pkg.dependencies,
|
|
86
|
-
...pkg.devDependencies,
|
|
87
|
-
...pkg.peerDependencies,
|
|
88
|
-
};
|
|
89
|
-
const hasPackage = packageName in allDeps;
|
|
90
|
-
return present ? hasPackage : hasPackage;
|
|
91
|
-
}
|
|
92
|
-
function parseTodoCondition(commentValue, term) {
|
|
93
|
-
const todoRegex = new RegExp(`\\*?\\s*${term}\\s*(?:\\([^)]+\\))?\\s*\\[([^\\]]+)\\]\\s*:\\s*(.+)$`, 'im');
|
|
94
|
-
const match = commentValue.match(todoRegex);
|
|
95
|
-
if (!match)
|
|
96
|
-
return null;
|
|
97
|
-
const conditionStr = match[1];
|
|
98
|
-
const rest = match[2];
|
|
99
|
-
const conditions = conditionStr.split(/\s*,\s*/);
|
|
100
|
-
return { conditions, rest };
|
|
101
|
-
}
|
|
102
|
-
function validateCondition(condition) {
|
|
103
|
-
if (/^\d{4}-\d{2}-\d{2}$/.test(condition)) {
|
|
104
|
-
return { type: 'date', value: condition };
|
|
105
|
-
}
|
|
106
|
-
const packageVersionMatch = condition.match(/^([><]=?|=|==)\s*([\d.]+)$/);
|
|
107
|
-
if (packageVersionMatch) {
|
|
108
|
-
return {
|
|
109
|
-
type: 'package-version',
|
|
110
|
-
operator: packageVersionMatch[1],
|
|
111
|
-
value: packageVersionMatch[2],
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
const engineVersionMatch = condition.match(/^engine:(\w+)@([><]=?)\s*([\d.]+)$/);
|
|
115
|
-
if (engineVersionMatch) {
|
|
116
|
-
const engine = engineVersionMatch[1];
|
|
117
|
-
if (!['node', 'npm', 'yarn', 'pnpm'].includes(engine)) {
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
return {
|
|
121
|
-
type: 'engine-version',
|
|
122
|
-
value: `${engine}@${engineVersionMatch[3]}`,
|
|
123
|
-
operator: engineVersionMatch[2],
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
const depMatch = condition.match(/^([+-])(.+)$/);
|
|
127
|
-
if (depMatch) {
|
|
128
|
-
return {
|
|
129
|
-
type: 'dependency',
|
|
130
|
-
value: depMatch[2],
|
|
131
|
-
operator: depMatch[1],
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
function checkParsedCondition(getPkg, parsed) {
|
|
137
|
-
if (!parsed)
|
|
138
|
-
return false;
|
|
139
|
-
switch (parsed.type) {
|
|
140
|
-
case 'date':
|
|
141
|
-
return checkDateCondition(parsed.value);
|
|
142
|
-
case 'package-version':
|
|
143
|
-
return parsed.operator
|
|
144
|
-
? checkPackageVersionCondition(getPkg(), parsed.operator, parsed.value)
|
|
145
|
-
: false;
|
|
146
|
-
case 'engine-version': {
|
|
147
|
-
const [engine, version] = parsed.value.split('@');
|
|
148
|
-
return parsed.operator
|
|
149
|
-
? checkEngineVersionCondition(getPkg(), engine, parsed.operator, version)
|
|
150
|
-
: false;
|
|
151
|
-
}
|
|
152
|
-
case 'dependency':
|
|
153
|
-
return checkDependencyCondition(getPkg(), parsed.value, parsed.operator === '+');
|
|
154
|
-
default:
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
exports.expiringTodoComments = (0, eslint_devkit_1.createRule)({
|
|
159
|
-
name: 'expiring-todo-comments',
|
|
160
|
-
meta: {
|
|
161
|
-
type: 'problem',
|
|
162
|
-
docs: {
|
|
163
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/expiring-todo-comments.md',
|
|
164
|
-
description: 'Add expiration conditions to TODO comments to prevent forgotten tasks',
|
|
165
|
-
},
|
|
166
|
-
hasSuggestions: false,
|
|
167
|
-
messages: {
|
|
168
|
-
expiringTodoComment: (0, eslint_devkit_2.formatLLMMessage)({
|
|
169
|
-
icon: eslint_devkit_2.MessageIcons.WARNING,
|
|
170
|
-
issueName: 'Expiring TODO Comment',
|
|
171
|
-
description: 'TODO comment has expired condition',
|
|
172
|
-
severity: 'HIGH',
|
|
173
|
-
fix: 'Address the TODO or update/remove the condition',
|
|
174
|
-
documentationLink: 'https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/expiring-todo-comments.md',
|
|
175
|
-
}),
|
|
176
|
-
invalidTodoCondition: (0, eslint_devkit_2.formatLLMMessage)({
|
|
177
|
-
icon: eslint_devkit_2.MessageIcons.WARNING,
|
|
178
|
-
issueName: 'Invalid TODO Condition',
|
|
179
|
-
description: '{{term}} TODO comment has invalid condition format',
|
|
180
|
-
severity: 'MEDIUM',
|
|
181
|
-
fix: 'Fix the condition format or remove the condition',
|
|
182
|
-
documentationLink: 'https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/expiring-todo-comments.md',
|
|
183
|
-
}),
|
|
184
|
-
multipleTodoConditions: (0, eslint_devkit_2.formatLLMMessage)({
|
|
185
|
-
icon: eslint_devkit_2.MessageIcons.WARNING,
|
|
186
|
-
issueName: 'Multiple TODO Conditions',
|
|
187
|
-
description: 'TODO comment has multiple conditions',
|
|
188
|
-
severity: 'MEDIUM',
|
|
189
|
-
fix: 'Use only one condition per TODO comment',
|
|
190
|
-
documentationLink: 'https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/expiring-todo-comments.md',
|
|
191
|
-
}),
|
|
192
|
-
},
|
|
193
|
-
schema: [
|
|
194
|
-
{
|
|
195
|
-
type: 'object',
|
|
196
|
-
properties: {
|
|
197
|
-
terms: {
|
|
198
|
-
type: 'array',
|
|
199
|
-
items: { type: 'string' },
|
|
200
|
-
default: ['TODO', 'FIXME', 'XXX'],
|
|
201
|
-
},
|
|
202
|
-
dateFormat: {
|
|
203
|
-
type: 'string',
|
|
204
|
-
default: 'YYYY-MM-DD',
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
additionalProperties: false,
|
|
208
|
-
},
|
|
209
|
-
],
|
|
210
|
-
},
|
|
211
|
-
defaultOptions: [
|
|
212
|
-
{ terms: ['TODO', 'FIXME', 'XXX'], dateFormat: 'YYYY-MM-DD' },
|
|
213
|
-
],
|
|
214
|
-
create(context) {
|
|
215
|
-
const [options] = context.options;
|
|
216
|
-
const { terms = ['TODO', 'FIXME', 'XXX'] } = options || {};
|
|
217
|
-
let packageJson = null;
|
|
218
|
-
function loadPackageJson() {
|
|
219
|
-
if (packageJson !== null) {
|
|
220
|
-
return packageJson;
|
|
221
|
-
}
|
|
222
|
-
const packageJsonPath = (0, eslint_devkit_4.resolvePath)(process.cwd(), 'package.json');
|
|
223
|
-
packageJson = (0, eslint_devkit_3.readJsonFileSync)(packageJsonPath);
|
|
224
|
-
return packageJson;
|
|
225
|
-
}
|
|
226
|
-
function checkComment(comment) {
|
|
227
|
-
const commentValue = comment.value;
|
|
228
|
-
for (const term of terms) {
|
|
229
|
-
const parsed = parseTodoCondition(commentValue, term);
|
|
230
|
-
if (!parsed)
|
|
231
|
-
continue;
|
|
232
|
-
const { conditions, rest } = parsed;
|
|
233
|
-
if (conditions.length > 1) {
|
|
234
|
-
context.report({
|
|
235
|
-
loc: comment.loc,
|
|
236
|
-
messageId: 'multipleTodoConditions',
|
|
237
|
-
data: {
|
|
238
|
-
term,
|
|
239
|
-
conditions: conditions.join(', '),
|
|
240
|
-
},
|
|
241
|
-
});
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
const condition = conditions[0];
|
|
245
|
-
const parsedCondition = validateCondition(condition);
|
|
246
|
-
if (!parsedCondition) {
|
|
247
|
-
context.report({
|
|
248
|
-
loc: comment.loc,
|
|
249
|
-
messageId: 'invalidTodoCondition',
|
|
250
|
-
data: {
|
|
251
|
-
condition,
|
|
252
|
-
term,
|
|
253
|
-
},
|
|
254
|
-
});
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
if (checkParsedCondition(loadPackageJson, parsedCondition)) {
|
|
258
|
-
context.report({
|
|
259
|
-
loc: comment.loc,
|
|
260
|
-
messageId: 'expiringTodoComment',
|
|
261
|
-
data: {
|
|
262
|
-
term,
|
|
263
|
-
condition,
|
|
264
|
-
message: rest,
|
|
265
|
-
},
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
break;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
return {
|
|
272
|
-
Program() {
|
|
273
|
-
const sourceCode = context.sourceCode;
|
|
274
|
-
const comments = sourceCode.getAllComments();
|
|
275
|
-
for (const comment of comments) {
|
|
276
|
-
if (comment.type === 'Line' || comment.type === 'Block') {
|
|
277
|
-
checkComment(comment);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
},
|
|
281
|
-
};
|
|
282
|
-
},
|
|
283
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.expiringTodoComments=void 0;exports.parseVersionParts=parseVersionParts;exports.compareVersions=compareVersions;exports.checkDateCondition=checkDateCondition;exports.checkPackageVersionCondition=checkPackageVersionCondition;exports.checkEngineVersionCondition=checkEngineVersionCondition;exports.checkDependencyCondition=checkDependencyCondition;exports.parseTodoCondition=parseTodoCondition;exports.validateCondition=validateCondition;exports.checkParsedCondition=checkParsedCondition;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 eslint_devkit_4=require("@interlace/eslint-devkit");function parseVersionParts(version){const cleaned=version.replace(/^[\^~>=<]+/,"").trim();const parts=cleaned.split(".").map(p=>/^\d+$/.test(p)?parseInt(p,10):0);while(parts.length<3)parts.push(0);return{major:parts[0],minor:parts[1],patch:parts[2]}}function compareVersions(version1,version2){const v1=parseVersionParts(version1);const v2=parseVersionParts(version2);if(v1.major!==v2.major)return v1.major-v2.major;if(v1.minor!==v2.minor)return v1.minor-v2.minor;return v1.patch-v2.patch}function checkDateCondition(dateStr){const conditionDate=new Date(dateStr+"T00:00:00Z");const now=new Date;return now>=conditionDate}function checkPackageVersionCondition(pkg,operator,targetVersion){if(!pkg?.version)return false;const comparison=compareVersions(pkg.version,targetVersion);switch(operator){case">":return comparison>0;case">=":return comparison>=0;case"<":return comparison<0;case"<=":return comparison<=0;case"=":case"==":return comparison===0;default:return false}}function checkEngineVersionCondition(pkg,engine,operator,targetVersion){if(engine!=="node")return false;const nodeVersion=pkg?.engines?.node;if(!nodeVersion)return false;const currentVersion=nodeVersion.replace(/^[\^~>=<]+/,"");const comparison=compareVersions(currentVersion,targetVersion);switch(operator){case">":return comparison>0;case">=":return comparison>=0;case"<":return comparison<0;case"<=":return comparison<=0;default:return false}}function checkDependencyCondition(pkg,packageName,present){if(!pkg)return false;const allDeps={...pkg.dependencies,...pkg.devDependencies,...pkg.peerDependencies};const hasPackage=packageName in allDeps;return present?hasPackage:hasPackage}function parseTodoCondition(commentValue,term){const todoRegex=new RegExp(`\\*?\\s*${term}\\s*(?:\\([^)]+\\))?\\s*\\[([^\\]]+)\\]\\s*:\\s*(.+)$`,"im");const match=commentValue.match(todoRegex);if(!match)return null;const conditionStr=match[1];const rest=match[2];const conditions=conditionStr.split(/\s*,\s*/);return{conditions,rest}}function validateCondition(condition){if(/^\d{4}-\d{2}-\d{2}$/.test(condition)){return{type:"date",value:condition}}const packageVersionMatch=condition.match(/^([><]=?|=|==)\s*([\d.]+)$/);if(packageVersionMatch){return{type:"package-version",operator:packageVersionMatch[1],value:packageVersionMatch[2]}}const engineVersionMatch=condition.match(/^engine:(\w+)@([><]=?)\s*([\d.]+)$/);if(engineVersionMatch){const engine=engineVersionMatch[1];if(!["node","npm","yarn","pnpm"].includes(engine)){return null}return{type:"engine-version",value:`${engine}@${engineVersionMatch[3]}`,operator:engineVersionMatch[2]}}const depMatch=condition.match(/^([+-])(.+)$/);if(depMatch){return{type:"dependency",value:depMatch[2],operator:depMatch[1]}}return null}function checkParsedCondition(getPkg,parsed){if(!parsed)return false;switch(parsed.type){case"date":return checkDateCondition(parsed.value);case"package-version":return parsed.operator?checkPackageVersionCondition(getPkg(),parsed.operator,parsed.value):false;case"engine-version":{const[engine,version]=parsed.value.split("@");return parsed.operator?checkEngineVersionCondition(getPkg(),engine,parsed.operator,version):false}case"dependency":return checkDependencyCondition(getPkg(),parsed.value,parsed.operator==="+");default:return false}}exports.expiringTodoComments=(0,eslint_devkit_1.createRule)({name:"expiring-todo-comments",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/expiring-todo-comments.md",description:"Add expiration conditions to TODO comments to prevent forgotten tasks"},hasSuggestions:false,messages:{expiringTodoComment:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Expiring TODO Comment",description:"TODO comment has expired condition",severity:"HIGH",fix:"Address the TODO or update/remove the condition",documentationLink:"https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/expiring-todo-comments.md"}),invalidTodoCondition:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Invalid TODO Condition",description:"{{term}} TODO comment has invalid condition format",severity:"MEDIUM",fix:"Fix the condition format or remove the condition",documentationLink:"https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/expiring-todo-comments.md"}),multipleTodoConditions:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Multiple TODO Conditions",description:"TODO comment has multiple conditions",severity:"MEDIUM",fix:"Use only one condition per TODO comment",documentationLink:"https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/expiring-todo-comments.md"})},schema:[{type:"object",properties:{terms:{type:"array",items:{type:"string"},default:["TODO","FIXME","XXX"]},dateFormat:{type:"string",default:"YYYY-MM-DD"}},additionalProperties:false}]},defaultOptions:[{terms:["TODO","FIXME","XXX"],dateFormat:"YYYY-MM-DD"}],create(context){const[options]=context.options;const{terms=["TODO","FIXME","XXX"]}=options||{};let packageJson=null;function loadPackageJson(){if(packageJson!==null){return packageJson}const packageJsonPath=(0,eslint_devkit_4.resolvePath)(process.cwd(),"package.json");packageJson=(0,eslint_devkit_3.readJsonFileSync)(packageJsonPath);return packageJson}function checkComment(comment){const commentValue=comment.value;for(const term of terms){const parsed=parseTodoCondition(commentValue,term);if(!parsed)continue;const{conditions,rest}=parsed;if(conditions.length>1){context.report({loc:comment.loc,messageId:"multipleTodoConditions",data:{term,conditions:conditions.join(", ")}});continue}const condition=conditions[0];const parsedCondition=validateCondition(condition);if(!parsedCondition){context.report({loc:comment.loc,messageId:"invalidTodoCondition",data:{condition,term}});continue}if(checkParsedCondition(loadPackageJson,parsedCondition)){context.report({loc:comment.loc,messageId:"expiringTodoComment",data:{term,condition,message:rest}})}break}}return{Program(){const sourceCode=context.sourceCode;const comments=sourceCode.getAllComments();for(const comment of comments){if(comment.type==="Line"||comment.type==="Block"){checkComment(comment)}}}}}});
|