Skip to content

Commit

Permalink
Merge branch 'release/0.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
hiukky committed Oct 9, 2021
2 parents 2e8d44b + 7915c09 commit cd7dc76
Show file tree
Hide file tree
Showing 13 changed files with 1,141 additions and 658 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
yalc.lock
/temp
3 changes: 3 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
.husky
.github
node_modules
tests
lib
.eslintrc
.nvmrc
commitlint.config.js
jest.config.js
.prettierrc
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,13 @@ Then configure the rules you want to use under the rules section.
2,
{
"groups": [
"useSelector",
"useReducer",
"useContext",
"useState",
"useRef",
"useDispatch",
"useCallback"
"useCallback",
"useEffect"
]
}
]
Expand Down
89 changes: 0 additions & 89 deletions lib/rules/sort.ts

This file was deleted.

135 changes: 135 additions & 0 deletions lib/rules/sort/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/**
* @fileoverview A simple organizer for ordering hooks.
* @author Romullo @hiukky
*/
'use strict'

import { Context, Program, Node } from './types'

export const DEFAULT_GROUPS: string[] = [
'useReducer',
'useContext',
'useState',
'useRef',
'useDispatch',
'useCallback',
'useEffect',
]

module.exports = {
meta: {
docs: {
description: 'A simple organizer for ordering hooks.',
category: 'Non-matching declaration order.',
url: 'https://github.com/hiukky/eslint-plugin-hooks/blob/main/docs/rules/sort.md',
recommended: false,
},
fixable: undefined,
schema: [
{
type: 'object',
properties: {
groups: {
type: 'array',
},
},
},
],
},

create: (ctx: Context) => {
const options = ctx.options[0]
const groups: string[] = options?.groups || DEFAULT_GROUPS

return {
Program({ body }: Program) {
body
.filter(({ type }) =>
[
'FunctionDeclaration',
'VariableDeclaration',
'ExportNamedDeclaration',
'ExportDefaultDeclaration',
].includes(type),
)
.map(node => {
let declarations: Node = node

const isExportableDeclaration = (): boolean =>
['ExportNamedDeclaration', 'ExportDefaultDeclaration'].includes(
node.type,
)

if (isExportableDeclaration()) {
declarations =
node['declaration']['declarations']?.[0]['init'] ||
node['declaration']
} else {
declarations = node['declarations']?.[0]['init'] || node
}

return declarations['body']?.['body']
})
.filter(Boolean)
.forEach((declarations: Node[]) => {
let nodes: Node[] = []

declarations.forEach(node => {
if (node['type'] === 'ExpressionStatement') {
nodes.push(node['expression'])
}

if (node['type'] === 'VariableDeclaration') {
nodes.push(...node['declarations'])
}
})

const hooks = nodes
.map(
({ type, callee, init }) =>
(type === 'CallExpression'
? [type, callee]
: type === 'VariableDeclarator'
? [type, init]
: []) as [string, Node],
)
.filter(node => node.length)
.map(([type, declaration]) => {
const getHookName = (): string =>
type === 'CallExpression'
? declaration.name
: declaration.callee.name

const getHookNode = (): Node =>
type === 'CallExpression' ? declaration : declaration.callee

return [getHookName(), getHookNode()] as [string, Node]
})
.filter(
([name]) =>
name?.slice(0, 3) === 'use' && groups.includes(name),
)

const correctOrdering: [string, Node][] = [...hooks].sort(
(a, b) => groups.indexOf(a[0]) - groups.indexOf(b[0]),
)

hooks.forEach((hook, index) => {
const noMatching = (): boolean =>
correctOrdering.length > 1 &&
correctOrdering[index][0] !== hook[0]

if (noMatching()) {
ctx.report(
hook[1],
`Non-matching declaration order. ${hook[0]} comes ${
!index ? 'after' : 'before'
} ${correctOrdering[index][0]}.`,
)
}
})
})
},
}
},
}
Loading

0 comments on commit cd7dc76

Please sign in to comment.