wishthis/node_modules/detect-indent/index.js
2022-01-21 09:28:41 +01:00

160 lines
4 KiB
JavaScript

'use strict';
// Detect either spaces or tabs but not both to properly handle tabs for indentation and spaces for alignment
const INDENT_REGEX = /^(?:( )+|\t+)/;
const INDENT_TYPE_SPACE = 'space';
const INDENT_TYPE_TAB = 'tab';
// Make a Map that counts how many indents/unindents have occurred for a given size and how many 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 = {
// t3: [1, 0],
// t4: [1, 5],
// s5: [1, 0],
// s12: [1, 0],
// }
function makeIndentsMap(string, ignoreSingleSpaces) {
const indents = new Map();
// Remember the size of previous line's indentation
let previousSize = 0;
let previousIndentType;
// Indents key (ident type + size of the indents/unindents)
let key;
for (const line of string.split(/\n/g)) {
if (!line) {
// Ignore empty lines
continue;
}
let indent;
let indentType;
let weight;
let entry;
const matches = line.match(INDENT_REGEX);
if (matches === null) {
previousSize = 0;
previousIndentType = '';
} else {
indent = matches[0].length;
if (matches[1]) {
indentType = INDENT_TYPE_SPACE;
} else {
indentType = INDENT_TYPE_TAB;
}
// Ignore single space unless it's the only indent detected to prevent common false positives
if (ignoreSingleSpaces && indentType === INDENT_TYPE_SPACE && indent === 1) {
continue;
}
if (indentType !== previousIndentType) {
previousSize = 0;
}
previousIndentType = indentType;
weight = 0;
const indentDifference = indent - previousSize;
previousSize = indent;
// Previous line have same indent?
if (indentDifference === 0) {
weight++;
// We use the key from previous loop
} else {
const absoluteIndentDifference = indentDifference > 0 ? indentDifference : -indentDifference;
key = encodeIndentsKey(indentType, absoluteIndentDifference);
}
// Update the stats
entry = indents.get(key);
if (entry === undefined) {
entry = [1, 0]; // Init
} else {
entry = [++entry[0], entry[1] + weight];
}
indents.set(key, entry);
}
}
return indents;
}
// Encode the indent type and amount as a string (e.g. 's4') for use as a compound key in the indents Map.
function encodeIndentsKey(indentType, indentAmount) {
const typeCharacter = indentType === INDENT_TYPE_SPACE ? 's' : 't';
return typeCharacter + String(indentAmount);
}
// Extract the indent type and amount from a key of the indents Map.
function decodeIndentsKey(indentsKey) {
const keyHasTypeSpace = indentsKey[0] === 's';
const type = keyHasTypeSpace ? INDENT_TYPE_SPACE : INDENT_TYPE_TAB;
const amount = Number(indentsKey.slice(1));
return {type, amount};
}
// Return the key (e.g. 's4') from the indents Map that represents the most common indent,
// or return undefined if there are no indents.
function getMostUsedKey(indents) {
let result;
let maxUsed = 0;
let maxWeight = 0;
for (const [key, [usedCount, weight]] of indents) {
if (usedCount > maxUsed || (usedCount === maxUsed && weight > maxWeight)) {
maxUsed = usedCount;
maxWeight = weight;
result = key;
}
}
return result;
}
function makeIndentString(type, amount) {
const indentCharacter = type === INDENT_TYPE_SPACE ? ' ' : '\t';
return indentCharacter.repeat(amount);
}
module.exports = string => {
if (typeof string !== 'string') {
throw new TypeError('Expected a string');
}
// Identify indents while skipping single space indents to avoid common edge cases (e.g. code comments)
// If no indents are identified, run again and include all indents for comprehensive detection
let indents = makeIndentsMap(string, true);
if (indents.size === 0) {
indents = makeIndentsMap(string, false);
}
const keyOfMostUsedIndent = getMostUsedKey(indents);
let type;
let amount = 0;
let indent = '';
if (keyOfMostUsedIndent !== undefined) {
({type, amount} = decodeIndentsKey(keyOfMostUsedIndent));
indent = makeIndentString(type, amount);
}
return {
amount,
type,
indent
};
};