Skip to content

Commit

Permalink
[New] version settings: Allow react defaultVersion to be configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
onlywei authored and ljharb committed Jun 20, 2024
1 parent 4d2fd86 commit 418374f
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 11 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ 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)

[#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
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <pragma>), 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": [
Expand Down
56 changes: 46 additions & 10 deletions lib/util/version.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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}”`);
}
Expand All @@ -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;
}
Expand All @@ -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}”`);
}
Expand All @@ -158,4 +193,5 @@ module.exports = {
testFlowVersion,
resetWarningFlag,
resetDetectedVersion,
resetDefaultVersion,
};
28 changes: 28 additions & 0 deletions tests/util/version.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ describe('Version', () => {
expectedErrorArgs = [];
versionUtil.resetWarningFlag();
versionUtil.resetDetectedVersion();
versionUtil.resetDefaultVersion();
});

afterEach(() => {
Expand Down Expand Up @@ -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'));

Expand Down

0 comments on commit 418374f

Please sign in to comment.