Mercurial > hg
annotate contrib/import-checker.py @ 27142:060f83d219b9
extensions: refuse to load extensions if minimum hg version not met
As the author of several 3rd party extensions, I frequently see bug
reports from users attempting to run my extension with an old version
of Mercurial that I no longer support in my extension. Oftentimes, the
extension will import just fine. But as soon as we run extsetup(),
reposetup(), or get into the guts of a wrapped function, we encounter
an exception and abort. Today, Mercurial will print a message about
extensions that don't have a "testedwith" declaring explicit
compatibility with the current version.
The existing mechanism is a good start. But it isn't as robust as I
would like. Specifically, Mercurial assumes compatibility by default.
This means extension authors must perform compatibility checking in
their extsetup() or we wait and see if we encounter an abort at
runtime. And, compatibility checking can involve a lot of code and
lots of error checking. It's a lot of effort for extension authors.
Oftentimes, extension authors know which versions of Mercurial there
extension works on and more importantly where it is broken.
This patch introduces a magic "minimumhgversion" attribute in
extensions. When found, the extension loading mechanism will compare
the declared version against the current Mercurial version. If the
extension explicitly states we require a newer Mercurial version, a
warning is printed and the extension isn't loaded beyond importing
the Python module. This causes a graceful failure while alerting
the user of the compatibility issue.
I would be receptive to the idea of making the failure more fatal.
However, care would need to be taken to not criple every hg command.
e.g. the user may use `hg config` to fix the hgrc and if we aborted
trying to run that, the user would effectively be locked out of `hg`!
A potential future improvement to this functionality would be to catch
ImportError for the extension/module and parse the source code for
"minimumhgversion = 'XXX'" and do similar checking. This way we could
give more information about why the extension failed to load.
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Tue, 24 Nov 2015 15:16:25 -0800 |
parents | e5be48dd8215 |
children | 69308357ecd1 |
rev | line source |
---|---|
26954
f804bf27439b
import-checker: make it executable for convenience
Yuya Nishihara <yuya@tcha.org>
parents:
26781
diff
changeset
|
1 #!/usr/bin/env python |
f804bf27439b
import-checker: make it executable for convenience
Yuya Nishihara <yuya@tcha.org>
parents:
26781
diff
changeset
|
2 |
20036 | 3 import ast |
26965
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
4 import collections |
20036 | 5 import os |
6 import sys | |
7 | |
20198
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
8 # Import a minimal set of stdlib modules needed for list_stdlib_modules() |
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
9 # to work when run from a virtualenv. The modules were chosen empirically |
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
10 # so that the return value matches the return value without virtualenv. |
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
11 import BaseHTTPServer |
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
12 import zlib |
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
13 |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
14 # Whitelist of modules that symbols can be directly imported from. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
15 allowsymbolimports = ( |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
16 '__future__', |
27018
e5be48dd8215
import-checker: allow symbol imports from hgweb.common and .request
Yuya Nishihara <yuya@tcha.org>
parents:
26965
diff
changeset
|
17 'mercurial.hgweb.common', |
e5be48dd8215
import-checker: allow symbol imports from hgweb.common and .request
Yuya Nishihara <yuya@tcha.org>
parents:
26965
diff
changeset
|
18 'mercurial.hgweb.request', |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
19 'mercurial.i18n', |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
20 'mercurial.node', |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
21 ) |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
22 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
23 # Modules that must be aliased because they are commonly confused with |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
24 # common variables and can create aliasing and readability issues. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
25 requirealias = { |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
26 'ui': 'uimod', |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
27 } |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
28 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
29 def usingabsolute(root): |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
30 """Whether absolute imports are being used.""" |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
31 if sys.version_info[0] >= 3: |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
32 return True |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
33 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
34 for node in ast.walk(root): |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
35 if isinstance(node, ast.ImportFrom): |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
36 if node.module == '__future__': |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
37 for n in node.names: |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
38 if n.name == 'absolute_import': |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
39 return True |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
40 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
41 return False |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
42 |
26965
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
43 def walklocal(root): |
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
44 """Recursively yield all descendant nodes but not in a different scope""" |
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
45 todo = collections.deque(ast.iter_child_nodes(root)) |
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
46 yield root, False |
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
47 while todo: |
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
48 node = todo.popleft() |
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
49 newscope = isinstance(node, ast.FunctionDef) |
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
50 if not newscope: |
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
51 todo.extend(ast.iter_child_nodes(node)) |
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
52 yield node, newscope |
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
53 |
22975
461342e1c8aa
import-checker: check modules for pure Python build correctly
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
22974
diff
changeset
|
54 def dotted_name_of_path(path, trimpure=False): |
20036 | 55 """Given a relative path to a source file, return its dotted module name. |
56 | |
57 >>> dotted_name_of_path('mercurial/error.py') | |
58 'mercurial.error' | |
22975
461342e1c8aa
import-checker: check modules for pure Python build correctly
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
22974
diff
changeset
|
59 >>> dotted_name_of_path('mercurial/pure/parsers.py', trimpure=True) |
461342e1c8aa
import-checker: check modules for pure Python build correctly
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
22974
diff
changeset
|
60 'mercurial.parsers' |
20383
4990abb4729d
import-checker: fix names of dynamically loaded modules
Mads Kiilerich <madski@unity3d.com>
parents:
20238
diff
changeset
|
61 >>> dotted_name_of_path('zlibmodule.so') |
4990abb4729d
import-checker: fix names of dynamically loaded modules
Mads Kiilerich <madski@unity3d.com>
parents:
20238
diff
changeset
|
62 'zlib' |
20036 | 63 """ |
64 parts = path.split('/') | |
20391
466e4c574db0
import-checker: handle standard modules with arch in the filename
Mads Kiilerich <madski@unity3d.com>
parents:
20386
diff
changeset
|
65 parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so |
20383
4990abb4729d
import-checker: fix names of dynamically loaded modules
Mads Kiilerich <madski@unity3d.com>
parents:
20238
diff
changeset
|
66 if parts[-1].endswith('module'): |
4990abb4729d
import-checker: fix names of dynamically loaded modules
Mads Kiilerich <madski@unity3d.com>
parents:
20238
diff
changeset
|
67 parts[-1] = parts[-1][:-6] |
22975
461342e1c8aa
import-checker: check modules for pure Python build correctly
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
22974
diff
changeset
|
68 if trimpure: |
461342e1c8aa
import-checker: check modules for pure Python build correctly
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
22974
diff
changeset
|
69 return '.'.join(p for p in parts if p != 'pure') |
20036 | 70 return '.'.join(parts) |
71 | |
25173
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
72 def fromlocalfunc(modulename, localmods): |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
73 """Get a function to examine which locally defined module the |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
74 target source imports via a specified name. |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
75 |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
76 `modulename` is an `dotted_name_of_path()`-ed source file path, |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
77 which may have `.__init__` at the end of it, of the target source. |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
78 |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
79 `localmods` is a dict (or set), of which key is an absolute |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
80 `dotted_name_of_path()`-ed source file path of locally defined (= |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
81 Mercurial specific) modules. |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
82 |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
83 This function assumes that module names not existing in |
26781
1aee2ab0f902
spelling: trivial spell checking
Mads Kiilerich <madski@unity3d.com>
parents:
26221
diff
changeset
|
84 `localmods` are from the Python standard library. |
25173
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
85 |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
86 This function returns the function, which takes `name` argument, |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
87 and returns `(absname, dottedpath, hassubmod)` tuple if `name` |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
88 matches against locally defined module. Otherwise, it returns |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
89 False. |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
90 |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
91 It is assumed that `name` doesn't have `.__init__`. |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
92 |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
93 `absname` is an absolute module name of specified `name` |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
94 (e.g. "hgext.convert"). This can be used to compose prefix for sub |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
95 modules or so. |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
96 |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
97 `dottedpath` is a `dotted_name_of_path()`-ed source file path |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
98 (e.g. "hgext.convert.__init__") of `name`. This is used to look |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
99 module up in `localmods` again. |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
100 |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
101 `hassubmod` is whether it may have sub modules under it (for |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
102 convenient, even though this is also equivalent to "absname != |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
103 dottednpath") |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
104 |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
105 >>> localmods = {'foo.__init__': True, 'foo.foo1': True, |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
106 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True, |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
107 ... 'baz.__init__': True, 'baz.baz1': True } |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
108 >>> fromlocal = fromlocalfunc('foo.xxx', localmods) |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
109 >>> # relative |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
110 >>> fromlocal('foo1') |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
111 ('foo.foo1', 'foo.foo1', False) |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
112 >>> fromlocal('bar') |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
113 ('foo.bar', 'foo.bar.__init__', True) |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
114 >>> fromlocal('bar.bar1') |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
115 ('foo.bar.bar1', 'foo.bar.bar1', False) |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
116 >>> # absolute |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
117 >>> fromlocal('baz') |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
118 ('baz', 'baz.__init__', True) |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
119 >>> fromlocal('baz.baz1') |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
120 ('baz.baz1', 'baz.baz1', False) |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
121 >>> # unknown = maybe standard library |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
122 >>> fromlocal('os') |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
123 False |
25701
1f88c0f6ff5a
import-checker: resolve relative imports
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25660
diff
changeset
|
124 >>> fromlocal(None, 1) |
1f88c0f6ff5a
import-checker: resolve relative imports
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25660
diff
changeset
|
125 ('foo', 'foo.__init__', True) |
1f88c0f6ff5a
import-checker: resolve relative imports
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25660
diff
changeset
|
126 >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods) |
1f88c0f6ff5a
import-checker: resolve relative imports
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25660
diff
changeset
|
127 >>> fromlocal2(None, 2) |
1f88c0f6ff5a
import-checker: resolve relative imports
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25660
diff
changeset
|
128 ('foo', 'foo.__init__', True) |
25173
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
129 """ |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
130 prefix = '.'.join(modulename.split('.')[:-1]) |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
131 if prefix: |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
132 prefix += '.' |
25701
1f88c0f6ff5a
import-checker: resolve relative imports
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25660
diff
changeset
|
133 def fromlocal(name, level=0): |
1f88c0f6ff5a
import-checker: resolve relative imports
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25660
diff
changeset
|
134 # name is None when relative imports are used. |
1f88c0f6ff5a
import-checker: resolve relative imports
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25660
diff
changeset
|
135 if name is None: |
1f88c0f6ff5a
import-checker: resolve relative imports
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25660
diff
changeset
|
136 # If relative imports are used, level must not be absolute. |
1f88c0f6ff5a
import-checker: resolve relative imports
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25660
diff
changeset
|
137 assert level > 0 |
1f88c0f6ff5a
import-checker: resolve relative imports
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25660
diff
changeset
|
138 candidates = ['.'.join(modulename.split('.')[:-level])] |
1f88c0f6ff5a
import-checker: resolve relative imports
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25660
diff
changeset
|
139 else: |
1f88c0f6ff5a
import-checker: resolve relative imports
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25660
diff
changeset
|
140 # Check relative name first. |
1f88c0f6ff5a
import-checker: resolve relative imports
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25660
diff
changeset
|
141 candidates = [prefix + name, name] |
1f88c0f6ff5a
import-checker: resolve relative imports
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25660
diff
changeset
|
142 |
1f88c0f6ff5a
import-checker: resolve relative imports
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25660
diff
changeset
|
143 for n in candidates: |
25173
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
144 if n in localmods: |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
145 return (n, n, False) |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
146 dottedpath = n + '.__init__' |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
147 if dottedpath in localmods: |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
148 return (n, dottedpath, True) |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
149 return False |
7358b5d9991e
import-checker: add utility to examine what module is imported easily
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25064
diff
changeset
|
150 return fromlocal |
20036 | 151 |
152 def list_stdlib_modules(): | |
153 """List the modules present in the stdlib. | |
154 | |
155 >>> mods = set(list_stdlib_modules()) | |
156 >>> 'BaseHTTPServer' in mods | |
157 True | |
158 | |
159 os.path isn't really a module, so it's missing: | |
160 | |
161 >>> 'os.path' in mods | |
162 False | |
163 | |
164 sys requires special treatment, because it's baked into the | |
165 interpreter, but it should still appear: | |
166 | |
167 >>> 'sys' in mods | |
168 True | |
169 | |
170 >>> 'collections' in mods | |
171 True | |
172 | |
173 >>> 'cStringIO' in mods | |
174 True | |
175 """ | |
176 for m in sys.builtin_module_names: | |
177 yield m | |
178 # These modules only exist on windows, but we should always | |
179 # consider them stdlib. | |
180 for m in ['msvcrt', '_winreg']: | |
181 yield m | |
182 # These get missed too | |
26062
7154a4a08b96
import-checker: ensure multiprocessing is treated as from stdlib
Augie Fackler <augie@google.com>
parents:
25734
diff
changeset
|
183 for m in 'ctypes', 'email', 'multiprocessing': |
20036 | 184 yield m |
185 yield 'builtins' # python3 only | |
24669
fbdbff1b486a
import-checker: force 'fcntl', 'grp', 'pwd', and 'termios' to stdlib modules
Matt Harbison <matt_harbison@yahoo.com>
parents:
24668
diff
changeset
|
186 for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only |
fbdbff1b486a
import-checker: force 'fcntl', 'grp', 'pwd', and 'termios' to stdlib modules
Matt Harbison <matt_harbison@yahoo.com>
parents:
24668
diff
changeset
|
187 yield m |
20197
761f2929a6ad
import-checker: refactor sys.path prefix check (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20038
diff
changeset
|
188 stdlib_prefixes = set([sys.prefix, sys.exec_prefix]) |
20198
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
189 # We need to supplement the list of prefixes for the search to work |
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
190 # when run from within a virtualenv. |
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
191 for mod in (BaseHTTPServer, zlib): |
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
192 try: |
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
193 # Not all module objects have a __file__ attribute. |
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
194 filename = mod.__file__ |
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
195 except AttributeError: |
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
196 continue |
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
197 dirname = os.path.dirname(filename) |
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
198 for prefix in stdlib_prefixes: |
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
199 if dirname.startswith(prefix): |
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
200 # Then this directory is redundant. |
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
201 break |
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
202 else: |
f5393a9dc4e5
import-checker: make test-module-imports.t work using virtualenv (issue4129)
Chris Jerdonek <chris.jerdonek@gmail.com>
parents:
20197
diff
changeset
|
203 stdlib_prefixes.add(dirname) |
20036 | 204 for libpath in sys.path: |
20201
bc3b48b0f5c8
import-checker: suppress check-code about any()
Augie Fackler <raf@durin42.com>
parents:
20200
diff
changeset
|
205 # We want to walk everything in sys.path that starts with |
bc3b48b0f5c8
import-checker: suppress check-code about any()
Augie Fackler <raf@durin42.com>
parents:
20200
diff
changeset
|
206 # something in stdlib_prefixes. check-code suppressed because |
bc3b48b0f5c8
import-checker: suppress check-code about any()
Augie Fackler <raf@durin42.com>
parents:
20200
diff
changeset
|
207 # the ast module used by this script implies the availability |
bc3b48b0f5c8
import-checker: suppress check-code about any()
Augie Fackler <raf@durin42.com>
parents:
20200
diff
changeset
|
208 # of any(). |
20238
81e905790b30
check-code: do not skip entire file, skip only one match instead
Simon Heimberg <simohe@besonet.ch>
parents:
20201
diff
changeset
|
209 if not any(libpath.startswith(p) for p in stdlib_prefixes): # no-py24 |
20036 | 210 continue |
211 for top, dirs, files in os.walk(libpath): | |
25733
f99c066f5f9a
import-checker: recurse into subtree of sys.path only if __init__.py exists
Yuya Nishihara <yuya@tcha.org>
parents:
25731
diff
changeset
|
212 for i, d in reversed(list(enumerate(dirs))): |
25734
9086d0c1def3
import-checker: exclude mercurial packages installed into the system path
Yuya Nishihara <yuya@tcha.org>
parents:
25733
diff
changeset
|
213 if (not os.path.exists(os.path.join(top, d, '__init__.py')) |
9086d0c1def3
import-checker: exclude mercurial packages installed into the system path
Yuya Nishihara <yuya@tcha.org>
parents:
25733
diff
changeset
|
214 or top == libpath and d in ('hgext', 'mercurial')): |
25733
f99c066f5f9a
import-checker: recurse into subtree of sys.path only if __init__.py exists
Yuya Nishihara <yuya@tcha.org>
parents:
25731
diff
changeset
|
215 del dirs[i] |
20036 | 216 for name in files: |
217 if name == '__init__.py': | |
218 continue | |
26221
ae65b1b4cb46
import-checker: use modern .endswith for multiple suffixes
Augie Fackler <augie@google.com>
parents:
26166
diff
changeset
|
219 if not name.endswith(('.py', '.so', '.pyc', '.pyo', '.pyd')): |
20036 | 220 continue |
221 full_path = os.path.join(top, name) | |
222 rel_path = full_path[len(libpath) + 1:] | |
223 mod = dotted_name_of_path(rel_path) | |
224 yield mod | |
225 | |
226 stdlib_modules = set(list_stdlib_modules()) | |
227 | |
25174
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
228 def imported_modules(source, modulename, localmods, ignore_nested=False): |
20036 | 229 """Given the source of a file as a string, yield the names |
230 imported by that file. | |
231 | |
20037
957b43371928
import-checker: ignore nested imports
Augie Fackler <raf@durin42.com>
parents:
20036
diff
changeset
|
232 Args: |
957b43371928
import-checker: ignore nested imports
Augie Fackler <raf@durin42.com>
parents:
20036
diff
changeset
|
233 source: The python source to examine as a string. |
25174
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
234 modulename: of specified python source (may have `__init__`) |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
235 localmods: dict of locally defined module names (may have `__init__`) |
20037
957b43371928
import-checker: ignore nested imports
Augie Fackler <raf@durin42.com>
parents:
20036
diff
changeset
|
236 ignore_nested: If true, import statements that do not start in |
957b43371928
import-checker: ignore nested imports
Augie Fackler <raf@durin42.com>
parents:
20036
diff
changeset
|
237 column zero will be ignored. |
957b43371928
import-checker: ignore nested imports
Augie Fackler <raf@durin42.com>
parents:
20036
diff
changeset
|
238 |
957b43371928
import-checker: ignore nested imports
Augie Fackler <raf@durin42.com>
parents:
20036
diff
changeset
|
239 Returns: |
25174
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
240 A list of absolute module names imported by the given source. |
20037
957b43371928
import-checker: ignore nested imports
Augie Fackler <raf@durin42.com>
parents:
20036
diff
changeset
|
241 |
25174
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
242 >>> modulename = 'foo.xxx' |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
243 >>> localmods = {'foo.__init__': True, |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
244 ... 'foo.foo1': True, 'foo.foo2': True, |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
245 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True, |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
246 ... 'baz.__init__': True, 'baz.baz1': True } |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
247 >>> # standard library (= not locally defined ones) |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
248 >>> sorted(imported_modules( |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
249 ... 'from stdlib1 import foo, bar; import stdlib2', |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
250 ... modulename, localmods)) |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
251 [] |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
252 >>> # relative importing |
20037
957b43371928
import-checker: ignore nested imports
Augie Fackler <raf@durin42.com>
parents:
20036
diff
changeset
|
253 >>> sorted(imported_modules( |
25174
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
254 ... 'import foo1; from bar import bar1', |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
255 ... modulename, localmods)) |
26964
5abba2c92da3
import-checker: allow import of child modules from package root
Yuya Nishihara <yuya@tcha.org>
parents:
26956
diff
changeset
|
256 ['foo.bar.bar1', 'foo.foo1'] |
25174
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
257 >>> sorted(imported_modules( |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
258 ... 'from bar.bar1 import name1, name2, name3', |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
259 ... modulename, localmods)) |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
260 ['foo.bar.bar1'] |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
261 >>> # absolute importing |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
262 >>> sorted(imported_modules( |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
263 ... 'from baz import baz1, name1', |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
264 ... modulename, localmods)) |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
265 ['baz.__init__', 'baz.baz1'] |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
266 >>> # mixed importing, even though it shouldn't be recommended |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
267 >>> sorted(imported_modules( |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
268 ... 'import stdlib, foo1, baz', |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
269 ... modulename, localmods)) |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
270 ['baz.__init__', 'foo.foo1'] |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
271 >>> # ignore_nested |
20037
957b43371928
import-checker: ignore nested imports
Augie Fackler <raf@durin42.com>
parents:
20036
diff
changeset
|
272 >>> sorted(imported_modules( |
957b43371928
import-checker: ignore nested imports
Augie Fackler <raf@durin42.com>
parents:
20036
diff
changeset
|
273 ... '''import foo |
957b43371928
import-checker: ignore nested imports
Augie Fackler <raf@durin42.com>
parents:
20036
diff
changeset
|
274 ... def wat(): |
957b43371928
import-checker: ignore nested imports
Augie Fackler <raf@durin42.com>
parents:
20036
diff
changeset
|
275 ... import bar |
25174
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
276 ... ''', modulename, localmods)) |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
277 ['foo.__init__', 'foo.bar.__init__'] |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
278 >>> sorted(imported_modules( |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
279 ... '''import foo |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
280 ... def wat(): |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
281 ... import bar |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
282 ... ''', modulename, localmods, ignore_nested=True)) |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
283 ['foo.__init__'] |
20036 | 284 """ |
25174
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
285 fromlocal = fromlocalfunc(modulename, localmods) |
20036 | 286 for node in ast.walk(ast.parse(source)): |
20037
957b43371928
import-checker: ignore nested imports
Augie Fackler <raf@durin42.com>
parents:
20036
diff
changeset
|
287 if ignore_nested and getattr(node, 'col_offset', 0) > 0: |
957b43371928
import-checker: ignore nested imports
Augie Fackler <raf@durin42.com>
parents:
20036
diff
changeset
|
288 continue |
20036 | 289 if isinstance(node, ast.Import): |
290 for n in node.names: | |
25174
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
291 found = fromlocal(n.name) |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
292 if not found: |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
293 # this should import standard library |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
294 continue |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
295 yield found[1] |
20036 | 296 elif isinstance(node, ast.ImportFrom): |
25701
1f88c0f6ff5a
import-checker: resolve relative imports
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25660
diff
changeset
|
297 found = fromlocal(node.module, node.level) |
25174
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
298 if not found: |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
299 # this should import standard library |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
300 continue |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
301 |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
302 absname, dottedpath, hassubmod = found |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
303 if not hassubmod: |
26964
5abba2c92da3
import-checker: allow import of child modules from package root
Yuya Nishihara <yuya@tcha.org>
parents:
26956
diff
changeset
|
304 # "dottedpath" is not a package; must be imported |
5abba2c92da3
import-checker: allow import of child modules from package root
Yuya Nishihara <yuya@tcha.org>
parents:
26956
diff
changeset
|
305 yield dottedpath |
25174
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
306 # examination of "node.names" should be redundant |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
307 # e.g.: from mercurial.node import nullid, nullrev |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
308 continue |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
309 |
26964
5abba2c92da3
import-checker: allow import of child modules from package root
Yuya Nishihara <yuya@tcha.org>
parents:
26956
diff
changeset
|
310 modnotfound = False |
25174
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
311 prefix = absname + '.' |
20036 | 312 for n in node.names: |
25174
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
313 found = fromlocal(prefix + n.name) |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
314 if not found: |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
315 # this should be a function or a property of "node.module" |
26964
5abba2c92da3
import-checker: allow import of child modules from package root
Yuya Nishihara <yuya@tcha.org>
parents:
26956
diff
changeset
|
316 modnotfound = True |
25174
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
317 continue |
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
318 yield found[1] |
26964
5abba2c92da3
import-checker: allow import of child modules from package root
Yuya Nishihara <yuya@tcha.org>
parents:
26956
diff
changeset
|
319 if modnotfound: |
5abba2c92da3
import-checker: allow import of child modules from package root
Yuya Nishihara <yuya@tcha.org>
parents:
26956
diff
changeset
|
320 # "dottedpath" is a package, but imported because of non-module |
5abba2c92da3
import-checker: allow import of child modules from package root
Yuya Nishihara <yuya@tcha.org>
parents:
26956
diff
changeset
|
321 # lookup |
5abba2c92da3
import-checker: allow import of child modules from package root
Yuya Nishihara <yuya@tcha.org>
parents:
26956
diff
changeset
|
322 yield dottedpath |
20036 | 323 |
25702
ab2c5163900e
import-checker: establish new function for verifying import conventions
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25701
diff
changeset
|
324 def verify_import_convention(module, source): |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
325 """Verify imports match our established coding convention. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
326 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
327 We have 2 conventions: legacy and modern. The modern convention is in |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
328 effect when using absolute imports. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
329 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
330 The legacy convention only looks for mixed imports. The modern convention |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
331 is much more thorough. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
332 """ |
25702
ab2c5163900e
import-checker: establish new function for verifying import conventions
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25701
diff
changeset
|
333 root = ast.parse(source) |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
334 absolute = usingabsolute(root) |
25702
ab2c5163900e
import-checker: establish new function for verifying import conventions
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25701
diff
changeset
|
335 |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
336 if absolute: |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
337 return verify_modern_convention(module, root) |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
338 else: |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
339 return verify_stdlib_on_own_line(root) |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
340 |
26965
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
341 def verify_modern_convention(module, root, root_col_offset=0): |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
342 """Verify a file conforms to the modern import convention rules. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
343 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
344 The rules of the modern convention are: |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
345 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
346 * Ordering is stdlib followed by local imports. Each group is lexically |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
347 sorted. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
348 * Importing multiple modules via "import X, Y" is not allowed: use |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
349 separate import statements. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
350 * Importing multiple modules via "from X import ..." is allowed if using |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
351 parenthesis and one entry per line. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
352 * Only 1 relative import statement per import level ("from .", "from ..") |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
353 is allowed. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
354 * Relative imports from higher levels must occur before lower levels. e.g. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
355 "from .." must be before "from .". |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
356 * Imports from peer packages should use relative import (e.g. do not |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
357 "import mercurial.foo" from a "mercurial.*" module). |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
358 * Symbols can only be imported from specific modules (see |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
359 `allowsymbolimports`). For other modules, first import the module then |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
360 assign the symbol to a module-level variable. In addition, these imports |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
361 must be performed before other relative imports. This rule only |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
362 applies to import statements outside of any blocks. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
363 * Relative imports from the standard library are not allowed. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
364 * Certain modules must be aliased to alternate names to avoid aliasing |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
365 and readability problems. See `requirealias`. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
366 """ |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
367 topmodule = module.split('.')[0] |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
368 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
369 # Whether a local/non-stdlib import has been performed. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
370 seenlocal = False |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
371 # Whether a relative, non-symbol import has been seen. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
372 seennonsymbolrelative = False |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
373 # The last name to be imported (for sorting). |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
374 lastname = None |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
375 # Relative import levels encountered so far. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
376 seenlevels = set() |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
377 |
26965
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
378 for node, newscope in walklocal(root): |
26955
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
379 def msg(fmt, *args): |
26956
4b56214ebb7a
import-checker: include lineno in warning message
Yuya Nishihara <yuya@tcha.org>
parents:
26955
diff
changeset
|
380 return (fmt % args, node.lineno) |
26965
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
381 if newscope: |
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
382 # Check for local imports in function |
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
383 for r in verify_modern_convention(module, node, |
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
384 node.col_offset + 4): |
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
385 yield r |
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
386 elif isinstance(node, ast.Import): |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
387 # Disallow "import foo, bar" and require separate imports |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
388 # for each module. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
389 if len(node.names) > 1: |
26955
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
390 yield msg('multiple imported names: %s', |
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
391 ', '.join(n.name for n in node.names)) |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
392 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
393 name = node.names[0].name |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
394 asname = node.names[0].asname |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
395 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
396 # Ignore sorting rules on imports inside blocks. |
26965
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
397 if node.col_offset == root_col_offset: |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
398 if lastname and name < lastname: |
26955
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
399 yield msg('imports not lexically sorted: %s < %s', |
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
400 name, lastname) |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
401 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
402 lastname = name |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
403 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
404 # stdlib imports should be before local imports. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
405 stdlib = name in stdlib_modules |
26965
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
406 if stdlib and seenlocal and node.col_offset == root_col_offset: |
26955
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
407 yield msg('stdlib import follows local import: %s', name) |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
408 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
409 if not stdlib: |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
410 seenlocal = True |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
411 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
412 # Import of sibling modules should use relative imports. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
413 topname = name.split('.')[0] |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
414 if topname == topmodule: |
26955
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
415 yield msg('import should be relative: %s', name) |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
416 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
417 if name in requirealias and asname != requirealias[name]: |
26955
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
418 yield msg('%s module must be "as" aliased to %s', |
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
419 name, requirealias[name]) |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
420 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
421 elif isinstance(node, ast.ImportFrom): |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
422 # Resolve the full imported module name. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
423 if node.level > 0: |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
424 fullname = '.'.join(module.split('.')[:-node.level]) |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
425 if node.module: |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
426 fullname += '.%s' % node.module |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
427 else: |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
428 assert node.module |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
429 fullname = node.module |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
430 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
431 topname = fullname.split('.')[0] |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
432 if topname == topmodule: |
26955
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
433 yield msg('import should be relative: %s', fullname) |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
434 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
435 # __future__ is special since it needs to come first and use |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
436 # symbol import. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
437 if fullname != '__future__': |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
438 if not fullname or fullname in stdlib_modules: |
26955
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
439 yield msg('relative import of stdlib module') |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
440 else: |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
441 seenlocal = True |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
442 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
443 # Direct symbol import is only allowed from certain modules and |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
444 # must occur before non-symbol imports. |
26965
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
445 if node.module and node.col_offset == root_col_offset: |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
446 if fullname not in allowsymbolimports: |
26955
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
447 yield msg('direct symbol import from %s', fullname) |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
448 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
449 if seennonsymbolrelative: |
26955
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
450 yield msg('symbol import follows non-symbol import: %s', |
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
451 fullname) |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
452 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
453 if not node.module: |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
454 assert node.level |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
455 seennonsymbolrelative = True |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
456 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
457 # Only allow 1 group per level. |
26965
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
458 if (node.level in seenlevels |
1fa66d3ad28d
import-checker: reset context to verify convention in function scope
Yuya Nishihara <yuya@tcha.org>
parents:
26964
diff
changeset
|
459 and node.col_offset == root_col_offset): |
26955
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
460 yield msg('multiple "from %s import" statements', |
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
461 '.' * node.level) |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
462 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
463 # Higher-level groups come before lower-level groups. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
464 if any(node.level > l for l in seenlevels): |
26955
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
465 yield msg('higher-level import should come first: %s', |
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
466 fullname) |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
467 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
468 seenlevels.add(node.level) |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
469 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
470 # Entries in "from .X import ( ... )" lists must be lexically |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
471 # sorted. |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
472 lastentryname = None |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
473 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
474 for n in node.names: |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
475 if lastentryname and n.name < lastentryname: |
26955
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
476 yield msg('imports from %s not lexically sorted: %s < %s', |
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
477 fullname, n.name, lastentryname) |
25703
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
478 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
479 lastentryname = n.name |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
480 |
1a6a117d0b95
import-checker: establish modern import convention
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25702
diff
changeset
|
481 if n.name in requirealias and n.asname != requirealias[n.name]: |
26955
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
482 yield msg('%s from %s must be "as" aliased to %s', |
c4114e335b49
import-checker: extract function to generate a formatted warning
Yuya Nishihara <yuya@tcha.org>
parents:
26954
diff
changeset
|
483 n.name, fullname, requirealias[n.name]) |
25702
ab2c5163900e
import-checker: establish new function for verifying import conventions
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25701
diff
changeset
|
484 |
ab2c5163900e
import-checker: establish new function for verifying import conventions
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25701
diff
changeset
|
485 def verify_stdlib_on_own_line(root): |
20036 | 486 """Given some python source, verify that stdlib imports are done |
487 in separate statements from relative local module imports. | |
488 | |
489 Observing this limitation is important as it works around an | |
490 annoying lib2to3 bug in relative import rewrites: | |
491 http://bugs.python.org/issue19510. | |
492 | |
25702
ab2c5163900e
import-checker: establish new function for verifying import conventions
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25701
diff
changeset
|
493 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo'))) |
26956
4b56214ebb7a
import-checker: include lineno in warning message
Yuya Nishihara <yuya@tcha.org>
parents:
26955
diff
changeset
|
494 [('mixed imports\\n stdlib: sys\\n relative: foo', 1)] |
25702
ab2c5163900e
import-checker: establish new function for verifying import conventions
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25701
diff
changeset
|
495 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os'))) |
20036 | 496 [] |
25702
ab2c5163900e
import-checker: establish new function for verifying import conventions
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25701
diff
changeset
|
497 >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar'))) |
20036 | 498 [] |
499 """ | |
25702
ab2c5163900e
import-checker: establish new function for verifying import conventions
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25701
diff
changeset
|
500 for node in ast.walk(root): |
20036 | 501 if isinstance(node, ast.Import): |
20386
a05d31b040d7
import-checker: show stdlib and relative imports separately
Mads Kiilerich <madski@unity3d.com>
parents:
20383
diff
changeset
|
502 from_stdlib = {False: [], True: []} |
20036 | 503 for n in node.names: |
20386
a05d31b040d7
import-checker: show stdlib and relative imports separately
Mads Kiilerich <madski@unity3d.com>
parents:
20383
diff
changeset
|
504 from_stdlib[n.name in stdlib_modules].append(n.name) |
a05d31b040d7
import-checker: show stdlib and relative imports separately
Mads Kiilerich <madski@unity3d.com>
parents:
20383
diff
changeset
|
505 if from_stdlib[True] and from_stdlib[False]: |
a05d31b040d7
import-checker: show stdlib and relative imports separately
Mads Kiilerich <madski@unity3d.com>
parents:
20383
diff
changeset
|
506 yield ('mixed imports\n stdlib: %s\n relative: %s' % |
a05d31b040d7
import-checker: show stdlib and relative imports separately
Mads Kiilerich <madski@unity3d.com>
parents:
20383
diff
changeset
|
507 (', '.join(sorted(from_stdlib[True])), |
26956
4b56214ebb7a
import-checker: include lineno in warning message
Yuya Nishihara <yuya@tcha.org>
parents:
26955
diff
changeset
|
508 ', '.join(sorted(from_stdlib[False]))), node.lineno) |
20036 | 509 |
510 class CircularImport(Exception): | |
511 pass | |
512 | |
24490
fb4639d5268e
import-checker: make search algorithm non-recursive breadth-first
Matt Mackall <mpm@selenic.com>
parents:
24489
diff
changeset
|
513 def checkmod(mod, imports): |
fb4639d5268e
import-checker: make search algorithm non-recursive breadth-first
Matt Mackall <mpm@selenic.com>
parents:
24489
diff
changeset
|
514 shortest = {} |
fb4639d5268e
import-checker: make search algorithm non-recursive breadth-first
Matt Mackall <mpm@selenic.com>
parents:
24489
diff
changeset
|
515 visit = [[mod]] |
fb4639d5268e
import-checker: make search algorithm non-recursive breadth-first
Matt Mackall <mpm@selenic.com>
parents:
24489
diff
changeset
|
516 while visit: |
fb4639d5268e
import-checker: make search algorithm non-recursive breadth-first
Matt Mackall <mpm@selenic.com>
parents:
24489
diff
changeset
|
517 path = visit.pop(0) |
fb4639d5268e
import-checker: make search algorithm non-recursive breadth-first
Matt Mackall <mpm@selenic.com>
parents:
24489
diff
changeset
|
518 for i in sorted(imports.get(path[-1], [])): |
fb4639d5268e
import-checker: make search algorithm non-recursive breadth-first
Matt Mackall <mpm@selenic.com>
parents:
24489
diff
changeset
|
519 if len(path) < shortest.get(i, 1000): |
fb4639d5268e
import-checker: make search algorithm non-recursive breadth-first
Matt Mackall <mpm@selenic.com>
parents:
24489
diff
changeset
|
520 shortest[i] = len(path) |
fb4639d5268e
import-checker: make search algorithm non-recursive breadth-first
Matt Mackall <mpm@selenic.com>
parents:
24489
diff
changeset
|
521 if i in path: |
fb4639d5268e
import-checker: make search algorithm non-recursive breadth-first
Matt Mackall <mpm@selenic.com>
parents:
24489
diff
changeset
|
522 if i == path[0]: |
fb4639d5268e
import-checker: make search algorithm non-recursive breadth-first
Matt Mackall <mpm@selenic.com>
parents:
24489
diff
changeset
|
523 raise CircularImport(path) |
fb4639d5268e
import-checker: make search algorithm non-recursive breadth-first
Matt Mackall <mpm@selenic.com>
parents:
24489
diff
changeset
|
524 continue |
fb4639d5268e
import-checker: make search algorithm non-recursive breadth-first
Matt Mackall <mpm@selenic.com>
parents:
24489
diff
changeset
|
525 visit.append(path + [i]) |
20036 | 526 |
20038
c65a6937b828
import-checker: try a little harder to show fewer cycles
Augie Fackler <raf@durin42.com>
parents:
20037
diff
changeset
|
527 def rotatecycle(cycle): |
c65a6937b828
import-checker: try a little harder to show fewer cycles
Augie Fackler <raf@durin42.com>
parents:
20037
diff
changeset
|
528 """arrange a cycle so that the lexicographically first module listed first |
c65a6937b828
import-checker: try a little harder to show fewer cycles
Augie Fackler <raf@durin42.com>
parents:
20037
diff
changeset
|
529 |
24488
4b3fc46097f7
import-checker: drop duplicate element from cycle
Matt Mackall <mpm@selenic.com>
parents:
24487
diff
changeset
|
530 >>> rotatecycle(['foo', 'bar']) |
20038
c65a6937b828
import-checker: try a little harder to show fewer cycles
Augie Fackler <raf@durin42.com>
parents:
20037
diff
changeset
|
531 ['bar', 'foo', 'bar'] |
c65a6937b828
import-checker: try a little harder to show fewer cycles
Augie Fackler <raf@durin42.com>
parents:
20037
diff
changeset
|
532 """ |
c65a6937b828
import-checker: try a little harder to show fewer cycles
Augie Fackler <raf@durin42.com>
parents:
20037
diff
changeset
|
533 lowest = min(cycle) |
c65a6937b828
import-checker: try a little harder to show fewer cycles
Augie Fackler <raf@durin42.com>
parents:
20037
diff
changeset
|
534 idx = cycle.index(lowest) |
24488
4b3fc46097f7
import-checker: drop duplicate element from cycle
Matt Mackall <mpm@selenic.com>
parents:
24487
diff
changeset
|
535 return cycle[idx:] + cycle[:idx] + [lowest] |
20036 | 536 |
537 def find_cycles(imports): | |
538 """Find cycles in an already-loaded import graph. | |
539 | |
25175
10e6c4b7121b
import-checker: don't treat modules as relative one if not found
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25174
diff
changeset
|
540 All module names recorded in `imports` should be absolute one. |
10e6c4b7121b
import-checker: don't treat modules as relative one if not found
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25174
diff
changeset
|
541 |
10e6c4b7121b
import-checker: don't treat modules as relative one if not found
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25174
diff
changeset
|
542 >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'], |
10e6c4b7121b
import-checker: don't treat modules as relative one if not found
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25174
diff
changeset
|
543 ... 'top.bar': ['top.baz', 'sys'], |
10e6c4b7121b
import-checker: don't treat modules as relative one if not found
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25174
diff
changeset
|
544 ... 'top.baz': ['top.foo'], |
10e6c4b7121b
import-checker: don't treat modules as relative one if not found
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25174
diff
changeset
|
545 ... 'top.qux': ['top.foo']} |
20036 | 546 >>> print '\\n'.join(sorted(find_cycles(imports))) |
24487
642d245ff537
import-checker: fix rotatecycle
Matt Mackall <mpm@selenic.com>
parents:
22975
diff
changeset
|
547 top.bar -> top.baz -> top.foo -> top.bar |
642d245ff537
import-checker: fix rotatecycle
Matt Mackall <mpm@selenic.com>
parents:
22975
diff
changeset
|
548 top.foo -> top.qux -> top.foo |
20036 | 549 """ |
24491
784b278b349c
import-checker: rotatecycle is actually the canonical cycle key
Matt Mackall <mpm@selenic.com>
parents:
24490
diff
changeset
|
550 cycles = set() |
20036 | 551 for mod in sorted(imports.iterkeys()): |
552 try: | |
24490
fb4639d5268e
import-checker: make search algorithm non-recursive breadth-first
Matt Mackall <mpm@selenic.com>
parents:
24489
diff
changeset
|
553 checkmod(mod, imports) |
25660
328739ea70c3
global: mass rewrite to use modern exception syntax
Gregory Szorc <gregory.szorc@gmail.com>
parents:
25175
diff
changeset
|
554 except CircularImport as e: |
20036 | 555 cycle = e.args[0] |
24491
784b278b349c
import-checker: rotatecycle is actually the canonical cycle key
Matt Mackall <mpm@selenic.com>
parents:
24490
diff
changeset
|
556 cycles.add(" -> ".join(rotatecycle(cycle))) |
784b278b349c
import-checker: rotatecycle is actually the canonical cycle key
Matt Mackall <mpm@selenic.com>
parents:
24490
diff
changeset
|
557 return cycles |
20036 | 558 |
559 def _cycle_sortkey(c): | |
560 return len(c), c | |
561 | |
562 def main(argv): | |
25063
723e364488f4
import-checker: add xargs like mode
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
24669
diff
changeset
|
563 if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2): |
723e364488f4
import-checker: add xargs like mode
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
24669
diff
changeset
|
564 print 'Usage: %s {-|file [file] [file] ...}' |
20036 | 565 return 1 |
25063
723e364488f4
import-checker: add xargs like mode
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
24669
diff
changeset
|
566 if argv[1] == '-': |
723e364488f4
import-checker: add xargs like mode
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
24669
diff
changeset
|
567 argv = argv[:1] |
723e364488f4
import-checker: add xargs like mode
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
24669
diff
changeset
|
568 argv.extend(l.rstrip() for l in sys.stdin.readlines()) |
25064
3bbbadf69d0a
import-checker: loop to get list of locally defined modules at first
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25063
diff
changeset
|
569 localmods = {} |
20036 | 570 used_imports = {} |
571 any_errors = False | |
572 for source_path in argv[1:]: | |
25064
3bbbadf69d0a
import-checker: loop to get list of locally defined modules at first
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25063
diff
changeset
|
573 modname = dotted_name_of_path(source_path, trimpure=True) |
3bbbadf69d0a
import-checker: loop to get list of locally defined modules at first
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25063
diff
changeset
|
574 localmods[modname] = source_path |
3bbbadf69d0a
import-checker: loop to get list of locally defined modules at first
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25063
diff
changeset
|
575 for modname, source_path in sorted(localmods.iteritems()): |
20036 | 576 f = open(source_path) |
577 src = f.read() | |
20037
957b43371928
import-checker: ignore nested imports
Augie Fackler <raf@durin42.com>
parents:
20036
diff
changeset
|
578 used_imports[modname] = sorted( |
25174
86298718b01c
import-checker: make imported_modules yield absolute dotted_name_of_path
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25173
diff
changeset
|
579 imported_modules(src, modname, localmods, ignore_nested=True)) |
26956
4b56214ebb7a
import-checker: include lineno in warning message
Yuya Nishihara <yuya@tcha.org>
parents:
26955
diff
changeset
|
580 for error, lineno in verify_import_convention(modname, src): |
20036 | 581 any_errors = True |
26956
4b56214ebb7a
import-checker: include lineno in warning message
Yuya Nishihara <yuya@tcha.org>
parents:
26955
diff
changeset
|
582 print '%s:%d: %s' % (source_path, lineno, error) |
20036 | 583 f.close() |
584 cycles = find_cycles(used_imports) | |
585 if cycles: | |
586 firstmods = set() | |
587 for c in sorted(cycles, key=_cycle_sortkey): | |
588 first = c.split()[0] | |
589 # As a rough cut, ignore any cycle that starts with the | |
590 # same module as some other cycle. Otherwise we see lots | |
591 # of cycles that are effectively duplicates. | |
592 if first in firstmods: | |
593 continue | |
594 print 'Import cycle:', c | |
595 firstmods.add(first) | |
596 any_errors = True | |
25731
cd1daab5d036
import-checker.py: exit with code 0 if no error is detected
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
25703
diff
changeset
|
597 return any_errors != 0 |
20036 | 598 |
599 if __name__ == '__main__': | |
600 sys.exit(int(main(sys.argv))) |