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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
- ライブラリバージョン変更が必要な場合、まず提案を行え
- `import`無しでの外部定義利用は禁止(FQCNをコード中に直接書かない)
- クラスは1ファイルにつき1つまで
- 例外として、同一の`sealed`階層を構成するクラス群は1ファイルへまとめてよい
- 末端・中間の他、末端の吸収サブタイプや`kind`の親クラスも同階層の構成要素として扱う
- ファイル名は基底の名前とする
- 配置(ネスト/トップレベル)自体が意味を持つ場合を除き、ネスト配置を優先する
- 処理は意味単位で適切に関数分割すること
- 1クラスは500行に収める
- デフォルト引数は、コンフィグ系など表現が適切となる場合にのみ利用する
Expand Down
28 changes: 26 additions & 2 deletions docs/test/フィクスチャ構成.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,32 @@ src/test には test compilation 配線検証用の小さな @Enumize 階層(T
- 診断フィクスチャの行位置はアサートの一部であり、編集時は行番号照合を更新する。
TestKit フィクスチャは ktfmt 整形対象外。
- 順序フィクスチャの期待値は FQN の UTF-16 序数から機械的に導出し、期待列をコメントで併記する。
- producer-jvm のフィクスチャで「1 ファイル 1 クラス」規約より配置制約(同一ファイル要求・
複数トップレベル宣言)が優先される場合は、ファイル先頭コメントで適用外を明示する。

### 5.1 ファイル分割の単位

階層フィクスチャは 1 ファイル = 1 階層(基底・中間・末端・吸収サブタイプ・kind の親クラスを同居させる)、
診断フィクスチャは 1 ファイル = 1 診断ケースを既定とし、ファイル名はそれぞれ基底名・診断名とする。

ただし次のいずれかに当たる宣言はファイルを分けることが検証の成立条件であり、
その理由をファイル先頭コメントに明示する。

| 分離が要件となる条件 | 例 |
|---|---|
| `private` トップレベルの壁(ファイルスコープ)を作る | vis.pub の別ファイル private 末端・diag-fail の ScopePrivUse |
| K7 の「別ファイル配置」自体が次元値 | order.flat の別ファイルトップレベル末端 |
| `import` 別名 / star import の解決文脈がファイル単位 | diag-ok の NmAlImported / NmStarNoc |
| typealias の解決順(ファイル名順)が挙動を分ける | diag-ok の TypealiasAliases が TypealiasMatch より前 |
| ファイル単位の非発火アサートが同居を許さない | diag-mpp の ON_EXPECT / ON_ACTUAL 非発火・diag-fail の Mc1Si |
| IC / 決定性テストがファイル粒度で編集・追加削除する | ic-\* / determinism / abi-propagation |
| 別モジュール・別パッケージ・別ソースセットに置く | diag-cross / failother / diag-mpp |

**1 ケースが複数ファイルに跨る場合は、そのケース専用のパッケージへ切り出す。**
`sealed` 階層は同一パッケージを要求するため、切り出しは階層ごと丸ごと移す。
パッケージ名はケースの内容を表す小文字 1 語とする
(diag-fail の `visibilityscope` / `labelclash` / `memberconflict`、
diag-ok の `rawtracking` / `typealiasmatch` / `visibility`)。
「別パッケージであること」自体が次元値の場合は、そのケースのパッケージ配下へ入れ子にする
(diag-ok の `rawtracking.far`)。

## 6. maven-integration(Maven フィクスチャ)

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import kotlin.test.Test
// 単一モジュールの near-miss 非発火・EXTENSION_SHADOWED 警告・raw 追跡表記を diag-ok フィクスチャの
// 1 回の成功ビルドで全件検証する(docs/test/ケース04-診断.md の DIA near-miss 群・
// docs/test/フィクスチャ構成.md §3)。成功ビルド自体がエラー系診断の総体的な非発火を含意し、
// 各メソッドは当該断片の不在(またはファイル毎の無診断)で境界を固定する
// 各メソッドは当該断片の不在(またはファイル毎の無診断)で境界を固定する。
// フィクスチャは 1 ファイル = 1 診断ケースであり、ES 警告の行照合はその配置に対する実測値である
class DiagSingleOkTest : DiagTestBase() {
private fun ok(): String = successOutput("diag-ok", "compileKotlin")

Expand All @@ -18,10 +19,8 @@ class DiagSingleOkTest : DiagTestBase() {
@Test
fun wellFormedAndBoundaryHierarchiesReportNothing() {
val output = ok()
listOf("OkSi.kt", "OkSc.kt", "OkEmpty.kt", "OkGen.kt").forEach { file ->
assertFragmentAbsentAt(output, file, "e: ")
assertFragmentAbsentAt(output, file, "w: ")
}
assertFragmentAbsentAt(output, "WellFormed.kt", "e: ")
assertFragmentAbsentAt(output, "WellFormed.kt", "w: ")
}

// docs/test/ケース04-診断.md DIA-05: @Enumize 付き local class は全入口で除外され無診断素通り
Expand Down Expand Up @@ -53,8 +52,8 @@ class DiagSingleOkTest : DiagTestBase() {
@Test
fun nonInnerNestedLeafDoesNotReport() {
val output = ok()
assertFragmentAbsentAt(output, "NmNestHost.kt", "e: ")
assertFragmentAbsentAt(output, "NmNestHost.kt", "w: ")
assertFragmentAbsentAt(output, "AbsorptionNearMiss.kt", "e: ")
assertFragmentAbsentAt(output, "AbsorptionNearMiss.kt", "w: ")
}

// docs/test/ケース04-診断.md DIA-26: 3 段規則の成立形(規則 1 / 2・広い末端 object・private 基底・
Expand All @@ -77,24 +76,17 @@ class DiagSingleOkTest : DiagTestBase() {
fun everyRawSupertypeNotationYieldsAKind() {
val output = ok()
assertFragmentAbsent(output, DiagFragments.LANG_ABSTRACT_MEMBER_NOT_IMPLEMENTED)
listOf(
"NmDirNoc.kt",
"NmImNoc.kt",
"NmImBar.kt",
"NmFqFoo.kt",
"NmEnumLeaf.kt",
"NmOutHost.kt",
)
.forEach { file -> assertFragmentAbsentAt(output, file, "e: ") }
listOf("NmAl.kt", "NmAlImported.kt").forEach { file ->
assertFragmentAbsentAt(output, file, "e: ")
}
}

// docs/test/ケース04-診断.md DIA-32: typealias 表記(同一 pkg・別 pkg 明示 / star import)と
// 明示 companion 併用 2 形で候補判定成立
@Test
fun typealiasNotationsYieldAKind() {
val output = ok()
listOf("NmAlNoc.kt", "NmAlFoo.kt", "NmImFoo.kt", "NmFarNoc.kt", "NmStarNoc.kt").forEach {
file ->
listOf("NmAl.kt", "NmAlImported.kt", "NmFarNoc.kt", "NmStarNoc.kt").forEach { file ->
assertFragmentAbsentAt(output, file, "e: ")
}
}
Expand All @@ -111,57 +103,62 @@ class DiagSingleOkTest : DiagTestBase() {
@Test
fun declaredLabelMemberWarns() {
val output = ok()
assertDiagnosticAt(output, "WlSi.kt", 8, DiagFragments.EXTENSION_SHADOWED)
assertDiagnosticAt(output, "Wl2Si.kt", 8, DiagFragments.EXTENSION_SHADOWED)
assertDiagnosticAt(output, "WlSi.kt", 13, DiagFragments.EXTENSION_SHADOWED)
assertDiagnosticAt(output, "OkManLeaf.kt", 8, DiagFragments.EXTENSION_SHADOWED)
assertDiagnosticAt(output, "ExtensionShadowed.kt", 13, DiagFragments.EXTENSION_SHADOWED)
assertDiagnosticAt(output, "ExtensionShadowed.kt", 18, DiagFragments.EXTENSION_SHADOWED)
assertDiagnosticAt(output, "ExtensionShadowed.kt", 29, DiagFragments.EXTENSION_SHADOWED)
assertDiagnosticAt(output, "ManualImplAllowed.kt", 20, DiagFragments.EXTENSION_SHADOWED)
}

// docs/test/ケース04-診断.md DIA-38: 継承のみの label はクラス位置 + 宣言元 FQN で ES 警告
@Test
fun inheritedLabelWarnsWithDeclarationSite() {
val output = ok()
assertDiagnosticAt(output, "Wl3Leaf.kt", 4, DiagFragments.EXTENSION_SHADOWED, "Wl3Named")
assertDiagnosticAt(output, "Wl2Si.kt", 12, DiagFragments.EXTENSION_SHADOWED, "Wl2Si")
assertDiagnosticAt(
output,
"ExtensionShadowed.kt",
44,
DiagFragments.EXTENSION_SHADOWED,
"Wl3Named",
)
assertDiagnosticAt(
output,
"ExtensionShadowed.kt",
33,
DiagFragments.EXTENSION_SHADOWED,
"Wl2Si",
)
}

// docs/test/ケース04-診断.md DIA-39: 非発火 3 形(label 以外・末端 object の継承・private label。
// 階層内手動実装 leaf の Enumish 由来 label は除外されず ES 発火 = DIA-37)
@Test
fun shadowingExclusionsDoNotWarn() {
val output = ok()
assertNoDiagnosticAt(output, "WlSi.kt", 10)
assertNoDiagnosticAt(output, "Wl2Si.kt", 10)
assertNoDiagnosticAt(output, "WlPriv.kt", 9)
listOf(15, 31, 50).forEach { line ->
assertNoDiagnosticAt(output, "ExtensionShadowed.kt", line)
}
}

// docs/test/ケース04-診断.md DIA-44: toString は手動宣言・継承具象とも MC 対象外
@Test
fun toStringIsExemptFromMemberConflict() {
val output = ok()
assertFragmentAbsentAt(output, "NmTsLeaf.kt", DiagFragments.MEMBER_CONFLICT)
assertFragmentAbsentAt(output, "NmTsMan.kt", DiagFragments.MEMBER_CONFLICT)
assertFragmentAbsentAt(ok(), "ToStringExempt.kt", DiagFragments.MEMBER_CONFLICT)
}

// docs/test/ケース04-診断.md DIA-70: クラス継承の非発火側 — open 具象(companion 経由・末端 object
// 直接)は生成 override が勝ち、宣言種別交差(final 関数 label)と private は衝突しない
@Test
fun classInheritanceNearMissesDoNotConflict() {
val output = ok()
listOf("OkCi.kt", "OkCiOpen.kt", "OkCiFn.kt", "OkCiPv.kt").forEach { file ->
assertFragmentAbsentAt(output, file, "e: ")
assertFragmentAbsentAt(output, file, "w: ")
}
assertFragmentAbsentAt(output, "ClassInheritance.kt", "e: ")
assertFragmentAbsentAt(output, "ClassInheritance.kt", "w: ")
}

// docs/test/ケース04-診断.md DIA-45: 生成先違い(末端本体 enumizedClass・kind companion の
// 手動 asEnumish・enumishCompanion override)は非発火
@Test
fun nonGeneratedTargetsDoNotConflict() {
val output = ok()
listOf("OkM.kt", "OkC.kt", "OkKca.kt").forEach { file ->
assertFragmentAbsentAt(output, file, DiagFragments.MEMBER_CONFLICT)
}
assertFragmentAbsentAt(ok(), "NonGeneratedTargets.kt", DiagFragments.MEMBER_CONFLICT)
}

// docs/test/ケース04-診断.md DIA-51: 型引数一致の手動宣言(直接・間接・末端冗長宣言)は注入スキップ
Expand All @@ -177,7 +174,7 @@ class DiagSingleOkTest : DiagTestBase() {
@Test
fun typealiasedSupertypesMatchAfterExpansion() {
val output = ok()
listOf("NmTaSi.kt", "NmThSi.kt", "NmTlSi.kt", "NmTlSame.kt").forEach { file ->
listOf("TypealiasMatch.kt", "NmTlSame.kt").forEach { file ->
assertFragmentAbsentAt(output, file, "e: ")
}
assertFragmentAbsent(output, DiagFragments.LANG_PLATFORM_DECLARATION_CLASH)
Expand All @@ -196,9 +193,7 @@ class DiagSingleOkTest : DiagTestBase() {
fun hierarchyInternalAndBaseEnumishImplsAreAllowed() {
val output = ok()
assertFragmentAbsent(output, DiagFragments.MANUAL_IMPL_OUTSIDE_HIERARCHY)
listOf("OkManLeaf.kt", "OkKex.kt", "NmRt.kt").forEach { file ->
assertFragmentAbsentAt(output, file, "e: ")
}
assertFragmentAbsentAt(output, "ManualImplAllowed.kt", "e: ")
}

// docs/test/ケース04-診断.md DIA-76: 中間 sealed への任意のユーザーアノテーションは基底判定を
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ class ProbeGateTest : DiagTestBase() {
@Test
fun companionNameClashBehaviorIsPinned() {
val output = failOutput("probe-companion-clash", "compileKotlin")
assertDiagnosticAt(output, "CcLeaf.kt", 6, DiagFragments.LANG_CONFLICTING_DECLARATIONS)
assertDiagnosticAt(output, "CcLeaf.kt", 7, DiagFragments.LANG_CONFLICTING_DECLARATIONS)
assertDiagnosticAt(output, "CcSi.kt", 12, DiagFragments.LANG_CONFLICTING_DECLARATIONS)
assertDiagnosticAt(output, "CcSi.kt", 13, DiagFragments.LANG_CONFLICTING_DECLARATIONS)
}

// docs/test/ケース04-診断.md DIA-67: @Enumize の import 別名・typealias 表記は述語
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ class TypealiasGateTest : DiagTestBase() {
val output = failOutput("sweep-typealias-impl", "compileKotlin")
assertDiagnosticAnywhere(
output,
"UseSwTiAl.kt",
"SwTiAl.kt",
DiagFragments.LANG_WHEN_NOT_EXHAUSTIVE,
"is SwTiAlLeaf",
)
assertDiagnosticAnywhere(
output,
"UseSwTiEx.kt",
"SwTiEx.kt",
DiagFragments.LANG_WHEN_NOT_EXHAUSTIVE,
"is SwTiExLeaf",
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,10 @@ private fun probeBaz(): String = try {
} catch (e: IllegalArgumentException) {
"IAE"
}

// v1(2 末端)に対して網羅の else 無し kind-when。実行時 v2 で未知 kind を通すと
// NoWhenBranchMatchedException になる(docs/test/ケース06-ビルド動態.md BLD-40)
fun describeKind(kind: SI.Enumish): String = when (kind) {
SI.Foo.Companion -> "foo"
SI.Bar -> "bar"
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,11 @@ private fun probeBaz(): String = try {
} catch (e: IllegalArgumentException) {
"IAE"
}

// v2(3 末端)に対して網羅の else 無し kind-when。実行時 v1 では存在する kind だけが通り
// 全枝が解決される(docs/test/ケース06-ビルド動態.md BLD-40 の削除方向)
fun describeKind(kind: SI.Enumish): String = when (kind) {
SI.Foo.Companion -> "foo"
SI.Bar -> "bar"
SI.Baz -> "baz"
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.github.projectmapk.diag.xlib

import io.github.projectmapk.sealedClassEnumizer.Enumize

// docs/test/ケース04-診断.md DIA-22 用の 2 階層(利用側 app の Cross2 が両末端を実装する)

@Enumize
sealed interface XfamSi1 {
interface LeafC : XfamSi1
}

@Enumize
sealed interface XfamSi2 {
interface LeafD : XfamSi2
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.github.projectmapk.diag.fail

import io.github.projectmapk.sealedClassEnumizer.Enumize

// docs/test/ケース04-診断.md DIA-62 用の基底
@Enumize
sealed interface AtSi

// toString の抽象再宣言(Any の実装では充足されない)
abstract class AtAbs {
abstract override fun toString(): String
}

// DIA-62: supertype の toString 抽象再宣言 + 手動実装なし
// → 言語 abstract 未実装エラーのみ・MC 不在(生成は充足に使えない)
object AtLeaf : AtAbs(), AtSi

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.github.projectmapk.diag.fail

import io.github.projectmapk.sealedClassEnumizer.Enumize

// docs/test/ケース04-診断.md DIA-17: 1 宣言が 2 つの kind に属する形 → ENUMIZE_AMBIGUOUS_KIND + 末端 FQN

@Enumize
sealed interface AmbSi

// 非 final 末端(V10)
abstract class AmbA : AmbSi

// 末端 AmbA を継承しつつ基底も直接実装
class AmbB : AmbA(), AmbSi

@Enumize
sealed interface Amb2 {
interface LeafA : Amb2

interface LeafB : Amb2
}

// 2 末端 interface の実装 → AK + 末端 FQN 2 件
class Amb2C : Amb2.LeafA, Amb2.LeafB
Loading