index.js 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. 'use strict';
  2. // detect either spaces or tabs but not both to properly handle tabs
  3. // for indentation and spaces for alignment
  4. const INDENT_RE = /^(?:( )+|\t+)/;
  5. function getMostUsed(indents) {
  6. let result = 0;
  7. let maxUsed = 0;
  8. let maxWeight = 0;
  9. for (const entry of indents) {
  10. // TODO: use destructuring when targeting Node.js 6
  11. const key = entry[0];
  12. const val = entry[1];
  13. const u = val[0];
  14. const w = val[1];
  15. if (u > maxUsed || (u === maxUsed && w > maxWeight)) {
  16. maxUsed = u;
  17. maxWeight = w;
  18. result = Number(key);
  19. }
  20. }
  21. return result;
  22. }
  23. module.exports = str => {
  24. if (typeof str !== 'string') {
  25. throw new TypeError('Expected a string');
  26. }
  27. // used to see if tabs or spaces are the most used
  28. let tabs = 0;
  29. let spaces = 0;
  30. // remember the size of previous line's indentation
  31. let prev = 0;
  32. // remember how many indents/unindents as occurred for a given size
  33. // and how much lines follow a given indentation
  34. //
  35. // indents = {
  36. // 3: [1, 0],
  37. // 4: [1, 5],
  38. // 5: [1, 0],
  39. // 12: [1, 0],
  40. // }
  41. const indents = new Map();
  42. // pointer to the array of last used indent
  43. let current;
  44. // whether the last action was an indent (opposed to an unindent)
  45. let isIndent;
  46. for (const line of str.split(/\n/g)) {
  47. if (!line) {
  48. // ignore empty lines
  49. continue;
  50. }
  51. let indent;
  52. const matches = line.match(INDENT_RE);
  53. if (matches) {
  54. indent = matches[0].length;
  55. if (matches[1]) {
  56. spaces++;
  57. } else {
  58. tabs++;
  59. }
  60. } else {
  61. indent = 0;
  62. }
  63. const diff = indent - prev;
  64. prev = indent;
  65. if (diff) {
  66. // an indent or unindent has been detected
  67. isIndent = diff > 0;
  68. current = indents.get(isIndent ? diff : -diff);
  69. if (current) {
  70. current[0]++;
  71. } else {
  72. current = [1, 0];
  73. indents.set(diff, current);
  74. }
  75. } else if (current) {
  76. // if the last action was an indent, increment the weight
  77. current[1] += Number(isIndent);
  78. }
  79. }
  80. const amount = getMostUsed(indents);
  81. let type;
  82. let indent;
  83. if (!amount) {
  84. type = null;
  85. indent = '';
  86. } else if (spaces >= tabs) {
  87. type = 'space';
  88. indent = ' '.repeat(amount);
  89. } else {
  90. type = 'tab';
  91. indent = '\t'.repeat(amount);
  92. }
  93. return {
  94. amount,
  95. type,
  96. indent
  97. };
  98. };