-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
/
Copy pathno-multi-comp.js
148 lines (129 loc) · 4.2 KB
/
no-multi-comp.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/**
* @fileoverview Prevent multiple component definition per file
* @author Yannick Croissant
*/
'use strict';
const values = require('object.values');
const Components = require('../util/Components');
const docsUrl = require('../util/docsUrl');
const report = require('../util/report');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
onlyOneComponent: 'Declare only one React component per file',
onlyOneExportedComponent: 'Declare only one exported React component per file',
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
description: 'Disallow multiple component definition per file',
category: 'Stylistic Issues',
recommended: false,
url: docsUrl('no-multi-comp'),
},
messages,
schema: [{
type: 'object',
properties: {
ignoreStateless: {
default: false,
type: 'boolean',
},
ignorePrivate: {
default: false,
type: 'boolean',
},
},
additionalProperties: false,
}],
},
create: Components.detect((context, components, utils) => {
const configuration = context.options[0] || {};
const ignoreStateless = configuration.ignoreStateless || false;
const ignorePrivate = configuration.ignorePrivate || false;
const exportedComponents = new Set(); // Track exported components
const validIdentifiers = new Set(['ArrowFunctionExpression', 'Identifier', 'FunctionExpression']);
/**
* Given an export declaration, find the export name.
* @param {Object} node
* @returns {string}
*/
function getExportedComponentName(node) {
if (node.declaration.type === 'ClassDeclaration') {
return node.declaration.id.name;
}
if (node.declaration.declarations) {
const declarator = node.declaration.declarations.find((declarator) => validIdentifiers.has(declarator.init.type));
if (declarator) {
return declarator.id.name;
}
}
}
/**
* Given a React component, find the exported name.
* @param {Object} component
* @returns {string}
*/
function findComponentIdentifierFromComponent(component) {
let name;
if (component.node.parent.id) {
name = component.node.parent.id.name;
}
if (!name) {
name = component.node.id.name;
}
return name;
}
/**
* Checks if the component is ignored
* @param {Object} component The component being checked.
* @returns {boolean} True if the component is ignored, false if not.
*/
function isIgnored(component) {
return (
ignoreStateless && (
/Function/.test(component.node.type)
|| utils.isPragmaComponentWrapper(component.node)
)
);
}
/**
* Checks if the component is exported, if exportOnly is set
* @param {Object} component The component being checked.
* @returns {boolean} True if the component is exported or exportOnly is false
*/
function isPrivate(component) {
return ignorePrivate && exportedComponents.has(findComponentIdentifierFromComponent(component));
}
const rule = {
'Program:exit'() {
if (components.length() <= 1) {
return;
}
values(components.list())
.filter((component) => !isIgnored(component))
.filter((component) => !isIgnored(component) && !isPrivate(component))
.slice(1)
.forEach((component) => {
report(context,
ignorePrivate ? messages.onlyOneExportedComponent : messages.onlyOneComponent,
ignorePrivate ? 'onlyOneExportedComponent' : 'onlyOneComponent',
{
node: component.node,
});
});
},
};
if (ignorePrivate) {
rule.ExportNamedDeclaration = (node) => {
exportedComponents.add(getExportedComponentName(node));
};
rule.ExportDefaultDeclaration = (node) => {
exportedComponents.add(getExportedComponentName(node));
};
}
return rule;
}),
};