Skip to content

Commit

Permalink
Merge pull request #632 from k15a/master
Browse files Browse the repository at this point in the history
exports-last
  • Loading branch information
benmosher committed Aug 27, 2017
2 parents 146f87f + 1ba1c3a commit 92dd662
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -67,6 +67,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
**Style guide:**

* Ensure all imports appear before other statements ([`first`])
* Ensure all exports appear after other statements ([`exports-last`])
* Report repeated import of the same module in multiple places ([`no-duplicates`])
* Report namespace imports ([`no-namespace`])
* Ensure consistent use of file extension within the import path ([`extensions`])
Expand All @@ -79,6 +80,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
* Forbid anonymous values as default exports ([`no-anonymous-default-export`])

[`first`]: ./docs/rules/first.md
[`exports-last`]: ./docs/rules/exports-last.md
[`no-duplicates`]: ./docs/rules/no-duplicates.md
[`no-namespace`]: ./docs/rules/no-namespace.md
[`extensions`]: ./docs/rules/extensions.md
Expand Down
50 changes: 50 additions & 0 deletions docs/rules/exports-last.md
@@ -0,0 +1,50 @@
# exports-last

This rule enforces that all exports are declared at the bottom of the file. This rule will report any export declarations that comes before any non-export statements.


## This will be reported

```JS

const bool = true

export default bool

const str = 'foo'

```

```JS

export const bool = true

const str = 'foo'

```

## This will not be reported

```JS
const arr = ['bar']

export const bool = true

export default bool

export function func() {
console.log('Hello World 🌍')
}

export const str = 'foo'
```

## When Not To Use It

If you don't mind exports being sprinkled throughout a file, you may not want to enable this rule.

#### ES6 exports only

The exports-last rule is currently only working on ES6 exports. You may not want to enable this rule if you're using CommonJS exports.

If you need CommonJS support feel free to open an issue or create a PR.
3 changes: 3 additions & 0 deletions src/index.js
Expand Up @@ -31,6 +31,9 @@ export const rules = {
'unambiguous': require('./rules/unambiguous'),
'no-unassigned-import': require('./rules/no-unassigned-import'),

// export
'exports-last': require('./rules/exports-last'),

// metadata-based
'no-deprecated': require('./rules/no-deprecated'),

Expand Down
31 changes: 31 additions & 0 deletions src/rules/exports-last.js
@@ -0,0 +1,31 @@
function isNonExportStatement({ type }) {
return type !== 'ExportDefaultDeclaration' &&
type !== 'ExportNamedDeclaration' &&
type !== 'ExportAllDeclaration'
}

module.exports = {
create: function (context) {
return {
Program: function ({ body }) {
const lastNonExportStatementIndex = body.reduce(function findLastIndex(acc, item, index) {
if (isNonExportStatement(item)) {
return index
}
return acc
}, -1)

if (lastNonExportStatementIndex !== -1) {
body.slice(0, lastNonExportStatementIndex).forEach(function checkNonExport(node) {
if (!isNonExportStatement(node)) {
context.report({
node,
message: 'Export statements should appear at the end of the file',
})
}
})
}
},
}
},
}
124 changes: 124 additions & 0 deletions tests/src/rules/exports-last.js
@@ -0,0 +1,124 @@
import { test } from '../utils'

import { RuleTester } from 'eslint'
import rule from 'rules/exports-last'

const ruleTester = new RuleTester()

const error = type => ({
ruleId: 'exports-last',
message: 'Export statements should appear at the end of the file',
type
});

ruleTester.run('exports-last', rule, {
valid: [
// Empty file
test({
code: '// comment',
}),
test({
// No exports
code: `
const foo = 'bar'
const bar = 'baz'
`,
}),
test({
code: `
const foo = 'bar'
export {foo}
`,
}),
test({
code: `
const foo = 'bar'
export default foo
`,
}),
// Only exports
test({
code: `
export default foo
export const bar = true
`,
}),
test({
code: `
const foo = 'bar'
export default foo
export const bar = true
`,
}),
// Multiline export
test({
code: `
const foo = 'bar'
export default function foo () {
const very = 'multiline'
}
export const bar = true
`,
}),
// Many exports
test({
code: `
const foo = 'bar'
export default foo
export const so = 'many'
export const exports = ':)'
export const i = 'cant'
export const even = 'count'
export const how = 'many'
`,
}),
// Export all
test({
code: `
export * from './foo'
`,
}),
],
invalid: [
// Default export before variable declaration
test({
code: `
export default 'bar'
const bar = true
`,
errors: [error('ExportDefaultDeclaration')],
}),
// Named export before variable declaration
test({
code: `
export const foo = 'bar'
const bar = true
`,
errors: [error('ExportNamedDeclaration')],
}),
// Export all before variable declaration
test({
code: `
export * from './foo'
const bar = true
`,
errors: [error('ExportAllDeclaration')],
}),
// Many exports arround variable declaration
test({
code: `
export default 'such foo many bar'
export const so = 'many'
const foo = 'bar'
export const exports = ':)'
export const i = 'cant'
export const even = 'count'
export const how = 'many'
`,
errors: [
error('ExportDefaultDeclaration'),
error('ExportNamedDeclaration'),
],
}),
],
})

0 comments on commit 92dd662

Please sign in to comment.