• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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