1 | const {docsUrl} = require('../utilities');
|
2 |
|
3 | module.exports = {
|
4 | meta: {
|
5 | docs: {
|
6 | description:
|
7 | 'Prefer class properties to assignment of literals in constructors.',
|
8 | category: 'Stylistic Issues',
|
9 | recommended: false,
|
10 | uri: docsUrl('prefer-class-properties'),
|
11 | },
|
12 | schema: [
|
13 | {
|
14 | enum: ['always', 'never'],
|
15 | },
|
16 | ],
|
17 | },
|
18 |
|
19 | create(context) {
|
20 | const applyAlways = (context.options[0] || 'always') === 'always';
|
21 |
|
22 | function isSimpleLiteralProperty(prop) {
|
23 | return !prop.computed && isSimpleLiteral(prop.value);
|
24 | }
|
25 |
|
26 | function isSimpleLiteral(node) {
|
27 | return (
|
28 | node.type === 'Literal' ||
|
29 | (node.type === 'MemberExpression' && isSimpleLiteral(node.object)) ||
|
30 | (node.type === 'CallExpression' && isSimpleLiteral(node.callee)) ||
|
31 | (node.type === 'ArrayExpression' &&
|
32 | node.elements.every(isSimpleLiteral)) ||
|
33 | (node.type === 'ObjectExpression' &&
|
34 | node.properties.every(isSimpleLiteralProperty))
|
35 | );
|
36 | }
|
37 |
|
38 | function isStaticMemberExpression(node) {
|
39 | while (node && node.type === 'MemberExpression') {
|
40 | if (node.computed && node.property.type !== 'Literal') {
|
41 | return false;
|
42 | }
|
43 |
|
44 | node = node.object;
|
45 | }
|
46 |
|
47 | return true;
|
48 | }
|
49 |
|
50 | function checkConstructorThisAssignment(node) {
|
51 | if (isSimpleLiteral(node.right) && isStaticMemberExpression(node.left)) {
|
52 | context.report({
|
53 | node,
|
54 | message: 'Unexpected assignment of literal instance member.',
|
55 | });
|
56 | }
|
57 | }
|
58 |
|
59 | function getTopLevelThisAssignmentExpressions(functionNode) {
|
60 | return functionNode.body.body
|
61 | .filter((bodyNode) => {
|
62 | return (
|
63 | bodyNode.type === 'ExpressionStatement' &&
|
64 | bodyNode.expression.type === 'AssignmentExpression' &&
|
65 | bodyNode.expression.left.type === 'MemberExpression' &&
|
66 | bodyNode.expression.left.object.type === 'ThisExpression'
|
67 | );
|
68 | })
|
69 | .map((bodyNode) => {
|
70 | return bodyNode.expression;
|
71 | });
|
72 | }
|
73 |
|
74 | function getConstructor(classNode) {
|
75 | return classNode.body.body.find((propertyNode) => {
|
76 | return (
|
77 | propertyNode.type === 'MethodDefinition' &&
|
78 | propertyNode.key.name === 'constructor'
|
79 | );
|
80 | });
|
81 | }
|
82 |
|
83 | function getClassInstanceProperties(classNode) {
|
84 | return classNode.body.body.filter((propertyNode) => {
|
85 | return propertyNode.type === 'ClassProperty' && !propertyNode.static;
|
86 | });
|
87 | }
|
88 |
|
89 | function checkClassDeclaration(node) {
|
90 | if (applyAlways) {
|
91 | const constructor = getConstructor(node);
|
92 | if (!constructor) {
|
93 | return;
|
94 | }
|
95 |
|
96 | getTopLevelThisAssignmentExpressions(constructor.value).forEach(
|
97 | checkConstructorThisAssignment,
|
98 | );
|
99 | } else {
|
100 | getClassInstanceProperties(node).forEach((propertyNode) => {
|
101 | context.report({
|
102 | node: propertyNode,
|
103 | message: 'Unexpected class property.',
|
104 | });
|
105 | });
|
106 | }
|
107 | }
|
108 |
|
109 | return {
|
110 | ClassDeclaration: checkClassDeclaration,
|
111 | };
|
112 | },
|
113 | };
|