Lint (dm.lint)

Static checker against the dartwork-mpl anti-pattern catalog (0.4+).

dm.lint_code(source) checks a Python source string against the anti-pattern catalog shipped at asset/prompt/02-anti-patterns.yaml. The MCP lint_dartwork_mpl_code tool uses the same engine, so Python callers and AI assistants see the same violations. No lint CLI is provided.

Quick start

import dartwork_mpl as dm
from dartwork_mpl.lint import format_report

source = """
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(6.7, 4.0))
plt.tight_layout()
"""

issues = dm.lint_code(source)
for issue in issues:
    print(issue.rule_id, issue.severity, issue.message)

# Or render a human-readable report
print(format_report(issues))

Each rule entry has an id, severity (critical / warning / info), a short message, an optional why blurb, and a recommended fix_suggestion. The catalog lives in YAML so adding or tightening a rule is a single file edit; the lint engine reloads it on call.

load_rules(path) rejects unsupported severity, detector kind/check, invalid regex patterns, and malformed containers or required fields with contextual ValueError at load time. Previously some invalid catalogs loaded successfully or raised incidental access errors. Empty mappings and empty rules lists remain valid; filesystem and YAML syntax errors retain their native exceptions.

Catalog highlights

  • figsize-direct — raw figsize=(w, h) tuples are forbidden; use figsize=dm.figsize("<n>cm", "<aspect>").

  • dm-subplots-removed — dm.subplots / dm.figure were removed; use plt.subplots(figsize=dm.figsize(...)).

  • raw-width-number — bare numbers passed to dm.figsize are rejected because they carry no unit.

  • tight-layout — plt.tight_layout() is forbidden; use dm.simple_layout(fig).

  • deprecated-width-token — deprecated 0.3 width tokens (dm.SW / MW / TW / DW).

  • oversize-width — widths beyond 17 cm break most page layouts.

  • fontsize-literal / linewidth-literal — pass numeric values via dm.fs(n) / dm.lw(n) so they track the active style.

  • raw-hex-color — prefer named palette tokens (oc., tw., dc., …) over inline hex.

  • jet-cmap — flag rainbow colormaps that misrepresent ordinal data.

The full list (always authoritative) is at asset/prompt/02-anti-patterns.yaml in the source tree, or via the MCP resource dartwork-mpl://guide/anti-patterns.

API

dartwork-mpl lint engine.

Loads the anti-pattern catalog from asset/prompt/02-anti-patterns.yaml and applies it to a Python source string. Used by the MCP lint_dartwork_mpl_code tool, the Python dm.lint_code(code) API, and CI drift tests. No lint CLI is provided.

The catalog owns rule text and selects regex, substring, or static AST detectors. AST name checks consult the live public matplotlib registries; supplied source is never executed or imported.

class dartwork_mpl.lint.Issue(rule_id: str, severity: str, message: str, line: int | None = None, snippet: str | None = None, column: int | None = None, fix_suggestion: str | None = None)[source]

Bases: object

A detected violation.

column is the absolute character offset of the match in the source string (0-indexed). It is included to disambiguate multiple violations on the same line — (rule_id, line) alone collapses them and hides the second occurrence from auto-fixers.

fix_suggestion mirrors the YAML field of the same name and is surfaced inline by format_report() so AI agents can apply a fix without a second round-trip.

column: int | None = None
fix_suggestion: str | None = None
line: int | None = None
message: str
rule_id: str
severity: str
snippet: str | None = None
class dartwork_mpl.lint.Rule(id: str, severity: str, detector_kind: str, detector_value: str, message: str, why: str | None = None, fix_suggestion: str | None = None)[source]

Bases: object

A single anti-pattern definition.

detector_kind: str
detector_value: str
fix_suggestion: str | None = None
id: str
message: str
severity: str
why: str | None = None
dartwork_mpl.lint.apply_lint_fixes(code: str) → tuple[str, list[Issue], list[Issue]][source]

Fix proven local calls while preserving refused source and occurrence identity.

Applied Issue locations refer to the original source. Unfixed locations refer to the returned source. Malformed input is returned exactly with no applied issues; neither parsing nor rewriting executes supplied Python.

dartwork_mpl.lint.format_report(issues: list[Issue]) → str[source]

Render issues as a multi-line [SEV] rule-id: message report.

The full message is preserved (including any subsequent lines from a YAML | block scalar) and indented under the header line so reports stay readable in plain-text MCP/CLI output.

If a rule provides a fix_suggestion, it is emitted on its own line directly after the message as → fix: <suggestion> so AI agents can lift the replacement directly without a second round-trip.

dartwork_mpl.lint.lint(code: str, *, rules: Iterable[Rule] | None = None) → list[Issue][source]

Apply anti-pattern rules to a Python source string.

Note

code must be Python source, not YAML/Markdown/JSON. Most built-in executable rules require complete Python syntax. Deliberate text-only and custom regex/substring rules still inspect raw text.

Parameters:
  • code (str) – Python source to scan.

  • rules (Iterable[Rule] | None, optional) – Override the rule set (e.g. for tests). Defaults to load_rules() output.

Returns:

Issues in declaration order, deduplicated by (rule_id, column) so multiple violations on the same line are reported separately.

Return type:

list[Issue]

dartwork_mpl.lint.load_rules(path: Path | None = None) → list[Rule][source]

Load and parse the anti-pattern catalog.

Parameters:

path (Path | None, optional) – Override path for testing. Defaults to the bundled 02-anti-patterns.yaml.

Returns:

Parsed rule objects in declaration order. An empty mapping or explicit empty rules list returns an empty list.

Return type:

list[Rule]

Raises:

ValueError – If catalog containers or required rule fields are malformed, severity is unsupported, a detector kind/check is unsupported, or a regex is not a valid string pattern. These invalid-input errors are reported at load time with catalog/rule context. Filesystem and YAML syntax errors retain their native exceptions.

dartwork_mpl.lint.migrate_legacy_code(code: str) → str[source]

Rewrite proven supported styles and annotate executable legacy statements.

Comments, literal values and all bytes outside explicit edits are preserved. Unsafe style calls and malformed AST/token streams are returned unchanged. Hints never split continuations, inline suites or multiline literal contents.