From bfab4cc50821ff621c65e0061f02c5e1a14e424a Mon Sep 17 00:00:00 2001 From: Patrick McElhaney Date: Wed, 21 Jul 2021 09:38:44 -0400 Subject: [PATCH] [Fix] Use `context.getPhysicalFilename()` when available (ESLint 7.28+) --- CHANGELOG.md | 3 + src/core/packagePath.js | 2 +- src/rules/named.js | 2 +- src/rules/newline-after-import.js | 6 +- src/rules/no-cycle.js | 2 +- src/rules/no-extraneous-dependencies.js | 4 +- src/rules/no-import-module-exports.js | 4 +- src/rules/no-relative-packages.js | 2 +- src/rules/no-relative-parent-imports.js | 2 +- src/rules/no-restricted-paths.js | 2 +- src/rules/no-self-import.js | 2 +- src/rules/no-unassigned-import.js | 2 +- src/rules/no-unused-modules.js | 2 +- src/rules/no-useless-path-segments.js | 2 +- tests/src/core/getExports.js | 17 ++- tests/src/core/resolve.js | 154 ++++++++++++++++++++++++ utils/resolve.js | 2 +- 17 files changed, 187 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 294529304..e8173955f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`no-duplicates`]: ensure autofix avoids excessive newlines ([#2028], thanks [@ertrzyiks]) - [`extensions`]: avoid crashing on partially typed import/export statements ([#2118], thanks [@ljharb]) - [`no-extraneous-dependencies`]: add ESM intermediate package.json support] ([#2121], thanks [@paztis]) + - Use `context.getPhysicalFilename()` when available (ESLint 7.28+) ([#2160], thanks [@pmcelhaney]) ### Changed - [Docs] `extensions`: removed incorrect cases ([#2138], thanks [@wenfangdu]) @@ -809,6 +810,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#2160]: https://github.com/benmosher/eslint-plugin-import/pull/2160 [#2158]: https://github.com/benmosher/eslint-plugin-import/pull/2158 [#2138]: https://github.com/benmosher/eslint-plugin-import/pull/2138 [#2121]: https://github.com/benmosher/eslint-plugin-import/pull/2121 @@ -1374,6 +1376,7 @@ for info on changes for earlier releases. [@paztis]: https://github.com/paztis [@pcorpet]: https://github.com/pcorpet [@Pessimistress]: https://github.com/Pessimistress +[@pmcelhaney]: https://github.com/pmcelhaney [@preco21]: https://github.com/preco21 [@pzhine]: https://github.com/pzhine [@ramasilveyra]: https://github.com/ramasilveyra diff --git a/src/core/packagePath.js b/src/core/packagePath.js index 315ec0918..a8c3c6763 100644 --- a/src/core/packagePath.js +++ b/src/core/packagePath.js @@ -4,7 +4,7 @@ import readPkgUp from 'read-pkg-up'; export function getContextPackagePath(context) { - return getFilePackagePath(context.getFilename()); + return getFilePackagePath(context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename()); } export function getFilePackagePath(filePath) { diff --git a/src/rules/named.js b/src/rules/named.js index c4ce5ea91..7dee06ce1 100644 --- a/src/rules/named.js +++ b/src/rules/named.js @@ -43,7 +43,7 @@ module.exports = { if (!deepLookup.found) { if (deepLookup.path.length > 1) { const deepPath = deepLookup.path - .map(i => path.relative(path.dirname(context.getFilename()), i.path)) + .map(i => path.relative(path.dirname(context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename()), i.path)) .join(' -> '); context.report(im[key], diff --git a/src/rules/newline-after-import.js b/src/rules/newline-after-import.js index f9a817846..cf5dce831 100644 --- a/src/rules/newline-after-import.js +++ b/src/rules/newline-after-import.js @@ -48,7 +48,7 @@ function isExportDefaultClass(node) { } function isExportNameClass(node) { - + return node.type === 'ExportNamedDeclaration' && node.declaration && node.declaration.type === 'ClassDeclaration'; } @@ -124,7 +124,7 @@ after ${type} statement not followed by another ${type}.`, const { parent } = node; const nodePosition = parent.body.indexOf(node); const nextNode = parent.body[nodePosition + 1]; - + // skip "export import"s if (node.type === 'TSImportEqualsDeclaration' && node.isExport) { return; @@ -144,7 +144,7 @@ after ${type} statement not followed by another ${type}.`, } }, 'Program:exit': function () { - log('exit processing for', context.getFilename()); + log('exit processing for', context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename()); const scopeBody = getScopeBody(context.getScope()); log('got scope:', scopeBody); diff --git a/src/rules/no-cycle.js b/src/rules/no-cycle.js index 9d9a28cd6..ec4cfeae9 100644 --- a/src/rules/no-cycle.js +++ b/src/rules/no-cycle.js @@ -37,7 +37,7 @@ module.exports = { }, create: function (context) { - const myPath = context.getFilename(); + const myPath = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); if (myPath === '') return {}; // can't cycle-check a non-file const options = context.options[0] || {}; diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js index 7e46ac34b..9403931f5 100644 --- a/src/rules/no-extraneous-dependencies.js +++ b/src/rules/no-extraneous-dependencies.js @@ -69,7 +69,7 @@ function getDependencies(context, packageDir) { Object.assign( packageContent, extractDepFields( - readPkgUp.sync({ cwd: context.getFilename(), normalize: false }).pkg + readPkgUp.sync({ cwd: context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(), normalize: false }).pkg ) ); } @@ -254,7 +254,7 @@ module.exports = { create: function (context) { const options = context.options[0] || {}; - const filename = context.getFilename(); + const filename = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); const deps = getDependencies(context, options.packageDir) || extractDepFields({}); const depsOptions = { diff --git a/src/rules/no-import-module-exports.js b/src/rules/no-import-module-exports.js index 8ce5f4c9a..50ba212c8 100644 --- a/src/rules/no-import-module-exports.js +++ b/src/rules/no-import-module-exports.js @@ -3,7 +3,7 @@ import path from 'path'; import pkgUp from 'pkg-up'; function getEntryPoint(context) { - const pkgPath = pkgUp.sync(context.getFilename()); + const pkgPath = pkgUp.sync(context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename()); try { return require.resolve(path.dirname(pkgPath)); } catch (error) { @@ -39,7 +39,7 @@ module.exports = { let alreadyReported = false; function report(node) { - const fileName = context.getFilename(); + const fileName = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); const isEntryPoint = entryPoint === fileName; const isIdentifier = node.object.type === 'Identifier'; const hasKeywords = (/^(module|exports)$/).test(node.object.name); diff --git a/src/rules/no-relative-packages.js b/src/rules/no-relative-packages.js index a654c0839..90c1ecc70 100644 --- a/src/rules/no-relative-packages.js +++ b/src/rules/no-relative-packages.js @@ -21,7 +21,7 @@ function checkImportForRelativePackage(context, importPath, node) { } const resolvedImport = resolve(importPath, context); - const resolvedContext = context.getFilename(); + const resolvedContext = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); if (!resolvedImport || !resolvedContext) { return; diff --git a/src/rules/no-relative-parent-imports.js b/src/rules/no-relative-parent-imports.js index 9826da826..8e3696275 100644 --- a/src/rules/no-relative-parent-imports.js +++ b/src/rules/no-relative-parent-imports.js @@ -15,7 +15,7 @@ module.exports = { }, create: function noRelativePackages(context) { - const myPath = context.getFilename(); + const myPath = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); if (myPath === '') return {}; // can't check a non-file function checkSourceValue(sourceNode) { diff --git a/src/rules/no-restricted-paths.js b/src/rules/no-restricted-paths.js index 6409ff57a..058aa43ea 100644 --- a/src/rules/no-restricted-paths.js +++ b/src/rules/no-restricted-paths.js @@ -52,7 +52,7 @@ module.exports = { const options = context.options[0] || {}; const restrictedPaths = options.zones || []; const basePath = options.basePath || process.cwd(); - const currentFilename = context.getFilename(); + const currentFilename = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); const matchingZones = restrictedPaths.filter((zone) => { const targetPath = path.resolve(basePath, zone.target); diff --git a/src/rules/no-self-import.js b/src/rules/no-self-import.js index a10be5678..58c393b66 100644 --- a/src/rules/no-self-import.js +++ b/src/rules/no-self-import.js @@ -8,7 +8,7 @@ import moduleVisitor from 'eslint-module-utils/moduleVisitor'; import docsUrl from '../docsUrl'; function isImportingSelf(context, node, requireName) { - const filePath = context.getFilename(); + const filePath = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); // If the input is from stdin, this test can't fail if (filePath !== '' && filePath === resolve(requireName, context)) { diff --git a/src/rules/no-unassigned-import.js b/src/rules/no-unassigned-import.js index ed292e912..37be903e0 100644 --- a/src/rules/no-unassigned-import.js +++ b/src/rules/no-unassigned-import.js @@ -32,7 +32,7 @@ function testIsAllow(globs, filename, source) { function create(context) { const options = context.options[0] || {}; - const filename = context.getFilename(); + const filename = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); const isAllow = source => testIsAllow(options.allow, filename, source); return { diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 99b564eda..205259eef 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -463,7 +463,7 @@ module.exports = { doPreparation(src, ignoreExports, context); } - const file = context.getFilename(); + const file = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); const checkExportPresence = node => { if (!missingExports) { diff --git a/src/rules/no-useless-path-segments.js b/src/rules/no-useless-path-segments.js index b22aa9478..a05860802 100644 --- a/src/rules/no-useless-path-segments.js +++ b/src/rules/no-useless-path-segments.js @@ -58,7 +58,7 @@ module.exports = { }, create(context) { - const currentDir = path.dirname(context.getFilename()); + const currentDir = path.dirname(context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename()); const options = context.options[0]; function checkSourceValue(source) { diff --git a/tests/src/core/getExports.js b/tests/src/core/getExports.js index 5a9bdadb1..74c8d9ee9 100644 --- a/tests/src/core/getExports.js +++ b/tests/src/core/getExports.js @@ -11,11 +11,18 @@ import { getFilename } from '../utils'; import * as unambiguous from 'eslint-module-utils/unambiguous'; describe('ExportMap', function () { - const fakeContext = { - getFilename: getFilename, - settings: {}, - parserPath: 'babel-eslint', - }; + const fakeContext = Object.assign( + semver.satisfies(eslintPkg.version, '>= 7.28') ? { + getFilename: function () { throw new Error('Should call getPhysicalFilename() instead of getFilename()'); }, + getPhysicalFilename: getFilename, + } : { + getFilename, + }, + { + settings: {}, + parserPath: 'babel-eslint', + }, + ); it('handles ExportAllDeclaration', function () { let imports; diff --git a/tests/src/core/resolve.js b/tests/src/core/resolve.js index b8deaa6d2..ccfe5f6c2 100644 --- a/tests/src/core/resolve.js +++ b/tests/src/core/resolve.js @@ -1,4 +1,6 @@ import { expect } from 'chai'; +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'; @@ -162,6 +164,158 @@ describe('resolve', function () { expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 }); }); + // context.getPhysicalFilename() is available in ESLint 7.28+ + (semver.satisfies(eslintPkg.version, '>= 7.28') ? describe : describe.skip)('getPhysicalFilename()', () => { + function unexpectedCallToGetFilename() { + throw new Error('Expected to call to getPhysicalFilename() instead of getFilename()'); + } + + it('resolves via a custom resolver with interface version 1', function () { + const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v1' }); + + expect(resolve( '../files/foo' + , Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('foo.js'); } }), + )).to.equal(utils.testFilePath('./bar.jsx')); + + expect(resolve( '../files/exception' + , Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('exception.js'); } }), + )).to.equal(undefined); + + expect(resolve( '../files/not-found' + , Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('not-found.js'); } }), + )).to.equal(undefined); + }); + + it('resolves via a custom resolver with interface version 1 assumed if not specified', function () { + const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-no-version' }); + + expect(resolve( '../files/foo' + , Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('foo.js'); } }), + )).to.equal(utils.testFilePath('./bar.jsx')); + + expect(resolve( '../files/exception' + , Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('exception.js'); } }), + )).to.equal(undefined); + + expect(resolve( '../files/not-found' + , Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('not-found.js'); } }), + )).to.equal(undefined); + }); + + it('resolves via a custom resolver with interface version 2', function () { + const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v2' }); + const testContextReports = []; + testContext.report = function (reportInfo) { + testContextReports.push(reportInfo); + }; + + expect(resolve( '../files/foo' + , Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('foo.js'); } }), + )).to.equal(utils.testFilePath('./bar.jsx')); + + testContextReports.length = 0; + expect(resolve( '../files/exception' + , Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('exception.js'); } }), + )).to.equal(undefined); + expect(testContextReports[0]).to.be.an('object'); + expect(replaceErrorStackForTest(testContextReports[0].message)).to.equal('Resolve error: foo-bar-resolver-v2 resolve test exception\n'); + expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 }); + + testContextReports.length = 0; + expect(resolve( '../files/not-found' + , Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('not-found.js'); } }), + )).to.equal(undefined); + expect(testContextReports.length).to.equal(0); + }); + + it('respects import/resolver as array of strings', function () { + const testContext = utils.testContext({ 'import/resolver': [ './foo-bar-resolver-v2', './foo-bar-resolver-v1' ] }); + + expect(resolve( '../files/foo' + , Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('foo.js'); } }), + )).to.equal(utils.testFilePath('./bar.jsx')); + }); + + it('respects import/resolver as object', function () { + const testContext = utils.testContext({ 'import/resolver': { './foo-bar-resolver-v2': {} } }); + + expect(resolve( '../files/foo' + , Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('foo.js'); } }), + )).to.equal(utils.testFilePath('./bar.jsx')); + }); + + it('respects import/resolver as array of objects', function () { + const testContext = utils.testContext({ 'import/resolver': [ { './foo-bar-resolver-v2': {} }, { './foo-bar-resolver-v1': {} } ] }); + + expect(resolve( '../files/foo' + , Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('foo.js'); } }), + )).to.equal(utils.testFilePath('./bar.jsx')); + }); + + it('finds resolvers from the source files rather than eslint-module-utils', function () { + const testContext = utils.testContext({ 'import/resolver': { 'foo': {} } }); + + expect(resolve( '../files/foo' + , Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('foo.js'); } }), + )).to.equal(utils.testFilePath('./bar.jsx')); + }); + + it('reports invalid import/resolver config', function () { + const testContext = utils.testContext({ 'import/resolver': 123.456 }); + const testContextReports = []; + testContext.report = function (reportInfo) { + testContextReports.push(reportInfo); + }; + + testContextReports.length = 0; + expect(resolve( '../files/foo' + , Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('foo.js'); } }), + )).to.equal(undefined); + expect(testContextReports[0]).to.be.an('object'); + expect(testContextReports[0].message).to.equal('Resolve error: invalid resolver config'); + expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 }); + }); + + it('reports loaded resolver with invalid interface', function () { + const resolverName = './foo-bar-resolver-invalid'; + const testContext = utils.testContext({ 'import/resolver': resolverName }); + const testContextReports = []; + testContext.report = function (reportInfo) { + testContextReports.push(reportInfo); + }; + testContextReports.length = 0; + expect(resolve( '../files/foo' + , Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('foo.js'); } }), + )).to.equal(undefined); + expect(testContextReports[0]).to.be.an('object'); + expect(testContextReports[0].message).to.equal(`Resolve error: ${resolverName} with invalid interface loaded as resolver`); + expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 }); + }); + + it('respects import/resolve extensions', function () { + const testContext = utils.testContext({ 'import/resolve': { 'extensions': ['.jsx'] } }); + + expect(resolve( './jsx/MyCoolComponent' + , testContext, + )).to.equal(utils.testFilePath('./jsx/MyCoolComponent.jsx')); + }); + + it('reports load exception in a user resolver', function () { + const testContext = utils.testContext({ 'import/resolver': './load-error-resolver' }); + const testContextReports = []; + testContext.report = function (reportInfo) { + testContextReports.push(reportInfo); + }; + + expect(resolve( '../files/exception' + , Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('exception.js'); } }), + )).to.equal(undefined); + expect(testContextReports[0]).to.be.an('object'); + expect(replaceErrorStackForTest(testContextReports[0].message)).to.equal('Resolve error: SyntaxError: TEST SYNTAX ERROR\n'); + expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 }); + }); + }); + const caseDescribe = (!CASE_SENSITIVE_FS ? describe : describe.skip); caseDescribe('case sensitivity', function () { let file; diff --git a/utils/resolve.js b/utils/resolve.js index ab9f13d5e..f488ea798 100644 --- a/utils/resolve.js +++ b/utils/resolve.js @@ -217,7 +217,7 @@ const erroredContexts = new Set(); */ function resolve(p, context) { try { - return relative(p, context.getFilename(), context.settings); + return relative(p, context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(), context.settings); } catch (err) { if (!erroredContexts.has(context)) { // The `err.stack` string starts with `err.name` followed by colon and `err.message`.