From 34df6d284c07a8904245d093c9201808ff2359ca Mon Sep 17 00:00:00 2001 From: Denis Seleznev Date: Sun, 5 Apr 2020 23:56:48 +0300 Subject: [PATCH] Reports refactoring --- lib/cli/index.js | 2 +- lib/cli/options.js | 2 +- lib/reports/console.js | 3 +- lib/reports/error_dictionary.js | 1 + lib/reports/example.js | 9 +- lib/reports/html.js | 1 + lib/reports/index.js | 159 ++++++++++++++++++++++++-------- lib/reports/json.js | 1 + lib/reports/markdown.js | 1 + lib/tasks.js | 2 +- test/reports.test.js | 55 +++++++++++ test/reports/example.js | 14 +++ test/reports/without_methods.js | 5 + test/reports/without_name.js | 3 + 14 files changed, 210 insertions(+), 48 deletions(-) create mode 100644 test/reports.test.js create mode 100644 test/reports/example.js create mode 100644 test/reports/without_methods.js create mode 100644 test/reports/without_name.js diff --git a/lib/cli/index.js b/lib/cli/index.js index 5f40ca5..a71c9d7 100644 --- a/lib/cli/index.js +++ b/lib/cli/index.js @@ -34,7 +34,7 @@ if (!isStdin && !program.args.length) { program.help(); } -reports.addReports(mergedOptions.report); +reports.set(mergedOptions.report); reports.onStart(); async.series( diff --git a/lib/cli/options.js b/lib/cli/options.js index 29639b1..9e33532 100644 --- a/lib/cli/options.js +++ b/lib/cli/options.js @@ -55,7 +55,7 @@ function getMergedOptions(config) { excludeFiles: mergedConfig.excludeFiles, options: mergedConfig.options || {}, - configDictionary: mergedOptions.dictionary, + configDictionary: mergedConfig.dictionary, configRelativePath: mergedConfig.configRelativePath, }; diff --git a/lib/reports/console.js b/lib/reports/console.js index 6e26fd3..605f513 100644 --- a/lib/reports/console.js +++ b/lib/reports/console.js @@ -60,6 +60,7 @@ function getTyposByCode(code, data) { } module.exports = { + name: 'console', onStart() { consoleLog('Spelling check:'); }, @@ -109,7 +110,7 @@ module.exports = { path += '"dictionary" property)'; consoleWarn(`Fix typo or add word to dictionary at ${path} if you are sure about spelling. Docs: ${packageJson.homepage}#configuration`); } - + if (!stats.errors) { consoleOk('No errors.'); } diff --git a/lib/reports/error_dictionary.js b/lib/reports/error_dictionary.js index 0f902fb..49c843d 100644 --- a/lib/reports/error_dictionary.js +++ b/lib/reports/error_dictionary.js @@ -9,6 +9,7 @@ const { consoleError, consoleInfo } = require('../helpers/console'); const filename = 'yaspeller_error_dictionary.json'; module.exports = { + name: 'error_dictionary', onComplete(data) { let buffer = []; diff --git a/lib/reports/example.js b/lib/reports/example.js index 85ac9b2..cfcc04c 100644 --- a/lib/reports/example.js +++ b/lib/reports/example.js @@ -1,13 +1,14 @@ 'use strict'; module.exports = { + name: 'example', onStart() { - console.log('oSstart'); + console.log('onStart'); }, - onResourceComplete(err, data) { - console.log('onResourceComplete: ', err, data); + onResourceComplete(error, data) { + console.log('onResourceComplete', error, data); }, onComplete(data, stats) { - console.log('on: ', data, stats); + console.log('onComplete', data, stats); } }; diff --git a/lib/reports/html.js b/lib/reports/html.js index 1c57eb9..3cb1bcc 100644 --- a/lib/reports/html.js +++ b/lib/reports/html.js @@ -66,6 +66,7 @@ function loadFile(filename) { const filename = 'yaspeller_report.html'; module.exports = { + name: 'html', onResourceComplete(err, data) { const html = []; if (err) { diff --git a/lib/reports/index.js b/lib/reports/index.js index 9f6f011..151ad13 100644 --- a/lib/reports/index.js +++ b/lib/reports/index.js @@ -1,73 +1,152 @@ 'use strict'; const program = require('commander'); -const pth = require('path'); +const { defaultConfig } = require('../config'); + +const { uniq } = require('../helpers/array'); const { consoleError } = require('../helpers/console'); +const { splitByCommas } = require('../helpers/string'); + +const consoleReport = require('./console'); +const errorDictionaryReport = require('./error_dictionary'); +const htmlReport = require('./html'); +const jsonReport = require('./json'); +const markdownReport = require('./markdown'); + +class Reports { + constructor() { + this.buffer = []; + + this.innerReports = [ + consoleReport, + errorDictionaryReport, + htmlReport, + jsonReport, + markdownReport, + ]; + + this.innerReportsByName = this.innerReports.reduce((acc, current) => { + acc[current.name] = current; + + return acc; + }, {}); + + this.stats = { + errors: 0, + hasTypos: false, + ok: 0, + total: 0, + }; + + this.reports = []; + } + + /** + * Set reports. + * + * @param {string|string[]|undefined} names + */ + set(names) { + this.reports = []; + + if (typeof names === 'string') { + names = splitByCommas(names); + } else if (Array.isArray(names)) { + names = names.map(item => item.trim()); + } else { + names = []; + } + + names = uniq(names).filter(Boolean); + if (!names.length) { + names = defaultConfig.report; + } + + names.forEach(name => { + const report = this.innerReportsByName[name] || this.loadExternalReport(name); + if (report) { + this.reports.push(report); + } + }); + } + + /** + * Load external report. + * + * @param {string} name + * @returns {Report|undefined} + */ + loadExternalReport(name) { + try { + const report = require(require.resolve(name, { + paths: ['./'] + })); -const stats = { - errors: 0, - hasTypos: false, - ok: 0, - total: 0, -}; -const reports = []; -const reportNames = new Set(); -const buffer = []; - -module.exports = { - addReports(names) { - names.forEach(function(name) { - const moduleName = pth.extname(name) === '.js' ? name : './' + name; - - if (reportNames.has(moduleName)) { + if (!report.name) { + consoleError(`Missing "name" property in report module "${name}".`); return; } - try { - reports.push(require(moduleName)); - reportNames.add(moduleName); - } catch (e) { - consoleError(`Can't load report module "${moduleName}".`); - consoleError(e); + if (!report.onStart && !report.onComplete && !report.onResourceComplete) { + consoleError(`Missing methods (onStart, onResourceComplete or onComplete) in report module "${name}".`); + return; } - }); - }, + + return report; + } catch (e) { + consoleError(e); + } + } + onStart() { - reports.forEach(function(name) { - name.onStart && name.onStart(); + this.reports.forEach(report => { + report.onStart && report.onStart(); }); - }, + } + onResourceComplete(hasError, data, dictionary) { - stats.total++; + this.stats.total++; const hasTypos = Boolean(data && data.data && data.data.length); if (hasTypos) { - stats.hasTypos = true; + this.stats.hasTypos = true; } if (hasError || hasTypos) { - stats.errors++; + this.stats.errors++; - buffer.push([hasError, data]); + this.buffer.push([hasError, data]); } else { - stats.ok++; + this.stats.ok++; if (!program.onlyErrors) { - buffer.push([hasError, data]); + this.buffer.push([hasError, data]); } } if (!program.onlyErrors || hasError || hasTypos) { - reports.forEach(function(name) { - name.onResourceComplete && name.onResourceComplete(hasError, data, dictionary); + this.reports.forEach(report => { + report.onResourceComplete && report.onResourceComplete(hasError, data, dictionary); }); } - }, + } + onComplete(configPath) { - reports.forEach(function(name) { - name.onComplete && name.onComplete(buffer, stats, configPath); + this.reports.forEach(report => { + report.onComplete && report.onComplete(this.buffer, this.stats, configPath); }); } -}; +} + +module.exports = new Reports(); + +/** + @typedef Report + @type {Object} + @property {string} name + @property {Function?} onStart + @property {Function?} onResourceComplete + @property {Function?} onComplete + */ diff --git a/lib/reports/json.js b/lib/reports/json.js index 4acf945..b44c2f1 100644 --- a/lib/reports/json.js +++ b/lib/reports/json.js @@ -8,6 +8,7 @@ const { consoleError, consoleInfo } = require('../helpers/console'); const filename = 'yaspeller_report.json'; module.exports = { + name: 'json', onComplete(data) { try { fs.writeFileSync(filename, jsonStringify(data)); diff --git a/lib/reports/markdown.js b/lib/reports/markdown.js index 8bd9296..4106fab 100644 --- a/lib/reports/markdown.js +++ b/lib/reports/markdown.js @@ -32,6 +32,7 @@ function prepareResource(resource) { const filename = 'yaspeller_report.md'; module.exports = { + name: 'markdown', onResourceComplete(err, data) { const text = []; if (err) { diff --git a/lib/tasks.js b/lib/tasks.js index 41b4c9e..d58fb86 100644 --- a/lib/tasks.js +++ b/lib/tasks.js @@ -34,7 +34,7 @@ function onResource(err, data, originalText) { process.exitCode = exitCodes.ERROR_LOADING; } - reports.onResouceComplete(err, data); + reports.onResourceComplete(err, data); } module.exports = { diff --git a/test/reports.test.js b/test/reports.test.js new file mode 100644 index 0000000..3aef149 --- /dev/null +++ b/test/reports.test.js @@ -0,0 +1,55 @@ +const assert = require('chai').assert; +const reports = require('../lib/reports'); + +describe('Reports', () => { + it('should set default report', () => { + reports.set(); + + assert.equal(reports.reports.length, 1); + assert.equal(reports.reports[0].name, 'console'); + }); + + it('should set inner reports', () => { + reports.set('console,html'); + + assert.equal(reports.reports.length, 2); + assert.equal(reports.reports[0].name, 'console'); + assert.equal(reports.reports[1].name, 'html'); + + }); + + it('should set inner reports as array of strings', () => { + reports.set(['console', 'html']); + + assert.equal(reports.reports.length, 2); + assert.equal(reports.reports[0].name, 'console'); + assert.equal(reports.reports[1].name, 'html'); + }); + + it('should set internal and external report', () => { + reports.set(['console', './test/reports/example']); + + assert.equal(reports.reports.length, 2); + assert.equal(reports.reports[0].name, 'console'); + assert.equal(reports.reports[1].name, 'example'); + }); + + it('should set internal and unknown external report', () => { + reports.set(['console', './test/reports/example_unknown']); + + assert.equal(reports.reports.length, 1); + assert.equal(reports.reports[0].name, 'console'); + }); + + it('should not set external report without name property', () => { + reports.set(['./test/reports/without_name']); + + assert.equal(reports.reports.length, 0); + }); + + it('should not set external report without methods', () => { + reports.set(['./test/reports/without_methods']); + + assert.equal(reports.reports.length, 0); + }); +}); diff --git a/test/reports/example.js b/test/reports/example.js new file mode 100644 index 0000000..cfcc04c --- /dev/null +++ b/test/reports/example.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = { + name: 'example', + onStart() { + console.log('onStart'); + }, + onResourceComplete(error, data) { + console.log('onResourceComplete', error, data); + }, + onComplete(data, stats) { + console.log('onComplete', data, stats); + } +}; diff --git a/test/reports/without_methods.js b/test/reports/without_methods.js new file mode 100644 index 0000000..315f2a1 --- /dev/null +++ b/test/reports/without_methods.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + name: 'example', +}; diff --git a/test/reports/without_name.js b/test/reports/without_name.js new file mode 100644 index 0000000..8b46fbb --- /dev/null +++ b/test/reports/without_name.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = {};