danger-dangermattic 1.2.4 → 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/.buildkite/pipeline.yml +2 -2
- data/.github/CODEOWNERS +13 -0
- data/.github/dependabot.yml +32 -0
- data/.github/workflows/README.md +18 -3
- data/.github/workflows/reusable-check-labels-on-issues.yml +100 -27
- data/.github/workflows/reusable-run-danger.yml +2 -2
- data/AGENTS.md +35 -0
- data/CHANGELOG.md +12 -0
- data/CLAUDE.md +1 -0
- data/Gemfile.lock +32 -31
- data/README.md +28 -0
- data/danger-dangermattic.gemspec +1 -1
- data/lib/dangermattic/gem_version.rb +1 -1
- data/lib/dangermattic/plugins/android_strings_checker.rb +76 -0
- data/lib/dangermattic/plugins/pr_size_checker.rb +79 -12
- data/spec/android_strings_checker_spec.rb +218 -0
- data/spec/pr_size_checker_spec.rb +114 -0
- metadata +14 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1ea5b40de9b9c82ffa11d1f10449caacbdd87051a9f2d1d7765a73869fe923a6
|
|
4
|
+
data.tar.gz: 82a8e207c33c9cddde899d05094bd6e1a128d6d8ce95e4847f59248a3f0af1c0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7c059b2ecabb7c0e126811bb3f5f8b0b3555feec5ed14a1df606dfcc61201a7540cf1242622802c0f76b6b4f59836a8038f275fb0491476b397d0967a673f2ea
|
|
7
|
+
data.tar.gz: 9cf786de34875f9cdd091aa479d285f13a8a78950c86bb480467c3f8aab6499dbbf7b3b7142d8cd6c8237b38c6247f38251f77e0e264ab4fb3c65cc81d557302
|
data/.buildkite/pipeline.yml
CHANGED
|
@@ -41,13 +41,13 @@ steps:
|
|
|
41
41
|
#################
|
|
42
42
|
# Danger Lint
|
|
43
43
|
#################
|
|
44
|
-
- label: "
|
|
44
|
+
- label: ":danger: Lint (Danger)"
|
|
45
45
|
key: dangerlint
|
|
46
46
|
command: |
|
|
47
47
|
echo "--- :rubygems: Setting up Gems"
|
|
48
48
|
bundle install
|
|
49
49
|
|
|
50
|
-
echo "---
|
|
50
|
+
echo "--- :danger: Run Danger Lint"
|
|
51
51
|
bundle exec danger plugins lint
|
|
52
52
|
plugins: *common_plugins
|
|
53
53
|
|
data/.github/CODEOWNERS
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Ruby dependency surface
|
|
2
|
+
Gemfile* @Automattic/apps-infra-tooling
|
|
3
|
+
*.gemspec @Automattic/apps-infra-tooling
|
|
4
|
+
.bundle/ @Automattic/apps-infra-tooling
|
|
5
|
+
.rubocop*.yml @Automattic/apps-infra-tooling
|
|
6
|
+
|
|
7
|
+
# CI and automation
|
|
8
|
+
.buildkite/ @Automattic/apps-infra-tooling
|
|
9
|
+
.github/workflows/ @Automattic/apps-infra-tooling
|
|
10
|
+
.github/dependabot.yml @Automattic/apps-infra-tooling
|
|
11
|
+
|
|
12
|
+
# Toolchain and environment pins
|
|
13
|
+
.ruby-version @Automattic/apps-infra-tooling
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: "github-actions"
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: "weekly"
|
|
7
|
+
day: "monday"
|
|
8
|
+
open-pull-requests-limit: 10
|
|
9
|
+
groups:
|
|
10
|
+
actions-minor-patch:
|
|
11
|
+
patterns:
|
|
12
|
+
- '*'
|
|
13
|
+
update-types:
|
|
14
|
+
- minor
|
|
15
|
+
- patch
|
|
16
|
+
actions-major:
|
|
17
|
+
patterns:
|
|
18
|
+
- '*'
|
|
19
|
+
update-types:
|
|
20
|
+
- major
|
|
21
|
+
cooldown:
|
|
22
|
+
default-days: 7
|
|
23
|
+
semver-major-days: 14
|
|
24
|
+
|
|
25
|
+
- package-ecosystem: bundler
|
|
26
|
+
directory: /
|
|
27
|
+
schedule:
|
|
28
|
+
interval: daily
|
|
29
|
+
groups:
|
|
30
|
+
ruby-minor-and-patch:
|
|
31
|
+
update-types: [minor, patch]
|
|
32
|
+
open-pull-requests-limit: 5
|
data/.github/workflows/README.md
CHANGED
|
@@ -11,19 +11,34 @@ All jobs are run on `ubuntu-latest`.
|
|
|
11
11
|
This workflow is an independent check (not using Danger) to verify if the labels on an issue match specified regex patterns.
|
|
12
12
|
|
|
13
13
|
### Inputs:
|
|
14
|
-
- `label-format-list`: JSON
|
|
14
|
+
- `label-format-list`: JSON array of regex strings expected for the labels (default: `[".*"]`)
|
|
15
15
|
- `label-error-message`: Error message when labels don't match
|
|
16
|
-
- `label-success-message`:
|
|
16
|
+
- `label-success-message`: Deprecated and ignored. Kept only for backward compatibility with existing callers, and scheduled for removal in the next major release.
|
|
17
17
|
- `cancel-running-jobs`: Cancel in-progress jobs when new ones are created (default: `true`)
|
|
18
18
|
|
|
19
|
+
Example:
|
|
20
|
+
|
|
21
|
+
```yaml
|
|
22
|
+
with:
|
|
23
|
+
label-format-list: |
|
|
24
|
+
[
|
|
25
|
+
"^\\[.+\\]",
|
|
26
|
+
"^[[:alnum:]]"
|
|
27
|
+
]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Backslashes in regex patterns must be doubled because the workflow parses the input as JSON.
|
|
31
|
+
|
|
19
32
|
### Secrets:
|
|
20
33
|
- `github-token`: Required GitHub token
|
|
34
|
+
- The token must resolve through `gh api user` to the same login that appears on issue comments, because the workflow only manages comments authored by that login.
|
|
21
35
|
|
|
22
36
|
### Job: `check-issue-labels`
|
|
23
37
|
- Permissions: `issues: write`
|
|
24
38
|
- Main step: "🏷️ Check Issue Labels"
|
|
25
39
|
- Checks if issue labels match the specified regex patterns
|
|
26
|
-
-
|
|
40
|
+
- Updates a managed comment authored by the configured token when labels are missing
|
|
41
|
+
- Removes that managed comment when labels become valid
|
|
27
42
|
|
|
28
43
|
## Retry Buildkite Step on Pull Request Events
|
|
29
44
|
|
|
@@ -4,7 +4,7 @@ on:
|
|
|
4
4
|
workflow_call:
|
|
5
5
|
inputs:
|
|
6
6
|
label-format-list:
|
|
7
|
-
description: The
|
|
7
|
+
description: The regex formats expected for the labels; must be a JSON array of strings
|
|
8
8
|
default: |
|
|
9
9
|
[
|
|
10
10
|
".*"
|
|
@@ -17,8 +17,8 @@ on:
|
|
|
17
17
|
type: string
|
|
18
18
|
required: false
|
|
19
19
|
label-success-message:
|
|
20
|
-
description:
|
|
21
|
-
default:
|
|
20
|
+
description: Deprecated and ignored. Kept only for backward compatibility with existing callers, and scheduled for removal in the next major release.
|
|
21
|
+
default: ''
|
|
22
22
|
type: string
|
|
23
23
|
required: false
|
|
24
24
|
cancel-running-jobs:
|
|
@@ -42,22 +42,103 @@ jobs:
|
|
|
42
42
|
steps:
|
|
43
43
|
- name: 🏷️ Check Issue Labels
|
|
44
44
|
env:
|
|
45
|
-
|
|
45
|
+
GH_TOKEN: ${{ secrets.github-token }}
|
|
46
46
|
GH_REPO: ${{ github.repository }}
|
|
47
47
|
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
|
48
48
|
ISSUE_LABELS: ${{ toJSON(github.event.issue.labels.*.name) }}
|
|
49
49
|
LABEL_REGEX_LIST: ${{ inputs.label-format-list }}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
<!-- generated_by_dangermattic -->
|
|
53
|
-
ISSUE_ERROR_COMMENT: >
|
|
54
|
-
${{ inputs.label-error-message }}
|
|
55
|
-
<!-- generated_by_dangermattic -->
|
|
50
|
+
DEPRECATED_SUCCESS_MESSAGE: ${{ inputs.label-success-message }}
|
|
51
|
+
ISSUE_ERROR_MESSAGE: ${{ inputs.label-error-message }}
|
|
56
52
|
run: |
|
|
57
|
-
#!/bin/bash
|
|
53
|
+
#!/usr/bin/env bash
|
|
54
|
+
|
|
55
|
+
set -euo pipefail
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
comment_marker='<!-- generated_by_dangermattic -->'
|
|
58
|
+
authenticated_login=''
|
|
59
|
+
managed_comment_id=''
|
|
60
|
+
declare -a managed_comment_ids=()
|
|
61
|
+
|
|
62
|
+
if [ -n "$DEPRECATED_SUCCESS_MESSAGE" ]; then
|
|
63
|
+
echo "⚠️ label-success-message is deprecated and ignored. Success now removes the managed workflow comment."
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
build_comment_body() {
|
|
67
|
+
local message="$1"
|
|
68
|
+
|
|
69
|
+
printf '%s\n%s' "$message" "$comment_marker"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
load_authenticated_login() {
|
|
73
|
+
authenticated_login="$(gh api user --jq '.login')"
|
|
74
|
+
|
|
75
|
+
if [ -z "$authenticated_login" ] || [ "$authenticated_login" = 'null' ]; then
|
|
76
|
+
echo '❌ Unable to determine the authenticated GitHub login for managed comment filtering.'
|
|
77
|
+
exit 1
|
|
78
|
+
fi
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
load_managed_comment_ids() {
|
|
82
|
+
readarray -t managed_comment_ids < <(
|
|
83
|
+
gh api "repos/$GH_REPO/issues/$ISSUE_NUMBER/comments" --paginate |
|
|
84
|
+
jq -r --arg author "$authenticated_login" --arg marker "$comment_marker" '.[] | select(.user.login == $author) | select((.body // "") | contains($marker)) | .id'
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if [ "${#managed_comment_ids[@]}" -gt 0 ]; then
|
|
88
|
+
managed_comment_id="${managed_comment_ids[$((${#managed_comment_ids[@]} - 1))]}"
|
|
89
|
+
else
|
|
90
|
+
managed_comment_id=''
|
|
91
|
+
fi
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
delete_extra_managed_comments() {
|
|
95
|
+
local duplicate_count=0
|
|
96
|
+
|
|
97
|
+
duplicate_count=$((${#managed_comment_ids[@]} - 1))
|
|
98
|
+
if [ "$duplicate_count" -le 0 ]; then
|
|
99
|
+
return
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
for comment_id in "${managed_comment_ids[@]:0:$duplicate_count}"; do
|
|
103
|
+
echo "🧹 Deleting duplicate managed comment $comment_id"
|
|
104
|
+
gh api --method DELETE "repos/$GH_REPO/issues/comments/$comment_id" > /dev/null
|
|
105
|
+
done
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
upsert_managed_comment() {
|
|
109
|
+
local comment_body="$1"
|
|
110
|
+
|
|
111
|
+
if [ -n "$managed_comment_id" ]; then
|
|
112
|
+
echo "✍️ Updating managed comment $managed_comment_id on issue $ISSUE_NUMBER"
|
|
113
|
+
gh api --method PATCH "repos/$GH_REPO/issues/comments/$managed_comment_id" --raw-field "body=$comment_body" > /dev/null
|
|
114
|
+
else
|
|
115
|
+
echo "✍️ Creating managed comment on issue $ISSUE_NUMBER"
|
|
116
|
+
gh api --method POST "repos/$GH_REPO/issues/$ISSUE_NUMBER/comments" --raw-field "body=$comment_body" > /dev/null
|
|
117
|
+
fi
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
delete_managed_comments() {
|
|
121
|
+
if [ "${#managed_comment_ids[@]}" -eq 0 ]; then
|
|
122
|
+
echo "🧼 No managed comment found to delete."
|
|
123
|
+
return
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
for comment_id in "${managed_comment_ids[@]}"; do
|
|
127
|
+
echo "🧼 Deleting managed comment $comment_id"
|
|
128
|
+
gh api --method DELETE "repos/$GH_REPO/issues/comments/$comment_id" > /dev/null
|
|
129
|
+
done
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
readarray -t labels < <(printf '%s\n' "$ISSUE_LABELS" | jq -r '.[]')
|
|
133
|
+
|
|
134
|
+
if ! printf '%s\n' "$LABEL_REGEX_LIST" | jq -e 'type == "array" and all(.[]; type == "string")' > /dev/null; then
|
|
135
|
+
echo "❌ LABEL_REGEX_LIST must be a JSON array of strings."
|
|
136
|
+
echo ' Example: ["^\\[.+\\]", "^[[:alnum:]]"]'
|
|
137
|
+
echo ' Regex backslashes must be doubled because the input is parsed as JSON.'
|
|
138
|
+
exit 1
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
readarray -t label_regex_list < <(printf '%s\n' "$LABEL_REGEX_LIST" | jq -r '.[]')
|
|
61
142
|
|
|
62
143
|
all_patterns_matched=true
|
|
63
144
|
|
|
@@ -78,22 +159,14 @@ jobs:
|
|
|
78
159
|
fi
|
|
79
160
|
done
|
|
80
161
|
|
|
81
|
-
|
|
162
|
+
load_authenticated_login
|
|
163
|
+
load_managed_comment_ids
|
|
164
|
+
delete_extra_managed_comments
|
|
165
|
+
|
|
82
166
|
if [ "$all_patterns_matched" = true ]; then
|
|
83
167
|
echo "✅ All regex patterns have at least one match."
|
|
84
|
-
|
|
168
|
+
delete_managed_comments
|
|
85
169
|
else
|
|
86
170
|
echo "❌ Not all regex patterns have at least one match."
|
|
87
|
-
|
|
88
|
-
fi
|
|
89
|
-
|
|
90
|
-
set +e
|
|
91
|
-
echo "✍️ Attempting to edit existing comment on issue $ISSUE_NUMBER, if it exists:"
|
|
92
|
-
gh issue comment $ISSUE_NUMBER --body "$ISSUE_COMMENT" --edit-last
|
|
93
|
-
comment_update_status=$?
|
|
94
|
-
set -e
|
|
95
|
-
|
|
96
|
-
if [ $comment_update_status -ne 0 ]; then
|
|
97
|
-
echo "✍️ Adding new comment on issue $ISSUE_NUMBER:"
|
|
98
|
-
gh issue comment $ISSUE_NUMBER --body "$ISSUE_COMMENT"
|
|
171
|
+
upsert_managed_comment "$(build_comment_body "$ISSUE_ERROR_MESSAGE")"
|
|
99
172
|
fi
|
|
@@ -26,11 +26,11 @@ jobs:
|
|
|
26
26
|
runs-on: ubuntu-latest
|
|
27
27
|
steps:
|
|
28
28
|
- name: 📥 Checkout Repo
|
|
29
|
-
uses: actions/checkout@
|
|
29
|
+
uses: actions/checkout@v7
|
|
30
30
|
with:
|
|
31
31
|
fetch-depth: 100
|
|
32
32
|
- name: 💎 Ruby Setup
|
|
33
|
-
uses: ruby/setup-ruby@v1
|
|
33
|
+
uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0
|
|
34
34
|
with:
|
|
35
35
|
bundler-cache: true
|
|
36
36
|
- name: ☢️ Danger PR Check
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
## Overview
|
|
2
|
+
|
|
3
|
+
Dangermattic is a shared collection of Danger plugins used across Automattic's mobile repositories.
|
|
4
|
+
It provides reusable Danger rules for PR checks, code review automation, and CI enforcement.
|
|
5
|
+
|
|
6
|
+
## Bootstrap
|
|
7
|
+
|
|
8
|
+
Requires Ruby at the version specified in `.ruby-version`.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
bundle install
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Commands
|
|
15
|
+
|
|
16
|
+
- `bundle exec rake` — run all checks (specs + RuboCop + Danger lint)
|
|
17
|
+
- `bundle exec rspec` — run tests only
|
|
18
|
+
- `bundle exec rubocop` — run linter only
|
|
19
|
+
|
|
20
|
+
## Project Structure
|
|
21
|
+
|
|
22
|
+
- `lib/dangermattic/plugins/` — Danger plugin implementations
|
|
23
|
+
- `lib/dangermattic/plugins/common/` — shared helpers used across plugins
|
|
24
|
+
- `spec/` — RSpec tests; each plugin has a matching `*_spec.rb`
|
|
25
|
+
- `spec/fixtures/` — test fixtures
|
|
26
|
+
|
|
27
|
+
## Conventions
|
|
28
|
+
|
|
29
|
+
- Tests written with RSpec
|
|
30
|
+
- CI via Buildkite, see `.buildkite`
|
|
31
|
+
- Gem releases are triggered by Git tags pushed to the remote and run in CI.
|
|
32
|
+
|
|
33
|
+
## Pitfalls
|
|
34
|
+
|
|
35
|
+
- Together with unit tests, `bundle exec danger plugins lint` must also pass — it validates plugin metadata and code correctness.
|
data/CHANGELOG.md
CHANGED
|
@@ -20,6 +20,18 @@ _None_
|
|
|
20
20
|
|
|
21
21
|
_None_
|
|
22
22
|
|
|
23
|
+
## 1.4.0
|
|
24
|
+
|
|
25
|
+
### New Features
|
|
26
|
+
|
|
27
|
+
- `pr_size_checker`: add optional `line_selector` to `check_diff_size` (and `insertions_size` / `deletions_size` / `diff_size`) to exclude specific changed lines (e.g. comments and blank lines) from the diff-size metric. [#133]
|
|
28
|
+
|
|
29
|
+
## 1.3.0
|
|
30
|
+
|
|
31
|
+
### New Features
|
|
32
|
+
|
|
33
|
+
- Added `android_strings_checker.check_existing_strings_not_modified`, which fails when the value of an existing translatable `<string>` is changed in place (rather than added under a new key). This enforces string-key immutability, which keeps in-progress translations valid in a continuous-localization setup. [#126]
|
|
34
|
+
|
|
23
35
|
## 1.2.4
|
|
24
36
|
|
|
25
37
|
### Internal Changes
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
data/Gemfile.lock
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
danger-dangermattic (1.
|
|
5
|
-
danger (~> 9.
|
|
4
|
+
danger-dangermattic (1.4.0)
|
|
5
|
+
danger (~> 9.5, >= 9.5.3)
|
|
6
6
|
danger-plugin-api (~> 1.0)
|
|
7
7
|
danger-rubocop (~> 0.13)
|
|
8
8
|
rubocop (~> 1.63)
|
|
@@ -10,25 +10,24 @@ PATH
|
|
|
10
10
|
GEM
|
|
11
11
|
remote: https://rubygems.org/
|
|
12
12
|
specs:
|
|
13
|
-
activesupport (8.
|
|
13
|
+
activesupport (8.1.3)
|
|
14
14
|
base64
|
|
15
|
-
benchmark (>= 0.3)
|
|
16
15
|
bigdecimal
|
|
17
16
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
18
17
|
connection_pool (>= 2.2.5)
|
|
19
18
|
drb
|
|
20
19
|
i18n (>= 1.6, < 2)
|
|
20
|
+
json
|
|
21
21
|
logger (>= 1.4.2)
|
|
22
22
|
minitest (>= 5.1)
|
|
23
23
|
securerandom (>= 0.3)
|
|
24
24
|
tzinfo (~> 2.0, >= 2.0.5)
|
|
25
25
|
uri (>= 0.13.1)
|
|
26
|
-
addressable (2.
|
|
27
|
-
public_suffix (>= 2.0.2, <
|
|
26
|
+
addressable (2.9.0)
|
|
27
|
+
public_suffix (>= 2.0.2, < 8.0)
|
|
28
28
|
ast (2.4.3)
|
|
29
29
|
base64 (0.3.0)
|
|
30
|
-
|
|
31
|
-
bigdecimal (3.2.2)
|
|
30
|
+
bigdecimal (4.0.1)
|
|
32
31
|
claide (1.1.0)
|
|
33
32
|
claide-plugins (0.9.2)
|
|
34
33
|
cork
|
|
@@ -36,8 +35,8 @@ GEM
|
|
|
36
35
|
open4 (~> 1.3)
|
|
37
36
|
coderay (1.1.3)
|
|
38
37
|
colored2 (3.1.2)
|
|
39
|
-
concurrent-ruby (1.3.
|
|
40
|
-
connection_pool (
|
|
38
|
+
concurrent-ruby (1.3.7)
|
|
39
|
+
connection_pool (3.0.2)
|
|
41
40
|
cork (0.3.0)
|
|
42
41
|
colored2 (~> 3.1)
|
|
43
42
|
danger (9.5.3)
|
|
@@ -61,14 +60,14 @@ GEM
|
|
|
61
60
|
rubocop (~> 1.0)
|
|
62
61
|
diff-lcs (1.6.2)
|
|
63
62
|
drb (2.2.3)
|
|
64
|
-
faraday (2.
|
|
63
|
+
faraday (2.14.3)
|
|
65
64
|
faraday-net_http (>= 2.0, < 3.5)
|
|
66
65
|
json
|
|
67
66
|
logger
|
|
68
|
-
faraday-http-cache (2.
|
|
67
|
+
faraday-http-cache (2.6.1)
|
|
69
68
|
faraday (>= 0.8)
|
|
70
|
-
faraday-net_http (3.4.
|
|
71
|
-
net-http (
|
|
69
|
+
faraday-net_http (3.4.4)
|
|
70
|
+
net-http (~> 0.5)
|
|
72
71
|
ffi (1.17.2)
|
|
73
72
|
ffi (1.17.2-arm64-darwin)
|
|
74
73
|
formatador (1.1.1)
|
|
@@ -93,11 +92,11 @@ GEM
|
|
|
93
92
|
guard (~> 2.1)
|
|
94
93
|
guard-compat (~> 1.1)
|
|
95
94
|
rspec (>= 2.99.0, < 4.0)
|
|
96
|
-
i18n (1.14.
|
|
95
|
+
i18n (1.14.8)
|
|
97
96
|
concurrent-ruby (~> 1.0)
|
|
98
|
-
json (2.
|
|
99
|
-
kramdown (2.5.
|
|
100
|
-
rexml (>= 3.
|
|
97
|
+
json (2.20.0)
|
|
98
|
+
kramdown (2.5.2)
|
|
99
|
+
rexml (>= 3.4.4)
|
|
101
100
|
kramdown-parser-gfm (1.1.0)
|
|
102
101
|
kramdown (~> 2.0)
|
|
103
102
|
language_server-protocol (3.17.0.5)
|
|
@@ -108,11 +107,13 @@ GEM
|
|
|
108
107
|
logger (1.7.0)
|
|
109
108
|
lumberjack (1.4.0)
|
|
110
109
|
method_source (1.1.0)
|
|
111
|
-
minitest (
|
|
110
|
+
minitest (6.0.2)
|
|
111
|
+
drb (~> 2.0)
|
|
112
|
+
prism (~> 1.5)
|
|
112
113
|
nap (1.1.0)
|
|
113
114
|
nenv (0.3.0)
|
|
114
|
-
net-http (0.
|
|
115
|
-
uri
|
|
115
|
+
net-http (0.9.1)
|
|
116
|
+
uri (>= 0.11.1)
|
|
116
117
|
notiffany (0.1.3)
|
|
117
118
|
nenv (~> 0.1)
|
|
118
119
|
shellany (~> 0.0)
|
|
@@ -125,22 +126,22 @@ GEM
|
|
|
125
126
|
parser (3.3.9.0)
|
|
126
127
|
ast (~> 2.4.1)
|
|
127
128
|
racc
|
|
128
|
-
prism (1.
|
|
129
|
+
prism (1.9.0)
|
|
129
130
|
process_executer (1.3.0)
|
|
130
131
|
pry (0.15.2)
|
|
131
132
|
coderay (~> 1.1)
|
|
132
133
|
method_source (~> 1.0)
|
|
133
134
|
pstore (0.2.0)
|
|
134
|
-
public_suffix (
|
|
135
|
+
public_suffix (7.0.5)
|
|
135
136
|
racc (1.8.1)
|
|
136
137
|
rainbow (3.1.1)
|
|
137
138
|
rake (13.3.0)
|
|
138
139
|
rb-fsevent (0.11.2)
|
|
139
140
|
rb-inotify (0.11.1)
|
|
140
141
|
ffi (~> 1.0)
|
|
141
|
-
rchardet (1.
|
|
142
|
+
rchardet (1.10.0)
|
|
142
143
|
regexp_parser (2.11.0)
|
|
143
|
-
rexml (3.4.
|
|
144
|
+
rexml (3.4.4)
|
|
144
145
|
rspec (3.13.1)
|
|
145
146
|
rspec-core (~> 3.13.0)
|
|
146
147
|
rspec-expectations (~> 3.13.0)
|
|
@@ -175,7 +176,7 @@ GEM
|
|
|
175
176
|
lint_roller (~> 1.1)
|
|
176
177
|
rubocop (~> 1.72, >= 1.72.1)
|
|
177
178
|
ruby-progressbar (1.13.0)
|
|
178
|
-
sawyer (0.9.
|
|
179
|
+
sawyer (0.9.3)
|
|
179
180
|
addressable (>= 2.3.5)
|
|
180
181
|
faraday (>= 0.17.3, < 3)
|
|
181
182
|
securerandom (0.4.1)
|
|
@@ -185,11 +186,11 @@ GEM
|
|
|
185
186
|
thor (1.4.0)
|
|
186
187
|
tzinfo (2.0.6)
|
|
187
188
|
concurrent-ruby (~> 1.0)
|
|
188
|
-
unicode-display_width (3.
|
|
189
|
-
unicode-emoji (~> 4.
|
|
190
|
-
unicode-emoji (4.0
|
|
191
|
-
uri (1.
|
|
192
|
-
yard (0.9.
|
|
189
|
+
unicode-display_width (3.2.0)
|
|
190
|
+
unicode-emoji (~> 4.1)
|
|
191
|
+
unicode-emoji (4.2.0)
|
|
192
|
+
uri (1.1.1)
|
|
193
|
+
yard (0.9.44)
|
|
193
194
|
|
|
194
195
|
PLATFORMS
|
|
195
196
|
arm64-darwin-22
|
data/README.md
CHANGED
|
@@ -71,6 +71,34 @@ my_new_plugin_checker.check_method(param: my_param_value)
|
|
|
71
71
|
|
|
72
72
|
Please follow the existing naming convention for validation and check plugins: classes end with a `*Checker` suffix and the main validation methods are named with a `check_*` prefix.
|
|
73
73
|
|
|
74
|
+
### How to verify a change against a real pull request
|
|
75
|
+
|
|
76
|
+
Unit tests are the main development loop, but before releasing a new or changed check it's often useful to see it run end-to-end against a real pull request.
|
|
77
|
+
You can do this locally with `danger pr`, which evaluates a `Dangerfile` against an existing PR's diff and prints the result to your terminal only, without posting upstream.
|
|
78
|
+
|
|
79
|
+
Point a `Gemfile` at your branch and add the check you want to exercise to a `Dangerfile`:
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
# Gemfile
|
|
83
|
+
source 'https://rubygems.org'
|
|
84
|
+
gem 'danger-dangermattic', git: 'https://github.com/Automattic/dangermattic', branch: 'my-branch'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
# Dangerfile
|
|
89
|
+
my_new_plugin_checker.check_method
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
After a `bundle install`, run it against a PR from inside a checkout of that PR's repository:
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
DANGER_GITHUB_API_TOKEN="$(gh auth token)" \
|
|
96
|
+
BUNDLE_GEMFILE=/path/to/Gemfile \
|
|
97
|
+
bundle exec danger pr https://github.com/<org>/<repo>/pull/<number> \
|
|
98
|
+
--dangerfile=/path/to/Dangerfile
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
A failure prints as an `Errors:` block. `danger pr` only fetches and creates temporary refs that it cleans up afterwards, so it won't change your current branch or working tree.
|
|
74
102
|
## Releasing a new version
|
|
75
103
|
|
|
76
104
|
To create a new release of the Dangermattic gem, use the `new_release` Rake task:
|
data/danger-dangermattic.gemspec
CHANGED
|
@@ -7,6 +7,10 @@ module Danger
|
|
|
7
7
|
#
|
|
8
8
|
# android_strings_checker.check_strings_do_not_refer_resource
|
|
9
9
|
#
|
|
10
|
+
# @example Check that existing translatable strings are not modified in place
|
|
11
|
+
#
|
|
12
|
+
# android_strings_checker.check_existing_strings_not_modified
|
|
13
|
+
#
|
|
10
14
|
# @see Automattic/dangermattic
|
|
11
15
|
# @tags android, localization
|
|
12
16
|
#
|
|
@@ -14,6 +18,18 @@ module Danger
|
|
|
14
18
|
MESSAGE = "This PR adds a translatable entry which references another string resource; this usually causes issues with translations.\n" \
|
|
15
19
|
'Please make sure to set the `translatable="false"` attribute.'
|
|
16
20
|
|
|
21
|
+
STRING_MODIFIED_MESSAGE = 'This PR changes the value of an existing translatable string. Existing string keys must stay immutable so that ' \
|
|
22
|
+
'in-progress and existing translations remain valid: please add a **new** string key for the new copy instead of ' \
|
|
23
|
+
'editing the existing one (old translations stay attached to the old key).'
|
|
24
|
+
|
|
25
|
+
# Default file selector: only the app's source (English) strings, living at `…/res/values/strings.xml`.
|
|
26
|
+
# This deliberately excludes localized `values-<locale>/strings.xml` files (whose values legitimately change
|
|
27
|
+
# when translations are pulled in) and any generated/frozen copies that live outside a `res` directory.
|
|
28
|
+
DEFAULT_SOURCE_STRINGS_SELECTOR = ->(path) { path.match?(%r{(^|/)res/values/strings\.xml$}) }
|
|
29
|
+
|
|
30
|
+
# Matches a single-line `<string name="…">value</string>` entry, capturing its name and value.
|
|
31
|
+
STRING_LINE_REGEX = %r{<string\b[^>]*\sname="(?<name>[^"]+)"[^>]*>(?<value>.*)</string>}
|
|
32
|
+
|
|
17
33
|
# Check if translatable strings reference another string resource in 'strings.xml' files in a pull request.
|
|
18
34
|
#
|
|
19
35
|
# @param report_type [Boolean] (optional) Type of report (:error, :warning, :message) whenever a line matches the criteria. Default is :warning.
|
|
@@ -27,5 +43,65 @@ module Danger
|
|
|
27
43
|
report_type: report_type
|
|
28
44
|
)
|
|
29
45
|
end
|
|
46
|
+
|
|
47
|
+
# Check that the value of an existing translatable string is not modified in place.
|
|
48
|
+
#
|
|
49
|
+
# In a continuous-localization setup, source strings flow to the translation system constantly, so changing
|
|
50
|
+
# the English value of an existing key silently invalidates the translations already done for that key. The
|
|
51
|
+
# safe convention is to leave existing keys untouched and introduce a new key for any new copy. This check
|
|
52
|
+
# enforces that convention by failing when a `<string>` entry present in the base is modified (rather than
|
|
53
|
+
# added under a new key). Adding new keys, removing keys, and renaming keys are all allowed.
|
|
54
|
+
#
|
|
55
|
+
# @param report_type [Symbol] (optional) Type of report (:error, :warning, :message). Default is :error.
|
|
56
|
+
# @param file_selector [Proc] (optional) Selects which files to check. Defaults to the app's source
|
|
57
|
+
# `…/res/values/strings.xml` files, excluding localized and generated copies.
|
|
58
|
+
#
|
|
59
|
+
# @return [void]
|
|
60
|
+
def check_existing_strings_not_modified(report_type: :error, file_selector: DEFAULT_SOURCE_STRINGS_SELECTOR)
|
|
61
|
+
files = git_utils.added_and_modified_files.select(&file_selector)
|
|
62
|
+
|
|
63
|
+
files.each do |file|
|
|
64
|
+
diff = danger.git.diff_for_file(file)
|
|
65
|
+
next if diff.nil?
|
|
66
|
+
|
|
67
|
+
removed = translatable_strings_by_name(git_utils.removed_lines(diff_patch: diff.patch))
|
|
68
|
+
added = translatable_strings_by_name(git_utils.added_lines(diff_patch: diff.patch))
|
|
69
|
+
|
|
70
|
+
modified_keys = removed.keys & added.keys
|
|
71
|
+
modified_keys.each do |name|
|
|
72
|
+
next if removed[name].value == added[name].value
|
|
73
|
+
|
|
74
|
+
final_message = <<~MESSAGE
|
|
75
|
+
#{STRING_MODIFIED_MESSAGE}
|
|
76
|
+
File `#{file}`, string `#{name}`:
|
|
77
|
+
```diff
|
|
78
|
+
-#{removed[name].line.chomp}
|
|
79
|
+
+#{added[name].line.chomp}
|
|
80
|
+
```
|
|
81
|
+
MESSAGE
|
|
82
|
+
|
|
83
|
+
reporter.report(message: final_message, type: report_type)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
ParsedString = Struct.new(:value, :line)
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
# Parses `<string>` entries out of a set of diff content lines, keyed by their `name` attribute.
|
|
93
|
+
# `translatable="false"` entries are ignored, as they are never sent for translation.
|
|
94
|
+
#
|
|
95
|
+
# @param lines [String] Newline-separated content lines (with the diff `+`/`-` markers already stripped).
|
|
96
|
+
#
|
|
97
|
+
# @return [Hash{String => ParsedString}] A mapping of string name to its parsed value and originating line.
|
|
98
|
+
def translatable_strings_by_name(lines)
|
|
99
|
+
lines.each_line.with_object({}) do |line, result|
|
|
100
|
+
next if line.include?('translatable="false"')
|
|
101
|
+
|
|
102
|
+
match = STRING_LINE_REGEX.match(line)
|
|
103
|
+
result[match[:name]] = ParsedString.new(match[:value], line) unless match.nil?
|
|
104
|
+
end
|
|
105
|
+
end
|
|
30
106
|
end
|
|
31
107
|
end
|
|
@@ -18,6 +18,14 @@ module Danger
|
|
|
18
18
|
# # Check the size of insertions in the files selected by the file_selector
|
|
19
19
|
# pr_size_checker.check_diff_size(file_selector: ->(file) { file.include?('/java/test/') }, type: :insertions)
|
|
20
20
|
#
|
|
21
|
+
# @example Running a PR diff size check that excludes comment and blank lines from the count
|
|
22
|
+
#
|
|
23
|
+
# # Only count changed lines that are neither blank nor comments (e.g. Kotlin/Java/Swift)
|
|
24
|
+
# pr_size_checker.check_diff_size(
|
|
25
|
+
# max_size: 300,
|
|
26
|
+
# line_selector: ->(line) { stripped = line.strip; !(stripped.empty? || stripped.start_with?('//', '/*', '*', '*/')) }
|
|
27
|
+
# )
|
|
28
|
+
#
|
|
21
29
|
# @example Running a PR description length check
|
|
22
30
|
#
|
|
23
31
|
# # Check the PR Body using the default parameters, reporting a warning if the PR is smaller than 10 characters
|
|
@@ -39,20 +47,28 @@ module Danger
|
|
|
39
47
|
#
|
|
40
48
|
# @param max_size [Integer] The maximum allowed size for the diff.
|
|
41
49
|
# @param file_selector [Proc] Optional closure to filter the files in the diff to be used for size calculation.
|
|
50
|
+
# @param line_selector [Proc] Optional closure to filter the individual changed lines counted towards the size.
|
|
51
|
+
# It receives the content of an added/removed line (without the leading `+`/`-` diff marker or trailing newline)
|
|
52
|
+
# and should return `true` for lines that should be counted. When provided, the size is computed by iterating the
|
|
53
|
+
# diff patches instead of the cached numstats, which is slower but allows excluding lines such as comments or blanks.
|
|
42
54
|
# @param type [:insertions, :deletions, :all] The type of diff size to check. (default: :all)
|
|
43
55
|
# @param message [String] The message to display if the diff size exceeds the maximum. (default: DEFAULT_DIFF_SIZE_MESSAGE)
|
|
44
56
|
# @param report_type [Symbol] (optional) The type of report for the message. Types: :error, :warning (default), :message.
|
|
45
57
|
#
|
|
46
58
|
# @return [void]
|
|
47
|
-
def check_diff_size(max_size:, file_selector: nil, type: :all, message: format(DEFAULT_DIFF_SIZE_MESSAGE_FORMAT, max_size), report_type: :warning)
|
|
48
|
-
case type
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
def check_diff_size(max_size:, file_selector: nil, line_selector: nil, type: :all, message: format(DEFAULT_DIFF_SIZE_MESSAGE_FORMAT, max_size), report_type: :warning)
|
|
60
|
+
size = case type
|
|
61
|
+
when :insertions
|
|
62
|
+
insertions_size(file_selector: file_selector, line_selector: line_selector)
|
|
63
|
+
when :deletions
|
|
64
|
+
deletions_size(file_selector: file_selector, line_selector: line_selector)
|
|
65
|
+
when :all
|
|
66
|
+
diff_size(file_selector: file_selector, line_selector: line_selector)
|
|
67
|
+
else
|
|
68
|
+
raise ArgumentError, "Unknown diff size type: #{type.inspect}. Use :insertions, :deletions, or :all."
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
reporter.report(message: message, type: report_type) if size > max_size
|
|
56
72
|
end
|
|
57
73
|
|
|
58
74
|
# Check the size of the Pull Request description (PR body) against a specified minimum size.
|
|
@@ -71,9 +87,12 @@ module Danger
|
|
|
71
87
|
# Calculate the total size of insertions in modified files that match the file selector.
|
|
72
88
|
#
|
|
73
89
|
# @param file_selector [Proc] Select the files to be used for the insertions calculation.
|
|
90
|
+
# @param line_selector [Proc] Optional closure to select which added lines are counted (see #check_diff_size).
|
|
74
91
|
#
|
|
75
92
|
# @return [Integer] The total size of insertions in the selected modified files.
|
|
76
|
-
def insertions_size(file_selector: nil)
|
|
93
|
+
def insertions_size(file_selector: nil, line_selector: nil)
|
|
94
|
+
return filtered_diff_size(file_selector: file_selector, line_selector: line_selector, change_types: [:added]) if line_selector
|
|
95
|
+
|
|
77
96
|
return danger.git.insertions unless file_selector
|
|
78
97
|
|
|
79
98
|
# Only check added and modified files - deleted files have 0 insertions
|
|
@@ -88,9 +107,12 @@ module Danger
|
|
|
88
107
|
# Calculate the total size of deletions in modified files that match the file selector.
|
|
89
108
|
#
|
|
90
109
|
# @param file_selector [Proc] Select the files to be used for the deletions calculation.
|
|
110
|
+
# @param line_selector [Proc] Optional closure to select which removed lines are counted (see #check_diff_size).
|
|
91
111
|
#
|
|
92
112
|
# @return [Integer] The total size of deletions in the selected modified files.
|
|
93
|
-
def deletions_size(file_selector: nil)
|
|
113
|
+
def deletions_size(file_selector: nil, line_selector: nil)
|
|
114
|
+
return filtered_diff_size(file_selector: file_selector, line_selector: line_selector, change_types: [:removed]) if line_selector
|
|
115
|
+
|
|
94
116
|
return danger.git.deletions unless file_selector
|
|
95
117
|
|
|
96
118
|
filtered_files = git_utils.all_changed_files.select(&file_selector)
|
|
@@ -104,9 +126,12 @@ module Danger
|
|
|
104
126
|
# Calculate the total size of changes (insertions and deletions) in modified files that match the file selector.
|
|
105
127
|
#
|
|
106
128
|
# @param file_selector [Proc] Select the files to be used for the total insertions and deletions calculation.
|
|
129
|
+
# @param line_selector [Proc] Optional closure to select which added/removed lines are counted (see #check_diff_size).
|
|
107
130
|
#
|
|
108
131
|
# @return [Integer] The total size of changes in the selected modified files.
|
|
109
|
-
def diff_size(file_selector: nil)
|
|
132
|
+
def diff_size(file_selector: nil, line_selector: nil)
|
|
133
|
+
return filtered_diff_size(file_selector: file_selector, line_selector: line_selector, change_types: %i[added removed]) if line_selector
|
|
134
|
+
|
|
110
135
|
return danger.git.lines_of_code unless file_selector
|
|
111
136
|
|
|
112
137
|
filtered_files = git_utils.all_changed_files.select(&file_selector)
|
|
@@ -119,5 +144,47 @@ module Danger
|
|
|
119
144
|
stats[:deletions].to_i + stats[:insertions].to_i
|
|
120
145
|
end
|
|
121
146
|
end
|
|
147
|
+
|
|
148
|
+
private
|
|
149
|
+
|
|
150
|
+
# Count the changed lines across the selected files by iterating the diff patches, keeping only the lines
|
|
151
|
+
# whose change type is included in `change_types` and for which `line_selector` returns true.
|
|
152
|
+
#
|
|
153
|
+
# This is slower than the cached-numstats path used when no `line_selector` is given, since it needs the
|
|
154
|
+
# actual patch content to evaluate each line, but it is the only way to exclude specific lines (e.g. comments).
|
|
155
|
+
#
|
|
156
|
+
# @param file_selector [Proc, nil] Optional closure to select the files to inspect.
|
|
157
|
+
# @param line_selector [Proc] Closure receiving a changed line's content (without the `+`/`-` marker),
|
|
158
|
+
# returning true when the line should be counted.
|
|
159
|
+
# @param change_types [Array<Symbol>] The diff change types to count (any of :added, :removed).
|
|
160
|
+
#
|
|
161
|
+
# @return [Integer] The total number of counted changed lines.
|
|
162
|
+
def filtered_diff_size(file_selector:, line_selector:, change_types:)
|
|
163
|
+
files = git_utils.all_changed_files
|
|
164
|
+
files = files.select(&file_selector) if file_selector
|
|
165
|
+
|
|
166
|
+
files.sum do |file|
|
|
167
|
+
# `patch` can be nil (e.g. binary files), in which case there are no textual lines to count.
|
|
168
|
+
patch = danger.git.diff_for_file(file)&.patch
|
|
169
|
+
next 0 unless patch
|
|
170
|
+
|
|
171
|
+
patch.each_line.count do |diff_line|
|
|
172
|
+
next false unless change_types.include?(git_utils.change_type(diff_line: diff_line))
|
|
173
|
+
|
|
174
|
+
line_selector.call(strip_diff_marker(diff_line))
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Strip the leading `+`/`-` diff marker and the trailing newline from a diff patch line,
|
|
180
|
+
# so that `line_selector` only sees the actual line content.
|
|
181
|
+
#
|
|
182
|
+
# @param diff_line [String] A line from a diff patch, e.g. `"+ // a comment\n"`.
|
|
183
|
+
#
|
|
184
|
+
# @return [String] The line content, e.g. `" // a comment"`.
|
|
185
|
+
def strip_diff_marker(diff_line)
|
|
186
|
+
without_marker = diff_line[1..].to_s
|
|
187
|
+
without_marker.chomp
|
|
188
|
+
end
|
|
122
189
|
end
|
|
123
190
|
end
|
|
@@ -180,6 +180,224 @@ module Danger
|
|
|
180
180
|
expect(@dangerfile).to not_report
|
|
181
181
|
end
|
|
182
182
|
end
|
|
183
|
+
|
|
184
|
+
context 'when checking that existing strings are not modified' do
|
|
185
|
+
let(:source_strings_xml) { 'WordPress/src/main/res/values/strings.xml' }
|
|
186
|
+
|
|
187
|
+
def stub_diff(path, patch)
|
|
188
|
+
allow(@plugin.git).to receive(:modified_files).and_return([path])
|
|
189
|
+
diff = GitDiffStruct.new('modified', path, patch)
|
|
190
|
+
allow(@plugin.git).to receive(:diff_for_file).with(path).and_return(diff)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it 'reports an error when the value of an existing string is modified in place' do
|
|
194
|
+
stub_diff(source_strings_xml, <<~STRINGS)
|
|
195
|
+
diff --git a/#{source_strings_xml} b/#{source_strings_xml}
|
|
196
|
+
index 5794d472..772e2b99 100644
|
|
197
|
+
--- a/#{source_strings_xml}
|
|
198
|
+
+++ b/#{source_strings_xml}
|
|
199
|
+
@@ -1,3 +1,3 @@
|
|
200
|
+
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
201
|
+
- <string name="greeting">Hello</string>
|
|
202
|
+
+ <string name="greeting">Hi there</string>
|
|
203
|
+
</resources>
|
|
204
|
+
STRINGS
|
|
205
|
+
|
|
206
|
+
@plugin.check_existing_strings_not_modified
|
|
207
|
+
|
|
208
|
+
expected_error = <<~ERROR
|
|
209
|
+
#{AndroidStringsChecker::STRING_MODIFIED_MESSAGE}
|
|
210
|
+
File `#{source_strings_xml}`, string `greeting`:
|
|
211
|
+
```diff
|
|
212
|
+
- <string name="greeting">Hello</string>
|
|
213
|
+
+ <string name="greeting">Hi there</string>
|
|
214
|
+
```
|
|
215
|
+
ERROR
|
|
216
|
+
|
|
217
|
+
expect(@dangerfile).to report_errors([expected_error])
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it 'does nothing when a brand new string key is added' do
|
|
221
|
+
stub_diff(source_strings_xml, <<~STRINGS)
|
|
222
|
+
diff --git a/#{source_strings_xml} b/#{source_strings_xml}
|
|
223
|
+
index 5794d472..772e2b99 100644
|
|
224
|
+
--- a/#{source_strings_xml}
|
|
225
|
+
+++ b/#{source_strings_xml}
|
|
226
|
+
@@ -1,2 +1,3 @@
|
|
227
|
+
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
228
|
+
+ <string name="greeting">Hello</string>
|
|
229
|
+
</resources>
|
|
230
|
+
STRINGS
|
|
231
|
+
|
|
232
|
+
@plugin.check_existing_strings_not_modified
|
|
233
|
+
|
|
234
|
+
expect(@dangerfile).to not_report
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
it 'does nothing when an existing string key is removed' do
|
|
238
|
+
stub_diff(source_strings_xml, <<~STRINGS)
|
|
239
|
+
diff --git a/#{source_strings_xml} b/#{source_strings_xml}
|
|
240
|
+
index 5794d472..772e2b99 100644
|
|
241
|
+
--- a/#{source_strings_xml}
|
|
242
|
+
+++ b/#{source_strings_xml}
|
|
243
|
+
@@ -1,3 +1,2 @@
|
|
244
|
+
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
245
|
+
- <string name="greeting">Hello</string>
|
|
246
|
+
</resources>
|
|
247
|
+
STRINGS
|
|
248
|
+
|
|
249
|
+
@plugin.check_existing_strings_not_modified
|
|
250
|
+
|
|
251
|
+
expect(@dangerfile).to not_report
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
it 'does nothing when a string is only reordered (value unchanged)' do
|
|
255
|
+
stub_diff(source_strings_xml, <<~STRINGS)
|
|
256
|
+
diff --git a/#{source_strings_xml} b/#{source_strings_xml}
|
|
257
|
+
index 5794d472..772e2b99 100644
|
|
258
|
+
--- a/#{source_strings_xml}
|
|
259
|
+
+++ b/#{source_strings_xml}
|
|
260
|
+
@@ -1,4 +1,4 @@
|
|
261
|
+
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
262
|
+
- <string name="greeting">Hello</string>
|
|
263
|
+
<string name="other">Other</string>
|
|
264
|
+
+ <string name="greeting">Hello</string>
|
|
265
|
+
</resources>
|
|
266
|
+
STRINGS
|
|
267
|
+
|
|
268
|
+
@plugin.check_existing_strings_not_modified
|
|
269
|
+
|
|
270
|
+
expect(@dangerfile).to not_report
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
it 'does nothing when a key is renamed (old key removed, new key added)' do
|
|
274
|
+
stub_diff(source_strings_xml, <<~STRINGS)
|
|
275
|
+
diff --git a/#{source_strings_xml} b/#{source_strings_xml}
|
|
276
|
+
index 5794d472..772e2b99 100644
|
|
277
|
+
--- a/#{source_strings_xml}
|
|
278
|
+
+++ b/#{source_strings_xml}
|
|
279
|
+
@@ -1,3 +1,3 @@
|
|
280
|
+
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
281
|
+
- <string name="greeting">Hello</string>
|
|
282
|
+
+ <string name="greeting_v2">Hello</string>
|
|
283
|
+
</resources>
|
|
284
|
+
STRINGS
|
|
285
|
+
|
|
286
|
+
@plugin.check_existing_strings_not_modified
|
|
287
|
+
|
|
288
|
+
expect(@dangerfile).to not_report
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
it 'ignores modifications to non-translatable strings' do
|
|
292
|
+
stub_diff(source_strings_xml, <<~STRINGS)
|
|
293
|
+
diff --git a/#{source_strings_xml} b/#{source_strings_xml}
|
|
294
|
+
index 5794d472..772e2b99 100644
|
|
295
|
+
--- a/#{source_strings_xml}
|
|
296
|
+
+++ b/#{source_strings_xml}
|
|
297
|
+
@@ -1,3 +1,3 @@
|
|
298
|
+
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
299
|
+
- <string name="app_name" translatable="false">WordPress</string>
|
|
300
|
+
+ <string name="app_name" translatable="false">WordPress.com</string>
|
|
301
|
+
</resources>
|
|
302
|
+
STRINGS
|
|
303
|
+
|
|
304
|
+
@plugin.check_existing_strings_not_modified
|
|
305
|
+
|
|
306
|
+
expect(@dangerfile).to not_report
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
it 'ignores localized strings.xml files by default' do
|
|
310
|
+
localized_path = 'WordPress/src/main/res/values-fr/strings.xml'
|
|
311
|
+
stub_diff(localized_path, <<~STRINGS)
|
|
312
|
+
diff --git a/#{localized_path} b/#{localized_path}
|
|
313
|
+
index 5794d472..772e2b99 100644
|
|
314
|
+
--- a/#{localized_path}
|
|
315
|
+
+++ b/#{localized_path}
|
|
316
|
+
@@ -1,3 +1,3 @@
|
|
317
|
+
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
318
|
+
- <string name="greeting">Bonjour</string>
|
|
319
|
+
+ <string name="greeting">Salut</string>
|
|
320
|
+
</resources>
|
|
321
|
+
STRINGS
|
|
322
|
+
|
|
323
|
+
@plugin.check_existing_strings_not_modified
|
|
324
|
+
|
|
325
|
+
expect(@dangerfile).to not_report
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
it 'reports an error for each modified string across multiple source files' do
|
|
329
|
+
wp_path = 'WordPress/src/main/res/values/strings.xml'
|
|
330
|
+
jp_path = 'WordPress/src/jetpack/res/values/strings.xml'
|
|
331
|
+
allow(@plugin.git).to receive(:modified_files).and_return([wp_path, jp_path])
|
|
332
|
+
|
|
333
|
+
wp_diff = GitDiffStruct.new('modified', wp_path, <<~STRINGS)
|
|
334
|
+
diff --git a/#{wp_path} b/#{wp_path}
|
|
335
|
+
index 5794d472..772e2b99 100644
|
|
336
|
+
--- a/#{wp_path}
|
|
337
|
+
+++ b/#{wp_path}
|
|
338
|
+
@@ -1,3 +1,3 @@
|
|
339
|
+
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
340
|
+
- <string name="greeting">Hello</string>
|
|
341
|
+
+ <string name="greeting">Hi</string>
|
|
342
|
+
</resources>
|
|
343
|
+
STRINGS
|
|
344
|
+
allow(@plugin.git).to receive(:diff_for_file).with(wp_path).and_return(wp_diff)
|
|
345
|
+
|
|
346
|
+
jp_diff = GitDiffStruct.new('modified', jp_path, <<~STRINGS)
|
|
347
|
+
diff --git a/#{jp_path} b/#{jp_path}
|
|
348
|
+
index 5794d472..772e2b99 100644
|
|
349
|
+
--- a/#{jp_path}
|
|
350
|
+
+++ b/#{jp_path}
|
|
351
|
+
@@ -1,3 +1,3 @@
|
|
352
|
+
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
353
|
+
- <string name="farewell">Bye</string>
|
|
354
|
+
+ <string name="farewell">Goodbye</string>
|
|
355
|
+
</resources>
|
|
356
|
+
STRINGS
|
|
357
|
+
allow(@plugin.git).to receive(:diff_for_file).with(jp_path).and_return(jp_diff)
|
|
358
|
+
|
|
359
|
+
@plugin.check_existing_strings_not_modified
|
|
360
|
+
|
|
361
|
+
wp_error = <<~ERROR
|
|
362
|
+
#{AndroidStringsChecker::STRING_MODIFIED_MESSAGE}
|
|
363
|
+
File `#{wp_path}`, string `greeting`:
|
|
364
|
+
```diff
|
|
365
|
+
- <string name="greeting">Hello</string>
|
|
366
|
+
+ <string name="greeting">Hi</string>
|
|
367
|
+
```
|
|
368
|
+
ERROR
|
|
369
|
+
|
|
370
|
+
jp_error = <<~ERROR
|
|
371
|
+
#{AndroidStringsChecker::STRING_MODIFIED_MESSAGE}
|
|
372
|
+
File `#{jp_path}`, string `farewell`:
|
|
373
|
+
```diff
|
|
374
|
+
- <string name="farewell">Bye</string>
|
|
375
|
+
+ <string name="farewell">Goodbye</string>
|
|
376
|
+
```
|
|
377
|
+
ERROR
|
|
378
|
+
|
|
379
|
+
expect(@dangerfile.status_report[:errors]).to contain_exactly(wp_error, jp_error)
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
it 'can be configured to report a warning instead of an error' do
|
|
383
|
+
stub_diff(source_strings_xml, <<~STRINGS)
|
|
384
|
+
diff --git a/#{source_strings_xml} b/#{source_strings_xml}
|
|
385
|
+
index 5794d472..772e2b99 100644
|
|
386
|
+
--- a/#{source_strings_xml}
|
|
387
|
+
+++ b/#{source_strings_xml}
|
|
388
|
+
@@ -1,3 +1,3 @@
|
|
389
|
+
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
390
|
+
- <string name="greeting">Hello</string>
|
|
391
|
+
+ <string name="greeting">Hi there</string>
|
|
392
|
+
</resources>
|
|
393
|
+
STRINGS
|
|
394
|
+
|
|
395
|
+
@plugin.check_existing_strings_not_modified(report_type: :warning)
|
|
396
|
+
|
|
397
|
+
expect(@dangerfile.status_report[:warnings].count).to eq(1)
|
|
398
|
+
expect(@dangerfile.status_report[:errors]).to be_empty
|
|
399
|
+
end
|
|
400
|
+
end
|
|
183
401
|
end
|
|
184
402
|
end
|
|
185
403
|
end
|
|
@@ -177,6 +177,120 @@ module Danger
|
|
|
177
177
|
it_behaves_like 'using the default diff size counter, without a file selector', :deletions
|
|
178
178
|
it_behaves_like 'using a file selector to filter and count the changes in a diff', :deletions, [221, 380, 1157]
|
|
179
179
|
end
|
|
180
|
+
|
|
181
|
+
context 'when using a line_selector to exclude comment and blank lines' do
|
|
182
|
+
# Counts a changed line only if, once trimmed, it is neither empty nor the start of a comment.
|
|
183
|
+
let(:code_line_selector) do
|
|
184
|
+
lambda do |line|
|
|
185
|
+
stripped = line.strip
|
|
186
|
+
!(stripped.empty? || stripped.start_with?('//', '/*', '*', '*/'))
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# 4 added code lines (fun/val/return/}) + 1 removed code line, plus comment and blank lines that must be ignored.
|
|
191
|
+
let(:kotlin_patch) do
|
|
192
|
+
<<~PATCH
|
|
193
|
+
diff --git a/Foo.kt b/Foo.kt
|
|
194
|
+
index 1234567..89abcde 100644
|
|
195
|
+
--- a/Foo.kt
|
|
196
|
+
+++ b/Foo.kt
|
|
197
|
+
@@ -1,2 +1,10 @@
|
|
198
|
+
package com.example
|
|
199
|
+
+
|
|
200
|
+
+// a single line comment
|
|
201
|
+
+/* a block comment */
|
|
202
|
+
+/** kdoc opening */
|
|
203
|
+
+ * kdoc continuation line
|
|
204
|
+
+fun foo(): Int {
|
|
205
|
+
+ val x = 1
|
|
206
|
+
+ return x
|
|
207
|
+
+}
|
|
208
|
+
-val removedCode = 0
|
|
209
|
+
-// removed comment
|
|
210
|
+
PATCH
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
before do
|
|
214
|
+
stub_const('GitDiffStruct', Struct.new(:type, :path, :patch))
|
|
215
|
+
allow(@plugin.git).to receive_messages(added_files: ['Foo.kt'], modified_files: [], deleted_files: [])
|
|
216
|
+
allow(@plugin.git).to receive(:diff_for_file).with('Foo.kt').and_return(GitDiffStruct.new('added', 'Foo.kt', kotlin_patch))
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it 'reports a warning when the non-comment, non-blank changes exceed the max (type :all counts 5)' do
|
|
220
|
+
@plugin.check_diff_size(max_size: 4, type: :all, line_selector: code_line_selector)
|
|
221
|
+
|
|
222
|
+
expect(@dangerfile).to report_warnings([format(described_class::DEFAULT_DIFF_SIZE_MESSAGE_FORMAT, 4)])
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it 'does nothing when the non-comment, non-blank changes are within the max (type :all counts 5)' do
|
|
226
|
+
@plugin.check_diff_size(max_size: 5, type: :all, line_selector: code_line_selector)
|
|
227
|
+
|
|
228
|
+
expect(@dangerfile).to not_report
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
it 'counts only added code lines for :insertions (counts 4)' do
|
|
232
|
+
@plugin.check_diff_size(max_size: 3, type: :insertions, line_selector: code_line_selector)
|
|
233
|
+
|
|
234
|
+
expect(@dangerfile).to report_warnings([format(described_class::DEFAULT_DIFF_SIZE_MESSAGE_FORMAT, 3)])
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
it 'does nothing for :insertions when the added code lines are within the max (counts 4)' do
|
|
238
|
+
@plugin.check_diff_size(max_size: 4, type: :insertions, line_selector: code_line_selector)
|
|
239
|
+
|
|
240
|
+
expect(@dangerfile).to not_report
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
it 'counts only removed code lines for :deletions (counts 1)' do
|
|
244
|
+
@plugin.check_diff_size(max_size: 0, type: :deletions, line_selector: code_line_selector)
|
|
245
|
+
|
|
246
|
+
expect(@dangerfile).to report_warnings([format(described_class::DEFAULT_DIFF_SIZE_MESSAGE_FORMAT, 0)])
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
it 'does nothing for :deletions when the removed code lines are within the max (counts 1)' do
|
|
250
|
+
@plugin.check_diff_size(max_size: 1, type: :deletions, line_selector: code_line_selector)
|
|
251
|
+
|
|
252
|
+
expect(@dangerfile).to not_report
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
it 'exposes the filtered counts through the size helpers' do
|
|
256
|
+
expect(@plugin.diff_size(line_selector: code_line_selector)).to eq(5)
|
|
257
|
+
expect(@plugin.insertions_size(line_selector: code_line_selector)).to eq(4)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
context 'when combined with a file_selector' do
|
|
261
|
+
before do
|
|
262
|
+
excluded_test_file = 'src/test/java/FooTest.kt'
|
|
263
|
+
allow(@plugin.git).to receive_messages(added_files: ['Foo.kt', excluded_test_file], modified_files: [], deleted_files: [])
|
|
264
|
+
allow(@plugin.git).to receive(:diff_for_file).with(excluded_test_file).and_return(GitDiffStruct.new('added', excluded_test_file, kotlin_patch))
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
it 'only counts lines in files accepted by the file_selector' do
|
|
268
|
+
# Both files carry the same patch (5 code lines each); excluding the test file must keep the count at 5, not 10.
|
|
269
|
+
file_selector = ->(path) { !path.include?('src/test') }
|
|
270
|
+
|
|
271
|
+
expect(
|
|
272
|
+
@plugin.diff_size(file_selector: file_selector, line_selector: code_line_selector)
|
|
273
|
+
).to eq(5)
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
context 'without a line_selector (default numstats path)' do
|
|
278
|
+
before do
|
|
279
|
+
allow(@plugin.git).to receive(:diff).and_return(instance_double(Git::Diff))
|
|
280
|
+
allow(@plugin.git.diff).to receive(:stats).and_return({ files: { 'Foo.kt' => { insertions: 9, deletions: 2 } } })
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
it 'counts every changed line including comments and blanks (counts 11)' do
|
|
284
|
+
@plugin.check_diff_size(max_size: 10, type: :all, file_selector: ->(_path) { true })
|
|
285
|
+
|
|
286
|
+
expect(@dangerfile).to report_warnings([format(described_class::DEFAULT_DIFF_SIZE_MESSAGE_FORMAT, 10)])
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
it 'raises an ArgumentError when given an unknown diff size type' do
|
|
292
|
+
expect { @plugin.check_diff_size(max_size: 100, type: :unknown) }.to raise_error(ArgumentError)
|
|
293
|
+
end
|
|
180
294
|
end
|
|
181
295
|
|
|
182
296
|
context 'when checking a PR body size' do
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: danger-dangermattic
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Automattic
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-07-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: danger
|
|
@@ -16,14 +16,20 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '9.
|
|
19
|
+
version: '9.5'
|
|
20
|
+
- - ">="
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: 9.5.3
|
|
20
23
|
type: :runtime
|
|
21
24
|
prerelease: false
|
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
26
|
requirements:
|
|
24
27
|
- - "~>"
|
|
25
28
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '9.
|
|
29
|
+
version: '9.5'
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 9.5.3
|
|
27
33
|
- !ruby/object:Gem::Dependency
|
|
28
34
|
name: danger-plugin-api
|
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -201,6 +207,8 @@ files:
|
|
|
201
207
|
- ".buildkite/gem-push.sh"
|
|
202
208
|
- ".buildkite/pipeline.yml"
|
|
203
209
|
- ".bundle/config"
|
|
210
|
+
- ".github/CODEOWNERS"
|
|
211
|
+
- ".github/dependabot.yml"
|
|
204
212
|
- ".github/workflows/README.md"
|
|
205
213
|
- ".github/workflows/reusable-check-labels-on-issues.yml"
|
|
206
214
|
- ".github/workflows/reusable-retry-buildkite-step-on-events.yml"
|
|
@@ -209,7 +217,9 @@ files:
|
|
|
209
217
|
- ".rubocop.yml"
|
|
210
218
|
- ".ruby-version"
|
|
211
219
|
- ".yardopts"
|
|
220
|
+
- AGENTS.md
|
|
212
221
|
- CHANGELOG.md
|
|
222
|
+
- CLAUDE.md
|
|
213
223
|
- Gemfile
|
|
214
224
|
- Gemfile.lock
|
|
215
225
|
- Guardfile
|