diff --git a/CHANGELOG.md b/CHANGELOG.md index 1687ebee0d..115e1ad8c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`no-extraneous-dependencies`]: Add package.json cache ([#1948], thanks @fa93hws) - [`prefer-default-export`]: handle empty array destructuring ([#1965], thanks @ljharb) - [`no-unused-modules`]: make type imports mark a module as used (fixes #1924) ([#1974], thanks [@cherryblossom000]) +- [`import/no-cycle`]: fix perf regression ([#1944], thanks [@Blasz]) ## [2.22.1] - 2020-09-27 ### Fixed @@ -741,6 +742,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md [#1974]: https://github.com/benmosher/eslint-plugin-import/pull/1974 +[#1944]: https://github.com/benmosher/eslint-plugin-import/pull/1944 [#1924]: https://github.com/benmosher/eslint-plugin-import/issues/1924 [#1965]: https://github.com/benmosher/eslint-plugin-import/issues/1965 [#1948]: https://github.com/benmosher/eslint-plugin-import/pull/1948 @@ -1293,3 +1295,4 @@ for info on changes for earlier releases. [@straub]: https://github.com/straub [@andreubotella]: https://github.com/andreubotella [@cherryblossom000]: https://github.com/cherryblossom000 +[@Blasz]: https://github.com/Blasz \ No newline at end of file diff --git a/src/ExportMap.js b/src/ExportMap.js index 9ffe7ac8d6..215f3de716 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -22,6 +22,7 @@ let parseConfigFileTextToJson; const log = debug('eslint-plugin-import:ExportMap'); const exportCache = new Map(); +const tsConfigCache = new Map(); export default class ExportMap { constructor(path) { @@ -438,9 +439,11 @@ ExportMap.parse = function (path, content, context) { const source = makeSourceCode(content, ast); - function isEsModuleInterop() { + function readTsConfig() { const tsConfigInfo = tsConfigLoader({ - cwd: context.parserOptions && context.parserOptions.tsconfigRootDir || process.cwd(), + cwd: + (context.parserOptions && context.parserOptions.tsconfigRootDir) || + process.cwd(), getEnv: (key) => process.env[key], }); try { @@ -450,12 +453,26 @@ ExportMap.parse = function (path, content, context) { // this is because projects not using TypeScript won't have typescript installed ({ parseConfigFileTextToJson } = require('typescript')); } - const tsConfig = parseConfigFileTextToJson(tsConfigInfo.tsConfigPath, jsonText).config; - return tsConfig.compilerOptions.esModuleInterop; + return parseConfigFileTextToJson(tsConfigInfo.tsConfigPath, jsonText).config; } } catch (e) { - return false; + // Catch any errors } + + return null; + } + + function isEsModuleInterop() { + const cacheKey = hashObject({ + tsconfigRootDir: context.parserOptions && context.parserOptions.tsconfigRootDir, + }).digest('hex'); + let tsConfig = tsConfigCache.get(cacheKey); + if (typeof tsConfig === 'undefined') { + tsConfig = readTsConfig(); + tsConfigCache.set(cacheKey, tsConfig); + } + + return tsConfig !== null ? tsConfig.compilerOptions.esModuleInterop : false; } ast.body.forEach(function (n) { diff --git a/tests/files/typescript-export-assign-property.ts b/tests/files/typescript-export-assign-property.ts new file mode 100644 index 0000000000..8dc2b9981e --- /dev/null +++ b/tests/files/typescript-export-assign-property.ts @@ -0,0 +1,3 @@ +const AnalyticsNode = { Analytics: {} }; + +export = AnalyticsNode.Analytics; diff --git a/tests/src/core/getExports.js b/tests/src/core/getExports.js index 523b0ef4cf..5684d7cdf6 100644 --- a/tests/src/core/getExports.js +++ b/tests/src/core/getExports.js @@ -1,6 +1,8 @@ import { expect } from 'chai'; import semver from 'semver'; +import sinon from 'sinon'; import eslintPkg from 'eslint/package.json'; +import * as tsConfigLoader from 'tsconfig-paths/lib/tsconfig-loader'; import ExportMap from '../../../src/ExportMap'; import * as fs from 'fs'; @@ -51,7 +53,8 @@ describe('ExportMap', function () { const differentSettings = Object.assign( {}, fakeContext, - { parserPath: 'espree' }); + { parserPath: 'espree' }, + ); expect(ExportMap.get('./named-exports', differentSettings)) .to.exist.and @@ -358,8 +361,12 @@ describe('ExportMap', function () { let imports; before('load imports', function () { this.timeout(20000); // takes a long time :shrug: + sinon.spy(tsConfigLoader, 'tsConfigLoader'); imports = ExportMap.get('./typescript.ts', context); }); + after('clear spies', function () { + tsConfigLoader.tsConfigLoader.restore(); + }); it('returns something for a TypeScript file', function () { expect(imports).to.exist; @@ -388,9 +395,38 @@ describe('ExportMap', function () { it('has exported abstract class', function () { expect(imports.has('Bar')).to.be.true; }); + + it('should cache tsconfig until tsconfigRootDir parser option changes', function () { + const customContext = Object.assign( + {}, + context, + { + parserOptions: { + tsconfigRootDir: null, + }, + }, + ); + expect(tsConfigLoader.tsConfigLoader.callCount).to.equal(0); + ExportMap.parse('./baz.ts', 'export const baz = 5', customContext); + expect(tsConfigLoader.tsConfigLoader.callCount).to.equal(1); + ExportMap.parse('./baz.ts', 'export const baz = 5', customContext); + expect(tsConfigLoader.tsConfigLoader.callCount).to.equal(1); + + const differentContext = Object.assign( + {}, + context, + { + parserOptions: { + tsconfigRootDir: process.cwd(), + }, + }, + ); + + ExportMap.parse('./baz.ts', 'export const baz = 5', differentContext); + expect(tsConfigLoader.tsConfigLoader.callCount).to.equal(2); + }); }); }); - }); // todo: move to utils