mirror of
https://github.com/esphome/esphome.git
synced 2025-06-19 23:05:41 +01:00
add-black (#1593)
* Add black Update pre commit Update pre commit add empty line * Format with black
This commit is contained in:
committed by
GitHub
parent
2b60b0f1fa
commit
69879920eb
.pre-commit-config.yamlsetup.cfg
esphome
__main__.pyconfig.pyconfig_helpers.pyconfig_validation.pyconst.pycore.pycore_config.pycpp_generator.pycpp_helpers.pycpp_types.py
pylintrcpyproject.tomlrequirements_test.txtapi
automation.pycodegen.pycomponents
a4988
ac_dimmer
adalight
adc
ade7953
ads1115
aht10
am2320
animation
apds9960
api
as3935
as3935_i2c
as3935_spi
async_tcp
atc_mithermometer
atm90e32
bang_bang
bh1750
binary
binary_sensor
binary_sensor_map
ble_presence
ble_rssi
ble_scanner
bme280
bme680
bmp085
bmp280
canbus
captive_portal
ccs811
climate
climate_ir
climate_ir_lg
color
coolix
cover
cse7766
ct_clamp
custom
custom_component
cwww
daikin
dallas
debug
deep_sleep
dfplayer
dht
dht12
display
ds1307
duty_cycle
e131
endstop
esp32_ble_beacon
esp32_ble_tracker
esp32_camera
esp32_dac
esp32_hall
esp32_touch
esp8266_pwm
ethernet
exposure_notifications
ezo
fan
fastled_base
fastled_clockless
fastled_spi
font
fujitsu_general
globals
gpio
gps
hbridge
hdc1080
hitachi_ac344
hlw8012
hm3301
hmc5883l
homeassistant
http_request
htu21d
hx711
i2c
ili9341
image
ina219
ina226
ina3221
inkbird_ibsth1_mini
inkplate6
integration
interval
json
lcd_base
lcd_gpio
lcd_pcf8574
ledc
light
logger
max31855
max31856
max31865
max6675
max7219
mcp23008
mcp23016
mcp23017
mcp23s08
mcp23s17
mcp2515
mcp3008
mcp4725
mcp9808
mhz19
mitsubishi
modbus
monochromatic
mpr121
mpu6050
mqtt
mqtt_subscribe
ms5611
my9231
neopixelbus
network
nextion
nfc
ntc
ota
output
packages
partition
pca9685
pcd8544
pcf8574
pid
pmsx003
pn532
pn532_i2c
pn532_spi
power_supply
prometheus
pulse_counter
pulse_width
pzem004t
pzemac
pzemdc
qmc5883l
rc522
rc522_i2c
rc522_spi
rdm6300
remote_base
remote_receiver
remote_transmitter
resistance
restart
rf_bridge
rgb
rgbw
rgbww
rotary_encoder
rtttl
ruuvi_ble
ruuvitag
scd30
script
sds011
senseair
sensor
servo
sgp30
sht3xd
shtcx
shutdown
sim800l
sm16716
sm300d2
sn74hc595
sntp
speed
spi
sps30
ssd1306_base
ssd1306_i2c
ssd1306_spi
ssd1322_base
ssd1322_spi
ssd1325_base
ssd1325_spi
ssd1327_base
ssd1327_i2c
ssd1327_spi
ssd1331_base
ssd1331_spi
ssd1351_base
ssd1351_spi
st7735
st7789v
status
status_led
stepper
sts3x
substitutions
sun
switch
sx1509
tcl112
tcs34725
teleinfo
template
text_sensor
thermostat
time
time_based
tlc59208f
tm1637
tm1651
tmp102
tmp117
toshiba
total_daily_energy
tsl2561
ttp229_bsf
ttp229_lsf
tuya
tx20
uart
uln2003
ultrasonic
uptime
version
vl53l0x
voltage_sampler
waveshare_epaper
web_server
web_server_base
whirlpool
wifi
wifi_info
wifi_signal
wled
xiaomi_ble
xiaomi_cgd1
xiaomi_cgg1
xiaomi_gcls002
xiaomi_hhccjcy01
xiaomi_hhccpot002
xiaomi_jqjcy01ym
xiaomi_lywsd02
xiaomi_lywsd03mmc
xiaomi_lywsdcgq
xiaomi_mhoc401
xiaomi_miscale
xiaomi_miscale2
xiaomi_mjyd02yla
xiaomi_mue4094rt
xiaomi_wx08zm
yashima
zyaura
dashboard
espota2.pyhelpers.pylegacy.pymqtt.pypins.pyplatformio_api.pystorage_json.pyutil.pyvoluptuous_schema.pyvscode.pywizard.pywriter.pyyaml_util.pyzeroconf.pyscript
api_protobuf
build_codeowners.pybuild_compile_commands.pybump-docker-base-version.pybump-version.pyci-custom.pyhelpers.pylint-pythonsetuptests
@ -14,6 +14,7 @@ import argparse
|
||||
sys.path.append(os.path.dirname(__file__))
|
||||
from helpers import git_ls_files, filter_changed
|
||||
|
||||
|
||||
def find_all(a_str, sub):
|
||||
if not a_str.find(sub):
|
||||
# Optimization: If str is not in whole text, then do not try
|
||||
@ -30,18 +31,21 @@ def find_all(a_str, sub):
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('files', nargs='*', default=[],
|
||||
help='files to be processed (regex on path)')
|
||||
parser.add_argument('-c', '--changed', action='store_true',
|
||||
help='Only run on changed files')
|
||||
parser.add_argument('--print-slowest', action='store_true',
|
||||
help='Print the slowest checks')
|
||||
parser.add_argument(
|
||||
"files", nargs="*", default=[], help="files to be processed (regex on path)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c", "--changed", action="store_true", help="Only run on changed files"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--print-slowest", action="store_true", help="Print the slowest checks"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
EXECUTABLE_BIT = git_ls_files()
|
||||
files = list(EXECUTABLE_BIT.keys())
|
||||
# Match against re
|
||||
file_name_re = re.compile('|'.join(args.files))
|
||||
file_name_re = re.compile("|".join(args.files))
|
||||
files = [p for p in files if file_name_re.search(p)]
|
||||
|
||||
if args.changed:
|
||||
@ -49,11 +53,32 @@ if args.changed:
|
||||
|
||||
files.sort()
|
||||
|
||||
file_types = ('.h', '.c', '.cpp', '.tcc', '.yaml', '.yml', '.ini', '.txt', '.ico', '.svg',
|
||||
'.py', '.html', '.js', '.md', '.sh', '.css', '.proto', '.conf', '.cfg',
|
||||
'.woff', '.woff2', '')
|
||||
cpp_include = ('*.h', '*.c', '*.cpp', '*.tcc')
|
||||
ignore_types = ('.ico', '.woff', '.woff2', '')
|
||||
file_types = (
|
||||
".h",
|
||||
".c",
|
||||
".cpp",
|
||||
".tcc",
|
||||
".yaml",
|
||||
".yml",
|
||||
".ini",
|
||||
".txt",
|
||||
".ico",
|
||||
".svg",
|
||||
".py",
|
||||
".html",
|
||||
".js",
|
||||
".md",
|
||||
".sh",
|
||||
".css",
|
||||
".proto",
|
||||
".conf",
|
||||
".cfg",
|
||||
".woff",
|
||||
".woff2",
|
||||
"",
|
||||
)
|
||||
cpp_include = ("*.h", "*.c", "*.cpp", "*.tcc")
|
||||
ignore_types = (".ico", ".woff", ".woff2", "")
|
||||
|
||||
LINT_FILE_CHECKS = []
|
||||
LINT_CONTENT_CHECKS = []
|
||||
@ -61,9 +86,9 @@ LINT_POST_CHECKS = []
|
||||
|
||||
|
||||
def run_check(lint_obj, fname, *args):
|
||||
include = lint_obj['include']
|
||||
exclude = lint_obj['exclude']
|
||||
func = lint_obj['func']
|
||||
include = lint_obj["include"]
|
||||
exclude = lint_obj["exclude"]
|
||||
func = lint_obj["func"]
|
||||
if include is not None:
|
||||
for incl in include:
|
||||
if fnmatch.fnmatch(fname, incl):
|
||||
@ -85,21 +110,24 @@ def run_checks(lints, fname, *args):
|
||||
print(f"Check {lint['func'].__name__} on file {fname} failed:")
|
||||
raise
|
||||
duration = time.process_time() - start
|
||||
lint.setdefault('durations', []).append(duration)
|
||||
lint.setdefault("durations", []).append(duration)
|
||||
|
||||
|
||||
def _add_check(checks, func, include=None, exclude=None):
|
||||
checks.append({
|
||||
'include': include,
|
||||
'exclude': exclude or [],
|
||||
'func': func,
|
||||
})
|
||||
checks.append(
|
||||
{
|
||||
"include": include,
|
||||
"exclude": exclude or [],
|
||||
"func": func,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def lint_file_check(**kwargs):
|
||||
def decorator(func):
|
||||
_add_check(LINT_FILE_CHECKS, func, **kwargs)
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@ -107,6 +135,7 @@ def lint_content_check(**kwargs):
|
||||
def decorator(func):
|
||||
_add_check(LINT_CONTENT_CHECKS, func, **kwargs)
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@ -116,7 +145,7 @@ def lint_post_check(func):
|
||||
|
||||
|
||||
def lint_re_check(regex, **kwargs):
|
||||
flags = kwargs.pop('flags', re.MULTILINE)
|
||||
flags = kwargs.pop("flags", re.MULTILINE)
|
||||
prog = re.compile(regex, flags)
|
||||
decor = lint_content_check(**kwargs)
|
||||
|
||||
@ -125,18 +154,19 @@ def lint_re_check(regex, **kwargs):
|
||||
def new_func(fname, content):
|
||||
errors = []
|
||||
for match in prog.finditer(content):
|
||||
if 'NOLINT' in match.group(0):
|
||||
if "NOLINT" in match.group(0):
|
||||
continue
|
||||
lineno = content.count("\n", 0, match.start()) + 1
|
||||
substr = content[:match.start()]
|
||||
col = len(substr) - substr.rfind('\n')
|
||||
substr = content[: match.start()]
|
||||
col = len(substr) - substr.rfind("\n")
|
||||
err = func(fname, match)
|
||||
if err is None:
|
||||
continue
|
||||
errors.append((lineno, col+1, err))
|
||||
errors.append((lineno, col + 1, err))
|
||||
return errors
|
||||
|
||||
return decor(new_func)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@ -152,73 +182,99 @@ def lint_content_find_check(find, **kwargs):
|
||||
errors = []
|
||||
for line, col in find_all(content, find_):
|
||||
err = func(fname)
|
||||
errors.append((line+1, col+1, err))
|
||||
errors.append((line + 1, col + 1, err))
|
||||
return errors
|
||||
|
||||
return decor(new_func)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@lint_file_check(include=['*.ino'])
|
||||
@lint_file_check(include=["*.ino"])
|
||||
def lint_ino(fname):
|
||||
return "This file extension (.ino) is not allowed. Please use either .cpp or .h"
|
||||
|
||||
|
||||
@lint_file_check(exclude=[f'*{f}' for f in file_types] + [
|
||||
'.clang-*', '.dockerignore', '.editorconfig', '*.gitignore', 'LICENSE', 'pylintrc',
|
||||
'MANIFEST.in', 'docker/Dockerfile*', 'docker/rootfs/*', 'script/*',
|
||||
])
|
||||
@lint_file_check(
|
||||
exclude=[f"*{f}" for f in file_types]
|
||||
+ [
|
||||
".clang-*",
|
||||
".dockerignore",
|
||||
".editorconfig",
|
||||
"*.gitignore",
|
||||
"LICENSE",
|
||||
"pylintrc",
|
||||
"MANIFEST.in",
|
||||
"docker/Dockerfile*",
|
||||
"docker/rootfs/*",
|
||||
"script/*",
|
||||
]
|
||||
)
|
||||
def lint_ext_check(fname):
|
||||
return "This file extension is not a registered file type. If this is an error, please " \
|
||||
"update the script/ci-custom.py script."
|
||||
return (
|
||||
"This file extension is not a registered file type. If this is an error, please "
|
||||
"update the script/ci-custom.py script."
|
||||
)
|
||||
|
||||
|
||||
@lint_file_check(exclude=[
|
||||
'docker/rootfs/*', 'script/*', 'setup.py'
|
||||
])
|
||||
@lint_file_check(exclude=["docker/rootfs/*", "script/*", "setup.py"])
|
||||
def lint_executable_bit(fname):
|
||||
ex = EXECUTABLE_BIT[fname]
|
||||
if ex != 100644:
|
||||
return 'File has invalid executable bit {}. If running from a windows machine please ' \
|
||||
'see disabling executable bit in git.'.format(ex)
|
||||
return (
|
||||
"File has invalid executable bit {}. If running from a windows machine please "
|
||||
"see disabling executable bit in git.".format(ex)
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
@lint_content_find_check('\t', exclude=[
|
||||
'esphome/dashboard/static/ace.js', 'esphome/dashboard/static/ext-searchbox.js',
|
||||
])
|
||||
@lint_content_find_check(
|
||||
"\t",
|
||||
exclude=[
|
||||
"esphome/dashboard/static/ace.js",
|
||||
"esphome/dashboard/static/ext-searchbox.js",
|
||||
],
|
||||
)
|
||||
def lint_tabs(fname):
|
||||
return "File contains tab character. Please convert tabs to spaces."
|
||||
|
||||
|
||||
@lint_content_find_check('\r')
|
||||
@lint_content_find_check("\r")
|
||||
def lint_newline(fname):
|
||||
return "File contains windows newline. Please set your editor to unix newline mode."
|
||||
|
||||
|
||||
@lint_content_check(exclude=['*.svg'])
|
||||
@lint_content_check(exclude=["*.svg"])
|
||||
def lint_end_newline(fname, content):
|
||||
if content and not content.endswith('\n'):
|
||||
if content and not content.endswith("\n"):
|
||||
return "File does not end with a newline, please add an empty line at the end of the file."
|
||||
return None
|
||||
|
||||
|
||||
CPP_RE_EOL = r'\s*?(?://.*?)?$'
|
||||
CPP_RE_EOL = r"\s*?(?://.*?)?$"
|
||||
|
||||
|
||||
def highlight(s):
|
||||
return f'\033[36m{s}\033[0m'
|
||||
return f"\033[36m{s}\033[0m"
|
||||
|
||||
|
||||
@lint_re_check(r'^#define\s+([a-zA-Z0-9_]+)\s+([0-9bx]+)' + CPP_RE_EOL,
|
||||
include=cpp_include, exclude=['esphome/core/log.h'])
|
||||
@lint_re_check(
|
||||
r"^#define\s+([a-zA-Z0-9_]+)\s+([0-9bx]+)" + CPP_RE_EOL,
|
||||
include=cpp_include,
|
||||
exclude=["esphome/core/log.h"],
|
||||
)
|
||||
def lint_no_defines(fname, match):
|
||||
s = highlight('static const uint8_t {} = {};'.format(match.group(1), match.group(2)))
|
||||
return ("#define macros for integer constants are not allowed, please use "
|
||||
"{} style instead (replace uint8_t with the appropriate "
|
||||
"datatype). See also Google style guide.".format(s))
|
||||
s = highlight(
|
||||
"static const uint8_t {} = {};".format(match.group(1), match.group(2))
|
||||
)
|
||||
return (
|
||||
"#define macros for integer constants are not allowed, please use "
|
||||
"{} style instead (replace uint8_t with the appropriate "
|
||||
"datatype). See also Google style guide.".format(s)
|
||||
)
|
||||
|
||||
|
||||
@lint_re_check(r'^\s*delay\((\d+)\);' + CPP_RE_EOL, include=cpp_include)
|
||||
@lint_re_check(r"^\s*delay\((\d+)\);" + CPP_RE_EOL, include=cpp_include)
|
||||
def lint_no_long_delays(fname, match):
|
||||
duration_ms = int(match.group(1))
|
||||
if duration_ms < 50:
|
||||
@ -232,7 +288,7 @@ def lint_no_long_delays(fname, match):
|
||||
)
|
||||
|
||||
|
||||
@lint_content_check(include=['esphome/const.py'])
|
||||
@lint_content_check(include=["esphome/const.py"])
|
||||
def lint_const_ordered(fname, content):
|
||||
"""Lint that value in const.py are ordered.
|
||||
|
||||
@ -240,54 +296,67 @@ def lint_const_ordered(fname, content):
|
||||
"""
|
||||
lines = content.splitlines()
|
||||
errors = []
|
||||
for start in ['CONF_', 'ICON_', 'UNIT_']:
|
||||
matching = [(i+1, line) for i, line in enumerate(lines) if line.startswith(start)]
|
||||
ordered = list(sorted(matching, key=lambda x: x[1].replace('_', ' ')))
|
||||
for start in ["CONF_", "ICON_", "UNIT_"]:
|
||||
matching = [
|
||||
(i + 1, line) for i, line in enumerate(lines) if line.startswith(start)
|
||||
]
|
||||
ordered = list(sorted(matching, key=lambda x: x[1].replace("_", " ")))
|
||||
ordered = [(mi, ol) for (mi, _), (_, ol) in zip(matching, ordered)]
|
||||
for (mi, ml), (oi, ol) in zip(matching, ordered):
|
||||
if ml == ol:
|
||||
continue
|
||||
target = next(i for i, l in ordered if l == ml)
|
||||
target_text = next(l for i, l in matching if target == i)
|
||||
errors.append((mi, 1,
|
||||
f"Constant {highlight(ml)} is not ordered, please make sure all "
|
||||
f"constants are ordered. See line {mi} (should go to line {target}, "
|
||||
f"{target_text})"))
|
||||
errors.append(
|
||||
(
|
||||
mi,
|
||||
1,
|
||||
f"Constant {highlight(ml)} is not ordered, please make sure all "
|
||||
f"constants are ordered. See line {mi} (should go to line {target}, "
|
||||
f"{target_text})",
|
||||
)
|
||||
)
|
||||
return errors
|
||||
|
||||
|
||||
@lint_re_check(r'^\s*CONF_([A-Z_0-9a-z]+)\s+=\s+[\'"](.*?)[\'"]\s*?$', include=['*.py'])
|
||||
@lint_re_check(r'^\s*CONF_([A-Z_0-9a-z]+)\s+=\s+[\'"](.*?)[\'"]\s*?$', include=["*.py"])
|
||||
def lint_conf_matches(fname, match):
|
||||
const = match.group(1)
|
||||
value = match.group(2)
|
||||
const_norm = const.lower()
|
||||
value_norm = value.replace('.', '_')
|
||||
value_norm = value.replace(".", "_")
|
||||
if const_norm == value_norm:
|
||||
return None
|
||||
return ("Constant {} does not match value {}! Please make sure the constant's name matches its "
|
||||
"value!"
|
||||
"".format(highlight('CONF_' + const), highlight(value)))
|
||||
return (
|
||||
"Constant {} does not match value {}! Please make sure the constant's name matches its "
|
||||
"value!"
|
||||
"".format(highlight("CONF_" + const), highlight(value))
|
||||
)
|
||||
|
||||
|
||||
CONF_RE = r'^(CONF_[a-zA-Z0-9_]+)\s*=\s*[\'"].*?[\'"]\s*?$'
|
||||
with codecs.open('esphome/const.py', 'r', encoding='utf-8') as f_handle:
|
||||
with codecs.open("esphome/const.py", "r", encoding="utf-8") as f_handle:
|
||||
constants_content = f_handle.read()
|
||||
CONSTANTS = [m.group(1) for m in re.finditer(CONF_RE, constants_content, re.MULTILINE)]
|
||||
|
||||
CONSTANTS_USES = collections.defaultdict(list)
|
||||
|
||||
|
||||
@lint_re_check(CONF_RE, include=['*.py'], exclude=['esphome/const.py'])
|
||||
@lint_re_check(CONF_RE, include=["*.py"], exclude=["esphome/const.py"])
|
||||
def lint_conf_from_const_py(fname, match):
|
||||
name = match.group(1)
|
||||
if name not in CONSTANTS:
|
||||
CONSTANTS_USES[name].append(fname)
|
||||
return None
|
||||
return ("Constant {} has already been defined in const.py - please import the constant from "
|
||||
"const.py directly.".format(highlight(name)))
|
||||
return (
|
||||
"Constant {} has already been defined in const.py - please import the constant from "
|
||||
"const.py directly.".format(highlight(name))
|
||||
)
|
||||
|
||||
|
||||
RAW_PIN_ACCESS_RE = r'^\s(pinMode|digitalWrite|digitalRead)\((.*)->get_pin\(\),\s*([^)]+).*\)'
|
||||
RAW_PIN_ACCESS_RE = (
|
||||
r"^\s(pinMode|digitalWrite|digitalRead)\((.*)->get_pin\(\),\s*([^)]+).*\)"
|
||||
)
|
||||
|
||||
|
||||
@lint_re_check(RAW_PIN_ACCESS_RE, include=cpp_include)
|
||||
@ -296,33 +365,49 @@ def lint_no_raw_pin_access(fname, match):
|
||||
pin = match.group(2)
|
||||
mode = match.group(3)
|
||||
new_func = {
|
||||
'pinMode': 'pin_mode',
|
||||
'digitalWrite': 'digital_write',
|
||||
'digitalRead': 'digital_read',
|
||||
"pinMode": "pin_mode",
|
||||
"digitalWrite": "digital_write",
|
||||
"digitalRead": "digital_read",
|
||||
}[func]
|
||||
new_code = highlight(f'{pin}->{new_func}({mode})')
|
||||
return (f"Don't use raw {func} calls. Instead, use the `->{new_func}` function: {new_code}")
|
||||
new_code = highlight(f"{pin}->{new_func}({mode})")
|
||||
return f"Don't use raw {func} calls. Instead, use the `->{new_func}` function: {new_code}"
|
||||
|
||||
|
||||
# Functions from Arduino framework that are forbidden to use directly
|
||||
ARDUINO_FORBIDDEN = [
|
||||
'digitalWrite', 'digitalRead', 'pinMode',
|
||||
'shiftOut', 'shiftIn',
|
||||
'radians', 'degrees',
|
||||
'interrupts', 'noInterrupts',
|
||||
'lowByte', 'highByte',
|
||||
'bitRead', 'bitSet', 'bitClear', 'bitWrite',
|
||||
'bit', 'analogRead', 'analogWrite',
|
||||
'pulseIn', 'pulseInLong',
|
||||
'tone',
|
||||
"digitalWrite",
|
||||
"digitalRead",
|
||||
"pinMode",
|
||||
"shiftOut",
|
||||
"shiftIn",
|
||||
"radians",
|
||||
"degrees",
|
||||
"interrupts",
|
||||
"noInterrupts",
|
||||
"lowByte",
|
||||
"highByte",
|
||||
"bitRead",
|
||||
"bitSet",
|
||||
"bitClear",
|
||||
"bitWrite",
|
||||
"bit",
|
||||
"analogRead",
|
||||
"analogWrite",
|
||||
"pulseIn",
|
||||
"pulseInLong",
|
||||
"tone",
|
||||
]
|
||||
ARDUINO_FORBIDDEN_RE = r'[^\w\d](' + r'|'.join(ARDUINO_FORBIDDEN) + r')\(.*'
|
||||
ARDUINO_FORBIDDEN_RE = r"[^\w\d](" + r"|".join(ARDUINO_FORBIDDEN) + r")\(.*"
|
||||
|
||||
|
||||
@lint_re_check(ARDUINO_FORBIDDEN_RE, include=cpp_include, exclude=[
|
||||
'esphome/components/mqtt/custom_mqtt_device.h',
|
||||
'esphome/core/esphal.*',
|
||||
])
|
||||
@lint_re_check(
|
||||
ARDUINO_FORBIDDEN_RE,
|
||||
include=cpp_include,
|
||||
exclude=[
|
||||
"esphome/components/mqtt/custom_mqtt_device.h",
|
||||
"esphome/core/esphal.*",
|
||||
],
|
||||
)
|
||||
def lint_no_arduino_framework_functions(fname, match):
|
||||
nolint = highlight("// NOLINT")
|
||||
return (
|
||||
@ -334,9 +419,13 @@ def lint_no_arduino_framework_functions(fname, match):
|
||||
)
|
||||
|
||||
|
||||
@lint_re_check(r'[^\w\d]byte\s+[\w\d]+\s*=', include=cpp_include, exclude={
|
||||
'esphome/components/tuya/tuya.h',
|
||||
})
|
||||
@lint_re_check(
|
||||
r"[^\w\d]byte\s+[\w\d]+\s*=",
|
||||
include=cpp_include,
|
||||
exclude={
|
||||
"esphome/components/tuya/tuya.h",
|
||||
},
|
||||
)
|
||||
def lint_no_byte_datatype(fname, match):
|
||||
return (
|
||||
f"The datatype {highlight('byte')} is not allowed to be used in ESPHome. "
|
||||
@ -350,112 +439,143 @@ def lint_constants_usage():
|
||||
for constant, uses in CONSTANTS_USES.items():
|
||||
if len(uses) < 4:
|
||||
continue
|
||||
errors.append("Constant {} is defined in {} files. Please move all definitions of the "
|
||||
"constant to const.py (Uses: {})"
|
||||
"".format(highlight(constant), len(uses), ', '.join(uses)))
|
||||
errors.append(
|
||||
"Constant {} is defined in {} files. Please move all definitions of the "
|
||||
"constant to const.py (Uses: {})"
|
||||
"".format(highlight(constant), len(uses), ", ".join(uses))
|
||||
)
|
||||
return errors
|
||||
|
||||
|
||||
def relative_cpp_search_text(fname, content):
|
||||
parts = fname.split('/')
|
||||
parts = fname.split("/")
|
||||
integration = parts[2]
|
||||
return f'#include "esphome/components/{integration}'
|
||||
|
||||
|
||||
@lint_content_find_check(relative_cpp_search_text, include=['esphome/components/*.cpp'])
|
||||
@lint_content_find_check(relative_cpp_search_text, include=["esphome/components/*.cpp"])
|
||||
def lint_relative_cpp_import(fname):
|
||||
return ("Component contains absolute import - Components must always use "
|
||||
"relative imports.\n"
|
||||
"Change:\n"
|
||||
' #include "esphome/components/abc/abc.h"\n'
|
||||
'to:\n'
|
||||
' #include "abc.h"\n\n')
|
||||
return (
|
||||
"Component contains absolute import - Components must always use "
|
||||
"relative imports.\n"
|
||||
"Change:\n"
|
||||
' #include "esphome/components/abc/abc.h"\n'
|
||||
"to:\n"
|
||||
' #include "abc.h"\n\n'
|
||||
)
|
||||
|
||||
|
||||
def relative_py_search_text(fname, content):
|
||||
parts = fname.split('/')
|
||||
parts = fname.split("/")
|
||||
integration = parts[2]
|
||||
return f'esphome.components.{integration}'
|
||||
return f"esphome.components.{integration}"
|
||||
|
||||
|
||||
@lint_content_find_check(relative_py_search_text, include=['esphome/components/*.py'],
|
||||
exclude=['esphome/components/web_server/__init__.py'])
|
||||
@lint_content_find_check(
|
||||
relative_py_search_text,
|
||||
include=["esphome/components/*.py"],
|
||||
exclude=["esphome/components/web_server/__init__.py"],
|
||||
)
|
||||
def lint_relative_py_import(fname):
|
||||
return ("Component contains absolute import - Components must always use "
|
||||
"relative imports within the integration.\n"
|
||||
"Change:\n"
|
||||
' from esphome.components.abc import abc_ns"\n'
|
||||
'to:\n'
|
||||
' from . import abc_ns\n\n')
|
||||
return (
|
||||
"Component contains absolute import - Components must always use "
|
||||
"relative imports within the integration.\n"
|
||||
"Change:\n"
|
||||
' from esphome.components.abc import abc_ns"\n'
|
||||
"to:\n"
|
||||
" from . import abc_ns\n\n"
|
||||
)
|
||||
|
||||
|
||||
@lint_content_check(include=['esphome/components/*.h', 'esphome/components/*.cpp',
|
||||
'esphome/components/*.tcc'])
|
||||
@lint_content_check(
|
||||
include=[
|
||||
"esphome/components/*.h",
|
||||
"esphome/components/*.cpp",
|
||||
"esphome/components/*.tcc",
|
||||
]
|
||||
)
|
||||
def lint_namespace(fname, content):
|
||||
expected_name = re.match(r'^esphome/components/([^/]+)/.*',
|
||||
fname.replace(os.path.sep, '/')).group(1)
|
||||
search = f'namespace {expected_name}'
|
||||
expected_name = re.match(
|
||||
r"^esphome/components/([^/]+)/.*", fname.replace(os.path.sep, "/")
|
||||
).group(1)
|
||||
search = f"namespace {expected_name}"
|
||||
if search in content:
|
||||
return None
|
||||
return 'Invalid namespace found in C++ file. All integration C++ files should put all ' \
|
||||
'functions in a separate namespace that matches the integration\'s name. ' \
|
||||
'Please make sure the file contains {}'.format(highlight(search))
|
||||
return (
|
||||
"Invalid namespace found in C++ file. All integration C++ files should put all "
|
||||
"functions in a separate namespace that matches the integration's name. "
|
||||
"Please make sure the file contains {}".format(highlight(search))
|
||||
)
|
||||
|
||||
|
||||
@lint_content_find_check('"esphome.h"', include=cpp_include, exclude=['tests/custom.h'])
|
||||
@lint_content_find_check('"esphome.h"', include=cpp_include, exclude=["tests/custom.h"])
|
||||
def lint_esphome_h(fname):
|
||||
return ("File contains reference to 'esphome.h' - This file is "
|
||||
"auto-generated and should only be used for *custom* "
|
||||
"components. Please replace with references to the direct files.")
|
||||
return (
|
||||
"File contains reference to 'esphome.h' - This file is "
|
||||
"auto-generated and should only be used for *custom* "
|
||||
"components. Please replace with references to the direct files."
|
||||
)
|
||||
|
||||
|
||||
@lint_content_check(include=['*.h'])
|
||||
@lint_content_check(include=["*.h"])
|
||||
def lint_pragma_once(fname, content):
|
||||
if '#pragma once' not in content:
|
||||
return ("Header file contains no 'pragma once' header guard. Please add a "
|
||||
"'#pragma once' line at the top of the file.")
|
||||
if "#pragma once" not in content:
|
||||
return (
|
||||
"Header file contains no 'pragma once' header guard. Please add a "
|
||||
"'#pragma once' line at the top of the file."
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
@lint_re_check(r'(whitelist|blacklist|slave)',
|
||||
exclude=['script/ci-custom.py'], flags=re.IGNORECASE | re.MULTILINE)
|
||||
@lint_re_check(
|
||||
r"(whitelist|blacklist|slave)",
|
||||
exclude=["script/ci-custom.py"],
|
||||
flags=re.IGNORECASE | re.MULTILINE,
|
||||
)
|
||||
def lint_inclusive_language(fname, match):
|
||||
# From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=49decddd39e5f6132ccd7d9fdc3d7c470b0061bb
|
||||
return ("Avoid the use of whitelist/blacklist/slave.\n"
|
||||
"Recommended replacements for 'master / slave' are:\n"
|
||||
" '{primary,main} / {secondary,replica,subordinate}\n"
|
||||
" '{initiator,requester} / {target,responder}'\n"
|
||||
" '{controller,host} / {device,worker,proxy}'\n"
|
||||
" 'leader / follower'\n"
|
||||
" 'director / performer'\n"
|
||||
"\n"
|
||||
"Recommended replacements for 'blacklist/whitelist' are:\n"
|
||||
" 'denylist / allowlist'\n"
|
||||
" 'blocklist / passlist'")
|
||||
return (
|
||||
"Avoid the use of whitelist/blacklist/slave.\n"
|
||||
"Recommended replacements for 'master / slave' are:\n"
|
||||
" '{primary,main} / {secondary,replica,subordinate}\n"
|
||||
" '{initiator,requester} / {target,responder}'\n"
|
||||
" '{controller,host} / {device,worker,proxy}'\n"
|
||||
" 'leader / follower'\n"
|
||||
" 'director / performer'\n"
|
||||
"\n"
|
||||
"Recommended replacements for 'blacklist/whitelist' are:\n"
|
||||
" 'denylist / allowlist'\n"
|
||||
" 'blocklist / passlist'"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@lint_content_find_check('ESP_LOG', include=['*.h', '*.tcc'], exclude=[
|
||||
'esphome/components/binary_sensor/binary_sensor.h',
|
||||
'esphome/components/cover/cover.h',
|
||||
'esphome/components/display/display_buffer.h',
|
||||
'esphome/components/i2c/i2c.h',
|
||||
'esphome/components/mqtt/mqtt_component.h',
|
||||
'esphome/components/output/binary_output.h',
|
||||
'esphome/components/output/float_output.h',
|
||||
'esphome/components/sensor/sensor.h',
|
||||
'esphome/components/stepper/stepper.h',
|
||||
'esphome/components/switch/switch.h',
|
||||
'esphome/components/text_sensor/text_sensor.h',
|
||||
'esphome/components/climate/climate.h',
|
||||
'esphome/core/component.h',
|
||||
'esphome/core/esphal.h',
|
||||
'esphome/core/log.h',
|
||||
'tests/custom.h',
|
||||
])
|
||||
@lint_content_find_check(
|
||||
"ESP_LOG",
|
||||
include=["*.h", "*.tcc"],
|
||||
exclude=[
|
||||
"esphome/components/binary_sensor/binary_sensor.h",
|
||||
"esphome/components/cover/cover.h",
|
||||
"esphome/components/display/display_buffer.h",
|
||||
"esphome/components/i2c/i2c.h",
|
||||
"esphome/components/mqtt/mqtt_component.h",
|
||||
"esphome/components/output/binary_output.h",
|
||||
"esphome/components/output/float_output.h",
|
||||
"esphome/components/sensor/sensor.h",
|
||||
"esphome/components/stepper/stepper.h",
|
||||
"esphome/components/switch/switch.h",
|
||||
"esphome/components/text_sensor/text_sensor.h",
|
||||
"esphome/components/climate/climate.h",
|
||||
"esphome/core/component.h",
|
||||
"esphome/core/esphal.h",
|
||||
"esphome/core/log.h",
|
||||
"tests/custom.h",
|
||||
],
|
||||
)
|
||||
def lint_log_in_header(fname):
|
||||
return ('Found reference to ESP_LOG in header file. Using ESP_LOG* in header files '
|
||||
'is currently not possible - please move the definition to a source file (.cpp)')
|
||||
return (
|
||||
"Found reference to ESP_LOG in header file. Using ESP_LOG* in header files "
|
||||
"is currently not possible - please move the definition to a source file (.cpp)"
|
||||
)
|
||||
|
||||
|
||||
errors = collections.defaultdict(list)
|
||||
@ -488,14 +608,17 @@ for fname in files:
|
||||
if ext in ignore_types:
|
||||
continue
|
||||
try:
|
||||
with codecs.open(fname, 'r', encoding='utf-8') as f_handle:
|
||||
with codecs.open(fname, "r", encoding="utf-8") as f_handle:
|
||||
content = f_handle.read()
|
||||
except UnicodeDecodeError:
|
||||
add_errors(fname, "File is not readable as UTF-8. Please set your editor to UTF-8 mode.")
|
||||
add_errors(
|
||||
fname,
|
||||
"File is not readable as UTF-8. Please set your editor to UTF-8 mode.",
|
||||
)
|
||||
continue
|
||||
run_checks(LINT_CONTENT_CHECKS, fname, fname, content)
|
||||
|
||||
run_checks(LINT_POST_CHECKS, 'POST')
|
||||
run_checks(LINT_POST_CHECKS, "POST")
|
||||
|
||||
for f, errs in sorted(errors.items()):
|
||||
print(f"\033[0;32m************* File \033[1;32m{f}\033[0m")
|
||||
@ -506,8 +629,8 @@ for f, errs in sorted(errors.items()):
|
||||
if args.print_slowest:
|
||||
lint_times = []
|
||||
for lint in LINT_FILE_CHECKS + LINT_CONTENT_CHECKS + LINT_POST_CHECKS:
|
||||
durations = lint.get('durations', [])
|
||||
lint_times.append((sum(durations), len(durations), lint['func'].__name__))
|
||||
durations = lint.get("durations", [])
|
||||
lint_times.append((sum(durations), len(durations), lint["func"].__name__))
|
||||
lint_times.sort(key=lambda x: -x[0])
|
||||
for i in range(min(len(lint_times), 10)):
|
||||
dur, invocations, name = lint_times[i]
|
||||
|
Reference in New Issue
Block a user