motoko-core-code-improvements
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePurpose & Scope
目的与范围
Use this skill after test pass status to raise readability and consistency without changing behavior.
This skill focuses on mechanical, semantics‑preserving improvements:
- Aggregate imports into sections (1) mo:core/... (2) other mo:*/... from mops or similar third‑party sources (3) local project modules; sort each section alphabetically per file
- Prefer dot‑notation where available in
mo:core - Clean up truly unused lines while respecting implicit needs created by dot‑notation
import - Remove redundant in single‑expression functions
return - Use direct string‑to‑Blob assignment for constant ASCII strings where appropriate
Safety first:
- Run each improvement category independently; commit after each to isolate diffs
- Prefer scripted, reviewable changes; use audit checks provided below
- Rebuild after every category; run tests if present
在测试通过后使用本技能,可在不改变代码行为的前提下提升可读性与一致性。
本技能专注于语义保留的机械性改进:
- 将导入内容归为三类:(1) mo:core/... (2) 来自mops或类似第三方源的其他mo:*/... (3) 本地项目模块;按文件对每个部分进行字母排序
- 在中优先使用点表示法(dot-notation)
mo:core - 清理真正未使用的行,同时尊重点表示法带来的隐式依赖需求
import - 移除单表达式函数中冗余的语句
return - 在合适的场景下,对常量ASCII字符串使用直接的字符串到Blob赋值
安全第一:
- 独立执行每一类改进;每完成一类就提交代码,以隔离差异
- 优先使用可脚本化、可审核的变更;使用下方提供的审计检查方法
- 完成每一类改进后重新构建项目;若有测试则运行测试
AI Quick Checklist (Do Not Skip)
AI快速检查清单(请勿跳过)
- Preconditions
- Project compiles on (see Migration Skill). Keep
mo:corearound only if still referenced; otherwise remove base dependency already.mo:base - Ensure consistent Motoko and dfx versions per migration skill (moc ≥ 1.3.0, dfx ≥ 0.31).
- Order of improvements (recommended)
- A. Remove in single‑expression functions
return - B. Convert to dot‑notation where available — see Motoko Dot‑Notation Migration Skill ()
skills/dot-notation-migration/SKILL.md - C. Ensure necessary imports for dot‑notation — see Motoko Dot‑Notation Migration Skill (import mapping)
mo:core - D. Clean up unused imports (be conservative re: dot‑notation)
- E. Shorten local (sibling) import paths (drop the prefix where applicable)
./ - F. Aggregate imports into three sections and sort each section alphabetically per file: (1) , (2) other
mo:core/...from mops/third‑party, (3) local project modulesmo:*/... - G. Use direct string‑to‑Blob assignment for constant ASCII strings where appropriate
- Verify after each step
- Build all canisters or packages
- Grep/audit with provided commands
- Keep diffs minimal and readable
Acceptance Criteria
- No compiler errors or warnings introduced by the changes (esp. missing imports)
- No behavior changes; public interfaces unchanged unless stylistic
- Imports are aggregated into three sections — (1) , (2) other
mo:core/...from mops/third‑party, (3) local project modules — and each section is alphabetized; no truly unusedmo:*/...s remainimport - Dot‑notation is consistently used where directly supported
- Constant Text strings are assigned directly to without redundant
BlobcallsText.encodeUtf8
- 前置条件
- 项目基于可编译(参考迁移技能)。仅当仍有引用时保留
mo:core;否则请移除base依赖。mo:base - 确保迁移技能中要求的Motoko和dfx版本一致(moc ≥ 1.3.0,dfx ≥ 0.31)。
- 推荐的改进顺序
- A. 移除单表达式函数中的语句
return - B. 在可用场景下转换为点表示法——参考Motoko点表示法迁移技能()
skills/dot-notation-migration/SKILL.md - C. 确保点表示法所需的导入——参考Motoko点表示法迁移技能(导入映射)
mo:core - D. 清理未使用的导入(对点表示法相关的导入需谨慎处理)
- E. 缩短本地(同级)导入路径(在适用情况下移除前缀)
./ - F. 将导入内容归为三类并按文件对每个部分进行字母排序:(1) ,(2) 来自mops/第三方的其他
mo:core/...,(3) 本地项目模块mo:*/... - G. 在合适的场景下,对常量ASCII字符串使用直接的字符串到Blob赋值
- 每一步完成后验证
- 构建所有canister或包
- 使用提供的命令进行Grep/审计
- 保持差异最小且可读
验收标准
- 变更未引入编译器错误或警告(尤其是缺失导入的情况)
- 无行为变更;公共接口仅做风格调整,无功能变化
- 导入内容被归为三类——(1) ,(2) 来自mops/第三方的其他
mo:core/...,(3) 本地项目模块——且每个部分按字母排序;无真正未使用的mo:*/...语句残留import - 在直接支持的场景下一致使用点表示法
- 常量Text字符串直接赋值给,无需冗余的
Blob调用Text.encodeUtf8
A) Remove return
in single‑expression functions
returnA) 移除单表达式函数中的return
returnPattern
motoko
// Before
func f(x : T) : U { return <expr>; };
// After
func f(x : T) : U { <expr> };Notes
- Only apply when the function body consists of a single statement.
return <expr>; - Do not transform multi‑statement bodies or bodies that include ,
try,label, orswitchleading to different control flow.await - A function with multiple statements (e.g., early returns in
returncases likeswitch) must NOT have any returns removed.return null - Single non-terminal (early/conditional exit): if a function has exactly one
returnbut it is not the final statement of the body (e.g., an early return inside anreturnorifcase, followed by a fall-through final expression), the function has two distinct return paths. Do NOT remove the earlyswitch. Instead, add an explicitreturnto the final expression as well, so the function has tworeturnkeywords total — one per exit path. This makes both control-flow exits explicit and consistent.returnmotoko// Before — one explicit return, one implicit fall-through return func lookup(k : Key) : ?V { if (cache.contains(k)) { return cache.get(k) }; table.find(k) }; // After — both exit paths use `return` func lookup(k : Key) : ?V { if (cache.contains(k)) { return cache.get(k) }; return table.find(k); }; - at the end of a function is OK. Each case block ends with the expression being returned (no
return switch (...) { ... }keyword inside the cases). This is the preferred style when all cases produce values normally.returnmotoko// OK — all cases produce values, no traps or throws return switch (decode(data)) { case (#ok key) { (key.x, key.y) }; case (#err msg) { #err(msg) }; }; - Exception: if any case has a return-equivalent statement (or
Runtime.trap) that represents a genuine function exit, pullthrowinside the case blocks. Removereturnbeforereturn, then addswitchonly in the case blocks that produce values. Cases withreturnorRuntime.trapdo not needthrow. This makes it clear which branches return and which abort:returnmotoko// Avoid — trap is hidden inside return switch return switch (decode(data)) { case (#ok key) { (key.x, key.y) }; case (#err msg) { Runtime.trap(msg) }; }; // Prefer — return only in value-producing cases switch (decode(data)) { case (#ok key) { return (key.x, key.y) }; case (#err msg) { Runtime.trap(msg) }; }; - Unreachable traps are NOT return-equivalent. If is used, or a comment or message indicates the branch is only reachable through a bug, that trap is not a normal function exit — it's a defensive assertion. In that case,
Runtime.trap("unreachable")is fine andreturn switchshould NOT be pulled inside:returnmotoko// OK — the trap just guards an impossible case return switch (Jacobi.fromNat(x, y, 1, curve)) { case (null) Runtime.trap("unreachable"); case (?point) point; };
Automation (example)
- Grep candidates:
grep -rn "func \\w\\+(.*) *:.*{ *return .*; *};" . --include="*.mo" | grep -v \.mops - Review matches; then apply with your editor or a scripted replacement.
Script Safety Requirements (learned from real migration)
- A simple line-based return counter is NOT sufficient. You must track function boundaries using brace-depth parsing at the character level.
- Nested functions: When a function body contains nested declarations, skip the nested function's body entirely — only count returns at the direct (outermost) function scope.
func - Accurate function boundary detection: Use character-level scanning that handles string literals (skip including
"..."escapes), comments (\"line comments and//block comments), and tracks brace depth to find the true closing/* ... */of each function.} - Counting rule: Count every anywhere inside the outer function body, including inside nested control-flow blocks such as
return,switch,if, andfor, but excluding anywhileinside nestedreturnbodies. A function is safe to rewrite only if this total count is exactly 1 and that singlefuncis the terminal direct-body statement (only whitespace, comments, and an optionalreturnmay follow it before the closing;). If the single}is an early/conditional exit, the function has an additional implicit return path via its fall-through final expression — leave both alone (the script will not remove it; manually add an explicitreturnto the fall-through expression per the style note above).return
Battle-tested Python script
Save as in the project root, run with , then delete the script.
remove_returns.pypython3 remove_returns.pypython
#!/usr/bin/env python3
"""
Remove terminal `return` from Motoko functions that have exactly one return
statement in their direct body (excluding nested functions).
Usage:
1. Set src_dirs to match your project layout.
2. Run: python3 remove_returns.py
3. Run tests: npx mops test
4. Delete this script after confirming all tests pass.
Safety:
- Tracks function boundaries via character-level brace-depth parsing.
- Skips string literals ("..." with \" escapes) and comments (// and /* */).
- Detects nested `func` declarations and skips their bodies entirely.
- Only removes the `return` keyword (+ trailing space) from functions with
exactly 1 return at any depth within the direct body.
"""
import re
import glob
import os模式
motoko
// 优化前
func f(x : T) : U { return <expr>; };
// 优化后
func f(x : T) : U { <expr> };注意事项
- 仅当函数体仅包含单个语句时应用此优化。
return <expr>; - 不要转换多语句函数体,或包含、
try、label、switch导致控制流不同的函数体。await - 包含多个语句的函数(例如
return分支中的提前返回switch)绝对不能移除任何return语句。return null - 单个非终结(提前/条件退出):如果函数仅有一个
return但它不是函数体的最终语句(例如return或if分支中的提前返回,后续还有一个默认的返回表达式),则函数有两个不同的返回路径。不要移除提前的switch,反而要给最终的表达式添加显式的return,使函数共有两个return关键字——每个退出路径对应一个。这样能让两个控制流退出路径都清晰且一致。returnmotoko// 优化前——一个显式return,一个隐式默认返回 func lookup(k : Key) : ?V { if (cache.contains(k)) { return cache.get(k) }; table.find(k) }; // 优化后——两个退出路径都使用`return` func lookup(k : Key) : ?V { if (cache.contains(k)) { return cache.get(k) }; return table.find(k); }; - 函数末尾的是允许的。每个分支块以要返回的表达式结尾(分支内无
return switch (...) { ... }关键字)。当所有分支正常生成值时,这是首选风格。returnmotoko// 允许——所有分支生成值,无陷阱或抛出异常 return switch (decode(data)) { case (#ok key) { (key.x, key.y) }; case (#err msg) { #err(msg) }; }; - 例外情况:如果任何分支包含等效于return的语句(或
Runtime.trap)表示真正的函数退出,则将throw移到分支块内部。移除return前的switch,然后仅在生成值的分支块中添加return。包含return或Runtime.trap的分支不需要throw。这样能明确哪些分支返回值,哪些分支终止执行:returnmotoko// 避免——trap隐藏在return switch中 return switch (decode(data)) { case (#ok key) { (key.x, key.y) }; case (#err msg) { Runtime.trap(msg) }; }; // 首选——仅在生成值的分支中使用return switch (decode(data)) { case (#ok key) { return (key.x, key.y) }; case (#err msg) { Runtime.trap(msg) }; }; - 不可达的trap不等同于return。如果使用,或注释/消息表明该分支仅在出现bug时才会进入,那么该trap不是正常的函数退出——而是一个防御性断言。这种情况下,
Runtime.trap("unreachable")是可行的,不应将return switch移到内部:returnmotoko// 允许——trap仅用于防护不可能的分支 return switch (Jacobi.fromNat(x, y, 1, curve)) { case (null) Runtime.trap("unreachable"); case (?point) point; };
自动化示例
- 查找候选函数:
grep -rn "func \\\\w\\\\+(.*) *:.*{ *return .*; *};" . --include="*.mo" | grep -v \\.mops - 检查匹配结果;然后使用编辑器或脚本替换工具应用优化。
脚本安全要求(从实际迁移中总结)
- 简单的基于行的return计数器是不够的。必须以字符级别跟踪函数边界,解析大括号深度。
- 嵌套函数:当函数体包含嵌套的声明时,完全跳过嵌套函数的函数体——仅统计最外层函数作用域内的return语句。
func - 准确的函数边界检测:使用字符级别扫描,处理字符串字面量(跳过包括
"..."转义)、注释(\\"行注释和//块注释),并跟踪大括号深度以找到每个函数真正的闭合/* ... */。} - 计数规则:统计最外层函数体内任何位置的每个,包括嵌套在
return、switch、if、for等控制流块内的while,但排除嵌套return体内的func。只有当总数恰好为1,且该单个return是函数体的终结直接语句(闭合return之前只能有空白、注释和可选的})时,函数才可以安全重写。如果单个;是提前/条件退出,函数还有一个额外的隐式返回路径(默认的最终表达式)——保留两者不变(脚本不会移除它;请根据上述风格说明手动给默认表达式添加显式return)。return
经过实战检验的Python脚本
将脚本保存为项目根目录下的,运行,然后删除脚本。
remove_returns.pypython3 remove_returns.pypython
#!/usr/bin/env python3
"""
Remove terminal `return` from Motoko functions that have exactly one return
statement in their direct body (excluding nested functions).
Usage:
1. Set src_dirs to match your project layout.
2. Run: python3 remove_returns.py
3. Run tests: npx mops test
4. Delete this script after confirming all tests pass.
Safety:
- Tracks function boundaries via character-level brace-depth parsing.
- Skips string literals ("..." with \\" escapes) and comments (// and /* */).
- Detects nested `func` declarations and skips their bodies entirely.
- Only removes the `return` keyword (+ trailing space) from functions with
exactly 1 return at any depth within the direct body.
"""
import re
import glob
import os── Configuration ──────────────────────────────────────────────────────
── Configuration ──────────────────────────────────────────────────────
Directories to process (relative to script location or cwd).
Directories to process (relative to script location or cwd).
SRC_DIRS = ["src", "test", "bench"]
SRC_DIRS = ["src", "test", "bench"]
──────────────────────────────────────────────────────────────────────
──────────────────────────────────────────────────────────────────────
def find_func_bodies(text):
"""
Yield (body_start, body_end) for every top-level and nested function
found in . body_start is the index of the opening '{' of the
function body; body_end is the index of the matching closing '}'.
textHandles:
- String literals (skips content inside "...")
- Line comments (// ...)
- Block comments (/* ... */)
- Nested braces
"""
i = 0
n = len(text)
while i < n:
# Skip string literals
if text[i] == '"':
i += 1
while i < n and text[i] != '"':
if text[i] == '\\':
i += 1 # skip escaped char
i += 1
i += 1 # skip closing "
continue
# Skip line comments
if text[i] == '/' and i + 1 < n and text[i + 1] == '/':
i += 2
while i < n and text[i] != '\n':
i += 1
continue
# Skip block comments
if text[i] == '/' and i + 1 < n and text[i + 1] == '*':
i += 2
while i < n and not (text[i] == '*' and i + 1 < n and text[i + 1] == '/'):
i += 1
i += 2 # skip */
continue
# Look for 'func' keyword at a word boundary
if text[i:i+4] == 'func' and (i == 0 or not text[i-1].isalnum() and text[i-1] != '_'):
after = text[i+4:i+5] if i + 4 < n else ''
if after == '' or not (after.isalnum() or after == '_'):
# Found a func keyword. Scan forward to find the opening '{'.
j = i + 4
while j < n:
if text[j] == '"':
j += 1
while j < n and text[j] != '"':
if text[j] == '\\':
j += 1
j += 1
j += 1
continue
if text[j] == '{':
# Found the opening brace of the function body.
brace_start = j
depth = 1
j += 1
while j < n and depth > 0:
if text[j] == '"':
j += 1
while j < n and text[j] != '"':
if text[j] == '\\':
j += 1
j += 1
j += 1
continue
if text[j] == '/' and j + 1 < n and text[j + 1] == '/':
j += 2
while j < n and text[j] != '\n':
j += 1
continue
if text[j] == '/' and j + 1 < n and text[j + 1] == '*':
j += 2
while j < n and not (text[j] == '*' and j + 1 < n and text[j + 1] == '/'):
j += 1
j += 2
continue
if text[j] == '{':
depth += 1
elif text[j] == '}':
depth -= 1
j += 1
brace_end = j - 1 # index of closing '}'
yield (brace_start, brace_end)
i = j
break
if text[j] == '=' or text[j] == ';':
# func ... = expr; (no body) or forward decl
i = j + 1
break
j += 1
else:
i = j
continue
i += 1def count_returns_in_direct_body(text, body_start, body_end):
"""
Count statements that are directly inside this function body
(not inside nested functions). Returns list of (return_keyword_start,
return_keyword_end) positions.
"""
body = text[body_start + 1 : body_end] # content between { and }
offset = body_start + 1
return# First, find all nested func bodies within this body so we can skip them.
nested_ranges = []
for ns, ne in find_func_bodies(body):
# Adjust to absolute positions
nested_ranges.append((ns + offset, ne + offset))
def is_inside_nested(pos):
for ns, ne in nested_ranges:
if ns <= pos <= ne:
return True
return False
# Now scan for `return` keywords in the body, skipping nested funcs.
returns = []
i = 0
while i < len(body):
# Skip strings
if body[i] == '"':
i += 1
while i < len(body) and body[i] != '"':
if body[i] == '\\':
i += 1
i += 1
i += 1
continue
# Skip line comments
if body[i] == '/' and i + 1 < len(body) and body[i + 1] == '/':
i += 2
while i < len(body) and body[i] != '\n':
i += 1
continue
# Skip block comments
if body[i] == '/' and i + 1 < len(body) and body[i + 1] == '*':
i += 2
while i < len(body) and not (body[i] == '*' and i + 1 < len(body) and body[i + 1] == '/'):
i += 1
i += 2
continue
# Check for 'return' keyword
if body[i:i+6] == 'return' and (i == 0 or not body[i-1].isalnum() and body[i-1] != '_'):
after = body[i+6:i+7] if i + 6 < len(body) else ''
if after == '' or not (after.isalnum() or after == '_'):
abs_pos = i + offset
if not is_inside_nested(abs_pos):
returns.append((abs_pos, abs_pos + 6))
i += 6
continue
i += 1
return returnsdef process_file(filepath):
with open(filepath, 'r') as f:
text = f.read()
original = text
removals = 0
# Collect all function bodies
func_bodies = list(find_func_bodies(text))
# For each function, check if it has exactly 1 return in its direct body
# Process in reverse order to preserve indices when editing
edits = [] # list of (start, end) of "return " to remove
for body_start, body_end in func_bodies:
returns = count_returns_in_direct_body(text, body_start, body_end)
if len(returns) == 1:
ret_start, ret_end = returns[0]
# Verify this return is the terminal statement of the direct body:
# scan past the return's expression (tracking brackets/strings/comments)
# to its terminating ';' (or body_end), then ensure only whitespace,
# comments, and optional semicolons remain before body_end.
n = len(text)
j = ret_end
depth = 0
stmt_end = body_end # position after the return statement
while j < body_end:
c = text[j]
if c == '"':
j += 1
while j < body_end and text[j] != '"':
if text[j] == '\\':
j += 1
j += 1
j += 1
continue
if c == '/' and j + 1 < body_end and text[j + 1] == '/':
j += 2
while j < body_end and text[j] != '\n':
j += 1
continue
if c == '/' and j + 1 < body_end and text[j + 1] == '*':
j += 2
while j < body_end and not (text[j] == '*' and j + 1 < body_end and text[j + 1] == '/'):
j += 1
j += 2
continue
if c in '({[':
depth += 1
elif c in ')}]':
depth -= 1
elif c == ';' and depth == 0:
stmt_end = j + 1
break
elif c == '\n' and depth == 0:
# Statement terminated by newline (no semicolon)
stmt_end = j
break
j += 1
else:
stmt_end = body_end
# Now skip whitespace, comments, and stray semicolons after the return
k = stmt_end
terminal = True
while k < body_end:
c = text[k]
if c.isspace() or c == ';':
k += 1
continue
if c == '/' and k + 1 < body_end and text[k + 1] == '/':
k += 2
while k < body_end and text[k] != '\n':
k += 1
continue
if c == '/' and k + 1 < body_end and text[k + 1] == '*':
k += 2
while k < body_end and not (text[k] == '*' and k + 1 < body_end and text[k + 1] == '/'):
k += 1
k += 2
continue
# Found other code after the return — not terminal.
terminal = False
break
if not terminal:
continue
# Remove "return " (keyword + trailing space)
if ret_end < len(text) and text[ret_end] == ' ':
edits.append((ret_start, ret_end + 1))
else:
edits.append((ret_start, ret_end))
# Apply edits in reverse order to preserve positions
edits.sort(key=lambda x: x[0], reverse=True)
for start, end in edits:
text = text[:start] + text[end:]
removals += 1
if text != original:
with open(filepath, 'w') as f:
f.write(text)
return removalsdef main():
total = 0
files_modified = 0
for src_dir in SRC_DIRS:
for filepath in sorted(glob.glob(os.path.join(src_dir, "**/*.mo"), recursive=True)):
r = process_file(filepath)
if r > 0:
print(f" {filepath}: {r} returns removed")
total += r
files_modified += 1
print(f"\nTotal: {total} returns removed in {files_modified} files")
if name == 'main':
main()
---def find_func_bodies(text):
"""
Yield (body_start, body_end) for every top-level and nested function
found in . body_start is the index of the opening '{' of the
function body; body_end is the index of the matching closing '}'.
textHandles:
- String literals (skips content inside "...")
- Line comments (// ...)
- Block comments (/* ... */)
- Nested braces
"""
i = 0
n = len(text)
while i < n:
# Skip string literals
if text[i] == '"':
i += 1
while i < n and text[i] != '"':
if text[i] == '\\\\':
i += 1 # skip escaped char
i += 1
i += 1 # skip closing "
continue
# Skip line comments
if text[i] == '/' and i + 1 < n and text[i + 1] == '/':
i += 2
while i < n and text[i] != '\':
i += 1
continue
# Skip block comments
if text[i] == '/' and i + 1 < n and text[i + 1] == '*':
i += 2
while i < n and not (text[i] == '*' and i + 1 < n and text[i + 1] == '/'):
i += 1
i += 2 # skip */
continue
# Look for 'func' keyword at a word boundary
if text[i:i+4] == 'func' and (i == 0 or not text[i-1].isalnum() and text[i-1] != '_'):
after = text[i+4:i+5] if i + 4 < n else ''
if after == '' or not (after.isalnum() or after == '_'):
# Found a func keyword. Scan forward to find the opening '{'.
j = i + 4
while j < n:
if text[j] == '"':
j += 1
while j < n and text[j] != '"':
if text[j] == '\\\\':
j += 1
j += 1
j += 1
continue
if text[j] == '{':
# Found the opening brace of the function body.
brace_start = j
depth = 1
j += 1
while j < n and depth > 0:
if text[j] == '"':
j += 1
while j < n and text[j] != '"':
if text[j] == '\\\\':
j += 1
j += 1
j += 1
continue
if text[j] == '/' and j + 1 < n and text[j + 1] == '/':
j += 2
while j < n and text[j] != '\':
j += 1
continue
if text[j] == '/' and j + 1 < n and text[j + 1] == '':
j += 2
while j < n and not (text[j] == '' and j + 1 < n and text[j + 1] == '/'):
j += 1
j += 2
continue
if text[j] == '{':
depth += 1
elif text[j] == '}':
depth -= 1
j += 1
brace_end = j - 1 # index of closing '}'
yield (brace_start, brace_end)
i = j
break
if text[j] == '=' or text[j] == ';':
# func ... = expr; (no body) or forward decl
i = j + 1
break
j += 1
else:
i = j
continue
i += 1
def count_returns_in_direct_body(text, body_start, body_end):
"""
Count statements that are directly inside this function body
(not inside nested functions). Returns list of (return_keyword_start,
return_keyword_end) positions.
"""
body = text[body_start + 1 : body_end] # content between { and }
offset = body_start + 1
return# First, find all nested func bodies within this body so we can skip them.
nested_ranges = []
for ns, ne in find_func_bodies(body):
# Adjust to absolute positions
nested_ranges.append((ns + offset, ne + offset))
def is_inside_nested(pos):
for ns, ne in nested_ranges:
if ns <= pos <= ne:
return True
return False
# Now scan for `return` keywords in the body, skipping nested funcs.
returns = []
i = 0
while i < len(body):
# Skip strings
if body[i] == '"':
i += 1
while i < len(body) and body[i] != '"':
if body[i] == '\\\\':
i += 1
i += 1
i += 1
continue
# Skip line comments
if body[i] == '/' and i + 1 < len(body) and body[i + 1] == '/':
i += 2
while i < len(body) and body[i] != '\':
i += 1
continue
# Skip block comments
if body[i] == '/' and i + 1 < len(body) and body[i + 1] == '*':
i += 2
while i < len(body) and not (body[i] == '*' and i + 1 < len(body) and body[i + 1] == '/'):
i += 1
i += 2
continue
# Check for 'return' keyword
if body[i:i+6] == 'return' and (i == 0 or not body[i-1].isalnum() and body[i-1] != '_'):
after = body[i+6:i+7] if i + 6 < len(body) else ''
if after == '' or not (after.isalnum() or after == '_'):
abs_pos = i + offset
if not is_inside_nested(abs_pos):
returns.append((abs_pos, abs_pos + 6))
i += 6
continue
i += 1
return returnsdef process_file(filepath):
with open(filepath, 'r') as f:
text = f.read()
original = text
removals = 0
# Collect all function bodies
func_bodies = list(find_func_bodies(text))
# For each function, check if it has exactly 1 return in its direct body
# Process in reverse order to preserve indices when editing
edits = [] # list of (start, end) of "return " to remove
for body_start, body_end in func_bodies:
returns = count_returns_in_direct_body(text, body_start, body_end)
if len(returns) == 1:
ret_start, ret_end = returns[0]
# Verify this return is the terminal statement of the direct body:
# scan past the return's expression (tracking brackets/strings/comments)
# to its terminating ';' (or body_end), then ensure only whitespace,
# comments, and optional semicolons remain before body_end.
n = len(text)
j = ret_end
depth = 0
stmt_end = body_end # position after the return statement
while j < body_end:
c = text[j]
if c == '"':
j += 1
while j < body_end and text[j] != '"':
if text[j] == '\\\\':
j += 1
j += 1
j += 1
continue
if c == '/' and j + 1 < body_end and text[j + 1] == '/':
j += 2
while j < body_end and text[j] != '\':
j += 1
continue
if c == '/' and j + 1 < body_end and text[j + 1] == '':
j += 2
while j < body_end and not (text[j] == '' and j + 1 < body_end and text[j + 1] == '/'):
j += 1
j += 2
continue
if c in '({[':
depth += 1
elif c in ')}]':
depth -= 1
elif c == ';' and depth == 0:
stmt_end = j + 1
break
elif c == '
' and depth == 0: # Statement terminated by newline (no semicolon) stmt_end = j break j += 1 else: stmt_end = body_end
' and depth == 0: # Statement terminated by newline (no semicolon) stmt_end = j break j += 1 else: stmt_end = body_end
# Now skip whitespace, comments, and stray semicolons after the return
k = stmt_end
terminal = True
while k < body_end:
c = text[k]
if c.isspace() or c == ';':
k += 1
continue
if c == '/' and k + 1 < body_end and text[k + 1] == '/':
k += 2
while k < body_end and text[k] != '\':
k += 1
continue
if c == '/' and k + 1 < body_end and text[k + 1] == '':
k += 2
while k < body_end and not (text[k] == '' and k + 1 < body_end and text[k + 1] == '/'):
k += 1
k += 2
continue
# Found other code after the return — not terminal.
terminal = False
break
if not terminal:
continue
# Remove "return " (keyword + trailing space)
if ret_end < len(text) and text[ret_end] == ' ':
edits.append((ret_start, ret_end + 1))
else:
edits.append((ret_start, ret_end))
# Apply edits in reverse order to preserve positions
edits.sort(key=lambda x: x[0], reverse=True)
for start, end in edits:
text = text[:start] + text[end:]
removals += 1
if text != original:
with open(filepath, 'w') as f:
f.write(text)
return removalsdef main():
total = 0
files_modified = 0
for src_dir in SRC_DIRS:
for filepath in sorted(glob.glob(os.path.join(src_dir, "**/*.mo"), recursive=True)):
r = process_file(filepath)
if r > 0:
print(f" {filepath}: {r} returns removed")
total += r
files_modified += 1
print(f"
Total: {total} returns removed in {files_modified} files")
Total: {total} returns removed in {files_modified} files")
if name == 'main':
main()
---B) Dot‑notation conversion
B) 点表示法转换
For all Motoko dot‑notation rules, automation scripts, and pitfalls, see the dedicated skill:
- skills/dot-notation-migration/SKILL.md
This file intentionally does not duplicate those instructions. Apply dot‑notation changes using the dedicated skill, then continue here with import cleanup (Section D) and import ordering (Section F).
关于Motoko点表示法的所有规则、自动化脚本和注意事项,请参考专门的技能文档:
- skills/dot-notation-migration/SKILL.md
本文档不会重复这些说明。请使用专门的技能完成点表示法变更,然后回到本文档继续进行导入清理(D部分)和导入排序(F部分)。
C) Dot‑notation import requirements
C) 点表示法的导入要求
For import mapping and rules related to dot‑notation, use the dedicated skill:
- skills/dot-notation-migration/SKILL.md
This file intentionally does not duplicate the import mapping. After applying dot‑notation changes per that skill, proceed with Section D (unused import cleanup) and Section F (import ordering).
关于点表示法的导入映射和规则,请使用专门的技能文档:
- skills/dot-notation-migration/SKILL.md
本文档不会重复导入映射内容。根据该技能完成点表示法变更后,继续进行D部分(未使用导入清理)和F部分(导入排序)。
D) Clean up unused imports (safely)
D) 安全清理未使用的导入
Goal
- Remove imports that are truly unused after prior refactors, but do not remove modules implicitly required by dot‑notation.
Reality check
- Editor tooling (VSCode Motoko extension) correctly marks unused imports, including dot‑notation awareness. CLI detection can be trickier.
Common false positives (imports that LOOK unused but are REQUIRED)
- — needed when
Blob,.toArray(),.size(),.isEmpty()are called on.hash()values (e.g.,Blob,Sha256.fromArray(...).toArray())hmac.sum().toArray() - — needed when
Array,.flatten(),.foldLeft(),.sliceToArray(),.map()etc. are called on.filter()values (e.g.,[T])[arr1, arr2].flatten() - — needed when
Natis called on.toText()values fromNat(e.g.,.size())arr.size().toText() - — needed when
VarArrayis called on.toArray()values[var T] - Rule: If ANY dot-notation method is called on a value of that module's type, the import is required even though the module name never appears explicitly in the code.
Approaches
-
Editor‑guided
- Open the workspace in VSCode. For each file, accept quick‑fix to remove imports marked as unused. Review diffs.
*.mo
- Open the workspace in VSCode. For each
-
Compiler/LSP‑assisted batch
- Use the Motoko language server via the VSCode extension to surface all diagnostics; apply code actions in batches where supported.
-
Script‑assisted conservative removal
- Write a simple script that:
- Parses each
import ... "mo:core/XYZ"; - Searches file for either or any of the known dot‑patterns mapped to
XYZ.(see Dot‑Notation Migration Skill import mapping)XYZ - If neither is found, flag the line as removable
- Parses each
- Manually review flagged lines before deletion
- Write a simple script that:
Audit helpers
- After cleanup, search for "import" lines whose module name never appears and no mapped dot‑pattern is present.
- Build the project. If a required module was removed, dot‑calls will fail at compile time — restore import and refine rules.
目标
- 移除之前重构后真正未使用的导入,但不要移除点表示法隐式依赖的模块。
实际情况
- 编辑器工具(VSCode Motoko扩展)能正确标记未使用的导入,包括对点表示法的识别。CLI检测则更复杂。
常见误判(看起来未使用但实际必需的导入)
- ——当对
Blob值调用Blob、.toArray()、.size()、.isEmpty()时需要(例如.hash()、Sha256.fromArray(...).toArray())hmac.sum().toArray() - ——当对
Array值调用[T]、.flatten()、.foldLeft()、.sliceToArray()、.map()等方法时需要(例如.filter())[arr1, arr2].flatten() - ——当对
Nat返回的.size()值调用Nat时需要(例如.toText())arr.size().toText() - ——当对
VarArray值调用[var T]时需要.toArray() - 规则:如果对该模块类型的任何值调用了点表示法方法,即使代码中从未显式出现模块名称,该导入也是必需的。
方法
-
编辑器引导
- 在VSCode中打开工作区。对每个文件,接受快速修复以移除标记为未使用的导入。检查差异。
*.mo
- 在VSCode中打开工作区。对每个
-
编译器/LSP辅助批量处理
- 通过VSCode扩展使用Motoko语言服务器显示所有诊断信息;在支持的情况下批量应用代码操作。
-
脚本辅助的保守移除
- 编写一个简单脚本:
- 解析每个语句
import ... "mo:core/XYZ"; - 在文件中搜索或任何映射到
XYZ.的已知点模式(参考点表示法迁移技能的导入映射)XYZ - 如果两者都未找到,则标记该行可移除
- 解析每个
- 删除前手动检查标记的行
- 编写一个简单脚本:
审计辅助工具
- 清理完成后,搜索模块名称从未出现且无对应点模式的"import"行。
- 构建项目。如果必需的模块被移除,点调用会在编译时失败——恢复导入并优化规则。
E) Shorten local (sibling) import paths
E) 缩短本地(同级)导入路径
-
Use bare module names for local imports when possible:instead of
"Bech32". Both resolve correctly, but bare names are more concise and idiomatic."./Bech32"motoko// Good import Bech32 "Bech32"; import Script "Script"; import Types "Types"; // Acceptable (cross-directory) import ByteUtils "../ByteUtils"; import Curves "../ec/Curves"; // Avoid (unnecessary ./ prefix for siblings) import Bech32 "./Bech32"; -
For cross-directory imports, relative paths withare required and acceptable.
../
-
尽可能对本地导入使用裸模块名:而非
"Bech32"。两种方式都能正确解析,但裸模块名更简洁且符合惯用风格。"./Bech32"motoko// 推荐 import Bech32 "Bech32"; import Script "Script"; import Types "Types"; // 可接受(跨目录) import ByteUtils "../ByteUtils"; import Curves "../ec/Curves"; // 避免(同级导入无需./前缀) import Bech32 "./Bech32"; -
对于跨目录导入,带的相对路径是必需且可接受的。
../
F) Aggregate and alphabetize imports by section
F) 按部分聚合并按字母排序导入
Why
- Consistent ordering reduces merge conflicts and speeds reviews. Clear grouping improves scanning and avoids mixing external modules with local ones.
Sections (in this order, each separated by a single blank line)
- mo:core imports
- All imports whose path starts with "mo:core/..." (including ).
mo:core/Types
- All imports whose path starts with "mo:core/..." (including
- Other mo:* third‑party imports (mops or similar)
- Any imports that are not
mo:...(e.g.,mo:core/...,mo:uuid/UUID, etc.).mo:sha2/SHA256
- Any
- Local project modules
- Bare module name imports like ,
"Bech32"(preferred), or relative path imports like"Common","../ByteUtils"."./Script" - Prefer bare module names without prefix for sibling imports (e.g.,
./instead of"Bech32"). Both work, but bare names are cleaner."./Bech32" - Sort local project module imports by the local name they are imported as, not by the module name in the imported path.
- Bare module name imports like
Sorting rules (apply within each section independently)
- Sort alphabetically by the local name modules are imported as
- Preserve import style (module vs. named type imports).
- Keep multiple named‑type imports from the same path on a single line as‑is.
- Optionally keep a comment header above each section (Core, Third‑party, Local) if your repo style prefers.
Example
motoko
// Before (mixed)
import Runtime "mo:core/Runtime";
import { type Result } "mo:core/Types";
import SHA256 "mo:sha2/SHA256";
import Map "mo:core/Map";
import BitVec "mo:bitvec/BitVec";
import Utils "../lib/Utils";
import Logger "./Logger";
// After (aggregated and sorted per section)
//// Core
import Map "mo:core/Map";
import Runtime "mo:core/Runtime";
import { type Result } "mo:core/Types";
//// Third‑party (mops)
import BitVec "mo:bitvec/BitVec";
import SHA256 "mo:sha2/SHA256";
//// Local
import Logger "Logger";
import Utils "../lib/Utils";Lightweight automation idea (per file)
- Collect all import lines at the file top.
- Partition into the three sections by path prefix.
- Sort each partition alphabetically by the local name they are imported as
- Re‑emit sections in the order Core → Third‑party → Local, with a blank line between sections.
- Keep any non‑import comments at their relative positions unless they clearly belong to a section header.
原因
- 一致的排序减少合并冲突,加快审核速度。清晰的分组提升扫描效率,避免外部模块与本地模块混合。
部分(按此顺序,各部分之间用一个空行分隔)
- mo:core导入
- 所有路径以"mo:core/..."开头的导入(包括)。
mo:core/Types
- 所有路径以"mo:core/..."开头的导入(包括
- 其他mo:*第三方导入(mops或类似源)
- 所有非的
mo:core/...导入(例如mo:...、mo:uuid/UUID等)。mo:sha2/SHA256
- 所有非
- 本地项目模块
- 裸模块名导入如、
"Bech32"(推荐),或相对路径导入如"Common"、"../ByteUtils"。"./Script" - 同级导入优先使用不带前缀的裸模块名(例如
./而非"Bech32")。两种方式都可行,但裸模块名更简洁。"./Bech32" - 本地项目模块导入按导入的本地名称排序,而非导入路径中的模块名。
- 裸模块名导入如
排序规则(在每个部分内独立应用)
- 按导入的本地名称字母排序
- 保留导入风格(模块导入 vs 命名类型导入)。
- 同一路径的多个命名类型导入保持在同一行不变。
- 如果你的仓库风格偏好,可在每个部分上方保留注释标题(Core、Third-party、Local)。
示例
motoko
// 优化前(混合)
import Runtime "mo:core/Runtime";
import { type Result } "mo:core/Types";
import SHA256 "mo:sha2/SHA256";
import Map "mo:core/Map";
import BitVec "mo:bitvec/BitVec";
import Utils "../lib/Utils";
import Logger "./Logger";
// 优化后(按部分聚合并排序)
//// Core
import Map "mo:core/Map";
import Runtime "mo:core/Runtime";
import { type Result } "mo:core/Types";
//// Third‑party (mops)
import BitVec "mo:bitvec/BitVec";
import SHA256 "mo:sha2/SHA256";
//// Local
import Logger "Logger";
import Utils "../lib/Utils";轻量级自动化思路(按文件处理)
- 收集文件顶部的所有import行。
- 按路径前缀分为三个部分。
- 每个部分按导入的本地名称字母排序
- 按Core → Third-party mo:* → Local的顺序重新输出各部分,部分之间插入空行。
- 保留非导入注释的相对位置,除非它们明显属于某个部分标题。
G) Direct string‑to‑Blob assignment for constants
G) 常量的直接字符串到Blob赋值
Pattern
motoko
// Before
let b : Blob = Text.encodeUtf8("hello");
// After
let b : Blob = "hello";Why
- For constant Text strings, the Motoko compiler allows direct assignment to the type.
Blob - The result is identical to , but the code is cleaner and avoids an explicit function call.
Text.encodeUtf8
Examples
motoko
// Good: direct assignment
let blobs = [
"strategy",
Text.encodeUtf8(Nat.toText(slot)),
];
// Avoid: redundant encoding for constant string
let blobs = [
Text.encodeUtf8("strategy"),
Text.encodeUtf8(Nat.toText(slot)),
];模式
motoko
// 优化前
let b : Blob = Text.encodeUtf8("hello");
// 优化后
let b : Blob = "hello";原因
- 对于常量Text字符串,Motoko编译器允许直接赋值给类型。
Blob - 结果与完全相同,但代码更简洁,无需显式函数调用。
Text.encodeUtf8
示例
motoko
// 推荐:直接赋值
let blobs = [
"strategy",
Text.encodeUtf8(Nat.toText(slot)),
];
// 避免:常量字符串的冗余编码
let blobs = [
Text.encodeUtf8("strategy"),
Text.encodeUtf8(Nat.toText(slot)),
];Practical Automation Recipes (opt‑in)
实用自动化方案(可选)
These are optional starting points. Prefer editor‑integrated refactors when available. Always review diffs.
- Find one‑line return functions
bash
rg -n --glob '!**/.mops/**' --glob '**/*.mo' "func [A-Za-z_][A-Za-z0-9_]*\(.*\) *:.*\{ *return .*; *};"- Dot‑notation conversion & candidate detection
- See the dedicated skill for full automation and grep recipes:
- skills/dot-notation-migration/SKILL.md
- Flag possibly unused core imports (conservative)
bash
undefined这些是可选的起点。优先使用编辑器集成的重构工具。始终检查差异。
- 查找单行return函数
bash
rg -n --glob '!**/.mops/**' --glob '**/*.mo' "func [A-Za-z_][A-Za-z0-9_]*\\(.*\\) *:.*\\{ *return .*; *};"- 点表示法转换与候选检测
- 完整的自动化和grep方案请参考专门的技能文档:
- skills/dot-notation-migration/SKILL.md
- 标记可能未使用的core导入(保守策略)
bash
undefinedRough heuristic: list imports, then search for name or dot‑patterns
粗略的启发式方法:列出导入,然后搜索名称或点模式
rg -n --glob '!/.mops/' --glob '**/.mo' '^import ."mo:core/([A-Za-z/]+)";' -o -r '$1'
rg -n --glob '!/.mops/' --glob '**/.mo' '^import ."mo:core/([A-Za-z/]+)";' -o -r '$1'
For each file, ensure presence of module references OR mapped dot‑patterns before removal
对每个文件,在移除前确保存在模块引用或对应的点模式
4) Aggregate + sort imports into sections (editor macro)
- Select all `import` lines at the top of the file → group into three sections (Core, Third‑party mo:*, Local) → sort each group alphabetically by path → insert blank lines between sections → keep import styles as‑is.
---
4) 聚合+排序导入到各部分(编辑器宏)
- 选择文件顶部的所有`import`行 → 分为三个部分(Core、Third-party mo:*、Local) → 每个部分按路径字母排序 → 部分之间插入空行 → 保留导入风格不变。
---Agent Strategy (for AI assistants)
AI助手策略
- Confirm the project builds on before starting improvements.
mo:core - Work file‑by‑file. For each file:
- A. Remove single‑expression forms
return - B. Apply dot‑notation per skills/dot-notation-migration/SKILL.md
- C. Ensure required imports for any introduced dot‑notation (see import mapping in skills/dot-notation-migration/SKILL.md)
- D. Remove truly unused imports (respect the dot‑notation import mapping from the dedicated skill)
- E. Shorten local (sibling) import paths (remove prefix where applicable)
./ - F. Aggregate imports into the three sections and sort each section alphabetically (Core → Third‑party mo:* → Local)
- G. Replace with
Text.encodeUtf8("<literal>")where the target type is"<literal>".Blob
- A. Remove single‑expression
- After each file: compile; if failure due to missing import, restore and mark mapping
- After each category across repo: run a full build and optionally tests
- Produce a short report of changes and any edge cases deferred for manual review
- 开始改进前,确认项目基于可构建。
mo:core - 逐个文件处理。对每个文件:
- A. 移除单表达式形式
return - B. 根据skills/dot-notation-migration/SKILL.md应用点表示法
- C. 确保任何引入的点表示法所需的导入(参考skills/dot-notation-migration/SKILL.md中的导入映射)
- D. 移除真正未使用的导入(遵循专门技能中的点表示法导入映射)
- E. 缩短本地(同级)导入路径(在适用情况下移除前缀)
./ - F. 将导入内容归为三个部分并按字母排序(Core → Third-party mo:* → Local)
- G. 当目标类型为时,将
Blob替换为Text.encodeUtf8("<literal>")。"<literal>"
- A. 移除单表达式
- 每个文件处理完成后:编译;如果因缺失导入导致失败,恢复导入并标记映射
- 完成仓库中每一类改进后:运行完整构建,可选运行测试
- 生成简短的变更报告,以及任何推迟手动审核的边缘情况
H) Convert Array.fromVarArray(x)
to x.toArray()
Array.fromVarArray(x)x.toArray()H) 将Array.fromVarArray(x)
转换为x.toArray()
Array.fromVarArray(x)x.toArray()Pattern
motoko
// Before
Array.fromVarArray(buf)
Array.fromVarArray<Nat8>(buf)
// After
buf.toArray()Notes
- is a factory function (first param is NOT
Array.fromVarArray), so the dot-notation migration script does NOT convert it automatically. This is a separate conversion.self - is the dot-notation equivalent — it's defined on
VarArray.toArray()values.[var T] - After conversion, check if import can be removed (it may still be needed for
Array,Array.tabulatedot-notation onArray.flatten, etc.)[T] - Strip optional type params: →
Array.fromVarArray<Nat8>(buf)(the type is inferred from the var array).buf.toArray()
模式
motoko
// 优化前
Array.fromVarArray(buf)
Array.fromVarArray<Nat8>(buf)
// 优化后
buf.toArray()注意事项
- 是工厂函数(第一个参数不是
Array.fromVarArray),因此点表示法迁移脚本不会自动转换它。这是一个单独的转换操作。self - 是点表示法的等效形式——它定义在
VarArray.toArray()值上。[var T] - 转换后,检查是否可以移除导入(可能仍需要它用于
Array、Array.tabulate上的点表示法[T]等)Array.flatten - 移除可选的类型参数:→
Array.fromVarArray<Nat8>(buf)(类型可从可变数组推断)。buf.toArray()
I) Array.tabulate
type annotations are usually required
Array.tabulateI) Array.tabulate
通常需要类型注解
Array.tabulate- Do NOT remove type annotations from calls.
Array.tabulate<T>(...) - The Motoko compiler often cannot infer the element type, especially when the callback uses , arithmetic, or other expressions that could return multiple numeric types.
fromNat - Removing annotations caused 13 of 24 test files to fail in a real project with errors like .
expression of type [Any] cannot produce expected type [Nat8] - Keep them: .
Array.tabulate<Nat8>(n, func i { ... })
- 不要移除调用中的类型注解。
Array.tabulate<T>(...) - Motoko编译器通常无法推断元素类型,尤其是当回调使用、算术运算或其他可能返回多种数值类型的表达式时。
fromNat - 在实际项目中,移除注解导致24个测试文件中的13个失败,错误如。
expression of type [Any] cannot produce expected type [Nat8] - 保留类型注解:。
Array.tabulate<Nat8>(n, func i { ... })
Edge Cases & Gotchas
边缘情况与注意事项
- For all dot‑notation behavior, method availability, factories vs methods, and mutability notes, see:
- skills/dot-notation-migration/SKILL.md
- When aggregating imports, keep named type imports from within the
mo:core/Typesgroup; see Section F for ordering rules.mo:core - Local imports: prefer bare module names () over relative paths (
"Bech32") for sibling files. Both resolve correctly but bare names are more concise."./Bech32" - Import paths like from within
"../src/Bech32"are incorrect — usesrc/for siblings or"Bech32"for cross-directory references."../SubDir/Module"
- 关于点表示法的行为、方法可用性、工厂函数vs方法、可变性说明,请参考:
- skills/dot-notation-migration/SKILL.md
- 聚合导入时,将来自的命名类型导入保留在
mo:core/Types组内;参考F部分的排序规则。mo:core - 本地导入:同级文件优先使用裸模块名()而非相对路径(
"Bech32")。两种方式都能正确解析,但裸模块名更简洁。"./Bech32" - 从内部导入
src/这类路径是错误的——同级文件使用"../src/Bech32",跨目录引用使用"Bech32"。"../SubDir/Module"
Verification & Sign‑off
验证与验收
- Build all canisters successfully after changes.
- Run static audits:
- No lines flagged unused by editor or heuristic scripts (after accounting for dot‑notation needs).
import - Spot check: arrays, maps, sets, text operations use dot‑notation where natural.
- No
- Diffs remain mechanical; no public API or behavioral changes.
- 变更后所有canister成功构建。
- 运行静态审计:
- 编辑器或启发式脚本未标记未使用的行(已考虑点表示法需求)。
import - 抽查:数组、映射、集合、文本操作在合理场景下使用点表示法。
- 编辑器或启发式脚本未标记未使用的
- 差异仅为机械性变更;无公共API或行为变化。
Appendix: Dot‑notation reference
附录:点表示法参考
For the complete, maintained dot‑notation catalog, automation scripts, and import mapping, see:
- skills/dot-notation-migration/SKILL.md
完整的、维护中的点表示法目录、自动化脚本和导入映射,请参考:
- skills/dot-notation-migration/SKILL.md ",