├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .eslint-doc-generatorrc.js ├── .github ├── dependabot.yml └── workflows │ ├── nodejs.yml │ └── publish.yml ├── .gitignore ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bin └── eslint-ignore-errors.js ├── docs └── rules │ ├── a11y-aria-label-is-well-formatted.md │ ├── a11y-no-generic-link-text.md │ ├── a11y-no-title-attribute.md │ ├── a11y-no-visually-hidden-interactive-element.md │ ├── a11y-role-supports-aria-props.md │ ├── a11y-svg-has-accessible-name.md │ ├── array-foreach.md │ ├── async-currenttarget.md │ ├── async-preventdefault.md │ ├── authenticity-token.md │ ├── filenames-match-regex.md │ ├── get-attribute.md │ ├── js-class-name.md │ ├── no-blur.md │ ├── no-d-none.md │ ├── no-dataset.md │ ├── no-dynamic-script-tag.md │ ├── no-implicit-buggy-globals.md │ ├── no-inner-html.md │ ├── no-innerText.md │ ├── no-then.md │ ├── no-useless-passive.md │ ├── prefer-observers.md │ ├── require-passive-events.md │ └── unescaped-html-literal.md ├── eslint.config.js ├── lib ├── configs │ ├── browser.js │ ├── flat │ │ ├── browser.js │ │ ├── internal.js │ │ ├── react.js │ │ ├── recommended.js │ │ └── typescript.js │ ├── internal.js │ ├── react.js │ ├── recommended.js │ └── typescript.js ├── formatters │ └── stylish-fixes.js ├── index.js ├── plugin.js ├── rules │ ├── a11y-aria-label-is-well-formatted.js │ ├── a11y-no-generic-link-text.js │ ├── a11y-no-title-attribute.js │ ├── a11y-no-visually-hidden-interactive-element.js │ ├── a11y-role-supports-aria-props.js │ ├── a11y-svg-has-accessible-name.js │ ├── array-foreach.js │ ├── async-currenttarget.js │ ├── async-preventdefault.js │ ├── authenticity-token.js │ ├── filenames-match-regex.js │ ├── get-attribute.js │ ├── js-class-name.js │ ├── no-blur.js │ ├── no-d-none.js │ ├── no-dataset.js │ ├── no-dynamic-script-tag.js │ ├── no-implicit-buggy-globals.js │ ├── no-inner-html.js │ ├── no-innerText.js │ ├── no-then.js │ ├── no-useless-passive.js │ ├── prefer-observers.js │ ├── require-passive-events.js │ └── unescaped-html-literal.js ├── url.js └── utils │ ├── commonjs-json-wrappers.cjs │ ├── get-element-type.js │ ├── get-exported-name.js │ ├── get-role.js │ ├── is-ignored-filename.js │ ├── object-map.js │ └── parse-filename.js ├── package-lock.json ├── package.json ├── test-examples ├── flat │ ├── eslint.config.mjs │ ├── package-lock.json │ ├── package.json │ └── src │ │ ├── forEachTest.js │ │ ├── getAttribute.js │ │ ├── jsx.tsx │ │ ├── noBlur.js │ │ └── thisTypescriptTest.ts └── legacy │ ├── .eslintrc.cjs │ ├── package-lock.json │ ├── package.json │ └── src │ ├── forEachTest.js │ ├── getAttribute.js │ ├── jsx.tsx │ ├── noBlur.js │ └── thisTypescriptTest.ts └── tests ├── a11y-aria-label-is-well-formatted.js ├── a11y-no-generic-link-text.js ├── a11y-no-title-attribute.js ├── a11y-no-visually-hidden-interactive-element.js ├── a11y-role-supports-aria-props.js ├── a11y-svg-has-accessible-name.js ├── array-foreach.js ├── async-currenttarget.js ├── async-preventdefault.js ├── authenticity-token.js ├── check-rules.js ├── get-attribute.js ├── js-class-name.js ├── no-blur.js ├── no-d-none.js ├── no-dataset.js ├── no-dynamic-script-tag.js ├── no-implicit-buggy-globals.js ├── no-inner-html.js ├── no-innerText.js ├── no-then.js ├── no-useless-passive.js ├── prefer-observers.js ├── require-passive-events.js ├── unescaped-html-literal.js └── utils ├── get-element-type.mjs ├── get-role.mjs ├── mocks.js └── object-map.mjs /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.222.0/containers/javascript-node/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 16, 14, 12, 16-bullseye, 14-bullseye, 12-bullseye, 16-buster, 14-buster, 12-buster 4 | ARG VARIANT="16" 5 | FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:${VARIANT} 6 | 7 | # [Optional] Uncomment this section to install additional OS packages. 8 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 9 | # && apt-get -y install --no-install-recommends 10 | 11 | # [Optional] Uncomment if you want to install an additional version of node using nvm 12 | # ARG EXTRA_NODE_VERSION=10 13 | # RUN su node -c "source/usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" 14 | 15 | # [Optional] Uncomment if you want to install more global node modules 16 | # RUN su node -c "npm install -g " 17 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.222.0/containers/javascript-node 3 | { 4 | "name": "Node.js", 5 | "build": { 6 | "dockerfile": "Dockerfile", 7 | // Update 'VARIANT' to pick a Node version: 16, 14, 12. 8 | // Append -bullseye or -buster to pin to an OS version. 9 | // Use -bullseye variants on local arm64/Apple Silicon. 10 | "args": {"VARIANT": "22"} 11 | }, 12 | 13 | // Set *default* container specific settings.json values on container create. 14 | "settings": {}, 15 | 16 | // Add the IDs of extensions you want installed when the container is created. 17 | "extensions": ["dbaeumer.vscode-eslint"], 18 | 19 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 20 | // "forwardPorts": [], 21 | 22 | // Use 'postCreateCommand' to run commands after the container is created. 23 | // "postCreateCommand": "yarn install", 24 | 25 | // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 26 | "remoteUser": "node", 27 | "features": { 28 | "git": "latest" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.eslint-doc-generatorrc.js: -------------------------------------------------------------------------------- 1 | /** @type {import('eslint-doc-generator').GenerateOptions} */ 2 | export default { 3 | configEmoji: [ 4 | ['browser', '🔍'], 5 | ['internal', '🔐'], 6 | ['react', '⚛️'], 7 | ], 8 | ruleDocSectionInclude: ['Rule Details', 'Version'], 9 | } 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: '/' 5 | schedule: 6 | interval: weekly 7 | open-pull-requests-limit: 99 8 | groups: 9 | all-dependencies: 10 | patterns: 11 | - "*" 12 | - package-ecosystem: github-actions 13 | directory: '/' 14 | schedule: 15 | interval: weekly 16 | open-pull-requests-limit: 99 17 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - 'dependabot/**' 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [20, 22] 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | cache: npm 24 | - name: Install 25 | run: npm ci 26 | - name: Test 27 | run: npm test 28 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | permissions: 8 | contents: read 9 | id-token: write 10 | 11 | jobs: 12 | publish-npm: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-node@v4 17 | with: 18 | node-version: 22 19 | registry-url: https://registry.npmjs.org/ 20 | cache: npm 21 | - run: npm ci 22 | - run: npm test 23 | - run: npm version ${TAG_NAME} --git-tag-version=false 24 | env: 25 | TAG_NAME: ${{ github.event.release.tag_name }} 26 | - run: npm whoami; npm --ignore-scripts publish --provenance 27 | env: 28 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | yarn.lock 4 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @github/web-systems-reviewers 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Publishing this package 2 | 3 | Publishing this package to npm is done via a [GitHub action](https://github.com/github/eslint-plugin-github/blob/main/.github/workflows/publish.yml) which triggers when a new GitHub Release is created. 4 | 5 | To publish to npm, create a release, give it an appropriate [Semantic Versioning](https://semver.org/) tag and fill out the release description. Once you publish the release, the GitHub action will be triggered and it will publish to npm. 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 GitHub, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eslint-plugin-github 2 | 3 | ## Installation 4 | 5 | ```sh 6 | npm install --save-dev eslint eslint-plugin-github 7 | ``` 8 | 9 | ## Setup 10 | 11 | ### Legacy Configuration (`.eslintrc`) 12 | 13 | Add `github` to your list of plugins in your ESLint config. 14 | 15 | JSON ESLint config example: 16 | 17 | ```json 18 | { 19 | "plugins": ["github"] 20 | } 21 | ``` 22 | 23 | Extend the configs you wish to use. 24 | 25 | JSON ESLint config example: 26 | 27 | ```json 28 | { 29 | "extends": ["plugin:github/recommended"] 30 | } 31 | ``` 32 | 33 | ### Flat Configuration (`eslint-config.js`) 34 | 35 | Import the `eslint-plugin-github`, and extend any of the configurations using `getFlatConfigs()` as needed like so: 36 | 37 | ```js 38 | import github from 'eslint-plugin-github' 39 | 40 | export default [ 41 | github.getFlatConfigs().browser, 42 | github.getFlatConfigs().recommended, 43 | github.getFlatConfigs().react, 44 | ...github.getFlatConfigs().typescript, 45 | { 46 | files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], 47 | ignores: ['eslint.config.mjs'], 48 | rules: { 49 | 'github/array-foreach': 'error', 50 | 'github/async-preventdefault': 'warn', 51 | 'github/no-then': 'error', 52 | 'github/no-blur': 'error', 53 | }, 54 | }, 55 | ] 56 | ``` 57 | 58 | > [!NOTE] 59 | > If you configured the `filenames/match-regex` rule, please note we have adapted the match regex rule into `eslint-plugin-github` as the original `eslint-filenames-plugin` is no longer maintained and needed a flat config support update. 60 | > 61 | > Please update the name to `github/filenames-match-regex`, and note, the default rule is kebab case or camelCase with one hump. For custom configuration, such as matching for camelCase regex, here's an example: 62 | > 63 | > `'github/filenames-match-regex': ['error', '^([a-z0-9]+)([A-Z][a-z0-9]+)*$'],` 64 | 65 | The available configs are: 66 | 67 | - `internal` 68 | - Rules useful for github applications. 69 | - `browser` 70 | - Useful rules when shipping your app to the browser. 71 | - `react` 72 | - Recommended rules for React applications. 73 | - `recommended` 74 | - Recommended rules for every application. 75 | - `typescript` 76 | - Useful rules when writing TypeScript. 77 | 78 | ### Component mapping (Experimental) 79 | 80 | _Note: This is experimental and subject to change._ 81 | 82 | The `react` config includes rules which target specific HTML elements. You may provide a mapping of custom components to an HTML element in your `eslintrc` configuration to increase linter coverage. 83 | 84 | By default, these eslint rules will check the "as" prop for underlying element changes. If your repo uses a different prop name for polymorphic components provide the prop name in your `eslintrc` configuration under `polymorphicPropName`. 85 | 86 | ```json 87 | { 88 | "settings": { 89 | "github": { 90 | "polymorphicPropName": "asChild", 91 | "components": { 92 | "Box": "p", 93 | "Link": "a" 94 | } 95 | } 96 | } 97 | } 98 | ``` 99 | 100 | This config will be interpreted in the following way: 101 | 102 | - All `` elements will be treated as a `p` element type. 103 | - `` without a defined `as` prop will be treated as a `a`. 104 | - `` will be treated as a `button` element type. 105 | 106 | ### Rules 107 | 108 | 109 | 110 | 💼 Configurations enabled in.\ 111 | 🔍 Set in the `browser` configuration.\ 112 | 🔐 Set in the `internal` configuration.\ 113 | ⚛️ Set in the `react` configuration.\ 114 | ✅ Set in the `recommended` configuration.\ 115 | 🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\ 116 | ❌ Deprecated. 117 | 118 | | Name                                        | Description | 💼 | 🔧 | ❌ | 119 | | :------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | :- | :- | :- | 120 | | [a11y-aria-label-is-well-formatted](docs/rules/a11y-aria-label-is-well-formatted.md) | enforce [aria-label] text to be formatted as you would visual text. | ⚛️ | | | 121 | | [a11y-no-generic-link-text](docs/rules/a11y-no-generic-link-text.md) | disallow generic link text | | | ❌ | 122 | | [a11y-no-title-attribute](docs/rules/a11y-no-title-attribute.md) | disallow using the title attribute | ⚛️ | | | 123 | | [a11y-no-visually-hidden-interactive-element](docs/rules/a11y-no-visually-hidden-interactive-element.md) | enforce that interactive elements are not visually hidden | ⚛️ | | | 124 | | [a11y-role-supports-aria-props](docs/rules/a11y-role-supports-aria-props.md) | enforce that elements with explicit or implicit roles defined contain only `aria-*` properties supported by that `role`. | ⚛️ | | | 125 | | [a11y-svg-has-accessible-name](docs/rules/a11y-svg-has-accessible-name.md) | require SVGs to have an accessible name | ⚛️ | | | 126 | | [array-foreach](docs/rules/array-foreach.md) | enforce `for..of` loops over `Array.forEach` | ✅ | | | 127 | | [async-currenttarget](docs/rules/async-currenttarget.md) | disallow `event.currentTarget` calls inside of async functions | 🔍 | | | 128 | | [async-preventdefault](docs/rules/async-preventdefault.md) | disallow `event.preventDefault` calls inside of async functions | 🔍 | | | 129 | | [authenticity-token](docs/rules/authenticity-token.md) | disallow usage of CSRF tokens in JavaScript | 🔐 | | | 130 | | [filenames-match-regex](docs/rules/filenames-match-regex.md) | require filenames to match a regex naming convention | | | | 131 | | [get-attribute](docs/rules/get-attribute.md) | disallow wrong usage of attribute names | 🔍 | 🔧 | | 132 | | [js-class-name](docs/rules/js-class-name.md) | enforce a naming convention for js- prefixed classes | 🔐 | | | 133 | | [no-blur](docs/rules/no-blur.md) | disallow usage of `Element.prototype.blur()` | 🔍 | | | 134 | | [no-d-none](docs/rules/no-d-none.md) | disallow usage the `d-none` CSS class | 🔐 | | | 135 | | [no-dataset](docs/rules/no-dataset.md) | enforce usage of `Element.prototype.getAttribute` instead of `Element.prototype.datalist` | 🔍 | | | 136 | | [no-dynamic-script-tag](docs/rules/no-dynamic-script-tag.md) | disallow creating dynamic script tags | ✅ | | | 137 | | [no-implicit-buggy-globals](docs/rules/no-implicit-buggy-globals.md) | disallow implicit global variables | ✅ | | | 138 | | [no-inner-html](docs/rules/no-inner-html.md) | disallow `Element.prototype.innerHTML` in favor of `Element.prototype.textContent` | 🔍 | | | 139 | | [no-innerText](docs/rules/no-innerText.md) | disallow `Element.prototype.innerText` in favor of `Element.prototype.textContent` | 🔍 | 🔧 | | 140 | | [no-then](docs/rules/no-then.md) | enforce using `async/await` syntax over Promises | ✅ | | | 141 | | [no-useless-passive](docs/rules/no-useless-passive.md) | disallow marking a event handler as passive when it has no effect | 🔍 | 🔧 | | 142 | | [prefer-observers](docs/rules/prefer-observers.md) | disallow poorly performing event listeners | 🔍 | | | 143 | | [require-passive-events](docs/rules/require-passive-events.md) | enforce marking high frequency event handlers as passive | 🔍 | | | 144 | | [unescaped-html-literal](docs/rules/unescaped-html-literal.md) | disallow unescaped HTML literals | 🔍 | | | 145 | 146 | 147 | -------------------------------------------------------------------------------- /bin/eslint-ignore-errors.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Disables eslint rules in a JavaScript file with next-line comments. This is 3 | // useful when introducing a new rule that causes many failures. The comments 4 | // can be fixed and removed at while updating the file later. 5 | // 6 | // Usage: 7 | // 8 | // eslint-ignore-errors app/assets/javascripts/something.js 9 | 10 | const fs = require('fs') 11 | const execFile = require('child_process').execFile 12 | 13 | execFile('eslint', ['--format', 'json', process.argv[2]], (error, stdout) => { 14 | for (const result of JSON.parse(stdout)) { 15 | const filename = result.filePath 16 | const jsLines = fs.readFileSync(filename, 'utf8').split('\n') 17 | const offensesByLine = {} 18 | let addedLines = 0 19 | 20 | // Produces {47: ['github/no-d-none', 'github/no-blur'], 83: ['github/no-blur']} 21 | for (const message of result.messages) { 22 | if (offensesByLine[message.line]) { 23 | offensesByLine[message.line].push(message.ruleId) 24 | } else { 25 | offensesByLine[message.line] = [message.ruleId] 26 | } 27 | } 28 | 29 | for (const line of Object.keys(offensesByLine)) { 30 | const lineIndex = line - 1 + addedLines 31 | const previousLine = jsLines[lineIndex - 1] 32 | const ruleIds = offensesByLine[line].join(', ') 33 | if (isDisableComment(previousLine)) { 34 | jsLines[lineIndex - 1] = previousLine.replace(/\s?\*\/$/, `, ${ruleIds} */`) 35 | } else { 36 | const leftPad = ' '.repeat(jsLines[lineIndex].match(/^\s*/g)[0].length) 37 | jsLines.splice(lineIndex, 0, `${leftPad}/* eslint-disable-next-line ${ruleIds} */`) 38 | } 39 | addedLines += 1 40 | } 41 | 42 | if (result.messages.length !== 0) { 43 | fs.writeFileSync(filename, jsLines.join('\n'), 'utf8') 44 | } 45 | } 46 | }) 47 | 48 | function isDisableComment(line) { 49 | return line.match(/\/\* eslint-disable-next-line .+\*\//) 50 | } 51 | -------------------------------------------------------------------------------- /docs/rules/a11y-aria-label-is-well-formatted.md: -------------------------------------------------------------------------------- 1 | # Enforce [aria-label] text to be formatted as you would visual text (`github/a11y-aria-label-is-well-formatted`) 2 | 3 | 💼 This rule is enabled in the ⚛️ `react` config. 4 | 5 | 6 | 7 | ## Rule Details 8 | 9 | `[aria-label]` content should be formatted in the same way you would visual text. Please use sentence case. 10 | 11 | Do not connect the words like you would an ID. An `aria-label` is not an ID, and should be formatted as human-friendly text. 12 | 13 | ## Resources 14 | 15 | - [Using aria-label](https://www.w3.org/WAI/tutorials/forms/labels/#using-aria-label) 16 | 17 | ## Examples 18 | 19 | ### **Incorrect** code for this rule 👎 20 | 21 | ```html 22 | 23 | ``` 24 | 25 | ```html 26 | 27 | ``` 28 | 29 | ### **Correct** code for this rule 👍 30 | 31 | ```html 32 | 33 | ``` 34 | 35 | ```html 36 | 37 | ``` 38 | 39 | ## Version 40 | -------------------------------------------------------------------------------- /docs/rules/a11y-no-generic-link-text.md: -------------------------------------------------------------------------------- 1 | # Disallow generic link text (`github/a11y-no-generic-link-text`) 2 | 3 | ❌ This rule is deprecated. It was replaced by `jsx-a11y/anchor-ambiguous-text`. 4 | 5 | 6 | 7 | ## Rule Details 8 | 9 | Avoid setting generic link text like, "Click here", "Read more", and "Learn more" which do not make sense when read out of context. 10 | 11 | Screen reader users often tab through links on a page to quickly find content without needing to listen to the full page. When link text is too generic, it becomes difficult to quickly identify the destination of the link. While it is possible to provide a more specific link text by setting the `aria-label`, this results in divergence between the label and the text and is not an ideal, future-proof solution. 12 | 13 | Additionally, generic link text can also problematic for heavy zoom users where the link context is out of view. 14 | 15 | Ensure that your link text is descriptive and the purpose of the link is clear even when read out of context of surrounding text. 16 | Learn more about how to write descriptive link text at [Access Guide: Write descriptive link text](https://www.accessguide.io/guide/descriptive-link-text) 17 | 18 | ### Use of ARIA attributes 19 | 20 | If you _must_ use ARIA to replace the visible link text, include the visible text at the beginning. 21 | 22 | For example, on a pricing plans page, the following are good: 23 | 24 | - Visible text: `Learn more` 25 | - Accessible label: `Learn more about GitHub pricing plans` 26 | 27 | Accessible ✅ 28 | 29 | ```html 30 | Learn more 31 | ``` 32 | 33 | Inaccessible 🚫 34 | 35 | ```html 36 | Learn more 37 | ``` 38 | 39 | Including the visible text in the ARIA label satisfies [SC 2.5.3: Label in Name](https://www.w3.org/WAI/WCAG21/Understanding/label-in-name.html). 40 | 41 | #### False negatives 42 | 43 | Caution: because of the restrictions of static code analysis, we may not catch all violations. 44 | 45 | Please perform browser tests and spot checks: 46 | 47 | - when `aria-label` is set dynamically 48 | - when using `aria-labelledby` 49 | 50 | ## Resources 51 | 52 | - [Primer: Links](https://primer.style/design/accessibility/links) 53 | - [Understanding Success Criterion 2.4.4: Link Purpose (In Context)](https://www.w3.org/WAI/WCAG21/Understanding/link-purpose-in-context.html) 54 | - [WebAim: Links and Hypertext](https://webaim.org/techniques/hypertext/) 55 | - [Deque: Use link text that make sense when read out of context](https://dequeuniversity.com/tips/link-text) 56 | 57 | ## Examples 58 | 59 | ### **Incorrect** code for this rule 👎 60 | 61 | ```jsx 62 | Learn more 63 | ``` 64 | 65 | ```jsx 66 | Read more 67 | ``` 68 | 69 | ```jsx 70 | 71 | Read more 72 | 73 | ``` 74 | 75 | ```jsx 76 | 77 | Read more 78 | 79 | ``` 80 | 81 | ### **Correct** code for this rule 👍 82 | 83 | ```jsx 84 | Learn more about GitHub 85 | ``` 86 | 87 | ```jsx 88 | Create a new repository 89 | ``` 90 | 91 | ## Version 92 | -------------------------------------------------------------------------------- /docs/rules/a11y-no-title-attribute.md: -------------------------------------------------------------------------------- 1 | # Disallow using the title attribute (`github/a11y-no-title-attribute`) 2 | 3 | 💼 This rule is enabled in the ⚛️ `react` config. 4 | 5 | 6 | 7 | The title attribute is strongly discouraged. The only exception is on an ` 43 | ``` 44 | 45 | ## Version 46 | -------------------------------------------------------------------------------- /docs/rules/a11y-no-visually-hidden-interactive-element.md: -------------------------------------------------------------------------------- 1 | # Enforce that interactive elements are not visually hidden (`github/a11y-no-visually-hidden-interactive-element`) 2 | 3 | 💼 This rule is enabled in the ⚛️ `react` config. 4 | 5 | 6 | 7 | ## Rule Details 8 | 9 | This rule guards against visually hiding interactive elements. If a sighted keyboard user navigates to an interactive element that is visually hidden they might become confused and assume that keyboard focus has been lost. 10 | 11 | Note: we are not guarding against visually hidden `input` elements at this time. Some visually hidden inputs might cause a false positive (e.g. some file inputs). 12 | 13 | ### Why do we visually hide content? 14 | 15 | Visually hiding content can be useful when you want to provide information specifically to screen reader users or other assistive technology users while keeping content hidden from sighted users. 16 | 17 | Applying the following css will visually hide content while still making it accessible to screen reader users. 18 | 19 | ```css 20 | clip-path: inset(50%); 21 | height: 1px; 22 | overflow: hidden; 23 | position: absolute; 24 | white-space: nowrap; 25 | width: 1px; 26 | ``` 27 | 28 | 👎 Examples of **incorrect** code for this rule: 29 | 30 | ```jsx 31 | 32 | ``` 33 | 34 | ```jsx 35 | 36 | 37 | 38 | ``` 39 | 40 | ```jsx 41 | Submit 42 | ``` 43 | 44 | 👍 Examples of **correct** code for this rule: 45 | 46 | ```jsx 47 |

Welcome to GitHub

48 | ``` 49 | 50 | ```jsx 51 | 52 |

Welcome to GitHub

53 |
54 | ``` 55 | 56 | ```jsx 57 | Welcome to GitHub 58 | ``` 59 | 60 | ## Options 61 | 62 | - className - A css className that visually hides content. Defaults to `sr-only`. 63 | - componentName - A react component name that visually hides content. Defaults to `VisuallyHidden`. 64 | 65 | ```json 66 | { 67 | "a11y-no-visually-hidden-interactive-element": [ 68 | "error", 69 | { 70 | "className": "visually-hidden", 71 | "componentName": "VisuallyHidden" 72 | } 73 | ] 74 | } 75 | ``` 76 | 77 | ## Version 78 | -------------------------------------------------------------------------------- /docs/rules/a11y-role-supports-aria-props.md: -------------------------------------------------------------------------------- 1 | # Enforce that elements with explicit or implicit roles defined contain only `aria-*` properties supported by that `role` (`github/a11y-role-supports-aria-props`) 2 | 3 | 💼 This rule is enabled in the ⚛️ `react` config. 4 | 5 | 6 | 7 | ## Rule Details 8 | 9 | This rule enforces that elements with explicit or implicit roles defined contain only `aria-*` properties supported by that `role`. 10 | 11 | For example, this rule aims to discourage common misuse of the `aria-label` and `aria-labelledby` attribute. `aria-label` and `aria-labelledby` support is only guaranteed on interactive elements like `button` or `a`, or on static elements like `div` and `span` with a permitted `role`. This rule will allow `aria-label` and `aria-labelledby` usage on `div` and `span` elements if it set to a role other than the ones listed in [WSC: a list of ARIA roles which cannot be named](https://w3c.github.io/aria/#namefromprohibited). This rule will never permit usage of `aria-label` and `aria-labelledby` on `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `strong`, `i`, `p`, `b`, or `code`. 12 | 13 | ### "Help! I'm trying to set a tooltip on a static element and this rule flagged it!" 14 | 15 | Please do not use tooltips on static elements. It is a highly discouraged, inaccessible pattern. 16 | See [Primer: Tooltip alternatives](https://primer.style/design/accessibility/tooltip-alternatives) for what to do instead. 17 | 18 | ### Resources 19 | 20 | - [w3c/aria Consider prohibiting author naming certain roles #833](https://github.com/w3c/aria/issues/833) 21 | - [Not so short note on aria-label usage - Big Table Edition](https://html5accessibility.com/stuff/2020/11/07/not-so-short-note-on-aria-label-usage-big-table-edition/) 22 | - [Your tooltips are bogus](https://heydonworks.com/article/your-tooltips-are-bogus/) 23 | - [Primer: Tooltip alternatives](https://primer.style/design/accessibility/tooltip-alternatives) 24 | 25 | ### Disclaimer 26 | 27 | There are conflicting resources and opinions on what elements should support these naming attributes. For now, this rule will operate under a relatively simple heuristic aimed to minimize false positives. This may have room for future improvements. Learn more at [W3C Name Calcluation](https://w3c.github.io/aria/#namecalculation). 28 | 29 | ### **Incorrect** code for this rule 👎 30 | 31 | ```erb 32 | I am some text. 33 | ``` 34 | 35 | ```erb 36 | Please be careful of the following. 37 | ``` 38 | 39 | ```erb 40 |
Goodbye
41 | ``` 42 | 43 | ```erb 44 |

Page title

45 | ``` 46 | 47 | ### **Correct** code for this rule 👍 48 | 49 | ```erb 50 | 53 | ``` 54 | 55 | ```erb 56 | 59 |

Add bold text or turn selection into bold text

60 | ``` 61 | 62 | ```erb 63 | Hello 64 | ``` 65 | 66 | ```erb 67 |
Goodbye
68 | ``` 69 | 70 | ```erb 71 |

Page title

72 | ``` 73 | 74 | ```erb 75 |
76 |

Heading

77 |
78 | ``` 79 | 80 | ## Version 81 | -------------------------------------------------------------------------------- /docs/rules/a11y-svg-has-accessible-name.md: -------------------------------------------------------------------------------- 1 | # Require SVGs to have an accessible name (`github/a11y-svg-has-accessible-name`) 2 | 3 | 💼 This rule is enabled in the ⚛️ `react` config. 4 | 5 | 6 | 7 | ## Rule Details 8 | 9 | An `` must have an accessible name. Set `aria-label` or `aria-labelledby`, or nest a `` element as the first child of the `` element. 10 | 11 | However, if the `` is purely decorative, hide it with `aria-hidden="true"` or `role="presentation"`. 12 | 13 | ## Resources 14 | 15 | - [Accessible SVGs](https://css-tricks.com/accessible-svgs/) 16 | 17 | ## Examples 18 | 19 | ### **Incorrect** code for this rule 👎 20 | 21 | ```html 22 | 23 | 24 | 25 | ``` 26 | 27 | ```html 28 | 29 | 30 | 31 | ``` 32 | 33 | ```html 34 | 35 | 36 | Circle with a black outline and red fill 37 | 38 | ``` 39 | 40 | ### **Correct** code for this rule 👍 41 | 42 | ```html 43 | 44 | Circle with a black outline and red fill 45 | 46 | 47 | ``` 48 | 49 | ```html 50 | 51 | 52 | 53 | ``` 54 | 55 | ```html 56 | 57 | 58 | 59 | ``` 60 | 61 | ```html 62 | 65 | ``` 66 | 67 | ```html 68 | 69 | 70 | 71 | ``` 72 | 73 | ## Version 74 | -------------------------------------------------------------------------------- /docs/rules/array-foreach.md: -------------------------------------------------------------------------------- 1 | # Enforce `for..of` loops over `Array.forEach` (`github/array-foreach`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` config. 4 | 5 | 6 | 7 | Prefer `for...of` statement instead of `Array.forEach`. 8 | 9 | ## Rule Details 10 | 11 | Here's a summary of why `forEach` is disallowed, and why we prefer `for...of` for almost any use-case of `forEach`: 12 | 13 | - Allowing `forEach` encourages **layering of "bad practices"**, such as using `Array.from()` (which is less performant than using `for...of`). 14 | - When more requirements are added on, `forEach` typically gets **chained** with other methods like `filter` or `map`, causing multiple iterations over the same Array. Encouraging `for` loops discourages chaining and encourages single-iteration logic (e.g. using a `continue` instead of `filter`). 15 | - `for` loops are considered "more readable" and have **clearer intent**. 16 | - `for...of` loops offer the **most flexibility** for iteration (especially vs `Array.from`). 17 | 18 | Typically developers will reach for a `forEach` when they want to iterate over a set of items. However not all "iterables" have access to Array methods. So a developer might convert their iterable to an Array by using `Array.from(iter).forEach()`. This code has introduced performance problems, where a `for...of` loop would be more performant. 19 | 20 | `forEach` does not do anything special with the Array - it does not create a new array or does not aid in encapsulation (except for introducing a new lexical scope within the callback, which isn't a benefit considering we use `let`/`const`). We don't disallow `map`/`filter`/`reduce` because they have a tangible effect - they create a new array - which would take _more_ code and be _less_ readable to do with a `for...of` loop, the exception being as more requirements are added, and we start chaining array methods together... 21 | 22 | Often when using a method like `forEach` - when coming back to add new code, let's say to filter certain elements from the Array before operating on them, a developer is implicitly encouraged to use Array's method chaining to achieve this result. For example if we wanted to filter out bad apples from an Array of Apples, if the code already uses `forEach`, then its a simple addition to add `filter()`: 23 | 24 | ```diff 25 | apples 26 | + .filter(apple => !apple.bad) 27 | .forEach(polishApple) 28 | ``` 29 | 30 | The problem we now have is that we're iterating multiple times over the items in a collection. Using `forEach` to begin with is what encouraged the chaining, if this were a `for` loop then the equivalent behavior would be to use 2 `for` loops, which a developer is far less likely to write, so the `for` loop instead encourages an imperative style `continue`, keeping within a single set of iterations: 31 | 32 | ```diff 33 | for(const apple of apples) { 34 | + if (apple.bad) continue 35 | polishApple(apple) 36 | } 37 | ``` 38 | 39 | Chaining isn't always necessarily bad. Chaining can advertise a series of transformations that are independent from one another, and therefore aid readability. Additionally, sometimes the "goto-style" behavior of `continue` in for loops can hamper readability. For small Arrays, performance is not going to be of concern, but caution should be applied where there is a potentially unbounded Array (such as iterating over a fetched users list) as performance can easily become a bottleneck when unchecked. 40 | 41 | The `forEach` method passes more than just the current item it is iterating over. The signature of the `forEach` callback method is `(cur: T, i: Number, all: []T) => void` and it can _additionally_ override the `receiver` (`this` value), meaning that often the _intent_ of what the callback does is hidden. To put this another way, there is _no way_ to know what the following code operates on without reading the implementation: `forEach(polishApple)`. 42 | 43 | The `for` loop avoids this issue. Calls are explicit within the `for` loop, as they are not passed around. For example: 44 | 45 | ```js 46 | for (const apple of apples) { 47 | polishApple(apple) 48 | } 49 | ``` 50 | 51 | We know this code can only possibly mutate `apple`, as the return value is discarded, there is no `receiver` (`this` value) as `.call()` is not used, and it cannot operate on the whole array of `apples` because it is not passed as an argument. In this respect, we can establish what the intent of `polishApple(apple)` is far more than `forEach(polishApple)`. It is too easy for `forEach` to obscure the intent. 52 | 53 | While `forEach` provides a set of arguments to the callback, it is still overall _less flexible_ than a `for` loop. A `for` loop can conditionally call the callback, can pass additional arguments to the callback (which would otherwise need to be hoisted or curried), can opt to change the `receiver` (`this` value) or not pass any `receiver` at all. This extra flexibility is the reason we almost always prefer to use `for` loops over any of the Array iteration methods. 54 | 55 | A good example of how `for` loops provide flexibility, where `forEach` constrains it, is to see how an iteration would be refactored to handle async work. Consider the following... 56 | 57 | ```js 58 | apples.forEach(polishApple) 59 | // vs... 60 | for (const apple of apples) { 61 | polishApple(apple) 62 | } 63 | ``` 64 | 65 | If `polishApple` needed to do some serial async work, then we'd need to refactor the iteration steps to accommodate for this async work, by `await`ing each call to `polishApple`. We cannot simply pass an `async` function to `forEach`, as it does not understand async functions, instead we'd have to turn the `forEach` into a `reduce` and combine that with a `Promise` returning function. For example: 66 | 67 | ```diff 68 | - apples.forEach(polishApple) 69 | + await apples.reduce((cur, next) => cur.then(() => polishApple(next)), Promise.resolve()) 70 | ``` 71 | 72 | Compare this to the `for` loop, which has a much simpler path to refactoring: 73 | 74 | ```diff 75 | for (const apple of apples) { 76 | - polishApple(apple) 77 | + await polishApple(apple) 78 | } 79 | ``` 80 | 81 | See also https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of 82 | 83 | 👎 Examples of **incorrect** code for this rule: 84 | 85 | ```js 86 | els.forEach(el => { 87 | el 88 | }) 89 | ``` 90 | 91 | 👍 Examples of **correct** code for this rule: 92 | 93 | ```js 94 | for (const el of els) { 95 | el 96 | } 97 | ``` 98 | 99 | Use [`entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/entries) to get access to the index: 100 | 101 | ```js 102 | for (const [i, el] of els.entries()) { 103 | el.name = `Element ${i}` 104 | } 105 | ``` 106 | 107 | ## Version 108 | 109 | 4.3.2 110 | -------------------------------------------------------------------------------- /docs/rules/async-currenttarget.md: -------------------------------------------------------------------------------- 1 | # Disallow `event.currentTarget` calls inside of async functions (`github/async-currenttarget`) 2 | 3 | 💼 This rule is enabled in the 🔍 `browser` config. 4 | 5 | 6 | 7 | ## Rule Details 8 | 9 | Accessing `event.currentTarget` inside an `async function()` will likely be `null` as `currentTarget` is mutated as the event is propagated. 10 | 11 | 1. A `click` event is dispatched 12 | 2. The handler is invoked once with the expected `currentTarget` 13 | 3. An `await` defers the execution 14 | 4. The event dispatch continues, `event.currentTarget` is modified to point to the current target of another event handler and nulled out at the end of the dispatch 15 | 5. The async function resumes 16 | 6. `event.currentTarget` is now `null` 17 | 18 | If you're using `async`, you'll need to synchronously create a reference to `currentTarget` before any async activity. 19 | 20 | 👎 Examples of **incorrect** code for this rule: 21 | 22 | ```js 23 | document.addEventListener('click', async function (event) { 24 | // event.currentTarget will be an HTMLElement 25 | const url = event.currentTarget.getAttribute('data-url') 26 | const data = await fetch(url) 27 | 28 | // But now, event.currentTarget will be null 29 | const text = event.currentTarget.getAttribute('data-text') 30 | // ... 31 | }) 32 | ``` 33 | 34 | 👍 Examples of **correct** code for this rule: 35 | 36 | ```js 37 | document.addEventListener('click', function (event) { 38 | const currentTarget = event.currentTarget 39 | const url = currentTarget.getAttribute('data-url') 40 | 41 | // call async IIFE 42 | ;(async function () { 43 | const data = await fetch(url) 44 | 45 | const text = currentTarget.getAttribute('data-text') 46 | // ... 47 | })() 48 | }) 49 | ``` 50 | 51 | Alternatively, extract a function to create an element reference. 52 | 53 | ```js 54 | document.addEventListener('click', function (event) { 55 | fetchData(event.currentTarget) 56 | }) 57 | 58 | async function fetchData(el) { 59 | const url = el.getAttribute('data-url') 60 | const data = await fetch(url) 61 | const text = el.getAttribute('data-text') 62 | // ... 63 | } 64 | ``` 65 | 66 | ## Version 67 | 68 | 4.3.2 69 | -------------------------------------------------------------------------------- /docs/rules/async-preventdefault.md: -------------------------------------------------------------------------------- 1 | # Disallow `event.preventDefault` calls inside of async functions (`github/async-preventdefault`) 2 | 3 | 💼 This rule is enabled in the 🔍 `browser` config. 4 | 5 | 6 | 7 | Using `event.preventDefault()` inside an `async function()` won't likely work as you'd expect because synchronous nature of event dispatch. 8 | 9 | ## Rule Details 10 | 11 | 1. A `click` event is dispatched 12 | 2. This handler is scheduled but not ran immediately because its marked async. 13 | 3. The event dispatch completes and nothing has called `preventDefault()` _yet_ and the default click behavior occurs. 14 | 4. The async function is scheduled and runs. 15 | 5. Calling `preventDefault()` is now a no-op as the synchronous event dispatch has already completed. 16 | 17 | If you're using `async`, you likely need to wait on a promise in the event handler. In this case you can split the event handler in two parts, one synchronous and asynchronous. 18 | 19 | 👎 Examples of **incorrect** code for this rule: 20 | 21 | ```js 22 | document.addEventListener('click', async function (event) { 23 | const data = await fetch() 24 | 25 | event.preventDefault() 26 | }) 27 | ``` 28 | 29 | 👍 Examples of **correct** code for this rule: 30 | 31 | ```js 32 | document.addEventListener('click', function (event) { 33 | // preventDefault in a regular function 34 | event.preventDefault() 35 | 36 | // call async helper function 37 | loadData(event.target) 38 | }) 39 | 40 | async function loadData(el) { 41 | const data = await fetch() 42 | // ... 43 | } 44 | ``` 45 | 46 | This could also be done with an async IIFE. 47 | 48 | ```js 49 | document.addEventListener('click', function (event) { 50 | // preventDefault in a regular function 51 | event.preventDefault() 52 | 53 | // call async IIFE 54 | ;(async function () { 55 | const data = await fetch() 56 | // ... 57 | })() 58 | }) 59 | ``` 60 | 61 | ## Version 62 | 63 | 4.3.2 64 | -------------------------------------------------------------------------------- /docs/rules/authenticity-token.md: -------------------------------------------------------------------------------- 1 | # Disallow usage of CSRF tokens in JavaScript (`github/authenticity-token`) 2 | 3 | 💼 This rule is enabled in the 🔐 `internal` config. 4 | 5 | 6 | 7 | ## Rule Details 8 | 9 | The Rails `form_tag` helper creates a `
` element with a `
23 | ``` 24 | 25 | Allows you to select elements by `js-org-update` and still filter by the `data-org-name` attribute if you need to. Both `js-org-update` and `data-org-name` are clearly static symbols that are easy to search for. 26 | 27 | `js-` classes must start with `js-` (obviously) and only contain lowercase letters and numbers separated by `-`s. The ESLint [`github/js-class-name`](https://github.com/github/eslint-plugin-github/blob/master/lib/rules/js-class-name.js) rule enforces this style. 28 | 29 | [@defunkt's original proposal from 2010](https://web.archive.org/web/20180902223055/http://ozmm.org/posts/slightly_obtrusive_javascript.html). 30 | 31 | 👎 Examples of **incorrect** code for this rule: 32 | 33 | ```js 34 | const el = document.querySelector('.js-Foo') 35 | ``` 36 | 37 | 👍 Examples of **correct** code for this rule: 38 | 39 | ```js 40 | const el = document.querySelector('.js-foo') 41 | ``` 42 | 43 | ## Version 44 | 45 | 4.3.2 46 | -------------------------------------------------------------------------------- /docs/rules/no-blur.md: -------------------------------------------------------------------------------- 1 | # Disallow usage of `Element.prototype.blur()` (`github/no-blur`) 2 | 3 | 💼 This rule is enabled in the 🔍 `browser` config. 4 | 5 | 6 | 7 | Do not use `element.blur()`. Blurring an element causes the focus position to be reset causing accessibility issues when using keyboard or voice navigation. Instead, restore focus by calling `element.focus()` on a prior element. 8 | 9 | ## Rule Details 10 | 11 | - [Use of `blur()` is discouraged by WHATWG HTML spec](https://html.spec.whatwg.org/multipage/interaction.html#dom-blur) 12 | 13 | 👎 Examples of **incorrect** code for this rule: 14 | 15 | ```js 16 | menu.addEventListener('close', () => { 17 | input.blur() 18 | }) 19 | ``` 20 | 21 | 👍 Examples of **correct** code for this rule: 22 | 23 | ```js 24 | menu.addEventListener('open', () => { 25 | const previouslyFocusedElement = document.activeElement 26 | 27 | input.focus() 28 | 29 | menu.addEventListener('close', () => { 30 | previouslyFocusedElement.focus() 31 | }) 32 | }) 33 | ``` 34 | 35 | ## Version 36 | 37 | 4.3.2 38 | -------------------------------------------------------------------------------- /docs/rules/no-d-none.md: -------------------------------------------------------------------------------- 1 | # Disallow usage the `d-none` CSS class (`github/no-d-none`) 2 | 3 | 💼 This rule is enabled in the 🔐 `internal` config. 4 | 5 | 6 | 7 | ## Rule Details 8 | 9 | Ideally JavaScript behaviors should not rely on Primer CSS when the `hidden` property can be used. 10 | 11 | 👎 Examples of **incorrect** code for this rule: 12 | 13 | ```js 14 | div.classList.add('d-none') 15 | ``` 16 | 17 | 👍 Examples of **correct** code for this rule: 18 | 19 | ```js 20 | div.hidden = false 21 | ``` 22 | 23 | ## Version 24 | 25 | 4.3.2 26 | -------------------------------------------------------------------------------- /docs/rules/no-dataset.md: -------------------------------------------------------------------------------- 1 | # Enforce usage of `Element.prototype.getAttribute` instead of `Element.prototype.datalist` (`github/no-dataset`) 2 | 3 | 💼 This rule is enabled in the 🔍 `browser` config. 4 | 5 | 6 | 7 | ## Rule Details 8 | 9 | Due to [camel-case transformations](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset#Name_conversion), using dataset is not easily greppable. Instead, use `el.getAttribute('data-what-ever')`. 10 | 11 | 👎 Examples of **incorrect** code for this rule: 12 | 13 | ```js 14 | el.dataset.coolThing 15 | ``` 16 | 17 | 👍 Examples of **correct** code for this rule: 18 | 19 | ```js 20 | el.getAttribute('data-cool-thing') 21 | ``` 22 | 23 | ## Version 24 | 25 | 4.3.2 26 | -------------------------------------------------------------------------------- /docs/rules/no-dynamic-script-tag.md: -------------------------------------------------------------------------------- 1 | # Disallow creating dynamic script tags (`github/no-dynamic-script-tag`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` config. 4 | 5 | 6 | 7 | ## Rule Details 8 | 9 | Creating dynamic script tags bypasses a lot of security measures - like SRIs - and pose a potential threat to your application. 10 | Instead of creating a `script` tag in the client, provide all necessary `script` tags in the page's HTML. 11 | 12 | 👎 Examples of **incorrect** code for this rule: 13 | 14 | ```js 15 | document.createElement('script') 16 | document.getElementById('some-id').type = 'text/javascript' 17 | ``` 18 | 19 | 👍 Examples of **correct** code for this rule: 20 | 21 | ```html 22 | 23 |