docs: def getCallSourceLines(callFrame, icNames, icMethod):
      """Raises NoSourceAvailableError."""
      code = callFrame.f_code
  
      # inspect.getblock(), which is called internally by inspect.getsource(),
      # only returns the first line of <code> when <code> represents a top-level
      # module, not the entire module's source, as needed here. The
      #
      #   if ismodule(object):
      #       return lines, 0
      #
      # check in inspect.py doesn't account for code objects of modules, only
      # actual module objects themselves.
      #
      # A workaround is to call findsource() directly on code objects of modules,
      # which bypasses getblock().
      #
      # Also, the errors raised differ between Python2 and Python3 . In Python2,
      # inspect.findsource() and inspect.getsource() raise IOErrors. In Python3,
      # inspect.findsource() and inspect.getsource() raise OSErrors.
      try:
          if code.co_name == '<module>':  # Module -> use workaround above.
              parentBlockStartLine = 1
              lines = inspect.findsource(code)[0]  # Raises [IO/OS]Error.
              parentBlockSource = ''.join(lines)
          else:  # Not a module -> use inspect.getsource() normally.
              parentBlockStartLine = code.co_firstlineno
              parentBlockSource = inspect.getsource(code)  # Raises [IO/OS]Error.
      except (IOError, OSError) as err:
          if 'source code' in err.args[0]:
              raise NoSourceAvailableError()
          else:
              raise
  
      lineno = inspect.getframeinfo(callFrame)[1]
      linenoRelativeToParent = lineno - parentBlockStartLine + 1
  
      # There could be multiple ic() calls on the same line(s), like
      #
      #   ic(1); ic(2); ic(3,
      #       4,
      #       5); ic(6)
      #
      # so include all of them. Which invocation is the appropriate one will be
      # determined later via bytecode offset calculations.
      #
      # TODO(grun): Support invocations of ic() where ic() is an attribute chain
      # in the AST. For example, support
      #
      #   import icecream
      #   icecream.ic()
      #
      # and
      #
      #   class Foo:
      #       blah = ic
      #   Foo.blah()
      #
      parentBlockSource = textwrap.dedent(parentBlockSource)
      potentialCalls = [
          node for node in ast.walk(ast.parse(parentBlockSource))
          if isAstNodeIceCreamCall(node, icNames, icMethod) and
          linenoRelativeToParent in getAllLineNumbersOfAstNode(node)]
  
      if not potentialCalls:
          # TODO(grun): Add note that to NoSourceAvailableError that this
          # situation can occur when the underlying source changed during
          # execution.
          raise NoSourceAvailableError()
  
      endLine = lineno - parentBlockStartLine + 1
      startLine = min(call.lineno for call in potentialCalls)
      lines = parentBlockSource.splitlines()[startLine - 1: endLine]
  
      # inspect's lineno attribute doesn't point to the closing right parenthesis
      # if the closing right parenthesis is on its own line without any
      # arguments. E.g.
      #
      #  ic(1,
      #     2  <--- inspect's reported lineno.
      #     )  <--- Should be the reported lineno.
      #
      # Detect this situation and add the missing right parenthesis.
      if isCallStrMissingClosingRightParenthesis('\n'.join(lines).strip()):
          lines.append(')')
  
      source = stripCommentsAndNewlines('\n'.join(lines)).strip()
  
      absoluteStartLineNum = parentBlockStartLine + startLine - 1
      startLineOffset = calculateLineOffsets(code)[absoluteStartLineNum]
  
      return source, absoluteStartLineNum, startLineOffset