Skip to content

Commit 7a2addc

Browse files
committed
Code Review by Claude Code.
1 parent 14428f1 commit 7a2addc

File tree

7 files changed

+289
-3
lines changed

7 files changed

+289
-3
lines changed

.claude/settings.local.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"mcp__serena__list_dir",
5+
"mcp__serena__activate_project",
6+
"mcp__serena__read_file",
7+
"mcp__serena__get_symbols_overview",
8+
"Bash(npm test)",
9+
"mcp__serena__find_symbol",
10+
"mcp__serena__search_for_pattern",
11+
"mcp__serena__insert_before_symbol",
12+
"mcp__serena__replace_regex",
13+
"mcp__serena__insert_after_symbol"
14+
],
15+
"deny": [],
16+
"ask": []
17+
}
18+
}

.npmignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33
/src
44
tsconfig.json
55
.idea
6+
.claude
7+
.serena

.serena/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/cache

.serena/project.yml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# language of the project (csharp, python, rust, java, typescript, go, cpp, or ruby)
2+
# * For C, use cpp
3+
# * For JavaScript, use typescript
4+
# Special requirements:
5+
# * csharp: Requires the presence of a .sln file in the project folder.
6+
language: typescript
7+
8+
# whether to use the project's gitignore file to ignore files
9+
# Added on 2025-04-07
10+
ignore_all_files_in_gitignore: true
11+
# list of additional paths to ignore
12+
# same syntax as gitignore, so you can use * and **
13+
# Was previously called `ignored_dirs`, please update your config if you are using that.
14+
# Added (renamed) on 2025-04-07
15+
ignored_paths: []
16+
17+
# whether the project is in read-only mode
18+
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
19+
# Added on 2025-04-18
20+
read_only: false
21+
22+
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
23+
# Below is the complete list of tools for convenience.
24+
# To make sure you have the latest list of tools, and to view their descriptions,
25+
# execute `uv run scripts/print_tool_overview.py`.
26+
#
27+
# * `activate_project`: Activates a project by name.
28+
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
29+
# * `create_text_file`: Creates/overwrites a file in the project directory.
30+
# * `delete_lines`: Deletes a range of lines within a file.
31+
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
32+
# * `execute_shell_command`: Executes a shell command.
33+
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
34+
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
35+
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
36+
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
37+
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
38+
# * `initial_instructions`: Gets the initial instructions for the current project.
39+
# Should only be used in settings where the system prompt cannot be set,
40+
# e.g. in clients you have no control over, like Claude Desktop.
41+
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
42+
# * `insert_at_line`: Inserts content at a given line in a file.
43+
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
44+
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
45+
# * `list_memories`: Lists memories in Serena's project-specific memory store.
46+
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
47+
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
48+
# * `read_file`: Reads a file within the project directory.
49+
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
50+
# * `remove_project`: Removes a project from the Serena configuration.
51+
# * `replace_lines`: Replaces a range of lines within a file with new content.
52+
# * `replace_symbol_body`: Replaces the full definition of a symbol.
53+
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
54+
# * `search_for_pattern`: Performs a search for a pattern in the project.
55+
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
56+
# * `switch_modes`: Activates modes by providing a list of their names
57+
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
58+
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
59+
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
60+
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
61+
excluded_tools: []
62+
63+
# initial prompt for the project. It will always be given to the LLM upon activating the project
64+
# (contrary to the memories, which are loaded on demand).
65+
initial_prompt: ""
66+
67+
project_name: "attrpath"

CLAUDE.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# CLAUDE.md
2+
3+
このファイルは、このリポジトリで作業する際にClaude Code (claude.ai/code) へのガイダンスを提供します。
4+
5+
## コマンド
6+
7+
### ビルド
8+
```bash
9+
npm run build # CommonJSとESMモジュールの両方をビルド
10+
npm run build:common # CommonJSモジュールのみをビルド
11+
npm run build:esm # ESMモジュールのみをビルド
12+
```
13+
14+
### テスト
15+
```bash
16+
npm test # Jestで全てのテストを実行
17+
npm run test # 上記と同じ
18+
```
19+
20+
### ドキュメント
21+
```bash
22+
npm run doc # docs/typedoc/ にTypeDocドキュメントを生成
23+
```
24+
25+
### 開発ワークフロー
26+
```bash
27+
npm run prepare # 自動的にビルドを実行(npm installで実行される)
28+
```
29+
30+
## プロジェクト構成
31+
32+
AttrPathは、文字列記法を使用してJavaScriptオブジェクトの属性パスを安全に辿るためのTypeScriptライブラリです。コアアーキテクチャは以下で構成されています:
33+
34+
### コアコンポーネント
35+
36+
**AttrPath (src/index.ts)** - 静的メソッドを持つメインAPIクラス:
37+
- `traverse(target, path, default_value?)` - オブジェクトパスを安全にナビゲート
38+
- `update(target, path, value)` - 特定のパスで値を更新
39+
- `is_valid(path)` - パス構文を検証
40+
41+
**パーサーシステム (src/parser.ts)**
42+
- `AttributeParser` - 属性パス文字列のメインパーサー
43+
- `FormulaParser` - 数式表現の拡張パーサー
44+
- `BaseParser` - 共有パーサー機能
45+
- `TokenType` - パーシング用のトークンタイプを定義するEnum
46+
47+
**ハンドラーシステム (src/handler.ts)**
48+
- `ValueHandler` - パス走査中に値を抽出
49+
- `Updater` - パス走査中に値を更新
50+
- `BaseHandler` - ハンドラー実装の抽象ベース
51+
52+
**ストリーム処理 (src/stream.ts)**
53+
- `ParserStream` - パス解析のための文字ストリーム処理
54+
55+
**ユーティリティ (src/base.ts)**
56+
- 型チェックユーティリティ(`isNumber``isContainer``isValue`
57+
58+
### パス構文
59+
60+
ライブラリは複雑なパス表現をサポートします:
61+
- オブジェクトプロパティ:`.property` または `['property']`
62+
- 配列インデックス:`[0]` または `[index]`
63+
- 混合パス:`.children.john.hobby[1].name`
64+
- ドット付きキー:`['children.john']`
65+
66+
### ビルドシステム
67+
68+
デュアルモジュールサポート:
69+
- **CommonJS**`tsconfig.json`を使用して`dist/`にビルド
70+
- **ESM**`tsconfig.esm.json`を使用して`dist/esm/`にビルド
71+
72+
両方のビルドにはTypeScript宣言とソースマップが含まれます。
73+
74+
### テスト
75+
76+
- **フレームワーク**:ts-jest transformerを使用したJest
77+
- **カバレッジ**`docs/coverage/`に出力で有効
78+
- **テストファイル**`src/test.ts`に配置
79+
- **設定**`jest.config.ts`
80+
81+
テストスイートは、コアAPI、パーシングロジック、および安全な走査のエッジケースをカバーしています。

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "attrpath",
3-
"version": "0.5.6",
3+
"version": "0.6.0",
44
"description": "Attribute Path Traverser.",
55
"main": "dist/index.js",
66
"module": "dist/esm/index.js",

src/test.ts

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,57 @@ describe('Parts', () => {
221221

222222
const _handler: ValueHandler = new ValueHandler({});
223223

224+
// is_term メソッドを公開してテスト用に利用
225+
class TermParser extends AttributeParser {
226+
constructor(handler: BaseHandler | null, stream: ParserStream) {
227+
super(handler, stream);
228+
}
229+
230+
public is_term(): boolean {
231+
return super.is_term();
232+
}
233+
234+
public is_factor(): boolean {
235+
return super.is_factor();
236+
}
237+
}
238+
239+
// is_expr メソッドも公開してより詳細なテスト
240+
class ExprParser extends AttributeParser {
241+
constructor(handler: BaseHandler | null, stream: ParserStream) {
242+
super(handler, stream);
243+
}
244+
245+
public is_expr(): boolean {
246+
return super.is_expr();
247+
}
248+
249+
public is_term(): boolean {
250+
return super.is_term();
251+
}
252+
253+
public is_factor(): boolean {
254+
return super.is_factor();
255+
}
256+
}
257+
258+
// parser.ts:213-214の確実なカバレッジ: is_factor()がfalseを返すケース
259+
// 重要: 演算子の後にis_factor()がfalseになるが、すでにresult=trueのケース
260+
const testCases = [
261+
"3*", "4/", "5*xyz", "6/xyz", // xyzは無効なfactor
262+
"7* ", "8/ ", "9*!", "10/!" // スペースや記号は無効なfactor
263+
];
264+
testCases.forEach(testCase => {
265+
expect(new ExprParser(_handler, new ParserStream(testCase)).is_expr()).toBe(true);
266+
});
267+
268+
// 未カバー箇所のテスト: 乗算・除算演算子の後にfactorが続かない場合
269+
// parser.ts:213-214の完全カバレッジのため、is_factor()がfalseになるケースをテスト
270+
expect(new TermParser(_handler, new ParserStream("1*(")).is_term()).toBe(true); // (で始まるが不完全
271+
expect(new TermParser(_handler, new ParserStream("1/+")).is_term()).toBe(true); // +は無効なfactor開始
272+
expect(new TermParser(_handler, new ParserStream("1* ")).is_term()).toBe(true); // スペースの後に何もない
273+
expect(new TermParser(_handler, new ParserStream("1/ ")).is_term()).toBe(true); // 複数スペースの後に何もない
274+
224275
class FormParser extends AttributeParser {
225276

226277
constructor(handler: BaseHandler | null, stream: ParserStream) {
@@ -318,6 +369,73 @@ describe('ESModule', () => {
318369
expect(AttrPath.traverse({}, '.path')).toBeUndefined();
319370
});
320371

372+
it('Performance', () => {
373+
374+
const value = {
375+
children: {
376+
john: {
377+
hobby: [{name: "Cycling"}, {name: "Dance"}],
378+
pet: [{type: "dog", name: "Max"}]
379+
},
380+
"花子": {
381+
hobby: [{name: "Squash"}],
382+
pet: [{type: "cat", name: "Chloe"}]
383+
}
384+
}
385+
};
386+
387+
const start = process.hrtime();
388+
AttrPath.traverse(value, '.children')
389+
AttrPath.traverse(value, '.children.john')
390+
AttrPath.traverse(value, '.children.john.hobby')
391+
AttrPath.traverse(value, '.children.john.hobby[0]')
392+
AttrPath.traverse(value, '.children.john.hobby[0].name')
393+
AttrPath.traverse(value, '.children.john.hobby[0a].name')
394+
AttrPath.traverse(value, '.children.john.hobby[1].name')
395+
AttrPath.traverse(value, '.children.john.pet[0].type')
396+
AttrPath.traverse(value, '.children.john.pet[0].name')
397+
AttrPath.traverse(value, '.children.花子.hobby[0].name')
398+
AttrPath.traverse(value, '.children.花子.pet[0].type')
399+
AttrPath.traverse(value, '.children.花子.pet[0].name')
400+
AttrPath.traverse(value, '.children.john.hobby["0"].name')
401+
AttrPath.traverse(value, '.children.john.hobby["0"].name', "no name")
402+
AttrPath.traverse(value, '.children["john"].hobby[0].name')
403+
AttrPath.traverse(value, '.children["john"].hobby[0]["name"]')
404+
AttrPath.traverse(value, '["children"]["john"].hobby[0].name')
405+
AttrPath.traverse(value, '.children["john"].hobby[0].name')
406+
AttrPath.traverse(value, '["children"]["john"]["hobby"][0]["name"]')
407+
AttrPath.traverse(value, '["children"]["john"]["hobby"][0].["name"]')
408+
AttrPath.traverse(value, '["children"]["john"]["hobby"][0]["name"]')
409+
AttrPath.traverse(value, '.children["john"].hobby[1].name')
410+
411+
AttrPath.traverse([1], '[0]')
412+
AttrPath.traverse(AttrPath.traverse(value, '.children.john'), '.hobby')
413+
AttrPath.traverse(null, '.path')
414+
AttrPath.traverse(null, '.path.path')
415+
AttrPath.traverse(null, '["path"]')
416+
AttrPath.traverse(null, '.path["path"]')
417+
AttrPath.traverse(null, '["0"]')
418+
AttrPath.traverse(null, '[0]')
419+
AttrPath.traverse([1, 2, 3], '[3]')
420+
421+
AttrPath.traverse(undefined, '.path')
422+
423+
AttrPath.traverse(false, '.path')
424+
AttrPath.traverse(true, '.path')
425+
AttrPath.traverse(NaN, '.path')
426+
AttrPath.traverse(Infinity, '.path')
427+
AttrPath.traverse(0, '.path')
428+
AttrPath.traverse(-1, '.path')
429+
AttrPath.traverse("", '.path')
430+
AttrPath.traverse("1", '.path')
431+
AttrPath.traverse([1], '.path')
432+
433+
AttrPath.traverse({}, '.path')
434+
435+
const end = process.hrtime(start);
436+
console.log((end[1] / 1000000) + "ms");
437+
});
438+
321439
it('Key', () => {
322440

323441
const value = {
@@ -497,14 +615,12 @@ describe('ESModule', () => {
497615
});
498616

499617
it('Valid', () => {
500-
501618
expect(AttrPath.is_valid('[1]')).toBe(true);
502619
expect(AttrPath.is_valid('["john"]')).toBe(true);
503620
expect(AttrPath.is_valid('.children["john"].hobby[1].name')).toBe(true);
504621
expect(AttrPath.is_valid('.children["john"].hobby[1a].name')).toBe(false);
505622
expect(AttrPath.is_valid('.children["john"].hobby["1"].name')).toBe(false);
506623
expect(AttrPath.is_valid('this.name')).toBe(false);
507-
508624
});
509625

510626
it('Classes', () => {
@@ -539,6 +655,7 @@ describe('ESModule', () => {
539655
return new AttributeParser(null, new ParserStream(path)).parse_path();
540656
}
541657

658+
542659
expect(Traverse(value, '.children')).toStrictEqual({"john": {"hobby": [{"name": "Cycling"}, {"name": "Dance"}], "pet": [{"type": "dog", "name": "Max"}]}, "花子": {"hobby": [{"name": "Squash"}], "pet": [{"type": "cat", "name": "Chloe"}]}, _jack$: {_hobby$: [{$name: "Fury"}], $pet$: [{type_: "bat", $name: "Dread"}]}});
543660
expect(Traverse(value, '.children.john')).toStrictEqual({"hobby": [{"name": "Cycling"}, {"name": "Dance"}], "pet": [{"type": "dog", "name": "Max"}]});
544661
expect(Traverse(value, '.children.john.hobby')).toStrictEqual([{"name": "Cycling"}, {"name": "Dance"}]);

0 commit comments

Comments
 (0)