From 4d6c5394286e40bd239abca26ae23823727a6485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noe=CC=81=20Lebrun?= Date: Sun, 21 Jun 2020 01:53:44 +0200 Subject: [PATCH] [Fix] `dynamic-import-chunkname`/TypeScript: support `@typescript-eslint/parser` Fixes #1771. --- .travis.yml | 3 + CHANGELOG.md | 3 + package.json | 2 +- src/rules/dynamic-import-chunkname.js | 117 ++++---- tests/src/rules/dynamic-import-chunkname.js | 308 +++++++++++++++++++- 5 files changed, 376 insertions(+), 57 deletions(-) diff --git a/.travis.yml b/.travis.yml index fda8f0a5a..5aec9ffca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,9 @@ matrix: include: - env: LINT=true node_js: lts/* + - env: TS_PARSER=2 ESLINT_VERSION=7 + node_js: lts/* + before_script: 'npm install --no-save @typescript-eslint/parser@2' - env: PACKAGE=resolvers/node node_js: 14 - env: PACKAGE=resolvers/node diff --git a/CHANGELOG.md b/CHANGELOG.md index 32c1753ea..9256cd7f9 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`]/TypeScript: do not error when importing type from dev dependencies ([#1820], thanks [@fernandopasik]) - [`default`]: avoid crash with `export =` ([#1822], thanks [@AndrewLeedham]) - [`order`]/[`newline-after-import`]: ignore TypeScript's "export import object" ([#1830], thanks [@be5invis]) +- [`dynamic-import-chunkname`]/TypeScript: supports `@typescript-eslint/parser` ([#1833], thanks [@noelebrun]) ### Changed - [`no-extraneous-dependencies`]: add tests for importing types ([#1824], thanks [@taye]) @@ -713,6 +714,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#1833]: https://github.com/benmosher/eslint-plugin-import/pull/1833 [#1830]: https://github.com/benmosher/eslint-plugin-import/pull/1830 [#1824]: https://github.com/benmosher/eslint-plugin-import/pull/1824 [#1823]: https://github.com/benmosher/eslint-plugin-import/pull/1823 @@ -1238,3 +1240,4 @@ for info on changes for earlier releases. [@taye]: https://github.com/taye [@AndrewLeedham]: https://github.com/AndrewLeedham [@be5invis]: https://github.com/be5invis +[@noelebrun]: https://github.com/noelebrun diff --git a/package.json b/package.json index 9b42324f6..4bc07ee70 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "devDependencies": { "@eslint/import-test-order-redirect-scoped": "file:./tests/files/order-redirect-scoped", "@test-scope/some-module": "file:./tests/files/symlinked-module", - "@typescript-eslint/parser": "^2.23.0", + "@typescript-eslint/parser": "^2.23.0 || ^3.3.0", "array.prototype.flatmap": "^1.2.3", "babel-cli": "^6.26.0", "babel-core": "^6.26.3", diff --git a/src/rules/dynamic-import-chunkname.js b/src/rules/dynamic-import-chunkname.js index 40b99239a..cff4b1c2a 100644 --- a/src/rules/dynamic-import-chunkname.js +++ b/src/rules/dynamic-import-chunkname.js @@ -34,78 +34,85 @@ module.exports = { const chunkSubstrFormat = ` webpackChunkName: "${webpackChunknameFormat}",? ` const chunkSubstrRegex = new RegExp(chunkSubstrFormat) - return { - CallExpression(node) { - if (node.callee.type !== 'Import' && importFunctions.indexOf(node.callee.name) < 0) { - return - } + function run(node, arg) { + const sourceCode = context.getSourceCode() + const leadingComments = sourceCode.getCommentsBefore + ? sourceCode.getCommentsBefore(arg) // This method is available in ESLint >= 4. + : sourceCode.getComments(arg).leading // This method is deprecated in ESLint 7. - const sourceCode = context.getSourceCode() - const arg = node.arguments[0] - const leadingComments = sourceCode.getCommentsBefore - ? sourceCode.getCommentsBefore(arg) // This method is available in ESLint >= 4. - : sourceCode.getComments(arg).leading // This method is deprecated in ESLint 7. + if (!leadingComments || leadingComments.length === 0) { + context.report({ + node, + message: 'dynamic imports require a leading comment with the webpack chunkname', + }) + return + } - if (!leadingComments || leadingComments.length === 0) { + let isChunknamePresent = false + + for (const comment of leadingComments) { + if (comment.type !== 'Block') { context.report({ node, - message: 'dynamic imports require a leading comment with the webpack chunkname', + message: 'dynamic imports require a /* foo */ style comment, not a // foo comment', }) return } - let isChunknamePresent = false - - for (const comment of leadingComments) { - if (comment.type !== 'Block') { - context.report({ - node, - message: 'dynamic imports require a /* foo */ style comment, not a // foo comment', - }) - return - } - - if (!paddedCommentRegex.test(comment.value)) { - context.report({ - node, - message: `dynamic imports require a block comment padded with spaces - /* foo */`, - }) - return - } - - try { - // just like webpack itself does - vm.runInNewContext(`(function(){return {${comment.value}}})()`) - } - catch (error) { - context.report({ - node, - message: `dynamic imports require a "webpack" comment with valid syntax`, - }) - return - } - - if (!commentStyleRegex.test(comment.value)) { - context.report({ - node, - message: - `dynamic imports require a leading comment in the form /*${chunkSubstrFormat}*/`, - }) - return - } + if (!paddedCommentRegex.test(comment.value)) { + context.report({ + node, + message: `dynamic imports require a block comment padded with spaces - /* foo */`, + }) + return + } - if (chunkSubstrRegex.test(comment.value)) { - isChunknamePresent = true - } + try { + // just like webpack itself does + vm.runInNewContext(`(function(){return {${comment.value}}})()`) + } + catch (error) { + context.report({ + node, + message: `dynamic imports require a "webpack" comment with valid syntax`, + }) + return } - if (!isChunknamePresent) { + if (!commentStyleRegex.test(comment.value)) { context.report({ node, message: `dynamic imports require a leading comment in the form /*${chunkSubstrFormat}*/`, }) + return } + + if (chunkSubstrRegex.test(comment.value)) { + isChunknamePresent = true + } + } + + if (!isChunknamePresent) { + context.report({ + node, + message: + `dynamic imports require a leading comment in the form /*${chunkSubstrFormat}*/`, + }) + } + } + + return { + ImportExpression(node) { + run(node, node.source) + }, + + CallExpression(node) { + if (node.callee.type !== 'Import' && importFunctions.indexOf(node.callee.name) < 0) { + return + } + + run(node, node.arguments[0]) }, } }, diff --git a/tests/src/rules/dynamic-import-chunkname.js b/tests/src/rules/dynamic-import-chunkname.js index e8cbb9c6f..938f542e9 100644 --- a/tests/src/rules/dynamic-import-chunkname.js +++ b/tests/src/rules/dynamic-import-chunkname.js @@ -1,5 +1,6 @@ -import { SYNTAX_CASES } from '../utils' +import { SYNTAX_CASES, getTSParsers } from '../utils' import { RuleTester } from 'eslint' +import semver from 'semver' const rule = require('rules/dynamic-import-chunkname') const ruleTester = new RuleTester() @@ -482,3 +483,308 @@ ruleTester.run('dynamic-import-chunkname', rule, { }, ], }) + +context('TypeScript', () => { + getTSParsers().forEach((typescriptParser) => { + const nodeType = typescriptParser.includes('typescript-eslint-parser') || (typescriptParser.includes('@typescript-eslint/parser') && semver.satisfies(require('@typescript-eslint/parser/package.json').version, '^2')) + ? 'CallExpression' + : 'ImportExpression' + + ruleTester.run('dynamic-import-chunkname', rule, { + valid: [ + { + code: `import( + /* webpackChunkName: "someModule" */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackChunkName: "Some_Other_Module" */ + "test" + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackChunkName: "SomeModule123" */ + "test" + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackChunkName: "someModule", webpackPrefetch: true */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackChunkName: "someModule", webpackPrefetch: true, */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackPrefetch: true, webpackChunkName: "someModule" */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackPrefetch: true, webpackChunkName: "someModule", */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackPrefetch: true */ + /* webpackChunkName: "someModule" */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackChunkName: "someModule" */ + /* webpackPrefetch: true */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackChunkName: "someModule" */ + 'someModule' + )`, + options: pickyCommentOptions, + parser: typescriptParser, + errors: [{ + message: pickyCommentFormatError, + type: nodeType, + }], + }, + ], + invalid: [ + { + code: `import( + // webpackChunkName: "someModule" + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + // webpackChunkName: "someModule" + 'someModule' + )`, + errors: [{ + message: nonBlockCommentError, + type: nodeType, + }], + }, + { + code: 'import(\'test\')', + options, + parser: typescriptParser, + output: 'import(\'test\')', + errors: [{ + message: noLeadingCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName: someModule */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackChunkName: someModule */ + 'someModule' + )`, + errors: [{ + message: invalidSyntaxCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName: 'someModule' */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackChunkName: 'someModule' */ + 'someModule' + )`, + errors: [{ + message: commentFormatError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName "someModule" */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackChunkName "someModule" */ + 'someModule' + )`, + errors: [{ + message: invalidSyntaxCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName:"someModule" */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackChunkName:"someModule" */ + 'someModule' + )`, + errors: [{ + message: commentFormatError, + type: nodeType, + }], + }, + { + code: `import( + /*webpackChunkName: "someModule"*/ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /*webpackChunkName: "someModule"*/ + 'someModule' + )`, + errors: [{ + message: noPaddingCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName : "someModule" */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackChunkName : "someModule" */ + 'someModule' + )`, + errors: [{ + message: commentFormatError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName: "someModule" ; */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackChunkName: "someModule" ; */ + 'someModule' + )`, + errors: [{ + message: invalidSyntaxCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* totally not webpackChunkName: "someModule" */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* totally not webpackChunkName: "someModule" */ + 'someModule' + )`, + errors: [{ + message: invalidSyntaxCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackPrefetch: true */ + /* webpackChunk: "someModule" */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackPrefetch: true */ + /* webpackChunk: "someModule" */ + 'someModule' + )`, + errors: [{ + message: commentFormatError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackPrefetch: true, webpackChunk: "someModule" */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackPrefetch: true, webpackChunk: "someModule" */ + 'someModule' + )`, + errors: [{ + message: commentFormatError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName: "someModule123" */ + 'someModule' + )`, + options: pickyCommentOptions, + parser: typescriptParser, + output: `import( + /* webpackChunkName: "someModule123" */ + 'someModule' + )`, + errors: [{ + message: pickyCommentFormatError, + type: nodeType, + }], + }, + ], + }) + }) +})