1 |
|
2 | const INDENT_REGEX = /^(?:( )+|\t+)/;
|
3 |
|
4 | const INDENT_TYPE_SPACE = 'space';
|
5 | const INDENT_TYPE_TAB = 'tab';
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | function makeIndentsMap(string, ignoreSingleSpaces) {
|
22 | const indents = new Map();
|
23 |
|
24 |
|
25 | let previousSize = 0;
|
26 | let previousIndentType;
|
27 |
|
28 |
|
29 | let key;
|
30 |
|
31 | for (const line of string.split(/\n/g)) {
|
32 | if (!line) {
|
33 |
|
34 | continue;
|
35 | }
|
36 |
|
37 | let indent;
|
38 | let indentType;
|
39 | let weight;
|
40 | let entry;
|
41 | const matches = line.match(INDENT_REGEX);
|
42 |
|
43 | if (matches === null) {
|
44 | previousSize = 0;
|
45 | previousIndentType = '';
|
46 | } else {
|
47 | indent = matches[0].length;
|
48 | indentType = matches[1] ? INDENT_TYPE_SPACE : INDENT_TYPE_TAB;
|
49 |
|
50 |
|
51 | if (ignoreSingleSpaces && indentType === INDENT_TYPE_SPACE && indent === 1) {
|
52 | continue;
|
53 | }
|
54 |
|
55 | if (indentType !== previousIndentType) {
|
56 | previousSize = 0;
|
57 | }
|
58 |
|
59 | previousIndentType = indentType;
|
60 |
|
61 | weight = 0;
|
62 |
|
63 | const indentDifference = indent - previousSize;
|
64 | previousSize = indent;
|
65 |
|
66 |
|
67 | if (indentDifference === 0) {
|
68 | weight++;
|
69 |
|
70 | } else {
|
71 | const absoluteIndentDifference = indentDifference > 0 ? indentDifference : -indentDifference;
|
72 | key = encodeIndentsKey(indentType, absoluteIndentDifference);
|
73 | }
|
74 |
|
75 |
|
76 | entry = indents.get(key);
|
77 | entry = entry === undefined ? [1, 0] : [++entry[0], entry[1] + weight];
|
78 |
|
79 | indents.set(key, entry);
|
80 | }
|
81 | }
|
82 |
|
83 | return indents;
|
84 | }
|
85 |
|
86 |
|
87 | function encodeIndentsKey(indentType, indentAmount) {
|
88 | const typeCharacter = indentType === INDENT_TYPE_SPACE ? 's' : 't';
|
89 | return typeCharacter + String(indentAmount);
|
90 | }
|
91 |
|
92 |
|
93 | function decodeIndentsKey(indentsKey) {
|
94 | const keyHasTypeSpace = indentsKey[0] === 's';
|
95 | const type = keyHasTypeSpace ? INDENT_TYPE_SPACE : INDENT_TYPE_TAB;
|
96 |
|
97 | const amount = Number(indentsKey.slice(1));
|
98 |
|
99 | return {type, amount};
|
100 | }
|
101 |
|
102 |
|
103 |
|
104 | function getMostUsedKey(indents) {
|
105 | let result;
|
106 | let maxUsed = 0;
|
107 | let maxWeight = 0;
|
108 |
|
109 | for (const [key, [usedCount, weight]] of indents) {
|
110 | if (usedCount > maxUsed || (usedCount === maxUsed && weight > maxWeight)) {
|
111 | maxUsed = usedCount;
|
112 | maxWeight = weight;
|
113 | result = key;
|
114 | }
|
115 | }
|
116 |
|
117 | return result;
|
118 | }
|
119 |
|
120 | function makeIndentString(type, amount) {
|
121 | const indentCharacter = type === INDENT_TYPE_SPACE ? ' ' : '\t';
|
122 | return indentCharacter.repeat(amount);
|
123 | }
|
124 |
|
125 | export default function detectIndent(string) {
|
126 | if (typeof string !== 'string') {
|
127 | throw new TypeError('Expected a string');
|
128 | }
|
129 |
|
130 |
|
131 |
|
132 | let indents = makeIndentsMap(string, true);
|
133 | if (indents.size === 0) {
|
134 | indents = makeIndentsMap(string, false);
|
135 | }
|
136 |
|
137 | const keyOfMostUsedIndent = getMostUsedKey(indents);
|
138 |
|
139 | let type;
|
140 | let amount = 0;
|
141 | let indent = '';
|
142 |
|
143 | if (keyOfMostUsedIndent !== undefined) {
|
144 | ({type, amount} = decodeIndentsKey(keyOfMostUsedIndent));
|
145 | indent = makeIndentString(type, amount);
|
146 | }
|
147 |
|
148 | return {
|
149 | amount,
|
150 | type,
|
151 | indent,
|
152 | };
|
153 | }
|