@simplysm/lint 14.0.45 → 14.0.47

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":"AAsPA;;;;;;;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,189 @@ 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") {
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
+ // @let value 표현식: 타입 선언(LetDeclaration.value: AST)과 런타임 형상(ASTWithSource 유사체)이
72
+ // 불일치하므로, 공통 node.value?.ast 경로에 의존하지 않고 명시 분기로 추출한다.
73
+ if (node instanceof TmplAstLetDeclaration) {
74
+ const expr = node.value.ast ?? node.value;
75
+ collectExprIdentifiers(expr, currentLocals, ids);
76
+ }
77
+ // @if 브랜치의 expressionAlias (예: `@if (cond; as alias)`)
78
+ if (node instanceof TmplAstIfBlockBranch && node.expressionAlias?.name != null) {
79
+ currentLocals.add(node.expressionAlias.name);
80
+ }
81
+ // @for 로컬 변수: `item` 및 사용자 별칭 contextVariables (예: `let idx = $index`)
82
+ if (node instanceof TmplAstForLoopBlock) {
83
+ if (typeof node.item.name === "string") {
84
+ currentLocals.add(node.item.name);
85
+ }
86
+ for (const ctxVar of node.contextVariables) {
87
+ if (typeof ctxVar.name === "string") {
88
+ currentLocals.add(ctxVar.name);
89
+ }
90
+ }
91
+ }
92
+ // 보간 / BoundText / BoundAttribute 등 ASTWithSource 노드용 공통 경로.
93
+ // LetDeclaration 은 위에서 명시 분기로 처리되므로 여기서 제외한다 (이중 수집 방지).
94
+ if (!(node instanceof TmplAstLetDeclaration) && node.value?.ast != null) {
95
+ collectExprIdentifiers(node.value.ast, currentLocals, ids);
96
+ }
97
+ // @if 브랜치 조건 표현식
98
+ if (node instanceof TmplAstIfBlockBranch && node.expression != null) {
99
+ collectExprIdentifiers(node.expression, currentLocals, ids);
100
+ }
101
+ // @switch 표현식
102
+ if (node instanceof TmplAstSwitchBlock) {
103
+ collectExprIdentifiers(node.expression, currentLocals, ids);
104
+ }
105
+ // @case 표현식 (@default는 null)
106
+ if (node instanceof TmplAstSwitchBlockCase && node.expression != null) {
107
+ collectExprIdentifiers(node.expression, currentLocals, ids);
108
+ }
109
+ // @for iterable/trackBy 표현식 (item/contextVariables는 이미 currentLocals에 반영됨)
110
+ if (node instanceof TmplAstForLoopBlock) {
111
+ collectExprIdentifiers(node.expression, currentLocals, ids);
112
+ collectExprIdentifiers(node.trackBy, currentLocals, ids);
113
+ }
114
+ // @defer 트리거의 value 표현식 (BoundDeferredTrigger만 value를 가진다)
115
+ if (node instanceof TmplAstDeferredBlock) {
116
+ for (const bucket of [node.triggers, node.prefetchTriggers, node.hydrateTriggers]) {
117
+ for (const trigger of Object.values(bucket)) {
118
+ if (trigger instanceof TmplAstBoundDeferredTrigger) {
119
+ collectExprIdentifiers(trigger.value, currentLocals, ids);
120
+ }
121
+ }
122
+ }
123
+ }
124
+ // BoundAttribute (입력 바인딩)
125
+ if (Array.isArray(node.inputs)) {
126
+ for (const input of node.inputs) {
127
+ if (input.value?.ast != null) {
128
+ collectExprIdentifiers(input.value.ast, currentLocals, ids);
129
+ }
130
+ }
131
+ }
132
+ // BoundEvent (이벤트 바인딩)
133
+ if (Array.isArray(node.outputs)) {
134
+ for (const output of node.outputs) {
135
+ if (output.handler?.ast != null) {
136
+ collectExprIdentifiers(output.handler.ast, currentLocals, ids);
137
+ }
138
+ }
139
+ }
140
+ // 구조 디렉티브 속성 (templateAttrs)
141
+ if (Array.isArray(node.templateAttrs)) {
142
+ for (const attr of node.templateAttrs) {
143
+ if (attr.value?.ast != null) {
144
+ collectExprIdentifiers(attr.value.ast, currentLocals, ids);
145
+ }
146
+ }
147
+ }
148
+ // 자식 노드 재귀: @let 선언은 이후 형제에게 스코프를 전파한다
149
+ if (Array.isArray(node.children)) {
150
+ collectSiblingNodes(node.children, currentLocals, ids);
151
+ }
152
+ // @if의 분기 — IfBlock에는 children이 없고 branches로 노출된다
153
+ if (node instanceof TmplAstIfBlock) {
154
+ for (const branch of node.branches) {
155
+ collectTemplateNodeIdentifiers(branch, currentLocals, ids);
156
+ }
157
+ }
158
+ // @switch의 그룹 — SwitchBlock에는 children이 없고 groups로 노출된다
159
+ if (node instanceof TmplAstSwitchBlock) {
160
+ for (const group of node.groups) {
161
+ collectTemplateNodeIdentifiers(group, currentLocals, ids);
162
+ }
163
+ }
164
+ // @switch 그룹 내 @case 노드들 — children은 일반 경로로 처리된다
165
+ if (node instanceof TmplAstSwitchBlockCaseGroup) {
166
+ for (const c of node.cases) {
167
+ collectTemplateNodeIdentifiers(c, currentLocals, ids);
168
+ }
169
+ }
170
+ // @for의 @empty 블록 — ForLoopBlock.empty는 별도 BlockNode이며 children을 가진다
171
+ if (node instanceof TmplAstForLoopBlock && node.empty != null) {
172
+ collectSiblingNodes(node.empty.children, currentLocals, ids);
173
+ }
174
+ // @defer 분기 블록 (@placeholder, @loading, @error) — children은 별도 분기에 존재
175
+ if (node instanceof TmplAstDeferredBlock) {
176
+ for (const sub of [node.placeholder, node.loading, node.error]) {
177
+ if (sub != null) {
178
+ collectSiblingNodes(sub.children, currentLocals, ids);
179
+ }
180
+ }
181
+ }
182
+ }
183
+ /**
184
+ * 형제 노드 배열을 순회하며 식별자를 수집한다.
185
+ * @let 선언이 나오면 이후 형제 노드의 localVars에 이름을 추가해 스코프를 전파한다.
186
+ */
187
+ function collectSiblingNodes(nodes, localVars, ids) {
188
+ const scopedLocals = new Set(localVars);
189
+ for (const node of nodes) {
190
+ collectTemplateNodeIdentifiers(node, scopedLocals, ids);
191
+ if (node instanceof TmplAstLetDeclaration && typeof node.name === "string") {
192
+ scopedLocals.add(node.name);
193
+ }
194
+ }
195
+ }
196
+ /**
197
+ * Angular 인라인 템플릿 문자열에서 바인딩 표현식이 참조하는 식별자를 수집한다.
198
+ * @angular/compiler의 parseTemplate을 사용하여 AST 기반으로 정확히 추출한다.
199
+ */
200
+ function collectTemplateIdentifiers(templateText) {
201
+ const ids = new Set();
202
+ const result = parseTemplate(templateText, "");
203
+ collectSiblingNodes(result.nodes, new Set(), ids);
204
+ return ids;
23
205
  }
24
206
  /**
25
207
  * Angular `@Component` 내 미사용 `protected readonly` 필드를 감지하는 ESLint 규칙.
@@ -82,10 +264,10 @@ export default createRule({
82
264
  node.readonly === true &&
83
265
  !node.static &&
84
266
  node.key.type === AST_NODE_TYPES.Identifier);
267
+ const templateIdentifiers = collectTemplateIdentifiers(templateText);
85
268
  for (const field of protectedReadonlyFields) {
86
269
  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);
270
+ const usedInTemplate = templateIdentifiers.has(fieldName);
89
271
  const usedInClass = classNode.body.body.some((member) => {
90
272
  if (member === field)
91
273
  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":"AAAA,OAAO,EAAE,cAAc,EAAiB,MAAM,0BAA0B,CAAC;AACzE,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,2BAA2B,EAC3B,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,sBAAsB,EACtB,2BAA2B,GAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,SAAS,YAAY,CACnB,IAAmB,EACnB,QAAoC;IAEpC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACf,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,OAAO,IAAI,GAAG,KAAK,KAAK;YAAE,SAAS;QACnE,MAAM,KAAK,GAAI,IAA2C,CAAC,GAAG,CAAC,CAAC;QAChE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;gBACtB,IAAI,CAAC,IAAI,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;oBACtD,YAAY,CAAC,CAAkB,EAAE,QAAQ,CAAC,CAAC;gBAC7C,CAAC;YACH,CAAC;QACH,CAAC;aAAM,IAAI,KAAK,IAAI,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,IAAI,KAAK,EAAE,CAAC;YACzE,YAAY,CAAC,KAAsB,EAAE,QAAQ,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,sBAAsB,CAAC,GAAQ,EAAE,SAAsB,EAAE,GAAgB;IAChF,IAAI,GAAG,IAAI,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO;IAEnD,IACE,GAAG,YAAY,YAAY;QAC3B,CAAC,GAAG,CAAC,QAAQ,YAAY,gBAAgB,IAAI,GAAG,CAAC,QAAQ,YAAY,YAAY,CAAC;QAClF,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ;QAC5B,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EACxB,CAAC;QACD,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,YAAY,IAAI,GAAG,KAAK,UAAU;YAAE,SAAS;QAC3E,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QACrB,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC;gBACpB,IAAI,CAAC,IAAI,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;oBACvC,sBAAsB,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;gBAC5C,CAAC;YACH,CAAC;QACH,CAAC;aAAM,IAAI,GAAG,IAAI,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;YAClD,sBAAsB,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,8BAA8B,CACrC,IAAS,EACT,SAAsB,EACtB,GAAgB;IAEhB,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;IAEzC,8CAA8C;IAC9C,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QAClC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAC/B,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC/B,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;IAED,+CAA+C;IAC/C,gEAAgE;IAChE,IAAI,IAAI,YAAY,qBAAqB,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC3E,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED,+EAA+E;IAC/E,sDAAsD;IACtD,IAAI,IAAI,YAAY,qBAAqB,EAAE,CAAC;QAC1C,MAAM,IAAI,GAAI,IAAI,CAAC,KAA2B,CAAC,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC;QACjE,sBAAsB,CAAC,IAAI,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;IACnD,CAAC;IAED,uDAAuD;IACvD,IAAI,IAAI,YAAY,oBAAoB,IAAI,IAAI,CAAC,eAAe,EAAE,IAAI,IAAI,IAAI,EAAE,CAAC;QAC/E,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IAC/C,CAAC;IAED,uEAAuE;IACvE,IAAI,IAAI,YAAY,mBAAmB,EAAE,CAAC;QACxC,IAAI,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACvC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,CAAC;QACD,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC3C,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACpC,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IAED,6DAA6D;IAC7D,yDAAyD;IACzD,IAAI,CAAC,CAAC,IAAI,YAAY,qBAAqB,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC;QACxE,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;IAC7D,CAAC;IACD,iBAAiB;IACjB,IAAI,IAAI,YAAY,oBAAoB,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;QACpE,sBAAsB,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;IAC9D,CAAC;IACD,cAAc;IACd,IAAI,IAAI,YAAY,kBAAkB,EAAE,CAAC;QACvC,sBAAsB,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;IAC9D,CAAC;IACD,6BAA6B;IAC7B,IAAI,IAAI,YAAY,sBAAsB,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;QACtE,sBAAsB,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;IAC9D,CAAC;IACD,2EAA2E;IAC3E,IAAI,IAAI,YAAY,mBAAmB,EAAE,CAAC;QACxC,sBAAsB,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;QAC5D,sBAAsB,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;IAC3D,CAAC;IACD,2DAA2D;IAC3D,IAAI,IAAI,YAAY,oBAAoB,EAAE,CAAC;QACzC,KAAK,MAAM,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;YAClF,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC5C,IAAI,OAAO,YAAY,2BAA2B,EAAE,CAAC;oBACnD,sBAAsB,CAAC,OAAO,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;gBAC5D,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,0BAA0B;IAC1B,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChC,IAAI,KAAK,CAAC,KAAK,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC;gBAC7B,sBAAsB,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC;IACH,CAAC;IACD,uBAAuB;IACvB,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,IAAI,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC;gBAChC,sBAAsB,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;YACjE,CAAC;QACH,CAAC;IACH,CAAC;IACD,6BAA6B;IAC7B,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;QACtC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACtC,IAAI,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC;gBAC5B,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;IACH,CAAC;IACD,uCAAuC;IACvC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,mBAAmB,CAAC,IAAI,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;IACzD,CAAC;IAED,kDAAkD;IAClD,IAAI,IAAI,YAAY,cAAc,EAAE,CAAC;QACnC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnC,8BAA8B,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,wDAAwD;IACxD,IAAI,IAAI,YAAY,kBAAkB,EAAE,CAAC;QACvC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChC,8BAA8B,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAED,iDAAiD;IACjD,IAAI,IAAI,YAAY,2BAA2B,EAAE,CAAC;QAChD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC3B,8BAA8B,CAAC,CAAC,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IAED,qEAAqE;IACrE,IAAI,IAAI,YAAY,mBAAmB,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;QAC9D,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;IAC/D,CAAC;IAED,sEAAsE;IACtE,IAAI,IAAI,YAAY,oBAAoB,EAAE,CAAC;QACzC,KAAK,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/D,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;gBAChB,mBAAmB,CAAC,GAAG,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;YACxD,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,mBAAmB,CAC1B,KAAqB,EACrB,SAAsB,EACtB,GAAgB;IAEhB,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;IACxC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,8BAA8B,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,CAAC,CAAC;QACxD,IAAI,IAAI,YAAY,qBAAqB,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC3E,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,0BAA0B,CAAC,YAAoB;IACtD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,MAAM,MAAM,GAAG,aAAa,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAC/C,mBAAmB,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,GAAG,EAAE,EAAE,GAAG,CAAC,CAAC;IAClD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;GAOG;AACH,eAAe,UAAU,CAAC;IACxB,IAAI,EAAE,iCAAiC;IACvC,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,iEAAiE;SAC/E;QACD,OAAO,EAAE,MAAM;QACf,QAAQ,EAAE;YACR,WAAW,EAAE,uEAAuE;SACrF;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACZ,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QAEtC,OAAO;YACL,mCAAmC,CACjC,SAA+D;gBAE/D,MAAM,kBAAkB,GAAG,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;oBACzD,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,KAAK,cAAc,CAAC,cAAc,EAAE,CAAC;wBACxD,MAAM,MAAM,GAAG,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;wBACnC,OAAO,MAAM,CAAC,IAAI,KAAK,cAAc,CAAC,UAAU,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW,CAAC;oBAClF,CAAC;oBACD,OAAO,KAAK,CAAC;gBACf,CAAC,CAAC,CAAC;gBAEH,IAAI,kBAAkB,IAAI,IAAI;oBAAE,OAAO;gBAEvC,MAAM,IAAI,GAAG,kBAAkB,CAAC,UAAqC,CAAC;gBACtE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;gBAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBAC5B,IAAI,QAAQ,IAAI,IAAI,IAAI,QAAQ,CAAC,IAAI,KAAK,cAAc,CAAC,gBAAgB;oBAAE,OAAO;gBAElF,MAAM,YAAY,GAAG,QAAQ,CAAC,UAAU,CAAC,IAAI,CAC3C,CAAC,CAAC,EAA0B,EAAE,CAC5B,CAAC,CAAC,IAAI,KAAK,cAAc,CAAC,QAAQ;oBAClC,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,cAAc,CAAC,UAAU;oBACxC,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,UAAU,CAC5B,CAAC;gBAEF,IAAI,YAAY,IAAI,IAAI;oBAAE,OAAO;gBAEjC,IAAI,YAAY,GAAG,EAAE,CAAC;gBACtB,MAAM,aAAa,GAAG,YAAY,CAAC,KAAK,CAAC;gBACzC,IAAI,aAAa,CAAC,IAAI,KAAK,cAAc,CAAC,eAAe,EAAE,CAAC;oBAC1D,YAAY,GAAG,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACvE,CAAC;qBAAM,IACL,aAAa,CAAC,IAAI,KAAK,cAAc,CAAC,OAAO;oBAC7C,OAAO,aAAa,CAAC,KAAK,KAAK,QAAQ,EACvC,CAAC;oBACD,YAAY,GAAG,aAAa,CAAC,KAAK,CAAC;gBACrC,CAAC;gBAED,IAAI,YAAY,KAAK,EAAE;oBAAE,OAAO;gBAEhC,MAAM,uBAAuB,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CACxD,CAAC,IAAI,EAAuC,EAAE,CAC5C,IAAI,CAAC,IAAI,KAAK,cAAc,CAAC,kBAAkB;oBAC/C,IAAI,CAAC,aAAa,KAAK,WAAW;oBAClC,IAAI,CAAC,QAAQ,KAAK,IAAI;oBACtB,CAAC,IAAI,CAAC,MAAM;oBACZ,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,cAAc,CAAC,UAAU,CAC9C,CAAC;gBAEF,MAAM,mBAAmB,GAAG,0BAA0B,CAAC,YAAY,CAAC,CAAC;gBAErE,KAAK,MAAM,KAAK,IAAI,uBAAuB,EAAE,CAAC;oBAC5C,MAAM,SAAS,GAAI,KAAK,CAAC,GAA2B,CAAC,IAAI,CAAC;oBAE1D,MAAM,cAAc,GAAG,mBAAmB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;oBAE1D,MAAM,WAAW,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;wBACtD,IAAI,MAAM,KAAK,KAAK;4BAAE,OAAO,KAAK,CAAC;wBACnC,IAAI,KAAK,GAAG,KAAK,CAAC;wBAClB,YAAY,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;4BAC5B,IAAI,IAAI,CAAC,IAAI,KAAK,cAAc,CAAC,UAAU,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gCACvE,KAAK,GAAG,IAAI,CAAC;4BACf,CAAC;wBACH,CAAC,CAAC,CAAC;wBACH,OAAO,KAAK,CAAC;oBACf,CAAC,CAAC,CAAC;oBAEH,IAAI,CAAC,cAAc,IAAI,CAAC,WAAW,EAAE,CAAC;wBACpC,OAAO,CAAC,MAAM,CAAC;4BACb,IAAI,EAAE,KAAK;4BACX,SAAS,EAAE,aAAa;4BACxB,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;4BACzB,GAAG,CAAC,KAAK;gCACP,IAAI,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gCAC3B,IAAI,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gCAEzB,MAAM,UAAU,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;gCACnD,MAAM,YAAY,GAAG,UAAU,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;gCACnD,IAAI,YAAY,EAAE,CAAC;oCACjB,KAAK,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;gCACtC,CAAC;gCAED,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gCAC7C,MAAM,aAAa,GAAG,SAAS,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;gCACxD,IAAI,aAAa,EAAE,CAAC;oCAClB,GAAG,IAAI,aAAa,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;gCACjC,CAAC;gCAED,OAAO,KAAK,CAAC,WAAW,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;4BACzC,CAAC;yBACF,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/lint",
3
- "version": "14.0.45",
3
+ "version": "14.0.47",
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.9",
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,212 @@ 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") {
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
+ // @let value 표현식: 타입 선언(LetDeclaration.value: AST)과 런타임 형상(ASTWithSource 유사체)이
98
+ // 불일치하므로, 공통 node.value?.ast 경로에 의존하지 않고 명시 분기로 추출한다.
99
+ if (node instanceof TmplAstLetDeclaration) {
100
+ const expr = (node.value as { ast?: unknown }).ast ?? node.value;
101
+ collectExprIdentifiers(expr, currentLocals, ids);
102
+ }
103
+
104
+ // @if 브랜치의 expressionAlias (예: `@if (cond; as alias)`)
105
+ if (node instanceof TmplAstIfBlockBranch && node.expressionAlias?.name != null) {
106
+ currentLocals.add(node.expressionAlias.name);
107
+ }
108
+
109
+ // @for 로컬 변수: `item` 및 사용자 별칭 contextVariables (예: `let idx = $index`)
110
+ if (node instanceof TmplAstForLoopBlock) {
111
+ if (typeof node.item.name === "string") {
112
+ currentLocals.add(node.item.name);
113
+ }
114
+ for (const ctxVar of node.contextVariables) {
115
+ if (typeof ctxVar.name === "string") {
116
+ currentLocals.add(ctxVar.name);
117
+ }
118
+ }
119
+ }
120
+
121
+ // 보간 / BoundText / BoundAttribute 등 ASTWithSource 노드용 공통 경로.
122
+ // LetDeclaration 은 위에서 명시 분기로 처리되므로 여기서 제외한다 (이중 수집 방지).
123
+ if (!(node instanceof TmplAstLetDeclaration) && node.value?.ast != null) {
124
+ collectExprIdentifiers(node.value.ast, currentLocals, ids);
125
+ }
126
+ // @if 브랜치 조건 표현식
127
+ if (node instanceof TmplAstIfBlockBranch && node.expression != null) {
128
+ collectExprIdentifiers(node.expression, currentLocals, ids);
129
+ }
130
+ // @switch 표현식
131
+ if (node instanceof TmplAstSwitchBlock) {
132
+ collectExprIdentifiers(node.expression, currentLocals, ids);
133
+ }
134
+ // @case 표현식 (@default는 null)
135
+ if (node instanceof TmplAstSwitchBlockCase && node.expression != null) {
136
+ collectExprIdentifiers(node.expression, currentLocals, ids);
137
+ }
138
+ // @for iterable/trackBy 표현식 (item/contextVariables는 이미 currentLocals에 반영됨)
139
+ if (node instanceof TmplAstForLoopBlock) {
140
+ collectExprIdentifiers(node.expression, currentLocals, ids);
141
+ collectExprIdentifiers(node.trackBy, currentLocals, ids);
142
+ }
143
+ // @defer 트리거의 value 표현식 (BoundDeferredTrigger만 value를 가진다)
144
+ if (node instanceof TmplAstDeferredBlock) {
145
+ for (const bucket of [node.triggers, node.prefetchTriggers, node.hydrateTriggers]) {
146
+ for (const trigger of Object.values(bucket)) {
147
+ if (trigger instanceof TmplAstBoundDeferredTrigger) {
148
+ collectExprIdentifiers(trigger.value, currentLocals, ids);
149
+ }
150
+ }
151
+ }
152
+ }
153
+ // BoundAttribute (입력 바인딩)
154
+ if (Array.isArray(node.inputs)) {
155
+ for (const input of node.inputs) {
156
+ if (input.value?.ast != null) {
157
+ collectExprIdentifiers(input.value.ast, currentLocals, ids);
158
+ }
159
+ }
160
+ }
161
+ // BoundEvent (이벤트 바인딩)
162
+ if (Array.isArray(node.outputs)) {
163
+ for (const output of node.outputs) {
164
+ if (output.handler?.ast != null) {
165
+ collectExprIdentifiers(output.handler.ast, currentLocals, ids);
166
+ }
167
+ }
168
+ }
169
+ // 구조 디렉티브 속성 (templateAttrs)
170
+ if (Array.isArray(node.templateAttrs)) {
171
+ for (const attr of node.templateAttrs) {
172
+ if (attr.value?.ast != null) {
173
+ collectExprIdentifiers(attr.value.ast, currentLocals, ids);
174
+ }
175
+ }
176
+ }
177
+ // 자식 노드 재귀: @let 선언은 이후 형제에게 스코프를 전파한다
178
+ if (Array.isArray(node.children)) {
179
+ collectSiblingNodes(node.children, currentLocals, ids);
180
+ }
181
+
182
+ // @if의 분기 — IfBlock에는 children이 없고 branches로 노출된다
183
+ if (node instanceof TmplAstIfBlock) {
184
+ for (const branch of node.branches) {
185
+ collectTemplateNodeIdentifiers(branch, currentLocals, ids);
186
+ }
187
+ }
188
+
189
+ // @switch의 그룹 — SwitchBlock에는 children이 없고 groups로 노출된다
190
+ if (node instanceof TmplAstSwitchBlock) {
191
+ for (const group of node.groups) {
192
+ collectTemplateNodeIdentifiers(group, currentLocals, ids);
193
+ }
194
+ }
195
+
196
+ // @switch 그룹 내 @case 노드들 — children은 일반 경로로 처리된다
197
+ if (node instanceof TmplAstSwitchBlockCaseGroup) {
198
+ for (const c of node.cases) {
199
+ collectTemplateNodeIdentifiers(c, currentLocals, ids);
200
+ }
201
+ }
202
+
203
+ // @for의 @empty 블록 — ForLoopBlock.empty는 별도 BlockNode이며 children을 가진다
204
+ if (node instanceof TmplAstForLoopBlock && node.empty != null) {
205
+ collectSiblingNodes(node.empty.children, currentLocals, ids);
206
+ }
207
+
208
+ // @defer 분기 블록 (@placeholder, @loading, @error) — children은 별도 분기에 존재
209
+ if (node instanceof TmplAstDeferredBlock) {
210
+ for (const sub of [node.placeholder, node.loading, node.error]) {
211
+ if (sub != null) {
212
+ collectSiblingNodes(sub.children, currentLocals, ids);
213
+ }
214
+ }
215
+ }
216
+ }
217
+
218
+ /**
219
+ * 형제 노드 배열을 순회하며 식별자를 수집한다.
220
+ * @let 선언이 나오면 이후 형제 노드의 localVars에 이름을 추가해 스코프를 전파한다.
221
+ */
222
+ function collectSiblingNodes(
223
+ nodes: readonly any[],
224
+ localVars: Set<string>,
225
+ ids: Set<string>,
226
+ ): void {
227
+ const scopedLocals = new Set(localVars);
228
+ for (const node of nodes) {
229
+ collectTemplateNodeIdentifiers(node, scopedLocals, ids);
230
+ if (node instanceof TmplAstLetDeclaration && typeof node.name === "string") {
231
+ scopedLocals.add(node.name);
232
+ }
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Angular 인라인 템플릿 문자열에서 바인딩 표현식이 참조하는 식별자를 수집한다.
238
+ * @angular/compiler의 parseTemplate을 사용하여 AST 기반으로 정확히 추출한다.
239
+ */
240
+ function collectTemplateIdentifiers(templateText: string): Set<string> {
241
+ const ids = new Set<string>();
242
+ const result = parseTemplate(templateText, "");
243
+ collectSiblingNodes(result.nodes, new Set(), ids);
244
+ return ids;
26
245
  }
27
246
 
28
247
  /**
@@ -100,13 +319,12 @@ export default createRule({
100
319
  node.key.type === AST_NODE_TYPES.Identifier,
101
320
  );
102
321
 
322
+ const templateIdentifiers = collectTemplateIdentifiers(templateText);
323
+
103
324
  for (const field of protectedReadonlyFields) {
104
325
  const fieldName = (field.key as TSESTree.Identifier).name;
105
326
 
106
- const identifierPattern = new RegExp(
107
- `(?<![a-zA-Z0-9_$])${escapeRegExp(fieldName)}(?![a-zA-Z0-9_$])`,
108
- );
109
- const usedInTemplate = identifierPattern.test(templateText);
327
+ const usedInTemplate = templateIdentifiers.has(fieldName);
110
328
 
111
329
  const usedInClass = classNode.body.body.some((member) => {
112
330
  if (member === field) return false;