no-mixed-spaces-and-tabs.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. /**
  2. * @fileoverview Disallow mixed spaces and tabs for indentation
  3. * @author Jary Niebur
  4. * @deprecated in ESLint v8.53.0
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Rule Definition
  9. //------------------------------------------------------------------------------
  10. /** @type {import('../types').Rule.RuleModule} */
  11. module.exports = {
  12. meta: {
  13. deprecated: {
  14. message: "Formatting rules are being moved out of ESLint core.",
  15. url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
  16. deprecatedSince: "8.53.0",
  17. availableUntil: "11.0.0",
  18. replacedBy: [
  19. {
  20. message:
  21. "ESLint Stylistic now maintains deprecated stylistic core rules.",
  22. url: "https://eslint.style/guide/migration",
  23. plugin: {
  24. name: "@stylistic/eslint-plugin",
  25. url: "https://eslint.style",
  26. },
  27. rule: {
  28. name: "no-mixed-spaces-and-tabs",
  29. url: "https://eslint.style/rules/no-mixed-spaces-and-tabs",
  30. },
  31. },
  32. ],
  33. },
  34. type: "layout",
  35. docs: {
  36. description: "Disallow mixed spaces and tabs for indentation",
  37. recommended: false,
  38. url: "https://eslint.org/docs/latest/rules/no-mixed-spaces-and-tabs",
  39. },
  40. schema: [
  41. {
  42. enum: ["smart-tabs", true, false],
  43. },
  44. ],
  45. messages: {
  46. mixedSpacesAndTabs: "Mixed spaces and tabs.",
  47. },
  48. },
  49. create(context) {
  50. const sourceCode = context.sourceCode;
  51. let smartTabs;
  52. switch (context.options[0]) {
  53. case true: // Support old syntax, maybe add deprecation warning here
  54. case "smart-tabs":
  55. smartTabs = true;
  56. break;
  57. default:
  58. smartTabs = false;
  59. }
  60. //--------------------------------------------------------------------------
  61. // Public
  62. //--------------------------------------------------------------------------
  63. return {
  64. "Program:exit"(node) {
  65. const lines = sourceCode.lines,
  66. comments = sourceCode.getAllComments(),
  67. ignoredCommentLines = new Set();
  68. // Add all lines except the first ones.
  69. comments.forEach(comment => {
  70. for (
  71. let i = comment.loc.start.line + 1;
  72. i <= comment.loc.end.line;
  73. i++
  74. ) {
  75. ignoredCommentLines.add(i);
  76. }
  77. });
  78. /*
  79. * At least one space followed by a tab
  80. * or the reverse before non-tab/-space
  81. * characters begin.
  82. */
  83. let regex = /^(?=( +|\t+))\1(?:\t| )/u;
  84. if (smartTabs) {
  85. /*
  86. * At least one space followed by a tab
  87. * before non-tab/-space characters begin.
  88. */
  89. // eslint-disable-next-line regexp/no-empty-lookarounds-assertion -- False positive
  90. regex = /^(?=(\t*))\1(?=( +))\2\t/u;
  91. }
  92. lines.forEach((line, i) => {
  93. const match = regex.exec(line);
  94. if (match) {
  95. const lineNumber = i + 1;
  96. const loc = {
  97. start: {
  98. line: lineNumber,
  99. column: match[0].length - 2,
  100. },
  101. end: {
  102. line: lineNumber,
  103. column: match[0].length,
  104. },
  105. };
  106. if (!ignoredCommentLines.has(lineNumber)) {
  107. const containingNode =
  108. sourceCode.getNodeByRangeIndex(
  109. sourceCode.getIndexFromLoc(loc.start),
  110. );
  111. if (
  112. !(
  113. containingNode &&
  114. ["Literal", "TemplateElement"].includes(
  115. containingNode.type,
  116. )
  117. )
  118. ) {
  119. context.report({
  120. node,
  121. loc,
  122. messageId: "mixedSpacesAndTabs",
  123. });
  124. }
  125. }
  126. }
  127. });
  128. },
  129. };
  130. },
  131. };