Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Repository Guidelines

## Project Structure & Module Organization

This repository stores LeetCode solutions, local runners, and code-generation tooling across languages. Problem folders live under `problems/problems_<id>/` and usually contain `problem.md`, `problem_zh.md`, `solution.*`, `Solution.*`, `solution.md`, and testcase files. Shared helpers are in `python/`, `golang/`, `typescript/`, `cpp/`, and `rust/`; algorithm notes are in `algorithm_templates/`; metadata is in `data/`; automated tests are in `tests/`. Multithreading problems use `multi_threading/<id>/`.

## Build, Test, and Development Commands

- `make verify`: run stable local checks across Python codegen/unit tests, TypeScript smoke tests, and Go helper packages.
- `make health`: scan generated problem folders for missing docs, testcases, links, solutions, and Rust workspace drift.
- `make test`: run the full Python pytest suite with `PYTHONPATH=.`.
- `make test-unit`: run fast unit tests marked `unit`.
- `make test-integration`: run integration tests that may require language runtimes.
- `make test-codegen`: validate code generation behavior.
- `make test-coverage`: run pytest with terminal and HTML coverage reports.
- `npm test`: run the full TypeScript/Jest suite through `ts-jest`.
- `make test-go-libs`: run stable Go helper package tests.
- `python python/scripts/leetcode.py`: launch the interactive LeetCode helper for fetching/submitting problems.

Use `make help` for the maintained command list.

## Coding Style & Naming Conventions

Follow the target language style and existing generated files. Python uses pytest-compatible modules and 4-space indentation. Go code should be `gofmt` formatted. TypeScript uses lowercase filenames in `typescript/` and Jest tests such as `debug.test.ts`. Problem directories use `problems_<id>`, with solution names like `solution.py`, `solution.go`, `solution.ts`, `solution.rs`, `Solution.cpp`, and `Solution.java`.

## Testing Guidelines

Pytest discovers `test_*.py` and `*_test.py` under `tests/`; test classes start with `Test`, and test functions start with `test_`. Use markers from `pytest.ini`: `unit`, `integration`, `codegen`, and `slow`. For problem-level validation, run `make test-daily`, `make test-problems`, or the relevant language command. Add focused tests when changing code generation, language writers, shared models, or testcase parsing.

## Commit & Pull Request Guidelines

Recent commits use short prefixes such as `test:` and `fix:`; examples include `test: 3742 solution`, `test: [20260430] Add (3742)`, and `fix: 题解链接bug`. Keep messages scoped to one change. Pull requests should summarize changed problems or tooling, list test commands run, mention required environment variables, and link related issues when applicable.

## Security & Configuration Tips

Do not commit `.env`, LeetCode cookies, PushDeer keys, or other secrets. Required local values include `COOKIE`, `PROBLEM_FOLDER`, `LANGUAGES`, `LEETCODE_USER`, and `PYTHONPATH=.`; see `README.md` for an example configuration.
20 changes: 18 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,6 @@ members = [
"problems/problems_Interview_16__02",
"problems/problems_944",
"problems/problems_Interview_08__02",
"problems/problems_Interview_16__03",
"problems/problems_955",
"problems/problems_Interview_02__01",
"problems/problems_Interview_10__01",
Expand Down Expand Up @@ -547,6 +546,15 @@ members = [
"problems/problems_3225",
"problems/problems_3742",
"problems/problems_396",
"problems/problems_3655",
"problems/problems_2069",
"problems/problems_3653",
"problems/problems_3661",
"problems/problems_3740",
"problems/problems_874",
"problems/problems_2087",
"problems/problems_3418",
"problems/problems_657",
"problems/problems_788",
"problems/problems_796",
]
Expand Down Expand Up @@ -990,7 +998,6 @@ solution_2092 = { path = "problems/problems_2092", features = ["solution_2092"]
solution_Interview_16__02 = { path = "problems/problems_Interview_16__02", features = ["solution_Interview_16__02"] }
solution_944 = { path = "problems/problems_944", features = ["solution_944"] }
solution_Interview_08__02 = { path = "problems/problems_Interview_08__02", features = ["solution_Interview_08__02"] }
solution_Interview_16__03 = { path = "problems/problems_Interview_16__03", features = ["solution_Interview_16__03"] }
solution_955 = { path = "problems/problems_955", features = ["solution_955"] }
solution_Interview_02__01 = { path = "problems/problems_Interview_02__01", features = ["solution_Interview_02__01"] }
solution_Interview_10__01 = { path = "problems/problems_Interview_10__01", features = ["solution_Interview_10__01"] }
Expand Down Expand Up @@ -1118,5 +1125,14 @@ solution_2033 = { path = "problems/problems_2033", features = ["solution_2033"]
solution_3225 = { path = "problems/problems_3225", features = ["solution_3225"] }
solution_3742 = { path = "problems/problems_3742", features = ["solution_3742"] }
solution_396 = { path = "problems/problems_396", features = ["solution_396"] }
solution_3655 = { path = "problems/problems_3655", features = ["solution_3655"] }
solution_2069 = { path = "problems/problems_2069", features = ["solution_2069"] }
solution_3653 = { path = "problems/problems_3653", features = ["solution_3653"] }
solution_3661 = { path = "problems/problems_3661", features = ["solution_3661"] }
solution_3740 = { path = "problems/problems_3740", features = ["solution_3740"] }
solution_874 = { path = "problems/problems_874", features = ["solution_874"] }
solution_2087 = { path = "problems/problems_2087", features = ["solution_2087"] }
solution_3418 = { path = "problems/problems_3418", features = ["solution_3418"] }
solution_657 = { path = "problems/problems_657", features = ["solution_657"] }
solution_788 = { path = "problems/problems_788", features = ["solution_788"] }
solution_796 = { path = "problems/problems_796", features = ["solution_796"] }
24 changes: 23 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
# Makefile for LeetCode project

.PHONY: test test-unit test-integration test-codegen test-coverage clean help
.PHONY: verify health test test-unit test-integration test-codegen test-coverage test-typescript-smoke test-go-libs clean help

# Default target
help:
@echo "Available targets:"
@echo " verify - Run the practical local verification suite"
@echo " health - Check generated problem folder health"
@echo " test - Run all tests"
@echo " test-unit - Run unit tests only"
@echo " test-integration - Run integration tests (requires language runtimes)"
@echo " test-codegen - Run code generation tests"
@echo " test-coverage - Run tests with coverage report"
@echo " test-parallel - Run tests in parallel"
@echo " test-typescript-smoke - Run stable TypeScript smoke tests"
@echo " test-go-libs - Run stable Go helper package tests"
@echo " test-daily - Run daily problem test (current problem)"
@echo " test-problems - Run multiple problems test"
@echo " clean - Clean up temporary files"
Expand All @@ -20,6 +24,16 @@ help:
@echo " add-snippet - Add a problem to snippets (use PROBLEM=id)"
@echo " print-snippet - Print original snippet (use PROBLEM=id LANG=lang)"

# Run the practical local verification suite
verify:
PYTHONPATH=. pytest tests/ -m "(unit or codegen) and not slow" -v
npm test -- --runTestsByPath typescript/debug.test.ts
go test ./golang/models ./golang/node_random ./golang/node_neighbours ./golang/tree_next ./golang/double_linked_node_child

# Check generated problem folder health
health:
PYTHONPATH=. python python/scripts/leetcode.py --health

# Run all unit tests
test-unit:
PYTHONPATH=. pytest tests/ -m unit -v
Expand All @@ -44,6 +58,14 @@ test-coverage:
test-parallel:
PYTHONPATH=. pytest tests/ -v -n auto

# Run stable TypeScript smoke tests
test-typescript-smoke:
npm test -- --runTestsByPath typescript/debug.test.ts

# Run stable Go helper package tests
test-go-libs:
go test ./golang/models ./golang/node_random ./golang/node_neighbours ./golang/tree_next ./golang/double_linked_node_child

# Run daily problem test
test-daily:
PYTHONPATH=. python python/test.py
Expand Down
2 changes: 1 addition & 1 deletion python/lc_libs/rust_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def write_solution(
if not RustWriter.is_snake_case(f"{problem_folder}_{problem_id}"):
add_title = f"#![allow(non_snake_case)]\n"
code = code or code_default
if "object will be instantiated and called as such:" in code:
if "object will be instantiated and called as such:" in code and "impl Solution" not in code:
struct_map = RustWriter._parse_rust_structs(code_default)
solve_part = RustWriter._generate_solve_function(struct_map)
return SOLUTION_TEMPLATE_RUST.format(add_title, "\n".join([]), "",
Expand Down
14 changes: 12 additions & 2 deletions python/scripts/cli/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,14 @@
"cookie_continue": "继续使用现有 Cookie...",

# Main menu
"main_menu": "请选择功能 [0-9, 默认: 0]:\n0. 退出\n1. 获取题目\n2. 提交代码\n3. 切换测试题目\n4. 比赛\n5. 清理空 Java 文件\n6. 清理错误 Rust 文件\n7. 收藏夹管理\n8. 创建题目链接\n9. 题解中心\n",
"main_menu": "请选择功能 [0-10, 默认: 0]:\n0. 退出\n1. 获取题目\n2. 提交代码\n3. 切换测试题目\n4. 比赛\n5. 清理空 Java 文件\n6. 清理错误 Rust 文件\n7. 收藏夹管理\n8. 创建题目链接\n9. 题解中心\n10. 仓库健康检查\n",
"main_exit": "正在退出...",
"main_bye": "再见!",
"health_running": "正在检查生成题目目录: {folder}",
"health_fix_header": "可自动修复的问题:",
"health_fix_select": "选择要修复的项目 [例如: 1,2;a 全部;默认跳过]: ",
"health_fix_confirm": "确认执行修复 [{fix}]? [y/n, 默认: n]: ",
"health_fix_skipped": "已跳过",

# Get problem
"get_menu": "请选择获取题目方式 [0-6, 默认: 0]:\n0. 返回\n1. 每日自动\n2. 指定题目 ID\n3. 随机\n4. 随机未通过\n5. 分类\n6. 比赛\n",
Expand Down Expand Up @@ -237,9 +242,14 @@
"cookie_continue": "Continuing with existing cookie...",

# Main menu
"main_menu": "Please select the main function [0-9, default: 0]:\n0. Exit\n1. Get problem\n2. Submit\n3. Change test problem\n4. Contest\n5. Clean empty java\n6. Clean error rust\n7. Favorite management\n8. Link problems\n9. Solution Center\n",
"main_menu": "Please select the main function [0-10, default: 0]:\n0. Exit\n1. Get problem\n2. Submit\n3. Change test problem\n4. Contest\n5. Clean empty java\n6. Clean error rust\n7. Favorite management\n8. Link problems\n9. Solution Center\n10. Repository health\n",
"main_exit": "Exiting...",
"main_bye": "Bye!",
"health_running": "Checking generated problem folder: {folder}",
"health_fix_header": "Available automatic fixes:",
"health_fix_select": "Select fixes to apply [e.g. 1,2; a for all; default skips]: ",
"health_fix_confirm": "Apply fix [{fix}]? [y/n, default: n]: ",
"health_fix_skipped": "Skipped",

# Get problem
"get_menu": "Please select the get problem method [0-6, default: 0]:\n0. Back\n1. Daily auto\n2. Specified problem ID\n3. Random\n4. Random remain\n5. Category\n6. Contest\n",
Expand Down
55 changes: 52 additions & 3 deletions python/scripts/leetcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
from python.scripts.get_problem import main as get_problem_main, get_question_slug_by_id
from python.scripts.tools import lucky_main, remain_main, clean_empty_java_main, clean_error_rust_main
from python.scripts.fetch_solution_articles import main as fetch_solution_main
from python.scripts.repository_health import scan_repository, format_report, build_fix_suggestions, apply_fix

# Constants
SEPARATE_LINE = "-" * 50
Expand Down Expand Up @@ -837,6 +838,40 @@ def link_problems(problem_folder):
print(SEPARATE_LINE)


def repository_health(problem_folder: str):
"""Run generated problem repository health checks."""
print(t("health_running", folder=problem_folder))
report = scan_repository(root_path, [problem_folder])
print(format_report(report, root_path))
fixes = build_fix_suggestions(report, root_path)
if fixes:
print()
print(t("health_fix_header"))
for idx, fix in enumerate(fixes, start=1):
print(f"{idx}. {fix.display(root_path)}")
selection = input_until_valid(t("health_fix_select"), allow_all)
selected_indices: list[int] = []
if selection.lower() == "a":
selected_indices = list(range(len(fixes)))
else:
for part in selection.split(","):
part = part.strip()
if part.isdigit() and 1 <= int(part) <= len(fixes):
selected_indices.append(int(part) - 1)
for idx in dict.fromkeys(selected_indices):
fix = fixes[idx]
confirm = input_until_valid(t("health_fix_confirm", fix=fix.display(root_path)), allow_all)
if confirm.lower() != "y":
print(t("health_fix_skipped"))
continue
print(apply_fix(fix))
if selected_indices:
print()
report = scan_repository(root_path, [problem_folder])
print(format_report(report, root_path))
print(SEPARATE_LINE)


def _get_problem_slug_from_id(problem_id: str, cookie: str) -> Optional[str]:
"""通过题目 ID 获取 problem_slug"""
origin_problem_id = back_question_id(problem_id)
Expand Down Expand Up @@ -1148,12 +1183,24 @@ def main():
parser = argparse.ArgumentParser(description="LeetCode 工具集")
parser.add_argument('--en', action='store_true', help='Use English interface')
parser.add_argument('--init', action='store_true', help='Force initialization wizard')
parser.add_argument('--health', action='store_true', help='Run repository health checks and exit')
parser.add_argument('--health-folder', default=None, help='Problem folder to scan for --health')
args = parser.parse_args()

# Set language
if args.en:
set_language("en")

if args.health:
try:
load_dotenv()
except Exception:
logging.debug("Failed to load .env for health check", exc_info=True)
health_folder = args.health_folder or os.getenv(constant.PROBLEM_FOLDER, "problems")
report = scan_repository(root_path, [health_folder])
print(format_report(report, root_path))
return 0 if report.ok else 1

try:
if args.init:
languages, problem_folder, cookie, contest_folder = initialize_env()
Expand Down Expand Up @@ -1190,13 +1237,15 @@ def main():
link_problems(problem_folder)
case "9":
solution_center(cookie, problem_folder)
case "10":
repository_health(problem_folder)
case _:
print(t("main_exit"))
break
return 0
except KeyboardInterrupt:
print(f"\n{t('main_bye')}")
return 130


if __name__ == '__main__':
main()
sys.exit()
sys.exit(main())
Loading
Loading