1 | const Components = require('eslint-plugin-react/lib/util/Components');
|
2 |
|
3 | const {docsUrl, uncast, getName} = require('../utilities');
|
4 |
|
5 | module.exports = {
|
6 | meta: {
|
7 | docs: {
|
8 | description:
|
9 | 'Require that React component state be initialized when it has a non-empty type.',
|
10 | category: 'Possible Errors',
|
11 | recommended: true,
|
12 | uri: docsUrl('react-initialize-state'),
|
13 | },
|
14 | schema: [],
|
15 | },
|
16 |
|
17 | create: Components.detect((context, components, utils) => {
|
18 | let classInfo = null;
|
19 |
|
20 | return {
|
21 | ClassDeclaration(node) {
|
22 | if (!utils.isES6Component(node)) {
|
23 | return;
|
24 | }
|
25 |
|
26 | classInfo = {
|
27 | hasStateType: !classHasEmptyStateType(node),
|
28 | declaredState: false,
|
29 | };
|
30 | },
|
31 | ClassProperty(node) {
|
32 | if (classInfo == null || getName(node.key) !== 'state') {
|
33 | return;
|
34 | }
|
35 |
|
36 | if (node.typeAnnotation != null) {
|
37 | classInfo.hasStateType = true;
|
38 | }
|
39 |
|
40 | if (node.value == null || node.value.type === 'Literal') {
|
41 | return;
|
42 | }
|
43 |
|
44 | classInfo.declaredState = true;
|
45 | },
|
46 | AssignmentExpression(node) {
|
47 | if (classInfo == null) {
|
48 | return;
|
49 | }
|
50 |
|
51 | if (
|
52 | node.left.type === 'MemberExpression' &&
|
53 | uncast(node.left.object).type === 'ThisExpression' &&
|
54 | getName(node.left.property) === 'state' &&
|
55 | node.right.type !== 'Literal'
|
56 | ) {
|
57 | classInfo.declaredState = true;
|
58 | }
|
59 | },
|
60 | 'ClassDeclaration:exit': (node) => {
|
61 | if (classInfo && classInfo.hasStateType && !classInfo.declaredState) {
|
62 | context.report(
|
63 | node,
|
64 | 'You declared a type for state, but did not initialize it.',
|
65 | );
|
66 | }
|
67 |
|
68 | classInfo = null;
|
69 | },
|
70 | };
|
71 | }),
|
72 | };
|
73 |
|
74 | function classHasEmptyStateType({superTypeParameters}) {
|
75 | const hasNoStateType =
|
76 | !superTypeParameters ||
|
77 | superTypeParameters.params.length < 2 ||
|
78 | superTypeParameters.params[1].type === 'TSNeverKeyword' ||
|
79 | superTypeParameters.params[1].type === 'TSAnyKeyword' ||
|
80 | superTypeParameters.params[1].type === 'AnyTypeAnnotation';
|
81 |
|
82 | if (hasNoStateType) {
|
83 | return true;
|
84 | }
|
85 |
|
86 | if (superTypeParameters.params[1].type === 'ObjectTypeAnnotation') {
|
87 | return superTypeParameters.params[1].properties.length === 0;
|
88 | }
|
89 |
|
90 | if (superTypeParameters.params[1].type === 'TSTypeLiteral') {
|
91 | return superTypeParameters.params[1].members.length === 0;
|
92 | }
|
93 |
|
94 | return false;
|
95 | }
|