Skip to content

Commit

Permalink
Merge branch 'master' into feature-no-self-import
Browse files Browse the repository at this point in the history
  • Loading branch information
giodamelio authored Oct 13, 2017
2 parents fdb7823 + 68f63f5 commit df1d5cd
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 2 deletions.
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ This project adheres to [Semantic Versioning](http://semver.org/).
This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com).

## [Unreleased]
### Added
- [`exports-last`] rule ([#620] + [#632], thanks [@k15a])

### Changed
- Case-sensitivity checking ignores working directory and ancestors. ([#720] + [#858], thanks [@laysent])

## [2.7.0] - 2017-07-06
### Changed
- [`no-absolute-path`] picks up speed boost, optional AMD support ([#843], thansk [@jseminck])
- [`no-absolute-path`] picks up speed boost, optional AMD support ([#843], thanks [@jseminck])

## [2.6.1] - 2017-06-29
### Fixed
Expand Down Expand Up @@ -419,10 +423,12 @@ for info on changes for earlier releases.
[`no-unassigned-import`]: ./docs/rules/no-unassigned-import.md
[`unambiguous`]: ./docs/rules/unambiguous.md
[`no-anonymous-default-export`]: ./docs/rules/no-anonymous-default-export.md
[`exports-last`]: ./docs/rules/exports-last.md
[`no-self-import`]: ./docs/rules/no-self-import.md

[`memo-parser`]: ./memo-parser/README.md

[#858]: https://github.com/benmosher/eslint-plugin-import/pull/858
[#843]: https://github.com/benmosher/eslint-plugin-import/pull/843
[#871]: https://github.com/benmosher/eslint-plugin-import/pull/871
[#742]: https://github.com/benmosher/eslint-plugin-import/pull/742
Expand All @@ -434,6 +440,7 @@ for info on changes for earlier releases.
[#680]: https://github.com/benmosher/eslint-plugin-import/pull/680
[#654]: https://github.com/benmosher/eslint-plugin-import/pull/654
[#639]: https://github.com/benmosher/eslint-plugin-import/pull/639
[#632]: https://github.com/benmosher/eslint-plugin-import/pull/632
[#630]: https://github.com/benmosher/eslint-plugin-import/pull/630
[#628]: https://github.com/benmosher/eslint-plugin-import/pull/628
[#596]: https://github.com/benmosher/eslint-plugin-import/pull/596
Expand Down Expand Up @@ -488,11 +495,13 @@ for info on changes for earlier releases.

[#863]: https://github.com/benmosher/eslint-plugin-import/issues/863
[#839]: https://github.com/benmosher/eslint-plugin-import/issues/839
[#720]: https://github.com/benmosher/eslint-plugin-import/issues/720
[#686]: https://github.com/benmosher/eslint-plugin-import/issues/686
[#671]: https://github.com/benmosher/eslint-plugin-import/issues/671
[#660]: https://github.com/benmosher/eslint-plugin-import/issues/660
[#653]: https://github.com/benmosher/eslint-plugin-import/issues/653
[#627]: https://github.com/benmosher/eslint-plugin-import/issues/627
[#620]: https://github.com/benmosher/eslint-plugin-import/issues/620
[#609]: https://github.com/benmosher/eslint-plugin-import/issues/609
[#604]: https://github.com/benmosher/eslint-plugin-import/issues/604
[#602]: https://github.com/benmosher/eslint-plugin-import/issues/602
Expand Down Expand Up @@ -638,3 +647,5 @@ for info on changes for earlier releases.
[@eelyafi]: https://github.com/eelyafi
[@mastilver]: https://github.com/mastilver
[@jseminck]: https://github.com/jseminck
[@laysent]: https://github.com/laysent
[@k15a]: https://github.com/k15a
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,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 @@ -81,6 +82,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
Original file line number Diff line number Diff line change
@@ -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.
12 changes: 11 additions & 1 deletion src/core/importType.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,23 @@ function constant(value) {
return () => value
}

function baseModule(name) {
if (isScoped(name)) {
const [scope, pkg] = name.split('/')
return `${scope}/${pkg}`
}
const [pkg] = name.split('/')
return pkg
}

export function isAbsolute(name) {
return name.indexOf('/') === 0
}

export function isBuiltIn(name, settings) {
const base = baseModule(name)
const extras = (settings && settings['import/core-modules']) || []
return builtinModules.indexOf(name) !== -1 || extras.indexOf(name) > -1
return builtinModules.indexOf(base) !== -1 || extras.indexOf(base) > -1
}

function isExternalPath(path, name, settings) {
Expand Down
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,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
Original file line number Diff line number Diff line change
@@ -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',
})
}
})
}
},
}
},
}
12 changes: 12 additions & 0 deletions tests/src/core/importType.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,21 @@ describe('importType(name)', function () {
it("should return 'builtin' for additional core modules", function() {
// without extra config, should be marked external
expect(importType('electron', context)).to.equal('external')
expect(importType('@org/foobar', context)).to.equal('external')

const electronContext = testContext({ 'import/core-modules': ['electron'] })
expect(importType('electron', electronContext)).to.equal('builtin')

const scopedContext = testContext({ 'import/core-modules': ['@org/foobar'] })
expect(importType('@org/foobar', scopedContext)).to.equal('builtin')
})

it("should return 'builtin' for resources inside additional core modules", function() {
const electronContext = testContext({ 'import/core-modules': ['electron'] })
expect(importType('electron/some/path/to/resource.json', electronContext)).to.equal('builtin')

const scopedContext = testContext({ 'import/core-modules': ['@org/foobar'] })
expect(importType('@org/foobar/some/path/to/resource.json', scopedContext)).to.equal('builtin')
})

it("should return 'external' for module from 'node_modules' with default config", function() {
Expand Down
6 changes: 6 additions & 0 deletions tests/src/core/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { expect } from 'chai'
import resolve, { CASE_SENSITIVE_FS, fileExistsWithCaseSync } from 'eslint-module-utils/resolve'
import ModuleCache from 'eslint-module-utils/ModuleCache'

import * as path from 'path'
import * as fs from 'fs'
import * as utils from '../utils'

Expand Down Expand Up @@ -133,6 +134,11 @@ describe('resolve', function () {
expect(fileExistsWithCaseSync(file, ModuleCache.getSettings(testContext)))
.to.be.false
})
it('detecting case does not include parent folder path (issue #720)', function () {
const f = path.join(process.cwd().toUpperCase(), './tests/files/jsx/MyUnCoolComponent.jsx')
expect(fileExistsWithCaseSync(f, ModuleCache.getSettings(testContext), true))
.to.be.true
})
})

describe('rename cache correctness', function () {
Expand Down
124 changes: 124 additions & 0 deletions tests/src/rules/exports-last.js
Original file line number Diff line number Diff line change
@@ -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'),
],
}),
],
})
1 change: 1 addition & 0 deletions utils/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ exports.fileExistsWithCaseSync = function fileExistsWithCaseSync(filepath, cache

// null means it resolved to a builtin
if (filepath === null) return true
if (filepath.toLowerCase() === process.cwd().toLowerCase()) return true
const parsedPath = path.parse(filepath)
, dir = parsedPath.dir

Expand Down

0 comments on commit df1d5cd

Please sign in to comment.