├── .nvmrc ├── .gitignore ├── bin └── lintspaces-cli.js ├── test ├── samples │ ├── malformed.tabs.js │ └── malformed.newlines.js └── index.test.js ├── .editorconfig ├── CHANGELOG.md ├── .github └── workflows │ └── ci.yaml ├── LICENCE ├── package.json ├── README.md └── index.js /.nvmrc: -------------------------------------------------------------------------------- 1 | v8.12.0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *.log 4 | coverage 5 | .nyc_output 6 | -------------------------------------------------------------------------------- /bin/lintspaces-cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../index')(process, console) 4 | -------------------------------------------------------------------------------- /test/samples/malformed.tabs.js: -------------------------------------------------------------------------------- 1 | function add (a, b) { 2 | return a +b 3 | } 4 | 5 | function subtract (a, b) { 6 | return a - b 7 | } 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/samples/malformed.newlines.js: -------------------------------------------------------------------------------- 1 | function add (a, b) { 2 | return a +b 3 | 4 | 5 | } 6 | 7 | 8 | 9 | function subtract (a, b) { 10 | 11 | 12 | return a - b 13 | } 14 | 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Tells the .editorconfg plugin to stop searching once it finds this file 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = space 7 | charset = utf-8 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [*.py] 16 | indent_size = 4 -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 (18/11/24) 2 | 3 | * Update to use `lintspaces` v0.12.0. 4 | * Revert change from v0.8.0 that loaded a `.editorconfig` file from the current working directory by default (see #33) 5 | 6 | ## 0.8.0 7 | 8 | * Update to use `lintspaces` v0.10.0 to address Windows related issues. See issue #25. 9 | * Default to using the `.editorconfig` file in the current working directory if the `-e` option is not provided. 10 | 11 | ## 0.7.1 (22/03/2019) 12 | 13 | * Fix "is not a file" errors on folder globs 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [16.x, 18.x, 20.x, 22.x] 16 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - run: npm ci 25 | - run: npm test 26 | - run: npm run coverage 27 | - name: Coveralls 28 | uses: coverallsapp/github-action@v2.3.3 29 | with: 30 | github-token: ${{ secrets.GITHUB_TOKEN }} 31 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2022 Evan Shortiss 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lintspaces-cli", 3 | "version": "1.0.0", 4 | "description": "CLI for the lintspaces module", 5 | "main": "index.js", 6 | "scripts": { 7 | "coverage": "nyc mocha test/*.js && nyc report --reporter=lcov", 8 | "format": "prettier --no-semi --single-quote --write index.js", 9 | "test": "mocha test/*.test.js" 10 | }, 11 | "husky": { 12 | "hooks": { 13 | "pre-commit": "npm test && npm run format && git add ." 14 | } 15 | }, 16 | "bin": { 17 | "lintspaces": "bin/lintspaces-cli.js" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/evanshortiss/lintspaces-cli.git" 22 | }, 23 | "keywords": [ 24 | "lintspaces", 25 | "node-lintspaces", 26 | "cli" 27 | ], 28 | "engines": { 29 | "node": ">=6" 30 | }, 31 | "author": "Evan Shortiss", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/evanshortiss/lintspaces-cli/issues" 35 | }, 36 | "homepage": "https://github.com/evanshortiss/lintspaces-cli", 37 | "dependencies": { 38 | "colors": "~1.4.0", 39 | "commander": "~12.1.0", 40 | "glob": "~11.1.0", 41 | "lintspaces": "~0.12.0", 42 | "lodash.map": "~4.6.0" 43 | }, 44 | "devDependencies": { 45 | "coveralls": "~3.1.1", 46 | "expect": "~29.7.0", 47 | "mocha": "~10.7.3", 48 | "nyc": "~17.1.0", 49 | "prettier": "~3.3.3", 50 | "sinon": "~19.0.2" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../index') 2 | const { expect } = require('expect') 3 | const sinon = require('sinon') 4 | const path = require('path') 5 | 6 | describe('lintspaces-cli', () => { 7 | it('should find tabs, but expect spaces', () => { 8 | const filepath = path.join(__dirname, 'samples/malformed.tabs.js') 9 | const processStub = { 10 | argv: [ 11 | '/usr/local/bin/node', 12 | path.join(__dirname, '../bin/lintspaces-cli.js'), 13 | '-d', 14 | 'spaces', 15 | '-s', 16 | '2', 17 | filepath, 18 | '--json' 19 | ], 20 | exit: sinon.stub() 21 | } 22 | 23 | const consoleStub = { 24 | log: sinon.stub(), 25 | warn: sinon.stub() 26 | } 27 | 28 | const expectedResult = JSON.stringify({ 29 | [filepath]: { 30 | "2": [ 31 | { 32 | "line": 2, 33 | "code": "INDENTATION_SPACES", 34 | "type": "warning", 35 | "message": "Unexpected tabs found." 36 | } 37 | ], 38 | "6": [ 39 | { 40 | "line": 6, 41 | "code": "INDENTATION_SPACES", 42 | "type": "warning", 43 | "message": "Unexpected tabs found." 44 | } 45 | ] 46 | } 47 | }) 48 | 49 | cli(processStub, consoleStub) 50 | 51 | expect(consoleStub.log.calledOnce).toBeTruthy() 52 | expect(consoleStub.log.getCall(0).args[0]).toEqual(expectedResult) 53 | }) 54 | 55 | it('should find excessive newlines', () => { 56 | const filepath = path.join(__dirname, 'samples/malformed.newlines.js') 57 | const processStub = { 58 | argv: [ 59 | '/usr/local/bin/node', 60 | path.join(__dirname, '../bin/lintspaces-cli.js'), 61 | '-d', 62 | 'spaces', 63 | '-s', 64 | '2', 65 | '--maxnewlines=2', 66 | filepath 67 | ], 68 | exit: sinon.stub() 69 | } 70 | 71 | const consoleStub = { 72 | log: sinon.stub(), 73 | warn: sinon.stub() 74 | } 75 | 76 | cli(processStub, consoleStub) 77 | 78 | expect(consoleStub.warn.called).toBeTruthy() 79 | 80 | const output = consoleStub.warn.getCalls().map(c => c.args).join('\n') 81 | 82 | expect(output).toContain('Line: 6 Maximum amount of newlines exceeded.') 83 | expect(output).toContain(`File: ${filepath}`) 84 | }) 85 | }) 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lintspaces-cli 2 | ============== 3 | 4 | [![Coverage Status](https://coveralls.io/repos/github/evanshortiss/lintspaces-cli/badge.svg?branch=master)](https://coveralls.io/github/evanshortiss/lintspaces-cli?branch=master) 5 | [![npm version](https://badge.fury.io/js/lintspaces-cli.svg)](https://www.npmjs.com/package/lintspaces-cli) 6 | [![npm downloads](https://img.shields.io/npm/dm/lintspaces-cli.svg?style=flat)](https://www.npmjs.com/package/lintspaces-cli) 7 | 8 | 9 | Simple as pie CLI for the node-lintspaces module. Supports all the usual 10 | lintspaces args that the Grunt, Gulp and vanilla node.js module support. 11 | 12 | ## Install 13 | ``` 14 | $ npm install -g lintspaces-cli 15 | ``` 16 | 17 | 18 | ## Help Output 19 | ``` 20 | $ lintspaces --help 21 | 22 | Usage: lintspaces [options] 23 | 24 | Options: 25 | -V, --version output the version number 26 | -n, --newline Require newline at end of file. 27 | -g, --guessindentation Tries to guess the indention of a line depending on previous lines. 28 | -b, --skiptrailingonblank Skip blank lines in trailingspaces check. 29 | -it, --trailingspacestoignores Ignore trailing spaces in ignores. 30 | -l, --maxnewlines Specify max number of newlines between blocks. 31 | -t, --trailingspaces Tests for useless whitespaces (trailing whitespaces) at each line ending of all files. 32 | -d, --indentation Check indentation is "tabs" or "spaces". 33 | -s, --spaces Used in conjunction with -d to set number of spaces. 34 | -i, --ignores Comma separated list of ignores built in ignores. (default: []) 35 | -r, --regexignores Comma separated list of ignores that should be parsed as Regex (default: []) 36 | -e, --editorconfig Use editorconfig specified at this file path for settings. 37 | -o, --allowsBOM Sets the allowsBOM option to true 38 | -v, --verbose Be verbose when processing files 39 | -., --matchdotfiles Match dotfiles 40 | --endofline Enables EOL checks. Supports "LF" or "CRLF" or "CR" values 41 | --json Output the raw JSON results from lintspaces 42 | -h, --help output usage information 43 | ``` 44 | 45 | ## Example Commands 46 | 47 | Check all JavaScript files in directory for trailing spaces and newline at the 48 | end of file: 49 | 50 | ``` 51 | lintspaces -n -t ./*.js 52 | ``` 53 | 54 | Check all js and css files 55 | 56 | ``` 57 | lintspaces -n -t src/**/*.js src/**/*.css 58 | ``` 59 | 60 | Check that 2 spaces are used as indent: 61 | 62 | ``` 63 | lintspaces -nt -s 2 -d spaces ./*.js 64 | ``` 65 | 66 | ## Using Ignores 67 | lintspaces supports ignores, and we added support for those in version 0.3.0 of 68 | this module. 69 | 70 | Using built in ignores can be done like so: 71 | 72 | ``` 73 | lintspaces -i 'js-comments' -i 'c-comments' 74 | ``` 75 | 76 | To add Regex ignores a different flag is required: 77 | 78 | ``` 79 | lintspaces -r '/pointless|regex/g' -r '/and|another/gi ' 80 | ``` 81 | 82 | ## Changelog 83 | 84 | * 0.7.1 - Fix "is not a file" errors 85 | 86 | * 0.7.0 - Bump dependencies. Add `--json` output flag. Add tests. Normalise arguments to lowercase. 87 | 88 | * 0.6.0 - Added support for matching dotfiles (dzięki @jrencz) 89 | 90 | * 0.5.0 - Add support for glob patterns (thanks @jantimon) 91 | 92 | * 0.4.0 - Add verbose option (thank you @gemal) 93 | 94 | * 0.3.0 - Add support for Regex ignores by adding the *--regexIgnores* option. 95 | 96 | * 0.2.0 - Update to use lintspaces@0.5.0 and support new allowsBOM and 97 | endOfLine options. 98 | 99 | * 0.1.1 - Support for node.js v4+ (thank you @gurdiga) 100 | 101 | * 0.1.0 - Initial stable release 102 | 103 | * < 0.1.0 - Dark ages... 104 | 105 | ## Contributors 106 | * [Vlad Gurdiga (@gurdiga)](https://github.com/gurdiga) 107 | * [Henrik Gemal (@gemal)](https://github.com/gemal) 108 | * [Jan Nicklas (@jantimon)](https://github.com/jantimon) 109 | * [Jarek Rencz (@jrencz)](https://github.com/jrencz) 110 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = function (process, console) { 2 | // It's colours I tell ya. Colours!!! 3 | require('colors') 4 | 5 | const fs = require('fs') 6 | const map = require('lodash.map') 7 | const util = require('util') 8 | const path = require('path') 9 | const Command = require('commander').Command 10 | const glob = require('glob') 11 | const Validator = require('lintspaces') 12 | const version = require('./package.json').version 13 | 14 | // Ensures commander instances are unique per invocation. 15 | // This is only required if running tests or using this as a module. 16 | const program = new Command() 17 | 18 | let validator = null 19 | let targetFiles = null 20 | let files = null 21 | 22 | /** 23 | * Accumlate list items. 24 | * @param {String} 25 | */ 26 | function list(list) { 27 | list = list || [] 28 | 29 | return function(entry) { 30 | list.push(entry) 31 | return list 32 | } 33 | } 34 | 35 | /** 36 | * Check does the provided editorconfig exist 37 | * @param {String} 38 | */ 39 | function resolveEditorConfig(e) { 40 | if (e) { 41 | e = path.resolve(e) 42 | 43 | if (!fs.existsSync(e)) { 44 | console.log('Error: Specified .editorconfig "%s" doesn\'t exist'.red, e) 45 | process.exit(1) 46 | } 47 | 48 | return e 49 | } 50 | 51 | return e 52 | } 53 | 54 | program 55 | .name('lintspaces') 56 | .version(version) 57 | .option('-n, --newline', 'Require newline at end of file.') 58 | .option( 59 | '-g, --guessindentation', 60 | 'Tries to guess the indention of a line depending on previous lines.' 61 | ) 62 | .option( 63 | '-b, --skiptrailingonblank', 64 | 'Skip blank lines in trailingspaces check.' 65 | ) 66 | .option( 67 | '-it, --trailingspacestoignores', 68 | 'Ignore trailing spaces in ignores.' 69 | ) 70 | .option( 71 | '-l, --maxnewlines ', 72 | 'Specify max number of newlines between blocks.', 73 | x => parseInt(x, 10) 74 | ) 75 | .option( 76 | '-t, --trailingspaces', 77 | 'Tests for useless whitespaces' + 78 | ' (trailing whitespaces) at each line ending of all files.' 79 | ) 80 | .option('-d, --indentation ', 'Check indentation is "tabs" or "spaces".') 81 | .option( 82 | '-s, --spaces ', 83 | 'Used in conjunction with -d to set number of spaces.', 84 | x => parseInt(x, 10) 85 | ) 86 | .option( 87 | '-i, --ignores ', 88 | 'Comma separated list of ignores built in ignores.', 89 | list(), 90 | [] 91 | ) 92 | .option( 93 | '-r, --regexignores ', 94 | 'Comma separated list of ignores that should be parsed as Regex', 95 | list(), 96 | [] 97 | ) 98 | .option( 99 | '-e, --editorconfig ', 100 | 'Use editorconfig specified at this file path for settings.', 101 | resolveEditorConfig 102 | ) 103 | .option('-o, --allowsBOM', 'Sets the allowsBOM option to true') 104 | .option('-v, --verbose', 'Be verbose when processing files') 105 | .option('--matchdotfiles', 'Match dotfiles') 106 | .option('--endofline ', 'Enables EOL checks. Supports "LF" or "CRLF" or "CR" values') 107 | .option('--json', 'Output the raw JSON results from lintspaces') 108 | .parse(process.argv) 109 | 110 | // Map regexIgnores to RegExp objects 111 | program.regexIgnores = map(program.regexIgnores, function(r) { 112 | return new RegExp(r) 113 | }) 114 | 115 | 116 | // Setup validator with user options 117 | const opts = program.opts() 118 | 119 | validator = new Validator({ 120 | newline: opts.newline, 121 | newlineMaximum: opts.maxnewlines, 122 | trailingspaces: opts.trailingspaces, 123 | indentation: opts.indentation, 124 | spaces: opts.spaces, 125 | ignores: opts.ignores, 126 | editorconfig: opts.editorconfig, 127 | indentationGuess: opts.guessindentation, 128 | trailingspacesSkipBlanks: opts.skiptrailingonblank, 129 | trailingspacesToIgnores: opts.trailingspacesToIgnores, 130 | allowsBOM: opts.allowsBOM, 131 | verbose: opts.verbose, 132 | endOfLine: opts.endofline 133 | }) 134 | 135 | if (!program.args || program.args.length === 0) { 136 | console.warn('Please provide a list or glob pattern of files to lint.'.red) 137 | process.exit(1) 138 | } 139 | 140 | // Resolve all glob patterns and merge them into one array 141 | targetFiles = program.args.map((file) => { 142 | return glob.sync(file, { 143 | dot: program.matchdotfiles 144 | }) 145 | }) 146 | 147 | // Flatten into single array 148 | targetFiles = Array.prototype.concat.apply( 149 | [], 150 | targetFiles 151 | ) 152 | 153 | targetFiles = targetFiles.filter(fs.existsSync.bind(fs)).filter(function(path) { 154 | return fs.statSync(path).isFile() 155 | }) 156 | 157 | // Run validation 158 | if (program.verbose) { 159 | console.log('Number of files to check: ' + targetFiles.length) 160 | } 161 | for (let file in targetFiles) { 162 | const filepath = path.resolve(targetFiles[file]) 163 | if (program.verbose) { 164 | console.log('Checking: ' + filepath) 165 | } 166 | validator.validate(filepath) 167 | } 168 | files = validator.getInvalidFiles() 169 | 170 | // Output results 171 | if (opts.json) { 172 | console.log(JSON.stringify(files)) 173 | } else { 174 | for (let file in files) { 175 | const curFile = files[file] 176 | console.warn(util.format('\nFile: %s', file).red.underline) 177 | 178 | for (let line in curFile) { 179 | const curLine = curFile[line] 180 | 181 | for (let err in curLine) { 182 | const curErr = curLine[err] 183 | let errMsg = curErr.type 184 | 185 | if (errMsg.toLowerCase() === 'warning') { 186 | errMsg = errMsg.red 187 | } else { 188 | errMsg = errMsg.green 189 | } 190 | 191 | const msg = util.format('Line: %s %s [%s]', line, curErr.message, errMsg) 192 | 193 | console.warn(msg) 194 | } 195 | } 196 | } 197 | } 198 | 199 | // Give error exit code if required 200 | if (Object.keys(files).length) { 201 | process.exit(1) 202 | } 203 | } 204 | --------------------------------------------------------------------------------