Przejdź do treści

INW017 diagram-unknown-name

Co robi

Opt-in, a po włączeniu ostrzeżenie. Czyta diagramy architektury w Mermaid i PlantUML z plików, które wymienia klucz diagrams, i zgłasza nazwę, której nie zna [tool.inwards] albo kod, w linii samego diagramu. Wszystkie diagramy sprawdza uruchomienie dla całego projektu (inwards check bez ścieżek albo Stop gate z stop-gate = "project"); hook po edycji, inwards server, MCP check_files i inwards check FILE sprawdzają wymieniony diagram, gdy to on jest edytowanym albo podanym plikiem (gdzie działa).

Diagram się liczy tylko wtedy, gdy jest typu flowchart albo graph i zaczyna się od komentarza-znacznika, przed nagłówkiem albo zaraz po nim:

  • %% inwards: layers: każdy identyfikator węzła to nazwa warstwy.
  • %% inwards: contexts: każdy subgraph na najwyższym poziomie i każdy węzeł poza subgraphem to nazwa kontekstu. Węzły wewnątrz subgraphu mogą mieć dowolne identyfikatory.

Warstwę albo kontekst, którego nazwa nie może być identyfikatorem Mermaid, na przykład slices.domain z szablonu albo nazwa ze spacją, nazywa węzeł o dowolnym identyfikatorze, którego etykieta w cudzysłowie jest jednym z jej wpisów modules: slices_domain["shop.domain"]. Nazwa, która może być identyfikatorem, musi nim być, więc literówka w niej dalej jest zgłaszana.

W obu rodzajach etykieta w cudzysłowie to prefiks modułu albo selektor warstwy (shop.domain, shop.*.domain) i musi pasować do istniejącego modułu. Etykieta bez cudzysłowu albo ze spacjami to podpis i nie jest sprawdzana. Węzeł z klasą external (db:::external albo class db external) oznacza coś spoza [tool.inwards], na przykład bazę danych albo zewnętrzne API, i jest pomijany.

Co jest zgłaszane, raz na nazwę i diagram, przy pierwszym wystąpieniu:

  • identyfikator węzła albo subgraphu, który nie nazywa żadnej zadeklarowanej warstwy ani kontekstu, razem z najbliższą zadeklarowaną nazwą, gdy wygląda to na literówkę;
  • etykieta w cudzysłowie, do której nie pasuje żaden moduł, razem z najbliższym prefiksem modułu;
  • wpis diagrams, do którego nie pasuje żaden plik, przy tym wpisie w pyproject.toml.

Bloki Mermaid bez znacznika to zwykła dokumentacja i nie są czytane. Strzałki należą do INW018: porównuje je z kolejnością warstw, depends-on i public.

Dlaczego to źle

Zespoły rysują swoją architekturę w README albo na stronie dokumentacji, a GitHub, Zensical i większość edytorów renderuje Mermaid w Markdownie. Nic nie wiąże rysunku z kodem: warstwa zmienia nazwę, pakiet się przenosi, a diagram dalej pokazuje stary obraz. Nowe osoby w projekcie i agenci, którzy czytają dokumentację przed pisaniem kodu, pracują wtedy z błędną mapą. Agent poproszony, żeby „trzymał się architektury z docs/architecture.md”, wstawi kod do warstwy services, którą konfiguracja nazywa application.

[tool.inwards] pozostaje jedynym źródłem prawdy (ADR-045): diagram jest sprawdzany względem niej i kodu, a nigdy nie jest czytany jako konfiguracja. Zgłoszenie znaczy, że rysunek się rozjechał; poprawka to przerysować go albo zapytać użytkownika, czy to konfiguracja powinna się zmienić.

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 = ["INW017"]
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, który nazywa warstwę infrastruktury infra i ma literówkę w pakiecie domeny:

docs/layers.mmd
%% inwards: layers
flowchart LR
  infra --> application --> domain["shop.domian"]
  infra --> postgres[("Postgres")]:::external
inwards check
docs/layers.mmd:3:3: INW017 (warning) Node "infra" in this layers diagram names no layer: the layers are "domain", "application" and "infrastructure".
  fix: Rename "infra" or mark it :::external.
    1. Rename the node to the layer it shows.
    2. If it stands for something outside [tool.inwards] (a database, a third-party service), mark it infra:::external.
    3. If the diagram is right and [tool.inwards] is missing it, ask the user; don't edit [tool.inwards] yourself.
  docs: https://sircypkowskyy.github.io/inwards/rules/INW017/

docs/layers.mmd:3:29: INW017 (warning) "domain" is labelled "shop.domian" in this layers diagram, and no module matches "shop.domian".
  fix: Change the label to "shop.domain".

Po poprawce węzeł ma nazwę warstwy, a etykieta nazwę pakietu. Cały projekt przechodzi:

docs/layers.mmd
%% inwards: layers
flowchart LR
  infrastructure --> application --> domain["shop.domain"]
  infrastructure --> postgres[("Postgres")]:::external
inwards check

Jak działa

Czytnik to ręcznie napisany parser linii w src/core/src/diagram/; parser samego Mermaida potrzebuje DOM przeglądarki. Czyta składnię flowchartów opisaną w dokumentacji Mermaida:

  • Gdzie są diagramy. W pliku .mmd albo .mermaid cały plik. W każdym innym wymienionym pliku każdy blok kodu, którego info string to mermaid (z backtickami albo tyldami, tak jak czyta je CommonMark). Front matter YAML i dyrektywy %%{init: ...}%% przed nagłówkiem są pomijane. Diagramy komponentów PlantUML (pliki .puml i bloki kodu plantuml, oznaczone ' inwards: layers albo ' inwards: contexts) czyta osobny czytnik do tego samego modelu; co czyta, opisuje przewodnik po diagramach, a wskazówki naprawy używają dla nich składni PlantUML ([db] <<external>>).
  • Węzły. Identyfikator z liter, cyfr, _ i myślników w środku (order-api), w dowolnym kształcie: [ ], ( ), ([ ]), [[ ]], [( )], (( )), ((( ))), > ], { }, {{ }}, równoległoboki i trapezy oraz @{ shape: ..., label: "..." }. Etykieta zapisana jako napis Markdown ("`text`") to podpis.
  • Krawędzie. Wszystkie rodzaje, z tekstem w środku (-- text -->) albo za nimi (-->|text|), z identyfikatorem krawędzi (e1@-->), dłuższe (--->), łańcuchy (a --> b --> c) i & (a & b --> c). Czytnik zapisuje, które krawędzie są ciągłymi albo pogrubionymi strzałkami w jedną stronę; INW018 czyta je jako „może importować”.
  • Reszta. subgraph id [title] i subgraph id["title"] aż do end, także zagnieżdżone; class a,b name; :::name za węzłem; komentarze %% w osobnej linii albo za instrukcją; ; między instrukcjami. classDef, style, linkStyle, click, direction, accTitle i accDescr są pomijane.

Instrukcja, której czytnik nie potrafi śledzić, jest czytana tylko do miejsca, w którym przestaje mieć sens; to, co było przed nim, się liczy. Dla linii, której nie umie przeczytać, nic nie jest zgłaszane.

To, czy etykieta w cudzysłowie istnieje, rozstrzyga się tak jak w sprawdzeniu prefiksu warstwy przez INW006: jakiś moduł jest jej równy albo leży pod nią, albo selektor do jakiegoś pasuje. Moduły to moduły własne projektu z uruchomienia dla całego projektu, a gdy jeden diagram jest sprawdzany osobno (hook, edytor, podana ścieżka), pliki Pythona pod korzeniem konfiguracji, wylistowane, ale nie czytane.

Jak naprawić

  1. Zmień nazwę węzła albo subgraphu na warstwę lub kontekst, który pokazuje, zapisaną tak jak w [tool.inwards].
  2. Popraw etykietę na prefiks modułu, który węzeł oznacza, albo usuń cudzysłów, żeby była podpisem.
  3. Oznacz węzeł, który nie należy do projektu, jako :::external.
  4. 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). Zgłoszenie mówi, że diagram i konfiguracja się nie zgadzają; tylko człowiek wie, które z nich ma rację. Zmiana nazwy węzła zmienia tylko dokumentację i jest bezpieczna; 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 i wymień pliki w diagrams:

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

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

Reguła nie ma własnych opcji; [tool.inwards.rules.diagram-unknown-name] przyjmuje tylko modules, które nic nie zmienia, bo diagram nie jest modułem. Zgłoszenia nie da się wyciszyć w linii: diagram nie ma komentarza Pythona, a komentarz %% byłby kolejną rzeczą, którą czytnik musiałby pomijać. Zamiast tego oznacz węzeł jako :::external albo popraw nazwę.

Znane ograniczenia

  • Nazwy warstw i kontekstów, które nie są identyfikatorami Mermaid, na przykład slices.domain z szablonu, nie mogą być identyfikatorem węzła; taka warstwa jest rozpoznawana tylko przez etykietę w cudzysłowie, która jest dokładnie jednym z jej wpisów modules, i tylko wtedy, gdy żadna inna taka warstwa nie ma tego samego wpisu.
  • Tylko flowcharty Mermaid i diagramy komponentów PlantUML. C4Context, architecture-beta, diagramy klas i inne rodzaje diagramów PlantUML nie są czytane; znacznik na takim diagramie jest pomijany. Składnię PlantUML, którą czyta Inwards, opisuje przewodnik po diagramach.
  • Linia po linii. Instrukcja rozbita na kilka linii albo etykieta z " w środku (#quot; w Mermaid) może zostać przeczytana tylko w części.
  • Edycja kodu nie sprawdza diagramów. Edycja diagramu uruchamia regułę w hooku i w edytorze; pakiet przemianowany w kodzie dociera do rysunku przy następnym uruchomieniu dla całego projektu.
  • Ukryte katalogi (.github/) i katalogi, które pomija każde przejście po drzewie (virtualenvy, node_modules), nie są przeszukiwane przez glob; taki plik podaj bez symboli wieloznacznych.

Źródła