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