Skip to content

Commit f215eaf

Browse files
committed
Add rule no-multiple-style-changes
1 parent 189325f commit f215eaf

File tree

6 files changed

+206
-0
lines changed

6 files changed

+206
-0
lines changed

.yarn/versions/019b1e7a.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
releases:
2+
"@ecocode/eslint-plugin": minor

eslint-plugin/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Add support for TypeScript rules with **typescript-eslint**
1313
- Add rule `@ecocode/avoid-high-accuracy-geolocation`
1414
- Add rule `@ecocode/no-import-all-from-library`
15+
- Add rule `@ecocode/no-multiple-style-changes`
1516
- Add rule `@ecocode/prefer-collections-with-pagination`
1617

1718
## [0.1.0] - 2023-03-24

eslint-plugin/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ to have more information about the integration.
7373
| [avoid-high-accuracy-geolocation](docs/rules/avoid-high-accuracy-geolocation.md) | Avoid using high accuracy geolocation in web applications. ||
7474
| [no-import-all-from-library](docs/rules/no-import-all-from-library.md) | Should not import all from library ||
7575
| [no-multiple-access-dom-element](docs/rules/no-multiple-access-dom-element.md) | Disallow multiple access of same DOM element. ||
76+
| [no-multiple-style-changes](docs/rules/no-multiple-style-changes.md) | Disallow multiple style changes at once. ||
7677
| [prefer-collections-with-pagination](docs/rules/prefer-collections-with-pagination.md) | Prefer API collections with pagination. ||
7778

7879
<!-- end auto-generated rules list -->
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Disallow multiple style changes at once (`@ecocode/no-multiple-style-changes`)
2+
3+
⚠️ This rule _warns_ in the ✅ `recommended` config.
4+
5+
<!-- end auto-generated rule header -->
6+
7+
## Rule Details
8+
9+
This rule aims to batch multiple style changes at once.
10+
11+
To limit the number of repaint/reflow, it is advised to batch style modifications by adding a
12+
class containing all style changes that will generate a unique reflow.
13+
14+
## Examples
15+
16+
Examples of **non-compliant** code for this rule:
17+
18+
```html
19+
<script>
20+
element.style.height = "800px";
21+
element.style.width = "600px";
22+
element.style.color = "red";
23+
</script>
24+
```
25+
26+
Examples of **compliant** code for this rule:
27+
28+
```html
29+
<style>
30+
.in-error {
31+
color: red;
32+
height: 800px;
33+
width: 800px;
34+
}
35+
</style>
36+
37+
<script>
38+
element.addClass("in-error");
39+
</script>
40+
```
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* Copyright (C) 2023 Green Code Initiative
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
"use strict";
18+
19+
/** @type {import("eslint").Rule.RuleModule} */
20+
module.exports = {
21+
meta: {
22+
type: "suggestion",
23+
docs: {
24+
description: "Disallow multiple style changes at once.",
25+
category: "eco-design",
26+
recommended: "warn",
27+
},
28+
messages: {
29+
UseClassInstead:
30+
"There are more than two style assignations for '{{elementName}}'. Use a class instead.",
31+
},
32+
schema: [],
33+
},
34+
create: function (context) {
35+
function isNodeUseStyleProperty(node) {
36+
return (
37+
node != null &&
38+
node.object != null &&
39+
node.object.property != null &&
40+
node.object.property.name === "style"
41+
);
42+
}
43+
44+
return {
45+
AssignmentExpression(node) {
46+
// Are we checking an assignation on a style property
47+
if (isNodeUseStyleProperty(node.left)) {
48+
const domElementName = node.left.object.object.name;
49+
const currentRangestart = node.left.object.object.range[0];
50+
51+
/** We get the parent AST to check if there is more than one assignation on
52+
the style of the same domElement */
53+
const currentScopeASTBody =
54+
context.getScope().block.body.length != null
55+
? context.getScope().block.body
56+
: context.getScope().block.body.body;
57+
58+
const filtered = currentScopeASTBody.filter(
59+
(e) =>
60+
e.type === "ExpressionStatement" &&
61+
e.expression.type === "AssignmentExpression" &&
62+
isNodeUseStyleProperty(e.expression.left) &&
63+
e.expression.left.object.object.name === domElementName
64+
);
65+
66+
// De-duplication, prevents multiple alerts for each line involved
67+
const isCurrentNodeTheFirstAssignation =
68+
currentRangestart <=
69+
filtered[0].expression.left.object.object.range[0];
70+
71+
if (filtered.length > 1 && isCurrentNodeTheFirstAssignation) {
72+
context.report({
73+
node,
74+
messageId: "UseClassInstead",
75+
data: { elementName: domElementName },
76+
});
77+
}
78+
}
79+
},
80+
};
81+
},
82+
};
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Copyright (C) 2023 Green Code Initiative
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
"use strict";
18+
19+
//------------------------------------------------------------------------------
20+
// Requirements
21+
//------------------------------------------------------------------------------
22+
23+
const rule = require("../../../lib/rules/no-multiple-style-changes");
24+
const RuleTester = require("eslint").RuleTester;
25+
26+
//------------------------------------------------------------------------------
27+
// Tests
28+
//------------------------------------------------------------------------------
29+
30+
const ruleTester = new RuleTester();
31+
const expectedError = {
32+
messageId: "UseClassInstead",
33+
type: "AssignmentExpression",
34+
};
35+
36+
ruleTester.run("no-multiple-style-changes", rule, {
37+
valid: [
38+
{
39+
code: 'element.style.height = "800px";',
40+
},
41+
{
42+
code: `element.style.height = "800px";
43+
element2.style.width = "800px";`,
44+
},
45+
{
46+
code: `element.style.height = "800px";
47+
function a() { element.style.width = "800px"; }
48+
`,
49+
},
50+
],
51+
52+
invalid: [
53+
{
54+
code: `function a(element){
55+
element.style.height = "800px";
56+
element.style.width = "800px";
57+
}`,
58+
errors: [expectedError],
59+
},
60+
{
61+
code: `element.style.height = "800px";
62+
element.style.width = "800px";`,
63+
errors: [expectedError],
64+
},
65+
{
66+
code: `
67+
function changeStyle()
68+
{
69+
var anyScopedVar;
70+
element.style.any = "800px";
71+
anotherChildElement.style.any = "800px";
72+
anyGlobalVar.assignation = "any";
73+
anyScopedVar = anyGlobalVar.assignation;
74+
element.style.anyOther = "800px";
75+
}
76+
`,
77+
errors: [expectedError],
78+
},
79+
],
80+
});

0 commit comments

Comments
 (0)