From 1e31b4a6aef5e319d33689ac71c6014921dccb9f Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Fri, 7 Mar 2025 15:57:27 -0500 Subject: [PATCH 1/5] update fields of pyproject.toml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * merge pylint config * make version dynamic * better list of maintainers and major authors * remove "test" extra which now just duplicates "dev" * remove weird duplicated "testing" extra * remove setuptools—a build-system.requires—from optional requirements * remove unneeded pytest alias * more and better keywords (this is PyPI, we don't need a "Python" keyword * shorter desc --- .pylintrc | 49 --------------------------- pyproject.toml | 91 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 66 insertions(+), 74 deletions(-) delete mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 220215c..0000000 --- a/.pylintrc +++ /dev/null @@ -1,49 +0,0 @@ -[BASIC] -include-naming-hint=yes -method-rgx=[A-Z_][A-Za-z0-9]{2,49}$|__init__|__str__|__contains__ -function-rgx=[A-Z_][A-Za-z0-9]{2,49}$|main -const-rgx=[a-zA-Z_][A-Za-z0-9_]{2,49}$ -variable-rgx=[a-z_][a-z0-9_]{0,49}$ -argument-rgx=[a-z_][a-z0-9_]{0,49}$ -class-rgx=[A-Z_][a-zA-Z0-9]+$|basestring|unicode|long|xrange - -[MESSAGES CONTROL] -disable= - global-statement, - multiple-statements, - missing-docstring, - no-else-return, - no-self-use, - consider-merging-isinstance, - consider-using-min-builtin, - bad-continuation, - fixme, - bad-option-value, - anomalous-unicode-escape-in-string, - unused-argument, - useless-object-inheritance, - consider-using-dict-comprehension, - consider-using-in, - unnecessary-pass, - too-many-positional-arguments - -[REPORTS] -output-format=colorized -reports=no -score=no - -[FORMAT] -indent-string=' ' -indent-after-paren=4 -max-module-lines=10000 - -[DESIGN] -max-locals=25 -max-line-length=100 -max-attributes=10 -max-branches=30 -max-args=20 -max-statements=75 -max-returns=10 -min-public-methods=0 -max-bool-expr=10 diff --git a/pyproject.toml b/pyproject.toml index a194c2b..7dd5b75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,12 +4,21 @@ requires = [ "setuptools>=61.2" ] [project] name = "cpplint" -version = "2.0.0" -description = "Automated checker to ensure C++ files follow Google's style guide" +description = "Check C++ files configurably against Google's style guide" readme = "README.rst" -keywords = [ "c++", "lint", "python" ] +keywords = [ "c++", "cpp", "google style", "lint" ] license = { text = "BSD-3-Clause" } -maintainers = [ { name = "cpplint Developers" } ] +maintainers = [ + { name = "Aaron Liu", email = "aaronliu0130@gmail.com" }, + { name = "Christian Clauss", email = "cclauss@me.com" }, + { name = "John Vandenberg", email = "jayvdb@gmail.com" }, +] +authors = [ + { name = "Google Inc." }, + { name = "Thibault Kruse" }, + { name = "Andrew Davis", email = "theandrewdavis@gmail.com" }, + { name = "cpplint developers" }, +] requires-python = ">=3.8" classifiers = [ "Development Status :: 5 - Production/Stable", @@ -28,6 +37,7 @@ classifiers = [ "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Quality Assurance", ] +dynamic = [ "version" ] dependencies = [ ] optional-dependencies.dev = [ @@ -36,33 +46,16 @@ optional-dependencies.dev = [ "pytest", "pytest-cov", "pytest-timeout", - "setuptools", - "testfixtures", - "tox<5", -] -optional-dependencies.test = [ - "parameterized", - "pytest", - "pytest-cov", - "pytest-timeout", "testfixtures", "tox<5", ] -optional-dependencies.testing = [ - "parameterized", - "pytest", - "pytest-cov", - "pytest-timeout", - "testfixtures", - "tox<5", -] -urls.Download = "https://github.com/cpplint/cpplint" urls.Homepage = "https://github.com/cpplint/cpplint" scripts.cpplint = "cpplint:main" [tool.setuptools] py-modules = [ "cpplint" ] include-package-data = false +dynamic.version = { attr = "cpplint.__VERSION__" } [tool.ruff] target-version = "py38" @@ -143,6 +136,57 @@ lint.per-file-ignores."cpplint.py" = [ "ICN001", "PERF401" ] lint.per-file-ignores."cpplint_unittest.py" = [ "FLY002" ] lint.mccabe.max-complexity = 29 +[tool.pylint.basic] +include-naming-hint = true +method-rgx = "[A-Z_][A-Za-z0-9]{2,49}$|__init__|__str__|__contains__" +function-rgx = "[A-Z_][A-Za-z0-9]{2,49}$|main" +const-rgx = "[a-zA-Z_][A-Za-z0-9_]{2,49}$" +variable-rgx = "[a-z_][a-z0-9_]{0,49}$" +argument-rgx = "[a-z_][a-z0-9_]{0,49}$" +class-rgx = "[A-Z_][a-zA-Z0-9]+$|basestring|unicode|long|xrange" + +[tool.pylint.messages-control] +disable = [ + "global-statement", + "multiple-statements", + "missing-docstring", + "no-else-return", + "no-self-use", + "consider-merging-isinstance", + "consider-using-min-builtin", + "bad-continuation", + "fixme", + "bad-option-value", + "anomalous-unicode-escape-in-string", + "unused-argument", + "useless-object-inheritance", + "consider-using-dict-comprehension", + "consider-using-in", + "unnecessary-pass", + "too-many-positional-arguments", +] + +[tool.pylint.reports] +output-format = "colorized" +reports = false +score = false + +[tool.pylint.format] +indent-string = ' ' +indent-after-paren = 4 +max-module-lines = 10000 + +[tool.pylint.design] +max-locals = 25 +max-line-length = 100 +max-attributes = 10 +max-branches = 30 +max-args = 20 +max-statements = 75 +max-returns = 10 +min-public-methods = 0 +max-bool-expr = 10 + [tool.pytest.ini_options] python_files = [ "*test.py" ] testpaths = [ "." ] @@ -150,6 +194,3 @@ required_plugins = [ "pytest-cov", "pytest-timeout" ] timeout = 60 # fail if coverage is under 90% addopts = "--color=yes --cov-fail-under=90 --cov=cpplint" - -[tool.aliases] -test = "pytest" From feb0b75046c7e2e3eb92edfde64d94abe72270df Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Sat, 8 Mar 2025 17:40:20 -0500 Subject: [PATCH 2/5] update CONTRIBUTING.rst fix a typo in the bulleted list remove specific python version --- CONTRIBUTING.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 4ff2e0f..fe310c8 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -7,7 +7,7 @@ Thanks for your interest in contributing to cpplint. Any kinds of contributions are welcome: Bug reports, Documentation, Patches. However, here are some contributions you probably shouldn't make: * Drastic reorganization - * Making the code conform to Google's Python style guidelines + * Making the code conform to Google's Python style guidelines * Features that could be regarded as a security vulnerability If you need some ideas, you may check out some of the tasks in our `issue tracker `_. @@ -22,9 +22,9 @@ For many tasks, it is okay to just develop using a single installed python versi 1. (Optional) Install `pyenv `_ to manage python versions 2. (Optional) Using pyenv, install the python versions used in testing:: - pyenv install 3.12.6 + pyenv install 3. # ... - pyenv local 3.12.6 ... + pyenv local 3. ... It may be okay to run and test python against locally installed libraries, but if you need to have a consistent build, it is recommended to manage your environment using virtualenv: `virtualenv `_, `virtualenvwrapper `_:: From 47f9a127c469a9dec4d0762fbf0cf51e05fe2efa Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Sat, 1 Feb 2025 11:29:40 -0500 Subject: [PATCH 3/5] lint CLI tests --- cpplint_clitest.py | 118 +++++++++++++++++++++++---------------------- 1 file changed, 60 insertions(+), 58 deletions(-) diff --git a/cpplint_clitest.py b/cpplint_clitest.py index d49d08c..9cfc513 100755 --- a/cpplint_clitest.py +++ b/cpplint_clitest.py @@ -39,50 +39,56 @@ import tempfile import unittest +from parameterized import parameterized from pytest import mark from testfixtures import compare BASE_CMD = sys.executable + ' ' + os.path.abspath('./cpplint.py ') -def RunShellCommand(cmd: str, args: str, cwd='.'): - """ - executes a command - :param cmd: A string to execute. - :param cwd: from which folder to run. + +def run_shell_command(cmd: str, args: str, cwd='.'): + """Executes a command + + Args: + cmd: A string to execute. + args: A string with arguments to the command. + cwd: from which folder to run. """ stdout_target = subprocess.PIPE stderr_target = subprocess.PIPE - proc = subprocess.Popen(cmd + ' ' + args, + with subprocess.Popen(cmd + ' ' + args, shell=True, cwd=cwd, stdout=stdout_target, - stderr=stderr_target) - out, err = proc.communicate() + stderr=stderr_target) as proc: + out, err = proc.communicate() # Make output system-agnostic, aka support Windows if os.sep == '\\': - # TODO: Support scenario with multiple folder inputs - win_path = (os.path.dirname(args.split(' ')[-1]) + '\\').encode() - good_path = win_path.replace(b'\\', b'/') - out, err = out.replace(win_path, good_path), err.replace(win_path, good_path) + args_paths = args.split(' ') + for path in args_paths: + win_path = (os.path.dirname(path) + '\\').encode() + good_path = win_path.replace(b'\\', b'/') + out, err = out.replace(win_path, good_path), err.replace(win_path, good_path) if os.linesep == '\r\n': out, err = out.replace(b'\r\n', b'\n'), err.replace(b'\r\n', b'\n') # print(err) # to get the output at time of test - return (proc.returncode, out, err) + return proc.returncode, out, err class UsageTest(unittest.TestCase): def testHelp(self): - (status, out, err) = RunShellCommand(BASE_CMD, '--help') + (status, out, err) = run_shell_command(BASE_CMD, '--help') self.assertEqual(0, status) self.assertEqual(b'', out) self.assertTrue(err.startswith(b'\nSyntax: cpplint')) -class TemporaryFolderClassSetup(object): + +class TemporaryFolderClassSetup(unittest.TestCase): """ Regression tests: The test starts a filetreewalker scanning for files name *.def Such files are expected to have as first line the argument @@ -108,7 +114,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - if (cls._root): + if cls._root: # pass shutil.rmtree(cls._root) @@ -121,39 +127,39 @@ def get_extra_command_args(self, cwd): """Override in subclass to add arguments to command""" return '' - def checkAllInFolder(self, foldername, expectedDefs): + def check_all_in_folder(self, folder_name, expected_defs): # uncomment to show complete diff # self.maxDiff = None count = 0 - for dirpath, _, fnames in os.walk(foldername): + for dirpath, _, fnames in os.walk(folder_name): for f in fnames: if f.endswith('.def'): count += 1 - self._checkDef(os.path.join(dirpath, f)) - self.assertEqual(count, expectedDefs) + self.check_def(os.path.join(dirpath, f)) + self.assertEqual(count, expected_defs) - def _checkDef(self, path): + def check_def(self, path): """runs command and compares to expected output from def file""" # self.maxDiff = None # to see full diff - with open(path, 'rb') as filehandle: - datalines = filehandle.readlines() - stdoutLines = int(datalines[2]) - filenames = datalines[0].decode('utf8').strip() + with open(path, 'rb') as file_handle: + data = file_handle.readlines() + stdout_lines = int(data[2]) + filenames = data[0].decode('utf8').strip() args, _, filenames = filenames.rpartition(" ") if '*' in filenames: rel_cwd = os.path.dirname(path) filenames = ' '.join( - filename[len(rel_cwd)+1:] + filename[len(rel_cwd) + 1:] for filename in glob.glob(rel_cwd + '/' + filenames) ) args += ' ' + filenames - self._runAndCheck(path, - args, - int(datalines[1]), - [line.decode('utf8').strip() for line in datalines[3:3 + stdoutLines]], - [line.decode('utf8').strip() for line in datalines[3 + stdoutLines:]]) + self._run_and_compare(path, args, int(data[1]), + [line.decode('utf8').strip() + for line in data[3:3 + stdout_lines]], + [line.decode('utf8').strip() + for line in data[3 + stdout_lines:]]) - def _runAndCheck( + def _run_and_compare( self, definition_file, args, @@ -165,8 +171,8 @@ def _runAndCheck( cmd = BASE_CMD + self.get_extra_command_args(rel_cwd) cwd = os.path.join(self._root, rel_cwd) # command to reproduce, do not forget first two lines have special meaning - print("\ncd " + cwd + " && " + cmd + ' ' + args + " 2> ") - (status, out, err) = RunShellCommand(cmd, args, cwd) + print("\ncd " + cwd + " && " + cmd + ' ' + args + " 2> ") + (status, out, err) = run_shell_command(cmd, args, cwd) self.assertEqual(expected_status, status, 'bad command status %s' % status) prefix = 'Failed check in %s comparing to %s for command: %s' % (cwd, definition_file, cmd) compare('\n'.join(expected_err), err.decode('utf8'), prefix=prefix, show_whitespace=True) @@ -177,29 +183,22 @@ class NoRepoSignatureTests(TemporaryFolderClassSetup, unittest.TestCase): """runs in a temporary folder (under /tmp in linux) without any .git/.hg/.svn file""" def get_extra_command_args(self, cwd): - return (' --repository %s ' % self._root) - - def testChromiumSample(self): - self.checkAllInFolder('./samples/chromium-sample', 1) - - def testVlcSample(self): - self.checkAllInFolder('./samples/vlc-sample', 1) - - def testSillySample(self): - self.checkAllInFolder('./samples/silly-sample', 5) - - def testBoostSample(self): - self.checkAllInFolder('./samples/boost-sample', 4) - + return f' --repository {self._root} ' + + def _test_name_func(fun, _, x): + del fun + return f'test{x.args[0].capitalize()}Sample-{x.args[1]}' + + @parameterized.expand([(folder, case[:-4]) + for folder in ['chromium', 'vlc', 'silly', + 'boost', 'protobuf', 'codelite', 'v8'] + for case in os.listdir(f'./samples/{folder}-sample') + if case.endswith('.def')], + name_func=_test_name_func) @mark.timeout(180) - def testProtobufSample(self): - self.checkAllInFolder('./samples/protobuf-sample', 1) - - def testCodeliteSample(self): - self.checkAllInFolder('./samples/codelite-sample', 1) + def testSamples(self, folder, case): + self.check_def(os.path.join(f'./samples/{folder}-sample', case + '.def')) - def testV8Sample(self): - self.checkAllInFolder('./samples/v8-sample', 1) class GitRepoSignatureTests(TemporaryFolderClassSetup, unittest.TestCase): """runs in a temporary folder with .git file""" @@ -210,7 +209,8 @@ def prepare_directory(cls, root): pass def testCodeliteSample(self): - self.checkAllInFolder('./samples/codelite-sample', 1) + self.check_all_in_folder('./samples/codelite-sample', 1) + class MercurialRepoSignatureTests(TemporaryFolderClassSetup, unittest.TestCase): """runs in a temporary folder with .hg file""" @@ -221,7 +221,8 @@ def prepare_directory(cls, root): pass def testCodeliteSample(self): - self.checkAllInFolder('./samples/codelite-sample', 1) + self.check_all_in_folder('./samples/codelite-sample', 1) + class SvnRepoSignatureTests(TemporaryFolderClassSetup, unittest.TestCase): """runs in a temporary folder with .svn file""" @@ -232,7 +233,8 @@ def prepare_directory(cls, root): pass def testCodeliteSample(self): - self.checkAllInFolder('./samples/codelite-sample', 1) + self.check_all_in_folder('./samples/codelite-sample', 1) + if __name__ == '__main__': unittest.main() From 3a5eaeef53d02a13a0e5aa3b0d17f513e321daab Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Mon, 3 Mar 2025 18:46:18 -0500 Subject: [PATCH 4/5] fix: revert Win32 slash replacement changes, use subprocess.run() Yep, I forgot the purpose of something I coded myself. subprocess.run() is Python's recommended replacement to popen that doesn't need context management --- cpplint_clitest.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/cpplint_clitest.py b/cpplint_clitest.py index 9cfc513..3da296c 100755 --- a/cpplint_clitest.py +++ b/cpplint_clitest.py @@ -54,24 +54,19 @@ def run_shell_command(cmd: str, args: str, cwd='.'): args: A string with arguments to the command. cwd: from which folder to run. """ - - stdout_target = subprocess.PIPE - stderr_target = subprocess.PIPE - - with subprocess.Popen(cmd + ' ' + args, - shell=True, - cwd=cwd, - stdout=stdout_target, - stderr=stderr_target) as proc: - out, err = proc.communicate() + cmd, args = cmd.split(), args.split() + proc = subprocess.run(cmd + args, cwd=cwd, capture_output=True) + out, err = proc.stdout, proc.stderr # Make output system-agnostic, aka support Windows if os.sep == '\\': - args_paths = args.split(' ') - for path in args_paths: - win_path = (os.path.dirname(path) + '\\').encode() - good_path = win_path.replace(b'\\', b'/') - out, err = out.replace(win_path, good_path), err.replace(win_path, good_path) + # TODO: Support scenario with multiple input names + # We currently only support the last arguments as the input name + # to prevent accidentally replacing sed tests. + # Fixing would likely need coding an internal "replace slashes" option for cpplint itself. + win_path = (os.path.dirname(args[-1]) + '\\').encode() + good_path = win_path.replace(b'\\', b'/') + out, err = out.replace(win_path, good_path), err.replace(win_path, good_path) if os.linesep == '\r\n': out, err = out.replace(b'\r\n', b'\n'), err.replace(b'\r\n', b'\n') From 218a71cff552481c597eff16880e0f38f268628c Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 8 Mar 2025 17:21:36 -0500 Subject: [PATCH 5/5] remove Python 2 keyword artifacts from pylint class-rgx --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7dd5b75..ce149bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -143,7 +143,7 @@ function-rgx = "[A-Z_][A-Za-z0-9]{2,49}$|main" const-rgx = "[a-zA-Z_][A-Za-z0-9_]{2,49}$" variable-rgx = "[a-z_][a-z0-9_]{0,49}$" argument-rgx = "[a-z_][a-z0-9_]{0,49}$" -class-rgx = "[A-Z_][a-zA-Z0-9]+$|basestring|unicode|long|xrange" +class-rgx = "[A-Z_][a-zA-Z0-9]+$" [tool.pylint.messages-control] disable = [