Skip to content

Commit

Permalink
initial attempt
Browse files Browse the repository at this point in the history
  • Loading branch information
samchungy committed Jan 12, 2024
1 parent 2e6abb3 commit aebf595
Show file tree
Hide file tree
Showing 16 changed files with 104 additions and 17 deletions.
20 changes: 20 additions & 0 deletions docs/cli/lint.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,25 @@ you can limit this with the `--serial` flag.

[GitHub autofixes] are enabled when CI and GitHub environment variables are present.

### Linting/formatting specific files

`skuba format` and `skuba lint` accept optional glob patterns using glob syntax from the [fast-glob] module. which allow you to target specific files.

```shell
skuba format src/index.ts

Processed skuba lints in 0.03s.

ESLint
Processed 1 file in 2.13s.

Prettier
Processed 1 file in 0.11s.
✨ Done in 5.57s.
```

When using `skuba lint` with glob patterns, the `tsc` compiler will always run over the directories defined by your local project's `tsconfig.json`.

### Annotations

`skuba lint` can automatically emit annotations in CI.
Expand All @@ -75,6 +94,7 @@ you can limit this with the `--serial` flag.
[eslint deep dive]: ../deep-dives/eslint.md
[eslint-config-seek]: https://github.com/seek-oss/eslint-config-seek
[ESLint]: https://eslint.org/
[fast-glob]: https://github.com/mrmlnc/fast-glob#pattern-syntax
[GitHub annotations]: ../deep-dives/github.md#github-annotations
[GitHub autofixes]: ../deep-dives/github.md#github-autofixes
[prescribe ESLint]: https://myseek.atlassian.net/wiki/spaces/AA/pages/2358346041/#TypeScript
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"eslint": "^8.11.0",
"eslint-config-skuba": "3.1.0",
"execa": "^5.0.0",
"fast-glob": "^3.3.2",
"fdir": "^6.0.0",
"fs-extra": "^11.0.0",
"function-arguments": "^1.0.9",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src/cli/adapter/eslint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface ESLintOutput {
export const runESLint = async (
mode: 'format' | 'lint',
logger: Logger,
inputFiles: string[],
): Promise<ESLintOutput> => {
logger.debug('Initialising ESLint...');

Expand All @@ -46,7 +47,7 @@ export const runESLint = async (

const [formatter, results] = await Promise.all([
engine.loadFormatter(),
engine.lintFiles('.'),
engine.lintFiles(inputFiles.length ? inputFiles : '.'),
]);

const end = process.hrtime.bigint();
Expand Down
2 changes: 2 additions & 0 deletions src/cli/adapter/prettier.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ describe('runPrettier', () => {
runPrettier(
'lint',
log,
[],
path.join(__dirname, '../../../integration/base/fixable'),
),
).resolves.toMatchInlineSnapshot(`
Expand Down Expand Up @@ -77,6 +78,7 @@ describe('runPrettier', () => {
runPrettier(
'lint',
log,
[],
path.join(__dirname, '../../../integration/base/fixable'),
),
).resolves.toMatchInlineSnapshot(`
Expand Down
8 changes: 4 additions & 4 deletions src/cli/adapter/prettier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ export interface PrettierOutput {
export const runPrettier = async (
mode: 'format' | 'lint',
logger: Logger,
inputFiles: string[],
cwd = process.cwd(),
): Promise<PrettierOutput> => {
logger.debug('Initialising Prettier...');
Expand All @@ -176,10 +177,9 @@ export const runPrettier = async (
// This avoids exhibiting different behaviour than a Prettier IDE integration,
// though it may present headaches if `.gitignore` and `.prettierignore` rules
// conflict.
const relativeFilepaths = await crawlDirectory(directory, [
'.gitignore',
'.prettierignore',
]);
const relativeFilepaths = inputFiles.length
? inputFiles
: await crawlDirectory(directory, ['.gitignore', '.prettierignore']);

logger.debug(`Discovered ${pluralise(relativeFilepaths.length, 'file')}.`);

Expand Down
15 changes: 11 additions & 4 deletions src/cli/format.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,36 @@
import chalk from 'chalk';

import { hasDebugFlag } from '../utils/args';
import { hasDebugFlag, parseNonFlagArgs } from '../utils/args';
import { createLogger, log } from '../utils/logging';

import { runESLint } from './adapter/eslint';
import { runPrettier } from './adapter/prettier';
import { getFiles } from './lint/files';
import { internalLint } from './lint/internal';

export const format = async (args = process.argv.slice(2)): Promise<void> => {
const debug = hasDebugFlag(args);
const fileInputs = parseNonFlagArgs(args);
const files = getFiles(fileInputs);

log.plain(chalk.blueBright('skuba lints'));
const internal = await internalLint('format', { debug, serial: true });
const internal = await internalLint('format', {
debug,
serial: true,
inputFiles: [],
});

const logger = createLogger(debug);

log.newline();
log.plain(chalk.magenta('ESLint'));

const eslint = await runESLint('format', logger);
const eslint = await runESLint('format', logger, files);

log.newline();
log.plain(chalk.cyan('Prettier'));

const prettier = await runPrettier('format', logger);
const prettier = await runPrettier('format', logger, files);

if (eslint.ok && prettier.ok && internal.ok) {
return;
Expand Down
2 changes: 1 addition & 1 deletion src/cli/init/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export const init = async (args = process.argv.slice(2)) => {

// Templating can initially leave certain files in an unformatted state;
// consider a Markdown table with columns sized based on content length.
await runPrettier('format', createLogger(opts.debug), destinationDir);
await runPrettier('format', createLogger(opts.debug), [], destinationDir);

depsInstalled = true;
} catch (err) {
Expand Down
4 changes: 2 additions & 2 deletions src/cli/lint/autofix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,12 @@ export const autofix = async (params: AutofixParameters): Promise<void> => {
}

if (params.eslint) {
await runESLint('format', logger);
await runESLint('format', logger, []);
}

// Unconditionally re-run Prettier; reaching here means we have pre-existing
// format violations or may have created new ones through ESLint/internal fixes.
await runPrettier('format', logger);
await runPrettier('format', logger, []);

if (process.env.GITHUB_ACTIONS) {
// GitHub runners have Git installed locally
Expand Down
4 changes: 2 additions & 2 deletions src/cli/lint/eslint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import type { Input } from './types';

const LOG_PREFIX = chalk.magenta('ESLint │');

export const runESLintInCurrentThread = ({ debug }: Input) =>
runESLint('lint', createLogger(debug, LOG_PREFIX));
export const runESLintInCurrentThread = ({ debug, inputFiles }: Input) =>
runESLint('lint', createLogger(debug, LOG_PREFIX), inputFiles);

export const runESLintInWorkerThread = (input: Input) =>
execWorkerThread<Input, ESLintOutput>(
Expand Down
17 changes: 17 additions & 0 deletions src/cli/lint/files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { globSync } from 'fast-glob';

import { log } from '../../utils/logging';

export const getFiles = (globPatterns: string[]): string[] => {
const files = globSync(globPatterns);

if (globPatterns.length && !files.length) {
const error = `No files matching the patterns were found: ${globPatterns.join(
', ',
)}`;
log.err(error);
process.exit(1);
}

return files;
};
8 changes: 7 additions & 1 deletion src/cli/lint/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import type { Writable } from 'stream';
import { inspect } from 'util';

import { hasDebugFlag, hasSerialFlag } from '../../utils/args';
import {
hasDebugFlag,
hasSerialFlag,
parseNonFlagArgs,
} from '../../utils/args';
import { log } from '../../utils/logging';
import { detectPackageManager } from '../../utils/packageManager';
import { throwOnTimeout } from '../../utils/wait';

import { createAnnotations } from './annotate';
import { autofix } from './autofix';
import { externalLint } from './external';
import { getFiles } from './files';
import { internalLint } from './internal';
import type { Input } from './types';

Expand All @@ -22,6 +27,7 @@ export const lint = async (
serial: hasSerialFlag(args),
tscOutputStream: tscWriteable,
workerThreads,
inputFiles: getFiles(parseNonFlagArgs(args)),
};

const { eslint, prettier, tscOk, tscOutputStream } = await externalLint(opts);
Expand Down
4 changes: 2 additions & 2 deletions src/cli/lint/prettier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import type { Input } from './types';

const LOG_PREFIX = chalk.cyan('Prettier │');

export const runPrettierInCurrentThread = ({ debug }: Input) =>
runPrettier('lint', createLogger(debug, LOG_PREFIX));
export const runPrettierInCurrentThread = ({ debug, inputFiles }: Input) =>
runPrettier('lint', createLogger(debug, LOG_PREFIX), inputFiles);

export const runPrettierInWorkerThread = (input: Input) =>
execWorkerThread<Input, PrettierOutput>(
Expand Down
7 changes: 7 additions & 0 deletions src/cli/lint/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,11 @@ export interface Input {
* Defaults to `true`.
*/
workerThreads?: boolean;

/**
* Files specified by the user to be linted/formatted
*
* This may be an empty list, in which case all files in the current working directory will be linted/formatted
*/
inputFiles: string[];
}
18 changes: 18 additions & 0 deletions src/utils/args.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
hasDebugFlag,
hasSerialFlag,
parseNonFlagArgs,
parseProcessArgs,
parseRunArgs,
} from './args';
Expand Down Expand Up @@ -137,3 +138,20 @@ describe('parseRunArgs', () => {
expect(parseRunArgs(input.split(' '))).toEqual(expected),
);
});

describe('parseNonFlagArgs', () => {
interface TestCase {
input: string;
result: string[];
}

test.each`
input | result
${'foo.ts bar.ts --flag'} | ${['foo.ts', 'bar.ts']}
${'foo.ts --flag bar.ts'} | ${['foo.ts', 'bar.ts']}
${'--flag foo.ts bar.ts'} | ${['foo.ts', 'bar.ts']}
${'--flag'} | ${[]}
`('$input', ({ input, result }: TestCase) =>
expect(parseNonFlagArgs(input.split(' '))).toEqual(result),
);
});
5 changes: 5 additions & 0 deletions src/utils/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,8 @@ const parseRunArgsIteration = (state: RunArgs, args: string[]): string[] => {
state.script.push(...args.slice(1));
return [];
};

export const parseNonFlagArgs = (args: string[]): string[] => {
const files = args.filter((arg) => !arg.startsWith('-'));
return files;
};

0 comments on commit aebf595

Please sign in to comment.