@@ -10,322 +10,48 @@ paths:
1010
1111bashunit must work on ** Bash 3.0+** (macOS default). These features are ** prohibited** :
1212
13- ### ❌ Forbidden Features
13+ | Feature | Bash ver | Alternative |
14+ | ---------| ----------| -------------|
15+ | ` declare -A ` (associative arrays) | 4.0+ | Parallel indexed arrays |
16+ | ` [[ ]] ` (test operator) | — | ` [ ] ` with ` = ` not ` == ` |
17+ | ` ${var,,} ` / ` ${var^^} ` (case) | 4.0+ | ` tr '[:upper:]' '[:lower:]' ` |
18+ | ` ${array[-1]} ` (negative index) | 4.3+ | ` ${array[${#array[@]}-1]} ` |
19+ | ` &>> ` (append both) | 4.0+ | ` >> file 2>&1 ` |
1420
15- ** Associative arrays** (Bash 4.0+):
16- ``` bash
17- # ❌ DON'T
18- declare -A map
19- map[" key" ]=" value"
20-
21- # ✅ DO - Use indexed arrays or workarounds
22- declare -a keys=(" key1" " key2" )
23- declare -a values=(" val1" " val2" )
24- ```
25-
26- ** ` [[ ` test operator** - Use ` [ ` instead:
27- ``` bash
28- # ❌ DON'T
29- if [[ " $var " == " value" ]]; then
30-
31- # ✅ DO
32- if [ " $var " = " value" ]; then
33- ` ` `
34-
35- ** Case conversion** (` ${var,,} ` , ` ${var^^} ` ):
36- ` ` ` bash
37- # ❌ DON'T
38- lowercase=" ${var,,} "
39-
40- # ✅ DO
41- lowercase=$( echo " $var " | tr ' [:upper:]' ' [:lower:]' )
42- ` ` `
43-
44- ** Negative array indexing** (` ${array[-1]} ` ):
45- ` ` ` bash
46- # ❌ DON'T
47- last=" ${array[-1]} "
48-
49- # ✅ DO
50- last=" ${array[${#array[@]} -1]}"
51- ` ` `
52-
53- ** ` & >> ` redirect** (Bash 4.0+):
54- ` ` ` bash
55- # ❌ DON'T
56- command & >> file
57-
58- # ✅ DO
59- command >> file 2>&1
60- ` ` `
61-
62- # # Coding Style
63-
64- Follow [Google Shell Style Guide](https://google.github.io/styleguide/shellguide.html) with these specifics:
65-
66- # ## Indentation & Formatting
67-
68- - ** 2 spaces** (no tabs)
69- - Use ` shfmt -w .` to format
70- - Maximum line length: 120 characters (soft limit)
71-
72- # ## Function Naming
73-
74- ** Namespace all public functions:**
75- ` ` ` bash
76- # ✅ Public functions
77- function bashunit::assert_equals() { ... }
78- function bashunit::mock() { ... }
79-
80- # ✅ Private/internal functions (leading underscore)
81- function _internal_helper() { ... }
82- ` ` `
83-
84- # ## Variable Naming
85-
86- ` ` ` bash
87- # ✅ Local variables - lowercase with underscores
88- local test_name=" example"
89- local file_path=" /path/to/file"
90-
91- # ✅ Global/exported - uppercase
92- export BASHUNIT_LOG_JUNIT=" false"
93- readonly BASHUNIT_VERSION=" 0.32.0"
94-
95- # ✅ Function parameters - clear names
96- function process_file() {
97- local input_file=" $1 "
98- local output_dir=" ${2:- ./ output} "
99- }
100- ` ` `
21+ ## Coding Conventions
10122
102- # ## Quoting
23+ - ** 2 spaces** indent, no tabs — enforced by ` shfmt -w . `
24+ - ** 120 chars** max line length (soft)
25+ - Follow [ Google Shell Style Guide] ( https://google.github.io/styleguide/shellguide.html )
26+ - Always quote variables unless explicit word splitting is needed
27+ - Use ` $() ` for command substitution, never backticks
10328
104- ** Always quote variables ** unless you explicitly need word splitting:
29+ ### Naming
10530
106- ` ` ` bash
107- # ✅ DO
108- echo " $variable "
109- [[ -f " $file_path " ]]
110- command --arg=" $value "
111-
112- # ❌ DON'T (unless intentional word splitting)
113- echo $variable
114- [[ -f $file_path ]]
115- ` ` `
116-
117- # ## Error Handling
31+ - ** Public functions:** ` bashunit::function_name `
32+ - ** Private functions:** ` _function_name ` (leading underscore)
33+ - ** Local variables:** ` lowercase_with_underscores `
34+ - ** Globals/exports:** ` UPPERCASE_WITH_UNDERSCORES `
11835
119- Use ` set -euo pipefail` judiciously:
120-
121- ` ` ` bash
122- # ✅ In scripts
123- #! /usr/bin/env bash
124- set -euo pipefail
125-
126- # ⚠️ In functions - be cautious
127- # Don't use in functions that expect to handle failures
128- function might_fail() {
129- local result
130- result=$( command_that_might_fail) || return 1
131- echo " $result "
132- }
133- ` ` `
134-
135- # ## Function Documentation
136-
137- Document all public functions:
36+ ### Function Docs (public functions)
13837
13938``` bash
14039# #
141- # Brief description of what the function does
142- #
143- # Arguments:
144- # $1 - First parameter description
145- # $2 - Second parameter description (optional, default: "value")
146- #
147- # Returns:
148- # 0 on success
149- # 1 on validation failure
150- # 2 on execution error
151- #
152- # Example:
153- # bashunit::my_function "input" "optional"
40+ # Brief description
41+ # Arguments: $1 - desc, $2 - desc (optional, default: "x")
42+ # Returns: 0 success, 1 failure
15443# #
155- function bashunit::my_function() {
156- local required=" $1 "
157- local optional=" ${2:- default} "
158-
159- # Implementation
160- }
16144```
16245
163- # # ShellCheck Compliance
164-
165- All code must pass ShellCheck:
166-
167- ` ` ` bash
168- make sa
169- # or
170- shellcheck -x $( find . -name " *.sh" )
171- ` ` `
172-
173- ** Common directives:**
174-
175- ` ` ` bash
176- # Disable specific check with reason
177- # shellcheck disable=SC2034 # Variable appears unused
178- local unused_var=" value"
179-
180- # Source external file for shellcheck
181- # shellcheck source=src/assertions.sh
182- source " $( dirname " ${BASH_SOURCE[0]} " ) /assertions.sh"
183- ` ` `
184-
185- # # Portability
186-
187- # ## Path Handling
188-
189- ` ` ` bash
190- # ✅ Use BASH_SOURCE for relative paths
191- script_dir=" $( cd " $( dirname " ${BASH_SOURCE[0]} " ) " && pwd) "
192-
193- # ✅ Use dirname/basename
194- parent_dir=" $( dirname " $file_path " ) "
195- filename=" $( basename " $file_path " ) "
196- ` ` `
197-
198- # ## Command Substitution
199-
200- ` ` ` bash
201- # ✅ Use $() instead of backticks
202- result=$( command arg1 arg2)
203-
204- # ❌ DON'T
205- result=` command arg1 arg2`
206- ` ` `
207-
208- # ## Temporary Files
209-
210- ` ` ` bash
211- # ✅ Use bashunit globals
212- echo " content" > " $temp_file "
213- mkdir -p " $temp_dir "
214-
215- # ⚠️ If creating manually, ensure cleanup
216- cleanup () {
217- rm -rf " $temp_file "
218- }
219- trap cleanup EXIT
220- ` ` `
221-
222- # # Performance Considerations
223-
224- # ## Avoid Subshells When Possible
225-
226- ` ` ` bash
227- # ✅ Better
228- local count=0
229- while read -r line; do
230- (( count++ ))
231- done < file
232-
233- # ⚠️ Slower (creates subshell)
234- local count
235- count=$( wc -l < file)
236- ` ` `
237-
238- # ## Use Built-ins Over External Commands
239-
240- ` ` ` bash
241- # ✅ Built-in
242- [[ -f " $file " ]] && echo " exists"
243-
244- # ⚠️ External command (slower)
245- test -f " $file " && echo " exists"
246- ` ` `
247-
248- # # Code Organization
249-
25046### File Structure
25147
252- ` ` ` bash
253- #! /usr/bin/env bash
254- # Brief file description
255-
256- # Constants
257- readonly CONSTANT_VALUE=" value"
258-
259- # Global variables
260- declare -g global_var=" "
261-
262- # Private functions
263- function _private_helper() { ... }
264-
265- # Public functions
266- function bashunit::public_function() { ... }
267- ` ` `
268-
269- # ## Sourcing Dependencies
270-
271- ` ` ` bash
272- # ✅ Relative to script location
273- source " $( dirname " ${BASH_SOURCE[0]} " ) /dependency.sh"
274-
275- # ✅ With error checking
276- if [[ ! -f " $dependency_path " ]]; then
277- echo " Error: Cannot find dependency" >&2
278- return 1
279- fi
280- source " $dependency_path "
281- ` ` `
282-
283- # # Security
284-
285- # ## Input Validation
286-
287- ` ` ` bash
288- # ✅ Validate inputs
289- function process_user_input() {
290- local input=" $1 "
291-
292- if [[ -z " $input " ]]; then
293- echo " Error: Input required" >&2
294- return 1
295- fi
296-
297- # Process sanitized input
298- }
299- ` ` `
300-
301- # ## Safe File Operations
302-
303- ` ` ` bash
304- # ✅ Check before operations
305- if [[ -w " $file " ]]; then
306- echo " data" > " $file "
307- fi
308-
309- # ✅ Use -- to prevent flag injection
310- rm -- " $user_provided_file "
311- ` ` `
312-
313- # # Anti-Patterns to Avoid
48+ Constants -> Globals -> Private functions -> Public functions
31449
315- ❌ ** Global state without cleanup**
316- ❌ ** Unquoted variables**
317- ❌ ** Ignoring command failures silently**
318- ❌ ** Using eval without sanitization**
319- ❌ ** Hardcoded paths** (use relative or configurable)
320- ❌ ** Functions > 50 lines** (refactor into smaller pieces)
321- ❌ ** Deep nesting** (> 3 levels, extract functions)
50+ Source deps relative to script: ` "$(dirname "${BASH_SOURCE[0]}")/dep.sh" `
32251
323- # # Validation
52+ ## ShellCheck
32453
325- Before committing :
54+ All code must pass ` make sa ` . Use directives sparingly with reason :
32655``` bash
327- make sa # ShellCheck
328- make lint # EditorConfig
329- shfmt -w . # Format
330- ./bashunit tests/ # All tests pass
56+ # shellcheck disable=SC2034 # Variable used by caller
33157```
0 commit comments