├── cpplint ├── cpplint_test_header.h └── README ├── .travis.yml ├── changelog.rst ├── .gitignore ├── setup.py ├── README ├── README.md ├── nitpick.py └── nitpick_unittest.py /cpplint/cpplint_test_header.h: -------------------------------------------------------------------------------- 1 | // A test header for cpplint_unittest.py. 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.4" 4 | - "3.3" 5 | - "3.2" 6 | - "2.7" 7 | - "2.6" 8 | # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors 9 | # install: "sudo apt-get install" 10 | # command to run tests, e.g. python setup.py test 11 | script: python cpplint_unittest.py 12 | -------------------------------------------------------------------------------- /changelog.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | --------- 3 | 4 | 0.0.5 5 | ----- 6 | 7 | Maintenance release, undoes earlier project folder structure changes 8 | to remain as true to upstream as possible. 9 | 10 | 0.0.4 11 | ----- 12 | 13 | - Merged with upstream revision r141 (2014-12-04) 14 | This includes many new checks, see commit messages for details. 15 | This also reverts some renaming of files, to stay close to the original project. 16 | 17 | 18 | 0.0.3 19 | ----- 20 | 21 | - py3k compatibility 22 | 23 | 0.0.2 24 | ----- 25 | 26 | - fixed and extended allowed extensions 27 | 28 | 0.0.1 29 | ----- 30 | 31 | - import from googlecode, added setup.py 32 | - imported revision r83 (2012-05-11) 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | 56 | .svn 57 | *.pyc 58 | cpplint.egg-info 59 | build 60 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | setup(name='cpplint', 5 | version='0.0.5', 6 | py_modules=['cpplint', 'nitpick'], 7 | # generate platform specific start script 8 | entry_points={ 9 | 'console_scripts': [ 10 | 'cpplint = cpplint:main', 11 | 'nitpick = nitpick:main', 12 | ] 13 | }, 14 | install_requires=[], 15 | url="http://en.wikipedia.org/wiki/Cpplint", 16 | download_url="https://github.com/TheOstrichIO/cpplint", 17 | keywords=["lint", "python", "c++"], 18 | maintainer = 'Thibault Kruse, Itamar Ostricher', 19 | maintainer_email = 'see_github@nospam.com', 20 | classifiers=["Programming Language :: Python", 21 | "Programming Language :: Python :: 2", 22 | "Programming Language :: Python :: 3", 23 | "Programming Language :: C++", 24 | "License :: Freely Distributable"], 25 | description="This is automated checker to make sure a C++ file follows Google's C++ style guide", 26 | long_description="""This is automated checker to make sure a C++ file follows Google's C++ style 27 | guide (http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml). As it 28 | heavily relies on regular expressions, cpplint.py won't catch all violations of 29 | the style guide and will very occasionally report a false positive. There is a 30 | list of things we currently don't handle very well at the top of cpplint.py, 31 | and we welcome patches to improve it. 32 | Original SVN download URL: http://google-styleguide.googlecode.com/svn/trunk/cpplint/""") 33 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This is automated checker to make sure a C++ file follows Google's C++ style 2 | guide (http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml). As it 3 | heavily relies on regular expressions, cpplint.py won't catch all violations of 4 | the style guide and will very occasionally report a false positive. There is a 5 | list of things we currently don't handle very well at the top of cpplint.py, 6 | and we welcome patches to improve it. 7 | 8 | The linting tool takes a list of files as input. For full usage instructions, 9 | please see the output of: 10 | 11 | ./cpplint.py --help 12 | 13 | Unit tests are provided in cpplint_unittest.py. This file can safely be ignored 14 | by end users who have downloaded this package and only want to run the lint 15 | tool. 16 | 17 | --- 18 | 19 | cpplint.py and its corresponding unit tests are Copyright (C) 2009 Google Inc. 20 | 21 | Redistribution and use in source and binary forms, with or without 22 | modification, are permitted provided that the following conditions are 23 | met: 24 | 25 | * Redistributions of source code must retain the above copyright 26 | notice, this list of conditions and the following disclaimer. 27 | * Redistributions in binary form must reproduce the above 28 | copyright notice, this list of conditions and the following disclaimer 29 | in the documentation and/or other materials provided with the 30 | distribution. 31 | * Neither the name of Google Inc. nor the names of its 32 | contributors may be used to endorse or promote products derived from 33 | this software without specific prior written permission. 34 | 35 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cpplint - static code checker for C++ 2 | 3 | [![Build Status](https://travis-ci.org/tkruse/cpplint.svg)](https://travis-ci.org/tkruse/cpplint) 4 | 5 | This project provides cpplint as a pypi package 6 | (https://pypi.python.org/pypi/cpplint). It follows the code maintained as SVN 7 | repository by google employees at 8 | http://google-styleguide.googlecode.com/svn/trunk/cpplint. 9 | 10 | 11 | It is possible that this repo lags behind the SVN repo, if you notice this, 12 | feel free to open an issue asking for an update. 13 | The ```svn``` branch should be a 1-to-1 copy of the history in SVN. 14 | The ```pypi`` branch contains all local patches on top of svn. 15 | This branch will be rebased frequently. 16 | 17 | To install from pypi: 18 | 19 | ``` 20 | $ pip install cpplint 21 | ``` 22 | 23 | The run by calling 24 | ``` 25 | $ cpplint [OPTIONS] files 26 | ``` 27 | 28 | For more info, see [Original README](README) 29 | 30 | ## Customizations 31 | 32 | The modifications in this branch are minor fixes and cosmetic changes: 33 | 34 | - more default extensions 35 | - python 3k compatibility 36 | - minor fixes around default file extensions 37 | - continuous integration on travis 38 | 39 | ## Maintaining 40 | 41 | The strategy here is to have 3 branches: master, svn and pypi. 42 | In the svn branch, only commits from the original svn repo are added. 43 | The Pypi contains patches to upstream, that are intended to be rebased often. 44 | The master branch will be update to the latest pypi state (Either by tedious 45 | manual merging or by hard resets). 46 | 47 | Prerequisites: Install git-svn. 48 | To fetch the latest changes from SVN upstream after cloning: 49 | 50 | ``` 51 | # once after cloning 52 | git svn init http://google-styleguide.googlecode.com/svn/trunk/cpplint/ 53 | # this creates a remote git-svn and get all new commits 54 | git svn fetch 55 | # update svn branch 56 | git checkout master 57 | git branch -D svn 58 | git checkout git-svn -b svn 59 | # this build will fail in travis, ignore that 60 | git push origin -f svn 61 | # rebase local patches 62 | git checkout pypi 63 | git rebase svn 64 | # run tests, fix if fail 65 | python cpplint_unittest.py 66 | # when all good, push pypi to let travis have a go with all python versions 67 | git push origin -f pypi 68 | # check travis is happy (github badge on pypi branch) 69 | git checkout master 70 | # evil hard push, maintainer must be careful, or do a tedious merge instead 71 | git reset --hard pypi 72 | git push origin -f master 73 | ``` 74 | 75 | Then: 76 | - Wait and see if travis likes the changes 77 | - if necessary fix errors, like python3 compatibility (see earlier commit on common fixes) 78 | - Version Bump in setup.py, update changelog 79 | - Create new release in pypi: 80 | - ensure ~/.pypirc is valid 81 | - run: 82 | ``` 83 | python setup.py sdist register -r pypi 84 | python setup.py sdist bdist upload -r pypi 85 | ``` 86 | -------------------------------------------------------------------------------- /cpplint/README: -------------------------------------------------------------------------------- 1 | Usage example: 2 | 3 | This will style a single specified file: 4 | ./nitpick.py style 5 | 6 | This will style all files inside a directory: 7 | ./nitpick.py style $(find -maxdepth 1 -name '*.') 8 | 9 | This will style all files inside a directory and all it's subdirectories recursively: 10 | ./nitpick.py style $(find -name '*.') 11 | 12 | Use only one specific module 13 | ./nitpick.py style -m 14 | 15 | This is automated checker to make sure a C++ file follows Google's C++ style 16 | guide (http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml). As it 17 | heavily relies on regular expressions, cpplint.py won't catch all violations of 18 | the style guide and will very occasionally report a false positive. There is a 19 | list of things we currently don't handle very well at the top of cpplint.py, 20 | and we welcome patches to improve it. 21 | 22 | The linting tool takes a list of files as input. For full usage instructions, 23 | please see the output of: 24 | 25 | ./cpplint.py --help 26 | 27 | Unit tests are provided in cpplint_unittest.py. This file can safely be ignored 28 | by end users who have downloaded this package and only want to run the lint 29 | tool. 30 | 31 | --- 32 | 33 | cpplint.py and its corresponding unit tests are Copyright (C) 2009 Google Inc. 34 | 35 | Redistribution and use in source and binary forms, with or without 36 | modification, are permitted provided that the following conditions are 37 | met: 38 | 39 | * Redistributions of source code must retain the above copyright 40 | notice, this list of conditions and the following disclaimer. 41 | * Redistributions in binary form must reproduce the above 42 | copyright notice, this list of conditions and the following disclaimer 43 | in the documentation and/or other materials provided with the 44 | distribution. 45 | * Neither the name of Google Inc. nor the names of its 46 | contributors may be used to endorse or promote products derived from 47 | this software without specific prior written permission. 48 | 49 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 50 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 51 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 52 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 53 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 54 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 55 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 56 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 57 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 58 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 59 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 60 | -------------------------------------------------------------------------------- /nitpick.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8; -*- 3 | # Copyright (c) 2014 The Ostrich | by Itamar O 4 | # pylint: disable=protected-access,bad-indentation,too-few-public-methods,global-statement 5 | 6 | """Utility script to automate style stuff and other nitpickings.""" 7 | 8 | __author__ = 'Itamar Ostricher' 9 | 10 | import argparse 11 | import codecs 12 | import difflib 13 | import os 14 | import re 15 | import sys 16 | from collections import defaultdict 17 | 18 | import cpplint 19 | from cpplint import FileInfo 20 | from cpplint import _ClassifyInclude as classify_include 21 | 22 | _STYLE_MODULES_DICT = { 23 | u'sort_includes': (u'Automatically divide includes into sections and sort ' 24 | 'them, according to Google C++ Style Guide'), 25 | u'correct_spacing': (u'Add and/or remove spaces and tabs, ' 26 | 'according to Google C++ Style Guide'), 27 | } 28 | _STYLE_MODULES = frozenset(_STYLE_MODULES_DICT.keys()) 29 | _RE_PATTERN_INCLUDE = re.compile( # from cpplint + modifications 30 | r'^\s*#\s*include\s*([<"])\s*([^>"\s]*)\s*[>"](.*$)') 31 | _SECTIONS_ORDER = [ 32 | (cpplint._LIKELY_MY_HEADER, cpplint._POSSIBLE_MY_HEADER), 33 | (cpplint._C_SYS_HEADER,), 34 | (cpplint._CPP_SYS_HEADER,), 35 | (cpplint._LIBS_HEADER,), 36 | (cpplint._OTHER_HEADER,), 37 | ] 38 | _QUIET = False 39 | _ROOT = None 40 | 41 | def stringify(message, *args): 42 | """Return formatted message by applying args, if any.""" 43 | if args: 44 | return u'%s\n' % (message % (args)) 45 | else: 46 | return u'%s\n' % (message) 47 | 48 | def err(message, *args): 49 | """Error message printer.""" 50 | formatted = stringify(message, *args) 51 | sys.stderr.write(u'ERROR: %s' % (formatted)) 52 | raise RuntimeError(formatted.strip()) 53 | 54 | def warn(message, *args): 55 | """Warning message printer.""" 56 | sys.stderr.write(u'WARNING: ' + stringify(message, *args)) 57 | 58 | def info(message, *args): 59 | """Info message printer.""" 60 | if not _QUIET: 61 | sys.stderr.write(u'INFO: ' + stringify(message, *args)) 62 | 63 | def differ(line): 64 | """Diff line printer.""" 65 | sys.stderr.write(u'%s\n' % (line)) 66 | 67 | class HFile(object): 68 | """Included h-file class.""" 69 | def __init__(self, include_str): 70 | """Initialize an include file instance from the include string.""" 71 | match = _RE_PATTERN_INCLUDE.match(include_str) 72 | if not match: 73 | raise Exception(u'Not an include line: "%s"' % (include_str)) 74 | self.name = match.group(2) 75 | if match.group(1) == '<': 76 | self.is_system = True 77 | self.repr_pattern = u'#include <{hfile}>{post_str}' 78 | else: 79 | self.is_system = False 80 | self.repr_pattern = u'#include "{hfile}"{post_str}' 81 | self.post_str = match.group(3) 82 | 83 | def __repr__(self): 84 | """Return a string representation of the included h file.""" 85 | return self.repr_pattern.format(hfile=self.name, post_str=self.post_str) 86 | 87 | def is_own_header(src_file, include): 88 | """Return True if `include` is the 'self'-header-file for `src_file`.""" 89 | if _ROOT: 90 | src_file = os.path.relpath(src_file, _ROOT) 91 | inc_pref = os.path.normpath(os.path.splitext(include)[0]) 92 | src_pref = os.path.normpath(os.path.splitext(src_file)[0]) 93 | if src_pref.endswith('_test'): 94 | src_pref = src_pref[:-5] 95 | return inc_pref == src_pref 96 | 97 | def is_project_file(file_path): 98 | """Return True if `file_path` belongs to the project.""" 99 | file_path = os.path.normpath(file_path) 100 | file_dir = file_path.split(os.path.sep)[0] 101 | if _ROOT: 102 | file_path = os.path.join(_ROOT, file_path) 103 | file_dir = os.path.join(_ROOT, file_dir) 104 | return os.path.isfile(file_path) or os.path.isdir(file_dir) 105 | 106 | def sort_includes_batch(filename, includes): 107 | """Return a list with the includes, sorted in sections.""" 108 | by_type = defaultdict(list) 109 | for name in sorted(includes.keys()): 110 | hfile = includes[name] 111 | inc_type = classify_include(FileInfo(filename), hfile.name, 112 | hfile.is_system) 113 | if inc_type in (cpplint._LIKELY_MY_HEADER, cpplint._POSSIBLE_MY_HEADER): 114 | if not is_own_header(filename, name): 115 | inc_type = cpplint._OTHER_HEADER 116 | by_type[inc_type].append(repr(hfile)) 117 | sorted_includes = [] 118 | for sections in _SECTIONS_ORDER: 119 | for inc_type in by_type.keys(): 120 | if inc_type in sections: 121 | sorted_includes.extend(by_type[inc_type]) 122 | if sorted_includes and sorted_includes[-1]: 123 | sorted_includes.append(u'') 124 | return sorted_includes 125 | 126 | def sort_includes(filename, lines): 127 | """Return `lines` with include sections replaced with sorted versions.""" 128 | includes_batches = 0 129 | in_batch = False 130 | includes = dict() 131 | new_lines = [] 132 | for lnum, line in enumerate(lines): 133 | if line.strip().startswith(u'#include'): 134 | if not in_batch: 135 | includes_batches += 1 136 | in_batch = True 137 | # Process include line 138 | hfile = HFile(line) 139 | hkey = hfile.name.lower() 140 | if hkey in includes: 141 | # Include repeats in batch 142 | if repr(hfile) == repr(includes[hkey]): 143 | # Occurrences are consistent - just a warning then 144 | warn(u'"%s" included more than once (consistently) in "%s:%d": %s', 145 | hfile.name, filename, lnum+1, hfile) 146 | else: 147 | # Occurrences inconsistent! it's an error. 148 | err(u'"%s" included more than once (inconsistently) in "%s:%d": %s', 149 | hfile.name, filename, lnum+1, hfile) 150 | else: 151 | # Add include to batch 152 | includes[hkey] = hfile 153 | # Sanity check system-vs-project include 154 | if is_project_file(hfile.name): 155 | if hfile.is_system: 156 | warn(u'"%s" looks like a project-file, but is included with <> ' 157 | 'in "%s:%d": %s', hfile.name, filename, lnum+1, repr(hfile)) 158 | else: 159 | if not hfile.is_system: 160 | warn(u'"%s" looks like a system-file, but is included with "" ' 161 | 'in "%s:%d": %s', hfile.name, filename, lnum+1, repr(hfile)) 162 | else: 163 | if in_batch: 164 | # Maybe end of includes batch? 165 | if line.strip(): 166 | # Yes! 167 | in_batch = False 168 | new_lines.extend(sort_includes_batch(filename, includes)) 169 | includes = dict() 170 | else: 171 | # No, just a blank line 172 | continue 173 | new_lines.append(line) 174 | if in_batch: 175 | # In case the source file ends with batch of includes 176 | new_lines.extend(sort_includes_batch(filename, includes)) 177 | if includes_batches > 1: 178 | warn(u'More than 1 batch of #include\'s in "%s"', filename) 179 | return new_lines 180 | 181 | def correct_spacing(a_line): 182 | """Used to find and correct spacing issues. 183 | Bread and butter - actual work is done here. 184 | It follows the guidelines of the cpplint. 185 | """ 186 | # Used to search for: 187 | # Tabs 188 | # TODO: allow user to specify how many spaces in each tab 189 | tabs = r'\t' 190 | # Lines that end with whitespace. 191 | endline_whitespace = r'(\s*$)' 192 | # Commas and semicolons that aren't followed by a space 193 | # or a line's end. 194 | semicolon0 = r'(?<=[;,])(?!($|\s))' 195 | # Spaces directly prior to a comma or a semicolon. 196 | semicolon1 = r'(\s*)(?=[;,])' 197 | # Curly braces directly followed by letters or 198 | # directly preceded by a round bracket or a letter 199 | # e.g: }else{ 200 | # Both the first and second curly brace would match 201 | curly_braces = r'(?<=[}])(?=\w)|(?<=[)\w])(?=[{])' 202 | # Looks for '=', '<' and '>' directly by letters, numbers or quotes. 203 | assignment_gt_lt = r'(?<=[\w\"\'])(?=[=<>])|(?<=[=<>])(?=[\w\"\'])' 204 | # Looks for '==', '!=', '<=', '>=', '&&', '>>', '<<' and '||' 205 | # that are next to anything other than whitespace or end of line 206 | oper_wo_space_in = r'(?=|&&|>>|<<|\|\|))' 207 | oper_wo_space_out = r'(?<=(==|!=|<=|>=|&&|>>|<<|\|\|))(?!$|\s)' 208 | # ifs, fors, whiles & switches, followed directly by round brackets 209 | loops_and_conds = r'(^|\W)(if|for|while|switch)(?=[(])' 210 | # Looks for improperly spaced one line comments // 211 | comments0 = r'(?<=[/]{2})(?=[\S])' 212 | comments1 = r'(? a; b 223 | result = re.sub(semicolon0, r' ', result) 224 | # Remove space before semicolon 225 | # a ; => a; 226 | result = re.sub(semicolon1, r'', result) 227 | # Adding space between bracket and brace 228 | # ){ => ) { 229 | result = re.sub(curly_braces, r' ', result) 230 | # Adding a space near assignment, gt and lt 231 | # a=b => a = b 232 | result = re.sub(assignment_gt_lt, r' ', result) 233 | # Adding a space near opers 234 | # a&&b => a && b 235 | result = re.sub(oper_wo_space_in, r' ', result) 236 | result = re.sub(oper_wo_space_out, r' ', result) 237 | # Adding a space before conds & loops brackets 238 | # if() => if () 239 | result = re.sub(loops_and_conds, r'\1\2 ', result) 240 | # Adding space before a comments text 241 | # //abc => // abc 242 | result = re.sub(comments0, r' ', result) 243 | # Adding two spaces between code and comments 244 | # abc//def => abc //def 245 | result = re.sub(comments1, r' ', result) 246 | # Replacing multiple spaces in the beginning of a comment with one 247 | result = re.sub(comments2, r' ', result) 248 | return result 249 | 250 | def stylify_lines(args, filename, lines): 251 | """Run stylify modules on `lines` and return styled lines.""" 252 | for mod in args.modules: 253 | if u'sort_includes' == mod: 254 | lines = sort_includes(filename, lines) 255 | if u'correct_spacing' == mod: 256 | lines = [correct_spacing(line) for line in lines] 257 | return lines 258 | 259 | def stylify_file(args, filepath): 260 | """Stylify `filepath`.""" 261 | if '-' == filepath: 262 | # Reading from STDIN 263 | old_content = sys.stdin.read() 264 | filename = args.filename or '-' 265 | else: 266 | # Reading from filepath 267 | filename = filepath 268 | with codecs.open(filepath, 'r', 'utf8', 'replace') as src_f: 269 | old_content = src_f.read() 270 | if _ROOT: 271 | cpplint._root = _ROOT 272 | cpplint.ProcessConfigOverrides(filename) 273 | lines = old_content.split(u'\n') 274 | new_lines = stylify_lines(args, filename, lines) 275 | new_content = u'\n'.join(new_lines) 276 | if new_content != old_content: 277 | if args.show_diff: 278 | info(u'Modified content of %s. Diff:', filename) 279 | for diffline in difflib.unified_diff(lines, new_lines, 280 | fromfile='%s (before)' % (filename), 281 | tofile='%s (after)' % (filename)): 282 | differ(diffline) 283 | if '-' == filepath: 284 | sys.stdout.write(new_content) 285 | elif not args.no_edit: 286 | info(u'Writing changes back to filepath %s ...', filepath) 287 | with codecs.open(filepath, 'w', 'utf8', 'replace') as f_out: 288 | f_out.write(new_content) 289 | else: 290 | info(u'No changes for %s ...', filename) 291 | 292 | def stylify(args, files): 293 | """Stylify files.""" 294 | if args.modules: 295 | args.modules = set(args.modules) 296 | assert args.modules <= _STYLE_MODULES 297 | else: 298 | args.modules = _STYLE_MODULES 299 | if files: 300 | for filepath in files: 301 | filepath = os.path.normpath(filepath) 302 | info(u'Stylifying file %s ...', filepath) 303 | try: 304 | stylify_file(args, filepath) 305 | except RuntimeError: 306 | info(u'Skipping file %s ...', filepath) 307 | else: 308 | info(u'Done with file %s ...', filepath) 309 | else: 310 | stylify_file(args, '-') 311 | 312 | def main(): 313 | """Run nitpick command on input(s).""" 314 | parser = argparse.ArgumentParser() 315 | subparsers = parser.add_subparsers() 316 | style_parser = subparsers.add_parser('style', 317 | help=u'Stylify C++ source code') 318 | style_parser.add_argument('--no_edit', action='store_true', 319 | help=u'Don\'t overwrite source file with ' 320 | 'stylified output') 321 | style_parser.add_argument('--show_diff', action='store_true', 322 | help=u'Print diffs between input file and ' 323 | 'stylified file to STDERR') 324 | style_parser.add_argument('--quiet', action='store_true', 325 | help=u'Don\'t print progress ' 326 | '(only warnings and errors)') 327 | style_parser.add_argument('-m', '--modules', action='append', metavar='MOD', 328 | help=(u'Enabled style modules (choose from {%s}, ' 329 | 'or default to all modules)' % 330 | (u','.join(_STYLE_MODULES_DICT.keys())))) 331 | style_parser.add_argument('--filename', 332 | help=u'When reading source code from STDIN, speci' 333 | 'fy the filename of the processed source code') 334 | style_parser.add_argument('--root', 335 | help=u'Path to project root directory, if ' 336 | 'different from current directory') 337 | style_parser.set_defaults(func=stylify) 338 | # Change stderr to write with replacement characters so we don't die 339 | # if we try to print something containing non-ASCII characters. 340 | sys.stderr = codecs.StreamReaderWriter(sys.stderr, 341 | codecs.getreader('utf8'), 342 | codecs.getwriter('utf8'), 343 | 'replace') 344 | 345 | args, files = parser.parse_known_args() 346 | global _QUIET 347 | _QUIET = args.quiet 348 | global _ROOT 349 | _ROOT = args.root 350 | args.func(args, files) 351 | 352 | if __name__ == '__main__': 353 | main() 354 | -------------------------------------------------------------------------------- /nitpick_unittest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8; -*- 3 | # Copyright (c) 2014 The Ostrich | by Itamar O 4 | # pylint: disable=protected-access,too-many-public-methods,too-few-public-methods,bad-indentation,bad-continuation,invalid-name 5 | 6 | """Unit tests for nitpick.py.""" 7 | 8 | import os 9 | import unittest 10 | 11 | import nitpick 12 | 13 | def setup_mocks(base_obj, mock_spec): 14 | """Setup mocking.""" 15 | restore = dict() 16 | for name, mock in mock_spec.iteritems(): 17 | obj = base_obj 18 | name_parts = name.split(u'.') 19 | for name_part in name_parts[:-1]: 20 | obj = getattr(obj, name_part) 21 | # save original object for restoring in tearDown 22 | restore[name] = getattr(obj, name_parts[-1]) 23 | # mock it 24 | setattr(obj, name_parts[-1], mock) 25 | return restore 26 | 27 | def restore_mocks(base_obj, restore_spec): 28 | """Restore mocked objects to originals.""" 29 | for name, restore in restore_spec.iteritems(): 30 | obj = base_obj 31 | name_parts = name.split(u'.') 32 | for name_part in name_parts[:-1]: 33 | obj = getattr(obj, name_part) 34 | # restore it 35 | setattr(obj, name_parts[-1], restore) 36 | 37 | class ProjectOwnershipTest(unittest.TestCase): 38 | """Test is_project_file function""" 39 | 40 | def setUp(self): 41 | """Mock os.path.isfile for test.""" 42 | def mock_is_file(file_path): 43 | """Mock checking for file existence to bypass filesystem.""" 44 | if nitpick._ROOT: 45 | return os.path.normpath(file_path) in ( 46 | os.path.join(os.path.normpath(nitpick._ROOT), u'foo', u'bar.h'),) 47 | return os.path.normpath(file_path) in (os.path.join(u'foo', u'bar.h'),) 48 | def mock_is_dir(dir_path): 49 | """Mock checking for dir existence to bypass filesystem.""" 50 | if nitpick._ROOT: 51 | return os.path.normpath(dir_path) in ( 52 | os.path.normpath(os.path.join(nitpick._ROOT, u'foo')),) 53 | return os.path.normpath(dir_path) in (u'foo',) 54 | self._mocks = setup_mocks( 55 | nitpick, 56 | { 57 | u'os.path.isfile': mock_is_file, 58 | u'os.path.isdir': mock_is_dir, 59 | } 60 | ) 61 | 62 | def tearDown(self): 63 | """Restore mocked os.path.isfile.""" 64 | restore_mocks(nitpick, self._mocks) 65 | # Restore mocked root to None 66 | nitpick._ROOT = None 67 | 68 | def test_simple_check(self): 69 | """Test that is_project_file behaves as expected for simple cases.""" 70 | self.assertTrue(nitpick.is_project_file(u'foo/bar.h')) 71 | self.assertTrue(nitpick.is_project_file(u'./foo/bar.h')) 72 | self.assertFalse(nitpick.is_project_file(u'proj/foo/bar.h')) 73 | self.assertFalse(nitpick.is_project_file(u'./proj/foo/bar.h')) 74 | 75 | def test_check_with_root(self): 76 | """Test that is_project_file behaves as expected when root is set.""" 77 | nitpick._ROOT = u'proj' 78 | self.assertTrue(nitpick.is_project_file(u'foo/bar.h')) 79 | self.assertTrue(nitpick.is_project_file(u'./foo/bar.h')) 80 | self.assertFalse(nitpick.is_project_file(u'proj/foo/bar.h')) 81 | self.assertFalse(nitpick.is_project_file(u'./proj/foo/bar.h')) 82 | nitpick._ROOT = u'./proj' 83 | self.assertTrue(nitpick.is_project_file(u'foo/bar.h')) 84 | self.assertTrue(nitpick.is_project_file(u'./foo/bar.h')) 85 | self.assertFalse(nitpick.is_project_file(u'proj/foo/bar.h')) 86 | self.assertFalse(nitpick.is_project_file(u'./proj/foo/bar.h')) 87 | 88 | class IncludeSorterTest(unittest.TestCase): 89 | """Test include-sorter nitpick module""" 90 | 91 | def setUp(self): 92 | """Mock stuff for tests""" 93 | def mock_is_proj_file(file_path): 94 | """Mock testing for project-association to bypass filesystem.""" 95 | base_dir = file_path.split(os.path.sep)[0] 96 | return base_dir in (u'foo', u'common', u'mymath') 97 | class MyStdErr(object): 98 | """Mock STDERR class""" 99 | def __init__(self): 100 | """Init empty STDERR mock.""" 101 | self.buffer = list() 102 | def write(self, message): 103 | """Write string to mock STDERR.""" 104 | self.buffer.append(message) 105 | self._stderr = MyStdErr() 106 | self._mocks = setup_mocks( 107 | nitpick, 108 | { 109 | u'is_project_file': mock_is_proj_file, 110 | u'sys.stderr': self._stderr, 111 | u'cpplint._system_wide_external_libs': True, 112 | u'cpplint._external_lib_prefixes': [u'glog', u'gflags'], 113 | } 114 | ) 115 | 116 | def tearDown(self): 117 | """Restore mocked objects to originals.""" 118 | restore_mocks(nitpick, self._mocks) 119 | # Restore mocked root to None 120 | nitpick._ROOT = None 121 | 122 | def test_nop_sort(self): 123 | """Test that already sorted includes returned as it.""" 124 | src_lines = [ 125 | u'#include "foo/bar.h"', 126 | u'', 127 | u'#include ', 128 | u'', 129 | u'#include ', 130 | u'#include ', 131 | u'#include ', 132 | u'', 133 | u'#include "common/logging.h"', 134 | u'#include "common/util.h"', 135 | u'#include "mymath/factoiral.h"', 136 | u'', 137 | ] 138 | self.assertListEqual( 139 | src_lines, 140 | nitpick.sort_includes('foo/bar.cc', src_lines), 141 | ) 142 | 143 | def test_simple_sort(self): 144 | """Test that a straight forward sort is performed as expected.""" 145 | src_lines = [ 146 | u'#include "common/util.h"', 147 | u'#include ', 148 | u'#include "mymath/factoiral.h"', 149 | u'#include ', 150 | u'#include ', 151 | u'#include ', 152 | u'#include "common/logging.h"', 153 | u'#include "foo/bar.h"', 154 | u'', 155 | ] 156 | exp_lines = [ 157 | u'#include "foo/bar.h"', # own include 158 | u'', 159 | u'#include ', # C system 160 | u'', 161 | u'#include ', # C++ system 162 | u'#include ', 163 | u'#include ', 164 | u'', 165 | u'#include "common/logging.h"', # Project 166 | u'#include "common/util.h"', 167 | u'#include "mymath/factoiral.h"', 168 | u'', 169 | ] 170 | self.assertListEqual( 171 | exp_lines, 172 | nitpick.sort_includes('foo/bar.cc', src_lines), 173 | ) 174 | 175 | def test_ext_lib_sort(self): 176 | """Test that external lib includes are detected and sorted.""" 177 | src_lines = [ 178 | u'#include ', 179 | u'#include ', 180 | u'#include "mymath/factoiral.h"', 181 | u'#include ', 182 | u'#include ', 183 | u'#include ', 184 | u'#include ', 185 | u'#include "foo/bar.h"', 186 | u'', 187 | ] 188 | exp_lines = [ 189 | u'#include "foo/bar.h"', # own include 190 | u'', 191 | u'#include ', # C system 192 | u'', 193 | u'#include ', # C++ system 194 | u'#include ', 195 | u'#include ', 196 | u'', 197 | u'#include ', # Ext libs 198 | u'#include ', 199 | u'', 200 | u'#include "mymath/factoiral.h"', # Project 201 | u'', 202 | ] 203 | self.assertListEqual( 204 | exp_lines, 205 | nitpick.sort_includes('foo/bar.cc', src_lines), 206 | ) 207 | 208 | def test_preserve_poststr(self): 209 | """Test that the sort preserves post-strings of include lines.""" 210 | src_lines = [ 211 | u'#include // old school', 212 | u'#include\t<\tglog/logging.h\t>\t//for logging, ya know', 213 | u'#include "foo/bar.h" // implemented interface', 214 | u'', 215 | ] 216 | exp_lines = [ 217 | u'#include "foo/bar.h" // implemented interface', # own include 218 | u'', 219 | u'#include // old school', # C system 220 | u'', 221 | u'#include \t//for logging, ya know', # Ext libs 222 | u'', 223 | ] 224 | self.assertListEqual( 225 | exp_lines, 226 | nitpick.sort_includes('foo/bar.cc', src_lines), 227 | ) 228 | 229 | def test_surrounding_lines(self): 230 | """Test that sorter doesn't mess with surrounding lines.""" 231 | src_lines = [ 232 | u'// Copyright message', 233 | u'// By someone', 234 | u'#include "common/util.h"', 235 | u'#include ', 236 | u'#include "mymath/factoiral.h"', 237 | u'#include "foo/bar.h"', 238 | u'int main() {', 239 | u' return 42;', 240 | u'}', 241 | u'', 242 | ] 243 | exp_lines = [ 244 | u'// Copyright message', 245 | u'// By someone', 246 | u'#include "foo/bar.h"', # own include 247 | u'', 248 | u'#include ', # C++ system 249 | u'', 250 | u'#include "common/util.h"', # Project 251 | u'#include "mymath/factoiral.h"', 252 | u'', 253 | u'int main() {', 254 | u' return 42;', 255 | u'}', 256 | u'', 257 | ] 258 | self.assertListEqual( 259 | exp_lines, 260 | nitpick.sort_includes('foo/bar.cc', src_lines), 261 | ) 262 | 263 | def test_with_root_dir(self): 264 | """Test that sorter understands the root option.""" 265 | nitpick._ROOT = 'proj' 266 | src_lines = [ 267 | u'#include "common/util.h"', 268 | u'#include ', 269 | u'#include "mymath/factoiral.h"', 270 | u'#include ', 271 | u'#include ', 272 | u'#include ', 273 | u'#include "common/logging.h"', 274 | u'#include "foo/bar.h"', 275 | u'', 276 | ] 277 | exp_lines = [ 278 | u'#include "foo/bar.h"', # own include 279 | u'', 280 | u'#include ', # C system 281 | u'', 282 | u'#include ', # C++ system 283 | u'#include ', 284 | u'#include ', 285 | u'', 286 | u'#include "common/logging.h"', # Project 287 | u'#include "common/util.h"', 288 | u'#include "mymath/factoiral.h"', 289 | u'', 290 | ] 291 | self.assertListEqual( 292 | exp_lines, 293 | nitpick.sort_includes('proj/foo/bar.cc', src_lines), 294 | ) 295 | 296 | def test_consistent_duplicate(self): 297 | """Test that sorter detects consistent duplicates, keeps one, and warns.""" 298 | src_lines = [ 299 | u'#include "common/util.h"', 300 | u'#include ', 301 | u'#include "mymath/factoiral.h"', 302 | u'#include ', 303 | u'#include "foo/bar.h"', 304 | u'', 305 | ] 306 | exp_lines = [ 307 | u'#include "foo/bar.h"', # own include 308 | u'', 309 | u'#include ', # C++ system 310 | u'', 311 | u'#include "common/util.h"', # Project 312 | u'#include "mymath/factoiral.h"', 313 | u'', 314 | ] 315 | self.assertListEqual( 316 | exp_lines, 317 | nitpick.sort_includes('foo/bar.cc', src_lines), 318 | ) 319 | self.assertListEqual( 320 | [u'WARNING: "algorithm" included more than once (consistently) in ' 321 | '"foo/bar.cc:4": #include \n'], 322 | self._stderr.buffer 323 | ) 324 | 325 | def test_inconsistent_duplicate(self): 326 | """Test that sorter detects inconsistent duplicates, and breaks.""" 327 | src_lines = [ 328 | u'#include "common/util.h"', 329 | u'#include // for std::max', 330 | u'#include "mymath/factoiral.h"', 331 | u'#include ', 332 | u'#include "foo/bar.h"', 333 | u'', 334 | ] 335 | with self.assertRaises(RuntimeError): 336 | nitpick.sort_includes('foo/bar.cc', src_lines) 337 | self.assertListEqual( 338 | [u'ERROR: "algorithm" included more than once (inconsistently) in ' 339 | '"foo/bar.cc:4": #include \n'], 340 | self._stderr.buffer 341 | ) 342 | 343 | def test_wrong_include_style(self): 344 | """Test that sorter detects wrong include style and warns about it.""" 345 | src_lines = [ 346 | u'#include ', 347 | u'#include "algorithm"', 348 | u'#include "mymath/factoiral.h"', 349 | u'#include "foo/bar.h"', 350 | u'', 351 | ] 352 | exp_lines = [ 353 | u'#include "foo/bar.h"', 354 | u'', 355 | u'#include ', 356 | u'', 357 | u'#include "algorithm"', 358 | u'#include "mymath/factoiral.h"', 359 | u'', 360 | ] 361 | self.assertListEqual( 362 | exp_lines, 363 | nitpick.sort_includes('foo/bar.cc', src_lines), 364 | ) 365 | self.assertListEqual( 366 | [u'WARNING: "common/util.h" looks like a project-file, but is included ' 367 | 'with <> in "foo/bar.cc:1": #include \n', 368 | u'WARNING: "algorithm" looks like a system-file, but is included with ' 369 | '"" in "foo/bar.cc:2": #include "algorithm"\n'], 370 | self._stderr.buffer 371 | ) 372 | 373 | class WhiteSpaceFixerTest(unittest.TestCase): 374 | """Test correct-spacing nitpick module""" 375 | 376 | def test_replacing_tabs_with_two_spaces(self): 377 | """Test that tabs are removed""" 378 | src_lines = [ 379 | u'for (int i = 100 ; i < 0; i++) { ' 380 | ] 381 | exp_lines = [ 382 | u'for (int i = 100; i < 0; i++) {' 383 | ] 384 | self.assertListEqual( 385 | exp_lines, 386 | [nitpick.correct_spacing(line) for line in src_lines] 387 | ) 388 | 389 | def test_whitespace_at_end_of_line_removal(self): 390 | """Test that any whitespace in the end of the line is removed""" 391 | src_lines = [ 392 | u'for (int i = 100 ; i < 0; i++) { ', 393 | u' some_line = value + another value ; ' 394 | ] 395 | exp_lines = [ 396 | u'for (int i = 100; i < 0; i++) {', 397 | u' some_line = value + another value;' 398 | ] 399 | self.assertListEqual( 400 | exp_lines, 401 | [nitpick.correct_spacing(line) for line in src_lines] 402 | ) 403 | 404 | def test_spacing_semicolon_and_comma(self): 405 | """Test that semicolon and comma spacing is corrected""" 406 | src_lines = [ 407 | u'for(int i = 100 ;i < 0;i++) {', 408 | u'int y = 100 ; ' 409 | ] 410 | exp_lines = [ 411 | u'for (int i = 100; i < 0; i++) {', 412 | u'int y = 100;' 413 | ] 414 | self.assertListEqual( 415 | exp_lines, 416 | [nitpick.correct_spacing(line) for line in src_lines] 417 | ) 418 | 419 | def test_curly_brace_spacing_fixer(self): 420 | """Test that curly braces are spaced correctly""" 421 | src_lines = [ 422 | u'for(int i = 100 ;i < 0;i++){', 423 | u'}else{' 424 | ] 425 | exp_lines = [ 426 | u'for (int i = 100; i < 0; i++) {', 427 | u'} else {' 428 | ] 429 | self.assertListEqual( 430 | exp_lines, 431 | [nitpick.correct_spacing(line) for line in src_lines] 432 | ) 433 | 434 | def test_spacing_with_assignment_and_gt_lt(self): 435 | """Test spacing with =, < and >""" 436 | src_lines = [ 437 | u'if (ab || c< d || c >d) {', 438 | ] 439 | exp_lines = [ 440 | u'if (a < b || a > b || c < d || c > d) {', 441 | ] 442 | self.assertListEqual( 443 | exp_lines, 444 | [nitpick.correct_spacing(line) for line in src_lines] 445 | ) 446 | 447 | def test_spacing_with_some_common_two_char_operators(self): 448 | """Test spacing with operators like ==, !=, >=, << and such""" 449 | src_lines = [ 450 | u'a = 1<<2', 451 | u'if (a==b||c >=d&& bob!=mob)' 452 | u'b = 256 >>4' 453 | ] 454 | exp_lines = [ 455 | u'a = 1 << 2', 456 | u'if (a == b || c >= d && bob != mob)' 457 | u'b = 256 >> 4' 458 | ] 459 | self.assertListEqual( 460 | exp_lines, 461 | [nitpick.correct_spacing(line) for line in src_lines] 462 | ) 463 | 464 | def test_spacing_near_if_for_while_switch(self): 465 | """Test spacing near conditions, loops and switches""" 466 | src_lines = [ 467 | u'if(a) while(b) for(; i < 10; i++) switch(c)', 468 | ] 469 | exp_lines = [ 470 | u'if (a) while (b) for (; i < 10; i++) switch (c)', 471 | ] 472 | self.assertListEqual( 473 | exp_lines, 474 | [nitpick.correct_spacing(line) for line in src_lines] 475 | ) 476 | 477 | def test_spacing_around_single_line_comments(self): 478 | """Test spacing near single line comments (//)""" 479 | src_lines = [ 480 | u'if (a)//we like a so lets do stuff', 481 | u'while (b) //as long as there is b, there is hope', 482 | u'some_var = other_var; //other_var is a reference to some_var', 483 | u'do_good_stuff(a, b);// possibly rename this function' 484 | u'some_syntax_here // too much spacing here' 485 | ] 486 | exp_lines = [ 487 | u'if (a) // we like a so lets do stuff', 488 | u'while (b) // as long as there is b, there is hope', 489 | u'some_var = other_var; // other_var is a reference to some_var', 490 | u'do_good_stuff(a, b); // possibly rename this function' 491 | u'some_syntax_here // too much spacing here' 492 | ] 493 | self.assertListEqual( 494 | exp_lines, 495 | [nitpick.correct_spacing(line) for line in src_lines] 496 | ) 497 | 498 | def test_no_space_around_factors(self): 499 | """Test that multiple allowed styles are left unchanged.""" 500 | src_lines = [ 501 | u'v = w * x + y / z; // Binary operators usually have spaces around', 502 | u'v = w*x + y/z; // it\'s okay to remove spaces around factors.', 503 | u'v = w * (x + z); // Parentheses should have no spaces inside them.', 504 | ] 505 | exp_lines = [ 506 | u'v = w * x + y / z; // Binary operators usually have spaces around', 507 | u'v = w*x + y/z; // it\'s okay to remove spaces around factors.', 508 | u'v = w * (x + z); // Parentheses should have no spaces inside them.', 509 | ] 510 | self.assertListEqual( 511 | exp_lines, 512 | [nitpick.correct_spacing(line) for line in src_lines] 513 | ) 514 | 515 | if __name__ == '__main__': 516 | unittest.main() 517 | --------------------------------------------------------------------------------