decode 0.12.0 → 0.15.1

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/bake/decode/index.rb +1 -1
  3. data/lib/decode/comment/attribute.rb +53 -0
  4. data/lib/decode/comment/node.rb +90 -0
  5. data/lib/decode/comment/parameter.rb +58 -0
  6. data/lib/decode/comment/pragma.rb +50 -0
  7. data/lib/decode/comment/raises.rb +32 -0
  8. data/lib/decode/comment/returns.rb +32 -0
  9. data/lib/decode/comment/tag.rb +50 -0
  10. data/lib/decode/comment/tags.rb +80 -0
  11. data/lib/decode/comment/text.rb +37 -0
  12. data/lib/decode/comment/throws.rb +32 -0
  13. data/lib/decode/comment/yields.rb +52 -0
  14. data/lib/decode/definition.rb +26 -19
  15. data/lib/decode/documentation.rb +21 -66
  16. data/lib/decode/index.rb +16 -8
  17. data/lib/decode/language.rb +1 -19
  18. data/lib/decode/language/generic.rb +51 -0
  19. data/lib/decode/language/reference.rb +108 -0
  20. data/lib/decode/language/ruby.rb +50 -8
  21. data/lib/decode/language/ruby/block.rb +1 -1
  22. data/lib/decode/language/ruby/class.rb +2 -2
  23. data/lib/decode/language/ruby/code.rb +85 -0
  24. data/lib/decode/language/ruby/definition.rb +1 -1
  25. data/lib/decode/language/ruby/method.rb +7 -1
  26. data/lib/decode/language/ruby/parser.rb +11 -3
  27. data/lib/decode/language/ruby/reference.rb +29 -30
  28. data/lib/decode/language/ruby/segment.rb +1 -1
  29. data/lib/decode/languages.rb +92 -0
  30. data/lib/decode/scope.rb +3 -0
  31. data/lib/decode/segment.rb +4 -4
  32. data/lib/decode/source.rb +26 -16
  33. data/lib/decode/syntax/link.rb +44 -0
  34. data/lib/decode/syntax/match.rb +53 -0
  35. data/lib/decode/syntax/rewriter.rb +70 -0
  36. data/lib/decode/trie.rb +13 -13
  37. data/lib/decode/version.rb +1 -1
  38. metadata +27 -16
  39. data/.github/workflows/development.yml +0 -50
  40. data/.gitignore +0 -13
  41. data/.rspec +0 -2
  42. data/README.md +0 -47
  43. data/decode.gemspec +0 -33
  44. data/gems.rb +0 -3
  45. data/guides/extract-symbols/extract.rb +0 -26
@@ -0,0 +1,51 @@
1
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'reference'
22
+
23
+ module Decode
24
+ module Language
25
+ # The Ruby language.
26
+ class Generic
27
+ def initialize(name)
28
+ @name = name
29
+ end
30
+
31
+ attr :name
32
+
33
+ # Generate a generic reference.
34
+ def reference_for(identifier)
35
+ Reference.new(identifier, self)
36
+ end
37
+
38
+ # Parse the input yielding definitions.
39
+ # @block {|definition| ... }
40
+ # @yield definition [Definition]
41
+ def definitions_for(input, &block)
42
+ end
43
+
44
+ # Parse the input yielding interleaved comments and code segments.
45
+ # @block {|segment| ... }
46
+ # @yield segment [Segment]
47
+ def segments_for(input, &block)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,108 @@
1
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Decode
22
+ module Language
23
+ # An reference which can be resolved to zero or more definitions.
24
+ class Reference
25
+ # Initialize the reference.
26
+ # @parameter identifier [String] The identifier part of the reference.
27
+ def initialize(identifier, language, lexical_path = nil)
28
+ @identifier = identifier
29
+ @language = language
30
+
31
+ @lexical_path = lexical_path
32
+ @path = nil
33
+ end
34
+
35
+ def to_s
36
+ "{#{self.language} #{self.identifier}}"
37
+ end
38
+
39
+ def inspect
40
+ "\#<#{self.class} {#{self.identifier}}>"
41
+ end
42
+
43
+ # The identifier part of the reference.
44
+ # @attribute [String]
45
+ attr :identifier
46
+
47
+ # The language associated with this reference.
48
+ # @attribute [Language::Generic]
49
+ attr :language
50
+
51
+ # Whether the reference starts at the base of the lexical tree.
52
+ def absolute?
53
+ !self.relative?
54
+ end
55
+
56
+ def relative?
57
+ prefix, name = self.lexical_path.first
58
+
59
+ return prefix.nil?
60
+ end
61
+
62
+ def split(identifier)
63
+ identifier.scan(/(\W+)?(\w+)/)
64
+ end
65
+
66
+ def lexical_path
67
+ @lexical_path ||= self.split(@identifier)
68
+ end
69
+
70
+ def priority(definition, prefix)
71
+ if prefix.nil?
72
+ return 1
73
+ elsif definition.start_with?(prefix)
74
+ return 0
75
+ else
76
+ return 2
77
+ end
78
+ end
79
+
80
+ def best(definitions)
81
+ prefix, name = lexical_path.last
82
+
83
+ first = nil
84
+ without_prefix = nil
85
+
86
+ definitions.each do |definition|
87
+ first ||= definition
88
+
89
+ next unless definition.language == @language
90
+
91
+ if prefix.nil?
92
+ without_prefix ||= definition
93
+ elsif definition.start_with?(prefix)
94
+ return definition
95
+ end
96
+ end
97
+
98
+ return without_prefix || first
99
+ end
100
+
101
+ # The lexical path of the reference.
102
+ # @returns [Array(String)]
103
+ def path
104
+ @path ||= self.lexical_path.map{|_, name| name.to_sym}
105
+ end
106
+ end
107
+ end
108
+ end
@@ -20,10 +20,16 @@
20
20
 
21
21
  require_relative 'ruby/reference'
22
22
  require_relative 'ruby/parser'
23
+ require_relative 'ruby/code'
24
+
25
+ require_relative '../comment/tags'
26
+ require_relative '../comment/parameter'
27
+ require_relative '../comment/yields'
28
+ require_relative '../comment/returns'
23
29
 
24
30
  module Decode
25
31
  module Language
26
- # The Ruby language.
32
+ # An interface for extracting information from Ruby source code.
27
33
  module Ruby
28
34
  # The canoical name of the language for use in output formatting.
29
35
  # e.g. source code highlighting.
@@ -31,24 +37,60 @@ module Decode
31
37
  "ruby"
32
38
  end
33
39
 
40
+ def self.names
41
+ [self.name]
42
+ end
43
+
44
+ def self.extensions
45
+ ['.rb', '.ru']
46
+ end
47
+
48
+ TAGS = Comment::Tags.build do |tags|
49
+ tags['attribute'] = Comment::Attribute
50
+ tags['parameter'] = Comment::Parameter
51
+ tags['yields'] = Comment::Yields
52
+ tags['returns'] = Comment::Returns
53
+ tags['raises'] = Comment::Raises
54
+ tags['throws'] = Comment::Throws
55
+
56
+ tags['reentrant'] = Comment::Pragma
57
+ tags['deprecated'] = Comment::Pragma
58
+ tags['blocking'] = Comment::Pragma
59
+ tags['asynchronous'] = Comment::Pragma
60
+ end
61
+
62
+ def self.tags
63
+ TAGS
64
+ end
65
+
34
66
  # Generate a language-specific reference.
35
- def self.reference(value)
36
- Reference.new(value)
67
+ # @parameter identifier [String] A valid identifier.
68
+ def self.reference_for(identifier)
69
+ Reference.new(identifier, self)
37
70
  end
38
71
 
39
72
  # Parse the input yielding definitions.
40
- # @block `{|definition| ...}`
41
- # @yield definition [Definition]
73
+ # @parameter input [File] The input file which contains the source code.
74
+ # @yields {|definition| ...} Receives the definitions extracted from the source code.
75
+ # @parameter definition [Definition] The source code definition including methods, classes, etc.
76
+ # @returns [Enumerator(Segment)] If no block given.
42
77
  def self.definitions_for(input, &block)
43
78
  Parser.new.definitions_for(input, &block)
44
79
  end
45
80
 
46
- # Parse the input yielding interleaved comments and code segments.
47
- # @block `{|segment| ...}`
48
- # @yield segment [Segment]
81
+ # Parse the input yielding segments.
82
+ # Segments are constructed from a block of top level comments followed by a block of code.
83
+ # @parameter input [File] The input file which contains the source code.
84
+ # @yields {|segment| ...}
85
+ # @parameter segment [Segment]
86
+ # @returns [Enumerator(Segment)] If no block given.
49
87
  def self.segments_for(input, &block)
50
88
  Parser.new.segments_for(input, &block)
51
89
  end
90
+
91
+ def self.code_for(text, index, relative_to: nil)
92
+ Code.new(text, index, relative_to: relative_to, language: self)
93
+ end
52
94
  end
53
95
  end
54
96
  end
@@ -58,7 +58,7 @@ module Decode
58
58
 
59
59
  def convert(kind)
60
60
  case kind
61
- when :attr
61
+ when :attribute
62
62
  Attribute.new(@node, @name,
63
63
  comments: @comments, parent: @parent, language: @language
64
64
  )
@@ -62,13 +62,13 @@ module Decode
62
62
  # A Ruby-specific singleton class.
63
63
  class Singleton < Definition
64
64
  # A singleton class is a container for other definitions.
65
- # @return [Boolean]
65
+ # @returns [Boolean]
66
66
  def container?
67
67
  true
68
68
  end
69
69
 
70
70
  # Typically, a singleton class does not contain other definitions.
71
- # @return [Boolean]
71
+ # @returns [Boolean]
72
72
  def nested?
73
73
  false
74
74
  end
@@ -0,0 +1,85 @@
1
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'definition'
22
+ require_relative '../../syntax/link'
23
+
24
+ require 'parser/current'
25
+
26
+ module Decode
27
+ module Language
28
+ module Ruby
29
+ # A Ruby-specific block of code.
30
+ class Code
31
+ def initialize(text, index, relative_to: nil, language: relative_to&.language)
32
+ @text = text
33
+ @root = ::Parser::CurrentRuby.parse(text)
34
+ @index = index
35
+ @relative_to = relative_to
36
+ @language = language
37
+ end
38
+
39
+ attr :text
40
+
41
+ attr :language
42
+
43
+ def extract(into = [])
44
+ if @index
45
+ traverse(@root, into)
46
+ end
47
+
48
+ return into
49
+ end
50
+
51
+ private
52
+
53
+ def traverse(node, into)
54
+ case node&.type
55
+ when :send
56
+ if reference = Reference.from_const(node, @language)
57
+ if definition = @index.lookup(reference, relative_to: @relative_to)
58
+ expression = node.location.selector
59
+ range = expression.begin_pos...expression.end_pos
60
+ into << Syntax::Link.new(range, definition)
61
+ end
62
+ end
63
+
64
+ # Extract constants from arguments:
65
+ children = node.children[2..-1].each do |node|
66
+ traverse(node, into)
67
+ end
68
+ when :const
69
+ if reference = Reference.from_const(node, @language)
70
+ if definition = @index.lookup(reference, relative_to: @relative_to)
71
+ expression = node.location.name
72
+ range = expression.begin_pos...expression.end_pos
73
+ into << Syntax::Link.new(range, definition)
74
+ end
75
+ end
76
+ when :begin
77
+ node.children.each do |child|
78
+ traverse(child, into)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -44,7 +44,7 @@ module Decode
44
44
  end
45
45
 
46
46
  # The source code associated with the definition.
47
- # @return [String]
47
+ # @returns [String]
48
48
  def text
49
49
  expression = @node.location.expression
50
50
  lines = expression.source.lines
@@ -52,9 +52,15 @@ module Decode
52
52
  end
53
53
  end
54
54
 
55
+ # The fully qualified name of the block.
56
+ # e.g. `::Barnyard#foo`.
57
+ def qualified_form
58
+ self.qualified_name
59
+ end
60
+
55
61
  def convert(kind)
56
62
  case kind
57
- when :attr
63
+ when :attribute
58
64
  Attribute.new(@node, @name,
59
65
  comments: @comments, parent: @parent, language: @language
60
66
  )
@@ -40,7 +40,7 @@ module Decode
40
40
  class Parser
41
41
  # Extract definitions from the given input file.
42
42
  def definitions_for(input, &block)
43
- top, comments = ::Parser::CurrentRuby.parse_with_comments(input.read)
43
+ top, comments = ::Parser::CurrentRuby.parse_with_comments(input)
44
44
 
45
45
  if top
46
46
  walk_definitions(top, comments, &block)
@@ -207,7 +207,7 @@ module Decode
207
207
  end
208
208
 
209
209
  KIND_ATTRIBUTE = /\A
210
- (@(?<kind>attr)\s+(?<value>.*?))|
210
+ (@(?<kind>attribute)\s+(?<value>.*?))|
211
211
  (@define\s+(?<kind>)\s+(?<value>.*?))
212
212
  \Z/x
213
213
 
@@ -241,7 +241,7 @@ module Decode
241
241
 
242
242
  # Extract segments from the given input file.
243
243
  def segments_for(input, &block)
244
- top, comments = ::Parser::CurrentRuby.parse_with_comments(input.read)
244
+ top, comments = ::Parser::CurrentRuby.parse_with_comments(input)
245
245
 
246
246
  # We delete any leading comments:
247
247
  line = 0
@@ -279,6 +279,14 @@ module Decode
279
279
  end
280
280
 
281
281
  yield segment if segment
282
+ else
283
+ # One top level segment:
284
+ segment = Segment.new(
285
+ extract_comments_for(node, comments),
286
+ Ruby, node
287
+ )
288
+
289
+ yield segment
282
290
  end
283
291
  end
284
292
  end
@@ -18,47 +18,46 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
+ require_relative '../reference'
22
+
21
23
  module Decode
22
24
  module Language
23
25
  module Ruby
24
26
  # An Ruby-specific reference which can be resolved to zero or more definitions.
25
- class Reference
26
- # Initialize the reference.
27
- # @param text [String] The text text of the reference.
28
- def initialize(text)
29
- @text = text
27
+ class Reference < Language::Reference
28
+ def self.from_const(node, language)
29
+ lexical_path = append_const(node)
30
30
 
31
- @lexical_path = nil
32
- @path = nil
33
- end
34
-
35
- def language
36
- Ruby
37
- end
38
-
39
- attr :text
40
-
41
- # Whether the reference starts at the base of the lexical tree.
42
- def absolute?
43
- @text.start_with?('::')
31
+ return self.new(node.location.expression.source, language, lexical_path)
44
32
  end
45
33
 
46
- def lexical_path
47
- @lexical_path ||= @text.scan(/(::|\.|#|:)?([^:.#]+)/)
48
- end
49
-
50
- def best(definitions)
51
- prefix, name = lexical_path.last
34
+ def self.append_const(node, path = [])
35
+ parent, name = node.children
52
36
 
53
- definitions.select do |definition|
54
- prefix.nil? || definition.start_with?(prefix)
37
+ if parent and parent.type != :cbase
38
+ append_const(parent, path)
55
39
  end
40
+
41
+ case node.type
42
+ when :const
43
+ if parent && parent.type != :cbase
44
+ path << ['::', name]
45
+ else
46
+ path << [nil, name]
47
+ end
48
+ when :send
49
+ path << ['#', name]
50
+ when :cbase
51
+ # Ignore.
52
+ else
53
+ raise ArgumentError, "Could not determine reference for #{node}!"
54
+ end
55
+
56
+ return path
56
57
  end
57
58
 
58
- # The lexical path of the reference.
59
- # @return [Array(String)]
60
- def path
61
- @path ||= self.lexical_path.map{|_, name| name.to_sym}
59
+ def split(text)
60
+ text.scan(/(::|\.|#|:)?([^:.#]+)/)
62
61
  end
63
62
  end
64
63
  end