Skip to content

Commit 2c2cf9b

Browse files
committed
Improve prefer-collections-with-pagination rule
1 parent 354248b commit 2c2cf9b

File tree

9 files changed

+156
-127
lines changed

9 files changed

+156
-127
lines changed

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: 4 additions & 1 deletion
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`:

eslint-plugin/docs/rules/prefer-collections-with-pagination.md

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,32 @@
77
## Rule details
88

99
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 but can work with a controller @Controller() and a decorated method @Get().
10+
is built for the [NestJS framework](https://nestjs.com) but can work with a controller @Controller() and a decorated method @Get().
1111

1212
## Examples
1313

14-
Examples of **incorrect** code for this rule:
14+
Examples of **non-compliant** code for this rule:
1515

1616
```ts
1717
@Controller()
1818
class Test {
19-
@Get()
20-
public find(): Promise<string[]> {
21-
}
19+
@Get()
20+
public find(): Promise<string[]> {}
2221
}
2322
```
2423

25-
Examples of **correct** code for this rule:
24+
Examples of **compliant** code for this rule:
2625

2726
```ts
2827
interface Pagination {
29-
items: string[];
30-
currentPage: number;
31-
totalPages: number;
28+
items: string[];
29+
currentPage: number;
30+
totalPages: number;
3231
}
3332

3433
@Controller()
3534
class Test {
36-
@Get()
37-
public find(): Promise<Pagination> {
38-
}
35+
@Get()
36+
public find(): Promise<Pagination> {}
3937
}
4038
```

eslint-plugin/lib/index.js

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,35 +24,13 @@
2424
const fs = require("fs");
2525
const path = require("path");
2626

27-
const resolveRule = (rulePath) => {
28-
try {
29-
return require(rulePath);
30-
} catch (e) {
31-
return null;
32-
}
33-
};
34-
35-
const hasTypescriptParser = () => {
36-
try {
37-
// eslint-disable-next-line node/no-unpublished-require
38-
require("@typescript-eslint/parser");
39-
return true;
40-
} catch (e) {
41-
return false;
42-
}
43-
};
44-
4527
const ruleModules = {};
28+
const configs = { recommended: { plugins: ["@ecocode"], rules: {} } };
4629

47-
const configs = {
48-
recommended: { plugins: ["@ecocode"], rules: {} },
49-
...(hasTypescriptParser() ? { parser: "@typescript-eslint/parser" } : {}),
50-
};
51-
52-
const rulesDirectory = "./rules";
53-
fs.readdirSync(path.resolve(__dirname, rulesDirectory)).forEach((file) => {
30+
const rulesDirectory = path.resolve(__dirname, "rules");
31+
fs.readdirSync(rulesDirectory).forEach((file) => {
5432
const ruleName = path.parse(file).name;
55-
const resolvedRule = resolveRule(`./${path.join(rulesDirectory, ruleName)}`);
33+
const resolvedRule = require(path.join(rulesDirectory, ruleName));
5634

5735
if (resolvedRule != null) {
5836
ruleModules[ruleName] = resolvedRule;

eslint-plugin/lib/rules/prefer-collections-with-pagination.js

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,7 @@
1616
*/
1717
"use strict";
1818

19-
const { ESLintUtils } = require("@typescript-eslint/utils");
20-
21-
const createRule = ESLintUtils.RuleCreator(
22-
(name) => `https://example.com/rule/${name}`
23-
);
24-
25-
const PAGINATION_KEY_WORDS = ["page", "pagination"];
19+
const PAGINATION_KEY_WORDS = ["page", "pagination", "paginated"];
2620

2721
const isPaginationName = (name) => {
2822
return PAGINATION_KEY_WORDS.some((keyWord) =>
@@ -51,15 +45,11 @@ const isPaginated = (objectType) => {
5145
return false;
5246
};
5347

54-
const report = (context, node) => {
55-
context.report({
56-
node,
57-
messageId: "PreferReturnCollectionsWithPagination",
58-
});
59-
};
48+
const report = (context, node) =>
49+
context.report({ node, messageId: "PreferReturnCollectionsWithPagination" });
6050

6151
// Type: RuleModule<"uppercase", ...>
62-
const rule = createRule({
52+
module.exports = {
6353
name: "prefer-collections-with-pagination",
6454
meta: {
6555
docs: {
@@ -76,10 +66,18 @@ const rule = createRule({
7666
},
7767
defaultOptions: [],
7868
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+
7977
return {
8078
Decorator(node) {
8179
if (
82-
node.expression.callee.name.toLowerCase() === "get" &&
80+
isValidDecorator(node) &&
8381
node.parent.parent.parent.type === "ClassDeclaration" &&
8482
node.parent.parent.parent.decorators.find(
8583
(decorator) =>
@@ -101,19 +99,17 @@ const rule = createRule({
10199
returnType.typeParameters.params.length === 1 &&
102100
!isPaginated(returnType.typeParameters.params[0])
103101
) {
104-
report(context, node);
102+
report(context, returnType);
105103
}
106104
} else if (
107105
returnType.type === "TSArrayType" ||
108106
!isPaginated(returnType)
109107
) {
110-
report(context, node);
108+
report(context, returnType);
111109
}
112110
}
113111
}
114112
},
115113
};
116114
},
117-
});
118-
119-
module.exports = rule;
115+
};

eslint-plugin/package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,8 @@
2828
"update:eslint-docs": "eslint-doc-generator"
2929
},
3030
"devDependencies": {
31-
"@typescript-eslint/eslint-plugin": "^5.57.1",
32-
"@typescript-eslint/parser": "^5.57.1",
33-
"@typescript-eslint/utils": "^5.57.1",
31+
"@typescript-eslint/eslint-plugin": "^5.59.6",
32+
"@typescript-eslint/parser": "^5.59.6",
3433
"eslint": "^8.19.0",
3534
"eslint-config-prettier": "^8.6.0",
3635
"eslint-doc-generator": "^1.0.0",

eslint-plugin/tests/lib/rules/prefer-collections-with-pagination.js

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,43 @@
1-
const { ESLintUtils } = require("@typescript-eslint/utils");
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+
223
const rule = require("../../../lib/rules/prefer-collections-with-pagination");
24+
const RuleTester = require("eslint").RuleTester;
25+
26+
//------------------------------------------------------------------------------
27+
// Tests
28+
//------------------------------------------------------------------------------
329

4-
const ruleTester = new ESLintUtils.RuleTester({
5-
parser: "@typescript-eslint/parser",
30+
const ruleTester = new RuleTester({
31+
parser: require.resolve("@typescript-eslint/parser"),
632
});
733

8-
const expectedError = {
34+
const expectedArrayError = {
935
messageId: "PreferReturnCollectionsWithPagination",
10-
type: "Decorator",
36+
type: "TSArrayType",
37+
};
38+
const expectedReferenceError = {
39+
messageId: "PreferReturnCollectionsWithPagination",
40+
type: "TSTypeReference",
1141
};
1242

1343
ruleTester.run("prefer-collections-with-pagination", rule, {
@@ -28,6 +58,20 @@ ruleTester.run("prefer-collections-with-pagination", rule, {
2858
`,
2959
`
3060
@Controller()
61+
public class Test {
62+
@Get()
63+
public find() {}
64+
}
65+
`,
66+
`
67+
@Controller()
68+
public class Test {
69+
@Get(':id')
70+
public findOne(): Promise<string> {}
71+
}
72+
`,
73+
`
74+
@Controller()
3175
public class Test {
3276
@Get()
3377
public find(): Promise<{items: string[], currentPage: number, totalPages: number}> {}
@@ -43,7 +87,17 @@ ruleTester.run("prefer-collections-with-pagination", rule, {
4387
public find(): Promise<string[]> {}
4488
}
4589
`,
46-
errors: [expectedError],
90+
errors: [expectedReferenceError],
91+
},
92+
{
93+
code: `
94+
@Controller()
95+
public class Test {
96+
@Get()
97+
public find(): Promise<ClassicList> {}
98+
}
99+
`,
100+
errors: [expectedReferenceError],
47101
},
48102
{
49103
code: `
@@ -53,7 +107,7 @@ ruleTester.run("prefer-collections-with-pagination", rule, {
53107
public find(): string[] {}
54108
}
55109
`,
56-
errors: [expectedError],
110+
errors: [expectedArrayError],
57111
},
58112
{
59113
code: `
@@ -63,7 +117,7 @@ ruleTester.run("prefer-collections-with-pagination", rule, {
63117
public find(): Promise<{items: string[]}> {}
64118
}
65119
`,
66-
errors: [expectedError],
120+
errors: [expectedReferenceError],
67121
},
68122
],
69123
});

0 commit comments

Comments
 (0)