diff --git a/CHANGELOG.md b/CHANGELOG.md index a5c4623ee3..aebca00e47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## [Unreleased] +## [2.18.1] - 2019-07-18 + +### Fixed + - Improve parse perf when using `@typescript-eslint/parser` ([#1409], thanks [@bradzacher]) + - [`prefer-default-export`]: don't warn on TypeAlias & TSTypeAliasDeclaration ([#1377], thanks [@sharmilajesupaul]) + - [`no-unused-modules`]: Exclude package "main"/"bin"/"browser" entry points ([#1404], thanks [@rfermann]) + - [`export`]: false positive for typescript overloads ([#1412], thanks [@golopot]) + +### Refactors + - [`no-extraneous-dependencies`], `importType`: remove lodash ([#1419], thanks [@ljharb]) ## [2.18.0] - 2019-06-24 @@ -14,7 +24,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`order`]: add fixer for destructuring commonjs import ([#1372], thanks [@golopot]) - typescript config: add TS def extensions + defer to TS over JS ([#1366], thanks [@benmosher]) -### Fixes +### Fixed - [`no-unused-modules`]: handle ClassDeclaration ([#1371], thanks [@golopot]) ### Docs @@ -586,8 +596,13 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#1419]: https://github.com/benmosher/eslint-plugin-import/pull/1419 +[#1412]: https://github.com/benmosher/eslint-plugin-import/pull/1412 +[#1409]: https://github.com/benmosher/eslint-plugin-import/pull/1409 +[#1404]: https://github.com/benmosher/eslint-plugin-import/pull/1404 [#1393]: https://github.com/benmosher/eslint-plugin-import/pull/1393 [#1389]: https://github.com/benmosher/eslint-plugin-import/pull/1389 +[#1377]: https://github.com/benmosher/eslint-plugin-import/pull/1377 [#1375]: https://github.com/benmosher/eslint-plugin-import/pull/1375 [#1372]: https://github.com/benmosher/eslint-plugin-import/pull/1372 [#1371]: https://github.com/benmosher/eslint-plugin-import/pull/1371 @@ -950,3 +965,4 @@ for info on changes for earlier releases. [@benmosher]: https://github.com/benmosher [@fooloomanzoo]: https://github.com/fooloomanzoo [@sheepsteak]: https://github.com/sheepsteak +[@sharmilajesupaul]: https://github.com/sharmilajesupaul diff --git a/docs/rules/no-unused-modules.md b/docs/rules/no-unused-modules.md index 32db6465fc..4302bc8458 100644 --- a/docs/rules/no-unused-modules.md +++ b/docs/rules/no-unused-modules.md @@ -105,7 +105,8 @@ export function doAnything() { export default 5 // will not be reported ``` - +#### Important Note +Exports from files listed as a main file (`main`, `browser`, or `bin` fields in `package.json`) will be ignored by default. This only applies if the `package.json` is not set to `private: true` ## When not to use diff --git a/package.json b/package.json index 7a25c7363f..873a387a13 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-import", - "version": "2.18.0", + "version": "2.18.1", "description": "Import with sanity.", "engines": { "node": ">=4" @@ -87,8 +87,8 @@ "eslint-import-resolver-node": "^0.3.2", "eslint-module-utils": "^2.4.0", "has": "^1.0.3", - "lodash": "^4.17.11", "minimatch": "^3.0.4", + "object.values": "^1.1.0", "read-pkg-up": "^2.0.0", "resolve": "^1.11.0" }, diff --git a/src/core/importType.js b/src/core/importType.js index 7755bb4a24..b948ea2bb9 100644 --- a/src/core/importType.js +++ b/src/core/importType.js @@ -1,13 +1,8 @@ -import cond from 'lodash/cond' import coreModules from 'resolve/lib/core' import { join } from 'path' import resolve from 'eslint-module-utils/resolve' -function constant(value) { - return () => value -} - function baseModule(name) { if (isScoped(name)) { const [scope, pkg] = name.split('/') @@ -76,17 +71,17 @@ function isRelativeToSibling(name) { return /^\.[\\/]/.test(name) } -const typeTest = cond([ - [isAbsolute, constant('absolute')], - [isBuiltIn, constant('builtin')], - [isInternalModule, constant('internal')], - [isExternalModule, constant('external')], - [isScoped, constant('external')], - [isRelativeToParent, constant('parent')], - [isIndex, constant('index')], - [isRelativeToSibling, constant('sibling')], - [constant(true), constant('unknown')], -]) +function typeTest(name, settings, path) { + if (isAbsolute(name, settings, path)) { return 'absolute' } + if (isBuiltIn(name, settings, path)) { return 'builtin' } + if (isInternalModule(name, settings, path)) { return 'internal' } + if (isExternalModule(name, settings, path)) { return 'external' } + if (isScoped(name, settings, path)) { return 'external' } + if (isRelativeToParent(name, settings, path)) { return 'parent' } + if (isIndex(name, settings, path)) { return 'index' } + if (isRelativeToSibling(name, settings, path)) { return 'sibling' } + return 'unknown' +} export default function resolveImportType(name, context) { return typeTest(name, context.settings, resolve(name, context)) diff --git a/src/rules/export.js b/src/rules/export.js index caa28e119f..a9fba849e0 100644 --- a/src/rules/export.js +++ b/src/rules/export.js @@ -24,6 +24,21 @@ ambient namespaces: const rootProgram = 'root' const tsTypePrefix = 'type:' +/** + * Detect function overloads like: + * ```ts + * export function foo(a: number); + * export function foo(a: string); + * export function foo(a: number|string) { return a; } + * ``` + * @param {Set} nodes + * @returns {boolean} + */ +function isTypescriptFunctionOverloads(nodes) { + const types = new Set(Array.from(nodes, node => node.parent.type)) + return types.size === 2 && types.has('TSDeclareFunction') && types.has('FunctionDeclaration') +} + module.exports = { meta: { type: 'problem', @@ -123,6 +138,8 @@ module.exports = { for (let [name, nodes] of named) { if (nodes.size <= 1) continue + if (isTypescriptFunctionOverloads(nodes)) continue + for (let node of nodes) { if (name === 'default') { context.report(node, 'Multiple default exports.') diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js index d2c7cac6ee..647481a374 100644 --- a/src/rules/no-extraneous-dependencies.js +++ b/src/rules/no-extraneous-dependencies.js @@ -1,6 +1,5 @@ import path from 'path' import fs from 'fs' -import { isArray, isEmpty } from 'lodash' import readPkgUp from 'read-pkg-up' import minimatch from 'minimatch' import resolve from 'eslint-module-utils/resolve' @@ -31,15 +30,15 @@ function getDependencies(context, packageDir) { peerDependencies: {}, } - if (!isEmpty(packageDir)) { - if (!isArray(packageDir)) { + if (packageDir && packageDir.length > 0) { + if (!Array.isArray(packageDir)) { paths = [path.resolve(packageDir)] } else { paths = packageDir.map(dir => path.resolve(dir)) } } - if (!isEmpty(paths)) { + if (paths.length > 0) { // use rule config to find package.json paths.forEach(dir => { const _packageContent = extractDepFields( @@ -70,7 +69,7 @@ function getDependencies(context, packageDir) { return packageContent } catch (e) { - if (!isEmpty(paths) && e.code === 'ENOENT') { + if (paths.length > 0 && e.code === 'ENOENT') { context.report({ message: 'The package.json file could not be found.', loc: { line: 0, column: 0 }, diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 16130d47d5..47cd11c0df 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -7,6 +7,10 @@ import Exports from '../ExportMap' import resolve from 'eslint-module-utils/resolve' import docsUrl from '../docsUrl' +import { dirname, join } from 'path' +import readPkgUp from 'read-pkg-up' +import values from 'object.values' +import includes from 'array-includes' // eslint/lib/util/glob-util has been moved to eslint/lib/util/glob-utils with version 5.3 // and has been moved to eslint/lib/cli-engine/file-enumerator in version 6 @@ -80,7 +84,7 @@ const prepareImportsAndExports = (srcFiles, context) => { if (currentExports) { const { dependencies, reexports, imports: localImportList, namespace } = currentExports - // dependencies === export * from + // dependencies === export * from const currentExportAll = new Set() dependencies.forEach(value => { currentExportAll.add(value().path) @@ -146,7 +150,7 @@ const prepareImportsAndExports = (srcFiles, context) => { } /** - * traverse through all imports and add the respective path to the whereUsed-list + * traverse through all imports and add the respective path to the whereUsed-list * of the corresponding export */ const determineUsage = () => { @@ -201,6 +205,58 @@ const newNamespaceImportExists = specifiers => const newDefaultImportExists = specifiers => specifiers.some(({ type }) => type === IMPORT_DEFAULT_SPECIFIER) +const fileIsInPkg = file => { + const { path, pkg } = readPkgUp.sync({cwd: file, normalize: false}) + const basePath = dirname(path) + + const checkPkgFieldString = pkgField => { + if (join(basePath, pkgField) === file) { + return true + } + } + + const checkPkgFieldObject = pkgField => { + const pkgFieldFiles = values(pkgField).map(value => join(basePath, value)) + if (includes(pkgFieldFiles, file)) { + return true + } + } + + const checkPkgField = pkgField => { + if (typeof pkgField === 'string') { + return checkPkgFieldString(pkgField) + } + + if (typeof pkgField === 'object') { + return checkPkgFieldObject(pkgField) + } + } + + if (pkg.private === true) { + return false + } + + if (pkg.bin) { + if (checkPkgField(pkg.bin)) { + return true + } + } + + if (pkg.browser) { + if (checkPkgField(pkg.browser)) { + return true + } + } + + if (pkg.main) { + if (checkPkgFieldString(pkg.main)) { + return true + } + } + + return false +} + module.exports = { meta: { docs: { url: docsUrl('no-unused-modules') }, @@ -315,6 +371,10 @@ module.exports = { return } + if (fileIsInPkg(file)) { + return + } + // refresh list of source files const srcFiles = resolveFiles(getSrc(src), ignoreExports) @@ -325,7 +385,7 @@ module.exports = { exports = exportList.get(file) - // special case: export * from + // special case: export * from const exportAll = exports.get(EXPORT_ALL_DECLARATION) if (typeof exportAll !== 'undefined' && exportedValue !== IMPORT_DEFAULT_SPECIFIER) { if (exportAll.whereUsed.size > 0) { @@ -362,7 +422,7 @@ module.exports = { /** * only useful for tools like vscode-eslint - * + * * update lists of existing exports during runtime */ const updateExportUsage = node => { @@ -384,7 +444,7 @@ module.exports = { node.body.forEach(({ type, declaration, specifiers }) => { if (type === EXPORT_DEFAULT_DECLARATION) { newExportIdentifiers.add(IMPORT_DEFAULT_SPECIFIER) - } + } if (type === EXPORT_NAMED_DECLARATION) { if (specifiers.length > 0) { specifiers.forEach(specifier => { @@ -399,7 +459,7 @@ module.exports = { declaration.type === CLASS_DECLARATION ) { newExportIdentifiers.add(declaration.id.name) - } + } if (declaration.type === VARIABLE_DECLARATION) { declaration.declarations.forEach(({ id }) => { newExportIdentifiers.add(id.name) @@ -438,7 +498,7 @@ module.exports = { /** * only useful for tools like vscode-eslint - * + * * update lists of existing imports during runtime */ const updateImportUsage = node => { diff --git a/src/rules/prefer-default-export.js b/src/rules/prefer-default-export.js index 0e31346f3b..01b8d8be02 100644 --- a/src/rules/prefer-default-export.js +++ b/src/rules/prefer-default-export.js @@ -47,9 +47,16 @@ module.exports = { // if there are specifiers, node.declaration should be null if (!node.declaration) return - // don't count flow types exports + // don't warn on single type aliases or declarations if (node.exportKind === 'type') return + if ( + node.declaration.type === 'TSTypeAliasDeclaration' || + node.declaration.type === 'TypeAlias' + ) { + return + } + if (node.declaration.declarations) { node.declaration.declarations.forEach(function(declaration) { captureDeclaration(declaration.id) diff --git a/tests/dep-time-travel.sh b/tests/dep-time-travel.sh index fcd3cda1dc..996ed0b1cf 100755 --- a/tests/dep-time-travel.sh +++ b/tests/dep-time-travel.sh @@ -7,7 +7,7 @@ npm install --no-save eslint@$ESLINT_VERSION --ignore-scripts || true # completely remove the new typescript parser for ESLint < v5 if [[ "$ESLINT_VERSION" -lt "5" ]]; then echo "Removing @typescript-eslint/parser..." - npm uninstall @typescript-eslint/parser + npm uninstall --no-save @typescript-eslint/parser fi # use these alternate typescript dependencies for ESLint < v4 diff --git a/tests/files/no-unused-modules/bin.js b/tests/files/no-unused-modules/bin.js new file mode 100644 index 0000000000..c755d14027 --- /dev/null +++ b/tests/files/no-unused-modules/bin.js @@ -0,0 +1 @@ +export const bin = 'bin' diff --git a/tests/files/no-unused-modules/binObject/index.js b/tests/files/no-unused-modules/binObject/index.js new file mode 100644 index 0000000000..53cc33821f --- /dev/null +++ b/tests/files/no-unused-modules/binObject/index.js @@ -0,0 +1 @@ +export const binObject = 'binObject' diff --git a/tests/files/no-unused-modules/binObject/package.json b/tests/files/no-unused-modules/binObject/package.json new file mode 100644 index 0000000000..fa9c85326d --- /dev/null +++ b/tests/files/no-unused-modules/binObject/package.json @@ -0,0 +1,6 @@ +{ + "bin": { + "binObject": "./index.js" + }, + "private": false +} diff --git a/tests/files/no-unused-modules/browser.js b/tests/files/no-unused-modules/browser.js new file mode 100644 index 0000000000..daad8d2942 --- /dev/null +++ b/tests/files/no-unused-modules/browser.js @@ -0,0 +1 @@ +export const browser = 'browser' diff --git a/tests/files/no-unused-modules/browserObject/index.js b/tests/files/no-unused-modules/browserObject/index.js new file mode 100644 index 0000000000..92d09f8f11 --- /dev/null +++ b/tests/files/no-unused-modules/browserObject/index.js @@ -0,0 +1 @@ +export const browserObject = 'browserObject' diff --git a/tests/files/no-unused-modules/browserObject/package.json b/tests/files/no-unused-modules/browserObject/package.json new file mode 100644 index 0000000000..28272c6fef --- /dev/null +++ b/tests/files/no-unused-modules/browserObject/package.json @@ -0,0 +1,5 @@ +{ + "browser": { + "browserObject": "./index.js" + } +} diff --git a/tests/files/no-unused-modules/main/index.js b/tests/files/no-unused-modules/main/index.js new file mode 100644 index 0000000000..9eb0aade18 --- /dev/null +++ b/tests/files/no-unused-modules/main/index.js @@ -0,0 +1 @@ +export const main = 'main' diff --git a/tests/files/no-unused-modules/package.json b/tests/files/no-unused-modules/package.json new file mode 100644 index 0000000000..5aae42b811 --- /dev/null +++ b/tests/files/no-unused-modules/package.json @@ -0,0 +1,5 @@ +{ + "bin": "./bin.js", + "browser": "./browser.js", + "main": "./main/index.js" +} diff --git a/tests/files/no-unused-modules/privatePkg/index.js b/tests/files/no-unused-modules/privatePkg/index.js new file mode 100644 index 0000000000..c936cd4930 --- /dev/null +++ b/tests/files/no-unused-modules/privatePkg/index.js @@ -0,0 +1 @@ +export const privatePkg = 'privatePkg' diff --git a/tests/files/no-unused-modules/privatePkg/package.json b/tests/files/no-unused-modules/privatePkg/package.json new file mode 100644 index 0000000000..618c66d18c --- /dev/null +++ b/tests/files/no-unused-modules/privatePkg/package.json @@ -0,0 +1,4 @@ +{ + "main": "./index.js", + "private": true +} diff --git a/tests/files/ts-deprecated.ts b/tests/files/ts-deprecated.ts new file mode 100644 index 0000000000..f12e93035f --- /dev/null +++ b/tests/files/ts-deprecated.ts @@ -0,0 +1,8 @@ +/** + * this is what you get when you trust a mouse talk show + * @deprecated don't use this! + * @returns {string} nonsense + */ +export function foo() { + return 'bar' +} diff --git a/tests/src/rules/export.js b/tests/src/rules/export.js index 5ca7f458c3..a858250e2b 100644 --- a/tests/src/rules/export.js +++ b/tests/src/rules/export.js @@ -1,8 +1,6 @@ -import { test, SYNTAX_CASES } from '../utils' +import { test, SYNTAX_CASES, getTSParsers } from '../utils' import { RuleTester } from 'eslint' -import eslintPkg from 'eslint/package.json' -import semver from 'semver' var ruleTester = new RuleTester() , rule = require('rules/export') @@ -111,18 +109,7 @@ ruleTester.run('export', rule, { context('Typescript', function () { - // Typescript - const parsers = [] - - if (semver.satisfies(eslintPkg.version, '>5.0.0')) { - parsers.push(require.resolve('@typescript-eslint/parser')) - } - - if (semver.satisfies(eslintPkg.version, '<6.0.0')) { - parsers.push(require.resolve('typescript-eslint-parser')) - } - - parsers.forEach((parser) => { + getTSParsers().forEach((parser) => { const parserConfig = { parser: parser, settings: { @@ -147,6 +134,14 @@ context('Typescript', function () { `, }, parserConfig)), + test(Object.assign({ + code: ` + export function fff(a: string); + export function fff(a: number); + export function fff(a: string|number) {}; + `, + }, parserConfig)), + // namespace test(Object.assign({ code: ` diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index 90e9c8b9b5..ec8a1dbecd 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -1,7 +1,5 @@ -import { test, SYNTAX_CASES } from '../utils' +import { test, SYNTAX_CASES, getTSParsers } from '../utils' import { RuleTester } from 'eslint' -import eslintPkg from 'eslint/package.json' -import semver from 'semver' import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve' @@ -285,18 +283,7 @@ ruleTester.run('named (export *)', rule, { context('Typescript', function () { - // Typescript - const parsers = [] - - if (semver.satisfies(eslintPkg.version, '>5.0.0')) { - parsers.push(require.resolve('@typescript-eslint/parser')) - } - - if (semver.satisfies(eslintPkg.version, '<6.0.0')) { - parsers.push(require.resolve('typescript-eslint-parser')) - } - - parsers.forEach((parser) => { + getTSParsers().forEach((parser) => { ['typescript', 'typescript-declare', 'typescript-export-assign'].forEach((source) => { ruleTester.run(`named`, rule, { valid: [ diff --git a/tests/src/rules/no-amd.js b/tests/src/rules/no-amd.js index d67739f716..62de5ac26a 100644 --- a/tests/src/rules/no-amd.js +++ b/tests/src/rules/no-amd.js @@ -1,4 +1,6 @@ import { RuleTester } from 'eslint' +import eslintPkg from 'eslint/package.json' +import semver from 'semver' var ruleTester = new RuleTester() @@ -25,11 +27,11 @@ ruleTester.run('no-amd', require('rules/no-amd'), { 'define("a")', ], - invalid: [ - { code: 'define([], function() {})', errors: [ { message: 'Expected imports instead of AMD define().' }] }, - { code: 'define(["a"], function(a) { console.log(a); })', errors: [ { message: 'Expected imports instead of AMD define().' }] }, + invalid: semver.satisfies(eslintPkg.version, '< 4.0.0') ? [] : [ + { code: 'define([], function() {})', errors: [ { message: 'Expected imports instead of AMD define().' }] }, + { code: 'define(["a"], function(a) { console.log(a); })', errors: [ { message: 'Expected imports instead of AMD define().' }] }, - { code: 'require([], function() {})', errors: [ { message: 'Expected imports instead of AMD require().' }] }, - { code: 'require(["a"], function(a) { console.log(a); })', errors: [ { message: 'Expected imports instead of AMD require().' }] }, + { code: 'require([], function() {})', errors: [ { message: 'Expected imports instead of AMD require().' }] }, + { code: 'require(["a"], function(a) { console.log(a); })', errors: [ { message: 'Expected imports instead of AMD require().' }] }, ], }) diff --git a/tests/src/rules/no-commonjs.js b/tests/src/rules/no-commonjs.js index ae0377f4a1..8ca8fde509 100644 --- a/tests/src/rules/no-commonjs.js +++ b/tests/src/rules/no-commonjs.js @@ -1,4 +1,6 @@ import { RuleTester } from 'eslint' +import eslintPkg from 'eslint/package.json' +import semver from 'semver' const EXPORT_MESSAGE = 'Expected "export" or "export default"' , IMPORT_MESSAGE = 'Expected "import" instead of "require()"' @@ -59,9 +61,11 @@ ruleTester.run('no-commonjs', require('rules/no-commonjs'), { invalid: [ // imports - { code: 'var x = require("x")', errors: [ { message: IMPORT_MESSAGE }] }, - { code: 'x = require("x")', errors: [ { message: IMPORT_MESSAGE }] }, - { code: 'require("x")', errors: [ { message: IMPORT_MESSAGE }] }, + ...(semver.satisfies(eslintPkg.version, '< 4.0.0') ? [] : [ + { code: 'var x = require("x")', errors: [ { message: IMPORT_MESSAGE }] }, + { code: 'x = require("x")', errors: [ { message: IMPORT_MESSAGE }] }, + { code: 'require("x")', errors: [ { message: IMPORT_MESSAGE }] }, + ]), // exports { code: 'exports.face = "palm"', errors: [ { message: EXPORT_MESSAGE }] }, diff --git a/tests/src/rules/no-deprecated.js b/tests/src/rules/no-deprecated.js index 1ab242b007..28b8734f7e 100644 --- a/tests/src/rules/no-deprecated.js +++ b/tests/src/rules/no-deprecated.js @@ -1,4 +1,4 @@ -import { test, SYNTAX_CASES } from '../utils' +import { test, SYNTAX_CASES, getTSParsers } from '../utils' import { RuleTester } from 'eslint' @@ -197,3 +197,32 @@ ruleTester.run('no-deprecated: hoisting', rule, { ], }) + +describe('Typescript', function () { + getTSParsers().forEach((parser) => { + const parserConfig = { + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + } + + ruleTester.run(parser, rule, { + valid: [ + test(Object.assign({ + code: 'import * as hasDeprecated from \'./ts-deprecated.ts\'', + }, parserConfig)), + ], + invalid: [ + test(Object.assign({ + code: 'import { foo } from \'./ts-deprecated.ts\'; console.log(foo())', + errors: [ + { type: 'ImportSpecifier', message: 'Deprecated: don\'t use this!' }, + { type: 'Identifier', message: 'Deprecated: don\'t use this!' }, + ]}, + parserConfig)), + ], + }) + }) +}) diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index cefa6ff214..8050b56935 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -610,4 +610,32 @@ ruleTester.run('no-unused-modules', rule, { filename: testFilePath('../jsx/named.jsx')}), ], invalid: [], -}) \ No newline at end of file +}) + +describe('do not report unused export for files mentioned in package.json', () => { + ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: 'export const bin = "bin"', + filename: testFilePath('./no-unused-modules/bin.js')}), + test({ options: unusedExportsOptions, + code: 'export const binObject = "binObject"', + filename: testFilePath('./no-unused-modules/binObject/index.js')}), + test({ options: unusedExportsOptions, + code: 'export const browser = "browser"', + filename: testFilePath('./no-unused-modules/browser.js')}), + test({ options: unusedExportsOptions, + code: 'export const browserObject = "browserObject"', + filename: testFilePath('./no-unused-modules/browserObject/index.js')}), + test({ options: unusedExportsOptions, + code: 'export const main = "main"', + filename: testFilePath('./no-unused-modules/main/index.js')}), + ], + invalid: [ + test({ options: unusedExportsOptions, + code: 'export const privatePkg = "privatePkg"', + filename: testFilePath('./no-unused-modules/privatePkg/index.js'), + errors: [error(`exported declaration 'privatePkg' not used within other modules`)]}), + ], + }) +}) diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index 04236420c1..426c40a104 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -1,4 +1,4 @@ -import { test, testVersion } from '../utils' +import { test, testVersion, getTSParsers } from '../utils' import { RuleTester } from 'eslint' @@ -1372,8 +1372,7 @@ ruleTester.run('order', rule, { message: '`fs` import should occur before import of `async`', }], })), - // fix incorrect order with typescript-eslint-parser - testVersion('<6.0.0', () => ({ + ...getTSParsers().map(parser => ({ code: ` var async = require('async'); var fs = require('fs'); @@ -1382,23 +1381,7 @@ ruleTester.run('order', rule, { var fs = require('fs'); var async = require('async'); `, - parser: require.resolve('typescript-eslint-parser'), - errors: [{ - ruleId: 'order', - message: '`fs` import should occur before import of `async`', - }], - })), - // fix incorrect order with @typescript-eslint/parser - testVersion('>5.0.0', () => ({ - code: ` - var async = require('async'); - var fs = require('fs'); - `, - output: ` - var fs = require('fs'); - var async = require('async'); - `, - parser: require.resolve('@typescript-eslint/parser'), + parser, errors: [{ ruleId: 'order', message: '`fs` import should occur before import of `async`', diff --git a/tests/src/rules/prefer-default-export.js b/tests/src/rules/prefer-default-export.js index 1d670a02fe..5f96454633 100644 --- a/tests/src/rules/prefer-default-export.js +++ b/tests/src/rules/prefer-default-export.js @@ -1,4 +1,4 @@ -import { test } from '../utils' +import { test, getNonDefaultParsers } from '../utils' import { RuleTester } from 'eslint' @@ -83,7 +83,6 @@ ruleTester.run('prefer-default-export', rule, { code: 'export { a, b } from "foo.js"', parser: require.resolve('babel-eslint'), }), - // ...SYNTAX_CASES, ], invalid: [ @@ -129,4 +128,55 @@ ruleTester.run('prefer-default-export', rule, { }], }), ], -}) +}); + +context('Typescript', function() { + getNonDefaultParsers().forEach((parser) => { + const parserConfig = { + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }; + + ruleTester.run('prefer-default-export', rule, { + valid: [ + // Exporting types + test( + { + code: ` + export type foo = string; + export type bar = number;`, + parser, + }, + parserConfig, + ), + test( + { + code: ` + export type foo = string; + export type bar = number;`, + parser, + }, + parserConfig, + ), + test( + { + code: 'export type foo = string', + parser, + }, + parserConfig, + ), + test( + { + code: 'export type foo = string', + parser, + }, + parserConfig, + ), + ], + invalid: [], + }); + }); +}); diff --git a/tests/src/utils.js b/tests/src/utils.js index 9d71ad84ac..9e0c4a1e05 100644 --- a/tests/src/utils.js +++ b/tests/src/utils.js @@ -9,6 +9,22 @@ export function testFilePath(relativePath) { return path.join(process.cwd(), './tests/files', relativePath) } +export function getTSParsers() { + const parsers = []; + if (semver.satisfies(eslintPkg.version, '>=4.0.0 <6.0.0')) { + parsers.push(require.resolve('typescript-eslint-parser')); + } + + if (semver.satisfies(eslintPkg.version, '>5.0.0')) { + parsers.push(require.resolve('@typescript-eslint/parser')); + } + return parsers; +} + +export function getNonDefaultParsers() { + return getTSParsers().concat(require.resolve('babel-eslint')); +} + export const FILENAME = testFilePath('foo.js') export function testVersion(specifier, t) { diff --git a/utils/parse.js b/utils/parse.js index 2946047ad5..99e5a9334e 100644 --- a/utils/parse.js +++ b/utils/parse.js @@ -31,7 +31,14 @@ exports.default = function parse(path, content, context) { // provide the `filePath` like eslint itself does, in `parserOptions` // https://github.com/eslint/eslint/blob/3ec436ee/lib/linter.js#L637 parserOptions.filePath = path - + + // @typescript-eslint/parser will parse the entire project with typechecking if you provide + // "project" or "projects" in parserOptions. Removing these options means the parser will + // only parse one file in isolate mode, which is much, much faster. + // https://github.com/benmosher/eslint-plugin-import/issues/1408#issuecomment-509298962 + delete parserOptions.project; + delete parserOptions.projects; + // require the parser relative to the main module (i.e., ESLint) const parser = moduleRequire(parserPath)