Skip to content
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

Add prefer-structured-clone rule #2329

Merged
merged 9 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Range
  • Loading branch information
fisker committed May 7, 2024
commit 4bd215097f1d90e65db1d9461c0ce285e194848a
47 changes: 44 additions & 3 deletions rules/prefer-structured-clone.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
'use strict';
const {isCallExpression} = require('./ast/index.js');
const {isCallExpression, isMethodCall} = require('./ast/index.js');
const {} = require('./fix/index.js');
const {isNodeMatchesNameOrPath} = require('./utils/index.js');


const MESSAGE_ID_ERROR = 'prefer-structured-clone/error';
const MESSAGE_ID_SUGGESTION = 'prefer-structured-clone/suggestion';
const messages = {
[MESSAGE_ID_ERROR]: 'Prefer `structuredClone(…)` over `{{description}}(…)` to create a deep clone.',
[MESSAGE_ID_ERROR]: 'Prefer `structuredClone(…)` over `{{description}}` to create a deep clone.',
[MESSAGE_ID_SUGGESTION]: 'Switch to `structuredClone(…)`.',
};

Expand All @@ -24,11 +24,52 @@ const create = context => {
};
const functions = [...configFunctions, ...lodashCloneDeepFunctions];

// `JSON.parse(JSON.stringify(…))`
context.on('CallExpression', callExpression => {
if (!(
// `JSON.stringify()`
isMethodCall(callExpression, {
object: 'JSON',
method: 'parse',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
// `JSON.parse()`
&& isMethodCall(callExpression.arguments[0], {
object: 'JSON',
method: 'stringify',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
)) {
return;
}

const jsonParse = callExpression;
const jsonStringify = callExpression.arguments[0];

return {
node: jsonParse,
loc: {
start: jsonParse.loc.start,
end: jsonStringify.callee.loc.end,
},
messageId: MESSAGE_ID_ERROR,
data: {
description: 'JSON.parse(JSON.stringify(…))',
},
suggest: [
{
messageId: MESSAGE_ID_SUGGESTION,
fix(fixer) {},
}
],
}
});

// `_.cloneDeep(foo)`
context.on('CallExpression', callExpression => {
if (!isCallExpression(callExpression, {
argumentsLength: 1,
Expand All @@ -48,7 +89,7 @@ const create = context => {
node: callee,
messageId: MESSAGE_ID_ERROR,
data: {
description: matchedFunction.trim(),
description: `${matchedFunction.trim()}(…)`,
},
suggest: [
{
Expand Down
8 changes: 6 additions & 2 deletions test/prefer-structured-clone.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ const {test} = getTester(import.meta);
// `JSON.parse(JSON.stringify(…))`
test.snapshot({
valid: [
'JSON.parse(JSON.stringify(foo))',
'JSON.parse(new JSON.stringify(foo))',
'new JSON.parse(JSON.stringify(foo))',
'JSON.parse(JSON.stringify())',
'JSON.parse(JSON.stringify(...foo))',
'JSON.parse(JSON.stringify(foo, extraArgument))',
'JSON.parse(JSON.stringify(foo))',
'JSON.parse(...JSON.stringify(foo))',
'JSON.parse(JSON.stringify(foo), extraArgument)',
'JSON.parse(JSON.stringify?.(foo))',
'JSON.parse(JSON?.stringify(foo))',
'JSON.parse?.(JSON.stringify(foo))',
Expand All @@ -20,6 +22,8 @@ test.snapshot({
'JSON.not_parse(JSON.stringify(foo))',
'not_JSON.parse(JSON.stringify(foo))',
'JSON.stringify(JSON.parse(foo))',
// Not checking
'JSON.parse(JSON.stringify(foo, undefined, 2))',
],
invalid: [
'JSON.parse(JSON.stringify(foo))',
Expand Down
45 changes: 45 additions & 0 deletions test/snapshots/prefer-structured-clone.mjs.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,51 @@ The actual snapshot is saved in `prefer-structured-clone.mjs.snap`.

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

## invalid(1): JSON.parse(JSON.stringify(foo))

> Input

`␊
1 | JSON.parse(JSON.stringify(foo))␊
`

> Error 1/1

`␊
> 1 | JSON.parse(JSON.stringify(foo))␊
| ^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`structuredClone(…)\` over \`JSON.parse(JSON.stringify(…))\` to create a deep clone.␊
`

## invalid(2): JSON.parse(JSON.stringify(foo),)

> Input

`␊
1 | JSON.parse(JSON.stringify(foo),)␊
`

> Error 1/1

`␊
> 1 | JSON.parse(JSON.stringify(foo),)␊
| ^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`structuredClone(…)\` over \`JSON.parse(JSON.stringify(…))\` to create a deep clone.␊
`

## invalid(3): JSON.parse(JSON.stringify(foo,))

> Input

`␊
1 | JSON.parse(JSON.stringify(foo,))␊
`

> Error 1/1

`␊
> 1 | JSON.parse(JSON.stringify(foo,))␊
| ^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`structuredClone(…)\` over \`JSON.parse(JSON.stringify(…))\` to create a deep clone.␊
`

## invalid(1): _.cloneDeep(foo)

> Input
Expand Down
Binary file modified test/snapshots/prefer-structured-clone.mjs.snap
Binary file not shown.