Skip to content
This repository has been archived by the owner on Apr 24, 2024. It is now read-only.

Reports refactoring #155

Merged
merged 1 commit into from Apr 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/cli/index.js
Expand Up @@ -34,7 +34,7 @@ if (!isStdin && !program.args.length) {
program.help();
}

reports.addReports(mergedOptions.report);
reports.set(mergedOptions.report);
reports.onStart();

async.series(
Expand Down
2 changes: 1 addition & 1 deletion lib/cli/options.js
Expand Up @@ -55,7 +55,7 @@ function getMergedOptions(config) {
excludeFiles: mergedConfig.excludeFiles,
options: mergedConfig.options || {},

configDictionary: mergedOptions.dictionary,
configDictionary: mergedConfig.dictionary,
configRelativePath: mergedConfig.configRelativePath,
};

Expand Down
3 changes: 2 additions & 1 deletion lib/reports/console.js
Expand Up @@ -60,6 +60,7 @@ function getTyposByCode(code, data) {
}

module.exports = {
name: 'console',
onStart() {
consoleLog('Spelling check:');
},
Expand Down Expand Up @@ -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.');
}
Expand Down
1 change: 1 addition & 0 deletions lib/reports/error_dictionary.js
Expand Up @@ -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 = [];

Expand Down
9 changes: 5 additions & 4 deletions 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);
}
};
1 change: 1 addition & 0 deletions lib/reports/html.js
Expand Up @@ -66,6 +66,7 @@ function loadFile(filename) {
const filename = 'yaspeller_report.html';

module.exports = {
name: 'html',
onResourceComplete(err, data) {
const html = [];
if (err) {
Expand Down
159 changes: 119 additions & 40 deletions 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
*/
1 change: 1 addition & 0 deletions lib/reports/json.js
Expand Up @@ -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));
Expand Down
1 change: 1 addition & 0 deletions lib/reports/markdown.js
Expand Up @@ -32,6 +32,7 @@ function prepareResource(resource) {
const filename = 'yaspeller_report.md';

module.exports = {
name: 'markdown',
onResourceComplete(err, data) {
const text = [];
if (err) {
Expand Down
2 changes: 1 addition & 1 deletion lib/tasks.js
Expand Up @@ -34,7 +34,7 @@ function onResource(err, data, originalText) {
process.exitCode = exitCodes.ERROR_LOADING;
}

reports.onResouceComplete(err, data);
reports.onResourceComplete(err, data);
}

module.exports = {
Expand Down
55 changes: 55 additions & 0 deletions 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);
});
});
14 changes: 14 additions & 0 deletions 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);
}
};
5 changes: 5 additions & 0 deletions test/reports/without_methods.js
@@ -0,0 +1,5 @@
'use strict';

module.exports = {
name: 'example',
};
3 changes: 3 additions & 0 deletions test/reports/without_name.js
@@ -0,0 +1,3 @@
'use strict';

module.exports = {};