@simplysm/eslint-plugin 12.8.21 → 12.9.1

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/eslint-plugin",
3
- "version": "12.8.21",
3
+ "version": "12.9.1",
4
4
  "description": "심플리즘 패키지 - ESLINT 플러그인",
5
5
  "author": "김석래",
6
6
  "repository": {
@@ -12,13 +12,16 @@
12
12
  "type": "module",
13
13
  "main": "src/index.js",
14
14
  "dependencies": {
15
- "@eslint/compat": "^1.2.8",
15
+ "@eslint/compat": "^1.2.9",
16
16
  "@typescript-eslint/utils": "^8.31.1",
17
17
  "angular-eslint": "^19.3.0",
18
- "eslint": "^9.25.1",
18
+ "eslint": "^9.26.0",
19
19
  "eslint-plugin-import": "^2.31.0",
20
20
  "globals": "^16.0.0",
21
21
  "typescript": "~5.7.3",
22
22
  "typescript-eslint": "^8.31.1"
23
+ },
24
+ "devDependencies": {
25
+ "vitest": "^3.1.2"
23
26
  }
24
27
  }
@@ -27,6 +27,14 @@ export default [
27
27
  // 기본
28
28
  "no-console": ["warn"],
29
29
  "no-warning-comments": ["warn"],
30
+ 'no-restricted-syntax': [
31
+ 'error',
32
+ {
33
+ selector: 'PropertyDefinition[key.type="PrivateIdentifier"]',
34
+ message: 'Do not use ECMAScript private fields (e.g. #myField); use TypeScript "private" instead.',
35
+ },
36
+ ],
37
+ "eqeqeq": ["error", "always", { "null": "ignore" }],
30
38
 
31
39
  "require-await": ["error"],
32
40
  // "semi": ["error"],
@@ -59,6 +67,14 @@ export default [
59
67
  // 기본
60
68
  "no-console": ["warn"],
61
69
  "no-warning-comments": ["warn"],
70
+ 'no-restricted-syntax': [
71
+ 'error',
72
+ {
73
+ selector: 'PropertyDefinition[key.type="PrivateIdentifier"]',
74
+ message: 'Do not use ECMAScript private fields (e.g. #myField); use TypeScript "private" instead.',
75
+ },
76
+ ],
77
+ "eqeqeq": ["error", "always", { "null": "ignore" }],
62
78
 
63
79
  // 타입스크립트
64
80
  "@typescript-eslint/require-await": ["error"],
@@ -85,6 +101,20 @@ export default [
85
101
  },
86
102
  ],
87
103
  "@typescript-eslint/prefer-ts-expect-error": ["error"],
104
+ "@typescript-eslint/explicit-member-accessibility": [
105
+ "error",
106
+ {
107
+ "accessibility": "no-public",
108
+ },
109
+ ],
110
+ "@typescript-eslint/prefer-nullish-coalescing": [
111
+ "error",
112
+ {
113
+ "ignoreIfStatements": true,
114
+ // "ignoreConditionalTests": true,
115
+ // "ignoreTernaryTests": true,
116
+ },
117
+ ],
88
118
 
89
119
  // import
90
120
  "import/no-extraneous-dependencies": ["error"], // 느림
@@ -1,45 +1,47 @@
1
1
  export default {
2
2
  meta: {
3
- type: "suggestion",
3
+ type: "problem",
4
4
  docs: {
5
- description: "HTML 'TODO' 주석 경고",
5
+ description: "HTML 템플릿내 TODO 주석을 경고합니다.",
6
6
  },
7
-
8
7
  schema: [],
8
+ messages: {
9
+ noTodo: "Unexpected TODO comment in HTML template: '{{content}}'",
10
+ },
9
11
  },
10
12
 
11
- create: (context) => {
12
- // const parserServices = context.parserServices;
13
- return {
14
- Program(node) {
15
- if ("value" in node && typeof node.value === "string") {
16
- const comments = node.value.match(/<!--(((?!-->)[\s\S])*)-->/g);
17
- if (!comments) return;
18
-
19
- let cursor = 0;
20
- for (const comment of comments) {
21
- if (!comment.includes("TODO:")) continue;
22
-
23
- const index = node.value.slice(cursor).indexOf(comment) + cursor;
24
- const line = node.value.slice(0, index).split("\n").length;
25
- const column = index - node.value.slice(0, index).lastIndexOf("\n") - 1;
26
-
27
- const endIndex = index + comment.length;
28
- const endLine = node.value.slice(0, endIndex).split("\n").length;
29
- const endColumn = endIndex - node.value.slice(0, endIndex).lastIndexOf("\n") - 1;
30
-
31
- cursor += index;
32
-
33
- context.report({
34
- loc: {
35
- start: { line, column },
36
- end: { line: endLine, column: endColumn },
37
- },
38
- message: comment.match(/<!--(((?!-->)[\s\S])*)-->/)[1].trim(),
39
- });
40
- }
41
- }
42
- },
43
- };
13
+ create(context) {
14
+ const source = context.getSourceCode().getText();
15
+
16
+ const commentRegex = /<!--[\s\S]*?-->/g;
17
+ let match;
18
+
19
+ while ((match = commentRegex.exec(source)) !== null) {
20
+ const comment = match[0];
21
+ if (!comment.includes("TODO:")) continue;
22
+
23
+ const start = match.index;
24
+ const end = start + comment.length;
25
+
26
+ const contentMatch = comment.match(/<!--([\s\S]*?)-->/);
27
+ let content = contentMatch ? contentMatch[1].trim() : "";
28
+ const todoIndex = content.indexOf("TODO:");
29
+ if (todoIndex !== -1) {
30
+ content = content.substring(todoIndex + 5).trim();
31
+ }
32
+
33
+ const loc = context.getSourceCode().getLocFromIndex(start);
34
+ const endLoc = context.getSourceCode().getLocFromIndex(end);
35
+
36
+ context.report({
37
+ loc: { start: loc, end: endLoc },
38
+ messageId: "noTodo",
39
+ data: {
40
+ content: content,
41
+ },
42
+ });
43
+ }
44
+
45
+ return {};
44
46
  },
45
- };
47
+ };
@@ -0,0 +1,262 @@
1
+ import { RuleTester } from 'eslint';
2
+ import rule from '../src/rules/ng-template-no-todo-comments';
3
+ import { describe, expect, it } from "vitest";
4
+ import { templateParser } from "angular-eslint";
5
+
6
+ describe('ng-template-no-todo-comments 규칙 테스트', () => {
7
+ // 인라인 템플릿을 가진 Angular 컴포넌트 생성을 위한 헬퍼 함수
8
+ const createComponentWithTemplate = (template) => `
9
+ import { Component } from '@angular/core';
10
+
11
+ @Component({
12
+ selector: 'app-test',
13
+ template: \`${template}\`,
14
+ standalone: true
15
+ })
16
+ export class TestComponent {}
17
+ `;
18
+
19
+ // ESLint 9 플랫 설정 형식 사용
20
+ const ruleTester = new RuleTester({
21
+ languageOptions: {
22
+ parser: templateParser
23
+ }
24
+ });
25
+
26
+ // 기본 템플릿 테스트
27
+ describe('기본 템플릿 테스트', () => {
28
+ // 유효한 템플릿 테스트 (규칙 위반 없음)
29
+ it('정상적인 HTML 템플릿은 경고가 없어야 함', () => {
30
+ ruleTester.run('ng-template-no-todo-comments', rule, {
31
+ valid: [
32
+ {
33
+ code: createComponentWithTemplate('<div>정상적인 HTML 템플릿</div>'),
34
+ filename: 'test.component.ts'
35
+ }
36
+ ],
37
+ invalid: []
38
+ });
39
+ });
40
+
41
+ it('일반 주석은 경고가 없어야 함', () => {
42
+ ruleTester.run('ng-template-no-todo-comments', rule, {
43
+ valid: [
44
+ {
45
+ code: createComponentWithTemplate('<!-- 일반 주석 -->'),
46
+ filename: 'test.component.ts'
47
+ }
48
+ ],
49
+ invalid: []
50
+ });
51
+ });
52
+
53
+ it('NOTE 주석은 경고가 없어야 함', () => {
54
+ ruleTester.run('ng-template-no-todo-comments', rule, {
55
+ valid: [
56
+ {
57
+ code: createComponentWithTemplate('<!-- NOTE: 이것은 메모입니다 -->'),
58
+ filename: 'test.component.ts'
59
+ }
60
+ ],
61
+ invalid: []
62
+ });
63
+ });
64
+
65
+ it('여러 줄의 일반 주석은 경고가 없어야 함', () => {
66
+ ruleTester.run('ng-template-no-todo-comments', rule, {
67
+ valid: [
68
+ {
69
+ code: createComponentWithTemplate(`<div>
70
+ <!-- 여러 줄에 걸친
71
+ 일반 주석입니다 -->
72
+ </div>`),
73
+ filename: 'test.component.ts'
74
+ }
75
+ ],
76
+ invalid: []
77
+ });
78
+ });
79
+ });
80
+
81
+ // TODO 주석 테스트
82
+ describe('TODO 주석 테스트', () => {
83
+ it('단일 TODO 주석은 경고가 발생해야 함', () => {
84
+ ruleTester.run('ng-template-no-todo-comments', rule, {
85
+ valid: [],
86
+ invalid: [
87
+ {
88
+ code: createComponentWithTemplate('<!-- TODO: 이 기능 구현 필요 -->'),
89
+ filename: 'test.component.ts',
90
+ errors: [{ messageId: 'noTodo' }]
91
+ }
92
+ ]
93
+ });
94
+ });
95
+
96
+ it('요소 내부의 TODO 주석은 경고가 발생해야 함', () => {
97
+ ruleTester.run('ng-template-no-todo-comments', rule, {
98
+ valid: [],
99
+ invalid: [
100
+ {
101
+ code: createComponentWithTemplate(`<div>
102
+ <!-- TODO: 여기에 폼 추가하기 -->
103
+ <span>임시 내용</span>
104
+ </div>`),
105
+ filename: 'test.component.ts',
106
+ errors: [{ messageId: 'noTodo' }]
107
+ }
108
+ ]
109
+ });
110
+ });
111
+
112
+ it('주석 텍스트 안에 TODO 키워드가 있으면 경고가 발생해야 함', () => {
113
+ ruleTester.run('ng-template-no-todo-comments', rule, {
114
+ valid: [],
115
+ invalid: [
116
+ {
117
+ code: createComponentWithTemplate(`<div>
118
+ <!-- 이 부분은 TODO: 수정이 필요합니다 -->
119
+ </div>`),
120
+ filename: 'test.component.ts',
121
+ errors: [{ messageId: 'noTodo' }]
122
+ }
123
+ ]
124
+ });
125
+ });
126
+
127
+ it('여러 줄 주석 내의 TODO 키워드도 감지해야 함', () => {
128
+ ruleTester.run('ng-template-no-todo-comments', rule, {
129
+ valid: [],
130
+ invalid: [
131
+ {
132
+ code: createComponentWithTemplate(`<div>
133
+ <!--
134
+ 여러 줄 주석에
135
+ TODO: 이 부분 확인 필요
136
+ 가 포함된 케이스
137
+ -->
138
+ </div>`),
139
+ filename: 'test.component.ts',
140
+ errors: [{ messageId: 'noTodo' }]
141
+ }
142
+ ]
143
+ });
144
+ });
145
+
146
+ it('여러 개의 TODO 주석은 각각 감지되어야 함', () => {
147
+ ruleTester.run('ng-template-no-todo-comments', rule, {
148
+ valid: [],
149
+ invalid: [
150
+ {
151
+ code: createComponentWithTemplate(`<!-- TODO: 첫 번째 할 일 -->
152
+ <div>내용</div>
153
+ <!-- TODO: 두 번째 할 일 -->`),
154
+ filename: 'test.component.ts',
155
+ errors: [
156
+ { messageId: 'noTodo' },
157
+ { messageId: 'noTodo' }
158
+ ]
159
+ }
160
+ ]
161
+ });
162
+ });
163
+ });
164
+
165
+ // ng-template 지시자 테스트
166
+ describe('ng-template 지시자 테스트', () => {
167
+ it('ng-template 안의 TODO 주석을 감지해야 함', () => {
168
+ ruleTester.run('ng-template-no-todo-comments', rule, {
169
+ valid: [],
170
+ invalid: [
171
+ {
172
+ code: `
173
+ import { Component } from '@angular/core';
174
+
175
+ @Component({
176
+ selector: 'app-test',
177
+ template: \`<ng-template><!-- TODO: 구현 필요 --></ng-template>\`,
178
+ standalone: true
179
+ })
180
+ export class TestComponent {}
181
+ `,
182
+ filename: 'test.component.ts',
183
+ errors: [{ messageId: 'noTodo' }]
184
+ }
185
+ ]
186
+ });
187
+ });
188
+
189
+ it('ng-template 지시자가 있는 경우에도 정상 작동해야 함', () => {
190
+ ruleTester.run('ng-template-no-todo-comments', rule, {
191
+ valid: [
192
+ {
193
+ code: `
194
+ import { Component } from '@angular/core';
195
+
196
+ @Component({
197
+ selector: 'app-test',
198
+ template: \`
199
+ <ng-template itemOf>
200
+ <!-- 일반 주석 -->
201
+ <div>콘텐츠</div>
202
+ </ng-template>
203
+ \`,
204
+ standalone: true
205
+ })
206
+ export class TestComponent {}
207
+ `,
208
+ filename: 'test.component.ts'
209
+ }
210
+ ],
211
+ invalid: []
212
+ });
213
+ });
214
+
215
+ it('ng-template 지시자가 있는 경우 TODO 주석을 감지해야 함', () => {
216
+ ruleTester.run('ng-template-no-todo-comments', rule, {
217
+ valid: [],
218
+ invalid: [
219
+ {
220
+ code: `
221
+ import { Component } from '@angular/core';
222
+
223
+ @Component({
224
+ selector: 'app-test',
225
+ template: \`
226
+ <ng-template itemOf>
227
+ <!-- TODO: 여기에 구현 필요 -->
228
+ <div>임시 콘텐츠</div>
229
+ </ng-template>
230
+ \`,
231
+ standalone: true
232
+ })
233
+ export class TestComponent {}
234
+ `,
235
+ filename: 'test.component.ts',
236
+ errors: [{ messageId: 'noTodo' }]
237
+ }
238
+ ]
239
+ });
240
+ });
241
+ });
242
+
243
+ // 정규식 테스트
244
+ describe('정규식 패턴 테스트', () => {
245
+ it('정규식이 올바르게 TODO 주석을 감지해야 함', () => {
246
+ const testHtml = `
247
+ <!-- 일반 주석 -->
248
+ <!-- TODO: 할 일 항목 -->
249
+ <!-- todo: 소문자로 된 할 일 -->
250
+ <!-- To-Do: 하이픈이 있는 할 일 -->
251
+ `;
252
+
253
+ // 정규식 테스트
254
+ const commentRegex = /<!--[\s\S]*?-->/g;
255
+ const matches = [...testHtml.matchAll(commentRegex)];
256
+
257
+ // 매칭된 주석 중 'TODO:'가 포함된 주석 수 확인
258
+ const todoComments = matches.filter(match => match[0].includes('TODO:'));
259
+ expect(todoComments.length).toBe(1);
260
+ });
261
+ });
262
+ });