Skip to content

Commit 8405684

Browse files
committed
feat: rule: no-unused-keys
1 parent cec27bb commit 8405684

File tree

1 file changed

+104
-0
lines changed

1 file changed

+104
-0
lines changed

lib/rules/no-unused-keys.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
'use strict'
2+
3+
/**
4+
* @param {import('eslint').Rule.RuleContext} context
5+
* @returns {import('eslint').Filename}
6+
*/
7+
function getFilename(context) {
8+
return context.getFilename ? context.getFilename() : context.filename
9+
}
10+
11+
/** @type {import('eslint').Rule.RuleModule} */
12+
module.exports = {
13+
meta: {
14+
type: 'problem',
15+
docs: {
16+
description:
17+
'Detect unused object keys if they are not referenced anywhere',
18+
recommended: true,
19+
},
20+
messages: {
21+
unusedKey:
22+
"The key '{{ key }}' is defined but never referenced anywhere.",
23+
},
24+
},
25+
26+
create(context) {
27+
const filename = getFilename(context)
28+
if (filename.endsWith('.ts')) {
29+
return {}
30+
}
31+
const parserServices = context.parserServices
32+
const checker = parserServices?.program?.getTypeChecker()
33+
const definedKeys = new Map()
34+
const referencedKeys = new Set()
35+
36+
return {
37+
// Filter out `css.global` and find objects in functions
38+
CallExpression(node) {
39+
if (
40+
node.callee.type === 'MemberExpression' &&
41+
node.callee.object.name === 'css' &&
42+
node.callee.property.name !== 'global' &&
43+
node.callee.property.name !== 'defineThemeVars' &&
44+
node.callee.property.name !== 'keyframes'
45+
) {
46+
const arg = node.arguments[0]
47+
if (
48+
arg &&
49+
arg.type === 'ObjectExpression' &&
50+
node.parent.type === 'VariableDeclarator'
51+
) {
52+
const variableName = node.parent.id.name
53+
const keyMap = new Map()
54+
55+
arg.properties.forEach((prop) => {
56+
if (
57+
prop.key &&
58+
prop.key.type === 'Identifier' &&
59+
prop.value.type === 'ObjectExpression'
60+
) {
61+
keyMap.set(prop.key.name, prop.key)
62+
}
63+
})
64+
65+
definedKeys.set(variableName, keyMap)
66+
}
67+
}
68+
},
69+
// `styles.header_main` の参照を記録
70+
MemberExpression(node) {
71+
if (
72+
node.object.type === 'Identifier' &&
73+
node.property.type === 'Identifier'
74+
) {
75+
const fullKey = `${node.object.name}.${node.property.name}`
76+
referencedKeys.add(fullKey)
77+
78+
// TypeScript の型情報を取得し、ジャンプ可能かチェック
79+
if (parserServices && checker) {
80+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node)
81+
const symbol = checker.getSymbolAtLocation(tsNode)
82+
if (symbol) {
83+
referencedKeys.add(fullKey)
84+
}
85+
}
86+
}
87+
},
88+
'Program:exit'() {
89+
definedKeys.forEach((keyMap, variableName) => {
90+
keyMap.forEach((keyNode, keyName) => {
91+
const fullKey = `${variableName}.${keyName}`
92+
if (!referencedKeys.has(fullKey)) {
93+
context.report({
94+
node: keyNode,
95+
messageId: 'unusedKey',
96+
data: { key: keyName },
97+
})
98+
}
99+
})
100+
})
101+
},
102+
}
103+
},
104+
}

0 commit comments

Comments
 (0)