refactor: lint for inefficient deep clones (#4378)

* 👕 Create rule `no-json-parse-json-stringify`

* 🧪 Add tests

* 👕 Enable new rule

* 👕 FIx unrelated lint issue
This commit is contained in:
Iván Ovejero 2022-10-19 09:36:25 +02:00 committed by GitHub
parent a02e92d664
commit 6a1838d8c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 77 additions and 2 deletions

View file

@ -323,6 +323,9 @@ const config = (module.exports = {
// TODO: set to `error` and fix offenses
'n8n-local-rules/no-uncaught-json-parse': 'warn',
// TODO: set to `error` and fix offenses
'n8n-local-rules/no-json-parse-json-stringify': 'warn',
// ******************************************************************
// overrides to base ruleset
// ******************************************************************

View file

@ -46,7 +46,7 @@ module.exports = {
return;
}
if (isDeepCloneOperation(node)) {
if (isJsonStringifyCall(node)) {
return;
}
@ -63,6 +63,47 @@ module.exports = {
};
},
},
'no-json-parse-json-stringify': {
meta: {
type: 'problem',
docs: {
description:
'Calls to `JSON.parse(JSON.stringify(arg))` must be replaced with `deepCopy(arg)` from `n8n-workflow`.',
recommended: 'error',
},
messages: {
noJsonParseJsonStringify: 'Replace with `deepCopy({{ argText }})`',
},
fixable: 'code',
},
create(context) {
return {
CallExpression(node) {
if (isJsonParseCall(node) && isJsonStringifyCall(node)) {
const [callExpression] = node.arguments;
const { arguments: args } = callExpression;
if (!Array.isArray(args) || args.length !== 1) return;
const [arg] = args;
if (!arg) return;
const argText = context.getSourceCode().getText(arg);
context.report({
messageId: 'noJsonParseJsonStringify',
node,
data: { argText },
fix: (fixer) => fixer.replaceText(node, `deepCopy(${argText})`),
});
}
},
};
},
},
};
const isJsonParseCall = (node) =>
@ -72,7 +113,7 @@ const isJsonParseCall = (node) =>
node.callee.property.type === 'Identifier' &&
node.callee.property.name === 'parse';
const isDeepCloneOperation = (node) => {
const isJsonStringifyCall = (node) => {
const parseArg = node.arguments?.[0];
return (
parseArg !== undefined &&

View file

@ -21,3 +21,33 @@ ruleTester.run('no-uncaught-json-parse', rules['no-uncaught-json-parse'], {
},
],
});
ruleTester.run('no-json-parse-json-stringify', rules['no-json-parse-json-stringify'], {
valid: [
{
code: 'deepCopy(foo)',
},
],
invalid: [
{
code: 'JSON.parse(JSON.stringify(foo))',
errors: [{ messageId: 'noJsonParseJsonStringify' }],
output: 'deepCopy(foo)',
},
{
code: 'JSON.parse(JSON.stringify(foo.bar))',
errors: [{ messageId: 'noJsonParseJsonStringify' }],
output: 'deepCopy(foo.bar)',
},
{
code: 'JSON.parse(JSON.stringify(foo.bar.baz))',
errors: [{ messageId: 'noJsonParseJsonStringify' }],
output: 'deepCopy(foo.bar.baz)',
},
{
code: 'JSON.parse(JSON.stringify(foo.bar[baz]))',
errors: [{ messageId: 'noJsonParseJsonStringify' }],
output: 'deepCopy(foo.bar[baz])',
},
],
});

View file

@ -62,6 +62,7 @@ export class ScheduleTrigger implements INodeType {
name: 'field',
type: 'options',
default: 'days',
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
options: [
{
name: 'Seconds',