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