| #!/bin/bash |
| |
| # The single source of truth for reserved words, categorized by PHP documentation |
| # Taken from https://www.php.net/manual/en/reserved.keywords.php |
| PHP_RESERVED_KEYWORDS=( |
| "abstract" "and" "array" "as" "break" "callable" "case" "catch" |
| "class" "clone" "const" "continue" "declare" "default" "die" "do" |
| "echo" "else" "elseif" "empty" "enddeclare" "endfor" "endforeach" |
| "endif" "endswitch" "endwhile" "eval" "exit" "extends" "final" |
| "finally" "fn" "for" "foreach" "function" "global" "goto" "if" |
| "implements" "include" "include_once" "instanceof" "insteadof" |
| "interface" "isset" "list" "match" "namespace" "new" "or" "parent" |
| "print" "private" "protected" "public" "readonly" "require" |
| "require_once" "return" "self" "static" "switch" "throw" "trait" |
| "try" "unset" "use" "var" "while" "xor" "yield" |
| ) |
| |
| # Taken from https://www.php.net/manual/en/reserved.other-reserved-words.php |
| PHP_OTHER_RESERVED_WORDS=( |
| "parent" "self" "int" "float" "bool" "string" "true" "false" "null" |
| "void" "iterable" "object" "mixed" "never" "array" "callable" |
| ) |
| |
| # Derived lists: |
| # RESERVED_WORDS is the union of keywords and soft reserved words. |
| RESERVED_WORDS=($(echo "${PHP_RESERVED_KEYWORDS[@]}" "${PHP_OTHER_RESERVED_WORDS[@]}" | tr ' ' '\n' | sort -u)) |
| |
| # This is for backwards compatibility. New PHP_RESERVED_KEYWORDS should be added |
| # here to prevent unnecessary breaking changes, as constants do not need the PB prefix. |
| # @TODO: allow ALL names for constants except for "class" |
| VALID_CONSTANT_NAMES=( |
| "bool" "false" "float" "int" "iterable" "mixed" "never" "null" |
| "object" "parent" "readonly" "self" "string" "true" "void" |
| ) |
| VALID_CONSTANT_NAMES=($(echo "${VALID_CONSTANT_NAMES[@]}" | tr ' ' '\n' | sort -u)) |
| |
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| REPO_ROOT="$(dirname "$SCRIPT_DIR")" |
| |
| format_columns() { |
| local per_line=$1 |
| local indent=$2 |
| shift 2 |
| local words=("$@") |
| local num_words=${#words[@]} |
| |
| # Default to original behavior of quoting the word. |
| local item_fmt="${ITEM_FMT:-\"%s\"}" |
| if [[ -z "$ITEM_FMT" ]]; then |
| item_fmt="\"%s\"" |
| fi |
| |
| # Compute max width per column to match Google C++/C formatting |
| local col_widths=() |
| local c=0 |
| while (( c < per_line )); do |
| local max_w=0 |
| local k=$c |
| while (( k < num_words )); do |
| local w="${words[k]}" |
| local s |
| printf -v s "$item_fmt" "$w" |
| if (( k < num_words - 1 )); then |
| s+="," |
| fi |
| if (( ${#s} > max_w )); then |
| max_w=${#s} |
| fi |
| k=$((k + per_line)) |
| done |
| col_widths+=($((max_w + 1))) |
| c=$((c + 1)) |
| done |
| |
| local i=0 |
| while (( i < num_words )); do |
| local line="$indent" |
| local j=0 |
| while (( j < per_line && i < num_words )); do |
| local w="${words[i]}" |
| local s |
| printf -v s "$item_fmt" "$w" |
| if (( i < num_words - 1 )); then |
| s+="," |
| fi |
| |
| if (( j < per_line - 1 && i < num_words - 1 )); then |
| local cw="${col_widths[j]}" |
| printf -v padded "%-${cw}s" "$s" |
| line+="$padded" |
| else |
| line+="$s" |
| fi |
| |
| i=$((i + 1)) |
| j=$((j + 1)) |
| done |
| echo "${line%" "}" |
| done |
| } |
| |
| update_file() { |
| local path="$1" |
| local label="$2" |
| local new_content="$3" |
| |
| local begin="// BEGIN $label" |
| local end="// END $label" |
| local full_path="$REPO_ROOT/$path" |
| if [[ ! -f "$full_path" ]]; then |
| echo "Skipping $path (not found)" |
| return |
| fi |
| |
| # Extract indentation of the first line of content |
| local indent=$(echo "$new_content" | head -n 1 | grep -o '^[[:space:]]*') |
| |
| local temp_file=$(mktemp) |
| |
| # Use perl to replace the block, writing to a temporary file |
| BEGIN_MARKER="$begin" END_MARKER="$end" NEW_CONTENT="$new_content" INDENT="$indent" \ |
| perl -0777 -pe 's{^[ \t]*\Q$ENV{BEGIN_MARKER}\E.*?^[ \t]*\Q$ENV{END_MARKER}\E}{$ENV{INDENT}$ENV{BEGIN_MARKER}\n$ENV{NEW_CONTENT}\n$ENV{INDENT}$ENV{END_MARKER}}sm' "$full_path" > "$temp_file" |
| |
| # Move the temporary file to the original file's path |
| mv "$temp_file" "$full_path" |
| |
| echo "Updated $path" |
| } |
| |
| generate_names_cc() { |
| local lines=() |
| lines+=("// @see php/update_reserved_words.sh - DO NOT MODIFY THIS LIST MANUALLY") |
| lines+=("const char* const kReservedNames[] = {") |
| local col_lines=() |
| while IFS= read -r line; do |
| col_lines+=("$line") |
| done < <(format_columns 5 " " "${RESERVED_WORDS[@]}") |
| for l in "${col_lines[@]}"; do lines+=("$l"); done |
| lines[${#lines[@]}-1]+="};" |
| lines+=("const int kReservedNamesSize = ${#RESERVED_WORDS[@]};") |
| printf "%s\n" "${lines[@]}" |
| } |
| |
| generate_names_c() { |
| local lines=() |
| lines+=("// @see php/update_reserved_words.sh - DO NOT MODIFY THIS LIST MANUALLY") |
| lines+=("const char* const kReservedNames[] = {") |
| local decorated=() |
| for w in "${RESERVED_WORDS[@]}"; do |
| decorated+=("\"$w\"") |
| done |
| decorated+=("NULL") |
| local col_lines=() |
| while IFS= read -r line; do |
| col_lines+=("$line") |
| done < <(ITEM_FMT="%s" format_columns 4 " " "${decorated[@]}") |
| for l in "${col_lines[@]}"; do lines+=("$l"); done |
| lines[${#lines[@]}-1]+="};" |
| printf "%s\n" "${lines[@]}" |
| } |
| |
| generate_php_generator_cc() { |
| local lines=() |
| lines+=("// @see php/update_reserved_words.sh - DO NOT MODIFY THIS LIST MANUALLY") |
| lines+=("constexpr absl::string_view kValidConstantNames[] = {") |
| local col_lines=() |
| while IFS= read -r line; do |
| col_lines+=("$line") |
| done < <(format_columns 5 " " "${VALID_CONSTANT_NAMES[@]}") |
| for l in "${col_lines[@]}"; do lines+=("$l"); done |
| lines[${#lines[@]}-1]+="};" |
| lines+=("const int kValidConstantNamesSize = ${#VALID_CONSTANT_NAMES[@]};") |
| printf "%s\n" "${lines[@]}" |
| } |
| |
| generate_proto_messages() { |
| local upper=$1 |
| echo "// @see php/update_reserved_words.sh - DO NOT MODIFY THIS LIST MANUALLY" |
| for w in "${RESERVED_WORDS[@]}"; do |
| local name="$w" |
| if [[ "$upper" == "true" ]]; then name=$(echo "$w" | tr '[:lower:]' '[:upper:]'); fi |
| echo "message $name {}" |
| done |
| } |
| |
| generate_proto_enums() { |
| local upper=$1 |
| echo "// @see php/update_reserved_words.sh - DO NOT MODIFY THIS LIST MANUALLY" |
| local i=1 |
| for w in "${RESERVED_WORDS[@]}"; do |
| local name="$w" |
| if [[ "$upper" == "true" ]]; then name=$(echo "$w" | tr '[:lower:]' '[:upper:]'); fi |
| echo "enum $name {" |
| echo " ZERO$i = 0;" |
| echo "}" |
| i=$((i + 1)) |
| done |
| } |
| |
| generate_proto_enum_values() { |
| local upper=$1 |
| echo " // @see php/update_reserved_words.sh - DO NOT MODIFY THIS LIST MANUALLY" |
| local i=0 |
| for w in "${RESERVED_WORDS[@]}"; do |
| local name="$w" |
| if [[ "$upper" == "true" ]]; then name=$(echo "$w" | tr '[:lower:]' '[:upper:]'); fi |
| echo " $name = $i;" |
| i=$((i + 1)) |
| done |
| } |
| |
| generate_test_cases() { |
| echo " // @see php/update_reserved_words.sh - DO NOT MODIFY THIS LIST MANUALLY" |
| # Lower messages |
| for w in "${RESERVED_WORDS[@]}"; do |
| echo " \$m = new \\Lower\\PB$w();" |
| done |
| echo "" |
| # Upper messages |
| for w in "${RESERVED_WORDS[@]}"; do |
| local upper=$(echo "$w" | tr '[:lower:]' '[:upper:]') |
| echo " \$m = new \\Upper\\PB$upper();" |
| done |
| echo "" |
| # Lower enums |
| for w in "${RESERVED_WORDS[@]}"; do |
| echo " \$m = new \\Lower_enum\\PB$w();" |
| done |
| echo "" |
| # Upper enums |
| for w in "${RESERVED_WORDS[@]}"; do |
| local upper=$(echo "$w" | tr '[:lower:]' '[:upper:]') |
| echo " \$m = new \\Upper_enum\\PB$upper();" |
| done |
| echo "" |
| # Lower enum values |
| for w in "${RESERVED_WORDS[@]}"; do |
| local prefix="PB" |
| for v in "${VALID_CONSTANT_NAMES[@]}"; do |
| if [[ "$w" == "$v" ]]; then prefix=""; break; fi |
| done |
| echo " \$m = \\Lower_enum_value\\NotAllowed::${prefix}${w};" |
| done |
| echo "" |
| # Upper enum values |
| for w in "${RESERVED_WORDS[@]}"; do |
| local prefix="PB" |
| for v in "${VALID_CONSTANT_NAMES[@]}"; do |
| if [[ "$w" == "$v" ]]; then prefix=""; break; fi |
| done |
| local upper=$(echo "$w" | tr '[:lower:]' '[:upper:]') |
| echo " \$m = \\Upper_enum_value\\NotAllowed::${prefix}${upper};" |
| done |
| } |
| |
| generate_gpbutil_reserved_words() { |
| echo " // @see php/update_reserved_words.sh - DO NOT MODIFY THIS LIST MANUALLY" |
| echo " \$reserved_words = array(" |
| ITEM_FMT="\"%s\"=>0" format_columns 3 " " "${RESERVED_WORDS[@]}" |
| echo " );" |
| } |
| |
| update_file "src/google/protobuf/compiler/php/names.cc" "RESERVED NAMES" "$(generate_names_cc)" |
| update_file "php/ext/google/protobuf/names.c" "RESERVED NAMES" "$(generate_names_c)" |
| update_file "src/google/protobuf/compiler/php/php_generator.cc" "VALID CONSTANT NAMES" "$(generate_php_generator_cc)" |
| update_file "php/tests/proto/test_reserved_message_lower.proto" "RESERVED NAMES" "$(generate_proto_messages false)" |
| update_file "php/tests/proto/test_reserved_message_upper.proto" "RESERVED NAMES" "$(generate_proto_messages true)" |
| update_file "php/tests/proto/test_reserved_enum_lower.proto" "RESERVED NAMES" "$(generate_proto_enums false)" |
| update_file "php/tests/proto/test_reserved_enum_upper.proto" "RESERVED NAMES" "$(generate_proto_enums true)" |
| update_file "php/tests/proto/test_reserved_enum_value_lower.proto" "RESERVED NAMES" "$(generate_proto_enum_values false)" |
| update_file "php/tests/proto/test_reserved_enum_value_upper.proto" "RESERVED NAMES" "$(generate_proto_enum_values true)" |
| update_file "php/tests/GeneratedClassTest.php" "RESERVED NAMES" "$(generate_test_cases)" |
| update_file "php/src/Google/Protobuf/Internal/GPBUtil.php" "RESERVED WORDS" "$(generate_gpbutil_reserved_words)" |