Repository: gristlabs/asttokens

Issue:
Official support for 3.9 and maybe 3.10
https://github.com/gristlabs/asttokens/pull/56 added testing for 3.9 but this should be made official in setup.cfg.

3.10 should also be tested.

This is based on https://github.com/aroberge/friendly-traceback/issues/96 thinking that only 3.8 is supported.

Discussion:

Patch:
diff --git a/.travis.yml b/.travis.yml
index d86cffc..4276339 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,4 @@
-dist: xenial
+dist: bionic
 language: python
 sudo: false
 
@@ -9,8 +9,8 @@ python:
   - 3.7
   - 3.8-dev
   - 3.9-dev
-  - pypy2.7-6.0
-  - pypy3.5
+  - pypy2
+  - pypy3
 
 env:
   global:
diff --git a/asttokens/util.py b/asttokens/util.py
index c29dd07..79ac430 100644
--- a/asttokens/util.py
+++ b/asttokens/util.py
@@ -142,6 +142,11 @@ def is_joined_str(node):
   return node.__class__.__name__ == 'JoinedStr'
 
 
+def is_starred(node):
+  """Returns whether node is a starred expression node."""
+  return node.__class__.__name__ == 'Starred'
+
+
 def is_slice(node):
   """Returns whether node represents a slice, e.g. `1:2` in `x[1:2]`"""
   # Before 3.9, a tuple containing a slice is an ExtSlice,
diff --git a/setup.cfg b/setup.cfg
index fe07a14..1ebe03d 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -26,6 +26,7 @@ classifiers =
     Programming Language :: Python :: 3.6
     Programming Language :: Python :: 3.7
     Programming Language :: Python :: 3.8
+    Programming Language :: Python :: 3.9
     Programming Language :: Python :: Implementation :: CPython
     Programming Language :: Python :: Implementation :: PyPy
 


Test patch:
diff --git a/tests/test_mark_tokens.py b/tests/test_mark_tokens.py
index a0f68b2..90cff20 100644
--- a/tests/test_mark_tokens.py
+++ b/tests/test_mark_tokens.py
@@ -728,9 +728,12 @@ partial_sums = [total := total + v for v in values]
     # For expressions and statements, we add a dummy statement '_' before it because if it's just a
     # string contained in an astroid.Const or astroid.Expr it will end up in the doc attribute and be
     # a pain to extract for comparison
+    # For starred expressions, e.g. `*args`, we wrap it in a function call to make it parsable.
     indented = re.match(r'^[ \t]+\S', text)
     if indented:
       return self.module.parse('def dummy():\n' + text).body[0].body[0]
+    if util.is_starred(node):
+      return self.module.parse('f(' + text + ')').body[0].value.args[0]
     if util.is_expr(node):
       return self.module.parse('_\n(' + text + ')').body[1].value
     if util.is_module(node):