commit-code-check.sh

Same filename and directory in other branches
  1. 10 core/scripts/dev/commit-code-check.sh
  2. 11.x core/scripts/dev/commit-code-check.sh
  3. 9 core/scripts/dev/commit-code-check.sh
  4. 8.9.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.
# - PHPStan checks PHP files.
# - ESLint checks JavaScript and YAML files.
# - 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
}

MEMORY_UNLIMITED=0
CACHED=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 "--memory-unlimited        bypass PHP memory limit for PHPStan and PHPCS"
      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
      ;;
    --memory-unlimited)
      MEMORY_UNLIMITED=1
      shift
      ;;
    *)
      break
      ;;
  esac
done

memory_limit=""
phpcs_memory_limit=""

if [[ "$MEMORY_UNLIMITED" == "1" ]]; then
  memory_limit="--memory-limit=-1"
  phpcs_memory_limit="-d memory_limit=-1"
fi

# Set up variables to make colored output simple.
red=$(tput setaf 1 && tput bold)
blue=$(tput setaf 4 && tput bold)
green=$(tput setaf 2)
reset=$(tput sgr0)
GIT="git"

# Gets list of files to check.
if [[ "$BRANCH" != "" ]]; then
  FILES=$($GIT diff --name-only $BRANCH HEAD);
elif [[ "$CACHED" == "0" ]]; then
  # 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

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 the files core/.phpstan-baseline.php or
# core/phpstan.neon.dist are changed.
PHPSTAN_DIST_FILE_CHANGED=0

# This variable will be set to one when one of the eslint config file is
# changed:
#  - core/eslint.passing.config.mjs
#  - core/eslint.config.mjs
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 the twig-cs-fixer config file is
# changed:
#  - core/.twig-cs-fixer.php
TWIGCSFIXER_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

# This variable will be set to one when either of the core dictionaries or the
# .cspell.json config has changed.
CSPELL_DICTIONARY_FILE_CHANGED=0

# Build up a list of absolute file names.
ABS_FILES=
for FILE in $FILES; do
  if [ -f "$TOP_LEVEL/$FILE" ]; then
    ABS_FILES="$ABS_FILES $TOP_LEVEL/$FILE"
  fi

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

  if [[ $FILE == "core/.twig-cs-fixer.php" ]]; then
    TWIGCSFIXER_CONFIG_FILE_CHANGED=1;
  fi;

  if [[ $FILE == "core/.phpstan-baseline.php" || $FILE == "core/phpstan.neon.dist" ]]; then
    PHPSTAN_DIST_FILE_CHANGED=1;
  fi;

  if [[ $FILE == "core/eslint.config.mjs" || $FILE == "core/eslint.passing.config.mjs" ]]; 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;

  if [[ $FILE == "core/misc/cspell/dictionary.txt" || $FILE == "core/misc/cspell/drupal-dictionary.txt" || $FILE == "core/.cspell.json" ]]; then
    CSPELL_DICTIONARY_FILE_CHANGED=1;
  fi

  if [[ $FILE == "core/MAINTAINERS.txt" ]]; then
    MAINTAINERS_TXT_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 --version
yarn >/dev/null

# Check all files for spelling in one go for better performance.
if [[ $CSPELL_DICTIONARY_FILE_CHANGED == "1" ]] ; then
  printf "\nRunning spellcheck on *all* files.\n"
  yarn run spellcheck:core --no-must-find-files --no-progress
else
  # Check all files for spelling in one go for better performance. We pipe the
  # list files in so we obey the globs set on the spellcheck:core command in
  # core/package.json.
  echo "${ABS_FILES}" | tr ' ' '\n' | yarn run spellcheck:core --no-must-find-files --file-list stdin
fi

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 PHPStan on all files when phpstan files are changed.
# APCu is disabled to ensure that the composer classmap is not corrupted.
if [[ $PHPSTAN_DIST_FILE_CHANGED == "1" ]]; then
  printf "\nRunning PHPStan on *all* files.\n"
  php -d apc.enabled=0 -d apc.enable_cli=0 vendor/bin/phpstan analyze --no-progress --configuration="$TOP_LEVEL/core/phpstan.neon.dist" $memory_limit
else
  # Only run PHPStan on changed files locally.
  printf "\nRunning PHPStan on changed files.\n"
  php -d apc.enabled=0 -d apc.enable_cli=0 vendor/bin/phpstan analyze --no-progress --configuration="$TOP_LEVEL/core/phpstan-partial.neon" $ABS_FILES $memory_limit
fi

if [ "$?" -ne "0" ]; then
  # If there are failures set the status to a number other than 0.
  FINAL_STATUS=1
  printf "\nPHPStan: ${red}failed${reset}\n"
else
  printf "\nPHPStan: ${green}passed${reset}\n"
fi

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

# Run PHPCS on all files when phpcs files are changed.
if [[ $PHPCS_XML_DIST_FILE_CHANGED == "1" ]]; then
  # Test all files with phpcs rules.
  vendor/bin/phpcs $phpcs_memory_limit -ps --parallel="$( (nproc || sysctl -n hw.logicalcpu || echo 4) 2>/dev/null)" --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

# Run Twig CS Fixer on all files when twig cs fixer files are changed.
if [[ $TWIGCSFIXER_CONFIG_FILE_CHANGED == "1" ]]; then
  # Test all files with twig-cs-fixer rules.
  vendor/bin/twig-cs-fixer lint -c "$TOP_LEVEL/core/.twig-cs-fixer.php"
  TWIGCS=$?
  if [ "$TWIGCS" -ne "0" ]; then
    # If there are failures set the status to a number other than 0.
    FINAL_STATUS=1
    printf "\TWIGCS: ${red}failed${reset}\n"
  else
    printf "\TWIGCS: ${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 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 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 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

# Build file type lists for batch checks.
PHP_FILES=""
JS_FILES=""
CSS_FILES=""
TWIG_FILES=""
for FILE in $FILES; do
  if [[ -f "$TOP_LEVEL/$FILE" ]]; then
    if [[ $FILE =~ \.(inc|install|module|php|profile|test|theme|yml)$ ]]; then
      PHP_FILES="$PHP_FILES $TOP_LEVEL/$FILE"
    fi
    if [[ $FILE =~ \.(yml|js)$ ]]; then
      JS_FILES="$JS_FILES $TOP_LEVEL/$FILE"
    fi
    if [[ $FILE =~ \.css$ ]]; then
      BASENAME=${FILE%.css}
      if [[ $FILE =~ \.pcss\.css$ ]] || [[ ! -f "$TOP_LEVEL/$BASENAME.pcss.css" ]]; then
        CSS_FILES="$CSS_FILES $TOP_LEVEL/$FILE"
      fi
    fi
    if [[ $FILE =~ \.twig$ ]]; then
      TWIG_FILES="$TWIG_FILES $TOP_LEVEL/$FILE"
    fi
  fi
done

# Run PHPCS on changed PHP and YAML files.
if [[ "$PHP_FILES" != "" ]] && [[ $PHPCS_XML_DIST_FILE_CHANGED == "0" ]]; then
  vendor/bin/phpcs $phpcs_memory_limit --standard="$TOP_LEVEL/core/phpcs.xml.dist" $PHP_FILES
  if [ "$?" -ne "0" ]; then
    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

# Run Twig CS Fixer on changed Twig files.
if [[ "$TWIG_FILES" != "" ]] && [[ $TWIGCSFIXER_CONFIG_FILE_CHANGED == "0" ]]; then
  vendor/bin/twig-cs-fixer lint $TWIG_FILES -c "$TOP_LEVEL/core/.twig-cs-fixer.php"
  if [ "$?" -ne "0" ]; then
    FINAL_STATUS=1
    printf "\TWIGCS: ${red}failed${reset}\n"
  else
    printf "\TWIGCS: ${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

# Run ESLint on changed YAML and JavaScript files.
if [[ "$JS_FILES" != "" ]] && [[ $ESLINT_CONFIG_PASSING_FILE_CHANGED == "0" ]]; then
  cd "$TOP_LEVEL/core"
  node ./node_modules/eslint/bin/eslint.js --quiet --config=eslint.passing.config.mjs $JS_FILES
  if [ "$?" -ne "0" ]; then
    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

# Run Stylelint on changed CSS files.
if [[ "$CSS_FILES" != "" ]] && [[ $STYLELINT_CONFIG_FILE_CHANGED == "0" ]] && [[ -f "core/node_modules/.bin/stylelint" ]]; then
  cd "$TOP_LEVEL/core"
  node_modules/.bin/stylelint --allow-empty-input $CSS_FILES
  if [ "$?" -ne "0" ]; then
    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

for FILE in $FILES; do
  # Ensure the file still exists (i.e. is not being deleted).
  if [ -a $FILE ]; then
    if [ ${FILE: -3} != ".sh" ] && [ "$FILE" != "core/scripts/dr" ]; then
      if [ -x $FILE ]; then
        printf "${red}check failed:${reset} file $FILE should not be executable\n"
        FINAL_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"
    FINAL_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"
    FINAL_STATUS=1
  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.
        FINAL_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
done

if [[ "$MAINTAINERS_TXT_CHANGED" == "1" ]]; then
  printf "\n${blue}INFO: MAINTAINERS.TXT changed"
  printf "\n      Make sure follow up changes are made to documentation, Slack channel, email group etc."
  printf "\n      See https://www.drupal.org/about/core/policies/maintainers/add-or-remove-a-subsystem-or-topic-maintainer.${reset}\n\n"

  # Add a separator line to make the output easier to read.
  printf "\n"
  printf -- '-%.0s' {1..100}
  printf "\n"
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. # - PHPStan checks PHP files.
  15. # - ESLint checks JavaScript and YAML files.
  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. MEMORY_UNLIMITED=0
  26. CACHED=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 "--memory-unlimited bypass PHP memory limit for PHPStan and PHPCS"
  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. --memory-unlimited)
  55. MEMORY_UNLIMITED=1
  56. shift
  57. ;;
  58. *)
  59. break
  60. ;;
  61. esac
  62. done
  63. memory_limit=""
  64. phpcs_memory_limit=""
  65. if [[ "$MEMORY_UNLIMITED" == "1" ]]; then
  66. memory_limit="--memory-limit=-1"
  67. phpcs_memory_limit="-d memory_limit=-1"
  68. fi
  69. # Set up variables to make colored output simple.
  70. red=$(tput setaf 1 && tput bold)
  71. blue=$(tput setaf 4 && tput bold)
  72. green=$(tput setaf 2)
  73. reset=$(tput sgr0)
  74. GIT="git"
  75. # Gets list of files to check.
  76. if [[ "$BRANCH" != "" ]]; then
  77. FILES=$($GIT diff --name-only $BRANCH HEAD);
  78. elif [[ "$CACHED" == "0" ]]; then
  79. # List of all changes in the working directory.
  80. FILES=$($GIT ls-files --other --modified --exclude-standard --exclude=vendor)
  81. else
  82. # Check staged files only.
  83. if $GIT rev-parse --verify HEAD >/dev/null 2>&1
  84. then
  85. AGAINST=HEAD
  86. else
  87. # Initial commit: diff against an empty tree object
  88. AGAINST=4b825dc642cb6eb9a060e54bf8d69288fbee4904
  89. fi
  90. FILES=$($GIT diff --cached --name-only $AGAINST);
  91. fi
  92. TOP_LEVEL=$($GIT rev-parse --show-toplevel)
  93. # This variable will be set to one when the file core/phpcs.xml.dist is changed.
  94. PHPCS_XML_DIST_FILE_CHANGED=0
  95. # This variable will be set to one when the files core/.phpstan-baseline.php or
  96. # core/phpstan.neon.dist are changed.
  97. PHPSTAN_DIST_FILE_CHANGED=0
  98. # This variable will be set to one when one of the eslint config file is
  99. # changed:
  100. # - core/eslint.passing.config.mjs
  101. # - core/eslint.config.mjs
  102. ESLINT_CONFIG_PASSING_FILE_CHANGED=0
  103. # This variable will be set to one when the stylelint config file is changed.
  104. # changed:
  105. # - core/.stylelintignore
  106. # - core/.stylelintrc.json
  107. STYLELINT_CONFIG_FILE_CHANGED=0
  108. # This variable will be set to one when the twig-cs-fixer config file is
  109. # changed:
  110. # - core/.twig-cs-fixer.php
  111. TWIGCSFIXER_CONFIG_FILE_CHANGED=0
  112. # This variable will be set to one when JavaScript packages files are changed.
  113. # changed:
  114. # - core/package.json
  115. # - core/yarn.lock
  116. JAVASCRIPT_PACKAGES_CHANGED=0
  117. # This variable will be set when a Drupal-specific CKEditor 5 plugin has changed
  118. # it is used to make sure the compiled JS is valid.
  119. CKEDITOR5_PLUGINS_CHANGED=0
  120. # This variable will be set to one when either of the core dictionaries or the
  121. # .cspell.json config has changed.
  122. CSPELL_DICTIONARY_FILE_CHANGED=0
  123. # Build up a list of absolute file names.
  124. ABS_FILES=
  125. for FILE in $FILES; do
  126. if [ -f "$TOP_LEVEL/$FILE" ]; then
  127. ABS_FILES="$ABS_FILES $TOP_LEVEL/$FILE"
  128. fi
  129. if [[ $FILE == "core/phpcs.xml.dist" ]]; then
  130. PHPCS_XML_DIST_FILE_CHANGED=1;
  131. fi;
  132. if [[ $FILE == "core/.twig-cs-fixer.php" ]]; then
  133. TWIGCSFIXER_CONFIG_FILE_CHANGED=1;
  134. fi;
  135. if [[ $FILE == "core/.phpstan-baseline.php" || $FILE == "core/phpstan.neon.dist" ]]; then
  136. PHPSTAN_DIST_FILE_CHANGED=1;
  137. fi;
  138. if [[ $FILE == "core/eslint.config.mjs" || $FILE == "core/eslint.passing.config.mjs" ]]; then
  139. ESLINT_CONFIG_PASSING_FILE_CHANGED=1;
  140. fi;
  141. if [[ $FILE == "core/.stylelintignore" || $FILE == "core/.stylelintrc.json" ]]; then
  142. STYLELINT_CONFIG_FILE_CHANGED=1;
  143. fi;
  144. # If JavaScript packages change, then rerun all JavaScript style checks.
  145. if [[ $FILE == "core/package.json" || $FILE == "core/yarn.lock" ]]; then
  146. ESLINT_CONFIG_PASSING_FILE_CHANGED=1;
  147. STYLELINT_CONFIG_FILE_CHANGED=1;
  148. JAVASCRIPT_PACKAGES_CHANGED=1;
  149. fi;
  150. if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.js$ ]] && [[ $FILE =~ ^core/modules/ckeditor5/js/build || $FILE =~ ^core/modules/ckeditor5/js/ckeditor5_plugins ]]; then
  151. CKEDITOR5_PLUGINS_CHANGED=1;
  152. fi;
  153. if [[ $FILE == "core/misc/cspell/dictionary.txt" || $FILE == "core/misc/cspell/drupal-dictionary.txt" || $FILE == "core/.cspell.json" ]]; then
  154. CSPELL_DICTIONARY_FILE_CHANGED=1;
  155. fi
  156. if [[ $FILE == "core/MAINTAINERS.txt" ]]; then
  157. MAINTAINERS_TXT_CHANGED=1;
  158. fi
  159. done
  160. # Exit early if there are no files.
  161. if [[ "$ABS_FILES" == "" ]]; then
  162. printf "There are no files to check. If you have staged a commit use the --cached option.\n"
  163. exit;
  164. fi;
  165. # This script assumes that composer install and yarn install have already been
  166. # run and all dependencies are updated.
  167. FINAL_STATUS=0
  168. DEPENDENCIES_NEED_INSTALLING=0
  169. # Ensure PHP development dependencies are installed.
  170. # @todo https://github.com/composer/composer/issues/4497 Improve this to
  171. # determine if dependencies in the lock file match the installed versions.
  172. # Using composer install --dry-run is not valid because it would depend on
  173. # user-facing strings in Composer.
  174. if ! [[ -f 'vendor/bin/phpcs' ]]; then
  175. printf "Drupal's PHP development dependencies are not installed. Run 'composer install' from the root directory.\n"
  176. DEPENDENCIES_NEED_INSTALLING=1;
  177. fi
  178. cd "$TOP_LEVEL/core"
  179. # Ensure JavaScript development dependencies are installed.
  180. yarn --version
  181. yarn >/dev/null
  182. # Check all files for spelling in one go for better performance.
  183. if [[ $CSPELL_DICTIONARY_FILE_CHANGED == "1" ]] ; then
  184. printf "\nRunning spellcheck on *all* files.\n"
  185. yarn run spellcheck:core --no-must-find-files --no-progress
  186. else
  187. # Check all files for spelling in one go for better performance. We pipe the
  188. # list files in so we obey the globs set on the spellcheck:core command in
  189. # core/package.json.
  190. echo "${ABS_FILES}" | tr ' ' '\n' | yarn run spellcheck:core --no-must-find-files --file-list stdin
  191. fi
  192. if [ "$?" -ne "0" ]; then
  193. # If there are failures set the status to a number other than 0.
  194. FINAL_STATUS=1
  195. printf "\nCSpell: ${red}failed${reset}\n"
  196. else
  197. printf "\nCSpell: ${green}passed${reset}\n"
  198. fi
  199. cd "$TOP_LEVEL"
  200. # Add a separator line to make the output easier to read.
  201. printf "\n"
  202. printf -- '-%.0s' {1..100}
  203. printf "\n"
  204. # Run PHPStan on all files when phpstan files are changed.
  205. # APCu is disabled to ensure that the composer classmap is not corrupted.
  206. if [[ $PHPSTAN_DIST_FILE_CHANGED == "1" ]]; then
  207. printf "\nRunning PHPStan on *all* files.\n"
  208. php -d apc.enabled=0 -d apc.enable_cli=0 vendor/bin/phpstan analyze --no-progress --configuration="$TOP_LEVEL/core/phpstan.neon.dist" $memory_limit
  209. else
  210. # Only run PHPStan on changed files locally.
  211. printf "\nRunning PHPStan on changed files.\n"
  212. php -d apc.enabled=0 -d apc.enable_cli=0 vendor/bin/phpstan analyze --no-progress --configuration="$TOP_LEVEL/core/phpstan-partial.neon" $ABS_FILES $memory_limit
  213. fi
  214. if [ "$?" -ne "0" ]; then
  215. # If there are failures set the status to a number other than 0.
  216. FINAL_STATUS=1
  217. printf "\nPHPStan: ${red}failed${reset}\n"
  218. else
  219. printf "\nPHPStan: ${green}passed${reset}\n"
  220. fi
  221. # Add a separator line to make the output easier to read.
  222. printf "\n"
  223. printf -- '-%.0s' {1..100}
  224. printf "\n"
  225. # Run PHPCS on all files when phpcs files are changed.
  226. if [[ $PHPCS_XML_DIST_FILE_CHANGED == "1" ]]; then
  227. # Test all files with phpcs rules.
  228. vendor/bin/phpcs $phpcs_memory_limit -ps --parallel="$( (nproc || sysctl -n hw.logicalcpu || echo 4) 2>/dev/null)" --standard="$TOP_LEVEL/core/phpcs.xml.dist"
  229. PHPCS=$?
  230. if [ "$PHPCS" -ne "0" ]; then
  231. # If there are failures set the status to a number other than 0.
  232. FINAL_STATUS=1
  233. printf "\nPHPCS: ${red}failed${reset}\n"
  234. else
  235. printf "\nPHPCS: ${green}passed${reset}\n"
  236. fi
  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. # Run Twig CS Fixer on all files when twig cs fixer files are changed.
  243. if [[ $TWIGCSFIXER_CONFIG_FILE_CHANGED == "1" ]]; then
  244. # Test all files with twig-cs-fixer rules.
  245. vendor/bin/twig-cs-fixer lint -c "$TOP_LEVEL/core/.twig-cs-fixer.php"
  246. TWIGCS=$?
  247. if [ "$TWIGCS" -ne "0" ]; then
  248. # If there are failures set the status to a number other than 0.
  249. FINAL_STATUS=1
  250. printf "\TWIGCS: ${red}failed${reset}\n"
  251. else
  252. printf "\TWIGCS: ${green}passed${reset}\n"
  253. fi
  254. # Add a separator line to make the output easier to read.
  255. printf "\n"
  256. printf -- '-%.0s' {1..100}
  257. printf "\n"
  258. fi
  259. # When the eslint config has been changed, then eslint must check all files.
  260. if [[ $ESLINT_CONFIG_PASSING_FILE_CHANGED == "1" ]]; then
  261. cd "$TOP_LEVEL/core"
  262. yarn run lint:core-js-passing "$TOP_LEVEL/core"
  263. CORRECTJS=$?
  264. if [ "$CORRECTJS" -ne "0" ]; then
  265. # If there are failures set the status to a number other than 0.
  266. FINAL_STATUS=1
  267. printf "\neslint: ${red}failed${reset}\n"
  268. else
  269. printf "\neslint: ${green}passed${reset}\n"
  270. fi
  271. cd $TOP_LEVEL
  272. # Add a separator line to make the output easier to read.
  273. printf "\n"
  274. printf -- '-%.0s' {1..100}
  275. printf "\n"
  276. fi
  277. # When the stylelint config has been changed, then stylelint must check all files.
  278. if [[ $STYLELINT_CONFIG_FILE_CHANGED == "1" ]]; then
  279. cd "$TOP_LEVEL/core"
  280. yarn run lint:css
  281. if [ "$?" -ne "0" ]; then
  282. # If there are failures set the status to a number other than 0.
  283. FINAL_STATUS=1
  284. printf "\nstylelint: ${red}failed${reset}\n"
  285. else
  286. printf "\nstylelint: ${green}passed${reset}\n"
  287. fi
  288. cd $TOP_LEVEL
  289. # Add a separator line to make the output easier to read.
  290. printf "\n"
  291. printf -- '-%.0s' {1..100}
  292. printf "\n"
  293. fi
  294. # When JavaScript packages change, then rerun all JavaScript style checks.
  295. if [[ "$JAVASCRIPT_PACKAGES_CHANGED" == "1" ]]; then
  296. cd "$TOP_LEVEL/core"
  297. yarn run build:css --check
  298. CORRECTCSS=$?
  299. if [ "$CORRECTCSS" -ne "0" ]; then
  300. FINAL_STATUS=1
  301. printf "\n${red}ERROR: The compiled CSS from the PCSS files"
  302. printf "\n does not match the current CSS files. Some added"
  303. printf "\n or updated JavaScript package made changes."
  304. printf "\n Recompile the CSS with: yarn run build:css${reset}\n\n"
  305. fi
  306. cd $TOP_LEVEL
  307. # Add a separator line to make the output easier to read.
  308. printf "\n"
  309. printf -- '-%.0s' {1..100}
  310. printf "\n"
  311. fi
  312. # Build file type lists for batch checks.
  313. PHP_FILES=""
  314. JS_FILES=""
  315. CSS_FILES=""
  316. TWIG_FILES=""
  317. for FILE in $FILES; do
  318. if [[ -f "$TOP_LEVEL/$FILE" ]]; then
  319. if [[ $FILE =~ \.(inc|install|module|php|profile|test|theme|yml)$ ]]; then
  320. PHP_FILES="$PHP_FILES $TOP_LEVEL/$FILE"
  321. fi
  322. if [[ $FILE =~ \.(yml|js)$ ]]; then
  323. JS_FILES="$JS_FILES $TOP_LEVEL/$FILE"
  324. fi
  325. if [[ $FILE =~ \.css$ ]]; then
  326. BASENAME=${FILE%.css}
  327. if [[ $FILE =~ \.pcss\.css$ ]] || [[ ! -f "$TOP_LEVEL/$BASENAME.pcss.css" ]]; then
  328. CSS_FILES="$CSS_FILES $TOP_LEVEL/$FILE"
  329. fi
  330. fi
  331. if [[ $FILE =~ \.twig$ ]]; then
  332. TWIG_FILES="$TWIG_FILES $TOP_LEVEL/$FILE"
  333. fi
  334. fi
  335. done
  336. # Run PHPCS on changed PHP and YAML files.
  337. if [[ "$PHP_FILES" != "" ]] && [[ $PHPCS_XML_DIST_FILE_CHANGED == "0" ]]; then
  338. vendor/bin/phpcs $phpcs_memory_limit --standard="$TOP_LEVEL/core/phpcs.xml.dist" $PHP_FILES
  339. if [ "$?" -ne "0" ]; then
  340. FINAL_STATUS=1
  341. printf "\nPHPCS: ${red}failed${reset}\n"
  342. else
  343. printf "\nPHPCS: ${green}passed${reset}\n"
  344. fi
  345. # Add a separator line to make the output easier to read.
  346. printf "\n"
  347. printf -- '-%.0s' {1..100}
  348. printf "\n"
  349. fi
  350. # Run Twig CS Fixer on changed Twig files.
  351. if [[ "$TWIG_FILES" != "" ]] && [[ $TWIGCSFIXER_CONFIG_FILE_CHANGED == "0" ]]; then
  352. vendor/bin/twig-cs-fixer lint $TWIG_FILES -c "$TOP_LEVEL/core/.twig-cs-fixer.php"
  353. if [ "$?" -ne "0" ]; then
  354. FINAL_STATUS=1
  355. printf "\TWIGCS: ${red}failed${reset}\n"
  356. else
  357. printf "\TWIGCS: ${green}passed${reset}\n"
  358. fi
  359. # Add a separator line to make the output easier to read.
  360. printf "\n"
  361. printf -- '-%.0s' {1..100}
  362. printf "\n"
  363. fi
  364. # Run ESLint on changed YAML and JavaScript files.
  365. if [[ "$JS_FILES" != "" ]] && [[ $ESLINT_CONFIG_PASSING_FILE_CHANGED == "0" ]]; then
  366. cd "$TOP_LEVEL/core"
  367. node ./node_modules/eslint/bin/eslint.js --quiet --config=eslint.passing.config.mjs $JS_FILES
  368. if [ "$?" -ne "0" ]; then
  369. FINAL_STATUS=1
  370. printf "\nESLint: ${red}failed${reset}\n"
  371. else
  372. printf "\nESLint: ${green}passed${reset}\n"
  373. fi
  374. cd "$TOP_LEVEL"
  375. # Add a separator line to make the output easier to read.
  376. printf "\n"
  377. printf -- '-%.0s' {1..100}
  378. printf "\n"
  379. fi
  380. # Run Stylelint on changed CSS files.
  381. if [[ "$CSS_FILES" != "" ]] && [[ $STYLELINT_CONFIG_FILE_CHANGED == "0" ]] && [[ -f "core/node_modules/.bin/stylelint" ]]; then
  382. cd "$TOP_LEVEL/core"
  383. node_modules/.bin/stylelint --allow-empty-input $CSS_FILES
  384. if [ "$?" -ne "0" ]; then
  385. FINAL_STATUS=1
  386. printf "\nStylelint: ${red}failed${reset}\n"
  387. else
  388. printf "\nStylelint: ${green}passed${reset}\n"
  389. fi
  390. cd "$TOP_LEVEL"
  391. # Add a separator line to make the output easier to read.
  392. printf "\n"
  393. printf -- '-%.0s' {1..100}
  394. printf "\n"
  395. fi
  396. for FILE in $FILES; do
  397. # Ensure the file still exists (i.e. is not being deleted).
  398. if [ -a $FILE ]; then
  399. if [ ${FILE: -3} != ".sh" ] && [ "$FILE" != "core/scripts/dr" ]; then
  400. if [ -x $FILE ]; then
  401. printf "${red}check failed:${reset} file $FILE should not be executable\n"
  402. FINAL_STATUS=1
  403. fi
  404. fi
  405. fi
  406. # Don't commit changes to vendor.
  407. if [[ "$FILE" =~ ^vendor/ ]]; then
  408. printf "${red}check failed:${reset} file in vendor directory being committed ($FILE)\n"
  409. FINAL_STATUS=1
  410. fi
  411. # Don't commit changes to core/node_modules.
  412. if [[ "$FILE" =~ ^core/node_modules/ ]]; then
  413. printf "${red}check failed:${reset} file in core/node_modules directory being committed ($FILE)\n"
  414. FINAL_STATUS=1
  415. fi
  416. ############################################################################
  417. ### CSS FILES
  418. ############################################################################
  419. if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.css$ ]]; then
  420. # Work out the root name of the CSS so we can ensure that the PostCSS
  421. # version has been compiled correctly.
  422. if [[ $FILE =~ \.pcss\.css$ ]]; then
  423. BASENAME=${FILE%.pcss.css}
  424. COMPILE_CHECK=1
  425. else
  426. BASENAME=${FILE%.css}
  427. # We only need to compile check if the .pcss.css file is not also
  428. # changing. This is because the compile check will occur for the
  429. # .pcss.css file. This might occur if the compiled stylesheets have
  430. # changed.
  431. contains_element "$BASENAME.pcss.css" "${FILES[@]}"
  432. HASPOSTCSS=$?
  433. if [ "$HASPOSTCSS" -ne "0" ]; then
  434. COMPILE_CHECK=1
  435. else
  436. COMPILE_CHECK=0
  437. fi
  438. fi
  439. # PostCSS
  440. if [[ "$COMPILE_CHECK" == "1" ]] && [[ -f "$TOP_LEVEL/$BASENAME.pcss.css" ]]; then
  441. cd "$TOP_LEVEL/core"
  442. yarn run build:css --check --file "$TOP_LEVEL/$BASENAME.pcss.css"
  443. CORRECTCSS=$?
  444. if [ "$CORRECTCSS" -ne "0" ]; then
  445. # If the CSS does not match the PCSS, set the status to a number other
  446. # than 0.
  447. FINAL_STATUS=1
  448. printf "\n${red}ERROR: The compiled CSS from"
  449. printf "\n ${BASENAME}.pcss.css"
  450. printf "\n does not match its CSS file. Recompile the CSS with:"
  451. printf "\n yarn run build:css${reset}\n\n"
  452. fi
  453. cd $TOP_LEVEL
  454. fi
  455. fi
  456. done
  457. if [[ "$MAINTAINERS_TXT_CHANGED" == "1" ]]; then
  458. printf "\n${blue}INFO: MAINTAINERS.TXT changed"
  459. printf "\n Make sure follow up changes are made to documentation, Slack channel, email group etc."
  460. printf "\n See https://www.drupal.org/about/core/policies/maintainers/add-or-remove-a-subsystem-or-topic-maintainer.${reset}\n\n"
  461. # Add a separator line to make the output easier to read.
  462. printf "\n"
  463. printf -- '-%.0s' {1..100}
  464. printf "\n"
  465. fi
  466. exit $FINAL_STATUS

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