dash-devtools 1.0.0__py3-none-any.whl

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.
Files changed (53) hide show
  1. dash_devtools/__init__.py +8 -0
  2. dash_devtools/__main__.py +11 -0
  3. dash_devtools/ai_engine.py +441 -0
  4. dash_devtools/browser.py +541 -0
  5. dash_devtools/cli.py +1452 -0
  6. dash_devtools/database.py +338 -0
  7. dash_devtools/dbdiagram.py +183 -0
  8. dash_devtools/e2e.py +329 -0
  9. dash_devtools/fixers/__init__.py +57 -0
  10. dash_devtools/fixers/migration_fixer.py +115 -0
  11. dash_devtools/fixers/ux_fixer.py +106 -0
  12. dash_devtools/fixers/version_bumper.py +115 -0
  13. dash_devtools/gas_mes_test.py +1241 -0
  14. dash_devtools/generators/__init__.py +84 -0
  15. dash_devtools/health.py +476 -0
  16. dash_devtools/hooks/__init__.py +250 -0
  17. dash_devtools/hooks/pre_commit.py +161 -0
  18. dash_devtools/hooks/pre_push.py +275 -0
  19. dash_devtools/init_test.py +352 -0
  20. dash_devtools/markdown_report.py +309 -0
  21. dash_devtools/migrators/__init__.py +21 -0
  22. dash_devtools/perf.py +321 -0
  23. dash_devtools/report.py +667 -0
  24. dash_devtools/reporters/__init__.py +11 -0
  25. dash_devtools/spec.py +230 -0
  26. dash_devtools/stats.py +355 -0
  27. dash_devtools/test_suite.py +690 -0
  28. dash_devtools/testing.py +416 -0
  29. dash_devtools/validators/__init__.py +157 -0
  30. dash_devtools/validators/backend/__init__.py +12 -0
  31. dash_devtools/validators/backend/nodejs.py +245 -0
  32. dash_devtools/validators/backend/python.py +439 -0
  33. dash_devtools/validators/code_quality.py +243 -0
  34. dash_devtools/validators/common/__init__.py +11 -0
  35. dash_devtools/validators/common/quality.py +319 -0
  36. dash_devtools/validators/common/security.py +270 -0
  37. dash_devtools/validators/common/spec.py +273 -0
  38. dash_devtools/validators/detector.py +394 -0
  39. dash_devtools/validators/frontend/__init__.py +14 -0
  40. dash_devtools/validators/frontend/angular.py +245 -0
  41. dash_devtools/validators/frontend/gas.py +310 -0
  42. dash_devtools/validators/frontend/vite.py +539 -0
  43. dash_devtools/validators/migration.py +292 -0
  44. dash_devtools/validators/performance.py +167 -0
  45. dash_devtools/validators/security.py +205 -0
  46. dash_devtools/vision/__init__.py +368 -0
  47. dash_devtools/watch.py +266 -0
  48. dash_devtools/word_report.py +690 -0
  49. dash_devtools-1.0.0.dist-info/METADATA +834 -0
  50. dash_devtools-1.0.0.dist-info/RECORD +53 -0
  51. dash_devtools-1.0.0.dist-info/WHEEL +5 -0
  52. dash_devtools-1.0.0.dist-info/entry_points.txt +2 -0
  53. dash_devtools-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,243 @@
1
+ """
2
+ 程式碼品質驗證器
3
+
4
+ 檢查項目:
5
+ 1. 檔案行數限制 (500 行)
6
+ 2. 命名規範
7
+ 3. 中文註解
8
+ 4. 禁止簡體字
9
+ 5. 禁止 Emoji(應使用 icon font)
10
+ """
11
+
12
+ import re
13
+ from pathlib import Path
14
+
15
+
16
+ class CodeQualityValidator:
17
+ """程式碼品質驗證器"""
18
+
19
+ name = 'code_quality'
20
+
21
+ # 設定
22
+ MAX_FILE_LINES = 500
23
+
24
+ # 常見簡體字
25
+ SIMPLIFIED_CHINESE = [
26
+ '这', '个', '们', '为', '与', '来', '对', '时', '后', '进',
27
+ '发', '会', '过', '着', '动', '机', '关', '开', '门', '问',
28
+ '间', '还', '应', '该', '当', '电', '并', '长', '设', '现',
29
+ '实', '点', '将', '从', '头', '见', '两', '无', '产', '业',
30
+ '经', '变', '虽', '统', '义', '语', '说', '话', '认', '让',
31
+ '请', '马', '车', '书', '学', '习', '写', '医', '药', '师'
32
+ ]
33
+
34
+ # 忽略目錄
35
+ IGNORE_DIRS = [
36
+ 'node_modules', '.git', 'dist', 'build', '.next', '__pycache__',
37
+ '.angular', 'venv', '.venv', '.cache', 'coverage'
38
+ ]
39
+
40
+ # 日文專案(允許使用日文漢字)
41
+ JAPANESE_PROJECTS = ['jinkochino']
42
+
43
+ def __init__(self, project_path):
44
+ self.project_path = Path(project_path)
45
+ self.project_name = self.project_path.name
46
+ self.result = {
47
+ 'name': self.name,
48
+ 'passed': True,
49
+ 'errors': [],
50
+ 'warnings': [],
51
+ 'checks': {}
52
+ }
53
+
54
+ def run(self):
55
+ """執行所有驗證"""
56
+ if not self.project_path.exists():
57
+ self.result['passed'] = False
58
+ self.result['errors'].append(f'專案路徑不存在: {self.project_path}')
59
+ return self.result
60
+
61
+ self.check_file_length()
62
+ self.check_simplified_chinese()
63
+ self.check_naming_conventions()
64
+ self.check_emoji_usage()
65
+
66
+ return self.result
67
+
68
+ def check_file_length(self):
69
+ """檢查檔案行數"""
70
+ large_files = []
71
+
72
+ for file_path in self._get_source_files():
73
+ try:
74
+ content = file_path.read_text(encoding='utf-8')
75
+ line_count = len(content.splitlines())
76
+
77
+ if line_count > self.MAX_FILE_LINES:
78
+ large_files.append({
79
+ 'file': str(file_path.relative_to(self.project_path)),
80
+ 'lines': line_count
81
+ })
82
+ except Exception:
83
+ pass
84
+
85
+ self.result['checks']['file_length'] = {
86
+ 'count': len(large_files),
87
+ 'files': large_files
88
+ }
89
+
90
+ if large_files:
91
+ for f in large_files[:5]:
92
+ self.result['warnings'].append(
93
+ f"檔案過長: {f['file']} ({f['lines']} 行)"
94
+ )
95
+
96
+ def check_simplified_chinese(self):
97
+ """檢查簡體字"""
98
+ # 跳過日文專案
99
+ if self.project_name in self.JAPANESE_PROJECTS:
100
+ self.result['checks']['simplified_chinese'] = {
101
+ 'count': 0,
102
+ 'files': [],
103
+ 'skipped': '日文專案'
104
+ }
105
+ return
106
+
107
+ issues = []
108
+
109
+ for file_path in self._get_source_files():
110
+ try:
111
+ content = file_path.read_text(encoding='utf-8')
112
+ found_chars = []
113
+
114
+ for char in self.SIMPLIFIED_CHINESE:
115
+ if char in content:
116
+ found_chars.append(char)
117
+
118
+ if found_chars:
119
+ issues.append({
120
+ 'file': str(file_path.relative_to(self.project_path)),
121
+ 'chars': found_chars[:5]
122
+ })
123
+ except Exception:
124
+ pass
125
+
126
+ self.result['checks']['simplified_chinese'] = {
127
+ 'count': len(issues),
128
+ 'files': issues
129
+ }
130
+
131
+ if issues:
132
+ for issue in issues[:3]:
133
+ self.result['errors'].append(
134
+ f"發現簡體字在 {issue['file']}: {', '.join(issue['chars'])}"
135
+ )
136
+ self.result['passed'] = False
137
+
138
+ def check_naming_conventions(self):
139
+ """檢查命名規範"""
140
+ issues = []
141
+
142
+ for file_path in self._get_source_files():
143
+ # 檢查檔案名稱
144
+ name = file_path.stem
145
+
146
+ # JS/TS 檔案應該是 kebab-case 或 PascalCase
147
+ if file_path.suffix in ['.js', '.ts', '.jsx', '.tsx']:
148
+ if not self._is_valid_js_filename(name):
149
+ issues.append({
150
+ 'file': str(file_path.relative_to(self.project_path)),
151
+ 'issue': '檔名應為 kebab-case 或 PascalCase'
152
+ })
153
+
154
+ self.result['checks']['naming'] = {
155
+ 'count': len(issues),
156
+ 'issues': issues
157
+ }
158
+
159
+ if issues:
160
+ for issue in issues[:3]:
161
+ self.result['warnings'].append(
162
+ f"命名問題: {issue['file']} - {issue['issue']}"
163
+ )
164
+
165
+ def _get_source_files(self):
166
+ """取得所有原始碼檔案"""
167
+ extensions = ['*.js', '*.ts', '*.jsx', '*.tsx', '*.py', '*.css', '*.scss']
168
+ files = []
169
+
170
+ for ext in extensions:
171
+ for f in self.project_path.rglob(ext):
172
+ if not any(ignore in str(f) for ignore in self.IGNORE_DIRS):
173
+ files.append(f)
174
+
175
+ return files
176
+
177
+ def _is_valid_js_filename(self, name):
178
+ """檢查 JS 檔名是否有效"""
179
+ # kebab-case
180
+ if re.match(r'^[a-z][a-z0-9-]*$', name):
181
+ return True
182
+ # PascalCase
183
+ if re.match(r'^[A-Z][a-zA-Z0-9]*$', name):
184
+ return True
185
+ # camelCase
186
+ if re.match(r'^[a-z][a-zA-Z0-9]*$', name):
187
+ return True
188
+ return False
189
+
190
+ def check_emoji_usage(self):
191
+ """檢查程式碼中的 Emoji 使用(應改用 icon font)"""
192
+ # 精確的 emoji Unicode 範圍(避免誤判中文字)
193
+ emoji_pattern = re.compile(
194
+ "["
195
+ "\U0001F300-\U0001F5FF" # 雜項符號和象形文字
196
+ "\U0001F600-\U0001F64F" # 表情符號
197
+ "\U0001F680-\U0001F6FF" # 交通和地圖符號
198
+ "\U0001F900-\U0001F9FF" # 補充符號和象形文字
199
+ "\U0001FA00-\U0001FA6F" # 棋類符號
200
+ "\U0001FA70-\U0001FAFF" # 符號和象形文字擴展-A
201
+ "\U00002600-\U000026FF" # 雜項符號
202
+ "\U00002700-\U000027BF" # 裝飾符號
203
+ "]+",
204
+ flags=re.UNICODE
205
+ )
206
+
207
+ issues = []
208
+
209
+ for file_path in self._get_source_files():
210
+ # 只檢查 JS/TS 檔案(不檢查 CSS)
211
+ if file_path.suffix not in ['.js', '.ts', '.jsx', '.tsx']:
212
+ continue
213
+
214
+ try:
215
+ content = file_path.read_text(encoding='utf-8')
216
+ matches = emoji_pattern.findall(content)
217
+
218
+ if matches:
219
+ # 過濾掉註解中的 emoji
220
+ unique_emojis = list(set(matches))[:5]
221
+ issues.append({
222
+ 'file': str(file_path.relative_to(self.project_path)),
223
+ 'emojis': unique_emojis,
224
+ 'count': len(matches)
225
+ })
226
+ except Exception:
227
+ pass
228
+
229
+ self.result['checks']['emoji_usage'] = {
230
+ 'count': sum(i['count'] for i in issues) if issues else 0,
231
+ 'files': issues
232
+ }
233
+
234
+ if issues:
235
+ total = sum(i['count'] for i in issues)
236
+ self.result['errors'].append(
237
+ f"[EMOJI] 程式碼中禁止使用 Emoji: {total} 個(請改用 icon font)"
238
+ )
239
+ for issue in issues[:5]:
240
+ self.result['errors'].append(
241
+ f" {issue['file']}: {''.join(issue['emojis'])}"
242
+ )
243
+ self.result['passed'] = False
@@ -0,0 +1,11 @@
1
+ """
2
+ 通用驗證器
3
+
4
+ 適用於所有專案類型
5
+ """
6
+
7
+ from .security import SecurityValidator
8
+ from .quality import QualityValidator
9
+ from .spec import SpecValidator
10
+
11
+ __all__ = ['SecurityValidator', 'QualityValidator', 'SpecValidator']
@@ -0,0 +1,319 @@
1
+ """
2
+ 程式碼品質驗證器(通用)
3
+
4
+ 檢查項目:
5
+ 1. 檔案行數限制 (500 行)
6
+ 2. 命名規範
7
+ 3. 中文註解
8
+ 4. 禁止簡體字
9
+ 5. 禁止 Emoji(應使用 icon font)
10
+ """
11
+
12
+ import re
13
+ from fnmatch import fnmatch
14
+ from pathlib import Path
15
+
16
+
17
+ class QualityValidator:
18
+ """程式碼品質驗證器"""
19
+
20
+ name = 'quality'
21
+
22
+ # 設定
23
+ MAX_FILE_LINES = 500
24
+
25
+ # 常見簡體字
26
+ SIMPLIFIED_CHINESE = [
27
+ '这', '个', '们', '为', '与', '来', '对', '时', '后', '进',
28
+ '发', '会', '过', '着', '动', '机', '关', '开', '门', '问',
29
+ '间', '还', '应', '该', '当', '电', '并', '长', '设', '现',
30
+ '实', '点', '将', '从', '头', '见', '两', '无', '产', '业',
31
+ '经', '变', '虽', '统', '义', '语', '说', '话', '认', '让',
32
+ '请', '马', '车', '书', '学', '习', '写', '医', '药', '师'
33
+ ]
34
+
35
+ # 忽略目錄
36
+ IGNORE_DIRS = [
37
+ 'node_modules', '.git', 'dist', 'build', '.next', '__pycache__',
38
+ '.angular', 'venv', '.venv', '.cache', 'coverage',
39
+ 'playwright-report'
40
+ ]
41
+
42
+ # 日文專案(允許使用日文漢字)
43
+ JAPANESE_PROJECTS = ['jinkochino']
44
+
45
+ def __init__(self, project_path):
46
+ self.project_path = Path(project_path)
47
+ self.project_name = self.project_path.name
48
+ self.scanignore = self._parse_scanignore()
49
+ self.result = {
50
+ 'name': self.name,
51
+ 'passed': True,
52
+ 'errors': [],
53
+ 'warnings': [],
54
+ 'checks': {}
55
+ }
56
+
57
+ def run(self):
58
+ """執行所有驗證"""
59
+ if not self.project_path.exists():
60
+ self.result['passed'] = False
61
+ self.result['errors'].append(f'專案路徑不存在: {self.project_path}')
62
+ return self.result
63
+
64
+ self.check_file_length()
65
+ self.check_simplified_chinese()
66
+ self.check_naming_conventions()
67
+ self.check_emoji_usage()
68
+
69
+ return self.result
70
+
71
+ def check_file_length(self):
72
+ """檢查檔案行數"""
73
+ large_files = []
74
+
75
+ for file_path in self._get_source_files():
76
+ try:
77
+ content = file_path.read_text(encoding='utf-8')
78
+ line_count = len(content.splitlines())
79
+
80
+ if line_count > self.MAX_FILE_LINES:
81
+ large_files.append({
82
+ 'file': str(file_path.relative_to(self.project_path)),
83
+ 'lines': line_count
84
+ })
85
+ except Exception:
86
+ pass
87
+
88
+ self.result['checks']['file_length'] = {
89
+ 'count': len(large_files),
90
+ 'files': large_files
91
+ }
92
+
93
+ if large_files:
94
+ for f in large_files[:5]:
95
+ self.result['warnings'].append(
96
+ f"檔案過長: {f['file']} ({f['lines']} 行)"
97
+ )
98
+
99
+ def check_simplified_chinese(self):
100
+ """檢查簡體字"""
101
+ # 跳過日文專案
102
+ if self.project_name in self.JAPANESE_PROJECTS:
103
+ self.result['checks']['simplified_chinese'] = {
104
+ 'count': 0,
105
+ 'files': [],
106
+ 'skipped': '日文專案'
107
+ }
108
+ return
109
+
110
+ issues = []
111
+
112
+ for file_path in self._get_source_files():
113
+ try:
114
+ rel_path = str(file_path.relative_to(self.project_path))
115
+
116
+ # 檢查 [pattern:簡體字] 排除
117
+ if self._is_pattern_ignored(rel_path, '簡體字'):
118
+ continue
119
+
120
+ content = file_path.read_text(encoding='utf-8')
121
+ found_chars = []
122
+
123
+ for char in self.SIMPLIFIED_CHINESE:
124
+ if char in content:
125
+ found_chars.append(char)
126
+
127
+ if found_chars:
128
+ issues.append({
129
+ 'file': rel_path,
130
+ 'chars': found_chars[:5]
131
+ })
132
+ except Exception:
133
+ pass
134
+
135
+ self.result['checks']['simplified_chinese'] = {
136
+ 'count': len(issues),
137
+ 'files': issues
138
+ }
139
+
140
+ if issues:
141
+ for issue in issues[:3]:
142
+ self.result['errors'].append(
143
+ f"發現簡體字在 {issue['file']}: {', '.join(issue['chars'])}"
144
+ )
145
+ self.result['passed'] = False
146
+
147
+ def check_naming_conventions(self):
148
+ """檢查命名規範"""
149
+ issues = []
150
+
151
+ for file_path in self._get_source_files():
152
+ name = file_path.stem
153
+
154
+ # JS/TS 檔案應該是 kebab-case 或 PascalCase
155
+ if file_path.suffix in ['.js', '.ts', '.jsx', '.tsx']:
156
+ if not self._is_valid_js_filename(name):
157
+ issues.append({
158
+ 'file': str(file_path.relative_to(self.project_path)),
159
+ 'issue': '檔名應為 kebab-case 或 PascalCase'
160
+ })
161
+
162
+ self.result['checks']['naming'] = {
163
+ 'count': len(issues),
164
+ 'issues': issues
165
+ }
166
+
167
+ if issues:
168
+ for issue in issues[:3]:
169
+ self.result['warnings'].append(
170
+ f"命名問題: {issue['file']} - {issue['issue']}"
171
+ )
172
+
173
+ def check_emoji_usage(self):
174
+ """檢查程式碼中的 Emoji 使用(應改用 icon font)"""
175
+ # 精確的 emoji Unicode 範圍(避免誤判中文字)
176
+ emoji_pattern = re.compile(
177
+ "["
178
+ "\U0001F300-\U0001F5FF" # 雜項符號和象形文字
179
+ "\U0001F600-\U0001F64F" # 表情符號
180
+ "\U0001F680-\U0001F6FF" # 交通和地圖符號
181
+ "\U0001F900-\U0001F9FF" # 補充符號和象形文字
182
+ "\U0001FA00-\U0001FA6F" # 棋類符號
183
+ "\U0001FA70-\U0001FAFF" # 符號和象形文字擴展-A
184
+ "\U00002600-\U000026FF" # 雜項符號
185
+ "\U00002700-\U000027BF" # 裝飾符號
186
+ "]+",
187
+ flags=re.UNICODE
188
+ )
189
+
190
+ issues = []
191
+
192
+ for file_path in self._get_source_files():
193
+ # 檢查 JS/TS/HTML 檔案(不檢查 CSS)
194
+ if file_path.suffix not in ['.js', '.ts', '.jsx', '.tsx', '.html']:
195
+ continue
196
+
197
+ try:
198
+ content = file_path.read_text(encoding='utf-8')
199
+ matches = emoji_pattern.findall(content)
200
+
201
+ if matches:
202
+ unique_emojis = list(set(matches))[:5]
203
+ issues.append({
204
+ 'file': str(file_path.relative_to(self.project_path)),
205
+ 'emojis': unique_emojis,
206
+ 'count': len(matches)
207
+ })
208
+ except Exception:
209
+ pass
210
+
211
+ self.result['checks']['emoji_usage'] = {
212
+ 'count': sum(i['count'] for i in issues) if issues else 0,
213
+ 'files': issues
214
+ }
215
+
216
+ if issues:
217
+ total = sum(i['count'] for i in issues)
218
+ self.result['warnings'].append(
219
+ f"程式碼中使用 Emoji: {total} 個(建議改用 icon font)"
220
+ )
221
+ for issue in issues[:3]:
222
+ self.result['warnings'].append(
223
+ f" {issue['file']}: {''.join(issue['emojis'])}"
224
+ )
225
+
226
+ def _parse_scanignore(self):
227
+ """解析 .scanignore 檔案"""
228
+ scanignore_file = self.project_path / '.scanignore'
229
+ result = {'paths': [], 'pattern_paths': []}
230
+
231
+ if not scanignore_file.exists():
232
+ return result
233
+
234
+ try:
235
+ for line in scanignore_file.read_text(encoding='utf-8').splitlines():
236
+ line = line.strip()
237
+ if not line or line.startswith('#'):
238
+ continue
239
+
240
+ pattern_match = re.match(r'^\[pattern:(.+?)\]\s+(.+)$', line)
241
+ if pattern_match:
242
+ pattern_name = pattern_match.group(1).strip()
243
+ path_glob = pattern_match.group(2).strip()
244
+ result['pattern_paths'].append((pattern_name, path_glob))
245
+ else:
246
+ result['paths'].append(line)
247
+ except Exception:
248
+ pass
249
+
250
+ return result
251
+
252
+ def _is_scanignored(self, file_path):
253
+ """檢查檔案是否被 .scanignore 全排除"""
254
+ rel_path = str(file_path.relative_to(self.project_path))
255
+ for pattern in self.scanignore['paths']:
256
+ # 目錄排除(以 / 結尾)
257
+ if pattern.endswith('/'):
258
+ if rel_path.startswith(pattern) or f'/{pattern}' in f'/{rel_path}':
259
+ return True
260
+ # 精確檔名比對
261
+ elif '/' in pattern:
262
+ if rel_path == pattern:
263
+ return True
264
+ else:
265
+ # 只比對檔名部分
266
+ if file_path.name == pattern:
267
+ return True
268
+ return False
269
+
270
+ def _is_pattern_ignored(self, rel_path, pattern_name):
271
+ """檢查檔案是否被 .scanignore 的特定 pattern 排除"""
272
+ for p_name, p_glob in self.scanignore['pattern_paths']:
273
+ if p_name == pattern_name:
274
+ if fnmatch(rel_path, p_glob):
275
+ return True
276
+ if rel_path == p_glob:
277
+ return True
278
+ return False
279
+
280
+ def _get_source_files(self):
281
+ """取得所有原始碼檔案"""
282
+ extensions = ['*.js', '*.ts', '*.jsx', '*.tsx', '*.py', '*.css', '*.scss', '*.html']
283
+ files = []
284
+
285
+ for ext in extensions:
286
+ for f in self.project_path.rglob(ext):
287
+ if any(ignore in str(f) for ignore in self.IGNORE_DIRS):
288
+ continue
289
+ if self._is_scanignored(f):
290
+ continue
291
+ files.append(f)
292
+
293
+ return files
294
+
295
+ def _is_valid_js_filename(self, name):
296
+ """檢查 JS 檔名是否有效"""
297
+ # 允許設定檔名(含點號)
298
+ config_patterns = [
299
+ 'vite.config', 'tailwind.config', 'postcss.config',
300
+ 'eslint.config', 'prettier.config', 'jest.config',
301
+ 'webpack.config', 'rollup.config', 'tsconfig',
302
+ 'karma.conf', 'playwright.config', 'vitest.config',
303
+ 'babel.config', 'nuxt.config', 'next.config',
304
+ 'cypress.config', 'angular.json'
305
+ ]
306
+ if any(name.startswith(p) for p in config_patterns):
307
+ return True
308
+
309
+ # kebab-case 含 dot-separated 後綴
310
+ # 如 xxx.component, xxx.service.spec, xxx.e2e 等
311
+ if re.match(r'^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)*$', name):
312
+ return True
313
+ # PascalCase
314
+ if re.match(r'^[A-Z][a-zA-Z0-9]*$', name):
315
+ return True
316
+ # camelCase
317
+ if re.match(r'^[a-z][a-zA-Z0-9]*$', name):
318
+ return True
319
+ return False