Skip to content

Commit 682007e

Browse files
authored
Improvements to formatter cli interface (#3553)
1 parent b3c4deb commit 682007e

File tree

6 files changed

+72
-30
lines changed

6 files changed

+72
-30
lines changed

docs/docs/communityhub/release_notes/jaclang.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
This document provides a summary of new features, improvements, and bug fixes in each version of **Jaclang**. For details on changes that might require updates to your existing code, please refer to the [Breaking Changes](../breaking_changes.md) page.
44

5-
## jaclang 0.9.3 (Unreleased)
5+
## jaclang 0.9.4 (Unreleased)
66

7-
## jaclang 0.9.2(Latest Release)
7+
- **Format Command Enhancements**: The `jac format` command now tracks and reports which files were actually changed during formatting. The summary output shows both total files processed and the count of files that were modified (e.g., `Formatted 10/12 '.jac' files (3 changed).`). Additionally, syntax errors encountered during formatting are now printed with full error details.
8+
9+
## jaclang 0.9.3 (Latest Release)
810

911
- **Fixed JSX Text Parsing for Keywords**: Fixed a parser issue where keywords like `to`, `as`, `in`, `is`, `for`, `if`, etc. appearing as text content within JSX elements would cause parse errors. The grammar now correctly recognizes these common English words as valid JSX text content.
1012
- **Support iter for statement**: Iter for statement is supported in order to utilize traditional for loop in javascript.

jac/jaclang/cli/cli.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -61,40 +61,54 @@ def write_formatted_code(code: str, target_path: str) -> None:
6161
with open(target_path, "w") as f:
6262
f.write(code)
6363

64-
def format_single_file(file_path: str) -> bool:
65-
"""Format a single .jac file. Returns True on success."""
64+
def format_single_file(file_path: str) -> tuple[bool, bool]:
65+
"""Format a single .jac file. Returns (success, changed)."""
6666
path_obj = Path(file_path)
6767
if not path_obj.exists():
6868
print(f"Error: File '{file_path}' does not exist.", file=sys.stderr)
69-
return False
69+
return False, False
7070
try:
71-
formatted_code = JacProgram.jac_file_formatter(str(path_obj))
71+
prog = JacProgram.jac_file_formatter(str(path_obj))
72+
if prog.errors_had:
73+
for error in prog.errors_had:
74+
print(f"{error}", file=sys.stderr)
75+
return False, False
76+
formatted_code = prog.mod.main.gen.jac
77+
original_code = prog.mod.main.source.code
78+
changed = formatted_code != original_code
7279
write_formatted_code(formatted_code, str(path_obj))
73-
return True
80+
return True, changed
7481
except Exception as e:
7582
print(f"Error formatting '{file_path}': {e}", file=sys.stderr)
76-
return False
83+
return False, False
7784

7885
total_files = 0
7986
failed_files = 0
87+
changed_files = 0
8088

8189
for path in paths:
8290
path_obj = Path(path)
8391

8492
# Case 1: Single .jac file
8593
if path.endswith(".jac"):
8694
total_files += 1
87-
if not format_single_file(path):
95+
success, changed = format_single_file(path)
96+
if not success:
8897
failed_files += 1
98+
elif changed:
99+
changed_files += 1
89100
continue
90101

91102
# Case 2: Directory with .jac files
92103
if path_obj.is_dir():
93104
jac_files = list(path_obj.glob("**/*.jac"))
94105
for jac_file in jac_files:
95106
total_files += 1
96-
if not format_single_file(str(jac_file)):
107+
success, changed = format_single_file(str(jac_file))
108+
if not success:
97109
failed_files += 1
110+
elif changed:
111+
changed_files += 1
98112
continue
99113

100114
# Case 3: Invalid path
@@ -104,7 +118,8 @@ def format_single_file(file_path: str) -> bool:
104118
# Only print summary for directory processing or when there are failures
105119
if (len(paths) == 1 and Path(paths[0]).is_dir()) or failed_files > 0:
106120
print(
107-
f"Formatted {total_files - failed_files}/{total_files} '.jac' files.",
121+
f"Formatted {total_files - failed_files}/{total_files} '.jac' files "
122+
f"({changed_files} changed).",
108123
file=sys.stderr,
109124
)
110125

@@ -658,14 +673,11 @@ def py2jac(filename: str) -> None:
658673
),
659674
prog=JacProgram(),
660675
).ir_out.unparse(requires_format=False)
661-
formatted_code = JacProgram().jac_str_formatter(
662-
source_str=code, file_path=filename
663-
)
664-
if formatted_code:
665-
print(formatted_code)
666-
else:
676+
prog = JacProgram.jac_str_formatter(source_str=code, file_path=filename)
677+
if prog.errors_had:
667678
print("Error converting Python code to Jac.", file=sys.stderr)
668679
exit(1)
680+
print(prog.mod.main.gen.jac)
669681
else:
670682
print("Not a .py file.")
671683
exit(1)

jac/jaclang/compiler/passes/tool/tests/test_jac_format_pass.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ def compare_files(
1919
with open(original_path) as file:
2020
original_file_content = file.read()
2121
if formatted_file is None:
22-
formatted_content = JacProgram.jac_file_formatter(original_path)
22+
prog = JacProgram.jac_file_formatter(original_path)
23+
formatted_content = prog.mod.main.gen.jac
2324
else:
2425
with open(fixture_path(formatted_file)) as file:
2526
formatted_content = file.read()
@@ -132,7 +133,8 @@ def micro_suite_test(filename: str) -> None:
132133
Includes a specific token check for 'circle_clean_tests.jac'.
133134
"""
134135
code_gen_pure = JacProgram().compile(filename)
135-
code_gen_format = JacProgram.jac_file_formatter(filename)
136+
format_prog = JacProgram.jac_file_formatter(filename)
137+
code_gen_format = format_prog.mod.main.gen.jac
136138
code_gen_jac = JacProgram().compile(use_str=code_gen_format, file_path=filename)
137139
if "circle_clean_tests.jac" in filename:
138140
tokens = code_gen_format.split()

jac/jaclang/compiler/program.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -180,28 +180,26 @@ def run_schedule(
180180
current_pass(ir_in=mod, prog=self, cancel_token=cancel_token) # type: ignore
181181

182182
@staticmethod
183-
def jac_file_formatter(file_path: str) -> str:
184-
"""Convert a Jac file to an AST."""
183+
def jac_file_formatter(file_path: str) -> JacProgram:
184+
"""Format a Jac file and return the JacProgram."""
185185
prog = JacProgram()
186186
source_str = read_file_with_encoding(file_path)
187187
source = uni.Source(source_str, mod_path=file_path)
188188
parser_pass = JacParser(root_ir=source, prog=prog)
189189
current_mod = parser_pass.ir_out
190190
for pass_cls in format_sched:
191191
current_mod = pass_cls(ir_in=current_mod, prog=prog).ir_out
192-
parser_pass.errors_had = prog.errors_had
193-
parser_pass.warnings_had = prog.warnings_had
194-
return current_mod.gen.jac if not parser_pass.errors_had else source_str
192+
prog.mod = uni.ProgramModule(current_mod)
193+
return prog
195194

196195
@staticmethod
197-
def jac_str_formatter(source_str: str, file_path: str) -> str:
198-
"""Convert a Jac file to an AST."""
196+
def jac_str_formatter(source_str: str, file_path: str) -> JacProgram:
197+
"""Format a Jac string and return the JacProgram."""
199198
prog = JacProgram()
200199
source = uni.Source(source_str, mod_path=file_path)
201200
parser_pass = JacParser(root_ir=source, prog=prog)
202201
current_mod = parser_pass.ir_out
203202
for pass_cls in format_sched:
204203
current_mod = pass_cls(ir_in=current_mod, prog=prog).ir_out
205-
parser_pass.errors_had = prog.errors_had
206-
parser_pass.warnings_had = prog.warnings_had
207-
return current_mod.gen.jac if not parser_pass.errors_had else source_str
204+
prog.mod = uni.ProgramModule(current_mod)
205+
return prog

jac/jaclang/langserve/engine.jac

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,9 +399,10 @@ class JacLangServer(JacProgram , LanguageServer) {
399399
def formatted_jac(self: JacLangServer, file_path: str) -> list[lspt.TextEdit] {
400400
try {
401401
document = self.workspace.get_text_document(file_path);
402-
formatted_text = JacProgram.jac_str_formatter(
402+
prog = JacProgram.jac_str_formatter(
403403
source_str=document.source, file_path=document.path
404404
);
405+
formatted_text = prog.mod.main.gen.jac if not prog.errors_had else document.source;
405406
} except Exception as e {
406407
self.log_error(f"'Error during formatting: '{e}");
407408
formatted_text = document.source;

jac/jaclang/tests/test_cli.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,3 +744,30 @@ def test_cli_error_exit_codes(fixture_path) -> None:
744744
stdout, stderr = process.communicate()
745745
assert process.returncode == 0, "run command should exit with code 0 on success"
746746
assert "Hello World!" in stdout
747+
748+
749+
def test_format_tracks_changed_files() -> None:
750+
"""Test that format command correctly tracks and reports changed files."""
751+
with tempfile.TemporaryDirectory() as tmpdir:
752+
# Create a file that needs formatting (bad indentation/spacing)
753+
needs_formatting = os.path.join(tmpdir, "needs_format.jac")
754+
with open(needs_formatting, "w") as f:
755+
f.write('with entry{print("hello");}')
756+
757+
# Create a file that is already formatted
758+
already_formatted = os.path.join(tmpdir, "already_formatted.jac")
759+
with open(already_formatted, "w") as f:
760+
f.write('with entry {\n print("hello");\n}\n')
761+
762+
# Run format on the directory
763+
process = subprocess.Popen(
764+
["jac", "format", tmpdir],
765+
stdout=subprocess.PIPE,
766+
stderr=subprocess.PIPE,
767+
text=True,
768+
)
769+
stdout, stderr = process.communicate()
770+
771+
assert process.returncode == 0
772+
assert "2/2" in stderr
773+
assert "(1 changed)" in stderr

0 commit comments

Comments
 (0)