Path: blob/main/tests/src/core/resolve.js
829 views
import { expect } from 'chai';1import eslintPkg from 'eslint/package.json';2import semver from 'semver';34import resolve, { CASE_SENSITIVE_FS, fileExistsWithCaseSync } from 'eslint-module-utils/resolve';56import * as path from 'path';7import * as fs from 'fs';8import * as utils from '../utils';910describe('resolve', function () {11// We don't want to test for a specific stack, just that it was there in the error message.12function replaceErrorStackForTest(str) {13return typeof str === 'string' ? str.replace(/(\n\s+at .+:\d+\)?)+$/, '\n<stack-was-here>') : str;14}1516it('throws on bad parameters', function () {17expect(resolve.bind(null, null, null)).to.throw(Error);18});1920it('resolves via a custom resolver with interface version 1', function () {21const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v1' });2223expect(resolve( '../files/foo'24, Object.assign({}, testContext, { getFilename() { return utils.getFilename('foo.js'); } }),25)).to.equal(utils.testFilePath('./bar.jsx'));2627expect(resolve( '../files/exception'28, Object.assign({}, testContext, { getFilename() { return utils.getFilename('exception.js'); } }),29)).to.equal(undefined);3031expect(resolve( '../files/not-found'32, Object.assign({}, testContext, { getFilename() { return utils.getFilename('not-found.js'); } }),33)).to.equal(undefined);34});3536it('resolves via a custom resolver with interface version 1 assumed if not specified', function () {37const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-no-version' });3839expect(resolve( '../files/foo'40, Object.assign({}, testContext, { getFilename() { return utils.getFilename('foo.js'); } }),41)).to.equal(utils.testFilePath('./bar.jsx'));4243expect(resolve( '../files/exception'44, Object.assign({}, testContext, { getFilename() { return utils.getFilename('exception.js'); } }),45)).to.equal(undefined);4647expect(resolve( '../files/not-found'48, Object.assign({}, testContext, { getFilename() { return utils.getFilename('not-found.js'); } }),49)).to.equal(undefined);50});5152it('resolves via a custom resolver with interface version 2', function () {53const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v2' });54const testContextReports = [];55testContext.report = function (reportInfo) {56testContextReports.push(reportInfo);57};5859expect(resolve( '../files/foo'60, Object.assign({}, testContext, { getFilename() { return utils.getFilename('foo.js'); } }),61)).to.equal(utils.testFilePath('./bar.jsx'));6263testContextReports.length = 0;64expect(resolve( '../files/exception'65, Object.assign({}, testContext, { getFilename() { return utils.getFilename('exception.js'); } }),66)).to.equal(undefined);67expect(testContextReports[0]).to.be.an('object');68expect(replaceErrorStackForTest(testContextReports[0].message)).to.equal('Resolve error: foo-bar-resolver-v2 resolve test exception\n<stack-was-here>');69expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 });7071testContextReports.length = 0;72expect(resolve( '../files/not-found'73, Object.assign({}, testContext, { getFilename() { return utils.getFilename('not-found.js'); } }),74)).to.equal(undefined);75expect(testContextReports.length).to.equal(0);76});7778it('respects import/resolver as array of strings', function () {79const testContext = utils.testContext({ 'import/resolver': [ './foo-bar-resolver-v2', './foo-bar-resolver-v1' ] });8081expect(resolve( '../files/foo'82, Object.assign({}, testContext, { getFilename() { return utils.getFilename('foo.js'); } }),83)).to.equal(utils.testFilePath('./bar.jsx'));84});8586it('respects import/resolver as object', function () {87const testContext = utils.testContext({ 'import/resolver': { './foo-bar-resolver-v2': {} } });8889expect(resolve( '../files/foo'90, Object.assign({}, testContext, { getFilename() { return utils.getFilename('foo.js'); } }),91)).to.equal(utils.testFilePath('./bar.jsx'));92});9394it('respects import/resolver as array of objects', function () {95const testContext = utils.testContext({ 'import/resolver': [ { './foo-bar-resolver-v2': {} }, { './foo-bar-resolver-v1': {} } ] });9697expect(resolve( '../files/foo'98, Object.assign({}, testContext, { getFilename() { return utils.getFilename('foo.js'); } }),99)).to.equal(utils.testFilePath('./bar.jsx'));100});101102it('finds resolvers from the source files rather than eslint-module-utils', function () {103const testContext = utils.testContext({ 'import/resolver': { 'foo': {} } });104105expect(resolve( '../files/foo'106, Object.assign({}, testContext, { getFilename() { return utils.getFilename('foo.js'); } }),107)).to.equal(utils.testFilePath('./bar.jsx'));108});109110it('reports invalid import/resolver config', function () {111const testContext = utils.testContext({ 'import/resolver': 123.456 });112const testContextReports = [];113testContext.report = function (reportInfo) {114testContextReports.push(reportInfo);115};116117testContextReports.length = 0;118expect(resolve( '../files/foo'119, Object.assign({}, testContext, { getFilename() { return utils.getFilename('foo.js'); } }),120)).to.equal(undefined);121expect(testContextReports[0]).to.be.an('object');122expect(testContextReports[0].message).to.equal('Resolve error: invalid resolver config');123expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 });124});125126it('reports loaded resolver with invalid interface', function () {127const resolverName = './foo-bar-resolver-invalid';128const testContext = utils.testContext({ 'import/resolver': resolverName });129const testContextReports = [];130testContext.report = function (reportInfo) {131testContextReports.push(reportInfo);132};133testContextReports.length = 0;134expect(resolve( '../files/foo'135, Object.assign({}, testContext, { getFilename() { return utils.getFilename('foo.js'); } }),136)).to.equal(undefined);137expect(testContextReports[0]).to.be.an('object');138expect(testContextReports[0].message).to.equal(`Resolve error: ${resolverName} with invalid interface loaded as resolver`);139expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 });140});141142it('respects import/resolve extensions', function () {143const testContext = utils.testContext({ 'import/resolve': { 'extensions': ['.jsx'] } });144145expect(resolve( './jsx/MyCoolComponent'146, testContext,147)).to.equal(utils.testFilePath('./jsx/MyCoolComponent.jsx'));148});149150it('reports load exception in a user resolver', function () {151const testContext = utils.testContext({ 'import/resolver': './load-error-resolver' });152const testContextReports = [];153testContext.report = function (reportInfo) {154testContextReports.push(reportInfo);155};156157expect(resolve( '../files/exception'158, Object.assign({}, testContext, { getFilename() { return utils.getFilename('exception.js'); } }),159)).to.equal(undefined);160expect(testContextReports[0]).to.be.an('object');161expect(replaceErrorStackForTest(testContextReports[0].message)).to.equal('Resolve error: SyntaxError: TEST SYNTAX ERROR\n<stack-was-here>');162expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 });163});164165// context.getPhysicalFilename() is available in ESLint 7.28+166(semver.satisfies(eslintPkg.version, '>= 7.28') ? describe : describe.skip)('getPhysicalFilename()', () => {167function unexpectedCallToGetFilename() {168throw new Error('Expected to call to getPhysicalFilename() instead of getFilename()');169}170171it('resolves via a custom resolver with interface version 1', function () {172const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v1' });173174expect(resolve( '../files/foo'175, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('foo.js'); } }),176)).to.equal(utils.testFilePath('./bar.jsx'));177178expect(resolve( '../files/exception'179, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('exception.js'); } }),180)).to.equal(undefined);181182expect(resolve( '../files/not-found'183, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('not-found.js'); } }),184)).to.equal(undefined);185});186187it('resolves via a custom resolver with interface version 1 assumed if not specified', function () {188const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-no-version' });189190expect(resolve( '../files/foo'191, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('foo.js'); } }),192)).to.equal(utils.testFilePath('./bar.jsx'));193194expect(resolve( '../files/exception'195, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('exception.js'); } }),196)).to.equal(undefined);197198expect(resolve( '../files/not-found'199, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('not-found.js'); } }),200)).to.equal(undefined);201});202203it('resolves via a custom resolver with interface version 2', function () {204const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v2' });205const testContextReports = [];206testContext.report = function (reportInfo) {207testContextReports.push(reportInfo);208};209210expect(resolve( '../files/foo'211, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('foo.js'); } }),212)).to.equal(utils.testFilePath('./bar.jsx'));213214testContextReports.length = 0;215expect(resolve( '../files/exception'216, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('exception.js'); } }),217)).to.equal(undefined);218expect(testContextReports[0]).to.be.an('object');219expect(replaceErrorStackForTest(testContextReports[0].message)).to.equal('Resolve error: foo-bar-resolver-v2 resolve test exception\n<stack-was-here>');220expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 });221222testContextReports.length = 0;223expect(resolve( '../files/not-found'224, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('not-found.js'); } }),225)).to.equal(undefined);226expect(testContextReports.length).to.equal(0);227});228229it('respects import/resolver as array of strings', function () {230const testContext = utils.testContext({ 'import/resolver': [ './foo-bar-resolver-v2', './foo-bar-resolver-v1' ] });231232expect(resolve( '../files/foo'233, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('foo.js'); } }),234)).to.equal(utils.testFilePath('./bar.jsx'));235});236237it('respects import/resolver as object', function () {238const testContext = utils.testContext({ 'import/resolver': { './foo-bar-resolver-v2': {} } });239240expect(resolve( '../files/foo'241, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('foo.js'); } }),242)).to.equal(utils.testFilePath('./bar.jsx'));243});244245it('respects import/resolver as array of objects', function () {246const testContext = utils.testContext({ 'import/resolver': [ { './foo-bar-resolver-v2': {} }, { './foo-bar-resolver-v1': {} } ] });247248expect(resolve( '../files/foo'249, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('foo.js'); } }),250)).to.equal(utils.testFilePath('./bar.jsx'));251});252253it('finds resolvers from the source files rather than eslint-module-utils', function () {254const testContext = utils.testContext({ 'import/resolver': { 'foo': {} } });255256expect(resolve( '../files/foo'257, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('foo.js'); } }),258)).to.equal(utils.testFilePath('./bar.jsx'));259});260261it('reports invalid import/resolver config', function () {262const testContext = utils.testContext({ 'import/resolver': 123.456 });263const testContextReports = [];264testContext.report = function (reportInfo) {265testContextReports.push(reportInfo);266};267268testContextReports.length = 0;269expect(resolve( '../files/foo'270, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('foo.js'); } }),271)).to.equal(undefined);272expect(testContextReports[0]).to.be.an('object');273expect(testContextReports[0].message).to.equal('Resolve error: invalid resolver config');274expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 });275});276277it('reports loaded resolver with invalid interface', function () {278const resolverName = './foo-bar-resolver-invalid';279const testContext = utils.testContext({ 'import/resolver': resolverName });280const testContextReports = [];281testContext.report = function (reportInfo) {282testContextReports.push(reportInfo);283};284testContextReports.length = 0;285expect(resolve( '../files/foo'286, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('foo.js'); } }),287)).to.equal(undefined);288expect(testContextReports[0]).to.be.an('object');289expect(testContextReports[0].message).to.equal(`Resolve error: ${resolverName} with invalid interface loaded as resolver`);290expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 });291});292293it('respects import/resolve extensions', function () {294const testContext = utils.testContext({ 'import/resolve': { 'extensions': ['.jsx'] } });295296expect(resolve( './jsx/MyCoolComponent'297, testContext,298)).to.equal(utils.testFilePath('./jsx/MyCoolComponent.jsx'));299});300301it('reports load exception in a user resolver', function () {302const testContext = utils.testContext({ 'import/resolver': './load-error-resolver' });303const testContextReports = [];304testContext.report = function (reportInfo) {305testContextReports.push(reportInfo);306};307308expect(resolve( '../files/exception'309, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename() { return utils.getFilename('exception.js'); } }),310)).to.equal(undefined);311expect(testContextReports[0]).to.be.an('object');312expect(replaceErrorStackForTest(testContextReports[0].message)).to.equal('Resolve error: SyntaxError: TEST SYNTAX ERROR\n<stack-was-here>');313expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 });314});315});316317const caseDescribe = (!CASE_SENSITIVE_FS ? describe : describe.skip);318caseDescribe('case sensitivity', function () {319let file;320const testContext = utils.testContext({321'import/resolve': { 'extensions': ['.jsx'] },322'import/cache': { lifetime: 0 },323});324const testSettings = testContext.settings;325before('resolve', function () {326file = resolve(327// Note the case difference 'MyUncoolComponent' vs 'MyUnCoolComponent'328'./jsx/MyUncoolComponent', testContext);329});330it('resolves regardless of case', function () {331expect(file, 'path to ./jsx/MyUncoolComponent').to.exist;332});333it('detects case does not match FS', function () {334expect(fileExistsWithCaseSync(file, testSettings))335.to.be.false;336});337it('detecting case does not include parent folder path (issue #720)', function () {338const f = path.join(process.cwd().toUpperCase(), './tests/files/jsx/MyUnCoolComponent.jsx');339expect(fileExistsWithCaseSync(f, testSettings))340.to.be.true;341});342it('detecting case should include parent folder path', function () {343const f = path.join(process.cwd().toUpperCase(), './tests/files/jsx/MyUnCoolComponent.jsx');344expect(fileExistsWithCaseSync(f, testSettings, true))345.to.be.false;346});347});348349describe('rename cache correctness', function () {350const context = utils.testContext({351'import/cache': { 'lifetime': 1 },352});353354const infiniteContexts = [ '∞', 'Infinity' ].map(inf => [inf,355utils.testContext({356'import/cache': { 'lifetime': inf },357})]);358359360const pairs = [361['./CaseyKasem.js', './CASEYKASEM2.js'],362];363364pairs.forEach(([original, changed]) => {365describe(`${original} => ${changed}`, function () {366367before('sanity check', function () {368expect(resolve(original, context)).to.exist;369expect(resolve(changed, context)).not.to.exist;370});371372// settings are part of cache key373before('warm up infinite entries', function () {374infiniteContexts.forEach(([,c]) => {375expect(resolve(original, c)).to.exist;376});377});378379before('rename', function (done) {380fs.rename(381utils.testFilePath(original),382utils.testFilePath(changed),383done);384});385386before('verify rename', (done) =>387fs.exists(388utils.testFilePath(changed),389exists => done(exists ? null : new Error('new file does not exist'))));390391it('gets cached values within cache lifetime', function () {392// get cached values initially393expect(resolve(original, context)).to.exist;394});395396it('gets updated values immediately', function () {397// get cached values initially398expect(resolve(changed, context)).to.exist;399});400401// special behavior for infinity402describe('infinite cache', function () {403this.timeout(1500);404405before((done) => setTimeout(done, 1100));406407infiniteContexts.forEach(([inf, infiniteContext]) => {408it(`lifetime: ${inf} still gets cached values after ~1s`, function () {409expect(resolve(original, infiniteContext), original).to.exist;410});411});412413});414415describe('finite cache', function () {416this.timeout(1200);417before((done) => setTimeout(done, 1000));418it('gets correct values after cache lifetime', function () {419expect(resolve(original, context)).not.to.exist;420expect(resolve(changed, context)).to.exist;421});422});423424after('restore original case', function (done) {425fs.rename(426utils.testFilePath(changed),427utils.testFilePath(original),428done);429});430});431});432});433434});435436437