Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .changeset/bumpy-planes-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
'skuba': patch
---

format, lint: Respect Prettier ignore files in parent directories

Previously, `skuba format` and `skuba lint` only looked for `.gitignore` and `.prettierignore` in the current working directory. Now, you can execute these commands in a subdirectory while centralising ignore files in the root directory.

```text
┌── apps/
│ └── a/
│ ├── src/
│ ├── package.json
│ └── ...
│ └── b/ <- execute skuba lint here
│ ├── src/
│ ├── package.json
│ └── ...
├── .gitignore
├── .prettierignore <- and it will respect this
├── package.json
└── ...
```
Empty file.
1 change: 1 addition & 0 deletions integration/base/mono/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/apps/ignore/src/ignore.json
13 changes: 13 additions & 0 deletions integration/base/mono/apps/ignore/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"dependencies": {},
"devDependencies": {},
"license": "UNLICENSED",
"private": true,
"sideEffects": false,
"skuba": {
"entryPoint": null,
"template": null,
"type": "application",
"version": "0.0.1"
}
}
3 changes: 3 additions & 0 deletions integration/base/mono/apps/ignore/src/bad.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"key": "value"
}
3 changes: 3 additions & 0 deletions integration/base/mono/apps/ignore/src/ignore.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"key": "value"
}
13 changes: 13 additions & 0 deletions integration/base/mono/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"dependencies": {},
"devDependencies": {},
"license": "UNLICENSED",
"private": true,
"sideEffects": false,
"skuba": {
"entryPoint": null,
"template": null,
"type": "application",
"version": "0.0.1"
}
}
9 changes: 9 additions & 0 deletions integration/base/mono/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"incremental": true,
"moduleResolution": "node",
"outDir": "lib",
"skipLibCheck": true
},
"extends": "tsconfig-seek"
}
30 changes: 30 additions & 0 deletions src/cli/adapter/prettier.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,34 @@ describe('runPrettier', () => {
}
`);
});

it('handles a nested directory with a parent .prettierignore', async () => {
const dirname = path.join(
__dirname,
'../../../integration/base/mono/apps/ignore',
);

process.chdir(dirname);

// Should exclude `src/ignore.json`
await expect(runPrettier('lint', log, dirname)).resolves
.toMatchInlineSnapshot(`
{
"ok": false,
"result": {
"count": 2,
"errored": [
{
"filepath": "package.json",
},
{
"filepath": "src/bad.json",
},
],
"touched": [],
"unparsed": [],
},
}
`);
});
});
15 changes: 11 additions & 4 deletions src/cli/adapter/prettier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
resolveConfig,
} from 'prettier';

import { crawlDirectory } from '../../utils/dir';
import { crawlDirectory, locateNearestFile } from '../../utils/dir';
import { type Logger, pluralise } from '../../utils/logging';
import { getConsumerManifest } from '../../utils/manifest';
import { formatPackage, parsePackage } from '../configure/processing/package';
Expand Down Expand Up @@ -176,11 +176,18 @@ 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',
// TODO: should these bottom out at the project root?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe git root?

const ignoreFilepaths = await Promise.all([
locateNearestFile({ cwd, filename: '.gitignore' }),
locateNearestFile({ cwd, filename: '.prettierignore' }),
]);

const ignoreFilenames = ignoreFilepaths.flatMap((filepath) =>
filepath ? path.relative(cwd, filepath) : [],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible for file path to be empty?

);

const relativeFilepaths = await crawlDirectory(directory, ignoreFilenames);

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

const result: Result = {
Expand Down
30 changes: 27 additions & 3 deletions src/utils/dir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,38 @@ export const crawlDirectory = async (
root: string,
ignoreFilenames = ['.gitignore'],
) => {
const ignoreFileFilter = await createInclusionFilter(
ignoreFilenames.map((ignoreFilename) => path.join(root, ignoreFilename)),
const ignoreFilenamesByPrefix = Object.groupBy(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fancy

ignoreFilenames,
(filename) => {
const prefix = path.relative(
path.dirname(path.resolve(root, filename)),
root,
);

return prefix;
},
);

const ignores = await Promise.all(
Object.entries(ignoreFilenamesByPrefix).flatMap(
async ([prefix, filenames]) =>
({
prefix,
filter: await createInclusionFilter(
(filenames ?? []).map((filename) => path.join(root, filename)),
),
}) as const,
),
);

const absoluteFilenames = await crawl(root, {
includeDirName: (dirname) => !['.git', 'node_modules'].includes(dirname),
includeFilePath: (pathname) =>
ignoreFileFilter(path.relative(root, pathname)),
ignores.every(({ filter, prefix }) => {
const filepath = path.join(prefix, path.relative(root, pathname));

return filter(filepath);
}),
});

const relativeFilepaths = absoluteFilenames.map((filepath) =>
Expand Down