eslint-plugin-conventions 5.2.0 → 5.3.0

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/README.md CHANGED
@@ -13,6 +13,7 @@
13
13
  <p align="center">
14
14
  <a href="https://www.npmjs.com/package/eslint-plugin-conventions" target="_blank"><img src="https://img.shields.io/npm/v/eslint-plugin-conventions.svg" alt="NPM Version" /></a>
15
15
  <a href="https://www.npmjs.com/package/eslint-plugin-conventions" target="_blank"><img src="https://img.shields.io/npm/dm/eslint-plugin-conventions.svg" alt="NPM Downloads" /></a>
16
+ <a href="https://packagephobia.com/result?p=eslint-plugin-conventions" target="_blank"><img src="https://badgen.net/packagephobia/install/eslint-plugin-conventions" alt="Install Size" /></a>
16
17
  <a href="https://opensource.org/licenses/MIT" target="_blank"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="Package License" /></a>
17
18
  <a href="https://app.codecov.io/gh/ofri-peretz/eslint/components?components%5B0%5D=eslint-plugin-conventions" target="_blank"><img src="https://codecov.io/gh/ofri-peretz/eslint/graph/badge.svg?component=eslint-plugin-conventions" alt="Codecov" /></a>
18
19
  <a href="https://github.com/ofri-peretz/eslint" target="_blank"><img src="https://img.shields.io/badge/Since-Dec_2025-blue?logo=rocket&logoColor=white" alt="Since Dec 2025" /></a>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-conventions",
3
- "version": "5.2.0",
3
+ "version": "5.3.0",
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.18.0"
53
+ "@interlace/eslint-devkit": "^1.19.0"
54
54
  },
55
55
  "repository": {
56
56
  "type": "git",
package/src/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.configs=exports.plugin=exports.rules=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");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}};(0,eslint_devkit_1.withCanonicalDocsUrls)("plugin-conventions",exports.rules);exports.plugin={meta:{name:"eslint-plugin-conventions",version:"5.2.0"},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"}}};exports.default=exports.plugin;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.configs=exports.plugin=exports.rules=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");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}};(0,eslint_devkit_1.withCanonicalDocsUrls)("plugin-conventions",exports.rules);exports.plugin={meta:{name:"eslint-plugin-conventions",version:"5.3.0"},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"}}};exports.default=exports.plugin;
@@ -1 +1 @@
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","copy"].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, copy}. Vendor reserved events ($pageview, $set, \u2026) are exempt.",documentationLink:"https://github.com/ofri-peretz/interlace/blob/main/docs/philosophies/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/interlace/blob/main/docs/philosophies/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];const staticText=(0,eslint_devkit_1.staticString)(first);if(staticText!==null){if(!EVENT_NAME_RE.test(staticText)){context.report({node:first,messageId:"invalidEventName",data:{name:staticText}})}return}if(first.type==="TemplateLiteral"&&first.expressions.length>0){context.report({node:first,messageId:"interpolatedEventName"})}}}}});
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","copy"].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, copy}. Vendor reserved events ($pageview, $set, \u2026) are exempt.",documentationLink:"https://github.com/ofri-peretz/interlace/blob/main/docs/philosophies/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/interlace/blob/main/docs/philosophies/ANALYTICS_PHILOSOPHY.md"})},schema:[]},defaultOptions:[],create(context){function isCaptureLike(node){if(node.callee.type==="MemberExpression"&&((0,eslint_devkit_1.propertyName)(node.callee)==="capture"||(0,eslint_devkit_1.propertyName)(node.callee)==="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];const staticText=(0,eslint_devkit_1.staticString)(first);if(staticText!==null){if(!EVENT_NAME_RE.test(staticText)){context.report({node:first,messageId:"invalidEventName",data:{name:staticText}})}return}if(first.type==="TemplateLiteral"&&first.expressions.length>0){context.report({node:first,messageId:"interpolatedEventName"})}}}}});
@@ -1 +1 @@
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",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
+ "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",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"&&(0,eslint_devkit_1.propertyName)(node.callee)==="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"&&(0,eslint_devkit_1.propertyName)(node.callee.object.object)==="prototype"&&(0,eslint_devkit_1.propertyName)(node.callee.object)==="hasOwnProperty"&&(0,eslint_devkit_1.propertyName)(node.callee)==="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"&&(0,eslint_devkit_1.propertyName)(node.callee)==="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 +1 @@
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){if(isBlockComment&&comment.startsWith("*")){return false}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 STRUCTURAL_CODE=[/^(const|let|var)\s+[A-Za-z_$][\w$]*(\s*:[^=]+)?\s*=/,/^import\s+[^;]*\bfrom\b/,/^import\s+["']/,/^export\s+(default|const|let|var|function|class|\{|\*)/,/^throw\s+new\s+[A-Za-z_$]/,/^(throw|await|yield)\s+[A-Za-z_$][\w$]*\s*[.(]/,/^return\s+(new|await)\s/,/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)+\s*\(/,/=>/,/===|!==/,/^@[A-Za-z_$][\w$]*\s*\(/,/^<[A-Z][\w$]*[\s/>]/];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_$]*\(/,/^[{}[\]]/];const URL_LINE=/^[a-z][a-z0-9+.-]*:\/\//i;const ENDS_LIKE_CODE=/[;{},()[\]]$|=>$/;let codeLikeLines=0;for(const line of lines){if(/^(TODO|FIXME|HACK|XXX)/i.test(line)){continue}if(URL_LINE.test(line)){continue}if(!STRUCTURAL_CODE.some(pattern=>pattern.test(line))&&!ENDS_LIKE_CODE.test(line)){continue}if(STRUCTURAL_CODE.some(pattern=>pattern.test(line))){codeLikeLines++;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",skipGeneratedFiles:true,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
+ "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){if(isBlockComment&&comment.startsWith("*")){return false}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 STRUCTURAL_CODE=[/^(const|let|var)\s+[A-Za-z_$][\w$]*(\s*:[^=]+)?\s*=/,/^import\s+[^;]*\bfrom\b/,/^import\s+["']/,/^export\s+(default|const|let|var|function|class|\{|\*)/,/^throw\s+new\s+[A-Za-z_$]/,/^(throw|await|yield)\s+[A-Za-z_$][\w$]*\s*[.([]/,/^return\s+(new|await)\s/,/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\[\s*['"][^'"]+['"]\s*\])+\s*\(/,/=>/,/===|!==/,/^@[A-Za-z_$][\w$]*\s*\(/,/^<[A-Z][\w$]*[\s/>]/];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_$]*\(/,/^[{}[\]]/];const URL_LINE=/^[a-z][a-z0-9+.-]*:\/\//i;const ENDS_LIKE_CODE=/[;{},()[\]]$|=>$/;let codeLikeLines=0;for(const line of lines){if(/^(TODO|FIXME|HACK|XXX)/i.test(line)){continue}if(URL_LINE.test(line)){continue}if(!STRUCTURAL_CODE.some(pattern=>pattern.test(line))&&!ENDS_LIKE_CODE.test(line)){continue}if(STRUCTURAL_CODE.some(pattern=>pattern.test(line))){codeLikeLines++;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",skipGeneratedFiles:true,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 +1 @@
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",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){const staticText=arg.type==="TemplateLiteral"?null:(0,eslint_devkit_1.staticString)(arg);if(staticText!==null){if(hasLeadingOrTrailingSpaces(staticText)){context.report({node:arg,messageId:"noConsoleSpaces",data:{method:getConsoleMethodName(node),arg:staticText},fix(fixer){const trimmed=staticText.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})}}}}}}}});
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",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"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(node.callee),consoleMethods)){return true}return false}function getConsoleMethodName(node){return(0,eslint_devkit_1.propertyName)(node.callee)}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){const staticText=arg.type==="TemplateLiteral"?null:(0,eslint_devkit_1.staticString)(arg);if(staticText!==null){if(hasLeadingOrTrailingSpaces(staticText)){context.report({node:arg,messageId:"noConsoleSpaces",data:{method:getConsoleMethodName(node),arg:staticText},fix(fixer){const trimmed=staticText.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})}}}}}}}});
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noMagicNumbers=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const DEFAULT_IGNORE=new Set([-1,0,1,2]);function constNameFor(value){const prefix=value<0?"MAGIC_NEG_":"MAGIC_";const digits=String(Math.abs(value)).toUpperCase().replaceAll(/[^0-9A-Z]+/g,"_").replace(/_+$/,"");return`${prefix}${digits}`}function nearestStatement(node){const STATEMENT_TYPES=new Set(["ExpressionStatement","VariableDeclaration","ReturnStatement","IfStatement","WhileStatement","ForStatement","ForInStatement","ForOfStatement","ThrowStatement","SwitchStatement"]);let current=node.parent;while(current){if(STATEMENT_TYPES.has(current.type))return current;current=current.parent}return null}exports.noMagicNumbers=(0,eslint_devkit_1.createRule)({name:"no-magic-numbers",skipGeneratedFiles:true,skipMinifiedFiles:true,meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/no-magic-numbers.md",description:"Disallow magic numbers (numeric literals without a named constant)"},hasSuggestions:true,messages:{noMagicNumber:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.INFO,issueName:"Magic Number",description:"The number {{value}} is a magic literal. Extract it to a named constant to make the intent clear.",severity:"LOW",fix:"const TIMEOUT_MS = {{value}}; // use the named constant everywhere",documentationLink:"https://refactoring.guru/replace-magic-literal"}),extractConst:"Extract {{value}} to a named constant ({{constName}})"},schema:[{type:"object",properties:{ignore:{type:"array",items:{type:"number"},description:"Additional numbers to allow"},ignoreArrayIndexes:{type:"boolean",default:true},detectObjects:{type:"boolean",default:false},ignoreLoopBounds:{type:"boolean",default:true},ignoreLengthComparisons:{type:"boolean",default:true},ignoreNumericArrays:{type:"boolean",default:true},namedConstArithmetic:{type:"boolean",default:true},ignoreDefaultValues:{type:"boolean",default:true},ignoreEnums:{type:"boolean",default:true},ignoreBitwiseExpressions:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{ignore:[],ignoreArrayIndexes:true,detectObjects:false,ignoreLoopBounds:true,ignoreLengthComparisons:true,ignoreNumericArrays:true,namedConstArithmetic:true,ignoreDefaultValues:true,ignoreEnums:true,ignoreBitwiseExpressions:false}],create(context){const[options={}]=context.options;const{ignore=[],ignoreArrayIndexes=true,detectObjects=false,ignoreLoopBounds=true,ignoreLengthComparisons=true,ignoreNumericArrays=true,namedConstArithmetic=true,ignoreDefaultValues=true,ignoreEnums=true,ignoreBitwiseExpressions=false}=options;const ignoredValues=new Set([...DEFAULT_IGNORE,...ignore]);function isIgnoredNumber(value){return ignoredValues.has(value)}const MAX_ARRAY_INDEX=2**32-2;function isArrayIndex(node){if(!ignoreArrayIndexes)return false;const parent=node.parent;const inIndexPosition=parent?.type==="MemberExpression"&&parent.computed&&parent.property===node;if(!inIndexPosition)return false;const value=node.value;return typeof value==="number"&&Number.isInteger(value)&&value>=0&&value<=MAX_ARRAY_INDEX}function isObjectPropertyValue(node){if(detectObjects)return false;const parent=node.parent;return parent?.type==="Property"&&parent.value===node}function isLoopBound(node){if(!ignoreLoopBounds)return false;let current=node;let parent=current.parent;while(parent){if(parent.type==="ForStatement"){const loop=parent;return loop.test===current||loop.update===current||loop.init===current}if(parent.type==="BlockStatement"||parent.type==="Program")return false;current=parent;parent=current.parent}return false}function isLengthComparison(node){if(!ignoreLengthComparisons)return false;const parent=node.parent;if(parent?.type!=="BinaryExpression")return false;const comparison=parent;const EQUALITY=new Set(["===","!==","==","!="]);if(!EQUALITY.has(comparison.operator))return false;const other=comparison.left===node?comparison.right:comparison.left;return other.type==="MemberExpression"&&!other.computed&&other.property.type==="Identifier"&&other.property.name==="length"}function isDefaultValue(node){if(!ignoreDefaultValues)return false;const parent=node.parent;return parent?.type==="AssignmentPattern"&&parent.right===node}function isEnumMember(node){if(!ignoreEnums)return false;const parent=node.parent;return parent?.type==="TSEnumMember"}function isBitwiseContext(node){if(!ignoreBitwiseExpressions)return false;const parent=node.parent;const BITWISE_OPS=new Set(["&","|","^","<<",">>",">>>"]);return parent?.type==="BinaryExpression"&&BITWISE_OPS.has(parent.operator)}const allLiteralOperands=node=>{if(!node||typeof node!=="object")return false;if(node.type==="Literal")return typeof node.value==="number";if(node.type==="UnaryExpression")return allLiteralOperands(node.argument);if(node.type==="TSAsExpression"||node.type==="TSSatisfiesExpression"){return allLiteralOperands(node.expression)}if(node.type==="BinaryExpression"){return allLiteralOperands(node.left)&&allLiteralOperands(node.right)}return false};function isVariableDeclarator(node){let current=node;let parent=current.parent;while(parent&&(parent.type==="TSAsExpression"||parent.type==="TSSatisfiesExpression"||parent.type==="UnaryExpression"||parent.type==="BinaryExpression"&&namedConstArithmetic&&allLiteralOperands(parent))){current=parent;parent=current.parent}return parent?.type==="VariableDeclarator"&&parent.id.type==="Identifier"}function isNumericDataArray(node){if(!ignoreNumericArrays)return false;const parent=node.parent;const array=parent?.type==="ArrayExpression"?parent:parent?.type==="UnaryExpression"&&parent.parent?.type==="ArrayExpression"?parent.parent:null;if(array===null)return false;const numeric=element=>{if(element===null)return false;if(element.type==="Literal")return typeof element.value==="number";if(element.type==="UnaryExpression")return numeric(element.argument);return false};return array.elements.length>=2&&array.elements.every(numeric)}function isExportedConst(node){let current=node;while(current){if(current.type==="ExportNamedDeclaration")return true;if(current.type==="VariableDeclaration"||current.type==="VariableDeclarator"){current=current.parent;continue}break}return false}function isPropertyKey(node){const parent=node.parent;return parent?.type==="Property"&&parent.key===node}return{Literal(node){if(typeof node.value!=="number")return;const value=node.value;if(isIgnoredNumber(value))return;if(!isFinite(value))return;if(isVariableDeclarator(node))return;if(isExportedConst(node))return;if(isArrayIndex(node))return;if(isObjectPropertyValue(node))return;if(isLoopBound(node))return;if(isLengthComparison(node))return;if(isDefaultValue(node))return;if(isEnumMember(node))return;if(isBitwiseContext(node))return;if(isPropertyKey(node))return;if(isNumericDataArray(node))return;const constName=constNameFor(value);const sourceCode=context.sourceCode;context.report({node,messageId:"noMagicNumber",data:{value:String(value)},suggest:[{messageId:"extractConst",data:{value:String(value),constName},fix(fixer){const stmt=nearestStatement(node);if(!stmt)return null;const firstToken=sourceCode.getFirstToken(stmt);if(!firstToken)return null;const col=firstToken.loc.start.column;const indent=" ".repeat(col);return[fixer.insertTextBefore(stmt,`const ${constName} = ${value};
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noMagicNumbers=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const DEFAULT_IGNORE=new Set([-1,0,1,2]);function constNameFor(value){const prefix=value<0?"MAGIC_NEG_":"MAGIC_";const digits=String(Math.abs(value)).toUpperCase().replaceAll(/[^0-9A-Z]+/g,"_").replace(/_+$/,"");return`${prefix}${digits}`}function nearestStatement(node){const STATEMENT_TYPES=new Set(["ExpressionStatement","VariableDeclaration","ReturnStatement","IfStatement","WhileStatement","ForStatement","ForInStatement","ForOfStatement","ThrowStatement","SwitchStatement"]);let current=node.parent;while(current){if(STATEMENT_TYPES.has(current.type))return current;current=current.parent}return null}exports.noMagicNumbers=(0,eslint_devkit_1.createRule)({name:"no-magic-numbers",skipGeneratedFiles:true,skipMinifiedFiles:true,meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/no-magic-numbers.md",description:"Disallow magic numbers (numeric literals without a named constant)"},hasSuggestions:true,messages:{noMagicNumber:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.INFO,issueName:"Magic Number",description:"The number {{value}} is a magic literal. Extract it to a named constant to make the intent clear.",severity:"LOW",fix:"const TIMEOUT_MS = {{value}}; // use the named constant everywhere",documentationLink:"https://refactoring.guru/replace-magic-literal"}),extractConst:"Extract {{value}} to a named constant ({{constName}})"},schema:[{type:"object",properties:{ignore:{type:"array",items:{type:"number"},description:"Additional numbers to allow"},ignoreArrayIndexes:{type:"boolean",default:true},detectObjects:{type:"boolean",default:false},ignoreLoopBounds:{type:"boolean",default:true},ignoreLengthComparisons:{type:"boolean",default:true},ignoreNumericArrays:{type:"boolean",default:true},namedConstArithmetic:{type:"boolean",default:true},ignoreDefaultValues:{type:"boolean",default:true},ignoreEnums:{type:"boolean",default:true},ignoreBitwiseExpressions:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{ignore:[],ignoreArrayIndexes:true,detectObjects:false,ignoreLoopBounds:true,ignoreLengthComparisons:true,ignoreNumericArrays:true,namedConstArithmetic:true,ignoreDefaultValues:true,ignoreEnums:true,ignoreBitwiseExpressions:false}],create(context){const[options={}]=context.options;const{ignore=[],ignoreArrayIndexes=true,detectObjects=false,ignoreLoopBounds=true,ignoreLengthComparisons=true,ignoreNumericArrays=true,namedConstArithmetic=true,ignoreDefaultValues=true,ignoreEnums=true,ignoreBitwiseExpressions=false}=options;const ignoredValues=new Set([...DEFAULT_IGNORE,...ignore]);function isIgnoredNumber(value){return ignoredValues.has(value)}const MAX_ARRAY_INDEX=2**32-2;function isArrayIndex(node){if(!ignoreArrayIndexes)return false;const parent=node.parent;const inIndexPosition=parent?.type==="MemberExpression"&&parent.computed&&parent.property===node;if(!inIndexPosition)return false;const value=node.value;return typeof value==="number"&&Number.isInteger(value)&&value>=0&&value<=MAX_ARRAY_INDEX}function isObjectPropertyValue(node){if(detectObjects)return false;const parent=node.parent;return parent?.type==="Property"&&parent.value===node}function isLoopBound(node){if(!ignoreLoopBounds)return false;let current=node;let parent=current.parent;while(parent){if(parent.type==="ForStatement"){const loop=parent;return loop.test===current||loop.update===current||loop.init===current}if(parent.type==="BlockStatement"||parent.type==="Program")return false;current=parent;parent=current.parent}return false}function isLengthComparison(node){if(!ignoreLengthComparisons)return false;const parent=node.parent;if(parent?.type!=="BinaryExpression")return false;const comparison=parent;const EQUALITY=new Set(["===","!==","==","!="]);if(!EQUALITY.has(comparison.operator))return false;const other=comparison.left===node?comparison.right:comparison.left;return other.type==="MemberExpression"&&(0,eslint_devkit_1.propertyName)(other)==="length"}function isDefaultValue(node){if(!ignoreDefaultValues)return false;const parent=node.parent;return parent?.type==="AssignmentPattern"&&parent.right===node}function isEnumMember(node){if(!ignoreEnums)return false;const parent=node.parent;return parent?.type==="TSEnumMember"}function isBitwiseContext(node){if(!ignoreBitwiseExpressions)return false;const parent=node.parent;const BITWISE_OPS=new Set(["&","|","^","<<",">>",">>>"]);return parent?.type==="BinaryExpression"&&BITWISE_OPS.has(parent.operator)}const allLiteralOperands=node=>{if(!node||typeof node!=="object")return false;if(node.type==="Literal")return typeof node.value==="number";if(node.type==="UnaryExpression")return allLiteralOperands(node.argument);if(node.type==="TSAsExpression"||node.type==="TSSatisfiesExpression"){return allLiteralOperands(node.expression)}if(node.type==="BinaryExpression"){return allLiteralOperands(node.left)&&allLiteralOperands(node.right)}return false};function isVariableDeclarator(node){let current=node;let parent=current.parent;while(parent&&(parent.type==="TSAsExpression"||parent.type==="TSSatisfiesExpression"||parent.type==="UnaryExpression"||parent.type==="BinaryExpression"&&namedConstArithmetic&&allLiteralOperands(parent))){current=parent;parent=current.parent}return parent?.type==="VariableDeclarator"&&parent.id.type==="Identifier"}function isNumericDataArray(node){if(!ignoreNumericArrays)return false;const parent=node.parent;const array=parent?.type==="ArrayExpression"?parent:parent?.type==="UnaryExpression"&&parent.parent?.type==="ArrayExpression"?parent.parent:null;if(array===null)return false;const numeric=element=>{if(element===null)return false;if(element.type==="Literal")return typeof element.value==="number";if(element.type==="UnaryExpression")return numeric(element.argument);return false};return array.elements.length>=2&&array.elements.every(numeric)}function isExportedConst(node){let current=node;while(current){if(current.type==="ExportNamedDeclaration")return true;if(current.type==="VariableDeclaration"||current.type==="VariableDeclarator"){current=current.parent;continue}break}return false}function isPropertyKey(node){const parent=node.parent;return parent?.type==="Property"&&parent.key===node}return{Literal(node){if(typeof node.value!=="number")return;const value=node.value;if(isIgnoredNumber(value))return;if(!isFinite(value))return;if(isVariableDeclarator(node))return;if(isExportedConst(node))return;if(isArrayIndex(node))return;if(isObjectPropertyValue(node))return;if(isLoopBound(node))return;if(isLengthComparison(node))return;if(isDefaultValue(node))return;if(isEnumMember(node))return;if(isBitwiseContext(node))return;if(isPropertyKey(node))return;if(isNumericDataArray(node))return;const constName=constNameFor(value);const sourceCode=context.sourceCode;context.report({node,messageId:"noMagicNumber",data:{value:String(value)},suggest:[{messageId:"extractConst",data:{value:String(value),constName},fix(fixer){const stmt=nearestStatement(node);if(!stmt)return null;const firstToken=sourceCode.getFirstToken(stmt);if(!firstToken)return null;const col=firstToken.loc.start.column;const indent=" ".repeat(col);return[fixer.insertTextBefore(stmt,`const ${constName} = ${value};
2
2
  ${indent}`),fixer.replaceText(node,constName)]}}]})}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.preferCodePoint=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.preferCodePoint=(0,eslint_devkit_1.createRule)({name:"prefer-code-point",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/prefer-code-point.md",description:"Prefer codePointAt over charCodeAt for proper Unicode character handling"},messages:{preferCodePoint:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Prefer codePointAt",description:"Use codePointAt instead of charCodeAt for Unicode safety",severity:"MEDIUM",fix:"Replace charCodeAt() with codePointAt()",documentationLink:"https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-code-point.md"})},schema:[{type:"object",properties:{},additionalProperties:false}]},defaultOptions:[],create(context){function isInAllowedContext(node){return false}function shouldIgnoreCall(node){if(node.callee.type==="MemberExpression"){if(node.callee.optional){return true}if(node.callee.computed&&node.callee.property.type==="Identifier"){return true}}return false}function isCharCodeAtCall(node){if(node.callee.type==="MemberExpression"){if(node.callee.property.type==="Identifier"&&node.callee.property.name==="charCodeAt"){return true}if(node.callee.computed&&node.callee.property.type==="Literal"&&node.callee.property.value==="charCodeAt"){return true}}return false}return{CallExpression(node){if(isCharCodeAtCall(node)&&!isInAllowedContext(node)&&!shouldIgnoreCall(node)){context.report({node,messageId:"preferCodePoint",data:{current:"charCodeAt()",fix:"codePointAt()"}})}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.preferCodePoint=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.preferCodePoint=(0,eslint_devkit_1.createRule)({name:"prefer-code-point",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/prefer-code-point.md",description:"Prefer codePointAt over charCodeAt for proper Unicode character handling"},messages:{preferCodePoint:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Prefer codePointAt",description:"Use codePointAt instead of charCodeAt for Unicode safety",severity:"MEDIUM",fix:"Replace charCodeAt() with codePointAt()",documentationLink:"https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-code-point.md"})},schema:[{type:"object",properties:{},additionalProperties:false}]},defaultOptions:[],create(context){function isInAllowedContext(node){return false}function shouldIgnoreCall(node){if(node.callee.type==="MemberExpression"){if(node.callee.optional){return true}}return false}function isCharCodeAtCall(node){if(node.callee.type==="MemberExpression"){if((0,eslint_devkit_1.propertyName)(node.callee)==="charCodeAt"){return true}}return false}return{CallExpression(node){if(isCharCodeAtCall(node)&&!isInAllowedContext(node)&&!shouldIgnoreCall(node)){context.report({node,messageId:"preferCodePoint",data:{current:"charCodeAt()",fix:"codePointAt()"}})}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.preferDomNodeTextContent=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.preferDomNodeTextContent=(0,eslint_devkit_1.createRule)({name:"prefer-dom-node-text-content",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/prefer-dom-node-text-content.md",description:"Prefer textContent over innerText for better performance and reliability"},hasSuggestions:true,messages:{preferDomNodeTextContent:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.PERFORMANCE,issueName:"DOM Text Access",description:"Use textContent instead of innerText",severity:"MEDIUM",fix:"Replace innerText with textContent",documentationLink:"https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-dom-node-text-content.md"})},schema:[{type:"object",properties:{},additionalProperties:false}]},defaultOptions:[],create(context){function isInAllowedContext(){return false}function isInnerTextAccess(node){if(node.property.type==="Identifier"&&node.property.name==="innerText"){return true}if(node.computed&&node.property.type==="Literal"&&node.property.value==="innerText"){return true}return false}return{MemberExpression(node){if(isInnerTextAccess(node)&&!isInAllowedContext()){context.report({node,messageId:"preferDomNodeTextContent",data:{current:"innerText",fix:"textContent"},suggest:[{messageId:"preferDomNodeTextContent",data:{replacement:"textContent",suggestion:"Replace with textContent"},fix(fixer){if(node.property.type==="Identifier"){return fixer.replaceText(node.property,"textContent")}else if(node.property.type==="Literal"&&node.property.value==="innerText"){return fixer.replaceText(node.property,'"textContent"')}return null}}]})}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.preferDomNodeTextContent=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.preferDomNodeTextContent=(0,eslint_devkit_1.createRule)({name:"prefer-dom-node-text-content",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/prefer-dom-node-text-content.md",description:"Prefer textContent over innerText for better performance and reliability"},hasSuggestions:true,messages:{preferDomNodeTextContent:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.PERFORMANCE,issueName:"DOM Text Access",description:"Use textContent instead of innerText",severity:"MEDIUM",fix:"Replace innerText with textContent",documentationLink:"https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-dom-node-text-content.md"})},schema:[{type:"object",properties:{},additionalProperties:false}]},defaultOptions:[],create(context){function isInAllowedContext(){return false}function isInnerTextAccess(node){if((0,eslint_devkit_1.propertyName)(node)==="innerText"){return true}return false}return{MemberExpression(node){if(isInnerTextAccess(node)&&!isInAllowedContext()){context.report({node,messageId:"preferDomNodeTextContent",data:{current:"innerText",fix:"textContent"},suggest:[{messageId:"preferDomNodeTextContent",data:{replacement:"textContent",suggestion:"Replace with textContent"},fix(fixer){if(node.property.type==="Identifier"){return fixer.replaceText(node.property,"textContent")}else if(node.property.type==="Literal"&&node.property.value==="innerText"){return fixer.replaceText(node.property,'"textContent"')}return null}}]})}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDeprecatedApi=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.noDeprecatedApi=(0,eslint_devkit_2.createRule)({name:"no-deprecated-api",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/no-deprecated-api.md",description:"Prevent usage of deprecated APIs with migration context",cwe:"CWE-1078",cvss:7.5},fixable:"code",hasSuggestions:true,messages:{deprecatedAPI:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.DEPRECATION,issueName:"Deprecated API",cwe:"CWE-1078",description:"Deprecated API detected",severity:"HIGH",fix:"Migrate to recommended alternative with timeline guidance",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference"}),useReplacement:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Replacement",description:"Replace with recommended API",severity:"LOW",fix:"Replace with {{replacement}}",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference"})},schema:[{type:"object",properties:{apis:{type:"array",items:{type:"object",properties:{name:{type:"string"},replacement:{type:"string"},deprecatedSince:{type:"string"},removalDate:{type:"string"},reason:{type:"string"},migrationGuide:{type:"string"}},required:["name","replacement","deprecatedSince","reason"]}},warnDaysBeforeRemoval:{type:"number",default:90}},additionalProperties:false}]},defaultOptions:[{apis:[],warnDaysBeforeRemoval:90}],create(context){const options=context.options[0]||{};const{apis=[],warnDaysBeforeRemoval=90}=options;if(!apis||apis.length===0){return{}}const calculateDaysRemaining=removalDate=>{if(!removalDate)return null;const removal=new Date(removalDate);const now=new Date;const diff=removal.getTime()-now.getTime();return Math.ceil(diff/(1e3*60*60*24))};const getUrgencyLevel=daysRemaining=>{if(daysRemaining===null)return"low";if(daysRemaining<0)return"critical";if(daysRemaining<30)return"critical";if(daysRemaining<warnDaysBeforeRemoval)return"high";return"medium"};return{MemberExpression(node){if(node.property.type!=="Identifier")return;const apiName=node.property.name;const deprecatedApi=apis.find(api=>api.name===apiName);if(!deprecatedApi)return;const daysRemaining=calculateDaysRemaining(deprecatedApi.removalDate);const urgency=getUrgencyLevel(daysRemaining);context.report({node,messageId:"deprecatedAPI",data:{apiName:deprecatedApi.name,replacement:deprecatedApi.replacement,deprecatedSince:deprecatedApi.deprecatedSince,daysRemaining:String(daysRemaining??"Unknown"),urgency:urgency.toUpperCase(),migrationGuide:deprecatedApi.migrationGuide||"See documentation"},suggest:[{messageId:"useReplacement",data:{replacement:deprecatedApi.replacement},fix:fixer=>{return fixer.replaceText(node.property,deprecatedApi.replacement)}}]})},CallExpression(node){if(node.callee.type!=="Identifier")return;const apiName=node.callee.name;const deprecatedApi=apis.find(api=>api.name===apiName);if(!deprecatedApi)return;const daysRemaining=calculateDaysRemaining(deprecatedApi.removalDate);const urgency=getUrgencyLevel(daysRemaining);context.report({node,messageId:"deprecatedAPI",data:{apiName:deprecatedApi.name,replacement:deprecatedApi.replacement,deprecatedSince:deprecatedApi.deprecatedSince,daysRemaining:String(daysRemaining??"Unknown"),urgency:urgency.toUpperCase(),migrationGuide:deprecatedApi.migrationGuide||"See documentation"},suggest:[{messageId:"useReplacement",data:{replacement:deprecatedApi.replacement},fix:fixer=>{return fixer.replaceText(node.callee,deprecatedApi.replacement)}}]})}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDeprecatedApi=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.noDeprecatedApi=(0,eslint_devkit_2.createRule)({name:"no-deprecated-api",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/no-deprecated-api.md",description:"Prevent usage of deprecated APIs with migration context",cwe:"CWE-1078",cvss:7.5},fixable:"code",hasSuggestions:true,messages:{deprecatedAPI:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.DEPRECATION,issueName:"Deprecated API",cwe:"CWE-1078",description:"Deprecated API detected",severity:"HIGH",fix:"Migrate to recommended alternative with timeline guidance",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference"}),useReplacement:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Replacement",description:"Replace with recommended API",severity:"LOW",fix:"Replace with {{replacement}}",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference"})},schema:[{type:"object",properties:{apis:{type:"array",items:{type:"object",properties:{name:{type:"string"},replacement:{type:"string"},deprecatedSince:{type:"string"},removalDate:{type:"string"},reason:{type:"string"},migrationGuide:{type:"string"}},required:["name","replacement","deprecatedSince","reason"]}},warnDaysBeforeRemoval:{type:"number",default:90}},additionalProperties:false}]},defaultOptions:[{apis:[],warnDaysBeforeRemoval:90}],create(context){const options=context.options[0]||{};const{apis=[],warnDaysBeforeRemoval=90}=options;if(!apis||apis.length===0){return{}}const calculateDaysRemaining=removalDate=>{if(!removalDate)return null;const removal=new Date(removalDate);const now=new Date;const diff=removal.getTime()-now.getTime();return Math.ceil(diff/(1e3*60*60*24))};const getUrgencyLevel=daysRemaining=>{if(daysRemaining===null)return"low";if(daysRemaining<0)return"critical";if(daysRemaining<30)return"critical";if(daysRemaining<warnDaysBeforeRemoval)return"high";return"medium"};return{MemberExpression(node){const apiName=(0,eslint_devkit_2.propertyName)(node);if(apiName===null)return;const deprecatedApi=apis.find(api=>api.name===apiName);if(!deprecatedApi)return;const daysRemaining=calculateDaysRemaining(deprecatedApi.removalDate);const urgency=getUrgencyLevel(daysRemaining);context.report({node,messageId:"deprecatedAPI",data:{apiName:deprecatedApi.name,replacement:deprecatedApi.replacement,deprecatedSince:deprecatedApi.deprecatedSince,daysRemaining:String(daysRemaining??"Unknown"),urgency:urgency.toUpperCase(),migrationGuide:deprecatedApi.migrationGuide||"See documentation"},suggest:[{messageId:"useReplacement",data:{replacement:deprecatedApi.replacement},fix:fixer=>{return fixer.replaceText(node.property,node.computed?JSON.stringify(deprecatedApi.replacement):deprecatedApi.replacement)}}]})},CallExpression(node){if(node.callee.type!=="Identifier")return;const apiName=node.callee.name;const deprecatedApi=apis.find(api=>api.name===apiName);if(!deprecatedApi)return;const daysRemaining=calculateDaysRemaining(deprecatedApi.removalDate);const urgency=getUrgencyLevel(daysRemaining);context.report({node,messageId:"deprecatedAPI",data:{apiName:deprecatedApi.name,replacement:deprecatedApi.replacement,deprecatedSince:deprecatedApi.deprecatedSince,daysRemaining:String(daysRemaining??"Unknown"),urgency:urgency.toUpperCase(),migrationGuide:deprecatedApi.migrationGuide||"See documentation"},suggest:[{messageId:"useReplacement",data:{replacement:deprecatedApi.replacement},fix:fixer=>{return fixer.replaceText(node.callee,deprecatedApi.replacement)}}]})}}}});