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";