mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-05 09:57:27 -08:00
perf(editor): Add lint rules for optimization-friendly syntax (#11681)
This commit is contained in:
parent
7b20c1e93d
commit
88295c7049
|
@ -178,9 +178,9 @@ export const useLinter = (
|
||||||
message: i18n.baseText('codeNodeEditor.linter.allItems.unavailableProperty'),
|
message: i18n.baseText('codeNodeEditor.linter.allItems.unavailableProperty'),
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
name: 'Remove',
|
name: 'Fix',
|
||||||
apply(view) {
|
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;
|
return lintings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.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.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.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.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.firstOrLastCall": "@:_reusableBaseText.codeNodeEditor.linter.useJson",
|
||||||
"codeNodeEditor.linter.bothModes.directAccess.itemProperty": "@:_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.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.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.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.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.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.intro": "Hey AI, generate JavaScript code that...",
|
||||||
"codeNodeEditor.askAi.help": "Help",
|
"codeNodeEditor.askAi.help": "Help",
|
||||||
|
|
Loading…
Reference in a new issue