From 2e3089744d0693aba3a44ff8468c6ca01d9ed136 Mon Sep 17 00:00:00 2001 From: Bartosz Date: Tue, 9 Jan 2024 11:09:04 +0100 Subject: [PATCH] [New] `jsx-filename-extension`: add `ignoreFilesWithoutCode` option to allow empty files --- CHANGELOG.md | 2 ++ docs/rules/jsx-filename-extension.md | 10 ++++++++++ lib/rules/jsx-filename-extension.js | 9 +++++++++ tests/lib/rules/jsx-filename-extension.js | 22 ++++++++++++++++++++++ 4 files changed, 43 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0d76055ae..d3bcba1c73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`prefer-read-only-props`], [`prop-types`], component detection: allow components to be async functions ([#3654][] @pnodet) * [`no-unknown-property`]: support `onResize` on audio/video tags ([#3662][] @caesar1030) * [`jsx-wrap-multilines`]: add `never` option to prohibit wrapping parens on multiline JSX ([#3668][] @reedws) +* [`jsx-filename-extension`]: add `ignoreFilesWithoutCode` option to allow empty files ([#3674][] @burtek) ### Fixed * [`jsx-no-leaked-render`]: preserve RHS parens for multiline jsx elements while fixing ([#3623][] @akulsr0) @@ -27,6 +28,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [Refactor] [`jsx-props-no-multi-spaces`]: extract type parameters to var ([#3634][] @HenryBrown0) * [Docs] [`jsx-key`]: fix correct example ([#3656][] @developer-bandi) +[#3674]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3674 [#3668]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3668 [#3666]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3666 [#3662]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3662 diff --git a/docs/rules/jsx-filename-extension.md b/docs/rules/jsx-filename-extension.md index 162b3dc2b8..8b09371218 100644 --- a/docs/rules/jsx-filename-extension.md +++ b/docs/rules/jsx-filename-extension.md @@ -46,6 +46,16 @@ The set of allowed extensions is configurable. By default '.jsx' is allowed. If } ``` +### `ignoreFilesWithoutCode` (default: `false`) + +If enabled, files that do not contain code (i.e. are empty, contain only whitespaces or comments) will not be rejected. + +```js +"rules": { + "react/jsx-filename-extension": [1, { "ignoreFilesWithoutCode": true }] +} +``` + ## When Not To Use It If you don't care about restricting the file extensions that may contain JSX. diff --git a/lib/rules/jsx-filename-extension.js b/lib/rules/jsx-filename-extension.js index ff6f7a7e88..578ffaf309 100644 --- a/lib/rules/jsx-filename-extension.js +++ b/lib/rules/jsx-filename-extension.js @@ -16,6 +16,7 @@ const report = require('../util/report'); const DEFAULTS = { allow: 'always', extensions: ['.jsx'], + ignoreFilesWithoutCode: false, }; // ------------------------------------------------------------------------------ @@ -50,6 +51,9 @@ module.exports = { type: 'string', }, }, + ignoreFilesWithoutCode: { + type: 'boolean', + }, }, additionalProperties: false, }], @@ -67,6 +71,8 @@ module.exports = { const allow = (context.options[0] && context.options[0].allow) || DEFAULTS.allow; const allowedExtensions = (context.options[0] && context.options[0].extensions) || DEFAULTS.extensions; + const ignoreFilesWithoutCode = (context.options[0] && context.options[0].ignoreFilesWithoutCode) + || DEFAULTS.ignoreFilesWithoutCode; const isAllowedExtension = allowedExtensions.some((extension) => filename.slice(-extension.length) === extension); function handleJSX(node) { @@ -97,6 +103,9 @@ module.exports = { } if (isAllowedExtension && allow === 'as-needed') { + if (ignoreFilesWithoutCode && node.body.length === 0) { + return; + } report(context, messages.extensionOnlyForJSX, 'extensionOnlyForJSX', { node, data: { diff --git a/tests/lib/rules/jsx-filename-extension.js b/tests/lib/rules/jsx-filename-extension.js index 2fa3d676f1..e924d2ede4 100644 --- a/tests/lib/rules/jsx-filename-extension.js +++ b/tests/lib/rules/jsx-filename-extension.js @@ -29,6 +29,13 @@ const parserOptions = { const withJSXElement = 'module.exports = function MyComponent() { return
\n
\n
; }'; const withJSXFragment = 'module.exports = function MyComponent() { return <>\n; }'; const withoutJSX = 'module.exports = {}'; +const onlyComments = [ + '// some initial comment', + '', + '/* multiline', + ' * comment', + ' */', +].join('\n'); // ------------------------------------------------------------------------------ // Tests @@ -80,6 +87,21 @@ ruleTester.run('jsx-filename-extension', rule, { code: withJSXFragment, features: ['fragment'], }, + { + filename: 'MyComponent.js', + code: onlyComments, + options: [{ allow: 'as-needed' }], + }, + { + filename: 'MyComponent.jsx', + code: onlyComments, + options: [{ allow: 'as-needed', ignoreFilesWithoutCode: true }], + }, + { + filename: 'MyComponent.jsx', + code: '', + options: [{ allow: 'as-needed', ignoreFilesWithoutCode: true }], + }, ]), invalid: parsers.all([