From 38c557755ce0cfc00007dbe26931998e2e8af706 Mon Sep 17 00:00:00 2001 From: Jesus David Garcia Gomez Date: Tue, 9 May 2017 14:53:41 -0700 Subject: [PATCH] Add new stylish formatter * Fix how `formatter` is used in `cli.ts` * Fix #187 --- .sonarrc | 4 +- package.json | 2 +- src/lib/cli.ts | 9 ++- src/lib/formatters/stylish/stylish.ts | 66 +++++++++++++++++++ src/lib/sonar.ts | 8 +++ tests/lib/cli.ts | 4 +- .../formatters/fixtures/list-of-problems.ts | 34 ++++++++++ tests/lib/formatters/json.ts | 1 - tests/lib/formatters/stylish.ts | 59 +++++++++++++++++ 9 files changed, 177 insertions(+), 10 deletions(-) create mode 100644 src/lib/formatters/stylish/stylish.ts create mode 100644 tests/lib/formatters/stylish.ts diff --git a/.sonarrc b/.sonarrc index 74adf6464c0..a27393d55cf 100644 --- a/.sonarrc +++ b/.sonarrc @@ -1,11 +1,11 @@ { "collector": { - "name": "jsdom", + "name": "cdp", "options": { "waitFor": 100 } }, - "formatter": "json", + "formatter": "stylish", "rules": { "axe": "warning", "disallowed-headers": "warning", diff --git a/package.json b/package.json index fe8cc1b4765..b11644ae774 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,8 @@ "bugs": "https://github.com/MicrosoftEdge/Sonar/issues", "homepage": "https://github.com/MicrosoftEdge/Sonar#readme", "dependencies": { - "browserslist": "^2.1.1", "axe-core": "^2.2.0", + "browserslist": "^2.1.1", "chalk": "^1.1.3", "chrome-remote-interface": "^0.22.0", "debug": "^2.6.1", diff --git a/src/lib/cli.ts b/src/lib/cli.ts index 9b2b13f0761..ec458a5079f 100644 --- a/src/lib/cli.ts +++ b/src/lib/cli.ts @@ -37,12 +37,11 @@ export const cli = { /** Executes the CLI based on an array of arguments that is passed in. */ execute: async (args: string | Array | Object): Promise => { - const format = (results) => { + const format = (formatterName, results) => { const formatters = resourceLoader.getFormatters(); + const formatter = formatters.get(formatterName) || formatters.get('json'); - formatters.forEach((formatter) => { - formatter.format(results); - }); + formatter.format(results); }; const currentOptions = options.parse(args); @@ -88,7 +87,7 @@ export const cli = { return result.severity === Severity.error; }); - format(results); + format(engine.formatter, results); if (hasError) { exitCode = 1; diff --git a/src/lib/formatters/stylish/stylish.ts b/src/lib/formatters/stylish/stylish.ts new file mode 100644 index 00000000000..3ee8fd065e0 --- /dev/null +++ b/src/lib/formatters/stylish/stylish.ts @@ -0,0 +1,66 @@ +/** + * @fileoverview The basic formatter, it just a table format with diferent colors + * for errors and warnings. + * + * This formatter is based on [eslint stylish formatter](https://github.com/eslint/eslint/blob/master/lib/formatters/stylish.js) + */ + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +import * as chalk from 'chalk'; +import * as _ from 'lodash'; +import * as table from 'text-table'; + +import { debug as d } from '../../utils/debug'; +import { IFormatter, Severity } from '../../types'; // eslint-disable-line no-unused-vars +import * as logger from '../../utils/logging'; + +const debug = d(__filename); + +const pluralize = (text, count) => { + return `${text}${count !== 1 ? 's' : ''}`; +}; + +// ------------------------------------------------------------------------------ +// Formatter +// ------------------------------------------------------------------------------ + +const formatter: IFormatter = { + /** Format the problems grouped by `resource` name and sorted by line and column number */ + format(messages) { + + debug('Formatting results'); + + const resources = _.groupBy(messages, 'resource'); + + _.forEach(resources, (msgs, resource) => { + let warnings = 0; + let errors = 0; + const sortedMessages = _.sortBy(msgs, ['line', 'column']); + const tableData = []; + + logger.log(chalk.cyan(`${resource}`)); + _.forEach(sortedMessages, (msg) => { + const severity = Severity.error === msg.severity ? chalk.red('Error') : chalk.yellow('Warning'); + + if (Severity.error === msg.severity) { + errors++; + } else { + warnings++; + } + tableData.push([severity, msg.message, msg.ruleId]); + }); + + logger.log(table(tableData)); + + const color = errors > 0 ? chalk.red : chalk.yellow; + + logger.log(color.bold(`\u2716 Found ${errors} ${pluralize('error', errors)} and ${warnings} ${pluralize('warning', warnings)}`)); + logger.log(''); + }); + } +}; + +export default formatter; diff --git a/src/lib/sonar.ts b/src/lib/sonar.ts index 6ce5f80b055..1b6a446fe80 100644 --- a/src/lib/sonar.ts +++ b/src/lib/sonar.ts @@ -33,6 +33,7 @@ export class Sonar extends EventEmitter { private messages: Array private browsersList: Array = []; private ignoredUrls: Map; + private _formatter: string get pageContent() { return this.collector.html; @@ -46,6 +47,10 @@ export class Sonar extends EventEmitter { return this.browsersList; } + get formatter() { + return this._formatter; + } + private isIgnored(urls: RegExp[], resource: string) { if (!urls) { return false; @@ -72,6 +77,9 @@ export class Sonar extends EventEmitter { this.browsersList = browserslist(config.browserslist); } + debug('Setting the selected formatter'); + this._formatter = config.formatter; + debug('Initializing ignored urls'); this.ignoredUrls = new Map(); if (config.ignoredUrls) { diff --git a/tests/lib/cli.ts b/tests/lib/cli.ts index f949f138e53..8e10281f29d 100644 --- a/tests/lib/cli.ts +++ b/tests/lib/cli.ts @@ -14,7 +14,9 @@ const sonar = { const formatter = { format: () => { } }; const resourceLoader = { getFormatters() { - return [formatter]; + return new Map([ + ['json', formatter] + ]); } }; const logger = { diff --git a/tests/lib/formatters/fixtures/list-of-problems.ts b/tests/lib/formatters/fixtures/list-of-problems.ts index 6338a607090..c11f483c92b 100644 --- a/tests/lib/formatters/fixtures/list-of-problems.ts +++ b/tests/lib/formatters/fixtures/list-of-problems.ts @@ -33,9 +33,43 @@ const multipleproblems: Array = [{ severity: Severity.warning }]; +const multipleproblemsandresources: Array = [{ + column: 10, + line: 1, + message: 'This is a problem in line 1 column 10', + resource: 'http://myresource.com/', + ruleId: 'random-rule', + severity: Severity.warning +}, +{ + column: 1, + line: 10, + message: 'This is a problem in line 10', + resource: 'http://myresource.com/', + ruleId: 'random-rule', + severity: Severity.warning +}, +{ + column: 1, + line: 5, + message: 'This is a problem in line 5', + resource: 'http://myresource2.com/', + ruleId: 'random-rule', + severity: Severity.error +}, +{ + column: 1, + line: 1, + message: 'This is a problem in line 1 column 1', + resource: 'http://myresource2.com/', + ruleId: 'random-rule', + severity: Severity.warning +}]; + const noproblems: Array = []; export { multipleproblems, + multipleproblemsandresources, noproblems }; diff --git a/tests/lib/formatters/json.ts b/tests/lib/formatters/json.ts index ccf8a43d263..8b6ecc70ef1 100644 --- a/tests/lib/formatters/json.ts +++ b/tests/lib/formatters/json.ts @@ -11,7 +11,6 @@ import * as problems from './fixtures/list-of-problems'; import { Severity } from '../../../src/lib/types'; test.beforeEach((t) => { - // const log = sinon.spy(logger, 'log'); sinon.spy(logging, 'log'); t.context.logger = logging; diff --git a/tests/lib/formatters/stylish.ts b/tests/lib/formatters/stylish.ts new file mode 100644 index 00000000000..72686f165e0 --- /dev/null +++ b/tests/lib/formatters/stylish.ts @@ -0,0 +1,59 @@ +import test from 'ava'; +import * as chalk from 'chalk'; +import * as sinon from 'sinon'; +import * as proxyquire from 'proxyquire'; +import * as table from 'text-table'; + +const logging = { log() { } }; + +proxyquire('../../../src/lib/formatters/stylish/stylish', { '../../utils/logging': logging }); + +import stylish from '../../../src/lib/formatters/stylish/stylish'; +import * as problems from './fixtures/list-of-problems'; + +test.beforeEach((t) => { + sinon.spy(logging, 'log'); + + t.context.logger = logging; +}); + +test.afterEach((t) => { + t.context.logger.log.restore(); +}); + +test(`Stylish formatter doesn't print anything if no values`, (t) => { + stylish.format(problems.noproblems); + + t.is(t.context.logger.log.callCount, 0); +}); + +test(`Stylish formatter prints a table and a summary for each resource`, (t) => { + stylish.format(problems.multipleproblemsandresources); + + const log = t.context.logger.log; + let problem = problems.multipleproblemsandresources[0]; + let tableData = []; + + tableData.push([chalk.yellow('Warning'), problem.message, problem.ruleId]); + problem = problems.multipleproblemsandresources[1]; + tableData.push([chalk.yellow('Warning'), problem.message, problem.ruleId]); + + let tableString = table(tableData); + + t.is(log.args[0][0], chalk.cyan('http://myresource.com/')); + t.is(log.args[1][0], tableString); + t.is(log.args[2][0], chalk.yellow.bold(`\u2716 Found 0 errors and 2 warnings`)); + t.is(log.args[3][0], ''); + t.is(log.args[4][0], chalk.cyan('http://myresource2.com/')); + + tableData = []; + problem = problems.multipleproblemsandresources[3]; + tableData.push([chalk.yellow('Warning'), problem.message, problem.ruleId]); + problem = problems.multipleproblemsandresources[2]; + tableData.push([chalk.red('Error'), problem.message, problem.ruleId]); + tableString = table(tableData); + + t.is(log.args[5][0], tableString); + t.is(log.args[6][0], chalk.red.bold(`\u2716 Found 1 error and 1 warning`)); + t.is(log.args[7][0], ''); +});