github.com/weaveworks/common@v0.0.0-20230728070032-dd9e68f319d5/tools/lint (about)

     1  #!/bin/bash
     2  # This scipt lints files for common errors.
     3  #
     4  # For go files, it runs gofmt and go vet, and optionally golint and
     5  # gocyclo, if they are installed.
     6  #
     7  # For shell files, it runs shfmt. If you don't have that installed, you can get
     8  # it with:
     9  #   curl -fsSLo shfmt https://github.com/mvdan/sh/releases/download/v1.3.0/shfmt_v1.3.0_linux_amd64
    10  #   chmod +x shfmt
    11  #
    12  # With no arguments, it lints the current files staged
    13  # for git commit.  Or you can pass it explicit filenames
    14  # (or directories) and it will lint them.
    15  #
    16  # To use this script automatically, run:
    17  #   ln -s ../../bin/lint .git/hooks/pre-commit
    18  
    19  set -e
    20  
    21  LINT_IGNORE_FILE=${LINT_IGNORE_FILE:-".lintignore"}
    22  
    23  IGNORE_LINT_COMMENT=
    24  IGNORE_SPELLINGS=
    25  PARALLEL=
    26  while true; do
    27      case "$1" in
    28          -nocomment)
    29              IGNORE_LINT_COMMENT=1
    30              shift 1
    31              ;;
    32          -notestpackage)
    33              # NOOP, still accepted for backwards compatibility
    34              shift 1
    35              ;;
    36          -ignorespelling)
    37              IGNORE_SPELLINGS="$2,$IGNORE_SPELLINGS"
    38              shift 2
    39              ;;
    40          -p)
    41              PARALLEL=1
    42              shift 1
    43              ;;
    44          *)
    45              break
    46              ;;
    47      esac
    48  done
    49  
    50  spell_check() {
    51      local filename="$1"
    52      local lint_result=0
    53  
    54      # misspell is completely optional.  If you don't like it
    55      # don't have it installed.
    56      if ! type misspell >/dev/null 2>&1; then
    57          return $lint_result
    58      fi
    59  
    60      if ! misspell -error -i "$IGNORE_SPELLINGS" "${filename}"; then
    61          lint_result=1
    62      fi
    63  
    64      return $lint_result
    65  }
    66  
    67  lint_go() {
    68      # This function is called on a whole directory containing Go files
    69      local filename="$1"
    70      local lint_result=0
    71  
    72      if [ -n "$(gofmt -s -l "${filename}")" ]; then
    73          lint_result=1
    74          echo "${filename}: run gofmt -s -w ${filename}"
    75      fi
    76  
    77      go vet "${filename}" || lint_result=$?
    78  
    79      # golint is completely optional.  If you don't like it
    80      # don't have it installed.
    81      if type golint >/dev/null 2>&1; then
    82          # golint doesn't set an exit code it seems
    83          if [ -z "$IGNORE_LINT_COMMENT" ]; then
    84              lintoutput=$(golint "${filename}")
    85          else
    86              lintoutput=$(golint "${filename}" | grep -vE 'comment|dot imports|ALL_CAPS')
    87          fi
    88          if [ -n "$lintoutput" ]; then
    89              lint_result=1
    90              echo "$lintoutput"
    91          fi
    92      fi
    93  
    94      # gocyclo is completely optional.  If you don't like it
    95      # don't have it installed.  Also never blocks a commit,
    96      # it just warns.
    97      if type gocyclo >/dev/null 2>&1; then
    98          gocyclo -over 25 "${filename}" | while read -r line; do
    99              echo "${filename}": higher than 25 cyclomatic complexity - "${line}"
   100          done
   101      fi
   102  
   103      return $lint_result
   104  }
   105  
   106  lint_sh() {
   107      local filename="$1"
   108      local lint_result=0
   109  
   110      # Skip shfmt validation, if not installed
   111      if type shfmt >/dev/null 2>&1; then
   112          if ! diff -u "${filename}" <(shfmt -i 4 "${filename}"); then
   113              lint_result=1
   114              echo "${filename}: run shfmt -i 4 -w ${filename}"
   115          fi
   116      fi
   117  
   118      # the shellcheck is completely optional.  If you don't like it
   119      # don't have it installed.
   120      if type shellcheck >/dev/null 2>&1; then
   121          shellcheck "${filename}" || lint_result=1
   122      fi
   123  
   124      return $lint_result
   125  }
   126  
   127  lint_tf() {
   128      local filename="$1"
   129      local lint_result=0
   130  
   131      if ! diff -u <(hclfmt "${filename}") "${filename}"; then
   132          lint_result=1
   133          echo "${filename}: run hclfmt -w ${filename}"
   134      fi
   135  
   136      return $lint_result
   137  }
   138  
   139  lint_md() {
   140      local filename="$1"
   141      local lint_result=0
   142  
   143      for i in '=======' '>>>>>>>'; do
   144          if grep -q "${i}" "${filename}"; then
   145              lint_result=1
   146              echo "${filename}: bad merge/rebase!"
   147          fi
   148      done
   149  
   150      return $lint_result
   151  }
   152  
   153  lint_py() {
   154      local filename="$1"
   155      local lint_result=0
   156  
   157      if yapf --diff "${filename}" | grep -qE '^[+-]'; then
   158          lint_result=1
   159          echo "${filename} needs reformatting. Run: yapf --in-place ${filename}"
   160      else
   161          # Only run flake8 if yapf passes, since they pick up a lot of similar issues
   162          flake8 "${filename}" || lint_result=1
   163      fi
   164  
   165      return $lint_result
   166  }
   167  
   168  lint() {
   169      filename="$1"
   170      ext="${filename##*\.}"
   171      local lint_result=0
   172  
   173      # Don't lint deleted files
   174      if [ ! -f "$filename" ]; then
   175          return
   176      fi
   177  
   178      # Don't lint specific files
   179      case "$(basename "${filename}")" in
   180          static.go) return ;;
   181          coverage.html) return ;;
   182          *.pb.go) return ;;
   183      esac
   184  
   185      mimetype=$(file --mime-type "${filename}" | awk '{print $2}')
   186  
   187      case "$mimetype.$ext" in
   188          text/x-shellscript.*) lint_sh "${filename}" || lint_result=1 ;;
   189          *.go) ;; # done at directory level
   190          *.tf) lint_tf "${filename}" || lint_result=1 ;;
   191          *.md) lint_md "${filename}" || lint_result=1 ;;
   192          *.py) lint_py "${filename}" || lint_result=1 ;;
   193      esac
   194  
   195      # we don't want to spell check tar balls, binaries, Makefile and json files
   196      case "$mimetype.$ext" in
   197          *.tar | *.gz | *.json) ;;
   198          *.req | *.key | *.pem | *.crt) ;;
   199          application/x-executable.*) ;;
   200          text/x-makefile.*) ;;
   201          *) spell_check "${filename}" || lint_result=1 ;;
   202      esac
   203  
   204      return $lint_result
   205  }
   206  
   207  lint_files() {
   208      local lint_result=0
   209      while read -r filename; do
   210          lint "${filename}" || lint_result=1
   211      done
   212      return $lint_result
   213  }
   214  
   215  matches_any() {
   216      local filename="$1"
   217      local patterns="$2"
   218      while read -r pattern; do
   219          # shellcheck disable=SC2053
   220          # Use the [[ operator without quotes on $pattern
   221          # in order to "glob" the provided filename:
   222          [[ "$filename" == $pattern ]] && return 0
   223      done <<<"$patterns"
   224      return 1
   225  }
   226  
   227  filter_out() {
   228      local patterns_file="$1"
   229      if [ -n "$patterns_file" ] && [ -r "$patterns_file" ]; then
   230          local patterns
   231          patterns=$(sed '/^#.*$/d ; /^\s*$/d' "$patterns_file") # Remove blank lines and comments before we start iterating.
   232          [ -n "$DEBUG" ] && echo >&2 "> Filters:" && echo >&2 "$patterns"
   233          local filtered_out=()
   234          while read -r filename; do
   235              matches_any "$filename" "$patterns" && filtered_out+=("$filename") || echo "$filename"
   236          done
   237          [ -n "$DEBUG" ] && echo >&2 "> Files filtered out (i.e. NOT linted):" && printf >&2 '%s\n' "${filtered_out[@]}"
   238      else
   239          cat # No patterns provided: simply propagate stdin to stdout.
   240      fi
   241  }
   242  
   243  lint_directory() {
   244      local dirname="$1"
   245      local lint_result=0
   246      # This test is just checking if there are any Go files in the directory
   247      if compgen -G "$dirname/*.go" >/dev/null; then
   248          lint_go "${dirname}" || lint_result=1
   249      fi
   250      ls $dirname/* | filter_out "$LINT_IGNORE_FILE" | lint_files
   251      return $lint_result
   252  }
   253  
   254  lint_directories() {
   255      local lint_result=0
   256      while read -r dirname; do
   257          lint_directory "${dirname}" || lint_result=1
   258      done
   259      exit $lint_result
   260  }
   261  
   262  list_directories() {
   263      if [ $# -gt 0 ]; then
   264          find "$@" \( -name vendor -o -name .git -o -name .cache -o -name .pkg \) -prune -o -type d
   265      fi
   266  }
   267  
   268  if [ $# = 1 ] && [ -f "$1" ]; then
   269      lint "$1"
   270  else
   271      list_directories "$@" | lint_directories
   272  fi