From 6f19984b2ef5df0bd72d359c408270d7ed69ca0a Mon Sep 17 00:00:00 2001 From: Thomas Marek Date: Fri, 14 Apr 2017 17:18:23 -0400 Subject: [PATCH] Add no-import-module-exports rule --- src/index.js | 1 + src/rules/no-import-module-exports.js | 72 +++++++++++++++ tests/files/package.json | 1 + tests/src/rules/no-import-module-exports.js | 98 +++++++++++++++++++++ 4 files changed, 172 insertions(+) create mode 100644 src/rules/no-import-module-exports.js create mode 100644 tests/src/rules/no-import-module-exports.js diff --git a/src/index.js b/src/index.js index 69cbc2f5e6..3519079203 100644 --- a/src/index.js +++ b/src/index.js @@ -30,6 +30,7 @@ export const rules = { 'no-dynamic-require': require('./rules/no-dynamic-require'), 'unambiguous': require('./rules/unambiguous'), 'no-unassigned-import': require('./rules/no-unassigned-import'), + 'no-import-module-exports': require('./rules/no-import-module-exports'), // metadata-based 'no-deprecated': require('./rules/no-deprecated'), diff --git a/src/rules/no-import-module-exports.js b/src/rules/no-import-module-exports.js new file mode 100644 index 0000000000..bd17e8c2c5 --- /dev/null +++ b/src/rules/no-import-module-exports.js @@ -0,0 +1,72 @@ +import minimatch from 'minimatch' +import path from 'path' +import readPkgUp from 'read-pkg-up' + +function getEntryPoint(context) { + try { + const pkg = readPkgUp.sync({cwd: context.getFilename(), normalize: false}) + return pkg && pkg.pkg + ? path.join(process.cwd(), pkg.pkg.main) + : null + } catch (e) { + return null + } +} + +module.exports = { + meta: { + docs: { + description: 'Disallow import statements with module.exports', + category: 'Best Practices', + recommended: true, + }, + fixable: 'code', + schema: [ + { + 'type': 'object', + 'properties': { + 'exceptions': { 'type': 'array' }, + }, + 'additionalProperties': false, + }, + ], + }, + create(context) { + const importDeclarations = [] + const entryPoint = getEntryPoint(context) + const options = context.options[0] || {} + let alreadyReported = false + + function report(node) { + const fileName = context.getFilename() + const isEntryPoint = entryPoint === fileName + const isIdentifier = node.object.type === 'Identifier' + const hasKeywords = (/^(module|exports)$/).test(node.object.name) + const isException = options.exceptions + ? options.exceptions.some(glob => minimatch(fileName, glob)) + : false + + if (isIdentifier && hasKeywords && !isEntryPoint && !isException) { + importDeclarations.forEach(importDeclaration => { + context.report({ + node: importDeclaration, + message: `Cannot use import declarations in modules that export using ` + + `CommonJS (module.exports = 'foo' or exports.bar = 'hi')`, + }) + }) + alreadyReported = true + } + } + + return { + ImportDeclaration(node) { + importDeclarations.push(node) + }, + MemberExpression(node) { + if (!alreadyReported) { + report(node) + } + }, + } + }, +} diff --git a/tests/files/package.json b/tests/files/package.json index 3be7b41ae0..81476b4407 100644 --- a/tests/files/package.json +++ b/tests/files/package.json @@ -1,5 +1,6 @@ { "dummy": true, + "main": "tests/files/lib/main.js", "devDependencies": { "glob": "1.0.0", "eslint": "2.x" diff --git a/tests/src/rules/no-import-module-exports.js b/tests/src/rules/no-import-module-exports.js new file mode 100644 index 0000000000..71f12695f7 --- /dev/null +++ b/tests/src/rules/no-import-module-exports.js @@ -0,0 +1,98 @@ +import path from 'path' +import { RuleTester } from 'eslint' + +import { test, testFilePath } from '../utils' + +const ruleTester = new RuleTester({ + parserOptions: { ecmaVersion: 6, sourceType: 'module' } +}) +const rule = require('rules/no-import-module-exports') + +const error = { + message: `Cannot use import declarations in modules that export using CommonJS ` + + `(module.exports = 'foo' or exports.bar = 'hi')`, + type: 'ImportDeclaration', +} + +ruleTester.run('no-import-module-exports', rule, { + valid: [ + test({ + code: ` + const thing = require('thing') + module.exports = thing + `, + }), + test({ + code: ` + import thing from 'otherthing' + console.log(thing.module.exports) + `, + }), + test({ + code: ` + import thing from 'other-thing' + export default thing + `, + }), + test({ + code: ` + import foo from 'path'; + module.exports = foo; + `, + // When the file matches the entry point defined in package.json + // See tests/files/package.json + filename: path.join(process.cwd(), 'tests/files/lib/main.js'), + }), + test({ + code: ` + import foo from 'path'; + module.exports = foo; + `, + filename: path.join(process.cwd(), 'tests/files/some/other/entry-point.js'), + options: [{ exceptions: ['**/*/other/entry-point.js'] }], + }), + ], + invalid: [ + test({ + code: ` + import { stuff } from 'starwars' + module.exports = thing + `, + errors: [error], + }), + test({ + code: ` + import thing from 'starwars' + const baz = module.exports = thing + console.log(baz) + `, + errors: [error], + }), + test({ + code: ` + import * as allThings from 'starwars' + exports.bar = thing + `, + errors: [error], + }), + test({ + code: ` + import foo from 'path'; + module.exports = foo; + `, + // When the file does not match the entry point defined in package.json + // See tests/files/package.json + filename: path.join(process.cwd(), 'tests/files/not/lib/main.js'), + errors: [error], + }), + test({ + code: ` + import foo from 'path'; + module.exports = foo; + `, + filename: path.join(process.cwd(), 'tests/files/some/other/entry-point.js'), + options: [{ exceptions: ['**/*/other/file.js'] }], + errors: [error], + }), + ], +})