-
-
Notifications
You must be signed in to change notification settings - Fork 421
Add prefer-response-static-json rule
#2778
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
sindresorhus
merged 9 commits into
sindresorhus:main
from
fisker:prefer-static-response-json
Oct 18, 2025
+373
−0
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
58a8404
Add `prefer-static-response-json` rule
fisker 9b5258f
Apply suggestion from @sindresorhus
fisker cd0ec4b
Apply suggestion from @sindresorhus
fisker 3322cb2
One more test as requested.
fisker 35c1ef1
Fix tests
fisker 87010a3
ASI
fisker d5c09e0
More tests
fisker c065e60
Rename as `prefer-response-static-json`
fisker 19c76f7
Fix
fisker File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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)) )) | ||
| `, | ||
| ], | ||
| }); | ||
fisker marked this conversation as resolved.
Show resolved
Hide resolved
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 not shown.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.