From 58e26e54ce209d54f199ce93d76158e7eb5c6bf1 Mon Sep 17 00:00:00 2001 From: Masafumi Koba Date: Thu, 9 Jan 2020 22:15:03 +0900 Subject: [PATCH 1/4] Refactor CLI options definition This aims to prepare for type-checking `cli.js` and does NOT type-check actually. - Camelize option names for type-checking. - Add missing `type` for some options. - Sort options alphabetically for readability. - Add a unit test for the definition (A system test is too much for this purpose). See for details. --- lib/__tests__/cli.test.js | 140 ++++++++++++++++++ lib/cli.js | 77 +++++----- .../__tests__/checkInvalidCLIOptions.test.js | 2 +- lib/utils/checkInvalidCLIOptions.js | 2 +- 4 files changed, 185 insertions(+), 36 deletions(-) create mode 100644 lib/__tests__/cli.test.js diff --git a/lib/__tests__/cli.test.js b/lib/__tests__/cli.test.js new file mode 100644 index 0000000000..f306d86656 --- /dev/null +++ b/lib/__tests__/cli.test.js @@ -0,0 +1,140 @@ +'use strict'; + +const { buildCLI } = require('../cli'); + +describe('buildCLI', () => { + it('flags - default', () => { + expect(buildCLI([]).flags).toEqual({ + allowEmptyInput: false, + cache: false, + color: false, + disableDefaultIgnores: false, + fix: false, + formatter: 'string', + help: false, + ignoreDisables: false, + printConfig: false, + quiet: false, + reportInvalidScopeDisables: false, + reportNeedlessDisables: false, + version: false, + }); + }); + + it('flags.allowEmptyInput', () => { + expect(buildCLI(['--allow-empty-input']).flags.allowEmptyInput).toBe(true); + expect(buildCLI(['--aei']).flags.allowEmptyInput).toBe(true); + }); + + it('flags.cache', () => { + expect(buildCLI(['--cache']).flags.cache).toBe(true); + }); + + it('flags.cacheLocation', () => { + expect(buildCLI(['--cache-location=foo']).flags.cacheLocation).toBe('foo'); + }); + + it('flags.color', () => { + expect(buildCLI(['--color']).flags.color).toBe(true); + expect(buildCLI(['--no-color']).flags.color).toBe(false); + }); + + it('flags.config', () => { + expect(buildCLI(['--config=/path/to/file']).flags.config).toBe('/path/to/file'); + }); + + it('flags.configBasedir', () => { + expect(buildCLI(['--config-basedir=/path/to/dir']).flags.configBasedir).toBe('/path/to/dir'); + }); + + it('flags.customFormatter', () => { + expect(buildCLI(['--custom-formatter=foo']).flags.customFormatter).toBe('foo'); + }); + + it('flags.customSyntax', () => { + expect(buildCLI(['--custom-syntax=foo']).flags.customSyntax).toBe('foo'); + }); + + it('flags.disableDefaultIgnores', () => { + expect(buildCLI(['--disable-default-ignores']).flags.disableDefaultIgnores).toBe(true); + expect(buildCLI(['--di']).flags.disableDefaultIgnores).toBe(true); + }); + + it('flags.fix', () => { + expect(buildCLI(['--fix']).flags.fix).toBe(true); + }); + + it('flags.formatter', () => { + expect(buildCLI(['--formatter=json']).flags.formatter).toBe('json'); + expect(buildCLI(['-f', 'json']).flags.formatter).toBe('json'); + }); + + it('flags.help', () => { + expect(buildCLI(['--help']).flags.help).toBe(true); + expect(buildCLI(['-h']).flags.help).toBe(true); + }); + + it('flags.ignoreDisables', () => { + expect(buildCLI(['--ignore-disables']).flags.ignoreDisables).toBe(true); + expect(buildCLI(['--id']).flags.ignoreDisables).toBe(true); + }); + + it('flags.ignorePath', () => { + expect(buildCLI(['--ignore-path=/path/to/file']).flags.ignorePath).toBe('/path/to/file'); + expect(buildCLI(['-i', '/path/to/file']).flags.ignorePath).toBe('/path/to/file'); + }); + + it('flags.ignorePattern', () => { + expect(buildCLI(['--ignore-pattern=vendor/**']).flags.ignorePattern).toBe('vendor/**'); + expect(buildCLI(['--ip', 'vendor/**']).flags.ignorePattern).toBe('vendor/**'); + expect(buildCLI(['--ip', 'vendor/**', '--ip', 'test/*.css']).flags.ignorePattern).toEqual([ + 'vendor/**', + 'test/*.css', + ]); + }); + + it('flags.maxWarnings', () => { + expect(buildCLI(['--max-warnings=7']).flags.maxWarnings).toBe(7); + expect(buildCLI(['--mw', '-1']).flags.maxWarnings).toBe(-1); + }); + + it('flags.outputFile', () => { + expect(buildCLI(['--output-file=/path/to/file']).flags.outputFile).toBe('/path/to/file'); + expect(buildCLI(['-o', '/path/to/file']).flags.outputFile).toBe('/path/to/file'); + }); + + it('flags.printConfig', () => { + expect(buildCLI(['--print-config']).flags.printConfig).toBe(true); + }); + + it('flags.quiet', () => { + expect(buildCLI(['--quiet']).flags.quiet).toBe(true); + expect(buildCLI(['-q']).flags.quiet).toBe(true); + }); + + it('flags.reportInvalidScopeDisables', () => { + expect(buildCLI(['--report-invalid-scope-disables']).flags.reportInvalidScopeDisables).toBe( + true, + ); + expect(buildCLI(['--risd']).flags.reportInvalidScopeDisables).toBe(true); + }); + + it('flags.reportNeedlessDisables', () => { + expect(buildCLI(['--report-needless-disables']).flags.reportNeedlessDisables).toBe(true); + expect(buildCLI(['--rd']).flags.reportNeedlessDisables).toBe(true); + }); + + it('flags.stdinFilename', () => { + expect(buildCLI(['--stdin-filename=foo.css']).flags.stdinFilename).toBe('foo.css'); + }); + + it('flags.syntax', () => { + expect(buildCLI(['--syntax=less']).flags.syntax).toBe('less'); + expect(buildCLI(['-s', 'less']).flags.syntax).toBe('less'); + }); + + it('flags.version', () => { + expect(buildCLI(['--version']).flags.version).toBe(true); + expect(buildCLI(['-v']).flags.version).toBe(true); + }); +}); diff --git a/lib/cli.js b/lib/cli.js index 250380a222..402ba73d8e 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -220,35 +220,32 @@ const meowOptions = { When glob pattern matches no files, the process will exit without throwing an error. `, flags: { - 'allow-empty-input': { + allowEmptyInput: { alias: 'aei', type: 'boolean', }, cache: { type: 'boolean', }, - 'cache-location': { + cacheLocation: { type: 'string', }, + color: { + type: 'boolean', + }, config: { type: 'string', }, - 'config-basedir': { + configBasedir: { type: 'string', }, - 'print-config': { - type: 'boolean', - }, - color: { - type: 'boolean', - }, - 'custom-formatter': { + customFormatter: { type: 'string', }, - 'custom-syntax': { + customSyntax: { type: 'string', }, - 'disable-default-ignores': { + disableDefaultIgnores: { alias: 'di', type: 'boolean', }, @@ -264,44 +261,47 @@ const meowOptions = { alias: 'h', type: 'boolean', }, - 'ignore-disables': { + ignoreDisables: { alias: 'id', type: 'boolean', }, - 'ignore-path': { + ignorePath: { alias: 'i', + type: 'string', }, - 'ignore-pattern': { + ignorePattern: { alias: 'ip', + type: 'string', }, - 'no-color': { - type: 'boolean', + maxWarnings: { + alias: 'mw', + type: 'number', }, - 'output-file': { + outputFile: { alias: 'o', type: 'string', }, - 'report-needless-disables': { - alias: 'rd', + printConfig: { + type: 'boolean', + }, + quiet: { + alias: 'q', type: 'boolean', }, - 'report-invalid-scope-disables': { + reportInvalidScopeDisables: { alias: 'risd', type: 'boolean', }, - 'max-warnings': { - alias: 'mw', + reportNeedlessDisables: { + alias: 'rd', + type: 'boolean', }, - 'stdin-filename': { + stdinFilename: { type: 'string', }, - quiet: { - alias: 'q', - type: 'boolean', - default: false, - }, syntax: { alias: 's', + type: 'string', }, version: { alias: 'v', @@ -317,11 +317,7 @@ const meowOptions = { * @returns {Promise} */ module.exports = (argv) => { - meowOptions.argv = argv; - /** @type {CLIOptions} */ - const cli = - // @ts-ignore TODO TYPES - meow(meowOptions); + const cli = buildCLI(argv); const invalidOptionsMessage = checkInvalidCLIOptions(meowOptions.flags, cli.flags); @@ -536,3 +532,16 @@ function handleError(err) { process.exit(exitCode); // eslint-disable-line no-process-exit } + +/** + * @param {string[]} argv + * @returns {CLIOptions} + */ +function buildCLI(argv) { + meowOptions.argv = argv; + + // @ts-ignore TODO TYPES + return meow(meowOptions); +} + +module.exports.buildCLI = buildCLI; diff --git a/lib/utils/__tests__/checkInvalidCLIOptions.test.js b/lib/utils/__tests__/checkInvalidCLIOptions.test.js index 5c1b281cf1..028b22a174 100644 --- a/lib/utils/__tests__/checkInvalidCLIOptions.test.js +++ b/lib/utils/__tests__/checkInvalidCLIOptions.test.js @@ -8,7 +8,7 @@ describe('checkInvalidCLIOptions', () => { const allowedOptions = { fix: {}, config: {}, - 'max-war': { alias: 'mw' }, + maxWar: { alias: 'mw' }, quiet: { alias: 'q' }, }; diff --git a/lib/utils/checkInvalidCLIOptions.js b/lib/utils/checkInvalidCLIOptions.js index 8f8b825f1f..320d549275 100644 --- a/lib/utils/checkInvalidCLIOptions.js +++ b/lib/utils/checkInvalidCLIOptions.js @@ -72,7 +72,7 @@ const buildMessageLine = (invalid, suggestion) => { * @return {string} */ module.exports = function checkInvalidCLIOptions(allowedOptions, inputOptions) { - const allOptions = buildAllowedOptions(allowedOptions); + const allOptions = buildAllowedOptions(allowedOptions).map((opt) => _.kebabCase(opt)); return Object.keys(inputOptions) .map((opt) => _.kebabCase(opt)) From e4aaa95856a210b8dae20433c4e58cb22a13cd89 Mon Sep 17 00:00:00 2001 From: Masafumi Koba Date: Thu, 9 Jan 2020 23:22:17 +0900 Subject: [PATCH 2/4] More readable code using `_.kebabCase` --- lib/utils/checkInvalidCLIOptions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/utils/checkInvalidCLIOptions.js b/lib/utils/checkInvalidCLIOptions.js index 320d549275..940e3aaa63 100644 --- a/lib/utils/checkInvalidCLIOptions.js +++ b/lib/utils/checkInvalidCLIOptions.js @@ -72,10 +72,10 @@ const buildMessageLine = (invalid, suggestion) => { * @return {string} */ module.exports = function checkInvalidCLIOptions(allowedOptions, inputOptions) { - const allOptions = buildAllowedOptions(allowedOptions).map((opt) => _.kebabCase(opt)); + const allOptions = buildAllowedOptions(allowedOptions).map(_.kebabCase); return Object.keys(inputOptions) - .map((opt) => _.kebabCase(opt)) + .map(_.kebabCase) .filter((opt) => !allOptions.includes(opt)) .reduce((msg, invalid) => { // NOTE: No suggestion for shortcut options because it's too difficult From 668bbf6a971e7949e9603fa3f51eb40680546370 Mon Sep 17 00:00:00 2001 From: Masafumi Koba Date: Sat, 11 Jan 2020 18:45:16 +0900 Subject: [PATCH 3/4] Use spread syntax for `meowOptions` --- lib/cli.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index 402ba73d8e..84211cdde2 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -538,10 +538,8 @@ function handleError(err) { * @returns {CLIOptions} */ function buildCLI(argv) { - meowOptions.argv = argv; - // @ts-ignore TODO TYPES - return meow(meowOptions); + return meow({ ...meowOptions, argv }); } module.exports.buildCLI = buildCLI; From 9c9d363116c2a9b7247e5455b33a2a7fda3242f2 Mon Sep 17 00:00:00 2001 From: Masafumi Koba Date: Sat, 11 Jan 2020 22:26:57 +0900 Subject: [PATCH 4/4] Refactor `checkInvalidCLIOptions()` more efficient --- lib/utils/checkInvalidCLIOptions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/utils/checkInvalidCLIOptions.js b/lib/utils/checkInvalidCLIOptions.js index 940e3aaa63..b5b80e2233 100644 --- a/lib/utils/checkInvalidCLIOptions.js +++ b/lib/utils/checkInvalidCLIOptions.js @@ -72,11 +72,11 @@ const buildMessageLine = (invalid, suggestion) => { * @return {string} */ module.exports = function checkInvalidCLIOptions(allowedOptions, inputOptions) { - const allOptions = buildAllowedOptions(allowedOptions).map(_.kebabCase); + const allOptions = buildAllowedOptions(allowedOptions); return Object.keys(inputOptions) - .map(_.kebabCase) .filter((opt) => !allOptions.includes(opt)) + .map(_.kebabCase) .reduce((msg, invalid) => { // NOTE: No suggestion for shortcut options because it's too difficult const suggestion = invalid.length >= 2 ? suggest(allOptions, invalid) : null;