Skip to content

Instantly share code, notes, and snippets.

@arxanas
Created January 28, 2016 20:22
Show Gist options
  • Save arxanas/f2795068513572430830 to your computer and use it in GitHub Desktop.
Save arxanas/f2795068513572430830 to your computer and use it in GitHub Desktop.
inclint
#!/bin/bash
# Linter for C++ imports (`using`s).
#
# Features:
# * Ensures that you don't use `std::foo` directly in your code, but rather
# mandates that you do `using std::foo` elsewhere.
# * Ensures that for every `using std::foo`, `foo` is used somewhere in your
# code. This is the killer feature because it helps you keep your `using`s
# in-sync with your actual code.
# * Ensures that includes are in alphabetical order.
# * Ensures that your header files don't use `using` statements.
#
# Bugs:
# * Is written in bash.
# * Nothing else. There are no possible bugs arising from parsing C++ with
# grep.
#
# Usage:
# * inclint foo.cpp bar.h
# * inclint # runs recursively on all source files found in the current dir
set -euo pipefail
IFS=$'\n\t'
readonly ID_REGEX="[a-zA-Z_][a-zA-Z_0-9]*"
failed='false'
check_includes_alphabetical_order() {
local file="$1"
compare_includes() {
local expected="$1"
local actual="$2"
if ! diff -q <(echo "$actual") <(echo "$expected") >/dev/null; then
echo "$file: includes in wrong order; should be"
echo '~~~'
diff -y <(echo "$actual") <(echo "$expected") || true
echo '~~~'
failed=true
fi
}
local quote_includes
local sorted_quote_includes
quote_includes=$(grep -E '#include \"' "$file" || true)
sorted_quote_includes=$(sort <<<"$quote_includes")
compare_includes "$sorted_quote_includes" "$quote_includes"
local angle_includes
local sorted_angle_includes
angle_includes=$(grep -E '#include <' "$file" || true)
sorted_angle_includes=$(sort <<<"$angle_includes")
compare_includes "$sorted_angle_includes" "$angle_includes"
}
check_no_unused_using_statements() {
local file="$1"
local using_statements
using_statements=$(grep -E "^using std::$ID_REGEX" "$file" || true)
local symbols
symbols=$(cut -d':' -f3 <<<"$using_statements" | sed 's/;//g')
_filter_comments() {
grep -vE '^(\s*//|#)'
}
local i
for i in $symbols; do
if ! _filter_comments <"$file" | grep -qE "[^:]$i"; then
echo "$file: unused symbol $i"
failed='true'
fi
done
}
check_no_std_members() {
local file="$1"
local std_members
std_members=$( (
grep "std::$ID_REGEX" |
grep -v using |
sed "s/.*\(std::$ID_REGEX\).*/\1/g"
) || true)
local i
for i in $std_members; do
i=$(sed 's/^[ \t]*//g' <<<"$i")
echo "$file: don't write '$i' inline in your code, use 'using $i' instead";
failed='true'
done
}
check_doesnt_use_using_statements() {
local file="$i"
if grep -qE '^\s*using'; then
echo "$file: don't use using-statements in header files"
failed='true'
fi
}
preprocess() {
local file="$1"
# http://stackoverflow.com/a/2394040/344643
g++ -fpreprocessed -dD -E "$file"
}
main() {
local files
if [[ "$#" -ge 1 ]]; then
files="$*"
else
files=$(find -E . -type f -iregex '.+\.(c|cc|cpp|h|hpp)')
fi
local i
for i in $files; do
check_includes_alphabetical_order "$i" <"$i"
if [[ "$i" = *.cpp ]]; then
preprocess "$i" | check_no_unused_using_statements "$i"
preprocess "$i" | check_no_std_members "$i"
else
preprocess "$i" | check_doesnt_use_using_statements "$i"
fi
done
if [[ "$failed" == 'true' ]]; then
exit 1
else
exit 0
fi
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment