def linter_header(linter_config, files_lines): """Linter for checking source file headers. Parameters ---------- linter_config : dict Dictionary that contains the configuration for the linter files_lines : dict Dictionary of filename to the set of line numbers (that have been modified). See `run_diff` function in `carboardlinter`. Returns ------- messages : list The list of messages generated by the external linter. """ config = DEFAULT_CONFIG.copy() config.update(linter_config) # Load the header file as a set of lines header_lines = list(config['extra']) with codecs.open(config['header'], encoding='utf-8') as f: for line in f: header_lines.append((config['comment'] + line).strip()) # Get all relevant filenames filenames = [ filename for filename in files_lines if matches_filefilter(filename, config['filefilter']) ] # Loop all files and check in the header each file. messages = [] for filename in filenames: with codecs.open(filename, encoding='utf-8') as f: iterator = iter(enumerate(f)) header_counter = 0 while header_counter < len(header_lines): try: lineno, line = next(iterator) except StopIteration: break if lineno == 0 and line.startswith( '#!') and config['shebang'] is not None: if line[:-1] != config['shebang']: messages.append( Message( filename, lineno, None, 'Shebang line should be {}.'.format( config['shebang']))) else: expected = header_lines[header_counter] if line[:-1] != expected: messages.append( Message(filename, lineno, None, 'Line should be: {}'.format(expected))) header_counter += 1 return messages
def run_namespace(config, filenames): """Linter for checking namespace Python namespace collisions. Parameters ---------- config : dict Dictionary that contains the configuration for the linter filenames : list A list of filenames to check Returns ------- messages : list The list of messages generated by the external linter. """ # Make sure we test the source tree and not some locally installed copy of HORTON. sys.path.insert(0, '.') # Find all module names and load namespaces namespace = {} messages = [] for filename in filenames: # remove extension and replace / by . modulename = os.path.splitext(filename)[0].replace('/', '.') # Check all public names of the module. module = importlib.import_module(modulename) names = dir(module) if '__all__' in names: for name in names: if name in module.__all__: namespace.setdefault(name, []).append(filename) if name in config['forbidden']: messages.append( Message( filename, None, None, 'Invalid name in namespace: {0}'.format(name))) else: messages.append(Message(filename, None, None, 'Missing __all__')) # Fix sys.path del sys.path[0] # Detect collisions for name, sourcefiles in namespace.items(): if len(sourcefiles) > 1: text = "Name '{0}' found in more than one module: {1}".format( name, ' '.join(sourcefiles)) messages.append(Message(sourcefiles[0], None, None, text)) return messages
def run_whitespace(_config, filenames): """Linter for checking whitespace conventions. Parameters ---------- config : dict Dictionary that contains the configuration for the linter filenames : list A list of filenames to check Returns ------- messages : list The list of messages generated by the external linter. """ # Loop over all files and check whitespace in each file. messages = [] for filename in filenames: with codecs.open(filename, encoding='utf-8') as f: line = None lineno = -1 for lineno, line in enumerate(f): # Check for tabs charno = line.find('\t') if charno >= 0: messages.append( Message(filename, lineno + 1, charno + 1, 'tab')) # Check for carriage return charno = line.find('\r') if charno >= 0: messages.append( Message(filename, lineno + 1, charno + 1, 'carriage return')) # Check for trailing whitespace if line[:-1] != line.rstrip(): messages.append( Message(filename, lineno + 1, None, 'trailing whitespace')) # Perform some checks on the last line if line is not None: if len(line.strip()) == 0: messages.append( Message(filename, lineno + 1, None, 'trailing empty line')) if not line.endswith("\n"): messages.append( Message(filename, lineno + 1, None, 'last line missing \\n')) return messages
def run_header(config, filenames): """Linter for checking source file headers. Parameters ---------- config : dict Dictionary that contains the configuration for the linter filenames : list A list of filenames to check Returns ------- messages : list The list of messages generated by the external linter. """ # Load the header file as a set of lines header_lines = list(config['extra']) with codecs.open(config['header'], encoding='utf-8') as f: for line in f: header_lines.append((config['comment'] + line).strip()) # Loop all files and check in the header each file. messages = [] for filename in filenames: with codecs.open(filename, encoding='utf-8') as f: iterator = iter(enumerate(f)) header_counter = 0 while header_counter < len(header_lines): try: lineno, line = next(iterator) except StopIteration: break if lineno == 0 and line.startswith( '#!') and config['shebang'] is not None: if line[:-1] != config['shebang']: messages.append( Message( filename, lineno + 1, None, 'Shebang line should be {}.'.format( config['shebang']))) else: expected = header_lines[header_counter] if line[:-1] != expected: messages.append( Message(filename, lineno + 1, None, 'Line should be: {}'.format(expected))) header_counter += 1 return messages
def run_import(config, filenames): """Linter for checking import statements. Parameters ---------- config : dict Dictionary that contains the configuration for the linter filenames : list A list of filenames to check Returns ------- messages : list The list of messages generated by the external linter. """ # Loop all python and cython files messages = [] if len(config['packages']) > 0: for filename in filenames: # Look for bad imports with codecs.open(filename, encoding='utf-8') as f: for lineno, line in enumerate(f): for package in config['packages']: # skip version import if line == u'from {0} import __version__\n'.format(package): continue if u'from {0} import'.format(package) in line: text = 'Wrong import from {0}'.format(package) messages.append(Message(filename, lineno+1, None, text)) return messages
def run_flake8(config, filenames): """Linter for checking flake8 results. Parameters ---------- config : dict Dictionary that contains the configuration for the linter filenames : list A list of filenames to check Returns ------- messages : list The list of messages generated by the external linter. """ # get flake8 version command = ['flake8', '--version'] version_info = run_command(command, verbose=False)[0] print('USING : {0}'.format(version_info)) messages = [] if len(filenames) > 0: command = ['flake8'] + filenames if config['config'] is not None: command += ['--config={0}'.format(config['config'])] output = run_command(command, has_failed=_has_failed)[0] if len(output) > 0: for line in output.splitlines(): words = line.split(':') messages.append( Message(words[0], int(words[1]), int(words[2]), words[3].strip())) return messages
def run_import(config, filenames): """Linter for checking import statements. Parameters ---------- config : dict Dictionary that contains the configuration for the linter filenames : list A list of filenames to check Returns ------- messages : list The list of messages generated by the external linter. """ # Loop all python and cython files messages = [] if len(config['packages']) > 0: for filename in filenames: try: _check_file(filename, config, messages) except UnicodeDecodeError as err: messages.append(Message(filename, None, None, str(err))) return messages
def run_header(config, filenames): """Linter for checking source file headers. Parameters ---------- config : dict Dictionary that contains the configuration for the linter filenames : list A list of filenames to check Returns ------- messages : list The list of messages generated by the external linter. """ # Load the header file as a set of lines header_lines = list(config['extra']) with codecs.open(config['header'], encoding='utf-8') as f: for line in f: header_lines.append((config['comment'] + line).strip()) # Loop all files and check in the header each file. messages = [] for filename in filenames: try: _check_file(filename, config, header_lines, messages) except UnicodeDecodeError as err: messages.append(Message(filename, None, None, str(err))) return messages
def linter_pylint(linter_config, files_lines): """Linter for checking pylint results. Parameters ---------- linter_config : dict Dictionary that contains the configuration for the linter files_lines : dict Dictionary of filename to the set of line numbers (that have been modified). See `run_diff` function in `carboardlinter`. Returns ------- messages : list The list of messages generated by the external linter. """ config = DEFAULT_CONFIG.copy() config.update(linter_config) # get Pylint version command = [ 'pylint', '--version', '--rcfile={0}'.format(config['pylintrc']) ] version_info = ''.join( run_command(command, verbose=False)[0].split('\n')[:2]) print('USING : {0}'.format(version_info)) # Get all relevant filenames filenames = [ filename for filename in files_lines if matches_filefilter(filename, config['filefilter']) ] def has_failed(returncode, _stdout, _stderr): """Determine if pylint ran correctly.""" return not 0 <= returncode < 32 messages = [] if len(filenames) > 0: command = ['pylint'] + filenames command += [ '--rcfile={0}'.format(config['pylintrc']), '--jobs=2', '--output-format=json' ] output = run_command(command, has_failed=has_failed)[0] if len(output) > 0: for plmap in json.loads(output): charno = plmap['column'] if charno == 0: charno = None messages.append( Message( plmap['path'], plmap['line'], charno, '{0} {1}'.format(plmap['symbol'], plmap['message']))) return messages
def linter_cppcheck(linter_config, files_lines): """Linter for cppcheck. Parameters ---------- linter_config : dict Dictionary that contains the configuration for the linter Not supported files_lines : dict Dictionary of filename to the set of line numbers (that have been modified) See `run_diff` function in `carboardlinter`. Returns ------- messages : list The list of messages generated by the external linter. """ config = DEFAULT_CONFIG.copy() config.update(linter_config) # Get version print('USING VERSION : {0}'.format( run_command(['cppcheck', '--version'], verbose=False)[0].strip())) # Get the relevant filenames filenames = [ filename for filename in files_lines if matches_filefilter(filename, config['filefilter']) ] messages = [] if len(filenames) > 0: # Call Cppcheck command = (['cppcheck'] + filenames + [ '-q', '--enable=all', '--language=c++', '--std=c++11', '--xml', '--suppress=missingIncludeSystem', '--suppress=unusedFunction' ]) xml_str = run_command(command)[1] etree = ElementTree.fromstring(xml_str) # Parse the output of Cppcheck into standard return values for error in etree: if 'file' not in error.attrib: continue # key = '{:<15} {:<40} {:<30}' % (error.attrib['severity'], # error.attrib['file'], # error.attrib['id']) text = '{} {} {}'.format(error.attrib['severity'], error.attrib['id'], error.attrib['msg']) messages.append( Message(error.attrib['file'], int(error.attrib['line']), None, text)) return messages
def linter_pydocstyle(linter_config, files_lines): """Linter for checking pydocstyle results. Parameters ---------- linter_config : dict Dictionary that contains the configuration for the linter files_lines : dict Dictionary of filename to the set of line numbers (that have been modified). See `run_diff` function in `carboardlinter`. Returns ------- messages : list The list of messages generated by the external linter. """ config = DEFAULT_CONFIG.copy() config.update(linter_config) # get pydocstyle version command = ['pydocstyle', '--version'] version_info = run_command(command, verbose=False)[0] print('USING : {0}'.format(version_info)) # Get all relevant filenames filenames = [ filename for filename in files_lines if matches_filefilter(filename, config['filefilter']) ] def has_failed(returncode, _stdout, _stderr): """Determine if pydocstyle ran correctly.""" return not 0 <= returncode < 2 messages = [] if len(filenames) > 0: command = ['pydocstyle'] + filenames command += ['--config={0}'.format(config['config'])] output = run_command(command, has_failed=has_failed)[0] lines = output.split('\n')[:-1] while len(lines) > 0: if 'WARNING: ' in lines[0]: lines.pop(0) else: words = lines.pop(0).split() filename, lineno = words[0].split(':') code, description = lines.pop(0).split(':', 1) code = code.strip() description = description.strip() messages.append( Message(filename, int(lineno), None, '%s %s' % (code, description))) return messages
def linter_doxygen(linter_config, files_lines): """Linter using doxygen to find undocumented cpp. Parameters ---------- linter_config : dict Dictionary that contains the configuration for the linter Not supported files_lines : dict Dictionary of filename to the set of line numbers (that have been modified) See `run_diff` function in `carboardlinter`. Returns ------- messages : list The list of messages generated by the external linter. """ config = DEFAULT_CONFIG.copy() config.update(linter_config) command = ['doxygen', '--version'] print('USING : doxygen', run_command(command, verbose=False)[0].strip()) # Get the relevant filenames filenames = [ filename for filename in files_lines if matches_filefilter(filename, config['filefilter']) ] messages = [] if len(filenames) > 0: # Call doxygen in the doc subdirectory, mute output because it only confuses command = ['doxygen', '-'] stdin = DOXYGEN_CONFIG.format(' '.join(filenames)) print('STDIN :') print(stdin) output = run_command(command, cwd=config['root'], stdin=stdin)[1] # Parse the file doxygen_warnings prefix = os.getcwd() + '/' lines = output.split('\n') for line in lines: if line.startswith('~~WARN~~'): location, description = line[9:].split(None, 1) filename, lineno = location.split(':')[:2] if filename.startswith(prefix): filename = filename[len(prefix):] message = Message(filename, int(lineno), None, description) messages.append(message) return messages
def _check_file(filename: str, config: dict, header_lines: List[str], messages: List[str]): """Look for bad filename headers. Parameters ---------- filename File to be checked config Dictionary with configuration of the linters. header_lines The expected header. messages A list of messages to append to. (output arg) """ with codecs.open(filename, encoding='utf-8') as f: iterator = iter(enumerate(f)) header_counter = 0 while header_counter < len(header_lines): try: lineno, line = next(iterator) except StopIteration: break if lineno == 0 and line.startswith( '#!') and config['shebang'] is not None: if line[:-1] != config['shebang']: messages.append( Message( filename, lineno + 1, None, 'Shebang line should be {}.'.format( config['shebang']))) else: expected = header_lines[header_counter] if line[:-1] != expected: messages.append( Message(filename, lineno + 1, None, 'Line should be: {}'.format(expected))) header_counter += 1
def _check_file(filename: str, messages: List[str]): """Look for white-space issues. Parameters ---------- filename File to be checked messages A list of messages to append to. (output arg) """ with codecs.open(filename, encoding='utf-8') as f: line = None lineno = -1 for lineno, line in enumerate(f): # Check for tabs charno = line.find('\t') if charno >= 0: messages.append( Message(filename, lineno + 1, charno + 1, 'tab')) # Check for carriage return charno = line.find('\r') if charno >= 0: messages.append( Message(filename, lineno + 1, charno + 1, 'carriage return')) # Check for trailing whitespace if line[:-1] != line.rstrip(): messages.append( Message(filename, lineno + 1, None, 'trailing whitespace')) # Perform some checks on the last line if line is not None: if len(line.strip()) == 0: messages.append( Message(filename, lineno + 1, None, 'trailing empty line')) if not line.endswith("\n"): messages.append( Message(filename, lineno + 1, None, 'last line missing \\n'))
def linter_pycodestyle(linter_config, files_lines): """Linter for checking pycodestyle results. Parameters ---------- linter_config : dict Dictionary that contains the configuration for the linter files_lines : dict Dictionary of filename to the set of line numbers (that have been modified). See `run_diff` function in `carboardlinter`. Returns ------- messages : list The list of messages generated by the external linter. """ config = DEFAULT_CONFIG.copy() config.update(linter_config) # get pycodestyle version command = ['pycodestyle', '--version'] version_info = run_command(command, verbose=False)[0] print('USING : {0}'.format(version_info)) # Get all relevant filenames filenames = [ filename for filename in files_lines if matches_filefilter(filename, config['filefilter']) ] def has_failed(returncode, _stdout, _stderr): """Determine if pycodestyle ran correctly.""" return not 0 <= returncode < 2 messages = [] if len(filenames) > 0: command = ['pycodestyle'] + filenames command += ['--config={0}'.format(config['config'])] output = run_command(command, has_failed=has_failed)[0] if len(output) > 0: for line in output.splitlines(): words = line.split(':') messages.append( Message(words[0], int(words[1]), int(words[2]), words[3].strip())) return messages
def linter_cpplint(linter_config, files_lines): """Linter for cpplint. Parameters ---------- linter_config : dict Dictionary that contains the configuration for the linter Not supported files_lines : dict Dictionary of filename to the set of line numbers (that have been modified) See `run_diff` function in `carboardlinter`. Returns ------- messages : list The list of messages generated by the external linter. """ config = DEFAULT_CONFIG.copy() config.update(linter_config) # Get the relevant filenames filenames = [filename for filename in files_lines if matches_filefilter(filename, config['filefilter'])] messages = [] if len(filenames) > 0: # Call cpplint command = ([config['script'], '--linelength=100', '--filter=-runtime/int'] + filenames) output = run_command(command, has_failed=has_failed)[1] # Parse the output of cpplint into standard return values for line in output.split('\n')[:-1]: words = line.split() if len(words) == 0 or words[0].count(':') != 2: continue filename, lineno = words[0].split(':')[:2] description = ' '.join(words[1:-2]) tag = words[-2] priority = words[-1] messages.append(Message(filename, int(lineno), None, '%s %s %s' % ( priority, tag, description))) return messages
def run_cppcheck(_config, filenames): """Linter for cppcheck. Parameters ---------- config : dict Dictionary that contains the configuration for the linter Not supported filenames : list A list of filenames to check Returns ------- messages : list The list of messages generated by the external linter. """ # Get version print('USING VERSION : {0}'.format( run_command(['cppcheck', '--version'], verbose=False)[0].strip())) messages = [] if len(filenames) > 0: # Call Cppcheck command = (['cppcheck'] + filenames + ['-q', '--enable=all', '--language=c++', '--std=c++11', '--xml', '--suppress=missingIncludeSystem', '--suppress=unusedFunction']) xml_str = run_command(command)[1] etree = ElementTree.fromstring(xml_str) # Parse the output of Cppcheck into standard return values for error in etree: if 'file' not in error.attrib: continue # key = '{:<15} {:<40} {:<30}' % (error.attrib['severity'], # error.attrib['file'], # error.attrib['id']) text = '{} {} {}'.format( error.attrib['severity'], error.attrib['id'], error.attrib['msg']) lineno = int(error.attrib['line']) if lineno == 0: lineno = None messages.append(Message(error.attrib['file'], lineno, None, text)) return messages
def run_pydocstyle(config, filenames): """Linter for checking pydocstyle results. Parameters ---------- config : dict Dictionary that contains the configuration for the linter filenames : list A list of filenames to check Returns ------- messages : list The list of messages generated by the external linter. """ # get pydocstyle version command = ['pydocstyle', '--version'] version_info = run_command(command, verbose=False)[0] print('USING : {0}'.format(version_info)) def has_failed(returncode, _stdout, _stderr): """Determine if pydocstyle ran correctly.""" return not 0 <= returncode < 2 messages = [] if len(filenames) > 0: command = ['pydocstyle'] + filenames if config['config'] is not None: command += ['--config={0}'.format(config['config'])] output = run_command(command, has_failed=has_failed)[0] lines = output.split('\n')[:-1] while len(lines) > 0: if 'WARNING: ' in lines[0]: lines.pop(0) else: words = lines.pop(0).split() filename, lineno = words[0].split(':') code, description = lines.pop(0).split(':', 1) code = code.strip() description = description.strip() messages.append(Message( filename, int(lineno), None, '%s %s' % (code, description))) return messages
def run_pylint(config, filenames): """Linter for checking pylint results. Parameters ---------- config : dict Dictionary that contains the configuration for the linter filenames : list A list of filenames to check Returns ------- messages : list The list of messages generated by the external linter. """ # get Pylint version command = ['pylint', '--version'] version_info = ''.join( run_command(command, verbose=False)[0].split('\n')[:2]) print('USING : {0}'.format(version_info)) def has_failed(returncode, _stdout, _stderr): """Determine if pylint ran correctly.""" return not 0 <= returncode < 32 messages = [] if len(filenames) > 0: command = ['pylint'] + filenames command += ['--jobs=2', '--output-format=json'] if config['config'] is not None: command += ['--rcfile={0}'.format(config['config'])] output = run_command(command, has_failed=has_failed)[0] if len(output) > 0: for plmap in json.loads(output): charno = plmap['column'] if charno in [0, -1]: charno = None messages.append( Message( plmap['path'], plmap['line'], charno, '{0} {1}'.format(plmap['symbol'], plmap['message']))) return messages
def run_doxygen(_config, filenames): """Linter using doxygen to find undocumented cpp. Parameters ---------- config : dict Dictionary that contains the configuration for the linter Not supported filenames : list A list of filenames to check Returns ------- messages : list The list of messages generated by the external linter. """ command = ['doxygen', '--version'] print('USING : doxygen', run_command(command, verbose=False)[0].strip()) messages = [] if len(filenames) > 0: # Call doxygen in the doc subdirectory, mute output because it only confuses command = ['doxygen', '-'] stdin = DOXYGEN_CONFIG.format(' '.join(filenames)) print('STDIN :') print(stdin) output = run_command(command, stdin=stdin)[1] # Parse the file doxygen_warnings prefix = os.getcwd() + '/' lines = output.split('\n') for line in lines: if line.startswith('~~WARN~~'): location, description = line[9:].split(None, 1) filename, lineno = location.split(':')[:2] if filename.startswith(prefix): filename = filename[len(prefix):] message = Message(filename, int(lineno), None, description) messages.append(message) return messages
def linter_import(linter_config, files_lines): """Linter for checking import statements. Parameters ---------- linter_config : dict Dictionary that contains the configuration for the linter files_lines : dict Dictionary of filename to the set of line numbers (that have been modified). See `run_diff` function in `carboardlinter`. Returns ------- messages : list The list of messages generated by the external linter. """ config = DEFAULT_CONFIG.copy() config.update(linter_config) # Get all relevant filenames filenames = [filename for filename in files_lines if matches_filefilter(filename, config['filefilter'])] # Loop all python and cython files messages = [] if len(config['packages']) > 0: for filename in filenames: # Look for bad imports with codecs.open(filename, encoding='utf-8') as f: for lineno, line in enumerate(f): for package in config['packages']: # skip version import if line == u'from {0} import __version__\n'.format(package): continue if u'from {0} import'.format(package) in line: text = 'Wrong import from {0}'.format(package) messages.append(Message(filename, lineno+1, None, text)) return messages
def run_cpplint(config, filenames): """Linter for cpplint. Parameters ---------- config : dict Dictionary that contains the configuration for the linter Not supported filenames : list A list of filenames to check Returns ------- messages : list The list of messages generated by the external linter. """ messages = [] if len(filenames) > 0: # Call cpplint command = ([config['script'], '--linelength=100', '--filter=-runtime/int'] + filenames) output = run_command(command, has_failed=_has_failed)[1] # Parse the output of cpplint into standard return values for line in output.split('\n')[:-1]: words = line.split() if len(words) == 0 or words[0].count(':') != 2: continue filename, lineno = words[0].split(':')[:2] description = ' '.join(words[1:-2]) tag = words[-2] priority = words[-1] lineno = int(lineno) if lineno == 0: lineno = None messages.append(Message(filename, lineno, None, '%s %s %s' % ( priority, tag, description))) return messages
def _check_file(filename: str, config: dict, messages: List[str]): """Look for bad imports in the given file. Parameters ---------- filename File to be checked config Dictionary with configuration of the linters. messages A list of messages to append to. (output arg) """ with codecs.open(filename, encoding='utf-8') as f: for lineno, line in enumerate(f): for package in config['packages']: # skip version import if line == u'from {0} import __version__\n'.format(package): continue if u'from {0} import'.format(package) in line: text = 'Wrong import from {0}'.format(package) messages.append(Message(filename, lineno + 1, None, text))
def run_yamllint(config, filenames): """Linter for checking yamllint results. Parameters ---------- config : dict Dictionary that contains the configuration for the linter filenames : list A list of filenames to check Returns ------- messages : list The list of messages generated by the external linter. """ # get yamllint version command = ['yamllint', '--version'] version_info = run_command(command, verbose=False)[0] print('USING : {0}'.format(version_info)) def has_failed(returncode, _stdout, _stderr): """Determine if yamllint ran correctly.""" return not 0 <= returncode < 2 messages = [] if len(filenames) > 0: command = ['yamllint', '-f', 'parsable'] + filenames if config['config'] is not None: command += ['-c', config['config']] output = run_command(command, has_failed=has_failed)[0] if len(output) > 0: for line in output.splitlines(): words = line.split(':') messages.append( Message(words[0], int(words[1]), int(words[2]), words[3].strip())) return messages
def run_whitespace(_config, filenames): """Linter for checking whitespace conventions. Parameters ---------- config : dict Dictionary that contains the configuration for the linter filenames : list A list of filenames to check Returns ------- messages : list The list of messages generated by the external linter. """ # Loop over all files and check whitespace in each file. messages = [] for filename in filenames: try: _check_file(filename, messages) except UnicodeDecodeError as err: messages.append(Message(filename, None, None, str(err))) return messages
def test_message(): # formatting msg1 = Message('test.txt', 1, 4, 'error') assert msg1.format( ) == '\x1b[1m\x1b[31m1:4 \x1b[0m \x1b[35mtest.txt\x1b[0m error' assert msg1.format(color=False) == '1:4 test.txt error' assert msg1.format(color=False) == str(msg1) msg2 = Message('test.txt', None, 4, 'error') assert msg2.format( ) == '\x1b[1m\x1b[31m-:4 \x1b[0m \x1b[35mtest.txt\x1b[0m error' assert msg2.format(color=False) == '-:4 test.txt error' msg3 = Message('test.txt', 1, None, 'error') assert msg3.format( ) == '\x1b[1m\x1b[31m1:- \x1b[0m \x1b[35mtest.txt\x1b[0m error' assert msg3.format(color=False) == '1:- test.txt error' msg4 = Message(None, 1, 3, 'error') assert msg4.format() == '\x1b[1m(nofile)\x1b[0m error' assert msg4.format(color=False) == '(nofile) error' # comparison assert msg1 > msg3 assert msg3 > msg2 assert msg2 > msg4 with assert_raises(TypeError): print(msg1 < 1) # sorting msgs = sorted([msg1, msg2, msg3, msg4]) assert msgs == [msg4, msg2, msg3, msg1] with assert_raises(TypeError): msgs.insert(0, 1) msgs.sort() # wrong arguments with assert_raises(TypeError): Message('foo', -1, 4, 'bar') with assert_raises(TypeError): Message('foo', 4, -1, 'bar') # indiff assert msg1.indiff({'test.txt': set([1])}) assert msg1.indiff({'test.txt': None}) assert not msg1.indiff({'test.txt': set([2])}) assert msg2.indiff({'test.txt': set([1])}) assert msg2.indiff({'test.txt': None}) assert msg2.indiff({'test.txt': set([2])}) assert not msg2.indiff({'foo.txt': set([2])})
def linter_namespace(linter_config, files_lines): """Linter for checking namespace Python namespace collisions. Parameters ---------- linter_config : dict Dictionary that contains the configuration for the linter files_lines : dict Dictionary of filename to the set of line numbers (that have been modified). See `run_diff` function in `carboardlinter`. Returns ------- messages : list The list of messages generated by the external linter. """ config = DEFAULT_CONFIG.copy() config.update(linter_config) # Get all relevant filenames filenames = [ filename for filename in files_lines if matches_filefilter(filename, config['filefilter']) ] # Make sure we test the source tree and not some locally installed copy of HORTON. sys.path.insert(0, '.') # Find all module names and load namespaces namespace = {} messages = [] for filename in filenames: # remove extension and replace / by . modulename = os.path.splitext(filename)[0].replace('/', '.') # Check all public names of the module. module = importlib.import_module(modulename) names = dir(module) if '__all__' in names: for name in names: if name in module.__all__: namespace.setdefault(name, []).append(filename) if name in config['forbidden']: messages.append( Message( filename, None, None, 'Invalid name in namespace: {0}'.format(name))) else: messages.append(Message(filename, None, None, 'Missing __all__')) # Fix sys.path del sys.path[0] # Detect collisions for name, filenames in namespace.items(): if len(filenames) > 1: text = "Name '{0}' found in more than one module: {1}".format( name, ' '.join(filenames)) messages.append(Message(filenames[0], None, None, text)) return messages