Path: blob/main/tests/src/rules/no-extraneous-dependencies.js
829 views
import { getTSParsers, test, testFilePath } from '../utils';1import typescriptConfig from '../../../config/typescript';2import path from 'path';3import fs from 'fs';45import { RuleTester } from 'eslint';6import flatMap from 'array.prototype.flatmap';78const ruleTester = new RuleTester();9const typescriptRuleTester = new RuleTester(typescriptConfig);10const rule = require('rules/no-extraneous-dependencies');1112const packageDirWithSyntaxError = path.join(__dirname, '../../files/with-syntax-error');13const packageFileWithSyntaxErrorMessage = (() => {14try {15JSON.parse(fs.readFileSync(path.join(packageDirWithSyntaxError, 'package.json')));16} catch (error) {17return error.message;18}19})();20const packageDirWithFlowTyped = path.join(__dirname, '../../files/with-flow-typed');21const packageDirWithTypescriptDevDependencies = path.join(__dirname, '../../files/with-typescript-dev-dependencies');22const packageDirMonoRepoRoot = path.join(__dirname, '../../files/monorepo');23const packageDirMonoRepoWithNested = path.join(__dirname, '../../files/monorepo/packages/nested-package');24const packageDirWithEmpty = path.join(__dirname, '../../files/empty');25const packageDirBundleDeps = path.join(__dirname, '../../files/bundled-dependencies/as-array-bundle-deps');26const packageDirBundledDepsAsObject = path.join(__dirname, '../../files/bundled-dependencies/as-object');27const packageDirBundledDepsRaceCondition = path.join(__dirname, '../../files/bundled-dependencies/race-condition');2829const {30dependencies: deps,31devDependencies: devDeps,32} = require('../../files/package.json');3334ruleTester.run('no-extraneous-dependencies', rule, {35valid: [36...flatMap(Object.keys(deps).concat(Object.keys(devDeps)), (pkg) => [37test({ code: `import "${pkg}"` }),38test({ code: `import foo, { bar } from "${pkg}"` }),39test({ code: `require("${pkg}")` }),40test({ code: `var foo = require("${pkg}")` }),41test({ code: `export { foo } from "${pkg}"` }),42test({ code: `export * from "${pkg}"` }),43]),44test({ code: 'import "eslint"' }),45test({ code: 'import "eslint/lib/api"' }),46test({ code: 'import "fs"' }),47test({ code: 'import "./foo"' }),48test({ code: 'import "@org/package"' }),4950test({ code: 'import "electron"', settings: { 'import/core-modules': ['electron'] } }),51test({52code: 'import "eslint"',53options: [{ peerDependencies: true }],54}),5556// 'project' type57test({58code: 'import "importType"',59settings: { 'import/resolver': { node: { paths: [ path.join(__dirname, '../../files') ] } } },60}),61test({62code: 'import chai from "chai"',63options: [{ devDependencies: ['*.spec.js'] }],64filename: 'foo.spec.js',65}),66test({67code: 'import chai from "chai"',68options: [{ devDependencies: ['*.spec.js'] }],69filename: path.join(process.cwd(), 'foo.spec.js'),70}),71test({72code: 'import chai from "chai"',73options: [{ devDependencies: ['*.test.js', '*.spec.js'] }],74filename: path.join(process.cwd(), 'foo.spec.js'),75}),76test({ code: 'require(6)' }),77test({78code: 'import "doctrine"',79options: [{ packageDir: path.join(__dirname, '../../../') }],80}),81test({82code: 'import type MyType from "myflowtyped";',83options: [{ packageDir: packageDirWithFlowTyped }],84parser: require.resolve('babel-eslint'),85}),86test({87code: `88// @flow89import typeof TypeScriptModule from 'typescript';90`,91options: [{ packageDir: packageDirWithFlowTyped }],92parser: require.resolve('babel-eslint'),93}),94test({95code: 'import react from "react";',96options: [{ packageDir: packageDirMonoRepoWithNested }],97}),98test({99code: 'import leftpad from "left-pad";',100options: [{ packageDir: [packageDirMonoRepoWithNested, packageDirMonoRepoRoot] }],101}),102test({103code: 'import leftpad from "left-pad";',104options: [{ packageDir: packageDirMonoRepoRoot }],105}),106test({107code: 'import react from "react";',108options: [{ packageDir: [packageDirMonoRepoRoot, packageDirMonoRepoWithNested] }],109}),110test({111code: 'import leftpad from "left-pad";',112options: [{ packageDir: [packageDirMonoRepoRoot, packageDirMonoRepoWithNested] }],113}),114test({115code: 'import rightpad from "right-pad";',116options: [{ packageDir: [packageDirMonoRepoRoot, packageDirMonoRepoWithNested] }],117}),118test({ code: 'import foo from "@generated/foo"' }),119test({120code: 'import foo from "@generated/foo"',121options: [{ packageDir: packageDirBundleDeps }],122}),123test({124code: 'import foo from "@generated/foo"',125options: [{ packageDir: packageDirBundledDepsAsObject }],126}),127test({128code: 'import foo from "@generated/foo"',129options: [{ packageDir: packageDirBundledDepsRaceCondition }],130}),131test({ code: 'export function getToken() {}' }),132test({ code: 'export class Component extends React.Component {}' }),133test({ code: 'export function Component() {}' }),134test({ code: 'export const Component = () => {}' }),135136test({137code: 'import "not-a-dependency"',138filename: path.join(packageDirMonoRepoRoot, 'foo.js'),139options: [{ packageDir: packageDirMonoRepoRoot }],140settings: { 'import/core-modules': ['not-a-dependency'] },141}),142test({143code: 'import "@generated/bar/module"',144settings: { 'import/core-modules': ['@generated/bar'] },145}),146test({147code: 'import "@generated/bar/and/sub/path"',148settings: { 'import/core-modules': ['@generated/bar'] },149}),150// check if "rxjs" dependency declaration fix the "rxjs/operators subpackage151test({152code: 'import "rxjs/operators"',153}),154155test({156code: 'import "esm-package/esm-module";',157}),158159test({160code: `161import "alias/esm-package/esm-module";162import 'expose-loader?exposes[]=$&exposes[]=jQuery!jquery';163`,164settings: { 'import/resolver': 'webpack' },165}),166],167invalid: [168test({169code: 'import "not-a-dependency"',170filename: path.join(packageDirMonoRepoRoot, 'foo.js'),171options: [{ packageDir: packageDirMonoRepoRoot }],172errors: [{173message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',174}],175}),176test({177code: 'import "not-a-dependency"',178filename: path.join(packageDirMonoRepoWithNested, 'foo.js'),179options: [{ packageDir: packageDirMonoRepoRoot }],180errors: [{181message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',182}],183}),184test({185code: 'import "not-a-dependency"',186options: [{ packageDir: packageDirMonoRepoRoot }],187errors: [{188message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',189}],190}),191test({192code: 'import "not-a-dependency"',193errors: [{194message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',195}],196}),197test({198code: 'var donthaveit = require("@org/not-a-dependency")',199errors: [{200message: '\'@org/not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S @org/not-a-dependency\' to add it',201}],202}),203test({204code: 'var donthaveit = require("@org/not-a-dependency/foo")',205errors: [{206message: '\'@org/not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S @org/not-a-dependency\' to add it',207}],208}),209test({210code: 'import "eslint"',211options: [{ devDependencies: false, peerDependencies: false }],212errors: [{213message: '\'eslint\' should be listed in the project\'s dependencies, not devDependencies.',214}],215}),216test({217code: 'import "lodash.isarray"',218options: [{ optionalDependencies: false }],219errors: [{220message: '\'lodash.isarray\' should be listed in the project\'s dependencies, not optionalDependencies.',221}],222}),223test({224code: 'var foo = require("not-a-dependency")',225errors: [{226message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',227}],228}),229test({230code: 'var glob = require("glob")',231options: [{ devDependencies: false }],232errors: [{233message: '\'glob\' should be listed in the project\'s dependencies, not devDependencies.',234}],235}),236test({237code: 'import chai from "chai"',238options: [{ devDependencies: ['*.test.js'] }],239filename: 'foo.tes.js',240errors: [{241message: '\'chai\' should be listed in the project\'s dependencies, not devDependencies.',242}],243}),244test({245code: 'import chai from "chai"',246options: [{ devDependencies: ['*.test.js'] }],247filename: path.join(process.cwd(), 'foo.tes.js'),248errors: [{249message: '\'chai\' should be listed in the project\'s dependencies, not devDependencies.',250}],251}),252test({253code: 'import chai from "chai"',254options: [{ devDependencies: ['*.test.js', '*.spec.js'] }],255filename: 'foo.tes.js',256errors: [{257message: '\'chai\' should be listed in the project\'s dependencies, not devDependencies.',258}],259}),260test({261code: 'import chai from "chai"',262options: [{ devDependencies: ['*.test.js', '*.spec.js'] }],263filename: path.join(process.cwd(), 'foo.tes.js'),264errors: [{265message: '\'chai\' should be listed in the project\'s dependencies, not devDependencies.',266}],267}),268test({269code: 'var eslint = require("lodash.isarray")',270options: [{ optionalDependencies: false }],271errors: [{272message: '\'lodash.isarray\' should be listed in the project\'s dependencies, not optionalDependencies.',273}],274}),275test({276code: 'import "not-a-dependency"',277options: [{ packageDir: path.join(__dirname, '../../../') }],278errors: [{279message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',280}],281}),282test({283code: 'import "bar"',284options: [{ packageDir: path.join(__dirname, './doesn-exist/') }],285errors: [{286message: 'The package.json file could not be found.',287}],288}),289test({290code: 'import foo from "foo"',291options: [{ packageDir: packageDirWithSyntaxError }],292errors: [{293message: 'The package.json file could not be parsed: ' + packageFileWithSyntaxErrorMessage,294}],295}),296test({297code: 'import leftpad from "left-pad";',298filename: path.join(packageDirMonoRepoWithNested, 'foo.js'),299options: [{ packageDir: packageDirMonoRepoWithNested }],300errors: [{301message: "'left-pad' should be listed in the project's dependencies. Run 'npm i -S left-pad' to add it",302}],303}),304test({305code: 'import react from "react";',306filename: path.join(packageDirMonoRepoRoot, 'foo.js'),307errors: [{308message: "'react' should be listed in the project's dependencies. Run 'npm i -S react' to add it",309}],310}),311test({312code: 'import react from "react";',313filename: path.join(packageDirMonoRepoWithNested, 'foo.js'),314options: [{ packageDir: packageDirMonoRepoRoot }],315errors: [{316message: "'react' should be listed in the project's dependencies. Run 'npm i -S react' to add it",317}],318}),319test({320code: 'import "react";',321filename: path.join(packageDirWithEmpty, 'index.js'),322options: [{ packageDir: packageDirWithEmpty }],323errors: [{324message: "'react' should be listed in the project's dependencies. Run 'npm i -S react' to add it",325}],326}),327test({328code: 'import bar from "@generated/bar"',329errors: ["'@generated/bar' should be listed in the project's dependencies. Run 'npm i -S @generated/bar' to add it"],330}),331test({332code: 'import foo from "@generated/foo"',333options: [{ bundledDependencies: false }],334errors: ["'@generated/foo' should be listed in the project's dependencies. Run 'npm i -S @generated/foo' to add it"],335}),336test({337code: 'import bar from "@generated/bar"',338options: [{ packageDir: packageDirBundledDepsRaceCondition }],339errors: ["'@generated/bar' should be listed in the project's dependencies. Run 'npm i -S @generated/bar' to add it"],340}),341test({342code: 'export { foo } from "not-a-dependency";',343errors: [{344message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',345}],346}),347test({348code: 'export * from "not-a-dependency";',349errors: [{350message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',351}],352}),353test({354code: 'import chai from "alias/chai";',355settings: { 'import/resolver': 'webpack' },356errors: [{357// missing dependency is chai not alias358message: "'chai' should be listed in the project's dependencies. Run 'npm i -S chai' to add it",359}],360}),361362test({363code: 'import "not-a-dependency"',364filename: path.join(packageDirMonoRepoRoot, 'foo.js'),365options: [{ packageDir: packageDirMonoRepoRoot }],366errors: [{367message: `'not-a-dependency' should be listed in the project's dependencies. Run 'npm i -S not-a-dependency' to add it`,368}],369}),370371test({372code: 'import "esm-package-not-in-pkg-json/esm-module";',373errors: [{374message: `'esm-package-not-in-pkg-json' should be listed in the project's dependencies. Run 'npm i -S esm-package-not-in-pkg-json' to add it`,375}],376}),377],378});379380describe('TypeScript', () => {381getTSParsers()382// Type-only imports were added in TypeScript ESTree 2.23.0383.filter((parser) => parser !== require.resolve('typescript-eslint-parser'))384.forEach((parser) => {385const parserConfig = {386parser,387settings: {388'import/parsers': { [parser]: ['.ts'] },389'import/resolver': ['node', 'typescript'],390},391};392393ruleTester.run('no-extraneous-dependencies', rule, {394valid: [395test(Object.assign({396code: 'import type T from "a";',397options: [{ packageDir: packageDirWithTypescriptDevDependencies, devDependencies: false }],398}, parserConfig)),399],400invalid: [401test(Object.assign({402code: 'import T from "a";',403options: [{ packageDir: packageDirWithTypescriptDevDependencies, devDependencies: false }],404errors: [{405message: "'a' should be listed in the project's dependencies, not devDependencies.",406}],407}, parserConfig)),408],409});410});411});412413typescriptRuleTester.run('no-extraneous-dependencies typescript type imports', rule, {414valid: [415test({416code: 'import type MyType from "not-a-dependency";',417filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'),418parser: require.resolve('babel-eslint'),419}),420test({421code: 'import type { MyType } from "not-a-dependency";',422filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'),423parser: require.resolve('babel-eslint'),424}),425],426invalid: [427],428});429430431