Skip to content

Commit

Permalink
Require Node.js 8, use undefined instead of null, add TypeScript …
Browse files Browse the repository at this point in the history
…definition (#25)
  • Loading branch information
BendingBender authored and sindresorhus committed Apr 30, 2019
1 parent b45b213 commit 7cd7e43
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 67 deletions.
3 changes: 1 addition & 2 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
* text=auto
*.js text eol=lf
* text=auto eol=lf
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
language: node_js
node_js:
- '10'
- '8'
- '6'
- '4'
50 changes: 50 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
declare namespace detectIndent {
interface Indent {
/**
Type of indentation. Is `undefined` if no indentation is detected.
*/
type: 'tab' | 'space' | undefined;

/**
Amount of indentation, for example `2`.
*/
amount: number;

/**
Actual indentation.
*/
indent: string;
}
}

/**
Detect the indentation of code.
@param string - A string of any kind of text.
@example
```
import * as fs from 'fs';
import detectIndent = require('detect-indent');
// {
// "ilove": "pizza"
// }
const file = fs.readFileSync('foo.json', 'utf8');
// Tries to detect the indentation and falls back to a default if it can't
const indent = detectIndent(file).indent || ' ';
const json = JSON.parse(file);
json.ilove = 'unicorns';
fs.writeFileSync('foo.json', JSON.stringify(json, null, indent));
// {
// "ilove": "unicorns"
// }
```
*/
declare function detectIndent(string: string): detectIndent.Indent;

export = detectIndent;
68 changes: 30 additions & 38 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,32 @@
'use strict';

// Detect either spaces or tabs but not both to properly handle tabs for indentation and spaces for alignment
const INDENT_RE = /^(?:( )+|\t+)/;
const INDENT_REGEX = /^(?:( )+|\t+)/;

function getMostUsed(indents) {
let result = 0;
let maxUsed = 0;
let maxWeight = 0;

for (const entry of indents) {
// TODO: use destructuring when targeting Node.js 6
const key = entry[0];
const val = entry[1];

const u = val[0];
const w = val[1];

if (u > maxUsed || (u === maxUsed && w > maxWeight)) {
maxUsed = u;
maxWeight = w;
for (const [key, [usedCount, weight]] of indents) {
if (usedCount > maxUsed || (usedCount === maxUsed && weight > maxWeight)) {
maxUsed = usedCount;
maxWeight = weight;
result = key;
}
}

return result;
}

module.exports = str => {
if (typeof str !== 'string') {
module.exports = string => {
if (typeof string !== 'string') {
throw new TypeError('Expected a string');
}

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

Expand All @@ -48,7 +41,7 @@ module.exports = str => {
// }
const indents = new Map();

for (const line of str.split(/\n/g)) {
for (const line of string.split(/\n/g)) {
if (!line) {
// Ignore empty lines
continue;
Expand All @@ -58,11 +51,11 @@ module.exports = str => {
let indentType;
let weight;
let entry;
const matches = line.match(INDENT_RE);
const matches = line.match(INDENT_REGEX);

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

Expand All @@ -72,22 +65,23 @@ module.exports = str => {
indentType = 't';
}

if (indentType !== indentTypePrev) {
prev = 0;
if (indentType !== previousIndentType) {
previousSize = 0;
}
indentTypePrev = indentType;

previousIndentType = indentType;

weight = 0;

const diff = indent - prev;
prev = indent;
const indentDifference = indent - previousSize;
previousSize = indent;

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

// Update the stats
Expand All @@ -98,29 +92,27 @@ module.exports = str => {
} else {
entry = [++entry[0], entry[1] + weight];
}

indents.set(key, entry);
}
}
}

const result = getMostUsed(indents);

let amount;
let amount = 0;
let type;
let indent;
if (!result) {
amount = 0;
type = null;
indent = '';
} else {
amount = Number(result.substring(1));
let indent = '';

if (result !== 0) {
amount = Number(result.slice(1));

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

return {
Expand Down
8 changes: 8 additions & 0 deletions index.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {expectType} from 'tsd';
import detectIndent = require('.');

const indent = detectIndent('');
expectType<detectIndent.Indent>(indent);
expectType<number>(indent.amount);
expectType<string>(indent.indent);
expectType<'space' | 'tab' | undefined>(indent.type);
17 changes: 12 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
"url": "sindresorhus.com"
},
"engines": {
"node": ">=4"
"node": ">=8"
},
"scripts": {
"test": "xo && ava"
"test": "xo && ava && tsd"
},
"files": [
"index.js"
"index.js",
"index.d.ts"
],
"keywords": [
"indent",
Expand All @@ -32,7 +33,13 @@
"tab"
],
"devDependencies": {
"ava": "*",
"xo": "*"
"ava": "^1.4.1",
"tsd": "^0.7.2",
"xo": "^0.24.0"
},
"xo": {
"ignores": [
"fixture"
]
}
}
4 changes: 2 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const detectIndent = require('detect-indent');
*/
const file = fs.readFileSync('foo.json', 'utf8');

// tries to detect the indentation and falls back to a default if it can't
// Tries to detect the indentation and falls back to a default if it can't
const indent = detectIndent(file).indent || ' ';

const json = JSON.parse(file);
Expand All @@ -55,7 +55,7 @@ fs.writeFileSync('foo.json', JSON.stringify(json, null, indent));
Accepts a string and returns an object with stats about the indentation:

* `amount` {number} - Amount of indentation, for example `2`
* `type` {string|null} - Type of indentation. Possible values are `tab`, `space` or `null` if no indentation is detected
* `type` {'tab' | 'space' | undefined} - Type of indentation. Possible values are `'tab'`, `'space'` or `undefined` if no indentation is detected
* `indent` {string} - Actual indentation


Expand Down
36 changes: 18 additions & 18 deletions test.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import fs from 'fs';
import path from 'path';
import test from 'ava';
import m from '.';
import detectIndent from '.';

const getFile = file => fs.readFileSync(path.join(__dirname, file), 'utf8');

test('detect the indent of a file with space indent', t => {
t.is(m(getFile('fixture/space.js')).indent, ' ');
t.is(detectIndent(getFile('fixture/space.js')).indent, ' ');
});

test('return indentation stats for spaces', t => {
const stats = m(getFile('fixture/space.js'));
const stats = detectIndent(getFile('fixture/space.js'));
t.deepEqual(stats, {
amount: 4,
indent: ' ',
Expand All @@ -19,7 +19,7 @@ test('return indentation stats for spaces', t => {
});

test('return indentation stats for multiple tabs', t => {
const stats = m(getFile('fixture/tab-four.js'));
const stats = detectIndent(getFile('fixture/tab-four.js'));
t.deepEqual(stats, {
amount: 4,
indent: '\t\t\t\t',
Expand All @@ -28,11 +28,11 @@ test('return indentation stats for multiple tabs', t => {
});

test('detect the indent of a file with tab indent', t => {
t.is(m(getFile('fixture/tab.js')).indent, '\t');
t.is(detectIndent(getFile('fixture/tab.js')).indent, '\t');
});

test('return indentation stats for tabs', t => {
const stats = m(getFile('fixture/tab.js'));
const stats = detectIndent(getFile('fixture/tab.js'));
t.deepEqual(stats, {
amount: 1,
indent: '\t',
Expand All @@ -41,11 +41,11 @@ test('return indentation stats for tabs', t => {
});

test('detect the indent of a file with equal tabs and spaces', t => {
t.is(m(getFile('fixture/mixed-tab.js')).indent, '\t');
t.is(detectIndent(getFile('fixture/mixed-tab.js')).indent, '\t');
});

test('return indentation stats for equal tabs and spaces', t => {
const indent = m(getFile('fixture/mixed-tab.js'));
const indent = detectIndent(getFile('fixture/mixed-tab.js'));
t.deepEqual(indent, {
amount: 1,
indent: '\t',
Expand All @@ -54,12 +54,12 @@ test('return indentation stats for equal tabs and spaces', t => {
});

test('detect the indent of a file with mostly spaces', t => {
const stats = m(getFile('fixture/mixed-space.js'));
const stats = detectIndent(getFile('fixture/mixed-space.js'));
t.is(stats.indent, ' ');
});

test('return indentation stats for mostly spaces', t => {
const stats = m(getFile('fixture/mixed-space.js'));
const stats = detectIndent(getFile('fixture/mixed-space.js'));
t.deepEqual(stats, {
amount: 4,
indent: ' ',
Expand All @@ -68,12 +68,12 @@ test('return indentation stats for mostly spaces', t => {
});

test('detect the indent of a weirdly indented vendor prefixed CSS', t => {
const stats = m(getFile('fixture/vendor-prefixed-css.css'));
const stats = detectIndent(getFile('fixture/vendor-prefixed-css.css'));
t.is(stats.indent, ' ');
});

test('return indentation stats for various spaces', t => {
const stats = m(getFile('fixture/vendor-prefixed-css.css'));
const stats = detectIndent(getFile('fixture/vendor-prefixed-css.css'));
t.deepEqual(stats, {
amount: 4,
indent: ' ',
Expand All @@ -82,20 +82,20 @@ test('return indentation stats for various spaces', t => {
});

test('return `0` when there is no indentation', t => {
t.is(m('<ul></ul>').amount, 0);
t.is(detectIndent('<ul></ul>').amount, 0);
});

test('return indentation stats for no indentation', t => {
const stats = m('<ul></ul>');
const stats = detectIndent('<ul></ul>');
t.deepEqual(stats, {
amount: 0,
indent: '',
type: null
type: undefined
});
});

test('return indentation stats for fifty-fifty indented files with spaces first', t => {
const stats = m(getFile('fixture/fifty-fifty-space-first.js'));
const stats = detectIndent(getFile('fixture/fifty-fifty-space-first.js'));
t.deepEqual(stats, {
amount: 4,
indent: ' ',
Expand All @@ -104,7 +104,7 @@ test('return indentation stats for fifty-fifty indented files with spaces first'
});

test('return indentation stats for fifty-fifty indented files with tabs first', t => {
const stats = m(getFile('fixture/fifty-fifty-tab-first.js'));
const stats = detectIndent(getFile('fixture/fifty-fifty-tab-first.js'));
t.deepEqual(stats, {
amount: 1,
indent: ' ',
Expand All @@ -113,7 +113,7 @@ test('return indentation stats for fifty-fifty indented files with tabs first',
});

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

0 comments on commit 7cd7e43

Please sign in to comment.