Skip to content

Commit 652bc36

Browse files
committed
feature/prefer-collections-with-pagination
- Rule to avoid the return of collections in HTTP GET without pagination (WIP)
1 parent 44b6c8f commit 652bc36

File tree

2 files changed

+168
-0
lines changed

2 files changed

+168
-0
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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 { 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"];
26+
27+
const isPaginationName = (name) => {
28+
return PAGINATION_KEY_WORDS.some((keyWord) =>
29+
name.toLowerCase().includes(keyWord)
30+
);
31+
};
32+
33+
const isPaginated = (objectType) => {
34+
if (objectType.type === "TSTypeReference") {
35+
// Pagination object should be an object, for example, it can't be an array
36+
37+
if (isPaginationName(objectType.typeName.name)) {
38+
return true;
39+
}
40+
41+
if (
42+
objectType.members != null &&
43+
objectType.members.some(
44+
(member) => member.key != null && isPaginationName(member.key)
45+
)
46+
) {
47+
return true;
48+
}
49+
}
50+
51+
return false;
52+
};
53+
54+
const report = (context, node) => {
55+
context.report({
56+
node,
57+
messageId: "PreferReturnCollectionsWithPagination",
58+
});
59+
};
60+
61+
// Type: RuleModule<"uppercase", ...>
62+
const rule = createRule({
63+
name: "prefer-collections-with-pagination",
64+
meta: {
65+
docs: {
66+
description: "TODO",
67+
category: "eco-design",
68+
recommended: "warn",
69+
},
70+
messages: {
71+
PreferReturnCollectionsWithPagination:
72+
"Prefer return collections with pagination in HTTP GET",
73+
},
74+
type: "suggestion",
75+
schema: [],
76+
},
77+
defaultOptions: [],
78+
create(context) {
79+
return {
80+
Decorator(node) {
81+
if (
82+
node.expression.callee.name.toLowerCase() === "get" &&
83+
node.parent.parent.parent.type === "ClassDeclaration" &&
84+
node.parent.parent.parent.decorators.find(
85+
(decorator) =>
86+
decorator.expression.callee.name.toLowerCase() === "controller"
87+
)
88+
) {
89+
const getMethod = node.parent;
90+
const returnType =
91+
getMethod.value.returnType != null
92+
? getMethod.value.returnType.typeAnnotation
93+
: null;
94+
95+
if (returnType != null) {
96+
if (returnType.typeName.name === "Promise") {
97+
if (
98+
returnType.typeParameters.params.length === 1 &&
99+
!isPaginated(returnType.typeParameters.params[0])
100+
) {
101+
report(context, returnType);
102+
} else if (!isPaginated(returnType)) {
103+
report(context, returnType);
104+
}
105+
}
106+
}
107+
}
108+
},
109+
};
110+
},
111+
});
112+
113+
module.exports = rule;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
const { ESLintUtils } = require("@typescript-eslint/utils");
2+
const rule = require("../../../lib/rules/prefer-collections-with-pagination");
3+
4+
const ruleTester = new ESLintUtils.RuleTester({
5+
parser: "@typescript-eslint/parser",
6+
});
7+
8+
ruleTester.run("prefer-collections-with-pagination", rule, {
9+
valid: [
10+
// `
11+
// @Controller()
12+
// public class Test {
13+
// @Get()
14+
// public find(): Page {}
15+
// }
16+
// `,
17+
`
18+
@Controller()
19+
public class Test {
20+
@Get()
21+
public find(): Promise<Pagination> {}
22+
}
23+
`,
24+
// `
25+
// @Controller()
26+
// public class Test {
27+
// @Get()
28+
// public find(): Promise<{items: string[], currentPage: number, totalPages: number}> {}
29+
// }
30+
// `,
31+
],
32+
invalid: [
33+
// `
34+
// @Controller()
35+
// public class Test {
36+
// @Get()
37+
// public find(): Promise<string[]> {}
38+
// }
39+
// `,
40+
// `
41+
// @Controller()
42+
// public class Test {
43+
// @Get()
44+
// public find(): string[] {}
45+
// }
46+
// `,
47+
// `
48+
// @Controller()
49+
// public class Test {
50+
// @Get()
51+
// public find(): Promise<{items: string[]}> {}
52+
// }
53+
// `,
54+
],
55+
});

0 commit comments

Comments
 (0)