This repository has been archived by the owner on Sep 15, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implementing extracting symbols from load()s and adding them to stubs.
This change adds LoadExtractor, which walks the AST of the .bzl file and extracts information from the load() statements about symbols that are loaded from other .bzl files. These symbols are then added to the set of global stubs in RuleDocExtractor so that, as a first step, if any of these symbols loaded from other .bzl files are referenced in the global scope, Skydoc would not raise a NameError.
- Loading branch information
1 parent
b14ff10
commit 48d5e34
Showing
7 changed files
with
313 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
# Copyright 2016 The Bazel Authors. All rights reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
"""Extracts information about symbols loaded from other .bzl files.""" | ||
|
||
import ast | ||
from collections import namedtuple | ||
|
||
LoadSymbol = namedtuple('LoadSymbol', ['label', 'symbol', 'alias']) | ||
"""Information about a symbol loaded from another .bzl file.""" | ||
|
||
|
||
class LoadExtractorError(Exception): | ||
"""Error raised by LoadExtractor""" | ||
pass | ||
|
||
|
||
class LoadExtractor(object): | ||
"""Extracts information on symbols load()ed from other .bzl files.""" | ||
|
||
def _extract_loads(self, bzl_file): | ||
"""Walks the AST and extracts information on loaded symbols.""" | ||
load_symbols = [] | ||
try: | ||
tree = ast.parse(open(bzl_file).read(), bzl_file) | ||
key = None | ||
for node in ast.iter_child_nodes(tree): | ||
if not isinstance(node, ast.Expr): | ||
continue | ||
call = node.value | ||
if (not isinstance(call, ast.Call) or | ||
not isinstance(call.func, ast.Name) or | ||
call.func.id != 'load'): | ||
continue | ||
|
||
args = [] | ||
for arg in call.args: | ||
if not isinstance(arg, ast.Str): | ||
raise LoadExtractorError( | ||
'Only string literals in load statments are supported.') | ||
args.append(arg.s) | ||
kwargs = {} | ||
for keyword in call.keywords: | ||
if not isinstance(keyword.value, ast.Str): | ||
raise LoadExtractorError( | ||
'Only string literals in load statments are supported.') | ||
kwargs[keyword.arg] = keyword.value.s | ||
|
||
label = args[0] | ||
for arg in args[1:]: | ||
load_symbol = LoadSymbol(label, arg, None) | ||
load_symbols.append(load_symbol) | ||
for alias, symbol in kwargs.iteritems(): | ||
load_symbol = LoadSymbol(label, symbol, alias) | ||
load_symbols.append(load_symbol) | ||
|
||
except IOError: | ||
print("Failed to parse {0}: {1}".format(bzl_file, e.strerror)) | ||
pass | ||
|
||
return load_symbols | ||
|
||
def _validate_loads(self, load_symbols): | ||
"""Checks that there are no collisions from the extracted symbols.""" | ||
symbols = set() | ||
for load in load_symbols: | ||
if load.alias: | ||
if load.alias in symbols: | ||
raise LoadExtractorError( | ||
"Load symbol conflict: %s (aliased from %s) loaded from %s" % | ||
(load.alias, load.symbol, load.label)) | ||
else: | ||
symbols.add(load.alias) | ||
elif load.symbol in symbols: | ||
raise LoadExtractorError( | ||
"Load symbol conflict: %s loaded from %s" % | ||
(load.alias, load.label)) | ||
else: | ||
symbols.add(load.symbol) | ||
|
||
def extract(self, bzl_file): | ||
"""Extracts symbols loaded from other .bzl files. | ||
Walks the AST of the .bzl files and extracts information about symbols | ||
loaded from other .bzl files from load() calls. Then, validate the | ||
extracted symbols to check that all symbols are unique. | ||
Note that only load() calls where all arguments are string literals | ||
(ast.Str) are supported. | ||
Args: | ||
bzl_file: The .bzl file to extract load symbols from. | ||
Returns: | ||
List of LoadSymbol objects. | ||
""" | ||
load_symbols = self._extract_loads(bzl_file) | ||
self._validate_loads(load_symbols) | ||
return load_symbols |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
# Copyright 2016 The Bazel Authors. All rights reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
import os | ||
import tempfile | ||
import textwrap | ||
import unittest | ||
|
||
from skydoc import load_extractor | ||
|
||
class LoadExtractorTest(unittest.TestCase): | ||
|
||
def check_symbols(self, src, expected): | ||
with tempfile.NamedTemporaryFile() as tf: | ||
tf.write(src) | ||
tf.flush() | ||
|
||
extractor = load_extractor.LoadExtractor() | ||
load_symbols = extractor.extract(tf.name) | ||
|
||
self.assertEqual(expected, load_symbols) | ||
|
||
def test_load(self): | ||
src = textwrap.dedent("""\ | ||
load("//foo/bar:bar.bzl", "foo_library") | ||
load("//foo/bar:baz.bzl", "foo_test", orig_foo_binary = "foo_binary") | ||
""") | ||
expected = [ | ||
load_extractor.LoadSymbol('//foo/bar:bar.bzl', 'foo_library', None), | ||
load_extractor.LoadSymbol('//foo/bar:baz.bzl', 'foo_test', None), | ||
load_extractor.LoadSymbol('//foo/bar:baz.bzl', 'foo_binary', | ||
'orig_foo_binary'), | ||
] | ||
self.check_symbols(src, expected) | ||
|
||
def raises_error(self, src): | ||
with tempfile.NamedTemporaryFile() as tf: | ||
tf.write(src) | ||
tf.flush() | ||
|
||
extractor = load_extractor.LoadExtractor() | ||
self.assertRaises(load_extractor.LoadExtractorError, | ||
extractor.extract, tf.name) | ||
|
||
def test_invalid_non_string_literal_in_label(self): | ||
src = textwrap.dedent("""\ | ||
load(load_label, "foo_library") | ||
""") | ||
self.raises_error(src) | ||
|
||
def test_invalid_non_string_literal_in_keywords(self): | ||
src = textwrap.dedent("""\ | ||
load("//foo/bar:bar.bzl", loaded_symbol) | ||
""") | ||
self.raises_error(src) | ||
|
||
def test_invalid_symbol_conflict(self): | ||
src = textwrap.dedent("""\ | ||
load("//foo:bar.bzl", "foo_binary", "foo_library") | ||
load("//foo:baz.bzl", "foo_library") | ||
""") | ||
self.raises_error(src) | ||
|
||
def test_invalid_symbol_alias_conflict(self): | ||
src = textwrap.dedent("""\ | ||
load("//foo:bar.bzl", foo_library="some_foo_library") | ||
load("//foo:baz.bzl", "foo_library") | ||
""") | ||
self.raises_error(src) | ||
|
||
def test_invalid_duplicate_symbol_loaded(self): | ||
src = textwrap.dedent("""\ | ||
load("//foo:bar.bzl", "foo_library", "foo_library") | ||
""") | ||
self.raises_error(src) | ||
|
||
|
||
if __name__ == '__main__': | ||
unittest.main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.