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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. /**
  2. * @fileoverview Disallow mixed spaces and tabs for indentation
  3. * @author Jary Niebur
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Rule Definition
  8. //------------------------------------------------------------------------------
  9. /** @type {import('../shared/types').Rule} */
  10. module.exports = {
  11. meta: {
  12. type: "layout",
  13. docs: {
  14. description: "Disallow mixed spaces and tabs for indentation",
  15. recommended: true,
  16. url: "https://eslint.org/docs/rules/no-mixed-spaces-and-tabs"
  17. },
  18. schema: [
  19. {
  20. enum: ["smart-tabs", true, false]
  21. }
  22. ],
  23. messages: {
  24. mixedSpacesAndTabs: "Mixed spaces and tabs."
  25. }
  26. },
  27. create(context) {
  28. const sourceCode = context.getSourceCode();
  29. let smartTabs;
  30. switch (context.options[0]) {
  31. case true: // Support old syntax, maybe add deprecation warning here
  32. case "smart-tabs":
  33. smartTabs = true;
  34. break;
  35. default:
  36. smartTabs = false;
  37. }
  38. //--------------------------------------------------------------------------
  39. // Public
  40. //--------------------------------------------------------------------------
  41. return {
  42. "Program:exit"(node) {
  43. const lines = sourceCode.lines,
  44. comments = sourceCode.getAllComments(),
  45. ignoredCommentLines = new Set();
  46. // Add all lines except the first ones.
  47. comments.forEach(comment => {
  48. for (let i = comment.loc.start.line + 1; i <= comment.loc.end.line; i++) {
  49. ignoredCommentLines.add(i);
  50. }
  51. });
  52. /*
  53. * At least one space followed by a tab
  54. * or the reverse before non-tab/-space
  55. * characters begin.
  56. */
  57. let regex = /^(?=( +|\t+))\1(?:\t| )/u;
  58. if (smartTabs) {
  59. /*
  60. * At least one space followed by a tab
  61. * before non-tab/-space characters begin.
  62. */
  63. regex = /^(?=(\t*))\1(?=( +))\2\t/u;
  64. }
  65. lines.forEach((line, i) => {
  66. const match = regex.exec(line);
  67. if (match) {
  68. const lineNumber = i + 1;
  69. const loc = {
  70. start: {
  71. line: lineNumber,
  72. column: match[0].length - 2
  73. },
  74. end: {
  75. line: lineNumber,
  76. column: match[0].length
  77. }
  78. };
  79. if (!ignoredCommentLines.has(lineNumber)) {
  80. const containingNode = sourceCode.getNodeByRangeIndex(sourceCode.getIndexFromLoc(loc.start));
  81. if (!(containingNode && ["Literal", "TemplateElement"].includes(containingNode.type))) {
  82. context.report({
  83. node,
  84. loc,
  85. messageId: "mixedSpacesAndTabs"
  86. });
  87. }
  88. }
  89. }
  90. });
  91. }
  92. };
  93. }
  94. };