diff --git a/.gitattributes b/.gitattributes index 391f0a4..6313b56 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1 @@ -* text=auto -*.js text eol=lf +* text=auto eol=lf diff --git a/.travis.yml b/.travis.yml index 7d69d74..f3fa8cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ language: node_js node_js: + - '10' - '8' - - '6' - - '4' diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..3dd8026 --- /dev/null +++ b/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; diff --git a/index.js b/index.js index b9062b3..f307856 100644 --- a/index.js +++ b/index.js @@ -1,24 +1,17 @@ '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; } } @@ -26,14 +19,14 @@ function getMostUsed(indents) { 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; @@ -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; @@ -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; @@ -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 @@ -98,21 +92,19 @@ 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'; @@ -120,7 +112,7 @@ module.exports = str => { } else { type = 'tab'; indent = '\t'.repeat(amount); - } + } } return { diff --git a/index.test-d.ts b/index.test-d.ts new file mode 100644 index 0000000..c28db0e --- /dev/null +++ b/index.test-d.ts @@ -0,0 +1,8 @@ +import {expectType} from 'tsd'; +import detectIndent = require('.'); + +const indent = detectIndent(''); +expectType(indent); +expectType(indent.amount); +expectType(indent.indent); +expectType<'space' | 'tab' | undefined>(indent.type); diff --git a/package.json b/package.json index d2b0909..2471f9c 100644 --- a/package.json +++ b/package.json @@ -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", @@ -32,7 +33,13 @@ "tab" ], "devDependencies": { - "ava": "*", - "xo": "*" + "ava": "^1.4.1", + "tsd": "^0.7.2", + "xo": "^0.24.0" + }, + "xo": { + "ignores": [ + "fixture" + ] } } diff --git a/readme.md b/readme.md index befce45..8b67fad 100644 --- a/readme.md +++ b/readme.md @@ -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); @@ -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 diff --git a/test.js b/test.js index 30ae13d..3264e5a 100644 --- a/test.js +++ b/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: ' ', @@ -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', @@ -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', @@ -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', @@ -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: ' ', @@ -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: ' ', @@ -82,20 +82,20 @@ test('return indentation stats for various spaces', t => { }); test('return `0` when there is no indentation', t => { - t.is(m('
    ').amount, 0); + t.is(detectIndent('
      ').amount, 0); }); test('return indentation stats for no indentation', t => { - const stats = m('
        '); + const stats = detectIndent('
          '); 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: ' ', @@ -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: ' ', @@ -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: ' ',