この文書は、DependencyContractAnalyzer の現在の実装スコープを整理したものです。
リポジトリルートの SPECIFICATION.md を正本の要約仕様とし、
この文書はその詳細版です。
詳細ルール、例、長い表、エッジケース、シナリオ説明はこの文書に記載します。
この文書と SPECIFICATION.md が矛盾する場合は、SPECIFICATION.md を優先します。
仕様の挙動が変わる場合は、editorial only を除き、
SPECIFICATION.md とこの文書を同じ変更で更新してください。
最終完成形の設計全体は docs/architecture.ja.md を参照してください。
クラスおよびインタフェースに依存契約を宣言し、依存元が要求する契約を依存先が満たしているかを静的解析で検証します。
解析対象は型依存のみであり、DI 登録解析には依存しません。
現在、解析対象としている依存種別は次のとおりです。
| 依存種別 | 対象 |
|---|---|
| コンストラクタ引数 | 対象 |
| コンストラクタ以外のメソッド引数 | 対象 |
| プロパティ型 | 対象 |
| フィールド型 | 対象 |
new 式 |
対象 |
| static メンバー利用 | 対象 |
| 継承 | 対象 |
| インタフェース実装 | 対象 |
次の依存種別は .editorconfig で無効化でき、既定値は true です。
dependency_contract_analyzer.analyze_fieldsdependency_contract_analyzer.analyze_base_typesdependency_contract_analyzer.analyze_interface_implementationsdependency_contract_analyzer.analyze_method_parametersdependency_contract_analyzer.analyze_propertiesdependency_contract_analyzer.analyze_object_creationdependency_contract_analyzer.analyze_static_members
コンストラクタ引数は常に解析対象です。
現在、実装済みのルールファミリは次のとおりです。
| ルール | 実装 |
|---|---|
ProvidesContract |
実装済み |
RequiresDependencyContract |
実装済み |
ContractTarget |
実装済み |
RequiresContractOnTarget |
実装済み |
ContractScope |
実装済み |
RequiresContractOnScope |
実装済み |
ContractHierarchy |
実装済み |
次は引き続き対象外です。
| 項目 | 理由 |
|---|---|
| 現在の最終セグメント fallback と optional な trailing 2-segment fallback を超える namespace ベース推定 | より高度な namespace 由来命名 heuristic は対象外 |
| 属性参照 | 依存抽出は strong type relationship に限定する |
| generic constraint | 依存抽出は strong type relationship に限定する |
typeof 参照 |
依存抽出は strong type relationship に限定する |
| return type | 依存抽出は strong type relationship に限定する |
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = true)]
public sealed class ProvidesContractAttribute : Attribute
{
public string Name { get; }
public ProvidesContractAttribute(string name)
{
Name = name;
}
}[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class RequiresDependencyContractAttribute : Attribute
{
public Type DependencyType { get; }
public string ContractName { get; }
public RequiresDependencyContractAttribute(Type dependencyType, string contractName)
{
DependencyType = dependencyType;
ContractName = contractName;
}
}[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = true)]
public sealed class ContractTargetAttribute : Attribute
{
public string Name { get; }
public ContractTargetAttribute(string name)
{
Name = name;
}
}[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class RequiresContractOnTargetAttribute : Attribute
{
public string TargetName { get; }
public string ContractName { get; }
public RequiresContractOnTargetAttribute(string targetName, string contractName)
{
TargetName = targetName;
ContractName = contractName;
}
}[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Assembly, AllowMultiple = true, Inherited = true)]
public sealed class ContractScopeAttribute : Attribute
{
public string Name { get; }
public ContractScopeAttribute(string name)
{
Name = name;
}
}[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class RequiresContractOnScopeAttribute : Attribute
{
public string ScopeName { get; }
public string ContractName { get; }
public RequiresContractOnScopeAttribute(string scopeName, string contractName)
{
ScopeName = scopeName;
ContractName = contractName;
}
}[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class ContractHierarchyAttribute : Attribute
{
public string Child { get; }
public string Parent { get; }
public ContractHierarchyAttribute(string child, string parent)
{
Child = child;
Parent = parent;
}
}ContractHierarchyはchild -> parentの有向包含辺を宣言します- 契約充足関係は implication graph 上で推移的です
- 契約は自分自身と、hierarchy をたどって到達可能な契約を満たします
- 同じ child に属性を繰り返すことで多親階層を表現できます
- implication graph の循環は無効であり、
DCA202で報告します
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false, Inherited = false)]
public sealed class ExcludeDependencyContractAnalysisAttribute : Attribute
{
}assembly または owner type に付与すると、その owner type に対する analyzer 実行をスキップします。
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class SuppressRequiredDependencyContractAttribute : Attribute
{
public Type DependencyType { get; }
public string ContractName { get; }
public SuppressRequiredDependencyContractAttribute(Type dependencyType, string contractName)
{
DependencyType = dependencyType;
ContractName = contractName;
}
}[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class SuppressRequiredTargetContractAttribute : Attribute
{
public string TargetName { get; }
public string ContractName { get; }
public SuppressRequiredTargetContractAttribute(string targetName, string contractName)
{
TargetName = targetName;
ContractName = contractName;
}
}[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class SuppressRequiredScopeContractAttribute : Attribute
{
public string ScopeName { get; }
public string ContractName { get; }
public SuppressRequiredScopeContractAttribute(string scopeName, string contractName)
{
ScopeName = scopeName;
ContractName = contractName;
}
}これらの属性は owner class 上で exact match した requirement の診断だけを抑止します。dependency discovery 自体は除外しません。
[AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
public sealed class ExcludeDependencyContractSourceAttribute : Attribute
{
}constructor / method / property / field に付けると、その member に由来する declared dependency source と syntax-based dependency discovery を除外します。
DI 解析を行わないため、依存がインタフェースや基底型で表現されている場合は、契約もその抽象側に宣言してください。
現在の Analyzer は、次の順で要件を評価します。
RequiresDependencyContractRequiresContractOnTargetRequiresContractOnScope- 包含グラフによる提供契約の展開
現在の振る舞い:
RequiresDependencyContractは一致する依存が存在し、なおかつ必要契約が満たされない場合に診断しますRequiresDependencyContractは宣言した依存型が未使用ならDCA002を報告しますRequiresContractOnTargetは正規化後の target 名が一致した依存だけを評価しますRequiresContractOnScopeは正規化後の scope 名が一致した依存だけを評価します- current compilation 外の依存は既定では無視します
dependency_contract_analyzer.external_dependency_policy = metadataの場合、DCA001評価前に一致した外部 dependency type から explicit provided-contract metadata を読み取りますdependency_contract_analyzer.external_dependency_policy = metadataの場合、RequiresContractOnTargetとRequiresContractOnScopeも一致した外部 dependency type / assembly の explicit target / scope metadata を読み取りますdependency_contract_analyzer.external_dependency_policy = metadataの場合、外部 dependency の提供契約展開時に参照先 assembly のContractHierarchy包含辺も読み取ります- namespace fallback inference は
metadataモードでも current compilation 内の型に限定します DCA200とDCA201の declared target / scope 判定は引き続き current compilation のみを対象にします- 参照先包含定義に対する診断は consumer compilation には報告しません
dependency_contract_analyzer.report_undeclared_requirement_diagnostics = falseの場合、target / scope requirement はDCA200/DCA201で停止せず、そのまま一致する dependency 評価を継続します- type-level の target / scope では明示属性を優先し、同種の namespace 推定名は追加しません
- type-level target が未指定なら current compilation 内の namespace 最終セグメントから推定します
dependency_contract_analyzer.namespace_inference_max_segments = 2の場合は trailing 2-segment fallback target 名も推定します- assembly-level scope 宣言は current compilation 内の型へ常に適用します
- type-level scope が明示されている型では namespace ベースの scope 推定を行いません
- type-level scope が未指定なら、assembly-level scope が存在していても current compilation 内の namespace 最終セグメントから scope を推定します
dependency_contract_analyzer.namespace_inference_max_segments = 2の場合は、type-level scope が未指定のときに trailing 2-segment fallback scope 名も推定します- assembly/type-level の
ExcludeDependencyContractAnalysisAttributeは owner type の analyzer 実行をスキップします ExcludeDependencyContractSourceAttributeは owner type exclusion 適用後に、対応する constructor / method / property / field の dependency source を除外します- exact match する requirement suppression 属性は、その requirement に対応する
DCA001、DCA002、DCA200、DCA201、DCA205、DCA206を抑止します
現在の Analyzer は、宣言された名前を共通ルールで正規化します。
- 前後空白を削除する
- 大文字小文字を無視する
- Ordinal 比較を使う
対象:
- 契約名
- target 名
- scope 名
- hierarchy の endpoint
対象型から次の依存情報を収集します。
public A(B b)取得元:
INamedTypeSymbolConstructorsParameters
public void Execute(B b)取得元:
IMethodSymbolParameters
対象に含めるメソッド:
- 通常メソッド
- explicit interface implementation メソッド
対象外:
- コンストラクタ
- property / event accessor
- operator / conversion
- 暗黙宣言メソッド
public B Dependency { get; set; }取得元:
IPropertySymbol.Type
private B _b;取得元:
IFieldSymbol.Type
var dependency = new B();B dependency = new();取得元:
ObjectCreationExpressionSyntaxImplicitObjectCreationExpressionSyntax- semantic model による型解決
代表的な取得元:
- static メソッド呼び出し
- static プロパティ参照
- static フィールド参照
using staticで取り込んだメンバー参照
対象外:
- reduced form の extension method
constフィールド- enum member
class A : B取得元:
BaseType
class A : IFoo取得元:
Interfaces
現在の Analyzer は次の範囲からメタデータを取得します。
- 提供契約: 依存先自身、実装インタフェース、基底型
- target: 依存先自身、実装インタフェース、基底型
- scope: 依存先自身、実装インタフェース、基底型、assembly-level scope 宣言
- 包含辺: assembly-level
ContractHierarchyAttribute
提供契約は、包含グラフの推移閉包を適用した後に requirement と照合します。
assembly-level scope は宣言元 assembly の型に常に適用します。type-level scope 宣言はその assembly-level scope に加算されます。namespace ベースの scope 推定は、type-level scope がない型に対してのみ追加の fallback 名を供給します。
dependency_contract_analyzer.external_dependency_policy = metadata の場合、参照先 assembly からも explicit provided contract / target / scope と implication edge を dependency matching に使います。local と referenced の implication graph は fixpoint に達するまでまとめて展開します。参照先 assembly は namespace inference 名を供給せず、参照先包含定義の診断も consumer compilation には出さず、undeclared target / scope 判定も current compilation のままです。
| ID | 既定 Severity | 意味 |
|---|---|---|
DCA001 |
Warning |
依存先が必要契約を提供していない |
DCA002 |
Warning |
宣言した依存型が使われていない |
DCA100 |
Warning |
契約名が空 |
DCA101 |
Warning |
契約名フォーマット違反 |
DCA102 |
Warning |
契約または requirement 宣言が重複している |
DCA200 |
Warning |
要求した target が compilation 内で未宣言 |
DCA201 |
Warning |
要求した scope が compilation 内で未宣言 |
DCA202 |
Warning |
契約包含定義が循環している |
DCA203 |
Warning |
scope 名が空 |
DCA204 |
Warning |
target 名が空 |
DCA205 |
Info |
要求した target を持つ analyzable dependency が存在しない |
DCA206 |
Info |
要求した scope を持つ analyzable dependency が存在しない |
Severity は .editorconfig により変更可能です。
既定 Severity は製品仕様であり、CI での推奨 Severity は別文書で扱う運用ガイドです。
DependencyContractAnalyzer は次の boolean .editorconfig option をサポートします。これらの既定値は behavior_preset = default を前提にしています。
dependency_contract_analyzer.analyze_fields(既定:true)dependency_contract_analyzer.analyze_base_types(既定:true)dependency_contract_analyzer.analyze_interface_implementations(既定:true)dependency_contract_analyzer.analyze_method_parameters(既定:true)dependency_contract_analyzer.analyze_properties(既定:true)dependency_contract_analyzer.analyze_object_creation(既定:true)dependency_contract_analyzer.analyze_static_members(既定:true)dependency_contract_analyzer.report_unused_requirement_diagnostics(既定:true)dependency_contract_analyzer.report_undeclared_requirement_diagnostics(既定:true)
値が未設定または不正な場合は既定値へフォールバックします。
あわせて、次の global integer .editorconfig option もサポートします。
dependency_contract_analyzer.namespace_inference_max_segments(既定:1、対応値:1、2)
あわせて、次の global string .editorconfig option もサポートします。
dependency_contract_analyzer.external_dependency_policy(既定:ignore、対応値:ignore、metadata)
あわせて、次の global preset .editorconfig option もサポートします。
dependency_contract_analyzer.behavior_preset(既定:default、対応値:default、strict、relaxed)
あわせて、次の list-valued .editorconfig option もサポートします。
dependency_contract_analyzer.excluded_namespacesdependency_contract_analyzer.excluded_types
現在サポートする EditorConfig policy surface はこの option set に限られます。命名 policy の設定化、より広い suppression model、rule family 単位の policy control に対する accepted roadmap はありません。
behavior_preset は他 option の default 挙動をまとめて切り替える preset です。
default: 現在の製品既定値strict: すべての optional dependency-source toggle を有効化し、namespace inference の既定を trailing 2-segment fallback にし、external dependency policy の既定をmetadataにしますrelaxed: optional dependency-source toggle を無効化し、namespace inference を無効化し、external dependency policy の既定をignoreにします
個別 option は常に preset より優先します。report_unused_requirement_diagnostics は DCA002、DCA205、DCA206 を制御します。report_undeclared_requirement_diagnostics は DCA200 と DCA201 を制御し、false の場合は target / scope requirement が undeclared check で停止せず一致する dependency 評価を継続します。excluded_namespaces は列挙した namespace とその subnamespace 配下の owner type 解析をスキップします。excluded_types は fully qualified owner type 名を指定して解析をスキップします。list 値は comma、semicolon、newline 区切りを受け付けます。namespace_inference_max_segments は fallback 推定に最終セグメントのみを使うか(1)、最終セグメントに加えて trailing 2-segment 組み合わせも使うか(2)を制御します。不正値や未指定時は preset 由来の既定値へフォールバックします。external_dependency_policy は current compilation 外 dependency を無視するか(ignore)、参照先 assembly の explicit metadata と implication edge と照合するか(metadata)を制御します。不正値や未指定時は preset 由来の既定値へフォールバックします。exclusion list と diagnostic severity は behavior_preset とは独立です。
analyze_*、report_*、excluded_namespaces、excluded_types は source-scoped option です。owner type が partial の場合、analyzer はその type のすべての宣言ソースファイルを評価します。boolean の source-scoped option は保守的に merge され、どこか 1 つでも明示的に false があればその type 全体で false になります。list-valued の source-scoped option は宣言全体で重複を除いて union されます。behavior_preset、namespace_inference_max_segments、external_dependency_policy は引き続き compilation 単位の option です。
DCA101 は契約名フォーマットを検証する Diagnostic です。
- 形式: lower-kebab-case
- 正規表現:
^[a-z0-9]+(-[a-z0-9]+)*$ - 適用対象は contract 名と hierarchy endpoint のみ
- requirement suppression 属性の contract 引数にも適用します
- target 名と scope 名には適用しません
対象:
ProvidesContractRequiresDependencyContractの contract 引数RequiresContractOnTargetの contract 引数RequiresContractOnScopeの contract 引数SuppressRequiredDependencyContractの contract 引数SuppressRequiredTargetContractの contract 引数SuppressRequiredScopeContractの contract 引数ContractHierarchyのchild/parent
現在の実装では次をサポートします。
#pragma warning disable[SuppressMessage].editorconfigによる severity 設定.editorconfigのexcluded_namespaces/excluded_typesによる owner type exclusion- assembly / owner type に付ける
ExcludeDependencyContractAnalysisAttribute - constructor / method / property / field に付ける
ExcludeDependencyContractSourceAttribute - owner type に付ける
SuppressRequiredDependencyContractAttribute/SuppressRequiredTargetContractAttribute/SuppressRequiredScopeContractAttributeによる exact-match suppression
member-level exclusion は dependency source のみを外し、requirement 自体は suppress しません。suppression は exact-match のみで、wildcard、prefix、contract-only form、他の requirement kind には拡張しません。
src/
└ DependencyContractAnalyzer
├ Analyzers
│ └ DependencyContractAnalyzer.cs
├ Attributes
│ ├ ContractHierarchyAttribute.cs
│ ├ ContractScopeAttribute.cs
│ ├ ContractTargetAttribute.cs
│ ├ ExcludeDependencyContractAnalysisAttribute.cs
│ ├ ExcludeDependencyContractSourceAttribute.cs
│ ├ ProvidesContractAttribute.cs
│ ├ RequiresContractOnScopeAttribute.cs
│ ├ RequiresContractOnTargetAttribute.cs
│ ├ RequiresDependencyContractAttribute.cs
│ ├ SuppressRequiredDependencyContractAttribute.cs
│ ├ SuppressRequiredScopeContractAttribute.cs
│ └ SuppressRequiredTargetContractAttribute.cs
├ Diagnostics
│ └ DiagnosticDescriptors.cs
├ Helpers
│ ├ AnalysisExclusionOptions.cs
│ ├ BehaviorPresetOptions.cs
│ ├ ContractImplicationResolver.cs
│ ├ ContractNameNormalizer.cs
│ ├ DependencyCollectionOptions.cs
│ ├ DependencyCollector.cs
│ ├ ExternalDependencyOptions.cs
│ ├ NamespaceInferenceOptions.cs
│ └ RequirementEvaluationOptions.cs
└ Utilities
└ SymbolExtensions.cs
samples/
└ DependencyContractAnalyzer.Sample
├ DependencyContractAnalyzer.Sample.csproj
├ Program.cs
└ README.md
CompilationStart
|
+-- 属性シンボルを解決
|
+-- assembly-level の包含定義を取得
|
+-- compilation end で包含定義の診断を報告
|
+-- SymbolAction(TypeSymbol)
|
+-- 契約 / target / scope 宣言を検証
|
+-- dependency / target / scope requirement を取得
|
+-- 依存型を収集
|
+-- 提供契約 / target / scope を取得
|
+-- 包含辺を通して提供契約を展開
|
+-- requirement 未充足時に診断を報告
Microsoft.CodeAnalysis.Testing を使用します。
代表ケース:
- 依存先が直接必要契約を提供している場合は Diagnostic なし
- 非コンストラクタのメソッド引数だけで依存が表現される場合も Diagnostic なし
- プロパティ型だけで依存が表現される場合も Diagnostic なし
new式だけで依存が表現される場合も Diagnostic なし- static メンバー利用だけで依存が表現される場合も Diagnostic なし
.editorconfigで field / base type / interface implementation / method parameter / property / object creation / static member の解析を無効化した場合はDCA002.editorconfigで owner type を namespace / type 単位 exclusion した場合は Diagnostic なし- 一致する dependency source が member-level exclusion された結果、
DCA002/DCA205/DCA206になるケース - owner type 上で一致する dependency / target / scope requirement を suppression した場合は Diagnostic なし
- 一致する依存先が必要契約を提供していない場合は
DCA001 RequiresDependencyContractが未使用依存型を指している場合はDCA002- 型レベル / assembly-level scope による scope マッチング
- 直接宣言と継承経由の target マッチング
- hierarchy と多段 chain による契約一致
- 空名、重複宣言、循環する包含グラフの診断
- custom exclusion attribute による owner type 解析スキップ
次の項目は将来の設計余地にすぎず、accepted roadmap ではありません。 現在の製品境界にも含まれず、scope に入れるには別の設計判断が必要です。
- 依存抽出トグルを超える EditorConfig ベースのポリシー制御
- trailing 2-segment 推定を超える namespace ベースのメタデータ推定
- DI 登録解析
- runtime 依存解決
- Scrutor の挙動
- Factory registration の挙動
- DI コンテナーの挙動
- Analyzer の allocation を抑制する
ImmutableArrayを優先するSymbolEqualityComparer.Defaultを使う- 文字列比較は ordinal の大文字小文字無視を使う
- この文書にある属性を実装する
- この文書にあるルール評価を実装する
- この文書にある診断を実装する
- 対応済みルールファミリの単体テストを追加する
- README と仕様書を実装状態に合わせて維持する