Skip to content

Commit 0240a08

Browse files
committed
Fix and test for missing semicolons in shell hooks
A missing semicolon on the line `local IFS=` was breaking the hook after it was collapsed to one line for eval. This fixes all missing semicolons and adds a test to catch this in future.
1 parent 0c0b1b3 commit 0240a08

File tree

2 files changed

+64
-11
lines changed

2 files changed

+64
-11
lines changed

src/HookFactory.php

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,16 @@ function %%function_name%% {
3333
3434
# Copy BASH's completion variables to the ones the completion command expects
3535
# These line up exactly as the library was originally designed for BASH
36-
local CMDLINE_CONTENTS="$COMP_LINE"
37-
local CMDLINE_CURSOR_INDEX="$COMP_POINT"
36+
local CMDLINE_CONTENTS="$COMP_LINE";
37+
local CMDLINE_CURSOR_INDEX="$COMP_POINT";
3838
local CMDLINE_WORDBREAKS="$COMP_WORDBREAKS";
3939
40-
export CMDLINE_CONTENTS CMDLINE_CURSOR_INDEX CMDLINE_WORDBREAKS
40+
export CMDLINE_CONTENTS CMDLINE_CURSOR_INDEX CMDLINE_WORDBREAKS;
4141
4242
local RESULT STATUS;
4343
4444
# Force splitting by newline instead of default delimiters
45-
local IFS=$'\n'
45+
local IFS=$'\n';
4646
4747
RESULT="$(%%completion_command%% </dev/null)";
4848
STATUS=$?;
@@ -78,19 +78,19 @@ function %%function_name%% {
7878
else
7979
>&2 echo "Completion was not registered for %%program_name%%:";
8080
>&2 echo "The 'bash-completion' package is required but doesn't appear to be installed.";
81-
fi
81+
fi;
8282
END
8383

8484
// ZSH Hook
8585
, 'zsh' => <<<'END'
8686
# ZSH completion for %%program_path%%
8787
function %%function_name%% {
88-
local -x CMDLINE_CONTENTS="$words"
89-
local -x CMDLINE_CURSOR_INDEX
90-
(( CMDLINE_CURSOR_INDEX = ${#${(j. .)words[1,CURRENT]}} ))
88+
local -x CMDLINE_CONTENTS="$words";
89+
local -x CMDLINE_CURSOR_INDEX;
90+
(( CMDLINE_CURSOR_INDEX = ${#${(j. .)words[1,CURRENT]}} ));
9191
92-
local RESULT STATUS
93-
RESULT=("${(@f)$( %%completion_command%% )}")
92+
local RESULT STATUS;
93+
RESULT=("${(@f)$( %%completion_command%% )}");
9494
STATUS=$?;
9595
9696
# Check if shell provided path completion is requested
@@ -105,7 +105,7 @@ function %%function_name%% {
105105
return $?;
106106
fi;
107107
108-
compadd -- $RESULT
108+
compadd -- $RESULT;
109109
};
110110
111111
compdef %%function_name%% "%%program_name%%";

tests/Stecman/Component/Symfony/Console/BashCompletion/HookFactoryTest.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,59 @@ public function generateHookDataProvider()
5454
);
5555
}
5656

57+
public function testForMissingSemiColons()
58+
{
59+
$class = new \ReflectionClass('Stecman\Component\Symfony\Console\BashCompletion\HookFactory');
60+
$properties = $class->getStaticProperties();
61+
$hooks = $properties['hooks'];
62+
63+
// Check each line is commented or closed correctly to be collapsed for eval
64+
foreach ($hooks as $shellType => $hook) {
65+
$line = strtok($hook, "\n");
66+
$lineNumber = 0;
67+
68+
while ($line !== false) {
69+
$lineNumber++;
70+
71+
if (!$this->isScriptLineValid($line)) {
72+
$this->fail("$shellType hook appears to be missing a semicolon on line $lineNumber:\n> $line");
73+
}
74+
75+
$line = strtok("\n");
76+
}
77+
}
78+
}
79+
80+
/**
81+
* Check if a line of shell script is safe to be collapsed to one line for eval
82+
*/
83+
protected function isScriptLineValid($line)
84+
{
85+
if (preg_match('/^\s*#/', $line)) {
86+
// Line is commented out
87+
return true;
88+
}
89+
90+
if (preg_match('/[;\{\}]\s*$/', $line)) {
91+
// Line correctly ends with a semicolon or syntax
92+
return true;
93+
}
94+
95+
if (preg_match('
96+
/(
97+
;\s*then |
98+
\s*else
99+
)
100+
\s*$
101+
/x', $line)
102+
) {
103+
// Line ends with another permitted sequence
104+
return true;
105+
}
106+
107+
return false;
108+
}
109+
57110
protected function hasProgram($programName)
58111
{
59112
exec(sprintf(

0 commit comments

Comments
 (0)