Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ESLint plugin 🦨 #2044

Open
wants to merge 38 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
7e2204b
Create few-years-stare.md
TheSonOfThomp Oct 23, 2023
00efab4
scaffolds eslint plugin
TheSonOfThomp Oct 23, 2023
47df2c9
scaffold test rule
TheSonOfThomp Oct 23, 2023
2c495dc
scaffold jsx rules
TheSonOfThomp Oct 23, 2023
ad83714
TS & JSX scaffold
TheSonOfThomp Oct 24, 2023
c974ba4
rule creator script
TheSonOfThomp Oct 24, 2023
a7d135e
Adds prebuild step
TheSonOfThomp Oct 24, 2023
ea769cb
resolves TS issues
TheSonOfThomp Oct 24, 2023
1a40e4c
Update buildRulesIndex.ts
TheSonOfThomp Oct 24, 2023
e468e71
fixes TS resolution
TheSonOfThomp Oct 25, 2023
5df5376
update rollup. mv ruletester
TheSonOfThomp Oct 25, 2023
883bd29
Creates standard-testid lint rule
TheSonOfThomp Oct 25, 2023
68b63bd
mv deepOmit
TheSonOfThomp Oct 25, 2023
b49cd58
Update createNewRule.ts
TheSonOfThomp Oct 25, 2023
f992a80
creates 'boolean-verb-prefix',
TheSonOfThomp Oct 25, 2023
6ced66c
fix tests
TheSonOfThomp Oct 26, 2023
214d1da
exclude tests from build
TheSonOfThomp Oct 26, 2023
a029595
update rule creator script
TheSonOfThomp Oct 26, 2023
4176537
updates readme
TheSonOfThomp Oct 26, 2023
afabeab
Update package.json
TheSonOfThomp Oct 26, 2023
8eba752
changesets
TheSonOfThomp Oct 26, 2023
e2a3e3b
rename rule-tester
TheSonOfThomp Oct 26, 2023
5758f6b
Create rude-scissors-deliver.md
TheSonOfThomp Oct 26, 2023
6dd1ffd
creates no-indirect-imports rule
TheSonOfThomp Oct 26, 2023
854469f
fix
TheSonOfThomp Oct 27, 2023
d128fef
updates docs
TheSonOfThomp Oct 27, 2023
611c10e
prettier
TheSonOfThomp Oct 27, 2023
d3836cc
Update react17 jest.config to omit eslint-plugin
TheSonOfThomp Oct 27, 2023
c1c84fd
Update yarn.lock
TheSonOfThomp Oct 27, 2023
f657606
fix default prefix
TheSonOfThomp Oct 27, 2023
df08d90
turns off rules by default
TheSonOfThomp Oct 31, 2023
1f1be18
rm config
TheSonOfThomp Nov 1, 2023
5dd551d
add comments
TheSonOfThomp Feb 15, 2024
4863f88
enables "no-indirect-imports"
TheSonOfThomp Feb 15, 2024
ba80868
address SL comments
TheSonOfThomp Mar 13, 2024
8d87382
bump lib deps
TheSonOfThomp Oct 16, 2024
ccf4a6c
Update yarn.lock
TheSonOfThomp Oct 16, 2024
7f081fd
bump build tool
TheSonOfThomp Oct 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/few-years-stare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lg-tools/meta': patch
---

Adds `getPackageJson` utility
5 changes: 5 additions & 0 deletions .changeset/popular-timers-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lg-tools/eslint-plugin': minor
---

First pre-release of `eslint-plugin`
5 changes: 5 additions & 0 deletions .changeset/rude-scissors-deliver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lg-tools/validate': patch
---

Updates the logic to check whether a dependency is used in a development file
5 changes: 5 additions & 0 deletions .changeset/sour-zebras-exercise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lg-tools/lint': minor
---

Adds `@lg-tools/eslint-plugin`
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
node_modules
dist
lib
coverage/
packages/icon/src/glyphs/*.svg
packages/icon/src/generated/*.tsx
storybook-static
Expand Down
2 changes: 1 addition & 1 deletion charts/line-chart/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
"dependencies": {
"@leafygreen-ui/emotion": "^4.0.7",
"@leafygreen-ui/lib": "^12.0.0",
"@leafygreen-ui/lib": "^13.7.0",
"@leafygreen-ui/palette": "^4.1.1",
"@leafygreen-ui/tokens": "^2.11.0",
"@lg-tools/storybook-utils": "^0.1.1",
Expand Down
2 changes: 1 addition & 1 deletion chat/rich-links/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@leafygreen-ui/emotion": "^4.0.7",
"@leafygreen-ui/icon": "^12.6.0",
"@leafygreen-ui/leafygreen-provider": "^3.1.12",
"@leafygreen-ui/lib": "^12.0.0",
"@leafygreen-ui/lib": "^13.7.0",
"@leafygreen-ui/palette": "^4.1.0",
"@leafygreen-ui/polymorphic": "^2.0.2",
"@leafygreen-ui/tokens": "^2.11.0",
Expand Down
110 changes: 110 additions & 0 deletions tools/eslint-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# eslint-plugin-leafygreen

Lint Rules for LeafyGreen UI

## Installation

You'll first need to install [ESLint](https://eslint.org/):

```sh
yarn add -D eslint
```

Next, install `@lg-tools/eslint-plugin`:

```sh
yarn add -D @lg-tools/eslint-plugin
```

## Usage

Add `@lg-tools` to the plugins section of your `.eslintrc` configuration file. (You can omit the `/eslint-plugin` suffix):

```json
{
"plugins": ["@lg-tools"],
"extends": ["plugin:@lg-tools/internal"]
}
```

Optionally configure the rules you want to use under the rules section.

```json
{
"rules": {
"@lg-tools/some-rule": ["warn"]
}
}
```

## Rules

<!-- begin auto-generated rules list -->

⚠️ Configurations set to warn in.\
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
💡 Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).

| Name                | Description | ⚠️ | 🔧 | 💡 |
| :------------------------------------------------------- | :----------------------------------------------------------------------- | :------------------ | :-- | :-- |
| [boolean-verb-prefix](docs/rules/boolean-verb-prefix.md) | Enforce prefixing boolean variables & properties with a conditional verb | ![badge-internal][] | | 💡 |
| [no-indirect-imports](docs/rules/no-indirect-imports.md) | Forbid indirect imports from `src/` or `packages/` | ![badge-internal][] | 🔧 | |
| [standard-testid](docs/rules/standard-testid.md) | Enforce a consistent prefix for hard-coded `data-testid` attributes | ![badge-internal][] | 🔧 | |

<!-- end auto-generated rules list -->

## Contributing

To create a new rule:

### 1. Run the `create-rule` script

```sh
yarn workspace @lg-tools/eslint-plugin run create-rule <rule-name>
```

or, with npm:

```sh
cd tools/eslint-plugin && npm run create-rule <rule-name>
```

This will create a new file in `src/rules`, and a test file in `src/tests`

### 2. Add AST Listeners

Inside your new `src/rules/<my-rule>.ts` file, add the appropriate metadata, and write AST listeners.

The return object of a rule's `create` method should return at least one AST listener.

These work similar to CSS selectors, and run a function when the ESLint static analyzer hits a specific AST node.

See [ESLint Docs](https://eslint.org/docs/latest/extend/custom-rules) for more details.

```ts
export const exampleRule = createRule({
// ...
create: context => {
return {
VariableDeclaration: node => {
// Executes on any variable declaration
// e.g. const myVar = 5;
},
};
},
});
```

## 3. Add tests

Inside the new `src/test/<my-rule>.spec.ts` file, add valid and invalid test cases.

See [ESLint RuleTester docs](https://eslint.org/docs/latest/integrate/nodejs-api#ruletester) for more details.

### Useful Resources

- [ESLint Docs](https://eslint.org/docs/latest/extend/custom-rules)
- [ESLint RuleTester](https://eslint.org/docs/latest/integrate/nodejs-api#ruletester)
- [ESTree Spec](https://github.com/estree/estree/blob/master/README.md)
- [JSX Spec](https://github.com/facebook/jsx/blob/main/README.md)
- [TSESLint Listener Definitions](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/utils/src/ts-eslint/Rule.ts#L293)
18 changes: 18 additions & 0 deletions tools/eslint-plugin/docs/rules/boolean-verb-prefix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Enforce prefixing boolean variables & properties with a conditional verb (`@lg-tools/boolean-verb-prefix`)

⚠️ This rule _warns_ in the `internal` config.

💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).

<!-- end auto-generated rule header -->

## Options

<!-- begin auto-generated rule options list -->

| Name | Description | Type | Default |
| :---------------- | :-------------------------------------------------------------- | :---- | :------ |
| `additionalVerbs` | Additional verbs to allow as prefixes to boolean variable names | Array | `` |
| `allowVarNames` | Un-prefixed variable names that should be allowed | Array | `` |

<!-- end auto-generated rule options list -->
7 changes: 7 additions & 0 deletions tools/eslint-plugin/docs/rules/no-indirect-imports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Forbid indirect imports from `src/` or `packages/` (`@lg-tools/no-indirect-imports`)

⚠️ This rule _warns_ in the `internal` config.

🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).

<!-- end auto-generated rule header -->
17 changes: 17 additions & 0 deletions tools/eslint-plugin/docs/rules/standard-testid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Enforce a consistent prefix for hard-coded `data-testid` attributes (`@lg-tools/standard-testid`)

⚠️ This rule _warns_ in the `internal` config.

🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).

<!-- end auto-generated rule header -->

## Options

<!-- begin auto-generated rule options list -->

| Name | Description | Type | Default |
| :------- | :---------------------------------- | :----- | :------ |
| `prefix` | Prefix for `data-testid` attributes | String | `lg-` |

<!-- end auto-generated rule options list -->
41 changes: 41 additions & 0 deletions tools/eslint-plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@lg-tools/eslint-plugin",
"version": "0.0.1",
"description": "ESLint Rules for LeafyGreen",
"keywords": [
"eslint",
"eslintplugin",
"eslint-plugin",
"leafygreen"
],
"main": "./dist/index.js",
"exports": "./dist/index.js",
"scripts": {
"postinstall": "npx ts-node ./scripts/buildRulesIndex.ts",
"build": "lg-internal-build-package",
"tsc": "tsc --build tsconfig.json",
"docs": "eslint-doc-generator",
"lint": "npm-run-all \"lint:*\"",
"lint:eslint-docs": "npm-run-all \"update:eslint-docs -- --check\"",
"lint:js": "eslint .",
"create-rule": "npx ts-node ./scripts/createNewRule.ts"
},
"dependencies": {
"@typescript-eslint/types": "6.9.0",
"@typescript-eslint/utils": "6.9.0",
"eslint": "8.43.0",
"lodash": "^4.17.21"
},
"devDependencies": {
"@lg-tools/build": "0.6.0",
"@types/eslint": "^8.44.6",
"@typescript-eslint/rule-tester": "6.9.0",
"commander": "^11.0.0",
"eslint-doc-generator": "^1.5.2",
"fs-extra": "11.1.1"
},
"peerDependencies": {
"eslint": "^8.43.0"
},
"license": "Apache-2.0"
}
8 changes: 8 additions & 0 deletions tools/eslint-plugin/rollup.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { umdConfig } from '@lg-tools/build/config/rollup.config.mjs';

const config = {
...umdConfig,
external: [...umdConfig.external, /^@typescript-eslint\//],
};

export default [config];
43 changes: 43 additions & 0 deletions tools/eslint-plugin/scripts/buildRulesIndex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import fse from 'fs-extra';
import path from 'path';

import { makeId, makeVarName } from './utils';

buildRulesIndexFile();

/**
* Utility function that creates or updates the Rules index file
* after a new Rule is created
*/
export function buildRulesIndexFile() {
const rulesDir = path.resolve(__dirname, '../src/rules');
fse.readdir(rulesDir).then(files => {
files = files.filter(file => file !== 'index.ts');
const importStatements = files
.map(fileName => {
const fileId = makeId(fileName.replace('.ts', ''));
return `import { ${makeVarName(fileId)} } from './${fileId}';`;
})
.join('\n');

const declarations = files
.map(fileName => {
const fileId = makeId(fileName.replace('.ts', ''));
return ` '${fileId}': ${makeVarName(fileId)},`;
})
.join('\n');

const indexContent = `/**
* DO NOT MODIFY THIS FILE
* ANY CHANGES WILL BE REMOVED ON THE NEXT BUILD
*/
${importStatements}

export const rules = {
${declarations}
};
`;

fse.writeFile(path.resolve(rulesDir, 'index.ts'), indexContent);
});
}
77 changes: 77 additions & 0 deletions tools/eslint-plugin/scripts/createNewRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Command } from 'commander';
import fse from 'fs-extra';
import path from 'path';

import { buildRulesIndexFile } from './buildRulesIndex';
import {
makeDefaultMessageId,
makeFileName,
makeId,
makeVarName,
} from './utils';

const cli = new Command('');
cli.argument('<rule-name>', 'The name of the rule');
cli.action(createNewRule);
cli.parse(process.argv);

/**
* Creates a new Rule within `eslint-plugin/src/rules`
*/
function createNewRule(ruleName: string) {
const rulesDir = path.resolve(__dirname, '../src/rules');
const testsDir = path.resolve(__dirname, '../src/tests');

const varName = makeVarName(ruleName);
const ruleId = makeId(ruleName);
const fileName = makeFileName(ruleName);
const msgId = makeDefaultMessageId(ruleName);

const ruleTemplate = `
import { createRule } from '../utils/createRule';

export const ${varName} = createRule({
name: '${ruleId}',
meta: {
type: 'suggestion',
messages: {
'${msgId}': '',
},
schema: [],
docs: {
description: '',
}
},
defaultOptions: [],
create: context => {
return {}
}
});
`;

const testTemplate = `
import { ${varName} } from '../rules/${fileName}';

import { ruleTester } from './utils/ruleTester.testutils';

ruleTester.run('${ruleId}', ${varName}, {
valid: [{
code: \`\`, // valid code snippet
}],
invalid: [{
code: \`\`, // code with lint errors
// output: '', // fixed code
errors: [{
messageId: '${msgId}',
}]
}]
});
`;

fse.writeFileSync(path.resolve(rulesDir, `${fileName}.ts`), ruleTemplate);
fse.writeFileSync(
path.resolve(testsDir, `${fileName}.spec.ts`),
testTemplate,
);
buildRulesIndexFile();
}
Loading
Loading