@simplysm/eslint-plugin 12.15.69 → 12.15.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -14,29 +14,35 @@ export default {
|
|
|
14
14
|
const sourceCode = context.sourceCode;
|
|
15
15
|
|
|
16
16
|
return {
|
|
17
|
-
ClassDeclaration(classNode) {
|
|
17
|
+
"ClassDeclaration, ClassExpression"(classNode) {
|
|
18
18
|
// @Component 데코레이터 찾기
|
|
19
|
-
const componentDecorator = classNode.decorators?.find(
|
|
20
|
-
(d)
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
const componentDecorator = classNode.decorators?.find((d) => {
|
|
20
|
+
if (d.expression?.type === "CallExpression") {
|
|
21
|
+
const callee = d.expression.callee;
|
|
22
|
+
return callee?.type === "Identifier" && callee?.name === "Component";
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
});
|
|
23
26
|
|
|
24
27
|
if (!componentDecorator) return;
|
|
25
28
|
|
|
26
29
|
// template 문자열 추출
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
30
|
+
const args = componentDecorator.expression.arguments;
|
|
31
|
+
if (!args?.[0] || args[0].type !== "ObjectExpression") return;
|
|
32
|
+
|
|
33
|
+
const templateProp = args[0].properties.find(
|
|
34
|
+
(p) => p.type === "Property" && p.key?.type === "Identifier" && p.key?.name === "template"
|
|
35
|
+
);
|
|
29
36
|
|
|
30
|
-
const templateProp = decoratorArg.properties.find((p) => p.key?.name === "template");
|
|
31
37
|
if (!templateProp) return;
|
|
32
38
|
|
|
33
|
-
const templateValue = templateProp.value;
|
|
34
39
|
let templateText = "";
|
|
40
|
+
const templateValue = templateProp.value;
|
|
35
41
|
|
|
36
42
|
if (templateValue.type === "TemplateLiteral") {
|
|
37
43
|
templateText = templateValue.quasis.map((q) => q.value.raw).join("");
|
|
38
|
-
} else if (templateValue.type === "Literal") {
|
|
39
|
-
templateText =
|
|
44
|
+
} else if (templateValue.type === "Literal" && typeof templateValue.value === "string") {
|
|
45
|
+
templateText = templateValue.value;
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
if (!templateText) return;
|
|
@@ -48,25 +54,33 @@ export default {
|
|
|
48
54
|
node.accessibility === "protected" &&
|
|
49
55
|
node.readonly === true &&
|
|
50
56
|
!node.static &&
|
|
51
|
-
node.key?.type === "Identifier"
|
|
57
|
+
node.key?.type === "Identifier"
|
|
52
58
|
);
|
|
53
59
|
|
|
54
60
|
for (const field of protectedReadonlyFields) {
|
|
55
61
|
const fieldName = field.key.name;
|
|
56
62
|
|
|
57
|
-
// 템플릿에서 사용 여부
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
// 템플릿에서 사용 여부
|
|
64
|
+
// \b는 $로 시작하는 identifier에서 동작 안 함 → lookahead/lookbehind 사용
|
|
65
|
+
const identifierPattern = new RegExp(
|
|
66
|
+
`(?<![a-zA-Z0-9_$])${escapeRegExp(fieldName)}(?![a-zA-Z0-9_$])`
|
|
67
|
+
);
|
|
68
|
+
const usedInTemplate = identifierPattern.test(templateText);
|
|
69
|
+
|
|
70
|
+
// 클래스 내 다른 곳에서 참조 여부 (해당 필드 선언부 제외)
|
|
71
|
+
let usedInClass = false;
|
|
72
|
+
for (const member of classNode.body.body) {
|
|
73
|
+
// 자기 자신 필드 선언은 스킵
|
|
74
|
+
if (member === field) continue;
|
|
60
75
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
});
|
|
76
|
+
traverseNode(member, (node) => {
|
|
77
|
+
if (node.type === "Identifier" && node.name === fieldName) {
|
|
78
|
+
usedInClass = true;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
68
81
|
|
|
69
|
-
|
|
82
|
+
if (usedInClass) break;
|
|
83
|
+
}
|
|
70
84
|
|
|
71
85
|
if (!usedInTemplate && !usedInClass) {
|
|
72
86
|
context.report({
|
|
@@ -74,9 +88,24 @@ export default {
|
|
|
74
88
|
messageId: "unusedField",
|
|
75
89
|
data: { name: fieldName },
|
|
76
90
|
fix(fixer) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
91
|
+
let start = field.range[0];
|
|
92
|
+
let end = field.range[1];
|
|
93
|
+
|
|
94
|
+
// 앞쪽 공백/줄바꿈 포함
|
|
95
|
+
const textBefore = sourceCode.text.slice(0, start);
|
|
96
|
+
const leadingMatch = textBefore.match(/\n[ \t]*$/);
|
|
97
|
+
if (leadingMatch) {
|
|
98
|
+
start -= leadingMatch[0].length - 1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 뒤쪽 세미콜론과 줄바꿈까지 포함
|
|
102
|
+
const afterText = sourceCode.text.slice(end);
|
|
103
|
+
const trailingMatch = afterText.match(/^;?[ \t]*\r?\n/);
|
|
104
|
+
if (trailingMatch) {
|
|
105
|
+
end += trailingMatch[0].length;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return fixer.removeRange([start, end]);
|
|
80
109
|
},
|
|
81
110
|
});
|
|
82
111
|
}
|
|
@@ -90,7 +119,7 @@ function traverseNode(node, callback) {
|
|
|
90
119
|
if (!node || typeof node !== "object") return;
|
|
91
120
|
callback(node);
|
|
92
121
|
for (const key of Object.keys(node)) {
|
|
93
|
-
if (key === "parent") continue;
|
|
122
|
+
if (key === "parent" || key === "range" || key === "loc") continue;
|
|
94
123
|
const child = node[key];
|
|
95
124
|
if (Array.isArray(child)) {
|
|
96
125
|
child.forEach((c) => traverseNode(c, callback));
|
|
@@ -99,3 +128,7 @@ function traverseNode(node, callback) {
|
|
|
99
128
|
}
|
|
100
129
|
}
|
|
101
130
|
}
|
|
131
|
+
|
|
132
|
+
function escapeRegExp(string) {
|
|
133
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
134
|
+
}
|