├── .eslintignore ├── .eslintrc.yaml ├── .gitignore ├── .travis.yml ├── README.md ├── _helpers.js ├── case-sensitive.js ├── index.js ├── no-cycles.js ├── no-unresolved.js ├── package.json ├── require-json-ext.js └── test ├── case-sensitive-test.js ├── case-sensitive ├── Bar │ └── baz.js ├── foo.js ├── quux.node └── qux.json ├── custom-path └── foo.js ├── cycles-dups ├── a.js ├── b.js └── c.js ├── cycles-multi-direct-extern ├── a.js ├── b.js └── c.js ├── cycles-multi-direct ├── a.js ├── b.js └── c.js ├── cycles-multi-part-direct ├── a.js ├── b.js └── c.js ├── cycles-self-ref-direct └── a.js ├── cycles-self-ref-extern ├── a.js └── b.js ├── cycles-types-multi-direct ├── a.js ├── b.js └── c.js ├── cycles-with-babel-eslint ├── a.js ├── b.js └── c.js ├── no-cycles-test.js ├── no-unresolved-test.js ├── require-json-test.js ├── require-json ├── bar.json ├── foo.js └── foo.json └── resolve ├── bar.json ├── baz.node ├── foo.js └── foo.json /.eslintignore: -------------------------------------------------------------------------------- 1 | test/*/** 2 | -------------------------------------------------------------------------------- /.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | env: 3 | node: true 4 | 5 | rules: 6 | # eslint:recommended 7 | constructor-super: 1 8 | no-class-assign: 1 9 | no-cond-assign: [1, except-parens] 10 | no-const-assign: 1 11 | no-constant-condition: 1 12 | no-control-regex: 1 13 | no-debugger: 1 14 | no-delete-var: 1 15 | no-dupe-args: 1 16 | no-dupe-class-members: 1 17 | no-dupe-keys: 1 18 | no-duplicate-case: 1 19 | no-empty-character-class: 1 20 | no-empty-pattern: 1 21 | no-ex-assign: 1 22 | no-extra-boolean-cast: 1 23 | no-extra-semi: 1 24 | no-fallthrough: 1 25 | no-func-assign: 1 26 | no-inner-declarations: [1, functions] 27 | no-invalid-regexp: 1 28 | no-irregular-whitespace: 1 29 | no-mixed-spaces-and-tabs: [1, smart-tabs] 30 | no-negated-in-lhs: 1 31 | no-new-symbol: 1 32 | no-obj-calls: 1 33 | no-octal: 1 34 | no-redeclare: 1 35 | no-regex-spaces: 1 36 | no-self-assign: 1 37 | no-sparse-arrays: 1 38 | no-this-before-super: 1 39 | no-undef: 1 40 | no-unexpected-multiline: 1 41 | no-unreachable: 1 42 | no-unused-vars: [1, args: none] 43 | use-isnan: 1 44 | valid-typeof: 1 45 | 46 | comma-dangle: [1, always-multiline] 47 | no-case-declarations: 1 48 | 49 | no-shadow: 1 50 | no-unsafe-finally: 1 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "0.10" 5 | - "0.12" 6 | - "4" 7 | - "5" 8 | - "6" 9 | os: 10 | - linux 11 | - osx 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eslint-plugin-dependencies 2 | 3 | [![Build Status](https://travis-ci.org/zertosh/eslint-plugin-dependencies.svg?branch=master)](https://travis-ci.org/zertosh/eslint-plugin-dependencies) 4 | 5 | ## Usage 6 | 7 | ```sh 8 | npm install eslint-plugin-dependencies 9 | ``` 10 | 11 | In your `.eslintrc`: 12 | 13 | ```json 14 | { 15 | "plugins": [ 16 | "dependencies" 17 | ], 18 | "rules": { 19 | "dependencies/case-sensitive": 1, 20 | "dependencies/no-cycles": 1, 21 | "dependencies/no-unresolved": 1, 22 | "dependencies/require-json-ext": 1 23 | } 24 | } 25 | ``` 26 | 27 | ## Rules 28 | 29 | An [eslint](https://github.com/eslint/eslint) plugin that ... 30 | 31 | ### `dependencies/case-sensitive` 32 | 33 | Verifies that `require("…")`, `require.resolve(…)`, `import "…"` and `export … from "…"` ids match the case that is reported by a directory listing. 34 | 35 | ```json 36 | { 37 | "plugins": [ 38 | "dependencies" 39 | ], 40 | "rules": { 41 | "dependencies/case-sensitive": [1, {"paths": ["custom-path-to-search-for-modules"]}] 42 | } 43 | } 44 | ``` 45 | 46 | ### `dependencies/no-cycles` 47 | 48 | Prevents cyclic references between modules. It resolves `require("…")`, `import "…"` and `export … from "…"` references to internal modules (i.e. not `node_modules`), to determine whether there is a cycle. If you're using a custom parser, the rule will use that to parse the dependencies. The rule takes a `skip` array of strings, that will be treated as regexps to skip checking files. 49 | 50 | Additionally, with the `types` option enabled, you can detect and prevent `import type` cycles as well. This can be helpful, since the [Flow checker](https://flow.org) can exhibit unexpected behavior with such cycles. 51 | 52 | ```json 53 | { 54 | "plugins": [ 55 | "dependencies" 56 | ], 57 | "rules": { 58 | "dependencies/no-cycles": [1, {"skip": ["/spec/", "/vendor/"], "types": true}] 59 | } 60 | } 61 | ``` 62 | 63 | ### `dependencies/no-unresolved` 64 | 65 | Checks that `require("…")`, `require.resolve(…)`, `import "…"` and `export … from "…"` reference modules that exist. Takes an `ignore` array of modules to ignore. 66 | 67 | ```json 68 | { 69 | "plugins": [ 70 | "dependencies" 71 | ], 72 | "rules": { 73 | "dependencies/no-unresolved": [1, {"ignore": ["atom"], "paths": ["custom-path-to-search-for-modules"]}] 74 | } 75 | } 76 | ``` 77 | 78 | ### `dependencies/require-json-ext` 79 | 80 | Ensures that modules are that are `.json` include their extension in the module id. Supports auto fix. 81 | -------------------------------------------------------------------------------- /_helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var resolve = require('resolve'); 4 | 5 | function StorageObject() {} 6 | StorageObject.prototype = Object.create(null); 7 | 8 | // A cache that resets itself after one tick. 9 | function oneTickCache() { 10 | var store = null; 11 | function reset() { store = null; } 12 | return function() { 13 | if (store === null) { 14 | store = new StorageObject(); 15 | process.nextTick(reset); 16 | } 17 | return store; 18 | }; 19 | } 20 | 21 | // The resolve cache is shared by all the rules, since the operation is very 22 | // common and expensive. 23 | var _resolveCache = oneTickCache(); 24 | function resolveSync(x, opts) { 25 | var resolveCache = _resolveCache(); 26 | var cacheKey = JSON.stringify([x, opts]); 27 | if (!(cacheKey in resolveCache)) { 28 | try { 29 | resolveCache[cacheKey] = resolve.sync(x, opts); 30 | } catch(err) { 31 | resolveCache[cacheKey] = null; 32 | } 33 | } 34 | return resolveCache[cacheKey]; 35 | } 36 | 37 | function isRequireCall(node) { 38 | // require("…"); 39 | return ( 40 | node.type === 'CallExpression' && 41 | node.callee.type === 'Identifier' && 42 | node.callee.name === 'require' && 43 | node.arguments[0] && 44 | node.arguments[0].type === 'Literal' 45 | ); 46 | } 47 | 48 | function isRequireResolveCall(node) { 49 | // require.resolve("…"); 50 | return ( 51 | node.type === 'CallExpression' && 52 | node.callee.type === 'MemberExpression' && 53 | node.callee.computed === false && 54 | node.callee.object.type === 'Identifier' && 55 | node.callee.object.name === 'require' && 56 | node.callee.property.type === 'Identifier' && 57 | node.callee.property.name === 'resolve' && 58 | node.arguments[0] && 59 | node.arguments[0].type === 'Literal' 60 | ); 61 | } 62 | 63 | function isImport(node) { 64 | // import … from "…"; 65 | // import "…"; 66 | return ( 67 | node.type === 'ImportDeclaration' && 68 | (node.importKind == null || node.importKind === 'value') && 69 | node.source && 70 | node.source.type === 'Literal' 71 | ); 72 | } 73 | 74 | function isImportType(node) { 75 | // import type … from "…"; 76 | return ( 77 | node.type === 'ImportDeclaration' && 78 | node.importKind === 'type' && 79 | node.source && 80 | node.source.type === 'Literal' 81 | ); 82 | } 83 | 84 | function isExportFrom(node) { 85 | // export * from "…"; 86 | // export … from "…"; 87 | return ( 88 | node.type === 'ExportAllDeclaration' || 89 | node.type === 'ExportNamedDeclaration' 90 | ) && ( 91 | (node.exportKind == null || node.exportKind === 'value') && 92 | node.source && 93 | node.source.type === 'Literal' 94 | ); 95 | } 96 | 97 | function isExportTypeFrom(node) { 98 | // export type * from "…"; 99 | // export type … from "…"; 100 | return ( 101 | node.type === 'ExportAllDeclaration' || 102 | node.type === 'ExportNamedDeclaration' 103 | ) && ( 104 | node.exportKind === 'type' && 105 | node.source && 106 | node.source.type === 'Literal' 107 | ); 108 | } 109 | 110 | function getModuleId(node) { 111 | if (isRequireCall(node) || isRequireResolveCall(node)) { 112 | return node.arguments[0].value; 113 | } else if (isImport(node) || isExportFrom(node) || 114 | isImportType(node) || isExportTypeFrom(node)) { 115 | return node.source.value; 116 | } else { 117 | return null; 118 | } 119 | } 120 | 121 | function getIdNode(node) { 122 | if (isRequireCall(node) || isRequireResolveCall(node)) { 123 | return node.arguments[0]; 124 | } else if (isImport(node) || isExportFrom(node) || 125 | isImportType(node) || isExportTypeFrom(node)) { 126 | return node.source; 127 | } else { 128 | return null; 129 | } 130 | } 131 | 132 | module.exports = { 133 | StorageObject: StorageObject, 134 | oneTickCache: oneTickCache, 135 | resolveSync: resolveSync, 136 | isRequireCall: isRequireCall, 137 | isRequireResolveCall: isRequireResolveCall, 138 | isImport: isImport, 139 | isImportType: isImportType, 140 | isExportFrom: isExportFrom, 141 | isExportTypeFrom: isExportTypeFrom, 142 | getIdNode: getIdNode, 143 | getModuleId: getModuleId, 144 | }; 145 | -------------------------------------------------------------------------------- /case-sensitive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var commondir = require('commondir'); 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var resolve = require('resolve'); 7 | 8 | var helpers = require('./_helpers'); 9 | 10 | var nodeExts = /\.(js|json|node)$/; 11 | 12 | var _readdirCache = helpers.oneTickCache(); 13 | function readdirSync(dirname) { 14 | var readdirCache = _readdirCache(); 15 | if (!(dirname in readdirCache)) { 16 | try { 17 | readdirCache[dirname] = fs.readdirSync(dirname); 18 | } catch (err) { 19 | readdirCache[dirname] = null; 20 | } 21 | } 22 | return readdirCache[dirname]; 23 | } 24 | 25 | // turns "/a/b/c.js" into ["/a", "/a/b", "/a/b/c.js"] 26 | function pathSteps(pathString) { 27 | return pathString 28 | .split(path.sep) 29 | .map(function(part, i, parts) { 30 | return parts.slice(0, i + 1).join(path.sep); 31 | }) 32 | .filter(Boolean); 33 | } 34 | 35 | // if more than one possible suggestion is found, return none. 36 | function getCaseSuggestion(needle, haystack) { 37 | var found = false; 38 | var lneedle = needle.toLowerCase(); 39 | for (var i = 0; i < haystack.length; i++) { 40 | if (lneedle === haystack[i].toLowerCase()) { 41 | if (found) return false; 42 | found = haystack[i]; 43 | } 44 | } 45 | return found; 46 | } 47 | 48 | module.exports = function(context) { 49 | var target = context.getFilename(); 50 | 51 | var resolveOpts = { 52 | basedir: path.dirname(target), 53 | extensions: ['.js', '.json', '.node'], 54 | }; 55 | 56 | if (context.options[0] && context.options[0].paths) { 57 | resolveOpts.paths = context.options[0].paths.map(function(single_path) { 58 | return path.resolve(single_path); 59 | }); 60 | } 61 | 62 | function validate(node) { 63 | var id = helpers.getModuleId(node); 64 | var resolved = helpers.resolveSync(id, resolveOpts, context); 65 | if (!resolved || resolve.isCore(resolved)) return; 66 | var prefix = commondir([target, resolved]); 67 | pathSteps(resolved) 68 | .filter(function(step) { 69 | // remove the steps outside of our request 70 | return step.indexOf(prefix) !== -1; 71 | }) 72 | .forEach(function(step, i, steps) { 73 | var basename = path.basename(step); 74 | var dirname = path.dirname(step); 75 | var dirlist = readdirSync(dirname); 76 | 77 | // we don't have permission? 78 | if (!dirlist) return; 79 | 80 | // compare the directory listing to the requested path. this works 81 | // because "resolve" resolves by concating the path segments from the 82 | // input, so the resolved path will have the incorrect case: 83 | if (dirlist.indexOf(basename) !== -1) return; 84 | 85 | var shouldRemoveExt = 86 | i === steps.length - 1 && // last step 87 | nodeExts.test(basename) && // expected 88 | !nodeExts.test(id); // actual 89 | 90 | var suggestion = getCaseSuggestion(basename, dirlist); 91 | 92 | var incorrect = shouldRemoveExt 93 | ? basename.replace(nodeExts, '') 94 | : basename; 95 | 96 | var correct = shouldRemoveExt && suggestion 97 | ? suggestion.replace(nodeExts, '') 98 | : suggestion; 99 | 100 | var idNode = helpers.getIdNode(node); 101 | 102 | if (correct) { 103 | context.report({ 104 | node: idNode, 105 | data: {incorrect: incorrect, correct: correct}, 106 | message: 'Case mismatch in "{{incorrect}}", expected "{{correct}}".', 107 | }); 108 | } else { 109 | context.report({ 110 | node: idNode, 111 | data: {incorrect: incorrect}, 112 | message: 'Case mismatch in "{{incorrect}}".', 113 | }); 114 | } 115 | }); 116 | } 117 | 118 | return { 119 | CallExpression: function(node) { 120 | if (helpers.isRequireCall(node) || 121 | helpers.isRequireResolveCall(node)) { 122 | validate(node); 123 | } 124 | }, 125 | ImportDeclaration: function(node) { 126 | if (helpers.isImport(node) || helpers.isImportType(node)) { 127 | validate(node); 128 | } 129 | }, 130 | ExportAllDeclaration: function(node) { 131 | if (helpers.isExportFrom(node) || helpers.isExportTypeFrom(node)) { 132 | validate(node); 133 | } 134 | }, 135 | ExportNamedDeclaration: function(node) { 136 | if (helpers.isExportFrom(node) || helpers.isExportTypeFrom(node)) { 137 | validate(node); 138 | } 139 | }, 140 | }; 141 | }; 142 | 143 | module.exports.schema = { 144 | paths: { 145 | type: 'array', 146 | items: { 147 | type: 'string', 148 | }, 149 | }, 150 | }; 151 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | rules: { 5 | 'case-sensitive': require('./case-sensitive'), 6 | 'no-cycles': require('./no-cycles'), 7 | 'no-unresolved': require('./no-unresolved'), 8 | 'require-json-ext': require('./require-json-ext'), 9 | }, 10 | rulesConfig: { 11 | 'case-sensitive': 0, 12 | 'no-cycles': 0, 13 | 'no-unresolved': 0, 14 | 'require-json-ext': 0, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /no-cycles.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var helpers = require('./_helpers'); 6 | 7 | /** 8 | * A reference to the eslint module is needed to be able to require the same 9 | * parser that used to parse the file being linted, as well as to use eslint's 10 | * own traverser. 11 | */ 12 | 13 | var eslintModule = (function() { 14 | var parent = module.parent; 15 | var eslintLibRe = new RegExp( 16 | '\\' + path.sep + 'node_modules' + 17 | '\\' + path.sep + 'eslint' + 18 | '\\' + path.sep + 'lib' + 19 | '\\' + path.sep + '[^\\' + path.sep + ']+\\.js$' 20 | ); 21 | do { 22 | if (eslintLibRe.test(parent.filename)) { 23 | return parent; 24 | } 25 | } while ((parent = parent.parent)); 26 | throw new Error('Could not find eslint'); 27 | })(); 28 | 29 | var traverser = (function() { 30 | var Traverser; 31 | try { 32 | // ESLint >=6.0.0 33 | Traverser = eslintModule.require('./shared/traverser'); 34 | } catch (err) { 35 | // ESLint <6.0.0 36 | Traverser = eslintModule.require('./util/traverser'); 37 | } 38 | return new Traverser(); 39 | })(); 40 | 41 | //------------------------------------------------------------------------------ 42 | // Utils 43 | //------------------------------------------------------------------------------ 44 | 45 | var externalRe = /^[^./]/; 46 | var skipExts = /\.(?:json|node)$/; 47 | var searchRe = /\b(?:require|import|export)\b/; 48 | 49 | var NoopVisitor = { 50 | Program: function() {}, 51 | }; 52 | 53 | function parse(src, parserPath, parserOptions) { 54 | var parser = eslintModule.require(parserPath); 55 | try { 56 | return parser.parse(src, parserOptions); 57 | } catch (err) { 58 | return null; 59 | } 60 | } 61 | 62 | function read(filename) { 63 | try { 64 | return fs.readFileSync(filename, 'utf8'); 65 | } catch(err) { 66 | return null; 67 | } 68 | } 69 | 70 | function resolver(name, basedir) { 71 | if (!externalRe.test(name)) { 72 | var opts = { 73 | basedir: basedir, 74 | extensions: ['.js', '.json', '.node'], 75 | }; 76 | var resolved = helpers.resolveSync(name, opts); 77 | if (resolved && !skipExts.test(resolved)) { 78 | return resolved; 79 | } 80 | } 81 | return null; 82 | } 83 | 84 | function relativizeTrace(trace, basedir) { 85 | var out = []; 86 | for (var i = 0; i < trace.length; i++) { 87 | out.push(path.relative(basedir, trace[i])); 88 | basedir = path.dirname(trace[i]); 89 | } 90 | return out; 91 | } 92 | 93 | var _depsCache = helpers.oneTickCache(); 94 | function getDeps(filename, src, ast, context, options) { 95 | var depsCache = _depsCache(); 96 | if (depsCache[filename]) return depsCache[filename]; 97 | var found = depsCache[filename] = []; 98 | 99 | if (skipExts.test(filename)) return found; 100 | 101 | if (!src) src = read(filename); 102 | if (!src) return found; 103 | 104 | if (!searchRe.test(src)) return found; 105 | 106 | if (!ast) ast = parse(src, context.parserPath, context.parserOptions); 107 | if (!ast) return found; 108 | 109 | var basedir = path.dirname(filename); 110 | 111 | traverser.traverse(ast, { 112 | enter: function(node, parent) { 113 | var start = node.range ? node.range[0] : node.start; 114 | var end = node.range ? node.range[1] : node.end; 115 | var section = src.slice(start, end); 116 | if (!searchRe.test(section)) return this.skip(); 117 | 118 | if (helpers.isRequireCall(node) || 119 | helpers.isImport(node) || 120 | (options.types && helpers.isImportType(node)) || 121 | helpers.isExportFrom(node)) { 122 | var id = helpers.getModuleId(node); 123 | var resolved = resolver(id, basedir); 124 | if (resolved) found.push(resolved); 125 | } 126 | }, 127 | }); 128 | 129 | return found; 130 | } 131 | 132 | //------------------------------------------------------------------------------ 133 | // Rule 134 | //------------------------------------------------------------------------------ 135 | 136 | module.exports = function(context) { 137 | var target = context.getFilename(); 138 | 139 | var options = context.options[0] || {}; 140 | var skip = options.skip; 141 | var shouldSkip = skip && skip.some(function(pattern) { 142 | return RegExp(pattern).test(target); 143 | }); 144 | if (shouldSkip) { 145 | return NoopVisitor; 146 | } 147 | 148 | var seen = new helpers.StorageObject(); 149 | var basedir = path.dirname(target); 150 | 151 | function trace(filename, depth, refs) { 152 | if (!depth) depth = []; 153 | if (!refs) refs = []; 154 | var deps = getDeps(filename, null, null, context, options); 155 | depth.push(filename); 156 | for (var i = 0; i < deps.length; i++) { 157 | filename = deps[i]; 158 | if (filename === target) { 159 | refs.push(depth.slice()); 160 | } else if (!seen[filename]) { 161 | seen[filename] = true; 162 | trace(filename, depth, refs); 163 | } 164 | } 165 | depth.pop(); 166 | return refs; 167 | } 168 | 169 | function validate(node) { 170 | var id = helpers.getModuleId(node); 171 | var resolved = resolver(id, basedir); 172 | if (resolved === target) { 173 | context.report({ 174 | node: node, 175 | message: 'Self-reference cycle.', 176 | }); 177 | } else if (resolved) { 178 | var refs = trace(resolved); 179 | for (var i = 0; i < refs.length; i++) { 180 | var prettyTrace = relativizeTrace(refs[i], basedir).join(' => '); 181 | context.report({ 182 | node: node, 183 | data: {trace: prettyTrace}, 184 | message: 'Cycle in {{trace}}.', 185 | }); 186 | } 187 | } 188 | } 189 | 190 | return { 191 | CallExpression: function(node) { 192 | // no-cycles doesn't test for "require.resolve" 193 | if (helpers.isRequireCall(node)) { 194 | validate(node); 195 | } 196 | }, 197 | ImportDeclaration: function(node) { 198 | if (helpers.isImport(node) || 199 | (options.types && helpers.isImportType(node))) { 200 | validate(node); 201 | } 202 | }, 203 | ExportAllDeclaration: function(node) { 204 | if (helpers.isExportFrom(node)) { 205 | validate(node); 206 | } 207 | }, 208 | ExportNamedDeclaration: function(node) { 209 | if (helpers.isExportFrom(node)) { 210 | validate(node); 211 | } 212 | }, 213 | 'Program:exit': function(node) { 214 | // since this ast has already been built, and traversing is cheap, 215 | // run it through references.deps so it's cached for future runs. 216 | getDeps(target, context.getSourceCode().text, node, context, options); 217 | }, 218 | }; 219 | }; 220 | 221 | module.exports.schema = { 222 | skip: { 223 | type: 'array', 224 | items: { 225 | type: 'string', 226 | }, 227 | }, 228 | types: { 229 | type: 'boolean' 230 | } 231 | }; 232 | -------------------------------------------------------------------------------- /no-unresolved.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var helpers = require('./_helpers'); 5 | 6 | module.exports = function(context) { 7 | var target = context.getFilename(); 8 | 9 | var ignore = context.options[0] && context.options[0].ignore; 10 | 11 | var resolveOpts = { 12 | basedir: path.dirname(target), 13 | extensions: ['.js', '.json', '.node'], 14 | }; 15 | 16 | if (context.options[0] && context.options[0].paths) { 17 | resolveOpts.paths = context.options[0].paths.map(function(single_path) { 18 | return path.resolve(single_path); 19 | }); 20 | } 21 | 22 | function validate(node) { 23 | var id = helpers.getModuleId(node); 24 | if (ignore && ignore.indexOf(id) !== -1) return; 25 | var resolved = helpers.resolveSync(id, resolveOpts); 26 | if (!resolved) { 27 | context.report({ 28 | node: helpers.getIdNode(node), 29 | data: {id: id}, 30 | message: '"{{id}}" does not exist.', 31 | }); 32 | } 33 | } 34 | 35 | return { 36 | CallExpression: function(node) { 37 | if (helpers.isRequireCall(node) || 38 | helpers.isRequireResolveCall(node)) { 39 | validate(node); 40 | } 41 | }, 42 | ImportDeclaration: function(node) { 43 | if (helpers.isImport(node) || helpers.isImportType(node)) { 44 | validate(node); 45 | } 46 | }, 47 | ExportAllDeclaration: function(node) { 48 | if (helpers.isExportFrom(node) || helpers.isExportTypeFrom(node)) { 49 | validate(node); 50 | } 51 | }, 52 | ExportNamedDeclaration: function(node) { 53 | if (helpers.isExportFrom(node) || helpers.isExportTypeFrom(node)) { 54 | validate(node); 55 | } 56 | }, 57 | }; 58 | }; 59 | 60 | module.exports.schema = { 61 | ignore: { 62 | type: 'array', 63 | items: { 64 | type: 'string', 65 | }, 66 | }, 67 | paths: { 68 | type: 'array', 69 | items: { 70 | type: 'string', 71 | }, 72 | }, 73 | }; 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-dependencies", 3 | "version": "2.5.0", 4 | "description": "require/import dependency validation", 5 | "keywords": [ 6 | "eslint", 7 | "eslint-plugin", 8 | "eslintplugin", 9 | "import", 10 | "require" 11 | ], 12 | "license": "MIT", 13 | "files": [ 14 | "README.md", 15 | "*.js" 16 | ], 17 | "author": "Andres Suarez ", 18 | "repository": "zertosh/eslint-plugin-dependencies", 19 | "scripts": { 20 | "test": "find test -name '*-test.js' -print0 | xargs -n 1 -0 node" 21 | }, 22 | "dependencies": { 23 | "commondir": "^1.0.1", 24 | "resolve": "^1.1.6" 25 | }, 26 | "devDependencies": { 27 | "babel-eslint": "^6.0.4", 28 | "eslint": "^2.9.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /require-json-ext.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var helpers = require('./_helpers'); 5 | var jsonExtRe = /\.json$/; 6 | 7 | module.exports = function(context) { 8 | var target = context.getFilename(); 9 | 10 | var resolveOpts = { 11 | basedir: path.dirname(target), 12 | extensions: ['.js', '.json', '.node'], 13 | }; 14 | 15 | function validate(node) { 16 | var id = helpers.getModuleId(node); 17 | if (jsonExtRe.test(id)) return; 18 | var resolved = helpers.resolveSync(id, resolveOpts); 19 | if (jsonExtRe.test(resolved)) { 20 | var basename = path.basename(id); 21 | var idNode = helpers.getIdNode(node); 22 | context.report({ 23 | node: idNode, 24 | data: {basename: basename}, 25 | message: '"{{basename}}" missing ".json" extension.', 26 | fix: function(fixer) { 27 | return fixer.insertTextBeforeRange([idNode.range[1] - 1], '.json'); 28 | }, 29 | }); 30 | } 31 | } 32 | 33 | return { 34 | CallExpression: function(node) { 35 | if (helpers.isRequireCall(node) || 36 | helpers.isRequireResolveCall(node)) { 37 | validate(node); 38 | } 39 | }, 40 | ImportDeclaration: function(node) { 41 | if (helpers.isImport(node)) { 42 | validate(node); 43 | } 44 | }, 45 | ExportAllDeclaration: function(node) { 46 | if (helpers.isExportFrom(node)) { 47 | validate(node); 48 | } 49 | }, 50 | ExportNamedDeclaration: function(node) { 51 | if (helpers.isExportFrom(node)) { 52 | validate(node); 53 | } 54 | }, 55 | }; 56 | }; 57 | 58 | module.exports.schema = []; 59 | -------------------------------------------------------------------------------- /test/case-sensitive-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (require('os').platform() !== 'darwin') { 4 | console.log('Skipping case-sensitive test.'); 5 | process.exit(0); 6 | } 7 | 8 | var RuleTester = require('eslint').RuleTester; 9 | 10 | var ruleTester = new RuleTester(); 11 | ruleTester.run('case-sensitive', require.resolve('../case-sensitive'), { 12 | valid: [ 13 | // require(…); 14 | { 15 | filename: __filename, 16 | code: 'require("../index")', 17 | }, 18 | { 19 | filename: __filename, 20 | code: 'require("..")', 21 | }, 22 | { 23 | filename: __filename, 24 | code: 'require("./case-sensitive/foo")', 25 | }, 26 | { 27 | filename: __filename, 28 | code: 'require("resolve")', 29 | }, 30 | { 31 | filename: __filename, 32 | code: 'require("assert")', 33 | }, 34 | { 35 | filename: __filename, 36 | code: 'require("non-existing-filename")', 37 | }, 38 | { 39 | filename: __filename, 40 | code: 'require(foo)', 41 | }, 42 | // custom path 43 | { 44 | filename: __filename, 45 | code: 'require("foo")', 46 | options: [{ paths: ["test/custom-path"] }], 47 | }, 48 | 49 | // require.resolve(…); 50 | { 51 | filename: __filename, 52 | code: 'require.resolve("../index")', 53 | }, 54 | // import "…"; 55 | { 56 | filename: __filename, 57 | parserOptions: {sourceType: 'module'}, 58 | code: 'import "../index"', 59 | }, 60 | // import … from …; 61 | { 62 | filename: __filename, 63 | parserOptions: {sourceType: 'module'}, 64 | code: 'import x from "../index"', 65 | }, 66 | // import type … from …; 67 | { 68 | filename: __filename, 69 | parser: 'babel-eslint', 70 | code: 'import type x from "../index"', 71 | }, 72 | // export * from "…"; 73 | { 74 | filename: __filename, 75 | parserOptions: {sourceType: 'module'}, 76 | code: 'export {foo} from "../index"', 77 | }, 78 | // export … from "…"; 79 | { 80 | filename: __filename, 81 | parserOptions: {sourceType: 'module'}, 82 | code: 'export * from "../index"', 83 | }, 84 | // export type … from "…"; 85 | { 86 | filename: __filename, 87 | parser: 'babel-eslint', 88 | code: 'export type {foo} from "../index"', 89 | }, 90 | ], 91 | invalid: [ 92 | // 93 | // parent dir 94 | // 95 | { 96 | filename: __filename, 97 | code: 'require("../Index")', 98 | errors: [ 99 | 'Case mismatch in "Index", expected "index".', 100 | ], 101 | }, 102 | { 103 | filename: __filename, 104 | code: 'require("../Index.js")', 105 | errors: [ 106 | 'Case mismatch in "Index.js", expected "index.js".', 107 | ], 108 | }, 109 | 110 | // 111 | // nested dir 112 | // 113 | { 114 | filename: __filename, 115 | code: 'require("./Case-Sensitive/Bar/baz")', 116 | errors: [ 117 | 'Case mismatch in "Case-Sensitive", expected "case-sensitive".', 118 | ], 119 | }, 120 | { 121 | filename: __filename, 122 | code: 'require("./case-sensitive/bar/baz")', 123 | errors: [ 124 | 'Case mismatch in "bar", expected "Bar".', 125 | ], 126 | }, 127 | { 128 | filename: __filename, 129 | code: 'require("./case-sensitive/Bar/bAz")', 130 | errors: [ 131 | 'Case mismatch in "bAz", expected "baz".', 132 | ], 133 | }, 134 | { 135 | filename: __filename, 136 | code: 'require("./case-sensitive/Bar/Baz.js")', 137 | errors: [ 138 | 'Case mismatch in "Baz.js", expected "baz.js".', 139 | ], 140 | }, 141 | { 142 | filename: __filename, 143 | code: 'require("./Case-Sensitive/bar/Baz.js")', 144 | errors: [ 145 | 'Case mismatch in "Case-Sensitive", expected "case-sensitive".', 146 | 'Case mismatch in "bar", expected "Bar".', 147 | 'Case mismatch in "Baz.js", expected "baz.js".', 148 | ], 149 | }, 150 | 151 | // 152 | // node_modules 153 | // 154 | { 155 | filename: __filename, 156 | code: 'require("Resolve")', 157 | errors: [ 158 | 'Case mismatch in "Resolve", expected "resolve".', 159 | ], 160 | }, 161 | { 162 | filename: __filename, 163 | code: 'require("Resolve/index")', 164 | errors: [ 165 | 'Case mismatch in "Resolve", expected "resolve".', 166 | ], 167 | }, 168 | { 169 | filename: __filename, 170 | code: 'require("Resolve/Index")', 171 | errors: [ 172 | 'Case mismatch in "Resolve", expected "resolve".', 173 | 'Case mismatch in "Index", expected "index".', 174 | ], 175 | }, 176 | { 177 | filename: __filename, 178 | code: 'require("Resolve/Index.js")', 179 | errors: [ 180 | 'Case mismatch in "Resolve", expected "resolve".', 181 | 'Case mismatch in "Index.js", expected "index.js".', 182 | ], 183 | }, 184 | { 185 | filename: __filename, 186 | code: 'require("resolve/Index")', 187 | errors: [ 188 | 'Case mismatch in "Index", expected "index".', 189 | ], 190 | }, 191 | 192 | // 193 | // custom path 194 | // 195 | { 196 | filename: __filename, 197 | code: 'require("Foo")', 198 | options: [{ paths: ["test/custom-path"] }], 199 | errors: [ 200 | 'Case mismatch in "Foo", expected "foo".', 201 | ], 202 | }, 203 | 204 | // 205 | // require.resolve, import, export 206 | // 207 | { 208 | filename: __filename, 209 | code: 'require.resolve("./Case-Sensitive/bar/Baz.js")', 210 | parserOptions: {sourceType: 'module'}, 211 | errors: [ 212 | 'Case mismatch in "Case-Sensitive", expected "case-sensitive".', 213 | 'Case mismatch in "bar", expected "Bar".', 214 | 'Case mismatch in "Baz.js", expected "baz.js".', 215 | ], 216 | }, 217 | { 218 | filename: __filename, 219 | code: 'import "./Case-Sensitive/bar/Baz.js"', 220 | parserOptions: {sourceType: 'module'}, 221 | errors: [ 222 | 'Case mismatch in "Case-Sensitive", expected "case-sensitive".', 223 | 'Case mismatch in "bar", expected "Bar".', 224 | 'Case mismatch in "Baz.js", expected "baz.js".', 225 | ], 226 | }, 227 | { 228 | filename: __filename, 229 | code: 'import baz from "./Case-Sensitive/bar/Baz.js"', 230 | parserOptions: {sourceType: 'module'}, 231 | errors: [ 232 | 'Case mismatch in "Case-Sensitive", expected "case-sensitive".', 233 | 'Case mismatch in "bar", expected "Bar".', 234 | 'Case mismatch in "Baz.js", expected "baz.js".', 235 | ], 236 | }, 237 | { 238 | filename: __filename, 239 | code: 'import type baz from "./Case-Sensitive/bar/Baz.js"', 240 | parser: 'babel-eslint', 241 | errors: [ 242 | 'Case mismatch in "Case-Sensitive", expected "case-sensitive".', 243 | 'Case mismatch in "bar", expected "Bar".', 244 | 'Case mismatch in "Baz.js", expected "baz.js".', 245 | ], 246 | }, 247 | { 248 | filename: __filename, 249 | code: 'export * from "./Case-Sensitive/bar/Baz.js"', 250 | parserOptions: {sourceType: 'module'}, 251 | errors: [ 252 | 'Case mismatch in "Case-Sensitive", expected "case-sensitive".', 253 | 'Case mismatch in "bar", expected "Bar".', 254 | 'Case mismatch in "Baz.js", expected "baz.js".', 255 | ], 256 | }, 257 | { 258 | filename: __filename, 259 | code: 'export {baz} from "./Case-Sensitive/bar/Baz.js"', 260 | parserOptions: {sourceType: 'module'}, 261 | errors: [ 262 | 'Case mismatch in "Case-Sensitive", expected "case-sensitive".', 263 | 'Case mismatch in "bar", expected "Bar".', 264 | 'Case mismatch in "Baz.js", expected "baz.js".', 265 | ], 266 | }, 267 | { 268 | filename: __filename, 269 | code: 'export type {baz} from "./Case-Sensitive/bar/Baz.js"', 270 | parser: 'babel-eslint', 271 | errors: [ 272 | 'Case mismatch in "Case-Sensitive", expected "case-sensitive".', 273 | 'Case mismatch in "bar", expected "Bar".', 274 | 'Case mismatch in "Baz.js", expected "baz.js".', 275 | ], 276 | }, 277 | 278 | // 279 | // .json, .node 280 | // 281 | { 282 | filename: __filename, 283 | code: 'require("./case-sensitive/Quux")', 284 | errors: [ 285 | 'Case mismatch in "Quux", expected "quux".', 286 | ], 287 | }, 288 | { 289 | filename: __filename, 290 | code: 'require("./case-sensitive/Quux.node")', 291 | errors: [ 292 | 'Case mismatch in "Quux.node", expected "quux.node".', 293 | ], 294 | }, 295 | { 296 | filename: __filename, 297 | code: 'require("./case-sensitive/Qux")', 298 | errors: [ 299 | 'Case mismatch in "Qux", expected "qux".', 300 | ], 301 | }, 302 | { 303 | filename: __filename, 304 | code: 'require("./case-sensitive/Qux.json")', 305 | errors: [ 306 | 'Case mismatch in "Qux.json", expected "qux.json".', 307 | ], 308 | }, 309 | ], 310 | }); 311 | -------------------------------------------------------------------------------- /test/case-sensitive/Bar/baz.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zertosh/eslint-plugin-dependencies/b2871145d9320f3f8fa324856ea18faf2d54238b/test/case-sensitive/Bar/baz.js -------------------------------------------------------------------------------- /test/case-sensitive/foo.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zertosh/eslint-plugin-dependencies/b2871145d9320f3f8fa324856ea18faf2d54238b/test/case-sensitive/foo.js -------------------------------------------------------------------------------- /test/case-sensitive/quux.node: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zertosh/eslint-plugin-dependencies/b2871145d9320f3f8fa324856ea18faf2d54238b/test/case-sensitive/quux.node -------------------------------------------------------------------------------- /test/case-sensitive/qux.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zertosh/eslint-plugin-dependencies/b2871145d9320f3f8fa324856ea18faf2d54238b/test/case-sensitive/qux.json -------------------------------------------------------------------------------- /test/custom-path/foo.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zertosh/eslint-plugin-dependencies/b2871145d9320f3f8fa324856ea18faf2d54238b/test/custom-path/foo.js -------------------------------------------------------------------------------- /test/cycles-dups/a.js: -------------------------------------------------------------------------------- 1 | // a ===========> a 2 | // a ===========> a 3 | // a => b => c => a 4 | // a => b => c => a 5 | // a ======> c => a 6 | // c => a 7 | 8 | require('./a'); 9 | require('./a'); 10 | require('./b'); 11 | require('./b'); 12 | require('./c'); 13 | -------------------------------------------------------------------------------- /test/cycles-dups/b.js: -------------------------------------------------------------------------------- 1 | require('./c'); 2 | require('./c'); 3 | -------------------------------------------------------------------------------- /test/cycles-dups/c.js: -------------------------------------------------------------------------------- 1 | require('./a'); 2 | require('./a'); 3 | -------------------------------------------------------------------------------- /test/cycles-multi-direct-extern/a.js: -------------------------------------------------------------------------------- 1 | // a => b => c => b 2 | // b => c => a 3 | require('./b'); 4 | -------------------------------------------------------------------------------- /test/cycles-multi-direct-extern/b.js: -------------------------------------------------------------------------------- 1 | require('./c'); 2 | -------------------------------------------------------------------------------- /test/cycles-multi-direct-extern/c.js: -------------------------------------------------------------------------------- 1 | require('./a'); 2 | require('./b'); 3 | -------------------------------------------------------------------------------- /test/cycles-multi-direct/a.js: -------------------------------------------------------------------------------- 1 | // a => b => c => a 2 | // a ======> c => a 3 | require('./b'); 4 | require('./c'); 5 | -------------------------------------------------------------------------------- /test/cycles-multi-direct/b.js: -------------------------------------------------------------------------------- 1 | require('./c'); 2 | -------------------------------------------------------------------------------- /test/cycles-multi-direct/c.js: -------------------------------------------------------------------------------- 1 | require('./a'); 2 | -------------------------------------------------------------------------------- /test/cycles-multi-part-direct/a.js: -------------------------------------------------------------------------------- 1 | // a => b => c => a 2 | // b ======> a 3 | require('./b'); 4 | -------------------------------------------------------------------------------- /test/cycles-multi-part-direct/b.js: -------------------------------------------------------------------------------- 1 | require('./a'); 2 | require('./c'); 3 | -------------------------------------------------------------------------------- /test/cycles-multi-part-direct/c.js: -------------------------------------------------------------------------------- 1 | require('./a'); 2 | -------------------------------------------------------------------------------- /test/cycles-self-ref-direct/a.js: -------------------------------------------------------------------------------- 1 | // a => a 2 | require('./a'); 3 | -------------------------------------------------------------------------------- /test/cycles-self-ref-extern/a.js: -------------------------------------------------------------------------------- 1 | // a => b => b 2 | require('./b'); 3 | -------------------------------------------------------------------------------- /test/cycles-self-ref-extern/b.js: -------------------------------------------------------------------------------- 1 | require('./b'); 2 | -------------------------------------------------------------------------------- /test/cycles-types-multi-direct/a.js: -------------------------------------------------------------------------------- 1 | // a => b => c => a 2 | // a ======> c => a 3 | import type { B } from './b'; 4 | import type { C } from './c'; 5 | -------------------------------------------------------------------------------- /test/cycles-types-multi-direct/b.js: -------------------------------------------------------------------------------- 1 | import type { C } from './c'; 2 | -------------------------------------------------------------------------------- /test/cycles-types-multi-direct/c.js: -------------------------------------------------------------------------------- 1 | import type { A } from './a'; 2 | -------------------------------------------------------------------------------- /test/cycles-with-babel-eslint/a.js: -------------------------------------------------------------------------------- 1 | // a => b => c => a 2 | // a ======> c => a 3 | function f(): void { 4 | require('./b'); 5 | require('./c'); 6 | } 7 | -------------------------------------------------------------------------------- /test/cycles-with-babel-eslint/b.js: -------------------------------------------------------------------------------- 1 | function f(): void { 2 | require('./c'); 3 | } 4 | -------------------------------------------------------------------------------- /test/cycles-with-babel-eslint/c.js: -------------------------------------------------------------------------------- 1 | function f(): void { 2 | require('./a'); 3 | } 4 | -------------------------------------------------------------------------------- /test/no-cycles-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | 6 | var RuleTester = require('eslint').RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | ruleTester.run('no-cycles', require.resolve('../no-cycles'), { 10 | valid: [ 11 | { 12 | code: 'console.log();', 13 | }, 14 | { 15 | // self-ref-extern 16 | // a => b => b 17 | filename: path.join(__dirname, 'cycles-self-ref-extern/a.js'), 18 | code: fs.readFileSync(path.join(__dirname, 'cycles-self-ref-extern/a.js'), 'utf8'), 19 | }, 20 | 21 | // 22 | // skip option 23 | // 24 | { 25 | // self-ref-direct 26 | // a => a 27 | filename: path.join(__dirname, 'cycles-self-ref-direct/a.js'), 28 | code: fs.readFileSync(path.join(__dirname, 'cycles-self-ref-direct/a.js'), 'utf8'), 29 | options: [{skip: ['/cycles-self-ref-direct/']}], 30 | }, 31 | ], 32 | invalid: [ 33 | { 34 | // self-ref-direct 35 | // a => a 36 | filename: path.join(__dirname, 'cycles-self-ref-direct/a.js'), 37 | code: fs.readFileSync(path.join(__dirname, 'cycles-self-ref-direct/a.js'), 'utf8'), 38 | errors: [ 39 | 'Self-reference cycle.', 40 | ], 41 | }, 42 | { 43 | // multi-direct 44 | // a => b => c => a 45 | // a ======> c => a 46 | filename: path.join(__dirname, 'cycles-multi-direct/a.js'), 47 | code: fs.readFileSync(path.join(__dirname, 'cycles-multi-direct/a.js'), 'utf8'), 48 | errors: [ 49 | 'Cycle in b.js => c.js.', 50 | 'Cycle in c.js.', 51 | ], 52 | }, 53 | { 54 | // types-multi-direct 55 | // a => b => c => a 56 | // a ======> c => a 57 | filename: path.join(__dirname, 'cycles-types-multi-direct/a.js'), 58 | parser: 'babel-eslint', 59 | parserOptions: {sourceType: 'module'}, 60 | code: fs.readFileSync(path.join(__dirname, 'cycles-types-multi-direct/a.js'), 'utf8'), 61 | options: [{types: true}], 62 | errors: [ 63 | 'Cycle in b.js => c.js.', 64 | 'Cycle in c.js.', 65 | ], 66 | }, 67 | { 68 | // multi-part-direct 69 | // a => b => c => a 70 | // b ======> a 71 | filename: path.join(__dirname, 'cycles-multi-part-direct/a.js'), 72 | code: fs.readFileSync(path.join(__dirname, 'cycles-multi-part-direct/a.js'), 'utf8'), 73 | errors: [ 74 | 'Cycle in b.js.', 75 | 'Cycle in b.js => c.js.', 76 | ], 77 | }, 78 | { 79 | // multi-direct-extern 80 | // a => b => c => b 81 | // b => c => a 82 | filename: path.join(__dirname, 'cycles-multi-direct-extern/a.js'), 83 | code: fs.readFileSync(path.join(__dirname, 'cycles-multi-direct-extern/a.js'), 'utf8'), 84 | errors: [ 85 | 'Cycle in b.js => c.js.', 86 | ], 87 | }, 88 | { 89 | // cycle dups 90 | // a ===========> a 91 | // a ===========> a 92 | // a => b => c => a 93 | // a => b => c => a 94 | // a ======> c => a 95 | // c => a 96 | 97 | filename: path.join(__dirname, 'cycles-dups/a.js'), 98 | code: fs.readFileSync(path.join(__dirname, 'cycles-dups/a.js'), 'utf8'), 99 | errors: [ 100 | 'Self-reference cycle.', 101 | 'Self-reference cycle.', 102 | 'Cycle in b.js => c.js.', 103 | 'Cycle in b.js => c.js.', 104 | 'Cycle in c.js.', 105 | 'Cycle in c.js.', 106 | ], 107 | }, 108 | 109 | // 110 | // babel-eslint 111 | // 112 | { 113 | // multi-direct 114 | // a => b => c => a 115 | // a ======> c => a 116 | filename: path.join(__dirname, 'cycles-with-babel-eslint/a.js'), 117 | code: fs.readFileSync(path.join(__dirname, 'cycles-with-babel-eslint/a.js'), 'utf8'), 118 | parser: 'babel-eslint', 119 | errors: [ 120 | 'Cycle in b.js => c.js.', 121 | 'Cycle in c.js.', 122 | ], 123 | }, 124 | 125 | // 126 | // skip 127 | // 128 | { 129 | // self-ref-direct 130 | // a => a 131 | filename: path.join(__dirname, 'cycles-self-ref-direct/a.js'), 132 | code: fs.readFileSync(path.join(__dirname, 'cycles-self-ref-direct/a.js'), 'utf8'), 133 | options: [{skip: ['/some-path-that-doesnt-exist/']}], 134 | errors: [ 135 | 'Self-reference cycle.', 136 | ], 137 | }, 138 | ], 139 | }); 140 | -------------------------------------------------------------------------------- /test/no-unresolved-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RuleTester = require('eslint').RuleTester; 4 | 5 | var ruleTester = new RuleTester(); 6 | ruleTester.run('no-unresolved', require.resolve('../no-unresolved'), { 7 | valid: [ 8 | { 9 | filename: __filename, 10 | code: 'require("./resolve/foo")', 11 | }, 12 | { 13 | filename: __filename, 14 | code: 'require("./resolve/foo.js")', 15 | }, 16 | { 17 | filename: __filename, 18 | code: 'require("./resolve/foo.json")', 19 | }, 20 | { 21 | filename: __filename, 22 | code: 'require("./resolve/bar")', 23 | }, 24 | { 25 | filename: __filename, 26 | code: 'require("./resolve/bar.json")', 27 | }, 28 | { 29 | filename: __filename, 30 | code: 'require("./resolve/baz")', 31 | }, 32 | { 33 | filename: __filename, 34 | code: 'require("./resolve/baz.node")', 35 | }, 36 | { 37 | filename: __filename, 38 | code: 'require("resolve")', 39 | }, 40 | { 41 | filename: __filename, 42 | code: 'require("resolve/package.json")', 43 | }, 44 | { 45 | filename: __filename, 46 | code: 'require("non-existing-package")', 47 | options: [{ignore: ['non-existing-package']}], 48 | }, 49 | // test custom path resolution 50 | { 51 | filename: __filename, 52 | code: 'require("foo")', 53 | options: [{paths: ['test/custom-path']}], 54 | }, 55 | 56 | // 57 | // require.resolve, import, export 58 | // 59 | { 60 | filename: __filename, 61 | code: 'require.resolve("./resolve/foo")', 62 | }, 63 | { 64 | filename: __filename, 65 | parserOptions: {sourceType: 'module'}, 66 | code: 'import "./resolve/foo"', 67 | }, 68 | { 69 | filename: __filename, 70 | parserOptions: {sourceType: 'module'}, 71 | code: 'import bar from "./resolve/foo"', 72 | }, 73 | { 74 | filename: __filename, 75 | parser: 'babel-eslint', 76 | code: 'import type bar from "./resolve/foo"', 77 | }, 78 | { 79 | filename: __filename, 80 | parserOptions: {sourceType: 'module'}, 81 | code: 'export * from "./resolve/foo"', 82 | }, 83 | { 84 | filename: __filename, 85 | parserOptions: {sourceType: 'module'}, 86 | code: 'export {bar} from "./resolve/foo"', 87 | }, 88 | { 89 | filename: __filename, 90 | parser: 'babel-eslint', 91 | code: 'export type {bar} from "./resolve/foo"', 92 | }, 93 | ], 94 | invalid: [ 95 | { 96 | filename: __filename, 97 | code: 'require("./resolve/qux")', 98 | errors: [ 99 | { 100 | type: 'Literal', 101 | message: '"./resolve/qux" does not exist.', 102 | }, 103 | ], 104 | }, 105 | { 106 | filename: __filename, 107 | code: 'require("non-existing-package")', 108 | errors: [ 109 | { 110 | type: 'Literal', 111 | message: '"non-existing-package" does not exist.', 112 | }, 113 | ], 114 | }, 115 | { 116 | filename: __filename, 117 | code: 'require("resolve/non-existing-file")', 118 | errors: [ 119 | { 120 | type: 'Literal', 121 | message: '"resolve/non-existing-file" does not exist.', 122 | }, 123 | ], 124 | }, 125 | { 126 | filename: __filename, 127 | code: 'require("./resolve")', 128 | errors: [ 129 | { 130 | type: 'Literal', 131 | message: '"./resolve" does not exist.', 132 | }, 133 | ], 134 | }, 135 | // test custom path resolution 136 | { 137 | filename: __filename, 138 | code: 'require("foo")', 139 | errors: [ 140 | { 141 | type: 'Literal', 142 | message: '"foo" does not exist.', 143 | }, 144 | ], 145 | }, 146 | 147 | 148 | 149 | // 150 | // require.resolve, import, export 151 | // 152 | { 153 | filename: __filename, 154 | code: 'require.resolve("./resolve/qux")', 155 | errors: [ 156 | { 157 | type: 'Literal', 158 | message: '"./resolve/qux" does not exist.', 159 | }, 160 | ], 161 | }, 162 | { 163 | filename: __filename, 164 | parserOptions: {sourceType: 'module'}, 165 | code: 'import "./resolve/qux"', 166 | errors: [ 167 | { 168 | type: 'Literal', 169 | message: '"./resolve/qux" does not exist.', 170 | }, 171 | ], 172 | }, 173 | { 174 | filename: __filename, 175 | parserOptions: {sourceType: 'module'}, 176 | code: 'import qux from "./resolve/qux"', 177 | errors: [ 178 | { 179 | type: 'Literal', 180 | message: '"./resolve/qux" does not exist.', 181 | }, 182 | ], 183 | }, 184 | { 185 | filename: __filename, 186 | parser: 'babel-eslint', 187 | code: 'import type qux from "./resolve/qux"', 188 | errors: [ 189 | { 190 | type: 'Literal', 191 | message: '"./resolve/qux" does not exist.', 192 | }, 193 | ], 194 | }, 195 | { 196 | filename: __filename, 197 | parserOptions: {sourceType: 'module'}, 198 | code: 'export {qux} from "./resolve/qux"', 199 | errors: [ 200 | { 201 | type: 'Literal', 202 | message: '"./resolve/qux" does not exist.', 203 | }, 204 | ], 205 | }, 206 | { 207 | filename: __filename, 208 | parserOptions: {sourceType: 'module'}, 209 | code: 'export * from "./resolve/qux"', 210 | errors: [ 211 | { 212 | type: 'Literal', 213 | message: '"./resolve/qux" does not exist.', 214 | }, 215 | ], 216 | }, 217 | { 218 | filename: __filename, 219 | parser: 'babel-eslint', 220 | code: 'export type {qux} from "./resolve/qux"', 221 | errors: [ 222 | { 223 | type: 'Literal', 224 | message: '"./resolve/qux" does not exist.', 225 | }, 226 | ], 227 | }, 228 | ], 229 | }); 230 | -------------------------------------------------------------------------------- /test/require-json-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RuleTester = require('eslint').RuleTester; 4 | 5 | var ruleTester = new RuleTester(); 6 | ruleTester.run('require-json-ext', require.resolve('../require-json-ext'), { 7 | valid: [ 8 | { 9 | filename: __filename, 10 | code: 'require("./require-json/foo")', 11 | }, 12 | { 13 | filename: __filename, 14 | code: 'require("./require-json/foo.js")', 15 | }, 16 | { 17 | filename: __filename, 18 | code: 'require("./require-json/foo.json")', 19 | }, 20 | { 21 | filename: __filename, 22 | code: 'require("./require-json/bar.json")', 23 | }, 24 | { 25 | filename: __filename, 26 | code: 'require("./require-json/non-existent-file")', 27 | }, 28 | { 29 | filename: __filename, 30 | code: 'require("./require-json/non-existent-file.json")', 31 | }, 32 | { 33 | filename: __filename, 34 | code: 'require("resolve/package.json")', 35 | }, 36 | 37 | // 38 | // require.resolve, import, export 39 | // 40 | { 41 | filename: __filename, 42 | code: 'require.resolve("./require-json/bar.json")', 43 | }, 44 | { 45 | filename: __filename, 46 | parserOptions: {sourceType: 'module'}, 47 | code: 'import "./require-json/bar.json"', 48 | }, 49 | { 50 | filename: __filename, 51 | parserOptions: {sourceType: 'module'}, 52 | code: 'import bar from "./require-json/bar.json"', 53 | }, 54 | { 55 | filename: __filename, 56 | parserOptions: {sourceType: 'module'}, 57 | code: 'export * from "./require-json/bar.json"', 58 | }, 59 | { 60 | filename: __filename, 61 | parserOptions: {sourceType: 'module'}, 62 | code: 'export {bar} from "./require-json/bar.json"', 63 | }, 64 | ], 65 | invalid: [ 66 | { 67 | filename: __filename, 68 | code: 'require("./require-json/bar")', 69 | errors: [ 70 | { 71 | type: 'Literal', 72 | message: '"bar" missing ".json" extension.', 73 | }, 74 | ], 75 | }, 76 | { 77 | filename: __filename, 78 | code: 'require("resolve/package")', 79 | errors: [ 80 | { 81 | type: 'Literal', 82 | message: '"package" missing ".json" extension.', 83 | }, 84 | ], 85 | }, 86 | 87 | // 88 | // require.resolve, import, export 89 | // 90 | { 91 | filename: __filename, 92 | code: 'require.resolve("./require-json/bar")', 93 | errors: [ 94 | { 95 | type: 'Literal', 96 | message: '"bar" missing ".json" extension.', 97 | }, 98 | ], 99 | }, 100 | { 101 | filename: __filename, 102 | parserOptions: {sourceType: 'module'}, 103 | code: 'import "./require-json/bar"', 104 | errors: [ 105 | { 106 | type: 'Literal', 107 | message: '"bar" missing ".json" extension.', 108 | }, 109 | ], 110 | }, 111 | { 112 | filename: __filename, 113 | parserOptions: {sourceType: 'module'}, 114 | code: 'import bar from "./require-json/bar"', 115 | errors: [ 116 | { 117 | type: 'Literal', 118 | message: '"bar" missing ".json" extension.', 119 | }, 120 | ], 121 | }, 122 | { 123 | filename: __filename, 124 | parserOptions: {sourceType: 'module'}, 125 | code: 'export {bar} from "./require-json/bar"', 126 | errors: [ 127 | { 128 | type: 'Literal', 129 | message: '"bar" missing ".json" extension.', 130 | }, 131 | ], 132 | }, 133 | { 134 | filename: __filename, 135 | parserOptions: {sourceType: 'module'}, 136 | code: 'export * from "./require-json/bar"', 137 | errors: [ 138 | { 139 | type: 'Literal', 140 | message: '"bar" missing ".json" extension.', 141 | }, 142 | ], 143 | }, 144 | ], 145 | }); 146 | -------------------------------------------------------------------------------- /test/require-json/bar.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zertosh/eslint-plugin-dependencies/b2871145d9320f3f8fa324856ea18faf2d54238b/test/require-json/bar.json -------------------------------------------------------------------------------- /test/require-json/foo.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zertosh/eslint-plugin-dependencies/b2871145d9320f3f8fa324856ea18faf2d54238b/test/require-json/foo.js -------------------------------------------------------------------------------- /test/require-json/foo.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zertosh/eslint-plugin-dependencies/b2871145d9320f3f8fa324856ea18faf2d54238b/test/require-json/foo.json -------------------------------------------------------------------------------- /test/resolve/bar.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zertosh/eslint-plugin-dependencies/b2871145d9320f3f8fa324856ea18faf2d54238b/test/resolve/bar.json -------------------------------------------------------------------------------- /test/resolve/baz.node: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zertosh/eslint-plugin-dependencies/b2871145d9320f3f8fa324856ea18faf2d54238b/test/resolve/baz.node -------------------------------------------------------------------------------- /test/resolve/foo.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zertosh/eslint-plugin-dependencies/b2871145d9320f3f8fa324856ea18faf2d54238b/test/resolve/foo.js -------------------------------------------------------------------------------- /test/resolve/foo.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zertosh/eslint-plugin-dependencies/b2871145d9320f3f8fa324856ea18faf2d54238b/test/resolve/foo.json --------------------------------------------------------------------------------