Przejdź do treści

INW018 diagram-forbidden-edge

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 zgłasza strzałkę, która rysuje import zabroniony przez [tool.inwards], w linii samego diagramu. Wszystkie diagramy sprawdza uruchomienie dla całego projektu; hook po edycji, edytor i MCP check_files sprawdzają wymieniony diagram, gdy to on jest edytowanym albo podanym plikiem.

Ciągła strzałka w jedną stronę (-->, ==>, --->, -- text -->) znaczy „może importować”: węzeł na początku strzałki może importować węzeł przy grocie. Co jest zgłaszane:

  • W diagramie %% inwards: layers strzałka skierowana na zewnątrz, z warstwy wewnętrznej do zewnętrznej, albo między dwiema warstwami sąsiednimi na tym samym miejscu (zagnieżdżona tablica w layers albo a | b w rolach szablonu).
  • W diagramie %% inwards: contexts strzałka z jednego kontekstu do innego, którego pierwszy nie wymienia w depends-on. Gdy go wymienia: strzałka do węzła wewnątrz drugiego kontekstu, którego etykieta w cudzysłowie nie jest jednym z jego modułów public.

Węzeł w diagramie kontekstów należy do subgraphu najwyższego poziomu, w którym został zapisany po raz pierwszy, a poza wszystkimi subgraphami jest własnym kontekstem. Każda zabroniona para jest zgłaszana raz na diagram, przy pierwszej strzałce.

Pomijane: krawędzie przerywane (-.->), niewidoczne (~~~), bez grotu (---) i dwukierunkowe (<-->); węzły z klasą external; strzałki wewnątrz jednej warstwy albo jednego kontekstu; koniec, który nie nazywa żadnej zadeklarowanej warstwy ani kontekstu (to zgłasza INW017); strzałka do węzła bez etykiety wewnątrz kontekstu, bo diagram nie mówi, jaki to moduł.

Dlaczego to źle

Diagram, który pokazuje domain --> infrastructure, mówi każdemu czytelnikowi i każdemu agentowi, który czyta dokumentację przed pisaniem kodu, że domena może importować infrastrukturę. Sprawdzenia odrzucają taki import, więc kto idzie za rysunkiem, pisze kod, który nie przechodzi INW001, INW002 albo INW003. Albo rysunek ma rację, a konfiguracja nie nadąża, i nikt nie wie, które z nich. Tak czy inaczej dokumentacja i reguły nie zgadzają się co do architektury.

[tool.inwards] pozostaje jedynym źródłem prawdy (ADR-045): strzałki są sprawdzane względem niej, a nigdy nie są czytane jako konfiguracja. INW017 sprawdza nazwy w diagramie, INW018 jego strzałki.

Przykład

pyproject.toml
[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 = ["INW018"]
shop/domain/order.py
class Order:
    pass
shop/application/place_order.py
from shop.domain.order import Order
shop/infrastructure/db.py
from shop.domain.order import Order

Diagram, na którym domena importuje infrastrukturę:

docs/layers.mmd
%% inwards: layers
flowchart LR
  infrastructure --> application --> domain
  domain --> infrastructure
inwards check
docs/layers.mmd:4:3: INW018 (warning) "domain --> infrastructure" draws an import [tool.inwards] forbids: "domain" is inner to "infrastructure", and an inner layer may not import an outer one.
  fix: Turn the arrow around or remove it: "infrastructure --> domain" is the direction [tool.inwards] allows.
    1. Arrows point from the importing layer to the imported one, so they point inward.
    2. If the arrow shows a call at run time or a flow of data rather than an import, draw it dotted (-.->), which INW018 doesn't read.
    3. If the code really needs this import, ask the user whether [tool.inwards] should allow it; don't edit [tool.inwards] yourself.
  docs: https://sircypkowskyy.github.io/inwards/rules/INW018/

Po poprawce strzałka pokazuje to, co naprawdę znaczy: infrastruktura w czasie działania wywołuje domenę z powrotem, więc strzałka jest przerywana. Cały projekt przechodzi:

docs/layers.mmd
%% inwards: layers
flowchart LR
  infrastructure --> application --> domain
  domain -. events .-> infrastructure
inwards check

W diagramie kontekstów, gdzie sales zależy od catalog, a catalog udostępnia shop.catalog.api, pierwsza strzałka jest w porządku, a dwie pozostałe są zgłaszane:

pyproject.toml
[[tool.inwards.contexts]]
name = "sales"
modules = ["shop.sales"]
depends-on = ["catalog"]

[[tool.inwards.contexts]]
name = "catalog"
modules = ["shop.catalog"]
public = ["shop.catalog.api"]
docs/contexts.mmd
%% inwards: contexts
flowchart LR
  subgraph sales["shop.sales"]
    checkout["shop.sales.checkout"]
  end
  subgraph catalog["shop.catalog"]
    api["shop.catalog.api"]:::public
    store["shop.catalog.store"]
  end
  checkout --> api      %% allowed
  checkout --> store    %% store isn't public
  catalog --> sales     %% catalog doesn't depend on sales

Jak działa

Diagram jest czytany raz dla INW017 i INW018 (czytnik opisuje strona INW017). Dla każdej ciągłej albo pogrubionej strzałki w jedną stronę między dwoma różnymi węzłami, z których żaden nie jest external:

  • Warstwy. Oba końce muszą nazywać warstwę: identyfikator to nazwa warstwy albo, dla warstwy, której nazwa nie może być identyfikatorem Mermaid (slices.domain z szablonu), etykieta węzła w cudzysłowie jest jednym z wpisów modules tej warstwy. Strzałka jest dozwolona, gdy grot wskazuje warstwę na niższym miejscu niż początek. Miejsce warstwy to jej pozycja w layers, od najbardziej wewnętrznej; warstwy sąsiednie dzielą jedno miejsce.
  • Konteksty. Każdy koniec oznacza kontekst subgraphu najwyższego poziomu, w którym leży, albo siebie samego poza wszystkimi subgraphami, rozpoznany tak samo jak warstwa. Kontekst początku musi wymieniać kontekst grotu w depends-on. Gdy grot to węzeł wewnątrz swojego kontekstu (nie sam subgraph) i ma etykietę w cudzysłowie, ta etykieta musi leżeć pod jednym z wpisów public kontekstu albo być modułem wpisu dokładnego (=), tak jak INW003 rozstrzyga to dla importu.

Oceniane są tylko strzałki samego diagramu. Import w kodzie, którego diagram nie rysuje, należy do INW019.

Jak naprawić

  1. Odwróć strzałkę skierowaną na zewnątrz: strzałki biegną od warstwy, która importuje, do warstwy importowanej.
  2. Usuń strzałkę między warstwami sąsiednimi albo między kontekstami, które od siebie nie zależą.
  3. Strzałkę do innego kontekstu skieruj na jeden z jego modułów publicznych.
  4. Strzałkę, która oznacza coś innego niż import (wywołanie w czasie działania, zdarzenia, przepływ danych), narysuj jako przerywaną: -.->.
  5. Jeśli diagram jest dobry, a konfiguracja nie nadąża, poproś użytkownika o zmianę [tool.inwards]; agent jej nie edytuje.

Bezpieczeństwo poprawki

Bez automatycznej poprawki (autofix: false). Tylko człowiek wie, czy błędny jest rysunek, czy konfiguracja. Przerysowanie strzałki zmienia tylko dokumentację; zmiana [tool.inwards] zmienia to, co sprawdza każda reguła.

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 wymień pliki w diagrams:

pyproject.toml
[tool.inwards]
diagrams = ["README.md", "docs/**/*.md"]

[tool.inwards.rules]
extend-select = ["INW017", "INW018"]
severity = { INW018 = "error" }      # optional: block on drift

Reguła nie ma własnych opcji; [tool.inwards.rules.diagram-forbidden-edge] przyjmuje tylko modules, które nic nie zmienia, bo diagram nie jest modułem. Zgłoszenia nie da się wyciszyć w linii: zamiast tego narysuj krawędź jako przerywaną albo oznacz węzeł jako :::external. Wpis diagrams, do którego nie pasuje żaden plik, zgłasza INW017.

inwards import-diagram zapisuje na podstawie diagramu [tool.inwards], przez który INW017 i INW018 potem przechodzą.

Znane ograniczenia

  • Tylko bezpośrednie strzałki. a --> b --> c pozwala a importować b, a b importować c; o a i c nic nie mówi, bo depends-on nie jest przechodnie.
  • Moduł węzła pochodzi z etykiety w cudzysłowie. Strzałka do węzła bez etykiety wewnątrz kontekstu nie jest sprawdzana względem public.
  • Te same ograniczenia co INW017: tylko flowcharty, linia po linii, edycja kodu nie sprawdza diagramów, a globy pomijają ukryte katalogi.

Źródła