Skip to content

Commit

Permalink
add fixer for first
Browse files Browse the repository at this point in the history
  • Loading branch information
fnknzzz committed Mar 14, 2018
1 parent 158f4e8 commit c638fdd
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 3 deletions.
6 changes: 6 additions & 0 deletions docs/rules/first.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ mode](http://www.ecma-international.org/ecma-262/6.0/#sec-strict-mode-code) so i

Given that, see [#255] for the reasoning.

### With Fixer

This rule contains a fixer to reorder in-body import to top, the following criteria applied:
1. Never re-order relative to each other, even if `absolute-first` is set.
2. If an import creates an identifier, and that identifier is referenced at module level *before* the import itself, that won't be re-ordered.

## When Not To Use It

If you don't mind imports being sprinkled throughout, you may not want to
Expand Down
69 changes: 66 additions & 3 deletions src/rules/first.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = {
docs: {
url: docsUrl('first'),
},
fixable: 'code',
},

create: function (context) {
Expand All @@ -18,10 +19,20 @@ module.exports = {
'Program': function (n) {
const body = n.body
, absoluteFirst = context.options[0] === 'absolute-first'
, message = 'Import in body of module; reorder to top.'
, sourceCode = context.getSourceCode()
, originSourceCode = sourceCode.getText()
, scopeManager = sourceCode.scopeManager
, moduleScope = scopeManager.scopes.find(function (scope) {
return scope.type === 'module'
})
let nonImportCount = 0
, anyExpressions = false
, anyRelative = false
body.forEach(function (node){
, lastLegalImp = null
, errorInfos = []

body.forEach(function (node, index){
if (!anyExpressions && isPossibleDirective(node)) {
return
}
Expand All @@ -40,15 +51,67 @@ module.exports = {
}
}
if (nonImportCount > 0) {
context.report({
let shouldSort = true
for (let variable of scopeManager.getDeclaredVariables(node)) {
const references = variable.references
if (references.length) {
for (let reference of references) {
if (reference.identifier.range[0] >= node.range[1]) {
break
} else if (reference.from === moduleScope) {
shouldSort = false
break
}
}
}
}
shouldSort && errorInfos.push({
node,
message: 'Import in body of module; reorder to top.',
range: [body[index - 1].range[1], node.range[1]],
})
} else {
lastLegalImp = node
}
} else {
nonImportCount++
}
})
if (!errorInfos.length) return
errorInfos.slice(0, -1).forEach(function (errorInfo) {
const node = errorInfo.node
context.report({
node,
message,
// fake fixer
fix: function (fixer) {
return fixer.insertTextAfter(node, '')
},
})
})
context.report({
node: errorInfos[errorInfos.length - 1].node,
message,
// fixers batch
fix: function (fixer) {
const removeFixers = errorInfos.map(function (errorInfo) {
return fixer.removeRange(errorInfo.range)
})
, insertSourceCode = errorInfos.map(function (errorInfo) {
const nodeSourceCode = String.prototype.slice.apply(
originSourceCode, errorInfo.range
)
if (/\S/.test(nodeSourceCode[0])) {
return '\n' + nodeSourceCode
} else {
return nodeSourceCode
}
}).join('')
, insertFixer = lastLegalImp ?
fixer.insertTextAfter(lastLegalImp, insertSourceCode) :
fixer.insertTextBefore(body[0], insertSourceCode.trim() + '\n')
return removeFixers.concat([insertFixer])
},
})
},
}
},
Expand Down
37 changes: 37 additions & 0 deletions tests/src/rules/first.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,19 @@ ruleTester.run('first', rule, {
export { x };\
import { y } from './foo';"
, errors: 1
, output: "import { x } from './foo';\
import { y } from './foo';\
export { x };"
})
, test({ code: "import { x } from './foo';\
export { x };\
import { y } from './bar';\
import { z } from './baz';"
, errors: 2
, output: "import { x } from './foo';\
import { y } from './bar';\
import { z } from './baz';\
export { x };"
})
, test({ code: "import { x } from './foo'; import { y } from 'bar'"
, options: ['absolute-first']
Expand All @@ -35,7 +42,37 @@ ruleTester.run('first', rule, {
'use directive';\
import { y } from 'bar';"
, errors: 1
, output: "import { x } from 'foo';\
import { y } from 'bar';\
'use directive';"
})
, test({ code: "var foo = bar;\
import { x } from './foo';\
import { y } from './bar';\
import { z } from './baz';"
, errors: 3
, output: "import { x } from './foo';\
import { y } from './bar';\
import { z } from './baz';\nvar foo = bar;"
})
, test({ code: "var a = x;\
import { x } from './foo';\
import { y } from './bar';\
import { z } from './baz';"
, errors: 2
, output: "import { y } from './bar';\
import { z } from './baz';\nvar a = x;\
import { x } from './foo';"
})
, test({ code: "if (true) { x() };\
import { x } from './foo';\
import { y } from './bar';\
import { z } from './baz';"
, errors: 3
, output: "import { x } from './foo';\
import { y } from './bar';\
import { z } from './baz';\nif (true) { x() };"
})
,
]
})

0 comments on commit c638fdd

Please sign in to comment.