template/.planning/codebase/CONVENTIONS.md

203 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Coding Conventions
**Analysis Date:** 2026-09-07
> Note: this is a Chinese-language game-engine monorepo (魔塔 / Mota). All in-code comments and docs are written in Chinese, and convention rules are codified in `dev.md` and `.agents/code.md`. Those two files are the source of truth; this document distills them into prescriptive rules for the executor.
## Naming Patterns
The canonical naming table is in `dev.md` under "命名规则".
**Files:**
- Source files (`.ts`, `.tsx`, `.vue`): **camelCase** — e.g. `dirtyTracker.ts`, `faceManager.ts`, `mapStore.ts`
- Markdown doc files: **kebab-case** — e.g. `face-manager.md`, `hero-equipment.md`
- Barrel/entry files are always `index.ts` and `types.ts` (a package's public types live in `types.ts`)
**Functions / Methods / Variables / Members / general constants:** **camelCase**
- `getDamageInfo()`, `setPos()`, `markAllDirty()`, `moveQueue`, `dirtyFlag`
**Classes / Interfaces / Type aliases / Namespaces / Generics / Enums / Components:** **PascalCase**
- `DamageSystem`, `IObjectMover`, `ObjectMoveType`, `LogLevel`, `IDataCommon`
**Immutable constants:** **UPPER_SNAKE_CASE** — e.g. `MAX_COUNT`
**Acronyms (HTTP, URI, etc.):** all-caps
**Interfaces intended to be `implements`-ed:** prefixed with capital `I` — e.g. `IObjectMover`, `IDamageSystem`, `IHookable`, `IDataCommon`
**HTML/CSS `id` / `class`:** kebab-case — e.g. `box-main`, `ui-list`, `border-vertical`
**Never** use underscore prefix for private members/methods. Unused variables/methods use a leading `_` (e.g. `_param`) so they pass the `no-unused-vars` rule.
## Code Style
**Formatting (Prettier 3.8.1) — config in `.prettierrc`:**
```json
{
"printWidth": 80,
"tabWidth": 4,
"useTabs": false,
"semi": true,
"singleQuote": true,
"quoteProps": "as-needed",
"bracketSpacing": true,
"vueIndentScriptAndStyle": false,
"arrowParens": "avoid",
"trailingComma": "none",
"endOfLine": "crlf"
}
```
- 4-space indent, single quotes, no trailing commas, `arrowParens: avoid` (e.g. `v => v.x`), **CRLF line endings**
- `.prettierignore` excludes generated/build files (`dist/`, `public/project/*.js`, `script/**/*.js`, `docs/.vitepress/dist`, etc.)
**Linting (ESLint 9 flat config) — `eslint.config.js`:**
- Uses `@eslint/js` recommended, `typescript-eslint` recommended, `eslint-plugin-vue` `flat/recommended`, and `eslint-plugin-prettier/recommended` (prettier as the last rule set, so prettier wins)
- `eslint-plugin-react` is loaded for `**/*.{ts,tsx,vue}` files (for JSX/TSX)
- Key rules applied across `**/*.{js,mjs,cjs,vue}`:
- `no-console`: `warn`
- `eqeqeq`: `['error', 'always']` (always `===`)
- Key rules for `**/*.{ts,tsx,vue}`:
- `@typescript-eslint/no-empty-object-type`: `off`
- `@typescript-eslint/no-explicit-any`: `off`
- `@typescript-eslint/no-namespace`: `off`
- `@typescript-eslint/no-this-alias`: `off`
- `@typescript-eslint/no-unused-vars`: `error` with `argsIgnorePattern: '^_'`, `caughtErrorsIgnorePattern: '^_'`, `varsIgnorePattern: '^_'`, `ignoreRestSiblings: true`
- `vue/multi-word-component-names`: `off`
- `vue/no-mutating-props`: `error` with `shallowOnly: true`
- `react/jsx-boolean-value`: `['error', 'never']`
- Ignores: `node_modules`, `dist`, `public`
**Lint scripts** (`package.json`): `pnpm lint:packages` (`eslint packages/`), `pnpm lint:user` (`eslint packages-user/`), `pnpm lint:custom` (bare `eslint`)
## Import Organization
**Order (not enforced by a plugin, but observed in practice):** third-party libraries first, then aliased `@motajs/*` / `@user/*` imports, then relative `./` imports. Example from `packages-user/data-system/src/combat/damage.ts`:
```ts
import { clamp } from 'lodash-es';
import { ITileLocator, logger } from '@motajs/common';
import { ... } from './types';
import { ... } from '@user/data-base';
```
**Path Aliases** (defined in `tsconfig.json` and `vite.config.ts`):
- `@motajs/*``packages/*/src` (core engine)
- `@user/*``packages-user/*/src` (user code)
**No `import type`:** per `dev.md` "无类型导入", all imports are normal value imports. The only sanctioned exception is the module-interface registration file `packages-user/entry-data/src/mota.ts`, which uses `import type * as X` deliberately (it only needs types to build the `ModuleInterface` map). Do not introduce `import type` in new code.
**Barrel exports:** each package exposes `index.ts` with `export * from './...'` for its subfolders. Do not re-export content from outside the current package (`dev.md` "不转发导出").
## Error Handling
**Core principle:** errors/warnings are reported through the `logger` singleton — never silently swallowed via `return null` / `return false`.
**Logger** (`packages/common/src/logger.ts`):
- `logger.error(code, ...params)` — fatal-adjacent errors, each with a unique numeric `code`
- `logger.warn(code, ...params)` — warnings, unique numeric `code`
- `logger.log(text)` — informational
- `logger.catch(fn)` — runs `fn` while capturing any errors/warnings it emits, returns `{ ret, info }` without throwing (see `packages/common/src/logger.ts:189`)
- `logger.disable()` / `logger.enable()`
**Error codes are data, not code:** all messages live in `packages/common/src/logger.json`, keyed by `error` / `warn` maps of `code -> message`. Messages use `$1`, `$2` positional placeholders substituted by the params passed to `error`/`warn`. Codes are unique and never reused; do not use code `0`. Internal meta-error for a missing message is code `16` (`logger.error(16, ...)`).
Example usage:
```ts
if (!this.calculator) {
logger.warn(106);
return null;
}
```
```ts
if (!obj) {
logger.warn(85);
return;
}
```
**The logger never throws and never interrupts execution.** It is designed so a warning/error does not break the game loop or replay verification.
## Logging
**Framework:** the custom `logger` (above), plus `console` directly for debug/tooling in `script/` files. `no-console` is `warn`-level so plain `console.log` in scripts is tolerated but discouraged in library code.
**Patterns:**
- Library/engine code: use `logger.error/warn/log` with a registered code. Do not `throw`.
- A genuine programming fault that must halt (e.g. unknown module in `Mota.require`) may `throw new Error(...)` — see `packages-user/entry-data/src/mota.ts:96`.
## Comments
All comments are written in **Chinese**. Guidelines from `dev.md` "注释规范" and `.agents/code.md` "注释":
- **Public methods/interfaces/members** get jsDoc comments **at the source** (usually the `interface`). Inherited / `implements`-ed members do **not** repeat the comment unless the semantics change.
- **Private methods and private members must be commented** (jsDoc), and private method params must be commented. Exception: constructor parameter-property declarations.
- **Method jsDoc uses multi-line style**; **member jsDoc uses single-line style** when short.
- **No comment on constructors.** No comment on the `interface`/`type alias`/`enum`/`class` itself (only its members).
- **TODO format:** `// TODO:` or `// todo:`.
- Single-line comments: `//` followed by one space. No non-jsDoc multi-line comments — use multiple single-line comments instead.
- **`#region` / `#endregion`** partition long files by function — see `packages/common/src/types.ts`, `packages-user/data-common/src/common/mover.ts`, `packages/common/src/utils/types.ts`.
- Wrap comments reasonably (Chinese chars are wide): ~4060 chars per line, break at punctuation, keep lines roughly even, no mid-sentence breaks.
- Comments must add value (explain *why* the next line exists), not restate the code (e.g. `// 清空 Xxx` is disallowed).
Example jsDoc (member, single-line):
```ts
/** 怪物生命值 */
hp: number;
```
Example jsDoc (method, multi-line):
```ts
/**
* 创建只读信息对象
* @param enemy 怪物对象
* @param locator 怪物位置
* @param hero 勇士属性对象
*/
```
## Function Design
**Size:** no hard limit, but single-responsibility is expected. Long classes are partitioned with `#region`.
**Parameters:**
- More than 2 optional params → switch to an object param.
- Unused trailing params are omitted, not named `_` (in method implementations).
- `{@link}` references used in jsDoc to cross-reference related members.
**Return Values:**
- Builder-style chaining methods return `this` (e.g. `step()`, `speed()`, `face()` in `packages-user/data-common/src/common/mover.ts`).
- "May not be available" results return `T | null` and call `logger.warn` rather than throwing.
**Design rules (from `.agents/code.md`):**
- Complete `if - else` when both branches must do work — no early `return` to fake an `else` for same-level conditions.
- Minimal abstraction: local repetition is allowed; do not add indirection just to reduce line count.
- Do not define local functions inside a function unless a function argument is required.
- Avoid `getter`/`setter` (only for operator-method scenarios).
- Avoid `?.` except (1) side-effect calls like `this.obj?.func()`, (2) object "Required"-ification like `{ value: obj?.value ?? 0 }`.
- Do not line-break ternary expressions or `private readonly` members.
- Single-property destructuring is disallowed — write `const value = obj.value` instead of `const { value } = obj`.
## Module Design
**Exports:** barrel `index.ts` with `export * from './subdir'` and `export * from './types'`. Each package's public types are in `types.ts`.
**Module principles (`dev.md` "模块原则"):**
- **No side effects** in modules: only function/class/constant declarations; no exported `let`/`var`, no top-level execution.
- **No circular imports** (checked by `pnpm check:circular` via `madge`; config `.madgerc`).
- **No re-export** of content outside the current package.
- **One class per file.** Multiple small implementations of the same interface may share a file only with explicit approval.
**Type rules (`dev.md` "类型规范"):**
- No unnecessary `any` (though `no-explicit-any` is `off`, it's still discouraged).
- All class members have explicit type annotations.
- Unavoidable type errors → `// @ts-expect-error` + explanation (see `packages-user/entry-data/src/mota.ts:137`).
- Avoid `as`; never chain `as unknown as`.
- Function types → separate `type` alias (unless <20 chars).
- Object types separate `interface`, never an inline object type.
- Object members use interface types, not class types (`map: IGameMap` not `map: GameMap`).
- Enums use `const enum` for zero-runtime-cost (e.g. `LogLevel`, `ObjectMoveType`, `ObjectSpecialStep`).
**Architecture constraint:** rendering side never pushes updates to the data side; it only observes via hooks. Data-side code calling render-side code must wrap it in `Mota.r(() => {})` / `Mota.rf(fn)` (see `packages-user/entry-data/src/mota.ts`).
---
*Convention analysis: 2026-09-07*