Skip to content

Commit

Permalink
lint: Add linter rules for markdown.
Browse files Browse the repository at this point in the history
  • Loading branch information
adambirds committed Apr 30, 2021
1 parent 9b08351 commit 349d9e6
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,5 @@ dmypy.json

# Exclusions

!src/xkcd_pass/lib/
!src/xkcd_pass/lib/
!tools/lib
Empty file added tools/__init__.py
Empty file.
Empty file added tools/lib/__init__.py
Empty file.
53 changes: 53 additions & 0 deletions tools/lib/custom_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from typing import List

from zulint.custom_rules import Rule, RuleList

trailing_whitespace_rule: "Rule" = {
"pattern": r"\s+$",
"strip": "\n",
"description": "Fix trailing whitespace",
}
whitespace_rules: List["Rule"] = [
# This linter should be first since bash_rules depends on it.
trailing_whitespace_rule,
]

markdown_whitespace_rules: List["Rule"] = [
*(rule for rule in whitespace_rules if rule["pattern"] != r"\s+$"),
# Two spaces trailing a line with other content is okay--it's a Markdown line break.
# This rule finds one space trailing a non-space, three or more trailing spaces, and
# spaces on an empty line.
{
"pattern": r"((?<!\s)\s$)|(\s\s\s+$)|(^\s+$)",
"strip": "\n",
"description": "Fix trailing whitespace",
},
{
"pattern": "^#+[A-Za-z0-9]",
"strip": "\n",
"description": "Missing space after # in heading",
"good_lines": ["### some heading", "# another heading"],
"bad_lines": ["###some heading", "#another heading"],
},
]

markdown_rules = RuleList(
langs=["md"],
rules=[
*markdown_whitespace_rules,
{
"pattern": r"\[(?P<url>[^\]]+)\]\((?P=url)\)",
"description": "Linkified Markdown URLs should use cleaner <http://example.com> syntax.",
},
{
"pattern": r"\][(][^#h]",
"include_only": {"README.md"},
"description": "Use absolute links from docs served by GitHub",
},
],
max_length=120,
)

non_py_rules = [
markdown_rules,
]
110 changes: 110 additions & 0 deletions tools/lib/gitlint-rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from typing import List

from gitlint.git import GitCommit
from gitlint.rules import CommitMessageTitle, LineRule, RuleViolation

# Word list from https://github.com/m1foley/fit-commit
# Copyright (c) 2015 Mike Foley
# License: MIT
# Ref: fit_commit/validators/tense.rb
TENSE_DATA = [
(["adds", "adding", "added"], "add"),
(["allows", "allowing", "allowed"], "allow"),
(["amends", "amending", "amended"], "amend"),
(["bumps", "bumping", "bumped"], "bump"),
(["calculates", "calculating", "calculated"], "calculate"),
(["changes", "changing", "changed"], "change"),
(["cleans", "cleaning", "cleaned"], "clean"),
(["commits", "committing", "committed"], "commit"),
(["corrects", "correcting", "corrected"], "correct"),
(["creates", "creating", "created"], "create"),
(["darkens", "darkening", "darkened"], "darken"),
(["disables", "disabling", "disabled"], "disable"),
(["displays", "displaying", "displayed"], "display"),
(["documents", "documenting", "documented"], "document"),
(["drys", "drying", "dryed"], "dry"),
(["ends", "ending", "ended"], "end"),
(["enforces", "enforcing", "enforced"], "enforce"),
(["enqueues", "enqueuing", "enqueued"], "enqueue"),
(["extracts", "extracting", "extracted"], "extract"),
(["finishes", "finishing", "finished"], "finish"),
(["fixes", "fixing", "fixed"], "fix"),
(["formats", "formatting", "formatted"], "format"),
(["guards", "guarding", "guarded"], "guard"),
(["handles", "handling", "handled"], "handle"),
(["hides", "hiding", "hid"], "hide"),
(["increases", "increasing", "increased"], "increase"),
(["ignores", "ignoring", "ignored"], "ignore"),
(["implements", "implementing", "implemented"], "implement"),
(["improves", "improving", "improved"], "improve"),
(["keeps", "keeping", "kept"], "keep"),
(["kills", "killing", "killed"], "kill"),
(["makes", "making", "made"], "make"),
(["merges", "merging", "merged"], "merge"),
(["moves", "moving", "moved"], "move"),
(["permits", "permitting", "permitted"], "permit"),
(["prevents", "preventing", "prevented"], "prevent"),
(["pushes", "pushing", "pushed"], "push"),
(["rebases", "rebasing", "rebased"], "rebase"),
(["refactors", "refactoring", "refactored"], "refactor"),
(["removes", "removing", "removed"], "remove"),
(["renames", "renaming", "renamed"], "rename"),
(["reorders", "reordering", "reordered"], "reorder"),
(["replaces", "replacing", "replaced"], "replace"),
(["requires", "requiring", "required"], "require"),
(["restores", "restoring", "restored"], "restore"),
(["sends", "sending", "sent"], "send"),
(["sets", "setting"], "set"),
(["separates", "separating", "separated"], "separate"),
(["shows", "showing", "showed"], "show"),
(["simplifies", "simplifying", "simplified"], "simplify"),
(["skips", "skipping", "skipped"], "skip"),
(["sorts", "sorting"], "sort"),
(["speeds", "speeding", "sped"], "speed"),
(["starts", "starting", "started"], "start"),
(["supports", "supporting", "supported"], "support"),
(["takes", "taking", "took"], "take"),
(["testing", "tested"], "test"), # "tests" excluded to reduce false negatives
(["truncates", "truncating", "truncated"], "truncate"),
(["updates", "updating", "updated"], "update"),
(["uses", "using", "used"], "use"),
]

TENSE_CORRECTIONS = {word: imperative for words, imperative in TENSE_DATA for word in words}


class ImperativeMood(LineRule):
"""This rule will enforce that the commit message title uses imperative
mood. This is done by checking if the first word is in `WORD_SET`, if so
show the word in the correct mood."""

name = "title-imperative-mood"
id = "Z1"
target = CommitMessageTitle

error_msg = (
"The first word in commit title should be in imperative mood "
'("{word}" -> "{imperative}"): "{title}"'
)

def validate(self, line: str, commit: GitCommit) -> List[RuleViolation]:
violations = []

# Ignore the section tag (ie `<section tag>: <message body>.`)
words = line.split(": ", 1)[-1].split()
first_word = words[0].lower()

if first_word in TENSE_CORRECTIONS:
imperative = TENSE_CORRECTIONS[first_word]
violation = RuleViolation(
self.id,
self.error_msg.format(
word=first_word,
imperative=imperative,
title=commit.message.title,
),
)

violations.append(violation)

return violations
9 changes: 9 additions & 0 deletions tools/lint
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ from __future__ import absolute_import, print_function
import argparse
import re

from lib.custom_check import non_py_rules
from zulint.command import LinterConfig, add_default_linter_arguments
from zulint.custom_rules import RuleList
from zulint.linters import run_pyflakes
Expand Down Expand Up @@ -92,6 +93,14 @@ def run():
failed = trailing_whitespace_rule.check(by_lang, verbose=args.verbose)
return 1 if failed else 0

@linter_config.lint
def custom_nonpy() -> int:
"""Runs custom checks for non-python files (config: tools/lib/custom_check.py)"""
failed = False
for rule in non_py_rules:
failed = failed or rule.check(by_lang, verbose=args.verbose)
return 1 if failed else 0

@linter_config.lint
def pyflakes():
# type: () -> int
Expand Down

0 comments on commit 349d9e6

Please sign in to comment.