commit-code-check.sh

Same filename in other branches
  1. 8.9.x core/scripts/dev/commit-code-check.sh
  2. 10 core/scripts/dev/commit-code-check.sh
  3. 11.x core/scripts/dev/commit-code-check.sh
#!/bin/bash
#
# This script performs code quality checks.
#
# @internal
#   This script is not covered by Drupal core's backwards compatibility promise.
#   It exists only for core development purposes.
#
# The script makes the following checks:
# - Spell checking.
# - File modes.
# - No changes to core/node_modules directory.
# - PHPCS checks PHP and YAML files.
# - ESLint checks JavaScript and YAML files.
# - Checks .es6.js and .js files are equivalent.
# - Stylelint checks CSS files.
# - Checks .pcss.css and .css files are equivalent.

# cSpell:disable

# Searches an array.
contains_element() {
  local e
  for e in ${@:2}; do [[ "$e" == "$1" ]] && return 0; done
  return 1
}

CACHED=0
DRUPALCI=0
BRANCH=""
while test $# -gt 0; do
  case "$1" in
    -h|--help)
      echo "Drupal code quality checks"
      echo " "
      echo "options:"
      echo "-h, --help                show brief help"
      echo "--branch BRANCH           creates list of files to check by comparing against a branch"
      echo "--cached                  checks staged files"
      echo "--drupalci                a special mode for DrupalCI"
      echo " "
      echo "Example usage: sh ./core/scripts/dev/commit-code-check.sh --branch 9.2.x"
      exit 0
      ;;
    --branch)
      BRANCH="$2"
      if [[ "$BRANCH" == "" ]]; then
        printf "The --branch option requires a value. For example: --branch 9.2.x\n"
        exit;
      fi
      shift 2
      ;;
    --cached)
      CACHED=1
      shift
      ;;
    --drupalci)
      DRUPALCI=1
      shift
      ;;
    *)
      break
      ;;
  esac
done

# Set up variables to make colored output simple. Color output is disabled on
# DrupalCI because it is breaks reporting.
# @todo https://www.drupal.org/project/drupalci_testbot/issues/3181869
if [[ "$DRUPALCI" == "1" ]]; then
  red=""
  green=""
  reset=""
  DRUPAL_VERSION=$(php -r "include 'vendor/autoload.php'; print preg_replace('#\.[0-9]+-dev#', '.x', \Drupal::VERSION);")
  GIT="sudo -u www-data git"
else
  red=$(tput setaf 1 && tput bold)
  green=$(tput setaf 2)
  reset=$(tput sgr0)
  GIT="git"
fi

# Gets list of files to check.
if [[ "$BRANCH" != "" ]]; then
  FILES=$($GIT diff --name-only $BRANCH HEAD);
elif [[ "$CACHED" == "0" ]]; then
  # For DrupalCI patch testing or when running without --cached or --branch,
  # list of all changes in the working directory.
  FILES=$($GIT ls-files --other --modified --exclude-standard --exclude=vendor)
else
  # Check staged files only.
  if $GIT rev-parse --verify HEAD >/dev/null 2>&1
  then
    AGAINST=HEAD
  else
    # Initial commit: diff against an empty tree object
    AGAINST=4b825dc642cb6eb9a060e54bf8d69288fbee4904
  fi
  FILES=$($GIT diff --cached --name-only $AGAINST);
fi

if [[ "$FILES" == "" ]] && [[ "$DRUPALCI" == "1" ]]; then
  # If the FILES is empty we might be testing a merge request on DrupalCI. We
  # need to diff against the Drupal branch or tag related to the Drupal version.
  printf "Creating list of files to check by comparing branch to %s\n" "$DRUPAL_VERSION"
  # On DrupalCI there's a merge commit so we can compare to HEAD~1.
  FILES=$($GIT diff --name-only HEAD~1 HEAD);
fi

TOP_LEVEL=$($GIT rev-parse --show-toplevel)

# This variable will be set to one when the file core/phpcs.xml.dist is changed.
PHPCS_XML_DIST_FILE_CHANGED=0

# This variable will be set to one when one of the eslint config file is
# changed:
#  - core/.eslintrc.passing.json
#  - core/.eslintrc.json
#  - core/.eslintrc.jquery.json
ESLINT_CONFIG_PASSING_FILE_CHANGED=0

# This variable will be set to one when the stylelint config file is changed.
# changed:
#  - core/.stylelintignore
#  - core/.stylelintrc.json
STYLELINT_CONFIG_FILE_CHANGED=0

# This variable will be set to one when JavaScript packages files are changed.
# changed:
#  - core/package.json
#  - core/yarn.lock
JAVASCRIPT_PACKAGES_CHANGED=0

# This variable will be set when a Drupal-specific CKEditor 5 plugin has changed
# it is used to make sure the compiled JS is valid.
CKEDITOR5_PLUGINS_CHANGED=0

# Build up a list of absolute file names.
ABS_FILES=
for FILE in $FILES; do
  ABS_FILES="$ABS_FILES $TOP_LEVEL/$FILE"

  if [[ $FILE == "core/phpcs.xml.dist" ]]; then
    PHPCS_XML_DIST_FILE_CHANGED=1;
  fi;

  if [[ $FILE == "core/.eslintrc.json" || $FILE == "core/.eslintrc.passing.json" || $FILE == "core/.eslintrc.jquery.json" ]]; then
    ESLINT_CONFIG_PASSING_FILE_CHANGED=1;
  fi;

  if [[ $FILE == "core/.stylelintignore" || $FILE == "core/.stylelintrc.json" ]]; then
    STYLELINT_CONFIG_FILE_CHANGED=1;
  fi;

  # If JavaScript packages change, then rerun all JavaScript style checks.
  if [[ $FILE == "core/package.json" || $FILE == "core/yarn.lock" ]]; then
    ESLINT_CONFIG_PASSING_FILE_CHANGED=1;
    STYLELINT_CONFIG_FILE_CHANGED=1;
    JAVASCRIPT_PACKAGES_CHANGED=1;
  fi;

  if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.js$ ]] && [[ $FILE =~ ^core/modules/ckeditor5/js/build || $FILE =~ ^core/modules/ckeditor5/js/ckeditor5_plugins ]]; then
    CKEDITOR5_PLUGINS_CHANGED=1;
  fi;
done

# Exit early if there are no files.
if [[ "$ABS_FILES" == "" ]]; then
  printf "There are no files to check. If you have staged a commit use the --cached option.\n"
  exit;
fi;

# This script assumes that composer install and yarn install have already been
# run and all dependencies are updated.
FINAL_STATUS=0

DEPENDENCIES_NEED_INSTALLING=0
# Ensure PHP development dependencies are installed.
# @todo https://github.com/composer/composer/issues/4497 Improve this to
#  determine if dependencies in the lock file match the installed versions.
#  Using composer install --dry-run is not valid because it would depend on
#  user-facing strings in Composer.
if ! [[ -f 'vendor/bin/phpcs' ]]; then
  printf "Drupal's PHP development dependencies are not installed. Run 'composer install' from the root directory.\n"
  DEPENDENCIES_NEED_INSTALLING=1;
fi

cd "$TOP_LEVEL/core"

# Ensure JavaScript development dependencies are installed.
yarn check -s 2>/dev/null
if [ "$?" -ne "0" ]; then
  printf "Drupal's JavaScript development dependencies are not installed or cannot be resolved. Run 'yarn install' inside the core directory, or 'yarn check -s' to list other errors.\n"
  DEPENDENCIES_NEED_INSTALLING=1;
fi

if [ $DEPENDENCIES_NEED_INSTALLING -ne 0 ]; then
  exit 1;
fi

# Check all files for spelling in one go for better performance.
yarn run -s spellcheck --no-must-find-files -c $TOP_LEVEL/core/.cspell.json $ABS_FILES
if [ "$?" -ne "0" ]; then
  # If there are failures set the status to a number other than 0.
  FINAL_STATUS=1
  printf "\nCSpell: ${red}failed${reset}\n"
else
  printf "\nCSpell: ${green}passed${reset}\n"
fi
cd "$TOP_LEVEL"

# Add a separator line to make the output easier to read.
printf "\n"
printf -- '-%.0s' {1..100}
printf "\n"

# Run PHPCS on all files on DrupalCI or when phpcs files are changed.
if [[ $PHPCS_XML_DIST_FILE_CHANGED == "1" ]] || [[ "$DRUPALCI" == "1" ]]; then
  # Test all files with phpcs rules.
  vendor/bin/phpcs -ps --parallel=$(nproc) --standard="$TOP_LEVEL/core/phpcs.xml.dist"
  PHPCS=$?
  if [ "$PHPCS" -ne "0" ]; then
    # If there are failures set the status to a number other than 0.
    FINAL_STATUS=1
    printf "\nPHPCS: ${red}failed${reset}\n"
  else
    printf "\nPHPCS: ${green}passed${reset}\n"
  fi
  # Add a separator line to make the output easier to read.
  printf "\n"
  printf -- '-%.0s' {1..100}
  printf "\n"
fi

# When the eslint config has been changed, then eslint must check all files.
if [[ $ESLINT_CONFIG_PASSING_FILE_CHANGED == "1" ]]; then
  cd "$TOP_LEVEL/core"
  yarn run -s lint:core-js-passing "$TOP_LEVEL/core"
  CORRECTJS=$?
  if [ "$CORRECTJS" -ne "0" ]; then
    # If there are failures set the status to a number other than 0.
    FINAL_STATUS=1
    printf "\neslint: ${red}failed${reset}\n"
  else
    printf "\neslint: ${green}passed${reset}\n"
  fi
  cd $TOP_LEVEL
  # Add a separator line to make the output easier to read.
  printf "\n"
  printf -- '-%.0s' {1..100}
  printf "\n"
fi

# When the stylelint config has been changed, then stylelint must check all files.
if [[ $STYLELINT_CONFIG_FILE_CHANGED == "1" ]]; then
  cd "$TOP_LEVEL/core"
  yarn run -s lint:css
  if [ "$?" -ne "0" ]; then
    # If there are failures set the status to a number other than 0.
    FINAL_STATUS=1
    printf "\nstylelint: ${red}failed${reset}\n"
  else
    printf "\nstylelint: ${green}passed${reset}\n"
  fi
  cd $TOP_LEVEL
  # Add a separator line to make the output easier to read.
  printf "\n"
  printf -- '-%.0s' {1..100}
  printf "\n"
fi

# When a Drupal-specific CKEditor 5 plugin changed ensure that it is compiled
# properly. Only check on DrupalCI, since we're concerned about the build being
# run with the expected package versions and making sure the result of the build
# is in sync and conform to expectations.
if [[ "$DRUPALCI" == "1" ]] && [[ $CKEDITOR5_PLUGINS_CHANGED == "1" ]]; then
  cd "$TOP_LEVEL/core"
  yarn run -s check:ckeditor5
  if [ "$?" -ne "0" ]; then
    # If there are failures set the status to a number other than 0.
    FINAL_STATUS=1
    printf "\nDrupal-specific CKEditor 5 plugins: ${red}failed${reset}\n"
  else
    printf "\nDrupal-specific CKEditor 5 plugins: ${green}passed${reset}\n"
  fi
  cd $TOP_LEVEL
  # Add a separator line to make the output easier to read.
  printf "\n"
  printf -- '-%.0s' {1..100}
  printf "\n"
fi

# When JavaScript packages change, then rerun all JavaScript style checks.
if [[ "$JAVASCRIPT_PACKAGES_CHANGED" == "1" ]]; then
  cd "$TOP_LEVEL/core"
  yarn run build:css --check
  CORRECTCSS=$?
  if [ "$CORRECTCSS" -ne "0" ]; then
    FINAL_STATUS=1
    printf "\n${red}ERROR: The compiled CSS from the PCSS files"
    printf "\n       does not match the current CSS files. Some added"
    printf "\n       or updated JavaScript package made changes."
    printf "\n       Recompile the CSS with: yarn run build:css${reset}\n\n"
  fi
  cd $TOP_LEVEL
  # Add a separator line to make the output easier to read.
  printf "\n"
  printf -- '-%.0s' {1..100}
  printf "\n"
fi

for FILE in $FILES; do
  STATUS=0;
  # Print a line to separate spellcheck output from per file output.
  printf "Checking %s\n" "$FILE"
  printf "\n"

  # Ensure the file still exists (i.e. is not being deleted).
  if [ -a $FILE ]; then
    if [ ${FILE: -3} != ".sh" ]; then
      # Ensure the file has the correct mode.
      STAT="$(stat -f "%A" $FILE 2>/dev/null)"
      if [ $? -ne 0 ]; then
        STAT="$(stat -c "%a" $FILE 2>/dev/null)"
      fi
      if [ "$STAT" -ne "644" ]; then
        printf "${red}check failed:${reset} file $FILE should be 644 not $STAT\n"
        STATUS=1
      fi
    fi
  fi

  # Don't commit changes to vendor.
  if [[ "$FILE" =~ ^vendor/ ]]; then
    printf "${red}check failed:${reset} file in vendor directory being committed ($FILE)\n"
    STATUS=1
  fi

  # Don't commit changes to core/node_modules.
  if [[ "$FILE" =~ ^core/node_modules/ ]]; then
    printf "${red}check failed:${reset} file in core/node_modules directory being committed ($FILE)\n"
    STATUS=1
  fi

  ############################################################################
  ### PHP AND YAML FILES
  ############################################################################
  if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.(inc|install|module|php|profile|test|theme|yml)$ ]] && [[ $PHPCS_XML_DIST_FILE_CHANGED == "0" ]] && [[ "$DRUPALCI" == "0" ]]; then
    # Test files with phpcs rules.
    vendor/bin/phpcs "$TOP_LEVEL/$FILE" --standard="$TOP_LEVEL/core/phpcs.xml.dist"
    PHPCS=$?
    if [ "$PHPCS" -ne "0" ]; then
      # If there are failures set the status to a number other than 0.
      STATUS=1
    else
      printf "PHPCS: $FILE ${green}passed${reset}\n"
    fi
  fi

  ############################################################################
  ### YAML FILES
  ############################################################################
  if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.yml$ ]]; then
    # Test files with ESLint.
    cd "$TOP_LEVEL/core"
    node ./node_modules/eslint/bin/eslint.js --quiet --resolve-plugins-relative-to . "$TOP_LEVEL/$FILE"
    YAMLLINT=$?
    if [ "$YAMLLINT" -ne "0" ]; then
      # If there are failures set the status to a number other than 0.
      STATUS=1
    else
      printf "ESLint: $FILE ${green}passed${reset}\n"
    fi
    cd $TOP_LEVEL
  fi

  ############################################################################
  ### JAVASCRIPT FILES
  ############################################################################
  if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.js$ ]] && [[ ! $FILE =~ ^core/tests/Drupal/Nightwatch ]] && [[ ! $FILE =~ /tests/src/Nightwatch/ ]] && [[ ! $FILE =~ ^core/modules/ckeditor5/js/ckeditor5_plugins ]]; then
    # Work out the root name of the JavaScript so we can ensure that the ES6
    # version has been compiled correctly.
    if [[ $FILE =~ \.es6\.js$ ]]; then
      BASENAME=${FILE%.es6.js}
      COMPILE_CHECK=1
    else
      BASENAME=${FILE%.js}
      # We only need to compile check if the .es6.js file is not also
      # changing. This is because the compile check will occur for the
      # .es6.js file. This might occur if the compile scripts have changed.
      contains_element "$BASENAME.es6.js" "${FILES[@]}"
      HASES6=$?
      if [ "$HASES6" -ne "0" ]; then
        COMPILE_CHECK=1
      else
        COMPILE_CHECK=0
      fi
    fi
    if [[ "$COMPILE_CHECK" == "1" ]] && [[ -f "$TOP_LEVEL/$BASENAME.es6.js" ]]; then
      cd "$TOP_LEVEL/core"
      yarn run build:js --check --file "$TOP_LEVEL/$BASENAME.es6.js"
      CORRECTJS=$?
      if [ "$CORRECTJS" -ne "0" ]; then
        # No need to write any output the yarn run command will do this for
        # us.
        STATUS=1
      fi
      # Check the coding standards.
      if [[ -f ".eslintrc.passing.json" ]]; then
        node ./node_modules/eslint/bin/eslint.js --quiet --config=.eslintrc.passing.json "$TOP_LEVEL/$BASENAME.es6.js"
        CORRECTJS=$?
        if [ "$CORRECTJS" -ne "0" ]; then
          # No need to write any output the node command will do this for us.
          STATUS=1
        fi
      fi
      cd $TOP_LEVEL
    else
      # If there is no .es6.js file then there should be unless the .js is
      # not really Drupal's.
      if ! [[ "$FILE" =~ ^core/assets/vendor ]] && ! [[ "$FILE" =~ ^core/modules/ckeditor5/js/build ]] && ! [[ "$FILE" =~ ^core/scripts/js ]] && ! [[ "$FILE" =~ ^core/scripts/css ]] && ! [[ "$FILE" =~ webpack.config.js$ ]] && ! [[ -f "$TOP_LEVEL/$BASENAME.es6.js" ]] && ! [[ "$FILE" =~ core/modules/ckeditor5/tests/modules/ckeditor5_test/js/build/layercake.js ]]; then
        printf "${red}FAILURE${reset} $FILE does not have a corresponding $BASENAME.es6.js\n"
        STATUS=1
      fi
    fi
  else
    # Check coding standards of Nightwatch files.
    if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.js$ ]]; then
      cd "$TOP_LEVEL/core"
      # Check the coding standards.
      if [[ -f ".eslintrc.passing.json" ]]; then
        node ./node_modules/eslint/bin/eslint.js --quiet --config=.eslintrc.passing.json "$TOP_LEVEL/$FILE"
        CORRECTJS=$?
        if [ "$CORRECTJS" -ne "0" ]; then
          # No need to write any output the node command will do this for us.
          STATUS=1
        fi
      fi
      cd $TOP_LEVEL
    fi
  fi

  ############################################################################
  ### CSS FILES
  ############################################################################
  if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.css$ ]]; then
    # Work out the root name of the CSS so we can ensure that the PostCSS
    # version has been compiled correctly.
    if [[ $FILE =~ \.pcss\.css$ ]]; then
      BASENAME=${FILE%.pcss.css}
      COMPILE_CHECK=1
    else
      BASENAME=${FILE%.css}
      # We only need to compile check if the .pcss.css file is not also
      # changing. This is because the compile check will occur for the
      # .pcss.css file. This might occur if the compiled stylesheets have
      # changed.
      contains_element "$BASENAME.pcss.css" "${FILES[@]}"
      HASPOSTCSS=$?
      if [ "$HASPOSTCSS" -ne "0" ]; then
        COMPILE_CHECK=1
      else
        COMPILE_CHECK=0
      fi
    fi
    # PostCSS
    if [[ "$COMPILE_CHECK" == "1" ]] && [[ -f "$TOP_LEVEL/$BASENAME.pcss.css" ]]; then
      cd "$TOP_LEVEL/core"
      yarn run build:css --check --file "$TOP_LEVEL/$BASENAME.pcss.css"
      CORRECTCSS=$?
      if [ "$CORRECTCSS" -ne "0" ]; then
        # If the CSS does not match the PCSS, set the status to a number other
        # than 0.
        STATUS=1
        printf "\n${red}ERROR: The compiled CSS from"
        printf "\n       ${BASENAME}.pcss.css"
        printf "\n       does not match its CSS file. Recompile the CSS with:"
        printf "\n       yarn run build:css${reset}\n\n"
      fi
      cd $TOP_LEVEL
    fi
  fi
  if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.css$ ]] && [[ -f "core/node_modules/.bin/stylelint" ]]; then
    BASENAME=${FILE%.css}
    # We only need to use stylelint on the .pcss.css file. So if this CSS file
    # has a corresponding .pcss don't do stylelint.
    if [[ $FILE =~ \.pcss\.css$ ]] || [[ ! -f "$TOP_LEVEL/$BASENAME.pcss.css" ]]; then
      cd "$TOP_LEVEL/core"
      node_modules/.bin/stylelint --allow-empty-input "$TOP_LEVEL/$FILE"
      if [ "$?" -ne "0" ]; then
        STATUS=1
      else
        printf "STYLELINT: $FILE ${green}passed${reset}\n"
      fi
      cd $TOP_LEVEL
    fi
  fi

  if [[ "$STATUS" == "1" ]]; then
    FINAL_STATUS=1
    # There is no need to print a failure message. The fail will be described
    # already.
  else
    printf "%s ${green}passed${reset}\n" "$FILE"
  fi

  # Print a line to separate each file's checks.
  printf "\n"
  printf -- '-%.0s' {1..100}
  printf "\n"
done

if [[ "$FINAL_STATUS" == "1" ]] && [[ "$DRUPALCI" == "1" ]]; then
  printf "${red}Drupal code quality checks failed.${reset}\n"
  printf "To reproduce this output locally:\n"
  printf "* Apply the change as a patch\n"
  printf "* Run this command locally: sh ./core/scripts/dev/commit-code-check.sh\n"
  printf "OR:\n"
  printf "* From the merge request branch\n"
  printf "* Run this command locally: sh ./core/scripts/dev/commit-code-check.sh --branch %s\n" "$DRUPAL_VERSION"
fi
exit $FINAL_STATUS

File

core/scripts/dev/commit-code-check.sh

View source
  1. #!/bin/bash
  2. #
  3. # This script performs code quality checks.
  4. #
  5. # @internal
  6. # This script is not covered by Drupal core's backwards compatibility promise.
  7. # It exists only for core development purposes.
  8. #
  9. # The script makes the following checks:
  10. # - Spell checking.
  11. # - File modes.
  12. # - No changes to core/node_modules directory.
  13. # - PHPCS checks PHP and YAML files.
  14. # - ESLint checks JavaScript and YAML files.
  15. # - Checks .es6.js and .js files are equivalent.
  16. # - Stylelint checks CSS files.
  17. # - Checks .pcss.css and .css files are equivalent.
  18. # cSpell:disable
  19. # Searches an array.
  20. contains_element() {
  21. local e
  22. for e in ${@:2}; do [[ "$e" == "$1" ]] && return 0; done
  23. return 1
  24. }
  25. CACHED=0
  26. DRUPALCI=0
  27. BRANCH=""
  28. while test $# -gt 0; do
  29. case "$1" in
  30. -h|--help)
  31. echo "Drupal code quality checks"
  32. echo " "
  33. echo "options:"
  34. echo "-h, --help show brief help"
  35. echo "--branch BRANCH creates list of files to check by comparing against a branch"
  36. echo "--cached checks staged files"
  37. echo "--drupalci a special mode for DrupalCI"
  38. echo " "
  39. echo "Example usage: sh ./core/scripts/dev/commit-code-check.sh --branch 9.2.x"
  40. exit 0
  41. ;;
  42. --branch)
  43. BRANCH="$2"
  44. if [[ "$BRANCH" == "" ]]; then
  45. printf "The --branch option requires a value. For example: --branch 9.2.x\n"
  46. exit;
  47. fi
  48. shift 2
  49. ;;
  50. --cached)
  51. CACHED=1
  52. shift
  53. ;;
  54. --drupalci)
  55. DRUPALCI=1
  56. shift
  57. ;;
  58. *)
  59. break
  60. ;;
  61. esac
  62. done
  63. # Set up variables to make colored output simple. Color output is disabled on
  64. # DrupalCI because it is breaks reporting.
  65. # @todo https://www.drupal.org/project/drupalci_testbot/issues/3181869
  66. if [[ "$DRUPALCI" == "1" ]]; then
  67. red=""
  68. green=""
  69. reset=""
  70. DRUPAL_VERSION=$(php -r "include 'vendor/autoload.php'; print preg_replace('#\.[0-9]+-dev#', '.x', \Drupal::VERSION);")
  71. GIT="sudo -u www-data git"
  72. else
  73. red=$(tput setaf 1 && tput bold)
  74. green=$(tput setaf 2)
  75. reset=$(tput sgr0)
  76. GIT="git"
  77. fi
  78. # Gets list of files to check.
  79. if [[ "$BRANCH" != "" ]]; then
  80. FILES=$($GIT diff --name-only $BRANCH HEAD);
  81. elif [[ "$CACHED" == "0" ]]; then
  82. # For DrupalCI patch testing or when running without --cached or --branch,
  83. # list of all changes in the working directory.
  84. FILES=$($GIT ls-files --other --modified --exclude-standard --exclude=vendor)
  85. else
  86. # Check staged files only.
  87. if $GIT rev-parse --verify HEAD >/dev/null 2>&1
  88. then
  89. AGAINST=HEAD
  90. else
  91. # Initial commit: diff against an empty tree object
  92. AGAINST=4b825dc642cb6eb9a060e54bf8d69288fbee4904
  93. fi
  94. FILES=$($GIT diff --cached --name-only $AGAINST);
  95. fi
  96. if [[ "$FILES" == "" ]] && [[ "$DRUPALCI" == "1" ]]; then
  97. # If the FILES is empty we might be testing a merge request on DrupalCI. We
  98. # need to diff against the Drupal branch or tag related to the Drupal version.
  99. printf "Creating list of files to check by comparing branch to %s\n" "$DRUPAL_VERSION"
  100. # On DrupalCI there's a merge commit so we can compare to HEAD~1.
  101. FILES=$($GIT diff --name-only HEAD~1 HEAD);
  102. fi
  103. TOP_LEVEL=$($GIT rev-parse --show-toplevel)
  104. # This variable will be set to one when the file core/phpcs.xml.dist is changed.
  105. PHPCS_XML_DIST_FILE_CHANGED=0
  106. # This variable will be set to one when one of the eslint config file is
  107. # changed:
  108. # - core/.eslintrc.passing.json
  109. # - core/.eslintrc.json
  110. # - core/.eslintrc.jquery.json
  111. ESLINT_CONFIG_PASSING_FILE_CHANGED=0
  112. # This variable will be set to one when the stylelint config file is changed.
  113. # changed:
  114. # - core/.stylelintignore
  115. # - core/.stylelintrc.json
  116. STYLELINT_CONFIG_FILE_CHANGED=0
  117. # This variable will be set to one when JavaScript packages files are changed.
  118. # changed:
  119. # - core/package.json
  120. # - core/yarn.lock
  121. JAVASCRIPT_PACKAGES_CHANGED=0
  122. # This variable will be set when a Drupal-specific CKEditor 5 plugin has changed
  123. # it is used to make sure the compiled JS is valid.
  124. CKEDITOR5_PLUGINS_CHANGED=0
  125. # Build up a list of absolute file names.
  126. ABS_FILES=
  127. for FILE in $FILES; do
  128. ABS_FILES="$ABS_FILES $TOP_LEVEL/$FILE"
  129. if [[ $FILE == "core/phpcs.xml.dist" ]]; then
  130. PHPCS_XML_DIST_FILE_CHANGED=1;
  131. fi;
  132. if [[ $FILE == "core/.eslintrc.json" || $FILE == "core/.eslintrc.passing.json" || $FILE == "core/.eslintrc.jquery.json" ]]; then
  133. ESLINT_CONFIG_PASSING_FILE_CHANGED=1;
  134. fi;
  135. if [[ $FILE == "core/.stylelintignore" || $FILE == "core/.stylelintrc.json" ]]; then
  136. STYLELINT_CONFIG_FILE_CHANGED=1;
  137. fi;
  138. # If JavaScript packages change, then rerun all JavaScript style checks.
  139. if [[ $FILE == "core/package.json" || $FILE == "core/yarn.lock" ]]; then
  140. ESLINT_CONFIG_PASSING_FILE_CHANGED=1;
  141. STYLELINT_CONFIG_FILE_CHANGED=1;
  142. JAVASCRIPT_PACKAGES_CHANGED=1;
  143. fi;
  144. if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.js$ ]] && [[ $FILE =~ ^core/modules/ckeditor5/js/build || $FILE =~ ^core/modules/ckeditor5/js/ckeditor5_plugins ]]; then
  145. CKEDITOR5_PLUGINS_CHANGED=1;
  146. fi;
  147. done
  148. # Exit early if there are no files.
  149. if [[ "$ABS_FILES" == "" ]]; then
  150. printf "There are no files to check. If you have staged a commit use the --cached option.\n"
  151. exit;
  152. fi;
  153. # This script assumes that composer install and yarn install have already been
  154. # run and all dependencies are updated.
  155. FINAL_STATUS=0
  156. DEPENDENCIES_NEED_INSTALLING=0
  157. # Ensure PHP development dependencies are installed.
  158. # @todo https://github.com/composer/composer/issues/4497 Improve this to
  159. # determine if dependencies in the lock file match the installed versions.
  160. # Using composer install --dry-run is not valid because it would depend on
  161. # user-facing strings in Composer.
  162. if ! [[ -f 'vendor/bin/phpcs' ]]; then
  163. printf "Drupal's PHP development dependencies are not installed. Run 'composer install' from the root directory.\n"
  164. DEPENDENCIES_NEED_INSTALLING=1;
  165. fi
  166. cd "$TOP_LEVEL/core"
  167. # Ensure JavaScript development dependencies are installed.
  168. yarn check -s 2>/dev/null
  169. if [ "$?" -ne "0" ]; then
  170. printf "Drupal's JavaScript development dependencies are not installed or cannot be resolved. Run 'yarn install' inside the core directory, or 'yarn check -s' to list other errors.\n"
  171. DEPENDENCIES_NEED_INSTALLING=1;
  172. fi
  173. if [ $DEPENDENCIES_NEED_INSTALLING -ne 0 ]; then
  174. exit 1;
  175. fi
  176. # Check all files for spelling in one go for better performance.
  177. yarn run -s spellcheck --no-must-find-files -c $TOP_LEVEL/core/.cspell.json $ABS_FILES
  178. if [ "$?" -ne "0" ]; then
  179. # If there are failures set the status to a number other than 0.
  180. FINAL_STATUS=1
  181. printf "\nCSpell: ${red}failed${reset}\n"
  182. else
  183. printf "\nCSpell: ${green}passed${reset}\n"
  184. fi
  185. cd "$TOP_LEVEL"
  186. # Add a separator line to make the output easier to read.
  187. printf "\n"
  188. printf -- '-%.0s' {1..100}
  189. printf "\n"
  190. # Run PHPCS on all files on DrupalCI or when phpcs files are changed.
  191. if [[ $PHPCS_XML_DIST_FILE_CHANGED == "1" ]] || [[ "$DRUPALCI" == "1" ]]; then
  192. # Test all files with phpcs rules.
  193. vendor/bin/phpcs -ps --parallel=$(nproc) --standard="$TOP_LEVEL/core/phpcs.xml.dist"
  194. PHPCS=$?
  195. if [ "$PHPCS" -ne "0" ]; then
  196. # If there are failures set the status to a number other than 0.
  197. FINAL_STATUS=1
  198. printf "\nPHPCS: ${red}failed${reset}\n"
  199. else
  200. printf "\nPHPCS: ${green}passed${reset}\n"
  201. fi
  202. # Add a separator line to make the output easier to read.
  203. printf "\n"
  204. printf -- '-%.0s' {1..100}
  205. printf "\n"
  206. fi
  207. # When the eslint config has been changed, then eslint must check all files.
  208. if [[ $ESLINT_CONFIG_PASSING_FILE_CHANGED == "1" ]]; then
  209. cd "$TOP_LEVEL/core"
  210. yarn run -s lint:core-js-passing "$TOP_LEVEL/core"
  211. CORRECTJS=$?
  212. if [ "$CORRECTJS" -ne "0" ]; then
  213. # If there are failures set the status to a number other than 0.
  214. FINAL_STATUS=1
  215. printf "\neslint: ${red}failed${reset}\n"
  216. else
  217. printf "\neslint: ${green}passed${reset}\n"
  218. fi
  219. cd $TOP_LEVEL
  220. # Add a separator line to make the output easier to read.
  221. printf "\n"
  222. printf -- '-%.0s' {1..100}
  223. printf "\n"
  224. fi
  225. # When the stylelint config has been changed, then stylelint must check all files.
  226. if [[ $STYLELINT_CONFIG_FILE_CHANGED == "1" ]]; then
  227. cd "$TOP_LEVEL/core"
  228. yarn run -s lint:css
  229. if [ "$?" -ne "0" ]; then
  230. # If there are failures set the status to a number other than 0.
  231. FINAL_STATUS=1
  232. printf "\nstylelint: ${red}failed${reset}\n"
  233. else
  234. printf "\nstylelint: ${green}passed${reset}\n"
  235. fi
  236. cd $TOP_LEVEL
  237. # Add a separator line to make the output easier to read.
  238. printf "\n"
  239. printf -- '-%.0s' {1..100}
  240. printf "\n"
  241. fi
  242. # When a Drupal-specific CKEditor 5 plugin changed ensure that it is compiled
  243. # properly. Only check on DrupalCI, since we're concerned about the build being
  244. # run with the expected package versions and making sure the result of the build
  245. # is in sync and conform to expectations.
  246. if [[ "$DRUPALCI" == "1" ]] && [[ $CKEDITOR5_PLUGINS_CHANGED == "1" ]]; then
  247. cd "$TOP_LEVEL/core"
  248. yarn run -s check:ckeditor5
  249. if [ "$?" -ne "0" ]; then
  250. # If there are failures set the status to a number other than 0.
  251. FINAL_STATUS=1
  252. printf "\nDrupal-specific CKEditor 5 plugins: ${red}failed${reset}\n"
  253. else
  254. printf "\nDrupal-specific CKEditor 5 plugins: ${green}passed${reset}\n"
  255. fi
  256. cd $TOP_LEVEL
  257. # Add a separator line to make the output easier to read.
  258. printf "\n"
  259. printf -- '-%.0s' {1..100}
  260. printf "\n"
  261. fi
  262. # When JavaScript packages change, then rerun all JavaScript style checks.
  263. if [[ "$JAVASCRIPT_PACKAGES_CHANGED" == "1" ]]; then
  264. cd "$TOP_LEVEL/core"
  265. yarn run build:css --check
  266. CORRECTCSS=$?
  267. if [ "$CORRECTCSS" -ne "0" ]; then
  268. FINAL_STATUS=1
  269. printf "\n${red}ERROR: The compiled CSS from the PCSS files"
  270. printf "\n does not match the current CSS files. Some added"
  271. printf "\n or updated JavaScript package made changes."
  272. printf "\n Recompile the CSS with: yarn run build:css${reset}\n\n"
  273. fi
  274. cd $TOP_LEVEL
  275. # Add a separator line to make the output easier to read.
  276. printf "\n"
  277. printf -- '-%.0s' {1..100}
  278. printf "\n"
  279. fi
  280. for FILE in $FILES; do
  281. STATUS=0;
  282. # Print a line to separate spellcheck output from per file output.
  283. printf "Checking %s\n" "$FILE"
  284. printf "\n"
  285. # Ensure the file still exists (i.e. is not being deleted).
  286. if [ -a $FILE ]; then
  287. if [ ${FILE: -3} != ".sh" ]; then
  288. # Ensure the file has the correct mode.
  289. STAT="$(stat -f "%A" $FILE 2>/dev/null)"
  290. if [ $? -ne 0 ]; then
  291. STAT="$(stat -c "%a" $FILE 2>/dev/null)"
  292. fi
  293. if [ "$STAT" -ne "644" ]; then
  294. printf "${red}check failed:${reset} file $FILE should be 644 not $STAT\n"
  295. STATUS=1
  296. fi
  297. fi
  298. fi
  299. # Don't commit changes to vendor.
  300. if [[ "$FILE" =~ ^vendor/ ]]; then
  301. printf "${red}check failed:${reset} file in vendor directory being committed ($FILE)\n"
  302. STATUS=1
  303. fi
  304. # Don't commit changes to core/node_modules.
  305. if [[ "$FILE" =~ ^core/node_modules/ ]]; then
  306. printf "${red}check failed:${reset} file in core/node_modules directory being committed ($FILE)\n"
  307. STATUS=1
  308. fi
  309. ############################################################################
  310. ### PHP AND YAML FILES
  311. ############################################################################
  312. if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.(inc|install|module|php|profile|test|theme|yml)$ ]] && [[ $PHPCS_XML_DIST_FILE_CHANGED == "0" ]] && [[ "$DRUPALCI" == "0" ]]; then
  313. # Test files with phpcs rules.
  314. vendor/bin/phpcs "$TOP_LEVEL/$FILE" --standard="$TOP_LEVEL/core/phpcs.xml.dist"
  315. PHPCS=$?
  316. if [ "$PHPCS" -ne "0" ]; then
  317. # If there are failures set the status to a number other than 0.
  318. STATUS=1
  319. else
  320. printf "PHPCS: $FILE ${green}passed${reset}\n"
  321. fi
  322. fi
  323. ############################################################################
  324. ### YAML FILES
  325. ############################################################################
  326. if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.yml$ ]]; then
  327. # Test files with ESLint.
  328. cd "$TOP_LEVEL/core"
  329. node ./node_modules/eslint/bin/eslint.js --quiet --resolve-plugins-relative-to . "$TOP_LEVEL/$FILE"
  330. YAMLLINT=$?
  331. if [ "$YAMLLINT" -ne "0" ]; then
  332. # If there are failures set the status to a number other than 0.
  333. STATUS=1
  334. else
  335. printf "ESLint: $FILE ${green}passed${reset}\n"
  336. fi
  337. cd $TOP_LEVEL
  338. fi
  339. ############################################################################
  340. ### JAVASCRIPT FILES
  341. ############################################################################
  342. if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.js$ ]] && [[ ! $FILE =~ ^core/tests/Drupal/Nightwatch ]] && [[ ! $FILE =~ /tests/src/Nightwatch/ ]] && [[ ! $FILE =~ ^core/modules/ckeditor5/js/ckeditor5_plugins ]]; then
  343. # Work out the root name of the JavaScript so we can ensure that the ES6
  344. # version has been compiled correctly.
  345. if [[ $FILE =~ \.es6\.js$ ]]; then
  346. BASENAME=${FILE%.es6.js}
  347. COMPILE_CHECK=1
  348. else
  349. BASENAME=${FILE%.js}
  350. # We only need to compile check if the .es6.js file is not also
  351. # changing. This is because the compile check will occur for the
  352. # .es6.js file. This might occur if the compile scripts have changed.
  353. contains_element "$BASENAME.es6.js" "${FILES[@]}"
  354. HASES6=$?
  355. if [ "$HASES6" -ne "0" ]; then
  356. COMPILE_CHECK=1
  357. else
  358. COMPILE_CHECK=0
  359. fi
  360. fi
  361. if [[ "$COMPILE_CHECK" == "1" ]] && [[ -f "$TOP_LEVEL/$BASENAME.es6.js" ]]; then
  362. cd "$TOP_LEVEL/core"
  363. yarn run build:js --check --file "$TOP_LEVEL/$BASENAME.es6.js"
  364. CORRECTJS=$?
  365. if [ "$CORRECTJS" -ne "0" ]; then
  366. # No need to write any output the yarn run command will do this for
  367. # us.
  368. STATUS=1
  369. fi
  370. # Check the coding standards.
  371. if [[ -f ".eslintrc.passing.json" ]]; then
  372. node ./node_modules/eslint/bin/eslint.js --quiet --config=.eslintrc.passing.json "$TOP_LEVEL/$BASENAME.es6.js"
  373. CORRECTJS=$?
  374. if [ "$CORRECTJS" -ne "0" ]; then
  375. # No need to write any output the node command will do this for us.
  376. STATUS=1
  377. fi
  378. fi
  379. cd $TOP_LEVEL
  380. else
  381. # If there is no .es6.js file then there should be unless the .js is
  382. # not really Drupal's.
  383. if ! [[ "$FILE" =~ ^core/assets/vendor ]] && ! [[ "$FILE" =~ ^core/modules/ckeditor5/js/build ]] && ! [[ "$FILE" =~ ^core/scripts/js ]] && ! [[ "$FILE" =~ ^core/scripts/css ]] && ! [[ "$FILE" =~ webpack.config.js$ ]] && ! [[ -f "$TOP_LEVEL/$BASENAME.es6.js" ]] && ! [[ "$FILE" =~ core/modules/ckeditor5/tests/modules/ckeditor5_test/js/build/layercake.js ]]; then
  384. printf "${red}FAILURE${reset} $FILE does not have a corresponding $BASENAME.es6.js\n"
  385. STATUS=1
  386. fi
  387. fi
  388. else
  389. # Check coding standards of Nightwatch files.
  390. if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.js$ ]]; then
  391. cd "$TOP_LEVEL/core"
  392. # Check the coding standards.
  393. if [[ -f ".eslintrc.passing.json" ]]; then
  394. node ./node_modules/eslint/bin/eslint.js --quiet --config=.eslintrc.passing.json "$TOP_LEVEL/$FILE"
  395. CORRECTJS=$?
  396. if [ "$CORRECTJS" -ne "0" ]; then
  397. # No need to write any output the node command will do this for us.
  398. STATUS=1
  399. fi
  400. fi
  401. cd $TOP_LEVEL
  402. fi
  403. fi
  404. ############################################################################
  405. ### CSS FILES
  406. ############################################################################
  407. if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.css$ ]]; then
  408. # Work out the root name of the CSS so we can ensure that the PostCSS
  409. # version has been compiled correctly.
  410. if [[ $FILE =~ \.pcss\.css$ ]]; then
  411. BASENAME=${FILE%.pcss.css}
  412. COMPILE_CHECK=1
  413. else
  414. BASENAME=${FILE%.css}
  415. # We only need to compile check if the .pcss.css file is not also
  416. # changing. This is because the compile check will occur for the
  417. # .pcss.css file. This might occur if the compiled stylesheets have
  418. # changed.
  419. contains_element "$BASENAME.pcss.css" "${FILES[@]}"
  420. HASPOSTCSS=$?
  421. if [ "$HASPOSTCSS" -ne "0" ]; then
  422. COMPILE_CHECK=1
  423. else
  424. COMPILE_CHECK=0
  425. fi
  426. fi
  427. # PostCSS
  428. if [[ "$COMPILE_CHECK" == "1" ]] && [[ -f "$TOP_LEVEL/$BASENAME.pcss.css" ]]; then
  429. cd "$TOP_LEVEL/core"
  430. yarn run build:css --check --file "$TOP_LEVEL/$BASENAME.pcss.css"
  431. CORRECTCSS=$?
  432. if [ "$CORRECTCSS" -ne "0" ]; then
  433. # If the CSS does not match the PCSS, set the status to a number other
  434. # than 0.
  435. STATUS=1
  436. printf "\n${red}ERROR: The compiled CSS from"
  437. printf "\n ${BASENAME}.pcss.css"
  438. printf "\n does not match its CSS file. Recompile the CSS with:"
  439. printf "\n yarn run build:css${reset}\n\n"
  440. fi
  441. cd $TOP_LEVEL
  442. fi
  443. fi
  444. if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.css$ ]] && [[ -f "core/node_modules/.bin/stylelint" ]]; then
  445. BASENAME=${FILE%.css}
  446. # We only need to use stylelint on the .pcss.css file. So if this CSS file
  447. # has a corresponding .pcss don't do stylelint.
  448. if [[ $FILE =~ \.pcss\.css$ ]] || [[ ! -f "$TOP_LEVEL/$BASENAME.pcss.css" ]]; then
  449. cd "$TOP_LEVEL/core"
  450. node_modules/.bin/stylelint --allow-empty-input "$TOP_LEVEL/$FILE"
  451. if [ "$?" -ne "0" ]; then
  452. STATUS=1
  453. else
  454. printf "STYLELINT: $FILE ${green}passed${reset}\n"
  455. fi
  456. cd $TOP_LEVEL
  457. fi
  458. fi
  459. if [[ "$STATUS" == "1" ]]; then
  460. FINAL_STATUS=1
  461. # There is no need to print a failure message. The fail will be described
  462. # already.
  463. else
  464. printf "%s ${green}passed${reset}\n" "$FILE"
  465. fi
  466. # Print a line to separate each file's checks.
  467. printf "\n"
  468. printf -- '-%.0s' {1..100}
  469. printf "\n"
  470. done
  471. if [[ "$FINAL_STATUS" == "1" ]] && [[ "$DRUPALCI" == "1" ]]; then
  472. printf "${red}Drupal code quality checks failed.${reset}\n"
  473. printf "To reproduce this output locally:\n"
  474. printf "* Apply the change as a patch\n"
  475. printf "* Run this command locally: sh ./core/scripts/dev/commit-code-check.sh\n"
  476. printf "OR:\n"
  477. printf "* From the merge request branch\n"
  478. printf "* Run this command locally: sh ./core/scripts/dev/commit-code-check.sh --branch %s\n" "$DRUPAL_VERSION"
  479. fi
  480. exit $FINAL_STATUS

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.