Skip to content

Commit 189325f

Browse files
authored
Merge pull request #6 from JuBaas/feature/prefer-collections-with-pagination
Prefer collections with pagination (team 106 critères)
2 parents 7863820 + 2c2cf9b commit 189325f

File tree

10 files changed

+483
-25
lines changed

10 files changed

+483
-25
lines changed

.yarn/versions/37179847.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

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ Plugins are working nicely on their own! Follow instructions in the dedicated RE
5353
SonarQube plugins can be downloaded from _ecoCode_ repository.\
5454
Linters are available as dedicated NPM packages.
5555

56-
| SonarQube plugin | Linter name | Latest version |
57-
|:-------------------------------------|:--------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------|
58-
| [JavaScript][ecoCode-latest-release] | [@ecocode/eslint-plugin](eslint-plugin/README.md) | [![eslint-plugin version](https://img.shields.io/npm/v/@ecocode/eslint-plugin)](https://npmjs.com/package/@ecocode/eslint-plugin) |
56+
| SonarQube plugin | Linter name | Latest version |
57+
|:------------------------------------------------|:--------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------|
58+
| [JavaScript/TypeScript][ecoCode-latest-release] | [@ecocode/eslint-plugin](eslint-plugin/README.md) | [![eslint-plugin version](https://img.shields.io/npm/v/@ecocode/eslint-plugin)](https://npmjs.com/package/@ecocode/eslint-plugin) |
5959

6060
You can follow changelogs on [GitHub Releases page](https://github.com/green-code-initiative/ecoCode-linter/releases).
6161

eslint-plugin/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- Add support for TypeScript rules with **typescript-eslint**
1213
- Add rule `@ecocode/avoid-high-accuracy-geolocation`
1314
- Add rule `@ecocode/no-import-all-from-library`
15+
- Add rule `@ecocode/prefer-collections-with-pagination`
1416

1517
## [0.1.0] - 2023-03-24
1618

eslint-plugin/README.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
![Logo](https://github.com/green-code-initiative/ecoCode/blob/main/docs/resources/logo-large.png?raw=true)
22
======================================
33

4-
An ESLint plugin which provides JavaScript rules of the ecoCode project.
4+
An ESLint plugin which provides JavaScript and TypeScript rules of the ecoCode project.
55

66
👉 See [ecoCode-linter README](https://github.com/green-code-initiative/ecoCode-linter#readme) to have more information.
77

@@ -21,6 +21,9 @@ yarn add -D eslint @ecocode/eslint-plugin
2121
npm install -D eslint @ecocode/eslint-plugin
2222
```
2323

24+
> You are using TypeScript? You will also need to install [typescript-eslint](https://typescript-eslint.io/) to enable our rules.\
25+
> Follow [this official guide](https://typescript-eslint.io/getting-started) to install it in a few steps.
26+
2427
### Enable whole plugin
2528

2629
Add `@ecocode` recommended configuration to `extends` section of your `.eslintrc`:
@@ -65,11 +68,12 @@ to have more information about the integration.
6568
⚠️ Configurations set to warn in.\
6669
✅ Set in the `recommended` configuration.
6770

68-
| Name | Description | ⚠️ |
69-
| :------------------------------------------------------------------------------- | :--------------------------------------------------------- | :- |
70-
| [avoid-high-accuracy-geolocation](docs/rules/avoid-high-accuracy-geolocation.md) | Avoid using high accuracy geolocation in web applications. ||
71-
| [no-import-all-from-library](docs/rules/no-import-all-from-library.md) | Should not import all from library ||
72-
| [no-multiple-access-dom-element](docs/rules/no-multiple-access-dom-element.md) | Disallow multiple access of same DOM element. ||
71+
| Name | Description | ⚠️ |
72+
| :------------------------------------------------------------------------------------- | :--------------------------------------------------------- | :- |
73+
| [avoid-high-accuracy-geolocation](docs/rules/avoid-high-accuracy-geolocation.md) | Avoid using high accuracy geolocation in web applications. ||
74+
| [no-import-all-from-library](docs/rules/no-import-all-from-library.md) | Should not import all from library ||
75+
| [no-multiple-access-dom-element](docs/rules/no-multiple-access-dom-element.md) | Disallow multiple access of same DOM element. ||
76+
| [prefer-collections-with-pagination](docs/rules/prefer-collections-with-pagination.md) | Prefer API collections with pagination. ||
7377

7478
<!-- end auto-generated rules list -->
7579

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Prefer API collections with pagination (`@ecocode/prefer-collections-with-pagination`)
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 reduce the size and thus the network weight of API returns that may contain many elements. This rule
10+
is built for the [NestJS framework](https://nestjs.com) but can work with a controller @Controller() and a decorated method @Get().
11+
12+
## Examples
13+
14+
Examples of **non-compliant** code for this rule:
15+
16+
```ts
17+
@Controller()
18+
class Test {
19+
@Get()
20+
public find(): Promise<string[]> {}
21+
}
22+
```
23+
24+
Examples of **compliant** code for this rule:
25+
26+
```ts
27+
interface Pagination {
28+
items: string[];
29+
currentPage: number;
30+
totalPages: number;
31+
}
32+
33+
@Controller()
34+
class Test {
35+
@Get()
36+
public find(): Promise<Pagination> {}
37+
}
38+
```

eslint-plugin/lib/index.js

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,27 @@
2121
*/
2222
"use strict";
2323

24-
const rules = [
25-
// add rule names here in an alphabetical order to avoid conflicts
26-
"avoid-high-accuracy-geolocation",
27-
"no-import-all-from-library",
28-
"no-multiple-access-dom-element",
29-
];
24+
const fs = require("fs");
25+
const path = require("path");
3026

3127
const ruleModules = {};
3228
const configs = { recommended: { plugins: ["@ecocode"], rules: {} } };
3329

34-
rules.forEach((rule) => {
35-
ruleModules[rule] = require(`./rules/${rule}`);
36-
const {
37-
meta: {
38-
docs: { recommended },
39-
},
40-
} = ruleModules[rule];
41-
configs.recommended.rules[`@ecocode/${rule}`] =
42-
recommended === false ? "off" : recommended;
30+
const rulesDirectory = path.resolve(__dirname, "rules");
31+
fs.readdirSync(rulesDirectory).forEach((file) => {
32+
const ruleName = path.parse(file).name;
33+
const resolvedRule = require(path.join(rulesDirectory, ruleName));
34+
35+
if (resolvedRule != null) {
36+
ruleModules[ruleName] = resolvedRule;
37+
const {
38+
meta: {
39+
docs: { recommended },
40+
},
41+
} = ruleModules[ruleName];
42+
configs.recommended.rules[`@ecocode/${ruleName}`] =
43+
recommended === false ? "off" : recommended;
44+
}
4345
});
4446

4547
module.exports = { rules: ruleModules, configs };
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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+
const PAGINATION_KEY_WORDS = ["page", "pagination", "paginated"];
20+
21+
const isPaginationName = (name) => {
22+
return PAGINATION_KEY_WORDS.some((keyWord) =>
23+
name.toLowerCase().includes(keyWord)
24+
);
25+
};
26+
27+
const isPaginated = (objectType) => {
28+
if (objectType.type === "TSTypeReference") {
29+
// Pagination object should be an object, for example, it can't be an array
30+
31+
if (isPaginationName(objectType.typeName.name)) {
32+
return true;
33+
}
34+
} else if (objectType.type === "TSTypeLiteral") {
35+
if (
36+
objectType.members != null &&
37+
objectType.members.some(
38+
(member) => member.key != null && isPaginationName(member.key.name)
39+
)
40+
) {
41+
return true;
42+
}
43+
}
44+
45+
return false;
46+
};
47+
48+
const report = (context, node) =>
49+
context.report({ node, messageId: "PreferReturnCollectionsWithPagination" });
50+
51+
// Type: RuleModule<"uppercase", ...>
52+
module.exports = {
53+
name: "prefer-collections-with-pagination",
54+
meta: {
55+
docs: {
56+
description: "Prefer API collections with pagination.",
57+
category: "eco-design",
58+
recommended: "warn",
59+
},
60+
messages: {
61+
PreferReturnCollectionsWithPagination:
62+
"Prefer return collections with pagination in HTTP GET",
63+
},
64+
type: "suggestion",
65+
schema: [],
66+
},
67+
defaultOptions: [],
68+
create(context) {
69+
const isValidDecorator = (decorator) => {
70+
return (
71+
decorator.expression.callee.name.toLowerCase() === "get" &&
72+
(decorator.expression.arguments.length === 0 ||
73+
!decorator.expression.arguments[0].value.includes(":"))
74+
);
75+
};
76+
77+
return {
78+
Decorator(node) {
79+
if (
80+
isValidDecorator(node) &&
81+
node.parent.parent.parent.type === "ClassDeclaration" &&
82+
node.parent.parent.parent.decorators.find(
83+
(decorator) =>
84+
decorator.expression.callee.name.toLowerCase() === "controller"
85+
)
86+
) {
87+
const getMethod = node.parent;
88+
const returnType =
89+
getMethod.value.returnType != null
90+
? getMethod.value.returnType.typeAnnotation
91+
: null;
92+
93+
if (returnType != null) {
94+
if (
95+
returnType.type === "TSTypeReference" &&
96+
returnType.typeName.name === "Promise"
97+
) {
98+
if (
99+
returnType.typeParameters.params.length === 1 &&
100+
!isPaginated(returnType.typeParameters.params[0])
101+
) {
102+
report(context, returnType);
103+
}
104+
} else if (
105+
returnType.type === "TSArrayType" ||
106+
!isPaginated(returnType)
107+
) {
108+
report(context, returnType);
109+
}
110+
}
111+
}
112+
},
113+
};
114+
},
115+
};

eslint-plugin/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
"update:eslint-docs": "eslint-doc-generator"
2929
},
3030
"devDependencies": {
31+
"@typescript-eslint/eslint-plugin": "^5.59.6",
32+
"@typescript-eslint/parser": "^5.59.6",
3133
"eslint": "^8.19.0",
3234
"eslint-config-prettier": "^8.6.0",
3335
"eslint-doc-generator": "^1.0.0",
@@ -38,7 +40,8 @@
3840
"mocha": "^10.0.0",
3941
"npm-run-all": "^4.1.5",
4042
"nyc": "^15.1.0",
41-
"prettier": "^2.8.4"
43+
"prettier": "^2.8.4",
44+
"typescript": "^5.0.3"
4245
},
4346
"engines": {
4447
"node": "^14.17.0 || ^16.0.0 || >= 18.0.0"

0 commit comments

Comments
 (0)