diff --git a/CHANGE.md b/CHANGE.md index 3cfb95804..bc615bf02 100644 --- a/CHANGE.md +++ b/CHANGE.md @@ -1,6 +1,14 @@ HTMLHint change log ==================== +## ver 0.9.13 (2015-10-25) + +add: + +1. change cli parameter: `--plugin` to `--rulesdir` +2. add formatter directory support +3. add formatters: compact, markdown + ## ver 0.9.10 (2015-10-12) add: diff --git a/bin/formatters/checkstyle.js b/bin/formatters/checkstyle.js new file mode 100644 index 000000000..563d2410f --- /dev/null +++ b/bin/formatters/checkstyle.js @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2015, Yanis Wang + * MIT Licensed + */ +var xml = require('xml'); + +var formatter = { + onEnd: function(hintInfo){ + var arrFiles = []; + var arrAllMessages = hintInfo.arrAllMessages; + arrAllMessages.forEach(function(fileInfo){ + var arrMessages = fileInfo.messages; + var arrErrors = []; + arrMessages.forEach(function(message){ + arrErrors.push({ + error: { + _attr: { + line: message.line, + column: message.col, + severity: message.type, + message: message.message, + source: 'htmlhint.'+message.rule.id + } + } + }); + }); + arrFiles.push({ + file: [ + { + _attr: { + name: fileInfo.file + } + } + ].concat(arrErrors) + }); + }); + var objXml = { + checkstyle: [ + { + _attr: { + version: '4.3' + } + } + ].concat(arrFiles) + }; + console.log(xml(objXml, { + declaration: true, + indent: ' ' + })); + } +}; +module.exports = formatter; diff --git a/bin/formatters/compact.js b/bin/formatters/compact.js new file mode 100644 index 000000000..bba600d4c --- /dev/null +++ b/bin/formatters/compact.js @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2015, Yanis Wang + * MIT Licensed + */ +var formatter = { + onFileHint: function(result){ + result.messages.forEach(function (message) { + console.log('%s: line %d, col %d, %s - %s (%s)', + result.file, + message.line, + message.col, + message.type, + message.message, + message.rule.id + ); + }); + }, + onEnd: function(hintInfo){ + var allHintCount = hintInfo.allHintCount; + if(allHintCount > 0){ + console.log(''); + console.log('%d problems', hintInfo.allHintCount); + } + } +}; +module.exports = formatter; diff --git a/bin/formatters/default.js b/bin/formatters/default.js new file mode 100644 index 000000000..b1a5b3db4 --- /dev/null +++ b/bin/formatters/default.js @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2015, Yanis Wang + * MIT Licensed + */ +var formatter = { + onStart: function(){ + console.log(''); + }, + onConfigLoaded: function(config, configPath){ + console.log(' Config loaded: %s', configPath.cyan); + console.log(''); + }, + onFileHint: function(result, HTMLHint){ + console.log(' '+result.file.white); + var arrLogs = HTMLHint.format(result.messages, { + colors: true, + indent: 6 + }); + arrLogs.forEach(function(str){ + console.log(str); + }); + console.log(''); + }, + onEnd: function(hintInfo){ + var allFileCount = hintInfo.allFileCount; + var allHintCount = hintInfo.allHintCount; + var allHintFileCount = hintInfo.allHintFileCount; + var time = hintInfo.time; + if(allHintCount > 0){ + console.log('Scan %d files, found %d errors in %d files (%d ms)'.red, allFileCount, allHintCount, allHintFileCount, time); + } + else{ + console.log('Scan %d files, without errors (%d ms).'.green, allFileCount, time); + } + } +}; +module.exports = formatter; diff --git a/bin/formatters/json.js b/bin/formatters/json.js new file mode 100644 index 000000000..83002e1a0 --- /dev/null +++ b/bin/formatters/json.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) 2015, Yanis Wang + * MIT Licensed + */ +var formatter = { + onEnd: function(hintInfo){ + console.log(JSON.stringify(hintInfo.arrAllMessages)); + } +}; +module.exports = formatter; diff --git a/bin/formatters/junit.js b/bin/formatters/junit.js new file mode 100644 index 000000000..7ac5bac07 --- /dev/null +++ b/bin/formatters/junit.js @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2015, Yanis Wang + * MIT Licensed + */ +var xml = require('xml'); + +var formatter = { + onEnd: function(hintInfo, HTMLHint){ + var arrTestcase = []; + var arrAllMessages = hintInfo.arrAllMessages; + arrAllMessages.forEach(function(fileInfo){ + var arrMessages = fileInfo.messages; + var arrLogs = HTMLHint.format(arrMessages); + arrTestcase.push({ + testcase: [ + { + _attr: { + name: fileInfo.file, + time: (fileInfo.time / 1000).toFixed(3) + } + }, + { + failure: { + _attr: { + message: 'Found '+arrMessages.length+' errors' + }, + _cdata: arrLogs.join('\r\n') + } + } + ] + }); + }); + var objXml = { + testsuites: [ + { + testsuite: [ + { + _attr: { + name: 'HTMLHint Tests', + time: (hintInfo.time / 1000).toFixed(3), + tests: hintInfo.allFileCount, + failures: arrAllMessages.length + } + } + ].concat(arrTestcase) + } + ] + }; + console.log(xml(objXml, { + declaration: true, + indent: ' ' + })); + } +}; +module.exports = formatter; diff --git a/bin/formatters/markdown.js b/bin/formatters/markdown.js new file mode 100644 index 000000000..62b8a1517 --- /dev/null +++ b/bin/formatters/markdown.js @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2015, Yanis Wang + * MIT Licensed + */ +var formatter = { + onEnd: function(hintInfo, HTMLHint){ + console.log('# TOC'); + var arrToc = []; + var arrContents = []; + var arrAllMessages = hintInfo.arrAllMessages; + arrAllMessages.forEach(function(fileInfo){ + var filePath = fileInfo.file; + var arrMessages = fileInfo.messages; + var errorCount = 0; + var warningCount = 0; + arrMessages.forEach(function(message){ + if(message.type === 'error'){ + errorCount ++; + } + else{ + warningCount ++; + } + }); + arrToc.push(' - ['+filePath+'](#'+filePath+')'); + arrContents.push(''); + arrContents.push('# '+filePath); + arrContents.push(''); + arrContents.push('Found '+errorCount+' errors, '+warningCount+' warnings'); + var arrLogs = HTMLHint.format(arrMessages); + arrContents.push(''); + arrLogs.forEach(function(log){ + arrContents.push(' '+log); + }); + arrContents.push(''); + }); + console.log(arrToc.join('\r\n')+'\r\n'); + console.log(arrContents.join('\r\n')); + } +}; +module.exports = formatter; diff --git a/bin/formatters/unix.js b/bin/formatters/unix.js new file mode 100644 index 000000000..1a5b5aedb --- /dev/null +++ b/bin/formatters/unix.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2015, Yanis Wang + * MIT Licensed + */ +var formatter = { + onFileHint: function(result){ + result.messages.forEach(function (message) { + console.log([ + result.file, + message.line, + message.col, + " " + message.message + ' ['+message.type+'/'+message.rule.id+']' + ].join(":")); + }); + }, + onEnd: function(hintInfo){ + var allHintCount = hintInfo.allHintCount; + if(allHintCount > 0){ + console.log(''); + console.log('%d problems', hintInfo.allHintCount); + } + } +}; +module.exports = formatter; diff --git a/bin/htmlhint b/bin/htmlhint index 239dc181f..ab3bceb60 100755 --- a/bin/htmlhint +++ b/bin/htmlhint @@ -7,7 +7,6 @@ var stripJsonComments = require('strip-json-comments'); var async = require('async'); var glob = require("glob"); var parseGlob = require('parse-glob'); -var xml = require('xml'); var HTMLHint = require("../index").HTMLHint; var pkg = require('../package.json'); @@ -39,6 +38,15 @@ program.on('--help', function(){ console.log(''); }); +// load custom formatters +var mapFormatters = loadFormatters(); +var arrSupportedFormatters = []; +for(var formatterName in mapFormatters){ + if(formatterName !== 'default'){ + arrSupportedFormatters.push(formatterName); + } +} + program .version(pkg.version) .usage(' [options]') @@ -46,7 +54,7 @@ program .option('-c, --config ', 'custom configuration file') .option('-r, --rules ', 'set all of the rules available', map) .option('-R, --rulesdir ', 'load custom rules from file or folder') - .option('-f, --format ', 'output messages as custom format') + .option('-f, --format <'+arrSupportedFormatters.join('|')+'>', 'output messages as custom format') .option('-i, --ignore ', 'add pattern to exclude matches') .parse(process.argv); @@ -60,10 +68,18 @@ if(arrTargets.length === 0){ arrTargets.push('./'); } +// check format +var format = program.format || 'default'; +var formatter = mapFormatters[format]; +if(formatter === undefined){ + console.log('No supported formatter, supported formatters: %s'.red, arrSupportedFormatters.join(', ')); + process.exit(1); +} + hintTargets(arrTargets, { rulesdir: program.rulesdir, ruleset: program.rules, - format: program.format, + formatter: formatter, ignore: program.ignore }); @@ -80,23 +96,23 @@ function listRules(){ } function hintTargets(arrTargets, options){ + var arrAllMessages = []; var allFileCount = 0; var allHintFileCount = 0; var allHintCount = 0; var startTime = new Date().getTime(); + var formatter = options.formatter; + // load custom rules var rulesdir = options.rulesdir; if(rulesdir){ loadCustomRules(rulesdir); } - // custom format - var format = options.format; - var arrAllMessages = []; - - if(!format){ - console.log(''); + // start hint + if(formatter.onStart){ + formatter.onStart(); } var arrTasks = []; arrTargets.forEach(function(target){ @@ -111,27 +127,39 @@ function hintTargets(arrTargets, options){ }); }); async.series(arrTasks, function(){ + // end hint var spendTime = new Date().getTime() - startTime; - // output as custom format - if(format){ - formatResult({ + if(formatter.onEnd){ + formatter.onEnd({ arrAllMessages: arrAllMessages, allFileCount: allFileCount, + allHintFileCount: allHintFileCount, + allHintCount: allHintCount, time: spendTime - }, format); - } - else{ - if(allHintCount > 0){ - console.log('Scan %d files, found %d errors in %d files (%d ms)'.red, allFileCount, allHintCount, allHintFileCount, spendTime); - } - else{ - console.log('Scan %d files, without errors (%d ms).'.green, allFileCount, spendTime); - } + }, HTMLHint); } process.exit(allHintCount > 0 ? 1: 0); }); } +// load all formatters +function loadFormatters(){ + var arrFiles = glob.sync('./formatters/*.js', { + 'cwd': __dirname, + 'dot': false, + 'nodir': true, + 'strict': false, + 'silent': true + }); + var mapFormatters = {}; + arrFiles.forEach(function(file){ + var fileInfo = path.parse(file); + var formatterPath = path.resolve(__dirname, file); + mapFormatters[fileInfo.name] = require(formatterPath); + }); + return mapFormatters; +} + // load custom rles function loadCustomRules(rulesdir){ rulesdir = rulesdir.replace(/\\/g, '/'); @@ -165,154 +193,23 @@ function loadRule(filepath){ catch(e){} } -// output as custom format -function formatResult(hintInfo, format){ - switch(format){ - case 'json': - console.log(JSON.stringify(hintInfo.arrAllMessages)); - break; - case 'junit': - formatJunit(hintInfo); - break; - case 'checkstyle': - formatCheckstyle(hintInfo); - break; - case 'unix': - formatUnix(hintInfo); - break; - default: - console.log('No supported format, supported format:json, junit, checkstyle, unix.'.red); - process.exit(1); - } -} - -// format as junit -function formatJunit(hintInfo){ - var arrTestcase = []; - var arrAllMessages = hintInfo.arrAllMessages; - arrAllMessages.forEach(function(fileInfo){ - var arrMessages = fileInfo.messages; - var arrLogs = HTMLHint.format(arrMessages); - arrTestcase.push({ - testcase: [ - { - _attr: { - name: fileInfo.file, - time: (fileInfo.time / 1000).toFixed(3) - } - }, - { - failure: { - _attr: { - message: 'Found '+arrMessages.length+' errors' - }, - _cdata: arrLogs.join('\r\n') - } - } - ] - }); - }); - var objXml = { - testsuites: [ - { - testsuite: [ - { - _attr: { - name: 'HTMLHint Tests', - time: (hintInfo.time / 1000).toFixed(3), - tests: hintInfo.allFileCount, - failures: arrAllMessages.length - } - } - ].concat(arrTestcase) - } - ] - }; - console.log(xml(objXml, { - declaration: true, - indent: ' ' - })); -} - -// format as checkstyle -function formatCheckstyle(hintInfo){ - var arrFiles = []; - var arrAllMessages = hintInfo.arrAllMessages; - arrAllMessages.forEach(function(fileInfo){ - var arrMessages = fileInfo.messages; - var arrErrors = []; - arrMessages.forEach(function(message){ - arrErrors.push({ - error: { - _attr: { - line: message.line, - column: message.col, - severity: message.type, - message: message.message, - source: 'htmlhint.'+message.rule.id - } - } - }); - }); - arrFiles.push({ - file: [ - { - _attr: { - name: fileInfo.file - } - } - ].concat(arrErrors) - }); - }); - var objXml = { - checkstyle: [ - { - _attr: { - version: '4.3' - } - } - ].concat(arrFiles) - }; - console.log(xml(objXml, { - declaration: true, - indent: ' ' - })); -} - -// format as unix style -function formatUnix(hintInfo){ - hintInfo.arrAllMessages.forEach(function (fileInfo) { - var file = path.relative(process.cwd(), fileInfo.file); - fileInfo.messages.forEach(function (message) { - console.log([ - file, - message.line, - message.col, - " " + message.type, - " " + message.message - ].join(":")); - }); - }); -} - // hint all files function hintAllFiles(target, options, onFinised){ var globInfo = getGlobInfo(target); globInfo.ignore = options.ignore; - // hint count + var formatter = options.formatter; + + // hint result var targetFileCount = 0; var targetHintFileCount = 0; var targetHintCount = 0; - - // custom format - var format = options.format; var arrTargetMessages = []; // init ruleset var ruleset = options.ruleset; if(ruleset === undefined){ - ruleset = getConfig(program.config, globInfo.base, format); + ruleset = getConfig(program.config, globInfo.base, formatter); } // hint queue @@ -322,24 +219,18 @@ function hintAllFiles(target, options, onFinised){ var spendTime = new Date().getTime() - startTime; var hintCount = messages.length; if(hintCount > 0){ - if(format){ - arrTargetMessages.push({ + if(formatter.onFileHint){ + formatter.onFileHint({ 'file': filepath, 'messages': messages, 'time': spendTime - }); - } - else{ - console.log(' '+filepath.white); - var arrLogs = HTMLHint.format(messages, { - colors: true, - indent: 6 - }); - arrLogs.forEach(function(str){ - console.log(str); - }); - console.log(''); + }, HTMLHint); } + arrTargetMessages.push({ + 'file': filepath, + 'messages': messages, + 'time': spendTime + }); targetHintFileCount ++; targetHintCount += hintCount; } @@ -406,8 +297,8 @@ function getGlobInfo(target){ } // search and load config -function getConfig(configFile, base, format){ - if(configFile === undefined && fs.existsSync(base)){ +function getConfig(configPath, base, formatter){ + if(configPath === undefined && fs.existsSync(base)){ // find default config file in parent directory if(fs.statSync(base).isDirectory() === false){ base = path.dirname(base); @@ -415,21 +306,20 @@ function getConfig(configFile, base, format){ while(base){ var tmpConfigFile = path.resolve(base+path.sep, '.htmlhintrc'); if(fs.existsSync(tmpConfigFile)){ - configFile = tmpConfigFile; + configPath = tmpConfigFile; break; } base = base.substring(0,base.lastIndexOf(path.sep)); } } - if(fs.existsSync(configFile)){ - var config = fs.readFileSync(configFile, 'utf-8'), + if(fs.existsSync(configPath)){ + var config = fs.readFileSync(configPath, 'utf-8'), ruleset; try{ ruleset = JSON.parse(stripJsonComments(config)); - if(!format){ - console.log(' Config loaded: %s', configFile.cyan); - console.log(''); + if(formatter.onConfigLoaded){ + formatter.onConfigLoaded(ruleset, configPath); } } catch(e){}