Skip to content

Commit

Permalink
Fix detection when string has equal amount of tabs and spaces (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
FR6 authored and sindresorhus committed Apr 29, 2019
1 parent cc210d3 commit b45b213
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 46 deletions.
5 changes: 5 additions & 0 deletions fixture/space-tab-last.js
@@ -0,0 +1,5 @@
4 spaces
4 spaces
1 tab
1 tab
1 tab
95 changes: 53 additions & 42 deletions index.js
Expand Up @@ -19,7 +19,7 @@ function getMostUsed(indents) {
if (u > maxUsed || (u === maxUsed && w > maxWeight)) {
maxUsed = u;
maxWeight = w;
result = Number(key);
result = key;
}
}

Expand All @@ -31,85 +31,96 @@ module.exports = str => {
throw new TypeError('Expected a string');
}

// Used to see if tabs or spaces are the most used
let tabs = 0;
let spaces = 0;

// Remember the size of previous line's indentation
let prev = 0;
let indentTypePrev;
// Indents key (ident type + size of the indents/unindents)
let key;

// Remember how many indents/unindents as occurred for a given size and how much lines follow a given indentation
// The key is a concatenation of the indentation type (s = space and t = tab) and the size of the indents/unindents
//
// indents = {
// 3: [1, 0],
// 4: [1, 5],
// 5: [1, 0],
// 12: [1, 0],
// t3: [1, 0],
// t4: [1, 5],
// s5: [1, 0],
// s12: [1, 0],
// }
const indents = new Map();

// Pointer to the array of last used indent
let current;

// Whether the last action was an indent (opposed to an unindent)
let isIndent;

for (const line of str.split(/\n/g)) {
if (!line) {
// Ignore empty lines
continue;
}

let indent;
let indentType;
let weight;
let entry;
const matches = line.match(INDENT_RE);

if (matches) {
if (!matches) {
prev = 0;
indentTypePrev = '';
} else {
indent = matches[0].length;

if (matches[1]) {
spaces++;
indentType = 's';
} else {
tabs++;
indentType = 't';
}
} else {
indent = 0;
}

const diff = indent - prev;
prev = indent;
if (indentType !== indentTypePrev) {
prev = 0;
}
indentTypePrev = indentType;

if (diff) {
// An indent or unindent has been detected
weight = 0;

isIndent = diff > 0;
const diff = indent - prev;
prev = indent;

current = indents.get(isIndent ? diff : -diff);
// Previous line have same indent?
if (diff === 0) {
weight++;
// We use the key from previous loop
} else {
key = indentType + String(diff > 0 ? diff : -diff);
}

// Update the stats
entry = indents.get(key);

if (current) {
current[0]++;
if (entry === undefined) {
entry = [1, 0]; // Init
} else {
current = [1, 0];
indents.set(diff, current);
entry = [++entry[0], entry[1] + weight];
}
} else if (current) {
// If the last action was an indent, increment the weight
current[1] += Number(isIndent);
}
indents.set(key, entry);
}
}

const amount = getMostUsed(indents);
const result = getMostUsed(indents);

let amount;
let type;
let indent;
if (!amount) {
if (!result) {
amount = 0;
type = null;
indent = '';
} else if (spaces >= tabs) {
type = 'space';
indent = ' '.repeat(amount);
} else {
type = 'tab';
indent = '\t'.repeat(amount);
amount = Number(result.substring(1));

if (result[0] === 's') {
type = 'space';
indent = ' '.repeat(amount);
} else {
type = 'tab';
indent = '\t'.repeat(amount);
}
}

return {
Expand Down
17 changes: 13 additions & 4 deletions test.js
Expand Up @@ -103,11 +103,20 @@ test('return indentation stats for fifty-fifty indented files with spaces first'
});
});

test.failing('return indentation stats for fifty-fifty indented files with tabs first', t => {
test('return indentation stats for fifty-fifty indented files with tabs first', t => {
const stats = m(getFile('fixture/fifty-fifty-tab-first.js'));
t.deepEqual(stats, {
amount: 4,
indent: ' ',
type: 'space'
amount: 1,
indent: ' ',
type: 'tab'
});
});

test('return indentation stats for indented files with spaces and tabs last', t => {
const stats = m(getFile('fixture/space-tab-last.js'));
t.deepEqual(stats, {
amount: 1,
indent: ' ',
type: 'tab'
});
});

0 comments on commit b45b213

Please sign in to comment.