Skip to content

Commit 9d2bb34

Browse files
kart-uarkid15r
andauthored
refactor-enforced-Number-namespace (#2536)
* refactor-enforced-Number-namespace * Add eslint rules --------- Co-authored-by: Arkadii Yakovets <[email protected]>
1 parent aef0783 commit 9d2bb34

File tree

13 files changed

+199
-10
lines changed

13 files changed

+199
-10
lines changed

frontend/__tests__/unit/components/AnimatedCounter.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ describe('AnimatedCounter', () => {
7171
})
7272

7373
// Should show intermediate value
74-
const displayedValue = parseInt(screen.getByText(/\d+/).textContent || '0')
74+
const displayedValue = Number.parseInt(screen.getByText(/\d+/).textContent || '0')
7575
expect(displayedValue).toBeGreaterThan(0)
7676
expect(displayedValue).toBeLessThanOrEqual(50)
7777
})

frontend/__tests__/unit/components/SearchPageLayout.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ describe('<SearchPageLayout />', () => {
251251
<SearchPageLayout
252252
isLoaded={true}
253253
totalPages={3}
254-
currentPage={NaN}
254+
currentPage={Number.NaN}
255255
onSearch={() => {}}
256256
onPageChange={handlePageChange}
257257
searchQuery=""
@@ -346,7 +346,7 @@ describe('<SearchPageLayout />', () => {
346346
render(
347347
<SearchPageLayout
348348
isLoaded={true}
349-
totalPages={NaN}
349+
totalPages={Number.NaN}
350350
currentPage={1}
351351
onSearch={() => {}}
352352
onPageChange={() => {}}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const noGlobalIsFiniteRule = {
2+
meta: {
3+
type: 'suggestion',
4+
docs: {
5+
description: 'Disallow use of global isFinite, use Number.isFinite instead',
6+
recommended: false,
7+
},
8+
fixable: 'code',
9+
messages: {
10+
useNumberIsFinite: 'Use Number.isFinite() instead of global isFinite().',
11+
},
12+
schema: [],
13+
},
14+
create(context) {
15+
return {
16+
CallExpression(node) {
17+
if (node.callee.type === 'Identifier' && node.callee.name === 'isFinite') {
18+
context.report({
19+
node: node.callee,
20+
messageId: 'useNumberIsFinite',
21+
fix(fixer) {
22+
return fixer.replaceText(node.callee, 'Number.isFinite')
23+
},
24+
})
25+
}
26+
},
27+
}
28+
},
29+
}
30+
31+
export default noGlobalIsFiniteRule
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const noGlobalIsNaNRule = {
2+
meta: {
3+
type: 'suggestion',
4+
docs: {
5+
description: 'Disallow use of global isNaN, use Number.isNaN instead',
6+
recommended: false,
7+
},
8+
fixable: 'code',
9+
messages: {
10+
useNumberIsNaN: 'Use Number.isNaN() instead of global isNaN().',
11+
},
12+
schema: [],
13+
},
14+
create(context) {
15+
return {
16+
CallExpression(node) {
17+
if (node.callee.type === 'Identifier' && node.callee.name === 'isNaN') {
18+
context.report({
19+
node: node.callee,
20+
messageId: 'useNumberIsNaN',
21+
fix(fixer) {
22+
return fixer.replaceText(node.callee, 'Number.isNaN')
23+
},
24+
})
25+
}
26+
},
27+
}
28+
},
29+
}
30+
31+
export default noGlobalIsNaNRule
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const noGlobalNaNRule = {
2+
meta: {
3+
type: 'suggestion',
4+
docs: {
5+
description: 'Disallow use of global NaN, use Number.NaN instead',
6+
recommended: false,
7+
},
8+
fixable: 'code',
9+
messages: {
10+
useNumberNaN: 'Use Number.NaN instead of global NaN.',
11+
},
12+
schema: [],
13+
},
14+
create(context) {
15+
return {
16+
Identifier(node) {
17+
// Only report if it's 'NaN' and not already part of 'Number.NaN'
18+
if (node.name === 'NaN') {
19+
const parent = node.parent
20+
if (
21+
parent &&
22+
parent.type === 'MemberExpression' &&
23+
parent.property === node &&
24+
parent.object &&
25+
parent.object.type === 'Identifier' &&
26+
parent.object.name === 'Number'
27+
) {
28+
return
29+
}
30+
31+
context.report({
32+
node,
33+
messageId: 'useNumberNaN',
34+
fix(fixer) {
35+
return fixer.replaceText(node, 'Number.NaN')
36+
},
37+
})
38+
}
39+
},
40+
}
41+
},
42+
}
43+
44+
export default noGlobalNaNRule
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const noGlobalParseFloatRule = {
2+
meta: {
3+
type: 'suggestion',
4+
docs: {
5+
description: 'Disallow use of global parseFloat, use Number.parseFloat instead',
6+
recommended: false,
7+
},
8+
fixable: 'code',
9+
messages: {
10+
useNumberParseFloat: 'Use Number.parseFloat() instead of global parseFloat().',
11+
},
12+
schema: [],
13+
},
14+
create(context) {
15+
return {
16+
CallExpression(node) {
17+
if (node.callee.type === 'Identifier' && node.callee.name === 'parseFloat') {
18+
context.report({
19+
node: node.callee,
20+
messageId: 'useNumberParseFloat',
21+
fix(fixer) {
22+
return fixer.replaceText(node.callee, 'Number.parseFloat')
23+
},
24+
})
25+
}
26+
},
27+
}
28+
},
29+
}
30+
31+
export default noGlobalParseFloatRule
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const noGlobalParseIntRule = {
2+
meta: {
3+
type: 'suggestion',
4+
docs: {
5+
description: 'Disallow use of global parseInt, use Number.parseInt instead',
6+
recommended: false,
7+
},
8+
fixable: 'code',
9+
messages: {
10+
useNumberParseInt: 'Use Number.parseInt() instead of global parseInt().',
11+
},
12+
schema: [],
13+
},
14+
create(context) {
15+
return {
16+
CallExpression(node) {
17+
if (node.callee.type === 'Identifier' && node.callee.name === 'parseInt') {
18+
context.report({
19+
node: node.callee,
20+
messageId: 'useNumberParseInt',
21+
fix(fixer) {
22+
return fixer.replaceText(node.callee, 'Number.parseInt')
23+
},
24+
})
25+
}
26+
},
27+
}
28+
},
29+
}
30+
31+
export default noGlobalParseIntRule

frontend/eslint.config.mjs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ import react from 'eslint-plugin-react'
1515
import reactHooks from 'eslint-plugin-react-hooks'
1616
import nextPlugin from '@next/eslint-plugin-next'
1717
import globals from 'globals'
18+
import noGlobalIsFiniteRule from './eslint-rules/no-global-isfinite.mjs'
19+
import noGlobalIsNaNRule from './eslint-rules/no-global-isnan.mjs'
20+
import noGlobalNaNRule from './eslint-rules/no-global-nan.mjs'
21+
import noGlobalParseFloatRule from './eslint-rules/no-global-parsefloat.mjs'
22+
import noGlobalParseIntRule from './eslint-rules/no-global-parseint.mjs'
1823

1924
const __filename = fileURLToPath(import.meta.url)
2025
const __dirname = dirname(__filename)
@@ -61,6 +66,15 @@ const eslintConfig = [
6166
react,
6267
'jsx-a11y': jsxA11y,
6368
'@next/next': nextPlugin,
69+
nest: {
70+
rules: {
71+
'no-global-isfinite': noGlobalIsFiniteRule,
72+
'no-global-isnan': noGlobalIsNaNRule,
73+
'no-global-nan': noGlobalNaNRule,
74+
'no-global-parsefloat': noGlobalParseFloatRule,
75+
'no-global-parseint': noGlobalParseIntRule,
76+
},
77+
},
6478
},
6579
settings: {
6680
'import/resolver': {
@@ -149,6 +163,11 @@ const eslintConfig = [
149163
'jsx-a11y/no-distracting-elements': 'warn',
150164
'jsx-a11y/label-has-associated-control': 'error',
151165
'jsx-a11y/click-events-have-key-events': 'warn',
166+
'nest/no-global-isfinite': 'error',
167+
'nest/no-global-isnan': 'error',
168+
'nest/no-global-nan': 'error',
169+
'nest/no-global-parsefloat': 'error',
170+
'nest/no-global-parseint': 'error',
152171
},
153172
},
154173
{

frontend/src/app/my/mentorship/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const MyMentorshipPage: React.FC = () => {
2424
const username = (session as ExtendedSession)?.user?.login
2525

2626
const initialQuery = searchParams.get('q') || ''
27-
const initialPage = parseInt(searchParams.get('page') || '1', 10)
27+
const initialPage = Number.parseInt(searchParams.get('page') || '1', 10)
2828

2929
const [searchQuery, setSearchQuery] = useState(initialQuery)
3030
const [debouncedQuery, setDebouncedQuery] = useState(initialQuery)

frontend/src/components/ModuleCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export default ModuleCard
9898
export const getSimpleDuration = (start: string, end: string): string => {
9999
const startDate = new Date(start)
100100
const endDate = new Date(end)
101-
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
101+
if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime())) {
102102
return 'Invalid duration'
103103
}
104104

0 commit comments

Comments
 (0)