This repository has been archived by the owner on Aug 18, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #219 from Shopify/images-no-direct-imports
Add images-no-direct-imports rule
- Loading branch information
Showing
9 changed files
with
202 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
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,30 @@ | ||
# Prevent directly importing image files and instead force usage of an index file (image-no-direct-imports) | ||
|
||
Image files should live in a directory (e.g. `icons`, `illustrations` or `images`) that must contain a dedicated index file that re-exports all the images in that directory. | ||
|
||
Files that consume images must import from that index file instead of importing images directly. This rule enforces that convention by disallowing importing directly from an image in any file except the index file in the same dorectory as the image. | ||
|
||
## Rule Details | ||
|
||
Example of **incorrect** code for this rule: | ||
|
||
|
||
```js | ||
// components/Foo/Foo.js | ||
import icon1 from './icons/icon1.svg'; | ||
``` | ||
|
||
|
||
Example of **correct** code for this rule: | ||
|
||
```js | ||
// components/Foo/icons/index.js | ||
export {default as icon1} from './icon1.svg'; | ||
|
||
// components/Foo/Foo.js | ||
import {icon1} from './icons'; | ||
``` | ||
|
||
## When Not To Use It | ||
|
||
If you do not wish to enforce import locations for images, then you can safely disable this rule. |
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,56 @@ | ||
const {basename, dirname, extname} = require('path'); | ||
const resolve = require('eslint-module-utils/resolve').default; | ||
const {docsUrl} = require('../utilities'); | ||
|
||
function isImageImport(resolvedSource) { | ||
return /\.(svg|png|jpg)$/.test(resolvedSource); | ||
} | ||
|
||
function isImportFromCurrentFolderIndex(contextFilename, resolvedSource) { | ||
const isIndex = | ||
basename(contextFilename, extname(contextFilename)) === 'index'; | ||
|
||
return isIndex && dirname(resolvedSource) === dirname(contextFilename); | ||
} | ||
|
||
module.exports = { | ||
meta: { | ||
docs: { | ||
description: | ||
'Prefer importing image files from the index file of the directory instead of the direct path to the image file.', | ||
category: 'Best Practices', | ||
recommended: false, | ||
uri: docsUrl('images-no-direct-imports'), | ||
}, | ||
fixable: null, | ||
}, | ||
create(context) { | ||
function checkNode(node) { | ||
if (!node.source || !node.source.value) { | ||
return; | ||
} | ||
|
||
const resolvedSource = resolve(node.source.value, context); | ||
|
||
if ( | ||
resolvedSource && | ||
isImageImport(resolvedSource) && | ||
!isImportFromCurrentFolderIndex(context.getFilename(), resolvedSource) | ||
) { | ||
context.report({ | ||
node, | ||
message: `Prefer importing image files from the index file of the directory ("{{folderPath}}") instead of the direct path to the image file ("{{filePath}}").`, | ||
data: { | ||
folderPath: dirname(node.source.value), | ||
filePath: node.source.value, | ||
}, | ||
}); | ||
} | ||
} | ||
return { | ||
ImportDeclaration: checkNode, | ||
ExportNamedDeclaration: checkNode, | ||
ExportAllDeclaration: checkNode, | ||
}; | ||
}, | ||
}; |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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,108 @@ | ||
const {RuleTester} = require('eslint'); | ||
const {fixtureFile} = require('../../utilities'); | ||
const rule = require('../../../lib/rules/images-no-direct-imports'); | ||
|
||
const ruleTester = new RuleTester({ | ||
parserOptions: { | ||
ecmaVersion: 6, | ||
sourceType: 'module', | ||
}, | ||
}); | ||
|
||
function errors(type, folderPath, filePath) { | ||
return [ | ||
{ | ||
type, | ||
message: `Prefer importing image files from the index file of the directory ("${folderPath}") instead of the direct path to the image file ("${filePath}").`, | ||
}, | ||
]; | ||
} | ||
|
||
ruleTester.run('images-no-direct-imports', rule, { | ||
valid: [ | ||
// Importing / Exporting svg files from the folder index is valid | ||
{ | ||
code: "import icon1 from './icon1.svg'", | ||
filename: fixtureFile('basic-app/app/components/Foo/icons/index.js'), | ||
}, | ||
{ | ||
code: "import * as icon1 from './icon1.svg'", | ||
filename: fixtureFile('basic-app/app/components/Foo/icons/index.js'), | ||
}, | ||
{ | ||
code: "export {default as icon1} from './icon1.svg'", | ||
filename: fixtureFile('basic-app/app/components/Foo/icons/index.js'), | ||
}, | ||
{ | ||
code: "export * from './icon1.svg'", | ||
filename: fixtureFile('basic-app/app/components/Foo/icons/index.js'), | ||
}, | ||
// Importing / Exporting icon index file contents from a component is valid | ||
{ | ||
code: "import {icon1} from './icons'", | ||
filename: fixtureFile('basic-app/app/components/Foo/Foo.js'), | ||
}, | ||
{ | ||
code: "import * as icon1 from './icons'", | ||
filename: fixtureFile('basic-app/app/components/Foo/Foo.js'), | ||
}, | ||
{ | ||
code: "export {default as icon1} from './icons'", | ||
filename: fixtureFile('basic-app/app/components/Foo/Foo.js'), | ||
}, | ||
{ | ||
code: "export * from './icons'", | ||
filename: fixtureFile('basic-app/app/components/Foo/Foo.js'), | ||
}, | ||
// Exports without a source file | ||
{ | ||
code: 'export {a, b}; export default c;', | ||
filename: fixtureFile('basic-app/app/components/Foo/Foo.js'), | ||
}, | ||
], | ||
|
||
invalid: [ | ||
// Importing / Exporting an icon directly from component file is invalid | ||
{ | ||
code: "import icon1 from './icons/icon1.svg'", | ||
errors: errors('ImportDeclaration', './icons', './icons/icon1.svg'), | ||
filename: fixtureFile('basic-app/app/components/Foo/Foo.js'), | ||
}, | ||
{ | ||
code: "import * as icon1 from './icons/icon1.svg'", | ||
errors: errors('ImportDeclaration', './icons', './icons/icon1.svg'), | ||
filename: fixtureFile('basic-app/app/components/Foo/index.js'), | ||
}, | ||
{ | ||
code: "export {default as icon1} from './icons/icon1.svg'", | ||
errors: errors('ExportNamedDeclaration', './icons', './icons/icon1.svg'), | ||
filename: fixtureFile('basic-app/app/components/Foo/Foo.js'), | ||
}, | ||
{ | ||
code: "export * from './icons/icon1.svg'", | ||
errors: errors('ExportAllDeclaration', './icons', './icons/icon1.svg'), | ||
filename: fixtureFile('basic-app/app/components/Foo/Foo.js'), | ||
}, | ||
// Importing / Exporting an icon directly from some other index file | ||
{ | ||
code: "import icon1 from './icons/icon1.svg'", | ||
errors: errors('ImportDeclaration', './icons', './icons/icon1.svg'), | ||
filename: fixtureFile('basic-app/app/components/Foo/index.js'), | ||
}, | ||
{ | ||
code: "import * as icon1 from './icons/icon1.svg'", | ||
errors: errors('ImportDeclaration', './icons', './icons/icon1.svg'), | ||
filename: fixtureFile('basic-app/app/components/Foo/index.js'), | ||
}, | ||
{ | ||
code: "export {default as icon1} from './icons/icon1.svg'", | ||
errors: errors('ExportNamedDeclaration', './icons', './icons/icon1.svg'), | ||
filename: fixtureFile('basic-app/app/components/Foo/index.js'), | ||
}, | ||
{ | ||
code: "export * from './icons/icon1.svg'", | ||
errors: errors('ExportAllDeclaration', './icons', './icons/icon1.svg'), | ||
filename: fixtureFile('basic-app/app/components/Foo/index.js'), | ||
}, | ||
], | ||
}); |