Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: github/eslint-plugin-github
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v4.9.0
Choose a base ref
...
head repository: github/eslint-plugin-github
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v4.9.1
Choose a head ref

Commits on Jul 13, 2023

  1. Copy the full SHA
    af415a6 View commit details

Commits on Jul 14, 2023

  1. Merge pull request #455 from github/remove-false-positive-for-innerte…

    …xt-rule
    
    Ignore calls to a method named innerText
    camchenry authored Jul 14, 2023
    Copy the full SHA
    c3b0621 View commit details
  2. Partially verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
    Copy the full SHA
    11adedf View commit details
  3. Merge pull request #457 from github/bump-prettier

    bump `prettier` and `eslint-plugin-prettier` to latest versions
    shiftkey authored Jul 14, 2023

    Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    6eed534 View commit details
  4. chore(deps): bump the all-dependencies group with 5 updates

    Bumps the all-dependencies group with 5 updates:
    
    | Package | Update |
    | --- | --- |
    | [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | 5.59.2 to 6.0.0 |
    | [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | 5.59.2 to 6.0.0 |
    | [jsx-ast-utils](https://github.com/jsx-eslint/jsx-ast-utils) | 3.3.3 to 3.3.4 |
    | [eslint](https://github.com/eslint/eslint) | 8.40.0 to 8.45.0 |
    | [eslint-plugin-eslint-plugin](https://github.com/eslint-community/eslint-plugin-eslint-plugin) | 5.0.8 to 5.1.0 |
    
    
    Updates `@typescript-eslint/eslint-plugin` from 5.59.2 to 6.0.0
    - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
    - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
    - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v6.0.0/packages/eslint-plugin)
    
    Updates `@typescript-eslint/parser` from 5.59.2 to 6.0.0
    - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
    - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
    - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v6.0.0/packages/parser)
    
    Updates `jsx-ast-utils` from 3.3.3 to 3.3.4
    - [Release notes](https://github.com/jsx-eslint/jsx-ast-utils/releases)
    - [Changelog](https://github.com/jsx-eslint/jsx-ast-utils/blob/main/CHANGELOG.md)
    - [Commits](jsx-eslint/jsx-ast-utils@v3.3.3...v3.3.4)
    
    Updates `eslint` from 8.40.0 to 8.45.0
    - [Release notes](https://github.com/eslint/eslint/releases)
    - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
    - [Commits](eslint/eslint@v8.40.0...v8.45.0)
    
    Updates `eslint-plugin-eslint-plugin` from 5.0.8 to 5.1.0
    - [Release notes](https://github.com/eslint-community/eslint-plugin-eslint-plugin/releases)
    - [Changelog](https://github.com/eslint-community/eslint-plugin-eslint-plugin/blob/main/CHANGELOG.md)
    - [Commits](eslint-community/eslint-plugin-eslint-plugin@v5.0.8...v5.1.0)
    
    ---
    updated-dependencies:
    - dependency-name: "@typescript-eslint/eslint-plugin"
      dependency-type: direct:production
      update-type: version-update:semver-major
      dependency-group: all-dependencies
    - dependency-name: "@typescript-eslint/parser"
      dependency-type: direct:production
      update-type: version-update:semver-major
      dependency-group: all-dependencies
    - dependency-name: jsx-ast-utils
      dependency-type: direct:production
      update-type: version-update:semver-patch
      dependency-group: all-dependencies
    - dependency-name: eslint
      dependency-type: direct:development
      update-type: version-update:semver-minor
      dependency-group: all-dependencies
    - dependency-name: eslint-plugin-eslint-plugin
      dependency-type: direct:development
      update-type: version-update:semver-minor
      dependency-group: all-dependencies
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored Jul 14, 2023
    Copy the full SHA
    f78ea4c View commit details
  5. Merge pull request #458 from github/dependabot/npm_and_yarn/all-depen…

    …dencies-a854cd774b
    
    chore(deps): bump the all-dependencies group with 5 updates
    jfuchs authored Jul 14, 2023
    Copy the full SHA
    209d482 View commit details

Commits on Jul 17, 2023

  1. Copy the full SHA
    5253542 View commit details
  2. Copy the full SHA
    f54eae3 View commit details
  3. use literal prop value method

    khiga8 committed Jul 17, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    9bd7c06 View commit details
  4. add tests

    khiga8 committed Jul 17, 2023
    Copy the full SHA
    c485717 View commit details
  5. Copy the full SHA
    43bdeaf View commit details
  6. Merge pull request #460 from github/kh-temp-override-grid-cell

    Set config override for false positive rule
    khiga8 authored Jul 17, 2023
    Copy the full SHA
    83f90f0 View commit details
  7. Copy the full SHA
    e95423d View commit details
  8. Copy the full SHA
    30cf0d9 View commit details
  9. Fix lint issue

    khiga8 committed Jul 17, 2023
    Copy the full SHA
    20a6c45 View commit details
  10. Merge pull request #459 from github/kh-reintroduce-deleted-lint-command

    Re-introduce accidentally removed testing commands
    khiga8 authored Jul 17, 2023
    Copy the full SHA
    8385442 View commit details
  11. Copy the full SHA
    6896b71 View commit details
  12. Merge pull request #461 from github/kh-fix-bug-with-methods

    Fix bugs with `getRole` and `getElementType`
    khiga8 authored Jul 17, 2023
    Copy the full SHA
    8abef65 View commit details
  13. Copy the full SHA
    7036141 View commit details
  14. Do not look up as prop

    kendallgassner committed Jul 17, 2023
    Copy the full SHA
    b05b0cb View commit details
  15. add test

    kendallgassner committed Jul 17, 2023
    Copy the full SHA
    29ead03 View commit details
  16. lint

    kendallgassner committed Jul 17, 2023
    Copy the full SHA
    ee9e16d View commit details
  17. Merge pull request #464 from github/fix-a11y-no-title-attribute

    [Fix] Only look at semantic elements for `a11y-no-title-attribute`
    kendallgassner authored Jul 17, 2023
    Copy the full SHA
    125ac51 View commit details
  18. Copy the full SHA
    8c5f809 View commit details
  19. Merge pull request #463 from github/kh-use-prop-value

    Check for presence of attribute in `getRole` rather than the value
    khiga8 authored Jul 17, 2023
    Copy the full SHA
    0c0441f View commit details
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -87,7 +87,7 @@ This config will be interpreted in the following way:
| [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) | Guards against developers using the title attribute | ⚛️ | | |
| [a11y-no-visually-hidden-interactive-element](docs/rules/a11y-no-visually-hidden-interactive-element.md) | Ensures 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-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) | SVGs must 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 | 🔍 | | |
8 changes: 8 additions & 0 deletions lib/configs/react.js
Original file line number Diff line number Diff line change
@@ -22,5 +22,13 @@ module.exports = {
words: ['this', 'more', 'read here', 'read more'],
},
],
'jsx-a11y/no-interactive-element-to-noninteractive-role': [
'error',
{
tr: ['none', 'presentation'],
td: ['cell'], // TODO: Remove once https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/pull/937#issuecomment-1638128318 is addressed.
canvas: ['img'],
},
],
},
}
2 changes: 1 addition & 1 deletion lib/rules/a11y-no-title-attribute.js
Original file line number Diff line number Diff line change
@@ -50,7 +50,7 @@ module.exports = {
create(context) {
return {
JSXElement: node => {
const elementType = getElementType(context, node.openingElement)
const elementType = getElementType(context, node.openingElement, true)
if (elementType !== `iframe` && ifSemanticElement(context, node)) {
const titleProp = getPropValue(getProp(node.openingElement.attributes, `title`))
if (titleProp) {
4 changes: 4 additions & 0 deletions lib/rules/no-innerText.js
Original file line number Diff line number Diff line change
@@ -12,6 +12,10 @@ module.exports = {
create(context) {
return {
MemberExpression(node) {
// If the member expression is part of a call expression like `.innerText()` then it is not the same
// as the `Element.innerText` property, and should not trigger a warning
if (node.parent.type === 'CallExpression') return

if (node.property && node.property.name === 'innerText') {
context.report({
meta: {
12 changes: 8 additions & 4 deletions lib/utils/get-element-type.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const {elementType, getProp, getPropValue} = require('jsx-ast-utils')
const {elementType, getProp, getLiteralPropValue} = require('jsx-ast-utils')

/*
Allows custom component to be mapped to an element type.
@@ -7,15 +7,19 @@ If a prop determines the type, it can be specified with `props`.
For now, we only support the mapping of one prop type to an element type, rather than combinations of props.
*/
function getElementType(context, node, ignoreMap = false) {
function getElementType(context, node, lazyElementCheck = false) {
const {settings} = context

if (lazyElementCheck) {
return elementType(node)
}

// check if the node contains a polymorphic prop
const polymorphicPropName = settings?.github?.polymorphicPropName ?? 'as'
const rawElement = getPropValue(getProp(node.attributes, polymorphicPropName)) ?? elementType(node)
const rawElement = getLiteralPropValue(getProp(node.attributes, polymorphicPropName)) ?? elementType(node)

// if a component configuration does not exists, return the raw element
if (ignoreMap || !settings?.github?.components?.[rawElement]) return rawElement
if (!settings?.github?.components?.[rawElement]) return rawElement

const defaultComponent = settings.github.components[rawElement]

10 changes: 5 additions & 5 deletions lib/utils/get-role.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const {getProp, getPropValue} = require('jsx-ast-utils')
const {getProp, getLiteralPropValue} = require('jsx-ast-utils')
const {elementRoles} = require('aria-query')
const {getElementType} = require('./get-element-type')
const ObjectMap = require('./object-map')
@@ -43,7 +43,7 @@ function cleanElementRolesMap() {
*/
function getRole(context, node) {
// Early return if role is explicitly set
const explicitRole = getPropValue(getProp(node.attributes, 'role'))
const explicitRole = getLiteralPropValue(getProp(node.attributes, 'role'))
if (explicitRole) {
return explicitRole
}
@@ -80,8 +80,8 @@ function getRole(context, node) {
continue
}

const value = getPropValue(propOnNode)
if (value || (value === '' && prop === 'alt')) {
const value = getLiteralPropValue(propOnNode)
if (propOnNode) {
if (
prop === 'href' ||
prop === 'aria-labelledby' ||
@@ -90,7 +90,7 @@ function getRole(context, node) {
(prop === 'alt' && value !== '')
) {
key.attributes.push({name: prop, constraints: ['set']})
} else {
} else if (value || (value === '' && prop === 'alt')) {
key.attributes.push({name: prop, value})
}
}
1,627 changes: 1,414 additions & 213 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@
"lint:eslint-docs": "npm run update:eslint-docs -- --check",
"lint:js": "eslint .",
"pretest": "mkdir -p node_modules/ && ln -fs $(pwd) node_modules/",
"test": "mocha tests/**/*.js tests/",
"test": "npm run eslint-check && npm run lint && mocha tests/**/*.js tests/",
"update:eslint-docs": "eslint-doc-generator"
},
"repository": {
@@ -30,8 +30,8 @@
"homepage": "https://github.com/github/eslint-plugin-github#readme",
"dependencies": {
"@github/browserslist-config": "^1.0.0",
"@typescript-eslint/eslint-plugin": "^5.1.0",
"@typescript-eslint/parser": "^5.1.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"aria-query": "^5.3.0",
"eslint-config-prettier": ">=8.0.0",
"eslint-plugin-escompat": "^3.3.3",
@@ -41,10 +41,10 @@
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-no-only-tests": "^3.0.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-rule-documentation": ">=1.0.0",
"jsx-ast-utils": "^3.3.2",
"prettier": "^2.2.1",
"prettier": "^3.0.0",
"svg-element-attributes": "^1.3.1"
},
"prettier": "@github/prettier-config",
15 changes: 4 additions & 11 deletions tests/a11y-no-title-attribute.js
Original file line number Diff line number Diff line change
@@ -40,20 +40,13 @@ ruleTester.run('a11y-no-title-attribute', rule, {
},
},
},
{
// Note: we are only checking semantic elements. We cannot make assumptions about how a React Components is using the title prop.
code: '<Link as="a" title="some title">Submit</Link>',
},
],
invalid: [
{code: '<a title="some title" href="github.com">GitHub</a>', errors: [{message: errorMessage}]},
{code: '<span><button title="some title">submit</button></span>', errors: [{message: errorMessage}]},
{
code: '<Component as="a" title="some title">Submit</Component>',
errors: [{message: errorMessage}],
settings: {
github: {
components: {
Component: 'iframe',
},
},
},
},
],
})
16 changes: 12 additions & 4 deletions tests/a11y-role-supports-aria-props.js
Original file line number Diff line number Diff line change
@@ -35,8 +35,11 @@ ruleTester.run('a11y-role-supports-aria-props', rule, {
{code: '<div role />'},
{code: '<div role="presentation" {...props} />'},
{code: '<Foo.Bar baz={true} />'},
{code: '<Foo as="a" href={url} aria-label={`Issue #${title}`} />'},
{code: '<Link href="#" aria-checked />'},

// Don't try to evaluate expression
{code: '<Box aria-labelledby="some-id" role={role} />'},
{code: '<Box aria-labelledby="some-id" as={isNavigationOpen ? "div" : "nav"} />'},
// IMPLICIT ROLE TESTS
// A TESTS - implicit role is `link`
{code: '<a href="#" aria-expanded />'},
@@ -479,12 +482,17 @@ ruleTester.run('a11y-role-supports-aria-props', rule, {
errors: [getErrorMessage('aria-labelledby', 'generic')],
},
{
code: '<div aria-label />',
errors: [getErrorMessage('aria-label', 'generic')],
code: '<div aria-labelledby />',
errors: [getErrorMessage('aria-labelledby', 'generic')],
},
// Determines role from literal `as` prop.
{
code: '<div aria-labelledby />',
code: '<Box as="span" aria-labelledby />',
errors: [getErrorMessage('aria-labelledby', 'generic')],
},
{
code: '<p role="generic" aria-label />',
errors: [getErrorMessage('aria-label', 'generic')],
},
],
})
4 changes: 4 additions & 0 deletions tests/no-innerText.js
Original file line number Diff line number Diff line change
@@ -11,6 +11,10 @@ ruleTester.run('no-innerText', rule, {
{
code: 'document.querySelector("js-flash-text").textContent = "bar"',
},
{
// This is unrelated to the `HTMLElement.innerText` property, and should not trigger a warning
code: 'var text = element.textContent()',
},
],
invalid: [
{
10 changes: 9 additions & 1 deletion tests/utils/get-element-type.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const {getElementType} = require('../../lib/utils/get-element-type')
const {mockJSXAttribute, mockJSXOpeningElement} = require('./mocks')
const {mockJSXAttribute, mockJSXConditionalAttribute, mockJSXOpeningElement} = require('./mocks')

const mocha = require('mocha')
const describe = mocha.describe
@@ -55,4 +55,12 @@ describe('getElementType', function () {
const node = mockJSXOpeningElement('Link', [mockJSXAttribute('as', 'Button')])
expect(getElementType(setting, node)).to.equal('button')
})

it('returns raw type when polymorphic prop is set to non-literal expression', function () {
// <Box as={isNavigationOpen ? 'generic' : 'navigation'} />
const node = mockJSXOpeningElement('Box', [
mockJSXConditionalAttribute('as', 'isNavigationOpen', 'generic', 'navigation'),
])
expect(getElementType({}, node)).to.equal('Box')
})
})
43 changes: 42 additions & 1 deletion tests/utils/get-role.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,31 @@
const {getRole} = require('../../lib/utils/get-role')
const {mockJSXAttribute, mockJSXOpeningElement} = require('./mocks')
const {mockJSXAttribute, mockJSXConditionalAttribute, mockJSXOpeningElement} = require('./mocks')
const mocha = require('mocha')
const describe = mocha.describe
const it = mocha.it
const expect = require('chai').expect

describe('getRole', function () {
it('returns undefined when polymorphic prop is set with a non-literal expression', function () {
// <Box as={isNavigationOpen ? 'div' : 'nav'} />
const node = mockJSXOpeningElement('Box', [mockJSXConditionalAttribute('as', 'isNavigationOpen', 'div', 'nav')])
expect(getRole({}, node)).to.equal(undefined)
})

it('returns undefined when role is set to non-literal expression', function () {
// <Box role={isNavigationOpen ? 'generic' : 'navigation'} />
const node = mockJSXOpeningElement('Box', [
mockJSXConditionalAttribute('role', 'isNavigationOpen', 'generic', 'navigation'),
])
expect(getRole({}, node)).to.equal(undefined)
})

it('returns `role` when set to a literal expression', function () {
// <Box role="generic" />
const node = mockJSXOpeningElement('Box', [mockJSXAttribute('role', 'generic')])
expect(getRole({}, node)).to.equal('generic')
})

it('returns generic role for <span> regardless of attribute', function () {
const node = mockJSXOpeningElement('span', [mockJSXAttribute('aria-label', 'something')])
expect(getRole({}, node)).to.equal('generic')
@@ -26,6 +46,27 @@ describe('getRole', function () {
expect(getRole({}, node)).to.equal('link')
})

it('returns link role for <Foo> with polymorphic prop set to "a" and conditional href', function () {
const node = mockJSXOpeningElement('Foo', [
mockJSXAttribute('as', 'a'),
mockJSXConditionalAttribute('href', 'getUrl', '#', 'https://github.com/'),
])
expect(getRole({}, node)).to.equal('link')
})

it('returns link role for <Foo> with polymorphic prop set to "a" and literal href', function () {
const node = mockJSXOpeningElement('Foo', [
mockJSXAttribute('as', 'a'),
mockJSXAttribute('href', 'https://github.com/'),
])
expect(getRole({}, node)).to.equal('link')
})

it('returns generic role for <Foo> with polymorphic prop set to "a" and no href', function () {
const node = mockJSXOpeningElement('Foo', [mockJSXAttribute('as', 'a')])
expect(getRole({}, node)).to.equal('generic')
})

it('returns region role for <section> with aria-label', function () {
const node = mockJSXOpeningElement('section', [mockJSXAttribute('aria-label', 'something')])
expect(getRole({}, node)).to.equal('region')
34 changes: 33 additions & 1 deletion tests/utils/mocks.js
Original file line number Diff line number Diff line change
@@ -12,6 +12,38 @@ function mockJSXAttribute(prop, propValue) {
}
}

/* Mocks conditional expression
e.g. <Box as={isNavigationOpen ? 'generic' : 'navigation'} /> can be by calling
mockJSXConditionalAttribute('as', 'isNavigationOpen', 'generic', 'navigation')
*/
function mockJSXConditionalAttribute(prop, expression, consequentValue, alternateValue) {
return {
type: 'JSXAttribute',
name: {
type: 'JSXIdentifier',
name: prop,
},
value: {
type: 'JSXExpressionContainer',
value: prop,
expression: {
type: 'ConditionalExpression',
test: {
type: expression,
},
consequent: {
type: 'Literal',
value: consequentValue,
},
alternate: {
type: 'Literal',
value: alternateValue,
},
},
},
}
}

function mockJSXOpeningElement(tagName, attributes = []) {
return {
type: 'JSXOpeningElement',
@@ -23,4 +55,4 @@ function mockJSXOpeningElement(tagName, attributes = []) {
}
}

module.exports = {mockJSXAttribute, mockJSXOpeningElement}
module.exports = {mockJSXAttribute, mockJSXOpeningElement, mockJSXConditionalAttribute}