From 78c9a48f781f3bf0a9431ae054371cfe2dbbcaef Mon Sep 17 00:00:00 2001 From: Ryan Ling Date: Thu, 25 Feb 2021 10:40:10 +1100 Subject: [PATCH] Support debug flag on format and lint (#367) This can clue you in on which files ESLint and Prettier are formatting and linting, which can in turn inform your `.eslintignore` and `.prettierignore` files. --- .changeset/nice-apricots-turn.md | 5 ++ README.md | 8 +++ src/cli/format.test.ts | 100 +++++++++++++++++++++++++++++++ src/cli/format.ts | 18 +++++- src/cli/lint.test.ts | 86 +++++++++++++++++++++++++- src/cli/lint.ts | 21 +++++-- src/utils/args.test.ts | 17 +++++- src/utils/args.ts | 3 + 8 files changed, 249 insertions(+), 9 deletions(-) create mode 100644 .changeset/nice-apricots-turn.md create mode 100644 src/cli/format.test.ts diff --git a/.changeset/nice-apricots-turn.md b/.changeset/nice-apricots-turn.md new file mode 100644 index 000000000..6e7faaf00 --- /dev/null +++ b/.changeset/nice-apricots-turn.md @@ -0,0 +1,5 @@ +--- +'skuba': minor +--- + +**format, lint:** Support `--debug` flag diff --git a/README.md b/README.md index d2ee468fd..e22b3ddc8 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,10 @@ Apply automatic fixes to code quality and flag issues that require manual interv This script should be run locally before pushing code to a remote branch. +| Option | Description | +| :-------- | :-------------------------- | +| `--debug` | Enable debug console output | + ### `skuba help` ```shell @@ -221,6 +225,10 @@ Check for code quality issues. This script should be run in CI to verify that [`skuba format`] was applied and triaged locally. +| Option | Description | +| :-------- | :-------------------------- | +| `--debug` | Enable debug console output | + ### `skuba node` Run a TypeScript source file, or open a REPL if none is provided: diff --git a/src/cli/format.test.ts b/src/cli/format.test.ts new file mode 100644 index 000000000..62ce3aaa3 --- /dev/null +++ b/src/cli/format.test.ts @@ -0,0 +1,100 @@ +import execa from 'execa'; + +import { hasStringProp } from '../utils/validation'; + +import { format } from './format'; + +jest.mock('execa'); + +const execaCalls = () => + ((execa as unknown) as jest.Mock).mock.calls + .flat(2) + .map((val) => + Array.isArray(val) || !hasStringProp(val, 'localDir') + ? val + : { ...val, localDir: 'REDACTED' }, + ); + +describe('format', () => { + const consoleLog = jest.spyOn(global.console, 'log').mockReturnValue(); + + const consoleLogCalls = () => consoleLog.mock.calls.flat(2); + + afterEach(jest.clearAllMocks); + + const oldProcessArgv = process.argv; + afterAll(() => (process.argv = oldProcessArgv)); + + it('handles no flags', async () => { + process.argv = []; + + await expect(format()).resolves.toBeUndefined(); + + expect(execaCalls()).toMatchInlineSnapshot(` + Array [ + "eslint", + "--ext=js,ts,tsx", + "--fix", + ".", + Object { + "localDir": "REDACTED", + "preferLocal": true, + "stdio": "inherit", + }, + "prettier", + "--write", + ".", + Object { + "localDir": "REDACTED", + "preferLocal": true, + "stdio": "inherit", + }, + ] + `); + + expect(consoleLogCalls()).toMatchInlineSnapshot(` + Array [ + "✔ ESLint", + "✔ Prettier", + ] + `); + }); + + it('handles debug flag', async () => { + process.argv = ['something', '--dEbUg', 'else']; + + await expect(format()).resolves.toBeUndefined(); + + expect(execaCalls()).toMatchInlineSnapshot(` + Array [ + "eslint", + "--debug", + "--ext=js,ts,tsx", + "--fix", + ".", + Object { + "localDir": "REDACTED", + "preferLocal": true, + "stdio": "inherit", + }, + "prettier", + "--loglevel", + "debug", + "--write", + ".", + Object { + "localDir": "REDACTED", + "preferLocal": true, + "stdio": "inherit", + }, + ] + `); + + expect(consoleLogCalls()).toMatchInlineSnapshot(` + Array [ + "✔ ESLint", + "✔ Prettier", + ] + `); + }); +}); diff --git a/src/cli/format.ts b/src/cli/format.ts index 767360912..36df745a3 100644 --- a/src/cli/format.ts +++ b/src/cli/format.ts @@ -1,10 +1,24 @@ +import { hasDebugFlag } from '../utils/args'; import { exec } from '../utils/exec'; import { log } from '../utils/logging'; export const format = async () => { - await exec('eslint', '--ext=js,ts,tsx', '--fix', '.'); + const debug = hasDebugFlag(); + + await exec( + 'eslint', + ...(debug ? ['--debug'] : []), + '--ext=js,ts,tsx', + '--fix', + '.', + ); log.ok('✔ ESLint'); - await exec('prettier', '--write', '.'); + await exec( + 'prettier', + ...(debug ? ['--loglevel', 'debug'] : []), + '--write', + '.', + ); log.ok('✔ Prettier'); }; diff --git a/src/cli/lint.test.ts b/src/cli/lint.test.ts index fd39c9d48..e485180b0 100644 --- a/src/cli/lint.test.ts +++ b/src/cli/lint.test.ts @@ -1,4 +1,88 @@ -import { internalLint } from './lint'; +import concurrently from 'concurrently'; + +import { internalLint, lint } from './lint'; + +jest.mock('concurrently'); + +const concurrentlyCalls = () => + ((concurrently as unknown) as jest.Mock).mock.calls + .flat(2) + .map(({ env, maxProcesses, ...rest }) => ({ + ...(typeof env !== 'undefined' && { env: 'REDACTED' }), + ...(typeof maxProcesses !== 'undefined' && { maxProcesses: 'REDACTED' }), + ...rest, + })); + +describe('lint', () => { + afterEach(jest.clearAllMocks); + + const oldProcessArgv = process.argv; + afterAll(() => (process.argv = oldProcessArgv)); + + it('handles no flags', async () => { + process.argv = []; + + await expect(lint()).resolves.toBeUndefined(); + + expect(concurrentlyCalls()).toMatchInlineSnapshot(` + Array [ + Object { + "command": "eslint --ext=js,ts,tsx --report-unused-disable-directives .", + "env": "REDACTED", + "name": "ESLint ", + "prefixColor": "magenta", + }, + Object { + "command": "tsc --noEmit", + "env": "REDACTED", + "name": "tsc ", + "prefixColor": "blue", + }, + Object { + "command": "prettier --check .", + "env": "REDACTED", + "name": "Prettier", + "prefixColor": "cyan", + }, + Object { + "maxProcesses": "REDACTED", + }, + ] + `); + }); + + it('handles debug flag', async () => { + process.argv = ['something', '--DeBuG', 'else']; + + await expect(lint()).resolves.toBeUndefined(); + + expect(concurrentlyCalls()).toMatchInlineSnapshot(` + Array [ + Object { + "command": "eslint --debug --ext=js,ts,tsx --report-unused-disable-directives .", + "env": "REDACTED", + "name": "ESLint ", + "prefixColor": "magenta", + }, + Object { + "command": "tsc --extendedDiagnostics --noEmit", + "env": "REDACTED", + "name": "tsc ", + "prefixColor": "blue", + }, + Object { + "command": "prettier --check --loglevel debug .", + "env": "REDACTED", + "name": "Prettier", + "prefixColor": "cyan", + }, + Object { + "maxProcesses": "REDACTED", + }, + ] + `); + }); +}); describe('internalLint', () => { it('passes on skuba itself', () => diff --git a/src/cli/lint.ts b/src/cli/lint.ts index ce0ab6896..c40aae117 100644 --- a/src/cli/lint.ts +++ b/src/cli/lint.ts @@ -3,23 +3,30 @@ import path from 'path'; import chalk from 'chalk'; import fs from 'fs-extra'; +import { hasDebugFlag } from '../utils/args'; import { execConcurrently } from '../utils/exec'; import { getConsumerManifest } from '../utils/manifest'; -const externalLint = () => +interface Options { + debug: boolean; +} + +const externalLint = ({ debug }: Options) => execConcurrently([ { - command: 'eslint --ext=js,ts,tsx --report-unused-disable-directives .', + command: `eslint${ + debug ? ' --debug' : '' + } --ext=js,ts,tsx --report-unused-disable-directives .`, name: 'ESLint', prefixColor: 'magenta', }, { - command: 'tsc --noEmit', + command: `tsc${debug ? ' --extendedDiagnostics' : ''} --noEmit`, name: 'tsc', prefixColor: 'blue', }, { - command: 'prettier --check .', + command: `prettier --check${debug ? ' --loglevel debug' : ''} .`, name: 'Prettier', prefixColor: 'cyan', }, @@ -51,7 +58,11 @@ export const internalLint = async () => { }; export const lint = async () => { - await externalLint(); + const opts: Options = { + debug: hasDebugFlag(), + }; + + await externalLint(opts); await internalLint(); }; diff --git a/src/utils/args.test.ts b/src/utils/args.test.ts index 6b64f4747..baab7ad90 100644 --- a/src/utils/args.test.ts +++ b/src/utils/args.test.ts @@ -1,4 +1,19 @@ -import { parseProcessArgs, parseRunArgs } from './args'; +import { hasDebugFlag, parseProcessArgs, parseRunArgs } from './args'; + +describe('hasDebugFlag', () => { + test.each` + description | args | expected + ${'no args'} | ${[]} | ${false} + ${'unrelated args'} | ${['something', 'else']} | ${false} + ${'single dash'} | ${['-debug']} | ${false} + ${'matching lowercase arg'} | ${['--debug']} | ${true} + ${'matching uppercase arg'} | ${['--DEBUG']} | ${true} + ${'matching spongebob arg'} | ${['--dEBuG']} | ${true} + ${'matching arg among others'} | ${['something', '--debug', 'else']} | ${true} + `('$description => $expected', ({ args, expected }) => + expect(hasDebugFlag(args)).toBe(expected), + ); +}); describe('parseProcessArgs', () => { it('parses a macOS command with args', () => { diff --git a/src/utils/args.ts b/src/utils/args.ts index 9c3320ae3..7ef680c50 100644 --- a/src/utils/args.ts +++ b/src/utils/args.ts @@ -2,6 +2,9 @@ import assert from 'assert'; import { COMMAND_ALIASES } from './command'; +export const hasDebugFlag = (args = process.argv) => + args.some((arg) => arg.toLocaleLowerCase() === '--debug'); + /** * Parse process arguments. *