version: 1
rules:
- id: no-tabs
pattern: '(?m)^\t+'
message: "PEP 8: spaces, not tabs, for indentation"
severity: warning
tests:
- name: tab indentation is flagged
text: "\tindented"
violations: 1
- name: clean lines pass
text: "indented"
violations: 0
- id: no-semicolons
pattern: '(?m)^\s*[^#\s].*;\s*$'
message: "PEP 8: avoid semicolons to separate statements"
severity: warning
- id: no-wildcard-imports
pattern: '(?m)^\s*from\s+[\w.]+\s+import\s+\*'
message: "wildcard imports make names ambiguous"
severity: warning
- id: bare-except
pattern: '(?m)^\s*except\s*:'
message: "bare except catches everything — name the exception"
severity: warning
- id: except-pass
pattern: '\bexcept\b[^:]*:\s*pass\b'
message: "swallowed exception — at least log it"
severity: warning
tests:
- name: except-pass is flagged
text: "try:\n x\nexcept ValueError:\n pass\n"
violations: 1
- name: logged exception passes
text: "try:\n x\nexcept ValueError as e:\n log(e)\n"
violations: 0
- id: open-without-with
pattern: '=\s*open\('
message: "open() without a with block leaks the handle"
severity: warning
tests:
- name: bare open() is flagged
text: "f = open(path)\n"
violations: 1
- name: with-open passes
text: "with open(path) as f:\n pass\n"
violations: 0
- id: type-ignore
pattern: '#\s*type:\s*ignore'
message: "type: ignore — leave a comment saying why"
severity: info
- id: compare-to-none
pattern: '==\s*None\b'
message: "use 'is None', not '== None'"
severity: warning
- id: assignment-in-condition
pattern: '(?m)^\s*(?:if|elif|while)\s+[^#=]*[^=!<>]=[^=]'
message: "single '=' assigns — did you mean '=='?"
severity: error
- id: mutable-default
pattern: '(?m)^\s*def\s+(?<function>\w+)\s*\([^)]*=[ \t]*(?<default>\[\]|\{\})(?<rest>[^\n]*)'
severity: error
callback: |
return function(c)
if c.captures.rest:find("declint:allow") then
return nil
end
return { message = "default " .. c.captures["default"] .. " in '" ..
c.captures["function"] .. "' is shared across calls" }
end
tests:
- name: shared default is flagged
text: "def f(a = []):"
violations: 1
messages: ["default [] in 'f' is shared across calls"]
- name: allow marker vetoes
text: "def f(a = []): # declint:allow"
violations: 0
scopes:
- id: function
start: '^(?:async\s+)?def\s'
end: '^\S'
rules:
- id: print-in-function
pattern: '\bprint\('
message: "leftover print() — remove or use logging"
severity: warning
tests:
- name: print inside a function is flagged
text: "def f():\n print(1)\n"
violations: 1
- name: top-level print is allowed
text: "print(1)\n"
violations: 0
- id: missing-docstring
parser: |
return function(text, offset)
if text:find('"""') then
return nil
end
local first = text:match('^[^\n]*')
return { { start = offset, finish = offset + #first } }
end
message: "no docstring — say what this function does"
severity: info
tests:
- name: undocumented function is suggested
text: "def f():\n pass\n"
violations: 1
- name: documented function passes
text: "def f():\n \"\"\"Does a thing.\"\"\"\n"
violations: 0
- id: class
start: '^class\s'
end: '^\S'
rules:
- id: missing-class-docstring
parser: |
return function(text, offset)
if text:find('"""') then
return nil
end
local first = text:match('^[^\n]*')
return { { start = offset, finish = offset + #first } }
end
message: "no docstring — say what this class does"
severity: info