minecraft-datapack-language 15.4.28__py3-none-any.whl → 15.4.30__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.
- minecraft_datapack_language/__init__.py +23 -2
- minecraft_datapack_language/_version.py +2 -2
- minecraft_datapack_language/ast_nodes.py +87 -59
- minecraft_datapack_language/cli.py +276 -139
- minecraft_datapack_language/mdl_compiler.py +470 -0
- minecraft_datapack_language/mdl_errors.py +14 -0
- minecraft_datapack_language/mdl_lexer.py +624 -0
- minecraft_datapack_language/mdl_parser.py +573 -0
- minecraft_datapack_language-15.4.30.dist-info/METADATA +266 -0
- minecraft_datapack_language-15.4.30.dist-info/RECORD +17 -0
- minecraft_datapack_language/cli_build.py +0 -1292
- minecraft_datapack_language/cli_check.py +0 -155
- minecraft_datapack_language/cli_colors.py +0 -264
- minecraft_datapack_language/cli_help.py +0 -508
- minecraft_datapack_language/cli_new.py +0 -300
- minecraft_datapack_language/cli_utils.py +0 -276
- minecraft_datapack_language/expression_processor.py +0 -352
- minecraft_datapack_language/linter.py +0 -409
- minecraft_datapack_language/mdl_lexer_js.py +0 -754
- minecraft_datapack_language/mdl_parser_js.py +0 -1049
- minecraft_datapack_language/pack.py +0 -758
- minecraft_datapack_language-15.4.28.dist-info/METADATA +0 -1274
- minecraft_datapack_language-15.4.28.dist-info/RECORD +0 -25
- {minecraft_datapack_language-15.4.28.dist-info → minecraft_datapack_language-15.4.30.dist-info}/WHEEL +0 -0
- {minecraft_datapack_language-15.4.28.dist-info → minecraft_datapack_language-15.4.30.dist-info}/entry_points.txt +0 -0
- {minecraft_datapack_language-15.4.28.dist-info → minecraft_datapack_language-15.4.30.dist-info}/licenses/LICENSE +0 -0
- {minecraft_datapack_language-15.4.28.dist-info → minecraft_datapack_language-15.4.30.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()
|