eslint-plugin-react
eslint-plugin-react copied to clipboard
[Bug]: `jsx-newline` sometimes breaks with comments
Is there an existing issue for this?
- [X] I have searched the existing issues and my issue is unique
- [X] My issue appears in the command-line and not only in the text editor
Description Overview
In the following minimal example, the react/jsx-newline rule fails to parse the file:
function Test() {
return (
<div>
<div />
{/* a comment */}
</div>
);
}
The following error is printed on running eslint:
Oops! Something went wrong! :(
ESLint: 8.40.0
TypeError: Cannot read properties of undefined (reading 'loc')
Occurred while linting /home/happens/tmp/jsx-newline-test/test.jsx:1
Rule: "react/jsx-newline"
at isMultilined (/home/happens/tmp/jsx-newline-test/node_modules/eslint-plugin-react/lib/rules/jsx-newline.js:23:15)
at /home/happens/tmp/jsx-newline-test/node_modules/eslint-plugin-react/lib/rules/jsx-newline.js:111:22
at Array.forEach (<anonymous>)
at /home/happens/tmp/jsx-newline-test/node_modules/eslint-plugin-react/lib/rules/jsx-newline.js:87:27
at Set.forEach (<anonymous>)
at Program:exit (/home/happens/tmp/jsx-newline-test/node_modules/eslint-plugin-react/lib/rules/jsx-newline.js:86:27)
at ruleErrorHandler (/home/happens/tmp/jsx-newline-test/node_modules/eslint/lib/linter/linter.js:1049:28)
at /home/happens/tmp/jsx-newline-test/node_modules/eslint/lib/linter/safe-emitter.js:45:58
at Array.forEach (<anonymous>)
at Object.emit (/home/happens/tmp/jsx-newline-test/node_modules/eslint/lib/linter/safe-emitter.js:45:38)
Moving the comment up a row or removing the sibling fixes the problem.
Expected Behavior
The rule should be able to correctly parse the file.
eslint-plugin-react version
v7.32.2
eslint version
v8.40.0
node version
v19.9.0
This cannot be reproduced with the default parser, can you show what parser you are using? For example running npx eslint --print-config src/index.js
I'm having the same issue. Package versions and my eslint config file is below. Thanks!
Packages
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.14.18",
"@mui/material": "^5.14.18",
"axios": "^1.6.2",
"fuse.js": "^7.0.0",
"immer": "^10.0.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.19.0",
"zustand": "^4.4.6"
},
"devDependencies": {
"@types/node": "^20.9.2",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"dotenv": "^16.3.1",
"eslint": "^8.54.0",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-json": "^3.1.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.4",
"typescript": "^5.2.2",
"vite": "^5.0.0"
}
ESLint Config
module.exports = {
root: true,
env: { browser: true, es2020: true },
settings: {
react: {
version: "detect"
}
},
extends: [
"eslint:recommended",
"plugin:import/typescript",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:json/recommended"
],
parserOptions: {
project: ["./tsconfig.json"]
},
parser: "@typescript-eslint/parser",
ignorePatterns: ["node_modules", "dist", "build", "dist", "package.json", "tsconfig.json", "tsconfig.node.json", ".eslintrc.cjs", "vite.config.ts", "**/components_old/**/*.tsx", "**/oldStores/**/*.tsx"],
plugins: ["import", "react", "react-hooks", "react-refresh", "@typescript-eslint", "json"],
rules: {
"json/*": ["error", "allowComments"],
"import/named": "off",
"import/newline-after-import": ["error", { count: 2 }],
"import/no-default-export": "error",
"import/no-unresolved": ["error", { ignore: ['\\.woff$'] }],
"import/no-self-import": "error",
"import/no-duplicates": "error",
"import/order": [
"error",
{
"newlines-between": "always",
alphabetize: { "order": "asc", "caseInsensitive": true },
groups: ["builtin", "external", "internal", ["parent", "sibling", "index", "object"], "type", "unknown"],
pathGroups: [
{
pattern: "**/*.+(css|sass|less|scss|pcss|styl|woff)",
patternOptions: { dot: true, nocomment: true },
group: "unknown",
position: "after"
},
{
pattern: "{.,..}/**/*.+(css|sass|less|scss|pcss|styl|woff)",
patternOptions: { dot: true, nocomment: true },
group: "unknown",
position: "after"
}
],
warnOnUnassignedImports: true
}
],
"eqeqeq": "error",
"no-debugger": "warn",
"eol-last": ["error", "always"],
"prefer-const": "error",
"no-mixed-spaces-and-tabs": "error",
"linebreak-style": "off",
"operator-linebreak": ["error", "before"],
"quotes": ["error", "double"],
"semi": ["error", "always"],
"indent": "off",
"no-empty": "off",
"no-empty-function": "off",
"no-unused-vars": "off",
"comma-dangle": "off",
"no-multi-spaces": ["error"],
"no-multiple-empty-lines": "error",
"no-multiple-empty-lines": ["error", { max: 2, maxBOF: 0 }],
"no-restricted-imports": ["error", {
"paths": [
{ name: "react", importNames: ["default"], message: "Please do not import React." },
]
}],
"@typescript-eslint/comma-dangle": "error",
"@typescript-eslint/indent": ["error", "tab"],
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-inferrable-types": "error",
"@typescript-eslint/no-empty-function": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-unsafe-declaration-merging": "error",
"@typescript-eslint/no-unsafe-enum-comparison": "error",
"@typescript-eslint/explicit-module-boundary-types": "error",
"@typescript-eslint/no-duplicate-enum-values": "error",
"@typescript-eslint/no-unused-vars": ["warn", { vars: "all", args: "after-used" }],
"@typescript-eslint/naming-convention": [
"error",
{ selector: "default", format: ["camelCase"] },
{ selector: "variable", modifiers: ["exported"], filter: { regex: "^use", match: true }, format: ["camelCase"] },
{ selector: "variable", modifiers: ["global"], format: ["PascalCase"] },
{ selector: "variable", format: ["camelCase"] },
{ selector: "function", modifiers: ["exported"], filter: { regex: "^use", match: true }, format: ["camelCase"] },
{ selector: "function", modifiers: ["global"], format: ["PascalCase"] },
{ selector: "function", format: ["camelCase"] },
{ selector: "parameter", format: ["camelCase"], format: null },
{ selector: "property", format: null },
{ selector: "property", types: ["array", "boolean", "function", "string"], format: ["camelCase", "snake_case", "PascalCase"] },
{ selector: "classProperty", types: ["number"], format: ["camelCase"], leadingUnderscore: "allow" },
{ selector: "property", types: ["string"], filter: { regex: "^Access-Control-Allow-Origin$", match: true }, format: null },
{ selector: "method", format: ["camelCase"] },
{ selector: "enumMember", format: ["PascalCase"] },
{ selector: "typeLike", format: ["PascalCase"] },
{ selector: "import", format: null }
],
"react/jsx-uses-react": "off",
"react/react-in-jsx-scope": "off",
"react/jsx-fragments": ["error", "element"],
"react/jsx-filename-extension": ["warn", { extensions: [".ts", ".tsx"] }],
"react/no-arrow-function-lifecycle": "error",
"react/no-danger": "error",
"react/no-deprecated": "error",
"react/no-did-mount-set-state": "error",
"react/no-did-update-set-state": "error",
"react/no-direct-mutation-state": "error",
"react/no-is-mounted": "error",
"react/no-unused-state": "warn",
"react/no-multi-comp": ["error", { "ignoreStateless": true }],
"react/no-unescaped-entities": "off",
"react/jsx-newline": ["error", { prevent: true, allowMultilines: true }],
"react/hook-use-state": ["off", { allowDestructuredState: true }],
"react/jsx-curly-newline": ["error", { multiline: "consistent", singleline: "consistent" }],
"react/self-closing-comp": ["error", { component: true, html: true }],
"react/jsx-wrap-multilines": ["error", {
"declaration": "parens-new-line",
"assignment": "parens-new-line",
"return": "parens-new-line",
"arrow": "parens-new-line",
"condition": "ignore",
"logical": "ignore",
"prop": "ignore"
}],
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "error",
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }]
},
overrides: [
{
files: ["./src/store/store.ts"],
rules: {
"no-restricted-imports": ["error", { "paths": [{ name: "react", importNames: ["default"], message: "Please do not import React." }] }]
}
}
]
};
Same issue here. It seems the problem is that
function isMultilined(node) {
return node.loc.start.line !== node.loc.end.line;
}
is called with node being undefined.
I see that this simple change
function isMultilined(node) {
return node?.loc.start.line !== node?.loc.end.line;
}
seems to fix the bug. Not sure if the output is correct/as expected, but at least the execution does not crash