Repository: yannickcr/eslint-plugin-react (js)

Issue:
Warning: React version not specified when used with monorepo
I looked through the code for [version.js](https://github.com/yannickcr/eslint-plugin-react/blob/master/lib/util/version.js) and the [version test](https://github.com/yannickcr/eslint-plugin-react/blob/master/tests/util/version.js) so I believe this is a valid issue.

I am running eslint from a monorepo I've set up with lerna. One of the packages in the monorepo using react so I've only installed react-specific eslint plugins in that package. ie `eslint-plugin-react`, `eslint-plugin-react-hooks` etc

Using eslints [overrides feature](https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy) and running an `eslint` command from the _root_ (`./`) works picking up and overriding the root configuration when it finds a `eslintrc` in a sub package, however, when I've set my react version to `detect` from the react projects `.eslintrc` it throws this warning.

```bash
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.
```

`react` _is_ installed and included in _that_ packages `/react-foo/package.json`. But in order to prevent this warning I would need to install `react` at the root package of the monorepo which isn't correct or desired.

Here is an example project hierarchy with the `.eslintrc` config overrides:

```bash
./
├── package.json            # react is not installed in this package
├── .eslintrc               # root lint config
├── packages/
|   └── react-foo/
|   |   ├── package.json    # react installed
|   |   ├── .eslintrc       # react-specific lint overrides
|   |   └── src/
|   |       └──  ...
|   └── not-react-foo/
|       ├── package.json
|       └── src/
|           └── ...
└── ...
```

I believe this is due to this tools `version.js` using `process.cwd()` to check for react at the (cwd) directory where the command was ran, ie the root (`./.eslintrc`), instead of directory from where the eslint configuration for eslint-plugin-react is specified, ie `./packages/react-foo/.eslintrc`.

Pull request:
Detect the React and Flow versions relative to the file being linted.
This fixes #2218.  This changes version.js to detect the React and Flow versions relative to the file being linted.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 820ef387..5c53e403 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,12 +20,14 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
 * [`destructuring-assignment`]: fix a false positive for local prop named `context` in SFC ([#2929][] @SyMind)
 * [`jsx-no-target-blank`]: Allow rel="noreferrer" when `allowReferrer` is true ([#2925][] @edemaine)
 * [`boolean-prop-naming`]: add check for typescript "boolean" type ([#2930][] @vedadeepta)
+* version detection: Add tests that verify versioning works for sibling and child projects ([#2943][] @jcrosetto)
 
 ### Changed
 * [Docs] [`jsx-no-constructed-context-values`][]: fix invalid example syntax ([#2910][] @kud)
 * [readme] Replace lists of rules with tables in readme ([#2908][] @motato1)
 * [Docs] added missing curly braces ([#2923][] @Muditxofficial)
 
+[#2943]: https://github.com/yannickcr/eslint-plugin-react/pull/2943
 [#2930]: https://github.com/yannickcr/eslint-plugin-react/pull/2930
 [#2929]: https://github.com/yannickcr/eslint-plugin-react/pull/2929
 [#2925]: https://github.com/yannickcr/eslint-plugin-react/pull/2925
diff --git a/lib/util/version.js b/lib/util/version.js
index 951837b8..c34b7c55 100644
--- a/lib/util/version.js
+++ b/lib/util/version.js
@@ -6,6 +6,7 @@
 'use strict';
 
 const resolve = require('resolve');
+const path = require('path');
 const error = require('./error');
 
 let warnedForMissingVersion = false;
@@ -20,13 +21,14 @@ function resetDetectedVersion() {
   cachedDetectedReactVersion = undefined;
 }
 
-function detectReactVersion() {
+// TODO, semver-major: remove context fallback
+function detectReactVersion(context) {
   if (cachedDetectedReactVersion) {
     return cachedDetectedReactVersion;
   }
 
   try {
-    const reactPath = resolve.sync('react', {basedir: process.cwd()});
+    const reactPath = resolve.sync('react', {basedir: context ? path.dirname(context.getFilename()) : process.cwd()});
     const react = require(reactPath); // eslint-disable-line global-require, import/no-dynamic-require
     cachedDetectedReactVersion = react.version;
     return cachedDetectedReactVersion;
@@ -50,7 +52,7 @@ function getReactVersionFromContext(context) {
   if (context.settings && context.settings.react && context.settings.react.version) {
     let settingsVersion = context.settings.react.version;
     if (settingsVersion === 'detect') {
-      settingsVersion = detectReactVersion();
+      settingsVersion = detectReactVersion(context);
     }
     if (typeof settingsVersion !== 'string') {
       error('Warning: React version specified in eslint-plugin-react-settings must be a string; '
@@ -66,9 +68,10 @@ function getReactVersionFromContext(context) {
   return confVer.split('.').map((part) => Number(part));
 }
 
-function detectFlowVersion() {
+// TODO, semver-major: remove context fallback
+function detectFlowVersion(context) {
   try {
-    const flowPackageJsonPath = resolve.sync('flow-bin/package.json', {basedir: process.cwd()});
+    const flowPackageJsonPath = resolve.sync('flow-bin/package.json', {basedir: context ? path.dirname(context.getFilename()) : process.cwd()});
     const flowPackageJson = require(flowPackageJsonPath); // eslint-disable-line global-require, import/no-dynamic-require
     return flowPackageJson.version;
   } catch (e) {
@@ -87,7 +90,7 @@ function getFlowVersionFromContext(context) {
   if (context.settings.react && context.settings.react.flowVersion) {
     let flowVersion = context.settings.react.flowVersion;
     if (flowVersion === 'detect') {
-      flowVersion = detectFlowVersion();
+      flowVersion = detectFlowVersion(context);
     }
     if (typeof flowVersion !== 'string') {
       error('Warning: Flow version specified in eslint-plugin-react-settings must be a string; '


Test patch:
diff --git a/tests/fixtures/version/detect-version-sibling/node_modules/flow-bin/package.json b/tests/fixtures/version/detect-version-sibling/node_modules/flow-bin/package.json
new file mode 100644
index 00000000..a3730382
--- /dev/null
+++ b/tests/fixtures/version/detect-version-sibling/node_modules/flow-bin/package.json
@@ -0,0 +1,4 @@
+{
+  "name": "flow-bin",
+  "version": "2.92.0"
+}
diff --git a/tests/fixtures/version/detect-version-sibling/node_modules/react/index.js b/tests/fixtures/version/detect-version-sibling/node_modules/react/index.js
new file mode 100644
index 00000000..568d6dc9
--- /dev/null
+++ b/tests/fixtures/version/detect-version-sibling/node_modules/react/index.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = {
+  version: '2.3.4'
+};
diff --git a/tests/fixtures/version/detect-version-sibling/node_modules/react/package.json b/tests/fixtures/version/detect-version-sibling/node_modules/react/package.json
new file mode 100644
index 00000000..54271ba2
--- /dev/null
+++ b/tests/fixtures/version/detect-version-sibling/node_modules/react/package.json
@@ -0,0 +1,5 @@
+{
+  "name": "react",
+  "version": "2.3.4",
+  "main": "index.js"
+}
diff --git a/tests/fixtures/version/detect-version-sibling/test.js b/tests/fixtures/version/detect-version-sibling/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/fixtures/version/detect-version/detect-version-child/node_modules/flow-bin/package.json b/tests/fixtures/version/detect-version/detect-version-child/node_modules/flow-bin/package.json
new file mode 100644
index 00000000..07dbe6ad
--- /dev/null
+++ b/tests/fixtures/version/detect-version/detect-version-child/node_modules/flow-bin/package.json
@@ -0,0 +1,4 @@
+{
+  "name": "flow-bin",
+  "version": "3.92.0"
+}
diff --git a/tests/fixtures/version/detect-version/detect-version-child/node_modules/react/index.js b/tests/fixtures/version/detect-version/detect-version-child/node_modules/react/index.js
new file mode 100644
index 00000000..ad81c76a
--- /dev/null
+++ b/tests/fixtures/version/detect-version/detect-version-child/node_modules/react/index.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = {
+  version: '3.4.5'
+};
diff --git a/tests/fixtures/version/detect-version/detect-version-child/node_modules/react/package.json b/tests/fixtures/version/detect-version/detect-version-child/node_modules/react/package.json
new file mode 100644
index 00000000..3b3aeee3
--- /dev/null
+++ b/tests/fixtures/version/detect-version/detect-version-child/node_modules/react/package.json
@@ -0,0 +1,5 @@
+{
+  "name": "react",
+  "version": "3.4.5",
+  "main": "index.js"
+}
diff --git a/tests/fixtures/version/detect-version/detect-version-child/test.js b/tests/fixtures/version/detect-version/detect-version-child/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/fixtures/version/detect-version/test.js b/tests/fixtures/version/detect-version/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/util/version.js b/tests/util/version.js
index 3e7a0ac5..226ba65f 100644
--- a/tests/util/version.js
+++ b/tests/util/version.js
@@ -7,12 +7,9 @@ const versionUtil = require('../../lib/util/version');
 
 describe('Version', () => {
   const base = path.resolve(__dirname, '..', 'fixtures', 'version');
-  let cwd;
   let expectedErrorArgs = [];
 
   beforeEach(() => {
-    cwd = process.cwd();
-    process.chdir(base);
     sinon.stub(console, 'error');
     expectedErrorArgs = [];
     versionUtil.resetWarningFlag();
@@ -20,23 +17,44 @@ describe('Version', () => {
   });
 
   afterEach(() => {
-    process.chdir(cwd);
-
     const actualArgs = console.error.args; // eslint-disable-line no-console
     console.error.restore(); // eslint-disable-line no-console
     assert.deepEqual(actualArgs, expectedErrorArgs);
   });
 
   describe('Detect version', () => {
-    const context = {settings: {react: {version: 'detect', flowVersion: 'detect'}}};
+    const context = {settings: {react: {version: 'detect', flowVersion: 'detect'}}, getFilename: () => path.resolve(base, 'test.js')};
+
+    afterEach(() => {
+      if (context.getFilename.restore) {
+        context.getFilename.restore();
+      }
+    });
 
     it('matches detected version', () => {
-      process.chdir('detect-version');
+      sinon.stub(context, 'getFilename').callsFake(() => path.resolve(base, 'detect-version', 'test.js'));
+
       assert.equal(versionUtil.testReactVersion(context, '1.2.3'), true);
       assert.equal(versionUtil.testReactVersion(context, '1.2.4'), false);
       assert.equal(versionUtil.testFlowVersion(context, '0.92.0'), true);
     });
 
+    it('matches detected version in sibling project', () => {
+      sinon.stub(context, 'getFilename').callsFake(() => path.resolve(base, 'detect-version-sibling', 'test.js'));
+
+      assert.equal(versionUtil.testReactVersion(context, '2.3.4'), true);
+      assert.equal(versionUtil.testReactVersion(context, '2.3.5'), false);
+      assert.equal(versionUtil.testFlowVersion(context, '2.92.0'), true);
+    });
+
+    it('matches detected version in child project', () => {
+      sinon.stub(context, 'getFilename').callsFake(() => path.resolve(base, 'detect-version', 'detect-version-child', 'test.js'));
+
+      assert.equal(versionUtil.testReactVersion(context, '3.4.5'), true);
+      assert.equal(versionUtil.testReactVersion(context, '3.4.6'), false);
+      assert.equal(versionUtil.testFlowVersion(context, '3.92.0'), true);
+    });
+
     it('assumes latest version if react is not installed', () => {
       assert.equal(versionUtil.testReactVersion(context, '999.999.999'), true);