Skip to content

Commit

Permalink
feat: Allow clients to access messages from eslint (#827)
Browse files Browse the repository at this point in the history
Co-authored-by: Rebecca Vest <rvest@equinoxmedia.com>
Co-authored-by: Rebecca Vest <olserebe@hotmail.com>
  • Loading branch information
3 people committed Dec 19, 2023
1 parent d4d55fd commit 6ba8025
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 8 deletions.
9 changes: 9 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,15 @@
"avatar_url": "https://avatars.githubusercontent.com/u/999845?v=4",
"profile": "https://jonathan.rehm.me/",
"contributions": ["bug", "code"]
},
{
"login": "gwhitney",
"name": "Glen Whitney",
"avatar_url": "https://avatars.githubusercontent.com/u/3825429?v=4",
"profile": "https://github.com/gwhitney",
"contributions": [
"code"
]
}
],
"repoType": "github",
Expand Down
8 changes: 8 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
5 changes: 5 additions & 0 deletions .changeset/six-cycles-double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'prettier-eslint': minor
---

feat: Allow clients to access messages from ESLint
3 changes: 3 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ const config = {
'import/no-import-module-exports': 'off',
'arrow-parens': ['error', 'as-needed'],
quotes: ['error', 'single', { avoidEscape: true }],
},
settings: {
'import/ignore': ['node_modules', 'src'] // Using CommonJS in src
}
};

Expand Down
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,46 @@ This allows you to use `eslint` to look for bugs and/or bad practices, and use

`prettier-eslint` will **only** propagate _parsing_ errors when either `prettier` or `eslint` fails. In addition to propagating the errors, it will also log a specific message indicating what it was doing at the time of the failure.

**Note:** `prettier-eslint` will not show any message regarding broken rules in either `prettier` or `eslint`.
**Note:** `format` will not show any message regarding broken rules in either `prettier` or `eslint`.

## Capturing ESLint messages

```javascript
const {analyze} = require("prettier-eslint");

const text = 'var x = 0;';
const result = await analyze({
text,
eslintConfig: {
rules: { 'no-var': 'error' }
}
})
console.log(result.messages);
```

produces on the console

```
[{
ruleId: 'no-var',
severity: 2,
message: 'Unexpected var, use let or const instead.',
line: 1,
column: 1,
nodeType: 'VariableDeclaration',
messageId: 'unexpectedVar',
endLine: 1,
endColumn: 11
}]
```

The additional export `analyze` is identical to `format` except that it
returns a simple object with properties `output` giving the exact string
that `format` would return, and `messages` giving the array of message
descriptions (with the structure shown above) produced by the `eslint`
analysis of the code. You may use `analyze` in place of `format` if you
would like to perform the formatting but also capture any errors that
`eslint` may notice.

## Technical details

Expand Down Expand Up @@ -308,6 +347,7 @@ Thanks goes to these people ([emoji key][emojis]):
<td align="center" valign="top" width="14.28%"><a href="https://github.com/chrisbobbe"><img src="https://avatars.githubusercontent.com/u/22248748?v=4?s=100" width="100px;" alt="Chris Bobbe"/><br /><sub><b>Chris Bobbe</b></sub></a><br /><a href="https://github.com/prettier/prettier-eslint/issues?q=author%3Achrisbobbe" title="Bug reports">🐛</a> <a href="https://github.com/prettier/prettier-eslint/commits?author=chrisbobbe" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.1stg.me/"><img src="https://avatars.githubusercontent.com/u/8336744?v=4?s=100" width="100px;" alt="JounQin"/><br /><sub><b>JounQin</b></sub></a><br /><a href="#question-JounQin" title="Answering Questions">💬</a> <a href="https://github.com/prettier/prettier-eslint/commits?author=JounQin" title="Code">💻</a> <a href="#design-JounQin" title="Design">🎨</a> <a href="https://github.com/prettier/prettier-eslint/commits?author=JounQin" title="Documentation">📖</a> <a href="#ideas-JounQin" title="Ideas, Planning, & Feedback">🤔</a> <a href="#infra-JounQin" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#maintenance-JounQin" title="Maintenance">🚧</a> <a href="#plugin-JounQin" title="Plugin/utility libraries">🔌</a> <a href="#projectManagement-JounQin" title="Project Management">📆</a> <a href="https://github.com/prettier/prettier-eslint/pulls?q=is%3Apr+reviewed-by%3AJounQin" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/prettier/prettier-eslint/commits?author=JounQin" title="Tests">⚠️</a> <a href="#tool-JounQin" title="Tools">🔧</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://jonathan.rehm.me/"><img src="https://avatars.githubusercontent.com/u/999845?v=4?s=100" width="100px;" alt="Jonathan Rehm"/><br /><sub><b>Jonathan Rehm</b></sub></a><br /><a href="https://github.com/prettier/prettier-eslint/commits?author=jkrehm" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gwhitney"><img src="https://avatars.githubusercontent.com/u/3825429?v=4?s=100" width="100px;" alt="Glen Whitney"/><br /><sub><b>Glen Whitney</b></sub></a><br /><a href="https://github.com/prettier/prettier-eslint/commits?author=gwhitney" title="Code">💻</a></td>
</tr>
</tbody>
</table>
Expand Down
18 changes: 17 additions & 1 deletion src/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import stripIndent from 'strip-indent';
import eslintMock from 'eslint';
import prettierMock from 'prettier';
import loglevelMock from 'loglevel-colored-level-prefix';
import format from '../';
import {format, analyze} from '../';

jest.mock('fs');

Expand Down Expand Up @@ -256,6 +256,22 @@ tests.forEach(({ title, modifier, input, output }) => {
});
});

test('analyze returns the messages', async () => {
const text = 'var x = 0;';
const result = await analyze({
text,
eslintConfig: {
rules: { 'no-var': 'error' }
}
})
expect(result.output).toBe(`${text}\n`);
expect(result.messages).toHaveLength(1);
const msg = result.messages[0];
expect(msg.ruleId).toBe('no-var');
expect(msg.column).toBe(1);
expect(msg.endColumn).toBe(11);
})

test('failure to fix with eslint throws and logs an error', async () => {
const { lintText } = eslintMock.mock;
const error = new Error('Something happened');
Expand Down
39 changes: 33 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,25 @@ module.exports = format;
* @return {Promise<String>} - the formatted string
*/
async function format(options) {
const {output} = await analyze(options);
return output;
}

/**
* Analyzes and formats text with prettier and eslint, based on the
* identical options as for the `format` function. It differs from
* `format` only in that the return value is a simple object with
* properties `output` giving the formatted code and `messages` giving
* any error messages generated in the analysis.
* @param {Object} identical to options parameter of `format`
* @returns {Promise<Object>} the return value is an object `r` such that
* `r.output` is the formatted string and `r.messages` is an array of
* message specifications from eslint.
*/
async function analyze(options) {
const { logLevel = getDefaultLogLevel() } = options;
logger.setLevel(logLevel);
logger.trace('called format with options:', prettyFormat(options));
logger.trace('called analyze with options:', prettyFormat(options));

const {
filePath,
Expand Down Expand Up @@ -128,11 +144,17 @@ async function format(options) {
const eslintFixed = await eslintFix(text, filePath);
return prettify(eslintFixed);
}
return eslintFix(await prettify(text), filePath);
return eslintFix((await prettify(text)).output, filePath);
}

function createPrettify(formatOptions, prettierPath) {
return async function prettify(text) {
return async function prettify(param) {
let text = param
let messages = []
if (typeof param !== 'string') {
text = param.output
messages = param.text
}
logger.debug('calling prettier on text');
logger.trace(
stripIndent`
Expand All @@ -153,7 +175,7 @@ function createPrettify(formatOptions, prettierPath) {
${indentString(output, 2)}
`
);
return output;
return {output, messages};
} catch (error) {
logger.error('prettier formatting failed due to a prettier error');
throw error;
Expand Down Expand Up @@ -208,7 +230,7 @@ function createEslintFix(eslintConfig, eslintPath) {
);
// default the output to text because if there's nothing
// to fix, eslint doesn't provide `output`
const [{ output = text }] = await report;
const [{ output = text, messages }] = await report;
logger.trace('eslint --fix: output === input', output === text);
// NOTE: We're ignoring linting errors/warnings here and
// defaulting to the given text if there are any
Expand All @@ -221,7 +243,7 @@ function createEslintFix(eslintConfig, eslintPath) {
${indentString(output, 2)}
`
);
return output;
return {output, messages};
} catch (error) {
logger.error('eslint fix failed due to an eslint error');
throw error;
Expand Down Expand Up @@ -320,3 +342,8 @@ function getModulePath(filePath = __filename, moduleName) {
function getDefaultLogLevel() {
return process.env.LOG_LEVEL || 'warn';
}

// Allow named imports of either `analyze` or `format` from this module,
// while leaving `format` in place as the default import:
module.exports.format = format
module.exports.analyze = analyze

0 comments on commit 6ba8025

Please sign in to comment.