graphql_tag_pluck 0.2.0 → 0.3.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/CHANGELOG.md +28 -1
- data/README.md +16 -1
- data/VERSION +1 -1
- data/lib/graphql_tag_pluck/graphql_tag_pluck.rb +33 -22
- data/lib/graphql_tag_pluck/heredoc_collector.rb +80 -0
- data/lib/graphql_tag_pluck/parser/ast/node_ext.rb +0 -13
- data/lib/graphql_tag_pluck.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ba2b33d369224dd7521d95c6cd1cae8839bf350694d10343872e4e98e153d5dd
|
|
4
|
+
data.tar.gz: 3f5a2db373cff91ceb6916be998f6ad3082dee1f4cc1124505e6db68ace3ff73
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a93e45d44f8b3f9f89449447962ae3380fe4b0bcdd34528bf58c5b62b8a0f0e840d4eb423bc2e41032c6a0e5c047e571e31740f3928f888f8bb043477f1e467a
|
|
7
|
+
data.tar.gz: 171fccfe0c3775df5cd4917fea80567ad41c14509c79036ec56c0e8361348e772165d23af5ac994a9171d5f79e799424546bd565cd61b110cb13f3e8375b0181
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.0] - 2026-11-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Each exported entry now records where it was defined: `file` (relative to
|
|
15
|
+
the current directory, or absolute for files outside it), `line` (the line
|
|
16
|
+
the definition starts on, also correct for several definitions in one
|
|
17
|
+
heredoc), and `constant` (the fully qualified constant the heredoc is
|
|
18
|
+
assigned to, such as `Clients::UserClient::USER_QUERY`, or `null`).
|
|
19
|
+
([#16](https://github.com/indigolain/graphql_tag_pluck/pull/16))
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **Breaking:** unnamed operations are keyed by their location, such as
|
|
24
|
+
`"app/clients/user_client.rb:4"`, instead of `unnamed_operation_N`. The old
|
|
25
|
+
counter depended on the order files were read in.
|
|
26
|
+
([#16](https://github.com/indigolain/graphql_tag_pluck/pull/16))
|
|
27
|
+
- **Breaking:** any definition name that appears more than once, even with an
|
|
28
|
+
identical definition, now raises `GraphqlTagPluck::Error` naming both
|
|
29
|
+
locations, and no output file is written. 0.2.0 only warned and kept the
|
|
30
|
+
last definition.
|
|
31
|
+
([#16](https://github.com/indigolain/graphql_tag_pluck/pull/16))
|
|
32
|
+
- Files are processed in sorted order, so output no longer depends on file
|
|
33
|
+
system order. Warnings for invalid GraphQL now include the `path:line`.
|
|
34
|
+
([#16](https://github.com/indigolain/graphql_tag_pluck/pull/16))
|
|
35
|
+
|
|
10
36
|
## [0.2.0] - 2026-11-03
|
|
11
37
|
|
|
12
38
|
### Changed
|
|
@@ -65,7 +91,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
65
91
|
- Configuration through `.graphqltagpluckconfig.yaml`
|
|
66
92
|
(`graphql_heredoc_identifiers`, `file_glob_pattern`, `output_path`).
|
|
67
93
|
|
|
68
|
-
[Unreleased]: https://github.com/indigolain/graphql_tag_pluck/compare/v0.
|
|
94
|
+
[Unreleased]: https://github.com/indigolain/graphql_tag_pluck/compare/v0.3.0...HEAD
|
|
95
|
+
[0.3.0]: https://github.com/indigolain/graphql_tag_pluck/compare/v0.2.0...v0.3.0
|
|
69
96
|
[0.2.0]: https://github.com/indigolain/graphql_tag_pluck/compare/v0.1.1...v0.2.0
|
|
70
97
|
[0.1.1]: https://github.com/indigolain/graphql_tag_pluck/compare/v0.1.0...v0.1.1
|
|
71
98
|
[0.1.0]: https://github.com/indigolain/graphql_tag_pluck/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -32,11 +32,26 @@ will output json file like: (following json content is formatted)
|
|
|
32
32
|
"SampleQuery": {
|
|
33
33
|
"name": "SampleQuery",
|
|
34
34
|
"source": "query SampleQuery {\n hoge\n fuga {\n piyo\n }\n}",
|
|
35
|
-
"type": "query"
|
|
35
|
+
"type": "query",
|
|
36
|
+
"file": "app/clients/sample_client.rb",
|
|
37
|
+
"line": 2,
|
|
38
|
+
"constant": "SAMPLE_QUERY"
|
|
36
39
|
}
|
|
37
40
|
}
|
|
38
41
|
```
|
|
39
42
|
|
|
43
|
+
Each entry contains:
|
|
44
|
+
|
|
45
|
+
- `name`: the operation / fragment name (`null` for unnamed operations)
|
|
46
|
+
- `source`: the normalized GraphQL document of the definition
|
|
47
|
+
- `type`: `query`, `mutation`, `subscription`, or `fragment`
|
|
48
|
+
- `file`: the file the definition was found in, relative to the current directory
|
|
49
|
+
- `line`: the line the definition starts on
|
|
50
|
+
- `constant`: the fully qualified constant the heredoc is assigned to (e.g. `Clients::UserClient::USER_QUERY`), or `null` when the heredoc is not assigned to a constant
|
|
51
|
+
|
|
52
|
+
Entries are keyed by definition name. Unnamed operations are keyed by their location (e.g. `"app/clients/sample_client.rb:2"`).
|
|
53
|
+
Definition names must be unique: any name defined more than once, even identically, raises `GraphqlTagPluck::Error` and no file is written.
|
|
54
|
+
|
|
40
55
|
by executing
|
|
41
56
|
|
|
42
57
|
```sh
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.3.0
|
|
@@ -5,8 +5,6 @@ module GraphqlTagPluck
|
|
|
5
5
|
class Error < StandardError; end
|
|
6
6
|
|
|
7
7
|
class << self
|
|
8
|
-
using GraphqlTagPluck::Parser::AST::NodeExt
|
|
9
|
-
|
|
10
8
|
attr_accessor :options
|
|
11
9
|
|
|
12
10
|
def default_options
|
|
@@ -18,9 +16,9 @@ module GraphqlTagPluck
|
|
|
18
16
|
end
|
|
19
17
|
|
|
20
18
|
def get_graphql_heredocs
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
# Sorted so the output, and which duplicate wins, does not depend on file system order.
|
|
20
|
+
files = Dir.glob(GraphqlTagPluck.options[:file_glob_pattern]).sort
|
|
21
|
+
collector = HeredocCollector.new
|
|
24
22
|
|
|
25
23
|
{}.tap do |hash|
|
|
26
24
|
files.each do |file|
|
|
@@ -30,40 +28,53 @@ module GraphqlTagPluck
|
|
|
30
28
|
warn "graphql_tag_pluck: skipping #{file}, which could not be parsed as Ruby: #{e.message}"
|
|
31
29
|
next
|
|
32
30
|
end
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
next if node.nil?
|
|
32
|
+
|
|
33
|
+
path = relative_path(file)
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
GraphQL.parse(
|
|
35
|
+
collector.collect(node).each do |heredoc|
|
|
36
|
+
document = begin
|
|
37
|
+
GraphQL.parse(heredoc.source)
|
|
39
38
|
rescue GraphQL::ParseError => e
|
|
40
|
-
warn "graphql_tag_pluck: skipping invalid GraphQL heredoc in #{
|
|
39
|
+
warn "graphql_tag_pluck: skipping invalid GraphQL heredoc in #{path}:#{heredoc.line}: #{e.message}"
|
|
41
40
|
next
|
|
42
41
|
end
|
|
43
|
-
parsed_graphql_doc_string.definitions.each do |definition|
|
|
44
|
-
key = if definition.name.nil?
|
|
45
|
-
unnamed_operation_count += 1
|
|
46
|
-
"unnamed_operation_#{unnamed_operation_count}"
|
|
47
|
-
else
|
|
48
|
-
definition.name
|
|
49
|
-
end
|
|
50
42
|
|
|
43
|
+
document.definitions.each do |definition|
|
|
44
|
+
line = heredoc.line + definition.line - 1
|
|
45
|
+
# Unnamed operations are keyed by their location so keys stay stable across runs.
|
|
46
|
+
key = definition.name || "#{path}:#{line}"
|
|
51
47
|
source = definition.to_query_string
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
|
|
49
|
+
# Operation names must be unique, even for identical copies, so the
|
|
50
|
+
# generated list never silently depends on which definition wins.
|
|
51
|
+
if (existing = hash[key])
|
|
52
|
+
raise Error, "Duplicate GraphQL definition name `#{key}`: " \
|
|
53
|
+
"defined at #{existing[:file]}:#{existing[:line]} and #{path}:#{line}"
|
|
54
54
|
end
|
|
55
|
-
origin_files[key] = file
|
|
56
55
|
|
|
57
56
|
hash[key] = {
|
|
58
57
|
name: definition.name,
|
|
59
58
|
source: source,
|
|
60
|
-
type: definition.instance_of?(GraphQL::Language::Nodes::FragmentDefinition) ? 'fragment' : definition.operation_type
|
|
59
|
+
type: definition.instance_of?(GraphQL::Language::Nodes::FragmentDefinition) ? 'fragment' : definition.operation_type,
|
|
60
|
+
file: path,
|
|
61
|
+
line: line,
|
|
62
|
+
constant: heredoc.constant
|
|
61
63
|
}
|
|
62
64
|
end
|
|
63
65
|
end
|
|
64
66
|
end
|
|
65
67
|
end
|
|
66
68
|
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
# Relative to the current directory when the file is inside it, absolute otherwise.
|
|
73
|
+
def relative_path(file)
|
|
74
|
+
absolute = File.realpath(file)
|
|
75
|
+
prefix = "#{File.realpath(Dir.pwd)}/"
|
|
76
|
+
absolute.start_with?(prefix) ? absolute.delete_prefix(prefix) : absolute
|
|
77
|
+
end
|
|
67
78
|
end
|
|
68
79
|
|
|
69
80
|
self.options = default_options.merge(Config.load)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphqlTagPluck
|
|
4
|
+
# Walks a Ruby AST and collects GraphQL heredocs together with
|
|
5
|
+
# the line their body starts on and the constant they are assigned to.
|
|
6
|
+
class HeredocCollector
|
|
7
|
+
using GraphqlTagPluck::Parser::AST::NodeExt
|
|
8
|
+
|
|
9
|
+
Heredoc = Struct.new(:source, :line, :constant, keyword_init: true)
|
|
10
|
+
|
|
11
|
+
def collect(node)
|
|
12
|
+
[].tap { |result| visit(node, [], nil, result) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
# namespace: lexical module / class nesting, e.g. ["Clients", "UserClient"]
|
|
18
|
+
# constant: fully qualified constant name the current node is assigned to, if any
|
|
19
|
+
def visit(node, namespace, constant, result)
|
|
20
|
+
return unless node.is_a?(::Parser::AST::Node)
|
|
21
|
+
|
|
22
|
+
if node.is_graphql_heredoc_node?
|
|
23
|
+
if node.interpolated?
|
|
24
|
+
warn "graphql_tag_pluck: skipping GraphQL heredoc with Ruby interpolation at #{node.loc.expression.source_buffer.name}:#{node.loc.line}"
|
|
25
|
+
else
|
|
26
|
+
body = node.loc.heredoc_body
|
|
27
|
+
result << Heredoc.new(source: body.source, line: body.line, constant: constant)
|
|
28
|
+
end
|
|
29
|
+
return
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
case node.type
|
|
33
|
+
when :module, :class
|
|
34
|
+
name_node, *rest = node.children
|
|
35
|
+
inner_namespace = nest(namespace, const_name(name_node))
|
|
36
|
+
rest.each { |child| visit(child, inner_namespace, nil, result) }
|
|
37
|
+
when :casgn
|
|
38
|
+
scope, name, value = node.children
|
|
39
|
+
visit(value, namespace, assigned_constant(namespace, scope, name), result)
|
|
40
|
+
when :send, :csend
|
|
41
|
+
# Keep the constant for receivers so `FOO = <<~GRAPHQL.freeze` is still attributed to FOO,
|
|
42
|
+
# but not for arguments: `FOO = wrap(<<~GRAPHQL)` does not assign the heredoc itself.
|
|
43
|
+
receiver, _method_name, *args = node.children
|
|
44
|
+
visit(receiver, namespace, constant, result)
|
|
45
|
+
args.each { |child| visit(child, namespace, nil, result) }
|
|
46
|
+
else
|
|
47
|
+
node.children.each { |child| visit(child, namespace, nil, result) }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns e.g. "Foo::Bar", "::Foo", or nil when the constant path is dynamic (e.g. `self::Foo`).
|
|
52
|
+
def const_name(node)
|
|
53
|
+
return nil unless node.is_a?(::Parser::AST::Node) && node.type == :const
|
|
54
|
+
|
|
55
|
+
scope, name = node.children
|
|
56
|
+
case scope&.type
|
|
57
|
+
when nil then name.to_s
|
|
58
|
+
when :cbase then "::#{name}"
|
|
59
|
+
when :const
|
|
60
|
+
prefix = const_name(scope)
|
|
61
|
+
prefix && "#{prefix}::#{name}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def nest(namespace, name)
|
|
66
|
+
return namespace if name.nil?
|
|
67
|
+
return [name.delete_prefix("::")] if name.start_with?("::")
|
|
68
|
+
|
|
69
|
+
namespace + [name]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def assigned_constant(namespace, scope, name)
|
|
73
|
+
return (namespace + [name.to_s]).join("::") if scope.nil?
|
|
74
|
+
return name.to_s if scope.type == :cbase
|
|
75
|
+
|
|
76
|
+
prefix = const_name(scope)
|
|
77
|
+
prefix && nest(nest(namespace, prefix), name.to_s).join("::")
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -9,19 +9,6 @@ module GraphqlTagPluck
|
|
|
9
9
|
type == :dstr && children.any? { |child| child.type != :str && !(child.type == :dstr && !child.interpolated?) }
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def parse_node_recursively
|
|
13
|
-
return nil unless respond_to?(:loc)
|
|
14
|
-
if is_graphql_heredoc_node?
|
|
15
|
-
return loc.heredoc_body.source unless interpolated?
|
|
16
|
-
|
|
17
|
-
warn "graphql_tag_pluck: skipping GraphQL heredoc with Ruby interpolation at #{loc.expression.source_buffer.name}:#{loc.line}"
|
|
18
|
-
return nil
|
|
19
|
-
end
|
|
20
|
-
if respond_to?(:children)
|
|
21
|
-
children.map {|child| child.respond_to?(:parse_node_recursively) ? child.parse_node_recursively : nil }
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
12
|
def is_graphql_heredoc_node?
|
|
26
13
|
loc.instance_of?(::Parser::Source::Map::Heredoc) &&
|
|
27
14
|
GraphqlTagPluck.options[:graphql_heredoc_identifiers].include?(heredoc_identifier)
|
data/lib/graphql_tag_pluck.rb
CHANGED
|
@@ -7,6 +7,7 @@ require "yaml"
|
|
|
7
7
|
require_relative "graphql_tag_pluck/version"
|
|
8
8
|
require_relative "graphql_tag_pluck/config"
|
|
9
9
|
require_relative "graphql_tag_pluck/parser/ast/node_ext"
|
|
10
|
+
require_relative "graphql_tag_pluck/heredoc_collector"
|
|
10
11
|
require_relative "graphql_tag_pluck/railtie" if defined?(Rails)
|
|
11
12
|
|
|
12
13
|
require_relative 'graphql_tag_pluck/graphql_tag_pluck'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: graphql_tag_pluck
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- indigolain
|
|
@@ -91,6 +91,7 @@ files:
|
|
|
91
91
|
- lib/graphql_tag_pluck/Rakefile
|
|
92
92
|
- lib/graphql_tag_pluck/config.rb
|
|
93
93
|
- lib/graphql_tag_pluck/graphql_tag_pluck.rb
|
|
94
|
+
- lib/graphql_tag_pluck/heredoc_collector.rb
|
|
94
95
|
- lib/graphql_tag_pluck/parser/ast/node_ext.rb
|
|
95
96
|
- lib/graphql_tag_pluck/railtie.rb
|
|
96
97
|
- lib/graphql_tag_pluck/rake_tasks.rb
|