Advertisement
djbob2000

Untitled

Mar 24th, 2025
8
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 3.85 KB | None | 0 0
  1. import { ESLintUtils } from "@typescript-eslint/utils";
  2. import type { TSESTree } from "@typescript-eslint/utils";
  3.  
  4. const createRule = ESLintUtils.RuleCreator((name) => `https://example.com/rules/${name}`);
  5.  
  6. const mustUseOptionalChaining = createRule({
  7. name: "must-use-optional-chaining",
  8. meta: {
  9. type: "suggestion",
  10. docs: {
  11. description: "Enforce optional chaining (?.) for nullable property access with ??",
  12. },
  13. fixable: "code",
  14. hasSuggestions: true,
  15. schema: [],
  16. messages: {
  17. useOptionalChaining:
  18. 'Use optional chaining (?.) with ?? instead of "{{operator}}" for nullable properties.',
  19. suggestReplace: "Replace with optional chaining and nullish coalescing",
  20. },
  21. },
  22. defaultOptions: [],
  23.  
  24. create(context) {
  25. const sourceCode = context.sourceCode;
  26.  
  27. const checkChainForMissingOptional = (node: TSESTree.MemberExpression) => {
  28. let current: TSESTree.Node = node;
  29. while (current.type === "MemberExpression") {
  30. if (!current.optional) {
  31. return true;
  32. }
  33. current = current.object;
  34. }
  35. return false;
  36. };
  37.  
  38. const buildFixedChain = (node: TSESTree.MemberExpression) => {
  39. const parts = [];
  40. let current: TSESTree.Node = node;
  41. while (current.type === "MemberExpression") {
  42. const property = current.computed
  43. ? `[${sourceCode.getText(current.property)}]`
  44. : sourceCode.getText(current.property);
  45. parts.unshift(property);
  46. current = current.object;
  47. }
  48. const base = sourceCode.getText(current);
  49. return `${base}?.${parts.join("?.")}`;
  50. };
  51.  
  52. return {
  53. LogicalExpression(node: TSESTree.LogicalExpression) {
  54. if (node.operator !== "||" && node.operator !== "??") return;
  55.  
  56. let memberExpr;
  57. if (
  58. node.left.type === "ChainExpression" &&
  59. node.left.expression.type === "MemberExpression"
  60. ) {
  61. memberExpr = node.left.expression;
  62. } else if (node.left.type === "MemberExpression") {
  63. memberExpr = node.left;
  64. } else {
  65. return;
  66. }
  67.  
  68. const needsOptionalChaining = checkChainForMissingOptional(memberExpr);
  69.  
  70. if (!needsOptionalChaining) return;
  71.  
  72. const fixedChain = buildFixedChain(memberExpr);
  73. const rightSide = sourceCode.getText(node.right) || '""';
  74. const fixedText = `${fixedChain} ?? ${rightSide}`;
  75.  
  76. context.report({
  77. node,
  78. messageId: "useOptionalChaining",
  79. data: { operator: node.operator },
  80. fix:
  81. node.operator === "||"
  82. ? undefined
  83. : function (fixer) {
  84. return fixer.replaceText(node, fixedText);
  85. },
  86. suggest:
  87. node.operator === "||"
  88. ? [
  89. {
  90. messageId: "suggestReplace",
  91. fix(fixer) {
  92. return fixer.replaceText(node, fixedText);
  93. },
  94. },
  95. ]
  96. : undefined,
  97. });
  98. },
  99. };
  100. },
  101. });
  102.  
  103. export default mustUseOptionalChaining;
  104.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement