minecraft-datapack-language 15.4.28__py3-none-any.whl → 15.4.29__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 (27) hide show
  1. minecraft_datapack_language/__init__.py +17 -2
  2. minecraft_datapack_language/_version.py +2 -2
  3. minecraft_datapack_language/ast_nodes.py +87 -59
  4. minecraft_datapack_language/mdl_compiler.py +470 -0
  5. minecraft_datapack_language/mdl_errors.py +14 -0
  6. minecraft_datapack_language/mdl_lexer.py +624 -0
  7. minecraft_datapack_language/mdl_parser.py +573 -0
  8. minecraft_datapack_language-15.4.29.dist-info/METADATA +266 -0
  9. minecraft_datapack_language-15.4.29.dist-info/RECORD +16 -0
  10. minecraft_datapack_language/cli.py +0 -159
  11. minecraft_datapack_language/cli_build.py +0 -1292
  12. minecraft_datapack_language/cli_check.py +0 -155
  13. minecraft_datapack_language/cli_colors.py +0 -264
  14. minecraft_datapack_language/cli_help.py +0 -508
  15. minecraft_datapack_language/cli_new.py +0 -300
  16. minecraft_datapack_language/cli_utils.py +0 -276
  17. minecraft_datapack_language/expression_processor.py +0 -352
  18. minecraft_datapack_language/linter.py +0 -409
  19. minecraft_datapack_language/mdl_lexer_js.py +0 -754
  20. minecraft_datapack_language/mdl_parser_js.py +0 -1049
  21. minecraft_datapack_language/pack.py +0 -758
  22. minecraft_datapack_language-15.4.28.dist-info/METADATA +0 -1274
  23. minecraft_datapack_language-15.4.28.dist-info/RECORD +0 -25
  24. {minecraft_datapack_language-15.4.28.dist-info → minecraft_datapack_language-15.4.29.dist-info}/WHEEL +0 -0
  25. {minecraft_datapack_language-15.4.28.dist-info → minecraft_datapack_language-15.4.29.dist-info}/entry_points.txt +0 -0
  26. {minecraft_datapack_language-15.4.28.dist-info → minecraft_datapack_language-15.4.29.dist-info}/licenses/LICENSE +0 -0
  27. {minecraft_datapack_language-15.4.28.dist-info → minecraft_datapack_language-15.4.29.dist-info}/top_level.txt +0 -0
@@ -1,409 +0,0 @@
1
- """
2
- MCFunction Linter for MDL
3
-
4
- This module provides linting capabilities for generated mcfunction files,
5
- detecting common issues and providing suggestions for improvement.
6
- """
7
-
8
- import re
9
- import os
10
- from typing import List, Dict, Tuple, Optional
11
- from dataclasses import dataclass
12
- from pathlib import Path
13
-
14
-
15
- @dataclass
16
- class LintIssue:
17
- """Represents a linting issue found in an mcfunction file"""
18
- line_number: int
19
- severity: str # 'error', 'warning', 'info'
20
- category: str
21
- message: str
22
- suggestion: Optional[str] = None
23
- command: Optional[str] = None
24
-
25
-
26
- class MCFunctionLinter:
27
- """Linter for mcfunction files with comprehensive rule checking"""
28
-
29
- def __init__(self):
30
- self.issues = []
31
- self.rules = {
32
- 'redundant_operations': self._check_redundant_operations,
33
- 'inefficient_storage': self._check_inefficient_storage,
34
- 'unnecessary_temp_vars': self._check_unnecessary_temp_vars,
35
- 'complex_operations': self._check_complex_operations,
36
- 'potential_errors': self._check_potential_errors,
37
- 'performance_issues': self._check_performance_issues,
38
- 'style_issues': self._check_style_issues
39
- }
40
-
41
- def lint_file(self, file_path: str) -> List[LintIssue]:
42
- """Lint a single mcfunction file"""
43
- self.issues = []
44
-
45
- if not os.path.exists(file_path):
46
- self.issues.append(LintIssue(
47
- line_number=0,
48
- severity='error',
49
- category='file',
50
- message=f"File not found: {file_path}"
51
- ))
52
- return self.issues
53
-
54
- try:
55
- with open(file_path, 'r', encoding='utf-8') as f:
56
- lines = f.readlines()
57
-
58
- for line_num, line in enumerate(lines, 1):
59
- line = line.strip()
60
- if not line or line.startswith('#'):
61
- continue
62
-
63
- # Run all linting rules on this line
64
- for rule_name, rule_func in self.rules.items():
65
- rule_func(line, line_num)
66
-
67
- return self.issues
68
-
69
- except Exception as e:
70
- self.issues.append(LintIssue(
71
- line_number=0,
72
- severity='error',
73
- category='file',
74
- message=f"Error reading file: {str(e)}"
75
- ))
76
- return self.issues
77
-
78
- def lint_directory(self, directory_path: str) -> Dict[str, List[LintIssue]]:
79
- """Lint all mcfunction files in a directory"""
80
- results = {}
81
-
82
- for root, dirs, files in os.walk(directory_path):
83
- for file in files:
84
- if file.endswith('.mcfunction'):
85
- file_path = os.path.join(root, file)
86
- results[file_path] = self.lint_file(file_path)
87
-
88
- return results
89
-
90
- def _check_redundant_operations(self, line: str, line_num: int):
91
- """Check for redundant operations"""
92
- # Redundant scoreboard operations
93
- if re.match(r'scoreboard players operation @s (\w+) = @s \1', line):
94
- self.issues.append(LintIssue(
95
- line_number=line_num,
96
- severity='warning',
97
- category='redundant_operations',
98
- message="Redundant scoreboard operation: variable assigned to itself",
99
- suggestion="Remove this line as it has no effect",
100
- command=line
101
- ))
102
-
103
- # Redundant storage operations
104
- if re.match(r'data modify storage mdl:variables (\w+) set value ""', line):
105
- # Check if next line sets the same variable
106
- self.issues.append(LintIssue(
107
- line_number=line_num,
108
- severity='info',
109
- category='redundant_operations',
110
- message="Empty string initialization may be redundant",
111
- suggestion="Consider removing if immediately followed by assignment",
112
- command=line
113
- ))
114
-
115
- def _check_inefficient_storage(self, line: str, line_num: int):
116
- """Check for inefficient storage operations"""
117
- # Complex temporary storage operations
118
- if 'execute store result storage mdl:temp' in line and 'run data get storage' in line:
119
- self.issues.append(LintIssue(
120
- line_number=line_num,
121
- severity='warning',
122
- category='inefficient_storage',
123
- message="Complex temporary storage operation detected",
124
- suggestion="Consider using direct storage operations where possible",
125
- command=line
126
- ))
127
-
128
- # Multiple append operations that could be combined
129
- if 'data modify storage mdl:variables' in line and 'append value' in line:
130
- # This is just a note, not necessarily an issue
131
- pass
132
-
133
- def _check_unnecessary_temp_vars(self, line: str, line_num: int):
134
- """Check for unnecessary temporary variables"""
135
- # Temporary variables with generic names
136
- if re.search(r'mdl:temp \w+', line):
137
- self.issues.append(LintIssue(
138
- line_number=line_num,
139
- severity='info',
140
- category='unnecessary_temp_vars',
141
- message="Temporary storage variable used",
142
- suggestion="Consider if this temporary variable is necessary",
143
- command=line
144
- ))
145
-
146
- # Generated temporary variables
147
- if re.search(r'left_\d+|right_\d+|concat_\d+', line):
148
- self.issues.append(LintIssue(
149
- line_number=line_num,
150
- severity='warning',
151
- category='unnecessary_temp_vars',
152
- message="Generated temporary variable detected",
153
- suggestion="This may indicate complex expression that could be simplified",
154
- command=line
155
- ))
156
-
157
- def _check_complex_operations(self, line: str, line_num: int):
158
- """Check for overly complex operations"""
159
- # Multi-line operations
160
- if '\n' in line:
161
- self.issues.append(LintIssue(
162
- line_number=line_num,
163
- severity='warning',
164
- category='complex_operations',
165
- message="Multi-line operation detected",
166
- suggestion="Consider breaking into separate commands for clarity",
167
- command=line
168
- ))
169
-
170
- # Complex scoreboard operations
171
- if line.count('scoreboard players') > 1:
172
- self.issues.append(LintIssue(
173
- line_number=line_num,
174
- severity='info',
175
- category='complex_operations',
176
- message="Multiple scoreboard operations in single line",
177
- suggestion="Consider splitting for better readability",
178
- command=line
179
- ))
180
-
181
- def _check_potential_errors(self, line: str, line_num: int):
182
- """Check for potential runtime errors"""
183
- # Missing objective declarations
184
- if line.startswith('scoreboard players') and 'add' in line and 'dummy' not in line:
185
- # Check if this is an operation without objective declaration
186
- if 'operation' in line and not line.startswith('scoreboard objectives add'):
187
- self.issues.append(LintIssue(
188
- line_number=line_num,
189
- severity='error',
190
- category='potential_errors',
191
- message="Scoreboard operation without objective declaration",
192
- suggestion="Ensure objective is declared before use",
193
- command=line
194
- ))
195
-
196
- # Invalid JSON in tellraw
197
- if 'tellraw' in line and '{' in line:
198
- try:
199
- # Basic JSON validation
200
- json_start = line.find('{')
201
- json_end = line.rfind('}') + 1
202
- json_str = line[json_start:json_end]
203
- # Simple bracket matching
204
- if json_str.count('{') != json_str.count('}'):
205
- self.issues.append(LintIssue(
206
- line_number=line_num,
207
- severity='error',
208
- category='potential_errors',
209
- message="Potential JSON syntax error in tellraw",
210
- suggestion="Check JSON syntax and bracket matching",
211
- command=line
212
- ))
213
- except:
214
- pass
215
-
216
- # Check for common mcfunction syntax errors
217
- self._check_mcfunction_syntax(line, line_num)
218
-
219
- def _check_mcfunction_syntax(self, line: str, line_num: int):
220
- """Check for actual mcfunction syntax errors"""
221
- import re
222
-
223
- # Incomplete scoreboard commands
224
- if re.match(r'scoreboard players (add|remove|set|operation)\s*$', line):
225
- self.issues.append(LintIssue(
226
- line_number=line_num,
227
- severity='error',
228
- category='syntax_errors',
229
- message="Incomplete scoreboard command - missing target and value",
230
- suggestion="Add target selector and value/objective",
231
- command=line
232
- ))
233
-
234
- # Invalid scoreboard operation syntax
235
- # Check for valid patterns: =, +=, -=, *=, /=, %=
236
- valid_patterns = [
237
- r'@\w+\s+\w+\s*=\s*@\w+\s+\w+', # assignment: target = source
238
- r'@\w+\s+\w+\s*\+=\s*@\w+\s+\w+', # addition: target += source
239
- r'@\w+\s+\w+\s*-=\s*@\w+\s+\w+', # subtraction: target -= source
240
- r'@\w+\s+\w+\s*\*=\s*@\w+\s+\w+', # multiplication: target *= source
241
- r'@\w+\s+\w+\s*/=\s*@\w+\s+\w+', # division: target /= source
242
- r'@\w+\s+\w+\s*%=\s*@\w+\s+\w+' # modulo: target %= source
243
- ]
244
-
245
- if 'scoreboard players operation' in line:
246
- is_valid = any(re.search(pattern, line) for pattern in valid_patterns)
247
- if not is_valid:
248
- self.issues.append(LintIssue(
249
- line_number=line_num,
250
- severity='error',
251
- category='syntax_errors',
252
- message="Invalid scoreboard operation syntax",
253
- suggestion="Use format: scoreboard players operation <target> <objective> <operator> <source> <objective>",
254
- command=line
255
- ))
256
-
257
- # Unclosed quotes in data commands
258
- if 'data modify' in line and line.count('"') % 2 != 0:
259
- self.issues.append(LintIssue(
260
- line_number=line_num,
261
- severity='error',
262
- category='syntax_errors',
263
- message="Unclosed quotes in data command",
264
- suggestion="Check quote matching in data modify command",
265
- command=line
266
- ))
267
-
268
- # Invalid entity selectors
269
- if re.search(r'@[^aeprs]', line):
270
- self.issues.append(LintIssue(
271
- line_number=line_num,
272
- severity='error',
273
- category='syntax_errors',
274
- message="Invalid entity selector",
275
- suggestion="Use valid selectors: @a, @e, @p, @r, @s",
276
- command=line
277
- ))
278
-
279
- # Invalid effect names
280
- if 'effect give' in line and not re.search(r'minecraft:[a-z_]+', line):
281
- self.issues.append(LintIssue(
282
- line_number=line_num,
283
- severity='error',
284
- category='syntax_errors',
285
- message="Invalid effect name format",
286
- suggestion="Use format: minecraft:effect_name",
287
- command=line
288
- ))
289
-
290
- # Malformed execute commands
291
- if line.startswith('execute') and not re.search(r'run\s+\w+', line):
292
- self.issues.append(LintIssue(
293
- line_number=line_num,
294
- severity='error',
295
- category='syntax_errors',
296
- message="Malformed execute command - missing 'run' clause",
297
- suggestion="Add 'run <command>' to execute statement",
298
- command=line
299
- ))
300
-
301
- # Invalid data storage syntax
302
- if 'data modify storage' in line and not re.search(r'storage\s+\w+:\w+', line):
303
- self.issues.append(LintIssue(
304
- line_number=line_num,
305
- severity='error',
306
- category='syntax_errors',
307
- message="Invalid data storage syntax",
308
- suggestion="Use format: storage <namespace>:<path>",
309
- command=line
310
- ))
311
-
312
- def _check_performance_issues(self, line: str, line_num: int):
313
- """Check for performance issues"""
314
- # Expensive operations
315
- if 'execute store result' in line and 'run data get storage' in line:
316
- self.issues.append(LintIssue(
317
- line_number=line_num,
318
- severity='info',
319
- category='performance_issues',
320
- message="Potentially expensive storage operation",
321
- suggestion="Consider if this operation is necessary or can be optimized",
322
- command=line
323
- ))
324
-
325
- # Multiple storage operations
326
- if line.count('data modify storage') > 1:
327
- self.issues.append(LintIssue(
328
- line_number=line_num,
329
- severity='warning',
330
- category='performance_issues',
331
- message="Multiple storage operations in single line",
332
- suggestion="Consider combining or optimizing storage operations",
333
- command=line
334
- ))
335
-
336
- def _check_style_issues(self, line: str, line_num: int):
337
- """Check for style and consistency issues"""
338
- # Inconsistent spacing
339
- if re.search(r' +', line): # Multiple spaces
340
- self.issues.append(LintIssue(
341
- line_number=line_num,
342
- severity='info',
343
- category='style_issues',
344
- message="Inconsistent spacing detected",
345
- suggestion="Use consistent single spaces between command parts",
346
- command=line
347
- ))
348
-
349
- # Long lines
350
- if len(line) > 120:
351
- self.issues.append(LintIssue(
352
- line_number=line_num,
353
- severity='info',
354
- category='style_issues',
355
- message="Line is very long",
356
- suggestion="Consider breaking into multiple lines for readability",
357
- command=line
358
- ))
359
-
360
-
361
- def lint_mcfunction_file(file_path: str) -> List[LintIssue]:
362
- """Convenience function to lint a single mcfunction file"""
363
- linter = MCFunctionLinter()
364
- return linter.lint_file(file_path)
365
-
366
-
367
- def lint_mcfunction_directory(directory_path: str) -> Dict[str, List[LintIssue]]:
368
- """Convenience function to lint all mcfunction files in a directory"""
369
- linter = MCFunctionLinter()
370
- return linter.lint_directory(directory_path)
371
-
372
-
373
- def format_lint_report(issues: List[LintIssue], file_path: str = None) -> str:
374
- """Format lint issues into a readable report"""
375
- if not issues:
376
- return "[OK] No linting issues found!"
377
-
378
- report = []
379
- if file_path:
380
- report.append(f"[DIR] Linting Report for: {file_path}")
381
- else:
382
- report.append("[DIR] Linting Report")
383
-
384
- report.append("=" * 50)
385
-
386
- # Group by severity
387
- by_severity = {'error': [], 'warning': [], 'info': []}
388
- for issue in issues:
389
- by_severity[issue.severity].append(issue)
390
-
391
- for severity in ['error', 'warning', 'info']:
392
- if by_severity[severity]:
393
- icon = {'error': 'ERROR', 'warning': 'WARNING', 'info': 'INFO'}[severity]
394
- report.append(f"\n{icon} {severity.upper()}S ({len(by_severity[severity])})")
395
- report.append("-" * 30)
396
-
397
- for issue in by_severity[severity]:
398
- report.append(f"Line {issue.line_number}: {issue.message}")
399
- if issue.suggestion:
400
- report.append(f" Suggestion: {issue.suggestion}")
401
- if issue.command:
402
- report.append(f" Command: {issue.command[:80]}{'...' if len(issue.command) > 80 else ''}")
403
- report.append("")
404
-
405
- return "\n".join(report)
406
-
407
-
408
- # Global linter instance
409
- mcfunction_linter = MCFunctionLinter()