rubocop-rspec-unused-let 1.0.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 +7 -0
- data/.claude/hooks/claude-code-web-session-start.sh +14 -0
- data/.claude/hooks/pre-commit-check.sh +33 -0
- data/.claude/hooks/protect-sig-files.sh +30 -0
- data/.claude/hooks/rbs-inline.sh +95 -0
- data/.claude/hooks/self-review.sh +68 -0
- data/.claude/settings.json +58 -0
- data/.vscode/extensions.json +6 -0
- data/.vscode/settings.json +7 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +117 -0
- data/Rakefile +34 -0
- data/Steepfile +9 -0
- data/config/default.yml +9 -0
- data/lib/rubocop/cop/rspec/unused_let.rb +181 -0
- data/lib/rubocop/rspec/unused/let/plugin.rb +44 -0
- data/lib/rubocop/rspec/unused/let/version.rb +11 -0
- data/lib/rubocop/rspec/unused/let.rb +27 -0
- data/lib/rubocop-rspec-unused-let.rb +5 -0
- data/rbs_collection.lock.yaml +192 -0
- data/rbs_collection.yaml +14 -0
- data/sig/rubocop/cop/rspec/unused_let.rbs +108 -0
- data/sig/rubocop/rspec/unused/let/plugin.rbs +27 -0
- data/sig/rubocop/rspec/unused/let/version.rbs +11 -0
- data/sig/rubocop/rspec/unused/let.rbs +16 -0
- data/sig/rubocop-rspec-unused-let.rbs +2 -0
- data/sig/rubocop_rspec_stubs.rbs +27 -0
- metadata +117 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 414fa557bcae2f0ff55ef645b3f2732631290679b6bf8c43a16319b2cbc51646
|
|
4
|
+
data.tar.gz: f80247431cea5dbaa5cd8e287e40c3993aaf92abe2b0763ebbc3ba20ac14a2fa
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ccd5ca374b3e88c38203482a60ffefa15c2b8a35977ffa24d0ee9a912276cfac888177c3ee6c5b0c2da2b32cb0d89fc119689dd5f3268191d51e9d36da36bc2a
|
|
7
|
+
data.tar.gz: 605e20480cab1372b718d8796c5a2d736f27221e2e6e589f64c59201c85cda1496e2b560d0924224a34aef8ed0979710097e87505a12fb262f7ad6880ceb310d
|
|
@@ -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,33 @@
|
|
|
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
|
+
# Generate RBS and run all checks
|
|
22
|
+
if ! bundle exec rbs-inline --opt-out --output=sig/ lib/ >&2; then
|
|
23
|
+
echo "Error: RBS generation failed" >&2
|
|
24
|
+
exit 2
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
if ! bundle exec rake >&2; then
|
|
28
|
+
echo "Error: rake checks failed" >&2
|
|
29
|
+
exit 2
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
echo "All checks passed!" >&2
|
|
33
|
+
exit 0
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# .claude/hooks/protect-sig-files.sh
|
|
3
|
+
#
|
|
4
|
+
# Prevents direct editing of auto-generated RBS type definition files.
|
|
5
|
+
# Type definitions in */sig/ directories are managed by PostToolUse hooks.
|
|
6
|
+
|
|
7
|
+
TOOL_NAME="$CLAUDE_TOOL_NAME"
|
|
8
|
+
FILE_PATH="$CLAUDE_TOOL_PARAM_file_path"
|
|
9
|
+
COMMAND="$CLAUDE_TOOL_PARAM_command"
|
|
10
|
+
|
|
11
|
+
# Block Write/Edit operations on sig/ files
|
|
12
|
+
if [[ "$TOOL_NAME" == "Write" || "$TOOL_NAME" == "Edit" ]]; then
|
|
13
|
+
if [[ "$FILE_PATH" == */sig/* ]]; then
|
|
14
|
+
echo "❌ ERROR: Direct editing of sig/ files is not allowed."
|
|
15
|
+
echo "📝 Type definitions are auto-generated by PostToolUse hook."
|
|
16
|
+
echo "💡 Modify inline RBS annotations in .rb files instead."
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Block manual rbs-inline execution
|
|
22
|
+
if [[ "$TOOL_NAME" == "Bash" ]]; then
|
|
23
|
+
if [[ "$COMMAND" =~ rbs-inline ]]; then
|
|
24
|
+
echo "❌ ERROR: Manual rbs-inline execution is not allowed."
|
|
25
|
+
echo "🔄 Type definitions are auto-generated by hooks."
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
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,68 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# PostToolUse hook for self-review after git push
|
|
4
|
+
#
|
|
5
|
+
# This script triggers a self-review process after git push operations.
|
|
6
|
+
# The output is fed back to Claude via JSON, prompting it to perform
|
|
7
|
+
# a code review.
|
|
8
|
+
#
|
|
9
|
+
# NOTE: PostToolUse hooks with exit code 0 only show plain text stdout
|
|
10
|
+
# in verbose mode. To ensure Claude receives the message, we output JSON
|
|
11
|
+
# with "decision": "block" and "reason", which Claude Code delivers as
|
|
12
|
+
# a system-reminder.
|
|
13
|
+
#
|
|
14
|
+
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
# Read JSON input from stdin
|
|
18
|
+
input=$(cat)
|
|
19
|
+
|
|
20
|
+
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
|
|
21
|
+
command=$(echo "$input" | jq -r '.tool_input.command // empty')
|
|
22
|
+
|
|
23
|
+
# Only run for Bash tool
|
|
24
|
+
if [[ "$tool_name" != "Bash" ]]; then
|
|
25
|
+
exit 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# Check if it's a git push command
|
|
29
|
+
if [[ ! "$command" =~ git[[:space:]]+push ]]; then
|
|
30
|
+
exit 0
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# Check if push was successful (field may not exist; default to 0)
|
|
34
|
+
exit_code=$(echo "$input" | jq -r '.tool_response.exit_code // 0')
|
|
35
|
+
|
|
36
|
+
if [[ "$exit_code" != "0" ]]; then
|
|
37
|
+
# Push failed, no need for review
|
|
38
|
+
exit 0
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Output self-review prompt as JSON so Claude receives it
|
|
42
|
+
review_prompt='SELF-REVIEW REQUIRED: A git push has been completed. Please use the Task tool with subagent_type Explore to perform a self-review of the changes. Use this prompt for the Explore subagent:
|
|
43
|
+
|
|
44
|
+
First, read CLAUDE.md to understand the project rules and conventions.
|
|
45
|
+
Then review the recent git push. Run `git log origin/main..HEAD --stat` and
|
|
46
|
+
`git diff origin/main...HEAD` to examine all changes from the branch point.
|
|
47
|
+
Check for:
|
|
48
|
+
1. Correctness: requirements met, logic errors, edge cases
|
|
49
|
+
2. Code Quality: naming, readability, follows project conventions
|
|
50
|
+
3. Testing: test coverage for new functionality
|
|
51
|
+
4. Security: vulnerabilities, input validation
|
|
52
|
+
5. Commit Quality: each commit should be a meaningful unit of work.
|
|
53
|
+
If there are minor fix commits (typos, forgotten changes, careless mistakes),
|
|
54
|
+
suggest squashing them into the relevant commit before merging.
|
|
55
|
+
|
|
56
|
+
Report findings and suggest fixes if needed.
|
|
57
|
+
After the subagent review, if issues are found, create follow-up commits to address them.
|
|
58
|
+
|
|
59
|
+
If working in a git worktree (i.e., the working directory is under .claude/worktrees/),
|
|
60
|
+
use `git -C <worktree-path>` for all git commands instead of `cd <path> && git`.
|
|
61
|
+
Also avoid chaining commands with `&&` where possible — run each command separately.'
|
|
62
|
+
|
|
63
|
+
jq -n --arg reason "$review_prompt" '{
|
|
64
|
+
"decision": "block",
|
|
65
|
+
"reason": $reason
|
|
66
|
+
}'
|
|
67
|
+
|
|
68
|
+
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
|
+
"SessionStart": [
|
|
26
|
+
{
|
|
27
|
+
"hooks": [
|
|
28
|
+
{
|
|
29
|
+
"type": "command",
|
|
30
|
+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/claude-code-web-session-start.sh"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"PostToolUse": [
|
|
36
|
+
{
|
|
37
|
+
"matcher": "Edit|Write|Bash",
|
|
38
|
+
"hooks": [
|
|
39
|
+
{
|
|
40
|
+
"type": "command",
|
|
41
|
+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/rbs-inline.sh",
|
|
42
|
+
"timeout": 60000
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"matcher": "Bash",
|
|
48
|
+
"hooks": [
|
|
49
|
+
{
|
|
50
|
+
"type": "command",
|
|
51
|
+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/self-review.sh",
|
|
52
|
+
"timeout": 10000
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
}
|
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
"rubocop-rspec-unused-let" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
|
|
4
|
+
|
|
5
|
+
* Participants will be tolerant of opposing views.
|
|
6
|
+
* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
|
7
|
+
* When interpreting the words and actions of others, participants should always assume good intentions.
|
|
8
|
+
* Behaviour which can be reasonably considered harassment will not be tolerated.
|
|
9
|
+
|
|
10
|
+
If you have any concerns about behaviour within this project, please contact us at ["i.tkomiya@gmail.com"](mailto:"i.tkomiya@gmail.com").
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Takeshi KOMIYA
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# rubocop-rspec-unused-let
|
|
2
|
+
|
|
3
|
+
A [RuboCop](https://github.com/rubocop/rubocop) extension that detects
|
|
4
|
+
unreferenced RSpec `let` definitions.
|
|
5
|
+
|
|
6
|
+
It adds a single cop, `RSpec/UnusedLet`, which flags `let` (and optionally
|
|
7
|
+
`let!`) definitions whose name is never referenced within their scope. The cop
|
|
8
|
+
is deliberately conservative around `shared_examples` so that it avoids false
|
|
9
|
+
positives that a naive implementation would produce.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Add the gem to your `Gemfile`:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
gem "rubocop-rspec-unused-let", require: false
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This gem builds on [rubocop-rspec](https://github.com/rubocop/rubocop-rspec),
|
|
20
|
+
so make sure that is available too.
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
Enable both plugins in your `.rubocop.yml`:
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
plugins:
|
|
28
|
+
- rubocop-rspec
|
|
29
|
+
- rubocop-rspec-unused-let
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## What it detects
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
# bad
|
|
36
|
+
RSpec.describe Foo do
|
|
37
|
+
let(:used) { 1 }
|
|
38
|
+
let(:unused) { 2 } # never referenced
|
|
39
|
+
|
|
40
|
+
it { expect(used).to eq(1) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# good
|
|
44
|
+
RSpec.describe Foo do
|
|
45
|
+
let(:used) { 1 }
|
|
46
|
+
|
|
47
|
+
it { expect(used).to eq(1) }
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
A `let` is considered *used* when its name appears as a bare method call
|
|
52
|
+
anywhere in its scope — inside examples, hooks (`before`/`after`/`around`),
|
|
53
|
+
`subject`, other `let` blocks, and nested example groups. Dynamic references
|
|
54
|
+
such as `send(:name)` / `public_send("name")` are also treated as usages.
|
|
55
|
+
|
|
56
|
+
## How it handles `shared_examples`
|
|
57
|
+
|
|
58
|
+
Because RuboCop analyzes one file at a time, a `let` can be consumed by a shared
|
|
59
|
+
example block defined in another file. To avoid false positives, the cop stays
|
|
60
|
+
silent whenever it cannot see every possible reference:
|
|
61
|
+
|
|
62
|
+
- `let` definitions **inside** a `shared_examples` / `shared_context` block are
|
|
63
|
+
ignored (their consumers are the including groups, which may be external).
|
|
64
|
+
- When an example group's subtree contains a shared example inclusion
|
|
65
|
+
(`it_behaves_like`, `include_examples`, `include_context`, ...), the `let`
|
|
66
|
+
definitions **visible at that inclusion point** are ignored. Sibling subtrees
|
|
67
|
+
that do not include shared examples are still checked.
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
RSpec.describe Foo do
|
|
71
|
+
let(:a) { 1 } # skipped: visible at the inclusion below
|
|
72
|
+
|
|
73
|
+
context "with shared" do
|
|
74
|
+
let(:b) { 2 } # skipped: same
|
|
75
|
+
it_behaves_like "something"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
context "other" do
|
|
79
|
+
let(:c) { 3 } # checked: the shared block cannot see `c`
|
|
80
|
+
it { expect(c).to eq(3) }
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Configuration
|
|
86
|
+
|
|
87
|
+
```yaml
|
|
88
|
+
RSpec/UnusedLet:
|
|
89
|
+
# Whether to also check `let!`. On by default. Since `let!` is sometimes used
|
|
90
|
+
# purely for its side effects (e.g. `let!(:user) { create(:user) }`), set this
|
|
91
|
+
# to `false` to opt out.
|
|
92
|
+
CheckLetBang: true
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Known limitations
|
|
96
|
+
|
|
97
|
+
- Analysis is limited to a single file; references reachable only across files
|
|
98
|
+
(e.g. through external shared examples) are intentionally not flagged.
|
|
99
|
+
- `let` definitions in an override chain (redefined in a nested group, or
|
|
100
|
+
overriding an outer definition) are skipped, since the outer one may be
|
|
101
|
+
reached through `super`.
|
|
102
|
+
|
|
103
|
+
## Development
|
|
104
|
+
|
|
105
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then run
|
|
106
|
+
`rake spec` to run the tests and `rake rubocop` to lint the gem. `rake` runs
|
|
107
|
+
both.
|
|
108
|
+
|
|
109
|
+
## Contributing
|
|
110
|
+
|
|
111
|
+
Bug reports and pull requests are welcome on GitHub at
|
|
112
|
+
https://github.com/tk0miya/rubocop-rspec-unused-let.
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
The gem is available as open source under the terms of the
|
|
117
|
+
[MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rspec/core/rake_task"
|
|
5
|
+
require "rubocop/rake_task"
|
|
6
|
+
|
|
7
|
+
RuboCop::RakeTask.new
|
|
8
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
9
|
+
|
|
10
|
+
task default: :ci
|
|
11
|
+
|
|
12
|
+
task ci: %i[rubocop spec steep rbs:validate]
|
|
13
|
+
|
|
14
|
+
namespace :rbs do
|
|
15
|
+
desc "Install RBS signatures"
|
|
16
|
+
task :install do
|
|
17
|
+
sh "bundle exec rbs collection install --frozen"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
desc "Generate RBS files"
|
|
21
|
+
task :generate do
|
|
22
|
+
sh "rbs-inline", "--opt-out", "--output=sig", "lib"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
desc "Validate RBS files"
|
|
26
|
+
task validate: "rbs:install" do
|
|
27
|
+
sh "bundle exec rbs -Isig validate"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
desc "Run steep type check"
|
|
32
|
+
task steep: "rbs:install" do
|
|
33
|
+
sh "bundle exec steep check"
|
|
34
|
+
end
|
data/Steepfile
ADDED
data/config/default.yml
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module RSpec
|
|
6
|
+
# Checks for `let` definitions that are never referenced.
|
|
7
|
+
#
|
|
8
|
+
# A `let` (or `let!`) whose name is never used within its scope is dead
|
|
9
|
+
# code that makes specs harder to read. This cop flags such definitions.
|
|
10
|
+
#
|
|
11
|
+
# `let!` is checked as well by default. Since it is sometimes used purely
|
|
12
|
+
# for its side effects (e.g. `let!(:user) { create(:user) }`), you can opt
|
|
13
|
+
# out with `CheckLetBang: false`.
|
|
14
|
+
#
|
|
15
|
+
# To avoid false positives, the cop deliberately stays silent whenever it
|
|
16
|
+
# cannot see every possible reference:
|
|
17
|
+
#
|
|
18
|
+
# * `let` definitions inside a `shared_examples`/`shared_context` block are
|
|
19
|
+
# ignored, because their consumers live in the (possibly external)
|
|
20
|
+
# including example groups.
|
|
21
|
+
# * When an example group's subtree contains a shared example inclusion
|
|
22
|
+
# (`it_behaves_like`, `include_examples`, `include_context`, ...), the
|
|
23
|
+
# `let` definitions visible at that inclusion point are ignored, because
|
|
24
|
+
# the (possibly external) shared block may reference them.
|
|
25
|
+
# * `let` definitions that participate in an override chain (redefined in a
|
|
26
|
+
# nested group, or overriding an outer definition) are ignored, since the
|
|
27
|
+
# outer definition may be reached through `super`.
|
|
28
|
+
#
|
|
29
|
+
# Dynamic references such as `send(:foo)` are treated as usages.
|
|
30
|
+
#
|
|
31
|
+
# @example
|
|
32
|
+
# # bad
|
|
33
|
+
# describe Foo do
|
|
34
|
+
# let(:used) { 1 }
|
|
35
|
+
# let(:unused) { 2 }
|
|
36
|
+
#
|
|
37
|
+
# it { expect(used).to eq(1) }
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# # good
|
|
41
|
+
# describe Foo do
|
|
42
|
+
# let(:used) { 1 }
|
|
43
|
+
#
|
|
44
|
+
# it { expect(used).to eq(1) }
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# @example CheckLetBang: true (default)
|
|
48
|
+
# # bad
|
|
49
|
+
# describe Foo do
|
|
50
|
+
# let!(:widget) { create(:widget) }
|
|
51
|
+
#
|
|
52
|
+
# it { expect(Widget.count).to eq(1) }
|
|
53
|
+
# end
|
|
54
|
+
#
|
|
55
|
+
# @example CheckLetBang: false
|
|
56
|
+
# # good - `let!` is assumed to be used for its side effects
|
|
57
|
+
# describe Foo do
|
|
58
|
+
# let!(:widget) { create(:widget) }
|
|
59
|
+
#
|
|
60
|
+
# it { expect(Widget.count).to eq(1) }
|
|
61
|
+
# end
|
|
62
|
+
class UnusedLet < ::RuboCop::Cop::RSpec::Base
|
|
63
|
+
MSG = "`%<helper>s(:%<name>s)` is not referenced anywhere. " \
|
|
64
|
+
"Remove it or reference it in an example."
|
|
65
|
+
|
|
66
|
+
DYNAMIC_DISPATCH_METHODS = %i[
|
|
67
|
+
send public_send __send__ method respond_to?
|
|
68
|
+
].freeze
|
|
69
|
+
|
|
70
|
+
# Signatures for the helpers defined dynamically by the node pattern
|
|
71
|
+
# macros below (rbs-inline cannot infer these).
|
|
72
|
+
#
|
|
73
|
+
# @rbs!
|
|
74
|
+
# def example_group?: (RuboCop::AST::Node node) -> bool
|
|
75
|
+
# def spec_group?: (RuboCop::AST::Node node) -> bool
|
|
76
|
+
# def let_definition: (RuboCop::AST::Node node) -> [ Symbol, (Symbol | String) ]?
|
|
77
|
+
# def method_called?: (RuboCop::AST::Node node, Symbol name) -> bool
|
|
78
|
+
# def contains_inclusion?: (RuboCop::AST::Node node) -> bool
|
|
79
|
+
|
|
80
|
+
def_node_matcher :example_group?, <<~PATTERN
|
|
81
|
+
(block (send #rspec? #ExampleGroups.all ...) ...)
|
|
82
|
+
PATTERN
|
|
83
|
+
|
|
84
|
+
def_node_matcher :spec_group?, <<~PATTERN
|
|
85
|
+
(block (send #rspec? {#ExampleGroups.all #SharedGroups.all} ...) ...)
|
|
86
|
+
PATTERN
|
|
87
|
+
|
|
88
|
+
def_node_matcher :let_definition, <<~PATTERN
|
|
89
|
+
{
|
|
90
|
+
(block (send nil? ${:let :let!} ({sym str} $_) ...) ...)
|
|
91
|
+
(send nil? ${:let :let!} ({sym str} $_) block_pass)
|
|
92
|
+
}
|
|
93
|
+
PATTERN
|
|
94
|
+
|
|
95
|
+
def_node_search :method_called?, "(send nil? %)"
|
|
96
|
+
|
|
97
|
+
def_node_search :contains_inclusion?, "(send nil? #Includes.all ...)"
|
|
98
|
+
|
|
99
|
+
# @rbs node: RuboCop::AST::Node
|
|
100
|
+
def on_block(node) #: void
|
|
101
|
+
return unless example_group?(node)
|
|
102
|
+
# A shared example inclusion anywhere in this group's subtree can
|
|
103
|
+
# consume any `let` visible here, so we cannot judge them.
|
|
104
|
+
return if contains_inclusion?(node)
|
|
105
|
+
|
|
106
|
+
RuboCop::RSpec::ExampleGroup.new(node).lets.each do |let|
|
|
107
|
+
check_let(node, let)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
# @rbs group: RuboCop::AST::Node
|
|
114
|
+
# @rbs let: RuboCop::AST::Node
|
|
115
|
+
def check_let(group, let) #: void
|
|
116
|
+
helper, name = let_definition(let)
|
|
117
|
+
return unless name
|
|
118
|
+
return if helper == :let! && !cop_config["CheckLetBang"]
|
|
119
|
+
return if override_chain?(group, name)
|
|
120
|
+
return if referenced?(group, name)
|
|
121
|
+
|
|
122
|
+
node = let #: untyped
|
|
123
|
+
send_node = node.block_type? ? node.send_node : node
|
|
124
|
+
add_offense(send_node, message: format(MSG, helper: helper, name: name))
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @rbs group: RuboCop::AST::Node
|
|
128
|
+
# @rbs name: Symbol | String
|
|
129
|
+
def referenced?(group, name) #: bool
|
|
130
|
+
method_called?(group, name.to_sym) ||
|
|
131
|
+
dynamically_referenced?(group, name)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# @rbs group: RuboCop::AST::Node
|
|
135
|
+
# @rbs name: Symbol | String
|
|
136
|
+
def dynamically_referenced?(group, name) #: bool
|
|
137
|
+
target = name.to_s
|
|
138
|
+
group.each_descendant(:send).any? do |send_node|
|
|
139
|
+
node = send_node #: untyped
|
|
140
|
+
next false unless DYNAMIC_DISPATCH_METHODS.include?(node.method_name)
|
|
141
|
+
|
|
142
|
+
arg = node.first_argument
|
|
143
|
+
arg&.type?(:sym, :str) && arg.value.to_s == target
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# @rbs group: RuboCop::AST::Node
|
|
148
|
+
# @rbs name: Symbol | String
|
|
149
|
+
def override_chain?(group, name) #: bool
|
|
150
|
+
overrides_outer?(group, name) ||
|
|
151
|
+
redefined_in_descendant?(group, name)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# @rbs group: RuboCop::AST::Node
|
|
155
|
+
# @rbs name: Symbol | String
|
|
156
|
+
def overrides_outer?(group, name) #: bool
|
|
157
|
+
group.each_ancestor(:block).any? do |ancestor|
|
|
158
|
+
next false unless spec_group?(ancestor)
|
|
159
|
+
|
|
160
|
+
RuboCop::RSpec::ExampleGroup.new(ancestor).lets.any? do |let|
|
|
161
|
+
_, other = let_definition(let)
|
|
162
|
+
other == name
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# @rbs group: RuboCop::AST::Node
|
|
168
|
+
# @rbs name: Symbol | String
|
|
169
|
+
def redefined_in_descendant?(group, name) #: bool
|
|
170
|
+
count = 0
|
|
171
|
+
group.each_descendant(:block, :send) do |descendant|
|
|
172
|
+
_, other = let_definition(descendant)
|
|
173
|
+
count += 1 if other == name
|
|
174
|
+
return true if count > 1
|
|
175
|
+
end
|
|
176
|
+
false
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lint_roller"
|
|
4
|
+
|
|
5
|
+
module Rubocop
|
|
6
|
+
module Rspec
|
|
7
|
+
module Unused
|
|
8
|
+
module Let
|
|
9
|
+
# A RuboCop plugin (LintRoller) that registers this gem's cops and
|
|
10
|
+
# default configuration. Referenced from the gemspec via the
|
|
11
|
+
# `default_lint_roller_plugin` metadata, so users can enable it with:
|
|
12
|
+
#
|
|
13
|
+
# plugins:
|
|
14
|
+
# - rubocop-rspec
|
|
15
|
+
# - rubocop-rspec-unused-let
|
|
16
|
+
class Plugin < LintRoller::Plugin
|
|
17
|
+
# @rbs override
|
|
18
|
+
def about
|
|
19
|
+
LintRoller::About.new(
|
|
20
|
+
name: "rubocop-rspec-unused-let",
|
|
21
|
+
version: VERSION,
|
|
22
|
+
homepage: "https://github.com/tk0miya/rubocop-rspec-unused-let",
|
|
23
|
+
description: "Detects unreferenced RSpec `let` definitions."
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @rbs override
|
|
28
|
+
def supported?(context)
|
|
29
|
+
context.engine == :rubocop
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @rbs override
|
|
33
|
+
def rules(_context)
|
|
34
|
+
LintRoller::Rules.new(
|
|
35
|
+
type: :path,
|
|
36
|
+
config_format: :rubocop,
|
|
37
|
+
value: CONFIG_DEFAULT
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
require "rubocop"
|
|
6
|
+
require "rubocop-rspec"
|
|
7
|
+
|
|
8
|
+
require_relative "let/version"
|
|
9
|
+
|
|
10
|
+
module Rubocop
|
|
11
|
+
module Rspec
|
|
12
|
+
module Unused
|
|
13
|
+
module Let
|
|
14
|
+
class Error < StandardError; end
|
|
15
|
+
|
|
16
|
+
PROJECT_ROOT = Pathname.new(__dir__ || ".").join("../../../..").expand_path.freeze #: Pathname
|
|
17
|
+
CONFIG_DEFAULT = PROJECT_ROOT.join("config", "default.yml").freeze #: Pathname
|
|
18
|
+
|
|
19
|
+
private_constant :PROJECT_ROOT
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
require_relative "let/plugin"
|
|
26
|
+
|
|
27
|
+
require_relative "../../cop/rspec/unused_let"
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
---
|
|
2
|
+
path: ".gem_rbs_collection"
|
|
3
|
+
gems:
|
|
4
|
+
- name: ast
|
|
5
|
+
version: '2.4'
|
|
6
|
+
source:
|
|
7
|
+
type: git
|
|
8
|
+
name: ruby/gem_rbs_collection
|
|
9
|
+
revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
|
|
10
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
11
|
+
repo_dir: gems
|
|
12
|
+
- name: concurrent-ruby
|
|
13
|
+
version: '1.1'
|
|
14
|
+
source:
|
|
15
|
+
type: git
|
|
16
|
+
name: ruby/gem_rbs_collection
|
|
17
|
+
revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
|
|
18
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
19
|
+
repo_dir: gems
|
|
20
|
+
- name: csv
|
|
21
|
+
version: '3.3'
|
|
22
|
+
source:
|
|
23
|
+
type: git
|
|
24
|
+
name: ruby/gem_rbs_collection
|
|
25
|
+
revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
|
|
26
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
27
|
+
repo_dir: gems
|
|
28
|
+
- name: diff-lcs
|
|
29
|
+
version: '1.5'
|
|
30
|
+
source:
|
|
31
|
+
type: git
|
|
32
|
+
name: ruby/gem_rbs_collection
|
|
33
|
+
revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
|
|
34
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
35
|
+
repo_dir: gems
|
|
36
|
+
- name: ffi
|
|
37
|
+
version: 1.17.4
|
|
38
|
+
source:
|
|
39
|
+
type: rubygems
|
|
40
|
+
- name: fileutils
|
|
41
|
+
version: '0'
|
|
42
|
+
source:
|
|
43
|
+
type: stdlib
|
|
44
|
+
- name: forwardable
|
|
45
|
+
version: '0'
|
|
46
|
+
source:
|
|
47
|
+
type: stdlib
|
|
48
|
+
- name: json
|
|
49
|
+
version: '0'
|
|
50
|
+
source:
|
|
51
|
+
type: stdlib
|
|
52
|
+
- name: language_server-protocol
|
|
53
|
+
version: 3.17.0.6
|
|
54
|
+
source:
|
|
55
|
+
type: rubygems
|
|
56
|
+
- name: lint_roller
|
|
57
|
+
version: '1.1'
|
|
58
|
+
source:
|
|
59
|
+
type: git
|
|
60
|
+
name: ruby/gem_rbs_collection
|
|
61
|
+
revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
|
|
62
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
63
|
+
repo_dir: gems
|
|
64
|
+
- name: listen
|
|
65
|
+
version: '3.9'
|
|
66
|
+
source:
|
|
67
|
+
type: git
|
|
68
|
+
name: ruby/gem_rbs_collection
|
|
69
|
+
revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
|
|
70
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
71
|
+
repo_dir: gems
|
|
72
|
+
- name: logger
|
|
73
|
+
version: '1.7'
|
|
74
|
+
source:
|
|
75
|
+
type: git
|
|
76
|
+
name: ruby/gem_rbs_collection
|
|
77
|
+
revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
|
|
78
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
79
|
+
repo_dir: gems
|
|
80
|
+
- name: monitor
|
|
81
|
+
version: '0'
|
|
82
|
+
source:
|
|
83
|
+
type: stdlib
|
|
84
|
+
- name: optparse
|
|
85
|
+
version: '0'
|
|
86
|
+
source:
|
|
87
|
+
type: stdlib
|
|
88
|
+
- name: parallel
|
|
89
|
+
version: '1.20'
|
|
90
|
+
source:
|
|
91
|
+
type: git
|
|
92
|
+
name: ruby/gem_rbs_collection
|
|
93
|
+
revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
|
|
94
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
95
|
+
repo_dir: gems
|
|
96
|
+
- name: parser
|
|
97
|
+
version: '3.2'
|
|
98
|
+
source:
|
|
99
|
+
type: git
|
|
100
|
+
name: ruby/gem_rbs_collection
|
|
101
|
+
revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
|
|
102
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
103
|
+
repo_dir: gems
|
|
104
|
+
- name: prism
|
|
105
|
+
version: 1.9.0
|
|
106
|
+
source:
|
|
107
|
+
type: rubygems
|
|
108
|
+
- name: rainbow
|
|
109
|
+
version: '3.0'
|
|
110
|
+
source:
|
|
111
|
+
type: git
|
|
112
|
+
name: ruby/gem_rbs_collection
|
|
113
|
+
revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
|
|
114
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
115
|
+
repo_dir: gems
|
|
116
|
+
- name: rake
|
|
117
|
+
version: '13.0'
|
|
118
|
+
source:
|
|
119
|
+
type: git
|
|
120
|
+
name: ruby/gem_rbs_collection
|
|
121
|
+
revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
|
|
122
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
123
|
+
repo_dir: gems
|
|
124
|
+
- name: random-formatter
|
|
125
|
+
version: '0'
|
|
126
|
+
source:
|
|
127
|
+
type: stdlib
|
|
128
|
+
- name: rbs
|
|
129
|
+
version: 4.0.3
|
|
130
|
+
source:
|
|
131
|
+
type: rubygems
|
|
132
|
+
- name: rbs-inline
|
|
133
|
+
version: 0.14.0
|
|
134
|
+
source:
|
|
135
|
+
type: rubygems
|
|
136
|
+
- name: rdoc
|
|
137
|
+
version: '0'
|
|
138
|
+
source:
|
|
139
|
+
type: stdlib
|
|
140
|
+
- name: regexp_parser
|
|
141
|
+
version: '2.8'
|
|
142
|
+
source:
|
|
143
|
+
type: git
|
|
144
|
+
name: ruby/gem_rbs_collection
|
|
145
|
+
revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
|
|
146
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
147
|
+
repo_dir: gems
|
|
148
|
+
- name: rubocop
|
|
149
|
+
version: '1.57'
|
|
150
|
+
source:
|
|
151
|
+
type: git
|
|
152
|
+
name: ruby/gem_rbs_collection
|
|
153
|
+
revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
|
|
154
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
155
|
+
repo_dir: gems
|
|
156
|
+
- name: rubocop-ast
|
|
157
|
+
version: '1.46'
|
|
158
|
+
source:
|
|
159
|
+
type: git
|
|
160
|
+
name: ruby/gem_rbs_collection
|
|
161
|
+
revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
|
|
162
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
163
|
+
repo_dir: gems
|
|
164
|
+
- name: rubocop-numbered-params
|
|
165
|
+
version: 1.0.0
|
|
166
|
+
source:
|
|
167
|
+
type: rubygems
|
|
168
|
+
- name: rubocop-rbs_inline
|
|
169
|
+
version: 1.6.0
|
|
170
|
+
source:
|
|
171
|
+
type: rubygems
|
|
172
|
+
- name: securerandom
|
|
173
|
+
version: '0'
|
|
174
|
+
source:
|
|
175
|
+
type: stdlib
|
|
176
|
+
- name: stringio
|
|
177
|
+
version: '0'
|
|
178
|
+
source:
|
|
179
|
+
type: stdlib
|
|
180
|
+
- name: strscan
|
|
181
|
+
version: '0'
|
|
182
|
+
source:
|
|
183
|
+
type: stdlib
|
|
184
|
+
- name: tsort
|
|
185
|
+
version: '0'
|
|
186
|
+
source:
|
|
187
|
+
type: stdlib
|
|
188
|
+
- name: uri
|
|
189
|
+
version: '0'
|
|
190
|
+
source:
|
|
191
|
+
type: stdlib
|
|
192
|
+
gemfile_lock_path: Gemfile.lock
|
data/rbs_collection.yaml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Download sources
|
|
2
|
+
sources:
|
|
3
|
+
- type: git
|
|
4
|
+
name: ruby/gem_rbs_collection
|
|
5
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
6
|
+
revision: main
|
|
7
|
+
repo_dir: gems
|
|
8
|
+
|
|
9
|
+
# You can specify local directories as sources also.
|
|
10
|
+
# - type: local
|
|
11
|
+
# path: path/to/local/repository
|
|
12
|
+
|
|
13
|
+
# A directory to install the downloaded RBS files.
|
|
14
|
+
path: .gem_rbs_collection
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Generated from lib/rubocop/cop/rspec/unused_let.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module RSpec
|
|
6
|
+
# Checks for `let` definitions that are never referenced.
|
|
7
|
+
#
|
|
8
|
+
# A `let` (or `let!`) whose name is never used within its scope is dead
|
|
9
|
+
# code that makes specs harder to read. This cop flags such definitions.
|
|
10
|
+
#
|
|
11
|
+
# `let!` is checked as well by default. Since it is sometimes used purely
|
|
12
|
+
# for its side effects (e.g. `let!(:user) { create(:user) }`), you can opt
|
|
13
|
+
# out with `CheckLetBang: false`.
|
|
14
|
+
#
|
|
15
|
+
# To avoid false positives, the cop deliberately stays silent whenever it
|
|
16
|
+
# cannot see every possible reference:
|
|
17
|
+
#
|
|
18
|
+
# * `let` definitions inside a `shared_examples`/`shared_context` block are
|
|
19
|
+
# ignored, because their consumers live in the (possibly external)
|
|
20
|
+
# including example groups.
|
|
21
|
+
# * When an example group's subtree contains a shared example inclusion
|
|
22
|
+
# (`it_behaves_like`, `include_examples`, `include_context`, ...), the
|
|
23
|
+
# `let` definitions visible at that inclusion point are ignored, because
|
|
24
|
+
# the (possibly external) shared block may reference them.
|
|
25
|
+
# * `let` definitions that participate in an override chain (redefined in a
|
|
26
|
+
# nested group, or overriding an outer definition) are ignored, since the
|
|
27
|
+
# outer definition may be reached through `super`.
|
|
28
|
+
#
|
|
29
|
+
# Dynamic references such as `send(:foo)` are treated as usages.
|
|
30
|
+
#
|
|
31
|
+
# @example
|
|
32
|
+
# # bad
|
|
33
|
+
# describe Foo do
|
|
34
|
+
# let(:used) { 1 }
|
|
35
|
+
# let(:unused) { 2 }
|
|
36
|
+
#
|
|
37
|
+
# it { expect(used).to eq(1) }
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# # good
|
|
41
|
+
# describe Foo do
|
|
42
|
+
# let(:used) { 1 }
|
|
43
|
+
#
|
|
44
|
+
# it { expect(used).to eq(1) }
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# @example CheckLetBang: true (default)
|
|
48
|
+
# # bad
|
|
49
|
+
# describe Foo do
|
|
50
|
+
# let!(:widget) { create(:widget) }
|
|
51
|
+
#
|
|
52
|
+
# it { expect(Widget.count).to eq(1) }
|
|
53
|
+
# end
|
|
54
|
+
#
|
|
55
|
+
# @example CheckLetBang: false
|
|
56
|
+
# # good - `let!` is assumed to be used for its side effects
|
|
57
|
+
# describe Foo do
|
|
58
|
+
# let!(:widget) { create(:widget) }
|
|
59
|
+
#
|
|
60
|
+
# it { expect(Widget.count).to eq(1) }
|
|
61
|
+
# end
|
|
62
|
+
class UnusedLet < ::RuboCop::Cop::RSpec::Base
|
|
63
|
+
MSG: ::String
|
|
64
|
+
|
|
65
|
+
DYNAMIC_DISPATCH_METHODS: untyped
|
|
66
|
+
|
|
67
|
+
def example_group?: (RuboCop::AST::Node node) -> bool
|
|
68
|
+
|
|
69
|
+
def spec_group?: (RuboCop::AST::Node node) -> bool
|
|
70
|
+
|
|
71
|
+
def let_definition: (RuboCop::AST::Node node) -> [ Symbol, Symbol | String ]?
|
|
72
|
+
|
|
73
|
+
def method_called?: (RuboCop::AST::Node node, Symbol name) -> bool
|
|
74
|
+
|
|
75
|
+
def contains_inclusion?: (RuboCop::AST::Node node) -> bool
|
|
76
|
+
|
|
77
|
+
# @rbs node: RuboCop::AST::Node
|
|
78
|
+
def on_block: (RuboCop::AST::Node node) -> void
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
# @rbs group: RuboCop::AST::Node
|
|
83
|
+
# @rbs let: RuboCop::AST::Node
|
|
84
|
+
def check_let: (RuboCop::AST::Node group, RuboCop::AST::Node let) -> void
|
|
85
|
+
|
|
86
|
+
# @rbs group: RuboCop::AST::Node
|
|
87
|
+
# @rbs name: Symbol | String
|
|
88
|
+
def referenced?: (RuboCop::AST::Node group, Symbol | String name) -> bool
|
|
89
|
+
|
|
90
|
+
# @rbs group: RuboCop::AST::Node
|
|
91
|
+
# @rbs name: Symbol | String
|
|
92
|
+
def dynamically_referenced?: (RuboCop::AST::Node group, Symbol | String name) -> bool
|
|
93
|
+
|
|
94
|
+
# @rbs group: RuboCop::AST::Node
|
|
95
|
+
# @rbs name: Symbol | String
|
|
96
|
+
def override_chain?: (RuboCop::AST::Node group, Symbol | String name) -> bool
|
|
97
|
+
|
|
98
|
+
# @rbs group: RuboCop::AST::Node
|
|
99
|
+
# @rbs name: Symbol | String
|
|
100
|
+
def overrides_outer?: (RuboCop::AST::Node group, Symbol | String name) -> bool
|
|
101
|
+
|
|
102
|
+
# @rbs group: RuboCop::AST::Node
|
|
103
|
+
# @rbs name: Symbol | String
|
|
104
|
+
def redefined_in_descendant?: (RuboCop::AST::Node group, Symbol | String name) -> bool
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Generated from lib/rubocop/rspec/unused/let/plugin.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Rubocop
|
|
4
|
+
module Rspec
|
|
5
|
+
module Unused
|
|
6
|
+
module Let
|
|
7
|
+
# A RuboCop plugin (LintRoller) that registers this gem's cops and
|
|
8
|
+
# default configuration. Referenced from the gemspec via the
|
|
9
|
+
# `default_lint_roller_plugin` metadata, so users can enable it with:
|
|
10
|
+
#
|
|
11
|
+
# plugins:
|
|
12
|
+
# - rubocop-rspec
|
|
13
|
+
# - rubocop-rspec-unused-let
|
|
14
|
+
class Plugin < LintRoller::Plugin
|
|
15
|
+
# @rbs override
|
|
16
|
+
def about: ...
|
|
17
|
+
|
|
18
|
+
# @rbs override
|
|
19
|
+
def supported?: ...
|
|
20
|
+
|
|
21
|
+
# @rbs override
|
|
22
|
+
def rules: ...
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Generated from lib/rubocop/rspec/unused/let.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Rubocop
|
|
4
|
+
module Rspec
|
|
5
|
+
module Unused
|
|
6
|
+
module Let
|
|
7
|
+
class Error < StandardError
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
PROJECT_ROOT: Pathname
|
|
11
|
+
|
|
12
|
+
CONFIG_DEFAULT: Pathname
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Hand-written stubs for rubocop-rspec, which ships no RBS signatures and is
|
|
2
|
+
# not available in the RBS collection. Only the surface used by this gem's cop
|
|
3
|
+
# is declared.
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module RSpec
|
|
8
|
+
class Base
|
|
9
|
+
def cop_config: () -> Hash[String, untyped]
|
|
10
|
+
|
|
11
|
+
def add_offense: (untyped node, ?message: String) -> void
|
|
12
|
+
|
|
13
|
+
def self.def_node_matcher: (Symbol, String) -> void
|
|
14
|
+
|
|
15
|
+
def self.def_node_search: (Symbol, String) -> void
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
module RSpec
|
|
21
|
+
class ExampleGroup
|
|
22
|
+
def initialize: (RuboCop::AST::Node node) -> void
|
|
23
|
+
|
|
24
|
+
def lets: () -> Array[RuboCop::AST::Node]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rubocop-rspec-unused-let
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Takeshi KOMIYA
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: lint_roller
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rubocop
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.72'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.72'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rubocop-rspec
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '3.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '3.0'
|
|
54
|
+
description: rubocop-rspec-unused-let adds the RSpec/UnusedLet cop, which flags `let`
|
|
55
|
+
(and optionally `let!`) definitions that are never referenced within their scope,
|
|
56
|
+
while staying conservative around shared_examples to avoid false positives.
|
|
57
|
+
email:
|
|
58
|
+
- i.tkomiya@gmail.com
|
|
59
|
+
executables: []
|
|
60
|
+
extensions: []
|
|
61
|
+
extra_rdoc_files: []
|
|
62
|
+
files:
|
|
63
|
+
- ".claude/hooks/claude-code-web-session-start.sh"
|
|
64
|
+
- ".claude/hooks/pre-commit-check.sh"
|
|
65
|
+
- ".claude/hooks/protect-sig-files.sh"
|
|
66
|
+
- ".claude/hooks/rbs-inline.sh"
|
|
67
|
+
- ".claude/hooks/self-review.sh"
|
|
68
|
+
- ".claude/settings.json"
|
|
69
|
+
- ".vscode/extensions.json"
|
|
70
|
+
- ".vscode/settings.json"
|
|
71
|
+
- CHANGELOG.md
|
|
72
|
+
- CODE_OF_CONDUCT.md
|
|
73
|
+
- LICENSE.txt
|
|
74
|
+
- README.md
|
|
75
|
+
- Rakefile
|
|
76
|
+
- Steepfile
|
|
77
|
+
- config/default.yml
|
|
78
|
+
- lib/rubocop-rspec-unused-let.rb
|
|
79
|
+
- lib/rubocop/cop/rspec/unused_let.rb
|
|
80
|
+
- lib/rubocop/rspec/unused/let.rb
|
|
81
|
+
- lib/rubocop/rspec/unused/let/plugin.rb
|
|
82
|
+
- lib/rubocop/rspec/unused/let/version.rb
|
|
83
|
+
- rbs_collection.lock.yaml
|
|
84
|
+
- rbs_collection.yaml
|
|
85
|
+
- sig/rubocop-rspec-unused-let.rbs
|
|
86
|
+
- sig/rubocop/cop/rspec/unused_let.rbs
|
|
87
|
+
- sig/rubocop/rspec/unused/let.rbs
|
|
88
|
+
- sig/rubocop/rspec/unused/let/plugin.rbs
|
|
89
|
+
- sig/rubocop/rspec/unused/let/version.rbs
|
|
90
|
+
- sig/rubocop_rspec_stubs.rbs
|
|
91
|
+
homepage: https://github.com/tk0miya/rubocop-rspec-unused-let
|
|
92
|
+
licenses:
|
|
93
|
+
- MIT
|
|
94
|
+
metadata:
|
|
95
|
+
homepage_uri: https://github.com/tk0miya/rubocop-rspec-unused-let
|
|
96
|
+
source_code_uri: https://github.com/tk0miya/rubocop-rspec-unused-let
|
|
97
|
+
changelog_uri: https://github.com/tk0miya/rubocop-rspec-unused-let/blob/main/CHANGELOG.md
|
|
98
|
+
default_lint_roller_plugin: Rubocop::Rspec::Unused::Let::Plugin
|
|
99
|
+
rubygems_mfa_required: 'true'
|
|
100
|
+
rdoc_options: []
|
|
101
|
+
require_paths:
|
|
102
|
+
- lib
|
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
104
|
+
requirements:
|
|
105
|
+
- - ">="
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: 2.7.0
|
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
|
+
requirements:
|
|
110
|
+
- - ">="
|
|
111
|
+
- !ruby/object:Gem::Version
|
|
112
|
+
version: '0'
|
|
113
|
+
requirements: []
|
|
114
|
+
rubygems_version: 4.0.10
|
|
115
|
+
specification_version: 4
|
|
116
|
+
summary: A RuboCop cop that detects unreferenced RSpec `let` definitions.
|
|
117
|
+
test_files: []
|