├── .npmrc
├── .gitignore
├── CHANGELOG.md
├── .prettierrc
├── SECURITY.md
├── tsconfig.json
├── src
├── index.ts
└── rules
│ ├── jsx-explicit-boolean.ts
│ └── jsx-explicit-boolean.test.tsx
├── .github
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── FUNDING.yml
└── workflows
│ ├── greetings.yml
│ ├── npm-publish.yml
│ └── codeql.yml
├── LICENSE
├── .eslintrc
├── README.md
├── package.json
├── CONTRIBUTING.md
└── CODE_OF_CONDUCT.md
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 | allow-same-version=true
3 | message=v%s
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | coverage/
3 | lib
4 | node_modules
5 | npm-debug.log
6 |
7 | npm-shrinkwrap.json
8 | package-lock.json
9 | yarn-error.log
10 | yarn.lock
11 |
12 | .npmignore
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "useTabs": false,
4 | "semi": true,
5 | "singleQuote": false,
6 | "quoteProps": "as-needed",
7 | "jsxSingleQuote": false,
8 | "trailingComma": "es5",
9 | "bracketSpacing": true,
10 | "arrowParens": "always",
11 | "printWidth": 80
12 | }
13 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | | Version | Supported |
6 | | ------- | ------------------ |
7 | | 1.1.x | :white_check_mark: |
8 |
9 | ## Reporting a Vulnerability
10 |
11 | Create an issue with the tag "vulnerability" and we will review it with priority.
12 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "commonjs",
5 | "strict": false,
6 | "esModuleInterop": true,
7 | "skipLibCheck": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "moduleResolution": "node",
10 | "resolveJsonModule": true,
11 | "lib": ["ES2020"],
12 | "outDir": "dist",
13 | "rootDir": "src"
14 | },
15 | "exclude": ["node_modules"]
16 | }
17 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | rules: {
3 | "jsx-explicit-boolean": require("./rules/jsx-explicit-boolean"),
4 | },
5 |
6 | configs: {
7 | recommended: {
8 | plugins: ["safe-jsx"],
9 | parserOptions: {
10 | ecmaFeatures: {
11 | jsx: true,
12 | },
13 | },
14 | rules: {
15 | "safe-jsx/jsx-explicit-boolean": "error",
16 | },
17 | },
18 |
19 | strict: {
20 | plugins: ["safe-jsx"],
21 | parserOptions: {
22 | ecmaFeatures: {
23 | jsx: true,
24 | },
25 | },
26 | rules: {
27 | "safe-jsx/jsx-explicit-boolean": "error",
28 | },
29 | },
30 | },
31 | };
32 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [gstj]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/.github/workflows/greetings.yml:
--------------------------------------------------------------------------------
1 | name: Greetings
2 |
3 | on: [pull_request_target, issues]
4 |
5 | jobs:
6 | greeting:
7 | runs-on: ubuntu-latest
8 | permissions:
9 | issues: write
10 | pull-requests: write
11 | steps:
12 | - uses: actions/first-interaction@v1
13 | with:
14 | repo-token: ${{ secrets.GITHUB_TOKEN }}
15 | issue-message: "👋 Hello! Thank you so much for creating your first issue in '@gstj/safe-jsx'! We appreciate your contribution and will review it as soon as possible. In the meantime, please make sure you have followed our guidelines and codes of conduct."
16 | pr-message: "🎉 Congratulations on making your first pull request in '@gstj/safe-jsx'! We appreciate your contribution and will review it as soon as possible. Thank you for helping us make this project better."
17 |
--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
3 |
4 | name: Node.js Package
5 |
6 | on:
7 | release:
8 | types: [created]
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v3
15 | - uses: actions/setup-node@v4
16 | with:
17 | node-version: 16
18 | - run: yarn build
19 | - run: yarn test
20 |
21 | publish-npm:
22 | needs: build
23 | runs-on: ubuntu-latest
24 | steps:
25 | - uses: actions/checkout@v3
26 | - uses: actions/setup-node@v4
27 | with:
28 | node-version: 16
29 | registry-url: https://registry.npmjs.org/
30 | - run: yarn build
31 | - run: yarn publish
32 | env:
33 | NODE_AUTH_TOKEN: ${{secrets.npm_token}}
34 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Gabriel Taveira
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": ["airbnb-base", "plugin:prettier/recommended"],
4 | "ignorePatterns": ["lib/", "reports/"],
5 | "parser": "@typescript-eslint/parser",
6 | "plugins": ["@typescript-eslint", "prettier"],
7 | "settings": {
8 | "import/resolver": {
9 | "node": {
10 | "extensions": [".js", ".jsx", ".ts", ".tsx"]
11 | }
12 | }
13 | },
14 | "rules": {
15 | "max-len": "off",
16 | "no-template-curly-in-string": "off",
17 | "global-require": "off",
18 | "import/extensions": [
19 | "error",
20 | "ignorePackages",
21 | {
22 | "js": "never",
23 | "jsx": "never",
24 | "ts": "never",
25 | "tsx": "never"
26 | }
27 | ]
28 | },
29 | "overrides": [
30 | {
31 | "files": ["src/rules/*"],
32 | "extends": ["plugin:eslint-plugin/rules-recommended"],
33 | "rules": {
34 | "eslint-plugin/require-meta-docs-description": [
35 | "error",
36 | { "pattern": "^(Enforce|Require|Disallow)" }
37 | ],
38 | "eslint-plugin/require-meta-type": "off"
39 | }
40 | },
41 | {
42 | "files": ["__tests__/src/rules/*.{js,ts,jsx,tsx}"],
43 | "extends": ["plugin:eslint-plugin/tests-recommended"]
44 | },
45 | {
46 | "files": ["**/*.test.{js,ts,jsx,tsx}"],
47 | "rules": {
48 | "import/no-extraneous-dependencies": "off"
49 | },
50 | "env": {
51 | "jest": true
52 | }
53 | }
54 | ]
55 | }
56 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🛡️ eslint-plugin-safe-jsx
2 |
3 | `eslint-plugin-safe-jsx` is an ESLint plugin that enforces explicit boolean conversion before using the && operator with JSX in React and React Native applications. This plugin improves the reliability of your code by helping prevent certain types of bugs that can break your app.
4 |
5 | ## 💡 Why Use eslint-plugin-safe-jsx?
6 |
7 | In JavaScript, certain "falsy" values such as `0`, `''`, and `null` can lead to unexpected behavior when used in a logical expression. This can be particularly problematic in React JSX code, where you might be expecting a boolean value.
8 |
9 | Consider the following example:
10 |
11 | ```jsx
12 | const myText = 0;
13 | myText && {myText};
14 | ```
15 |
16 | In this scenario, the code tries to render `0` outside the Text component, leading to a failure. The issue is even more critical when the variable value comes from a server or an external source. This ESLint rule helps prevent such scenarios from occurring.
17 |
18 | With `eslint-plugin-safe-jsx`, ESLint will alert you to these potential errors and can even auto-fix them, like so:
19 |
20 | ```jsx
21 | const myText = 0;
22 | Boolean(myText) && {myText};
23 | ```
24 |
25 | Now, `myText` is explicitly converted to a boolean before being used in the logical expression, preventing the `0` from being rendered.
26 |
27 | For more examples, check out our [test cases](./src/rules/jsx-explicit-boolean.test.tsx).
28 |
29 | ## 🚀 Installation
30 |
31 | You'll first need to install [ESLint](https://eslint.org/docs/latest/user-guide/getting-started):
32 |
33 | ```sh
34 | # npm
35 | npm install eslint --save-dev
36 |
37 | # yarn
38 | yarn add eslint --dev
39 | ```
40 |
41 | Next, install `eslint-plugin-safe-jsx`:
42 |
43 | ```sh
44 | # npm
45 | npm install eslint-plugin-safe-jsx --save-dev
46 |
47 | # yarn
48 | yarn add eslint-plugin-safe-jsx --dev
49 | ```
50 |
51 | **Note:** If you installed ESLint globally (using the `-g` flag in npm, or the `global` prefix in yarn) then you must also install `eslint-plugin-safe-jsx` globally.
52 |
53 | ## ⚙️ Usage
54 |
55 | Add `safe-jsx` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix:
56 |
57 | ```json
58 | {
59 | "plugins": ["safe-jsx"]
60 | }
61 | ```
62 |
63 | Then configure the rules you want to use under the rules section.
64 |
65 | ```json
66 | {
67 | "rules": {
68 | "safe-jsx/jsx-explicit-boolean": "error" // or "warn"
69 | }
70 | }
71 | ```
72 |
73 | ## 📚 Supported Rules
74 |
75 | - `jsx-explicit-boolean`: Enforces explicit boolean conversion before using the && operator with JSX.
76 |
77 | ## 🤝 Contributing
78 |
79 | We welcome your contributions! For major changes, please open an issue first to discuss what you would like to change. Don't forget to update tests as appropriate.
80 |
81 | ## 📃 License
82 |
83 | [MIT](./LICENSE)
84 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eslint-plugin-safe-jsx",
3 | "description": "An ESLint plugin that enforces explicit boolean conversion before using && operator with JSX in React and React Native.",
4 | "keywords": [
5 | "jsx",
6 | "react",
7 | "react-native",
8 | "eslint",
9 | "eslint-plugin",
10 | "boolean",
11 | "conversion",
12 | "javascript",
13 | "coding standards",
14 | "linting",
15 | "code quality"
16 | ],
17 | "version": "1.1.1",
18 | "main": "lib/index.js",
19 | "scripts": {
20 | "prepack": "npmignore --auto --commentLines=autogenerated && npm run build",
21 | "build": "rimraf lib && tsc --project . --outDir lib",
22 | "lint:fix": "npm run lint -- --fix",
23 | "lint": "eslint --ext=js,mjs,cjs,ts,tsx .",
24 | "prepublish": "not-in-publish || npm run prepublishOnly",
25 | "prepublishOnly": "safe-publish-latest && npm run lint && npm run jest",
26 | "jest": "jest --coverage src/**/*.test.tsx",
27 | "posttest": "aud --production",
28 | "version": "auto-changelog && git add CHANGELOG.md",
29 | "postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\""
30 | },
31 | "author": "Gabriel Taveira",
32 | "license": "MIT",
33 | "devDependencies": {
34 | "@typescript-eslint/eslint-plugin": "^6.0.0",
35 | "@typescript-eslint/experimental-utils": "^5.59.5",
36 | "@typescript-eslint/parser": "^6.0.0",
37 | "aud": "^2.0.2",
38 | "auto-changelog": "^2.4.0",
39 | "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8",
40 | "eslint-config-airbnb-base": "^15.0.0",
41 | "eslint-config-prettier": "^8.8.0",
42 | "eslint-doc-generator": "^1.4.1",
43 | "eslint-plugin-eslint-plugin": "^4.3.0",
44 | "eslint-plugin-import": "^2.26.0",
45 | "eslint-plugin-prettier": "^4.2.1",
46 | "jest": "^29.5.0",
47 | "npmignore": "^0.3.0",
48 | "prettier": "^2.8.8",
49 | "rimraf": "^5.0.0",
50 | "safe-publish-latest": "^2.0.0",
51 | "semver": "^6.3.0",
52 | "ts-jest": "^29.1.0",
53 | "tsc": "^2.0.4",
54 | "typescript": "^5.0.4"
55 | },
56 | "peerDependencies": {
57 | "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
58 | },
59 | "engines": {
60 | "node": ">=4.0"
61 | },
62 | "jest": {
63 | "preset": "ts-jest",
64 | "testEnvironment": "node",
65 | "coverageReporters": [
66 | "lcov",
67 | "json",
68 | "html"
69 | ],
70 | "coverageDirectory": "coverage",
71 | "roots": [
72 | "src"
73 | ]
74 | },
75 | "auto-changelog": {
76 | "output": "CHANGELOG.md",
77 | "template": "keepachangelog",
78 | "unreleased": false,
79 | "commitLimit": false,
80 | "backfillLimit": false,
81 | "hideCredit": true,
82 | "startingVersion": "6.6.2"
83 | },
84 | "publishConfig": {
85 | "ignore": [
86 | "!lib",
87 | ".github/workflows",
88 | "/src",
89 | "/reports",
90 | "CONTRIBUTING.md"
91 | ]
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ "main" ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ "main" ]
20 | schedule:
21 | - cron: '38 1 * * 2'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'javascript' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Use only 'java' to analyze code written in Java, Kotlin or both
38 | # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
39 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
40 |
41 | steps:
42 | - name: Checkout repository
43 | uses: actions/checkout@v3
44 |
45 | # Initializes the CodeQL tools for scanning.
46 | - name: Initialize CodeQL
47 | uses: github/codeql-action/init@v2
48 | with:
49 | languages: ${{ matrix.language }}
50 | # If you wish to specify custom queries, you can do so here or in a config file.
51 | # By default, queries listed here will override any specified in a config file.
52 | # Prefix the list here with "+" to use these queries and those in the config file.
53 |
54 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
55 | # queries: security-extended,security-and-quality
56 |
57 |
58 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
59 | # If this step fails, then you should remove it and run the build manually (see below)
60 | - name: Autobuild
61 | uses: github/codeql-action/autobuild@v2
62 |
63 | # ℹ️ Command-line programs to run using the OS shell.
64 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
65 |
66 | # If the Autobuild fails above, remove it and uncomment the following three lines.
67 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
68 |
69 | # - run: |
70 | # echo "Run, Build Application using script"
71 | # ./location_of_script_within_repo/buildscript.sh
72 |
73 | - name: Perform CodeQL Analysis
74 | uses: github/codeql-action/analyze@v2
75 | with:
76 | category: "/language:${{matrix.language}}"
77 |
--------------------------------------------------------------------------------
/src/rules/jsx-explicit-boolean.ts:
--------------------------------------------------------------------------------
1 | // Does not include sum or minus, for example, as they don't always evaluate to a boolean
2 | const binaryExpressionOperators = ["===", "!==", ">", "<", ">=", "<="];
3 |
4 | function findVariable(context, nodeName) {
5 | let scope = context.getScope();
6 |
7 | // Traverse the scope chain until we find the variable
8 | while (scope) {
9 | const variable = scope.variables.find((v) => v.name === nodeName);
10 | if (variable) return variable;
11 |
12 | scope = scope.upper;
13 | }
14 |
15 | return null;
16 | }
17 |
18 | function checkBooleanValidity(node, context) {
19 | const { type } = node;
20 |
21 | switch (type) {
22 | // Example: !a
23 | case "UnaryExpression":
24 | return node.operator === "!";
25 |
26 | // Example: true or false
27 | case "Literal":
28 | return typeof node.value === "boolean";
29 |
30 | // Example: a === b, a !== b, a > b, a < b, a >= b, a <= b
31 | case "BinaryExpression":
32 | return binaryExpressionOperators.includes(node.operator);
33 |
34 | // Example: Boolean(a) or new Boolean(a)
35 | case "CallExpression":
36 | case "NewExpression":
37 | return (
38 | node.callee.type === "Identifier" && node.callee.name === "Boolean"
39 | );
40 |
41 | // Example: a && b && c &&
, where all operands are boolean
42 | case "LogicalExpression": {
43 | const { operator, left, right } = node;
44 |
45 | if (operator !== "&&") return false;
46 |
47 | return (
48 | checkBooleanValidity(left, context) &&
49 | checkBooleanValidity(right, context)
50 | );
51 | }
52 |
53 | // Example: a ? b : c, where both b and c are boolean
54 | case "ConditionalExpression":
55 | return (
56 | checkBooleanValidity(node.test, context) &&
57 | checkBooleanValidity(node.consequent, context) &&
58 | checkBooleanValidity(node.alternate, context)
59 | );
60 |
61 | case "Identifier": {
62 | const variable = findVariable(context, node.name);
63 | if (!variable) return false;
64 |
65 | const variableDef = variable.defs.find((def) => def.type === "Variable");
66 | if (!variableDef) return false;
67 |
68 | return checkBooleanValidity(variableDef.node.init, context);
69 | }
70 |
71 | default:
72 | return false;
73 | }
74 | }
75 |
76 | module.exports = {
77 | meta: {
78 | type: "suggestion",
79 | fixable: "code",
80 | messages: {
81 | booleanConversion:
82 | "Please ensure a boolean conversion before using the && operator with JSX",
83 | },
84 | schema: [],
85 | },
86 | defaultOptions: [],
87 | create(context) {
88 | return {
89 | LogicalExpression(node) {
90 | // We're only interested in && operators
91 | if (node.operator !== "&&") return;
92 |
93 | // We're only interested in JSX elements on the right-hand side
94 | if (node.right.type !== "JSXElement") return;
95 |
96 | // Left-hand side part of the expression
97 | const { left } = node;
98 |
99 | // Check if it's a valid boolean usage, otherwise it must be fixed
100 | const isSafeBooleanUsage = checkBooleanValidity(left, context);
101 | if (isSafeBooleanUsage) return;
102 |
103 | // Report the error and fix it
104 | context.report({
105 | node,
106 | messageId: "booleanConversion",
107 | fix(fixer) {
108 | return fixer.replaceTextRange(
109 | [left.range[0], left.range[1]],
110 | `Boolean(${context.getSourceCode().getText(left)})`
111 | );
112 | },
113 | });
114 | },
115 | };
116 | },
117 | } as const;
118 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to @gstj/safe-jsx
2 |
3 | 🎉 First off, thanks for taking the time to contribute! 🎉
4 |
5 | The following is a set of guidelines for contributing to @gstj/safe-jsx. These are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request.
6 |
7 | ## Code of Conduct
8 |
9 | This project and everyone participating in it is governed by the [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [gstj@github.com](mailto:gstj@github.com).
10 |
11 | ## How Can I Contribute?
12 |
13 | ### Reporting Bugs
14 |
15 | - **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/gstj/safe-jsx/issues).
16 |
17 | - If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/gstj/safe-jsx/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring.
18 |
19 | ### Suggesting Enhancements
20 |
21 | If you find yourself wishing for a feature that doesn't exist in @gstj/safe-jsx, you are probably not alone. There are bound to be others out there with similar needs. Many of the features that @gstj/safe-jsx has today have been added because our users saw the need.
22 |
23 | Open an issue on our issues list on GitHub which describes the feature you would like to see, why you need it, and how it should work.
24 |
25 | ### Pull Requests
26 |
27 | - Fork the repo and create your branch from `master`.
28 | - If you've added code that should be tested, add tests.
29 | - Ensure the test suite passes.
30 | - Make sure your code lints.
31 |
32 | ## Commit Message Guidelines
33 |
34 | We have very precise rules over how our Git commit messages must be formatted. This format leads to **easier to read commit history**.
35 |
36 | - Wrap message lines to about 72 characters or so.
37 | - Each commit message consists of a **header**, a **body**, and a **footer**.
38 | - The header is mandatory and must conform to the [Commit Message Header](#commit-message-header) format.
39 | - The body is mandatory for all commits except for those of type "docs". When the body is required it must be at least 20 characters long.
40 | - The footer is optional.
41 |
42 | `(): `
43 |
44 | ### Commit Message Header
45 |
46 | The commit message header is a single line that summarizing the change. It should be no longer than 50 characters to make sure it's shown in GitHub.
47 |
48 | `(): `
49 |
50 | #### ``
51 |
52 | This describes the kind of change that this commit is providing.
53 |
54 | - **feat** (new feature for the user, not a new feature for build script)
55 | - **fix** (bug fix for the user, not a fix to a build script)
56 | - **docs** (changes to the documentation)
57 | - **style** (formatting, missing semi colons, etc; no production code change)
58 | - **refactor** (refactoring production code, eg. renaming a variable)
59 | - **test** (adding missing tests, refactoring tests; no production code change)
60 | - **chore** (updating grunt tasks etc; no production code change)
61 |
62 | #### ``
63 |
64 | The scope could be anything specifying the place of the commit change.
65 |
66 | For example `$location`, `$browser`, `$compile`,``, `*` etc.
67 |
68 | The `` can be empty (e.g. `revert: fix: 'alert()'`) when the change is global or does not relate to a single component.
69 |
70 | #### ``
71 |
72 | The `` is very important as it is the title of the commit message. As the name suggests, it should be a short summary about the improvements made in the commit.
73 |
74 | - use the imperative, present tense: "change" not "changed" nor "changes"
75 | - don't capitalize the first letter
76 | - no dot (.) at the end
77 |
78 | ### Commit Message Body
79 |
80 | Just as in the ``, use the imperative, present tense: "change" not "changed" nor "changes". The body should include the motivation for the change and contrast this with previous behavior.
81 |
82 | ### Commit Message Footer
83 |
84 | The footer is the place to reference any issues or pull requests related to this commit.
85 |
86 | For example:
87 |
88 | `Closes #204`
89 |
90 | ## Pull Request Process
91 |
92 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a build.
93 | 2. Update the README.md with details of changes to the interface, this includes new environment variables, exposed ports, useful file locations and container parameters.
94 | 3. Increase the version numbers in any examples files and the README.md to the new version that this Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
95 | 4. You may merge the Pull Request in once you have the sign-off of other developer, or if you do not have permission to do that, you may request the reviewer to merge it for you.
96 |
97 | Thank you for considering contributing to @gstj/safe-jsx!
98 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | gabrielstaveira@gmail.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/src/rules/jsx-explicit-boolean.test.tsx:
--------------------------------------------------------------------------------
1 | import { TSESLint } from "@typescript-eslint/experimental-utils";
2 |
3 | const parser = require.resolve("@typescript-eslint/parser");
4 |
5 | const parserOptions = {
6 | parserOptions: {
7 | project: "../../tsconfig.json",
8 | sourceType: "module",
9 | },
10 | ecmaFeatures: {
11 | experimentalObjectRestSpread: true,
12 | jsx: true,
13 | },
14 | };
15 |
16 | const ruleTester = new TSESLint.RuleTester({
17 | parser,
18 | parserOptions,
19 | });
20 |
21 | ruleTester.run("jsx-explicit-boolean", require("./jsx-explicit-boolean"), {
22 | valid: [
23 | // Nested expressions
24 | { code: "const a = true; const b = true; (a && b) && ;" },
25 |
26 | // Conditional expressions
27 | { code: "const a = true; const b = false; a ? b : false && ;" },
28 |
29 | // Scope of variables
30 | { code: "const a = true; const Component = () => a && ;" },
31 |
32 | // Rest
33 | { code: "const a = true; a && ;" },
34 | { code: "const a = true; const b = a; b && ;" },
35 | { code: "const a = false; a && ;" },
36 | { code: "const a = true; Boolean(a) && ;" },
37 | { code: "const a = false; Boolean(a) && ;" },
38 | { code: "const a = 1; Boolean(a) && ;" },
39 | {
40 | code: "const Component = ({ a }) => {Boolean(a) && {a}};",
41 | },
42 | {
43 | code: "const Component = ({ a }) => {new Boolean(a) && {a}};",
44 | },
45 | {
46 | code: "const Component = ({ a }) => {!!a && {a}};",
47 | },
48 | {
49 | code: "const Component = ({ a }) => {!a && {a}};",
50 | },
51 | {
52 | code: "const index = 1; {index === 0 && };",
53 | },
54 | {
55 | code: "const a = 1; b = '0'; {!!a && !!b && };",
56 | },
57 | {
58 | code: "const a = 1; b = '0'; c = '0'; {!!a && !!b && !!c && };",
59 | },
60 | {
61 | code: "{!!step.subtitle && index === activeStep - 1 && }",
62 | },
63 | ],
64 | invalid: [
65 | // Nested conditional expressions
66 | {
67 | code: "const a = true; const b = '0'; (a ? b : false) && ;",
68 | errors: [{ messageId: "booleanConversion" }],
69 | output:
70 | "const a = true; const b = '0'; (Boolean(a ? b : false)) && ;",
71 | },
72 |
73 | // Conditional expressions
74 | {
75 | code: "const a = true; const b = '0'; (a ? b : false) && ;",
76 | errors: [{ messageId: "booleanConversion" }],
77 | output:
78 | "const a = true; const b = '0'; (Boolean(a ? b : false)) && ;",
79 | },
80 |
81 | // Scope of variables
82 | {
83 | code: "const a = '0'; const Component = () => a && ;",
84 | errors: [{ messageId: "booleanConversion" }],
85 | output: "const a = '0'; const Component = () => Boolean(a) && ;",
86 | },
87 |
88 | // Rest
89 | {
90 | code: "const a = 0; a && ;",
91 | errors: [{ messageId: "booleanConversion" }],
92 | output: "const a = 0; Boolean(a) && ;",
93 | },
94 | {
95 | code: "const a = null; a && ;",
96 | errors: [{ messageId: "booleanConversion" }],
97 | output: "const a = null; Boolean(a) && ;",
98 | },
99 | {
100 | code: 'const a = ""; a && ;',
101 | errors: [{ messageId: "booleanConversion" }],
102 | output: 'const a = ""; Boolean(a) && ;',
103 | },
104 | {
105 | code: "const a = undefined; a && ;",
106 | errors: [{ messageId: "booleanConversion" }],
107 | output: "const a = undefined; Boolean(a) && ;",
108 | },
109 | {
110 | code: 'const a = "0"; a && ;',
111 | errors: [{ messageId: "booleanConversion" }],
112 | output: 'const a = "0"; Boolean(a) && ;',
113 | },
114 | {
115 | code: "const a = []; a && ;",
116 | errors: [{ messageId: "booleanConversion" }],
117 | output: "const a = []; Boolean(a) && ;",
118 | },
119 | {
120 | code: "const a = {}; a && ;",
121 | errors: [{ messageId: "booleanConversion" }],
122 | output: "const a = {}; Boolean(a) && ;",
123 | },
124 | {
125 | code: "const a = NaN; a && ;",
126 | errors: [{ messageId: "booleanConversion" }],
127 | output: "const a = NaN; Boolean(a) && ;",
128 | },
129 | {
130 | code: "const Component = ({ a }) => {a && {a}};",
131 | errors: [{ messageId: "booleanConversion" }],
132 | output:
133 | "const Component = ({ a }) => {Boolean(a) && {a}};",
134 | },
135 | {
136 | code: "const a = 1; b = '0'; {!!a && b && };",
137 | errors: [{ messageId: "booleanConversion" }],
138 | output:
139 | "const a = 1; b = '0'; {Boolean(!!a && b) && };",
140 | },
141 | {
142 | code: "const a = 1; const b = '0'; const c = 0; {a && !!b && !!c && };",
143 | errors: [{ messageId: "booleanConversion" }],
144 | output:
145 | "const a = 1; const b = '0'; const c = 0; {Boolean(a && !!b && !!c) && };",
146 | },
147 | {
148 | code: "const a = undefined; a && ;",
149 | errors: [{ messageId: "booleanConversion" }],
150 | output: "const a = undefined; Boolean(a) && ;",
151 | },
152 | {
153 | code: "const a = 0 + 0; a && ;",
154 | errors: [{ messageId: "booleanConversion" }],
155 | output: "const a = 0 + 0; Boolean(a) && ;",
156 | },
157 | ],
158 | });
159 |
--------------------------------------------------------------------------------