diff --git a/packages/wb/src/commands/lint.ts b/packages/wb/src/commands/lint.ts index 7a6e612e..d20fe812 100644 --- a/packages/wb/src/commands/lint.ts +++ b/packages/wb/src/commands/lint.ts @@ -96,6 +96,10 @@ const prettierFixtureIgnorePattern = '!**/test{-,/}fixtures/**'; type BufferedLintRunResult = BufferedRunResult & { command: string; cwd: string }; type LintRunResult = BufferedLintRunResult | { exitCode: number }; +interface LintRunCommand { + command: string; + project: Project; +} export const lintCommand: CommandModule = { command: 'lint [files...]', @@ -210,56 +214,75 @@ export async function lint(argv: LintCommandArgv): Promise { sortPackageJsonArgs = projects.descendants.map((p) => p.packageJsonPath); } - const lintPromises: Promise[] = []; + const formatterCommands: LintRunCommand[] = []; + const linterCommands: LintRunCommand[] = []; const lintRunOptions = { exitIfFailed: false, forceColor: !argv.printAllOutput } as const; + const shouldRunFormatters = Boolean(argv.format); + const shouldRunLinters = !argv.format || argv.fix; if (files.length > 0) { - for (const [project, lintFilePaths] of lintFilePathsByProject) { - const lintCommand = buildLintCommand(project, argv, lintFilePaths); - if (!lintCommand) continue; + if (shouldRunLinters) { + for (const [project, lintFilePaths] of lintFilePathsByProject) { + const lintCommand = buildLintCommand(project, argv, lintFilePaths); + if (!lintCommand) continue; - lintPromises.push(runLintCommand(lintCommand, project, argv, lintRunOptions)); - } - if (argv.format) { - for (const [project, oxfmtFilePaths] of oxfmtFilePathsByProject) { - lintPromises.push(runLintCommand(buildOxfmtCommand(oxfmtFilePaths), project, argv, lintRunOptions)); + linterCommands.push({ command: lintCommand, project }); } } for (const [project, pythonFilePaths] of pythonFilePathsByProject) { - lintPromises.push(runLintCommand(buildPoetryCommand(argv, pythonFilePaths), project, argv, lintRunOptions)); + if (shouldRunLinters) { + linterCommands.push({ command: buildPoetryLintCommand(argv, pythonFilePaths), project }); + } + if (shouldRunFormatters) { + formatterCommands.push({ command: buildPoetryFormatCommand(pythonFilePaths), project }); + } } for (const [project, dartFilePaths] of dartFilePathsByProject) { - lintPromises.push(runLintCommand(buildDartCommand(argv, dartFilePaths), project, argv, lintRunOptions)); + if (shouldRunLinters) { + linterCommands.push({ command: buildDartLintCommand(dartFilePaths), project }); + } + if (shouldRunFormatters) { + formatterCommands.push({ command: buildDartFormatCommand(dartFilePaths), project }); + } } - } else { - for (const project of projects.descendants) { - if (project.packageJson.workspaces && !project.hasSourceCode) continue; - - const lintCommand = buildLintCommand(project, argv); - if (!lintCommand) continue; - - lintPromises.push(runLintCommand(lintCommand, project, argv, lintRunOptions)); + if (shouldRunFormatters) { + for (const [project, oxfmtFilePaths] of oxfmtFilePathsByProject) { + formatterCommands.push({ command: buildOxfmtCommand(oxfmtFilePaths), project }); + } } + } else { for (const project of projects.descendants) { - if (project.hasPoetryLock) { - lintPromises.push(runLintCommand(buildPoetryCommand(argv), project, argv, lintRunOptions)); - } - if (project.hasPubspecYaml) { - lintPromises.push(runLintCommand(buildDartCommand(argv), project, argv, lintRunOptions)); + if (shouldRunLinters) { + if (!project.packageJson.workspaces || project.hasSourceCode) { + const lintCommand = buildLintCommand(project, argv); + if (lintCommand) linterCommands.push({ command: lintCommand, project }); + } + if (project.hasPoetryLock) linterCommands.push({ command: buildPoetryLintCommand(argv), project }); + if (project.hasPubspecYaml) linterCommands.push({ command: buildDartLintCommand(), project }); } - if (project.hasOxfmt && argv.format) { - lintPromises.push(runLintCommand(buildOxfmtCommand(), project, argv, lintRunOptions)); + if (shouldRunFormatters) { + if (project.hasOxfmt) formatterCommands.push({ command: buildOxfmtCommand(), project }); + if (project.hasPoetryLock) formatterCommands.push({ command: buildPoetryFormatCommand(), project }); + if (project.hasPubspecYaml) formatterCommands.push({ command: buildDartFormatCommand(), project }); } } } - const lintResults = await Promise.all(lintPromises); - printSilentLintOutputs(lintResults, argv); - const lintExitCodes = lintResults.map((result) => result.exitCode); + const lintExitCodes: number[] = []; + + if (shouldRunFormatters) { + const formatterResults = await runLintCommands(formatterCommands, argv, lintRunOptions); + printSilentLintOutputs(formatterResults, argv); + lintExitCodes.push(...formatterResults.map((result) => result.exitCode)); + + if (lintExitCodes.some((exitCode) => exitCode !== 0)) { + return 1; + } + } - if (missingLintToolForExplicitFiles || lintExitCodes.some((exitCode) => exitCode !== 0)) { + if (missingLintToolForExplicitFiles) { return 1; } - if (argv.format) { + if (shouldRunFormatters) { if (prettierArgs.length > 0 && projects.self.hasPrettier) { const prettierResult = await runLintCommand( buildShellCommand([ @@ -289,11 +312,29 @@ export async function lint(argv: LintCommandArgv): Promise { printSilentLintOutputs([sortPackageJsonResult], argv); lintExitCodes.push(sortPackageJsonResult.exitCode); } + + if (lintExitCodes.some((exitCode) => exitCode !== 0)) { + return 1; + } + } + + if (shouldRunLinters) { + const linterResults = await runLintCommands(linterCommands, argv, lintRunOptions); + printSilentLintOutputs(linterResults, argv); + lintExitCodes.push(...linterResults.map((result) => result.exitCode)); } return lintExitCodes.some((exitCode) => exitCode !== 0) ? 1 : 0; } +function runLintCommands( + commands: LintRunCommand[], + argv: LintCommandArgv, + options: Parameters[3] +): Promise { + return Promise.all(commands.map(({ command, project }) => runLintCommand(command, project, argv, options))); +} + function runLintCommand( command: string, project: Project, @@ -385,33 +426,27 @@ export function buildOxfmtCommand(files?: string[]): string { ]); } -export function buildPoetryCommand( - argv: Pick & Partial>, - files?: string[] -): string { +export function buildPoetryFormatCommand(files?: string[]): string { const targets = files && files.length > 0 ? files : ['.']; - const commands = - argv.fix || argv.format - ? [ - buildShellCommand(['poetry', 'run', 'isort', '--profile', 'black', '--filter-files', ...targets]), - buildShellCommand(['poetry', 'run', 'black', ...targets]), - buildShellCommand(['poetry', 'run', 'flake8', ...(argv.quiet ? ['-q'] : []), ...targets]), - ] - : [buildShellCommand(['poetry', 'run', 'flake8', ...(argv.quiet ? ['-q'] : []), ...targets])]; - return commands.join(' && '); + return [ + buildShellCommand(['poetry', 'run', 'isort', '--profile', 'black', '--filter-files', ...targets]), + buildShellCommand(['poetry', 'run', 'black', ...targets]), + ].join(' && '); } -export function buildDartCommand( - argv: Pick & Partial>, - files?: string[] -): string { +export function buildPoetryLintCommand(argv: Partial>, files?: string[]): string { const targets = files && files.length > 0 ? files : ['.']; - const commands: string[] = []; - if (argv.fix || argv.format) { - commands.push(buildShellCommand(['dart', 'format', ...targets])); - } - commands.push(buildShellCommand(['dart', 'analyze', ...targets])); - return commands.join(' && '); + return buildShellCommand(['poetry', 'run', 'flake8', ...(argv.quiet ? ['-q'] : []), ...targets]); +} + +export function buildDartFormatCommand(files?: string[]): string { + const targets = files && files.length > 0 ? files : ['.']; + return buildShellCommand(['dart', 'format', ...targets]); +} + +export function buildDartLintCommand(files?: string[]): string { + const targets = files && files.length > 0 ? files : ['.']; + return buildShellCommand(['dart', 'analyze', ...targets]); } export function buildPrettierArgs( diff --git a/packages/wb/src/commands/verifyCode.ts b/packages/wb/src/commands/verifyCode.ts index e25490a2..43dda6a5 100644 --- a/packages/wb/src/commands/verifyCode.ts +++ b/packages/wb/src/commands/verifyCode.ts @@ -60,27 +60,13 @@ async function verifyCode(project: Project, argv: VerifyCodeCommandArgv): Promis await runPackageCommand(`${packageManager} gen-code`, project, argv); } await runInProcessCommand( - 'format', - () => - lint({ - ...argv, - _: ['lint'], - format: true, - printAllOutput: true, - silent: true, - } as unknown as LintCommandArgv), - { - allowFailure: true, - silent: true, - } - ); - await runInProcessCommand( - 'lint-fix', + 'cleanup', () => lint({ ...argv, _: ['lint'], fix: true, + format: true, printAllOutput: true, quiet: true, silent: true, diff --git a/packages/wb/test/lint.test.ts b/packages/wb/test/lint.test.ts index 51c2503d..4091cb43 100644 --- a/packages/wb/test/lint.test.ts +++ b/packages/wb/test/lint.test.ts @@ -5,11 +5,13 @@ import path from 'node:path'; import { describe, expect, it } from 'vitest'; import { - buildDartCommand, + buildDartFormatCommand, + buildDartLintCommand, buildExplicitFormatterArgs, buildLintCommand, buildOxfmtCommand, - buildPoetryCommand, + buildPoetryFormatCommand, + buildPoetryLintCommand, buildPrettierArgs, getExplicitLintTargets, getLintTargetFileKind, @@ -32,15 +34,15 @@ describe('lint', () => { }); it('builds poetry commands for explicit python files', () => { - expect(buildPoetryCommand({ fix: true, format: true }, ['/tmp/example.py'])).toBe( - 'poetry run isort --profile black --filter-files /tmp/example.py && poetry run black /tmp/example.py && poetry run flake8 /tmp/example.py' + expect(buildPoetryFormatCommand(['/tmp/example.py'])).toBe( + 'poetry run isort --profile black --filter-files /tmp/example.py && poetry run black /tmp/example.py' ); + expect(buildPoetryLintCommand({}, ['/tmp/example.py'])).toBe('poetry run flake8 /tmp/example.py'); }); it('builds dart commands for explicit dart files', () => { - expect(buildDartCommand({ fix: true, format: true }, ['/tmp/example.dart'])).toBe( - 'dart format /tmp/example.dart && dart analyze /tmp/example.dart' - ); + expect(buildDartFormatCommand(['/tmp/example.dart'])).toBe('dart format /tmp/example.dart'); + expect(buildDartLintCommand(['/tmp/example.dart'])).toBe('dart analyze /tmp/example.dart'); }); it('builds an oxfmt command for explicit files', () => {