Skip to content

Commit e4ae67f

Browse files
tatethurstonTate
and
Tate
authored
Tate/v1.0.0 (#3)
* Revert "migrate to eslint-plugin-react internals (#1)" This reverts commit 2f48be4. * v1.0.0 Co-authored-by: Tate <[email protected]>
1 parent b64ffcb commit e4ae67f

File tree

12 files changed

+2438
-2566
lines changed

12 files changed

+2438
-2566
lines changed

.github/workflows/ci.yml

+11-33
Original file line numberDiff line numberDiff line change
@@ -4,40 +4,18 @@ on:
44
branches:
55
- main
66
pull_request:
7-
87
jobs:
9-
lint:
8+
ci:
9+
name: "Lint and Test"
1010
runs-on: ubuntu-latest
11-
name: Lint
1211
steps:
13-
- name: Check out code
14-
uses: actions/checkout@v2
15-
16-
- name: Setup NodeJS
17-
uses: actions/setup-node@v2
12+
- uses: actions/checkout@v2
13+
- uses: actions/setup-node@v2
1814
with:
19-
node-version: 14
20-
21-
- name: Install Node Modules
22-
run: yarn install
23-
24-
- name: Lint
25-
run: yarn run lint
26-
27-
test:
28-
runs-on: ubuntu-latest
29-
name: Test
30-
steps:
31-
- name: Check out code
32-
uses: actions/checkout@v2
33-
34-
- name: Setup NodeJS
35-
uses: actions/setup-node@v2
36-
with:
37-
node-version: 14
38-
39-
- name: Install Node Modules
40-
run: yarn install
41-
42-
- name: Test
43-
run: yarn run test
15+
node-version: "16.x"
16+
cache: "yarn"
17+
- run: yarn
18+
- run: yarn lint
19+
- run: yarn test:ci && yarn codecov --token=$CODECOV_TOKEN
20+
env:
21+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

.github/workflows/publish.yml

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Publish NPM Package
2+
on:
3+
release:
4+
types: [created]
5+
jobs:
6+
build:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v2
10+
- uses: actions/setup-node@v2
11+
with:
12+
node-version: "16.x"
13+
cache: "yarn"
14+
registry-url: "https://registry.npmjs.org"
15+
- run: yarn
16+
- run: yarn build
17+
- run: npm publish
18+
env:
19+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Changelog
2+
3+
## v1.0.0
4+
5+
No API changes. This library will now follow [semantic versioning](https://docs.npmjs.com/about-semantic-versioning).

README.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# ESLint-plugin-React-prefer-function-component
1+
# eslint-plugin-react-prefer-function-component
22

33
<blockquote>ESLint lint rule to enforce function components in React</blockquote>
44

@@ -19,6 +19,9 @@
1919
<a href="https://github.com/tatethurston/eslint-plugin-react-prefer-function-component/actions/workflows/ci.yml">
2020
<img src="https://github.com/tatethurston/eslint-plugin-react-prefer-function-component/actions/workflows/ci.yml/badge.svg">
2121
</a>
22+
<a href="https://codecov.io/gh/tatethurston/eslint-plugin-react-prefer-function-component">
23+
<img src="https://img.shields.io/codecov/c/github/tatethurston/eslint-plugin-react-prefer-function-component/main.svg?style=flat-square">
24+
</a>
2225

2326
## What is this? 🧐
2427

babel.config.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// eslint-disable-next-line no-undef
2+
module.exports = {
3+
presets: [
4+
["@babel/preset-env", { targets: { node: "current" } }],
5+
"@babel/preset-typescript",
6+
"@babel/preset-react",
7+
],
8+
};

jest.config.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// eslint-disable-next-line no-undef
2+
module.exports = {
3+
clearMocks: true,
4+
coverageDirectory: "coverage",
5+
testEnvironment: "node",
6+
};

package.json

+26-29
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"name": "eslint-plugin-react-prefer-function-component",
3+
"version": "1.0.0",
34
"description": "ESLint lint rule to enforce function components in React",
45
"license": "MIT",
56
"author": "Tate <[email protected]>",
@@ -11,9 +12,9 @@
1112
"bugs": {
1213
"url": "https://github.com/tatethurston/eslint-plugin-react-prefer-function-component/issues"
1314
},
14-
"version": "0.0.7",
1515
"main": "dist/index.js",
1616
"files": [
17+
"dist/index.d.ts",
1718
"dist/prefer-function-component/index.js"
1819
],
1920
"scripts": {
@@ -25,42 +26,38 @@
2526
"lint:fix:md": "prettier --write '*.md'",
2627
"lint:fix:package": "prettier-package-json --write package.json",
2728
"lint:fix:ts": "eslint --fix './src/**/*.ts{,x}'",
28-
"test": "yarn build && jest --testTimeout 5000 --rootDir dist",
29+
"test": "yarn jest src/*",
30+
"test:ci": "yarn test --coverage",
2931
"typecheck": "yarn tsc --noEmit",
30-
"typecheck:watch": "yarn typecheck --watch",
31-
"version": "yarn run build && git add -A package.json",
32-
"postversion": "git push && git push --tags"
32+
"typecheck:watch": "yarn typecheck --watch"
3333
},
3434
"types": "dist/index.d.ts",
35-
"peerDependencies": {
36-
"eslint-plugin-react": "^7.23.2"
37-
},
3835
"devDependencies": {
39-
"@types/eslint": "^7.2.9",
40-
"@types/estree": "^0.0.47",
41-
"@types/jest": "^26.0.15",
42-
"@types/node": "^13.13.5",
43-
"@typescript-eslint/eslint-plugin": "^4.1.0",
44-
"@typescript-eslint/parser": "^4.1.0",
45-
"eslint": "^7.8.1",
36+
"@babel/preset-env": "^7.14.1",
37+
"@babel/preset-react": "^7.13.13",
38+
"@babel/preset-typescript": "^7.13.0",
39+
"@types/eslint": "^8.4.0",
40+
"@types/estree": "^0.0.50",
41+
"@types/jest": "^27.4.0",
42+
"@types/node": "^17.0.10",
43+
"@typescript-eslint/eslint-plugin": "^5.10.0",
44+
"@typescript-eslint/parser": "^5.10.0",
45+
"codecov": "^3.8.3",
46+
"eslint": "^8.7.0",
4647
"eslint-config-prettier": "^8.1.0",
47-
"eslint-plugin-react": "^7.23.2",
48-
"eslint-plugin-react-hooks": "^4.2.0",
48+
"eslint-plugin-react": "^7.28.0",
49+
"eslint-plugin-react-hooks": "^4.3.0",
4950
"husky": "^4.3.0",
50-
"jest": "^26.6.3",
51-
"prettier": "^2.1.1",
52-
"prettier-package-json": "^2.1.3",
53-
"typescript": "^4.1.3"
51+
"jest": "^27.4.7",
52+
"prettier": "^2.5.1",
53+
"prettier-package-json": "^2.6.0",
54+
"typescript": "^4.5.5"
5455
},
5556
"keywords": [
56-
"class component",
57-
"eslint",
58-
"eslint-plugin",
59-
"eslintplugin",
60-
"function component",
61-
"functional component",
62-
"no class",
63-
"react"
57+
"eslint react no class",
58+
"react function component",
59+
"react functional component",
60+
"react no class"
6461
],
6562
"husky": {
6663
"hooks": {

src/eslint-plugin-react.d.ts

-3
This file was deleted.

src/index.ts

-13
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,6 @@ module.exports = {
99
"error",
1010
},
1111
},
12-
settings: {
13-
react: {
14-
// Regex for Component Factory to use (defaults to "createReactClass")
15-
createClass: "createReactClass",
16-
// Pragma to use (defaults to "React")
17-
pragma: "React",
18-
// Fragment to use (may be a property of <pragma>), default to "Fragment"
19-
fragment: "Fragment",
20-
// React version. "detect" automatically picks the version you have installed.
21-
// You can also use `16.0`, `16.3`, etc, if you want to override the detected value.
22-
version: "detect",
23-
},
24-
},
2512
},
2613
rules: {
2714
"react-prefer-function-component": PreferFunctionComponent,

src/prefer-function-component/index.ts

+78-30
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,53 @@
22
* @fileoverview Enforce components are written as function components
33
*/
44

5-
// TODO: improve typing
6-
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */
7-
85
import type { Rule } from "eslint";
9-
// Using eslint-plugin-react internals for upstream consideration
10-
// https://github.com/yannickcr/eslint-plugin-react/issues/2860#issuecomment-819784530
11-
import Components from "eslint-plugin-react/lib/util/Components";
12-
import {
13-
getComponentProperties,
14-
getPropertyName,
15-
} from "eslint-plugin-react/lib/util/ast";
166

7+
// TODO:
8+
// .eslintrc shared settings (http://eslint.org/docs/user-guide/configuring#adding-shared-settings)
9+
// https://github.com/yannickcr/eslint-plugin-react/blob/master/lib/util/pragma.js
10+
const pragma = "React";
11+
const createClass = "createReactClass";
1712
export const COMPONENT_SHOULD_BE_FUNCTION = "componentShouldBeFunction";
1813
export const ALLOW_COMPONENT_DID_CATCH = "allowComponentDidCatch";
1914
const COMPONENT_DID_CATCH = "componentDidCatch";
2015
// https://eslint.org/docs/developer-guide/working-with-rules
2116
const PROGRAM_EXIT = "Program:exit";
2217

18+
// TODO: Type definitions
19+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
20+
type Node = any;
21+
22+
/* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-assignment */
23+
24+
function getComponentProperties(node: Node): Node[] {
25+
switch (node.type) {
26+
case "ClassDeclaration":
27+
case "ClassExpression":
28+
return node.body.body;
29+
case "ObjectExpression":
30+
return node.properties;
31+
default:
32+
return [];
33+
}
34+
}
35+
36+
function getPropertyNameNode(node: Node): Node | undefined {
37+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
38+
if (node.key || ["MethodDefinition", "Property"].indexOf(node.type) !== -1) {
39+
return node.key;
40+
}
41+
if (node.type === "MemberExpression") {
42+
return node.property;
43+
}
44+
return undefined;
45+
}
46+
47+
function getPropertyName(node: Node): string {
48+
const nameNode = getPropertyNameNode(node);
49+
return nameNode ? nameNode.name : "";
50+
}
51+
2352
// https://eslint.org/docs/developer-guide/working-with-rules
2453
const rule: Rule.RuleModule = {
2554
meta: {
@@ -28,8 +57,7 @@ const rule: Rule.RuleModule = {
2857
category: "Stylistic Issues",
2958
recommended: false,
3059
suggestion: false,
31-
url:
32-
"https://github.com/tatethurston/eslint-plugin-react-prefer-function-component#rule-details",
60+
url: "https://github.com/tatethurston/eslint-plugin-react-prefer-function-component#rule-details",
3361
},
3462
type: "problem",
3563
messages: {
@@ -50,11 +78,34 @@ const rule: Rule.RuleModule = {
5078
],
5179
},
5280

53-
create: Components.detect((context: any, components: any, utils: any) => {
81+
create(context: Rule.RuleContext) {
5482
const allowComponentDidCatch =
5583
context.options[0]?.allowComponentDidCatch ?? true;
84+
const sourceCode = context.getSourceCode();
85+
86+
function isES5Component(node: Node): boolean {
87+
if (!node.parent) {
88+
return false;
89+
}
5690

57-
function shouldPreferFunction(node: any): boolean {
91+
return new RegExp(`^(${pragma}\\.)?${createClass}$`).test(
92+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
93+
sourceCode.getText(node.parent.callee)
94+
);
95+
}
96+
97+
function isES6Component(node: Node): boolean {
98+
if (!node.superClass) {
99+
return false;
100+
}
101+
102+
return new RegExp(`^(${pragma}\\.)?(Pure)?Component$`).test(
103+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
104+
sourceCode.getText(node.superClass)
105+
);
106+
}
107+
108+
function shouldPreferFunction(node: Node): boolean {
58109
if (!allowComponentDidCatch) {
59110
return true;
60111
}
@@ -63,32 +114,29 @@ const rule: Rule.RuleModule = {
63114
return !properties.includes(COMPONENT_DID_CATCH);
64115
}
65116

66-
const detect = (guard: (node: any) => boolean) => (node: any) => {
117+
const components = new Set<Node>();
118+
119+
const detect = (guard: (node: Node) => boolean) => (node: Node) => {
67120
if (guard(node) && shouldPreferFunction(node)) {
68-
components.set(node, {
69-
[COMPONENT_SHOULD_BE_FUNCTION]: true,
70-
});
121+
components.add(node);
71122
}
72123
};
73124

74125
return {
75-
ObjectExpression: detect(utils.isES5Component),
76-
ClassDeclaration: detect(utils.isES6Component),
77-
ClassExpression: detect(utils.isES6Component),
126+
ObjectExpression: detect(isES5Component),
127+
ClassDeclaration: detect(isES6Component),
128+
ClassExpression: detect(isES6Component),
78129

79130
[PROGRAM_EXIT]() {
80-
const list = components.list();
81-
Object.values(list).forEach((component: any) => {
82-
if (component[COMPONENT_SHOULD_BE_FUNCTION]) {
83-
context.report({
84-
node: component.node,
85-
messageId: COMPONENT_SHOULD_BE_FUNCTION,
86-
});
87-
}
131+
components.forEach((node) => {
132+
context.report({
133+
node,
134+
messageId: COMPONENT_SHOULD_BE_FUNCTION,
135+
});
88136
});
89137
},
90138
};
91-
}),
139+
},
92140
};
93141

94142
export default rule;

src/prefer-function-component/test.ts

-5
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,6 @@ const ruleTester = new RuleTester({
1212
jsx: true,
1313
},
1414
},
15-
settings: {
16-
react: {
17-
version: "latest",
18-
},
19-
},
2015
});
2116

2217
ruleTester.run("prefer-function-component", rule, {

0 commit comments

Comments
 (0)