Skip to content

Commit 7c3ec7c

Browse files
makinzmclaude
andauthored
feat: name_deny で文字列リテラルをチェック (StringLiteral) (#72)
* [test] add NameKind::StringLiteral + ParsedNames::string_literals because of name_deny must check string literals Phase 1: Domain型追加。ParsedNames のコンパイルタイムガードにより 全パーサーでコンパイルエラーが発生する状態(RED)。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * [test] add StringLiteral naming violation tests because of name_deny must cover string literals - string literal が name_deny でマッチしたとき NamingViolation が出るテスト - name_targets に StringLiteral を含めないとスキップされるテスト Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * [fix] implement StringLiteral extraction in all 8 parsers because of name_deny must check string literals - partition_names に StringLiteral 分岐追加 - strip_string_delimiters ヘルパー追加(クォート除去) - Rust/Go/Python/TypeScript/Java/Kotlin/PHP/C パーサーに文字列リテラル抽出追加 - mille.toml の domain/usecase で name_targets から string_literal を除外(自己dogfood対応) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * [refactor] update docs for StringLiteral name_deny support because of PR workflow checklist - README.md: name_targets に string_literal 追加、対応言語に C 追加 - Website (ja/en): name_targets テーブル・対応言語テーブル更新 - docs/TODO.md: 実装状況サマリー更新 - tasks/ TODO.md + timeline.md 更新 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * [refactor] remove emoji checkmark grids and ASCII art from README/website because of zero information density - Feature matrix: 全セル ✅ の表を「Languages + Check/Description」形式に変更 - ASCII art レイヤー図を削除(website は SVG hero で代替済み) - Naming 対応言語表を一文に置き換え(全言語・全ターゲット対応) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * [refactor] move language-specific logic out of domain/usecase because of clean architecture - ResolvedImport に package_name フィールド追加、各 resolver で設定 - violation_detector から拡張子ベースのパッケージ名抽出を削除(domain に言語知識が漏れていた) - extract_ts_package_name を infrastructure/resolver/typescript に移動 - ext_to_language を infrastructure/parser に移動(usecase/init.rs から参照) - mille.toml: name_deny_ignore でテストコード含むファイルを除外 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * [refactor] replace language names in test strings with neutral alternatives because of name_deny false positives - domain layer: name_deny_ignore 完全撤廃(テスト内の "rust" → "mylang"、"go" → "bad" 等に置換) - usecase layer: init.rs のみ name_deny_ignore 残置(言語検出テストが正当に言語名を使用するため) - check_architecture.rs: テストファイルパスから "go" を除去 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * [refactor] introduce LanguageDetector trait to decouple usecase from infrastructure because of clean architecture - domain/repository/language_detector.rs: LanguageDetector trait 追加 - infrastructure/parser/mod.rs: ExtensionLanguageDetector として実装 - usecase/init.rs: detect_languages が trait 経由で言語検出(infrastructure 直接参照を排除) - runner.rs: ExtensionLanguageDetector を注入 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * [refactor] make init.rs tests language-agnostic and remove all name_deny_ignore because of dogfood strictness - StubResolveGen を言語名に依存しない設計に変更(resolve_output/internal_pkgs を直接指定) - generate_toml テスト: 実言語名 → "lang_a"/"lang_b"/"lang_c"/"lang_d" に置換 - detect_languages テスト: FakeDetector で infrastructure 依存を排除 - assertion メッセージの "got" → "found" に変更("go" の部分一致回避) - mille.toml: name_deny_ignore を全レイヤーから完全撤廃 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * [test] add language-specific resolve tests to infrastructure because of test coverage moved from usecase - module_path が Go 以外で無視されることを検証 - package_prefix が Java/Kotlin 以外で無視されることを検証 - Kotlin でも package_prefix resolve が動作することを検証 - monorepo での package_names 重複排除を検証 - package_prefix_name なしで resolve セクションが出力されないことを検証 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 08fbeaa commit 7c3ec7c

File tree

37 files changed

+825
-450
lines changed

37 files changed

+825
-450
lines changed

.claude/rules/no-tmp.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# /tmp を使わない
2+
3+
一時ファイルやスクリプトを作成するとき `/tmp` を使わない。
4+
カレントディレクトリ(プロジェクトルート)に作成し、不要になったら削除する。
5+
6+
`/tmp` はあなたの作業場所ではない。

README.md

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,20 @@
22

33
> Like a mille crêpe — your architecture, one clean layer at a time.
44
5-
```
6-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ presentation
7-
· · · · · · · · · · · · · · · · · · (deps only flow inward)
8-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ infrastructure
9-
· · · · · · · · · · · · · · · · · ·
10-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ usecase
11-
· · · · · · · · · · · · · · · · · ·
12-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ domain
13-
```
14-
155
`mille` is a static analysis CLI that enforces **dependency rules for layered architectures** — Clean Architecture, Onion Architecture, Hexagonal Architecture, and more.
166

177
One TOML config. Rust-powered. CI-ready. Supports multiple languages from a single config file.
188

199
## What it checks
2010

21-
| Check | Rust | Go | TypeScript | JavaScript | Python | Java | Kotlin | PHP | C |
22-
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
23-
| Layer dependency rules (`dependency_mode`) ||||||||||
24-
| External library rules (`external_mode`) ||||||||||
25-
| DI method call rules (`allow_call_patterns`) ||||||||||
26-
| Naming convention rules (`name_deny`) ||||||||||
11+
**Languages:** Rust, Go, TypeScript, JavaScript, Python, Java, Kotlin, PHP, C
12+
13+
| Check | Description |
14+
|---|---|
15+
| `dependency_mode` | Layer dependency rules — control which layers can import from which |
16+
| `external_mode` | External library rules — restrict third-party package usage per layer |
17+
| `allow_call_patterns` | DI method call rules — limit which methods may be called on injected types |
18+
| `name_deny` | Naming convention rules — forbid infrastructure keywords in domain/usecase |
2719

2820
## Install
2921

@@ -435,7 +427,7 @@ Exit codes:
435427
| `external_deny` | Forbidden external packages (when `external_mode = "opt-out"`) |
436428
| `name_deny` | Forbidden keywords for naming convention check (case-insensitive partial match) |
437429
| `name_allow` | Substrings to strip before `name_deny` check (e.g. `"category"` prevents `"go"` match inside it) |
438-
| `name_targets` | Targets to check: `"file"`, `"symbol"`, `"variable"`, `"comment"` (default: all) |
430+
| `name_targets` | Targets to check: `"file"`, `"symbol"`, `"variable"`, `"comment"`, `"string_literal"` (default: all) |
439431
| `name_deny_ignore` | Glob patterns for files to exclude from naming checks (e.g. `"**/test_*.rs"`) |
440432

441433
#### Naming Convention Check (`name_deny`)
@@ -454,7 +446,7 @@ external_deny = []
454446
# Usecase layer must not reference specific infrastructure technologies
455447
name_deny = ["gcp", "aws", "azure", "mysql", "postgres"]
456448
name_allow = ["category"] # "category" contains "go" but should not be flagged
457-
name_targets = ["file", "symbol", "variable", "comment"] # default: all targets
449+
name_targets = ["file", "symbol", "variable", "comment", "string_literal"] # default: all targets
458450
name_deny_ignore = ["**/test_*.rs", "tests/**"] # exclude test files from naming checks
459451
```
460452

@@ -468,7 +460,8 @@ name_deny_ignore = ["**/test_*.rs", "tests/**"] # exclude test files from namin
468460
- `"symbol"`: function, class, struct, enum, trait, interface, type alias names
469461
- `"variable"`: variable, const, let, static declaration names
470462
- `"comment"`: inline comment content
471-
- Supported languages: Rust, TypeScript, JavaScript, Python, Go, Java, Kotlin, PHP
463+
- `"string_literal"`: string literal content
464+
- Supported languages: Rust, TypeScript, JavaScript, Python, Go, Java, Kotlin, PHP, C
472465
- Severity is controlled by `severity.naming_violation` (default: `"error"`)
473466

474467
### `[[layers.allow_call_patterns]]`

docs/TODO.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
- ✅ ネーミング規則チェック (`name_deny` / `name_targets` / `name_allow` / `name_deny_ignore`) — レイヤーごとに禁止キーワードを設定し、ファイル名・シンボル名・変数名・コメントに禁止キーワードが含まれる場合に `NamingViolation` を報告(大文字小文字区別なし・部分一致)。`name_allow` で false positive を抑制、`name_deny_ignore` でグロブパターンにマッチするファイルを除外可能。対応言語: Rust/TypeScript/Python/Go/Java/Kotlin/PHP。`severity.naming_violation` で重大度設定可(PR #65
3232
-`ParsedNames` 構造体によるコンパイルタイムガード — `Parser::parse_names` の戻り値を `ParsedNames` に変更し、新しい `NameKind` 追加時に全パーサーでコンパイルエラーが発生するよう保証。PHP/Python に Variable 抽出を追加(PR #68
3333
- ✅ C 言語サポート — `.c`/`.h` ファイルの `#include` パース、`"..."` → Internal / `<stdlib>` → Stdlib / `<other>` → External 分類、Symbol/Variable/Comment naming 抽出、E2E テスト追加(PR #69
34+
-`name_deny` 文字列リテラルチェック — `NameKind::StringLiteral` / `NameTarget::StringLiteral` 追加、全8言語パーサーで文字列リテラル抽出、`name_targets``"string_literal"` のオプトアウト可能(PR #71
3435
- ✅ PHP 言語サポート — `.php` ファイルの `use` 文パース(simple/aliased/grouped/function/const)・Internal/External/Stdlib 分類、`[resolve.php] namespace` 設定、`composer.json` `autoload.psr-4` 自動検出、PHP stdlib クラス(DateTime/PDO/Exception 等)の Stdlib 自動分類
3536

3637
以下は **設定ファイルにフィールドが存在しても、まだ動作していない** 項目です(README に掲載しないよう修正済み):

src/domain/entity/config.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ mod tests {
7777
[project]
7878
name = "myproject"
7979
root = "."
80-
languages = ["rust"]
80+
languages = ["mylang"]
8181
8282
[[layers]]
8383
name = "domain"
@@ -96,7 +96,7 @@ external_mode = "opt-in"
9696
[project]
9797
name = "myproject"
9898
root = "."
99-
languages = ["rust"]
99+
languages = ["mylang"]
100100
101101
[[layers]]
102102
name = "usecase"
@@ -123,7 +123,7 @@ name_deny = ["aws", "gcp"]
123123
[project]
124124
name = "myproject"
125125
root = "."
126-
languages = ["rust"]
126+
languages = ["mylang"]
127127
128128
[[layers]]
129129
name = "usecase"
@@ -154,7 +154,7 @@ name_targets = ["file", "symbol"]
154154
[project]
155155
name = "myproject"
156156
root = "."
157-
languages = ["rust"]
157+
languages = ["mylang"]
158158
159159
[[layers]]
160160
name = "usecase"
@@ -180,7 +180,7 @@ name_deny = ["aws"]
180180
[project]
181181
name = "myproject"
182182
root = "."
183-
languages = ["rust"]
183+
languages = ["mylang"]
184184
185185
[[layers]]
186186
name = "usecase"
@@ -208,7 +208,7 @@ naming_violation = "error"
208208
[project]
209209
name = "myproject"
210210
root = "."
211-
languages = ["rust"]
211+
languages = ["mylang"]
212212
213213
[[layers]]
214214
name = "usecase"
@@ -229,7 +229,7 @@ external_mode = "opt-out"
229229
[project]
230230
name = "myproject"
231231
root = "."
232-
languages = ["rust"]
232+
languages = ["mylang"]
233233
234234
[[layers]]
235235
name = "domain"

src/domain/entity/layer.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub enum NameTarget {
1010
Symbol,
1111
Variable,
1212
Comment,
13+
StringLiteral,
1314
}
1415

1516
impl NameTarget {
@@ -19,6 +20,7 @@ impl NameTarget {
1920
NameTarget::Symbol,
2021
NameTarget::Variable,
2122
NameTarget::Comment,
23+
NameTarget::StringLiteral,
2224
]
2325
}
2426

@@ -28,6 +30,7 @@ impl NameTarget {
2830
NameTarget::Symbol => NameKind::Symbol,
2931
NameTarget::Variable => NameKind::Variable,
3032
NameTarget::Comment => NameKind::Comment,
33+
NameTarget::StringLiteral => NameKind::StringLiteral,
3134
}
3235
}
3336
}

src/domain/entity/name.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ pub enum NameKind {
2222
Variable,
2323
/// Inline comment content.
2424
Comment,
25+
/// String literal content.
26+
StringLiteral,
2527
}
2628

2729
/// Parsed names grouped by kind.
@@ -36,16 +38,23 @@ pub struct ParsedNames {
3638
pub variables: Vec<RawName>,
3739
/// Inline comment contents.
3840
pub comments: Vec<RawName>,
41+
/// String literal contents.
42+
pub string_literals: Vec<RawName>,
3943
}
4044

4145
impl ParsedNames {
4246
/// Flatten all parsed names into a single `Vec<RawName>`.
4347
pub fn into_all(self) -> Vec<RawName> {
44-
let mut out =
45-
Vec::with_capacity(self.symbols.len() + self.variables.len() + self.comments.len());
48+
let mut out = Vec::with_capacity(
49+
self.symbols.len()
50+
+ self.variables.len()
51+
+ self.comments.len()
52+
+ self.string_literals.len(),
53+
);
4654
out.extend(self.symbols);
4755
out.extend(self.variables);
4856
out.extend(self.comments);
57+
out.extend(self.string_literals);
4958
out
5059
}
5160
}

src/domain/entity/resolved_import.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ pub struct ResolvedImport {
88
/// Normalised file-system path for `Internal` imports (e.g. `src/domain/entity/config`).
99
/// `None` when the path could not be resolved (wildcards, grouped imports, `super::` etc.).
1010
pub resolved_path: Option<String>,
11+
/// Top-level package/crate name for `External` imports (e.g. `"serde"`, `"matplotlib"`, `"@scope/pkg"`).
12+
/// Set by the resolver so that domain logic does not need to know language-specific separators.
13+
pub package_name: Option<String>,
1114
}
1215

1316
#[derive(Debug, PartialEq, Eq, Clone)]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/// Maps a file extension to a language name.
2+
pub trait LanguageDetector {
3+
fn detect_from_extension(&self, ext: &str) -> Option<String>;
4+
}

src/domain/repository/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod config_repository;
2+
pub mod language_detector;
23
pub mod parser;
34
pub mod resolve_config_generator;
45
pub mod resolver;

0 commit comments

Comments
 (0)