#!/usr/bin/env bash # # @license Apache-2.0 # # Copyright (c) 2017 The Stdlib Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # A Git hook called by `git commit`. If this scripts exits with a non-zero status, the commit will be aborted. # # This hook is called with no arguments. # shellcheck disable=SC2181 # VARIABLES # # Determine root directory: root=$(git rev-parse --show-toplevel) # Define the path to a utility for linting filenames: lint_filenames="${root}/lib/node_modules/@stdlib/_tools/lint/filenames/bin/cli" # Define the path to a utility for linting package.json files: lint_package_json="${root}/lib/node_modules/@stdlib/_tools/lint/pkg-json/bin/cli" # Define the path to a utility for linting REPL help files: lint_repl_help="${root}/lib/node_modules/@stdlib/_tools/lint/repl-txt/bin/cli" # Define the path to ESLint configuration file for linting examples: eslint_examples_conf="${root}/etc/eslint/.eslintrc.examples.js" # Define the path to ESLint configuration file for linting tests: eslint_tests_conf="${root}/etc/eslint/.eslintrc.tests.js" # Define the path to ESLint configuration file for linting benchmarks: eslint_benchmarks_conf="${root}/etc/eslint/.eslintrc.benchmarks.js" # FUNCTIONS # # Defines an error handler. # # $1 - error status on_error() { cleanup exit "$1" } # Runs clean-up tasks. cleanup() { echo '' >&2 } # Checks for non-ASCII filenames (to ensure cross platform portability). check_filenames() { local num_files local against local commit commit=$(git rev-parse --verify HEAD) if [[ -z "${commit}" ]]; then # This is the initial commit, so we diff against an empty tree object: against='4b825dc642cb6eb9a060e54bf8d69288fbee4904' else against='HEAD' fi # We exploit the fact that the printable range starts with the space character and ends with the tilde. Note that the use of brackets around a `tr` range is okay here, (for portability to Solaris 10's /usr/bin/tr, it's even required), since the square bracket bytes happen to fall in the designated range. num_files=$(git diff --cached --name-only --diff-filter=A -z "${against}" | LC_ALL=C tr -d '[ -~]\0' | wc -c) if [[ "${num_files}" -ne 0 ]]; then echo 'Error: Attempting to add a non-ASCII filename. Non-ASCII filenames limit cross-platform portability. Please rename offending files before committing.' >&2 return 1 fi return 0 } # Lints staged files. run_lint() { local changed_files local files # Get the set of changed files (added and modified): changed_files=$(git diff --name-only --cached --diff-filter AM) # Lint filenames: echo "${changed_files}" | "${lint_filenames}" if [[ "$?" -ne 0 ]]; then echo '' >&2 echo 'Filename lint errors.' >&2 return 1 fi # Lint Markdown files... files=$(echo "${changed_files}" | grep '\.md$' | tr '\n' ' ') if [[ -n "${files}" ]]; then make FILES="${files}" lint-markdown-files > /dev/null >&2 if [[ "$?" -ne 0 ]]; then echo '' >&2 echo 'Markdown lint errors.' >&2 return 1 fi fi # Lint package.json files... files=$(echo "${changed_files}" | grep 'package\.json$' | grep -v 'datapackage\.json$' ) if [[ -n "${files}" ]]; then echo "${files}" | "${lint_package_json}" >&2 if [[ "$?" -ne 0 ]]; then echo '' >&2 echo 'Package.json lint errors.' >&2 return 1 fi fi # Lint REPL help files... files=$(echo "${changed_files}" | grep 'repl\.txt$' ) if [[ -n "${files}" ]]; then echo "${files}" | "${lint_repl_help}" >&2 if [[ "$?" -ne 0 ]]; then echo '' >&2 echo 'REPL help lint errors.' >&2 return 1 fi fi # Lint JavaScript source files... files=$(echo "${changed_files}" | grep '\.js$' | grep -v -e '/examples' -e '/test' -e '/benchmark' -e '^dist/' | tr '\n' ' ') if [[ -n "${files}" ]]; then make FILES="${files}" lint-javascript-files > /dev/null >&2 if [[ "$?" -ne 0 ]]; then echo '' >&2 echo 'JavaScript lint errors for source files.' >&2 return 1 fi fi # Lint JavaScript command-line interfaces... files=$(echo "${changed_files}" | grep '/bin/cli$' | tr '\n' ' ') if [[ -n "${files}" ]]; then make FILES="${files}" lint-javascript-files > /dev/null >&2 if [[ "$?" -ne 0 ]]; then echo '' >&2 echo 'JavaScript lint errors for command-line interface files.' >&2 return 1 fi fi # Lint JavaScript examples files... files=$(echo "${changed_files}" | grep '/examples/.*\.js$' | tr '\n' ' ') if [[ -n "${files}" ]]; then make JAVASCRIPT_LINTER=eslint ESLINT_CONF="${eslint_examples_conf}" FILES="${files}" lint-javascript-files > /dev/null >&2 if [[ "$?" -ne 0 ]]; then echo '' >&2 echo 'JavaScript lint errors for example files.' >&2 return 1 fi fi # Lint JavaScript test files... files=$(echo "${changed_files}" | grep '/test/.*\.js$' | tr '\n' ' ') if [[ -n "${files}" ]]; then make JAVASCRIPT_LINTER=eslint ESLINT_CONF="${eslint_tests_conf}" FILES="${files}" lint-javascript-files > /dev/null >&2 if [[ "$?" -ne 0 ]]; then echo '' >&2 echo 'JavaScript lint errors for test files.' >&2 return 1 fi fi # Lint JavaScript benchmark files... files=$(echo "${changed_files}" | grep '/benchmark/.*\.js$' | tr '\n' ' ') if [[ -n "${files}" ]]; then make JAVASCRIPT_LINTER=eslint ESLINT_CONF="${eslint_benchmarks_conf}" FILES="${files}" lint-javascript-files > /dev/null >&2 if [[ "$?" -ne 0 ]]; then echo '' >&2 echo 'JavaScript lint errors for benchmark files.' >&2 return 1 fi fi # Lint Python files... files=$(echo "${changed_files}" | grep '\.py$' | tr '\n' ' ') if [[ -n "${files}" ]]; then make check-python-linters > /dev/null >&2 if [[ "$?" -ne 0 ]]; then echo '' >&2 echo 'Unable to lint Python files. Ensure that linters are installed.' >&2 else make FILES="${files}" lint-python-files > /dev/null >&2 if [[ "$?" -ne 0 ]]; then echo '' >&2 echo 'Python lint errors.' >&2 return 1 fi fi fi # Lint R files... files=$(echo "${changed_files}" | grep '\.R$' | tr '\n' ' ') if [[ -n "${files}" ]]; then make FILES="${files}" lint-r-files > /dev/null >&2 if [[ "$?" -ne 0 ]]; then echo '' >&2 echo 'R lint errors.' >&2 return 1 fi fi # Lint shell script files... files=$(echo "${changed_files}" | while read -r file; do head -n1 "$file" | grep -q '^\#\!/usr/bin/env bash' && echo "$file"; done | tr '\n' ' ') if [[ -n "${files}" ]]; then make check-shell-linters > /dev/null >&2 if [[ "$?" -ne 0 ]]; then echo '' >&2 echo 'Unable to lint shell script files. Ensure that linters are installed.' >&2 else make FILES="${files}" lint-shell-files > /dev/null >&2 if [[ "$?" -ne 0 ]]; then echo '' >&2 echo 'Shell script lint errors.' >&2 return 1 fi fi fi # Lint TypeScript declaration files... files=$(echo "${changed_files}" | grep '\.d\.ts$' | tr '\n' ' ') if [[ -n "${files}" ]]; then make TYPESCRIPT_DECLARATIONS_LINTER=dtslint FILES="${files}" lint-typescript-declarations-files > /dev/null >&2 if [[ "$?" -ne 0 ]]; then echo '' >&2 echo 'TypeScript declaration file lint errors.' >&2 return 1 fi fi # Lint license headers... files=$(echo "${changed_files}" | tr '\n' ' ') if [[ -n "${files}" ]]; then make FILES="${files}" lint-license-headers-files > /dev/null >&2 if [[ "$?" -ne 0 ]]; then echo '' >&2 echo 'License header lint errors.' >&2 return 1 fi fi # TODO: if datapackage.json, validate via schema return 0 } # Main execution sequence. main() { check_filenames if [[ "$?" -ne 0 ]]; then on_error 1 fi run_lint if [[ "$?" -ne 0 ]]; then on_error 1 fi cleanup exit 0 } # Run main: main