diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 84074b4791..8ec68a6e98 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -54,3 +54,16 @@ jobs: # (notably SC2155). Tighten in a follow-up after cleanup. - name: Run shellcheck on shell scripts run: git ls-files -z -- '*.sh' | xargs -0 shellcheck --severity=error + + # macOS ships bash 3.2, where bash 4+ case-modification parameter + # expansions error with "bad substitution". shellcheck assumes bash 4+ + # from the shebang and cannot flag these, so guard explicitly; use tr + # for portable case conversion. + - name: Reject bash 4+ case-modification expansions + run: | + matches=$(git ls-files -z -- '*.sh' | xargs -0 grep -nE '\$\{[A-Za-z_][A-Za-z0-9_]*(\[[^]]*\])?(\^\^?|,,?|~~?|@[UuLl])[^}]*\}' || true) + if [ -n "$matches" ]; then + echo "Found bash 4+ case-modification expansion(s); use tr for portability (macOS ships bash 3.2):" + echo "$matches" + exit 1 + fi diff --git a/extensions/git/scripts/bash/create-new-feature-branch.sh b/extensions/git/scripts/bash/create-new-feature-branch.sh index a13e49812c..c6e4e0668f 100755 --- a/extensions/git/scripts/bash/create-new-feature-branch.sh +++ b/extensions/git/scripts/bash/create-new-feature-branch.sh @@ -280,7 +280,7 @@ generate_branch_name() { local stop_words="^(i|a|an|the|to|for|of|in|on|at|by|with|from|is|are|was|were|be|been|being|have|has|had|do|does|did|will|would|should|could|can|may|might|must|shall|this|that|these|those|my|your|our|their|want|need|add|get|set)$" - local clean_name=$(echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g') + local clean_name=$(printf '%s' "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g') local meaningful_words=() for word in $clean_name; do @@ -288,6 +288,8 @@ generate_branch_name() { if ! echo "$word" | grep -qiE "$stop_words"; then if [ ${#word} -ge 3 ]; then meaningful_words+=("$word") + # Uppercase via tr (portable) rather than bash's 4+ "^^" case + # expansion, which breaks on macOS's default bash 3.2 (bad substitution). elif printf '%s' "$description" | grep -qw -- "$(printf '%s' "$word" | tr '[:lower:]' '[:upper:]')"; then meaningful_words+=("$word") fi diff --git a/extensions/git/scripts/powershell/create-new-feature-branch.ps1 b/extensions/git/scripts/powershell/create-new-feature-branch.ps1 index 597bdf40d2..0439ec80ad 100644 --- a/extensions/git/scripts/powershell/create-new-feature-branch.ps1 +++ b/extensions/git/scripts/powershell/create-new-feature-branch.ps1 @@ -253,9 +253,10 @@ function Get-BranchName { if ($word.Length -ge 3) { $meaningfulWords += $word } elseif ($Description -cmatch "\b$($word.ToUpper())\b") { - # Case-sensitive (-cmatch) to mirror the bash twin's `grep -qw -- "${word^^}"`: - # keep a short word only when its UPPERCASE form appears in the original - # (an acronym). -match is case-insensitive and would keep every short word. + # Case-sensitive (-cmatch) to mirror the bash twin's case-sensitive + # whole-word acronym match: keep a short word only when its UPPERCASE + # form appears in the original (an acronym). -match is case-insensitive + # and would keep every short word. $meaningfulWords += $word } } diff --git a/scripts/bash/create-new-feature.sh b/scripts/bash/create-new-feature.sh index 746e88a2dc..3cffce8602 100644 --- a/scripts/bash/create-new-feature.sh +++ b/scripts/bash/create-new-feature.sh @@ -140,7 +140,7 @@ generate_branch_name() { local stop_words="^(i|a|an|the|to|for|of|in|on|at|by|with|from|is|are|was|were|be|been|being|have|has|had|do|does|did|will|would|should|could|can|may|might|must|shall|this|that|these|those|my|your|our|their|want|need|add|get|set)$" # Convert to lowercase and split into words - local clean_name=$(echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g') + local clean_name=$(printf '%s' "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g') # Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original) local meaningful_words=() @@ -152,8 +152,10 @@ generate_branch_name() { if ! echo "$word" | grep -qiE "$stop_words"; then if [ ${#word} -ge 3 ]; then meaningful_words+=("$word") + # Keep short words that appear as an uppercase acronym in the original. + # Uppercase via tr and match with grep -w (both portable) rather than + # bash's 4+ "^^" case expansion (breaks on macOS bash 3.2) and \b (non-POSIX). elif printf '%s' "$description" | grep -qw -- "$(printf '%s' "$word" | tr '[:lower:]' '[:upper:]')"; then - # Keep short words if they appear as uppercase in original (likely acronyms) meaningful_words+=("$word") fi fi