graphql-modernize 0.1.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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +40 -0
  3. data/CHANGELOG.md +5 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +106 -0
  6. data/Rakefile +10 -0
  7. data/docs/migration-guide.md +23 -0
  8. data/docs/research/changelog-survey.md +68 -0
  9. data/docs/rules/GQLM101.md +5 -0
  10. data/docs/rules/GQLM102.md +12 -0
  11. data/docs/rules/GQLM103.md +5 -0
  12. data/docs/rules/GQLM104.md +5 -0
  13. data/docs/rules/GQLM105.md +9 -0
  14. data/docs/rules/GQLM106.md +7 -0
  15. data/docs/rules/GQLM107.md +7 -0
  16. data/docs/rules/GQLM108.md +4 -0
  17. data/docs/rules/GQLM201.md +5 -0
  18. data/docs/rules/GQLM202.md +4 -0
  19. data/docs/rules/GQLM203.md +5 -0
  20. data/docs/rules/GQLM204.md +5 -0
  21. data/docs/rules/GQLM205.md +5 -0
  22. data/docs/rules/GQLM301.md +5 -0
  23. data/docs/rules/GQLM302.md +5 -0
  24. data/docs/rules/GQLM303.md +6 -0
  25. data/docs/rules/GQLM304.md +4 -0
  26. data/docs/rules/GQLM305.md +8 -0
  27. data/docs/rules/GQLM306.md +8 -0
  28. data/docs/rules/GQLM307.md +11 -0
  29. data/docs/rules/GQLM308.md +5 -0
  30. data/docs/rules/GQLM309.md +4 -0
  31. data/docs/rules/GQLM310.md +9 -0
  32. data/docs/rules/GQLM401.md +6 -0
  33. data/docs/rules/GQLM402.md +6 -0
  34. data/docs/rules/GQLM501.md +6 -0
  35. data/docs/rules/GQLM502.md +5 -0
  36. data/docs/rules/README.md +35 -0
  37. data/docs/writing-rules.md +29 -0
  38. data/exe/graphql-modernize +6 -0
  39. data/lib/graphql/modernize/application.rb +172 -0
  40. data/lib/graphql/modernize/class_hierarchy.rb +329 -0
  41. data/lib/graphql/modernize/cli.rb +175 -0
  42. data/lib/graphql/modernize/config.rb +163 -0
  43. data/lib/graphql/modernize/context_builder.rb +78 -0
  44. data/lib/graphql/modernize/file_finder.rb +45 -0
  45. data/lib/graphql/modernize/offense.rb +28 -0
  46. data/lib/graphql/modernize/reporter.rb +103 -0
  47. data/lib/graphql/modernize/rule_registry.rb +26 -0
  48. data/lib/graphql/modernize/rules/base.rb +132 -0
  49. data/lib/graphql/modernize/rules/deprecation.rb +219 -0
  50. data/lib/graphql/modernize/rules/legacy.rb +301 -0
  51. data/lib/graphql/modernize/rules/modernize.rb +69 -0
  52. data/lib/graphql/modernize/rules/relay.rb +74 -0
  53. data/lib/graphql/modernize/rules/schema_config.rb +38 -0
  54. data/lib/graphql/modernize/runner.rb +34 -0
  55. data/lib/graphql/modernize/suppression_index.rb +88 -0
  56. data/lib/graphql/modernize/version.rb +7 -0
  57. data/lib/graphql/modernize.rb +30 -0
  58. metadata +120 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 367624e4b4eb0dcd521cc6d637179eccebeebd2a97d1ec6b4a074496a693d2a7
4
+ data.tar.gz: 1b144aa928b31103102daffa6cfd6de727dee13fdd89ad5fd4b87bc1e52137f5
5
+ SHA512:
6
+ metadata.gz: 9a0c14c313ab2c390d8aa22794a90246c1c5ed394d75820f83c1f5e2a3e3d784458eac33ee829e25d44e3f5b5223af0ac1535929c9ececb57800306decccf9bb
7
+ data.tar.gz: ec31abe6cae4bf0be75fa509e3c08b4e7f9f4e241541a0734d07e44b50abd2051a220b186ad4edddd0c1908b944ebe2488a783a5a290976c7878a6ae08027a16
data/.rubocop.yml ADDED
@@ -0,0 +1,40 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ SuggestExtensions: false
4
+ TargetRubyVersion: 3.3
5
+ UseCache: false
6
+
7
+ Layout/LineLength:
8
+ Max: 120
9
+
10
+ Metrics/AbcSize:
11
+ Max: 100
12
+
13
+ Metrics/BlockLength:
14
+ Exclude:
15
+ - "*.gemspec"
16
+ - "spec/**/*"
17
+
18
+ Metrics/ClassLength:
19
+ Max: 200
20
+
21
+ Metrics/CyclomaticComplexity:
22
+ Max: 30
23
+
24
+ Metrics/MethodLength:
25
+ Max: 50
26
+
27
+ Metrics/ParameterLists:
28
+ Max: 12
29
+
30
+ Metrics/PerceivedComplexity:
31
+ Max: 30
32
+
33
+ Style/Documentation:
34
+ Enabled: false
35
+
36
+ Style/MultilineBlockChain:
37
+ Enabled: false
38
+
39
+ Style/StringLiterals:
40
+ EnforcedStyle: double_quotes
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 - 2026-08-31
4
+
5
+ - Initial release.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Yudai Takada
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,106 @@
1
+ # graphql-modernize
2
+
3
+ `graphql-modernize` migrates deprecated graphql-ruby APIs without booting the
4
+ application. It parses Ruby with Prism through [astel](https://github.com/ydah/astel),
5
+ preserves formatting, validates every rewrite, and reports changes before writing.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ bundle add graphql-modernize
11
+ ```
12
+
13
+ Ruby 3.3 or newer is required. The tool does not depend on the `graphql` gem.
14
+
15
+ ## Quick start
16
+
17
+ Start with a dry run:
18
+
19
+ ```bash
20
+ bundle exec graphql-modernize run
21
+ ```
22
+
23
+ Review the unified diff, then apply safe rules:
24
+
25
+ ```bash
26
+ bundle exec graphql-modernize run --write
27
+ git diff
28
+ bundle exec rspec
29
+ ```
30
+
31
+ Without `--force`, writes require a clean Git working tree and every target
32
+ must be a tracked, non-symlinked path inside that working tree.
33
+
34
+ Unsafe rules require an explicit opt-in and can be run one at a time:
35
+
36
+ ```bash
37
+ bundle exec graphql-modernize run --only GQLM307 --unsafe --write
38
+ ```
39
+
40
+ Useful commands:
41
+
42
+ ```text
43
+ graphql-modernize list
44
+ graphql-modernize explain GQLM307
45
+ graphql-modernize doctor
46
+ graphql-modernize run --format json
47
+ graphql-modernize run --format github
48
+ graphql-modernize run --format sarif --out results.sarif
49
+ ```
50
+
51
+ Exit status is `0` when no unresolved offenses remain, `1` when manual or
52
+ unfixed offenses remain, and `2` for configuration, parse (`--strict`), or
53
+ internal errors.
54
+
55
+ ## Configuration
56
+
57
+ Create `.graphql-modernize.yml` in the project root:
58
+
59
+ ```yaml
60
+ from_version: "1.13"
61
+ target_version: "2.6"
62
+ ruby_version: "3.3"
63
+ include: ["app/graphql/**/*.rb", "lib/graphql/**/*.rb"]
64
+ exclude: ["app/graphql/legacy/**/*.rb"]
65
+ base_classes:
66
+ object: ["ApplicationObjectType"]
67
+ rules:
68
+ GQLM401:
69
+ enabled: true
70
+ GQLM307:
71
+ migration_errors: true
72
+ apply_unsafe: false
73
+ require_suppression_reason: true
74
+ ```
75
+
76
+ CLI options override configuration. GQLM401 and GQLM402 are disabled by
77
+ default because they can create large style-only diffs.
78
+
79
+ ## Suppression comments
80
+
81
+ ```ruby
82
+ field :name, types.String # graphql-modernize:disable GQLM105 -- staged migration
83
+
84
+ # graphql-modernize:disable GQLM401 -- keep explicit defaults here
85
+ argument :id, ID, required: true
86
+ # graphql-modernize:enable GQLM401
87
+ ```
88
+
89
+ Use `graphql-modernize:disable-file GQLM101 -- reason` near the top of a file
90
+ for file-wide suppression. Unused suppressions are reported as GQLM000.
91
+
92
+ See [the migration guide](docs/migration-guide.md), [rule index](docs/rules/README.md),
93
+ and [rule authoring guide](docs/writing-rules.md).
94
+
95
+ ## Development
96
+
97
+ Run `bundle install`, then `bundle exec rake`. Build the gem with
98
+ `bundle exec rake build`.
99
+
100
+ ## Contributing
101
+
102
+ Bug reports and pull requests are welcome at <https://github.com/ydah/graphql-modernize>.
103
+
104
+ ## License
105
+
106
+ The gem is available under the [MIT License](LICENSE.txt).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+ require "rubocop/rake_task"
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ RuboCop::RakeTask.new(:rubocop)
9
+
10
+ task default: %i[spec rubocop]
@@ -0,0 +1,23 @@
1
+ # Migration guide
2
+
3
+ ## 1.13 to 2.x
4
+
5
+ 1. Run `bundle exec graphql-modernize run` and review the proposed safe changes.
6
+ 2. Apply them with `--write`, then run the application's tests.
7
+ 3. Run GQLM102 separately with `--unsafe`; resolve its manual fallbacks and
8
+ GQLM101, GQLM104, and GQLM108 before updating.
9
+ 4. Apply each unsafe superclass or Relay migration separately with
10
+ `--only RULE --unsafe --write` and test it.
11
+ 5. Update graphql-ruby and rerun the tool with the target version.
12
+
13
+ ## 2.3 to 2.6
14
+
15
+ 1. Remove obsolete execution and pagination settings with the safe rules.
16
+ 2. Review type-index consumers reported by GQLM308.
17
+ 3. Run GQLM307 by itself. Keep `migration_errors: true` while comparing Warden
18
+ and Visibility, then remove that option after the differences are resolved.
19
+ 4. Decide the validation and complexity migration modes reported by GQLM310
20
+ and GQLM501 before accepting the new behavior.
21
+
22
+ Always review `git diff`; the tool deliberately leaves application-specific
23
+ choices as manual offenses.
@@ -0,0 +1,68 @@
1
+ # graphql-ruby migration survey
2
+
3
+ Surveyed against graphql-ruby's `CHANGELOG.md` through 2.6.10 (2026-08-27),
4
+ the historical official upgrader, rubocop-graphql's cop list, and astel 0.2.0.
5
+
6
+ ## Rules selected for the first releases
7
+
8
+ | Rule | Upstream boundary | Static | Correction | Safety |
9
+ |---|---:|---|---|---|
10
+ | GQLM101 `.define` DSL | deprecated 1.10 | yes | guidance only | manual |
11
+ | GQLM102 `resolve:` field option | removed 2.0.1 | yes | simple lambda to method | unsafe/manual fallback |
12
+ | GQLM103 `GraphQL::Function` | removed 2.0 | yes | superclass replacement | unsafe |
13
+ | GQLM104 `to_graphql` / `graphql_definition` | deprecated 1.13 | yes | guidance only | manual |
14
+ | GQLM105 `types.X` | class API migration | yes | remove `types.` | safe |
15
+ | GQLM106 `property:` | class API migration | yes | rename to `method:` | safe |
16
+ | GQLM107 `field:` / `function:` | removed 2.0.1 | yes | remove option | safe |
17
+ | GQLM108 `GraphQL::Query::Arguments` | removed 2.0 | yes | guidance only | manual |
18
+ | GQLM201 Relay mutation | class API migration | yes | superclass replacement | unsafe |
19
+ | GQLM205 load failure error | renamed 1.9.4 | yes | constant replacement | safe |
20
+ | GQLM301 Interpreter plugin | default since 1.12 | yes | remove line | safe |
21
+ | GQLM302 Analysis::AST plugin | default since 1.12 | yes | remove line | safe |
22
+ | GQLM303 Connections plugin | default since 1.12 | yes | remove line | safe |
23
+ | GQLM304 `error_bubbling(true)` | deprecated 2.2.7 | yes | guidance only | manual |
24
+ | GQLM305 bidirectional pagination setting | deprecated 1.13.10 | yes | remove line | safe |
25
+ | GQLM306 default nodes setting | deprecated 1.13.10 | yes | remove line | safe |
26
+ | GQLM307 custom `visible?` without visibility plugin | required since 2.4.0 | project scan | insert plugin | unsafe |
27
+ | GQLM308 possible type/reference keys | changed 2.3.5 | yes | guidance only | manual |
28
+ | GQLM310 empty Union selections | deprecated behavior 2.5.3 | schema scan | guidance only | manual |
29
+ | GQLM401 `required: true` | default since 1.13.0 | yes | remove option | safe, opt-in |
30
+ | GQLM402 `null: true` | default since 1.13.0 | yes | remove option | safe, opt-in |
31
+ | GQLM501 complexity cost mode | migration support 2.5.3 | schema scan | guidance only | manual |
32
+
33
+ The remaining catalog entries need application-specific intent or do not have a
34
+ single generally correct rewrite, so they are reported as manual guidance.
35
+
36
+ GraphQL-Ruby 2.6.3 requires `Schema::Visibility` to be attached after root type
37
+ configuration, so GQLM307 appends the plugin. GraphQL-Ruby 2.5.15 added the
38
+ type-aware `legacy_invalid_empty_selections_on_union_with_type` hook; GQLM310
39
+ recognizes either legacy-selection hook as an explicit policy.
40
+
41
+ ## Existing tools
42
+
43
+ The removed official `GraphQL::Upgrader` targeted the `.define` to class API
44
+ migration. Its transforms mixed regular expressions with `parser` AST
45
+ processing and did not cover later 2.x deprecations.
46
+
47
+ rubocop-graphql has `LegacyDSL`, but no equivalents of graphql-ruby's bundled
48
+ `GraphQL/DefaultNullTrue` and `GraphQL/DefaultRequiredTrue` cops. GQLM401 and
49
+ GQLM402 therefore overlap graphql-ruby's bundled cops and remain disabled by
50
+ default. GQLM403 is omitted: graphql-ruby's `GraphQL/FieldTypeInBlock` enforces
51
+ the opposite shape for lazy loading, so an automatic normalization would be a
52
+ style conflict rather than a migration.
53
+
54
+ ## astel findings
55
+
56
+ astel 0.2.0 provides every required write primitive: transactions, same-offset
57
+ insertions, parse validation, keyword/list editing, body insertion, comments,
58
+ and unified diff output. `NodePattern` handles the narrow node and field checks
59
+ used here, but its DSL has no list literal syntax for argument-list matching.
60
+ Rules therefore compile a broad call pattern once and use
61
+ `Rewriter#find_keyword_argument` for keyword details.
62
+
63
+ ## Sources
64
+
65
+ - <https://github.com/rmosolgo/graphql-ruby/blob/master/CHANGELOG.md>
66
+ - <https://github.com/rmosolgo/graphql-ruby/issues/3056>
67
+ - <https://github.com/DmitryTsepelev/rubocop-graphql/tree/master/lib/rubocop/cop/graphql>
68
+ - <https://github.com/ydah/astel>
@@ -0,0 +1,5 @@
1
+ # GQLM101 — `.define` DSL
2
+
3
+ Safety: manual. GraphQL-Ruby deprecated `.define` in 1.10 and removed legacy
4
+ definitions in 2.0. Replace each definition with the corresponding
5
+ `GraphQL::Schema::*` class or module and verify hooks manually.
@@ -0,0 +1,12 @@
1
+ # GQLM102 — `resolve:` field option
2
+
3
+ Safety: unsafe. With `--unsafe`, a self-contained three-argument lambda is
4
+ moved to an instance resolver method and legacy `obj` / `ctx` references become
5
+ `object` / `context`. The generated method accepts and ignores extra keyword
6
+ arguments, matching a legacy proc that did not read `args`. Captured locals,
7
+ `args`, nested blocks, bare calls,
8
+ dynamic field names, and method-name collisions are reported as manual because
9
+ an automatic rewrite could change their meaning. Static declarations made with
10
+ `attr`, `alias`, or `define_method` also count as existing methods. Duplicate
11
+ `resolve:` options and calls containing dynamic keyword expansion (`**options`)
12
+ are also left unchanged for manual resolution.
@@ -0,0 +1,5 @@
1
+ # GQLM103 — `GraphQL::Function`
2
+
3
+ Safety: unsafe. Replaces `GraphQL::Function` with
4
+ `GraphQL::Schema::Resolver`. Review arguments, return type, and the resolver
5
+ method after applying because the two class APIs are not identical.
@@ -0,0 +1,5 @@
1
+ # GQLM104 — legacy definition conversion
2
+
3
+ Safety: manual. `to_graphql` and `graphql_definition` disappeared with legacy
4
+ definitions. Remove the call when its result is unused; otherwise migrate the
5
+ consumer to class-based schema members.
@@ -0,0 +1,9 @@
1
+ # GQLM105 — `types.X`
2
+
3
+ Safety: safe. Rewrites the five legacy `TypeDefiner` scalars: `String` and
4
+ `Float` keep their Ruby classes, `Int` becomes `Integer`, and `Boolean` / `ID`
5
+ become `GraphQL::Types::Boolean` / `GraphQL::Types::ID`. Other `types.X` calls
6
+ and calls with arguments or blocks are left unchanged. Automatic rewrites are
7
+ limited to GraphQL type DSL arguments; other class-level uses and chained
8
+ wrappers such as `types.String.to_non_null_type` are reported for manual
9
+ class-API migration.
@@ -0,0 +1,7 @@
1
+ # GQLM106 — `property:`
2
+
3
+ Safety: safe. Renames the removed field option `property:` to its class API
4
+ equivalent `method:` inside GraphQL object and interface definitions.
5
+ Calls with duplicate `property:` or an existing `method:` are reported for
6
+ manual resolution, as are calls containing dynamic keyword expansion
7
+ (`**options`).
@@ -0,0 +1,7 @@
1
+ # GQLM107 — removed field options
2
+
3
+ Safety: safe. Removes `field:` and `function:` from field definitions. They
4
+ were already no-ops before GraphQL-Ruby removed them in 2.0.1. Values that may
5
+ have side effects are preserved and reported as manual. String-keyed hashes are
6
+ not treated as legacy keyword options. Calls containing dynamic keyword
7
+ expansion (`**options`) are also left unchanged for manual resolution.
@@ -0,0 +1,4 @@
1
+ # GQLM108 — `GraphQL::Query::Arguments`
2
+
3
+ Safety: manual. The legacy arguments container was removed in 2.0. Update code
4
+ to consume the argument values supplied by the class-based resolver API.
@@ -0,0 +1,5 @@
1
+ # GQLM201 — Relay mutation superclass
2
+
3
+ Safety: unsafe. Replaces `GraphQL::Relay::Mutation` with
4
+ `GraphQL::Schema::RelayClassicMutation`. Review the generated input and payload
5
+ types after applying.
@@ -0,0 +1,4 @@
1
+ # GQLM202 — connection type factory
2
+
3
+ Safety: manual. Replace `GraphQL::Relay::ConnectionType.create_type` consumers
4
+ with the class-based type's generated `connection_type`.
@@ -0,0 +1,5 @@
1
+ # GQLM203 — Relay node field
2
+
3
+ Safety: unsafe, detection only. Replace `GraphQL::Relay::Node.field` with an
4
+ appropriate field and implement `GraphQL::Types::Relay::Node` on the owning
5
+ type. The correct owner cannot be inferred statically.
@@ -0,0 +1,5 @@
1
+ # GQLM204 — direct BaseConnection inheritance
2
+
3
+ Safety: unsafe, detection only. Generate an application base connection and
4
+ inherit from it instead of directly subclassing
5
+ `GraphQL::Types::Relay::BaseConnection`.
@@ -0,0 +1,5 @@
1
+ # GQLM205 — load failure error
2
+
3
+ Safety: safe. Rewrites
4
+ `GraphQL::Schema::Resolver::LoadApplicationObjectFailedError` to the name used
5
+ since 1.9.4, `GraphQL::LoadApplicationObjectFailedError`.
@@ -0,0 +1,5 @@
1
+ # GQLM301 — Interpreter plugin
2
+
3
+ Safety: safe. Removes `use GraphQL::Execution::Interpreter`; Interpreter has
4
+ been the schema default since 1.12. Conditional, option-bearing, and block-based
5
+ uses, and uses with trailing comments, are preserved and reported as manual.
@@ -0,0 +1,5 @@
1
+ # GQLM302 — Analysis::AST plugin
2
+
3
+ Safety: safe. Removes `use GraphQL::Analysis::AST`; AST analysis has been the
4
+ schema default since 1.12. Conditional, option-bearing, and block-based uses are
5
+ preserved and reported as manual. Uses with trailing comments are also preserved.
@@ -0,0 +1,6 @@
1
+ # GQLM303 — Connections plugin
2
+
3
+ Safety: safe. Removes `use GraphQL::Pagination::Connections`; the new
4
+ connection implementation has been the schema default since 1.12. Conditional,
5
+ option-bearing, and block-based uses, and uses with trailing comments, are
6
+ preserved and reported as manual.
@@ -0,0 +1,4 @@
1
+ # GQLM304 — error bubbling
2
+
3
+ Safety: manual. `error_bubbling(true)` is deprecated without a direct
4
+ replacement. Test validation error responses before removing the setting.
@@ -0,0 +1,8 @@
1
+ # GQLM305 — bidirectional pagination
2
+
3
+ Safety: safe. Removes the obsolete
4
+ `GraphQL::Relay::ConnectionType.bidirectional_pagination` assignment,
5
+ which does not affect the current pagination implementation. Conditional
6
+ assignments and values that may have side effects are reported as manual.
7
+ Assignments nested in methods, blocks, or expressions and assignments with
8
+ trailing comments are also preserved for manual review.
@@ -0,0 +1,8 @@
1
+ # GQLM306 — default nodes field
2
+
3
+ Safety: safe. Removes the obsolete
4
+ `GraphQL::Relay::ConnectionType.default_nodes_field` assignment, which
5
+ does not affect the current pagination implementation. Conditional assignments
6
+ and values that may have side effects are reported as manual.
7
+ Assignments nested in methods, blocks, or expressions and assignments with
8
+ trailing comments are also preserved for manual review.
@@ -0,0 +1,11 @@
1
+ # GQLM307 — Visibility plugin
2
+
3
+ Safety: unsafe. When a GraphQL type defines the class method `visible?`, inserts
4
+ `use GraphQL::Schema::Visibility, migration_errors: true` after existing schema
5
+ configuration. Placement after root type configuration is required by
6
+ GraphQL-Ruby 2.6.3 and newer. Resolve migration errors before removing the
7
+ option. Existing one-line Visibility configuration is moved after root types;
8
+ conditional, block-based, or trailing-comment configuration is reported as
9
+ manual so its surrounding behavior and commentary are preserved. Conditional
10
+ plugin calls do not count as guaranteed configuration and prevent automatic
11
+ insertion until they are resolved.
@@ -0,0 +1,5 @@
1
+ # GQLM308 — schema type index keys
2
+
3
+ Safety: manual. Since 2.3.5, `Schema.possible_types` and
4
+ `Schema.references_to` use type classes instead of GraphQL name strings as
5
+ keys. Use `transform_keys(&:graphql_name)` only where the old shape is needed.
@@ -0,0 +1,4 @@
1
+ # GQLM309 — `rescue_from`
2
+
3
+ Safety: manual. Review schema error handler placement, exception specificity,
4
+ and handler parameters against the target GraphQL-Ruby version.
@@ -0,0 +1,9 @@
1
+ # GQLM310 — empty Union selections
2
+
3
+ Safety: manual. GraphQL-Ruby 2.5.3 began migrating toward rejecting Union
4
+ fields without subselections. Set
5
+ `allow_legacy_invalid_empty_selections_on_union` deliberately while fixing
6
+ affected queries. An override of
7
+ `legacy_invalid_empty_selections_on_union_with_type` also counts as an explicit
8
+ migration policy. Conditional configuration calls do not count as an explicit
9
+ migration policy.
@@ -0,0 +1,6 @@
1
+ # GQLM401 — redundant `required: true`
2
+
3
+ Safety: safe, disabled by default. Removes `required: true`, the argument
4
+ default since 1.13. Enable this rule explicitly when the style-only diff is
5
+ acceptable. Calls containing dynamic keyword expansion (`**options`) are left
6
+ unchanged for manual resolution, as are duplicate `required:` options.
@@ -0,0 +1,6 @@
1
+ # GQLM402 — redundant `null: true`
2
+
3
+ Safety: safe, disabled by default. Removes `null: true`, the field default
4
+ since 1.13. Enable this rule explicitly when the style-only diff is acceptable.
5
+ Calls containing dynamic keyword expansion (`**options`) are left unchanged
6
+ for manual resolution, as are duplicate `null:` options.
@@ -0,0 +1,6 @@
1
+ # GQLM501 — complexity cost mode
2
+
3
+ Safety: manual. GraphQL-Ruby 2.5.3 added migration support for corrected
4
+ complexity merging. Set `complexity_cost_calculation_mode` explicitly, compare
5
+ limits, then adopt the corrected behavior. Conditional configuration calls do
6
+ not count as an explicit migration policy.
@@ -0,0 +1,5 @@
1
+ # GQLM502 — page size settings
2
+
3
+ Safety: unsafe, detection only. Review `default_max_page_size` and split the
4
+ intent between `default_page_size` and `max_page_size`; one automatic mapping
5
+ would not preserve every application's behavior.
@@ -0,0 +1,35 @@
1
+ # Rule index
2
+
3
+ | ID | Safety | Migration |
4
+ |---|---|---|
5
+ | [GQLM101](GQLM101.md) | manual | `.define` DSL |
6
+ | [GQLM102](GQLM102.md) | unsafe/manual fallback | `resolve:` option |
7
+ | [GQLM103](GQLM103.md) | unsafe | `GraphQL::Function` |
8
+ | [GQLM104](GQLM104.md) | manual | legacy definition calls |
9
+ | [GQLM105](GQLM105.md) | safe | `types.X` |
10
+ | [GQLM106](GQLM106.md) | safe | `property:` |
11
+ | [GQLM107](GQLM107.md) | safe | removed field options |
12
+ | [GQLM108](GQLM108.md) | manual | `Query::Arguments` |
13
+ | [GQLM201](GQLM201.md) | unsafe | Relay mutation |
14
+ | [GQLM202](GQLM202.md) | manual | connection type factory |
15
+ | [GQLM203](GQLM203.md) | unsafe | Relay node field |
16
+ | [GQLM204](GQLM204.md) | unsafe | base connection |
17
+ | [GQLM205](GQLM205.md) | safe | load failure error |
18
+ | [GQLM301](GQLM301.md) | safe | Interpreter plugin |
19
+ | [GQLM302](GQLM302.md) | safe | Analysis plugin |
20
+ | [GQLM303](GQLM303.md) | safe | Connections plugin |
21
+ | [GQLM304](GQLM304.md) | manual | error bubbling |
22
+ | [GQLM305](GQLM305.md) | safe | bidirectional pagination |
23
+ | [GQLM306](GQLM306.md) | safe | default nodes field |
24
+ | [GQLM307](GQLM307.md) | unsafe | Visibility plugin |
25
+ | [GQLM308](GQLM308.md) | manual | schema type indexes |
26
+ | [GQLM309](GQLM309.md) | manual | error handlers |
27
+ | [GQLM310](GQLM310.md) | manual | empty Union selections |
28
+ | [GQLM401](GQLM401.md) | safe, opt-in | redundant required |
29
+ | [GQLM402](GQLM402.md) | safe, opt-in | redundant null |
30
+ | [GQLM501](GQLM501.md) | manual | complexity mode |
31
+ | [GQLM502](GQLM502.md) | unsafe | page size settings |
32
+
33
+ GQLM403 is intentionally not provided because graphql-ruby's
34
+ `GraphQL/FieldTypeInBlock` cop owns that style decision. GQLM503 was folded
35
+ into the specific GQLM310 and GQLM501 diagnostics.
@@ -0,0 +1,29 @@
1
+ # Writing rules
2
+
3
+ Rules inherit `GraphQL::Modernize::Rules::Base`, declare metadata once, and
4
+ compile their astel node pattern at class load time.
5
+
6
+ ```ruby
7
+ class PropertyOption < Base
8
+ rule id: "GQLM106", safety: :safe, summary: "property to method"
9
+ node_pattern :call_node, "(call_node receiver: nil name: :field)"
10
+
11
+ def on_match(node, _captures)
12
+ return unless inside?(node, :object, :interface)
13
+
14
+ arguments = keyword_arguments(node, :property)
15
+ return unless arguments.one? && !dynamic_keyword_arguments?(node)
16
+
17
+ argument = arguments.first
18
+
19
+ add_offense(argument.location, message: "replace property") do |rw|
20
+ rw.replace_keyword_key(argument, :method)
21
+ end
22
+ end
23
+ end
24
+ ```
25
+
26
+ Use the narrowest NodePattern that astel supports, then inspect GraphQL-specific
27
+ details in Ruby. `add_offense` wraps corrections in an astel transaction.
28
+ Every non-trivial correction needs a test for output, parse validity,
29
+ idempotence, and an unrelated DSL false-positive case.
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "graphql/modernize"
5
+
6
+ exit GraphQL::Modernize::CLI.start