How to get lineno of "end-of-statement" in Python ast
<p>I am trying to work on a script that manipulates another script in Python, the script to be modified has structure like:</p>

<pre><code>class SomethingRecord(Record):
    description = 'This records something'
    author = 'john smith'
</code></pre>

<p>I use <code>ast</code> to locate the <code>description</code> line number, and I use some code to change the original file with new description string base on the line number. So far so good.</p>

<p>Now the only issue is <code>description</code> occasionally is a multi-line string, e.g.</p>

<pre><code>    description = ('line 1'
                   'line 2'
                   'line 3')
</code></pre>

<p>or</p>

<pre><code>    description = 'line 1' \
        'line 2' \
        'line 3'
</code></pre>

<p>and I only have the line number of the first line, not the following lines. So my one-line replacer would do</p>

<pre><code>    description = 'new value'
        'line 2' \
        'line 3'
</code></pre>

<p>and the code is broken. I figured that if I know both the lineno of start and end/number of lines of <code>description</code> assignment I could repair my code to handle such situation. How do I get such information with Python standard library?</p>

<p><a href="https://docs.python.org/3.5/library/ast.html" rel="nofollow">ast</a> only stores the beginning line number for each node.</p>

<p>If your scripts always have another assignment (to <code>author</code>, from your example) after the <code>description</code> assignment, then you could do something like this ...</p>

<ul>
<li>Build a list of line numbers with assignments</li>
<li>When you find a <code>description</code> assignment node, look up the next assignment line</li>
<li>Use that to calculate then end of the <code>description</code> assignment</li>
</ul>

<pre class="lang-py prettyprint-override"><code>import ast


class Assignment(ast.NodeVisitor):
    lines = []

    def visit_Assign(self, node):
        for target in node.targets:
            self.lines.append(target.lineno)
        self.generic_visit(node)


class DescriptionUpdater(ast.NodeVisitor):

    def visit_Assign(self, node):
        if node.targets[0].id == 'description':
            current = Assignment.lines.index(node.lineno)
            next_line = Assignment.lines[current + 1]
            num_lines = next_line - node.lineno
            # update_description(node, num_lines)
        self.generic_visit(node)


with open('script.py') as script:
    tree = ast.parse(script.read())
    Assignment().visit(tree)
    DescriptionUpdater().visit(tree)
</code></pre>

python abstract-syntax-tree