Skip to content

Commit 6612375

Browse files
remcohaszingljharb
authored andcommitted
[New] jsx-filename-extension: Add allow option
1 parent 97ac0fa commit 6612375

File tree

3 files changed

+62
-27
lines changed

3 files changed

+62
-27
lines changed

docs/rules/jsx-filename-extension.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,22 @@ function MyComponent() {
2020
}
2121
```
2222

23+
Beware this rule **only** reports JSX syntax, **not** other non-standard syntax such as experimental features or type annotations.
24+
2325
## Rule Options
2426

27+
### `allow` (default: `"always"`)
28+
29+
When to allow a JSX filename extension. By default all files may have a JSX extension. Set this to `as-needed` to only allow JSX file extensions in files that contain JSX syntax.
30+
31+
```js
32+
"rules": {
33+
"react/jsx-filename-extension": [1, { "allow": "as-needed" }]
34+
}
35+
```
36+
37+
### `extensions` (default: `[".jsx"]`)
38+
2539
The set of allowed extensions is configurable. By default '.jsx' is allowed. If you wanted to allow both '.jsx' and '.js', the configuration would be:
2640

2741
```js

lib/rules/jsx-filename-extension.js

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const docsUrl = require('../util/docsUrl');
1313
// ------------------------------------------------------------------------------
1414

1515
const DEFAULTS = {
16+
allow: 'always',
1617
extensions: ['.jsx']
1718
};
1819

@@ -32,6 +33,9 @@ module.exports = {
3233
schema: [{
3334
type: 'object',
3435
properties: {
36+
allow: {
37+
enum: ['always', 'as-needed']
38+
},
3539
extensions: {
3640
type: 'array',
3741
items: {
@@ -44,32 +48,23 @@ module.exports = {
4448
},
4549

4650
create(context) {
47-
let invalidExtension;
48-
let invalidNode;
51+
const filename = context.getFilename();
4952

50-
function getExtensionsConfig() {
51-
return context.options[0] && context.options[0].extensions || DEFAULTS.extensions;
52-
}
53+
let jsxNode;
5354

54-
function handleJSX(node) {
55-
const filename = context.getFilename();
56-
if (filename === '<text>') {
57-
return;
58-
}
59-
60-
if (invalidNode) {
61-
return;
62-
}
55+
if (filename === '<text>') {
56+
// No need to traverse any nodes.
57+
return {};
58+
}
6359

64-
const allowedExtensions = getExtensionsConfig();
65-
const isAllowedExtension = allowedExtensions.some((extension) => filename.slice(-extension.length) === extension);
60+
const allow = (context.options[0] && context.options[0].allow) || DEFAULTS.allow;
61+
const allowedExtensions = (context.options[0] && context.options[0].extensions) || DEFAULTS.extensions;
62+
const isAllowedExtension = allowedExtensions.some((extension) => filename.slice(-extension.length) === extension);
6663

67-
if (isAllowedExtension) {
68-
return;
64+
function handleJSX(node) {
65+
if (!jsxNode) {
66+
jsxNode = node;
6967
}
70-
71-
invalidNode = node;
72-
invalidExtension = path.extname(filename);
7368
}
7469

7570
// --------------------------------------------------------------------------
@@ -80,15 +75,23 @@ module.exports = {
8075
JSXElement: handleJSX,
8176
JSXFragment: handleJSX,
8277

83-
'Program:exit'() {
84-
if (!invalidNode) {
78+
'Program:exit'(node) {
79+
if (jsxNode) {
80+
if (!isAllowedExtension) {
81+
context.report({
82+
node: jsxNode,
83+
message: `JSX not allowed in files with extension '${path.extname(filename)}'`
84+
});
85+
}
8586
return;
8687
}
8788

88-
context.report({
89-
node: invalidNode,
90-
message: `JSX not allowed in files with extension '${invalidExtension}'`
91-
});
89+
if (isAllowedExtension && allow === 'as-needed') {
90+
context.report({
91+
node,
92+
message: `Only files containing JSX may use the extension '${path.extname(filename)}'`
93+
});
94+
}
9295
}
9396
};
9497
}

tests/lib/rules/jsx-filename-extension.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ ruleTester.run('jsx-filename-extension', rule, {
4545
{
4646
filename: 'MyComponent.jsx',
4747
code: withJSXElement
48+
}, {
49+
filename: 'MyComponent.js',
50+
code: withoutJSX,
51+
options: [{allow: 'as-needed'}]
52+
}, {
53+
filename: 'MyComponent.jsx',
54+
code: withJSXElement,
55+
options: [{allow: 'as-needed'}]
4856
}, {
4957
filename: 'MyComponent.js',
5058
options: [{extensions: ['.js', '.jsx']}],
@@ -74,6 +82,16 @@ ruleTester.run('jsx-filename-extension', rule, {
7482
filename: 'MyComponent.js',
7583
code: withJSXElement,
7684
errors: [{message: 'JSX not allowed in files with extension \'.js\''}]
85+
}, {
86+
filename: 'MyComponent.jsx',
87+
code: withoutJSX,
88+
options: [{allow: 'as-needed'}],
89+
errors: [{message: 'Only files containing JSX may use the extension \'.jsx\''}]
90+
}, {
91+
filename: 'notAComponent.js',
92+
code: withJSXElement,
93+
options: [{allow: 'as-needed'}],
94+
errors: [{message: 'JSX not allowed in files with extension \'.js\''}]
7795
}, {
7896
filename: 'MyComponent.jsx',
7997
code: withJSXElement,

0 commit comments

Comments
 (0)