chore(ws): show ESLint errors from local rules on IDE (#439)

Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com>
This commit is contained in:
Guilherme Caponetto 2025-06-25 07:52:16 -03:00 committed by Bhakti Narvekar
parent c0b8f7b300
commit eb8d3acb93
8 changed files with 96 additions and 11 deletions

View File

@ -1,5 +1,3 @@
const noReactHookNamespace = require('./eslint-local-rules/no-react-hook-namespace');
module.exports = {
parser: '@typescript-eslint/parser',
env: {
@ -13,7 +11,7 @@ module.exports = {
js: true,
useJSXTextNode: true,
project: './tsconfig.json',
tsconfigRootDir: '.',
tsconfigRootDir: __dirname,
},
// includes the typescript specific rules found here: https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules
plugins: [
@ -24,6 +22,7 @@ module.exports = {
'no-only-tests',
'no-relative-import-paths',
'prettier',
'local-rules',
],
extends: [
'eslint:recommended',
@ -200,6 +199,17 @@ module.exports = {
'no-lone-blocks': 'error',
'no-lonely-if': 'error',
'no-promise-executor-return': 'error',
'no-restricted-imports': [
'error',
{
paths: [
{
name: 'react-router',
message: 'Use react-router-dom instead.',
},
],
},
],
'no-restricted-globals': [
'error',
{
@ -221,7 +231,8 @@ module.exports = {
'symbol-description': 'error',
yoda: 'error',
'func-names': 'warn',
'no-react-hook-namespace': 'error',
'local-rules/no-react-hook-namespace': 'error',
'local-rules/no-raw-react-router-hook': 'error',
},
overrides: [
{
@ -270,7 +281,20 @@ module.exports = {
{
files: ['**/*.{js,jsx,ts,tsx}'],
rules: {
'no-react-hook-namespace': 'error',
'local-rules/no-react-hook-namespace': 'error',
'local-rules/no-raw-react-router-hook': 'error',
},
},
{
files: ['.eslintrc.js'],
parserOptions: {
project: null,
},
},
{
files: ['eslint-local-rules/**/*.js'],
rules: {
'@typescript-eslint/no-require-imports': 'off',
},
},
],

View File

@ -0,0 +1,4 @@
module.exports = {
'no-react-hook-namespace': require('./no-react-hook-namespace'),
'no-raw-react-router-hook': require('./no-raw-react-router-hook'),
};

View File

@ -0,0 +1,46 @@
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'Disallow use of raw react-router-dom hooks. Use typed wrappers instead.',
},
messages: {
avoidRawHook:
'Use "{{typedHook}}" from `~/app/routerHelper` instead of raw React Router hook "{{rawHook}}".',
},
schema: [],
},
create(context) {
const forbiddenHooks = {
useNavigate: 'useTypedNavigate',
useParams: 'useTypedParams',
useSearchParams: 'useTypedSearchParams',
useLocation: 'useTypedLocation',
};
return {
ImportDeclaration(node) {
if (node.source.value !== 'react-router-dom') {
return;
}
for (const specifier of node.specifiers) {
if (
specifier.type === 'ImportSpecifier' &&
Object.prototype.hasOwnProperty.call(forbiddenHooks, specifier.imported.name)
) {
context.report({
node: specifier,
messageId: 'avoidRawHook',
data: {
rawHook: specifier.imported.name,
typedHook: forbiddenHooks[specifier.imported.name],
},
});
}
}
},
};
},
};

View File

@ -18,6 +18,7 @@
"@patternfly/react-tokens": "^6.2.0",
"@types/js-yaml": "^4.0.9",
"date-fns": "^4.1.0",
"eslint-plugin-local-rules": "^3.0.2",
"js-yaml": "^4.1.0",
"npm-run-all": "^4.1.5",
"react": "^18",
@ -9966,6 +9967,11 @@
"license": "MIT",
"optional": true
},
"node_modules/eslint-plugin-local-rules": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-local-rules/-/eslint-plugin-local-rules-3.0.2.tgz",
"integrity": "sha512-IWME7GIYHXogTkFsToLdBCQVJ0U4kbSuVyDT+nKoR4UgtnVrrVeNWuAZkdEu1nxkvi9nsPccGehEEF6dgA28IQ=="
},
"node_modules/eslint-plugin-no-only-tests": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-3.3.0.tgz",

View File

@ -24,8 +24,8 @@
"test:unit": "npm run test:jest -- --silent",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:fix": "eslint --rulesdir eslint-local-rules --ext .js,.ts,.jsx,.tsx ./src --fix",
"test:lint": "eslint --rulesdir eslint-local-rules --max-warnings 0 --ext .js,.ts,.jsx,.tsx ./src",
"test:fix": "eslint --ext .js,.ts,.jsx,.tsx ./src --fix",
"test:lint": "eslint --max-warnings 0 --ext .js,.ts,.jsx,.tsx ./src",
"cypress:open": "cypress open --project src/__tests__/cypress",
"cypress:open:mock": "CY_MOCK=1 CY_WS_PORT=9002 npm run cypress:open -- ",
"cypress:run": "cypress run -b chrome --project src/__tests__/cypress",
@ -108,6 +108,7 @@
"@patternfly/react-tokens": "^6.2.0",
"@types/js-yaml": "^4.0.9",
"date-fns": "^4.1.0",
"eslint-plugin-local-rules": "^3.0.2",
"js-yaml": "^4.1.0",
"npm-run-all": "^4.1.5",
"react": "^18",

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { NavLink, useLocation } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import {
Brand,
Nav,
@ -9,11 +9,12 @@ import {
PageSidebar,
PageSidebarBody,
} from '@patternfly/react-core';
import { useTypedLocation } from '~/app/routerHelper';
import { useNavData, isNavDataGroup, NavDataHref, NavDataGroup } from './AppRoutes';
import { isMUITheme, LOGO_LIGHT } from './const';
const NavHref: React.FC<{ item: NavDataHref }> = ({ item }) => {
const location = useLocation();
const location = useTypedLocation();
// With the redirect in place, we can now use a simple path comparison.
const isActive = location.pathname === item.path;

View File

@ -1,8 +1,9 @@
import { useLocation, matchPath } from 'react-router-dom';
import { matchPath } from 'react-router-dom';
import { AppRouteKey, AppRoutePaths } from '~/app/routes';
import { useTypedLocation } from '~/app/routerHelper';
export function useCurrentRouteKey(): AppRouteKey | undefined {
const location = useLocation();
const location = useTypedLocation();
const { pathname } = location;
const matchEntries = Object.entries(AppRoutePaths) as [AppRouteKey, string][];

View File

@ -1,3 +1,5 @@
/* eslint-disable local-rules/no-raw-react-router-hook */
import {
generatePath,
Location,