Skip to content

Commit

Permalink
add fixer for first (#1046)
Browse files Browse the repository at this point in the history
  • Loading branch information
fnknzzz authored and benmosher committed Mar 30, 2018
1 parent 6fe78a9 commit a2acbde
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 4 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
76 changes: 73 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,17 @@ 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()
let nonImportCount = 0
, anyExpressions = false
, anyRelative = false
body.forEach(function (node){
, lastLegalImp = null
, errorInfos = []
, shouldSort = true
, lastSortNodesIndex = 0
body.forEach(function (node, index){
if (!anyExpressions && isPossibleDirective(node)) {
return
}
Expand All @@ -40,15 +48,77 @@ module.exports = {
}
}
if (nonImportCount > 0) {
context.report({
for (let variable of context.getDeclaredVariables(node)) {
if (!shouldSort) break
const references = variable.references
if (references.length) {
for (let reference of references) {
if (reference.identifier.range[0] < node.range[1]) {
shouldSort = false
break
}
}
}
}
shouldSort && (lastSortNodesIndex = errorInfos.length)
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.forEach(function (errorInfo, index) {
const node = errorInfo.node
, infos = {
node,
message,
}
if (index < lastSortNodesIndex) {
infos.fix = function (fixer) {
return fixer.insertTextAfter(node, '')
}
} else if (index === lastSortNodesIndex) {
const sortNodes = errorInfos.slice(0, lastSortNodesIndex + 1)
infos.fix = function (fixer) {
const removeFixers = sortNodes.map(function (_errorInfo) {
return fixer.removeRange(_errorInfo.range)
})
, range = [0, removeFixers[removeFixers.length - 1].range[1]]
let insertSourceCode = sortNodes.map(function (_errorInfo) {
const nodeSourceCode = String.prototype.slice.apply(
originSourceCode, _errorInfo.range
)
if (/\S/.test(nodeSourceCode[0])) {
return '\n' + nodeSourceCode
}
return nodeSourceCode
}).join('')
, insertFixer = null
, replaceSourceCode = ''
if (!lastLegalImp) {
insertSourceCode =
insertSourceCode.trim() + insertSourceCode.match(/^(\s+)/)[0]
}
insertFixer = lastLegalImp ?
fixer.insertTextAfter(lastLegalImp, insertSourceCode) :
fixer.insertTextBefore(body[0], insertSourceCode)
const fixers = [insertFixer].concat(removeFixers)
fixers.forEach(function (computedFixer, i) {
replaceSourceCode += (originSourceCode.slice(
fixers[i - 1] ? fixers[i - 1].range[1] : 0, computedFixer.range[0]
) + computedFixer.text)
})
return fixer.replaceTextRange(range, replaceSourceCode)
}
}
context.report(infos)
})
},
}
},
Expand Down
28 changes: 27 additions & 1 deletion tests/src/rules/first.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,21 @@ ruleTester.run('first', rule, {
invalid: [
test({ code: "import { x } from './foo';\
export { x };\
import { y } from './foo';"
import { y } from './bar';"
, errors: 1
, output: "import { x } from './foo';\
import { y } from './bar';\
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,26 @@ 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 a = 1;\
import { y } from './bar';\
if (true) { x() };\
import { x } from './foo';\
import { z } from './baz';"
, errors: 3
, output: "import { y } from './bar';\
var a = 1;\
if (true) { x() };\
import { x } from './foo';\
import { z } from './baz';"
})
, test({ code: "if (true) { console.log(1) }import a from 'b'"
, errors: 1
, output: "import a from 'b'\nif (true) { console.log(1) }"
})
,
]
})

0 comments on commit a2acbde

Please sign in to comment.