Files
avrdude/tools/linter.py
2025-09-27 23:01:38 +02:00

140 lines
5.4 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# avrdude - A Downloader/Uploader for AVR device programmers
# Copyright (C) 2025 MX682X
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# this python skript provides a basic linting of the recently
# modified files (1 week). The rules are adjusted for avrdude specifically.
# The results are ot absolute, it just shows where to check things
import os
import pathlib
import time
# List of words that don't fllow normal writing conventions
c_keywords = ("if", "else", "do", "while", "for", "void", "unsigned", "char", "int", "return", "switch", "case", "default", )
other_keywords = ("debugWIRE", "debugWire", "UPDI", "MPLAB", "AVR", "PICkit")
chip_keywords = ("xMega", "CS", "RC", "HV")
def get_file_list(path):
return [f.path for f in os.scandir(path)
if f.name.endswith((".c", ".h")) # limit c and h files, 1 week old modification
if os.path.getmtime(f.path) > (time.time() - 7*24*3600)]
def check_slc(line, linenum, pos):
if line[0] != " " and line[0] != "/":
print(f"{linenum}:{pos+1} - missing space after '//'")
found_printable = False
for i in range(len(line)):
if line[i:i+2] == "//" and i > 2:
check_slc(line[i+2:], linenum, i + pos)
elif line[i] == " ":
continue
elif not found_printable:
if line[i].isprintable():
found_printable = True
if line[i].islower():
word = line[i:-1].split(" ", 1)[0]
if not (word in c_keywords or word in other_keywords):
if not any (x in word for x in ("->", ".", "_")):
print(f"{linenum+1}:{i+pos+1} - lower case '{word}' at beginning of '//'")
else:
continue
def check_console_output(line, linenumber):
try:
type, string = line.split("(")
word = string.split('"')[1].split(" ")[0]
if word[0].isupper(): # First letter is capital
if not word.isupper(): # Whole word capital is allowed, e.g. NOT
if not (word in other_keywords or word in chip_keywords): # reduce false positives
print(f"{linenumber+1} - {type} messages should start lower case: '{word}'")
except:
pass
def check_lines(lines):
mlc = 0 # multi-line comment
string = 0
for l, line in enumerate(lines):
for i in range(len(line)):
if mlc:
if line [i:i+2] == "*/": # ignore everything in multi-line comment
mlc = 0
continue
elif string: # ignore C strings
if line[i] == '"' and line[i-1] != "\\":
string = False
elif line [i] == "#":
break # skip preprocessor line
elif line [i:i+2] == "/*":
mlc = 1
elif line[i:i+2] == "//":
check_slc(line[i+2:], l, 2)
break # skip the rest of the single line comment
elif line[i] == '"':
string = True
elif line[i:i+4] == "if (":
print(f"{l+1}:{i+3} - found if space")
elif line[i:i+7] == "while (":
print(f"{l+1}:{i+6} - found while space")
elif line[i:i+5] == "for (":
print(f"{l+1}:{i+4} - found for space at")
elif line[i:i+4] == "char":
if line[i+4] == "*":
print(f"{l+1}:{i+5} - missing space between char and * ")
elif line[i+4:i+6] == " *":
if line[i+6] == " ":
if line[i+7:i+12] != "const":
print(f"{l+1}:{i+7} - space found between * and function/variable name")
elif line[i:i+3] == "int":
if line[i+3] == "*":
print(f"{l+1}:{i+4} - missing space between int and * ")
elif line[i+3:i+5] == " *":
if line[i+5] == " ":
if line[i+6:i+11] != "const":
print(f"{l+1}:{i+6} - space found between * and function/variable name")
elif line[i:i+4] == "msg_": # wildcarding pmsg/imsg, might pick up others though, careful there
check_console_output(line[i+4:], l)
if len(line) > 1 and line[-2:] == " \n":
print(f"{l+1} - Trailing whitespace found")
def main():
working_dir = pathlib.Path(__file__).parent.parent.resolve()
working_dir = os.path.join(working_dir, "src")
print("working dir:", working_dir)
files = get_file_list(working_dir)
for file in files:
print(file)
with open(file, "r") as f:
lines = f.readlines()
check_lines(lines)
main()