rbs_activerecord 1.2.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/hooks/claude-code-web-session-start.sh +14 -0
- data/.claude/hooks/pre-commit-check.sh +73 -0
- data/.claude/hooks/protect-sig-files.sh +45 -0
- data/.claude/hooks/rbs-inline.sh +95 -0
- data/.claude/hooks/self-review.sh +93 -0
- data/.claude/settings.json +58 -0
- data/.rubocop.yml +61 -1
- data/.vscode/settings.json +16 -1
- data/README.md +7 -0
- data/Rakefile +32 -3
- data/lib/generators/rbs_activerecord/install_generator.rb +5 -2
- data/lib/rbs_activerecord/generator/active_storage/instance_methods.rb +3 -2
- data/lib/rbs_activerecord/generator/active_storage/scopes.rb +1 -1
- data/lib/rbs_activerecord/generator/associations.rb +4 -4
- data/lib/rbs_activerecord/generator/attributes.rb +30 -5
- data/lib/rbs_activerecord/generator/delegated_type/instance_methods.rb +5 -5
- data/lib/rbs_activerecord/generator/delegated_type/scopes.rb +3 -3
- data/lib/rbs_activerecord/generator/enum/base.rb +2 -2
- data/lib/rbs_activerecord/generator/enum/instance_methods.rb +3 -3
- data/lib/rbs_activerecord/generator/enum/mappings.rb +2 -2
- data/lib/rbs_activerecord/generator/enum/scopes.rb +2 -2
- data/lib/rbs_activerecord/generator/scopes.rb +2 -2
- data/lib/rbs_activerecord/generator/secure_password.rb +1 -1
- data/lib/rbs_activerecord/generator.rb +6 -3
- data/lib/rbs_activerecord/model.rb +2 -1
- data/lib/rbs_activerecord/parser/evaluator.rb +3 -12
- data/lib/rbs_activerecord/parser/include_expander/module.rb +7 -5
- data/lib/rbs_activerecord/parser/include_expander.rb +4 -4
- data/lib/rbs_activerecord/parser/visitor.rb +1 -1
- data/lib/rbs_activerecord/parser.rb +2 -1
- data/lib/rbs_activerecord/rake_task.rb +6 -3
- data/lib/rbs_activerecord/utils.rb +8 -8
- data/lib/rbs_activerecord/version.rb +1 -1
- data/rbs_collection.lock.yaml +136 -64
- data/rbs_collection.yaml +1 -0
- data/sig/generators/rbs_activerecord/install_generator.rbs +1 -1
- data/sig/rbs_activerecord/generator/active_storage/instance_methods.rbs +1 -0
- data/sig/rbs_activerecord/generator/attributes.rbs +14 -1
- data/sig/rbs_activerecord/generator.rbs +4 -1
- data/sig/rbs_activerecord/parser.rbs +2 -1
- data/sig/rbs_activerecord/rake_task.rbs +4 -2
- data/sig/rbs_activerecord/utils.rbs +2 -2
- metadata +11 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 78a25f0014350c5542d7b2ed8eeb226c736c8f3416432261413e26f84b7d5877
|
|
4
|
+
data.tar.gz: 92684a1a6ac24afc2bcf1fe160ab6bf391f0ada8bd75ae337360c01d36891114
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 680a2e88d185d9a7c26d124ed5b6f0240233f4199006f4eb85e1639b25abb2925fa6ac6dd623c4d25f7777eea947a137357c05e274f3d8ac02c88ee9a01e2acc
|
|
7
|
+
data.tar.gz: db219887c854a4f412e0d5c1ba7713dc2425c45d985e9bb9fd5872424d51a84c17d8b843db400273fe7c47baa35e690fc2fe8ca2d45bfce2cdfeaf3e0aaf13b7
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
if [ "${CLAUDE_CODE_REMOTE:-}" = "true" ]; then
|
|
5
|
+
eval "$(rbenv init - bash)"
|
|
6
|
+
|
|
7
|
+
# Set the latest installed Ruby version as the global default
|
|
8
|
+
rbenv global `rbenv versions --bare | sort -rV | head -1`
|
|
9
|
+
|
|
10
|
+
echo 'eval "$(rbenv init - bash)"' >> "$CLAUDE_ENV_FILE"
|
|
11
|
+
echo 'export RUBYOPT="-rcgi"' >> "$CLAUDE_ENV_FILE"
|
|
12
|
+
RUBYOPT="-rcgi" bundle install
|
|
13
|
+
bundle exec rbs collection install --frozen
|
|
14
|
+
fi
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Hook input is JSON from stdin
|
|
4
|
+
input=$(cat)
|
|
5
|
+
tool_name=$(echo "$input" | jq -r '.tool_name')
|
|
6
|
+
command=$(echo "$input" | jq -r '.tool_input.command // ""')
|
|
7
|
+
|
|
8
|
+
# Only run for git commit commands
|
|
9
|
+
if [[ "$tool_name" != "Bash" ]] || [[ ! "$command" =~ git[[:space:]]+(commit|cherry-pick|merge|rebase) ]]; then
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
cd "$CLAUDE_PROJECT_DIR" || exit 1
|
|
14
|
+
|
|
15
|
+
if [ "${CLAUDE_CODE_REMOTE:-}" = "true" ]; then
|
|
16
|
+
eval "$(rbenv init - bash)"
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
echo "Running pre-commit checks..." >&2
|
|
20
|
+
|
|
21
|
+
# Regenerate RBS from scratch so that files orphaned by deleted lib/ sources are dropped too
|
|
22
|
+
if ! bundle exec rake rbs:regenerate >&2; then
|
|
23
|
+
echo "Error: RBS generation failed" >&2
|
|
24
|
+
exit 2
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# True when $command stages the whole of sig/ itself, which the check below cannot see because
|
|
28
|
+
# this hook runs first. Staging only part of sig/ does not count - the rest still drops out.
|
|
29
|
+
stages_sig() {
|
|
30
|
+
local segment tokens token stages_everything pathspec_given
|
|
31
|
+
|
|
32
|
+
while IFS= read -r segment; do
|
|
33
|
+
segment=${segment//[\"\']/}
|
|
34
|
+
read -ra tokens <<<"${segment#*add}"
|
|
35
|
+
stages_everything=0
|
|
36
|
+
pathspec_given=0
|
|
37
|
+
|
|
38
|
+
for token in "${tokens[@]}"; do
|
|
39
|
+
case "$token" in
|
|
40
|
+
--) ;;
|
|
41
|
+
-*A*|--all) stages_everything=1 ;;
|
|
42
|
+
-*) ;;
|
|
43
|
+
.|./|sig|sig/|./sig|./sig/) return 0 ;;
|
|
44
|
+
*) pathspec_given=1 ;;
|
|
45
|
+
esac
|
|
46
|
+
done
|
|
47
|
+
|
|
48
|
+
# -A alone stages every path; given a pathspec it stages that path only
|
|
49
|
+
[ "$stages_everything" = 1 ] && [ "$pathspec_given" = 0 ] && return 0
|
|
50
|
+
done < <(grep -oE '(^|[;&|(])[[:space:]]*git[[:space:]]+add[^;&|(]*' <<<"$command")
|
|
51
|
+
|
|
52
|
+
return 1
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Stop rather than let the RBS just regenerated above drop out of the commit unnoticed
|
|
56
|
+
if [[ "$command" =~ git[[:space:]]+commit([[:space:]]|$) ]] && ! stages_sig; then
|
|
57
|
+
unstaged_sig=$(git status --porcelain --untracked-files=all -- sig/ | awk 'substr($0, 2, 1) != " "')
|
|
58
|
+
|
|
59
|
+
if [ -n "$unstaged_sig" ]; then
|
|
60
|
+
echo "Error: sig/ has changes that are not staged:" >&2
|
|
61
|
+
echo "$unstaged_sig" >&2
|
|
62
|
+
echo "Run 'git add sig/' and commit again." >&2
|
|
63
|
+
exit 2
|
|
64
|
+
fi
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
if ! bundle exec rake >&2; then
|
|
68
|
+
echo "Error: rake checks failed" >&2
|
|
69
|
+
exit 2
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
echo "All checks passed!" >&2
|
|
73
|
+
exit 0
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Prevents direct editing of auto-generated RBS type definition files.
|
|
3
|
+
# Type definitions in the project's own sig/ are managed by PostToolUse hooks.
|
|
4
|
+
# Hand-written stubs for third-party gems under sig/gems/ are exempt.
|
|
5
|
+
|
|
6
|
+
# Hook input is JSON from stdin
|
|
7
|
+
input=$(cat)
|
|
8
|
+
tool_name=$(echo "$input" | jq -r '.tool_name // ""')
|
|
9
|
+
|
|
10
|
+
# Block Write/Edit operations on sig/ files
|
|
11
|
+
if [[ "$tool_name" == "Write" || "$tool_name" == "Edit" ]]; then
|
|
12
|
+
file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')
|
|
13
|
+
|
|
14
|
+
# Judge the path relative to the project root: only the sig/ there is
|
|
15
|
+
# generated. A sig/ nested elsewhere is hand-written and stays editable.
|
|
16
|
+
project_root="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
17
|
+
rel_path="${file_path#"${project_root}/"}"
|
|
18
|
+
|
|
19
|
+
if [[ "$rel_path" == sig/gems/* ]]; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
if [[ "$rel_path" == sig/* ]]; then
|
|
24
|
+
echo "❌ ERROR: Direct editing of generated sig/ files is not allowed." >&2
|
|
25
|
+
echo "📝 This repository's own types are generated: fix the inline RBS comments in the .rb file." >&2
|
|
26
|
+
echo "💡 Hand-written files are allowed only under sig/gems/ (stubs for third-party gems)." >&2
|
|
27
|
+
exit 2
|
|
28
|
+
fi
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Block manual rbs-inline execution
|
|
32
|
+
if [[ "$tool_name" == "Bash" ]]; then
|
|
33
|
+
command=$(echo "$input" | jq -r '.tool_input.command // ""')
|
|
34
|
+
|
|
35
|
+
# Match rbs-inline only at a command position, not as an argument.
|
|
36
|
+
rbs_inline_re=$'(^|[;&|(\n])[[:space:]]*(bundle[[:space:]]+exec[[:space:]]+)?([^[:space:]]*/)?rbs-inline([[:space:]]|[;&|)]|$)'
|
|
37
|
+
|
|
38
|
+
if [[ "$command" =~ $rbs_inline_re ]]; then
|
|
39
|
+
echo "❌ ERROR: Manual rbs-inline execution is not allowed." >&2
|
|
40
|
+
echo "🔄 Type definitions are auto-generated by hooks." >&2
|
|
41
|
+
exit 2
|
|
42
|
+
fi
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
exit 0
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Hook input is JSON from stdin
|
|
4
|
+
input=$(cat)
|
|
5
|
+
tool_name=$(echo "$input" | jq -r '.tool_name')
|
|
6
|
+
|
|
7
|
+
cd "$CLAUDE_PROJECT_DIR" || exit 1
|
|
8
|
+
|
|
9
|
+
if [ "${CLAUDE_CODE_REMOTE:-}" = "true" ]; then
|
|
10
|
+
eval "$(rbenv init - bash)"
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
# Handle Edit or Write tools
|
|
14
|
+
if [[ "$tool_name" == "Edit" || "$tool_name" == "Write" ]]; then
|
|
15
|
+
file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')
|
|
16
|
+
|
|
17
|
+
if [[ ! "$file_path" =~ \.rb$ ]]; then
|
|
18
|
+
exit 0
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if [[ ! "$file_path" =~ /lib/ ]]; then
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
echo "Running rbs-inline for $file_path..." >&2
|
|
26
|
+
|
|
27
|
+
if ! bundle exec rbs-inline --opt-out --output=sig/ "$file_path" >&2; then
|
|
28
|
+
echo "Warning: RBS generation failed for $file_path" >&2
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
echo "RBS generation completed." >&2
|
|
33
|
+
exit 0
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Handle Bash tool for mv command
|
|
37
|
+
if [[ "$tool_name" == "Bash" ]]; then
|
|
38
|
+
command=$(echo "$input" | jq -r '.tool_input.command // ""')
|
|
39
|
+
|
|
40
|
+
# Check if this is a mv command
|
|
41
|
+
if [[ ! "$command" =~ ^[[:space:]]*(mv|git\ mv)[[:space:]] ]]; then
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# Extract source and destination paths from mv command
|
|
46
|
+
paths=$(echo "$command" | sed -E 's/^[[:space:]]*(git[[:space:]]+)?mv[[:space:]]+//')
|
|
47
|
+
|
|
48
|
+
source_path=$(echo "$paths" | awk '{print $1}')
|
|
49
|
+
dest_path=$(echo "$paths" | awk '{print $2}')
|
|
50
|
+
|
|
51
|
+
# Check if source was a .rb file in lib/
|
|
52
|
+
if [[ ! "$source_path" =~ \.rb$ ]] || [[ ! "$source_path" =~ lib/ ]]; then
|
|
53
|
+
exit 0
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
echo "Detected mv of Ruby file in lib/: $source_path -> $dest_path" >&2
|
|
57
|
+
|
|
58
|
+
# Calculate the corresponding .rbs file path for the source
|
|
59
|
+
source_rbs=$(echo "$source_path" | sed -E 's|^(.*/)?lib/|sig/|; s|\.rb$|.rbs|')
|
|
60
|
+
|
|
61
|
+
# Remove the old .rbs file if it exists
|
|
62
|
+
if [[ -f "$source_rbs" ]]; then
|
|
63
|
+
echo "Removing old RBS file: $source_rbs" >&2
|
|
64
|
+
rm -f "$source_rbs"
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Determine the new .rb file path
|
|
68
|
+
if [[ -d "$dest_path" ]]; then
|
|
69
|
+
dest_file="$dest_path/$(basename "$source_path")"
|
|
70
|
+
else
|
|
71
|
+
dest_file="$dest_path"
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# Check if the destination is in lib/
|
|
75
|
+
if [[ ! "$dest_file" =~ lib/ ]]; then
|
|
76
|
+
echo "Destination is not in lib/, skipping RBS generation" >&2
|
|
77
|
+
exit 0
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# Generate RBS for the new file
|
|
81
|
+
if [[ -f "$dest_file" ]]; then
|
|
82
|
+
echo "Running rbs-inline for $dest_file..." >&2
|
|
83
|
+
|
|
84
|
+
if ! bundle exec rbs-inline --opt-out --output=sig/ "$dest_file" >&2; then
|
|
85
|
+
echo "Warning: RBS generation failed for $dest_file" >&2
|
|
86
|
+
exit 0
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
echo "RBS generation completed." >&2
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
exit 0
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
exit 0
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# PreToolUse hook: review code before `git commit`.
|
|
4
|
+
#
|
|
5
|
+
# Fires before a Bash `git commit` command and blocks the first commit attempt
|
|
6
|
+
# for a given change-set, asking Claude to review the code before committing.
|
|
7
|
+
# The review uses the `self-review` skill when available, and falls back to the
|
|
8
|
+
# bundled `code-review` skill otherwise — so the hook works even in projects
|
|
9
|
+
# that don't ship the custom self-review skill.
|
|
10
|
+
#
|
|
11
|
+
# A per-change-set marker (keyed by a hash of the diff, stored under .git/)
|
|
12
|
+
# prevents an infinite block loop: once a change-set has been surfaced for
|
|
13
|
+
# review, the matching commit is allowed through. If the review leads to fixes,
|
|
14
|
+
# the diff changes, so the new state is reviewed once more before it commits.
|
|
15
|
+
#
|
|
16
|
+
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
# Read JSON input from stdin
|
|
20
|
+
input=$(cat)
|
|
21
|
+
|
|
22
|
+
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
|
|
23
|
+
command=$(echo "$input" | jq -r '.tool_input.command // empty')
|
|
24
|
+
|
|
25
|
+
# Only gate the Bash tool running a git commit.
|
|
26
|
+
if [[ "$tool_name" != "Bash" ]]; then
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
# Match `git commit`, or `git -C <path> commit`.
|
|
30
|
+
commit_re='git[[:space:]]+(-C[[:space:]]+[^[:space:]]+[[:space:]]+)?commit'
|
|
31
|
+
if [[ ! "$command" =~ $commit_re ]]; then
|
|
32
|
+
exit 0
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# If the command targets a specific repo via `git -C <path>`, operate there too.
|
|
36
|
+
work_dir="."
|
|
37
|
+
if [[ "$command" =~ git[[:space:]]+-C[[:space:]]+([^[:space:]]+) ]]; then
|
|
38
|
+
work_dir="${BASH_REMATCH[1]}"
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Not a git repo → nothing to do.
|
|
42
|
+
git_dir=$(git -C "$work_dir" rev-parse --absolute-git-dir 2>/dev/null || true)
|
|
43
|
+
if [[ -z "$git_dir" ]]; then
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# Hash what will be committed. `git status --porcelain` is included so a commit
|
|
48
|
+
# that only adds untracked files isn't mistaken for empty; `git diff HEAD` adds
|
|
49
|
+
# tracked content. On the first commit there is no HEAD, so use the working state.
|
|
50
|
+
if git -C "$work_dir" rev-parse --verify HEAD >/dev/null 2>&1; then
|
|
51
|
+
changeset=$(git -C "$work_dir" status --porcelain; git -C "$work_dir" diff HEAD)
|
|
52
|
+
else
|
|
53
|
+
changeset=$(git -C "$work_dir" status --porcelain; git -C "$work_dir" diff; git -C "$work_dir" diff --cached)
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# Nothing to review (e.g. a message-only `--amend`) → let it proceed.
|
|
57
|
+
if [[ -z "$changeset" ]]; then
|
|
58
|
+
exit 0
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
hash=$(printf '%s' "$changeset" | git hash-object --stdin)
|
|
62
|
+
state_file="$git_dir/self-review-reviewed"
|
|
63
|
+
|
|
64
|
+
# Already surfaced for review → allow the commit.
|
|
65
|
+
if [[ -f "$state_file" ]] && grep -qxF "$hash" "$state_file"; then
|
|
66
|
+
exit 0
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# Record this change-set and block once to force a review first.
|
|
70
|
+
echo "$hash" >> "$state_file"
|
|
71
|
+
|
|
72
|
+
review_prompt='SELF-REVIEW REQUIRED before committing. Review the changes that are
|
|
73
|
+
about to be committed BEFORE running git commit again:
|
|
74
|
+
|
|
75
|
+
- If the `self-review` skill is available, invoke it (Skill tool, skill "self-review").
|
|
76
|
+
- Otherwise, run the bundled `/code-review` skill on the working diff.
|
|
77
|
+
|
|
78
|
+
Judge each finding the review returns rather than fixing it on sight. Weigh what the review
|
|
79
|
+
attached to it — how it graded the point, the basis it gave — and decide whether acting on it
|
|
80
|
+
is worth it. Where you skip a finding, say so; do not drop it silently. Where the choice needs
|
|
81
|
+
a requirement or an intent you do not have, leave it to the user. Once every fix you judged
|
|
82
|
+
necessary is in, review again; when nothing you judged worth fixing is left, run the same git
|
|
83
|
+
commit command again to proceed — it will be allowed through.'
|
|
84
|
+
|
|
85
|
+
jq -n --arg reason "$review_prompt" '{
|
|
86
|
+
"hookSpecificOutput": {
|
|
87
|
+
"hookEventName": "PreToolUse",
|
|
88
|
+
"permissionDecision": "deny",
|
|
89
|
+
"permissionDecisionReason": $reason
|
|
90
|
+
}
|
|
91
|
+
}'
|
|
92
|
+
|
|
93
|
+
exit 0
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "Edit|Write|Bash",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/protect-sig-files.sh",
|
|
10
|
+
"timeout": 5000
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"matcher": "Bash",
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/pre-commit-check.sh",
|
|
20
|
+
"timeout": 300000
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"matcher": "Bash",
|
|
26
|
+
"hooks": [
|
|
27
|
+
{
|
|
28
|
+
"type": "command",
|
|
29
|
+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/self-review.sh",
|
|
30
|
+
"timeout": 10000
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"SessionStart": [
|
|
36
|
+
{
|
|
37
|
+
"hooks": [
|
|
38
|
+
{
|
|
39
|
+
"type": "command",
|
|
40
|
+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/claude-code-web-session-start.sh"
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"PostToolUse": [
|
|
46
|
+
{
|
|
47
|
+
"matcher": "Edit|Write|Bash",
|
|
48
|
+
"hooks": [
|
|
49
|
+
{
|
|
50
|
+
"type": "command",
|
|
51
|
+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/rbs-inline.sh",
|
|
52
|
+
"timeout": 60000
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
}
|
data/.rubocop.yml
CHANGED
|
@@ -1,20 +1,80 @@
|
|
|
1
|
+
# NOTE: Rules are sorted in ASCII order.
|
|
2
|
+
|
|
1
3
|
AllCops:
|
|
2
|
-
|
|
4
|
+
NewCops: enable
|
|
5
|
+
TargetRubyVersion: 3.3
|
|
6
|
+
|
|
7
|
+
plugins:
|
|
8
|
+
- rubocop-instance_variable_access
|
|
9
|
+
- rubocop-numbered-params
|
|
10
|
+
- rubocop-rake
|
|
11
|
+
- rubocop-rbs_inline
|
|
12
|
+
- rubocop-rspec
|
|
13
|
+
- rubocop-rspec-unused-let
|
|
3
14
|
|
|
15
|
+
# Layout
|
|
4
16
|
Layout/LeadingCommentSpace:
|
|
5
17
|
AllowRBSInlineAnnotation: true
|
|
6
18
|
AllowSteepAnnotation: true
|
|
7
19
|
|
|
20
|
+
Layout/LineLength:
|
|
21
|
+
Max: 120
|
|
22
|
+
|
|
23
|
+
# Metrics
|
|
24
|
+
Metrics/AbcSize:
|
|
25
|
+
Max: 20
|
|
26
|
+
|
|
8
27
|
Metrics/BlockLength:
|
|
9
28
|
Exclude:
|
|
10
29
|
- "spec/**/*.rb"
|
|
11
30
|
|
|
31
|
+
Metrics/ClassLength:
|
|
32
|
+
Max: 200
|
|
33
|
+
|
|
12
34
|
Metrics/MethodLength:
|
|
13
35
|
Max: 30
|
|
14
36
|
|
|
37
|
+
# RSpec
|
|
38
|
+
RSpec/ExampleLength:
|
|
39
|
+
Enabled: false
|
|
40
|
+
|
|
41
|
+
RSpec/MultipleExpectations:
|
|
42
|
+
Enabled: false
|
|
43
|
+
|
|
44
|
+
RSpec/MultipleMemoizedHelpers:
|
|
45
|
+
Max: 10
|
|
46
|
+
|
|
47
|
+
RSpec/NamedSubject:
|
|
48
|
+
Enabled: false
|
|
49
|
+
|
|
50
|
+
RSpec/NestedGroups:
|
|
51
|
+
Max: 5
|
|
52
|
+
|
|
53
|
+
# Style
|
|
54
|
+
Style/AccessorGrouping:
|
|
55
|
+
Enabled: false
|
|
56
|
+
|
|
57
|
+
Style/CommentedKeyword:
|
|
58
|
+
Enabled: false
|
|
59
|
+
|
|
15
60
|
Style/Documentation:
|
|
16
61
|
Enabled: false
|
|
17
62
|
|
|
63
|
+
Style/HashSyntax:
|
|
64
|
+
EnforcedShorthandSyntax: always
|
|
65
|
+
|
|
66
|
+
Style/RbsInline:
|
|
67
|
+
Mode: opt_out
|
|
68
|
+
|
|
69
|
+
Style/RbsInline/MissingTypeAnnotation:
|
|
70
|
+
EnforcedStyle: doc_style_and_return_annotation
|
|
71
|
+
|
|
72
|
+
Style/RbsInline/RedundantTypeAnnotation:
|
|
73
|
+
EnforcedStyle: doc_style_and_return_annotation
|
|
74
|
+
|
|
75
|
+
Style/RedundantFormat:
|
|
76
|
+
Enabled: false
|
|
77
|
+
|
|
18
78
|
Style/StringLiterals:
|
|
19
79
|
EnforcedStyle: double_quotes
|
|
20
80
|
|
data/.vscode/settings.json
CHANGED
|
@@ -6,13 +6,28 @@
|
|
|
6
6
|
"rbs-helper.rbs-inline-on-save": true,
|
|
7
7
|
"cSpell.words": [
|
|
8
8
|
"activerecord",
|
|
9
|
+
"activestorage",
|
|
10
|
+
"bindir",
|
|
9
11
|
"codebases",
|
|
10
12
|
"Cyclomatic",
|
|
13
|
+
"entryable",
|
|
14
|
+
"foos",
|
|
11
15
|
"kaminari",
|
|
16
|
+
"KOMIYA",
|
|
17
|
+
"kwargs",
|
|
12
18
|
"mkpath",
|
|
13
19
|
"modname",
|
|
20
|
+
"noargs",
|
|
21
|
+
"popen",
|
|
22
|
+
"posargs",
|
|
23
|
+
"raketask",
|
|
24
|
+
"readlines",
|
|
14
25
|
"requireds",
|
|
15
26
|
"rmtree",
|
|
16
|
-
"
|
|
27
|
+
"rubocop",
|
|
28
|
+
"rubygems",
|
|
29
|
+
"Takeshi",
|
|
30
|
+
"tasklib",
|
|
31
|
+
"tk0miya"
|
|
17
32
|
]
|
|
18
33
|
}
|
data/README.md
CHANGED
|
@@ -23,6 +23,12 @@ Run `rbs:activerecord:setup` task:
|
|
|
23
23
|
Then rbs_activerecord will scan your code and generate RBS files into
|
|
24
24
|
`sig/activerecord` directory.
|
|
25
25
|
|
|
26
|
+
## Configurations
|
|
27
|
+
|
|
28
|
+
You can change the behavior of the generator via rbs_activerecord.rake:
|
|
29
|
+
|
|
30
|
+
* pure_accessors: Make accessor methods for columns pure. It helps [Type narrowing](https://github.com/soutaro/steep/wiki/Release-Note-1.1#type-narrowing-on-method-calls-590).
|
|
31
|
+
|
|
26
32
|
## The goal of rbs_activerecord
|
|
27
33
|
|
|
28
34
|
rbs_activerecord is an experimental project to enhance the type generation for ActiveRecord models.
|
|
@@ -43,6 +49,7 @@ Main differences between rbs_activerecord and rbs_rails are:
|
|
|
43
49
|
* Support extension ActiveRecord from 3rd party gems ([#254](https://github.com/pocke/rbs_rails/pull/254))
|
|
44
50
|
* ex. [kaminari-activerecord](https://github.com/ruby/gem_rbs_collection/pull/725)
|
|
45
51
|
* Support composite primary keys ([#275](https://github.com/pocke/rbs_rails/pull/275))
|
|
52
|
+
* Support pure_accessors ([#302](https://github.com/pocke/rbs_rails/issues/302))
|
|
46
53
|
* Extend return types of #pluck ([#273](https://github.com/pocke/rbs_rails/pull/273))
|
|
47
54
|
* Override types of CollectionProxy ([#289](https://github.com/pocke/rbs_rails/pull/289))
|
|
48
55
|
* Bug fixes
|
data/Rakefile
CHANGED
|
@@ -1,14 +1,43 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "bundler/gem_tasks"
|
|
4
|
+
require "rspec/core/rake_task"
|
|
4
5
|
require "rubocop/rake_task"
|
|
5
6
|
|
|
6
7
|
RuboCop::RakeTask.new
|
|
8
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
7
9
|
|
|
8
|
-
task default: :
|
|
10
|
+
task default: :ci
|
|
11
|
+
|
|
12
|
+
task ci: %i[rubocop spec steep rbs:validate]
|
|
9
13
|
|
|
10
14
|
namespace :rbs do
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
# sig/gems holds hand-written types for external gems, so it is never regenerated
|
|
16
|
+
kept = ->(path) { path == "sig/gems" || path.start_with?("sig/gems/") }
|
|
17
|
+
|
|
18
|
+
desc "Install RBS collection"
|
|
19
|
+
task :install do
|
|
20
|
+
sh "bundle exec rbs collection install --frozen"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
desc "Regenerate all RBS files under sig/ from scratch (run manually after editing lib/)"
|
|
24
|
+
task :regenerate do
|
|
25
|
+
Dir.glob("sig/**/*.rbs").reject(&kept).each { rm _1 }
|
|
26
|
+
|
|
27
|
+
sh "bundle exec rbs-inline --opt-out --output=sig lib"
|
|
28
|
+
|
|
29
|
+
# Drop the directories the deletion above left empty, deepest first
|
|
30
|
+
dirs = Dir.glob("sig/**/*").select { File.directory?(_1) }
|
|
31
|
+
dirs.reject(&kept).sort.reverse_each { rmdir _1 if Dir.empty?(_1) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
desc "Validate RBS files"
|
|
35
|
+
task validate: "rbs:install" do
|
|
36
|
+
sh "bundle exec rbs -Isig validate"
|
|
13
37
|
end
|
|
14
38
|
end
|
|
39
|
+
|
|
40
|
+
desc "Run steep type check"
|
|
41
|
+
task steep: "rbs:install" do
|
|
42
|
+
sh "bundle exec steep check"
|
|
43
|
+
end
|
|
@@ -4,14 +4,17 @@ require "rails"
|
|
|
4
4
|
|
|
5
5
|
module RbsActiverecord
|
|
6
6
|
class InstallGenerator < Rails::Generators::Base
|
|
7
|
-
def create_raketask
|
|
7
|
+
def create_raketask #: void
|
|
8
8
|
create_file "lib/tasks/rbs_activerecord.rake", <<~RUBY
|
|
9
9
|
# frozen_string_literal: true
|
|
10
10
|
|
|
11
11
|
begin
|
|
12
12
|
require "rbs_activerecord/rake_task"
|
|
13
13
|
|
|
14
|
-
RbsActiverecord::RakeTask.new
|
|
14
|
+
RbsActiverecord::RakeTask.new do |task|
|
|
15
|
+
# Make accessor methods for columns pure.
|
|
16
|
+
# task.pure_accessors = true
|
|
17
|
+
end
|
|
15
18
|
rescue LoadError
|
|
16
19
|
# failed to load rbs_activerecord. Skip to load rbs_activerecord tasks.
|
|
17
20
|
end
|
|
@@ -23,13 +23,14 @@ module RbsActiverecord
|
|
|
23
23
|
|
|
24
24
|
def attachments #: Hash[String, untyped]
|
|
25
25
|
if model.klass.respond_to?(:attachment_reflections)
|
|
26
|
-
model.klass.attachment_reflections
|
|
26
|
+
model.klass.attachment_reflections
|
|
27
27
|
else
|
|
28
28
|
{}
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
# @rbs name: String
|
|
33
|
+
# @rbs reflection: untyped
|
|
33
34
|
def attachment(name, reflection) #: String
|
|
34
35
|
case reflection.macro
|
|
35
36
|
when :has_one_attached
|
|
@@ -44,7 +45,7 @@ module RbsActiverecord
|
|
|
44
45
|
RBS
|
|
45
46
|
when :has_many_attached
|
|
46
47
|
<<~RBS
|
|
47
|
-
def #{name}: () -> ::
|
|
48
|
+
def #{name}: () -> ::ActiveStorage::Attached::Many
|
|
48
49
|
def #{name}=: (untyped) -> untyped
|
|
49
50
|
RBS
|
|
50
51
|
else
|
|
@@ -25,7 +25,7 @@ module RbsActiverecord
|
|
|
25
25
|
|
|
26
26
|
private
|
|
27
27
|
|
|
28
|
-
def has_many #: String
|
|
28
|
+
def has_many #: String # rubocop:disable Naming/PredicatePrefix
|
|
29
29
|
model.reflect_on_all_associations(:has_many).map do |assoc|
|
|
30
30
|
assoc_name = assoc.name.to_s
|
|
31
31
|
klass_name = "::#{assoc.klass.name}"
|
|
@@ -41,7 +41,7 @@ module RbsActiverecord
|
|
|
41
41
|
end.join("\n")
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
def has_one #: String
|
|
44
|
+
def has_one #: String # rubocop:disable Naming/PredicatePrefix
|
|
45
45
|
model.reflect_on_all_associations(:has_one).map do |assoc|
|
|
46
46
|
type = "::#{assoc.klass.name}"
|
|
47
47
|
optional = "#{type}?"
|
|
@@ -85,7 +85,7 @@ module RbsActiverecord
|
|
|
85
85
|
def polymorphic_owner_types(assoc) #: String # rubocop:disable Metrics/AbcSize
|
|
86
86
|
table_name = model.klass.name.to_s.tableize.to_sym
|
|
87
87
|
owners = ActiveRecord::Base.descendants.select do |klass|
|
|
88
|
-
klass.reflect_on_all_associations.any? {
|
|
88
|
+
klass.reflect_on_all_associations.any? { _1.name == table_name && _1.options[:as] == assoc.name }
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
if owners.empty?
|
|
@@ -98,7 +98,7 @@ module RbsActiverecord
|
|
|
98
98
|
end
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
-
def has_and_belongs_to_many #: String
|
|
101
|
+
def has_and_belongs_to_many #: String # rubocop:disable Naming/PredicatePrefix
|
|
102
102
|
model.reflect_on_all_associations(:has_and_belongs_to_many).map do |assoc|
|
|
103
103
|
assoc_name = assoc.name.to_s
|
|
104
104
|
klass_name = assoc.klass.name
|