Skip to content

Commit 7b1ec5b

Browse files
Enforce stricter linting across the entire codebase, covering both TypeScript and Python files. (#96)
* upgrade-to-eslint-9 * lint-python * add-eslint.config.mjs * Automatic application of license header * lint * fix-ruff-lints * more-python-lint-fixes * Automatic application of license header * move-comment-up * more-fixes * address-feedbacks * import-types * lint * fix-lint * lint-more-and-enable-mypy * lint-test-files * fix-tests * ignore-semver-file * revert-semver-file * add-mypy-ignore-comments --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 7dc5c0e commit 7b1ec5b

29 files changed

Lines changed: 949 additions & 1003 deletions

.licenserc.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ header:
2222
- 'yarn.lock'
2323
- 'jupyter_builder/yarn.js'
2424
- 'jupyter_builder/_version.py'
25+
- 'jupyter_builder/jupyterlab_semver.py'
2526
- THIRD_PARTY_LICENSES/**
2627

2728
comment: on-failure

.pre-commit-config.yaml

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,12 @@ repos:
4949
- id: rst-inline-touching-normal
5050

5151
- repo: https://github.com/pre-commit/mirrors-mypy
52-
rev: "v1.8.0"
52+
rev: "v1.15.0"
5353
hooks:
5454
- id: mypy
55-
files: jupyter_server
56-
stages: [manual]
55+
files: ^jupyter_builder/
5756
additional_dependencies:
58-
["traitlets>=5.13"]
57+
["traitlets", "jupyter-core", "types-requests"]
5958

6059
- repo: https://github.com/astral-sh/ruff-pre-commit
6160
rev: v0.15.11
@@ -65,9 +64,3 @@ repos:
6564
args: ["--fix", "--show-fixes"]
6665
- id: ruff-format
6766
types_or: [python, jupyter]
68-
69-
# - repo: https://github.com/scientific-python/cookie
70-
# rev: "2024.01.24"
71-
# hooks:
72-
# - id: sp-repo-review
73-
# additional_dependencies: ["repo-review[cli]"]

eslint.config.mjs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright (c) Jupyter Development Team.
3+
* Distributed under the terms of the Modified BSD License.
4+
*/
5+
6+
import js from '@eslint/js';
7+
import tseslint from 'typescript-eslint';
8+
import prettierPluginRecommended from 'eslint-plugin-prettier/recommended';
9+
import { defineConfig, globalIgnores } from 'eslint/config';
10+
11+
export default defineConfig([
12+
globalIgnores([
13+
'node_modules/',
14+
'lib/',
15+
'dist/',
16+
'coverage/',
17+
'**/*.d.ts',
18+
'jupyter_builder/yarn.js'
19+
]),
20+
{
21+
files: ['**/*.ts'],
22+
extends: [js.configs.recommended, tseslint.configs.recommended],
23+
plugins: {
24+
'@typescript-eslint': tseslint.plugin
25+
},
26+
languageOptions: {
27+
parser: tseslint.parser,
28+
parserOptions: {
29+
project: './tsconfig.json'
30+
}
31+
},
32+
rules: {
33+
'@typescript-eslint/no-namespace': 'off',
34+
'@typescript-eslint/no-require-imports': 'off'
35+
}
36+
},
37+
prettierPluginRecommended
38+
]);

jupyter_builder/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Jupyter Builder package."""
2+
13
# Copyright (c) Jupyter Development Team.
24
# Distributed under the terms of the Modified BSD License.
35

jupyter_builder/base_extension_app.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55

66
from __future__ import annotations
77

8-
import os
98
from copy import copy
9+
from pathlib import Path
1010

1111
from jupyter_core.application import JupyterApp, base_aliases, base_flags
1212
from jupyter_core.paths import jupyter_path
1313
from traitlets import List, Unicode, default
1414

1515
from .debug_log_file_mixin import DebugLogFileMixin
1616

17-
HERE = os.path.dirname(os.path.abspath(__file__))
17+
HERE = str(Path(__file__).resolve().parent)
1818

1919
flags = dict(base_flags)
2020

@@ -27,11 +27,12 @@
2727
aliases = dict(base_aliases)
2828
aliases["debug-log-path"] = "DebugLogFileMixin.debug_log_path"
2929

30-
# VERSION = get_app_version()
31-
VERSION = 1
30+
VERSION = "1"
3231

3332

3433
class BaseExtensionApp(JupyterApp, DebugLogFileMixin):
34+
"""Base application class for JupyterLab extension CLI commands."""
35+
3536
version = VERSION
3637
flags = flags
3738
aliases = aliases
@@ -42,22 +43,18 @@ class BaseExtensionApp(JupyterApp, DebugLogFileMixin):
4243
help="The standard paths to look in for prebuilt JupyterLab extensions",
4344
)
4445

45-
# @default("labextensions_path")
46-
# def _default_labextensions_path(self):
47-
# lab = LabApp()
48-
# lab.load_config_file()
49-
# return lab.extra_labextensions_path + lab.labextensions_path
5046
@default("labextensions_path")
5147
def _default_labextensions_path(self) -> list[str]:
5248
return jupyter_path("labextensions")
5349

54-
def start(self):
50+
def start(self) -> None:
51+
"""Start the extension app and run the configured task."""
5552
with self.debug_logging():
5653
self.run_task()
5754

58-
def run_task(self):
59-
pass
55+
def run_task(self) -> None:
56+
"""Execute the app's primary task; override in subclasses."""
6057

61-
def _log_format_default(self):
62-
"""A default format for messages"""
58+
def _log_format_default(self) -> str:
59+
"""Return the default log format string."""
6360
return "%(message)s"

jupyter_builder/commands.py

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,47 @@
1-
"""JupyterLab command handler"""
1+
"""JupyterLab command handler."""
22

33
# Copyright (c) Jupyter Development Team.
44
# Distributed under the terms of the Modified BSD License.
55

6+
from __future__ import annotations
7+
68
import itertools
9+
from collections.abc import Callable
10+
from typing import Any
711

812
from .jupyterlab_semver import Range, gt, gte, lt, lte
913

14+
_CmpFn = Callable[[str | Any, str | Any, bool], bool]
15+
1016

11-
def _test_overlap(spec1, spec2, drop_prerelease1=False, drop_prerelease2=False):
17+
def _test_overlap(
18+
spec1: str,
19+
spec2: str,
20+
drop_prerelease1: bool = False,
21+
drop_prerelease2: bool = False,
22+
) -> bool | None:
1223
"""Test whether two version specs overlap.
1324
1425
Returns `None` if we cannot determine compatibility,
1526
otherwise whether there is an overlap
1627
"""
1728
cmp = _compare_ranges(
18-
spec1, spec2, drop_prerelease1=drop_prerelease1, drop_prerelease2=drop_prerelease2
29+
spec1,
30+
spec2,
31+
drop_prerelease1=drop_prerelease1,
32+
drop_prerelease2=drop_prerelease2,
1933
)
2034
if cmp is None:
21-
return
35+
return None
2236
return cmp == 0
2337

2438

25-
def _compare_ranges(spec1, spec2, drop_prerelease1=False, drop_prerelease2=False): # noqa
39+
def _compare_ranges( # noqa: C901, PLR0912
40+
spec1: str,
41+
spec2: str,
42+
drop_prerelease1: bool = False,
43+
drop_prerelease2: bool = False,
44+
) -> int | None:
2645
"""Test whether two version specs overlap.
2746
2847
Returns `None` if we cannot determine compatibility,
@@ -31,15 +50,15 @@ def _compare_ranges(spec1, spec2, drop_prerelease1=False, drop_prerelease2=False
3150
is higher/newer than spec2.
3251
"""
3352
# Test for overlapping semver ranges.
34-
r1 = Range(spec1, True)
35-
r2 = Range(spec2, True)
53+
r1 = Range(spec1, True) # type: ignore[no-untyped-call]
54+
r2 = Range(spec2, True) # type: ignore[no-untyped-call]
3655

3756
# If either range is empty, we cannot verify.
3857
if not r1.range or not r2.range:
39-
return
58+
return None
4059

4160
# Set return_value to a sentinel value
42-
return_value = False
61+
return_value: int | bool | None = False
4362

4463
# r1.set may be a list of ranges if the range involved an ||,
4564
# so we need to test for overlaps between each pair.
@@ -63,13 +82,14 @@ def _compare_ranges(spec1, spec2, drop_prerelease1=False, drop_prerelease2=False
6382
continue
6483

6584
# Handle single value specifiers.
66-
lx = lte if x1 == x2 else lt
67-
ly = lte if y1 == y2 else lt
68-
gx = gte if x1 == x2 else gt
69-
gy = gte if x1 == x2 else gt
85+
lx: _CmpFn = lte if x1 == x2 else lt
86+
ly: _CmpFn = lte if y1 == y2 else lt
87+
gx: _CmpFn = gte if x1 == x2 else gt
88+
gy: _CmpFn = gte if x1 == x2 else gt
7089

7190
# Handle unbounded (>) specifiers.
72-
def noop(x, y, z):
91+
def noop(_x: str | Any, _y: str | Any, _z: bool) -> bool:
92+
"""Return True unconditionally, representing an unbounded range."""
7393
return True
7494

7595
if x1 == x2 and o1.startswith(">"):
@@ -79,15 +99,15 @@ def noop(x, y, z):
7999

80100
# Check for overlap.
81101
if (
82-
(gte(x1, y1, True) and ly(x1, y2, True))
102+
(gte(x1, y1, True) and ly(x1, y2, True)) # type: ignore[no-untyped-call]
83103
or (gy(x2, y1, True) and ly(x2, y2, True))
84-
or (gte(y1, x1, True) and lx(y1, x2, True))
104+
or (gte(y1, x1, True) and lx(y1, x2, True)) # type: ignore[no-untyped-call]
85105
or (gx(y2, x1, True) and lx(y2, x2, True))
86106
):
87107
# if we ever find an overlap, we can return immediately
88108
return 0
89109

90-
if gte(y1, x2, True):
110+
if gte(y1, x2, True): # type: ignore[no-untyped-call]
91111
if return_value is False:
92112
# We can possibly return 1
93113
return_value = 1
@@ -96,7 +116,7 @@ def noop(x, y, z):
96116
return_value = None
97117
continue
98118

99-
if gte(x1, y2, True):
119+
if gte(x1, y2, True): # type: ignore[no-untyped-call]
100120
if return_value is False:
101121
return_value = -1
102122
elif return_value == 1:

jupyter_builder/core_path.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Utilities for locating and resolving JupyterLab core package metadata."""
2+
13
# Copyright (c) Jupyter Development Team.
24
# Distributed under the terms of the Modified BSD License.
35

@@ -15,8 +17,10 @@ def _home_dir() -> Path:
1517

1618

1719
def get_core_meta(
18-
version: str | None = None, ext_path: str | os.PathLike[str] | None = None
20+
version: str | None = None,
21+
ext_path: str | os.PathLike[str] | None = None,
1922
) -> str:
23+
"""Return the path to the core package JSON, downloading it if needed."""
2024
requested_version = version
2125

2226
if requested_version is None:
@@ -48,13 +52,12 @@ def get_core_meta(
4852

4953

5054
def _is_wildcard_version(version: str) -> bool:
51-
"""Returns True for npm range-style versions like 4.5.x"""
55+
"""Return True for npm range-style versions like 4.5.x."""
5256
return bool(re.search(r"\.x(\.|$)|(^|\.)x\.", version, flags=re.IGNORECASE))
5357

5458

5559
def _resolve_npm_version(version: str) -> str:
56-
"""
57-
Resolves an abstract version specifier to a concrete npm version string.
60+
"""Resolve an abstract version specifier to a concrete npm version string.
5861
5962
- 'latest' → fetches the current latest tag from npm
6063
- '4.5.x' → fetches all published versions and returns the highest 4.5.x match
@@ -76,9 +79,7 @@ def _resolve_npm_version(version: str) -> str:
7679

7780

7881
def _resolve_wildcard_npm_version(version: str) -> str:
79-
"""
80-
Given a wildcard range like '4.5.x', fetches all published versions
81-
from npm and returns the highest semver match.
82+
"""Fetch the highest published npm version matching a wildcard range like '4.5.x'.
8283
8384
Raises requests.RequestException if no matching version is found.
8485
"""

jupyter_builder/debug_log_file_mixin.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Debug log file context manager mixin for Jupyter applications."""
2+
13
# Copyright (c) Jupyter Development Team.
24
# Distributed under the terms of the Modified BSD License.
35

@@ -8,19 +10,24 @@
810
import tempfile
911
import traceback
1012
import warnings
13+
from collections.abc import Iterator
14+
from pathlib import Path
1115

1216
from traitlets import Unicode
1317
from traitlets.config import Configurable
1418

1519

1620
class DebugLogFileMixin(Configurable):
21+
"""Mixin that adds a debug log file context manager to Jupyter applications."""
22+
1723
debug_log_path = Unicode("", config=True, help="Path to use for the debug log file")
1824

1925
@contextlib.contextmanager
20-
def debug_logging(self):
26+
def debug_logging(self) -> Iterator[None]:
27+
"""Context manager that routes all log output to a debug file."""
2128
log_path = self.debug_log_path
22-
if os.path.isdir(log_path):
23-
log_path = os.path.join(log_path, "jupyterlab-debug.log")
29+
if Path(log_path).is_dir():
30+
log_path = str(Path(log_path) / "jupyterlab-debug.log")
2431
if not log_path:
2532
handle, log_path = tempfile.mkstemp(prefix="jupyterlab-debug-", suffix=".log")
2633
os.close(handle)
@@ -48,7 +55,8 @@ def debug_logging(self):
4855
self.log.debug(line)
4956
if isinstance(ex, SystemExit):
5057
warnings.warn(
51-
f"An error occurred. See the log file for details: {log_path}", stacklevel=1
58+
f"An error occurred. See the log file for details: {log_path}",
59+
stacklevel=1,
5260
)
5361
raise
5462
warnings.warn("An error occurred.", stacklevel=1)
@@ -60,5 +68,5 @@ def debug_logging(self):
6068
_debug_handler.flush()
6169
_debug_handler.close()
6270
with contextlib.suppress(FileNotFoundError):
63-
os.remove(log_path)
71+
Path(log_path).unlink()
6472
log.removeHandler(_debug_handler)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1+
"""Extension command entry points for JupyterLab builder."""
2+
13
# Copyright (c) Jupyter Development Team.
24
# Distributed under the terms of the Modified BSD License.

0 commit comments

Comments
 (0)