perf(editor): Add lint rules for optimization-friendly syntax (#11681)

This commit is contained in:
Iván Ovejero 2024-11-12 13:28:51 +01:00 committed by GitHub
parent 7b20c1e93d
commit 88295c7049
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 75 additions and 3 deletions

View file

@ -178,9 +178,9 @@ export const useLinter = (
message: i18n.baseText('codeNodeEditor.linter.allItems.unavailableProperty'),
actions: [
{
name: 'Remove',
name: 'Fix',
apply(view) {
view.dispatch({ changes: { from: start - '.'.length, to: end } });
view.dispatch({ changes: { from: start, to: end, insert: 'first()' } });
},
},
],
@ -559,6 +559,76 @@ export const useLinter = (
});
});
/**
* Lint for `$(variable)` usage where variable is not a string, in both modes.
*
* $(nodeName) -> <no autofix>
*/
const isDollarSignWithVariable = (node: Node) =>
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
node.callee.name === '$' &&
node.arguments.length === 1 &&
((node.arguments[0].type !== 'Literal' && node.arguments[0].type !== 'TemplateLiteral') ||
(node.arguments[0].type === 'TemplateLiteral' && node.arguments[0].expressions.length > 0));
type TargetCallNode = RangeNode & {
callee: { name: string };
arguments: Array<{ type: string }>;
};
walk<TargetCallNode>(ast, isDollarSignWithVariable).forEach((node) => {
const [start, end] = getRange(node);
lintings.push({
from: start,
to: end,
severity: 'warning',
message: i18n.baseText('codeNodeEditor.linter.bothModes.dollarSignVariable'),
});
});
/**
* Lint for $("myNode").item access in runOnceForAllItems mode
*
* $("myNode").item -> $("myNode").first()
*/
if (toValue(mode) === 'runOnceForEachItem') {
type DollarItemNode = RangeNode & {
property: { name: string; type: string } & RangeNode;
};
const isDollarNodeItemAccess = (node: Node) =>
node.type === 'MemberExpression' &&
!node.computed &&
node.object.type === 'CallExpression' &&
node.object.callee.type === 'Identifier' &&
node.object.callee.name === '$' &&
node.object.arguments.length === 1 &&
node.object.arguments[0].type === 'Literal' &&
node.property.type === 'Identifier' &&
node.property.name === 'item';
walk<DollarItemNode>(ast, isDollarNodeItemAccess).forEach((node) => {
const [start, end] = getRange(node.property);
lintings.push({
from: start,
to: end,
severity: 'warning',
message: i18n.baseText('codeNodeEditor.linter.eachItem.preferFirst'),
actions: [
{
name: 'Fix',
apply(view) {
view.dispatch({ changes: { from: start, to: end, insert: 'first()' } });
},
},
],
});
});
}
return lintings;
}

View file

@ -447,7 +447,7 @@
"codeNodeEditor.linter.allItems.itemCall": "`item` is a property to access, not a method to call. Did you mean `.item` without brackets?",
"codeNodeEditor.linter.allItems.itemMatchingNoArg": "`.itemMatching()` expects an item index to be passed in as its argument.",
"codeNodeEditor.linter.allItems.unavailableItem": "Legacy `item` is only available in the 'Run Once for Each Item' mode.",
"codeNodeEditor.linter.allItems.unavailableProperty": "`.item` is only available in the 'Run Once for Each Item' mode.",
"codeNodeEditor.linter.allItems.unavailableProperty": "`.item` is only available in the 'Run Once for Each Item' mode. Use `.first()` instead.",
"codeNodeEditor.linter.allItems.unavailableVar": "is only available in the 'Run Once for Each Item' mode.",
"codeNodeEditor.linter.bothModes.directAccess.firstOrLastCall": "@:_reusableBaseText.codeNodeEditor.linter.useJson",
"codeNodeEditor.linter.bothModes.directAccess.itemProperty": "@:_reusableBaseText.codeNodeEditor.linter.useJson",
@ -458,7 +458,9 @@
"codeNodeEditor.linter.eachItem.returnArray": "Code doesn't return an object. Array found instead. Please return an object representing the output item",
"codeNodeEditor.linter.eachItem.unavailableItems": "Legacy `items` is only available in the 'Run Once for All Items' mode.",
"codeNodeEditor.linter.eachItem.unavailableMethod": "Method `$input.{method}()` is only available in the 'Run Once for All Items' mode.",
"codeNodeEditor.linter.eachItem.preferFirst": "Prefer `.first()` over `.item` so n8n can optimize execution",
"codeNodeEditor.linter.bothModes.syntaxError": "Syntax error",
"codeNodeEditor.linter.bothModes.dollarSignVariable": "Use a string literal instead of a variable so n8n can optimize execution.",
"codeNodeEditor.askAi.placeholder": "Tell AI what you want the code to achieve. You can reference input data fields using dot notation (e.g. user.email)",
"codeNodeEditor.askAi.intro": "Hey AI, generate JavaScript code that...",
"codeNodeEditor.askAi.help": "Help",