Skip to content
Merged
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
52 changes: 33 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

This project:

* Defines and explains the coding style and workflow for the S3 project. See
- Defines and explains the coding style and workflow for the S3 project. See
[CONTRIBUTING.md](CONTRIBUTING.md) for details.

* Provides an `eslint-config-scality` package that can be added as a dependency
- Provides an `eslint-config-scality` package that can be added as a dependency
in other projects. That way, coding style can automatically be checked using
eslint.

Expand All @@ -22,29 +22,30 @@ To use it in a project:

1. Install Prettier in the project:

```sh
yarn add --dev prettier
# or
npm install --save-dev prettier
```
```sh
yarn add --dev prettier
# or
npm install --save-dev prettier
```

2. Copy or extend the configuration:

```js
// prettier.config.cjs
module.exports = require('eslint-config-scality/prettier.config.cjs');
```
```js
// prettier.config.cjs
module.exports = require('eslint-config-scality/prettier.config.cjs');
```

3. Add convenience scripts:

```json
{
"scripts": {
"format": "prettier --write .",
"format:check": "prettier --check ."
}
}
```
```json
{
"scripts": {
"format": "prettier --write .",
"format:check": "prettier --check .",
"prettier:diff": "scality-prettier-diff"
}
}
```

Projects may override options locally (for example `tabWidth` or
`printWidth`) if they have strong legacy constraints, but this configuration
Expand All @@ -53,6 +54,19 @@ Node.js codebases. YAML/JSON/Markdown files default to a 2-space indentation
and Markdown prose is left un-reflowed by default (`proseWrap: 'preserve'`),
to avoid noisy diffs in existing documentation.

For pull request checks or local review of changed files, this package exposes
the `scality-prettier-diff` script. It runs Prettier on files returned by
`git diff --name-only --diff-filter=ACMRT`, defaults to `--check`, and forwards
remaining arguments to `git diff`.

Examples:

```sh
yarn run prettier:diff
yarn run prettier:diff --check HEAD~1..HEAD
yarn run prettier:diff --write --cached
```

## Contributing

- See `CONTRIBUTING.md` for contribution guidelines and coding standards.
Expand Down
107 changes: 107 additions & 0 deletions bin/prettier-diff.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env node

'use strict';

const { spawnSync } = require('child_process');

const supportedFilePattern = /\.(js|cjs|mjs|ts|tsx|json|ya?ml|md)$/;

function parseArgs(args) {
const prettierArgs = [];
const diffArgs = [];
let hasMode = false;

args.forEach(arg => {
if (arg === '--format') {
prettierArgs.push('--write');
hasMode = true;
return;
}
if (arg === '--check' || arg === '--write') {
prettierArgs.push(arg);
hasMode = true;
return;
}
diffArgs.push(arg);
});

if (!hasMode) {
prettierArgs.unshift('--check');
}

return { prettierArgs, diffArgs };
}

function filterSupportedFiles(files) {
return files.filter(file => supportedFilePattern.test(file));
}

function run(command, args, options = {}) {
return spawnSync(command, args, {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
...options,
});
}

function exitFromResult(result) {
if (result.error) {
process.stderr.write(`${result.error.message}\n`);
process.exit(1);
}
if (result.signal) {
process.stderr.write(`Command terminated by signal ${result.signal}\n`);
process.exit(1);
}
process.exit(result.status);
}

function getGitRoot() {
const result = run('git', ['rev-parse', '--show-toplevel']);
if (result.status !== 0) {
process.stderr.write(result.stderr);
exitFromResult(result);
}
return result.stdout.trim();
}

function getChangedFiles(diffArgs, cwd) {
const result = run('git', ['diff', '--name-only', '--diff-filter=ACMRT', ...diffArgs], { cwd });
if (result.status !== 0) {
process.stderr.write(result.stderr);
exitFromResult(result);
}
return result.stdout.split('\n').filter(Boolean);
}

function main() {
const { prettierArgs, diffArgs } = parseArgs(process.argv.slice(2));
const rootDir = getGitRoot();
const changedFiles = filterSupportedFiles(getChangedFiles(diffArgs, rootDir));

if (changedFiles.length === 0) {
process.stdout.write('No supported files changed; skipping Prettier.\n');
return;
}

process.stdout.write(`Running Prettier on ${changedFiles.length} file(s):\n`);
changedFiles.forEach(file => process.stdout.write(` - ${file}\n`));

const result = spawnSync(
'yarn',
['run', '--silent', 'prettier', ...changedFiles, ...prettierArgs],
{ cwd: rootDir, stdio: 'inherit' },
);
exitFromResult(result);
}

if (require.main === module) {
main();
}

module.exports = {
exitFromResult,
filterSupportedFiles,
main,
parseArgs,
};
14 changes: 9 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
{
"name": "eslint-config-scality",
"version": "8.3.1",
"version": "8.3.2",
"description": "ESLint config for Scality's Node.js coding guidelines",
"bin": {
"mdlint": "./bin/mdlint.js"
"mdlint": "./bin/mdlint.js",
"scality-prettier-diff": "./bin/prettier-diff.js"
},
"main": "index.js",
"scripts": {
"test": "yarn run --silent lint && yarn run --silent lint_md",
"test": "yarn run --silent lint && yarn run --silent lint_md && yarn run --silent prettier:diff",
"lint": "eslint -c eslint.config.cjs $(git ls-files '*.js')",
"lint_md": "node bin/mdlint.js $(git ls-files '*.md')"
"lint_md": "node bin/mdlint.js $(git ls-files '*.md')",
"prettier": "prettier",
"prettier:diff": "node tests/prettier-diff.js"
},
"repository": {
"type": "git",
Expand All @@ -20,7 +23,8 @@
"markdownlint": "0.31.1"
},
"devDependencies": {
"eslint": "^9.9.1"
"eslint": "^9.9.1",
"prettier": "^3.4.2"
},
"keywords": [
"eslint",
Expand Down
60 changes: 60 additions & 0 deletions tests/prettier-diff.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use strict';

const assert = require('assert');
const { exitFromResult, filterSupportedFiles, parseArgs } = require('../bin/prettier-diff');

assert.deepStrictEqual(parseArgs([]), {
prettierArgs: ['--check'],
diffArgs: [],
});

assert.deepStrictEqual(parseArgs(['--format', 'HEAD~1..HEAD']), {
prettierArgs: ['--write'],
diffArgs: ['HEAD~1..HEAD'],
});

assert.deepStrictEqual(parseArgs(['--write', '--cached']), {
prettierArgs: ['--write'],
diffArgs: ['--cached'],
});

assert.deepStrictEqual(
filterSupportedFiles([
'index.js',
'lib/config.cjs',
'src/main.ts',
'workflow.yaml',
'README.md',
'package-lock.json',
'image.png',
'script.sh',
]),
['index.js', 'lib/config.cjs', 'src/main.ts', 'workflow.yaml', 'README.md', 'package-lock.json'],
);

const originalExit = process.exit;
const originalStderrWrite = process.stderr.write;

let exitCode;
let stderr = '';

process.exit = code => {
exitCode = code;
throw new Error('process.exit');
};
process.stderr.write = message => {
stderr += message;
};

assert.throws(
() =>
exitFromResult({
error: new Error('spawn yarn ENOENT'),
}),
/process\.exit/,
);
assert.strictEqual(exitCode, 1);
assert.match(stderr, /spawn yarn ENOENT/);

process.exit = originalExit;
process.stderr.write = originalStderrWrite;
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,11 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==

prettier@^3.4.2:
version "3.8.3"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.3.tgz#560f2de55bf01b4c0503bc629d5df99b9a1d09b0"
integrity sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==

punycode@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
Expand Down
Loading