├── .gitignore ├── AUTHORS ├── LICENSE ├── README.md ├── bin └── cli.js ├── lib └── formatters │ ├── index.js │ ├── json.js │ └── simple.js ├── package.json └── test ├── fixtures ├── clean.html ├── dirty.html └── htmllintrc.json └── globals.js /.gitignore: -------------------------------------------------------------------------------- 1 | # coverage 2 | lib-cov 3 | coverage 4 | 5 | # node 6 | node_modules 7 | npm-debug.log 8 | 9 | # phpstorm 10 | .idea -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Michael Coleman (http://github.com/coalman) 2 | Andrew Messier (http://github.com/messman) 3 | Marshall Lochbaum (http://github.com/mlochbaum) 4 | Roman Myers (http://github.com/romnempire) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Michael Coleman 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | htmllint-cli 2 | ============ 3 | 4 | [![npm version](http://img.shields.io/npm/v/htmllint-cli.svg?style=flat-square)](https://npmjs.org/package/htmllint-cli) 5 | [![ISC license](http://img.shields.io/npm/l/htmllint-cli.svg?style=flat-square)](https://npmjs.org/package/htmllint-cli) 6 | [![dependencies](http://img.shields.io/david/htmllint/htmllint-cli.svg?style=flat-square)](https://david-dm.org/htmllint/htmllint-cli) 7 | [![devDependencies](http://img.shields.io/david/dev/htmllint/htmllint-cli.svg?style=flat-square)](https://david-dm.org/htmllint/htmllint-cli) 8 | 9 | [![stories in ready](https://badge.waffle.io/htmllint/htmllint-cli.svg?label=ready&title=Ready)](http://waffle.io/htmllint/htmllint-cli) 10 | 11 | Installing 12 | ---------- 13 | 14 | Install [nodejs](http://nodejs.org/) and install the `htmllint-cli` module globally: 15 | 16 | ```sh 17 | # you may have to sudo this line depending on your installation 18 | $ npm install -g htmllint-cli 19 | ``` 20 | 21 | Once installed, create a configuration file for your project: 22 | 23 | ```sh 24 | $ cd your-project 25 | $ htmllint init 26 | ``` 27 | 28 | This should create a `.htmllintrc` file in your current directory. This file should 29 | be a valid JSON file that contains options defined 30 | [on the htmllint wiki](https://github.com/htmllint/htmllint/wiki/Options). 31 | 32 | After creating your configuration, you can lint some files like so: 33 | 34 | ```sh 35 | $ htmllint index.html 36 | # also supports glob expansions 37 | $ htmllint **/*.html 38 | $ htmllint # by default expands to **/*.html 39 | 40 | $ htmllint --help # to get more information 41 | ``` 42 | 43 | Contributing 44 | ------------ 45 | 46 | You can use `npm link` to help with development. 47 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var chalk = require('chalk'), 4 | cjson = require('cjson'), 5 | Liftoff = require('liftoff'), 6 | fs = require('fs'), 7 | glob = require('glob'), 8 | path = require('path'), 9 | Promise = require('bluebird'); 10 | 11 | var readFilePromise = Promise.promisify(fs.readFile), 12 | globPromise = Promise.promisify(glob); 13 | 14 | var app = new Liftoff({ 15 | processTitle: 'htmllint', 16 | moduleName: 'htmllint', 17 | configName: '.htmllint', 18 | extensions: { 19 | 'rc': null 20 | } 21 | }); 22 | 23 | var argv = require('yargs') 24 | .usage([ 25 | 'Lints html files with htmllint.', 26 | 'Usage: $0 [OPTIONS] [ARGS]' 27 | ].join('\n')) 28 | .example('$0', 'lints all html files in the cwd and all child directories') 29 | .example('$0 init', 'creates a default .htmllintrc in the cwd') 30 | .example('$0 *.html', 'lints all html files in the cwd') 31 | .example('$0 public/*.html', 'lints all html files in the public directory') 32 | .default('rc', null) 33 | .describe('rc', 'path to a htmllintrc file to use (json)') 34 | .option('format', { 35 | describe: 'format to output', 36 | choices: ['simple', 'json'], 37 | default: 'simple' 38 | }) 39 | .default('cwd', null) 40 | .describe('cwd', 'path to use for the current working directory') 41 | .default('stdin', false) 42 | .describe('stdin', 'accept input from stdin (using option value for filename in output)') 43 | .argv; 44 | 45 | var STDIN_FILENO = 0; 46 | var args = argv._; 47 | 48 | app.launch({ 49 | cwd: argv.cwd, 50 | configPath: argv.rc 51 | }, function (env) { 52 | var cwd = argv.cwd || process.cwd(); 53 | 54 | var htmllintPath = 'htmllint'; 55 | 56 | if (env.modulePath) { 57 | var cliPackage = require('../package.json'), 58 | semver = require('semver'); 59 | 60 | var acceptedRange = cliPackage.dependencies.htmllint, 61 | localVersion = env.modulePackage.version; 62 | 63 | if (semver.satisfies(localVersion, acceptedRange)) { 64 | htmllintPath = env.modulePath; 65 | } else { 66 | console.log( 67 | chalk.red('local htmllint version is not supported:'), 68 | chalk.magenta(localVersion, '!=', acceptedRange) 69 | ); 70 | console.log('using builtin version of htmllint'); 71 | } 72 | } 73 | 74 | var htmllint = require(htmllintPath); 75 | var formatters = require('../lib/formatters'); 76 | 77 | if (args[0] === 'init') { 78 | // copy .htmllintrc file 79 | var srcPath = path.join(__dirname, '../lib/default_cfg.json'), 80 | outputPath = path.join(env.cwd, '.htmllintrc'); 81 | 82 | var opts = htmllint.Linter.getOptions('default'), 83 | config = JSON.stringify(opts, null, 4); 84 | config = '{\n "plugins": [], // npm modules to load\n' 85 | + config.slice(1); 86 | 87 | fs.writeFile(outputPath, config, function (err) { 88 | if (err) { 89 | console.error('error writing config file: ', err); 90 | } 91 | }); 92 | return; 93 | } 94 | 95 | if (!env.configPath) { 96 | console.log( 97 | chalk.red('local .htmllintrc file not found'), 98 | '(you can create one using "htmllint init")' 99 | ); 100 | process.exit(1); 101 | } 102 | 103 | var cfg = cjson.load(env.configPath); 104 | 105 | htmllint.use(cfg.plugins || []); 106 | delete cfg.plugins; 107 | 108 | if (argv.stdin) { 109 | args.unshift(STDIN_FILENO); 110 | } 111 | if (!args.length) { 112 | args = ['**/*.html']; 113 | } 114 | 115 | function lintFile(filename) { 116 | var p; 117 | if (filename === STDIN_FILENO) { 118 | filename = argv.stdin === true ? 'stdin' : argv.stdin; 119 | p = new Promise(function (resolve, reject) { 120 | process.stdin.resume(); 121 | process.stdin.setEncoding('utf8'); 122 | 123 | var content = ''; 124 | process.stdin.on('data', function (chunk) { 125 | content += chunk; 126 | }); 127 | process.stdin.on('end', function () { 128 | resolve(content); 129 | }); 130 | process.stdin.on('error', function (err) { 131 | reject(err); 132 | }); 133 | }); 134 | } else { 135 | var filepath = path.resolve(cwd, filename); 136 | p = readFilePromise(filepath, 'utf8'); 137 | } 138 | 139 | return p.then(function (src) { 140 | return htmllint(src, cfg); 141 | }) 142 | .then(function (issues) { 143 | formatters.formatMessage.call(htmllint, argv.format, filename, issues); 144 | return { errorCount: issues.length }; 145 | }) 146 | .catch(function (err) { 147 | // MC: muahahahahah :D 148 | throw ('[htmllint error in ' + filename + ' ] ' + err); 149 | }); 150 | } 151 | 152 | Promise.all( 153 | args.map(function (pattern) { 154 | if (pattern === STDIN_FILENO) { 155 | return STDIN_FILENO; 156 | } 157 | return globPromise(pattern, { cwd: cwd }); 158 | }) 159 | ).then(function (filesArr) { 160 | var files = Array.prototype.concat.apply([], filesArr); 161 | 162 | return Promise.settle( 163 | files.map(lintFile) 164 | ); 165 | }, function (err) { 166 | console.error(chalk.red.bold('error during glob expansion:'), err); 167 | }).done(function (results) { 168 | var errorCount = 0; 169 | 170 | results.forEach(function (result) { 171 | if (result.isFulfilled()) { 172 | var resultValue = result.value(); 173 | 174 | errorCount += resultValue.errorCount; 175 | } else { 176 | console.error(chalk.bold.red(result.reason())); 177 | } 178 | }); 179 | 180 | formatters.done.call(htmllint, argv.format, errorCount, results.length); 181 | 182 | if (errorCount > 0) { 183 | process.exit(1); 184 | } 185 | }); 186 | }); 187 | -------------------------------------------------------------------------------- /lib/formatters/index.js: -------------------------------------------------------------------------------- 1 | 2 | var formatters = { 3 | simple: require('./simple'), 4 | json: require('./json'), 5 | }; 6 | 7 | exports.formatMessage = function (formatter, filename, issues) { 8 | if (!(formatter in formatters)) { 9 | throw ('[htmllint] formatter \'' + formatter + '\' not found'); 10 | } 11 | formatters[formatter].formatMessage.call(this, filename, issues); 12 | }; 13 | 14 | exports.done = function (formatter, errorCount, fileCount) { 15 | formatters[formatter].done.call(this, errorCount, fileCount); 16 | }; 17 | -------------------------------------------------------------------------------- /lib/formatters/json.js: -------------------------------------------------------------------------------- 1 | 2 | var results = []; 3 | 4 | exports.formatMessage = function (filename, issues) { 5 | var that = this; 6 | var result = { 7 | filePath: filename, 8 | messages: issues.map(function (issue) { 9 | return Object.assign({}, issue, { 10 | message: that.messages.renderIssue(issue), 11 | }); 12 | }), 13 | }; 14 | results.push(result); 15 | }; 16 | 17 | exports.done = function (errorCount, fileCount) { 18 | console.log(JSON.stringify(results)); 19 | }; 20 | -------------------------------------------------------------------------------- /lib/formatters/simple.js: -------------------------------------------------------------------------------- 1 | 2 | var chalk = require('chalk'); 3 | 4 | exports.formatMessage = function (filename, issues) { 5 | var that = this; 6 | issues.forEach(function (issue) { 7 | var msg = [ 8 | chalk.magenta(filename), ': ', 9 | 'line ', issue.line, ', ', 10 | 'col ', issue.column, ', ', 11 | chalk.red(that.messages.renderIssue(issue)) 12 | ].join(''); 13 | 14 | console.log(msg); 15 | }); 16 | }; 17 | 18 | exports.done = function (errorCount, fileCount) { 19 | console.log(''); 20 | console.log(chalk.yellow('[htmllint] found %d errors out of %d files'), 21 | errorCount, fileCount); 22 | }; 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "htmllint-cli", 3 | "version": "0.0.7", 4 | "description": "A simple cli for htmllint.", 5 | "bin": { 6 | "htmllint": "./bin/cli.js" 7 | }, 8 | "preferGlobal": true, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/htmllint/htmllint-cli.git" 12 | }, 13 | "license": "ISC", 14 | "bugs": { 15 | "url": "https://github.com/htmllint/htmllint-cli/issues" 16 | }, 17 | "homepage": "https://github.com/htmllint/htmllint-cli", 18 | "engines": { 19 | "node": ">=4" 20 | }, 21 | "dependencies": { 22 | "bluebird": "^3.5.1", 23 | "chalk": "^2.4.0", 24 | "cjson": "^0.5.0", 25 | "glob": "^7.1.2", 26 | "htmllint": "^0.7.2", 27 | "liftoff": "^2.5.0", 28 | "semver": "^5.5.0", 29 | "yargs": "^11.0.0" 30 | }, 31 | "devDependencies": { 32 | "chai": "^4.1.2", 33 | "mocha": "^5.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/fixtures/clean.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/dirty.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/fixtures/htmllintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /test/globals.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | 3 | global.expect = chai.expect; 4 | global.assert = chai.assert; 5 | --------------------------------------------------------------------------------