eslint-plugin-reliability 4.1.9 → 4.1.11

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-reliability",
3
- "version": "4.1.9",
3
+ "version": "4.1.11",
4
4
  "description": "ESLint plugin for runtime reliability — enforces error handling, network timeouts, null checks, and safe type narrowing.",
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.19.0"
53
+ "@interlace/eslint-devkit": "^1.19.6"
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-unhandled-promise"(){return require("./rules/error-handling/no-unhandled-promise").noUnhandledPromise},get"no-silent-errors"(){return require("./rules/error-handling/no-silent-errors").noSilentErrors},get"no-missing-error-context"(){return require("./rules/error-handling/no-missing-error-context").noMissingErrorContext},get"error-message"(){return require("./rules/error-handling/error-message").errorMessage},get"no-missing-null-checks"(){return require("./rules/reliability/no-missing-null-checks").noMissingNullChecks},get"no-unsafe-type-narrowing"(){return require("./rules/reliability/no-unsafe-type-narrowing").noUnsafeTypeNarrowing},get"require-network-timeout"(){return require("./rules/reliability/require-network-timeout").requireNetworkTimeout},get"no-await-in-loop"(){return require("./rules/reliability/no-await-in-loop").noAwaitInLoop},get"no-jsdoc-terminator-in-example"(){return require("./rules/reliability/no-jsdoc-terminator-in-example").noJsdocTerminatorInExample},get"error-handling/no-unhandled-promise"(){return require("./rules/error-handling/no-unhandled-promise").noUnhandledPromise},get"error-handling/no-silent-errors"(){return require("./rules/error-handling/no-silent-errors").noSilentErrors},get"error-handling/no-missing-error-context"(){return require("./rules/error-handling/no-missing-error-context").noMissingErrorContext},get"error-handling/error-message"(){return require("./rules/error-handling/error-message").errorMessage},get"reliability/no-missing-null-checks"(){return require("./rules/reliability/no-missing-null-checks").noMissingNullChecks},get"reliability/no-unsafe-type-narrowing"(){return require("./rules/reliability/no-unsafe-type-narrowing").noUnsafeTypeNarrowing},get"reliability/require-network-timeout"(){return require("./rules/reliability/require-network-timeout").requireNetworkTimeout},get"reliability/no-await-in-loop"(){return require("./rules/reliability/no-await-in-loop").noAwaitInLoop},get"reliability/no-jsdoc-terminator-in-example"(){return require("./rules/reliability/no-jsdoc-terminator-in-example").noJsdocTerminatorInExample}};(0,eslint_devkit_1.withCanonicalDocsUrls)("plugin-reliability",exports.rules);exports.plugin={meta:{name:"eslint-plugin-reliability",version:"4.1.9"},rules:exports.rules};exports.configs={recommended:{plugins:{reliability:exports.plugin},rules:{"reliability/no-silent-errors":"error","reliability/no-missing-null-checks":"warn","reliability/require-network-timeout":"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-unhandled-promise"(){return require("./rules/error-handling/no-unhandled-promise").noUnhandledPromise},get"no-silent-errors"(){return require("./rules/error-handling/no-silent-errors").noSilentErrors},get"no-missing-error-context"(){return require("./rules/error-handling/no-missing-error-context").noMissingErrorContext},get"error-message"(){return require("./rules/error-handling/error-message").errorMessage},get"no-missing-null-checks"(){return require("./rules/reliability/no-missing-null-checks").noMissingNullChecks},get"no-unsafe-type-narrowing"(){return require("./rules/reliability/no-unsafe-type-narrowing").noUnsafeTypeNarrowing},get"require-network-timeout"(){return require("./rules/reliability/require-network-timeout").requireNetworkTimeout},get"no-await-in-loop"(){return require("./rules/reliability/no-await-in-loop").noAwaitInLoop},get"no-jsdoc-terminator-in-example"(){return require("./rules/reliability/no-jsdoc-terminator-in-example").noJsdocTerminatorInExample},get"error-handling/no-unhandled-promise"(){return require("./rules/error-handling/no-unhandled-promise").noUnhandledPromise},get"error-handling/no-silent-errors"(){return require("./rules/error-handling/no-silent-errors").noSilentErrors},get"error-handling/no-missing-error-context"(){return require("./rules/error-handling/no-missing-error-context").noMissingErrorContext},get"error-handling/error-message"(){return require("./rules/error-handling/error-message").errorMessage},get"reliability/no-missing-null-checks"(){return require("./rules/reliability/no-missing-null-checks").noMissingNullChecks},get"reliability/no-unsafe-type-narrowing"(){return require("./rules/reliability/no-unsafe-type-narrowing").noUnsafeTypeNarrowing},get"reliability/require-network-timeout"(){return require("./rules/reliability/require-network-timeout").requireNetworkTimeout},get"reliability/no-await-in-loop"(){return require("./rules/reliability/no-await-in-loop").noAwaitInLoop},get"reliability/no-jsdoc-terminator-in-example"(){return require("./rules/reliability/no-jsdoc-terminator-in-example").noJsdocTerminatorInExample}};(0,eslint_devkit_1.withCanonicalDocsUrls)("plugin-reliability",exports.rules);exports.plugin={meta:{name:"eslint-plugin-reliability",version:"4.1.11"},rules:exports.rules};exports.configs={recommended:{plugins:{reliability:exports.plugin},rules:{"reliability/no-silent-errors":"error","reliability/no-missing-null-checks":"warn","reliability/require-network-timeout":"error"}}};exports.default=exports.plugin;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noMissingErrorContext=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const BUILTIN_ERROR_CONSTRUCTORS=new Set(["Error","TypeError","RangeError","SyntaxError","ReferenceError","EvalError","URIError","AggregateError"]);function isProvablyString(node){if(node.type==="TemplateLiteral")return true;if(node.type==="Literal"){return typeof node.value==="string"&&node.value.length>0}if(node.type==="LogicalExpression"){return node.operator==="&&"?isProvablyString(node.right):isProvablyString(node.left)||isProvablyString(node.right)}if(node.type==="ConditionalExpression"){return isProvablyString(node.consequent)||isProvablyString(node.alternate)}if(node.type==="BinaryExpression"&&node.operator==="+"){return isProvablyString(node.left)||isProvablyString(node.right)}return false}function hasErrorMessage(node){if(!node.argument){return false}if(node.argument.type==="Identifier"&&node.argument.name!=="undefined"&&node.argument.name!=="NaN"&&node.argument.name!=="Infinity"){return true}const constructed=node.argument.type==="NewExpression"||node.argument.type==="CallExpression"?node.argument:null;if(constructed!==null&&constructed.callee.type==="Identifier"){if(constructed.type==="CallExpression"&&!BUILTIN_ERROR_CONSTRUCTORS.has(constructed.callee.name)){return false}if(constructed.arguments.length===0){return false}const firstArg=constructed.arguments[0];const staticText=(0,eslint_devkit_1.staticString)(firstArg);if(staticText!==null){return staticText.length>0}if(!BUILTIN_ERROR_CONSTRUCTORS.has(constructed.callee.name)){return true}return isProvablyString(firstArg)}if(node.argument.type==="Literal"&&typeof node.argument.value==="string"){return node.argument.value.length>0}if(node.argument.type==="TemplateLiteral"){return true}return false}function hasErrorStack(node){if(!node.argument){return false}if(node.argument.type==="Identifier"&&node.argument.name!=="undefined"&&node.argument.name!=="NaN"&&node.argument.name!=="Infinity"){return true}if((node.argument.type==="NewExpression"||node.argument.type==="CallExpression")&&node.argument.callee.type==="Identifier"){const calleeName=node.argument.callee.name;if(calleeName==="Error"||calleeName.endsWith("Error")){return true}}return false}exports.noMissingErrorContext=(0,eslint_devkit_2.createRule)({name:"no-missing-error-context",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-missing-error-context.md",description:"Detects thrown errors without context"},hasSuggestions:true,messages:{missingErrorContext:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing error context",description:"Thrown error missing {{missing}}",severity:"MEDIUM",fix:"Add error message and use Error class for stack trace",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-1128/"}),addErrorMessage:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add Error Message",description:"Add descriptive error message",severity:"LOW",fix:'throw new Error("descriptive message")',documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error"}),addErrorStack:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Error Class",description:"Use Error class for stack trace",severity:"LOW",fix:"throw new Error(message) instead of throw message",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error"}),useErrorClass:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Specific Error",description:"Use specific error class",severity:"LOW",fix:'throw new TypeError("message") or throw new RangeError("message")',documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError"})},schema:[{type:"object",properties:{requireMessage:{type:"boolean",default:true,description:"Require error message"},requireStackTrace:{type:"boolean",default:false,description:"Require stack trace"},ignoreInTests:{type:"boolean",default:true,description:"Ignore in test files"}},additionalProperties:false}]},defaultOptions:[{requireMessage:true,requireStackTrace:false,ignoreInTests:true}],create(context,[options={}]){const{requireMessage=true,requireStackTrace=false,ignoreInTests=true}=options||{};const filename=context.filename;const isTestFile=ignoreInTests&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);if(isTestFile){return{}}function checkThrowStatement(node){const missing=[];if(requireMessage&&!hasErrorMessage(node)){missing.push("message")}if(requireStackTrace&&!hasErrorStack(node)){missing.push("stack trace")}if(missing.length===0){return}context.report({node,messageId:"missingErrorContext",data:{missing:missing.join(" and ")},suggest:[{messageId:"addErrorMessage",fix:()=>null},{messageId:"addErrorStack",fix:()=>null},{messageId:"useErrorClass",fix:()=>null}]})}return{ThrowStatement:checkThrowStatement}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noMissingErrorContext=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const BUILTIN_ERROR_CONSTRUCTORS=new Set(["Error","TypeError","RangeError","SyntaxError","ReferenceError","EvalError","URIError","AggregateError"]);function isProvablyString(node,sourceCode){if(node.type==="TemplateLiteral")return true;if(node.type==="Identifier"){const init=constInitializer(node,sourceCode);return init!==null&&isProvablyString(init,sourceCode)}if(node.type==="Literal"){return typeof node.value==="string"&&node.value.length>0}if(node.type==="LogicalExpression"){return node.operator==="&&"?isProvablyString(node.right,sourceCode):isProvablyString(node.left,sourceCode)||isProvablyString(node.right,sourceCode)}if(node.type==="ConditionalExpression"){return isProvablyString(node.consequent,sourceCode)||isProvablyString(node.alternate,sourceCode)}if(node.type==="BinaryExpression"&&node.operator==="+"){return isProvablyString(node.left,sourceCode)||isProvablyString(node.right,sourceCode)}return false}function constInitializer(node,sourceCode){let scope=sourceCode.getScope(node);let variable;while(scope&&!variable){variable=scope.set?.get(node.name);scope=scope.upper}if(!variable||variable.defs.length!==1)return null;const def=variable.defs[0];if(def?.type!=="Variable"||def.parent.kind!=="const")return null;return def.node.init??null}function hasErrorMessage(node,sourceCode){if(!node.argument){return false}if(node.argument.type==="Identifier"&&node.argument.name!=="undefined"&&node.argument.name!=="NaN"&&node.argument.name!=="Infinity"){return true}const constructed=node.argument.type==="NewExpression"||node.argument.type==="CallExpression"?node.argument:null;if(constructed!==null&&constructed.callee.type==="Identifier"){if(constructed.type==="CallExpression"&&!BUILTIN_ERROR_CONSTRUCTORS.has(constructed.callee.name)){return false}if(constructed.arguments.length===0){return false}const firstArg=constructed.arguments[0];const staticText=(0,eslint_devkit_1.staticString)(firstArg);if(staticText!==null){return staticText.length>0}if(!BUILTIN_ERROR_CONSTRUCTORS.has(constructed.callee.name)){return true}return isProvablyString(firstArg,sourceCode)}if(node.argument.type==="Literal"&&typeof node.argument.value==="string"){return node.argument.value.length>0}if(node.argument.type==="TemplateLiteral"){return true}return false}function hasErrorStack(node){if(!node.argument){return false}if(node.argument.type==="Identifier"&&node.argument.name!=="undefined"&&node.argument.name!=="NaN"&&node.argument.name!=="Infinity"){return true}if((node.argument.type==="NewExpression"||node.argument.type==="CallExpression")&&node.argument.callee.type==="Identifier"){const calleeName=node.argument.callee.name;if(calleeName==="Error"||calleeName.endsWith("Error")){return true}}return false}exports.noMissingErrorContext=(0,eslint_devkit_2.createRule)({name:"no-missing-error-context",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-missing-error-context.md",description:"Detects thrown errors without context"},hasSuggestions:true,messages:{missingErrorContext:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing error context",description:"Thrown error missing {{missing}}",severity:"MEDIUM",fix:"Add error message and use Error class for stack trace",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-1128/"}),addErrorMessage:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add Error Message",description:"Add descriptive error message",severity:"LOW",fix:'throw new Error("descriptive message")',documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error"}),addErrorStack:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Error Class",description:"Use Error class for stack trace",severity:"LOW",fix:"throw new Error(message) instead of throw message",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error"}),useErrorClass:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Specific Error",description:"Use specific error class",severity:"LOW",fix:'throw new TypeError("message") or throw new RangeError("message")',documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError"})},schema:[{type:"object",properties:{requireMessage:{type:"boolean",default:true,description:"Require error message"},requireStackTrace:{type:"boolean",default:false,description:"Require stack trace"},ignoreInTests:{type:"boolean",default:true,description:"Ignore in test files"}},additionalProperties:false}]},defaultOptions:[{requireMessage:true,requireStackTrace:false,ignoreInTests:true}],create(context,[options={}]){const{requireMessage=true,requireStackTrace=false,ignoreInTests=true}=options||{};const filename=context.filename;const isTestFile=ignoreInTests&&/\.(test|spec)\.[cm]?[jt]sx?$/.test(filename);if(isTestFile){return{}}function checkThrowStatement(node){const missing=[];if(requireMessage&&!hasErrorMessage(node,context.sourceCode)){missing.push("message")}if(requireStackTrace&&!hasErrorStack(node)){missing.push("stack trace")}if(missing.length===0){return}context.report({node,messageId:"missingErrorContext",data:{missing:missing.join(" and ")},suggest:[{messageId:"addErrorMessage",fix:()=>null},{messageId:"addErrorStack",fix:()=>null},{messageId:"useErrorClass",fix:()=>null}]})}return{ThrowStatement:checkThrowStatement}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSilentErrors=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");function isEmptyCatchBlock(catchClause){const body=catchClause.body;if(!body||body.type!=="BlockStatement"){return true}const statements=body.body;if(statements.length===0){return true}const nonEmptyStatements=statements.filter(stmt=>stmt.type!=="EmptyStatement");return nonEmptyStatements.length===0}function hasExplanatoryComment(catchClause,sourceCode){const comments=sourceCode.getAllComments();const catchStart=catchClause.loc?.start;if(!catchStart||!comments.length){return false}const explanatoryPatterns=[/intentional/i,/expected/i,/ignore/i,/silent/i,/noop/i,/no-op/i,/by design/i,/known issue/i,/legacy/i,/third.?party/i,/framework/i,/library/i,/not implemented/i,/todo/i,/fixme/i];for(const comment of comments){const distance=catchStart.line-comment.loc.end.line;if(distance<0||distance>2)continue;const commentText=comment.value.toLowerCase();if(explanatoryPatterns.some(pattern=>pattern.test(commentText))){return true}}for(const comment of sourceCode.getCommentsInside(catchClause.body)){const commentText=comment.value.toLowerCase();if(explanatoryPatterns.some(pattern=>pattern.test(commentText))){return true}}return false}exports.noSilentErrors=(0,eslint_devkit_2.createRule)({name:"no-silent-errors",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-silent-errors.md",description:"Detects empty catch blocks"},hasSuggestions:true,messages:{silentError:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Silent error",description:"Empty catch block detected - errors are silently ignored",severity:"MEDIUM",fix:"Add error logging or handling in catch block",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-1186/"}),addErrorLogging:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add Error Logging",description:"Add error logging to catch block",severity:"LOW",fix:"catch (error) { console.error(error); }",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-1186/"}),addErrorHandling:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add Error Handling",description:"Add error handling to catch block",severity:"LOW",fix:"catch (error) { handleError(error); }",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-1186/"}),rethrowError:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Rethrow Error",description:"Rethrow error if needed",severity:"LOW",fix:"catch (error) { throw new CustomError(error); }",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-1186/"})},schema:[{type:"object",properties:{allowWithComment:{type:"boolean",default:false,description:"Allow empty catch if comment explains why"},ignoreInTests:{type:"boolean",default:true,description:"Ignore in test files"}},additionalProperties:false}]},defaultOptions:[{allowWithComment:false,ignoreInTests:true}],create(context,[options={}]){const{allowWithComment=false,ignoreInTests=true}=options||{};const filename=context.filename;const isTestFile=ignoreInTests&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);if(isTestFile){return{}}const sourceCode=context.sourceCode;function checkCatchClause(node){if(!isEmptyCatchBlock(node)){return}if(allowWithComment&&hasExplanatoryComment(node,sourceCode)){return}context.report({node,messageId:"silentError",suggest:[{messageId:"addErrorLogging",fix:()=>null},{messageId:"addErrorHandling",fix:()=>null},{messageId:"rethrowError",fix:()=>null}]})}return{CatchClause:checkCatchClause}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSilentErrors=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");function isEmptyCatchBlock(catchClause){const body=catchClause.body;if(!body||body.type!=="BlockStatement"){return true}const statements=body.body;if(statements.length===0){return true}const nonEmptyStatements=statements.filter(stmt=>stmt.type!=="EmptyStatement");return nonEmptyStatements.length===0}function hasExplanatoryComment(catchClause,sourceCode){const comments=sourceCode.getAllComments();const catchStart=catchClause.loc?.start;if(!catchStart||!comments.length){return false}const explanatoryPatterns=[/intentional/i,/expected/i,/ignore/i,/silent/i,/noop/i,/no-op/i,/by design/i,/known issue/i,/legacy/i,/third.?party/i,/framework/i,/library/i,/not implemented/i,/todo/i,/fixme/i];for(const comment of comments){const distance=catchStart.line-comment.loc.end.line;if(distance<0||distance>2)continue;const commentText=comment.value.toLowerCase();if(explanatoryPatterns.some(pattern=>pattern.test(commentText))){return true}}for(const comment of sourceCode.getCommentsInside(catchClause.body)){const commentText=comment.value.toLowerCase();if(explanatoryPatterns.some(pattern=>pattern.test(commentText))){return true}}return false}exports.noSilentErrors=(0,eslint_devkit_2.createRule)({name:"no-silent-errors",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-silent-errors.md",description:"Detects empty catch blocks"},hasSuggestions:true,messages:{silentError:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Silent error",description:"Empty catch block detected - errors are silently ignored",severity:"MEDIUM",fix:"Add error logging or handling in catch block",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-1186/"}),addErrorLogging:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add Error Logging",description:"Add error logging to catch block",severity:"LOW",fix:"catch (error) { console.error(error); }",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-1186/"}),addErrorHandling:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add Error Handling",description:"Add error handling to catch block",severity:"LOW",fix:"catch (error) { handleError(error); }",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-1186/"}),rethrowError:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Rethrow Error",description:"Rethrow error if needed",severity:"LOW",fix:"catch (error) { throw new CustomError(error); }",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-1186/"})},schema:[{type:"object",properties:{allowWithComment:{type:"boolean",default:false,description:"Allow empty catch if comment explains why"},ignoreInTests:{type:"boolean",default:true,description:"Ignore in test files"}},additionalProperties:false}]},defaultOptions:[{allowWithComment:false,ignoreInTests:true}],create(context,[options={}]){const{allowWithComment=false,ignoreInTests=true}=options||{};const filename=context.filename;const isTestFile=ignoreInTests&&/\.(test|spec)\.[cm]?[jt]sx?$/.test(filename);if(isTestFile){return{}}const sourceCode=context.sourceCode;function checkCatchClause(node){if(!isEmptyCatchBlock(node)){return}if(allowWithComment&&hasExplanatoryComment(node,sourceCode)){return}context.report({node,messageId:"silentError",suggest:[{messageId:"addErrorLogging",fix:()=>null},{messageId:"addErrorHandling",fix:()=>null},{messageId:"rethrowError",fix:()=>null}]})}return{CatchClause:checkCatchClause}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnhandledPromise=void 0;exports.hasPromiseEvidence=hasPromiseEvidence;exports.isLikelyPromiseExpression=isLikelyPromiseExpression;exports.isPromiseHandled=isPromiseHandled;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const NEVER_RETURNS_PROMISE_FUNCTIONS=new Set(["setTimeout","clearTimeout","setInterval","clearInterval","setImmediate","clearImmediate","requestAnimationFrame","cancelAnimationFrame","queueMicrotask","String","Number","Boolean","Symbol","BigInt","parseInt","parseFloat","isNaN","isFinite","Array","Object"]);const NEVER_RETURNS_PROMISE_METHODS=new Set(["log","error","warn","info","debug","trace","group","groupEnd","time","timeEnd","assert","floor","ceil","round","abs","min","max","pow","sqrt","random","sin","cos","tan","log2","log10","slice","split","join","concat","includes","indexOf","lastIndexOf","startsWith","endsWith","replace","replaceAll","trim","toLowerCase","toUpperCase","repeat","padStart","padEnd","charAt","charCodeAt","codePointAt","push","pop","shift","unshift","splice","reverse","sort","map","filter","reduce","reduceRight","forEach","every","some","find","findIndex","flat","flatMap","fill","copyWithin","entries","keys","values","parse","stringify","abort","addEventListener","removeEventListener","dispatchEvent","now","parse","UTC","from","of","isArray","isBuffer","isInteger","isFinite","isNaN","isSafeInteger","fromCharCode","fromCodePoint","raw","assign","freeze","isFrozen","create","defineProperty","defineProperties","getOwnPropertyDescriptor","getOwnPropertyNames","getPrototypeOf","setPrototypeOf","preventExtensions","isExtensible","seal","isSealed","fromEntries"]);const SYNC_NAMESPACE_OBJECTS=new Set(["Math","JSON","Date","Buffer","Array","Object","Number","String","Boolean","Symbol","BigInt","Reflect","console","process"]);const PROMISE_STATICS=new Set(["all","allSettled","any","race","resolve","reject"]);function isAsyncFunctionNode(node){if(!node)return false;return(node.type==="FunctionDeclaration"||node.type==="FunctionExpression"||node.type==="ArrowFunctionExpression")&&node.async===true}function hasPromiseEvidence(node,promiseReturning,resolveBinding){if(isAsyncFunctionNode(node.callee))return true;if(node.callee.type==="MemberExpression"){const{object}=node.callee;if(object.type==="Identifier"&&object.name==="Promise"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(node.callee),PROMISE_STATICS)){return true}const member=(0,eslint_devkit_1.propertyName)(node.callee);if(member==="then")return true;return object.type==="Identifier"&&promiseReturning.has(object.name)}if(node.callee.type==="Identifier"){if(promiseReturning.has(node.callee.name))return true;const bound=resolveBinding(node.callee.name);return isAsyncFunctionNode(bound)||isPromiseReturningAnnotation(bound)}return false}function isPromiseReturningAnnotation(node){if(node?.type!=="Identifier")return false;const annotation=node.typeAnnotation?.typeAnnotation;if(annotation?.type!=="TSFunctionType")return false;const returned=annotation.returnType?.typeAnnotation;return returned?.type==="TSTypeReference"&&returned.typeName.type==="Identifier"&&returned.typeName.name==="Promise"}function isLikelyPromiseExpression(node){if(node.type!=="CallExpression")return false;const callee=node.callee;if(callee.type==="Identifier"){const name=callee.name;if(NEVER_RETURNS_PROMISE_FUNCTIONS.has(name))return false;return true}if(callee.type==="MemberExpression"){const prop=callee.property;if(prop.type==="Identifier"){if(NEVER_RETURNS_PROMISE_METHODS.has(prop.name))return false}const obj=callee.object;if(obj.type==="Identifier"&&SYNC_NAMESPACE_OBJECTS.has(obj.name)){return false}return true}return true}function isPromiseDelegatedToCaller(node){const parent=node.parent;if(!parent)return false;if(parent.type==="ReturnStatement")return true;if(parent.type==="ArrowFunctionExpression"&&parent.body===node)return true;return false}function isInsidePromiseCallback(node){let current=node;let depth=0;const maxDepth=10;while(current&&depth<maxDepth){const parent=current.parent;if(!parent)break;if(parent.type==="ArrowFunctionExpression"||parent.type==="FunctionExpression"){const funcParent=parent.parent;if(funcParent&&funcParent.type==="CallExpression"&&funcParent.callee&&funcParent.callee.type==="MemberExpression"){const memberExpr=funcParent.callee;const settled=(0,eslint_devkit_1.propertyName)(memberExpr);if(settled!==null){const methodName=settled;if(methodName==="then"||methodName==="catch"||methodName==="finally"){return true}}}}current=parent;depth++}return false}const PROMISE_COMBINATORS=["all","allSettled","any","race"];function isPromiseCombinatorCall(node){const{callee}=node;return callee.type==="MemberExpression"&&callee.object.type==="Identifier"&&callee.object.name==="Promise"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(callee),PROMISE_COMBINATORS)}function isValueConsumed(node){let current=node;for(;;){const parent=current.parent;if(!parent)return false;switch(parent.type){case"MemberExpression":return false;case"CallExpression":if(parent.callee!==current){return current.type===eslint_devkit_1.AST_NODE_TYPES.ArrayExpression&&isPromiseCombinatorCall(parent)}break;case"ArrayExpression":break;case"TSAsExpression":case"TSNonNullExpression":break;case"UnaryExpression":if(parent.operator!=="void")return true;break;case"ExpressionStatement":return false;case"SequenceExpression":return parent.expressions[parent.expressions.length-1]===current;default:return true}current=parent}}function isThenWithRejectionHandler(node){if(node.callee.type!=="MemberExpression")return false;if((0,eslint_devkit_1.propertyName)(node.callee)!=="then")return false;const onRejected=node.arguments[1];if(onRejected===void 0)return false;let candidate=onRejected;while(candidate.type===eslint_devkit_1.AST_NODE_TYPES.TSAsExpression||candidate.type===eslint_devkit_1.AST_NODE_TYPES.TSSatisfiesExpression||candidate.type===eslint_devkit_1.AST_NODE_TYPES.TSNonNullExpression||candidate.type===eslint_devkit_1.AST_NODE_TYPES.TSTypeAssertion){candidate=candidate.expression}switch(candidate.type){case eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression:case eslint_devkit_1.AST_NODE_TYPES.FunctionExpression:case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:return true;case eslint_devkit_1.AST_NODE_TYPES.Identifier:return candidate.name!=="undefined";default:return false}}function isPromiseHandled(node){if(node.type==="Identifier"){const parent=node.parent;if(parent&&parent.type==="MemberExpression"&&parent.object===node){if(parent.property.type==="Identifier"){const methodName=parent.property.name;if(methodName==="catch"||methodName==="then"||methodName==="finally"){const memberParent=parent.parent;if(memberParent&&memberParent.type==="CallExpression"&&memberParent.callee===parent){return true}}}}}let current=node;let depth=0;const maxDepth=10;while(current&&depth<maxDepth){const parent=current.parent;if(!parent)break;if(parent.type==="MemberExpression"&&parent.object===current){if(parent.property.type==="Identifier"){const methodName=parent.property.name;if(methodName==="catch"||methodName==="then"||methodName==="finally"){const memberParent=parent.parent;if(memberParent&&memberParent.type==="CallExpression"&&memberParent.callee===parent){return true}}}}if(parent.type==="TryStatement"){return true}if(parent.type==="AwaitExpression"){return true}current=parent;depth++}return false}exports.noUnhandledPromise=(0,eslint_devkit_2.createRule)({name:"no-unhandled-promise",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-unhandled-promise.md",description:"Detects unhandled Promise rejections",cwe:"CWE-1024",cvss:7.5},hasSuggestions:true,messages:{unhandledPromise:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unhandled promise",cwe:"CWE-1024",description:"Unhandled Promise rejection detected",severity:"HIGH",fix:"Add .catch() handler or use try/catch with await",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-4635/"}),addCatch:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add catch handler",description:"Add .catch() handler to promise",severity:"LOW",fix:"promise.catch(error => console.error(error))",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch"}),useTryCatch:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use try/catch",description:"Use try/catch with await",severity:"LOW",fix:"try { await promise; } catch (error) { handle(error); }",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch"}),useAwait:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use await",description:"Use await to handle promise",severity:"LOW",fix:"await promise;",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await"})},schema:[{type:"object",properties:{ignoreInTests:{type:"boolean",default:true,description:"Ignore promises in test files"},requirePromiseEvidence:{type:"boolean",default:true,description:"Report only calls the file itself shows to be promise-producing."},promiseReturning:{type:"array",items:{type:"string"},default:["fetch"],description:"Names that return a promise and cannot be resolved from the file."},ignoreVoidExpressions:{type:"boolean",default:false,description:"Ignore promises in void expressions"}},additionalProperties:false}]},defaultOptions:[{ignoreInTests:true,ignoreVoidExpressions:false}],create(context,[options={}]){const{ignoreInTests=true,ignoreVoidExpressions=false,requirePromiseEvidence=true,promiseReturning=["fetch"]}=options||{};const promiseNames=new Set(promiseReturning);function resolveBinding(name,from){let scope=context.sourceCode.getScope(from);while(scope){const variable=scope.variables.find(v=>v.name===name);if(variable){const def=variable.defs[0];if(!def)return null;if(def.type==="Parameter"){const pattern=def.name.parent;if(pattern?.type==="AssignmentPattern"&&pattern.left===def.name){return pattern.right}return def.name}if(def.node.type==="FunctionDeclaration")return def.node;if(def.node.type==="VariableDeclarator"){return def.node.init??null}return null}scope=scope.upper}return null}const filename=context.filename;const isTestFile=ignoreInTests&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);if(isTestFile){return{}}function checkCallExpression(node){if(isInsidePromiseCallback(node)){return}if(isPromiseDelegatedToCaller(node)){return}if(node.callee.type==="MemberExpression"&&node.callee.property.type==="Identifier"){const methodName=node.callee.property.name;if(methodName==="catch"||methodName==="finally"){if(node.arguments.length>0&&node.arguments[0].type==="ArrowFunctionExpression"){const callback=node.arguments[0];if(callback.body.type==="BlockStatement"&&callback.body.body.length===0){}else{return}}else{return}}}if(!isLikelyPromiseExpression(node)){return}if(requirePromiseEvidence&&!hasPromiseEvidence(node,promiseNames,name=>resolveBinding(name,node))){return}if(isPromiseHandled(node)){return}if(isValueConsumed(node)){return}if(isThenWithRejectionHandler(node)){return}if(ignoreVoidExpressions){const parent2=node.parent;if(parent2&&parent2.type==="UnaryExpression"&&parent2.operator==="void"){return}}const parent=node.parent;if(parent&&parent.type==="CallExpression"){const grandParent=parent.parent;const chainOn=grandParent?.type==="MemberExpression"&&grandParent.object===parent?grandParent:null;const outerChainMethod=chainOn===null?null:(0,eslint_devkit_1.propertyName)(chainOn);const inPromiseChain=chainOn!==null&&chainOn.property.type==="Identifier"&&(outerChainMethod==="then"||outerChainMethod==="catch"||outerChainMethod==="finally");const isArgument=parent.arguments.some(a=>a===node);const outerIsPromise=outerChainMethod==="then"||outerChainMethod==="catch"||outerChainMethod==="finally"||hasPromiseEvidence(parent,promiseNames,name=>resolveBinding(name,parent));if(!inPromiseChain&&(!isArgument||outerIsPromise)){return}}context.report({node,messageId:"unhandledPromise",suggest:[{messageId:"addCatch",fix:()=>null},{messageId:"useTryCatch",fix:()=>null},{messageId:"useAwait",fix:()=>null}]})}function checkStatementPromise(node){const parent=node.parent;if(parent?.type!=="ExpressionStatement")return;context.report({node,messageId:"unhandledPromise"})}return{CallExpression:checkCallExpression,ImportExpression:checkStatementPromise,NewExpression(node){if(node.callee.type==="Identifier"&&node.callee.name==="Promise"){checkStatementPromise(node)}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnhandledPromise=void 0;exports.hasPromiseEvidence=hasPromiseEvidence;exports.isLikelyPromiseExpression=isLikelyPromiseExpression;exports.isPromiseHandled=isPromiseHandled;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const NEVER_RETURNS_PROMISE_FUNCTIONS=new Set(["setTimeout","clearTimeout","setInterval","clearInterval","setImmediate","clearImmediate","requestAnimationFrame","cancelAnimationFrame","queueMicrotask","String","Number","Boolean","Symbol","BigInt","parseInt","parseFloat","isNaN","isFinite","Array","Object"]);const NEVER_RETURNS_PROMISE_METHODS=new Set(["log","error","warn","info","debug","trace","group","groupEnd","time","timeEnd","assert","floor","ceil","round","abs","min","max","pow","sqrt","random","sin","cos","tan","log2","log10","slice","split","join","concat","includes","indexOf","lastIndexOf","startsWith","endsWith","replace","replaceAll","trim","toLowerCase","toUpperCase","repeat","padStart","padEnd","charAt","charCodeAt","codePointAt","push","pop","shift","unshift","splice","reverse","sort","map","filter","reduce","reduceRight","forEach","every","some","find","findIndex","flat","flatMap","fill","copyWithin","entries","keys","values","parse","stringify","abort","addEventListener","removeEventListener","dispatchEvent","now","parse","UTC","from","of","isArray","isBuffer","isInteger","isFinite","isNaN","isSafeInteger","fromCharCode","fromCodePoint","raw","assign","freeze","isFrozen","create","defineProperty","defineProperties","getOwnPropertyDescriptor","getOwnPropertyNames","getPrototypeOf","setPrototypeOf","preventExtensions","isExtensible","seal","isSealed","fromEntries"]);const SYNC_NAMESPACE_OBJECTS=new Set(["Math","JSON","Date","Buffer","Array","Object","Number","String","Boolean","Symbol","BigInt","Reflect","console","process"]);const PROMISE_STATICS=new Set(["all","allSettled","any","race","resolve","reject"]);function isAsyncFunctionNode(node){if(!node)return false;return(node.type==="FunctionDeclaration"||node.type==="FunctionExpression"||node.type==="ArrowFunctionExpression")&&node.async===true}function hasPromiseEvidence(node,promiseReturning,resolveBinding){if(isAsyncFunctionNode(node.callee))return true;if(node.callee.type==="MemberExpression"){const{object}=node.callee;if(object.type==="Identifier"&&object.name==="Promise"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(node.callee),PROMISE_STATICS)){return true}const member=(0,eslint_devkit_1.propertyName)(node.callee);if(member==="then")return true;return object.type==="Identifier"&&promiseReturning.has(object.name)}if(node.callee.type==="Identifier"){if(promiseReturning.has(node.callee.name))return true;const bound=resolveBinding(node.callee.name);return isAsyncFunctionNode(bound)||isPromiseReturningAnnotation(bound)}return false}function isPromiseReturningAnnotation(node){if(node?.type!=="Identifier")return false;const annotation=node.typeAnnotation?.typeAnnotation;if(annotation?.type!=="TSFunctionType")return false;const returned=annotation.returnType?.typeAnnotation;return returned?.type==="TSTypeReference"&&returned.typeName.type==="Identifier"&&returned.typeName.name==="Promise"}function isLikelyPromiseExpression(node){if(node.type!=="CallExpression")return false;const callee=node.callee;if(callee.type==="Identifier"){const name=callee.name;if(NEVER_RETURNS_PROMISE_FUNCTIONS.has(name))return false;return true}if(callee.type==="MemberExpression"){const prop=callee.property;if(prop.type==="Identifier"){if(NEVER_RETURNS_PROMISE_METHODS.has(prop.name))return false}const obj=callee.object;if(obj.type==="Identifier"&&SYNC_NAMESPACE_OBJECTS.has(obj.name)){return false}return true}return true}function isPromiseDelegatedToCaller(node){const parent=node.parent;if(!parent)return false;if(parent.type==="ReturnStatement")return true;if(parent.type==="ArrowFunctionExpression"&&parent.body===node)return true;return false}function isInsidePromiseCallback(node){let current=node;let depth=0;const maxDepth=10;while(current&&depth<maxDepth){const parent=current.parent;if(!parent)break;if(parent.type==="ArrowFunctionExpression"||parent.type==="FunctionExpression"){const funcParent=parent.parent;if(funcParent&&funcParent.type==="CallExpression"&&funcParent.callee&&funcParent.callee.type==="MemberExpression"){const memberExpr=funcParent.callee;const settled=(0,eslint_devkit_1.propertyName)(memberExpr);if(settled!==null){const methodName=settled;if(methodName==="then"||methodName==="catch"||methodName==="finally"){return true}}}}current=parent;depth++}return false}const PROMISE_COMBINATORS=["all","allSettled","any","race"];function isPromiseCombinatorCall(node){const{callee}=node;return callee.type==="MemberExpression"&&callee.object.type==="Identifier"&&callee.object.name==="Promise"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(callee),PROMISE_COMBINATORS)}function isValueConsumed(node){let current=node;for(;;){const parent=current.parent;if(!parent)return false;switch(parent.type){case"MemberExpression":return false;case"CallExpression":if(parent.callee!==current){return current.type===eslint_devkit_1.AST_NODE_TYPES.ArrayExpression&&isPromiseCombinatorCall(parent)}break;case"ArrayExpression":break;case"TSAsExpression":case"TSNonNullExpression":break;case"UnaryExpression":if(parent.operator!=="void")return true;break;case"ExpressionStatement":return false;case"SequenceExpression":return parent.expressions[parent.expressions.length-1]===current;default:return true}current=parent}}function isThenWithRejectionHandler(node){if(node.callee.type!=="MemberExpression")return false;if((0,eslint_devkit_1.propertyName)(node.callee)!=="then")return false;const onRejected=node.arguments[1];if(onRejected===void 0)return false;let candidate=onRejected;while(candidate.type===eslint_devkit_1.AST_NODE_TYPES.TSAsExpression||candidate.type===eslint_devkit_1.AST_NODE_TYPES.TSSatisfiesExpression||candidate.type===eslint_devkit_1.AST_NODE_TYPES.TSNonNullExpression||candidate.type===eslint_devkit_1.AST_NODE_TYPES.TSTypeAssertion){candidate=candidate.expression}switch(candidate.type){case eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression:case eslint_devkit_1.AST_NODE_TYPES.FunctionExpression:case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:return true;case eslint_devkit_1.AST_NODE_TYPES.Identifier:return candidate.name!=="undefined";default:return false}}function isPromiseHandled(node){if(node.type==="Identifier"){const parent=node.parent;if(parent&&parent.type==="MemberExpression"&&parent.object===node){if(parent.property.type==="Identifier"){const methodName=parent.property.name;if(methodName==="catch"||methodName==="then"||methodName==="finally"){const memberParent=parent.parent;if(memberParent&&memberParent.type==="CallExpression"&&memberParent.callee===parent){return true}}}}}let current=node;let depth=0;const maxDepth=10;while(current&&depth<maxDepth){const parent=current.parent;if(!parent)break;if(parent.type==="MemberExpression"&&parent.object===current){if(parent.property.type==="Identifier"){const methodName=parent.property.name;if(methodName==="catch"||methodName==="then"||methodName==="finally"){const memberParent=parent.parent;if(memberParent&&memberParent.type==="CallExpression"&&memberParent.callee===parent){return true}}}}if(parent.type==="TryStatement"){return true}if(parent.type==="AwaitExpression"){return true}current=parent;depth++}return false}exports.noUnhandledPromise=(0,eslint_devkit_2.createRule)({name:"no-unhandled-promise",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-unhandled-promise.md",description:"Detects unhandled Promise rejections",cwe:"CWE-1024",cvss:7.5},hasSuggestions:true,messages:{unhandledPromise:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unhandled promise",cwe:"CWE-1024",description:"Unhandled Promise rejection detected",severity:"HIGH",fix:"Add .catch() handler or use try/catch with await",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-4635/"}),addCatch:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add catch handler",description:"Add .catch() handler to promise",severity:"LOW",fix:"promise.catch(error => console.error(error))",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch"}),useTryCatch:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use try/catch",description:"Use try/catch with await",severity:"LOW",fix:"try { await promise; } catch (error) { handle(error); }",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch"}),useAwait:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use await",description:"Use await to handle promise",severity:"LOW",fix:"await promise;",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await"})},schema:[{type:"object",properties:{ignoreInTests:{type:"boolean",default:true,description:"Ignore promises in test files"},requirePromiseEvidence:{type:"boolean",default:true,description:"Report only calls the file itself shows to be promise-producing."},promiseReturning:{type:"array",items:{type:"string"},default:["fetch"],description:"Names that return a promise and cannot be resolved from the file."},ignoreVoidExpressions:{type:"boolean",default:false,description:"Ignore promises in void expressions"}},additionalProperties:false}]},defaultOptions:[{ignoreInTests:true,ignoreVoidExpressions:false}],create(context,[options={}]){const{ignoreInTests=true,ignoreVoidExpressions=false,requirePromiseEvidence=true,promiseReturning=["fetch"]}=options||{};const promiseNames=new Set(promiseReturning);function resolveBinding(name,from){let scope=context.sourceCode.getScope(from);while(scope){const variable=scope.variables.find(v=>v.name===name);if(variable){const def=variable.defs[0];if(!def)return null;if(def.type==="Parameter"){const pattern=def.name.parent;if(pattern?.type==="AssignmentPattern"&&pattern.left===def.name){return pattern.right}return def.name}if(def.node.type==="FunctionDeclaration")return def.node;if(def.node.type==="VariableDeclarator"){return def.node.init??null}return null}scope=scope.upper}return null}const filename=context.filename;const isTestFile=ignoreInTests&&/\.(test|spec)\.[cm]?[jt]sx?$/.test(filename);if(isTestFile){return{}}function checkCallExpression(node){if(isInsidePromiseCallback(node)){return}if(isPromiseDelegatedToCaller(node)){return}if(node.callee.type==="MemberExpression"&&node.callee.property.type==="Identifier"){const methodName=node.callee.property.name;if(methodName==="catch"||methodName==="finally"){if(node.arguments.length>0&&node.arguments[0].type==="ArrowFunctionExpression"){const callback=node.arguments[0];if(callback.body.type==="BlockStatement"&&callback.body.body.length===0){}else{return}}else{return}}}if(!isLikelyPromiseExpression(node)){return}if(requirePromiseEvidence&&!hasPromiseEvidence(node,promiseNames,name=>resolveBinding(name,node))){return}if(isPromiseHandled(node)){return}if(isValueConsumed(node)){return}if(isThenWithRejectionHandler(node)){return}if(ignoreVoidExpressions){const parent2=node.parent;if(parent2&&parent2.type==="UnaryExpression"&&parent2.operator==="void"){return}}const parent=node.parent;if(parent&&parent.type==="CallExpression"){const grandParent=parent.parent;const chainOn=grandParent?.type==="MemberExpression"&&grandParent.object===parent?grandParent:null;const outerChainMethod=chainOn===null?null:(0,eslint_devkit_1.propertyName)(chainOn);const inPromiseChain=chainOn!==null&&chainOn.property.type==="Identifier"&&(outerChainMethod==="then"||outerChainMethod==="catch"||outerChainMethod==="finally");const isArgument=parent.arguments.some(a=>a===node);const outerIsPromise=outerChainMethod==="then"||outerChainMethod==="catch"||outerChainMethod==="finally"||hasPromiseEvidence(parent,promiseNames,name=>resolveBinding(name,parent));if(!inPromiseChain&&(!isArgument||outerIsPromise)){return}}context.report({node,messageId:"unhandledPromise",suggest:[{messageId:"addCatch",fix:()=>null},{messageId:"useTryCatch",fix:()=>null},{messageId:"useAwait",fix:()=>null}]})}function checkStatementPromise(node){const parent=node.parent;if(parent?.type!=="ExpressionStatement")return;context.report({node,messageId:"unhandledPromise"})}return{CallExpression:checkCallExpression,ImportExpression:checkStatementPromise,NewExpression(node){if(node.callee.type==="Identifier"&&node.callee.name==="Promise"){checkStatementPromise(node)}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noMissingNullChecks=void 0;exports.hasNullCheck=hasNullCheck;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");function assignedValue(init){let current=init;while(current?.type==="AssignmentExpression"&&current.operator==="="){current=current.right}return current}const NULLABLE_RETURNS=new Set(["find","findLast","pop","shift","match","exec","getElementById","querySelector","closest","getAttribute","getNamedItem"]);function isShadowedBinding(scope,name){for(let s=scope;s;s=s.upper){const variable=s.variables.find(v=>v.name===name);if(variable)return variable.defs.length>0}return false}function nullabilityEvidence(ident,scope,seen=new Set){const readAt=ident.range?.[0]??Number.POSITIVE_INFINITY;if(seen.has(ident.name))return null;seen.add(ident.name);let s=scope;while(s){const variable=s.variables.find(v=>v.name===ident.name);if(!variable){s=s.upper;continue}if(variable.defs.length!==1)return null;const[def]=variable.defs;if(def.type!=="Variable"||def.node?.type!=="VariableDeclarator")return null;const declarator=def.node;const declaration=def.parent;if(declaration?.type==="VariableDeclaration"&&declaration.declare){return null}const init=assignedValue(declarator.init);if(init===null){const declParent=declarator.parent;const loopParent=declParent?.parent;if(loopParent?.type==="ForOfStatement"||loopParent?.type==="ForInStatement"){return null}const written=variable.references.some(reference=>reference.isWrite()&&!reference.init&&reference.identifier.range[0]<readAt);if(written)return null;return"declared-without-initializer"}const reassigned=variable.references.some(reference=>reference.isWrite()&&!reference.init&&reference.identifier.range[0]<readAt);if(reassigned)return null;if(init.type==="Literal"&&init.value===null)return"assigned-null";if(init.type==="Identifier"&&init.name==="undefined"&&!isShadowedBinding(s,"undefined")){return"assigned-null"}if(init.type==="Identifier"){return nullabilityEvidence(init,s,seen)}if(init.type==="ConditionalExpression"){for(const arm of[init.consequent,init.alternate]){if(arm.type==="Literal"&&arm.value===null)return"assigned-null";if(arm.type==="Identifier"&&arm.name==="undefined"&&!isShadowedBinding(s,"undefined")){return"assigned-null"}if(arm.type==="Identifier"){const viaArm=nullabilityEvidence(arm,s,seen);if(viaArm)return viaArm}if(arm.type==="CallExpression"&&arm.callee.type==="MemberExpression"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(arm.callee),NULLABLE_RETURNS)){return"nullable-return"}}return null}if(init.type==="CallExpression"&&init.callee.type==="MemberExpression"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(init.callee),NULLABLE_RETURNS)){return"nullable-return"}return null}return null}function baseHasNullabilityEvidence(objectNode,scope){let base=objectNode;while(base.type==="MemberExpression"){base=base.object}if(base.type!=="Identifier")return false;return nullabilityEvidence(base,scope)!==null}function hasNullCheck(node,sourceCode){if(node.optional){return true}const parent=node.parent;if(parent&&parent.type==="ChainExpression"){return true}if(usesNullishCoalescing(node)){return true}const objectText=sourceCode.getText(node.object);{let cur=node;for(let depth=0;depth<16;depth++){const up=cur.parent;if(!up)break;if(up.type==="FunctionDeclaration"||up.type==="FunctionExpression"||up.type==="ArrowFunctionExpression"){break}if(up.type==="LogicalExpression"&&up.operator==="&&"&&up.right===cur){const leftText=sourceCode.getText(up.left);if(chainStartsWith(leftText,objectText))return true}cur=up}}{let cur=node;for(let depth=0;depth<24;depth++){const p=cur.parent;if(!p)break;if(p.type==="FunctionDeclaration"||p.type==="FunctionExpression"||p.type==="ArrowFunctionExpression"){break}if(p.type==="ConditionalExpression"){const ternary=p;const test=ternary.test;if(ternary.consequent===cur&&(chainStartsWith(sourceCode.getText(test),objectText)||isNullCheckForObject(test,node.object,sourceCode))){return true}if(ternary.alternate===cur&&falsyGuardTargets(test).some(target=>guardsSameValue(target,node.object,sourceCode))){return true}}cur=p}}if(hasExplicitNullCheck(node,sourceCode)){return true}if(isGuardedByEarlyExit(node,sourceCode)){return true}return false}function endsInExit(stmt){switch(stmt.type){case"ReturnStatement":case"ThrowStatement":case"ContinueStatement":case"BreakStatement":return true;case"BlockStatement":{const last=stmt.body[stmt.body.length-1];return last!==void 0&&endsInExit(last)}case"TryStatement":{if(stmt.finalizer!==null&&endsInExit(stmt.finalizer))return true;if(!endsInExit(stmt.block))return false;return stmt.handler===null||endsInExit(stmt.handler.body)}default:return false}}function falsyGuardTargets(test){if(test.type==="UnaryExpression"&&test.operator==="!"){return[test.argument]}if(test.type==="BinaryExpression"&&(test.operator==="=="||test.operator==="===")){const isNil=e=>e.type==="Literal"&&e.value===null||e.type==="Identifier"&&e.name==="undefined";if(isNil(test.right))return[test.left];if(isNil(test.left))return[test.right];return[]}if(test.type==="LogicalExpression"&&test.operator==="||"){return[...falsyGuardTargets(test.left),...falsyGuardTargets(test.right)]}return[]}function rootBinding(expr,sourceCode){let base=expr;while(base.type==="MemberExpression")base=base.object;const name=base.name;for(let s=sourceCode.getScope(base);s;s=s.upper){const variable=s.variables.find(v=>v.name===name);if(variable)return variable}return null}function guardsSameValue(guarded,object,sourceCode){const binding=rootBinding(guarded,sourceCode);return binding!==null&&binding===rootBinding(object,sourceCode)&&chainStartsWith(sourceCode.getText(guarded),sourceCode.getText(object))}function statementListOf(node){switch(node.type){case"Program":case"BlockStatement":return node.body;case"SwitchCase":return node.consequent;default:return null}}function isGuardedByEarlyExit(node,sourceCode){let child=node;let parent=child.parent;while(parent){const list=statementListOf(parent);if(list){const index=list.indexOf(child);for(let i=0;i<index;i++){const stmt=list[i];if(stmt.type==="IfStatement"&&endsInExit(stmt.consequent)&&falsyGuardTargets(stmt.test).some(guarded=>guardsSameValue(guarded,node.object,sourceCode))){return true}}}child=parent;parent=child.parent}return false}function hasExplicitNullCheck(node,sourceCode){let current=node;let depth=0;const maxDepth=10;while(current&&depth<maxDepth){const parent=current.parent;if(parent&&parent.type==="IfStatement"){const test=parent.test;if(isNullCheckForObject(test,node.object,sourceCode)){return true}}current=parent;depth++}return false}function rootText(expr,sourceCode){let base=expr;for(;;){if(base.type==="MemberExpression")base=base.object;else if(base.type==="CallExpression")base=base.callee;else break}return sourceCode.getText(base)}function chainStartsWith(guard,objectText){if(guard===objectText)return true;if(!objectText.startsWith(guard))return false;const rest=objectText.slice(guard.length);return rest.startsWith(".")||rest.startsWith("[")}function rootGuards(root,object,sourceCode){return chainStartsWith(root,sourceCode.getText(object))}function isOptionalChainRootedAt(expr,object,sourceCode){return expr.type==="ChainExpression"&&rootGuards(rootText(expr.expression,sourceCode),object,sourceCode)}function narrowsToObject(test,object,sourceCode){if(test.type==="BinaryExpression"&&test.operator==="in"){return rootGuards(sourceCode.getText(test.right),object,sourceCode)}if(isOptionalChainRootedAt(test,object,sourceCode)){return true}if(test.type==="BinaryExpression"&&(test.operator==="!=="||test.operator==="!=")){const isNil=e=>e.type==="Literal"&&e.value===null||e.type==="Identifier"&&e.name==="undefined";if(isNil(test.right)){return isOptionalChainRootedAt(test.left,object,sourceCode)}if(isNil(test.left)){return isOptionalChainRootedAt(test.right,object,sourceCode)}}return false}function isNullCheckForObject(test,object,sourceCode){const objectText=sourceCode.getText(object);if(narrowsToObject(test,object,sourceCode)){return true}if(test.type==="Identifier"||test.type==="MemberExpression"){const testText=sourceCode.getText(test);if(testText===objectText||objectText.startsWith(testText+".")){return true}}if(test.type==="BinaryExpression"){const{left,right,operator}=test;if(operator==="!=="||operator==="!="||operator==="==="||operator==="=="){const leftText=sourceCode.getText(left);const rightText=sourceCode.getText(right);if(leftText===objectText&&(rightText==="null"||rightText==="undefined")||rightText===objectText&&(leftText==="null"||leftText==="undefined")){return true}}}if(test.type==="LogicalExpression"){return isNullCheckForObject(test.left,object,sourceCode)||isNullCheckForObject(test.right,object,sourceCode)}return false}function usesNullishCoalescing(node){let current=node;let depth=0;const maxDepth=5;while(current&&depth<maxDepth){const parent=current.parent;if(parent&&parent.type==="LogicalExpression"&&parent.operator==="??"){return true}current=parent;depth++}return false}exports.noMissingNullChecks=(0,eslint_devkit_2.createRule)({name:"no-missing-null-checks",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-missing-null-checks.md",description:"Detects potential null pointer dereferences",cwe:"CWE-476",cvss:7.5},hasSuggestions:true,messages:{missingNullCheck:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing null check",cwe:"CWE-476",description:"Potential null/undefined dereference detected",severity:"HIGH",fix:"Use optional chaining (?.) or add explicit null check",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-2259/"}),useOptionalChaining:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Optional Chaining",description:"Use optional chaining operator",severity:"LOW",fix:"obj?.property?.method()",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining"}),useNullishCoalescing:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Nullish Coalescing",description:"Use nullish coalescing operator",severity:"LOW",fix:"value ?? defaultValue",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing"}),addExplicitCheck:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add Explicit Check",description:"Add explicit null check",severity:"LOW",fix:"if (obj !== null) { obj.property }",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/null"})},schema:[{type:"object",properties:{ignoreInTests:{type:"boolean",default:true,description:"Ignore in test files"},requireExplicitChecks:{type:"boolean",default:false,description:"Require explicit null checks"}},additionalProperties:false}]},defaultOptions:[{ignoreInTests:true,requireExplicitChecks:false}],create(context,[options={}]){const{ignoreInTests=true}=options||{};const filename=context.filename;const isTestFile=ignoreInTests&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);if(isTestFile){return{}}const sourceCode=context.sourceCode;const services=(0,eslint_devkit_1.hasParserServices)(context)?(0,eslint_devkit_1.getParserServices)(context):null;function typeSaysNonNullable(objectNode){if(!services)return false;const type=(0,eslint_devkit_1.getTypeOfNode)(objectNode,services);if((0,eslint_devkit_1.isAnyType)(type)||(0,eslint_devkit_1.isUnknownType)(type))return false;return!(0,eslint_devkit_1.isNullableType)(type)}const reportedMemberExpressions=new Set;function getMemberExpressionKey(node){if(node.range&&Array.isArray(node.range)&&node.range.length>=2){return`me-${node.range[0]}-${node.range[1]}`}const loc=node.loc;if(loc&&loc.start){return`me-${loc.start.line}-${loc.start.column}-${loc.end?.line||loc.start.line}-${loc.end?.column||loc.start.column}`}return`me-${JSON.stringify(node).slice(0,50)}`}function checkMemberExpression(node){if(node.optional){return}const parent=node.parent;if(parent&&parent.type==="ChainExpression"){return}if(parent&&parent.type==="MemberExpression"&&parent.object===node){return}const objectNode=node.object;let shouldCheck=false;shouldCheck=baseHasNullabilityEvidence(objectNode,sourceCode.getScope(node))&&!typeSaysNonNullable(objectNode);if(shouldCheck&&!hasNullCheck(node,sourceCode)){const nodeKey=getMemberExpressionKey(node);if(reportedMemberExpressions.has(nodeKey)){return}try{reportedMemberExpressions.add(nodeKey);context.report({node,messageId:"missingNullCheck",suggest:[{messageId:"useOptionalChaining",fix:()=>null},{messageId:"useNullishCoalescing",fix:()=>null},{messageId:"addExplicitCheck",fix:()=>null}]})}catch{return}}}function checkCallExpression(node){if(node.type!=="CallExpression"){return}if(node.callee.type==="MemberExpression"){const memberExpr=node.callee;if(memberExpr.optional){return}const parent=memberExpr.parent;if(parent&&parent.type==="ChainExpression"){return}const objectNode=memberExpr.object;let shouldCheck=false;shouldCheck=baseHasNullabilityEvidence(objectNode,sourceCode.getScope(memberExpr))&&!typeSaysNonNullable(objectNode);if(shouldCheck&&!hasNullCheck(memberExpr,sourceCode)){const nodeKey=getMemberExpressionKey(memberExpr);if(reportedMemberExpressions.has(nodeKey)){return}try{reportedMemberExpressions.add(nodeKey);context.report({node:memberExpr,messageId:"missingNullCheck",suggest:[{messageId:"useOptionalChaining",fix:()=>null},{messageId:"useNullishCoalescing",fix:()=>null},{messageId:"addExplicitCheck",fix:()=>null}]})}catch{return}}}}return{MemberExpression:checkMemberExpression,CallExpression:checkCallExpression}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noMissingNullChecks=void 0;exports.hasNullCheck=hasNullCheck;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");function assignedValue(init){let current=init;while(current?.type==="AssignmentExpression"&&current.operator==="="){current=current.right}return current}const NULLABLE_RETURNS=new Set(["find","findLast","pop","shift","match","exec","getElementById","querySelector","closest","getAttribute","getNamedItem"]);function isShadowedBinding(scope,name){for(let s=scope;s;s=s.upper){const variable=s.variables.find(v=>v.name===name);if(variable)return variable.defs.length>0}return false}function nullabilityEvidence(ident,scope,seen=new Set){const readAt=ident.range?.[0]??Number.POSITIVE_INFINITY;if(seen.has(ident.name))return null;seen.add(ident.name);let s=scope;while(s){const variable=s.variables.find(v=>v.name===ident.name);if(!variable){s=s.upper;continue}if(variable.defs.length!==1)return null;const[def]=variable.defs;if(def.type!=="Variable"||def.node?.type!=="VariableDeclarator")return null;const declarator=def.node;const declaration=def.parent;if(declaration?.type==="VariableDeclaration"&&declaration.declare){return null}const init=assignedValue(declarator.init);if(init===null){const declParent=declarator.parent;const loopParent=declParent?.parent;if(loopParent?.type==="ForOfStatement"||loopParent?.type==="ForInStatement"){return null}const written=variable.references.some(reference=>reference.isWrite()&&!reference.init&&reference.identifier.range[0]<readAt);if(written)return null;return"declared-without-initializer"}const reassigned=variable.references.some(reference=>reference.isWrite()&&!reference.init&&reference.identifier.range[0]<readAt);if(reassigned)return null;if(init.type==="Literal"&&init.value===null)return"assigned-null";if(init.type==="Identifier"&&init.name==="undefined"&&!isShadowedBinding(s,"undefined")){return"assigned-null"}if(init.type==="Identifier"){return nullabilityEvidence(init,s,seen)}if(init.type==="ConditionalExpression"){for(const arm of[init.consequent,init.alternate]){if(arm.type==="Literal"&&arm.value===null)return"assigned-null";if(arm.type==="Identifier"&&arm.name==="undefined"&&!isShadowedBinding(s,"undefined")){return"assigned-null"}if(arm.type==="Identifier"){const viaArm=nullabilityEvidence(arm,s,seen);if(viaArm)return viaArm}if(arm.type==="CallExpression"&&arm.callee.type==="MemberExpression"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(arm.callee),NULLABLE_RETURNS)){return"nullable-return"}}return null}if(init.type==="CallExpression"&&init.callee.type==="MemberExpression"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(init.callee),NULLABLE_RETURNS)){return"nullable-return"}return null}return null}function baseHasNullabilityEvidence(objectNode,scope){let base=objectNode;while(base.type==="MemberExpression"){base=base.object}if(base.type!=="Identifier")return false;return nullabilityEvidence(base,scope)!==null}function hasNullCheck(node,sourceCode){if(node.optional){return true}const parent=node.parent;if(parent&&parent.type==="ChainExpression"){return true}if(usesNullishCoalescing(node)){return true}const objectText=sourceCode.getText(node.object);{let cur=node;for(let depth=0;depth<16;depth++){const up=cur.parent;if(!up)break;if(up.type==="FunctionDeclaration"||up.type==="FunctionExpression"||up.type==="ArrowFunctionExpression"){break}if(up.type==="LogicalExpression"&&up.operator==="&&"&&up.right===cur){const leftText=sourceCode.getText(up.left);if(chainStartsWith(leftText,objectText))return true}cur=up}}{let cur=node;for(let depth=0;depth<24;depth++){const p=cur.parent;if(!p)break;if(p.type==="FunctionDeclaration"||p.type==="FunctionExpression"||p.type==="ArrowFunctionExpression"){break}if(p.type==="ConditionalExpression"){const ternary=p;const test=ternary.test;if(ternary.consequent===cur&&(chainStartsWith(sourceCode.getText(test),objectText)||isNullCheckForObject(test,node.object,sourceCode))){return true}if(ternary.alternate===cur&&falsyGuardTargets(test).some(target=>guardsSameValue(target,node.object,sourceCode))){return true}}cur=p}}if(hasExplicitNullCheck(node,sourceCode)){return true}if(isGuardedByEarlyExit(node,sourceCode)){return true}return false}function endsInExit(stmt){switch(stmt.type){case"ReturnStatement":case"ThrowStatement":case"ContinueStatement":case"BreakStatement":return true;case"BlockStatement":{const last=stmt.body[stmt.body.length-1];return last!==void 0&&endsInExit(last)}case"TryStatement":{if(stmt.finalizer!==null&&endsInExit(stmt.finalizer))return true;if(!endsInExit(stmt.block))return false;return stmt.handler===null||endsInExit(stmt.handler.body)}default:return false}}function falsyGuardTargets(test){if(test.type==="UnaryExpression"&&test.operator==="!"){return[test.argument]}if(test.type==="BinaryExpression"&&(test.operator==="=="||test.operator==="===")){const isNil=e=>e.type==="Literal"&&e.value===null||e.type==="Identifier"&&e.name==="undefined";if(isNil(test.right))return[test.left];if(isNil(test.left))return[test.right];return[]}if(test.type==="LogicalExpression"&&test.operator==="||"){return[...falsyGuardTargets(test.left),...falsyGuardTargets(test.right)]}return[]}function rootBinding(expr,sourceCode){let base=expr;while(base.type==="MemberExpression")base=base.object;const name=base.name;for(let s=sourceCode.getScope(base);s;s=s.upper){const variable=s.variables.find(v=>v.name===name);if(variable)return variable}return null}function guardsSameValue(guarded,object,sourceCode){const binding=rootBinding(guarded,sourceCode);return binding!==null&&binding===rootBinding(object,sourceCode)&&chainStartsWith(sourceCode.getText(guarded),sourceCode.getText(object))}function statementListOf(node){switch(node.type){case"Program":case"BlockStatement":return node.body;case"SwitchCase":return node.consequent;default:return null}}function isGuardedByEarlyExit(node,sourceCode){let child=node;let parent=child.parent;while(parent){const list=statementListOf(parent);if(list){const index=list.indexOf(child);for(let i=0;i<index;i++){const stmt=list[i];if(stmt.type==="IfStatement"&&endsInExit(stmt.consequent)&&falsyGuardTargets(stmt.test).some(guarded=>guardsSameValue(guarded,node.object,sourceCode))){return true}}}child=parent;parent=child.parent}return false}function hasExplicitNullCheck(node,sourceCode){let current=node;let depth=0;const maxDepth=10;while(current&&depth<maxDepth){const parent=current.parent;if(parent&&parent.type==="IfStatement"){const test=parent.test;if(isNullCheckForObject(test,node.object,sourceCode)){return true}}current=parent;depth++}return false}function rootText(expr,sourceCode){let base=expr;for(;;){if(base.type==="MemberExpression")base=base.object;else if(base.type==="CallExpression")base=base.callee;else break}return sourceCode.getText(base)}function chainStartsWith(guard,objectText){if(guard===objectText)return true;if(!objectText.startsWith(guard))return false;const rest=objectText.slice(guard.length);return rest.startsWith(".")||rest.startsWith("[")}function rootGuards(root,object,sourceCode){return chainStartsWith(root,sourceCode.getText(object))}function isOptionalChainRootedAt(expr,object,sourceCode){return expr.type==="ChainExpression"&&rootGuards(rootText(expr.expression,sourceCode),object,sourceCode)}function narrowsToObject(test,object,sourceCode){if(test.type==="BinaryExpression"&&test.operator==="in"){return rootGuards(sourceCode.getText(test.right),object,sourceCode)}if(isOptionalChainRootedAt(test,object,sourceCode)){return true}if(test.type==="BinaryExpression"&&(test.operator==="!=="||test.operator==="!=")){const isNil=e=>e.type==="Literal"&&e.value===null||e.type==="Identifier"&&e.name==="undefined";if(isNil(test.right)){return isOptionalChainRootedAt(test.left,object,sourceCode)}if(isNil(test.left)){return isOptionalChainRootedAt(test.right,object,sourceCode)}}return false}function isNullCheckForObject(test,object,sourceCode){const objectText=sourceCode.getText(object);if(narrowsToObject(test,object,sourceCode)){return true}if(test.type==="Identifier"||test.type==="MemberExpression"){const testText=sourceCode.getText(test);if(testText===objectText||objectText.startsWith(testText+".")){return true}}if(test.type==="BinaryExpression"){const{left,right,operator}=test;if(operator==="!=="||operator==="!="||operator==="==="||operator==="=="){const leftText=sourceCode.getText(left);const rightText=sourceCode.getText(right);if(leftText===objectText&&(rightText==="null"||rightText==="undefined")||rightText===objectText&&(leftText==="null"||leftText==="undefined")){return true}}}if(test.type==="LogicalExpression"){return isNullCheckForObject(test.left,object,sourceCode)||isNullCheckForObject(test.right,object,sourceCode)}return false}function usesNullishCoalescing(node){let current=node;let depth=0;const maxDepth=5;while(current&&depth<maxDepth){const parent=current.parent;if(parent&&parent.type==="LogicalExpression"&&parent.operator==="??"){return true}current=parent;depth++}return false}exports.noMissingNullChecks=(0,eslint_devkit_2.createRule)({name:"no-missing-null-checks",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-missing-null-checks.md",description:"Detects potential null pointer dereferences",cwe:"CWE-476",cvss:7.5},hasSuggestions:true,messages:{missingNullCheck:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing null check",cwe:"CWE-476",description:"Potential null/undefined dereference detected",severity:"HIGH",fix:"Use optional chaining (?.) or add explicit null check",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-2259/"}),useOptionalChaining:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Optional Chaining",description:"Use optional chaining operator",severity:"LOW",fix:"obj?.property?.method()",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining"}),useNullishCoalescing:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Nullish Coalescing",description:"Use nullish coalescing operator",severity:"LOW",fix:"value ?? defaultValue",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing"}),addExplicitCheck:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add Explicit Check",description:"Add explicit null check",severity:"LOW",fix:"if (obj !== null) { obj.property }",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/null"})},schema:[{type:"object",properties:{ignoreInTests:{type:"boolean",default:true,description:"Ignore in test files"},requireExplicitChecks:{type:"boolean",default:false,description:"Require explicit null checks"}},additionalProperties:false}]},defaultOptions:[{ignoreInTests:true,requireExplicitChecks:false}],create(context,[options={}]){const{ignoreInTests=true}=options||{};const filename=context.filename;const isTestFile=ignoreInTests&&/\.(test|spec)\.[cm]?[jt]sx?$/.test(filename);if(isTestFile){return{}}const sourceCode=context.sourceCode;const services=(0,eslint_devkit_1.hasParserServices)(context)?(0,eslint_devkit_1.getParserServices)(context):null;function typeSaysNonNullable(objectNode){if(!services)return false;const type=(0,eslint_devkit_1.getTypeOfNode)(objectNode,services);if((0,eslint_devkit_1.isAnyType)(type)||(0,eslint_devkit_1.isUnknownType)(type))return false;return!(0,eslint_devkit_1.isNullableType)(type)}const reportedMemberExpressions=new Set;function getMemberExpressionKey(node){if(node.range&&Array.isArray(node.range)&&node.range.length>=2){return`me-${node.range[0]}-${node.range[1]}`}const loc=node.loc;if(loc&&loc.start){return`me-${loc.start.line}-${loc.start.column}-${loc.end?.line||loc.start.line}-${loc.end?.column||loc.start.column}`}return`me-${JSON.stringify(node).slice(0,50)}`}function checkMemberExpression(node){if(node.optional){return}const parent=node.parent;if(parent&&parent.type==="ChainExpression"){return}if(parent&&parent.type==="MemberExpression"&&parent.object===node){return}const objectNode=node.object;let shouldCheck=false;shouldCheck=baseHasNullabilityEvidence(objectNode,sourceCode.getScope(node))&&!typeSaysNonNullable(objectNode);if(shouldCheck&&!hasNullCheck(node,sourceCode)){const nodeKey=getMemberExpressionKey(node);if(reportedMemberExpressions.has(nodeKey)){return}try{reportedMemberExpressions.add(nodeKey);context.report({node,messageId:"missingNullCheck",suggest:[{messageId:"useOptionalChaining",fix:()=>null},{messageId:"useNullishCoalescing",fix:()=>null},{messageId:"addExplicitCheck",fix:()=>null}]})}catch{return}}}function checkCallExpression(node){if(node.type!=="CallExpression"){return}if(node.callee.type==="MemberExpression"){const memberExpr=node.callee;if(memberExpr.optional){return}const parent=memberExpr.parent;if(parent&&parent.type==="ChainExpression"){return}const objectNode=memberExpr.object;let shouldCheck=false;shouldCheck=baseHasNullabilityEvidence(objectNode,sourceCode.getScope(memberExpr))&&!typeSaysNonNullable(objectNode);if(shouldCheck&&!hasNullCheck(memberExpr,sourceCode)){const nodeKey=getMemberExpressionKey(memberExpr);if(reportedMemberExpressions.has(nodeKey)){return}try{reportedMemberExpressions.add(nodeKey);context.report({node:memberExpr,messageId:"missingNullCheck",suggest:[{messageId:"useOptionalChaining",fix:()=>null},{messageId:"useNullishCoalescing",fix:()=>null},{messageId:"addExplicitCheck",fix:()=>null}]})}catch{return}}}}return{MemberExpression:checkMemberExpression,CallExpression:checkCallExpression}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnsafeTypeNarrowing=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");function isUnsafeTypeAssertion(node){if(node.expression.type==="TSAsExpression"){const innerAssertion=node.expression;if(innerAssertion.typeAnnotation.type==="TSUnknownKeyword"){return true}if(innerAssertion.typeAnnotation.type==="TSAnyKeyword"){return true}}return false}function hasExplanatoryComment(node,sourceCode){const comments=sourceCode.getAllComments();const nodeStart=node.loc?.start;if(!nodeStart||!comments.length){return false}const explanatoryPatterns=[/type.?guard/i,/validated/i,/checked/i,/\bsafe\b/i,/\bknown\b/i,/intentional/i,/necessary/i,/framework/i,/library/i,/third.?party/i,/legacy/i,/todo/i,/fixme/i];for(const comment of comments){const distance=nodeStart.line-comment.loc.end.line;if(distance<0||distance>1)continue;if(distance===1){const before=sourceCode.getTokenBefore(comment,{includeComments:true});if(before&&before.loc.end.line===comment.loc.start.line)continue}const commentText=comment.value.toLowerCase();if(explanatoryPatterns.some(pattern=>pattern.test(commentText))){return true}}return false}exports.noUnsafeTypeNarrowing=(0,eslint_devkit_2.createRule)({name:"no-unsafe-type-narrowing",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-unsafe-type-narrowing.md",description:"Detects unsafe type narrowing patterns"},hasSuggestions:true,messages:{unsafeTypeNarrowing:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unsafe type narrowing",description:"Unsafe type assertion or narrowing detected",severity:"MEDIUM",fix:"Use type guards or proper validation before type assertion",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-4326/"}),useTypeGuard:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Type Guard",description:"Use type guard function",severity:"LOW",fix:"function isType(value: unknown): value is Type { return ... }",documentationLink:"https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates"}),useProperNarrowing:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Type Narrowing",description:"Use proper type narrowing",severity:"LOW",fix:'if (typeof value === "string") { ... }',documentationLink:"https://www.typescriptlang.org/docs/handbook/2/narrowing.html"}),validateBeforeAssert:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Validate First",description:"Validate before type assertion",severity:"LOW",fix:"if (isValid(value)) { const typed = value as Type; }",documentationLink:"https://www.typescriptlang.org/docs/handbook/2/narrowing.html"})},schema:[{type:"object",properties:{ignoreInTests:{type:"boolean",default:true,description:"Ignore in test files"},allowWithComment:{type:"boolean",default:false,description:"Allow type assertions with comments"}},additionalProperties:false}]},defaultOptions:[{ignoreInTests:true,allowWithComment:false}],create(context,[options={}]){const{ignoreInTests=true,allowWithComment=false}=options||{};const filename=context.filename;const isTestFile=ignoreInTests&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);if(isTestFile){return{}}const sourceCode=context.sourceCode;function checkTypeAssertion(node){if(!isUnsafeTypeAssertion(node)){return}if(allowWithComment&&hasExplanatoryComment(node,sourceCode)){return}context.report({node,messageId:"unsafeTypeNarrowing",suggest:[{messageId:"useTypeGuard",fix:()=>null},{messageId:"useProperNarrowing",fix:()=>null},{messageId:"validateBeforeAssert",fix:()=>null}]})}return{TSAsExpression:checkTypeAssertion}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnsafeTypeNarrowing=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");function isUnsafeTypeAssertion(node){if(node.expression.type==="TSAsExpression"){const innerAssertion=node.expression;if(innerAssertion.typeAnnotation.type==="TSUnknownKeyword"){return true}if(innerAssertion.typeAnnotation.type==="TSAnyKeyword"){return true}}return false}function hasExplanatoryComment(node,sourceCode){const comments=sourceCode.getAllComments();const nodeStart=node.loc?.start;if(!nodeStart||!comments.length){return false}const explanatoryPatterns=[/type.?guard/i,/validated/i,/checked/i,/\bsafe\b/i,/\bknown\b/i,/intentional/i,/necessary/i,/framework/i,/library/i,/third.?party/i,/legacy/i,/todo/i,/fixme/i];for(const comment of comments){const distance=nodeStart.line-comment.loc.end.line;if(distance<0||distance>1)continue;if(distance===1){const before=sourceCode.getTokenBefore(comment,{includeComments:true});if(before&&before.loc.end.line===comment.loc.start.line)continue}const commentText=comment.value.toLowerCase();if(explanatoryPatterns.some(pattern=>pattern.test(commentText))){return true}}return false}exports.noUnsafeTypeNarrowing=(0,eslint_devkit_2.createRule)({name:"no-unsafe-type-narrowing",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-unsafe-type-narrowing.md",description:"Detects unsafe type narrowing patterns"},hasSuggestions:true,messages:{unsafeTypeNarrowing:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unsafe type narrowing",description:"Unsafe type assertion or narrowing detected",severity:"MEDIUM",fix:"Use type guards or proper validation before type assertion",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-4326/"}),useTypeGuard:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Type Guard",description:"Use type guard function",severity:"LOW",fix:"function isType(value: unknown): value is Type { return ... }",documentationLink:"https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates"}),useProperNarrowing:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Type Narrowing",description:"Use proper type narrowing",severity:"LOW",fix:'if (typeof value === "string") { ... }',documentationLink:"https://www.typescriptlang.org/docs/handbook/2/narrowing.html"}),validateBeforeAssert:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Validate First",description:"Validate before type assertion",severity:"LOW",fix:"if (isValid(value)) { const typed = value as Type; }",documentationLink:"https://www.typescriptlang.org/docs/handbook/2/narrowing.html"})},schema:[{type:"object",properties:{ignoreInTests:{type:"boolean",default:true,description:"Ignore in test files"},allowWithComment:{type:"boolean",default:false,description:"Allow type assertions with comments"}},additionalProperties:false}]},defaultOptions:[{ignoreInTests:true,allowWithComment:false}],create(context,[options={}]){const{ignoreInTests=true,allowWithComment=false}=options||{};const filename=context.filename;const isTestFile=ignoreInTests&&/\.(test|spec)\.[cm]?[jt]sx?$/.test(filename);if(isTestFile){return{}}const sourceCode=context.sourceCode;function checkTypeAssertion(node){if(!isUnsafeTypeAssertion(node)){return}if(allowWithComment&&hasExplanatoryComment(node,sourceCode)){return}context.report({node,messageId:"unsafeTypeNarrowing",suggest:[{messageId:"useTypeGuard",fix:()=>null},{messageId:"useProperNarrowing",fix:()=>null},{messageId:"validateBeforeAssert",fix:()=>null}]})}return{TSAsExpression:checkTypeAssertion}}});