Files
harbour-aira/tests/qml_check_bindings.py

119 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""Assegnazioni duplicate di proprieta' negli stessi oggetti QML.
Perche' esiste: in Qt 5.6 assegnare due volte la stessa proprieta' di un oggetto
QML (tipico: due `Component.onCompleted` nella stessa pagina) non e' un errore
innocuo: il file compila con un warning "Property value set multiple times" e
l'oggetto NON si crea piu'. Dall'esterno si vede solo
harbour-aira.qml:9:30: Type MainPage unavailable
cioe' l'app che si apre senza la pagina iniziale, senza dire il perche'.
Il kit Silica non vede niente di tutto questo (qmllint conosce il QML, non il
significato di questi binding), quindi il controllo sta qui e gira in pochi
millisecondi.
Uso:
python3 tests/qml_check_bindings.py [cartella_qml]
python3 tests/qml_check_bindings.py --dettagli # elenca anche l'ok
Esce 1 se trova duplicati, 0 se e' pulito.
"""
import pathlib
import re
import sys
# Riga che apre un oggetto QML: un componente (maiuscola) oppure un gruppo di
# proprieta' noto. Il corpo di un gestore (onClicked:) o di una funzione e' JS,
# e li' dentro i "duplicati" sono normali (ternari, oggetti letterali, rami).
APRE_OGGETTO = re.compile(r"^\s*([A-Z][A-Za-z0-9_]*)\s*\{")
APRE_GRUPPO = re.compile(r"^\s*(anchors|font|Layout\.[A-Za-z]+)\s*\{")
APRE_JS = re.compile(r"^\s*(function\s+\w+|if\b|else\b|for\b|while\b|switch\b|do\b|try\b|catch\b)\s*[\({]")
APRE_GESTORE = re.compile(r"^\s*on[A-Z][A-Za-z0-9_]*\s*:")
PROPRIETA = re.compile(r"^\s*((?:readonly\s+)?property\s+\S+\s+([A-Za-z_]\w*)|signal\s+(\w+)|function\s+(\w+)|([A-Za-z_][\w.]*))\s*:")
SENZA_COMMENTO = re.compile(r"//.*$")
def chiave(riga: str):
"""Nome della proprieta' assegnata dalla riga, o None."""
m = PROPRIETA.match(SENZA_COMMENTO.sub("", riga))
if not m:
return None
return m.group(2) or m.group(3) or m.group(4) or m.group(5)
def controlla(testo: str):
"""Ritorna la lista di (riga, chiave) duplicate nello stesso oggetto."""
pila = [("qml", {})] # (tipo ambiente, chiavi viste)
problemi = []
for numero, riga in enumerate(testo.splitlines(), 1):
pulita = SENZA_COMMENTO.sub("", riga)
if not pulita.strip():
continue
# Chiusura: si esce dall'ambiente corrente.
while pulita.strip().startswith("}") and len(pila) > 1:
pila.pop()
pulita = pulita.strip()[1:]
if not pulita.strip():
break
if not pulita.strip():
continue
ambiente, chiavi = pila[-1]
if ambiente == "qml":
k = chiave(pulita)
if k:
if k in chiavi:
problemi.append((numero, k))
else:
chiavi[k] = numero
# Apertura di un nuovo ambiente: il contenuto e' QML solo se la riga
# apre un oggetto o un gruppo; gestori e funzioni aprono JS.
apre_js = bool(APRE_JS.match(pulita)) or bool(APRE_GESTORE.match(pulita))
apre_qml = (bool(APRE_OGGETTO.match(pulita)) or bool(APRE_GRUPPO.match(pulita))) \
and not apre_js
# Una proprieta' il cui valore e' un oggetto letterale JS non e' QML.
if ":" in pulita and "{" in pulita and not apre_qml and not apre_js:
apre_js = True
if pulita.count("{") > pulita.count("}"):
pila.append(("qml" if apre_qml else "js", {}))
return problemi
def main(argv):
argomenti = [a for a in argv[1:] if not a.startswith("--")]
radice = pathlib.Path(argomenti[0] if argomenti else "qml")
dettagli = "--dettagli" in argv
file_qml = sorted(radice.rglob("*.qml"))
if not file_qml:
print("nessun file QML in", radice)
return 1
totale = 0
for percorso in file_qml:
problemi = controlla(percorso.read_text(encoding="utf-8"))
relative = percorso.relative_to(radice.parent if radice.name == "qml" else radice)
if problemi:
totale += len(problemi)
print("%s: %d assegnazioni duplicate" % (relative, len(problemi)))
for numero, k in problemi:
print(" riga %d: '%s' gia' assegnata in questo oggetto" % (numero, k))
elif dettagli:
print("%s: pulito" % relative)
print("---")
if totale:
print("ESITO: %d assegnazioni duplicate (l'oggetto non si crea: "
"vedi Qt 5.6, 'Property value set multiple times')" % totale)
return 1
print("ESITO: nessuna assegnazione duplicata in %d file" % len(file_qml))
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))