Skip to content

Commit 4ddfabe

Browse files
committed
Add linting and type checking
Added a GitHub workflow to run pre-commit, pylint, and pyright. pylint and pyright are not included as pre-commit checks, because they need to analyze the project as a whole, not just the changed files, and they do not work correctly in an isolated environment without having access to dependencies. Adjusted the pylint configuration to ignore diagnostics that are either not useful or duplicate those reported by pyright. Fixed all type errors reported by pyright. Added VS Code extension recommendations for all the formatting and linting tools, and enabled type checking in VS Code.
1 parent 0cb912f commit 4ddfabe

30 files changed

+261
-110
lines changed

.github/workflows/ci.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
7+
jobs:
8+
lint:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v4
12+
- uses: actions/setup-python@v5
13+
with:
14+
python-version: ">=3.10"
15+
cache: "pip"
16+
- name: Install dependencies
17+
run: |
18+
pip install .
19+
pip install pylint
20+
21+
- name: pre-commit
22+
uses: pre-commit/[email protected]
23+
24+
- name: pyright
25+
uses: jakebailey/pyright-action@v2
26+
27+
- name: pylint
28+
run: pylint zmk

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
fail_fast: false
22
repos:
33
- repo: https://github.com/psf/black
4-
rev: "23.9.1"
4+
rev: "24.8.0"
55
hooks:
66
- id: black
77
- repo: https://github.com/pycqa/isort
88
rev: "5.13.2"
99
hooks:
1010
- id: isort
1111
- repo: https://github.com/Lucas-C/pre-commit-hooks
12-
rev: v1.5.2
12+
rev: v1.5.5
1313
hooks:
1414
- id: remove-tabs
1515
- repo: https://github.com/pre-commit/pre-commit-hooks
16-
rev: v4.4.0
16+
rev: v4.6.0
1717
hooks:
1818
- id: trailing-whitespace
1919
- id: check-yaml

.vscode/extensions.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"recommendations": [
3+
"ms-python.black-formatter",
4+
"ms-python.isort",
5+
"ms-python.python"
6+
]
7+
}

.vscode/settings.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,7 @@
2121
"**/templates/common/**": "mako"
2222
},
2323
"isort.check": true,
24-
"isort.args": ["--profile", "black"]
24+
"isort.args": ["--settings-path", "${workspaceFolder}"],
25+
"python.analysis.importFormat": "relative",
26+
"python.analysis.typeCheckingMode": "standard"
2527
}

pyproject.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@ build-backend = "setuptools.build_meta"
4242
[tool.isort]
4343
profile = "black"
4444

45+
[tool.pylint.MASTER]
46+
ignore = "_version.py"
47+
48+
[tool.pylint."MESSAGES CONTROL"]
49+
disable = [
50+
"arguments-differ", # Covered by pyright
51+
"fixme",
52+
"too-few-public-methods",
53+
"too-many-arguments",
54+
"too-many-branches",
55+
"too-many-instance-attributes",
56+
"too-many-locals",
57+
]
58+
4559
[tool.setuptools]
4660
packages = ["zmk"]
4761

zmk/backports.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
Backports from Python > 3.10.
33
"""
44

5+
# pyright: reportMissingImports = false
6+
57
try:
68
# pylint: disable=unused-import
79
from enum import StrEnum

zmk/build.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@
22
Build matrix processing.
33
"""
44

5-
import collections.abc
5+
from collections.abc import Mapping, Sequence
66
from dataclasses import asdict, dataclass, field
77
from pathlib import Path
8-
from typing import Any, Iterable, Optional
8+
from typing import Any, Iterable, Optional, TypeVar, cast, overload
99

1010
import dacite
1111

1212
from .repo import Repo
1313
from .yaml import YAML
1414

15+
T = TypeVar("T")
16+
1517

1618
@dataclass
1719
class BuildItem:
@@ -52,7 +54,7 @@ class BuildMatrix:
5254

5355
_path: Path
5456
_yaml: YAML
55-
_data: Any
57+
_data: dict[str, Any] | None
5658

5759
@classmethod
5860
def from_repo(cls, repo: Repo):
@@ -64,7 +66,7 @@ def __init__(self, path: Path) -> None:
6466
self._yaml = YAML(typ="rt")
6567
self._yaml.indent(mapping=2, sequence=4, offset=2)
6668
try:
67-
self._data = self._yaml.load(self._path)
69+
self._data = cast(dict[str, Any], self._yaml.load(self._path))
6870
except FileNotFoundError:
6971
self._data = None
7072

@@ -106,7 +108,7 @@ def append(self, items: BuildItem | Iterable[BuildItem]) -> list[BuildItem]:
106108
return []
107109

108110
if not self._data:
109-
self._data = self._yaml.map()
111+
self._data = cast(dict[str, Any], self._yaml.map())
110112

111113
if "include" not in self._data:
112114
self._data["include"] = self._yaml.seq()
@@ -121,7 +123,7 @@ def remove(self, items: BuildItem | Iterable[BuildItem]) -> list[BuildItem]:
121123
:return: the items that were removed.
122124
"""
123125
if not self._data or "include" not in self._data:
124-
return False
126+
return []
125127

126128
removed = []
127129
items = [items] if isinstance(items, BuildItem) else items
@@ -138,7 +140,25 @@ def remove(self, items: BuildItem | Iterable[BuildItem]) -> list[BuildItem]:
138140
return removed
139141

140142

141-
def _keys_to_python(data: Any):
143+
@overload
144+
def _keys_to_python(data: str) -> str: ...
145+
146+
147+
@overload
148+
def _keys_to_python(
149+
data: Sequence[T],
150+
) -> Sequence[T]: ...
151+
152+
153+
@overload
154+
def _keys_to_python(data: Mapping[str, T]) -> Mapping[str, T]: ...
155+
156+
157+
@overload
158+
def _keys_to_python(data: T) -> T: ...
159+
160+
161+
def _keys_to_python(data: Any) -> Any:
142162
"""
143163
Fix any keys with hyphens to underscores so that dacite.from_dict() will
144164
work correctly.
@@ -151,10 +171,10 @@ def fix_key(key: str):
151171
case str():
152172
return data
153173

154-
case collections.abc.Sequence():
174+
case Sequence():
155175
return [_keys_to_python(i) for i in data]
156176

157-
case collections.abc.Mapping():
177+
case Mapping():
158178
return {fix_key(k): _keys_to_python(v) for k, v in data.items()}
159179

160180
case _:

zmk/commands/cd.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import shellingham
1111
import typer
1212

13-
from ..config import Config
13+
from ..config import get_config
1414
from ..exceptions import FatalError, FatalHomeMissing, FatalHomeNotSet
1515

1616

@@ -22,7 +22,7 @@ def cd(ctx: typer.Context):
2222
'Use "cd $(zmk config user.home)" instead.'
2323
)
2424

25-
cfg = ctx.find_object(Config)
25+
cfg = get_config(ctx)
2626
home = cfg.home_path
2727

2828
if home is None:

zmk/commands/code.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import typer
1616
from rich.markdown import Markdown
1717

18-
from ..config import Config, Settings
18+
from ..config import Config, Settings, get_config
1919
from ..exceptions import FatalError
2020
from ..menu import show_menu
2121
from ..repo import Repo
@@ -42,7 +42,7 @@ def code(
4242
):
4343
"""Open the repo or a .keymap or .conf file in a text editor."""
4444

45-
cfg = ctx.find_object(Config)
45+
cfg = get_config(ctx)
4646
repo = cfg.get_repo()
4747

4848
if open_build_matrix:
@@ -56,7 +56,7 @@ def code(
5656
subprocess.call(cmd, shell=True)
5757

5858

59-
def _get_file(repo: Repo, keyboard: str, open_conf: bool):
59+
def _get_file(repo: Repo, keyboard: Optional[str], open_conf: bool):
6060
if not keyboard:
6161
return repo.path
6262

@@ -98,7 +98,7 @@ class Editor:
9898
"Executable name or command line to execute this tool"
9999
support: Support = Support.FILE
100100
"Types of files this tool supports editing"
101-
test: Callable[[], bool] = None
101+
test: Callable[[], bool] | None = None
102102
"""
103103
Function that returns true if the tool is installed.
104104
Defaults to `which {self.cmd}`.
@@ -169,7 +169,11 @@ def _select_editor(cfg: Config):
169169
)
170170

171171
editor = show_menu("Select a text editor:", file_editors, filter_func=_filter)
172-
cfg.set(Settings.CORE_EDITOR, editor.get_command())
172+
editor_command = editor.get_command()
173+
if not editor_command:
174+
raise TypeError(f"Invalid editor {editor.name}")
175+
176+
cfg.set(Settings.CORE_EDITOR, editor_command)
173177

174178
explorer = None
175179
if editor.support & Support.DIR:
@@ -181,7 +185,11 @@ def _select_editor(cfg: Config):
181185
dir_editors,
182186
filter_func=_filter,
183187
)
184-
cfg.set(Settings.CORE_EXPLORER, explorer.get_command())
188+
explorer_command = explorer.get_command()
189+
if not explorer_command:
190+
raise TypeError(f"Invalid explorer {editor.name}")
191+
192+
cfg.set(Settings.CORE_EXPLORER, explorer_command)
185193

186194
cfg.write()
187195

zmk/commands/config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from rich.console import Console
99

1010
from .. import styles
11-
from ..config import Config
11+
from ..config import Config, get_config
1212

1313
console = Console(
1414
highlighter=styles.KeyValueHighlighter(), theme=styles.KEY_VALUE_THEME
@@ -17,7 +17,7 @@
1717

1818
def _path_callback(ctx: typer.Context, value: bool):
1919
if value:
20-
cfg = ctx.find_object(Config)
20+
cfg = get_config(ctx)
2121
print(cfg.path)
2222
raise typer.Exit()
2323

@@ -51,7 +51,7 @@ def config(
5151
):
5252
"""Get and set ZMK CLI settings."""
5353

54-
cfg = ctx.find_object(Config)
54+
cfg = get_config(ctx)
5555

5656
if name is None:
5757
_list_settings(cfg)

0 commit comments

Comments
 (0)