Skip to content

Commit 9b54b90

Browse files
committed
Add acceptTranspilerName option to display-name rule (fixes #75)
1 parent 3a0be29 commit 9b54b90

File tree

3 files changed

+195
-2
lines changed

3 files changed

+195
-2
lines changed

docs/rules/display-name.md

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,62 @@ var Hello = React.createClass({
2525
});
2626
```
2727

28-
## When Not To Use It
28+
## Rule Options
2929

30-
If you are using JSX this value is already automatically set and it is safe for you to disable this rule.
30+
```js
31+
...
32+
"display-name": [<enabled>, { "acceptTranspilerName": <boolean> }]
33+
...
34+
```
35+
36+
### `acceptTranspilerName`
37+
38+
When `true` the rule will accept the name set by the transpiler and does not require a `displayName` property in this case.
39+
40+
The following patterns are considered okay and do not cause warnings:
41+
42+
```js
43+
var Hello = React.createClass({
44+
render: function() {
45+
return <div>Hello {this.props.name}</div>;
46+
}
47+
});
48+
module.exports = Hello;
49+
```
50+
51+
```js
52+
export default class Hello extends React.Component {
53+
render() {
54+
return <div>Hello {this.props.name}</div>;
55+
}
56+
}
57+
```
58+
59+
With the following patterns the transpiler can not assign a name for the component and therefore it will still cause warnings:
60+
61+
```js
62+
module.exports = React.createClass({
63+
render: function() {
64+
return <div>Hello {this.props.name}</div>;
65+
}
66+
});
67+
```
68+
69+
```js
70+
export default class extends React.Component {
71+
render() {
72+
return <div>Hello {this.props.name}</div>;
73+
}
74+
}
75+
```
76+
77+
```js
78+
function HelloComponent() {
79+
return React.createClass({
80+
render: function() {
81+
return <div>Hello {this.props.name}</div>;
82+
}
83+
});
84+
}
85+
module.exports = HelloComponent();
86+
```

lib/rules/display-name.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ var ComponentList = componentUtil.List;
1313

1414
module.exports = function(context) {
1515

16+
var config = context.options[0] || {};
17+
var acceptTranspilerName = config.acceptTranspilerName || false;
18+
1619
var componentList = new ComponentList();
1720

1821
var MISSING_MESSAGE = 'Component definition is missing display name';
@@ -80,6 +83,39 @@ module.exports = function(context) {
8083
);
8184
}
8285

86+
/**
87+
* Checks if the component have a name set by the transpiler
88+
* @param {ASTNode} node The AST node being checked.
89+
* @returns {Boolean} True ifcomponent have a name, false if not.
90+
*/
91+
function hasTranspilerName(node) {
92+
var namedAssignment = (
93+
node.type === 'ObjectExpression' &&
94+
node.parent &&
95+
node.parent.parent &&
96+
node.parent.parent.type === 'AssignmentExpression' && (
97+
!node.parent.parent.left.object ||
98+
node.parent.parent.left.object.name !== 'module' ||
99+
node.parent.parent.left.property.name !== 'exports'
100+
)
101+
);
102+
var namedDeclaration = (
103+
node.type === 'ObjectExpression' &&
104+
node.parent &&
105+
node.parent.parent &&
106+
node.parent.parent.type === 'VariableDeclarator'
107+
);
108+
var namedClass = (
109+
node.type === 'ClassDeclaration' &&
110+
node.id && node.id.name
111+
);
112+
113+
if (namedAssignment || namedDeclaration || namedClass) {
114+
return true;
115+
}
116+
return false;
117+
}
118+
83119
// --------------------------------------------------------------------------
84120
// Public
85121
// --------------------------------------------------------------------------
@@ -112,6 +148,13 @@ module.exports = function(context) {
112148
markDisplayNameAsDeclared(node);
113149
},
114150

151+
ClassDeclaration: function(node) {
152+
if (!acceptTranspilerName || !hasTranspilerName(node)) {
153+
return;
154+
}
155+
markDisplayNameAsDeclared(node);
156+
},
157+
115158
ObjectExpression: function(node) {
116159
// Search for the displayName declaration
117160
node.properties.forEach(function(property) {
@@ -120,6 +163,10 @@ module.exports = function(context) {
120163
}
121164
markDisplayNameAsDeclared(node);
122165
});
166+
// Has transpiler name
167+
if (acceptTranspilerName && hasTranspilerName(node)) {
168+
markDisplayNameAsDeclared(node);
169+
}
123170

124171
if (componentUtil.isComponentDefinition(node)) {
125172
componentList.set(context, node, {

tests/lib/rules/display-name.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,60 @@ eslintTester.addRuleTest('lib/rules/display-name', {
9797
classes: true,
9898
jsx: true
9999
}
100+
}, {
101+
code: [
102+
'var Hello = React.createClass({',
103+
' render: function() {',
104+
' return <div>Hello {this.props.name}</div>;',
105+
' }',
106+
'});'
107+
].join('\n'),
108+
args: [1, {
109+
acceptTranspilerName: true
110+
}],
111+
ecmaFeatures: {
112+
classes: true,
113+
jsx: true
114+
}
115+
}, {
116+
code: [
117+
'class Hello extends React.Component {',
118+
' render() {',
119+
' return <div>Hello {this.props.name}</div>;',
120+
' }',
121+
'}'
122+
].join('\n'),
123+
parser: 'babel-eslint',
124+
args: [1, {
125+
acceptTranspilerName: true
126+
}]
127+
}, {
128+
code: [
129+
'export default class Hello {',
130+
' render() {',
131+
' return <div>Hello {this.props.name}</div>;',
132+
' }',
133+
'}'
134+
].join('\n'),
135+
parser: 'babel-eslint',
136+
args: [1, {
137+
acceptTranspilerName: true
138+
}]
139+
}, {
140+
code: [
141+
'var Hello;',
142+
'Hello = React.createClass({',
143+
' render: function() {',
144+
' return <div>Hello {this.props.name}</div>;',
145+
' }',
146+
'});'
147+
].join('\n'),
148+
args: [1, {
149+
acceptTranspilerName: true
150+
}],
151+
ecmaFeatures: {
152+
jsx: true
153+
}
100154
}],
101155

102156
invalid: [{
@@ -142,5 +196,41 @@ eslintTester.addRuleTest('lib/rules/display-name', {
142196
errors: [{
143197
message: 'Hello component definition is missing display name'
144198
}]
199+
}, {
200+
code: [
201+
'function HelloComponent() {',
202+
' return React.createClass({',
203+
' render: function() {',
204+
' return <div>Hello {this.props.name}</div>;',
205+
' }',
206+
' });',
207+
'}',
208+
'module.exports = HelloComponent();'
209+
].join('\n'),
210+
args: [1, {
211+
acceptTranspilerName: true
212+
}],
213+
ecmaFeatures: {
214+
classes: true,
215+
jsx: true
216+
},
217+
errors: [{
218+
message: 'Component definition is missing display name'
219+
}]
220+
}, {
221+
code: [
222+
'export default class {',
223+
' render() {',
224+
' return <div>Hello {this.props.name}</div>;',
225+
' }',
226+
'}'
227+
].join('\n'),
228+
parser: 'babel-eslint',
229+
args: [1, {
230+
acceptTranspilerName: true
231+
}],
232+
errors: [{
233+
message: 'Component definition is missing display name'
234+
}]
145235
}
146236
]});

0 commit comments

Comments
 (0)