no-null.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. 'use strict';
  2. const {
  3. not,
  4. methodCallSelector,
  5. callExpressionSelector,
  6. } = require('./selectors/index.js');
  7. const ERROR_MESSAGE_ID = 'error';
  8. const SUGGESTION_REPLACE_MESSAGE_ID = 'replace';
  9. const SUGGESTION_REMOVE_MESSAGE_ID = 'remove';
  10. const messages = {
  11. [ERROR_MESSAGE_ID]: 'Use `undefined` instead of `null`.',
  12. [SUGGESTION_REPLACE_MESSAGE_ID]: 'Replace `null` with `undefined`.',
  13. [SUGGESTION_REMOVE_MESSAGE_ID]: 'Remove `null`.',
  14. };
  15. const selector = [
  16. 'Literal',
  17. '[raw="null"]',
  18. not([
  19. // `Object.create(null)`, `Object.create(null, foo)`
  20. `${methodCallSelector({object: 'Object', method: 'create', minimumArguments: 1, maximumArguments: 2})} > .arguments:first-child`,
  21. // `useRef(null)`
  22. `${callExpressionSelector({name: 'useRef', argumentsLength: 1})} > .arguments:first-child`,
  23. // `React.useRef(null)`
  24. `${methodCallSelector({object: 'React', method: 'useRef', argumentsLength: 1})} > .arguments:first-child`,
  25. // `foo.insertBefore(bar, null)`
  26. `${methodCallSelector({method: 'insertBefore', argumentsLength: 2})}[arguments.0.type!="SpreadElement"] > .arguments:nth-child(2)`,
  27. ]),
  28. ].join('');
  29. const isLooseEqual = node => node.type === 'BinaryExpression' && ['==', '!='].includes(node.operator);
  30. const isStrictEqual = node => node.type === 'BinaryExpression' && ['===', '!=='].includes(node.operator);
  31. /** @param {import('eslint').Rule.RuleContext} context */
  32. const create = context => {
  33. const {checkStrictEquality} = {
  34. checkStrictEquality: false,
  35. ...context.options[0],
  36. };
  37. return {
  38. [selector](node) {
  39. const {parent} = node;
  40. if (!checkStrictEquality && isStrictEqual(parent)) {
  41. return;
  42. }
  43. const problem = {
  44. node,
  45. messageId: ERROR_MESSAGE_ID,
  46. };
  47. const useUndefinedFix = fixer => fixer.replaceText(node, 'undefined');
  48. if (isLooseEqual(parent)) {
  49. problem.fix = useUndefinedFix;
  50. return problem;
  51. }
  52. const useUndefinedSuggestion = {
  53. messageId: SUGGESTION_REPLACE_MESSAGE_ID,
  54. fix: useUndefinedFix,
  55. };
  56. if (parent.type === 'ReturnStatement' && parent.argument === node) {
  57. problem.suggest = [
  58. {
  59. messageId: SUGGESTION_REMOVE_MESSAGE_ID,
  60. fix: fixer => fixer.remove(node),
  61. },
  62. useUndefinedSuggestion,
  63. ];
  64. return problem;
  65. }
  66. if (parent.type === 'VariableDeclarator' && parent.init === node && parent.parent.kind !== 'const') {
  67. problem.suggest = [
  68. {
  69. messageId: SUGGESTION_REMOVE_MESSAGE_ID,
  70. fix: fixer => fixer.removeRange([parent.id.range[1], node.range[1]]),
  71. },
  72. useUndefinedSuggestion,
  73. ];
  74. return problem;
  75. }
  76. problem.suggest = [useUndefinedSuggestion];
  77. return problem;
  78. },
  79. };
  80. };
  81. const schema = [
  82. {
  83. type: 'object',
  84. additionalProperties: false,
  85. properties: {
  86. checkStrictEquality: {
  87. type: 'boolean',
  88. default: false,
  89. },
  90. },
  91. },
  92. ];
  93. /** @type {import('eslint').Rule.RuleModule} */
  94. module.exports = {
  95. create,
  96. meta: {
  97. type: 'suggestion',
  98. docs: {
  99. description: 'Disallow the use of the `null` literal.',
  100. },
  101. fixable: 'code',
  102. hasSuggestions: true,
  103. schema,
  104. messages,
  105. },
  106. };