Skip to content

Commit

Permalink
add @elastic/eslint-plugin-eui package (#2218)
Browse files Browse the repository at this point in the history
* add `@elastic/eslint-plugin-eui` package

* Check for all relevant component names

* link to @elastic/eslint-plugin-eui readme from release docs

* fix link to readme

* add git repo and homepage to package.json
  • Loading branch information
Spencer authored Aug 15, 2019
1 parent b01ef08 commit 336666a
Show file tree
Hide file tree
Showing 11 changed files with 214 additions and 1 deletion.
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ test/
src-docs/
src-framer/
packages/react-datepicker
packages/eslint-plugin
.nvmrc

# typescript output
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"cross-env": "^5.2.0",
"css-loader": "^0.28.7",
"cssnano": "^4.0.5",
"dedent": "^0.7.0",
"dts-generator": "^2.1.0",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0",
Expand Down
1 change: 1 addition & 0 deletions packages/eslint-plugin/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rules/*.test.js
25 changes: 25 additions & 0 deletions packages/eslint-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# `@elastic/eslint-plugin-eui`

This package contains an eslint plugin that enforces some default rules for using EUI.

## Setup

1. install `@elastic/eslint-plugin-eui` as a dev dependency
2. extend `plugin:@elastic/eui/recommended` in your eslint config

## Rules

### `@elastic/eui/href-or-on-click`

`<EuiButton />` should either be a button or a link, for a11y purposes. When given an `href` the button behaves as a link, otherwise an `onClick` handler is expected and it will behave as a button.

In some cases it makes sense to disable this rule locally, such as when <kbd>cmd</kbd>+click should open the link in a new tab, but a standard click should use the `history.pushState()` API to change the URL without triggering a full page load.

## Publishing

This package is published separately from the rest of EUI, as required by eslint. The code is not transpiled, so make sure to use `require()` statements rather than `import`, and once the code is updated run:

1. `npm version patch|minor|major`
2. commit version bump
3. `npm publish` in this directory
4. push the version bump upstream
32 changes: 32 additions & 0 deletions packages/eslint-plugin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

module.exports = {
rules: {
'href-or-on-click': require('./rules/href_or_on_click'),
},
configs: {
recommended: {
plugins: ['@elastic/eslint-plugin-eui'],
rules: {
'@elastic/eui/href-or-on-click': 'error',
},
},
},
};
13 changes: 13 additions & 0 deletions packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "@elastic/eslint-plugin-eui",
"version": "0.0.1",
"license": "Apache-2.0",
"repository": {
"type" : "git",
"url" : "https://github.com/elastic/eui.git"
},
"homepage": "https://github.com/elastic/eui/blob/master/packages/eslint-plugin",
"peerDependencies": {
"eslint": "^5.0.0"
}
}
35 changes: 35 additions & 0 deletions packages/eslint-plugin/rules/href_or_on_click.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const componentNames = ['EuiButton', 'EuiButtonEmpty', 'EuiLink'];

module.exports = {
meta: {
fixable: null,
},
create(context) {
return {
JSXOpeningElement(node) {
if (
node.name.type !== 'JSXIdentifier' ||
!componentNames.includes(node.name.name)
) {
return;
}

const hasHref = node.attributes.some(
attr => attr.type === 'JSXAttribute' && attr.name.name === 'href'
);
const hasOnClick = node.attributes.some(
attr => attr.type === 'JSXAttribute' && attr.name.name === 'onClick'
);

if (hasHref && hasOnClick) {
context.report(
node,
`<${
node.name.name
}> accepts either \`href\` or \`onClick\`, not both.`
);
}
},
};
},
};
95 changes: 95 additions & 0 deletions packages/eslint-plugin/rules/href_or_on_click.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/* eslint-disable @typescript-eslint/no-var-requires */

const { RuleTester } = require('eslint');
const rule = require('./href_or_on_click');
const dedent = require('dedent');

const ruleTester = new RuleTester({
parser: 'babel-eslint',
parserOptions: {
ecmaVersion: 2018,
},
});

ruleTester.run('@elastic/eui/href-or-on-click', rule, {
valid: [
{
code: dedent(`
module.export = () => (
<EuiButton />
)
`),
},
{
code: dedent(`
module.export = () => (
<EuiButton href="/" />
)
`),
},
{
code: dedent(`
module.export = () => (
<EuiButton href={'/' + 'home'} />
)
`),
},
{
code: dedent(`
module.export = () => (
<EuiButton onClick={executeAction} />
)
`),
},
{
code: dedent(`
module.export = () => (
<EuiButton onClick={() => executeAction()} />
)
`),
},
],

invalid: [
{
code: dedent(`
module.export = () => (
<EuiButton href="/" onClick={fooBar} />
)
`),

errors: [
{
message: '<EuiButton> accepts either `href` or `onClick`, not both.',
},
],
},
{
code: dedent(`
module.export = () => (
<EuiButtonEmpty href="/" onClick={fooBar} />
)
`),

errors: [
{
message:
'<EuiButtonEmpty> accepts either `href` or `onClick`, not both.',
},
],
},
{
code: dedent(`
module.export = () => (
<EuiLink href="/" onClick={fooBar} />
)
`),

errors: [
{
message: '<EuiLink> accepts either `href` or `onClick`, not both.',
},
],
},
],
});
3 changes: 2 additions & 1 deletion scripts/jest/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"rootDir": "../../",
"roots": [
"<rootDir>/src/",
"<rootDir>/scripts/babel"
"<rootDir>/scripts/babel",
"<rootDir>/packages/eslint-plugin"
],
"collectCoverageFrom": [
"src/components/**/*.js",
Expand Down
4 changes: 4 additions & 0 deletions wiki/releasing-versions.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ That's it. The latest changes were published to GitHub, a new `git` tag now exis

<sup>_\* GitHub Pages sites are cached aggressively and can sometimes take a couple of minutes to update._</sup>

## `@elastic/eslint-plugin-eui`

For information on releasing the eslint plugin checkout the readme in [packages/eslint-plugin/README.md](../packages/eslint-plugin/README.md)

[docs]: https://elastic.github.io/eui/
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4112,6 +4112,11 @@ decompress-response@^3.2.0:
dependencies:
mimic-response "^1.0.0"

dedent@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=

deep-eql@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df"
Expand Down

0 comments on commit 336666a

Please sign in to comment.