Skip to content

Commit b16ee04

Browse files
authored
🔧 MAINTAIN: Add mypy type-checking (#64)
1 parent 3a5bdcc commit b16ee04

28 files changed

+142
-92
lines changed

.mypy.ini

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[mypy]
2+
warn_unused_ignores = True
3+
warn_redundant_casts = True
4+
no_implicit_optional = True
5+
strict_equality = True

.pre-commit-config.yaml

+9-3
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,30 @@ exclude: >
1515
repos:
1616

1717
- repo: https://github.com/pre-commit/pre-commit-hooks
18-
rev: v3.2.0
18+
rev: v3.3.0
1919
hooks:
2020
- id: check-json
2121
- id: check-yaml
2222
- id: end-of-file-fixer
2323
- id: trailing-whitespace
2424

2525
- repo: https://github.com/mgedmin/check-manifest
26-
rev: "0.42"
26+
rev: "0.44"
2727
hooks:
2828
- id: check-manifest
2929

3030
- repo: https://gitlab.com/pycqa/flake8
31-
rev: 3.8.3
31+
rev: 3.8.4
3232
hooks:
3333
- id: flake8
3434

3535
- repo: https://github.com/psf/black
3636
rev: 20.8b1
3737
hooks:
3838
- id: black
39+
40+
- repo: https://github.com/pre-commit/mirrors-mypy
41+
rev: v0.790
42+
hooks:
43+
- id: mypy
44+
additional_dependencies: [attrs]

MANIFEST.in

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ exclude .flake8
1313
exclude .circleci
1414
exclude .circleci/config.yml
1515
exclude codecov.yml
16+
exclude .mypy.ini
1617

1718
include LICENSE
1819
include LICENSE.markdown-it

markdown_it/common/entities.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""HTML5 entities map: { name -> characters }."""
2-
import html
2+
import html.entities
33

44

55
class _Entities:

markdown_it/common/normalize_url.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import html
22
import re
3-
from typing import Callable
3+
from typing import Callable, Optional
44
from urllib.parse import urlparse, urlunparse, quote, unquote # noqa: F401
55

66
from .utils import ESCAPABLE
@@ -166,7 +166,7 @@ def normalizeLinkText(link):
166166
GOOD_DATA_RE = re.compile(r"^data:image\/(gif|png|jpeg|webp);")
167167

168168

169-
def validateLink(url: str, validator: Callable = None):
169+
def validateLink(url: str, validator: Optional[Callable] = None):
170170
"""Validate URL link is allowed in output.
171171
172172
This validator can prohibit more than really needed to prevent XSS.

markdown_it/common/utils.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def assign(obj):
5353
# })
5454
# })
5555

56-
return obj
56+
# return obj
5757

5858

5959
def arrayReplaceAt(src: list, pos: int, newElements: list):
@@ -139,9 +139,10 @@ def replaceEntityPattern(match, name):
139139

140140

141141
def unescapeMd(string: str):
142-
if "\\" in string:
143-
return string
144-
return string.replace(UNESCAPE_MD_RE, "$1")
142+
raise NotImplementedError
143+
# if "\\" in string:
144+
# return string
145+
# return string.replace(UNESCAPE_MD_RE, "$1")
145146

146147

147148
def unescapeAll(string: str):

markdown_it/extensions/anchors/index.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import re
2-
from typing import Callable, List, Optional
2+
from typing import Callable, List, Optional, Set
33

44
from markdown_it import MarkdownIt
55
from markdown_it.rules_core import StateCore
@@ -65,19 +65,20 @@ def _make_anchors_func(
6565
permalinkBefore: bool,
6666
permalinkSpace: bool,
6767
):
68-
slugs = set()
68+
slugs: Set[str] = set()
6969

7070
def _anchor_func(state: StateCore):
7171
for (idx, token) in enumerate(state.tokens):
72-
token: Token
7372
if token.type != "heading_open":
7473
continue
7574
level = int(token.tag[1])
7675
if level not in selected_levels:
7776
continue
77+
inline_token = state.tokens[idx + 1]
78+
assert inline_token.children is not None
7879
title = "".join(
7980
child.content
80-
for child in state.tokens[idx + 1].children
81+
for child in inline_token.children
8182
if child.type in ["text", "code_inline"]
8283
)
8384
slug = unique_slug(slug_func(title), slugs)
@@ -95,17 +96,17 @@ def _anchor_func(state: StateCore):
9596
Token("link_close", "a", -1),
9697
]
9798
if permalinkBefore:
98-
state.tokens[idx + 1].children = (
99+
inline_token.children = (
99100
link_tokens
100101
+ (
101102
[Token("text", "", 0, content=" ")]
102103
if permalinkSpace
103104
else []
104105
)
105-
+ state.tokens[idx + 1].children
106+
+ inline_token.children
106107
)
107108
else:
108-
state.tokens[idx + 1].children.extend(
109+
inline_token.children.extend(
109110
([Token("text", "", 0, content=" ")] if permalinkSpace else [])
110111
+ link_tokens
111112
)

markdown_it/extensions/container/index.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Process block-level custom containers."""
22
from math import floor
3-
from typing import Callable
3+
from typing import Callable, Optional
44

55
from markdown_it import MarkdownIt
66
from markdown_it.common.utils import charCodeAt
@@ -11,7 +11,7 @@ def container_plugin(
1111
md: MarkdownIt,
1212
name: str,
1313
marker: str = ":",
14-
validate: Callable[[str, str], bool] = None,
14+
validate: Optional[Callable[[str, str], bool]] = None,
1515
render=None,
1616
):
1717
"""Plugin ported from
@@ -80,6 +80,7 @@ def container_func(state: StateBlock, startLine: int, endLine: int, silent: bool
8080

8181
markup = state.src[start:pos]
8282
params = state.src[pos:maximum]
83+
assert validate is not None
8384
if not validate(params, markup):
8485
return False
8586

markdown_it/extensions/deflist/index.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def deflist_plugin(md: MarkdownIt):
2222
~ Definition 2b
2323
2424
"""
25-
isSpace = md.utils.isSpace
25+
isSpace = md.utils.isSpace # type: ignore
2626

2727
def skipMarker(state: StateBlock, line: int):
2828
"""Search `[:~][\n ]`, returns next pos after marker on success or -1 on fail."""

markdown_it/extensions/footnote/index.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Process footnotes
22
#
33

4+
from typing import List, Optional
5+
46
from markdown_it import MarkdownIt
57
from markdown_it.token import Token
68
from markdown_it.rules_inline import StateInline
@@ -174,7 +176,7 @@ def footnote_inline(state: StateInline, silent: bool):
174176
refs = state.env.setdefault("footnotes", {}).setdefault("list", {})
175177
footnoteId = len(refs)
176178

177-
tokens = []
179+
tokens: List[Token] = []
178180
state.md.inline.parse(
179181
state.src[labelStart:labelEnd], state.md, state.env, tokens
180182
)
@@ -260,7 +262,7 @@ def footnote_tail(state: StateBlock, *args, **kwargs):
260262
if "footnotes" not in state.env:
261263
return
262264

263-
current = []
265+
current: List[Token] = []
264266
tok_filter = []
265267
for tok in state.tokens:
266268

@@ -320,7 +322,7 @@ def footnote_tail(state: StateBlock, *args, **kwargs):
320322

321323
state.tokens.extend(tokens)
322324
if state.tokens[len(state.tokens) - 1].type == "paragraph_close":
323-
lastParagraph = state.tokens.pop()
325+
lastParagraph: Optional[Token] = state.tokens.pop()
324326
else:
325327
lastParagraph = None
326328

markdown_it/extensions/tasklists/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ def is_todo_item(tokens, index):
8989
)
9090

9191
def todoify(token: Token, token_constructor):
92+
assert token.children is not None
9293
token.children.insert(0, make_checkbox(token, token_constructor))
9394
token.children[1].content = token.children[1].content[3:]
9495
token.content = token.content[3:]

markdown_it/main.py

+14-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from contextlib import contextmanager
2-
from typing import Any, Callable, Dict, List, Optional, Union
2+
from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, Union
33

44
from . import helpers, presets # noqa F401
55
from .common import utils # noqa F401
@@ -28,7 +28,7 @@
2828

2929
class MarkdownIt:
3030
def __init__(
31-
self, config: Union[str, AttrDict] = "commonmark", renderer_cls=RendererHTML
31+
self, config: Union[str, Mapping] = "commonmark", renderer_cls=RendererHTML
3232
):
3333
"""Main parser class
3434
@@ -43,7 +43,7 @@ def __init__(
4343

4444
self.utils = utils
4545
self.helpers = helpers
46-
self.options = {}
46+
self.options: Dict[str, Any] = {}
4747
self.configure(config)
4848

4949
self.linkify = linkify_it.LinkifyIt() if linkify_it else None
@@ -69,7 +69,7 @@ def set(self, options):
6969
"""
7070
self.options = options
7171

72-
def configure(self, presets: Union[str, AttrDict]):
72+
def configure(self, presets: Union[str, Mapping]):
7373
"""Batch load of all options and component settings.
7474
This is an internal method, and you probably will not need it.
7575
But if you will - see available presets and data structure
@@ -87,13 +87,13 @@ def configure(self, presets: Union[str, AttrDict]):
8787
)
8888
if not presets:
8989
raise ValueError("Wrong `markdown-it` preset, can't be empty")
90-
presets = AttrDict(presets)
90+
config = AttrDict(presets)
9191

92-
if "options" in presets:
93-
self.set(presets.options)
92+
if "options" in config:
93+
self.set(config.options)
9494

95-
if "components" in presets:
96-
for name, component in presets.components.items():
95+
if "components" in config:
96+
for name, component in config.components.items():
9797
rules = component.get("rules", None)
9898
if rules:
9999
self[name].ruler.enableOnly(rules)
@@ -122,7 +122,7 @@ def get_active_rules(self) -> Dict[str, List[str]]:
122122
return rules
123123

124124
def enable(
125-
self, names: Union[str, List[str]], ignoreInvalid: bool = False
125+
self, names: Union[str, Iterable[str]], ignoreInvalid: bool = False
126126
) -> "MarkdownIt":
127127
"""Enable list or rules. (chainable)
128128
@@ -155,7 +155,7 @@ def enable(
155155
return self
156156

157157
def disable(
158-
self, names: Union[str, List[str]], ignoreInvalid: bool = False
158+
self, names: Union[str, Iterable[str]], ignoreInvalid: bool = False
159159
) -> "MarkdownIt":
160160
"""The same as [[MarkdownIt.enable]], but turn specified rules off. (chainable)
161161
@@ -193,7 +193,7 @@ def add_render_rule(self, name: str, function: Callable, fmt="html"):
193193
Only applied when ``renderer.__output__ == fmt``
194194
"""
195195
if self.renderer.__output__ == fmt:
196-
self.renderer.rules[name] = function.__get__(self.renderer)
196+
self.renderer.rules[name] = function.__get__(self.renderer) # type: ignore
197197

198198
def use(self, plugin: Callable, *params, **options) -> "MarkdownIt":
199199
"""Load specified plugin with given params into current parser instance. (chainable)
@@ -225,7 +225,7 @@ def parse(self, src: str, env: Optional[AttrDict] = None) -> List[Token]:
225225
and then pass updated object to renderer.
226226
"""
227227
env = AttrDict() if env is None else env
228-
if not isinstance(env, AttrDict):
228+
if not isinstance(env, AttrDict): # type: ignore
229229
raise TypeError(f"Input data should be an AttrDict, not {type(env)}")
230230
if not isinstance(src, str):
231231
raise TypeError(f"Input data should be a string, not {type(src)}")
@@ -259,7 +259,7 @@ def parseInline(self, src: str, env: Optional[AttrDict] = None) -> List[Token]:
259259
tokens in `children` property. Also updates `env` object.
260260
"""
261261
env = AttrDict() if env is None else env
262-
if not isinstance(env, AttrDict):
262+
if not isinstance(env, AttrDict): # type: ignore
263263
raise TypeError(f"Input data should be an AttrDict, not {type(env)}")
264264
if not isinstance(src, str):
265265
raise TypeError(f"Input data should be a string, not {type(src)}")

markdown_it/parser_block.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Block-level tokenizer."""
22
import logging
3-
from typing import List
3+
from typing import List, Optional
44

55
from .ruler import Ruler
66
from .token import Token
@@ -92,7 +92,14 @@ def tokenize(
9292
line += 1
9393
state.line = line
9494

95-
def parse(self, src: str, md, env, outTokens: List[Token], ords: List[int] = None):
95+
def parse(
96+
self,
97+
src: str,
98+
md,
99+
env,
100+
outTokens: List[Token],
101+
ords: Optional[List[int]] = None,
102+
):
96103
"""Process input string and push block tokens into `outTokens`."""
97104
if not src:
98105
return

markdown_it/renderer.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ def render(self, tokens: List[Token], options, env) -> str:
6464
for i, token in enumerate(tokens):
6565

6666
if token.type == "inline":
67+
assert token.children is not None
6768
result += self.renderInline(token.children, options, env)
6869
elif token.type in self.rules:
6970
result += self.rules[token.type](tokens, i, options, env)
@@ -124,7 +125,7 @@ def renderToken(
124125
result += self.renderAttrs(token)
125126

126127
# Add a slash for self-closing tags, e.g. `<img src="foo" /`
127-
if token.nesting == 0 and options.xhtmlOut:
128+
if token.nesting == 0 and options["xhtmlOut"]:
128129
result += " /"
129130

130131
# Check if we need to add a newline after this tag
@@ -184,6 +185,7 @@ def renderInlineAsText(self, tokens: List[Token], options, env) -> str:
184185
if token.type == "text":
185186
result += token.content
186187
elif token.type == "image":
188+
assert token.children is not None
187189
result += self.renderInlineAsText(token.children, options, env)
188190

189191
return result

0 commit comments

Comments
 (0)