From 308e5a5dddf5701801ad83b29abb7a6a7134a901 Mon Sep 17 00:00:00 2001 From: Sergei Startsev Date: Mon, 23 Aug 2021 23:24:42 +0200 Subject: [PATCH] Fix #1259 for `no-unresolved`, add `caseSensitiveStrict` option --- docs/rules/no-unresolved.md | 16 ++++++++++++++++ src/rules/no-unresolved.js | 13 ++++++++----- tests/src/core/resolve.js | 16 ++++++++++++---- tests/src/rules/no-unresolved.js | 29 ++++++++++++++++++++++++++++- utils/resolve.js | 6 +++--- 5 files changed, 67 insertions(+), 13 deletions(-) diff --git a/docs/rules/no-unresolved.md b/docs/rules/no-unresolved.md index ae6177dfd9..c95a187a4d 100644 --- a/docs/rules/no-unresolved.md +++ b/docs/rules/no-unresolved.md @@ -76,6 +76,22 @@ By default, this rule will report paths whose case do not match the underlying f const { default: x } = require('./foo') // reported if './foo' is actually './Foo' and caseSensitive: true ``` +#### `caseSensitiveStrict` + +`caseSensitive` option does not detect case for current working derectory, `caseSensitiveStrict` option allows to check `cwd` in resolved path. By default, the options is disabled. + + +```js +/*eslint import/no-unresolved: [2, { caseSensitiveStrict: true }]*/ + +// Absolute paths +import Foo from `/Users/fOo/bar/file.js` // reported, /Users/foo/bar/file.js +import Foo from `d:/fOo/bar/file.js` // reported, d:/foo/bar/file.js + +// Relative paths, cwd is Users/foo/ +import Foo from `./../fOo/bar/file.js` // reported +``` + ## When Not To Use It If you're using a module bundler other than Node or Webpack, you may end up with diff --git a/src/rules/no-unresolved.js b/src/rules/no-unresolved.js index 719bbded9d..6ed009b1cf 100644 --- a/src/rules/no-unresolved.js +++ b/src/rules/no-unresolved.js @@ -18,14 +18,17 @@ module.exports = { schema: [ makeOptionsSchema({ caseSensitive: { type: 'boolean', default: true }, + caseSensitiveStrict: { type: 'boolean', default: false }, }), ], }, create: function (context) { + const options = context.options[0] || {}; + function checkSourceValue(source) { - const shouldCheckCase = !CASE_SENSITIVE_FS - && (!context.options[0] || context.options[0].caseSensitive !== false); + const caseSensitive = !CASE_SENSITIVE_FS && options.caseSensitive !== false; + const caseSensitiveStrict = !CASE_SENSITIVE_FS && options.caseSensitiveStrict; const resolvedPath = resolve(source.value, context); @@ -34,9 +37,9 @@ module.exports = { source, `Unable to resolve path to module '${source.value}'.` ); - } else if (shouldCheckCase) { + } else if (caseSensitive || caseSensitiveStrict) { const cacheSettings = ModuleCache.getSettings(context.settings); - if (!fileExistsWithCaseSync(resolvedPath, cacheSettings)) { + if (!fileExistsWithCaseSync(resolvedPath, cacheSettings, caseSensitiveStrict)) { context.report( source, `Casing of ${source.value} does not match the underlying filesystem.` @@ -45,6 +48,6 @@ module.exports = { } } - return moduleVisitor(checkSourceValue, context.options[0]); + return moduleVisitor(checkSourceValue, options); }, }; diff --git a/tests/src/core/resolve.js b/tests/src/core/resolve.js index ccfe5f6c27..02a2177c60 100644 --- a/tests/src/core/resolve.js +++ b/tests/src/core/resolve.js @@ -3,7 +3,6 @@ import eslintPkg from 'eslint/package.json'; import semver from 'semver'; import resolve, { CASE_SENSITIVE_FS, fileExistsWithCaseSync } from 'eslint-module-utils/resolve'; -import ModuleCache from 'eslint-module-utils/ModuleCache'; import * as path from 'path'; import * as fs from 'fs'; @@ -319,7 +318,11 @@ describe('resolve', function () { const caseDescribe = (!CASE_SENSITIVE_FS ? describe : describe.skip); caseDescribe('case sensitivity', function () { let file; - const testContext = utils.testContext({ 'import/resolve': { 'extensions': ['.jsx'] } }); + const testContext = utils.testContext({ + 'import/resolve': { 'extensions': ['.jsx'] }, + 'import/cache': { lifetime: 0 }, + }); + const testSettings = testContext.settings; before('resolve', function () { file = resolve( // Note the case difference 'MyUncoolComponent' vs 'MyUnCoolComponent' @@ -329,14 +332,19 @@ describe('resolve', function () { expect(file, 'path to ./jsx/MyUncoolComponent').to.exist; }); it('detects case does not match FS', function () { - expect(fileExistsWithCaseSync(file, ModuleCache.getSettings(testContext))) + expect(fileExistsWithCaseSync(file, testSettings)) .to.be.false; }); it('detecting case does not include parent folder path (issue #720)', function () { const f = path.join(process.cwd().toUpperCase(), './tests/files/jsx/MyUnCoolComponent.jsx'); - expect(fileExistsWithCaseSync(f, ModuleCache.getSettings(testContext), true)) + expect(fileExistsWithCaseSync(f, testSettings)) .to.be.true; }); + it('detecting case should include parent folder path', function () { + const f = path.join(process.cwd().toUpperCase(), './tests/files/jsx/MyUnCoolComponent.jsx'); + expect(fileExistsWithCaseSync(f, testSettings, true)) + .to.be.false; + }); }); describe('rename cache correctness', function () { diff --git a/tests/src/rules/no-unresolved.js b/tests/src/rules/no-unresolved.js index d21ee2fd8c..19203074e1 100644 --- a/tests/src/rules/no-unresolved.js +++ b/tests/src/rules/no-unresolved.js @@ -15,7 +15,7 @@ function runResolverTests(resolver) { function rest(specs) { specs.settings = Object.assign({}, specs.settings, - { 'import/resolver': resolver }, + { 'import/resolver': resolver, 'import/cache': { lifetime: 0 } }, ); return test(specs); @@ -227,6 +227,10 @@ function runResolverTests(resolver) { }); if (!CASE_SENSITIVE_FS) { + const relativePath = './tests/files/jsx/MyUnCoolComponent.jsx'; + const cwd = process.cwd(); + const mismatchedPath = path.join(cwd.toUpperCase(), relativePath).replace(/\\/g, '/'); + ruleTester.run('case sensitivity', rule, { valid: [ rest({ // test with explicit flag @@ -247,6 +251,29 @@ function runResolverTests(resolver) { }), ], }); + + ruleTester.run('case sensitivity strict', rule, { + valid: [ + // #1259 issue + rest({ // caseSensitiveStrict is disabled by default + code: `import foo from "${mismatchedPath}"`, + }), + ], + + invalid: [ + // #1259 issue + rest({ // test with enabled caseSensitiveStrict option + code: `import foo from "${mismatchedPath}"`, + options: [{ caseSensitiveStrict: true }], + errors: [`Casing of ${mismatchedPath} does not match the underlying filesystem.`], + }), + rest({ // test with enabled caseSensitiveStrict option and disabled caseSensitive + code: `import foo from "${mismatchedPath}"`, + options: [{ caseSensitiveStrict: true, caseSensitive: false }], + errors: [`Casing of ${mismatchedPath} does not match the underlying filesystem.`], + }), + ], + }); } } diff --git a/utils/resolve.js b/utils/resolve.js index f488ea798f..ec9397fa14 100644 --- a/utils/resolve.js +++ b/utils/resolve.js @@ -52,13 +52,13 @@ function tryRequire(target, sourceFile) { } // http://stackoverflow.com/a/27382838 -exports.fileExistsWithCaseSync = function fileExistsWithCaseSync(filepath, cacheSettings) { +exports.fileExistsWithCaseSync = function fileExistsWithCaseSync(filepath, cacheSettings, strict) { // don't care if the FS is case-sensitive if (CASE_SENSITIVE_FS) return true; // null means it resolved to a builtin if (filepath === null) return true; - if (filepath.toLowerCase() === process.cwd().toLowerCase()) return true; + if (filepath.toLowerCase() === process.cwd().toLowerCase() && !strict) return true; const parsedPath = path.parse(filepath); const dir = parsedPath.dir; @@ -73,7 +73,7 @@ exports.fileExistsWithCaseSync = function fileExistsWithCaseSync(filepath, cache if (filenames.indexOf(parsedPath.base) === -1) { result = false; } else { - result = fileExistsWithCaseSync(dir, cacheSettings); + result = fileExistsWithCaseSync(dir, cacheSettings, strict); } } fileExistsCache.set(filepath, result);