Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 10 additions & 61 deletions .github/workflows/lint_random_files.yml
Original file line number Diff line number Diff line change
Expand Up @@ -302,70 +302,19 @@ jobs:
- name: 'Lint JavaScript files'
id: lint-javascript
if: ( github.event.inputs.javascript != 'false' ) && ( success() || failure() )
env:
FIX: ${{ github.event.inputs.fix == 'true' && '1' || '0' }}
QUIET: '0'
ERR_FILE: ${{ github.workspace }}/lint_javascript_errors.txt
run: |
# If any command in a pipeline fails, the entire pipeline should fail:
set -o pipefail

# Determine root directory:
root=$(git rev-parse --show-toplevel)

# 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"
# Get JavaScript files to lint:
files=$(echo "${{ steps.random-files.outputs.files }}" | tr ',' '\n' | grep '\.js$' | tr '\n' ' ')

# Define the path to ESLint configuration file for linting benchmarks:
eslint_benchmarks_conf="${root}/etc/eslint/.eslintrc.benchmarks.js"

# Set `FIX` variable to `1` to enable automatic fixing of lint errors, `0` to disable:
if [ "${{ github.event.inputs.fix }}" == "true" ]; then
FIX=1
# Run the lint script:
if [ -n "${files}" ]; then
"$GITHUB_WORKSPACE/.github/workflows/scripts/lint_javascript_files_error_log" ${files}
else
FIX=0
fi

# Combined error file:
ERR_FILE="lint_javascript_errors.txt"
> "$ERR_FILE" # Initialize a clean file

# Lint JavaScript source files:
files=$(echo "${{ steps.random-files.outputs.files }}" | tr ',' '\n' | grep '\.js$' | grep -v -e '/examples' -e '/test' -e '/benchmark' -e '^dist/' | tr '\n' ' ')

# Build native addons if present:
packages=$(echo "${files}" | tr ' ' '\n' | sed 's/^lib\/node_modules\///g' | sed 's/\/lib\/.*//g' | sort | uniq)
for pkg in ${packages}; do
if [ -f "lib/node_modules/${pkg}/binding.gyp" ]; then
NODE_ADDONS_PATTERN="${pkg}" make install-node-addons
fi
done

if [[ -n "${files}" ]]; then
make lint-javascript-files FIX="${FIX}" FAST_FAIL=0 FILES="${files}" 2>&1 | tee -a "$ERR_FILE"
fi

# Lint JavaScript command-line interfaces...
file=$(echo "${{ steps.random-files.outputs.files }}" | tr ',' '\n' | grep '\.js$' | grep -E '/bin/cli$' | tr '\n' ' ')
if [[ -n "${file}" ]]; then
make lint-javascript-files FIX="${FIX}" FAST_FAIL=0 FILES="${file}" 2>&1 | tee -a "$ERR_FILE"
fi

# Lint JavaScript example files:
files=$(echo "${{ steps.random-files.outputs.files }}" | tr ',' '\n' | grep '/examples/.*\.js$' | tr '\n' ' ')
if [[ -n "${files}" ]]; then
make lint-javascript-files FIX="${FIX}" FAST_FAIL=0 FILES="${files}" ESLINT_CONF="${eslint_examples_conf}" 2>&1 | tee -a "$ERR_FILE"
fi

# Lint JavaScript test files:
files=$(echo "${{ steps.random-files.outputs.files }}" | tr ',' '\n' | grep '/test/.*\.js$' | tr '\n' ' ')
if [[ -n "${files}" ]]; then
make lint-javascript-files FIX="${FIX}" FAST_FAIL=0 FILES="${files}" ESLINT_CONF="${eslint_tests_conf}" 2>&1 | tee -a "$ERR_FILE"
fi

# Lint JavaScript benchmark files:
files=$(echo "${{ steps.random-files.outputs.files }}" | tr ',' '\n' | grep '/benchmark/.*\.js$' | tr '\n' ' ')
if [[ -n "${files}" ]]; then
make lint-javascript-files FIX="${FIX}" FAST_FAIL=0 FILES="${files}" ESLINT_CONF="${eslint_benchmarks_conf}" 2>&1 | tee -a "$ERR_FILE"
echo "No JavaScript files to lint."
fi

# Create sub-issue for JavaScript lint failures:
Expand Down
174 changes: 174 additions & 0 deletions .github/workflows/scripts/lint_javascript_files_error_log
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
#!/usr/bin/env bash
#
# @license Apache-2.0
#
# Copyright (c) 2025 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.

# Script to lint JavaScript files with extended error handling and reporting.
#
# Usage: lint_javascript_files_extended file1 [file2 file3 ...]
#
# Arguments:
#
# file1 File path.
# file2 File path.
# file3 File path.
#
# Environment variables:
#
# FIX 0 or 1 indicating whether to automatically fix errors.
# QUIET 0 or 1 indicating whether to run in quiet mode (errors only).
# ERR_FILE Path to error output file (optional).
#

# shellcheck disable=SC2086

# Ensure that the exit status of pipelines is non-zero in the event that at least one of the commands in a pipeline fails:
set -o pipefail


# VARIABLES #

# Determine root directory:
root=$(git rev-parse --show-toplevel)

# Flag indicating whether to automatically fix errors:
fix="${FIX:-0}"

# Flag indicating whether to run in quiet mode:
quiet="${QUIET:-0}"

# Error output file:
err_file="${ERR_FILE:-${TMPDIR:-/tmp}/lint_javascript_errors_$$.txt}"

# Files to lint:
files_to_lint="$*"

# 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"

# Initialize error flag:
error_occurred=0


# FUNCTIONS #

# Runs make lint command with error handling.
#
# $1 - files to lint
# $2 - additional make arguments
# $3 - ESLint configuration argument
run_make_lint() {
local files_to_lint="$1"
local make_args="$2"
local eslint_conf_arg="$3"

local eslint_flags=""
if [ "${quiet}" -eq 1 ]; then
eslint_flags="ESLINT_FLAGS='--quiet'"
fi

local make_cmd="make lint-javascript-files FIX=${fix} FAST_FAIL=0 FILES=\"${files_to_lint}\" ${make_args} ${eslint_conf_arg} ${eslint_flags}"

echo "Running: ${make_cmd}"
if ! eval "${make_cmd}"; then
if [ "${quiet}" -eq 0 ]; then
echo "Initial linting failed. Retrying with --quiet and logging errors..."
eval "make lint-javascript-files FIX=${fix} FAST_FAIL=0 FILES=\"${files_to_lint}\" ${make_args} ${eslint_conf_arg} ESLINT_FLAGS='--quiet'" >> "${err_file}" 2>&1
else
# Already running with --quiet, just log the error
echo "Linting failed for files." >> "${err_file}"
fi
error_occurred=1
fi
}


# MAIN #

# Initialize error file:
: > "${err_file}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is valid and a trick, but not typical. One would conventionally use touch here.


# Build native addons if present:
packages=$(echo "${files_to_lint}" | tr ' ' '\n' | grep '^lib/node_modules/' | sed 's/^lib\/node_modules\///g' | sed 's/\/lib\/.*//g' | sort | uniq)
for pkg in ${packages}; do
if [ -f "lib/node_modules/${pkg}/binding.gyp" ]; then
echo "Building native addon for ${pkg}..."
NODE_ADDONS_PATTERN="${pkg}" make install-node-addons
fi
done

# Lint JavaScript source files:
files=$(echo "${files_to_lint}" | tr ' ' '\n' | grep '\.js$' | grep -v -e '/examples' -e '/test' -e '/benchmark' -e '^dist/' | tr '\n' ' ' | sed 's/ $//')
if [ -n "${files}" ]; then
echo "Linting JavaScript source files..."
run_make_lint "${files}" "" ""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This parameterization of run_make_lint is a bit odd and not idiomatic. This isn't the end of the world, but the implementation could be cleaner.

fi

# Lint JavaScript command-line interfaces:
files=$(echo "${files_to_lint}" | tr ' ' '\n' | grep '\.js$' | grep -E '/bin/cli$' | tr '\n' ' ' | sed 's/ $//')
if [ -n "${files}" ]; then
echo "Linting JavaScript CLI files..."
run_make_lint "${files}" "" ""
fi

# Lint JavaScript example files:
files=$(echo "${files_to_lint}" | tr ' ' '\n' | grep '/examples/.*\.js$' | tr '\n' ' ' | sed 's/ $//')
if [ -n "${files}" ]; then
echo "Linting JavaScript example files..."
run_make_lint "${files}" "" "ESLINT_CONF=\"${eslint_examples_conf}\""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E.g., escaping environment variable strings, etc, is odd.

fi

# Lint JavaScript test files:
files=$(echo "${files_to_lint}" | tr ' ' '\n' | grep '/test/.*\.js$' | tr '\n' ' ' | sed 's/ $//')
if [ -n "${files}" ]; then
echo "Linting JavaScript test files..."
run_make_lint "${files}" "" "ESLINT_CONF=\"${eslint_tests_conf}\""
fi

# Lint JavaScript benchmark files:
files=$(echo "${files_to_lint}" | tr ' ' '\n' | grep '/benchmark/.*\.js$' | tr '\n' ' ' | sed 's/ $//')
if [ -n "${files}" ]; then
echo "Linting JavaScript benchmark files..."
run_make_lint "${files}" "" "ESLINT_CONF=\"${eslint_benchmarks_conf}\""
fi

# Report results:
if [ "${error_occurred}" -eq 1 ]; then
echo "JavaScript linting errors occurred. See details below:"
cat "${err_file}"

# Clean up temp file ONLY if it was auto-generated (ERR_FILE not provided):
if [ -z "${ERR_FILE}" ]; then
rm -f "${err_file}"
fi

exit 1
else
echo "JavaScript linting completed successfully."

# Clean up temp file ONLY if it was auto-generated (ERR_FILE not provided):
if [ -z "${ERR_FILE}" ]; then
rm -f "${err_file}"
fi

exit 0
fi
34 changes: 20 additions & 14 deletions tools/make/lib/lint/javascript/eslint.mk
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,29 @@ ESLINT_CONF_BENCHMARKS ?= $(CONFIG_DIR)/eslint/.eslintrc.benchmarks.js
ESLINT_IGNORE ?= $(ROOT_DIR)/.eslintignore

# Define the command-line options to use when invoking the ESLint executable:
ESLINT_FLAGS ?= \
eslint_flags := \
--ignore-path $(ESLINT_IGNORE) \
--report-unused-disable-directives

# Define user-supplied command-line options:
ESLINT_FLAGS ?=

ifeq ($(AUTOFIX),true)
ESLINT_FLAGS += --fix
eslint_flags += --fix
endif

FIX_TYPE ?=
ifneq ($(FIX_TYPE),)
ESLINT_FLAGS += --fix-type $(FIX_TYPE)
eslint_flags += --fix-type $(FIX_TYPE)
else
ifeq ($(AUTOFIX),true)
ESLINT_FLAGS += --fix-type problem,layout,directive
eslint_flags += --fix-type problem,layout,directive
endif
endif

# Append user-supplied command-line options:
eslint_flags += $(ESLINT_FLAGS)

# RULES #

#/
Expand All @@ -88,14 +94,14 @@ ifeq ($(FAIL_FAST), true)
$(QUIET) $(FIND_SOURCES_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r file; do \
echo ''; \
echo "Linting file: $$file"; \
$(ESLINT) $(ESLINT_FLAGS) --config $(ESLINT_CONF) $$file || exit 1; \
$(ESLINT) $(eslint_flags) --config $(ESLINT_CONF) $$file || exit 1; \
done
else
$(QUIET) status=0; \
$(FIND_SOURCES_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r file; do \
echo ''; \
echo "Linting file: $$file"; \
if ! $(ESLINT) $(ESLINT_FLAGS) --config $(ESLINT_CONF) $$file; then \
if ! $(ESLINT) $(eslint_flags) --config $(ESLINT_CONF) $$file; then \
echo 'Linting failed.'; \
status=1; \
fi; \
Expand Down Expand Up @@ -129,14 +135,14 @@ ifeq ($(FAIL_FAST), true)
$(QUIET) $(FIND_TESTS_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r file; do \
echo ''; \
echo "Linting file: $$file"; \
$(ESLINT) $(ESLINT_FLAGS) --config $(ESLINT_CONF_TESTS) $$file || exit 1; \
$(ESLINT) $(eslint_flags) --config $(ESLINT_CONF_TESTS) $$file || exit 1; \
done
else
$(QUIET) status=0; \
$(FIND_TESTS_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r file; do \
echo ''; \
echo "Linting file: $$file"; \
if ! $(ESLINT) $(ESLINT_FLAGS) --config $(ESLINT_CONF_TESTS) $$file; then \
if ! $(ESLINT) $(eslint_flags) --config $(ESLINT_CONF_TESTS) $$file; then \
echo 'Linting failed.'; \
status=1; \
fi; \
Expand Down Expand Up @@ -170,14 +176,14 @@ ifeq ($(FAIL_FAST), true)
$(QUIET) $(FIND_EXAMPLES_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r file; do \
echo ''; \
echo "Linting file: $$file"; \
$(ESLINT) $(ESLINT_FLAGS) --config $(ESLINT_CONF_EXAMPLES) $$file || exit 1; \
$(ESLINT) $(eslint_flags) --config $(ESLINT_CONF_EXAMPLES) $$file || exit 1; \
done
else
$(QUIET) status=0; \
$(FIND_EXAMPLES_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r file; do \
echo ''; \
echo "Linting file: $$file"; \
if ! $(ESLINT) $(ESLINT_FLAGS) --config $(ESLINT_CONF_EXAMPLES) $$file; then \
if ! $(ESLINT) $(eslint_flags) --config $(ESLINT_CONF_EXAMPLES) $$file; then \
echo 'Linting failed.'; \
status=1; \
fi; \
Expand Down Expand Up @@ -211,14 +217,14 @@ ifeq ($(FAIL_FAST), true)
$(QUIET) $(FIND_BENCHMARKS_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r file; do \
echo ''; \
echo "Linting file: $$file"; \
$(ESLINT) $(ESLINT_FLAGS) --config $(ESLINT_CONF_BENCHMARKS) $$file || exit 1; \
$(ESLINT) $(eslint_flags) --config $(ESLINT_CONF_BENCHMARKS) $$file || exit 1; \
done
else
$(QUIET) status=0; \
$(FIND_BENCHMARKS_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r file; do \
echo ''; \
echo "Linting file: $$file"; \
if ! $(ESLINT) $(ESLINT_FLAGS) --config $(ESLINT_CONF_BENCHMARKS) $$file; then \
if ! $(ESLINT) $(eslint_flags) --config $(ESLINT_CONF_BENCHMARKS) $$file; then \
echo 'Linting failed.'; \
status=1; \
fi; \
Expand Down Expand Up @@ -249,14 +255,14 @@ ifeq ($(FAIL_FAST), true)
$(QUIET) for file in $(FILES); do \
echo ''; \
echo "Linting file: $$file"; \
$(ESLINT) $(ESLINT_FLAGS) --config $(ESLINT_CONF) $$file || exit 1; \
$(ESLINT) $(eslint_flags) --config $(ESLINT_CONF) $$file || exit 1; \
done
else
$(QUIET) status=0; \
for file in $(FILES); do \
echo ''; \
echo "Linting file: $$file"; \
if ! $(ESLINT) $(ESLINT_FLAGS) --config $(ESLINT_CONF) $$file; then \
if ! $(ESLINT) $(eslint_flags) --config $(ESLINT_CONF) $$file; then \
echo 'Linting failed.'; \
status=1; \
fi; \
Expand Down
Loading