Repository: github/eslint-plugin-github Branch: main Commit: 88014ae2fe53 Files: 128 Total size: 204.1 KB Directory structure: gitextract_h6p_l7um/ ├── .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.json ├── test-examples/ │ ├── flat/ │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ └── src/ │ │ ├── forEachTest.js │ │ ├── getAttribute.js │ │ ├── jsx.tsx │ │ ├── noBlur.js │ │ └── thisTypescriptTest.ts │ └── legacy/ │ ├── .eslintrc.cjs │ ├── 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 ================================================ FILE CONTENTS ================================================ ================================================ FILE: .devcontainer/Dockerfile ================================================ # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.222.0/containers/javascript-node/.devcontainer/base.Dockerfile # [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 ARG VARIANT="16" FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:${VARIANT} # [Optional] Uncomment this section to install additional OS packages. # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # && apt-get -y install --no-install-recommends # [Optional] Uncomment if you want to install an additional version of node using nvm # ARG EXTRA_NODE_VERSION=10 # RUN su node -c "source/usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" # [Optional] Uncomment if you want to install more global node modules # RUN su node -c "npm install -g " ================================================ FILE: .devcontainer/devcontainer.json ================================================ // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: // https://github.com/microsoft/vscode-dev-containers/tree/v0.222.0/containers/javascript-node { "name": "Node.js", "build": { "dockerfile": "Dockerfile", // Update 'VARIANT' to pick a Node version: 16, 14, 12. // Append -bullseye or -buster to pin to an OS version. // Use -bullseye variants on local arm64/Apple Silicon. "args": {"VARIANT": "22"} }, // Set *default* container specific settings.json values on container create. "settings": {}, // Add the IDs of extensions you want installed when the container is created. "extensions": ["dbaeumer.vscode-eslint"], // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. // "postCreateCommand": "yarn install", // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "node", "features": { "git": "latest" } } ================================================ FILE: .eslint-doc-generatorrc.js ================================================ /** @type {import('eslint-doc-generator').GenerateOptions} */ export default { configEmoji: [ ['browser', '🔍'], ['internal', '🔐'], ['react', '⚛️'], ], ruleDocSectionInclude: ['Rule Details', 'Version'], } ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: npm directory: '/' schedule: interval: weekly open-pull-requests-limit: 99 groups: all-dependencies: patterns: - "*" - package-ecosystem: github-actions directory: '/' schedule: interval: weekly open-pull-requests-limit: 99 ================================================ FILE: .github/workflows/nodejs.yml ================================================ name: Node CI on: push: branches-ignore: - 'dependabot/**' pull_request: jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: [20, 22] steps: - uses: actions/checkout@v6 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} cache: npm - name: Install run: npm ci - name: Test run: npm test ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish on: release: types: [created] permissions: contents: read id-token: write jobs: publish-npm: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: 22 registry-url: https://registry.npmjs.org/ cache: npm - run: npm ci - run: npm test - run: npm version ${TAG_NAME} --git-tag-version=false env: TAG_NAME: ${{ github.event.release.tag_name }} - run: npm whoami; npm --ignore-scripts publish --provenance env: NODE_AUTH_TOKEN: ${{secrets.npm_token}} ================================================ FILE: .gitignore ================================================ node_modules/ npm-debug.log yarn.lock ================================================ FILE: CODEOWNERS ================================================ * @github/web-systems-reviewers ================================================ FILE: CONTRIBUTING.md ================================================ ## Publishing this package 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. 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. ================================================ FILE: LICENSE ================================================ Copyright (c) 2016 GitHub, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # eslint-plugin-github ## Installation ```sh npm install --save-dev eslint eslint-plugin-github ``` ## Setup ### Legacy Configuration (`.eslintrc`) Add `github` to your list of plugins in your ESLint config. JSON ESLint config example: ```json { "plugins": ["github"] } ``` Extend the configs you wish to use. JSON ESLint config example: ```json { "extends": ["plugin:github/recommended"] } ``` ### Flat Configuration (`eslint-config.js`) Import the `eslint-plugin-github`, and extend any of the configurations using `getFlatConfigs()` as needed like so: ```js import github from 'eslint-plugin-github' export default [ github.getFlatConfigs().browser, github.getFlatConfigs().recommended, github.getFlatConfigs().react, ...github.getFlatConfigs().typescript, { files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], ignores: ['eslint.config.mjs'], rules: { 'github/array-foreach': 'error', 'github/async-preventdefault': 'warn', 'github/no-then': 'error', 'github/no-blur': 'error', }, }, ] ``` > [!NOTE] > 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. > > 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: > > `'github/filenames-match-regex': ['error', '^([a-z0-9]+)([A-Z][a-z0-9]+)*$'],` The available configs are: - `internal` - Rules useful for github applications. - `browser` - Useful rules when shipping your app to the browser. - `react` - Recommended rules for React applications. - `recommended` - Recommended rules for every application. - `typescript` - Useful rules when writing TypeScript. ### Component mapping (Experimental) _Note: This is experimental and subject to change._ 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. 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`. ```json { "settings": { "github": { "polymorphicPropName": "asChild", "components": { "Box": "p", "Link": "a" } } } } ``` This config will be interpreted in the following way: - All `` elements will be treated as a `p` element type. - `` without a defined `as` prop will be treated as a `a`. - `` will be treated as a `button` element type. ### Rules 💼 Configurations enabled in.\ 🔍 Set in the `browser` configuration.\ 🔐 Set in the `internal` configuration.\ ⚛️ Set in the `react` configuration.\ ✅ Set in the `recommended` configuration.\ 🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\ ❌ Deprecated. | Name                                        | Description | 💼 | 🔧 | ❌ | | :------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | :- | :- | :- | | [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. | ⚛️ | | | | [a11y-no-generic-link-text](docs/rules/a11y-no-generic-link-text.md) | disallow generic link text | | | ❌ | | [a11y-no-title-attribute](docs/rules/a11y-no-title-attribute.md) | disallow using the title attribute | ⚛️ | | | | [a11y-no-visually-hidden-interactive-element](docs/rules/a11y-no-visually-hidden-interactive-element.md) | enforce that interactive elements are not visually hidden | ⚛️ | | | | [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`. | ⚛️ | | | | [a11y-svg-has-accessible-name](docs/rules/a11y-svg-has-accessible-name.md) | require SVGs to have an accessible name | ⚛️ | | | | [array-foreach](docs/rules/array-foreach.md) | enforce `for..of` loops over `Array.forEach` | ✅ | | | | [async-currenttarget](docs/rules/async-currenttarget.md) | disallow `event.currentTarget` calls inside of async functions | 🔍 | | | | [async-preventdefault](docs/rules/async-preventdefault.md) | disallow `event.preventDefault` calls inside of async functions | 🔍 | | | | [authenticity-token](docs/rules/authenticity-token.md) | disallow usage of CSRF tokens in JavaScript | 🔐 | | | | [filenames-match-regex](docs/rules/filenames-match-regex.md) | require filenames to match a regex naming convention | | | | | [get-attribute](docs/rules/get-attribute.md) | disallow wrong usage of attribute names | 🔍 | 🔧 | | | [js-class-name](docs/rules/js-class-name.md) | enforce a naming convention for js- prefixed classes | 🔐 | | | | [no-blur](docs/rules/no-blur.md) | disallow usage of `Element.prototype.blur()` | 🔍 | | | | [no-d-none](docs/rules/no-d-none.md) | disallow usage the `d-none` CSS class | 🔐 | | | | [no-dataset](docs/rules/no-dataset.md) | enforce usage of `Element.prototype.getAttribute` instead of `Element.prototype.datalist` | 🔍 | | | | [no-dynamic-script-tag](docs/rules/no-dynamic-script-tag.md) | disallow creating dynamic script tags | ✅ | | | | [no-implicit-buggy-globals](docs/rules/no-implicit-buggy-globals.md) | disallow implicit global variables | ✅ | | | | [no-inner-html](docs/rules/no-inner-html.md) | disallow `Element.prototype.innerHTML` in favor of `Element.prototype.textContent` | 🔍 | | | | [no-innerText](docs/rules/no-innerText.md) | disallow `Element.prototype.innerText` in favor of `Element.prototype.textContent` | 🔍 | 🔧 | | | [no-then](docs/rules/no-then.md) | enforce using `async/await` syntax over Promises | ✅ | | | | [no-useless-passive](docs/rules/no-useless-passive.md) | disallow marking a event handler as passive when it has no effect | 🔍 | 🔧 | | | [prefer-observers](docs/rules/prefer-observers.md) | disallow poorly performing event listeners | 🔍 | | | | [require-passive-events](docs/rules/require-passive-events.md) | enforce marking high frequency event handlers as passive | 🔍 | | | | [unescaped-html-literal](docs/rules/unescaped-html-literal.md) | disallow unescaped HTML literals | 🔍 | | | ================================================ FILE: bin/eslint-ignore-errors.js ================================================ #!/usr/bin/env node // Disables eslint rules in a JavaScript file with next-line comments. This is // useful when introducing a new rule that causes many failures. The comments // can be fixed and removed at while updating the file later. // // Usage: // // eslint-ignore-errors app/assets/javascripts/something.js const fs = require('fs') const execFile = require('child_process').execFile execFile('eslint', ['--format', 'json', process.argv[2]], (error, stdout) => { for (const result of JSON.parse(stdout)) { const filename = result.filePath const jsLines = fs.readFileSync(filename, 'utf8').split('\n') const offensesByLine = {} let addedLines = 0 // Produces {47: ['github/no-d-none', 'github/no-blur'], 83: ['github/no-blur']} for (const message of result.messages) { if (offensesByLine[message.line]) { offensesByLine[message.line].push(message.ruleId) } else { offensesByLine[message.line] = [message.ruleId] } } for (const line of Object.keys(offensesByLine)) { const lineIndex = line - 1 + addedLines const previousLine = jsLines[lineIndex - 1] const ruleIds = offensesByLine[line].join(', ') if (isDisableComment(previousLine)) { jsLines[lineIndex - 1] = previousLine.replace(/\s?\*\/$/, `, ${ruleIds} */`) } else { const leftPad = ' '.repeat(jsLines[lineIndex].match(/^\s*/g)[0].length) jsLines.splice(lineIndex, 0, `${leftPad}/* eslint-disable-next-line ${ruleIds} */`) } addedLines += 1 } if (result.messages.length !== 0) { fs.writeFileSync(filename, jsLines.join('\n'), 'utf8') } } }) function isDisableComment(line) { return line.match(/\/\* eslint-disable-next-line .+\*\//) } ================================================ FILE: docs/rules/a11y-aria-label-is-well-formatted.md ================================================ # Enforce [aria-label] text to be formatted as you would visual text (`github/a11y-aria-label-is-well-formatted`) 💼 This rule is enabled in the ⚛️ `react` config. ## Rule Details `[aria-label]` content should be formatted in the same way you would visual text. Please use sentence case. 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. ## Resources - [Using aria-label](https://www.w3.org/WAI/tutorials/forms/labels/#using-aria-label) ## Examples ### **Incorrect** code for this rule 👎 ```html ``` ```html ``` ### **Correct** code for this rule 👍 ```html ``` ```html ``` ## Version ================================================ FILE: docs/rules/a11y-no-generic-link-text.md ================================================ # Disallow generic link text (`github/a11y-no-generic-link-text`) ❌ This rule is deprecated. It was replaced by `jsx-a11y/anchor-ambiguous-text`. ## Rule Details Avoid setting generic link text like, "Click here", "Read more", and "Learn more" which do not make sense when read out of context. 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. Additionally, generic link text can also problematic for heavy zoom users where the link context is out of view. Ensure that your link text is descriptive and the purpose of the link is clear even when read out of context of surrounding text. Learn more about how to write descriptive link text at [Access Guide: Write descriptive link text](https://www.accessguide.io/guide/descriptive-link-text) ### Use of ARIA attributes If you _must_ use ARIA to replace the visible link text, include the visible text at the beginning. For example, on a pricing plans page, the following are good: - Visible text: `Learn more` - Accessible label: `Learn more about GitHub pricing plans` Accessible ✅ ```html Learn more ``` Inaccessible 🚫 ```html Learn more ``` 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). #### False negatives Caution: because of the restrictions of static code analysis, we may not catch all violations. Please perform browser tests and spot checks: - when `aria-label` is set dynamically - when using `aria-labelledby` ## Resources - [Primer: Links](https://primer.style/design/accessibility/links) - [Understanding Success Criterion 2.4.4: Link Purpose (In Context)](https://www.w3.org/WAI/WCAG21/Understanding/link-purpose-in-context.html) - [WebAim: Links and Hypertext](https://webaim.org/techniques/hypertext/) - [Deque: Use link text that make sense when read out of context](https://dequeuniversity.com/tips/link-text) ## Examples ### **Incorrect** code for this rule 👎 ```jsx Learn more ``` ```jsx Read more ``` ```jsx Read more ``` ```jsx Read more ``` ### **Correct** code for this rule 👍 ```jsx Learn more about GitHub ``` ```jsx Create a new repository ``` ## Version ================================================ FILE: docs/rules/a11y-no-title-attribute.md ================================================ # Disallow using the title attribute (`github/a11y-no-title-attribute`) 💼 This rule is enabled in the ⚛️ `react` config. The title attribute is strongly discouraged. The only exception is on an ` ``` ## Version ================================================ FILE: docs/rules/a11y-no-visually-hidden-interactive-element.md ================================================ # Enforce that interactive elements are not visually hidden (`github/a11y-no-visually-hidden-interactive-element`) 💼 This rule is enabled in the ⚛️ `react` config. ## Rule Details 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. 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). ### Why do we visually hide content? 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. Applying the following css will visually hide content while still making it accessible to screen reader users. ```css clip-path: inset(50%); height: 1px; overflow: hidden; position: absolute; white-space: nowrap; width: 1px; ``` 👎 Examples of **incorrect** code for this rule: ```jsx ``` ```jsx ``` ```jsx Submit ``` 👍 Examples of **correct** code for this rule: ```jsx

Welcome to GitHub

``` ```jsx

Welcome to GitHub

``` ```jsx Welcome to GitHub ``` ## Options - className - A css className that visually hides content. Defaults to `sr-only`. - componentName - A react component name that visually hides content. Defaults to `VisuallyHidden`. ```json { "a11y-no-visually-hidden-interactive-element": [ "error", { "className": "visually-hidden", "componentName": "VisuallyHidden" } ] } ``` ## Version ================================================ FILE: 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` (`github/a11y-role-supports-aria-props`) 💼 This rule is enabled in the ⚛️ `react` config. ## Rule Details This rule enforces that elements with explicit or implicit roles defined contain only `aria-*` properties supported by that `role`. 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`. ### "Help! I'm trying to set a tooltip on a static element and this rule flagged it!" Please do not use tooltips on static elements. It is a highly discouraged, inaccessible pattern. See [Primer: Tooltip alternatives](https://primer.style/design/accessibility/tooltip-alternatives) for what to do instead. ### Resources - [w3c/aria Consider prohibiting author naming certain roles #833](https://github.com/w3c/aria/issues/833) - [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/) - [Your tooltips are bogus](https://heydonworks.com/article/your-tooltips-are-bogus/) - [Primer: Tooltip alternatives](https://primer.style/design/accessibility/tooltip-alternatives) ### Disclaimer 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). ### **Incorrect** code for this rule 👎 ```erb I am some text. ``` ```erb Please be careful of the following. ``` ```erb
Goodbye
``` ```erb

Page title

``` ### **Correct** code for this rule 👍 ```erb ``` ```erb

Add bold text or turn selection into bold text

``` ```erb Hello ``` ```erb
Goodbye
``` ```erb

Page title

``` ```erb

Heading

``` ## Version ================================================ FILE: docs/rules/a11y-svg-has-accessible-name.md ================================================ # Require SVGs to have an accessible name (`github/a11y-svg-has-accessible-name`) 💼 This rule is enabled in the ⚛️ `react` config. ## Rule Details An `` must have an accessible name. Set `aria-label` or `aria-labelledby`, or nest a `` element as the first child of the `` element. However, if the `` is purely decorative, hide it with `aria-hidden="true"` or `role="presentation"`. ## Resources - [Accessible SVGs](https://css-tricks.com/accessible-svgs/) ## Examples ### **Incorrect** code for this rule 👎 ```html ``` ```html ``` ```html Circle with a black outline and red fill ``` ### **Correct** code for this rule 👍 ```html Circle with a black outline and red fill ``` ```html ``` ```html ``` ```html ``` ```html ``` ## Version ================================================ FILE: docs/rules/array-foreach.md ================================================ # Enforce `for..of` loops over `Array.forEach` (`github/array-foreach`) 💼 This rule is enabled in the ✅ `recommended` config. Prefer `for...of` statement instead of `Array.forEach`. ## Rule Details Here's a summary of why `forEach` is disallowed, and why we prefer `for...of` for almost any use-case of `forEach`: - Allowing `forEach` encourages **layering of "bad practices"**, such as using `Array.from()` (which is less performant than using `for...of`). - 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`). - `for` loops are considered "more readable" and have **clearer intent**. - `for...of` loops offer the **most flexibility** for iteration (especially vs `Array.from`). 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. `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... 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()`: ```diff apples + .filter(apple => !apple.bad) .forEach(polishApple) ``` 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: ```diff for(const apple of apples) { + if (apple.bad) continue polishApple(apple) } ``` 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. 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)`. The `for` loop avoids this issue. Calls are explicit within the `for` loop, as they are not passed around. For example: ```js for (const apple of apples) { polishApple(apple) } ``` 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. 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. 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... ```js apples.forEach(polishApple) // vs... for (const apple of apples) { polishApple(apple) } ``` 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: ```diff - apples.forEach(polishApple) + await apples.reduce((cur, next) => cur.then(() => polishApple(next)), Promise.resolve()) ``` Compare this to the `for` loop, which has a much simpler path to refactoring: ```diff for (const apple of apples) { - polishApple(apple) + await polishApple(apple) } ``` See also https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of 👎 Examples of **incorrect** code for this rule: ```js els.forEach(el => { el }) ``` 👍 Examples of **correct** code for this rule: ```js for (const el of els) { el } ``` Use [`entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/entries) to get access to the index: ```js for (const [i, el] of els.entries()) { el.name = `Element ${i}` } ``` ## Version 4.3.2 ================================================ FILE: docs/rules/async-currenttarget.md ================================================ # Disallow `event.currentTarget` calls inside of async functions (`github/async-currenttarget`) 💼 This rule is enabled in the 🔍 `browser` config. ## Rule Details Accessing `event.currentTarget` inside an `async function()` will likely be `null` as `currentTarget` is mutated as the event is propagated. 1. A `click` event is dispatched 2. The handler is invoked once with the expected `currentTarget` 3. An `await` defers the execution 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 5. The async function resumes 6. `event.currentTarget` is now `null` If you're using `async`, you'll need to synchronously create a reference to `currentTarget` before any async activity. 👎 Examples of **incorrect** code for this rule: ```js document.addEventListener('click', async function (event) { // event.currentTarget will be an HTMLElement const url = event.currentTarget.getAttribute('data-url') const data = await fetch(url) // But now, event.currentTarget will be null const text = event.currentTarget.getAttribute('data-text') // ... }) ``` 👍 Examples of **correct** code for this rule: ```js document.addEventListener('click', function (event) { const currentTarget = event.currentTarget const url = currentTarget.getAttribute('data-url') // call async IIFE ;(async function () { const data = await fetch(url) const text = currentTarget.getAttribute('data-text') // ... })() }) ``` Alternatively, extract a function to create an element reference. ```js document.addEventListener('click', function (event) { fetchData(event.currentTarget) }) async function fetchData(el) { const url = el.getAttribute('data-url') const data = await fetch(url) const text = el.getAttribute('data-text') // ... } ``` ## Version 4.3.2 ================================================ FILE: docs/rules/async-preventdefault.md ================================================ # Disallow `event.preventDefault` calls inside of async functions (`github/async-preventdefault`) 💼 This rule is enabled in the 🔍 `browser` config. Using `event.preventDefault()` inside an `async function()` won't likely work as you'd expect because synchronous nature of event dispatch. ## Rule Details 1. A `click` event is dispatched 2. This handler is scheduled but not ran immediately because its marked async. 3. The event dispatch completes and nothing has called `preventDefault()` _yet_ and the default click behavior occurs. 4. The async function is scheduled and runs. 5. Calling `preventDefault()` is now a no-op as the synchronous event dispatch has already completed. 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. 👎 Examples of **incorrect** code for this rule: ```js document.addEventListener('click', async function (event) { const data = await fetch() event.preventDefault() }) ``` 👍 Examples of **correct** code for this rule: ```js document.addEventListener('click', function (event) { // preventDefault in a regular function event.preventDefault() // call async helper function loadData(event.target) }) async function loadData(el) { const data = await fetch() // ... } ``` This could also be done with an async IIFE. ```js document.addEventListener('click', function (event) { // preventDefault in a regular function event.preventDefault() // call async IIFE ;(async function () { const data = await fetch() // ... })() }) ``` ## Version 4.3.2 ================================================ FILE: docs/rules/authenticity-token.md ================================================ # Disallow usage of CSRF tokens in JavaScript (`github/authenticity-token`) 💼 This rule is enabled in the 🔐 `internal` config. ## Rule Details The Rails `form_tag` helper creates a `
` element with a `
``` 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. `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. [@defunkt's original proposal from 2010](https://web.archive.org/web/20180902223055/http://ozmm.org/posts/slightly_obtrusive_javascript.html). 👎 Examples of **incorrect** code for this rule: ```js const el = document.querySelector('.js-Foo') ``` 👍 Examples of **correct** code for this rule: ```js const el = document.querySelector('.js-foo') ``` ## Version 4.3.2 ================================================ FILE: docs/rules/no-blur.md ================================================ # Disallow usage of `Element.prototype.blur()` (`github/no-blur`) 💼 This rule is enabled in the 🔍 `browser` config. 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. ## Rule Details - [Use of `blur()` is discouraged by WHATWG HTML spec](https://html.spec.whatwg.org/multipage/interaction.html#dom-blur) 👎 Examples of **incorrect** code for this rule: ```js menu.addEventListener('close', () => { input.blur() }) ``` 👍 Examples of **correct** code for this rule: ```js menu.addEventListener('open', () => { const previouslyFocusedElement = document.activeElement input.focus() menu.addEventListener('close', () => { previouslyFocusedElement.focus() }) }) ``` ## Version 4.3.2 ================================================ FILE: docs/rules/no-d-none.md ================================================ # Disallow usage the `d-none` CSS class (`github/no-d-none`) 💼 This rule is enabled in the 🔐 `internal` config. ## Rule Details Ideally JavaScript behaviors should not rely on Primer CSS when the `hidden` property can be used. 👎 Examples of **incorrect** code for this rule: ```js div.classList.add('d-none') ``` 👍 Examples of **correct** code for this rule: ```js div.hidden = false ``` ## Version 4.3.2 ================================================ FILE: docs/rules/no-dataset.md ================================================ # Enforce usage of `Element.prototype.getAttribute` instead of `Element.prototype.datalist` (`github/no-dataset`) 💼 This rule is enabled in the 🔍 `browser` config. ## Rule Details 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')`. 👎 Examples of **incorrect** code for this rule: ```js el.dataset.coolThing ``` 👍 Examples of **correct** code for this rule: ```js el.getAttribute('data-cool-thing') ``` ## Version 4.3.2 ================================================ FILE: docs/rules/no-dynamic-script-tag.md ================================================ # Disallow creating dynamic script tags (`github/no-dynamic-script-tag`) 💼 This rule is enabled in the ✅ `recommended` config. ## Rule Details Creating dynamic script tags bypasses a lot of security measures - like SRIs - and pose a potential threat to your application. Instead of creating a `script` tag in the client, provide all necessary `script` tags in the page's HTML. 👎 Examples of **incorrect** code for this rule: ```js document.createElement('script') document.getElementById('some-id').type = 'text/javascript' ``` 👍 Examples of **correct** code for this rule: ```html