-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement new rule: no-reaching-inside
- Loading branch information
spalger
committed
Aug 12, 2016
1 parent
90dedd7
commit f072e8a
Showing
9 changed files
with
259 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# no-reaching-inside | ||
|
||
Use this rule to prevent importing the submodules of other modules. | ||
|
||
## Rule Details | ||
|
||
This rule has one option, `allow` which is an array of minimatch patterns to identify directories whose children can be imported explicitly. | ||
|
||
### Examples | ||
|
||
Given the following folder structure: | ||
|
||
``` | ||
my-project | ||
├── actions | ||
│ └── getUser.js | ||
│ └── updateUser.js | ||
├── reducer | ||
│ └── index.js | ||
│ └── user.js | ||
├── redux | ||
│ └── index.js | ||
│ └── configureStore.js | ||
└── app | ||
│ └── index.js | ||
│ └── settings.js | ||
└── entry.js | ||
``` | ||
|
||
And the .eslintrc file: | ||
``` | ||
{ | ||
... | ||
"rules": { | ||
"import/no-reaching-inside": [ "error", { | ||
"allow": [ "**/actions", "source-map-support/*" ] | ||
} ] | ||
} | ||
} | ||
``` | ||
|
||
The following patterns are considered problems: | ||
|
||
```js | ||
/** | ||
* in my-project/entry.jz | ||
*/ | ||
|
||
import { settings } from './app/index'; // Reaching into "./app" is not allowed | ||
import userReducer from './reducer/user'; // Reaching into "./reducer" is not allowed | ||
import configureStore from './redux/configureStore'; // Reaching into "./redux" is not allowed | ||
``` | ||
|
||
The following patterns are NOT considered problems: | ||
|
||
```js | ||
/** | ||
* in my-project/entry.jz | ||
*/ | ||
|
||
import 'source-map-support/register'; | ||
import { settings } from '../app'; | ||
import getUser from '../actions/getUser'; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import path from 'path' | ||
import find from 'lodash.find' | ||
import minimatch from 'minimatch' | ||
|
||
import importType from '../core/importType' | ||
import isStaticRequire from '../core/staticRequire' | ||
|
||
module.exports = function noReachingInside(context) { | ||
const options = context.options[0] || {} | ||
const dirname = path.dirname(context.getFilename()) | ||
const allowRegexps = (options.allow || []).map(p => minimatch.makeRe(p)) | ||
|
||
// test if reaching into this directory is allowed by the | ||
// config, path.sep is automatically added so that globs like | ||
// "lodash/**" will match both "lodash" (which requires the trailing /) and "lodash/get" | ||
function reachingAllowed(someDir) { | ||
return !!find(allowRegexps, re => re.test(someDir) || re.test(someDir + path.sep)) | ||
} | ||
|
||
function isRelativeStep (step) { | ||
return step === '' || step === '.' || step === '..' | ||
} | ||
|
||
function report(reachedTo, node) { | ||
context.report({ | ||
node, | ||
message: `Reaching into "${reachedTo}" is not allowed.`, | ||
}) | ||
} | ||
|
||
function findNotAllowedReach(importPath, startingBase, join, ignoreStep) { | ||
const steps = importPath.split('/').filter(Boolean) | ||
let parentDir = startingBase | ||
while (steps.length) { | ||
const step = steps.shift() | ||
parentDir = join(parentDir, step) | ||
|
||
if (ignoreStep && ignoreStep(step)) continue | ||
|
||
if (steps.length) { | ||
if (!reachingAllowed(parentDir)) { | ||
return parentDir | ||
} | ||
} | ||
} | ||
} | ||
|
||
function checkRelativeImportForReaching(importPath, node) { | ||
const reachedInto = findNotAllowedReach(importPath, dirname, path.resolve, isRelativeStep) | ||
if (reachedInto) report(path.relative(dirname, reachedInto), node) | ||
} | ||
|
||
function checkAbsoluteImportForReaching(importPath, node) { | ||
const reachedInto = findNotAllowedReach(importPath, '', path.join) | ||
if (reachedInto) report(reachedInto, node) | ||
} | ||
|
||
function checkImportForReaching(importPath, node) { | ||
switch (importType(importPath, context)) { | ||
case 'parent': | ||
case 'index': | ||
case 'sibling': | ||
return checkRelativeImportForReaching(importPath, node) | ||
|
||
case 'external': | ||
case 'internal': | ||
return checkAbsoluteImportForReaching(importPath, node) | ||
default: | ||
return | ||
} | ||
} | ||
|
||
return { | ||
ImportDeclaration(node) { | ||
checkImportForReaching(node.source.value, node.source) | ||
}, | ||
CallExpression(node) { | ||
if (isStaticRequire(node)) { | ||
const [ firstArgument ] = node.arguments | ||
checkImportForReaching(firstArgument.value, firstArgument) | ||
} | ||
}, | ||
} | ||
} | ||
|
||
module.exports.schema = [ | ||
{ | ||
type: 'object', | ||
properties: { | ||
allow: { | ||
type: 'array', | ||
items: { | ||
type: 'string', | ||
}, | ||
}, | ||
}, | ||
additionalProperties: false, | ||
}, | ||
] |
Empty file.
Empty file.
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import { RuleTester } from 'eslint' | ||
import rule from 'rules/no-reaching-inside' | ||
|
||
import { test, testFilePath } from '../utils' | ||
|
||
const ruleTester = new RuleTester() | ||
|
||
ruleTester.run('no-reaching-inside', rule, { | ||
valid: [ | ||
test({ | ||
code: 'import a from "./plugin2"', | ||
filename: testFilePath('./reaching-inside/plugins/plugin.js'), | ||
options: [], | ||
}), | ||
test({ | ||
code: 'const a = require("./plugin2")', | ||
filename: testFilePath('./reaching-inside/plugins/plugin.js'), | ||
}), | ||
test({ | ||
code: 'const a = require("./plugin2/")', | ||
filename: testFilePath('./reaching-inside/plugins/plugin.js'), | ||
}), | ||
test({ | ||
code: 'const dynamic = "./plugin2/"; const a = require(dynamic)', | ||
filename: testFilePath('./reaching-inside/plugins/plugin.js'), | ||
}), | ||
test({ | ||
code: 'import b from "./internal.js"', | ||
filename: testFilePath('./reaching-inside/plugins/plugin2/index.js'), | ||
}), | ||
test({ | ||
code: 'import get from "lodash.get"', | ||
filename: testFilePath('./reaching-inside/plugins/plugin2/index.js'), | ||
}), | ||
test({ | ||
code: 'import b from "../../api/service"', | ||
filename: testFilePath('./reaching-inside/plugins/plugin2/internal.js'), | ||
options: [ { | ||
allow: [ '**/api' ], | ||
} ], | ||
}), | ||
test({ | ||
code: 'import "jquery/dist/jquery"', | ||
filename: testFilePath('./reaching-inside/plugins/plugin2/internal.js'), | ||
options: [ { | ||
allow: [ 'jquery/**' ], | ||
} ], | ||
}), | ||
test({ | ||
code: 'import "/app/index.js"', | ||
filename: testFilePath('./reaching-inside/plugins/plugin2/internal.js'), | ||
options: [ { | ||
allow: [ '/app' ], | ||
} ], | ||
}), | ||
], | ||
|
||
invalid: [ | ||
test({ | ||
code: 'import b from "./plugin2/internal"', | ||
filename: testFilePath('./reaching-inside/plugins/plugin.js'), | ||
errors: [ { | ||
message: 'Reaching into "plugin2" is not allowed.', | ||
line: 1, | ||
column: 15, | ||
} ], | ||
}), | ||
test({ | ||
code: 'import a from "../api/service/index"', | ||
filename: testFilePath('./reaching-inside/plugins/plugin.js'), | ||
options: [ { | ||
allow: [ '**/reaching-inside/*' ], | ||
} ], | ||
errors: [ | ||
{ | ||
message: 'Reaching into "../api/service" is not allowed.', | ||
line: 1, | ||
column: 15, | ||
}, | ||
], | ||
}), | ||
test({ | ||
code: 'import get from "lodash/get"', | ||
filename: testFilePath('./reaching-inside/plugins/plugin.js'), | ||
errors: [ | ||
{ | ||
message: 'Reaching into "lodash" is not allowed.', | ||
line: 1, | ||
column: 17, | ||
}, | ||
], | ||
}), | ||
], | ||
}) |