diff --git a/tests/src/core/getExports.js b/tests/src/core/getExports.js index 2c9f70d5ba..604ae5cf28 100644 --- a/tests/src/core/getExports.js +++ b/tests/src/core/getExports.js @@ -8,7 +8,7 @@ import ExportMap from '../../../src/ExportMap'; import * as fs from 'fs'; import { getFilename } from '../utils'; -import * as unambiguous from 'eslint-module-utils/unambiguous'; +import { test as testUnambiguous } from 'eslint-module-utils/unambiguous'; describe('ExportMap', function () { const fakeContext = Object.assign( @@ -438,7 +438,6 @@ describe('ExportMap', function () { // todo: move to utils describe('unambiguous regex', function () { - const testFiles = [ ['deep/b.js', true], ['bar.js', true], @@ -449,10 +448,8 @@ describe('ExportMap', function () { for (const [testFile, expectedRegexResult] of testFiles) { it(`works for ${testFile} (${expectedRegexResult})`, function () { const content = fs.readFileSync('./tests/files/' + testFile, 'utf8'); - expect(unambiguous.test(content)).to.equal(expectedRegexResult); + expect(testUnambiguous(content)).to.equal(expectedRegexResult); }); } - }); - }); diff --git a/tests/src/core/parse.js b/tests/src/core/parse.js index 7344d94f20..407070aa2f 100644 --- a/tests/src/core/parse.js +++ b/tests/src/core/parse.js @@ -69,5 +69,4 @@ describe('parse(content, { settings, ecmaFeatures })', function () { expect(parse.bind(null, path, content, { settings: { 'import/parsers': { [parseStubParserPath]: [ '.js' ] } }, parserPath: null, parserOptions })).not.to.throw(Error); expect(parseSpy.callCount, 'custom parser to be called once').to.equal(1); }); - }); diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md index f33001b6f9..241a205b47 100644 --- a/utils/CHANGELOG.md +++ b/utils/CHANGELOG.md @@ -7,6 +7,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ### Added - `fileExistsWithCaseSync`: add `strict` argument ([#1262], thanks [@sergei-startsev]) +- add `visit`, to support dynamic imports ([#1660], [#2212], thanks [@maxkomarychev], [@aladdin-add], [@Hypnosphi]) ## v2.6.2 - 2021-08-08 @@ -93,10 +94,12 @@ Yanked due to critical issue with cache key resulting from #839. ### Fixed - `unambiguous.test()` regex is now properly in multiline mode +[#2212]: https://github.com/import-js/eslint-plugin-import/pull/2212 [#2160]: https://github.com/import-js/eslint-plugin-import/pull/2160 [#2026]: https://github.com/import-js/eslint-plugin-import/pull/2026 [#1786]: https://github.com/import-js/eslint-plugin-import/pull/1786 [#1671]: https://github.com/import-js/eslint-plugin-import/pull/1671 +[#1660]: https://github.com/import-js/eslint-plugin-import/pull/1660 [#1606]: https://github.com/import-js/eslint-plugin-import/pull/1606 [#1602]: https://github.com/import-js/eslint-plugin-import/pull/1602 [#1591]: https://github.com/import-js/eslint-plugin-import/pull/1591 @@ -126,4 +129,7 @@ Yanked due to critical issue with cache key resulting from #839. [@sergei-startsev]: https://github.com/sergei-startsev [@sompylasar]: https://github.com/sompylasar [@timkraut]: https://github.com/timkraut -[@vikr01]: https://github.com/vikr01 \ No newline at end of file +[@vikr01]: https://github.com/vikr01 +[@maxkomarychev]: https://github.com/maxkomarychev +[@aladdin-add]: https://github.com/aladdin-add +[@Hypnosphi]: https://github.com/Hypnosphi \ No newline at end of file diff --git a/utils/parse.js b/utils/parse.js index 3b2ac028f0..d1dd4ef03b 100644 --- a/utils/parse.js +++ b/utils/parse.js @@ -3,9 +3,42 @@ exports.__esModule = true; const moduleRequire = require('./module-require').default; const extname = require('path').extname; +const fs = require('fs'); const log = require('debug')('eslint-plugin-import:parse'); +function getBabelVisitorKeys(parserPath) { + if (parserPath.endsWith('index.js')) { + const hypotheticalLocation = parserPath.replace('index.js', 'visitor-keys.js'); + if (fs.existsSync(hypotheticalLocation)) { + const keys = moduleRequire(hypotheticalLocation); + return keys.default || keys; + } + } else if (parserPath.endsWith('index.cjs')) { + const hypotheticalLocation = parserPath.replace('index.cjs', 'worker/ast-info.cjs'); + if (fs.existsSync(hypotheticalLocation)) { + const astInfo = moduleRequire(hypotheticalLocation); + return astInfo.getVisitorKeys(); + } + } + return null; +} + +function keysFromParser(parserPath, parserInstance, parsedResult) { + if (/.*espree.*/.test(parserPath)) { + return parserInstance.VisitorKeys; + } + if (/.*(babel-eslint|@babel\/eslint-parser).*/.test(parserPath)) { + return getBabelVisitorKeys(parserPath); + } + if (/.*@typescript-eslint\/parser/.test(parserPath)) { + if (parsedResult) { + return parsedResult.visitorKeys; + } + } + return null; +} + exports.default = function parse(path, content, context) { if (context == null) throw new Error('need context to parse properly'); @@ -45,20 +78,36 @@ exports.default = function parse(path, content, context) { if (typeof parser.parseForESLint === 'function') { let ast; try { - ast = parser.parseForESLint(content, parserOptions).ast; + const parserRaw = parser.parseForESLint(content, parserOptions); + ast = parserRaw.ast; + return { + ast, + visitorKeys: keysFromParser(parserPath, parser, parserRaw), + }; } catch (e) { console.warn(); console.warn('Error while parsing ' + parserOptions.filePath); console.warn('Line ' + e.lineNumber + ', column ' + e.column + ': ' + e.message); } if (!ast || typeof ast !== 'object') { - console.warn('`parseForESLint` from parser `' + parserPath + '` is invalid and will just be ignored'); + console.warn( + '`parseForESLint` from parser `' + + parserPath + + '` is invalid and will just be ignored', + ); } else { - return ast; + return { + ast, + visitorKeys: keysFromParser(parserPath, parser, undefined), + }; } } - return parser.parse(content, parserOptions); + const keys = keysFromParser(parserPath, parser, undefined); + return { + ast: parser.parse(content, parserOptions), + visitorKeys: keys, + }; }; function getParserPath(path, context) { diff --git a/utils/unambiguous.js b/utils/unambiguous.js index 1446632f39..75f21693b7 100644 --- a/utils/unambiguous.js +++ b/utils/unambiguous.js @@ -1,8 +1,7 @@ 'use strict'; exports.__esModule = true; - -const pattern = /(^|;)\s*(export|import)((\s+\w)|(\s*[{*=]))/m; +const pattern = /(^|;)\s*(export|import)((\s+\w)|(\s*[{*=]))|import\(/m; /** * detect possible imports/exports without a full parse. * @@ -26,5 +25,5 @@ const unambiguousNodeType = /^(?:(?:Exp|Imp)ort.*Declaration|TSExportAssignment) * @return {Boolean} */ exports.isModule = function isUnambiguousModule(ast) { - return ast.body.some(node => unambiguousNodeType.test(node.type)); + return ast.body && ast.body.some(node => unambiguousNodeType.test(node.type)); }; diff --git a/utils/visit.js b/utils/visit.js new file mode 100644 index 0000000000..77b09850ae --- /dev/null +++ b/utils/visit.js @@ -0,0 +1,24 @@ +'use strict'; +exports.__esModule = true; + +exports.default = function visit(node, keys, visitorSpec) { + if (!node || !keys) { + return; + } + const type = node.type; + if (typeof visitorSpec[type] === 'function') { + visitorSpec[type](node); + } + const childFields = keys[type]; + if (!childFields) { + return; + } + childFields.forEach((fieldName) => { + [].concat(node[fieldName]).forEach((item) => { + visit(item, keys, visitorSpec); + }); + }); + if (typeof visitorSpec[`${type}:Exit`] === 'function') { + visitorSpec[`${type}:Exit`](node); + } +};