INW019 diagram-missing-dependency¶
Co robi¶
Opt-in, a po włączeniu ostrzeżenie. Czyta oznaczone diagramy w Mermaid i PlantUML z plików, które wymienia klucz diagrams, tak jak INW017 i INW018, i porównuje je z importami w kodzie. Zgłasza import z jednej warstwy albo jednego kontekstu, który diagram rysuje, do innego, który też rysuje, gdy diagram nie ma dla niego strzałki. Diagramy sprawdza uruchomienie dla całego projektu; sprawdzenie wybranych ścieżek i hook po każdej edycji ich nie sprawdzają.
Co liczy się jako narysowane:
- W diagramie
%% inwards: layersścieżka ciągłych strzałek od warstwy, która importuje, do warstwy importowanej.api --> application --> domainrysuje też import zapidodomain, bo warstwa może importować każdą warstwę pod sobą, a diagram zwykle rysuje tylko kolejne kroki. - W diagramie
%% inwards: contextsciągła strzałka z kontekstu, który importuje, do kontekstu importowanego, z dowolnego węzła wewnątrz pierwszego do dowolnego węzła wewnątrz drugiego.depends-onnie jest przechodnie, więc diagram też nie.
Każda brakująca para jest zgłaszana raz na diagram, w linii, w której warstwa albo kontekst, który importuje, jest zapisany po raz pierwszy, a komunikat podaje pierwszy import, który za nią stoi: plik, linię i to, co importuje.
Pomijane: warstwa albo kontekst, którego diagram nie rysuje albo rysuje tylko jako węzeł z klasą external; import wewnątrz jednej warstwy albo jednego kontekstu; import czegoś spoza wszystkich warstw i kontekstów (biblioteki standardowej, pakietu zewnętrznego).
Dlaczego to źle¶
Diagram, który pokazuje mniej strzałek, niż ma kod, mówi każdemu czytelnikowi i każdemu agentowi, który czyta dokumentację przed pisaniem kodu, że kod jest mniej powiązany, niż jest naprawdę. Kto ufa rysunkowi, przenosi moduł albo usuwa warstwę i psuje import, którego nikt nie narysował. INW018 łapie strzałkę, której zabrania konfiguracja; INW019 łapie strzałkę, o której diagram zapomniał.
[tool.inwards] pozostaje jedynym źródłem prawdy (ADR-045): diagram jest porównywany z kodem, a nigdy nie jest czytany jako konfiguracja.
Przykład¶
[tool.inwards]
diagrams = ["docs/*.mmd"]
layers = [
{ name = "domain", modules = ["shop.domain"] },
{ name = "application", modules = ["shop.application"] },
{ name = "infrastructure", modules = ["shop.infrastructure"] },
]
[tool.inwards.rules]
extend-select = ["INW019"]
Diagram, który rysuje powiązanie infrastruktury z aplikacją jako przerywane, jakby nie było importem:
%% inwards: layers
flowchart LR
application --> domain
infrastructure -.-> application
docs/layers.mmd:4:3: INW019 (warning) Layer "infrastructure" imports layer "application" in the code (shop/infrastructure/db.py:1 imports shop.application.place_order.Order), and the diagram draws no arrow or path of arrows from "infrastructure" to "application".
fix: Draw the import in the diagram: "infrastructure --> application".
1. Arrows point from the importing layer to the imported one, and solid arrows only: a dotted one (-.->) stands for something other than an import.
2. If the code shouldn't import "application" from "infrastructure", remove the import instead; INW001 reports it when [tool.inwards] forbids it.
docs: https://sircypkowskyy.github.io/inwards/rules/INW019/
Po poprawce strzałka jest ciągła. Cały projekt przechodzi:
W diagramie kontekstów, gdzie sales importuje shop.catalog.api, strzałka z węzła checkout do węzła api rysuje ten import; bez niej INW019 zgłasza sales w linii 3:
[[tool.inwards.contexts]]
name = "sales"
modules = ["shop.sales"]
depends-on = ["catalog"]
[[tool.inwards.contexts]]
name = "catalog"
modules = ["shop.catalog"]
public = ["shop.catalog.api"]
%% inwards: contexts
flowchart LR
subgraph sales["shop.sales"]
checkout["shop.sales.checkout"]
end
subgraph catalog["shop.catalog"]
api["shop.catalog.api"]:::public
end
checkout --> api
Jak działa¶
Diagram jest czytany raz dla INW017, INW018 i INW019 (czytnik opisuje strona INW017). Importy to te, które uruchomienie dla całego projektu zbiera dla INW004, z dwiema różnicami:
- Import pod
if TYPE_CHECKING:się liczy. INW004 go pomija, bo nigdy się nie wykonuje, więc nie może zamknąć cyklu w czasie działania. INW001, INW002 i INW003 oceniają go jak każdy inny import, a strzałka znaczy „może importować” w ich sensie, więc diagram, który pomija import tylko dla typów, rysuje mniej, niż wynosi zależność kodu. - Import trafia do warstwy albo kontekstu, do którego należy jego cel, według tego samego dopasowania najdłuższego prefiksu, którego używają INW001 i INW002, niezależnie od tego, czy cel jest sprawdzanym modułem.
Warstwa albo kontekst jest narysowany, gdy w diagramie jest węzeł, który go nazywa (identyfikatorem albo, dla nazwy, która nie może być identyfikatorem Mermaid, etykietą w cudzysłowie), i nie jest external; w diagramie kontekstów węzeł wewnątrz subgraphu oznacza kontekst tego subgraphu. Strzałki to te ciągłe albo pogrubione w jedną stronę, które czyta INW018; krawędzie przerywane, bez grotu i dwukierunkowe nie rysują importu.
Szkielet importów może wziąć linię import wewnątrz napisu za import. Plik, na którym stoi takie zgłoszenie, jest czytany ponownie pełnym parsowaniem, a diagramy są oceniane jeszcze raz, tak jak INW004 potwierdza cykl, więc zgłoszenie zawsze stoi na prawdziwym imporcie.
Jak naprawić¶
- Narysuj strzałkę, którą podaje poprawka, od węzła, który importuje, do węzła importowanego, jako ciągłą.
- W diagramie warstw wystarczy strzałka do warstwy pośredniej, gdy ta ma już ścieżkę do warstwy importowanej.
- Jeśli import nie powinien istnieć, usuń go z kodu. Gdy
[tool.inwards]go zabrania, zgłasza go też INW001 (warstwy) albo INW002 (konteksty). - Jeśli diagram celowo pomija warstwę, nie rysuj jej albo oznacz jej węzeł jako
:::external: INW019 pomija wtedy każdy import do niej i z niej.
Bezpieczeństwo poprawki¶
Bez automatycznej poprawki (autofix: false). Dodanie strzałki zmienia tylko dokumentację, ale tylko człowiek wie, czy błędny jest rysunek, czy kod.
Konfiguracja¶
Poziom: ostrzeżenie, więc rozjazd dokumentacji domyślnie nie blokuje Stop gate. Opt-in: włącz ją przez extend-select, zwykle razem z INW017 i INW018, i wymień pliki w diagrams:
[tool.inwards]
diagrams = ["README.md", "docs/**/*.md"]
[tool.inwards.rules]
extend-select = ["INW017", "INW018", "INW019"]
severity = { INW019 = "error" } # optional: block on drift
Reguła nie ma własnych opcji; [tool.inwards.rules.diagram-missing-dependency] przyjmuje tylko modules, które nic nie zmienia, bo diagram nie jest modułem. Zgłoszenia nie da się wyciszyć w linii, ani w diagramie, ani w pliku Pythona: narysuj strzałkę albo oznacz węzeł jako :::external. inwards import-diagram włącza INW017 i INW018, ale nie INW019, bo diagram zaimportowany przed napisaniem kodu nie może rysować importów, których jeszcze nie ma.
Znane ograniczenia¶
- Tylko uruchomienia dla całego projektu. Sprawdzenie wybranych ścieżek nie wie, jakie importy wykonuje reszta projektu.
- Warstwy i konteksty, nie moduły. Strzałka między dwoma węzłami modułów wewnątrz jednego kontekstu nie jest porównywana z importami między tymi modułami.
- Komunikat podaje jeden import. Para, za którą stoi wiele importów, pokazuje pierwszy według ścieżki pliku i kolejności w źródle; popraw diagram, nie ten jeden plik.
- Te same ograniczenia co INW017: podzbiory flowchartów Mermaid i diagramów komponentów PlantUML, linia po linii, a globy pomijają ukryte katalogi.
Źródła¶
- #347: reguła; #332: decyzja projektowa, zapisana w ADR-045.
- Mermaid: Flowcharts syntax: rodzaje krawędzi, subgraphy i klasy.