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
20 changes: 20 additions & 0 deletions docs/rules/prefer-response-static-json.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Prefer `Response.json()` over `new Response(JSON.stringify())`

💼 This rule is enabled in the following [configs](https://github.com/sindresorhus/eslint-plugin-unicorn#recommended-config): ✅ `recommended`, ☑️ `unopinionated`.

🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).

<!-- end auto-generated rule header -->
<!-- Do not manually modify this header. Run: `npm run fix:eslint-docs` -->

Prefer using [`Response.json()`](https://developer.mozilla.org/en-US/docs/Web/API/Response/json_static) when possible.

## Examples

```js
// ❌
const response = new Response(JSON.stringify(data));

// ✅
const response = Response.json(data);
```
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ export default [
| [prefer-query-selector](docs/rules/prefer-query-selector.md) | Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()` and `.getElementsByName()`. | ✅ | 🔧 | |
| [prefer-reflect-apply](docs/rules/prefer-reflect-apply.md) | Prefer `Reflect.apply()` over `Function#apply()`. | ✅ ☑️ | 🔧 | |
| [prefer-regexp-test](docs/rules/prefer-regexp-test.md) | Prefer `RegExp#test()` over `String#match()` and `RegExp#exec()`. | ✅ ☑️ | 🔧 | 💡 |
| [prefer-response-static-json](docs/rules/prefer-response-static-json.md) | Prefer `Response.json()` over `new Response(JSON.stringify())`. | ✅ ☑️ | 🔧 | |
| [prefer-set-has](docs/rules/prefer-set-has.md) | Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence. | ✅ ☑️ | 🔧 | 💡 |
| [prefer-set-size](docs/rules/prefer-set-size.md) | Prefer using `Set#size` instead of `Array#length`. | ✅ ☑️ | 🔧 | |
| [prefer-single-call](docs/rules/prefer-single-call.md) | Enforce combining multiple `Array#push()`, `Element#classList.{add,remove}()`, and `importScripts()` into one call. | ✅ ☑️ | 🔧 | 💡 |
Expand Down
1 change: 1 addition & 0 deletions rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export {default as 'prefer-prototype-methods'} from './prefer-prototype-methods.
export {default as 'prefer-query-selector'} from './prefer-query-selector.js';
export {default as 'prefer-reflect-apply'} from './prefer-reflect-apply.js';
export {default as 'prefer-regexp-test'} from './prefer-regexp-test.js';
export {default as 'prefer-response-static-json'} from './prefer-response-static-json.js';
export {default as 'prefer-set-has'} from './prefer-set-has.js';
export {default as 'prefer-set-size'} from './prefer-set-size.js';
export {default as 'prefer-single-call'} from './prefer-single-call.js';
Expand Down
85 changes: 85 additions & 0 deletions rules/prefer-response-static-json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {
isNewExpression,
isMethodCall,
} from './ast/index.js';
import {
switchNewExpressionToCallExpression,
} from './fix/index.js';
import {
getParenthesizedRange,
isParenthesized,
needsSemicolon,
} from './utils/index.js';

const MESSAGE_ID = 'prefer-response-static-json';
const messages = {
[MESSAGE_ID]: 'Prefer using `Response.json(…)` over `JSON.stringify()`.',
};

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
NewExpression(newExpression) {
if (!isNewExpression(newExpression, {name: 'Response', minimumArguments: 1})) {
return;
}

const [jsonStringifyNode] = newExpression.arguments;
if (!isMethodCall(jsonStringifyNode, {
object: 'JSON',
method: 'stringify',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})) {
return;
}

return {
node: jsonStringifyNode.callee,
messageId: MESSAGE_ID,
/** @param {import('eslint').Rule.RuleFixer} fixer */
* fix(fixer) {
const {sourceCode} = context;
yield fixer.insertTextAfter(newExpression.callee, '.json');
yield * switchNewExpressionToCallExpression(newExpression, sourceCode, fixer);

const [dataNode] = jsonStringifyNode.arguments;
const callExpressionRange = getParenthesizedRange(jsonStringifyNode, sourceCode);
const dataNodeRange = getParenthesizedRange(dataNode, sourceCode);
// `(( JSON.stringify( (( data )), ) ))`
// ^^^^^^^^^^^^^^^^^^^
yield fixer.removeRange([callExpressionRange[0], dataNodeRange[0]]);
// `(( JSON.stringify( (( data )), ) ))`
// ^^^^^^
yield fixer.removeRange([dataNodeRange[1], callExpressionRange[1]]);

if (
!isParenthesized(newExpression, sourceCode)
&& isParenthesized(newExpression.callee, sourceCode)
) {
const tokenBefore = sourceCode.getTokenBefore(newExpression);
if (needsSemicolon(tokenBefore, sourceCode, '(')) {
yield fixer.insertTextBefore(newExpression, ';');
}
}
},
};
},
});

/** @type {import('eslint').Rule.RuleModule} */
const config = {
create,
meta: {
type: 'suggestion',
docs: {
description: 'Prefer `Response.json()` over `new Response(JSON.stringify())`.',
recommended: 'unopinionated',
},
fixable: 'code',

messages,
},
};

export default config;
51 changes: 51 additions & 0 deletions test/prefer-response-static-json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import outdent from 'outdent';
import {getTester} from './utils/test.js';

const {test} = getTester(import.meta);

test.snapshot({
valid: [
'Response.json(data)',
'Response(JSON.stringify(data))',
'new Response()',
'new NotResponse(JSON.stringify(data))',
'new Response(JSON.stringify(...data))',
'new Response(JSON.stringify())',
'new Response(JSON.stringify(data, extraArgument))',
'new Response(JSON.stringify?.(data))',
'new Response(JSON?.stringify(data))',
'new Response(new JSON.stringify(data))',
'new Response(JSON.not_stringify(data))',
'new Response(NOT_JSON.stringify(data))',
'new Response(data(JSON.stringify))',
'new Response("" + JSON.stringify(data))',
],
invalid: [
'new Response(JSON.stringify(data))',
'new Response(JSON.stringify(data), extraArgument)',
'new Response( (( JSON.stringify( (( 0, data )), ) )), )',
outdent`
function foo() {
return new // comment
Response(JSON.stringify(data))
}
`,
'new Response(JSON.stringify(data), {status: 200})',
outdent`
foo
new (( Response ))(JSON.stringify(data))
`,
outdent`
foo;
new (( Response ))(JSON.stringify(data))
`,
outdent`
foo;
(( new (( Response ))(JSON.stringify(data)) ))
`,
outdent`
foo
(( new (( Response ))(JSON.stringify(data)) ))
`,
],
});
215 changes: 215 additions & 0 deletions test/snapshots/prefer-response-static-json.js.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
# Snapshot report for `test/prefer-response-static-json.js`

The actual snapshot is saved in `prefer-response-static-json.js.snap`.

Generated by [AVA](https://avajs.dev).

## invalid(1): new Response(JSON.stringify(data))

> Input

`␊
1 | new Response(JSON.stringify(data))␊
`

> Output

`␊
1 | Response.json(data)␊
`

> Error 1/1

`␊
> 1 | new Response(JSON.stringify(data))␊
| ^^^^^^^^^^^^^^ Prefer using \`Response.json(…)\` over \`JSON.stringify()\`.␊
`

## invalid(2): new Response(JSON.stringify(data), extraArgument)

> Input

`␊
1 | new Response(JSON.stringify(data), extraArgument)␊
`

> Output

`␊
1 | Response.json(data, extraArgument)␊
`

> Error 1/1

`␊
> 1 | new Response(JSON.stringify(data), extraArgument)␊
| ^^^^^^^^^^^^^^ Prefer using \`Response.json(…)\` over \`JSON.stringify()\`.␊
`

## invalid(3): new Response( (( JSON.stringify( (( 0, data )), ) )), )

> Input

`␊
1 | new Response( (( JSON.stringify( (( 0, data )), ) )), )␊
`

> Output

`␊
1 | Response.json( (( 0, data )), )␊
`

> Error 1/1

`␊
> 1 | new Response( (( JSON.stringify( (( 0, data )), ) )), )␊
| ^^^^^^^^^^^^^^ Prefer using \`Response.json(…)\` over \`JSON.stringify()\`.␊
`

## invalid(4): function foo() { return new // comment Response(JSON.stringify(data)) }

> Input

`␊
1 | function foo() {␊
2 | return new // comment␊
3 | Response(JSON.stringify(data))␊
4 | }␊
`

> Output

`␊
1 | function foo() {␊
2 | return ( // comment␊
3 | Response.json(data))␊
4 | }␊
`

> Error 1/1

`␊
1 | function foo() {␊
2 | return new // comment␊
> 3 | Response(JSON.stringify(data))␊
| ^^^^^^^^^^^^^^ Prefer using \`Response.json(…)\` over \`JSON.stringify()\`.␊
4 | }␊
`

## invalid(5): new Response(JSON.stringify(data), {status: 200})

> Input

`␊
1 | new Response(JSON.stringify(data), {status: 200})␊
`

> Output

`␊
1 | Response.json(data, {status: 200})␊
`

> Error 1/1

`␊
> 1 | new Response(JSON.stringify(data), {status: 200})␊
| ^^^^^^^^^^^^^^ Prefer using \`Response.json(…)\` over \`JSON.stringify()\`.␊
`

## invalid(6): foo new (( Response ))(JSON.stringify(data))

> Input

`␊
1 | foo␊
2 | new (( Response ))(JSON.stringify(data))␊
`

> Output

`␊
1 | foo␊
2 | ;(( Response.json ))(data)␊
`

> Error 1/1

`␊
1 | foo␊
> 2 | new (( Response ))(JSON.stringify(data))␊
| ^^^^^^^^^^^^^^ Prefer using \`Response.json(…)\` over \`JSON.stringify()\`.␊
`

## invalid(7): foo; new (( Response ))(JSON.stringify(data))

> Input

`␊
1 | foo;␊
2 | new (( Response ))(JSON.stringify(data))␊
`

> Output

`␊
1 | foo;␊
2 | (( Response.json ))(data)␊
`

> Error 1/1

`␊
1 | foo;␊
> 2 | new (( Response ))(JSON.stringify(data))␊
| ^^^^^^^^^^^^^^ Prefer using \`Response.json(…)\` over \`JSON.stringify()\`.␊
`

## invalid(8): foo; (( new (( Response ))(JSON.stringify(data)) ))

> Input

`␊
1 | foo;␊
2 | (( new (( Response ))(JSON.stringify(data)) ))␊
`

> Output

`␊
1 | foo;␊
2 | (( (( Response.json ))(data) ))␊
`

> Error 1/1

`␊
1 | foo;␊
> 2 | (( new (( Response ))(JSON.stringify(data)) ))␊
| ^^^^^^^^^^^^^^ Prefer using \`Response.json(…)\` over \`JSON.stringify()\`.␊
`

## invalid(9): foo (( new (( Response ))(JSON.stringify(data)) ))

> Input

`␊
1 | foo␊
2 | (( new (( Response ))(JSON.stringify(data)) ))␊
`

> Output

`␊
1 | foo␊
2 | (( (( Response.json ))(data) ))␊
`

> Error 1/1

`␊
1 | foo␊
> 2 | (( new (( Response ))(JSON.stringify(data)) ))␊
| ^^^^^^^^^^^^^^ Prefer using \`Response.json(…)\` over \`JSON.stringify()\`.␊
`
Binary file added test/snapshots/prefer-response-static-json.js.snap
Binary file not shown.
Loading