Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔧 MAINTAIN: Add mypy type-checking #64

Merged
merged 13 commits into from
Dec 14, 2020
5 changes: 5 additions & 0 deletions .mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[mypy]
warn_unused_ignores = True
warn_redundant_casts = True
no_implicit_optional = True
strict_equality = True
12 changes: 9 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,30 @@ exclude: >
repos:

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
rev: v3.3.0
hooks:
- id: check-json
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace

- repo: https://github.com/mgedmin/check-manifest
rev: "0.42"
rev: "0.44"
hooks:
- id: check-manifest

- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.3
rev: 3.8.4
hooks:
- id: flake8

- repo: https://github.com/psf/black
rev: 20.8b1
hooks:
- id: black

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.790
hooks:
- id: mypy
additional_dependencies: [attrs]
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ exclude .flake8
exclude .circleci
exclude .circleci/config.yml
exclude codecov.yml
exclude .mypy.ini

include LICENSE
include LICENSE.markdown-it
Expand Down
2 changes: 1 addition & 1 deletion markdown_it/common/entities.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""HTML5 entities map: { name -> characters }."""
import html
import html.entities


class _Entities:
Expand Down
4 changes: 2 additions & 2 deletions markdown_it/common/normalize_url.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import html
import re
from typing import Callable
from typing import Callable, Optional
from urllib.parse import urlparse, urlunparse, quote, unquote # noqa: F401

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


def validateLink(url: str, validator: Callable = None):
def validateLink(url: str, validator: Optional[Callable] = None):
"""Validate URL link is allowed in output.

This validator can prohibit more than really needed to prevent XSS.
Expand Down
9 changes: 5 additions & 4 deletions markdown_it/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def assign(obj):
# })
# })

return obj
# return obj


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


def unescapeMd(string: str):
if "\\" in string:
return string
return string.replace(UNESCAPE_MD_RE, "$1")
raise NotImplementedError
# if "\\" in string:
# return string
# return string.replace(UNESCAPE_MD_RE, "$1")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I simply commented this function out, because str.replace() takes strings as input, not re.Patterns, so this is probably broken.



def unescapeAll(string: str):
Expand Down
15 changes: 8 additions & 7 deletions markdown_it/extensions/anchors/index.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import re
from typing import Callable, List, Optional
from typing import Callable, List, Optional, Set

from markdown_it import MarkdownIt
from markdown_it.rules_core import StateCore
Expand Down Expand Up @@ -65,19 +65,20 @@ def _make_anchors_func(
permalinkBefore: bool,
permalinkSpace: bool,
):
slugs = set()
slugs: Set[str] = set()

def _anchor_func(state: StateCore):
for (idx, token) in enumerate(state.tokens):
token: Token
if token.type != "heading_open":
continue
level = int(token.tag[1])
if level not in selected_levels:
continue
inline_token = state.tokens[idx + 1]
assert inline_token.children is not None
title = "".join(
child.content
for child in state.tokens[idx + 1].children
for child in inline_token.children
if child.type in ["text", "code_inline"]
)
slug = unique_slug(slug_func(title), slugs)
Expand All @@ -95,17 +96,17 @@ def _anchor_func(state: StateCore):
Token("link_close", "a", -1),
]
if permalinkBefore:
state.tokens[idx + 1].children = (
inline_token.children = (
link_tokens
+ (
[Token("text", "", 0, content=" ")]
if permalinkSpace
else []
)
+ state.tokens[idx + 1].children
+ inline_token.children
)
else:
state.tokens[idx + 1].children.extend(
inline_token.children.extend(
([Token("text", "", 0, content=" ")] if permalinkSpace else [])
+ link_tokens
)
Expand Down
5 changes: 3 additions & 2 deletions markdown_it/extensions/container/index.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Process block-level custom containers."""
from math import floor
from typing import Callable
from typing import Callable, Optional

from markdown_it import MarkdownIt
from markdown_it.common.utils import charCodeAt
Expand All @@ -11,7 +11,7 @@ def container_plugin(
md: MarkdownIt,
name: str,
marker: str = ":",
validate: Callable[[str, str], bool] = None,
validate: Optional[Callable[[str, str], bool]] = None,
render=None,
):
"""Plugin ported from
Expand Down Expand Up @@ -80,6 +80,7 @@ def container_func(state: StateBlock, startLine: int, endLine: int, silent: bool

markup = state.src[start:pos]
params = state.src[pos:maximum]
assert validate is not None
if not validate(params, markup):
return False

Expand Down
2 changes: 1 addition & 1 deletion markdown_it/extensions/deflist/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def deflist_plugin(md: MarkdownIt):
~ Definition 2b

"""
isSpace = md.utils.isSpace
isSpace = md.utils.isSpace # type: ignore

def skipMarker(state: StateBlock, line: int):
"""Search `[:~][\n ]`, returns next pos after marker on success or -1 on fail."""
Expand Down
8 changes: 5 additions & 3 deletions markdown_it/extensions/footnote/index.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Process footnotes
#

from typing import List, Optional

from markdown_it import MarkdownIt
from markdown_it.token import Token
from markdown_it.rules_inline import StateInline
Expand Down Expand Up @@ -174,7 +176,7 @@ def footnote_inline(state: StateInline, silent: bool):
refs = state.env.setdefault("footnotes", {}).setdefault("list", {})
footnoteId = len(refs)

tokens = []
tokens: List[Token] = []
state.md.inline.parse(
state.src[labelStart:labelEnd], state.md, state.env, tokens
)
Expand Down Expand Up @@ -260,7 +262,7 @@ def footnote_tail(state: StateBlock, *args, **kwargs):
if "footnotes" not in state.env:
return

current = []
current: List[Token] = []
tok_filter = []
for tok in state.tokens:

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

state.tokens.extend(tokens)
if state.tokens[len(state.tokens) - 1].type == "paragraph_close":
lastParagraph = state.tokens.pop()
lastParagraph: Optional[Token] = state.tokens.pop()
else:
lastParagraph = None

Expand Down
1 change: 1 addition & 0 deletions markdown_it/extensions/tasklists/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def is_todo_item(tokens, index):
)

def todoify(token: Token, token_constructor):
assert token.children is not None
token.children.insert(0, make_checkbox(token, token_constructor))
token.children[1].content = token.children[1].content[3:]
token.content = token.content[3:]
Expand Down
28 changes: 14 additions & 14 deletions markdown_it/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from contextlib import contextmanager
from typing import Any, Callable, Dict, List, Optional, Union
from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, Union

from . import helpers, presets # noqa F401
from .common import utils # noqa F401
Expand Down Expand Up @@ -28,7 +28,7 @@

class MarkdownIt:
def __init__(
self, config: Union[str, AttrDict] = "commonmark", renderer_cls=RendererHTML
self, config: Union[str, Mapping] = "commonmark", renderer_cls=RendererHTML
):
"""Main parser class

Expand All @@ -43,7 +43,7 @@ def __init__(

self.utils = utils
self.helpers = helpers
self.options = {}
self.options: Dict[str, Any] = {}
self.configure(config)

self.linkify = linkify_it.LinkifyIt() if linkify_it else None
Expand All @@ -69,7 +69,7 @@ def set(self, options):
"""
self.options = options

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

if "options" in presets:
self.set(presets.options)
if "options" in config:
self.set(config.options)

if "components" in presets:
for name, component in presets.components.items():
if "components" in config:
for name, component in config.components.items():
rules = component.get("rules", None)
if rules:
self[name].ruler.enableOnly(rules)
Expand Down Expand Up @@ -122,7 +122,7 @@ def get_active_rules(self) -> Dict[str, List[str]]:
return rules

def enable(
self, names: Union[str, List[str]], ignoreInvalid: bool = False
self, names: Union[str, Iterable[str]], ignoreInvalid: bool = False
) -> "MarkdownIt":
"""Enable list or rules. (chainable)

Expand Down Expand Up @@ -155,7 +155,7 @@ def enable(
return self

def disable(
self, names: Union[str, List[str]], ignoreInvalid: bool = False
self, names: Union[str, Iterable[str]], ignoreInvalid: bool = False
) -> "MarkdownIt":
"""The same as [[MarkdownIt.enable]], but turn specified rules off. (chainable)

Expand Down Expand Up @@ -193,7 +193,7 @@ def add_render_rule(self, name: str, function: Callable, fmt="html"):
Only applied when ``renderer.__output__ == fmt``
"""
if self.renderer.__output__ == fmt:
self.renderer.rules[name] = function.__get__(self.renderer)
self.renderer.rules[name] = function.__get__(self.renderer) # type: ignore

def use(self, plugin: Callable, *params, **options) -> "MarkdownIt":
"""Load specified plugin with given params into current parser instance. (chainable)
Expand Down Expand Up @@ -225,7 +225,7 @@ def parse(self, src: str, env: Optional[AttrDict] = None) -> List[Token]:
and then pass updated object to renderer.
"""
env = AttrDict() if env is None else env
if not isinstance(env, AttrDict):
if not isinstance(env, AttrDict): # type: ignore
raise TypeError(f"Input data should be an AttrDict, not {type(env)}")
if not isinstance(src, str):
raise TypeError(f"Input data should be a string, not {type(src)}")
Expand Down Expand Up @@ -259,7 +259,7 @@ def parseInline(self, src: str, env: Optional[AttrDict] = None) -> List[Token]:
tokens in `children` property. Also updates `env` object.
"""
env = AttrDict() if env is None else env
if not isinstance(env, AttrDict):
if not isinstance(env, AttrDict): # type: ignore
raise TypeError(f"Input data should be an AttrDict, not {type(env)}")
if not isinstance(src, str):
raise TypeError(f"Input data should be a string, not {type(src)}")
Expand Down
11 changes: 9 additions & 2 deletions markdown_it/parser_block.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Block-level tokenizer."""
import logging
from typing import List
from typing import List, Optional

from .ruler import Ruler
from .token import Token
Expand Down Expand Up @@ -92,7 +92,14 @@ def tokenize(
line += 1
state.line = line

def parse(self, src: str, md, env, outTokens: List[Token], ords: List[int] = None):
def parse(
self,
src: str,
md,
env,
outTokens: List[Token],
ords: Optional[List[int]] = None,
):
"""Process input string and push block tokens into `outTokens`."""
if not src:
return
Expand Down
4 changes: 3 additions & 1 deletion markdown_it/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def render(self, tokens: List[Token], options, env) -> str:
for i, token in enumerate(tokens):

if token.type == "inline":
assert token.children is not None
result += self.renderInline(token.children, options, env)
elif token.type in self.rules:
result += self.rules[token.type](tokens, i, options, env)
Expand Down Expand Up @@ -124,7 +125,7 @@ def renderToken(
result += self.renderAttrs(token)

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

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

return result
Expand Down
Loading