Skip to content
This repository has been archived by the owner on Aug 18, 2020. It is now read-only.

Commit

Permalink
no-ancestor-directory-import
Browse files Browse the repository at this point in the history
  • Loading branch information
cartogram committed Oct 15, 2018
1 parent 7d4d64e commit e257b2d
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

* Updated `plugin:shopify/prettier`, `plugin:shopify/react`, and `plugin:shopify/typescript` to use `overrides` ([#173](https://github.com/Shopify/eslint-plugin-shopify/pull/173))

### Added
* `shopify/no-ancestor-directory-import` ([#149](https://github.com/Shopify/eslint-plugin-shopify/pull/149))

## [25.1.0] - 2018-10-01

### Changed
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ This plugin provides the following custom rules, which are included as appropria
- [polaris-prefer-sectioned-prop](docs/rules/polaris-prefer-sectioned-prop.md): Prefer the use of the `sectioned` props in Polaris components instead of wrapping all contents in a `Section` component.
- [prefer-class-properties](docs/rules/prefer-class-properties.md): Prefer class properties to assignment of literals in constructors.
- [prefer-early-return](docs/rules/prefer-early-return.md): Prefer early returns over full-body conditional wrapping in function declarations.
- [no-ancestor-directory-import](docs/rules/no-ancestor-directory-import.md): Prefer imports from within a directory extend to the file from where they are importing without relying on an index file.
- [prefer-module-scope-constants](docs/rules/prefer-module-scope-constants.md): Prefer that screaming snake case variables always be defined using `const`, and always appear at module scope.
- [prefer-twine](docs/rules/prefer-twine.md): Prefer Twine over Bindings as the name for twine imports.
- [react-initialize-state](docs/rules/react-initialize-state.md): Require that React component state be initialized when it has a non-empty type.
Expand Down
27 changes: 27 additions & 0 deletions docs/rules/no-ancestor-directory-import.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Prefer that imports from within a directory extend to the file from where they are importing without relying on an index file. (no-ancestor-directory-import)

Imports inside the same directory should extend directly to the file from where they are importing without relying on an index file. This means the source of these imports should not end with a directory (`/`), but the path should terminate at an individual filename. This preserves the index file inside a directory as a mechanism for exposing pieces of the module to the outside application.

## Rule Details

This rule disallows any full directory imports from within that same directory.

Examples of **incorrect** code for this rule:

```ts
import Thing from '../';
import OtherThing from './';
import Module from '../../index.ts'
```

Examples of **correct** code for this rule:

```ts
import Thing from '../Thing';
import OtherThing from './OtherThing';
import Module from '../Module'
```

## When Not To Use It

If you do not wish to prevent directory imports from within that directory, you can safely disable this rule.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = {
'jsx-no-complex-expressions': require('./lib/rules/jsx-no-complex-expressions'),
'jsx-no-hardcoded-content': require('./lib/rules/jsx-no-hardcoded-content'),
'jsx-prefer-fragment-wrappers': require('./lib/rules/jsx-prefer-fragment-wrappers'),
'no-ancestor-directory-import': require('./lib/rules/no-ancestor-directory-import'),
'no-debugger': require('./lib/rules/no-debugger'),
'no-useless-computed-properties': require('./lib/rules/no-useless-computed-properties'),
'no-fully-static-classes': require('./lib/rules/no-fully-static-classes'),
Expand Down
2 changes: 2 additions & 0 deletions lib/config/rules/shopify.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ module.exports = {
'shopify/jsx-no-hardcoded-content': 'off',
// Disallow useless wrapping elements in favour of fragment shorthand in JSX.
'shopify/jsx-prefer-fragment-wrappers': 'off',
// Prefer that imports from within a directory extend to the file from where they are importing without relying on an index file.
'shopify/no-ancestor-directory-import': 'off',
// Disallow the use of debugger (without fixer to prevent autofix on save in editors)
'shopify/no-debugger': 'error',
// Prevent the usage of unnecessary computed properties.
Expand Down
49 changes: 49 additions & 0 deletions lib/rules/no-ancestor-directory-import.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const {extname, basename, relative, sep} = require('path');
const resolve = require('eslint-module-utils/resolve').default;

module.exports = {
meta: {
docs: {
description:
'Prefer that imports from within a directory extend to the file from where they are importing without relying on an index file.',
category: 'Best Practices',
recommended: false,
uri:
'https://github.com/Shopify/eslint-plugin-shopify/blob/master/docs/rules/no-ancestor-directory-import.md',
},
fixable: null,
},
create(context) {
function isAncestorDirectoryImport(resolvedSource) {
const relativeDifference = relative(
context.getFilename(),
resolvedSource,
);

const parts = relativeDifference
.split(sep)
.filter((part) => part[0] !== '.');

if (parts.length > 1) {
return false;
}

return basename(parts[0], extname(parts[0])) === 'index';
}

return {
ImportDeclaration(node) {
const importSource = node.source.value;
const resolvedSource = resolve(importSource, context);

if (resolvedSource && isAncestorDirectoryImport(resolvedSource)) {
context.report({
node,
message:
'Relative local imports should extend to the file from where they are importing without relying on an index file.',
});
}
},
};
},
};
Empty file.
Empty file.
98 changes: 98 additions & 0 deletions tests/lib/rules/no-ancestor-directory-import.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
const {RuleTester} = require('eslint');
const {fixtureFile} = require('../../utilities');
const rule = require('../../../lib/rules/no-ancestor-directory-import');

const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
},
});

const errors = [
{
type: 'ImportDeclaration',
message:
'Relative local imports should extend to the file from where they are importing without relying on an index file.',
},
];

ruleTester.run('no-ancestor-directory-import', rule, {
valid: [
{
code: "import Foo from '../Foo'",
filename: fixtureFile('basic-app/app/components/Foo/tests/Foo.test.js'),
},
{
code: "import {Foo, Bar} from '../../components'",
filename: fixtureFile('basic-app/app/sections/MySection/MySection.js'),
},
{
code: "import Bar from '../Bar'",
filename: fixtureFile('basic-app/app/components/Foo/Foo.js'),
},
{
code: "import Bar from '../Bar'",
filename: fixtureFile(
'basic-app/app/components/Foo/components/Baz/Baz.js',
),
},
{
code: "import Qux from '../Qux'",
filename: fixtureFile(
'basic-app/app/components/Foo/components/Baz/Baz.js',
),
},
{
code: "import Bar from '../../../Bar'",
filename: fixtureFile(
'basic-app/app/components/Foo/components/Baz/Baz.js',
),
},
],

invalid: [
{
code: "import Foo from '../'",
errors,
filename: fixtureFile('basic-app/app/components/Foo/tests/Foo.test.js'),
},
{
code: "import Foo from '..'",
errors,
filename: fixtureFile('basic-app/app/components/Foo/tests/Foo.test.js'),
},
{
code: "import Foo from './'",
errors,
filename: fixtureFile('basic-app/app/components/Foo.js'),
},
{
code: "import Foo from '.'",
errors,
filename: fixtureFile('basic-app/app/components/Foo/Foo.js'),
},
{
code: "import Foo from '../../index'",
errors,
filename: fixtureFile('basic-app/app/components/Foo/tests/Foo.test.js'),
},
{
code: "import Foo from '../../index.js'",
errors,
filename: fixtureFile('basic-app/app/components/Foo/tests/Foo.test.js'),
},
{
code: "import Bar from '..'",
errors,
filename: fixtureFile('basic-app/app/components/Foo/Foo.js'),
},
{
code: "import Bar from '../../..'",
errors,
filename: fixtureFile(
'basic-app/app/components/Foo/components/Baz/Baz.js',
),
},
],
});

0 comments on commit e257b2d

Please sign in to comment.