eslint-plugin-conventions 4.2.5 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-conventions",
3
- "version": "4.2.5",
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,8 +50,7 @@
50
50
  "node": ">=18.0.0"
51
51
  },
52
52
  "dependencies": {
53
- "tslib": "^2.3.0",
54
- "@interlace/eslint-devkit": "^1.6.1"
53
+ "@interlace/eslint-devkit": "^1.10.0"
55
54
  },
56
55
  "repository": {
57
56
  "type": "git",
package/src/index.js CHANGED
@@ -1,59 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.configs = exports.plugin = exports.rules = void 0;
4
- const no_commented_code_1 = require("./rules/conventions/no-commented-code");
5
- const expiring_todo_comments_1 = require("./rules/conventions/expiring-todo-comments");
6
- const prefer_code_point_1 = require("./rules/conventions/prefer-code-point");
7
- const prefer_dom_node_text_content_1 = require("./rules/conventions/prefer-dom-node-text-content");
8
- const no_console_spaces_1 = require("./rules/conventions/no-console-spaces");
9
- const no_deprecated_api_1 = require("./rules/deprecation/no-deprecated-api");
10
- const prefer_dependency_version_strategy_1 = require("./rules/conventions/prefer-dependency-version-strategy");
11
- const filename_case_1 = require("./rules/conventions/filename-case");
12
- const consistent_existence_index_check_1 = require("./rules/conventions/consistent-existence-index-check");
13
- const no_json_schema_tags_1 = require("./rules/conventions/no-json-schema-tags");
14
- const require_data_testid_1 = require("./rules/conventions/require-data-testid");
15
- const utm_taxonomy_1 = require("./rules/conventions/utm-taxonomy");
16
- const no_raw_cross_property_href_1 = require("./rules/conventions/no-raw-cross-property-href");
17
- const analytics_event_naming_1 = require("./rules/conventions/analytics-event-naming");
18
- const no_magic_numbers_1 = require("./rules/conventions/no-magic-numbers");
19
- exports.rules = {
20
- 'no-commented-code': no_commented_code_1.noCommentedCode,
21
- 'expiring-todo-comments': expiring_todo_comments_1.expiringTodoComments,
22
- 'prefer-code-point': prefer_code_point_1.preferCodePoint,
23
- 'prefer-dom-node-text-content': prefer_dom_node_text_content_1.preferDomNodeTextContent,
24
- 'no-console-spaces': no_console_spaces_1.noConsoleSpaces,
25
- 'no-deprecated-api': no_deprecated_api_1.noDeprecatedApi,
26
- 'prefer-dependency-version-strategy': prefer_dependency_version_strategy_1.preferDependencyVersionStrategy,
27
- 'filename-case': filename_case_1.filenameCase,
28
- 'consistent-existence-index-check': consistent_existence_index_check_1.consistentExistenceIndexCheck,
29
- 'no-json-schema-tags': no_json_schema_tags_1.noJsonSchemaTags,
30
- 'require-data-testid': require_data_testid_1.requireDataTestId,
31
- 'utm-taxonomy': utm_taxonomy_1.utmTaxonomy,
32
- 'no-raw-cross-property-href': no_raw_cross_property_href_1.noRawCrossPropertyHref,
33
- 'analytics-event-naming': analytics_event_naming_1.analyticsEventNaming,
34
- 'no-magic-numbers': no_magic_numbers_1.noMagicNumbers,
35
- };
36
- exports.plugin = {
37
- meta: {
38
- name: 'eslint-plugin-conventions',
39
- version: '4.2.5',
40
- },
41
- rules: exports.rules,
42
- };
43
- exports.configs = {
44
- recommended: {
45
- plugins: {
46
- conventions: exports.plugin,
47
- },
48
- rules: {
49
- 'conventions/no-commented-code': 'warn',
50
- 'conventions/expiring-todo-comments': 'warn',
51
- 'conventions/no-deprecated-api': 'warn',
52
- 'conventions/utm-taxonomy': 'error',
53
- 'conventions/no-raw-cross-property-href': 'error',
54
- 'conventions/analytics-event-naming': 'error',
55
- 'conventions/no-magic-numbers': 'warn',
56
- },
57
- },
58
- };
59
- 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,3 +1 @@
1
- "use strict";
2
- const index_1 = require("./index");
3
- module.exports = index_1.plugin;
1
+ "use strict";const index_1=require("./index");module.exports=index_1.plugin;
@@ -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)}}}}}});