Class: RBS::AST::Ruby::CommentBlock

Inherits:
Object
  • Object
show all
Defined in:
sig/ast/ruby/comment_block.rbs,
lib/rbs/ast/ruby/comment_block.rb

Overview

CommentBlock is a collection of comments

# Comment1      < block1
# Comment2      <

# Comment3      < block2

A comment block is a leading block or trailing block.

# This is leading block.
# This is the second line of the leading block.

foo      # This is trailing block.
         # This is second line of the trailing block.

A leading block is a comment block where all of the comments are at the start of the line content. A trailing block is a comment block where the first comment of the block has something at the line before the comment.

Defined Under Namespace

Classes: AnnotationSyntaxError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source_buffer, comments) ⇒ CommentBlock

Returns a new instance of CommentBlock.

Parameters:



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/rbs/ast/ruby/comment_block.rb', line 9

def initialize(source_buffer, comments)
  @name = source_buffer.name

  @offsets = []

  # Assume the comment starts with a prefix whitespace
  prefix_str = "# "

  ranges = [] #: Array[Range[Integer]]
  byte_ranges = [] #: Array[Range[Integer]]

  comments.each do |comment|
    tuple = [comment, 2] #: [Prism::Comment, Integer]

    unless comment.location.slice.start_with?(prefix_str)
      tuple[1] = 1
    end

    offsets << tuple

    start_char = source_buffer.character_offset(comment.location.start_offset) + tuple[1]
    end_char = source_buffer.character_offset(comment.location.end_offset)
    ranges << (start_char ... end_char)
    byte_ranges << ((comment.location.start_offset + tuple[1]) ... comment.location.end_offset)
  end

  @comment_buffer = source_buffer.sub_buffer(lines: ranges, byte_lines_hint: byte_ranges)
end

Instance Attribute Details

#comment_bufferBuffer (readonly)

Sub buffer of the contents of the comments

Returns:



33
34
35
# File 'sig/ast/ruby/comment_block.rbs', line 33

def comment_buffer
  @comment_buffer
end

#namePathname (readonly)

Returns the value of attribute name.

Returns:

  • (Pathname)


7
8
9
# File 'lib/rbs/ast/ruby/comment_block.rb', line 7

def name
  @name
end

#offsetsArray[ (readonly)

Returns the value of attribute offsets.

Returns:

  • (Array[)


7
8
9
# File 'lib/rbs/ast/ruby/comment_block.rb', line 7

def offsets
  @offsets
end

Class Method Details

.build(buffer, comments) ⇒ Array[instance]

Build comment block instances

Parameters:

Returns:

  • (Array[instance])


45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'sig/ast/ruby/comment_block.rbs', line 45

def self.build(buffer, comments)
  blocks = [] #: Array[CommentBlock]

  comments = comments.filter {|comment| comment.is_a?(Prism::InlineComment) }

  until comments.empty?
    block_comments = [] #: Array[Prism::Comment]

    until comments.empty?
      comment = comments.first or raise
      last_comment = block_comments.last

      if last_comment
        if last_comment.location.end_line + 1 == comment.location.start_line
          if last_comment.location.start_column == comment.location.start_column
            unless comment.location.start_line_slice.index(/\S/)
              block_comments << comments.shift
              next
            end
          end
        end

        break
      else
        block_comments << comments.shift
      end
    end

    unless block_comments.empty?
      blocks << CommentBlock.new(buffer, block_comments.dup)
    end
  end

  blocks
end

Instance Method Details

#as_commentAST::Comment?

Returns an comment object that contains the docs of from the comment block

It ignores type annotations and syntax errors.

Returns:



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'sig/ast/ruby/comment_block.rbs', line 123

def as_comment
  lines = [] #: Array[String]

  each_paragraph([]) do |paragraph|
    case paragraph
    when Location
      lines << paragraph.local_source
    end
  end

  string = lines.join("\n")

  unless string.strip.empty?
    AST::Comment.new(string: string, location: location)
  end
end

#commentsArray[Comment]

Returns:



213
214
215
# File 'lib/rbs/ast/ruby/comment_block.rb', line 213

def comments
  offsets.map { _1[0]}
end

#each_paragraph(variables) ⇒ void #each_paragraph(variables) ⇒ Enumerator[Location | AST::Ruby::Annotations::leading_annotation | AnnotationSyntaxError]

Yields paragraph and annotation

A paragraph is a sequence of lines that are separated by annotations. An annotation starts with a line starting with @rbs or :, and may continue with lines that has more leading spaces.

# Line 1         ^ Paragraph 1
#   Line 2       |
#                |
# Line 3         v
# @rbs ...       < Annotation 1
# @rbs ...       ^ Annotation 2
#   ...          |
#                |
#   ...          v
#                ^ Paragraph 2
# Line 4         |
# Line 5         v

Overloads:

  • #each_paragraph(variables) ⇒ void

    This method returns an undefined value.

    Parameters:

    • variables (Array[Symbol])
  • #each_paragraph(variables) ⇒ Enumerator[Location | AST::Ruby::Annotations::leading_annotation | AnnotationSyntaxError]

    Parameters:

    • variables (Array[Symbol])

    Returns:

Yields:

Yield Parameters:

Yield Returns:

  • (void)


86
87
88
89
90
91
92
93
94
95
96
# File 'sig/ast/ruby/comment_block.rbs', line 86

def each_paragraph(variables, &block)
  if block
    if leading_annotation?(0)
      yield_annotation(0, 0, 0, variables, &block)
    else
      yield_paragraph(0, 0, variables, &block)
    end
  else
    enum_for :each_paragraph, variables
  end
end

#end_lineInteger

The line number of the last comment in the block

Returns:

  • (Integer)


57
58
59
# File 'sig/ast/ruby/comment_block.rbs', line 57

def end_line
  comments[-1].location.end_line
end

#leading?Boolean

Returns true if the comment block is a leading comment, which is attached to the successor node

Returns:

  • (Boolean)


48
49
50
51
# File 'sig/ast/ruby/comment_block.rbs', line 48

def leading?
  comment = offsets[0][0] or raise
  comment.location.start_line_slice.index(/\S/) ? false : true
end

#leading_annotation?(index) ⇒ Boolean

Returns:

  • (Boolean)


217
218
219
220
221
222
223
224
225
226
# File 'lib/rbs/ast/ruby/comment_block.rb', line 217

def leading_annotation?(index)
  if index < comment_buffer.line_count
    text(index).start_with?(/@rbs\b/) and return true

    comment = offsets[index][0]
    comment.location.slice.start_with?(/\#:/) and return true
  end

  false
end

#line_location(start_line, end_line) ⇒ Location

Parameters:

  • start_line (Integer)
  • end_line (Integer)

Returns:



176
177
178
179
180
# File 'lib/rbs/ast/ruby/comment_block.rb', line 176

def line_location(start_line, end_line)
  start_offset = comment_buffer.ranges[start_line].begin
  end_offset = comment_buffer.ranges[end_line].end
  Location.new(comment_buffer, start_offset, end_offset)
end

#line_startsArray[Integer]

The character index of #comment_buffer at the start of the lines

Returns:

  • (Array[Integer])


61
62
63
64
65
# File 'sig/ast/ruby/comment_block.rbs', line 61

def line_starts
  offsets.map do |comment, prefix_size|
    comment_buffer.character_offset(comment.location.start_offset) + prefix_size
  end
end

#locationLocation

Returns:



182
183
184
185
186
187
# File 'lib/rbs/ast/ruby/comment_block.rb', line 182

def location()
  first_comment = comments[0] or raise
  last_comment = comments[-1] or raise

  comment_buffer.rbs_location(first_comment.location.join last_comment.location)
end

#parse_annotation_lines(start_line, end_line, variables) ⇒ Object



189
190
191
192
193
194
195
196
197
# File 'lib/rbs/ast/ruby/comment_block.rb', line 189

def parse_annotation_lines(start_line, end_line, variables)
  start_pos = comment_buffer.ranges[start_line].begin
  end_pos = comment_buffer.ranges[end_line].end
  begin
    Parser.parse_inline_leading_annotation(comment_buffer, start_pos...end_pos, variables: variables)
  rescue ParsingError => error
    AnnotationSyntaxError.new(line_location(start_line, end_line), error)
  end
end

#start_lineInteger

The line number of the first comment in the block

Returns:

  • (Integer)


54
55
56
# File 'sig/ast/ruby/comment_block.rbs', line 54

def start_line
  comments[0].location.start_line
end

#text(comment_index) ⇒ String

Returns the text content of the comment

Parameters:

  • index (Integer)

Returns:

  • (String)


64
65
66
67
# File 'sig/ast/ruby/comment_block.rbs', line 64

def text(comment_index)
  range = comment_buffer.ranges[comment_index]
  comment_buffer.content[range] or raise
end

#trailing?Boolean

Returns true if the comment block is a trailing comment, which is attached to the predecessor node

Returns:

  • (Boolean)


51
52
53
54
# File 'sig/ast/ruby/comment_block.rbs', line 51

def trailing?
  comment = offsets[0][0] or raise
  comment.location.start_line_slice.index(/\S/) ? true : false
end

#trailing_annotation(variables) ⇒ AST::Ruby::Annotations::trailing_annotation, ...

Returns a trailing annotation if it exists

  • Returns nil if the block is not a type annotation
  • Returns an annotation if the block has a type annotation
  • Returns AnnotationSyntaxError if the annotation has a syntax error

Parameters:

  • variables (Array[Symbol])

Returns:



95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'sig/ast/ruby/comment_block.rbs', line 95

def trailing_annotation(variables)
  if trailing?
    comment = comments[0] or raise
    if comment.location.slice.start_with?(/#[:\[]/)
      begin
        Parser.parse_inline_trailing_annotation(comment_buffer, 0...comment_buffer.last_position, variables: variables)
      rescue ParsingError => error
        location = line_location(0, offsets.size - 1)
        AnnotationSyntaxError.new(location, error)
      end
    end
  end
end

#yield_annotation(start_line, end_line, current_line, variables, &block) ⇒ Object



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/rbs/ast/ruby/comment_block.rb', line 132

def yield_annotation(start_line, end_line, current_line, variables, &block)
  # We already know at start_line..end_line are annotation.
  while true
    next_line = current_line + 1

    if next_line >= comment_buffer.line_count
      annotation = parse_annotation_lines(start_line, end_line, variables)
      yield annotation

      if end_line > current_line
        yield_paragraph(end_line + 1, end_line + 1, variables, &block)
      end

      return
    end

    line_text = text(next_line)
    if leading_spaces = line_text.index(/\S/)
      if leading_spaces == 0
        # End of annotation
        yield parse_annotation_lines(start_line, end_line, variables)

        if leading_annotation?(end_line + 1)
          yield_annotation(end_line + 1, end_line + 1, end_line + 1, variables, &block)
        else
          yield_paragraph(end_line + 1, end_line + 1, variables, &block)
        end

        return
      else
        current_line = next_line
        end_line = next_line
      end
    else
      current_line = next_line
    end
  end
end

#yield_paragraph(start_line, current_line, variables, &block) ⇒ Object



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/rbs/ast/ruby/comment_block.rb', line 112

def yield_paragraph(start_line, current_line, variables, &block)
  # We already know at start_line..current_line are paragraph.

  while true
    next_line = current_line + 1

    if next_line >= comment_buffer.line_count
      yield line_location(start_line, current_line)
      return
    end

    if leading_annotation?(next_line)
      yield line_location(start_line, current_line)
      return yield_annotation(next_line, next_line, next_line, variables, &block)
    else
      current_line = next_line
    end
  end
end