diff --git a/CHANGELOG.md b/CHANGELOG.md index b6aed61a3f..fa4fb56d90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * add [`jsx-props-no-spread-multi`] ([#3724][] @SimonSchick) * [`forbid-component-props`]: add `propNamePattern` to allow / disallow prop name patterns ([#3774][] @akulsr0) * [`jsx-handler-names`]: support ignoring component names ([#3772][] @akulsr0) +* Allow react defaultVersion to be configurable ([#3771][] @onlywei) ### Changed * [Refactor] `variableUtil`: Avoid creating a single flat variable scope for each lookup ([#3782][] @DanielRosenwasser) @@ -18,6 +19,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange [#3782]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3782 [#3774]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3774 [#3772]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3772 +[#3771]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3771 [#3759]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3759 [#3724]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3724 [#3694]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3694 diff --git a/README.md b/README.md index 693ad667b0..e77a809efe 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,9 @@ You should also specify settings that will be shared across all the plugin rules "fragment": "Fragment", // Fragment to use (may be a property of ), default to "Fragment" "version": "detect", // React version. "detect" automatically picks the version you have installed. // You can also use `16.0`, `16.3`, etc, if you want to override the detected value. - // It will default to "latest" and warn if missing, and to "detect" in the future + // Defaults to the "defaultVersion" setting and warns if missing, and to "detect" in the future + "defaultVersion": "", // Default React version to use when the version you have installed cannot be detected. + // If not provided, defaults to the latest React version. "flowVersion": "0.53" // Flow version }, "propWrapperFunctions": [ diff --git a/lib/util/version.js b/lib/util/version.js index cd8490c01f..9db44dbee2 100644 --- a/lib/util/version.js +++ b/lib/util/version.js @@ -12,6 +12,8 @@ const resolve = require('resolve'); const semver = require('semver'); const error = require('./error'); +const ULTIMATE_LATEST_SEMVER = '999.999.999'; + let warnedForMissingVersion = false; function resetWarningFlag() { @@ -44,6 +46,37 @@ function resolveBasedir(contextOrFilename) { return process.cwd(); } +function convertConfVerToSemver(confVer) { + const fullSemverString = /^[0-9]+\.[0-9]+$/.test(confVer) ? `${confVer}.0` : confVer; + return semver.coerce(fullSemverString.split('.').map((part) => Number(part)).join('.')); +} + +let defaultVersion = ULTIMATE_LATEST_SEMVER; + +function resetDefaultVersion() { + defaultVersion = ULTIMATE_LATEST_SEMVER; +} + +function readDefaultReactVersionFromContext(context) { + // .eslintrc shared settings (https://eslint.org/docs/user-guide/configuring#adding-shared-settings) + if (context.settings && context.settings.react && context.settings.react.defaultVersion) { + let settingsDefaultVersion = context.settings.react.defaultVersion; + if (typeof settingsDefaultVersion !== 'string') { + error('Warning: default React version specified in eslint-pluigin-react-settings must be a string; ' + + `got "${typeof settingsDefaultVersion}"`); + } + settingsDefaultVersion = String(settingsDefaultVersion); + const result = convertConfVerToSemver(settingsDefaultVersion); + if (result) { + defaultVersion = result.version; + } else { + error(`Warning: React version specified in eslint-plugin-react-settings must be a valid semver version, or "detect"; got “${settingsDefaultVersion}”. Falling back to latest version as default.`); + } + } else { + defaultVersion = ULTIMATE_LATEST_SEMVER; + } +} + // TODO, semver-major: remove context fallback function detectReactVersion(context) { if (cachedDetectedReactVersion) { @@ -60,20 +93,22 @@ function detectReactVersion(context) { } catch (e) { if (e.code === 'MODULE_NOT_FOUND') { if (!warnedForMissingVersion) { - error('Warning: React version was set to "detect" in eslint-plugin-react settings, ' - + 'but the "react" package is not installed. Assuming latest React version for linting.'); + let sentence2 = 'Assuming latest React version for linting.'; + if (defaultVersion !== ULTIMATE_LATEST_SEMVER) { + sentence2 = `Assuming default React version for linting: "${defaultVersion}".`; + } + error(`Warning: React version was set to "detect" in eslint-plugin-react settings, but the "react" package is not installed. ${sentence2}`); warnedForMissingVersion = true; } - cachedDetectedReactVersion = '999.999.999'; + cachedDetectedReactVersion = defaultVersion; return cachedDetectedReactVersion; } throw e; } } -const defaultVersion = '999.999.999'; - function getReactVersionFromContext(context) { + readDefaultReactVersionFromContext(context); let confVer = defaultVersion; // .eslintrc shared settings (https://eslint.org/docs/user-guide/configuring#adding-shared-settings) if (context.settings && context.settings.react && context.settings.react.version) { @@ -91,8 +126,8 @@ function getReactVersionFromContext(context) { + 'See https://github.com/jsx-eslint/eslint-plugin-react#configuration .'); warnedForMissingVersion = true; } - confVer = /^[0-9]+\.[0-9]+$/.test(confVer) ? `${confVer}.0` : confVer; - const result = semver.coerce(confVer.split('.').map((part) => Number(part)).join('.')); + + const result = convertConfVerToSemver(confVer); if (!result) { error(`Warning: React version specified in eslint-plugin-react-settings must be a valid semver version, or "detect"; got “${confVer}”`); } @@ -111,7 +146,7 @@ function detectFlowVersion(context) { if (e.code === 'MODULE_NOT_FOUND') { error('Warning: Flow version was set to "detect" in eslint-plugin-react settings, ' + 'but the "flow-bin" package is not installed. Assuming latest Flow version for linting.'); - return '999.999.999'; + return ULTIMATE_LATEST_SEMVER; } throw e; } @@ -133,8 +168,8 @@ function getFlowVersionFromContext(context) { } else { throw 'Could not retrieve flowVersion from settings'; // eslint-disable-line no-throw-literal } - confVer = /^[0-9]+\.[0-9]+$/.test(confVer) ? `${confVer}.0` : confVer; - const result = semver.coerce(confVer.split('.').map((part) => Number(part)).join('.')); + + const result = convertConfVerToSemver(confVer); if (!result) { error(`Warning: Flow version specified in eslint-plugin-react-settings must be a valid semver version, or "detect"; got “${confVer}”`); } @@ -158,4 +193,5 @@ module.exports = { testFlowVersion, resetWarningFlag, resetDetectedVersion, + resetDefaultVersion, }; diff --git a/tests/util/version.js b/tests/util/version.js index ad9bd4585c..3ff23e0230 100644 --- a/tests/util/version.js +++ b/tests/util/version.js @@ -14,6 +14,7 @@ describe('Version', () => { expectedErrorArgs = []; versionUtil.resetWarningFlag(); versionUtil.resetDetectedVersion(); + versionUtil.resetDefaultVersion(); }); afterEach(() => { @@ -65,6 +66,33 @@ describe('Version', () => { ]; }); + it('uses default version from settings if provided and react is not installed', () => { + context.settings.react.defaultVersion = '16.14.0'; + sinon.stub(context, 'getFilename').callsFake(() => path.resolve(base, 'detect-version-missing', 'test.js')); + + assert.equal(versionUtil.testReactVersion(context, '16.14.0'), true); + + expectedErrorArgs = [ + ['Warning: React version was set to "detect" in eslint-plugin-react settings, but the "react" package is not installed. Assuming default React version for linting: "16.14.0".'], + ]; + + delete context.settings.react.defaultVersion; + }); + + it('fails nicely with an invalid default version of react', () => { + context.settings.react.defaultVersion = 'not semver'; + sinon.stub(context, 'getFilename').callsFake(() => path.resolve(base, 'detect-version-missing', 'test.js')); + + assert.equal(versionUtil.testReactVersion(context, '999.999.999'), true); + + expectedErrorArgs = [ + ['Warning: React version specified in eslint-plugin-react-settings must be a valid semver version, or "detect"; got “not semver”. Falling back to latest version as default.'], + ['Warning: React version was set to "detect" in eslint-plugin-react settings, but the "react" package is not installed. Assuming latest React version for linting.'], + ]; + + delete context.settings.react.defaultVersion; + }); + it('warns only once for failure to detect react ', () => { sinon.stub(context, 'getFilename').callsFake(() => path.resolve(base, 'detect-version-missing', 'test.js'));