summaryrefslogtreecommitdiff
path: root/build-aux/lint-src
blob: 033340da1fc68dc0ef4de109b5d1621ba8eda6d8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#!/usr/bin/env bash
# build-aux/lint-src - Lint checks for source files
#
# Copyright (C) 2024-2025  Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later

RED=$(tput setaf 1)
RESET=$(tput sgr0)

err() {
	printf "${RED}%s${RESET}: %s\n" "$1" "$2" >&2
	r=1
}

get-dscname() {
	if [[ $1 == */Documentation/* ]] && [[ "$(sed 1q -- "$1")" == 'NAME' ]]; then
		sed -n '
			2{
				s,[/.],_,g;
				s,^\s*_,Documentation/,;
				s,$,.txt,;

				p;
				q;
			}
			' -- "$1"
	else
		sed -n '
			1,3{
				/^\#!/d;
				/^<!--$/d;
				/-\*- .* -\*-/d;
				s,[/*\# ]*,,;
				s/ - .*//;

				p;
				q;
			}
			' -- "$1"
	fi
}

{
	filetype=$1
	filenames=("${@:2}")

	r=0
	for filename in "${filenames[@]}"; do
		# File header ##########################################################

		shebang="$(sed -n '1{/^#!/p;}' "$filename")"
		if [[ -x $filename && (-z $shebang || $shebang == '#!/hint/'*) ]]; then
			err "$filename" 'is executable but does not have a shebang'
		elif [[ (-n $shebang && $shebang != '#!/hint/'*) && ! -x $filename ]]; then
			err "$filename" 'has a shebang but is not executable'
		fi
		case "$shebang" in
			'') : ;;
			'#!/bin/sh') : ;;
			'#!/usr/bin/env bash') : ;;
			'#!/usr/bin/env python3') : ;;
			*) err "$filename" 'has an unrecognized shebang' ;;
		esac
		if [[ -n $shebang && $shebang != */"$filetype" && $shebang != *' '"$filetype" ]]; then
			err "$filename" "wrong shebang for $filetype"
		fi

		if ! grep -E -q 'Copyright \(C\) 202[4-9]((-|, )202[5-9])*  Luke T. Shumaker' "$filename"; then
			err "$filename" 'is missing a copyright statement'
		fi
		if test -e .git && ! git diff --quiet milestone/2025-01-01 HEAD -- "$filename"; then
			if ! grep -E -q 'Copyright \(C\) .*2025  Luke T. Shumaker' "$filename"; then
				err "$filename" 'has an outdated copyright statement'
			fi
		fi
		if ! grep -q '\sSPDX-License-Identifier[:] ' "$filename"; then
			err "$filename" 'is missing an SPDX-License-Identifier'
		fi

		dscname_act=$(get-dscname "$filename")
		dscname_exp=$(echo "$filename" | sed \
			-e 's,.*/config/,,' \
			-e 's,.*/config\.h$,config.h,' \
			-e 's,.*include/,,' \
			-e 's,.*static/,,' \
			-e 's/\.wip$//')
		if [ "$dscname_act" != "$dscname_exp" ] && [ "cmd/$dscname_act" != "$dscname_exp" ]; then
			err "$filename" "self-identifies as $dscname_act (expected $dscname_exp)"
		fi

		# File body ############################################################

		if grep -n --color=auto "$(printf '\\S\t')" "$filename"; then
			err "$filename" 'uses tabs for alignment'
		fi
	done
	case "$filetype" in
		unknown)
			for filename in "${filenames[@]}"; do
				err "$filename" 'cannot lint unknown file type'
			done
			;;
		c)
			for filename in "${filenames[@]}"; do
				if [[ $filename == *.h ]]; then
					dscname=$(get-dscname "$filename")
					guard=${dscname//'/'/'_'}
					guard=${guard//'.'/'_'}
					guard="_${guard^^}_"
					if ! { grep -Fxq "#ifndef ${guard}" "$filename" &&
						grep -Fxq "#define ${guard}" "$filename" &&
						grep -Fxq "#endif /* ${guard} */" "$filename"; }; then
						err "$filename" "does not have ${guard} guard"
					fi
				fi
			done
			;;
		sh | bash)
			shellcheck "${filenames[@]}" || exit $?
			shfmt --diff --case-indent --simplify "${filenames[@]}" || exit $?
			;;
		python3)
			./build-aux/venv/bin/mypy --strict --scripts-are-modules "${filenames[@]}" || exit $?
			./build-aux/venv/bin/black --check "${filenames[@]}" || exit $?
			./build-aux/venv/bin/isort --check "${filenames[@]}" || exit $?
			./build-aux/venv/bin/pylint "${filenames[@]}" || exit $?
			if grep -nh 'SPECIAL$$' -- lib9p/core.gen lib9p/core_gen/*.py; then exit 1; fi
			testfiles=()
			for filename in "${filenames[@]}"; do
				if [[ ${filename##*/} == test_*.py ]]; then
					testfiles+=("$filename")
				fi
			done
			./build-aux/venv/bin/pytest "${testfiles[@]}" || exit $?
			;;
		make | cmake | gitignore | ini | 9p-idl | 9p-log | markdown | pip | man-cat)
			# TODO: Write/adopt linters for these file types
			:
			;;
		*)
			err "$0" "unknown filetype: ${filetype}"
			;;
	esac
	exit $r
}