check.sh
download
# Analizator statyczny dla jednego z projektów. Sprawdza kod pod kątem
# jakości począwszy od prostych sprawdzeń jak np. maksymalna długości
# linii na złożonych jak np. prawidłowo otwierane/zamykane tagi
# "<tr>", "<td>", oznaczenie kodowania znaków w plikach źródłowych
# *.py.
#
# Realizuje też mnóstwo testów specjalnie dla tego projektu np.
# sprawdzanie wykorzystania zapytań, konwencje nazewnicze w systemie,
# prawidłowe renderowanie kwot w plikach styli.
#
# Pilnowanie takich drobnych rzeczy ręcznie było by bardzo uciążliwe,
# dzięki odpowiednim konwencjom nazewniczym i prostym skryptom opartym
# na wyrażeniach regularnych można egzekwować te reguły przed każym
# uruchomieniem testów (co oszczędziło nam już bardzo wielu błędów).
#!/bin/sh
HTML="view.html dokumentyFiskalne.html prowizje.html klienci.html
zalaczniki.html zapotrzebowania.html prezentacje.html zamowienia.html
operacjeKasowe.html produkty.html crm.html zadania.html waluty.html
kontrahent.html"
SQL="queries.sql klienci.sql dokumentyFiskalne.sql prowizje.sql
zapotrzebowania.sql zamowienia.sql operacjeKasowe.sql produkty.sql
crm.sql zadania.sql prezentacje.sql waluty.sql kontrahent.sql"
######################################################################
awk '
# wychwycenie (zapewne) błędnej konstrukcji NOT LIKE - nieintuicyjny
# priorytet operatorów w SQL
/NOT [a-zA-Z]* LIKE/ {
print FILENAME ":" FNR ": dodaj nawiasy !"
err = 1
}
# Wszystkie zapytania operujące na tabeli zamówienie powinny sprawdzać
# obecność flagi "ukryjNiewidoczne"
/^\[/ {
if(byloUzycieZamowienie && !byloUzycieWidoczne)
{
print FILENAME ":" FNR ": brak if:ukryjNiewidoczne"
err = 1
}
byloUzycieZamowienie = 0
byloUzycieWidoczne = 0
}
!/^\[/ && /Zamowienie/ {
byloUzycieZamowienie = 1
}
!/^\[/ && /Prowizja/ {
byloUzycieZamowienie = 0
}
/{if:ukryjNiewidoczne} AND (Z.)?widoczne {endif}/ {
byloUzycieWidoczne = 1
}
/INSERT|UPDATE/ {
byloUzycieWidoczne = 1
}
END {
if(byloUzycieZamowienie && !byloUzycieWidoczne)
{
print FILENAME ":" FNR ": brak if:ukryjNiewidoczne"
err = 1
}
exit err
}
' $SQL || exit 1
######################################################################
DESCR='all modules with frm... are imported in dispatch.py'
F1=/tmp/$USER.1
F2=/tmp/$USER.2
grep -l 'class frm' *.py | sed 's/\.py//' | sort > $F1
sed -n '/from [a-zA-Z_]* import/ {
s/.*from \([a-zA-Z_]*\) import.*/\1/
p
}' dispatch.py | sort > $F2
diff $F1 $F2 || { echo $DESCR; exit 1; }
######################################################################
awk '
# jeśli przy testach wywołujemy mktemp(), to należy plik tymaczsowy
# skasować poprzez os.unlink()
#
/mktemp/ {
byloMktemp = 1
}
/os.unlink/ {
byloMktemp = 0
}
/^[^\t]/ {
if(byloMktemp) {
print FILENAME ":" FNR ": mktemp() without os.unlink()"
byloMktemp = 0
err = 1
}
}
# forsowanie konwencji nazewniczej dla szablonów HTML (nazwy zaczynają
# się od przedrostka "htm"
#
/handleTag\("/ && !/handleTag\("\/?htm/ {
print FILENAME ":" FNR ": handleTag() first arg should have htm prefix"
err = 1
}
# inne konwencje stylistyczne w kodzie źródłowym
#
/def [a-zA-Z_0-9]* \(/ {
print FILENAME ":" FNR ": usun spację pomiędzy "\
"nazwą funkcji a nawiasem"
err = 1
}
/class [a-zA-Z_0-9]* \(/ {
print FILENAME ":" FNR ": usun spację pomiędzy "\
"nazwą klasy a nawiasem"
err = 1
}
# każda formatka musi mieć explicite podany atrybut group określający
# poziom uprawnień potrzebny do jej używania
#
/^class frm/ {
if(byloFrm != "")
{
print FILENAME ":" FNR ": brak group dla " byloFrm
err = 1
}
byloFrm = $0
next
}
/group = / {
byloFrm = ""
next
}
# nazwy testów są unikalne - zabezpieczamy się przed nadpisaniem
# funkcji testującej w tym samym pliku
#
/def test/ {
match($0, /test[A-Za-z0-9_]*/)
sName = substr($0, RSTART, RLENGTH)
if(sName in testNames) {
print FILENAME ":" FNR ": powtórzona nazwa testu: " sName
err = 1
}
testNames[sName] = 1
}
# inne konwencje dotyczące stylu kodu źródłowego
#
/def.*:$/ || /class .*:$/ {
needEmptyLine = 1
next
}
/^[\t ]*$/ {
needEmptyLine = 0
}
needEmptyLine {
print FILENAME ":" FNR ": dodaj pustą linię tu (styl)"
err = 1
needEmptyLine = 0
}
END {
if(byloFrm != "")
{
print FILENAME ":" FNR ": brak group dla " byloFrm
err = 1
}
exit(err)
}
' *.py || exit 1
######################################################################
awk '
# obowiązkowe kodowanie urlencode() dla argumentów przekazanych
# poprzez żądanie HTTP typu GET
#
/={/ && !/={[^:]*:url}/ {
print FILENAME ":" FNR ": w URL musi być modyfikator :url"
err = 1
}
# nowa konwencja podawania nazwy skryptu zastępuje starą - tu
# sprawdzam czy stara konwencja nie występuje
#
/frm\.cgi/ {
print FILENAME ":" FNR ": use {_cgi} instead"
err = 1
}
# proste sprawdzenie na kodzie HTML (tagi tabelek, list, formularzy)
#
function handleTag(sName, bOpen) {
if(bOpen) {
if(openTags[sName]) {
print FILENAME ":" FNR ": double open " sName
err = 1
} else {
openTags[sName] = 1
}
} else {
if(openTags[sName]) {
openTags[sName] = 0
} else {
print FILENAME ":" FNR ": double close " sName
err = 1
}
}
}
function requireOpenTag(sName) {
if(!openTags[sName]) {
print FILENAME ":" FNR ": open of " s Name " missing"
err = 1
}
}
/<table/ { handleTag("table", 1) }
/<tr/ {
handleTag("tr", 1)
requireOpenTag("table")
}
/<td/ {
handleTag("td", 1)
requireOpenTag("tr")
}
/<\/td/ { handleTag("td", 0) }
/<\/tr/ { handleTag("tr", 0) }
/<\/table/ { handleTag("table", 0) }
/<ul/ { handleTag("ul", 1) }
/<li/ { handleTag("li", 1) }
/<\/li/ { handleTag("li", 0) }
/<\/ul/ { handleTag("ul", 0) }
/<th/ {
handleTag("th", 1)
requireOpenTag("tr")
}
/<\/th/ { handleTag("th", 0) }
/<h1/ { handleTag("h1", 1) }
/<\/h1/ { handleTag("h1", 0) }
/<form/ { handleTag("form", 1) }
/<input/ {
requireOpenTag("form")
}
/<\/form/ { handleTag("form", 0) }
/<h2/ { handleTag("h2", 2) }
/<\/h2/ { handleTag("h2", 0) }
# prawidłowe zapisywanie encji w strybutach HTML, tu forsuję
# (częściowo) zgodność z XHTML
#
/\?.*\&[^a][^m]/ {
print FILENAME ":" FNR ": zmień & -> &"
err = 1
}
/<br\/>/ {
print FILENAME ":" FNR ": zmień <br/> -> <br />"
err = 1
}
# strony przeznaczone do drukowania powinny się otwierać w nowym oknie
# przeglądarki
#
/_printable=/ && ! /target="_blank"/ {
print FILENAME ":" FNR ": _printable: dodaj: target=\" blank\""
err = 1
}
# sprawdzam, czy nie pozostawiono otwartych znaczników HTML
#
END {
handleTag("tr", 1)
handleTag("th", 1)
handleTag("td", 1)
handleTag("table", 1)
handleTag("form", 1)
exit(err)
}
' $HTML || exit 1
######################################################################
DESCR='cena, netto, suma, wartosc, rabat* has :money modifier'
# wszystkie zmienne typu wautowego powinny być renderowane wg.
# standardów polskich (z przecinkiem)
# W przypadku plików SQL jest to konwersja standardu polskiego na
# zapis z kropką
#
awk '
/{rabat[A-Za-z0-9_]*[^:}]}/ || \
/{cena[^:}]*}/ || \
/{brutto[^:}]*}/ || \
/{netto[^:}]*}/ || \
/{suma[^:}]*}/ || \
/{wplyw[^:}]*}/ || \
/{wyplyw[^:}]*}/ || \
/{wartosc[a-zA-Z0-9_]*}/ {
print FILENAME ":" FNR ": " $0
err = 1
}
END {
exit(err)
}
' $HTML *.sql || { echo $DESCR; exit 1; }
######################################################################
DESCR='names of frm... names dont match in source and style files'
F1=/tmp/$USER.1
F2=/tmp/$USER.2
sed -n '
/_frm=/{
s/^.*_frm=\([a-zA-Z0-9]*\).*$/\1/
p
}
/name="_frm" value="/{
s/^.*value="\([^"]*\)".*$/\1/
p
}
' $HTML | sort | uniq > $F1
sed -n '
/^class frm/{
s/^.*\(frm[a-zA-Z0-9]\+\)(.*$/\1/
p
}
' *.py | sort | uniq > $F2
diff $F1 $F2 || { echo $DESCR; exit 1; }
######################################################################
DESCR='names of htm... tags dont match in source and style files'
F1=/tmp/$USER.htm
F2=/tmp/$USER.py
sed -n '
/\[htm[A-Z]/{
s!.*\[\(htm[a-zA-Z0-9]*\)\].*!\1!
p
}
/\[\/htm[A-Z]/{
s!.*\[\(.htm[a-zA-Z0-9]*\)\].*!\1!
p
}
' $HTML | sort | uniq > $F1
sed -n '
/"htm[A-Z]/{
s!.*"\(htm[A-Z][a-zA-Z0-9]*\)".*!\1!
p
}
/"\/htm[A-Z]/{
s!.*"\(.htm[A-Z][a-zA-Z0-9]*\)".*!\1!
p
}
' *.py *.cgi | sort | uniq > $F2
diff $F1 $F2 || { echo $DESCR; exit 1; }
######################################################################
DESCR='names of sql tags dont matches in source and sql files'
F1=/tmp/$USER.sql
F2=/tmp/$USER.py
sed -n '
/\[qry[A-Z]/{
s!.*\[\(qry[a-zA-Z0-9_]*\)\].*!\1!
p
}
' *.sql | sort > $F1
sed -n '
/"qry[A-Z][a-z]/{
s!.*"\(qry[a-zA-Z0-9_]*\)".*!\1!
p
}
' *.py *.cgi | sort | uniq > $F2
diff $F1 $F2 || { echo $DESCR; exit 1; }
######################################################################
awk '
BEGIN {
err = 0
}
/--\[/ {
sTag = $0
}
/<form/ {
sid = 0
frm = 0
}
# przekazanie nazwy formatki poprzez HIDDEN
/name="_frm/ {
frm = 1
}
# stary, wycofany sposób przekazywania sesji - tu zgłaszam jako błąd
#
/name="sid"/ {
print FILENAME ":" FNR ": " $0
err = 1
}
# każda formatka powinna mieć naswę jako parametr HIDDEN
#
/<\/form/ {
if(!frm) {
print FILENAME ":" FNR ": brakuje frm w tagu " sTag
err = 1
}
}
# kontrolowanie obecności _frm dla wywołań GET
#
/href="{_cgi}/ || /HREF="{_cgi}/ {
if(/sid={sid:url}/) {
print FILENAME ":" FNR ": usuń sid"
err = 1
}
if(!/_frm=frm/) {
print FILENAME ":" FNR ": brak _frm"
err = 1
}
}
# stylistyka w HTML (nieco zbliża do XHTML)
#
/<[A-Z]/ {
print FILENAME ":" FNR ": html tags should be lower-case"
err = 1
}
/value=.*:url/ || /VALUE=.*:url/ {
print FILENAME ":" FNR ": if value= then should no :url modifier"
err = 1
}
END {
exit(err)
}
' $HTML || exit 1
######################################################################
DESCR='max length of line exceeded'
awk '
{
gsub("\t", " ")
if(length($0) > 80)
{
print FILENAME ":" FNR ": line length exceded"
err = 1
}
}
END {
exit(err)
}
' *.py *.sql $HTML *.sh || { echo $DESCR; exit 1; }
######################################################################
# python 2.3 wymaga zapisania kodowania na początku pliku
#
awk '
FNR == 2 && !/-\*- coding: iso-8859-2 -\*-/ {
print FILENAME ":" FNR ": brak: -*- coding: iso-8859-2 -*-"
err = 1
}
END {
exit err
}
' *.py || exit 1
awk '
FNR == 2 && !/-\*- coding: iso-8859-2 -\*-/ {
print FILENAME ":" FNR ": brak: -*- coding: iso-8859-2 -*-"
err = 1
}
END {
exit err
}
'