@simplysm/lint 14.0.45 → 14.0.46

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ts-no-unused-protected-readonly.d.ts","sourceRoot":"","sources":["../../src/rules/ts-no-unused-protected-readonly.ts"],"names":[],"mappings":"AA2BA;;;;;;;GAOG;;;;AACH,wBAmHG"}
1
+ {"version":3,"file":"ts-no-unused-protected-readonly.d.ts","sourceRoot":"","sources":["../../src/rules/ts-no-unused-protected-readonly.ts"],"names":[],"mappings":"AA8OA;;;;;;;GAOG;;;;AACH,wBAkHG"}
@@ -1,4 +1,5 @@
1
1
  import { AST_NODE_TYPES } from "@typescript-eslint/utils";
2
+ import { ImplicitReceiver, parseTemplate, PropertyRead, ThisReceiver, TmplAstBoundDeferredTrigger, TmplAstDeferredBlock, TmplAstForLoopBlock, TmplAstIfBlock, TmplAstIfBlockBranch, TmplAstLetDeclaration, TmplAstSwitchBlock, TmplAstSwitchBlockCase, TmplAstSwitchBlockCaseGroup, } from "@angular/compiler";
2
3
  import { createRule } from "../utils/create-rule.js";
3
4
  function traverseNode(node, callback) {
4
5
  callback(node);
@@ -18,8 +19,182 @@ function traverseNode(node, callback) {
18
19
  }
19
20
  }
20
21
  }
21
- function escapeRegExp(string) {
22
- return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22
+ /**
23
+ * Angular 표현식 AST를 재귀 순회하여 ImplicitReceiver/ThisReceiver 위의
24
+ * PropertyRead 이름(클래스 필드 참조)을 수집한다.
25
+ * 로컬 변수(localVars)에 포함된 이름은 제외한다.
26
+ */
27
+ function collectExprIdentifiers(ast, localVars, ids) {
28
+ if (ast == null || typeof ast !== "object")
29
+ return;
30
+ if (ast instanceof PropertyRead &&
31
+ (ast.receiver instanceof ImplicitReceiver || ast.receiver instanceof ThisReceiver) &&
32
+ typeof ast.name === "string" &&
33
+ !localVars.has(ast.name)) {
34
+ ids.add(ast.name);
35
+ }
36
+ for (const key of Object.keys(ast)) {
37
+ if (key === "span" || key === "sourceSpan" || key === "nameSpan")
38
+ continue;
39
+ const val = ast[key];
40
+ if (Array.isArray(val)) {
41
+ for (const v of val) {
42
+ if (v != null && typeof v === "object") {
43
+ collectExprIdentifiers(v, localVars, ids);
44
+ }
45
+ }
46
+ }
47
+ else if (val != null && typeof val === "object" && typeof val.constructor === "function") {
48
+ collectExprIdentifiers(val, localVars, ids);
49
+ }
50
+ }
51
+ }
52
+ /**
53
+ * Angular 템플릿 AST 노드를 재귀 순회하여 바인딩 표현식 내 식별자를 수집한다.
54
+ * 구조 디렉티브(*ngFor 등)의 로컬 변수와 @let 선언은 스코프에서 제외한다.
55
+ */
56
+ function collectTemplateNodeIdentifiers(node, localVars, ids) {
57
+ const currentLocals = new Set(localVars);
58
+ // 구조 디렉티브의 로컬 변수 (*ngFor="let item of items")
59
+ if (Array.isArray(node.variables)) {
60
+ for (const v of node.variables) {
61
+ if (typeof v.name === "string") {
62
+ currentLocals.add(v.name);
63
+ }
64
+ }
65
+ }
66
+ // @let 선언: 자기 자신의 value 표현식 스캔 시 자기 이름은 로컬이 아님
67
+ // (@let foo = foo + 1 같은 self-reference는 Angular가 금지하므로 고려 불필요)
68
+ if (node instanceof TmplAstLetDeclaration && typeof node.name === "string") {
69
+ currentLocals.add(node.name);
70
+ }
71
+ // @if 브랜치의 expressionAlias (예: `@if (cond; as alias)`)
72
+ if (node instanceof TmplAstIfBlockBranch && node.expressionAlias?.name != null) {
73
+ currentLocals.add(node.expressionAlias.name);
74
+ }
75
+ // @for 로컬 변수: `item` 및 사용자 별칭 contextVariables (예: `let idx = $index`)
76
+ if (node instanceof TmplAstForLoopBlock) {
77
+ if (typeof node.item.name === "string") {
78
+ currentLocals.add(node.item.name);
79
+ }
80
+ for (const ctxVar of node.contextVariables) {
81
+ if (typeof ctxVar.name === "string") {
82
+ currentLocals.add(ctxVar.name);
83
+ }
84
+ }
85
+ }
86
+ // 보간 / BoundText
87
+ if (node.value?.ast != null) {
88
+ collectExprIdentifiers(node.value.ast, currentLocals, ids);
89
+ }
90
+ // @if 브랜치 조건 표현식
91
+ if (node instanceof TmplAstIfBlockBranch && node.expression != null) {
92
+ collectExprIdentifiers(node.expression, currentLocals, ids);
93
+ }
94
+ // @switch 표현식
95
+ if (node instanceof TmplAstSwitchBlock) {
96
+ collectExprIdentifiers(node.expression, currentLocals, ids);
97
+ }
98
+ // @case 표현식 (@default는 null)
99
+ if (node instanceof TmplAstSwitchBlockCase && node.expression != null) {
100
+ collectExprIdentifiers(node.expression, currentLocals, ids);
101
+ }
102
+ // @for iterable/trackBy 표현식 (item/contextVariables는 이미 currentLocals에 반영됨)
103
+ if (node instanceof TmplAstForLoopBlock) {
104
+ collectExprIdentifiers(node.expression, currentLocals, ids);
105
+ collectExprIdentifiers(node.trackBy, currentLocals, ids);
106
+ }
107
+ // @defer 트리거의 value 표현식 (BoundDeferredTrigger만 value를 가진다)
108
+ if (node instanceof TmplAstDeferredBlock) {
109
+ for (const bucket of [node.triggers, node.prefetchTriggers, node.hydrateTriggers]) {
110
+ for (const trigger of Object.values(bucket)) {
111
+ if (trigger instanceof TmplAstBoundDeferredTrigger) {
112
+ collectExprIdentifiers(trigger.value, currentLocals, ids);
113
+ }
114
+ }
115
+ }
116
+ }
117
+ // BoundAttribute (입력 바인딩)
118
+ if (Array.isArray(node.inputs)) {
119
+ for (const input of node.inputs) {
120
+ if (input.value?.ast != null) {
121
+ collectExprIdentifiers(input.value.ast, currentLocals, ids);
122
+ }
123
+ }
124
+ }
125
+ // BoundEvent (이벤트 바인딩)
126
+ if (Array.isArray(node.outputs)) {
127
+ for (const output of node.outputs) {
128
+ if (output.handler?.ast != null) {
129
+ collectExprIdentifiers(output.handler.ast, currentLocals, ids);
130
+ }
131
+ }
132
+ }
133
+ // 구조 디렉티브 속성 (templateAttrs)
134
+ if (Array.isArray(node.templateAttrs)) {
135
+ for (const attr of node.templateAttrs) {
136
+ if (attr.value?.ast != null) {
137
+ collectExprIdentifiers(attr.value.ast, currentLocals, ids);
138
+ }
139
+ }
140
+ }
141
+ // 자식 노드 재귀: @let 선언은 이후 형제에게 스코프를 전파한다
142
+ if (Array.isArray(node.children)) {
143
+ collectSiblingNodes(node.children, currentLocals, ids);
144
+ }
145
+ // @if의 분기 — IfBlock에는 children이 없고 branches로 노출된다
146
+ if (node instanceof TmplAstIfBlock) {
147
+ for (const branch of node.branches) {
148
+ collectTemplateNodeIdentifiers(branch, currentLocals, ids);
149
+ }
150
+ }
151
+ // @switch의 그룹 — SwitchBlock에는 children이 없고 groups로 노출된다
152
+ if (node instanceof TmplAstSwitchBlock) {
153
+ for (const group of node.groups) {
154
+ collectTemplateNodeIdentifiers(group, currentLocals, ids);
155
+ }
156
+ }
157
+ // @switch 그룹 내 @case 노드들 — children은 일반 경로로 처리된다
158
+ if (node instanceof TmplAstSwitchBlockCaseGroup) {
159
+ for (const c of node.cases) {
160
+ collectTemplateNodeIdentifiers(c, currentLocals, ids);
161
+ }
162
+ }
163
+ // @for의 @empty 블록 — ForLoopBlock.empty는 별도 BlockNode이며 children을 가진다
164
+ if (node instanceof TmplAstForLoopBlock && node.empty != null) {
165
+ collectSiblingNodes(node.empty.children, currentLocals, ids);
166
+ }
167
+ // @defer 분기 블록 (@placeholder, @loading, @error) — children은 별도 분기에 존재
168
+ if (node instanceof TmplAstDeferredBlock) {
169
+ for (const sub of [node.placeholder, node.loading, node.error]) {
170
+ if (sub != null) {
171
+ collectSiblingNodes(sub.children, currentLocals, ids);
172
+ }
173
+ }
174
+ }
175
+ }
176
+ /**
177
+ * 형제 노드 배열을 순회하며 식별자를 수집한다.
178
+ * @let 선언이 나오면 이후 형제 노드의 localVars에 이름을 추가해 스코프를 전파한다.
179
+ */
180
+ function collectSiblingNodes(nodes, localVars, ids) {
181
+ const scopedLocals = new Set(localVars);
182
+ for (const node of nodes) {
183
+ collectTemplateNodeIdentifiers(node, scopedLocals, ids);
184
+ if (node instanceof TmplAstLetDeclaration && typeof node.name === "string") {
185
+ scopedLocals.add(node.name);
186
+ }
187
+ }
188
+ }
189
+ /**
190
+ * Angular 인라인 템플릿 문자열에서 바인딩 표현식이 참조하는 식별자를 수집한다.
191
+ * @angular/compiler의 parseTemplate을 사용하여 AST 기반으로 정확히 추출한다.
192
+ */
193
+ function collectTemplateIdentifiers(templateText) {
194
+ const ids = new Set();
195
+ const result = parseTemplate(templateText, "");
196
+ collectSiblingNodes(result.nodes, new Set(), ids);
197
+ return ids;
23
198
  }
24
199
  /**
25
200
  * Angular `@Component` 내 미사용 `protected readonly` 필드를 감지하는 ESLint 규칙.
@@ -82,10 +257,10 @@ export default createRule({
82
257
  node.readonly === true &&
83
258
  !node.static &&
84
259
  node.key.type === AST_NODE_TYPES.Identifier);
260
+ const templateIdentifiers = collectTemplateIdentifiers(templateText);
85
261
  for (const field of protectedReadonlyFields) {
86
262
  const fieldName = field.key.name;
87
- const identifierPattern = new RegExp(`(?<![a-zA-Z0-9_$])${escapeRegExp(fieldName)}(?![a-zA-Z0-9_$])`);
88
- const usedInTemplate = identifierPattern.test(templateText);
263
+ const usedInTemplate = templateIdentifiers.has(fieldName);
89
264
  const usedInClass = classNode.body.body.some((member) => {
90
265
  if (member === field)
91
266
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"ts-no-unused-protected-readonly.js","sourceRoot":"","sources":["../../src/rules/ts-no-unused-protected-readonly.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ts-no-unused-protected-readonly.js","sourceRoot":"","sources":["../../src/rules/ts-no-unused-protected-readonly.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/lint",
3
- "version": "14.0.45",
3
+ "version": "14.0.46",
4
4
  "description": "심플리즘 패키지 - ESLint plugin",
5
5
  "author": "심플리즘",
6
6
  "license": "Apache-2.0",
@@ -27,6 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@angular-eslint/utils": "^21.3.1",
30
+ "@angular/compiler": "^21.2.8",
30
31
  "@typescript-eslint/utils": "^8.58.2",
31
32
  "angular-eslint": "^21.3.1",
32
33
  "eslint": "^9.39.4",
@@ -1,4 +1,19 @@
1
1
  import { AST_NODE_TYPES, type TSESTree } from "@typescript-eslint/utils";
2
+ import {
3
+ ImplicitReceiver,
4
+ parseTemplate,
5
+ PropertyRead,
6
+ ThisReceiver,
7
+ TmplAstBoundDeferredTrigger,
8
+ TmplAstDeferredBlock,
9
+ TmplAstForLoopBlock,
10
+ TmplAstIfBlock,
11
+ TmplAstIfBlockBranch,
12
+ TmplAstLetDeclaration,
13
+ TmplAstSwitchBlock,
14
+ TmplAstSwitchBlockCase,
15
+ TmplAstSwitchBlockCaseGroup,
16
+ } from "@angular/compiler";
2
17
  import { createRule } from "../utils/create-rule";
3
18
 
4
19
  function traverseNode(
@@ -21,8 +36,204 @@ function traverseNode(
21
36
  }
22
37
  }
23
38
 
24
- function escapeRegExp(string: string): string {
25
- return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
39
+ /**
40
+ * Angular 표현식 AST를 재귀 순회하여 ImplicitReceiver/ThisReceiver 위의
41
+ * PropertyRead 이름(클래스 필드 참조)을 수집한다.
42
+ * 로컬 변수(localVars)에 포함된 이름은 제외한다.
43
+ */
44
+ function collectExprIdentifiers(ast: any, localVars: Set<string>, ids: Set<string>): void {
45
+ if (ast == null || typeof ast !== "object") return;
46
+
47
+ if (
48
+ ast instanceof PropertyRead &&
49
+ (ast.receiver instanceof ImplicitReceiver || ast.receiver instanceof ThisReceiver) &&
50
+ typeof ast.name === "string" &&
51
+ !localVars.has(ast.name)
52
+ ) {
53
+ ids.add(ast.name);
54
+ }
55
+
56
+ for (const key of Object.keys(ast)) {
57
+ if (key === "span" || key === "sourceSpan" || key === "nameSpan") continue;
58
+ const val = ast[key];
59
+ if (Array.isArray(val)) {
60
+ for (const v of val) {
61
+ if (v != null && typeof v === "object") {
62
+ collectExprIdentifiers(v, localVars, ids);
63
+ }
64
+ }
65
+ } else if (val != null && typeof val === "object" && typeof val.constructor === "function") {
66
+ collectExprIdentifiers(val, localVars, ids);
67
+ }
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Angular 템플릿 AST 노드를 재귀 순회하여 바인딩 표현식 내 식별자를 수집한다.
73
+ * 구조 디렉티브(*ngFor 등)의 로컬 변수와 @let 선언은 스코프에서 제외한다.
74
+ */
75
+ function collectTemplateNodeIdentifiers(
76
+ node: any,
77
+ localVars: Set<string>,
78
+ ids: Set<string>,
79
+ ): void {
80
+ const currentLocals = new Set(localVars);
81
+
82
+ // 구조 디렉티브의 로컬 변수 (*ngFor="let item of items")
83
+ if (Array.isArray(node.variables)) {
84
+ for (const v of node.variables) {
85
+ if (typeof v.name === "string") {
86
+ currentLocals.add(v.name);
87
+ }
88
+ }
89
+ }
90
+
91
+ // @let 선언: 자기 자신의 value 표현식 스캔 시 자기 이름은 로컬이 아님
92
+ // (@let foo = foo + 1 같은 self-reference는 Angular가 금지하므로 고려 불필요)
93
+ if (node instanceof TmplAstLetDeclaration && typeof node.name === "string") {
94
+ currentLocals.add(node.name);
95
+ }
96
+
97
+ // @if 브랜치의 expressionAlias (예: `@if (cond; as alias)`)
98
+ if (node instanceof TmplAstIfBlockBranch && node.expressionAlias?.name != null) {
99
+ currentLocals.add(node.expressionAlias.name);
100
+ }
101
+
102
+ // @for 로컬 변수: `item` 및 사용자 별칭 contextVariables (예: `let idx = $index`)
103
+ if (node instanceof TmplAstForLoopBlock) {
104
+ if (typeof node.item.name === "string") {
105
+ currentLocals.add(node.item.name);
106
+ }
107
+ for (const ctxVar of node.contextVariables) {
108
+ if (typeof ctxVar.name === "string") {
109
+ currentLocals.add(ctxVar.name);
110
+ }
111
+ }
112
+ }
113
+
114
+ // 보간 / BoundText
115
+ if (node.value?.ast != null) {
116
+ collectExprIdentifiers(node.value.ast, currentLocals, ids);
117
+ }
118
+ // @if 브랜치 조건 표현식
119
+ if (node instanceof TmplAstIfBlockBranch && node.expression != null) {
120
+ collectExprIdentifiers(node.expression, currentLocals, ids);
121
+ }
122
+ // @switch 표현식
123
+ if (node instanceof TmplAstSwitchBlock) {
124
+ collectExprIdentifiers(node.expression, currentLocals, ids);
125
+ }
126
+ // @case 표현식 (@default는 null)
127
+ if (node instanceof TmplAstSwitchBlockCase && node.expression != null) {
128
+ collectExprIdentifiers(node.expression, currentLocals, ids);
129
+ }
130
+ // @for iterable/trackBy 표현식 (item/contextVariables는 이미 currentLocals에 반영됨)
131
+ if (node instanceof TmplAstForLoopBlock) {
132
+ collectExprIdentifiers(node.expression, currentLocals, ids);
133
+ collectExprIdentifiers(node.trackBy, currentLocals, ids);
134
+ }
135
+ // @defer 트리거의 value 표현식 (BoundDeferredTrigger만 value를 가진다)
136
+ if (node instanceof TmplAstDeferredBlock) {
137
+ for (const bucket of [node.triggers, node.prefetchTriggers, node.hydrateTriggers]) {
138
+ for (const trigger of Object.values(bucket)) {
139
+ if (trigger instanceof TmplAstBoundDeferredTrigger) {
140
+ collectExprIdentifiers(trigger.value, currentLocals, ids);
141
+ }
142
+ }
143
+ }
144
+ }
145
+ // BoundAttribute (입력 바인딩)
146
+ if (Array.isArray(node.inputs)) {
147
+ for (const input of node.inputs) {
148
+ if (input.value?.ast != null) {
149
+ collectExprIdentifiers(input.value.ast, currentLocals, ids);
150
+ }
151
+ }
152
+ }
153
+ // BoundEvent (이벤트 바인딩)
154
+ if (Array.isArray(node.outputs)) {
155
+ for (const output of node.outputs) {
156
+ if (output.handler?.ast != null) {
157
+ collectExprIdentifiers(output.handler.ast, currentLocals, ids);
158
+ }
159
+ }
160
+ }
161
+ // 구조 디렉티브 속성 (templateAttrs)
162
+ if (Array.isArray(node.templateAttrs)) {
163
+ for (const attr of node.templateAttrs) {
164
+ if (attr.value?.ast != null) {
165
+ collectExprIdentifiers(attr.value.ast, currentLocals, ids);
166
+ }
167
+ }
168
+ }
169
+ // 자식 노드 재귀: @let 선언은 이후 형제에게 스코프를 전파한다
170
+ if (Array.isArray(node.children)) {
171
+ collectSiblingNodes(node.children, currentLocals, ids);
172
+ }
173
+
174
+ // @if의 분기 — IfBlock에는 children이 없고 branches로 노출된다
175
+ if (node instanceof TmplAstIfBlock) {
176
+ for (const branch of node.branches) {
177
+ collectTemplateNodeIdentifiers(branch, currentLocals, ids);
178
+ }
179
+ }
180
+
181
+ // @switch의 그룹 — SwitchBlock에는 children이 없고 groups로 노출된다
182
+ if (node instanceof TmplAstSwitchBlock) {
183
+ for (const group of node.groups) {
184
+ collectTemplateNodeIdentifiers(group, currentLocals, ids);
185
+ }
186
+ }
187
+
188
+ // @switch 그룹 내 @case 노드들 — children은 일반 경로로 처리된다
189
+ if (node instanceof TmplAstSwitchBlockCaseGroup) {
190
+ for (const c of node.cases) {
191
+ collectTemplateNodeIdentifiers(c, currentLocals, ids);
192
+ }
193
+ }
194
+
195
+ // @for의 @empty 블록 — ForLoopBlock.empty는 별도 BlockNode이며 children을 가진다
196
+ if (node instanceof TmplAstForLoopBlock && node.empty != null) {
197
+ collectSiblingNodes(node.empty.children, currentLocals, ids);
198
+ }
199
+
200
+ // @defer 분기 블록 (@placeholder, @loading, @error) — children은 별도 분기에 존재
201
+ if (node instanceof TmplAstDeferredBlock) {
202
+ for (const sub of [node.placeholder, node.loading, node.error]) {
203
+ if (sub != null) {
204
+ collectSiblingNodes(sub.children, currentLocals, ids);
205
+ }
206
+ }
207
+ }
208
+ }
209
+
210
+ /**
211
+ * 형제 노드 배열을 순회하며 식별자를 수집한다.
212
+ * @let 선언이 나오면 이후 형제 노드의 localVars에 이름을 추가해 스코프를 전파한다.
213
+ */
214
+ function collectSiblingNodes(
215
+ nodes: readonly any[],
216
+ localVars: Set<string>,
217
+ ids: Set<string>,
218
+ ): void {
219
+ const scopedLocals = new Set(localVars);
220
+ for (const node of nodes) {
221
+ collectTemplateNodeIdentifiers(node, scopedLocals, ids);
222
+ if (node instanceof TmplAstLetDeclaration && typeof node.name === "string") {
223
+ scopedLocals.add(node.name);
224
+ }
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Angular 인라인 템플릿 문자열에서 바인딩 표현식이 참조하는 식별자를 수집한다.
230
+ * @angular/compiler의 parseTemplate을 사용하여 AST 기반으로 정확히 추출한다.
231
+ */
232
+ function collectTemplateIdentifiers(templateText: string): Set<string> {
233
+ const ids = new Set<string>();
234
+ const result = parseTemplate(templateText, "");
235
+ collectSiblingNodes(result.nodes, new Set(), ids);
236
+ return ids;
26
237
  }
27
238
 
28
239
  /**
@@ -100,13 +311,12 @@ export default createRule({
100
311
  node.key.type === AST_NODE_TYPES.Identifier,
101
312
  );
102
313
 
314
+ const templateIdentifiers = collectTemplateIdentifiers(templateText);
315
+
103
316
  for (const field of protectedReadonlyFields) {
104
317
  const fieldName = (field.key as TSESTree.Identifier).name;
105
318
 
106
- const identifierPattern = new RegExp(
107
- `(?<![a-zA-Z0-9_$])${escapeRegExp(fieldName)}(?![a-zA-Z0-9_$])`,
108
- );
109
- const usedInTemplate = identifierPattern.test(templateText);
319
+ const usedInTemplate = templateIdentifiers.has(fieldName);
110
320
 
111
321
  const usedInClass = classNode.body.body.some((member) => {
112
322
  if (member === field) return false;