Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import { ESLintUtils } from "@typescript-eslint/utils";
- import type { TSESTree } from "@typescript-eslint/utils";
- const createRule = ESLintUtils.RuleCreator((name) => `https://example.com/rules/${name}`);
- const mustUseOptionalChaining = createRule({
- name: "must-use-optional-chaining",
- meta: {
- type: "suggestion",
- docs: {
- description: "Enforce optional chaining (?.) for nullable property access with ??",
- },
- fixable: "code",
- hasSuggestions: true,
- schema: [],
- messages: {
- useOptionalChaining:
- 'Use optional chaining (?.) with ?? instead of "{{operator}}" for nullable properties.',
- suggestReplace: "Replace with optional chaining and nullish coalescing",
- },
- },
- defaultOptions: [],
- create(context) {
- const sourceCode = context.sourceCode;
- const checkChainForMissingOptional = (node: TSESTree.MemberExpression) => {
- let current: TSESTree.Node = node;
- while (current.type === "MemberExpression") {
- if (!current.optional) {
- return true;
- }
- current = current.object;
- }
- return false;
- };
- const buildFixedChain = (node: TSESTree.MemberExpression) => {
- const parts = [];
- let current: TSESTree.Node = node;
- while (current.type === "MemberExpression") {
- const property = current.computed
- ? `[${sourceCode.getText(current.property)}]`
- : sourceCode.getText(current.property);
- parts.unshift(property);
- current = current.object;
- }
- const base = sourceCode.getText(current);
- return `${base}?.${parts.join("?.")}`;
- };
- return {
- LogicalExpression(node: TSESTree.LogicalExpression) {
- if (node.operator !== "||" && node.operator !== "??") return;
- let memberExpr;
- if (
- node.left.type === "ChainExpression" &&
- node.left.expression.type === "MemberExpression"
- ) {
- memberExpr = node.left.expression;
- } else if (node.left.type === "MemberExpression") {
- memberExpr = node.left;
- } else {
- return;
- }
- const needsOptionalChaining = checkChainForMissingOptional(memberExpr);
- if (!needsOptionalChaining) return;
- const fixedChain = buildFixedChain(memberExpr);
- const rightSide = sourceCode.getText(node.right) || '""';
- const fixedText = `${fixedChain} ?? ${rightSide}`;
- context.report({
- node,
- messageId: "useOptionalChaining",
- data: { operator: node.operator },
- fix:
- node.operator === "||"
- ? undefined
- : function (fixer) {
- return fixer.replaceText(node, fixedText);
- },
- suggest:
- node.operator === "||"
- ? [
- {
- messageId: "suggestReplace",
- fix(fixer) {
- return fixer.replaceText(node, fixedText);
- },
- },
- ]
- : undefined,
- });
- },
- };
- },
- });
- export default mustUseOptionalChaining;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement