version.js 5.5 KB

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