jsx-eslint / eslint-plugin-react

React-specific linting rules for ESLint
MIT License
8.88k stars 2.76k forks source link

[Bug]: `jsx-newline` sometimes breaks with comments #3573

Open happenslol opened 1 year ago

happenslol commented 1 year ago

Is there an existing issue for this?

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

golopot commented 1 year ago

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

yigitlevent commented 8 months ago

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." }] }]
            }
        }
    ]
};
chripi commented 7 months ago

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