From 1d080867fe5187ba8f6e5e6024bc13720ae0be58 Mon Sep 17 00:00:00 2001 From: John Cline <2057878+clinejj@users.noreply.github.com> Date: Wed, 25 Nov 2020 14:51:32 -0500 Subject: [PATCH 1/3] Add support for a TAP formatter --- docs/user-guide/usage/options.md | 1 + lib/__tests__/__snapshots__/cli.test.js.snap | 2 +- lib/__tests__/standalone-formatter.test.js | 2 +- lib/formatters/__tests__/tapFormatter.test.js | 200 ++++++++++++++++++ lib/formatters/index.js | 1 + lib/formatters/tapFormatter.js | 41 ++++ .../__tests__/getFormatterOptionsText.test.js | 4 +- types/stylelint/index.d.ts | 9 +- 8 files changed, 255 insertions(+), 5 deletions(-) create mode 100644 lib/formatters/__tests__/tapFormatter.test.js create mode 100644 lib/formatters/tapFormatter.js diff --git a/docs/user-guide/usage/options.md b/docs/user-guide/usage/options.md index f4850701a8..33894a04f5 100644 --- a/docs/user-guide/usage/options.md +++ b/docs/user-guide/usage/options.md @@ -48,6 +48,7 @@ Options are: - `compact` - `json` (default for Node API) - `string` (default for CLI) +- `tap` - `unix` - `verbose` diff --git a/lib/__tests__/__snapshots__/cli.test.js.snap b/lib/__tests__/__snapshots__/cli.test.js.snap index 2cda5d64de..fed9b943b5 100644 --- a/lib/__tests__/__snapshots__/cli.test.js.snap +++ b/lib/__tests__/__snapshots__/cli.test.js.snap @@ -96,7 +96,7 @@ exports[`CLI --help 1`] = ` --formatter, -f [default: \\"string\\"] - The output formatter: \\"compact\\", \\"json\\", \\"string\\", \\"unix\\" or \\"verbose\\". + The output formatter: \\"compact\\", \\"json\\", \\"string\\", \\"tap"\\, \\"unix\\" or \\"verbose\\". --custom-formatter diff --git a/lib/__tests__/standalone-formatter.test.js b/lib/__tests__/standalone-formatter.test.js index 19ab9dfef2..bbf0d5f594 100644 --- a/lib/__tests__/standalone-formatter.test.js +++ b/lib/__tests__/standalone-formatter.test.js @@ -45,7 +45,7 @@ it('standalone with invalid formatter option', () => { }).catch((err) => { expect(err).toEqual( new Error( - 'You must use a valid formatter option: "compact", "json", "string", "unix", "verbose" or a function', + 'You must use a valid formatter option: "compact", "json", "string", "tap", "unix", "verbose" or a function', ), ); }); diff --git a/lib/formatters/__tests__/tapFormatter.test.js b/lib/formatters/__tests__/tapFormatter.test.js new file mode 100644 index 0000000000..5b88806841 --- /dev/null +++ b/lib/formatters/__tests__/tapFormatter.test.js @@ -0,0 +1,200 @@ +'use strict'; + +const prepareFormatterOutput = require('./prepareFormatterOutput'); +const tapFormatter = require('../tapFormatter'); + +describe('tapFormatter', () => { + let actualTTY; + let actualColumns; + + beforeAll(() => { + actualTTY = process.stdout.isTTY; + actualColumns = process.stdout.columns; + }); + + afterAll(() => { + process.stdout.isTTY = actualTTY; + process.stdout.columns = actualColumns; + }); + + it('outputs no warnings', () => { + const results = [ + { + source: 'path/to/file.css', + errored: false, + warnings: [], + deprecations: [], + invalidOptionWarnings: [], + }, + ]; + + const output = tapFormatter(results); + + expect(output).toBe( + ` +TAP version 13 +1..1 +ok 1 - path/to/file.css +`.trimStart(), + ); + }); + + it('outputs warnings', () => { + const results = [ + { + source: 'path/to/file.css', + errored: true, + warnings: [ + { + line: 1, + column: 1, + rule: 'bar', + severity: 'error', + text: 'Unexpected foo', + }, + { + line: 10, + column: 1, + rule: 'bar2', + severity: 'error', + text: 'Unexpected foo 2', + }, + ], + deprecations: [], + invalidOptionWarnings: [], + }, + ]; + + const output = prepareFormatterOutput(results, tapFormatter); + + expect(output).toBe( + ` +TAP version 13 +1..1 +not ok 1 - path/to/file.css +--- +messages: + - message: "Unexpected foo" + severity: error + data: + line: 1 + column: 1 + ruleId: bar + - message: "Unexpected foo 2" + severity: error + data: + line: 10 + column: 1 + ruleId: bar2 +--- +`.trim(), + ); + }); + + it('outputs warnings without stdout `TTY`', () => { + process.stdout.isTTY = false; + + const results = [ + { + source: 'path/to/file.css', + errored: true, + warnings: [ + { + line: 1, + column: 1, + rule: 'bar', + severity: 'error', + text: 'Unexpected foo', + }, + ], + deprecations: [], + invalidOptionWarnings: [], + }, + ]; + + const output = prepareFormatterOutput(results, tapFormatter); + + expect(output).toBe( + ` +TAP version 13 +1..1 +not ok 1 - path/to/file.css +--- +messages: + - message: "Unexpected foo" + severity: error + data: + line: 1 + column: 1 + ruleId: bar +--- +`.trim(), + ); + }); + + it('output warnings with more than 80 characters and `process.stdout.columns` equal 90 characters', () => { + // For Windows tests + process.stdout.isTTY = true; + process.stdout.columns = 90; + + const results = [ + { + source: 'path/to/file.css', + errored: true, + warnings: [ + { + line: 1, + column: 1, + rule: 'bar-very-very-very-very-very-long', + severity: 'error', + text: + 'Unexpected very very very very very very very very very very very very very long foo', + }, + ], + deprecations: [], + invalidOptionWarnings: [], + }, + ]; + + const output = prepareFormatterOutput(results, tapFormatter); + + expect(output).toBe( + ` +TAP version 13 +1..1 +not ok 1 - path/to/file.css +--- +messages: + - message: "Unexpected very very very very very very very very very very very very very long foo" + severity: error + data: + line: 1 + column: 1 + ruleId: bar-very-very-very-very-very-long +--- +`.trim(), + ); + }); + + it('handles ignored file', () => { + const results = [ + { + source: 'file.css', + warnings: [], + deprecations: [], + invalidOptionWarnings: [], + ignored: true, + }, + ]; + + const output = prepareFormatterOutput(results, tapFormatter); + + expect(output).toBe( + ` +TAP version 13 +1..${results.length} +ok 1 - ignored ${results[0].source} +`.trim(), + ); + }); +}); diff --git a/lib/formatters/index.js b/lib/formatters/index.js index 30177cc984..ad2321ea8b 100644 --- a/lib/formatters/index.js +++ b/lib/formatters/index.js @@ -6,6 +6,7 @@ module.exports = { compact: importLazy(() => require('./compactFormatter'))(), json: importLazy(() => require('./jsonFormatter'))(), string: importLazy(() => require('./stringFormatter'))(), + tap: importLazy(() => require('./tapFormatter'))(), unix: importLazy(() => require('./unixFormatter'))(), verbose: importLazy(() => require('./verboseFormatter'))(), }; diff --git a/lib/formatters/tapFormatter.js b/lib/formatters/tapFormatter.js new file mode 100644 index 0000000000..8323657574 --- /dev/null +++ b/lib/formatters/tapFormatter.js @@ -0,0 +1,41 @@ +'use strict'; + +/** + * @type {import('stylelint').Formatter} + */ +const tapFormatter = (results) => { + let lines = [`TAP version 13\n1..${results.length}`]; + + results.forEach((result, index) => { + lines.push( + `${result.errored ? 'not ok' : 'ok'} ${index + 1} - ${result.ignored ? 'ignored ' : ''}${ + result.source + }`, + ); + + if (result.warnings.length > 0) { + lines.push(...['---', 'messages:']); + + result.warnings.forEach((warning) => { + lines.push( + ...[ + ` - message: "${warning.text}"`, + ` severity: ${warning.severity}`, + ` data:`, + ` line: ${warning.line}`, + ` column: ${warning.column}`, + ` ruleId: ${warning.rule}`, + ], + ); + }); + + lines.push('---'); + } + }); + + lines.push(''); + + return lines.join('\n'); +}; + +module.exports = tapFormatter; diff --git a/lib/utils/__tests__/getFormatterOptionsText.test.js b/lib/utils/__tests__/getFormatterOptionsText.test.js index 63ef6e0f65..824cf1a163 100644 --- a/lib/utils/__tests__/getFormatterOptionsText.test.js +++ b/lib/utils/__tests__/getFormatterOptionsText.test.js @@ -3,8 +3,8 @@ const getFormatterOptionsText = require('../getFormatterOptionsText'); it('getFormatterOptionsText', () => { - expect(getFormatterOptionsText()).toBe('"compact", "json", "string", "unix", "verbose"'); + expect(getFormatterOptionsText()).toBe('"compact", "json", "string", "tap", "unix", "verbose"'); expect(getFormatterOptionsText({ useOr: true })).toBe( - '"compact", "json", "string", "unix" or "verbose"', + '"compact", "json", "string", "tap", "unix" or "verbose"', ); }); diff --git a/types/stylelint/index.d.ts b/types/stylelint/index.d.ts index e24e2f65f1..64b8f9e3a5 100644 --- a/types/stylelint/index.d.ts +++ b/types/stylelint/index.d.ts @@ -91,7 +91,14 @@ declare module 'stylelint' { returnValue?: StylelintStandaloneReturnValue, ) => string; - export type FormatterIdentifier = 'compact' | 'json' | 'string' | 'unix' | 'verbose' | Formatter; + export type FormatterIdentifier = + | 'compact' + | 'json' + | 'string' + | 'tap' + | 'unix' + | 'verbose' + | Formatter; export type CustomSyntax = string | Syntax; From f2270cffbf695fc5a9a17814963b847473dbf428 Mon Sep 17 00:00:00 2001 From: John Cline <2057878+clinejj@users.noreply.github.com> Date: Wed, 25 Nov 2020 15:00:22 -0500 Subject: [PATCH 2/3] Update snapshots --- lib/__tests__/__snapshots__/cli.test.js.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/__tests__/__snapshots__/cli.test.js.snap b/lib/__tests__/__snapshots__/cli.test.js.snap index fed9b943b5..234a0320f2 100644 --- a/lib/__tests__/__snapshots__/cli.test.js.snap +++ b/lib/__tests__/__snapshots__/cli.test.js.snap @@ -96,7 +96,7 @@ exports[`CLI --help 1`] = ` --formatter, -f [default: \\"string\\"] - The output formatter: \\"compact\\", \\"json\\", \\"string\\", \\"tap"\\, \\"unix\\" or \\"verbose\\". + The output formatter: \\"compact\\", \\"json\\", \\"string\\", \\"tap\\", \\"unix\\" or \\"verbose\\". --custom-formatter From bb6defc1d3db4ad00e33675981c1f78b49c900a1 Mon Sep 17 00:00:00 2001 From: John Cline <2057878+clinejj@users.noreply.github.com> Date: Fri, 15 Jan 2021 13:27:10 -0500 Subject: [PATCH 3/3] Remove unnecessary spread usage Signed-off-by: John Cline <2057878+clinejj@users.noreply.github.com> --- lib/formatters/tapFormatter.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/formatters/tapFormatter.js b/lib/formatters/tapFormatter.js index 8323657574..fd05191401 100644 --- a/lib/formatters/tapFormatter.js +++ b/lib/formatters/tapFormatter.js @@ -14,18 +14,16 @@ const tapFormatter = (results) => { ); if (result.warnings.length > 0) { - lines.push(...['---', 'messages:']); + lines.push('---', 'messages:'); result.warnings.forEach((warning) => { lines.push( - ...[ - ` - message: "${warning.text}"`, - ` severity: ${warning.severity}`, - ` data:`, - ` line: ${warning.line}`, - ` column: ${warning.column}`, - ` ruleId: ${warning.rule}`, - ], + ` - message: "${warning.text}"`, + ` severity: ${warning.severity}`, + ` data:`, + ` line: ${warning.line}`, + ` column: ${warning.column}`, + ` ruleId: ${warning.rule}`, ); });