Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Require Node.js 8, add TypeScript definition #25

Merged
merged 2 commits into from Apr 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions .gitattributes
@@ -1,2 +1 @@
* text=auto
*.js text eol=lf
* text=auto eol=lf
3 changes: 1 addition & 2 deletions .travis.yml
@@ -1,5 +1,4 @@
language: node_js
node_js:
- '10'
- '8'
- '6'
- '4'
50 changes: 50 additions & 0 deletions index.d.ts
@@ -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
@@ -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
@@ -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
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
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
@@ -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