quantum-framework 0.18.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 (333) hide show
  1. quantum/__init__.py +1 -0
  2. quantum/cli/__init__.py +2 -0
  3. quantum/cli/admin.py +131 -0
  4. quantum/cli/check.py +223 -0
  5. quantum/cli/commands/__init__.py +15 -0
  6. quantum/cli/commands/build.py +414 -0
  7. quantum/cli/commands/dev.py +185 -0
  8. quantum/cli/commands/docs.py +329 -0
  9. quantum/cli/commands/lint.py +523 -0
  10. quantum/cli/commands/migrate.py +548 -0
  11. quantum/cli/commands/new.py +622 -0
  12. quantum/cli/commands/serve.py +190 -0
  13. quantum/cli/commands/test.py +193 -0
  14. quantum/cli/deploy.py +810 -0
  15. quantum/cli/hot_reload.py +951 -0
  16. quantum/cli/jobs.py +356 -0
  17. quantum/cli/mq.py +582 -0
  18. quantum/cli/pkg.py +390 -0
  19. quantum/cli/runner.py +565 -0
  20. quantum/cli/server_process.py +159 -0
  21. quantum/cli/utils.py +334 -0
  22. quantum/compiler/__init__.py +30 -0
  23. quantum/compiler/base_generator.py +367 -0
  24. quantum/compiler/cli.py +295 -0
  25. quantum/compiler/expression_transformer.py +444 -0
  26. quantum/compiler/javascript/__init__.py +10 -0
  27. quantum/compiler/javascript/generator.py +659 -0
  28. quantum/compiler/optimizer.py +270 -0
  29. quantum/compiler/python/__init__.py +10 -0
  30. quantum/compiler/python/generator.py +883 -0
  31. quantum/compiler/python/runtime.py +863 -0
  32. quantum/compiler/transpiler.py +330 -0
  33. quantum/core/__init__.py +3 -0
  34. quantum/core/ast_nodes.py +2619 -0
  35. quantum/core/config_env.py +54 -0
  36. quantum/core/docs_links.py +8 -0
  37. quantum/core/expression_diagnostics.py +87 -0
  38. quantum/core/expression_stdlib.py +208 -0
  39. quantum/core/expressions.py +677 -0
  40. quantum/core/features/agents/src/__init__.py +30 -0
  41. quantum/core/features/agents/src/ast_node.py +540 -0
  42. quantum/core/features/conditionals/src/__init__.py +8 -0
  43. quantum/core/features/conditionals/src/ast_node.py +68 -0
  44. quantum/core/features/data_import/src/__init__.py +0 -0
  45. quantum/core/features/data_import/src/ast_node.py +292 -0
  46. quantum/core/features/data_import/src/runtime.py +533 -0
  47. quantum/core/features/dump/src/__init__.py +12 -0
  48. quantum/core/features/dump/src/ast_node.py +106 -0
  49. quantum/core/features/dump/src/parser.py +61 -0
  50. quantum/core/features/dump/src/runtime.py +246 -0
  51. quantum/core/features/functions/src/__init__.py +8 -0
  52. quantum/core/features/functions/src/ast_node.py +149 -0
  53. quantum/core/features/game_engine_2d/src/__init__.py +19 -0
  54. quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
  55. quantum/core/features/game_engine_2d/src/parser.py +983 -0
  56. quantum/core/features/invocation/src/__init__.py +0 -0
  57. quantum/core/features/invocation/src/ast_node.py +147 -0
  58. quantum/core/features/invocation/src/runtime.py +332 -0
  59. quantum/core/features/knowledge_base/src/__init__.py +6 -0
  60. quantum/core/features/knowledge_base/src/ast_node.py +113 -0
  61. quantum/core/features/knowledge_base/src/parser.py +82 -0
  62. quantum/core/features/logging/src/__init__.py +12 -0
  63. quantum/core/features/logging/src/ast_node.py +111 -0
  64. quantum/core/features/logging/src/parser.py +50 -0
  65. quantum/core/features/logging/src/runtime.py +190 -0
  66. quantum/core/features/loops/src/__init__.py +8 -0
  67. quantum/core/features/loops/src/ast_node.py +60 -0
  68. quantum/core/features/query/src/__init__.py +0 -0
  69. quantum/core/features/query/src/query_validators.py +20 -0
  70. quantum/core/features/state_management/src/__init__.py +11 -0
  71. quantum/core/features/state_management/src/ast_node.py +121 -0
  72. quantum/core/features/terminal_engine/src/__init__.py +21 -0
  73. quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
  74. quantum/core/features/terminal_engine/src/parser.py +361 -0
  75. quantum/core/features/testing_engine/src/__init__.py +41 -0
  76. quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
  77. quantum/core/features/testing_engine/src/parser.py +604 -0
  78. quantum/core/features/theming/src/__init__.py +48 -0
  79. quantum/core/features/theming/src/ast_node.py +137 -0
  80. quantum/core/features/theming/src/presets.py +405 -0
  81. quantum/core/features/ui_engine/src/__init__.py +20 -0
  82. quantum/core/features/ui_engine/src/ast_nodes.py +1913 -0
  83. quantum/core/features/ui_engine/src/parser.py +1192 -0
  84. quantum/core/features/websocket/src/__init__.py +24 -0
  85. quantum/core/features/websocket/src/ast_node.py +247 -0
  86. quantum/core/guards.py +53 -0
  87. quantum/core/html_compat.py +299 -0
  88. quantum/core/parser.py +1551 -0
  89. quantum/core/parser_registry.py +213 -0
  90. quantum/core/parsers/__init__.py +76 -0
  91. quantum/core/parsers/ai/__init__.py +12 -0
  92. quantum/core/parsers/ai/agent_parser.py +106 -0
  93. quantum/core/parsers/ai/knowledge_parser.py +86 -0
  94. quantum/core/parsers/ai/llm_parser.py +78 -0
  95. quantum/core/parsers/ai/team_parser.py +88 -0
  96. quantum/core/parsers/base.py +322 -0
  97. quantum/core/parsers/composition/__init__.py +10 -0
  98. quantum/core/parsers/composition/import_parser.py +50 -0
  99. quantum/core/parsers/composition/slot_parser.py +48 -0
  100. quantum/core/parsers/control_flow/__init__.py +11 -0
  101. quantum/core/parsers/control_flow/if_parser.py +71 -0
  102. quantum/core/parsers/control_flow/loop_parser.py +105 -0
  103. quantum/core/parsers/control_flow/set_parser.py +93 -0
  104. quantum/core/parsers/data/__init__.py +12 -0
  105. quantum/core/parsers/data/data_parser.py +229 -0
  106. quantum/core/parsers/data/invoke_parser.py +133 -0
  107. quantum/core/parsers/data/query_parser.py +213 -0
  108. quantum/core/parsers/data/transaction_parser.py +86 -0
  109. quantum/core/parsers/events/__init__.py +9 -0
  110. quantum/core/parsers/events/dispatch_event_parser.py +44 -0
  111. quantum/core/parsers/forms/__init__.py +11 -0
  112. quantum/core/parsers/forms/action_parser.py +79 -0
  113. quantum/core/parsers/forms/flash_parser.py +34 -0
  114. quantum/core/parsers/forms/redirect_parser.py +33 -0
  115. quantum/core/parsers/functions/__init__.py +11 -0
  116. quantum/core/parsers/functions/function_parser.py +98 -0
  117. quantum/core/parsers/functions/param_parser.py +73 -0
  118. quantum/core/parsers/functions/return_parser.py +31 -0
  119. quantum/core/parsers/html/__init__.py +10 -0
  120. quantum/core/parsers/html/component_call_parser.py +130 -0
  121. quantum/core/parsers/html/html_parser.py +115 -0
  122. quantum/core/parsers/jobs/__init__.py +11 -0
  123. quantum/core/parsers/jobs/job_parser.py +71 -0
  124. quantum/core/parsers/jobs/schedule_parser.py +62 -0
  125. quantum/core/parsers/jobs/thread_parser.py +57 -0
  126. quantum/core/parsers/messaging/__init__.py +17 -0
  127. quantum/core/parsers/messaging/message_ack_parser.py +30 -0
  128. quantum/core/parsers/messaging/message_nack_parser.py +30 -0
  129. quantum/core/parsers/messaging/message_parser.py +114 -0
  130. quantum/core/parsers/messaging/queue_parser.py +61 -0
  131. quantum/core/parsers/messaging/websocket_parser.py +121 -0
  132. quantum/core/parsers/routing/__init__.py +9 -0
  133. quantum/core/parsers/routing/route_parser.py +41 -0
  134. quantum/core/parsers/scripting/__init__.py +12 -0
  135. quantum/core/parsers/scripting/pyclass_parser.py +62 -0
  136. quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
  137. quantum/core/parsers/scripting/pyimport_parser.py +52 -0
  138. quantum/core/parsers/scripting/python_parser.py +49 -0
  139. quantum/core/parsers/services/__init__.py +12 -0
  140. quantum/core/parsers/services/dump_parser.py +52 -0
  141. quantum/core/parsers/services/file_parser.py +48 -0
  142. quantum/core/parsers/services/log_parser.py +46 -0
  143. quantum/core/parsers/services/mail_parser.py +65 -0
  144. quantum/core/tiers.py +164 -0
  145. quantum/core/xml_lines.py +81 -0
  146. quantum/packages/__init__.py +28 -0
  147. quantum/packages/manager.py +413 -0
  148. quantum/packages/manifest.py +351 -0
  149. quantum/packages/registry.py +399 -0
  150. quantum/packages/resolver.py +336 -0
  151. quantum/plugins/__init__.py +33 -0
  152. quantum/plugins/hooks.py +329 -0
  153. quantum/plugins/loader.py +479 -0
  154. quantum/plugins/manifest.py +336 -0
  155. quantum/plugins/registry.py +371 -0
  156. quantum/runtime/__init__.py +25 -0
  157. quantum/runtime/action_handler.py +532 -0
  158. quantum/runtime/adapters/__init__.py +88 -0
  159. quantum/runtime/adapters/memory_adapter.py +690 -0
  160. quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
  161. quantum/runtime/adapters/redis_adapter.py +582 -0
  162. quantum/runtime/adapters/sqlite_adapter.py +414 -0
  163. quantum/runtime/agent_service.py +1133 -0
  164. quantum/runtime/ast_cache.py +506 -0
  165. quantum/runtime/auth_service.py +267 -0
  166. quantum/runtime/component.py +1048 -0
  167. quantum/runtime/component_composer.py +133 -0
  168. quantum/runtime/component_resolver.py +174 -0
  169. quantum/runtime/conditions.py +54 -0
  170. quantum/runtime/database_service.py +595 -0
  171. quantum/runtime/db_schema.py +100 -0
  172. quantum/runtime/dev_panel.py +178 -0
  173. quantum/runtime/email_service.py +162 -0
  174. quantum/runtime/error_handler.py +295 -0
  175. quantum/runtime/execution_context.py +283 -0
  176. quantum/runtime/executor_registry.py +178 -0
  177. quantum/runtime/executors/__init__.py +71 -0
  178. quantum/runtime/executors/ai/__init__.py +12 -0
  179. quantum/runtime/executors/ai/agent_executor.py +217 -0
  180. quantum/runtime/executors/ai/knowledge_executor.py +114 -0
  181. quantum/runtime/executors/ai/llm_executor.py +153 -0
  182. quantum/runtime/executors/ai/team_executor.py +171 -0
  183. quantum/runtime/executors/base.py +264 -0
  184. quantum/runtime/executors/control_flow/__init__.py +12 -0
  185. quantum/runtime/executors/control_flow/if_executor.py +98 -0
  186. quantum/runtime/executors/control_flow/loop_executor.py +308 -0
  187. quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
  188. quantum/runtime/executors/control_flow/set_executor.py +464 -0
  189. quantum/runtime/executors/data/__init__.py +12 -0
  190. quantum/runtime/executors/data/data_executor.py +156 -0
  191. quantum/runtime/executors/data/invoke_executor.py +228 -0
  192. quantum/runtime/executors/data/query_executor.py +290 -0
  193. quantum/runtime/executors/data/transaction_executor.py +91 -0
  194. quantum/runtime/executors/jobs/__init__.py +11 -0
  195. quantum/runtime/executors/jobs/job_executor.py +190 -0
  196. quantum/runtime/executors/jobs/schedule_executor.py +132 -0
  197. quantum/runtime/executors/jobs/thread_executor.py +127 -0
  198. quantum/runtime/executors/messaging/__init__.py +17 -0
  199. quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
  200. quantum/runtime/executors/messaging/message_executor.py +174 -0
  201. quantum/runtime/executors/messaging/queue_executor.py +103 -0
  202. quantum/runtime/executors/messaging/websocket_executor.py +197 -0
  203. quantum/runtime/executors/scripting/__init__.py +11 -0
  204. quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
  205. quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
  206. quantum/runtime/executors/scripting/python_executor.py +249 -0
  207. quantum/runtime/executors/services/__init__.py +12 -0
  208. quantum/runtime/executors/services/dump_executor.py +72 -0
  209. quantum/runtime/executors/services/file_executor.py +89 -0
  210. quantum/runtime/executors/services/log_executor.py +77 -0
  211. quantum/runtime/executors/services/mail_executor.py +81 -0
  212. quantum/runtime/expression_cache.py +498 -0
  213. quantum/runtime/file_upload_service.py +326 -0
  214. quantum/runtime/function_registry.py +118 -0
  215. quantum/runtime/game_builder.py +166 -0
  216. quantum/runtime/game_code_generator.py +2371 -0
  217. quantum/runtime/game_templates.py +2006 -0
  218. quantum/runtime/godot_code_generator.py +4681 -0
  219. quantum/runtime/godot_templates.py +1449 -0
  220. quantum/runtime/job_executor.py +1599 -0
  221. quantum/runtime/knowledge_service.py +546 -0
  222. quantum/runtime/llm_cache.py +100 -0
  223. quantum/runtime/llm_providers.py +704 -0
  224. quantum/runtime/llm_service.py +287 -0
  225. quantum/runtime/local_server.py +28 -0
  226. quantum/runtime/logging_setup.py +141 -0
  227. quantum/runtime/message_broker.py +364 -0
  228. quantum/runtime/message_queue_service.py +571 -0
  229. quantum/runtime/param_validation.py +218 -0
  230. quantum/runtime/pypy_compat.py +317 -0
  231. quantum/runtime/python_bridge.py +698 -0
  232. quantum/runtime/query_validators.py +304 -0
  233. quantum/runtime/renderer.py +734 -0
  234. quantum/runtime/service_container.py +470 -0
  235. quantum/runtime/table_params.py +163 -0
  236. quantum/runtime/terminal_builder.py +76 -0
  237. quantum/runtime/terminal_code_generator.py +607 -0
  238. quantum/runtime/terminal_templates.py +243 -0
  239. quantum/runtime/testing_builder.py +77 -0
  240. quantum/runtime/testing_code_generator.py +833 -0
  241. quantum/runtime/testing_templates.py +85 -0
  242. quantum/runtime/ui_builder.py +222 -0
  243. quantum/runtime/ui_console.py +405 -0
  244. quantum/runtime/ui_desktop.py +56 -0
  245. quantum/runtime/ui_form_rules.py +193 -0
  246. quantum/runtime/ui_html_adapter.py +2567 -0
  247. quantum/runtime/ui_html_templates.py +2106 -0
  248. quantum/runtime/ui_mobile_adapter.py +1832 -0
  249. quantum/runtime/ui_mobile_templates.py +1003 -0
  250. quantum/runtime/ui_page_renderer.py +392 -0
  251. quantum/runtime/ui_pager.py +73 -0
  252. quantum/runtime/ui_table_edit.py +139 -0
  253. quantum/runtime/ui_textual_adapter.py +1866 -0
  254. quantum/runtime/ui_textual_templates.py +45 -0
  255. quantum/runtime/ui_tokens.py +465 -0
  256. quantum/runtime/ui_validator.py +365 -0
  257. quantum/runtime/ui_view.py +221 -0
  258. quantum/runtime/validators.py +256 -0
  259. quantum/runtime/web_server.py +2131 -0
  260. quantum/runtime/websocket_adapter.py +501 -0
  261. quantum/runtime/websocket_service.py +585 -0
  262. quantum/runtime/websocket_transport.py +289 -0
  263. quantum/runtime/wsgi.py +101 -0
  264. quantum/services.py +109 -0
  265. quantum/utils/__init__.py +1 -0
  266. quantum_admin/__init__.py +4 -0
  267. quantum_admin/assets/quantum-admin-connectors.js +123 -0
  268. quantum_admin/assets/quantum-admin.css +2058 -0
  269. quantum_admin/assets/quantum-source-highlight.js +106 -0
  270. quantum_admin/backend/__init__.py +4 -0
  271. quantum_admin/backend/audit_service.py +346 -0
  272. quantum_admin/backend/auth_service.py +394 -0
  273. quantum_admin/backend/component_discovery.py +766 -0
  274. quantum_admin/backend/config_generator.py +586 -0
  275. quantum_admin/backend/connector_service.py +1016 -0
  276. quantum_admin/backend/crud.py +557 -0
  277. quantum_admin/backend/database.py +226 -0
  278. quantum_admin/backend/db_setup_service.py +193 -0
  279. quantum_admin/backend/deploy_service.py +698 -0
  280. quantum_admin/backend/docker_service.py +422 -0
  281. quantum_admin/backend/environment_service.py +418 -0
  282. quantum_admin/backend/git_service.py +370 -0
  283. quantum_admin/backend/health_service.py +390 -0
  284. quantum_admin/backend/job_service.py +347 -0
  285. quantum_admin/backend/log_service.py +331 -0
  286. quantum_admin/backend/main.py +14426 -0
  287. quantum_admin/backend/models.py +986 -0
  288. quantum_admin/backend/pipeline_service.py +812 -0
  289. quantum_admin/backend/resource_manager.py +1118 -0
  290. quantum_admin/backend/schemas.py +419 -0
  291. quantum_admin/backend/secret_manager.py +277 -0
  292. quantum_admin/backend/settings_service.py +488 -0
  293. quantum_admin/backend/template_service.py +710 -0
  294. quantum_admin/backend/test_execution_service.py +364 -0
  295. quantum_admin/backend/test_generator.py +782 -0
  296. quantum_admin/backend/webhook_service.py +454 -0
  297. quantum_admin/backend/websocket_manager.py +360 -0
  298. quantum_admin/components/admin/_layout/AdminShell.q +121 -0
  299. quantum_admin/components/admin/agents.q +107 -0
  300. quantum_admin/components/admin/app/[name].q +558 -0
  301. quantum_admin/components/admin/applications.q +160 -0
  302. quantum_admin/components/admin/component/[...path].q +205 -0
  303. quantum_admin/components/admin/components.q +59 -0
  304. quantum_admin/components/admin/connectors.q +283 -0
  305. quantum_admin/components/admin/dashboard.q +86 -0
  306. quantum_admin/components/admin/database.q +95 -0
  307. quantum_admin/components/admin/features.q +69 -0
  308. quantum_admin/components/admin/index.q +12 -0
  309. quantum_admin/components/admin/jobs.q +75 -0
  310. quantum_admin/components/admin/login.q +65 -0
  311. quantum_admin/components/admin/logout.q +11 -0
  312. quantum_admin/components/admin/settings.q +174 -0
  313. quantum_admin/components/admin/source.q +45 -0
  314. quantum_admin/components/admin/tests.q +73 -0
  315. quantum_admin/run.py +13 -0
  316. quantum_admin/services/__init__.py +16 -0
  317. quantum_admin/services/_base.py +84 -0
  318. quantum_admin/services/_component_test_generator.py +205 -0
  319. quantum_admin/services/_yaml_editor.py +82 -0
  320. quantum_admin/services/apps.py +330 -0
  321. quantum_admin/services/auth.py +61 -0
  322. quantum_admin/services/components.py +210 -0
  323. quantum_admin/services/connectors.py +164 -0
  324. quantum_admin/services/projects.py +189 -0
  325. quantum_admin/services/repository.py +223 -0
  326. quantum_admin/services/settings.py +150 -0
  327. quantum_admin/services/yaml_import.py +118 -0
  328. quantum_framework-0.18.0.dist-info/METADATA +281 -0
  329. quantum_framework-0.18.0.dist-info/RECORD +333 -0
  330. quantum_framework-0.18.0.dist-info/WHEEL +5 -0
  331. quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
  332. quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
  333. quantum_framework-0.18.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,523 @@
1
+ """
2
+ Quantum CLI - Lint Command
3
+
4
+ Lint and validate .q files.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Optional, List, Dict, Any
11
+ from dataclasses import dataclass
12
+ from enum import Enum
13
+
14
+ import click
15
+
16
+ from quantum.cli.utils import get_console, find_project_root, find_q_files
17
+
18
+
19
+ class Severity(Enum):
20
+ """Lint issue severity levels."""
21
+ ERROR = 'error'
22
+ WARNING = 'warning'
23
+ INFO = 'info'
24
+
25
+
26
+ @dataclass
27
+ class LintIssue:
28
+ """A linting issue found in a file."""
29
+ file: Path
30
+ line: int
31
+ column: int
32
+ severity: Severity
33
+ code: str
34
+ message: str
35
+ suggestion: Optional[str] = None
36
+
37
+
38
+ class QuantumLinter:
39
+ """Linter for Quantum .q files."""
40
+
41
+ def __init__(self, debug: bool = False):
42
+ self.debug = debug
43
+ self.issues: List[LintIssue] = []
44
+
45
+ def lint_file(self, file_path: Path) -> List[LintIssue]:
46
+ """Lint a single .q file."""
47
+ issues = []
48
+
49
+ try:
50
+ content = file_path.read_text(encoding='utf-8')
51
+ lines = content.splitlines()
52
+
53
+ # Run lint checks
54
+ issues.extend(self._check_xml_structure(file_path, content, lines))
55
+ issues.extend(self._check_quantum_conventions(file_path, content, lines))
56
+ issues.extend(self._check_databinding(file_path, content, lines))
57
+ issues.extend(self._check_component_structure(file_path, content, lines))
58
+ issues.extend(self._check_accessibility(file_path, content, lines))
59
+
60
+ # Try parsing with Quantum parser
61
+ issues.extend(self._check_parse_errors(file_path, content))
62
+
63
+ except Exception as e:
64
+ issues.append(LintIssue(
65
+ file=file_path,
66
+ line=0,
67
+ column=0,
68
+ severity=Severity.ERROR,
69
+ code='E001',
70
+ message=f"Failed to read file: {e}"
71
+ ))
72
+
73
+ return issues
74
+
75
+ def lint_directory(self, directory: Path, recursive: bool = True) -> List[LintIssue]:
76
+ """Lint all .q files in a directory."""
77
+ q_files = find_q_files(directory, recursive=recursive)
78
+ all_issues = []
79
+
80
+ for q_file in q_files:
81
+ issues = self.lint_file(q_file)
82
+ all_issues.extend(issues)
83
+
84
+ return all_issues
85
+
86
+ def _check_xml_structure(self, file_path: Path, content: str, lines: List[str]) -> List[LintIssue]:
87
+ """Check XML structure and syntax."""
88
+ issues = []
89
+ import re
90
+
91
+ # Check for unclosed tags
92
+ tag_pattern = re.compile(r'<(\/?)(q:)?(\w+)([^>]*?)(/?)>')
93
+ open_tags = []
94
+
95
+ for i, line in enumerate(lines, 1):
96
+ for match in tag_pattern.finditer(line):
97
+ is_closing = match.group(1) == '/'
98
+ is_self_closing = match.group(5) == '/'
99
+ tag_name = match.group(3)
100
+
101
+ if is_closing:
102
+ if open_tags and open_tags[-1] == tag_name:
103
+ open_tags.pop()
104
+ else:
105
+ issues.append(LintIssue(
106
+ file=file_path,
107
+ line=i,
108
+ column=match.start(),
109
+ severity=Severity.ERROR,
110
+ code='E010',
111
+ message=f"Unexpected closing tag </{tag_name}>"
112
+ ))
113
+ elif not is_self_closing:
114
+ # Skip void elements
115
+ void_elements = ['br', 'hr', 'img', 'input', 'meta', 'link', 'area', 'base', 'col', 'embed', 'param', 'source', 'track', 'wbr']
116
+ if tag_name.lower() not in void_elements:
117
+ open_tags.append(tag_name)
118
+
119
+ # Check for missing DOCTYPE/xml declaration (warning only)
120
+ if lines and not lines[0].strip().startswith('<?xml') and not lines[0].strip().startswith('<!DOCTYPE') and not lines[0].strip().startswith('<!--'):
121
+ # Not an error, just info
122
+ pass
123
+
124
+ return issues
125
+
126
+ def _check_quantum_conventions(self, file_path: Path, content: str, lines: List[str]) -> List[LintIssue]:
127
+ """Check Quantum-specific conventions."""
128
+ issues = []
129
+ import re
130
+
131
+ # Check for component name matching filename
132
+ component_pattern = re.compile(r'<q:component\s+name="([^"]+)"')
133
+ for i, line in enumerate(lines, 1):
134
+ match = component_pattern.search(line)
135
+ if match:
136
+ component_name = match.group(1)
137
+ expected_name = file_path.stem
138
+
139
+ # Component name should match file name (PascalCase)
140
+ if component_name.lower() != expected_name.lower().replace('-', '').replace('_', ''):
141
+ issues.append(LintIssue(
142
+ file=file_path,
143
+ line=i,
144
+ column=match.start(),
145
+ severity=Severity.WARNING,
146
+ code='W001',
147
+ message=f"Component name '{component_name}' does not match filename '{file_path.name}'",
148
+ suggestion=f"Rename component to '{expected_name}' or rename file"
149
+ ))
150
+
151
+ # Check for xmlns declaration
152
+ if 'xmlns:q=' not in content and '<q:' in content:
153
+ issues.append(LintIssue(
154
+ file=file_path,
155
+ line=1,
156
+ column=0,
157
+ severity=Severity.WARNING,
158
+ code='W002',
159
+ message="Missing xmlns:q namespace declaration",
160
+ suggestion='Add xmlns:q="https://quantum.lang/ns" to root element'
161
+ ))
162
+
163
+ # Check for deprecated tags
164
+ deprecated_tags = {
165
+ 'q:cfif': 'q:if',
166
+ 'q:cfset': 'q:set',
167
+ 'q:cfloop': 'q:loop',
168
+ 'q:cfquery': 'q:query',
169
+ }
170
+
171
+ for old_tag, new_tag in deprecated_tags.items():
172
+ for i, line in enumerate(lines, 1):
173
+ if f'<{old_tag}' in line.lower():
174
+ issues.append(LintIssue(
175
+ file=file_path,
176
+ line=i,
177
+ column=line.lower().find(f'<{old_tag}'),
178
+ severity=Severity.WARNING,
179
+ code='W003',
180
+ message=f"Deprecated tag '{old_tag}'",
181
+ suggestion=f"Use '{new_tag}' instead"
182
+ ))
183
+
184
+ return issues
185
+
186
+ def _check_databinding(self, file_path: Path, content: str, lines: List[str]) -> List[LintIssue]:
187
+ """Check databinding syntax."""
188
+ issues = []
189
+ import re
190
+
191
+ # Check for unclosed databinding braces
192
+ for i, line in enumerate(lines, 1):
193
+ # Count { and } not in strings
194
+ open_count = line.count('{')
195
+ close_count = line.count('}')
196
+
197
+ # Simple check - might have false positives for multi-line expressions
198
+ if open_count != close_count:
199
+ # Check if it's a JavaScript object (common false positive)
200
+ if not re.search(r'[=:]\s*\{', line):
201
+ issues.append(LintIssue(
202
+ file=file_path,
203
+ line=i,
204
+ column=0,
205
+ severity=Severity.WARNING,
206
+ code='W010',
207
+ message="Potentially unmatched databinding braces",
208
+ suggestion="Check for balanced { and } in expressions"
209
+ ))
210
+
211
+ # Check for double-braces which might be a mistake
212
+ double_brace_pattern = re.compile(r'\{\{([^}]+)\}\}')
213
+ for i, line in enumerate(lines, 1):
214
+ match = double_brace_pattern.search(line)
215
+ if match:
216
+ # Could be intentional (e.g., template syntax) - just info
217
+ issues.append(LintIssue(
218
+ file=file_path,
219
+ line=i,
220
+ column=match.start(),
221
+ severity=Severity.INFO,
222
+ code='I010',
223
+ message="Double braces {{ }} detected - Quantum uses single braces { }",
224
+ suggestion="Use single braces for databinding: {expression}"
225
+ ))
226
+
227
+ return issues
228
+
229
+ def _check_component_structure(self, file_path: Path, content: str, lines: List[str]) -> List[LintIssue]:
230
+ """Check component structure best practices."""
231
+ issues = []
232
+ import re
233
+
234
+ # Check for multiple root components
235
+ component_count = content.count('<q:component')
236
+ if component_count > 1:
237
+ issues.append(LintIssue(
238
+ file=file_path,
239
+ line=1,
240
+ column=0,
241
+ severity=Severity.WARNING,
242
+ code='W020',
243
+ message=f"File contains {component_count} components - consider splitting",
244
+ suggestion="One component per file is recommended"
245
+ ))
246
+
247
+ # Check for required params without defaults
248
+ param_pattern = re.compile(r'<q:param\s+name="([^"]+)"[^>]*required="true"[^>]*/>')
249
+ for i, line in enumerate(lines, 1):
250
+ match = param_pattern.search(line)
251
+ if match and 'default=' not in line:
252
+ # This is fine, just informational
253
+ pass
254
+
255
+ # Check for unused params (simplified check)
256
+ param_names = []
257
+ param_lines = {}
258
+ for i, line in enumerate(lines, 1):
259
+ match = re.search(r'<q:param\s+name="([^"]+)"', line)
260
+ if match:
261
+ param_names.append(match.group(1))
262
+ param_lines[match.group(1)] = i
263
+
264
+ for param in param_names:
265
+ # Check if param is used in databinding
266
+ usage_pattern = re.compile(r'\{[^}]*\b' + re.escape(param) + r'\b[^}]*\}')
267
+ if not usage_pattern.search(content):
268
+ issues.append(LintIssue(
269
+ file=file_path,
270
+ line=param_lines[param],
271
+ column=0,
272
+ severity=Severity.INFO,
273
+ code='I020',
274
+ message=f"Parameter '{param}' may be unused",
275
+ suggestion="Remove unused parameters or verify usage"
276
+ ))
277
+
278
+ return issues
279
+
280
+ def _check_accessibility(self, file_path: Path, content: str, lines: List[str]) -> List[LintIssue]:
281
+ """Check accessibility best practices."""
282
+ issues = []
283
+ import re
284
+
285
+ # Check for images without alt
286
+ img_pattern = re.compile(r'<img\s+[^>]*>')
287
+ for i, line in enumerate(lines, 1):
288
+ for match in img_pattern.finditer(line):
289
+ img_tag = match.group()
290
+ if 'alt=' not in img_tag:
291
+ issues.append(LintIssue(
292
+ file=file_path,
293
+ line=i,
294
+ column=match.start(),
295
+ severity=Severity.WARNING,
296
+ code='A001',
297
+ message="Image missing alt attribute",
298
+ suggestion='Add alt="description" for accessibility'
299
+ ))
300
+
301
+ # Check for buttons without type
302
+ button_pattern = re.compile(r'<button\s+[^>]*>')
303
+ for i, line in enumerate(lines, 1):
304
+ for match in button_pattern.finditer(line):
305
+ button_tag = match.group()
306
+ if 'type=' not in button_tag:
307
+ issues.append(LintIssue(
308
+ file=file_path,
309
+ line=i,
310
+ column=match.start(),
311
+ severity=Severity.INFO,
312
+ code='A002',
313
+ message="Button missing type attribute",
314
+ suggestion='Add type="button" or type="submit"'
315
+ ))
316
+
317
+ # Check for form inputs without labels
318
+ input_pattern = re.compile(r'<input\s+[^>]*id="([^"]+)"[^>]*>')
319
+ for i, line in enumerate(lines, 1):
320
+ for match in input_pattern.finditer(line):
321
+ input_id = match.group(1)
322
+ label_pattern = re.compile(rf'<label\s+[^>]*for="{input_id}"')
323
+ if not label_pattern.search(content):
324
+ issues.append(LintIssue(
325
+ file=file_path,
326
+ line=i,
327
+ column=match.start(),
328
+ severity=Severity.INFO,
329
+ code='A003',
330
+ message=f"Input '{input_id}' may be missing a label",
331
+ suggestion=f'Add <label for="{input_id}">Label</label>'
332
+ ))
333
+
334
+ return issues
335
+
336
+ def _check_parse_errors(self, file_path: Path, content: str) -> List[LintIssue]:
337
+ """Check for parser errors."""
338
+ issues = []
339
+
340
+ try:
341
+ from quantum.core.parser import QuantumParser, QuantumParseError
342
+
343
+ parser = QuantumParser()
344
+ parser.parse(content)
345
+
346
+ except QuantumParseError as e:
347
+ issues.append(LintIssue(
348
+ file=file_path,
349
+ line=getattr(e, 'line', 0) or 0,
350
+ column=getattr(e, 'column', 0) or 0,
351
+ severity=Severity.ERROR,
352
+ code='E100',
353
+ message=f"Parse error: {e}"
354
+ ))
355
+ except Exception as e:
356
+ # Other parsing errors
357
+ issues.append(LintIssue(
358
+ file=file_path,
359
+ line=0,
360
+ column=0,
361
+ severity=Severity.ERROR,
362
+ code='E101',
363
+ message=f"Failed to parse: {e}"
364
+ ))
365
+
366
+ return issues
367
+
368
+
369
+ @click.command('lint')
370
+ @click.argument('path', required=False, type=click.Path())
371
+ @click.option('--fix', is_flag=True, help='Attempt to fix issues automatically')
372
+ @click.option('--format', '-f', type=click.Choice(['text', 'json', 'github']), default='text',
373
+ help='Output format')
374
+ @click.option('--severity', '-s', type=click.Choice(['error', 'warning', 'info']), default='warning',
375
+ help='Minimum severity to report')
376
+ @click.option('--ignore', '-i', type=str, multiple=True, help='Issue codes to ignore')
377
+ @click.option('--quiet', '-q', is_flag=True, help='Quiet mode - only show errors')
378
+ def lint(
379
+ path: Optional[str],
380
+ fix: bool,
381
+ format: str,
382
+ severity: str,
383
+ ignore: tuple,
384
+ quiet: bool
385
+ ):
386
+ """Lint and validate .q files.
387
+
388
+ Checks for syntax errors, best practices, and accessibility issues.
389
+
390
+ Examples:
391
+
392
+ quantum lint
393
+
394
+ quantum lint ./components
395
+
396
+ quantum lint --format json
397
+
398
+ quantum lint --severity error
399
+
400
+ quantum lint --ignore W001 --ignore W002
401
+ """
402
+ console = get_console(quiet=quiet)
403
+
404
+ # Find project root
405
+ project_root = find_project_root()
406
+ if not project_root:
407
+ project_root = Path.cwd()
408
+
409
+ # Determine lint path
410
+ if path:
411
+ lint_path = Path(path)
412
+ if not lint_path.is_absolute():
413
+ lint_path = project_root / path
414
+ else:
415
+ lint_path = project_root
416
+
417
+ if not lint_path.exists():
418
+ console.error(f"Path not found: {lint_path}")
419
+ raise click.Abort()
420
+
421
+ console.header(
422
+ "Quantum Linter",
423
+ f"Checking: {lint_path}"
424
+ )
425
+
426
+ # Run linter
427
+ linter = QuantumLinter(debug=False)
428
+
429
+ if lint_path.is_file():
430
+ issues = linter.lint_file(lint_path)
431
+ else:
432
+ issues = linter.lint_directory(lint_path)
433
+
434
+ # Filter by severity
435
+ severity_order = {'error': 0, 'warning': 1, 'info': 2}
436
+ min_severity = severity_order[severity]
437
+ issues = [i for i in issues if severity_order[i.severity.value] <= min_severity]
438
+
439
+ # Filter by ignore codes
440
+ if ignore:
441
+ issues = [i for i in issues if i.code not in ignore]
442
+
443
+ # Sort issues
444
+ issues.sort(key=lambda i: (str(i.file), i.line, severity_order[i.severity.value]))
445
+
446
+ # Output results
447
+ if format == 'json':
448
+ _output_json(issues)
449
+ elif format == 'github':
450
+ _output_github(issues)
451
+ else:
452
+ _output_text(issues, console, project_root)
453
+
454
+ # Summary
455
+ error_count = sum(1 for i in issues if i.severity == Severity.ERROR)
456
+ warning_count = sum(1 for i in issues if i.severity == Severity.WARNING)
457
+ info_count = sum(1 for i in issues if i.severity == Severity.INFO)
458
+
459
+ console.print()
460
+ if error_count > 0:
461
+ console.error(f"Found {error_count} error(s), {warning_count} warning(s), {info_count} info(s)")
462
+ sys.exit(1)
463
+ elif warning_count > 0:
464
+ console.warning(f"Found {warning_count} warning(s), {info_count} info(s)")
465
+ else:
466
+ console.success(f"No issues found! ({info_count} info messages)")
467
+
468
+
469
+ def _output_text(issues: List[LintIssue], console, project_root: Path):
470
+ """Output issues in text format."""
471
+ current_file = None
472
+
473
+ for issue in issues:
474
+ if issue.file != current_file:
475
+ current_file = issue.file
476
+ rel_path = issue.file.relative_to(project_root) if issue.file.is_relative_to(project_root) else issue.file
477
+ console.print(f"\n[bold]{rel_path}[/bold]")
478
+
479
+ severity_colors = {
480
+ Severity.ERROR: 'red',
481
+ Severity.WARNING: 'yellow',
482
+ Severity.INFO: 'cyan',
483
+ }
484
+ color = severity_colors[issue.severity]
485
+
486
+ location = f"{issue.line}:{issue.column}" if issue.column else str(issue.line)
487
+ console.print(f" [{color}]{issue.severity.value.upper()}[/{color}] [{issue.code}] {location}: {issue.message}")
488
+
489
+ if issue.suggestion:
490
+ console.print(f" [dim]Suggestion: {issue.suggestion}[/dim]")
491
+
492
+
493
+ def _output_json(issues: List[LintIssue]):
494
+ """Output issues in JSON format."""
495
+ import json
496
+
497
+ output = [
498
+ {
499
+ 'file': str(issue.file),
500
+ 'line': issue.line,
501
+ 'column': issue.column,
502
+ 'severity': issue.severity.value,
503
+ 'code': issue.code,
504
+ 'message': issue.message,
505
+ 'suggestion': issue.suggestion,
506
+ }
507
+ for issue in issues
508
+ ]
509
+
510
+ print(json.dumps(output, indent=2))
511
+
512
+
513
+ def _output_github(issues: List[LintIssue]):
514
+ """Output issues in GitHub Actions format."""
515
+ severity_map = {
516
+ Severity.ERROR: 'error',
517
+ Severity.WARNING: 'warning',
518
+ Severity.INFO: 'notice',
519
+ }
520
+
521
+ for issue in issues:
522
+ severity = severity_map[issue.severity]
523
+ print(f"::{severity} file={issue.file},line={issue.line},col={issue.column}::[{issue.code}] {issue.message}")