From dcfdb618aad56e3e0cfe59e722ec74a9e86049a9 Mon Sep 17 00:00:00 2001
From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com>
Date: Thu, 3 Oct 2024 20:57:13 +0100
Subject: [PATCH 1/7] boolean-conditional-rendering rule
---
.../boolean-conditional-rendering.js | 51 ++++
.../boolean-conditional-rendering.test.js | 260 ++++++++++++++++++
eslint-plugin-expensify/tests/react.tsx | 0
rules/expensify.js | 1 +
4 files changed, 312 insertions(+)
create mode 100644 eslint-plugin-expensify/boolean-conditional-rendering.js
create mode 100644 eslint-plugin-expensify/tests/boolean-conditional-rendering.test.js
create mode 100644 eslint-plugin-expensify/tests/react.tsx
diff --git a/eslint-plugin-expensify/boolean-conditional-rendering.js b/eslint-plugin-expensify/boolean-conditional-rendering.js
new file mode 100644
index 0000000..be91c21
--- /dev/null
+++ b/eslint-plugin-expensify/boolean-conditional-rendering.js
@@ -0,0 +1,51 @@
+const { ESLintUtils } = require('@typescript-eslint/utils');
+
+module.exports = {
+ name: 'boolean-conditional-rendering',
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'Enforce boolean conditions in React conditional rendering',
+ recommended: 'error',
+ },
+ schema: [],
+ messages: {
+ nonBooleanConditional: 'The left side of conditional rendering should be a boolean, not \"{{type}}\".',
+ },
+ },
+ defaultOptions: [],
+ create(context) {
+ const parserServices = ESLintUtils.getParserServices(context);
+ const typeChecker = parserServices.program.getTypeChecker();
+ return {
+ LogicalExpression(node) {
+ if (node.operator === '&&' && isJSXElement(node.right)) {
+ const leftType = typeChecker.getTypeAtLocation(
+ parserServices.esTreeNodeToTSNodeMap.get(node.left)
+ );
+ if (!isBoolean(leftType)) {
+ const baseType = typeChecker.getBaseTypeOfLiteralType(leftType);
+ context.report({
+ node: node.left,
+ messageId: 'nonBooleanConditional',
+ data: {
+ type: typeChecker.typeToString(baseType),
+ },
+ });
+ }
+ }
+ },
+ };
+ function isJSXElement(node) {
+ return node.type === 'JSXElement' || node.type === 'JSXFragment';
+ }
+ function isBoolean(type) {
+ return (
+ (type.getFlags() & (16 | 528 | 512)) !== 0 || // TypeFlags.Boolean | TypeFlags.BooleanLike | TypeFlags.BooleanLiteral
+ (type.isUnion() && type.types.every(t =>
+ (t.getFlags() & (16 | 528 | 512)) !== 0 // TypeFlags.Boolean | TypeFlags.BooleanLike | TypeFlags.BooleanLiteral
+ ))
+ );
+ }
+ },
+}
diff --git a/eslint-plugin-expensify/tests/boolean-conditional-rendering.test.js b/eslint-plugin-expensify/tests/boolean-conditional-rendering.test.js
new file mode 100644
index 0000000..d04e8d2
--- /dev/null
+++ b/eslint-plugin-expensify/tests/boolean-conditional-rendering.test.js
@@ -0,0 +1,260 @@
+const RuleTester = require('@typescript-eslint/rule-tester').RuleTester;
+const rule = require('../boolean-conditional-rendering');
+
+const ruleTester = new RuleTester({
+ parser: '@typescript-eslint/parser',
+ parserOptions: {
+ project: './tsconfig.json',
+ tsconfigRootDir: __dirname,
+ sourceType: 'module',
+ ecmaVersion: 2020,
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+});
+
+ruleTester.run('boolean-conditional-rendering', rule, {
+ valid: [
+ {
+ code: `
+ const isActive = true;
+ isActive && ;
+ `,
+ },
+ {
+ code: `
+ const isActive = false;
+ isActive && ;
+ `,
+ },
+ {
+ code: `
+ const isVisible = Boolean(someValue);
+ isVisible && ;
+ `,
+ },
+ {
+ code: `
+ const user = { isLoggedIn: true, isBlocked: false };
+ const isAuthorized = user.isLoggedIn && !user.isBlocked;
+ isAuthorized && ;
+ `,
+ },
+ {
+ code: `
+ function isAuthenticated() { return true; }
+ isAuthenticated() && ;
+ `,
+ },
+ {
+ code: `
+ const isReady: boolean = true;
+ isReady && ;
+ `,
+ },
+ {
+ code: `
+ const isNotActive = !isActive;
+ isNotActive && ;
+ `,
+ },
+ {
+ code: `
+ const condition = !!someValue;
+ condition && ;
+ `,
+ },
+ {
+ code: `
+ const condition = someValue as boolean;
+ condition && ;
+ `,
+ },
+ {
+ code: `
+ enum Status { Active, Inactive }
+ const isActive = status === Status.Active;
+ isActive && ;
+ `,
+ },
+ {
+ code: `
+ const isAvailable = checkAvailability();
+ isAvailable && ;
+ function checkAvailability(): boolean { return true; }
+ `,
+ },
+ ],
+ invalid: [
+ {
+ code: `
+ const condition = "string";
+ condition && ;
+ `,
+ errors: [
+ {
+ messageId: 'nonBooleanConditional',
+ data: { type: 'string' },
+ },
+ ],
+ },
+ {
+ code: `
+ const condition = 42;
+ condition && ;
+ `,
+ errors: [
+ {
+ messageId: 'nonBooleanConditional',
+ data: { type: 'number' },
+ },
+ ],
+ },
+ {
+ code: `
+ const condition = [];
+ condition && ;
+ `,
+ errors: [
+ {
+ messageId: 'nonBooleanConditional',
+ data: { type: 'any[]' },
+ },
+ ],
+ },
+ {
+ code: `
+ const condition = {};
+ condition && ;
+ `,
+ errors: [
+ {
+ messageId: 'nonBooleanConditional',
+ data: { type: '{}' },
+ },
+ ],
+ },
+ {
+ code: `
+ const condition = null;
+ condition && ;
+ `,
+ errors: [
+ {
+ messageId: 'nonBooleanConditional',
+ data: { type: 'any' },
+ },
+ ],
+ },
+ {
+ code: `
+ const condition = undefined;
+ condition && ;
+ `,
+ errors: [
+ {
+ messageId: 'nonBooleanConditional',
+ data: { type: 'any' },
+ },
+ ],
+ },
+ {
+ code: `
+ const condition = () => {};
+ condition() && ;
+ `,
+ errors: [
+ {
+ messageId: 'nonBooleanConditional',
+ data: { type: 'void' },
+ },
+ ],
+ },
+ {
+ code: `
+ const condition: unknown = someValue;
+ condition && ;
+ `,
+ errors: [
+ {
+ messageId: 'nonBooleanConditional',
+ data: { type: 'unknown' },
+ },
+ ],
+ },
+ {
+ code: `
+ const condition: boolean | string = someValue;
+ condition && ;
+ `,
+ errors: [
+ {
+ messageId: 'nonBooleanConditional',
+ data: { type: 'string | boolean' },
+ },
+ ],
+ },
+ {
+ code: `
+ const condition = someObject?.property;
+ condition && ;
+ `,
+ errors: [
+ {
+ messageId: 'nonBooleanConditional',
+ data: { type: 'any' },
+ },
+ ],
+ },
+ {
+ code: `
+ enum Status { Active, Inactive }
+ const status = Status.Active;
+ status && ;
+ `,
+ errors: [
+ {
+ messageId: 'nonBooleanConditional',
+ data: { type: 'string' },
+ },
+ ],
+ },
+ {
+ code: `
+ const condition = Promise.resolve(true);
+ condition && ;
+ `,
+ errors: [
+ {
+ messageId: 'nonBooleanConditional',
+ data: { type: 'Promise' },
+ },
+ ],
+ },
+ {
+ code: `
+ function getValue() { return "value"; }
+ getValue() && ;
+ `,
+ errors: [
+ {
+ messageId: 'nonBooleanConditional',
+ data: { type: 'string' },
+ },
+ ],
+ },
+ {
+ code: `
+ const condition = someValue as string;
+ condition && ;
+ `,
+ errors: [
+ {
+ messageId: 'nonBooleanConditional',
+ data: { type: 'string' },
+ },
+ ],
+ },
+ ],
+});
diff --git a/eslint-plugin-expensify/tests/react.tsx b/eslint-plugin-expensify/tests/react.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/rules/expensify.js b/rules/expensify.js
index 7b203cb..047b058 100644
--- a/rules/expensify.js
+++ b/rules/expensify.js
@@ -28,6 +28,7 @@ module.exports = {
message: 'Please use SafeAreaView from react-native-safe-area-context',
}],
}],
+ 'rulesdir/boolean-conditional-rendering': 'error',
},
};
From 4ba4cbc30eab8561831997729364a6866bd4fa30 Mon Sep 17 00:00:00 2001
From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com>
Date: Thu, 3 Oct 2024 22:16:46 +0100
Subject: [PATCH 2/7] Fix lint errors
---
.../boolean-conditional-rendering.js | 98 ++++++++++---------
.../boolean-conditional-rendering.test.js | 28 +++---
rules/expensify.js | 1 -
3 files changed, 65 insertions(+), 62 deletions(-)
diff --git a/eslint-plugin-expensify/boolean-conditional-rendering.js b/eslint-plugin-expensify/boolean-conditional-rendering.js
index be91c21..520dba4 100644
--- a/eslint-plugin-expensify/boolean-conditional-rendering.js
+++ b/eslint-plugin-expensify/boolean-conditional-rendering.js
@@ -1,51 +1,55 @@
-const { ESLintUtils } = require('@typescript-eslint/utils');
+const _ = require('underscore');
+const {ESLintUtils} = require('@typescript-eslint/utils');
module.exports = {
- name: 'boolean-conditional-rendering',
- meta: {
- type: 'problem',
- docs: {
- description: 'Enforce boolean conditions in React conditional rendering',
- recommended: 'error',
+ name: 'boolean-conditional-rendering',
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'Enforce boolean conditions in React conditional rendering',
+ recommended: 'error',
+ },
+ schema: [],
+ messages: {
+ nonBooleanConditional:
+ 'The left side of conditional rendering should be a boolean, not "{{type}}".',
+ },
},
- schema: [],
- messages: {
- nonBooleanConditional: 'The left side of conditional rendering should be a boolean, not \"{{type}}\".',
- },
- },
- defaultOptions: [],
- create(context) {
- const parserServices = ESLintUtils.getParserServices(context);
- const typeChecker = parserServices.program.getTypeChecker();
- return {
- LogicalExpression(node) {
- if (node.operator === '&&' && isJSXElement(node.right)) {
- const leftType = typeChecker.getTypeAtLocation(
- parserServices.esTreeNodeToTSNodeMap.get(node.left)
- );
- if (!isBoolean(leftType)) {
- const baseType = typeChecker.getBaseTypeOfLiteralType(leftType);
- context.report({
- node: node.left,
- messageId: 'nonBooleanConditional',
- data: {
- type: typeChecker.typeToString(baseType),
- },
- });
- }
+ defaultOptions: [],
+ create(context) {
+ function isJSXElement(node) {
+ return node.type === 'JSXElement' || node.type === 'JSXFragment';
+ }
+ function isBoolean(type) {
+ return (
+ // eslint-disable-next-line no-bitwise
+ (type.getFlags() & (16 | 528 | 512)) !== 0 // TypeFlags.Boolean | TypeFlags.BooleanLike | TypeFlags.BooleanLiteral
+ || (type.isUnion()
+ // eslint-disable-next-line no-bitwise
+ && _.every(type.types, t => (t.getFlags() & (16 | 528 | 512)) !== 0))
+ );
}
- },
- };
- function isJSXElement(node) {
- return node.type === 'JSXElement' || node.type === 'JSXFragment';
- }
- function isBoolean(type) {
- return (
- (type.getFlags() & (16 | 528 | 512)) !== 0 || // TypeFlags.Boolean | TypeFlags.BooleanLike | TypeFlags.BooleanLiteral
- (type.isUnion() && type.types.every(t =>
- (t.getFlags() & (16 | 528 | 512)) !== 0 // TypeFlags.Boolean | TypeFlags.BooleanLike | TypeFlags.BooleanLiteral
- ))
- );
- }
- },
-}
+ const parserServices = ESLintUtils.getParserServices(context);
+ const typeChecker = parserServices.program.getTypeChecker();
+ return {
+ LogicalExpression(node) {
+ if (!(node.operator === '&&' && isJSXElement(node.right))) {
+ return;
+ }
+ const leftType = typeChecker.getTypeAtLocation(
+ parserServices.esTreeNodeToTSNodeMap.get(node.left),
+ );
+ if (!isBoolean(leftType)) {
+ const baseType = typeChecker.getBaseTypeOfLiteralType(leftType);
+ context.report({
+ node: node.left,
+ messageId: 'nonBooleanConditional',
+ data: {
+ type: typeChecker.typeToString(baseType),
+ },
+ });
+ }
+ },
+ };
+ },
+};
diff --git a/eslint-plugin-expensify/tests/boolean-conditional-rendering.test.js b/eslint-plugin-expensify/tests/boolean-conditional-rendering.test.js
index d04e8d2..3fd05f5 100644
--- a/eslint-plugin-expensify/tests/boolean-conditional-rendering.test.js
+++ b/eslint-plugin-expensify/tests/boolean-conditional-rendering.test.js
@@ -95,7 +95,7 @@ ruleTester.run('boolean-conditional-rendering', rule, {
errors: [
{
messageId: 'nonBooleanConditional',
- data: { type: 'string' },
+ data: {type: 'string'},
},
],
},
@@ -107,7 +107,7 @@ ruleTester.run('boolean-conditional-rendering', rule, {
errors: [
{
messageId: 'nonBooleanConditional',
- data: { type: 'number' },
+ data: {type: 'number'},
},
],
},
@@ -119,7 +119,7 @@ ruleTester.run('boolean-conditional-rendering', rule, {
errors: [
{
messageId: 'nonBooleanConditional',
- data: { type: 'any[]' },
+ data: {type: 'any[]'},
},
],
},
@@ -131,7 +131,7 @@ ruleTester.run('boolean-conditional-rendering', rule, {
errors: [
{
messageId: 'nonBooleanConditional',
- data: { type: '{}' },
+ data: {type: '{}'},
},
],
},
@@ -143,7 +143,7 @@ ruleTester.run('boolean-conditional-rendering', rule, {
errors: [
{
messageId: 'nonBooleanConditional',
- data: { type: 'any' },
+ data: {type: 'any'},
},
],
},
@@ -155,7 +155,7 @@ ruleTester.run('boolean-conditional-rendering', rule, {
errors: [
{
messageId: 'nonBooleanConditional',
- data: { type: 'any' },
+ data: {type: 'any'},
},
],
},
@@ -167,7 +167,7 @@ ruleTester.run('boolean-conditional-rendering', rule, {
errors: [
{
messageId: 'nonBooleanConditional',
- data: { type: 'void' },
+ data: {type: 'void'},
},
],
},
@@ -179,7 +179,7 @@ ruleTester.run('boolean-conditional-rendering', rule, {
errors: [
{
messageId: 'nonBooleanConditional',
- data: { type: 'unknown' },
+ data: {type: 'unknown'},
},
],
},
@@ -191,7 +191,7 @@ ruleTester.run('boolean-conditional-rendering', rule, {
errors: [
{
messageId: 'nonBooleanConditional',
- data: { type: 'string | boolean' },
+ data: {type: 'string | boolean'},
},
],
},
@@ -203,7 +203,7 @@ ruleTester.run('boolean-conditional-rendering', rule, {
errors: [
{
messageId: 'nonBooleanConditional',
- data: { type: 'any' },
+ data: {type: 'any'},
},
],
},
@@ -216,7 +216,7 @@ ruleTester.run('boolean-conditional-rendering', rule, {
errors: [
{
messageId: 'nonBooleanConditional',
- data: { type: 'string' },
+ data: {type: 'string'},
},
],
},
@@ -228,7 +228,7 @@ ruleTester.run('boolean-conditional-rendering', rule, {
errors: [
{
messageId: 'nonBooleanConditional',
- data: { type: 'Promise' },
+ data: {type: 'Promise'},
},
],
},
@@ -240,7 +240,7 @@ ruleTester.run('boolean-conditional-rendering', rule, {
errors: [
{
messageId: 'nonBooleanConditional',
- data: { type: 'string' },
+ data: {type: 'string'},
},
],
},
@@ -252,7 +252,7 @@ ruleTester.run('boolean-conditional-rendering', rule, {
errors: [
{
messageId: 'nonBooleanConditional',
- data: { type: 'string' },
+ data: {type: 'string'},
},
],
},
diff --git a/rules/expensify.js b/rules/expensify.js
index 047b058..7b203cb 100644
--- a/rules/expensify.js
+++ b/rules/expensify.js
@@ -28,7 +28,6 @@ module.exports = {
message: 'Please use SafeAreaView from react-native-safe-area-context',
}],
}],
- 'rulesdir/boolean-conditional-rendering': 'error',
},
};
From 46040c1628e4680433f3d9b9dde977d347aac05a Mon Sep 17 00:00:00 2001
From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com>
Date: Thu, 3 Oct 2024 22:31:26 +0100
Subject: [PATCH 3/7] Use TypeFlags from TypeScript API instead of hardcoded
values
---
.../boolean-conditional-rendering.js | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/eslint-plugin-expensify/boolean-conditional-rendering.js b/eslint-plugin-expensify/boolean-conditional-rendering.js
index 520dba4..63feae3 100644
--- a/eslint-plugin-expensify/boolean-conditional-rendering.js
+++ b/eslint-plugin-expensify/boolean-conditional-rendering.js
@@ -1,5 +1,7 @@
+/* eslint-disable no-bitwise */
const _ = require('underscore');
const {ESLintUtils} = require('@typescript-eslint/utils');
+const ts = require('typescript');
module.exports = {
name: 'boolean-conditional-rendering',
@@ -22,11 +24,20 @@ module.exports = {
}
function isBoolean(type) {
return (
- // eslint-disable-next-line no-bitwise
- (type.getFlags() & (16 | 528 | 512)) !== 0 // TypeFlags.Boolean | TypeFlags.BooleanLike | TypeFlags.BooleanLiteral
+ (type.getFlags()
+ & (ts.TypeFlags.Boolean
+ | ts.TypeFlags.BooleanLike
+ | ts.TypeFlags.BooleanLiteral))
+ !== 0
|| (type.isUnion()
- // eslint-disable-next-line no-bitwise
- && _.every(type.types, t => (t.getFlags() & (16 | 528 | 512)) !== 0))
+ && _.every(
+ type.types,
+ t => (t.getFlags()
+ & (ts.TypeFlags.Boolean
+ | ts.TypeFlags.BooleanLike
+ | ts.TypeFlags.BooleanLiteral))
+ !== 0,
+ ))
);
}
const parserServices = ESLintUtils.getParserServices(context);
From c3505c117214b31f87b95c6cdc3f31807590f87c Mon Sep 17 00:00:00 2001
From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com>
Date: Fri, 4 Oct 2024 00:04:47 +0100
Subject: [PATCH 4/7] remove line break
---
eslint-plugin-expensify/boolean-conditional-rendering.js | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/eslint-plugin-expensify/boolean-conditional-rendering.js b/eslint-plugin-expensify/boolean-conditional-rendering.js
index 63feae3..113ed89 100644
--- a/eslint-plugin-expensify/boolean-conditional-rendering.js
+++ b/eslint-plugin-expensify/boolean-conditional-rendering.js
@@ -13,8 +13,7 @@ module.exports = {
},
schema: [],
messages: {
- nonBooleanConditional:
- 'The left side of conditional rendering should be a boolean, not "{{type}}".',
+ nonBooleanConditional: 'The left side of conditional rendering should be a boolean, not "{{type}}".',
},
},
defaultOptions: [],
From fbbe2b221d0083d9cc0698388ca94564aac4b88b Mon Sep 17 00:00:00 2001
From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com>
Date: Thu, 10 Oct 2024 00:24:34 +0100
Subject: [PATCH 5/7] add a test case for boolean | undefined
---
.../tests/boolean-conditional-rendering.test.js | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/eslint-plugin-expensify/tests/boolean-conditional-rendering.test.js b/eslint-plugin-expensify/tests/boolean-conditional-rendering.test.js
index 3fd05f5..7d092d8 100644
--- a/eslint-plugin-expensify/tests/boolean-conditional-rendering.test.js
+++ b/eslint-plugin-expensify/tests/boolean-conditional-rendering.test.js
@@ -87,6 +87,23 @@ ruleTester.run('boolean-conditional-rendering', rule, {
},
],
invalid: [
+ {
+ code: `
+ type ComponentProps = {
+ condition?: boolean;
+ };
+
+ function Component({condition}: ComponentProps) {
+ return {condition && };
+ }
+ `,
+ errors: [
+ {
+ messageId: 'nonBooleanConditional',
+ data: {type: 'boolean | undefined'},
+ },
+ ],
+ },
{
code: `
const condition = "string";
From 48f83ecca2b9e33116d821c94580d8c60fb9d08d Mon Sep 17 00:00:00 2001
From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com>
Date: Thu, 10 Oct 2024 00:30:01 +0100
Subject: [PATCH 6/7] Fix lint errors
---
eslint-plugin-expensify/CONST.js | 2 +-
...rowLayout-instead-of-isSmallScreenWidth.js | 180 +++++++++---------
...yout-instead-of-isSmallScreenWidth.test.js | 113 ++++++-----
rules/expensify.js | 2 +-
4 files changed, 148 insertions(+), 149 deletions(-)
diff --git a/eslint-plugin-expensify/CONST.js b/eslint-plugin-expensify/CONST.js
index b4fcff9..79baf8f 100644
--- a/eslint-plugin-expensify/CONST.js
+++ b/eslint-plugin-expensify/CONST.js
@@ -33,6 +33,6 @@ module.exports = {
PREFER_TYPE_FEST_TUPLE_TO_UNION: 'Prefer using `TupleToUnion` from `type-fest` for converting tuple types to union types.',
PREFER_TYPE_FEST_VALUE_OF: 'Prefer using `ValueOf` from `type-fest` to extract the type of the properties of an object.',
PREFER_AT: 'Prefer using the `.at()` method for array element access.',
- PREFER_SHOULD_USE_NARROW_LAYOUT_INSTEAD_OF_IS_SMALL_SCREEN_WIDTH: 'Prefer using `shouldUseNarrowLayout` instead of `isSmallScreenWidth` from `useResponsiveLayout`.',
+ PREFER_SHOULD_USE_NARROW_LAYOUT_INSTEAD_OF_IS_SMALL_SCREEN_WIDTH: 'Prefer using `shouldUseNarrowLayout` instead of `isSmallScreenWidth` from `useResponsiveLayout`.',
},
};
diff --git a/eslint-plugin-expensify/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth.js b/eslint-plugin-expensify/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth.js
index 53030b9..b00d4a2 100644
--- a/eslint-plugin-expensify/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth.js
+++ b/eslint-plugin-expensify/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth.js
@@ -1,104 +1,104 @@
-const {AST_NODE_TYPES} = require("@typescript-eslint/utils");
-const _ = require("underscore");
-const CONST = require("./CONST");
+const {AST_NODE_TYPES} = require('@typescript-eslint/utils');
+const _ = require('underscore');
+const CONST = require('./CONST');
module.exports = {
- meta: {
- type: "problem",
- docs: {
- description:
- "Warn against using isSmallScreenWidth from useResponsiveLayout and suggest using shouldUseNarrowLayout instead.",
+ meta: {
+ type: 'problem',
+ docs: {
+ description:
+ 'Warn against using isSmallScreenWidth from useResponsiveLayout and suggest using shouldUseNarrowLayout instead.',
+ },
+ schema: [],
},
- schema: [],
- },
- create(context) {
+ create(context) {
+ // eslint-disable-next-line es/no-nullish-coalescing-operators
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
- const sourceCode = context.sourceCode ?? context.getSourceCode();
+ return {
+ VariableDeclarator(node) {
+ if (
+ !node.init
+ || !node.init.callee
+ || node.init.callee.name !== 'useResponsiveLayout'
+ ) {
+ return;
+ }
- return {
- VariableDeclarator(node) {
- if (
- !node.init ||
- !node.init.callee ||
- node.init.callee.name !== "useResponsiveLayout"
- ) {
- return;
- }
-
- // Check for 'const {isSmallScreenWidth, ...} = useResponsiveLayout();' pattern
- if (node.id.type === AST_NODE_TYPES.ObjectPattern) {
- node.id.properties.forEach((property) => {
- if (!property.key || property.key.name !== "isSmallScreenWidth") {
- return;
- }
- context.report({
- node: property,
- message:
+ // Check for 'const {isSmallScreenWidth, ...} = useResponsiveLayout();' pattern
+ if (node.id.type === AST_NODE_TYPES.ObjectPattern) {
+ node.id.properties.forEach((property) => {
+ if (!property.key || property.key.name !== 'isSmallScreenWidth') {
+ return;
+ }
+ context.report({
+ node: property,
+ message:
CONST.MESSAGE
- .PREFER_SHOULD_USE_NARROW_LAYOUT_INSTEAD_OF_IS_SMALL_SCREEN_WIDTH,
- });
- });
- }
+ .PREFER_SHOULD_USE_NARROW_LAYOUT_INSTEAD_OF_IS_SMALL_SCREEN_WIDTH,
+ });
+ });
+ }
- const scope = sourceCode.getScope ? sourceCode.getScope(node) : context.getScope();
+ const scope = sourceCode.getScope ? sourceCode.getScope(node) : context.getScope();
- // Check for 'const var = useResponsiveLayout();' and use of this var
- const variableName = node.id.name;
- const variableUsages = _.filter(
- scope.references,
- (reference) => reference.identifier.name === variableName
- );
- variableUsages.forEach((usage) => {
- const parent = usage.identifier.parent;
+ // Check for 'const var = useResponsiveLayout();' and use of this var
+ const variableName = node.id.name;
+ const variableUsages = _.filter(
+ scope.references,
+ reference => reference.identifier.name === variableName,
+ );
+ variableUsages.forEach((usage) => {
+ const parent = usage.identifier.parent;
- // Check for 'const isSmallScreenWidth = var.isSmallScreenWidth;' pattern
- if (
- parent.type === AST_NODE_TYPES.MemberExpression &&
- parent.property.name === "isSmallScreenWidth"
- ) {
- context.report({
- node: parent.property,
- message:
+ // Check for 'const isSmallScreenWidth = var.isSmallScreenWidth;' pattern
+ if (
+ parent.type === AST_NODE_TYPES.MemberExpression
+ && parent.property.name === 'isSmallScreenWidth'
+ ) {
+ context.report({
+ node: parent.property,
+ message:
CONST.MESSAGE
- .PREFER_SHOULD_USE_NARROW_LAYOUT_INSTEAD_OF_IS_SMALL_SCREEN_WIDTH,
- });
- }
+ .PREFER_SHOULD_USE_NARROW_LAYOUT_INSTEAD_OF_IS_SMALL_SCREEN_WIDTH,
+ });
+ }
- // Check for 'const {isSmallScreenWidth} = var;' pattern
- if (
- parent.type === AST_NODE_TYPES.VariableDeclarator &&
- parent.id.type === AST_NODE_TYPES.ObjectPattern
- ) {
- parent.id.properties.forEach((property) => {
- if (!property.key || property.key.name !== "isSmallScreenWidth") {
- return;
- }
- context.report({
- node: property,
- message:
+ // Check for 'const {isSmallScreenWidth} = var;' pattern
+ if (
+ parent.type === AST_NODE_TYPES.VariableDeclarator
+ && parent.id.type === AST_NODE_TYPES.ObjectPattern
+ ) {
+ parent.id.properties.forEach((property) => {
+ if (!property.key || property.key.name !== 'isSmallScreenWidth') {
+ return;
+ }
+ context.report({
+ node: property,
+ message:
CONST.MESSAGE
- .PREFER_SHOULD_USE_NARROW_LAYOUT_INSTEAD_OF_IS_SMALL_SCREEN_WIDTH,
- });
- });
- }
- });
- },
- MemberExpression(node) {
- // Check for 'const isSmallScreenWidth = useResponsiveLayout().isSmallScreenWidth;' pattern
- if (
- node.object.type !== "CallExpression" ||
- node.object.callee.name !== "useResponsiveLayout" ||
- node.property.name !== "isSmallScreenWidth"
- ) {
- return;
- }
- context.report({
- node,
- message:
+ .PREFER_SHOULD_USE_NARROW_LAYOUT_INSTEAD_OF_IS_SMALL_SCREEN_WIDTH,
+ });
+ });
+ }
+ });
+ },
+ MemberExpression(node) {
+ // Check for 'const isSmallScreenWidth = useResponsiveLayout().isSmallScreenWidth;' pattern
+ if (
+ node.object.type !== 'CallExpression'
+ || node.object.callee.name !== 'useResponsiveLayout'
+ || node.property.name !== 'isSmallScreenWidth'
+ ) {
+ return;
+ }
+ context.report({
+ node,
+ message:
CONST.MESSAGE
- .PREFER_SHOULD_USE_NARROW_LAYOUT_INSTEAD_OF_IS_SMALL_SCREEN_WIDTH,
- });
- },
- };
- },
+ .PREFER_SHOULD_USE_NARROW_LAYOUT_INSTEAD_OF_IS_SMALL_SCREEN_WIDTH,
+ });
+ },
+ };
+ },
};
diff --git a/eslint-plugin-expensify/tests/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth.test.js b/eslint-plugin-expensify/tests/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth.test.js
index 523d3dd..d4499a5 100644
--- a/eslint-plugin-expensify/tests/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth.test.js
+++ b/eslint-plugin-expensify/tests/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth.test.js
@@ -1,74 +1,73 @@
-const RuleTester = require("eslint").RuleTester;
-const rule = require("../prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth");
-const message =
- require("../CONST").MESSAGE
+const RuleTester = require('eslint').RuleTester;
+const rule = require('../prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth');
+const message = require('../CONST').MESSAGE
.PREFER_SHOULD_USE_NARROW_LAYOUT_INSTEAD_OF_IS_SMALL_SCREEN_WIDTH;
const ruleTester = new RuleTester({
- parserOptions: {
- ecmaVersion: 6,
- sourceType: "module",
- },
+ parserOptions: {
+ ecmaVersion: 6,
+ sourceType: 'module',
+ },
});
ruleTester.run(
- "prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth",
- rule,
- {
- valid: [
- {
- code: "const {shouldUseNarrowLayout} = useResponsiveLayout();",
- },
- ],
- invalid: [
- {
- code: "const {isSmallScreenWidth} = useResponsiveLayout();",
- errors: [
- {
- message,
- },
+ 'prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth',
+ rule,
+ {
+ valid: [
+ {
+ code: 'const {shouldUseNarrowLayout} = useResponsiveLayout();',
+ },
],
- },
- {
- code: "const {isSmallScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout();",
- errors: [
- {
- message,
- },
- ],
- },
- {
- code: `
+ invalid: [
+ {
+ code: 'const {isSmallScreenWidth} = useResponsiveLayout();',
+ errors: [
+ {
+ message,
+ },
+ ],
+ },
+ {
+ code: 'const {isSmallScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout();',
+ errors: [
+ {
+ message,
+ },
+ ],
+ },
+ {
+ code: `
const isSmallScreenWidth = useResponsiveLayout().isSmallScreenWidth;
`,
- errors: [
- {
- message,
- },
- ],
- },
- {
- code: `
+ errors: [
+ {
+ message,
+ },
+ ],
+ },
+ {
+ code: `
const dimensions = useResponsiveLayout();
const isSmallScreenWidth = dimensions.isSmallScreenWidth;
`,
- errors: [
- {
- message,
- },
- ],
- },
- {
- code: `
+ errors: [
+ {
+ message,
+ },
+ ],
+ },
+ {
+ code: `
const dimensions = useResponsiveLayout();
const {isSmallScreenWidth} = dimensions;
`,
- errors: [
- {
- message,
- },
+ errors: [
+ {
+ message,
+ },
+ ],
+ },
],
- },
- ],
- }
+ },
);
diff --git a/rules/expensify.js b/rules/expensify.js
index dace182..f3e6076 100644
--- a/rules/expensify.js
+++ b/rules/expensify.js
@@ -28,7 +28,7 @@ module.exports = {
message: 'Please use SafeAreaView from react-native-safe-area-context',
}],
}],
- "rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth": "warn",
+ 'rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth': 'warn',
},
};
From 7ae166654d6e3f1e83dceb7bb4875bf7db63a22c Mon Sep 17 00:00:00 2001
From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com>
Date: Wed, 16 Oct 2024 19:43:05 +0100
Subject: [PATCH 7/7] remove a test case
---
.../tests/boolean-conditional-rendering.test.js | 17 -----------------
1 file changed, 17 deletions(-)
diff --git a/eslint-plugin-expensify/tests/boolean-conditional-rendering.test.js b/eslint-plugin-expensify/tests/boolean-conditional-rendering.test.js
index 7d092d8..3fd05f5 100644
--- a/eslint-plugin-expensify/tests/boolean-conditional-rendering.test.js
+++ b/eslint-plugin-expensify/tests/boolean-conditional-rendering.test.js
@@ -87,23 +87,6 @@ ruleTester.run('boolean-conditional-rendering', rule, {
},
],
invalid: [
- {
- code: `
- type ComponentProps = {
- condition?: boolean;
- };
-
- function Component({condition}: ComponentProps) {
- return {condition && };
- }
- `,
- errors: [
- {
- messageId: 'nonBooleanConditional',
- data: {type: 'boolean | undefined'},
- },
- ],
- },
{
code: `
const condition = "string";