#! /bin/sh
# -*- coding: utf-8 -*-
# Copyright (C) 2009-2017 Laboratoire de Recherche et Développement de
# l'Epita (LRDE).
# Copyright (C) 2004, 2005 Laboratoire d'Informatique de Paris 6
# (LIP6), département Systèmes Répartis Coopératifs (SRC), Université
# Pierre et Marie Curie.
#
# This file is part of Spot, a model checking library.
#
# Spot 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 3 of the License, or
# (at your option) any later version.
#
# Spot 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 .
# Ensure consistent style by catching common improper constructs.
set -e
set +x
diag()
{
fail=:
echo "$file:" "$@"
echo ============================================================
}
rm -f failures.style
GREP=grep
# Get some help from GNU grep.
if (grep --color=auto -n --version)>/dev/null 2>&1; then
GREP="$GREP --color=auto -n"
GREP_COLOR='1;31'
export GREP_COLOR
fi
# Reset the locale, so for instance we don't get bugs because sed is
# expecting utf-8 and we feed it latin-1. The C locale should be OK,
# because we do not treat extended characters specifically in the
# following style rules.
LC_ALL=C
export LC_ALL
mkdir -p style.dir
tmp=style.dir/incltest.tmp.$$
# We used to loop over more directories before the source tree was
# rearranged. So there is only one left today, but we keep the loop
# in case we want to add more in the future.
TOP=${srcdir-.}/../..
for dir in "$TOP/spot" "$TOP/bin" "$TOP/tests"; do
find "$dir" \( -name "${1-*}.hh" \
-o -name "${1-*}.hxx" \
-o -name "${1-*}.cc" \
-o -name "${1-*}.test" \) \
-a -not -path '*.dir/*' \
-a -type f -a -print |
while read file; do
if $GREP 'GNU Bison' "$file" >/dev/null ||
$GREP 'generated by flex' "$file" >/dev/null ; then
continue
fi
# Skip the files used by sanity.
case $file in
*incltest.cc) continue;;
esac
fail=false
# Check this before stripping comments and strings.
$GREP -i 'accepting cond' $file && diag 'accepting -> acceptance'
$GREP -i 'dictionnar[yi]' $file && diag 'dictionnary -> dictionary'
# "an uninstalled" seems to be the exception so far, but we want
# "a unique", "a universal", etc.
$GREP -i 'an uni[^n]' $file && diag 'an uni... -> a uni...'
$GREP -i 'version 2 of the License' $file &&
diag 'license text should refer to version 2'
$GREP -i 'Temple Place' $file &&
diag 'license text should give a url instead of an address'
$GREP -q 'coding: utf-8' $file ||
diag 'missing -*- coding: utf-8 -*-'
$GREP Copyright $file >/dev/null ||
diag "missing copyright"
# If some grep implementation ignores LC_ALL=C, this rule might be
# a problem on utf-8 characters such as "δ" which really
# corresponds to multiple bytes, but might be matched as a single
# character by grep.
$GREP '<< *"\([^\\]\|\\.\)"' $file &&
diag "Use << 'c' instead" 'of << "c".'
# A doxygen comments such as
#
# | \brief foo
# | \ingroup bar
# |
# | baz
#
# will be output as "foobaz." But if the first two lines
# are reversed, it's output correctly.
perl -ne '/(.*\\brief.*\n.*\\ingroup.*)/ && print("$1\n") && exit(1)' \
-0777 $file || diag "always put 'ingroup' before 'brief'"
# Check this before we remove SPOT_API from the input.
case $file in
*.cc)
$GREP 'SPOT_API' $file &&
diag 'use SPOT_API only in header files';;
esac
# Strip comments and strings.
#
# Multi-line comments of the form
# /* Line 1
# Line 2
# Line 3 */
# are replaced by
# //
# //
# //
# to keep the line numbers correct in the diagnostics.
#
# Also get rid of the SPOT_API tags.
perl -pe 'sub f {my $a = shift; $a =~ s:[^\n]*://:g; return "$a"}
s,/\*(.*?)\*/,f($1),sge;
s,//.*?\n,//\n,g;
s,(?$tmp
$GREP '[ ]$' $tmp &&
diag 'Trailing whitespace.'
case $file in
*.test);;
*)
$GREP -E '(>[^>]*|^[^<]*)class[ \t]+[A-Z]' $tmp &&
diag 'Use lower case class names.'
$GREP '[ ]if(' $tmp &&
diag 'Missing space after "if"'
$GREP '[ ]if (.*).*{' $tmp &&
diag 'Opening { should be on its own line.'
$GREP '[ ]if (.*).*;' $tmp &&
diag 'if body should be on another line.'
$GREP '[ ]else.*;' $tmp &&
diag 'else body should be on another line.'
$GREP '[ ]while(' $tmp &&
diag 'Missing space after "while"'
$GREP '[ ]while (.*).*{' $tmp &&
diag 'Opening { should be on its own line.'
$GREP '[ ]while (.*).*[^)];' $tmp &&
diag 'while body should be on another line.'
$GREP '[ ]for(' $tmp &&
diag 'Missing space after "for"'
$GREP '[ ]for (.*).*{' $tmp &&
diag 'Opening { should be on its own line.'
$GREP '[ ]for (.*;.*;.*).*;' $tmp &&
diag 'for body should be on another line.'
$GREP '[ ]switch(' $tmp &&
diag 'Missing space after "switch"'
$GREP '[ ]switch (.*).*{' $tmp &&
diag 'Opening { should be on its own line.'
$GREP 'namespace .*{' $tmp &&
diag 'Opening { should be on its own line.'
$GREP 'class .*{' $tmp | $GREP -v enum &&
diag 'Opening { should be on its own line.'
$GREP '( ' $tmp &&
diag 'No space after opening (.'
$GREP ' )' $tmp &&
diag 'No space before closing ).'
$GREP '! ' $tmp &&
diag 'No space after unary operators (!).'
$GREP ",[^ \" %'\\\\]" $tmp &&
diag 'Space after coma.'
# The 'r' allows operator&&
# The '.' allows &&...
$GREP '[^ r]&&[^ .]' $tmp &&
diag 'Space around binary operators.'
# The 'r' allows operator||
$GREP '[^ r]||[^ ]' $tmp &&
diag 'Space around binary operators.'
# The 'r' allows operator==
$GREP '[^ r<>][!<>=]=[^ ]' $tmp &&
diag 'Space around binary operators.'
# The 'r' allows operator<<=
$GREP '[^ r][<>][<>]=[^ ]' $tmp &&
diag 'Space around binary operators.'
$GREP 'operator[^a-zA-Z0-9_(]*[ ][^a-zA-Z0-9_(]*(' $tmp &&
diag 'Write operatorXX(...) without spaces around XX.'
$GREP 'operator[^(]* (' $tmp &&
diag 'No space before ('
$GREP '[ ]default:[^:].*;' $tmp &&
diag 'Label should be on their own line.'
$GREP '[ ]case.*:[^:].*;' $tmp &&
diag 'Label should be on their own line.'
$GREP '[ ];' $tmp &&
diag 'No space before semicolon.'
$GREP -v 'for (.*;;)' $tmp | $GREP ';[^ ")'"']" &&
diag 'Must have space or newline after semicolon.'
# Allow several { or } on the same line only if they are mixed
# with parentheses, as this often occur with lambdas or
# initializer lists. What we want to forbid is cases where
# multiple scopes are opened/closed on the same line.
$GREP '^[^()]*}[^()]*}[^()]*$' $tmp &&
diag 'No two } on the same line.'
$GREP '^[^()]{[^()]*{[^()]$' $tmp &&
diag 'No two { on the same line.'
$GREP 'delete[ ]*[(][^(]*[)];' $tmp |
$GREP -v 'operator[ ]*delete[ ]*[(][^(]*[)];' &&
diag 'No useless parentheses after delete.'
$GREP 'return[ ]*[(][^(]*[)];' $tmp &&
diag 'No useless parentheses after return.'
$GREP 'NULL' $tmp &&
diag 'Use nullptr instead of NULL.'
# std::list::size() can be O(n). Better use empty() whenever
# possible, even for other containers.
e$GREP '(->|[.])size\(\) [=!]= 0|![a-zA-Z0-9_]*(->|[.])size\(\)|(if |while |assert)\([a-zA-Z0-9_]*(->|[.])size\(\)\)' $tmp &&
diag 'Prefer empty() to check emptiness.'
e$GREP 'assert\((0|!".*")\)' $tmp &&
diag 'Prefer SPOT_UNREACHABLE or SPOT_UNIMPLEMENTED.'
e$GREP '^[^=*<]*([+][+]|--);' $tmp &&
diag 'Take good habits: use ++i instead of i++ when you have the choice.'
$GREP '[^a-zA-Z0-9_](\*[a-zA-Z0-9_]*)\.' $tmp &&
diag 'Use "x->y", not "(*x).y"'
# we allow these functions only in ?...:...
e$GREP 'bdd_(false|true)[ ]*\(' $tmp | $GREP -v '[?:]' &&
diag 'Use bddfalse and bddtrue instead of bdd_false() and bdd_true()'
res=`perl -ne '$/ = undef;
print "$&\n"
while /if \((.*)(\s*==\s*0)?\)\s*delete(\[\])?\s+\1;(?!\s+else)/g' $tmp`
if test -n "$res"; then
echo "$res"
diag 'No "if (x)" required before "delete x;".'
fi
case $file in
*.hh | *.hxx)
if e$GREP '(<<|>>)' $tmp >/dev/null; then
:
else
$GREP '#.*include.*' $tmp &&
diag 'Avoid in headers, better use .'
fi
$GREP '^[ ]* ' $tmp &&
diag 'Use spaces instead of tabs.'
# Headers from spot/priv/ are not installed, so may only be
# included from *.cc files or from other spot/priv/ headers
# (in the latter case they do not have to specify the priv/
# directory, so they will not match this regex).
case $file in
*/priv/*|*/bin/*);;
*)
$GREP '#.*include.*priv/' $tmp &&
diag 'Do not include private headers in public headers.'
$GREP '^[^#]*[^#_]assert[ ]*(.*)' $tmp &&
diag 'Use SPOT_ASSERT() instead of assert() in public headers.'
;;
esac
;;
*.cc)
if $GREP 'namespace$' $tmp >/dev/null; then
:
else
# We only check classes, but the rule should apply to functions too
$GREP '^[ ]*class[ ]' $tmp &&
diag 'Private definitions must be in anonymous namespace.'
fi
e$GREP ' ' $tmp &&
diag 'Use spaces instead of tabs.'
;;
esac
case $file in
*/bin/*|*.cc);;
*)
$GREP -v '#.*include.*priv/' $tmp | $GREP '#.*include.*"' &&
diag 'Use instead of "header" for public headers.'
$GREP '#.*include.*' $tmp &&
diag 'Use "spot/priv/..." instead of ".'
;;
esac
;;
esac
$fail && echo "$file" >>failures.style
done || : # Make sure sh does not abort when read exits with false.
done
# Rules for Makefiles.
for dir in "${INCDIR-..}" "${INCDIR-..}/../bin" "${INCDIR-..}/../tests"; do
find "$dir" -name "Makefile.am" -a -type f -a -print |
while read file; do
fail=false
# Strip comments.
sed 's,#.*,,' < $file > $tmp
$GREP '[ ]$' $tmp &&
diag 'Trailing whitespace.'
$GREP '\.libs/' $tmp &&
diag "Don't reference files in .libs/, use Libtool instead."
$fail && echo "$file" >>failures.style
done || : # Make sure sh does not abort when read exits with false.
done
if test -f failures.style; then
echo "The following files contain style errors:"
cat failures.style
rm failures.style
exit 1;
fi
exit 0