Add custom binding check as plugin

This commit is contained in:
Mark Tolmacs
2025-08-21 14:27:46 +02:00
parent b4903a7eab
commit 0c5e420812
6 changed files with 107 additions and 1 deletions

View File

@@ -1,5 +1,6 @@
{
"extends": ["@excalidraw/eslint-config", "react-app"],
"plugins": ["excalidraw"],
"rules": {
"import/order": [
"warn",
@@ -38,6 +39,7 @@
{
"allowReferrer": true
}
]
],
"excalidraw/no-binding-direct-mod": "error"
}
}

View File

@@ -24,6 +24,7 @@
"dotenv": "16.0.1",
"eslint-config-prettier": "8.5.0",
"eslint-config-react-app": "7.0.1",
"eslint-plugin-eslint": "file:packages/eslint",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-prettier": "3.3.1",
"http-server": "14.1.1",

5
packages/eslint/index.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
rules: {
"no-binding-direct-mod": require("./no-binding-direct-mod"),
},
};

View File

@@ -0,0 +1,84 @@
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: "problem",
docs: {
description:
"disallow direct mutation of startBinding or endBinding via mutateElement",
category: "Best Practices",
recommended: false,
},
fixable: null,
schema: [],
messages: {
noDirectBindingMutation:
"Direct mutation of {{ property }} via mutateElement() is not allowed. Use proper binding update functions instead.",
},
},
create(context) {
return {
CallExpression(node) {
// Check if this is a call to mutateElement (direct call or method call)
let isMutateElementCall = false;
if (
node.callee.type === "Identifier" &&
node.callee.name === "mutateElement"
) {
// Direct call: mutateElement()
isMutateElementCall = true;
} else if (
node.callee.type === "MemberExpression" &&
node.callee.property.type === "Identifier" &&
node.callee.property.name === "mutateElement"
) {
// Method call: something.mutateElement() or this.scene.mutateElement()
isMutateElementCall = true;
}
if (isMutateElementCall) {
// mutateElement can have different argument patterns:
// 1. mutateElement(element, updates) - 2 args
// 2. mutateElement(element, elementsMap, updates) - 3 args
// 3. mutateElement(element, updates, options) - 3 args
let updatesArg = null;
if (node.arguments.length >= 2) {
// Try second argument first (most common pattern)
const secondArg = node.arguments[1];
if (secondArg.type === "ObjectExpression") {
updatesArg = secondArg;
} else if (node.arguments.length >= 3) {
// If second arg is not an object, try third argument
const thirdArg = node.arguments[2];
if (thirdArg.type === "ObjectExpression") {
updatesArg = thirdArg;
}
}
}
if (updatesArg) {
// Look for startBinding or endBinding properties
for (const property of updatesArg.properties) {
if (
property.type === "Property" &&
property.key.type === "Identifier" &&
(property.key.name === "startBinding" ||
property.key.name === "endBinding")
) {
context.report({
node: property,
messageId: "noDirectBindingMutation",
data: {
property: property.key.name,
},
});
}
}
}
}
},
};
},
};

View File

@@ -0,0 +1,11 @@
{
"name": "eslint-plugin-excalidraw",
"version": "0.1.0",
"main": "index.js",
"scripts": {
"lint": "eslint ."
},
"devDependencies": {
"eslint": "^7.32.0"
}
}

View File

@@ -5265,6 +5265,9 @@ eslint-module-utils@^2.12.0:
dependencies:
debug "^3.2.7"
"eslint-plugin-eslint@file:packages/eslint":
version "0.1.0"
eslint-plugin-flowtype@^8.0.3:
version "8.0.3"
resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz#e1557e37118f24734aa3122e7536a038d34a4912"