#!/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))