|
1 | 1 | #!/bin/bash |
2 | 2 |
|
3 | 3 | # Generate a markdown document listing all hook data issues |
4 | | -# Uses the npm show:* scripts to gather data |
5 | 4 |
|
6 | 5 | OUTPUT_FILE="ISSUES.md" |
7 | 6 |
|
| 7 | +# Helper function to run a jq query on both actions and filters, combine and dedupe results |
| 8 | +run_check() { |
| 9 | + local query="$1" |
| 10 | + local actions_result filters_result |
| 11 | + actions_result=$(jq -r "$query" hooks/actions.json 2>/dev/null) |
| 12 | + filters_result=$(jq -r "$query" hooks/filters.json 2>/dev/null) |
| 13 | + echo -e "${actions_result}\n${filters_result}" | grep -v '^$' | sort -u |
| 14 | +} |
| 15 | + |
| 16 | +# Helper function to write a section to the output file |
| 17 | +write_section() { |
| 18 | + local title="$1" |
| 19 | + local description="$2" |
| 20 | + local empty_message="$3" |
| 21 | + local results="$4" |
| 22 | + |
| 23 | + echo "## $title" >> "$OUTPUT_FILE" |
| 24 | + echo "" >> "$OUTPUT_FILE" |
| 25 | + if [ -n "$description" ]; then |
| 26 | + echo "$description" >> "$OUTPUT_FILE" |
| 27 | + echo "" >> "$OUTPUT_FILE" |
| 28 | + fi |
| 29 | + if [ -n "$results" ]; then |
| 30 | + echo "$results" >> "$OUTPUT_FILE" |
| 31 | + else |
| 32 | + echo "_${empty_message}_" >> "$OUTPUT_FILE" |
| 33 | + fi |
| 34 | + echo "" >> "$OUTPUT_FILE" |
| 35 | +} |
| 36 | + |
8 | 37 | echo "# WordPress Hooks Data Issues" > "$OUTPUT_FILE" |
9 | 38 | echo "" >> "$OUTPUT_FILE" |
10 | 39 | echo "This document lists known data quality issues in the hooks JSON files." >> "$OUTPUT_FILE" |
11 | 40 | echo "" >> "$OUTPUT_FILE" |
12 | 41 |
|
13 | 42 | # Duplicate Actions |
14 | | -echo "## Duplicate Actions" >> "$OUTPUT_FILE" |
15 | | -echo "" >> "$OUTPUT_FILE" |
16 | | - |
17 | | -DUPLICATE_ACTIONS=$(jq -r '[reduce .hooks[].name as $n ({}; .[$n] += 1) | to_entries[] | select(.value > 1) | .key] | .[]' hooks/actions.json) |
18 | | - |
19 | | -if [ -n "$DUPLICATE_ACTIONS" ]; then |
20 | | - echo "$DUPLICATE_ACTIONS" | while read -r name; do |
21 | | - echo "- \`$name\`" >> "$OUTPUT_FILE" |
22 | | - done |
23 | | -else |
24 | | - echo "_No duplicate actions._" >> "$OUTPUT_FILE" |
25 | | -fi |
26 | | - |
27 | | -echo "" >> "$OUTPUT_FILE" |
| 43 | +DUPLICATE_ACTIONS=$(jq -r '[reduce .hooks[].name as $n ({}; .[$n] += 1) | to_entries[] | select(.value > 1) | .key] | .[] | "- `\(.)`"' hooks/actions.json) |
| 44 | +write_section "Duplicate Actions" "" "No duplicate actions." "$DUPLICATE_ACTIONS" |
28 | 45 |
|
29 | 46 | # Duplicate Filters |
30 | | -echo "## Duplicate Filters" >> "$OUTPUT_FILE" |
31 | | -echo "" >> "$OUTPUT_FILE" |
32 | | - |
33 | | -DUPLICATE_FILTERS=$(jq -r '[reduce .hooks[].name as $n ({}; .[$n] += 1) | to_entries[] | select(.value > 1) | .key] | .[]' hooks/filters.json) |
34 | | - |
35 | | -if [ -n "$DUPLICATE_FILTERS" ]; then |
36 | | - echo "$DUPLICATE_FILTERS" | while read -r name; do |
37 | | - echo "- \`$name\`" >> "$OUTPUT_FILE" |
38 | | - done |
39 | | -else |
40 | | - echo "_No duplicate filters._" >> "$OUTPUT_FILE" |
41 | | -fi |
42 | | - |
43 | | -echo "" >> "$OUTPUT_FILE" |
| 47 | +DUPLICATE_FILTERS=$(jq -r '[reduce .hooks[].name as $n ({}; .[$n] += 1) | to_entries[] | select(.value > 1) | .key] | .[] | "- `\(.)`"' hooks/filters.json) |
| 48 | +write_section "Duplicate Filters" "" "No duplicate filters." "$DUPLICATE_FILTERS" |
44 | 49 |
|
45 | 50 | # Missing @since Tags |
46 | | -echo "## Missing @since Tags" >> "$OUTPUT_FILE" |
47 | | -echo "" >> "$OUTPUT_FILE" |
48 | | - |
49 | | -MISSING_SINCE_ACTIONS=$(jq -r '.hooks[] | select(.doc.tags | map(.name) | contains(["since"]) | not) | "- `\(.name)` (\(.file))"' hooks/actions.json) |
50 | | -MISSING_SINCE_FILTERS=$(jq -r '.hooks[] | select(.doc.tags | map(.name) | contains(["since"]) | not) | "- `\(.name)` (\(.file))"' hooks/filters.json) |
51 | | - |
52 | | -MISSING_SINCE=$(echo -e "${MISSING_SINCE_ACTIONS}\n${MISSING_SINCE_FILTERS}" | grep -v '^$' | sort) |
53 | | - |
54 | | -if [ -n "$MISSING_SINCE" ]; then |
55 | | - echo "$MISSING_SINCE" >> "$OUTPUT_FILE" |
56 | | -else |
57 | | - echo "_No hooks with missing @since tags._" >> "$OUTPUT_FILE" |
58 | | -fi |
59 | | - |
60 | | -echo "" >> "$OUTPUT_FILE" |
| 51 | +MISSING_SINCE=$(run_check '.hooks[] | select(.doc.tags | map(.name) | contains(["since"]) | not) | "- `\(.name)` (\(.file))"') |
| 52 | +write_section "Missing @since Tags" "" "No hooks with missing @since tags." "$MISSING_SINCE" |
61 | 53 |
|
62 | 54 | # Empty Descriptions |
63 | | -echo "## Empty Descriptions" >> "$OUTPUT_FILE" |
64 | | -echo "" >> "$OUTPUT_FILE" |
65 | | - |
66 | | -EMPTY_DESC_ACTIONS=$(jq -r '.hooks[] | select(.doc.description == "" or .doc.description == null) | "- `\(.name)` (\(.file))"' hooks/actions.json) |
67 | | -EMPTY_DESC_FILTERS=$(jq -r '.hooks[] | select(.doc.description == "" or .doc.description == null) | "- `\(.name)` (\(.file))"' hooks/filters.json) |
68 | | - |
69 | | -EMPTY_DESC=$(echo -e "${EMPTY_DESC_ACTIONS}\n${EMPTY_DESC_FILTERS}" | grep -v '^$' | sort) |
70 | | - |
71 | | -if [ -n "$EMPTY_DESC" ]; then |
72 | | - echo "$EMPTY_DESC" >> "$OUTPUT_FILE" |
73 | | -else |
74 | | - echo "_No hooks with empty descriptions._" >> "$OUTPUT_FILE" |
75 | | -fi |
76 | | - |
77 | | -echo "" >> "$OUTPUT_FILE" |
| 55 | +EMPTY_DESC=$(run_check '.hooks[] | select(.doc.description == "" or .doc.description == null) | "- `\(.name)` (\(.file))"') |
| 56 | +write_section "Empty Descriptions" "" "No hooks with empty descriptions." "$EMPTY_DESC" |
78 | 57 |
|
79 | 58 | # Empty Variables |
80 | | -echo "## Empty Variables" >> "$OUTPUT_FILE" |
81 | | -echo "" >> "$OUTPUT_FILE" |
82 | | -echo "Hooks with \`@param\` tags that have an empty variable name." >> "$OUTPUT_FILE" |
83 | | -echo "" >> "$OUTPUT_FILE" |
84 | | - |
85 | | -EMPTY_VARS_ACTIONS=$(jq -r '.hooks[] | select(.doc.tags[] | select(.name == "param" and .variable == "")) | "- `\(.name)` (\(.file))"' hooks/actions.json 2>/dev/null | sort -u) |
86 | | -EMPTY_VARS_FILTERS=$(jq -r '.hooks[] | select(.doc.tags[] | select(.name == "param" and .variable == "")) | "- `\(.name)` (\(.file))"' hooks/filters.json 2>/dev/null | sort -u) |
87 | | - |
88 | | -EMPTY_VARS=$(echo -e "${EMPTY_VARS_ACTIONS}\n${EMPTY_VARS_FILTERS}" | grep -v '^$' | sort -u) |
89 | | - |
90 | | -if [ -n "$EMPTY_VARS" ]; then |
91 | | - echo "$EMPTY_VARS" >> "$OUTPUT_FILE" |
92 | | -else |
93 | | - echo "_No hooks with empty variables._" >> "$OUTPUT_FILE" |
94 | | -fi |
95 | | - |
96 | | -echo "" >> "$OUTPUT_FILE" |
| 59 | +EMPTY_VARS=$(run_check '.hooks[] | select(.doc.tags[] | select(.name == "param" and .variable == "")) | "- `\(.name)` (\(.file))"') |
| 60 | +write_section "Empty Variables" "Hooks with \`@param\` tags that have an empty variable name." "No hooks with empty variables." "$EMPTY_VARS" |
97 | 61 |
|
98 | 62 | # Invalid Variable Names |
99 | | -echo "## Invalid Variable Names" >> "$OUTPUT_FILE" |
100 | | -echo "" >> "$OUTPUT_FILE" |
101 | | -echo "Hooks with \`@param\` tags where the variable is not a valid PHP variable name." >> "$OUTPUT_FILE" |
102 | | -echo "" >> "$OUTPUT_FILE" |
103 | | - |
104 | 63 | # Valid variable names match $[a-zA-Z_][a-zA-Z0-9_]* but not $this |
105 | | -INVALID_VAR_NAMES_ACTIONS=$(jq -r '.hooks[] | .name as $name | .file as $file | .doc.tags[] | select(.name == "param" and .variable != "" and .variable != null and ((.variable | test("^\\$[a-zA-Z_][a-zA-Z0-9_]*$") | not) or .variable == "$this")) | "- `\($name)` — \(.variable) (\($file))"' hooks/actions.json 2>/dev/null | sort -u) |
106 | | -INVALID_VAR_NAMES_FILTERS=$(jq -r '.hooks[] | .name as $name | .file as $file | .doc.tags[] | select(.name == "param" and .variable != "" and .variable != null and ((.variable | test("^\\$[a-zA-Z_][a-zA-Z0-9_]*$") | not) or .variable == "$this")) | "- `\($name)` — \(.variable) (\($file))"' hooks/filters.json 2>/dev/null | sort -u) |
107 | | - |
108 | | -INVALID_VAR_NAMES=$(echo -e "${INVALID_VAR_NAMES_ACTIONS}\n${INVALID_VAR_NAMES_FILTERS}" | grep -v '^$' | sort -u) |
109 | | - |
110 | | -if [ -n "$INVALID_VAR_NAMES" ]; then |
111 | | - echo "$INVALID_VAR_NAMES" >> "$OUTPUT_FILE" |
112 | | -else |
113 | | - echo "_No hooks with invalid variable names._" >> "$OUTPUT_FILE" |
114 | | -fi |
115 | | - |
116 | | -echo "" >> "$OUTPUT_FILE" |
| 64 | +INVALID_VAR_NAMES=$(run_check '.hooks[] | .name as $name | .file as $file | .doc.tags[] | select(.name == "param" and .variable != "" and .variable != null and ((.variable | test("^\\$[a-zA-Z_][a-zA-Z0-9_]*$") | not) or .variable == "$this")) | "- `\($name)` — \(.variable) (\($file))"') |
| 65 | +write_section "Invalid Variable Names" "Hooks with \`@param\` tags where the variable is not a valid PHP variable name." "No hooks with invalid variable names." "$INVALID_VAR_NAMES" |
117 | 66 |
|
118 | 67 | # Empty Param Descriptions |
119 | | -echo "## Empty Param Descriptions" >> "$OUTPUT_FILE" |
120 | | -echo "" >> "$OUTPUT_FILE" |
121 | | -echo "Hooks with \`@param\` tags that have an empty description." >> "$OUTPUT_FILE" |
122 | | -echo "" >> "$OUTPUT_FILE" |
123 | | - |
124 | | -EMPTY_PARAM_DESC_ACTIONS=$(jq -r '.hooks[] | .name as $name | .file as $file | .doc.tags[] | select(.name == "param" and (.content == "" or .content == null)) | "- `\($name)` — \(.variable) (\($file))"' hooks/actions.json 2>/dev/null | sort -u) |
125 | | -EMPTY_PARAM_DESC_FILTERS=$(jq -r '.hooks[] | .name as $name | .file as $file | .doc.tags[] | select(.name == "param" and (.content == "" or .content == null)) | "- `\($name)` — \(.variable) (\($file))"' hooks/filters.json 2>/dev/null | sort -u) |
126 | | - |
127 | | -EMPTY_PARAM_DESC=$(echo -e "${EMPTY_PARAM_DESC_ACTIONS}\n${EMPTY_PARAM_DESC_FILTERS}" | grep -v '^$' | sort -u) |
128 | | - |
129 | | -if [ -n "$EMPTY_PARAM_DESC" ]; then |
130 | | - echo "$EMPTY_PARAM_DESC" >> "$OUTPUT_FILE" |
131 | | -else |
132 | | - echo "_No hooks with empty param descriptions._" >> "$OUTPUT_FILE" |
133 | | -fi |
134 | | - |
135 | | -echo "" >> "$OUTPUT_FILE" |
| 68 | +EMPTY_PARAM_DESC=$(run_check '.hooks[] | .name as $name | .file as $file | .doc.tags[] | select(.name == "param" and (.content == "" or .content == null)) | "- `\($name)` — \(.variable) (\($file))"') |
| 69 | +write_section "Empty Param Descriptions" "Hooks with \`@param\` tags that have an empty description." "No hooks with empty param descriptions." "$EMPTY_PARAM_DESC" |
136 | 70 |
|
137 | 71 | # Missing Original Since |
138 | | -echo "## Missing Original Since" >> "$OUTPUT_FILE" |
139 | | -echo "" >> "$OUTPUT_FILE" |
140 | | -echo "Hooks where all \`@since\` tags have descriptions, indicating the original version is missing." >> "$OUTPUT_FILE" |
141 | | -echo "" >> "$OUTPUT_FILE" |
142 | | - |
143 | 72 | # Exclude hooks with @since MU (3.0.0) as this indicates the original multisite version |
144 | | -MISSING_ORIG_SINCE_ACTIONS=$(jq -r '.hooks[] | select((.doc.tags | map(select(.name == "since" and .description == "MU (3.0.0)")) | length == 0) and (.doc.tags | map(select(.name == "since")) | length > 0) and (.doc.tags | map(select(.name == "since" and (.description == null or .description == ""))) | length == 0)) | "- `\(.name)` (\(.file))"' hooks/actions.json 2>/dev/null | sort -u) |
145 | | -MISSING_ORIG_SINCE_FILTERS=$(jq -r '.hooks[] | select((.doc.tags | map(select(.name == "since" and .description == "MU (3.0.0)")) | length == 0) and (.doc.tags | map(select(.name == "since")) | length > 0) and (.doc.tags | map(select(.name == "since" and (.description == null or .description == ""))) | length == 0)) | "- `\(.name)` (\(.file))"' hooks/filters.json 2>/dev/null | sort -u) |
146 | | - |
147 | | -MISSING_ORIG_SINCE=$(echo -e "${MISSING_ORIG_SINCE_ACTIONS}\n${MISSING_ORIG_SINCE_FILTERS}" | grep -v '^$' | sort -u) |
148 | | - |
149 | | -if [ -n "$MISSING_ORIG_SINCE" ]; then |
150 | | - echo "$MISSING_ORIG_SINCE" >> "$OUTPUT_FILE" |
151 | | -else |
152 | | - echo "_No hooks with missing original since version._" >> "$OUTPUT_FILE" |
153 | | -fi |
154 | | - |
155 | | -echo "" >> "$OUTPUT_FILE" |
| 73 | +MISSING_ORIG_SINCE=$(run_check '.hooks[] | select((.doc.tags | map(select(.name == "since" and .description == "MU (3.0.0)")) | length == 0) and (.doc.tags | map(select(.name == "since")) | length > 0) and (.doc.tags | map(select(.name == "since" and (.description == null or .description == ""))) | length == 0)) | "- `\(.name)` (\(.file))"') |
| 74 | +write_section "Missing Original Since" "Hooks where all \`@since\` tags have descriptions, indicating the original version is missing." "No hooks with missing original since version." "$MISSING_ORIG_SINCE" |
156 | 75 |
|
157 | 76 | # Param Count Mismatches |
158 | | -echo "## Param Count Mismatches" >> "$OUTPUT_FILE" |
159 | | -echo "" >> "$OUTPUT_FILE" |
160 | | -echo "Hooks where the number of \`@param\` tags does not match the \`args\` count." >> "$OUTPUT_FILE" |
161 | | -echo "" >> "$OUTPUT_FILE" |
162 | | - |
163 | 77 | # Exclude action_reference and filter_reference types as they use ref_array functions which always report args as 1 |
164 | | -PARAM_MISMATCH_ACTIONS=$(jq -r '.hooks[] | select(.type != "action_reference" and .type != "filter_reference") | select(.args != ([.doc.tags[] | select(.name == "param")] | length)) | "- `\(.name)` — args: \(.args), params: \([.doc.tags[] | select(.name == "param")] | length) (\(.file))"' hooks/actions.json) |
165 | | -PARAM_MISMATCH_FILTERS=$(jq -r '.hooks[] | select(.type != "action_reference" and .type != "filter_reference") | select(.args != ([.doc.tags[] | select(.name == "param")] | length)) | "- `\(.name)` — args: \(.args), params: \([.doc.tags[] | select(.name == "param")] | length) (\(.file))"' hooks/filters.json) |
166 | | - |
167 | | -PARAM_MISMATCH=$(echo -e "${PARAM_MISMATCH_ACTIONS}\n${PARAM_MISMATCH_FILTERS}" | grep -v '^$' | sort) |
168 | | - |
169 | | -if [ -n "$PARAM_MISMATCH" ]; then |
170 | | - echo "$PARAM_MISMATCH" >> "$OUTPUT_FILE" |
171 | | -else |
172 | | - echo "_No hooks with param count mismatches._" >> "$OUTPUT_FILE" |
173 | | -fi |
174 | | - |
175 | | -echo "" >> "$OUTPUT_FILE" |
| 78 | +PARAM_MISMATCH=$(run_check '.hooks[] | select(.type != "action_reference" and .type != "filter_reference") | select(.args != ([.doc.tags[] | select(.name == "param")] | length)) | "- `\(.name)` — args: \(.args), params: \([.doc.tags[] | select(.name == "param")] | length) (\(.file))"') |
| 79 | +write_section "Param Count Mismatches" "Hooks where the number of \`@param\` tags does not match the \`args\` count." "No hooks with param count mismatches." "$PARAM_MISMATCH" |
176 | 80 |
|
177 | 81 | # Empty Param Types |
178 | | -echo "## Empty Param Types" >> "$OUTPUT_FILE" |
179 | | -echo "" >> "$OUTPUT_FILE" |
180 | | -echo "Hooks with \`@param\` tags that have empty or missing type arrays." >> "$OUTPUT_FILE" |
181 | | -echo "" >> "$OUTPUT_FILE" |
182 | | - |
183 | | -EMPTY_PARAM_TYPES_ACTIONS=$(jq -r '.hooks[] | .name as $name | .file as $file | .doc.tags[] | select(.name == "param" and (.types == null or .types == [] or (.types | length == 0))) | "- `\($name)` — \(.variable) (\($file))"' hooks/actions.json 2>/dev/null | sort -u) |
184 | | -EMPTY_PARAM_TYPES_FILTERS=$(jq -r '.hooks[] | .name as $name | .file as $file | .doc.tags[] | select(.name == "param" and (.types == null or .types == [] or (.types | length == 0))) | "- `\($name)` — \(.variable) (\($file))"' hooks/filters.json 2>/dev/null | sort -u) |
185 | | - |
186 | | -EMPTY_PARAM_TYPES=$(echo -e "${EMPTY_PARAM_TYPES_ACTIONS}\n${EMPTY_PARAM_TYPES_FILTERS}" | grep -v '^$' | sort -u) |
187 | | - |
188 | | -if [ -n "$EMPTY_PARAM_TYPES" ]; then |
189 | | - echo "$EMPTY_PARAM_TYPES" >> "$OUTPUT_FILE" |
190 | | -else |
191 | | - echo "_No hooks with empty param types._" >> "$OUTPUT_FILE" |
192 | | -fi |
193 | | - |
194 | | -echo "" >> "$OUTPUT_FILE" |
| 82 | +EMPTY_PARAM_TYPES=$(run_check '.hooks[] | .name as $name | .file as $file | .doc.tags[] | select(.name == "param" and (.types == null or .types == [] or (.types | length == 0))) | "- `\($name)` — \(.variable) (\($file))"') |
| 83 | +write_section "Empty Param Types" "Hooks with \`@param\` tags that have empty or missing type arrays." "No hooks with empty param types." "$EMPTY_PARAM_TYPES" |
195 | 84 |
|
196 | 85 | # Invalid Param Types |
197 | | -echo "## Invalid Param Types" >> "$OUTPUT_FILE" |
198 | | -echo "" >> "$OUTPUT_FILE" |
199 | | -echo "Hooks with \`@param\` tags where types are not valid PHP docblock types." >> "$OUTPUT_FILE" |
200 | | -echo "" >> "$OUTPUT_FILE" |
201 | | - |
202 | 86 | # Valid types: primitives, class names (with optional leading backslash), array modifiers [], generic syntax like array<type> |
203 | | -# This regex matches: word chars, backslashes, [], <>, commas, spaces (for generics), pipes, parentheses, $this |
204 | | -INVALID_TYPES_ACTIONS=$(jq -r '.hooks[] | .name as $name | .file as $file | .doc.tags[] | select(.name == "param") | .variable as $var | .types[]? | select(test("^[a-zA-Z_\\\\][a-zA-Z0-9_\\\\]*(<[^>]+>)?(\\[\\])*$") | not) | "- `\($name)` — \($var): `\(.)` (\($file))"' hooks/actions.json 2>/dev/null | sort -u) |
205 | | -INVALID_TYPES_FILTERS=$(jq -r '.hooks[] | .name as $name | .file as $file | .doc.tags[] | select(.name == "param") | .variable as $var | .types[]? | select(test("^[a-zA-Z_\\\\][a-zA-Z0-9_\\\\]*(<[^>]+>)?(\\[\\])*$") | not) | "- `\($name)` — \($var): `\(.)` (\($file))"' hooks/filters.json 2>/dev/null | sort -u) |
206 | | - |
207 | | -INVALID_TYPES=$(echo -e "${INVALID_TYPES_ACTIONS}\n${INVALID_TYPES_FILTERS}" | grep -v '^$' | sort -u) |
208 | | - |
209 | | -if [ -n "$INVALID_TYPES" ]; then |
210 | | - echo "$INVALID_TYPES" >> "$OUTPUT_FILE" |
211 | | -else |
212 | | - echo "_No hooks with invalid param types._" >> "$OUTPUT_FILE" |
213 | | -fi |
| 87 | +INVALID_TYPES=$(run_check '.hooks[] | .name as $name | .file as $file | .doc.tags[] | select(.name == "param") | .variable as $var | .types[]? | select(test("^[a-zA-Z_\\\\][a-zA-Z0-9_\\\\]*(<[^>]+>)?(\\[\\])*$") | not) | "- `\($name)` — \($var): `\(.)` (\($file))"') |
| 88 | +write_section "Invalid Param Types" "Hooks with \`@param\` tags where types are not valid PHP docblock types." "No hooks with invalid param types." "$INVALID_TYPES" |
214 | 89 |
|
215 | 90 | echo "Generated $OUTPUT_FILE" |
0 commit comments