version.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /**
  2. * @fileoverview Utility functions for React and Flow version configuration
  3. * @author Yannick Croissant
  4. */
  5. 'use strict';
  6. const fs = require('fs');
  7. const path = require('path');
  8. const resolve = require('resolve');
  9. const semver = require('semver');
  10. const error = require('./error');
  11. const ULTIMATE_LATEST_SEMVER = '999.999.999';
  12. let warnedForMissingVersion = false;
  13. function resetWarningFlag() {
  14. warnedForMissingVersion = false;
  15. }
  16. let cachedDetectedReactVersion;
  17. function resetDetectedVersion() {
  18. cachedDetectedReactVersion = undefined;
  19. }
  20. function resolveBasedir(contextOrFilename) {
  21. if (contextOrFilename) {
  22. const filename = typeof contextOrFilename === 'string' ? contextOrFilename : contextOrFilename.getFilename();
  23. const dirname = path.dirname(filename);
  24. try {
  25. if (fs.statSync(filename).isFile()) {
  26. // dirname must be dir here
  27. return dirname;
  28. }
  29. } catch (err) {
  30. // https://github.com/eslint/eslint/issues/11989
  31. if (err.code === 'ENOTDIR') {
  32. // virtual filename could be recursive
  33. return resolveBasedir(dirname);
  34. }
  35. }
  36. }
  37. return process.cwd();
  38. }
  39. function convertConfVerToSemver(confVer) {
  40. const fullSemverString = /^[0-9]+\.[0-9]+$/.test(confVer) ? `${confVer}.0` : confVer;
  41. return semver.coerce(fullSemverString.split('.').map((part) => Number(part)).join('.'));
  42. }
  43. let defaultVersion = ULTIMATE_LATEST_SEMVER;
  44. function resetDefaultVersion() {
  45. defaultVersion = ULTIMATE_LATEST_SEMVER;
  46. }
  47. function readDefaultReactVersionFromContext(context) {
  48. // .eslintrc shared settings (https://eslint.org/docs/user-guide/configuring#adding-shared-settings)
  49. if (context.settings && context.settings.react && context.settings.react.defaultVersion) {
  50. let settingsDefaultVersion = context.settings.react.defaultVersion;
  51. if (typeof settingsDefaultVersion !== 'string') {
  52. error(`Warning: default React version specified in eslint-pluigin-react-settings must be a string; got "${typeof settingsDefaultVersion}"`);
  53. }
  54. settingsDefaultVersion = String(settingsDefaultVersion);
  55. const result = convertConfVerToSemver(settingsDefaultVersion);
  56. if (result) {
  57. defaultVersion = result.version;
  58. } else {
  59. 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.`);
  60. }
  61. } else {
  62. defaultVersion = ULTIMATE_LATEST_SEMVER;
  63. }
  64. }
  65. // TODO, semver-major: remove context fallback
  66. function detectReactVersion(context) {
  67. if (cachedDetectedReactVersion) {
  68. return cachedDetectedReactVersion;
  69. }
  70. const basedir = resolveBasedir(context);
  71. try {
  72. const reactPath = resolve.sync('react', { basedir });
  73. const react = require(reactPath); // eslint-disable-line global-require, import/no-dynamic-require
  74. cachedDetectedReactVersion = react.version;
  75. return cachedDetectedReactVersion;
  76. } catch (e) {
  77. if (e.code === 'MODULE_NOT_FOUND') {
  78. if (!warnedForMissingVersion) {
  79. let sentence2 = 'Assuming latest React version for linting.';
  80. if (defaultVersion !== ULTIMATE_LATEST_SEMVER) {
  81. sentence2 = `Assuming default React version for linting: "${defaultVersion}".`;
  82. }
  83. error(`Warning: React version was set to "detect" in eslint-plugin-react settings, but the "react" package is not installed. ${sentence2}`);
  84. warnedForMissingVersion = true;
  85. }
  86. cachedDetectedReactVersion = defaultVersion;
  87. return cachedDetectedReactVersion;
  88. }
  89. throw e;
  90. }
  91. }
  92. function getReactVersionFromContext(context) {
  93. readDefaultReactVersionFromContext(context);
  94. let confVer = defaultVersion;
  95. // .eslintrc shared settings (https://eslint.org/docs/user-guide/configuring#adding-shared-settings)
  96. if (context.settings && context.settings.react && context.settings.react.version) {
  97. let settingsVersion = context.settings.react.version;
  98. if (settingsVersion === 'detect') {
  99. settingsVersion = detectReactVersion(context);
  100. }
  101. if (typeof settingsVersion !== 'string') {
  102. error(`Warning: React version specified in eslint-plugin-react-settings must be a string; got “${typeof settingsVersion}”`);
  103. }
  104. confVer = String(settingsVersion);
  105. } else if (!warnedForMissingVersion) {
  106. error('Warning: React version not specified in eslint-plugin-react settings. See https://github.com/jsx-eslint/eslint-plugin-react#configuration .');
  107. warnedForMissingVersion = true;
  108. }
  109. const result = convertConfVerToSemver(confVer);
  110. if (!result) {
  111. error(`Warning: React version specified in eslint-plugin-react-settings must be a valid semver version, or "detect"; got “${confVer}”`);
  112. }
  113. return result ? result.version : defaultVersion;
  114. }
  115. // TODO, semver-major: remove context fallback
  116. function detectFlowVersion(context) {
  117. const basedir = resolveBasedir(context);
  118. try {
  119. const flowPackageJsonPath = resolve.sync('flow-bin/package.json', { basedir });
  120. const flowPackageJson = require(flowPackageJsonPath); // eslint-disable-line global-require, import/no-dynamic-require
  121. return flowPackageJson.version;
  122. } catch (e) {
  123. if (e.code === 'MODULE_NOT_FOUND') {
  124. error('Warning: Flow version was set to "detect" in eslint-plugin-react settings, '
  125. + 'but the "flow-bin" package is not installed. Assuming latest Flow version for linting.');
  126. return ULTIMATE_LATEST_SEMVER;
  127. }
  128. throw e;
  129. }
  130. }
  131. function getFlowVersionFromContext(context) {
  132. let confVer = defaultVersion;
  133. // .eslintrc shared settings (https://eslint.org/docs/user-guide/configuring#adding-shared-settings)
  134. if (context.settings.react && context.settings.react.flowVersion) {
  135. let flowVersion = context.settings.react.flowVersion;
  136. if (flowVersion === 'detect') {
  137. flowVersion = detectFlowVersion(context);
  138. }
  139. if (typeof flowVersion !== 'string') {
  140. error('Warning: Flow version specified in eslint-plugin-react-settings must be a string; '
  141. + `got “${typeof flowVersion}”`);
  142. }
  143. confVer = String(flowVersion);
  144. } else {
  145. throw 'Could not retrieve flowVersion from settings'; // eslint-disable-line no-throw-literal
  146. }
  147. const result = convertConfVerToSemver(confVer);
  148. if (!result) {
  149. error(`Warning: Flow version specified in eslint-plugin-react-settings must be a valid semver version, or "detect"; got “${confVer}”`);
  150. }
  151. return result ? result.version : defaultVersion;
  152. }
  153. function test(semverRange, confVer) {
  154. return semver.satisfies(confVer, semverRange);
  155. }
  156. function testReactVersion(context, semverRange) {
  157. return test(semverRange, getReactVersionFromContext(context));
  158. }
  159. function testFlowVersion(context, semverRange) {
  160. return test(semverRange, getFlowVersionFromContext(context));
  161. }
  162. module.exports = {
  163. testReactVersion,
  164. testFlowVersion,
  165. resetWarningFlag,
  166. resetDetectedVersion,
  167. resetDefaultVersion,
  168. };