我正在将 ESLint 配置从 8 升级到 9.14.0。但是现在创建
eslint.config.js
文件后,当我运行 npx eslint
时,它给了我奇怪的错误。例如,我有一个名为 QuestionnaireTabNavigator.js
的组件文件,它正在使用一些组件,我在代码中使用它们,但在 ESLint 中仍然遇到问题,例如:
3:10 error 'Tab' is defined but never used no-unused-vars
3:15 error 'Tabs' is defined but never used no-unused-vars
9:8 error 'AssessmentSelector' is defined but never used no-unused-vars
10:8 error 'AssessmentsTable' is defined but never used no-unused-vars
11:8 error 'ErrorBoundaryWrapper' is defined but never used no-unused-vars
12:8 error 'Loader' is defined but never used no-unused-vars
13:8 error 'QuestionnaireDetailedResults' is defined but never used no-unused-vars
14:8 error 'QuestionnaireSummary' is defined but never used
问题是什么?
eslint.config.mjs
:
import babelParser from "@babel/eslint-parser";
import pluginJs from "@eslint/js";
import importConfig from "eslint-plugin-import";
import pluginJest from "eslint-plugin-jest";
import jsdocConfig from "eslint-plugin-jsdoc";
import jsoncConfig from "eslint-plugin-jsonc";
import pluginPrettier from "eslint-plugin-prettier";
import pluginReact from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import sortKeysFixConfig from "eslint-plugin-sort-keys-fix";
import spellcheck from "eslint-plugin-spellcheck";
import testingPlugin from "eslint-plugin-testing-library";
import globals from "globals";
const config = [
pluginJs.configs.recommended,
{
"files": ["**/*.{js,mjs,cjs,jsx}"],
"languageOptions": {
"ecmaVersion": 2020,
"globals": {
...globals.es2021,
...globals.node,
...globals.browser,
"describe": "readonly",
"expect": "readonly",
"indexedDB": "readonly",
"it": "readonly",
"jest": "readonly",
"test": "readonly"
},
"parser": babelParser,
"parserOptions": {
"babelOptions": {
"babelrc": false,
"configFile": false,
"presets": [["@babel/preset-react", { "runtime": "automatic" }], "@babel/preset-env"]
},
"ecmaFeatures": {
"jsx": true
},
"requireConfigFile": false,
"sourceType": "module"
}
},
"plugins": {
"import": importConfig,
"jsdoc": jsdocConfig,
"jsonc": jsoncConfig,
"prettier": pluginPrettier,
"react": pluginReact,
"react-hooks": reactHooks,
"sort-keys-fix": sortKeysFixConfig,
"spellcheck": spellcheck,
"testing-library": testingPlugin
},
"rules": {
"array-bracket-newline": ["error", "consistent"],
"array-bracket-spacing": ["error", "never"],
"array-callback-return": "error",
"array-element-newline": [
"error",
{
"ArrayExpression": "consistent",
"ArrayPattern": { "minItems": 3 }
}
],
"arrow-spacing": "error",
"brace-style": ["error", "1tbs"],
"camelcase": ["error", { "ignoreDestructuring": true, "properties": "never" }],
"comma-dangle": ["error", "never"],
"comma-spacing": [
"error",
{
"after": true,
"before": false
}
],
"curly": ["error", "all"],
"default-case": "off",
"eqeqeq": ["error", "always"],
"func-call-spacing": ["error", "never"],
"function-call-argument-newline": ["error", "consistent"],
"import/first": "off",
"import/order": ["error", { "groups": ["builtin", "external", "internal", "parent", "sibling", "index"] }],
"indent": ["error", 4, { "SwitchCase": 1, "ignoredNodes": ["ConditionalExpression"] }],
"jest/no-mocks-import": "off",
"jsdoc/newline-after-description": 0,
// Required for vs code auto formatting
"jsdoc/require-hyphen-before-param-description": 1,
"jsdoc/require-jsdoc": [
"error",
{
"require": {
"ArrowFunctionExpression": true,
"ClassExpression": true,
"FunctionDeclaration": true,
"FunctionExpression": true,
"MethodDefinition": true
}
}
],
"jsonc/sort-keys": "error",
"keyword-spacing": ["error", { "after": true, "before": true }],
"max-len": [
"error",
{
"code": 120,
"ignorePattern": "\".*\": \".*\"" // Ignore pattern for strings in json as they can't be broken in multi lines
}
],
"no-console": "error",
"no-dupe-else-if": "error",
"no-extend-native": "off",
"no-nested-ternary": "error",
// "no-unused-vars": "warn",
"no-useless-escape": "off",
"no-var": "error",
"object-curly-newline": ["error", { "consistent": true, "multiline": true }],
"object-curly-spacing": ["error", "always"],
"object-property-newline": ["error", { "allowAllPropertiesOnSameLine": true }],
"padding-line-between-statements": [
"error",
// Always one empty line before return statement
{
"blankLine": "always",
"next": "return",
"prev": "*"
},
// Always one empty line between methods
{
"blankLine": "always",
"next": ["block-like", "multiline-block-like"],
"prev": ["block-like", "multiline-block-like"]
},
// Avoids more than one empty line
{
"blankLine": "never",
"next": "empty",
"prev": "empty"
}
],
"prefer-const": "error",
"quote-props": ["error", "always"],
"quotes": ["error", "double"],
"radix": "off",
"react-hooks/exhaustive-deps": "error",
"react-hooks/rules-of-hooks": "error",
"react/jsx-boolean-value": ["warn", "always"],
"react/jsx-closing-bracket-location": "error",
"react/jsx-closing-tag-location": "error",
"react/jsx-curly-brace-presence": [
"error",
{
"children": "ignore",
"props": "always"
}
],
"react/jsx-tag-spacing": ["error", { "beforeSelfClosing": "always" }],
"react/no-multi-comp": ["error", { "ignoreStateless": true }],
"react/react-in-jsx-scope": "off",
"semi": ["error", "always"],
"sort-keys": [
"error",
"asc",
{
"caseSensitive": true,
"minKeys": 2,
"natural": false
}
],
"sort-keys-fix/sort-keys-fix": "error",
"space-before-blocks": "error",
"space-infix-ops": ["error", { "int32Hint": false }],
"testing-library/no-node-access": "off"
},
"settings": {
"react": {
// Only needed for older React versions
"fragment": "Fragment",
// Automatically detect React version
"pragma": "React",
// Only needed for older React versions
"runtime": "automatic",
"version": "detect" // Use React 17+ JSX transform
}
}
},
{
"files": ["**/*.test.{js,mjs,cjs,jsx}"],
"languageOptions": {
"globals": {
...globals.jest,
...globals.browser,
...globals.node,
"describe": "readonly",
"expect": "readonly",
"indexedDB": "readonly",
"it": "readonly",
"jest": "readonly",
"test": "readonly"
}
},
"plugins": {
"jest": pluginJest
},
"rules": {
"react/react-in-jsx-scope": "off"
},
"settings": {
"react": {
"version": "detect"
}
}
}
];
export default config;
package.json
文件:
{
"browserslist": [
">0.2%",
"not dead",
"not op_mini all"
],
"dependencies": {
"@types/jest": "^29.5.11",
"ajv": "^8.17.1",
"awesome-debounce-promise": "~2.1.0",
"axios": "~1.7.2",
"bootstrap": "~5.3.2",
"dotenv": "^16.4.1",
"i18next": "~23.15.1",
"i18next-http-backend": "~2.6.1",
"jest-mock-axios": "~4.7.3",
"lodash": "~4.17.21",
"moment": "~2.30.1",
"moment-timezone": "^0.5.43",
"react": "~18.3.1",
"react-bootstrap": "~2.10.2",
"react-bootstrap-icons": "~1.11.4",
"react-component-export-image": "~1.0.6",
"react-dom": "~18.3.1",
"react-draggable": "^4.4.6",
"react-dropzone": "^14.2.3",
"react-error-boundary": "^4.0.13",
"react-i18next": "~15.0.1",
"react-icons": "~5.3.0",
"react-infinite-scroller": "~1.2.6",
"react-intersection-observer": "~9.13.0",
"react-leaflet": "~4.2.1",
"react-multi-select-component": "~4.3.4",
"react-router": "~6.26.2",
"react-router-dom": "~6.26.2",
"react-router-prompt": "^0.7.0",
"react-scroll": "~1.9.0",
"react-select": "~5.8.0",
"react-switch": "~7.0.0",
"react-tabs": "~6.0.2",
"react-toastify": "~10.0.4",
"socket.io-client": "~4.8.0",
"use-csv-downloader": "0.0.0",
"web-vitals": "~4.2.2"
},
"devDependencies": {
"@babel/eslint-parser": "~7.25.9",
"@eslint/js": "^9.14.0",
"@testing-library/dom": "~10.4.0",
"@testing-library/jest-dom": "~6.6.3",
"@testing-library/react": "~16.0.1",
"@testing-library/user-event": "~14.5.1",
"@typescript-eslint/eslint-plugin": "~8.13.0",
"@typescript-eslint/parser": "~8.13.0",
"eslint": "~9.14.0",
"eslint-config-prettier": "~9.1.0",
"eslint-config-react-app": "~7.0.1",
"eslint-plugin-import": "~2.31.0",
"eslint-plugin-jest": "~28.9.0",
"eslint-plugin-jsdoc": "^50.5.0",
"eslint-plugin-jsonc": "~2.18.1",
"eslint-plugin-jsx-a11y": "~6.10.2",
"eslint-plugin-prettier": "~5.2.1",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "~5.0.0",
"eslint-plugin-sort-keys-fix": "~1.1.2",
"eslint-plugin-spellcheck": "0.0.20",
"eslint-plugin-testing-library": "~6.4.0",
"fake-indexeddb": "6.0.0",
"globals": "^15.12.0",
"pre-commit": "~1.2.2",
"prettier": "~3.3.3",
"prettier-eslint": "~16.3.0",
"prettier-eslint-cli": "~8.0.1",
"react-scripts": "~5.0.1",
"sass": "~1.80.6",
"stylelint": "~16.10.0",
"stylelint-config-prettier": "~9.0.5",
"stylelint-config-standard-scss": "~13.1.0",
"stylelint-prettier": "~5.0.2",
"stylelint-scss": "~6.8.1"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"jest": {
"collectCoverageFrom": [
"src/**/*.js",
"!src/**/*.scss",
"!src/assets/**/*.js"
],
"coverageThreshold": {
"global": {
"branches": 50,
"functions": 50,
"lines": 65,
"statements": 65
}
}
},
"name": "imp-fe",
"pre-commit": [
"lint-pre-commit",
"test-pre-commit"
],
"private": true,
"scripts": {
"build": "react-scripts build",
"eject": "react-scripts eject",
"format": "npm run prettier-fix && npm run lint-fix",
"lint": "npm run lint-check && npm run stylelint",
"lint-check": "eslint src/ --ignore-pattern 'src/__mocks__/'",
"lint-fix": "npm run lint-check -- --fix",
"lint-pre-commit": "FILE_DIFF=$(git diff --name-only --cached --diff-filter=ACMR | grep .js$ | sed 's#/[^/]*$##') && [ '$FILE_DIFF' != '' ] && (eslint --ext .jsx --ext .js --ext .json $(echo $FILE_DIFF) && npm run stylelint) || exit 0",
"prettier-fix": "npx prettier --write src/",
"pull-latest-and-verify": "npm run pull-latest-code && node ./.dev/check_if_env_latest.js",
"pull-latest-code": "git pull && npm i",
"start": "react-scripts start",
"stylelint": "npx stylelint 'src/**/*.{css,scss}'",
"stylelint-fix": "npm run stylelint -- --fix",
"test": "react-scripts test --maxWorkers 1",
"test-coverage": "npm run test -- --coverage --watchAll=false",
"test-no-watch": "npm run test -- --watchAll=false",
"test-pre-commit": "FILE_DIFF=$(git diff --name-only --cached --diff-filter=ACMR | grep .js$ | sed 's#/[^/]*$##') && [ '$FILE_DIFF' != '' ] && (npm run test-no-watch -- $(echo $FILE_DIFF) --passWithNoTests) || exit 0"
},
"version": "24.8.0"
}
组件文件
QuestionnaireTabNavigator.js
:
import PropTypes from "prop-types";
import { useCallback, useContext, useState } from "react";
import { Tab, Tabs } from "react-bootstrap";
import { withTranslation } from "react-i18next";
import { useNavigate, useSearchParams } from "react-router-dom";
import { CATEGORY_WISE_DATA_STRUCTURE, CATEGORY_WISE_DETAILED_RESULT } from "../../App.Constants";
import { QuestionnaireContext } from "../../contexts/QuestionnaireContext";
import { UserContext } from "../../contexts/UserContext";
import AssessmentSelector from "../AssessmentSelector/AssessmentSelector";
import AssessmentsTable from "../AssessmentsTable/AssessmentsTable";
import ErrorBoundaryWrapper from "../ErrorBoundaryWrapper/ErrorBoundaryWrapper";
import Loader from "../Loaders/Loader/Loader";
import QuestionnaireDetailedResults from "../QuestionnaireDetailedResults/QuestionnaireDetailedResults";
import QuestionnaireSummary from "../QuestionnaireSummary/QuestionnaireSummary";
import "./QuestionnaireTabNavigator.scss";
/**
*
* @param {*} props - Parent props
* @returns {Element} - Returns multiple tabs with different sub components
*/
const QuestionnaireTabNavigator = ({ t }) => {
const navigate = useNavigate();
const { fetchAssessmentsList, "allAssessmentsData": apiResponse } = useContext(QuestionnaireContext);
const { user } = useContext(UserContext);
const [searchParams] = useSearchParams();
const section = searchParams.get("section");
/**
* Added this extra check for apiResponse for smooth transitioning.
* Loader will be visible based on current state value.
* Also updating these state values from AssessmentSelector component.
*/
const [benchmarkingData, setBenchmarkingData] = useState(!apiResponse.length ? CATEGORY_WISE_DATA_STRUCTURE : null);
const [detailedResults, setDetailedResults] = useState(!apiResponse.length ? CATEGORY_WISE_DETAILED_RESULT : null);
let tab = "assessments";
if (window.location.pathname.includes("summary")) {
tab = "summary";
} else if (window.location.pathname.includes("detailed-results")) {
tab = "detailed-results";
}
const [selectedTab, setSelectedTab] = useState(tab);
const handleTabSelect = useCallback(
(tab) => {
if (tab === "detailed-results") {
navigate(
`/${user.selectedTenant}/maturity-assessment/detailed-results${
section ? `section=${section}` : ""
}`,
{
"state": { "redirect": "no" }
}
);
} else if (tab === "summary") {
navigate(`/${user.selectedTenant}/maturity-assessment/summary`, {
"state": { "redirect": "no" }
});
} else {
navigate(`/${user.selectedTenant}/maturity-assessment/all-assessments`, {
"state": { "redirect": "no" }
});
}
},
[navigate, section, user.selectedTenant]
);
const setStateMethod = selectedTab === "detailed-results" ? setDetailedResults : setBenchmarkingData;
return (
<div
className={"QuestionnaireTabNavigator bottom-border-tab-variant"}
data-testid={"QuestionnaireTabNavigator"}
>
{selectedTab !== "assessments" ? (
<AssessmentSelector
assessmentsList={apiResponse}
selectedTab={selectedTab}
setStateMethod={setStateMethod}
/>
) : null}
<Tabs
activeKey={selectedTab}
id={"questionnaire-tab-navigator"}
transition={false}
onSelect={handleTabSelect}
>
<Tab eventKey={"assessments"} title={t("questionnaire.tab_navigator.assessments")}>
<ErrorBoundaryWrapper componentTitle={"Assessment table"}>
<AssessmentsTable apiResponse={apiResponse} fetchAssessmentsList={fetchAssessmentsList} />
</ErrorBoundaryWrapper>
</Tab>
<Tab eventKey={"summary"} title={t("questionnaire.tab_navigator.summary")}>
<ErrorBoundaryWrapper componentTitle={"Questionnaire Summary"}>
{benchmarkingData ? (
<QuestionnaireSummary benchmarkingData={benchmarkingData} setSelectedTab={setSelectedTab} />
) : (
<Loader />
)}
</ErrorBoundaryWrapper>
</Tab>
<Tab eventKey={"detailed-results"} title={t("questionnaire.tab_navigator.detailed-results")}>
<ErrorBoundaryWrapper componentTitle={"Questionnaire Detailed Results"}>
{detailedResults ? (
<QuestionnaireDetailedResults detailedResults={detailedResults} />
) : (
<Loader />
)}
</ErrorBoundaryWrapper>
</Tab>
</Tabs>
</div>
);
};
QuestionnaireTabNavigator.propTypes = {
"t": PropTypes.func
};
export default withTranslation()(QuestionnaireTabNavigator);
我建议您遵循 ESLint 文档中的迁移指南。您可以开始在现有配置文件上使用配置迁移器。