diff --git a/check_source/check_descriptions.py b/check_source/check_descriptions.py new file mode 100644 index 0000000000000000000000000000000000000000..35e986d25d7fd965714016551b800cdc69a816b0 --- /dev/null +++ b/check_source/check_descriptions.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 + +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Contributor(s): Campbell Barton +# +# #**** END GPL LICENSE BLOCK #**** + +# <pep8 compliant> + +""" +this script updates XML themes once new settings are added + + ./blender.bin --background -noaudio --python source/tools/check_source/check_descriptions.py +""" + +import bpy + +DUPLICATE_WHITELIST = ( + # operators + ('ACTION_OT_clean', 'GRAPH_OT_clean'), + ('ACTION_OT_clickselect', 'GRAPH_OT_clickselect'), + ('ACTION_OT_copy', 'GRAPH_OT_copy'), + ('ACTION_OT_delete', 'GRAPH_OT_delete'), + ('ACTION_OT_duplicate', 'GRAPH_OT_duplicate'), + ('ACTION_OT_duplicate_move', 'GRAPH_OT_duplicate_move'), + ('ACTION_OT_extrapolation_type', 'GRAPH_OT_extrapolation_type'), + ('ACTION_OT_handle_type', 'GRAPH_OT_handle_type'), + ('ACTION_OT_interpolation_type', 'GRAPH_OT_interpolation_type'), + ('ACTION_OT_keyframe_insert', 'GRAPH_OT_keyframe_insert'), + ('ACTION_OT_mirror', 'GRAPH_OT_mirror'), + ('ACTION_OT_paste', 'GRAPH_OT_paste'), + ('ACTION_OT_sample', 'GRAPH_OT_sample'), + ('ACTION_OT_select_all_toggle', 'GRAPH_OT_select_all_toggle'), + ('ACTION_OT_select_border', 'GRAPH_OT_select_border'), + ('ACTION_OT_select_column', 'GRAPH_OT_select_column'), + ('ACTION_OT_select_leftright', 'GRAPH_OT_select_leftright'), + ('ACTION_OT_select_less', 'GRAPH_OT_select_less'), + ('ACTION_OT_select_linked', 'GRAPH_OT_select_linked'), + ('ACTION_OT_select_more', 'GRAPH_OT_select_more'), + ('ACTION_OT_view_all', 'CLIP_OT_dopesheet_view_all', 'GRAPH_OT_view_all'), + ('ANIM_OT_change_frame', 'CLIP_OT_change_frame'), + ('ARMATURE_OT_armature_layers', 'POSE_OT_armature_layers'), + ('ARMATURE_OT_autoside_names', 'POSE_OT_autoside_names'), + ('ARMATURE_OT_bone_layers', 'POSE_OT_bone_layers'), + ('ARMATURE_OT_extrude_forked', 'ARMATURE_OT_extrude_move'), + ('ARMATURE_OT_select_all', 'POSE_OT_select_all'), + ('ARMATURE_OT_select_hierarchy', 'POSE_OT_select_hierarchy'), + ('ARMATURE_OT_select_linked', 'POSE_OT_select_linked'), + ('CLIP_OT_cursor_set', 'UV_OT_cursor_set'), + ('CLIP_OT_disable_markers', 'CLIP_OT_graph_disable_markers'), + ('CLIP_OT_graph_select_border', 'MASK_OT_select_border'), + ('CLIP_OT_view_ndof', 'IMAGE_OT_view_ndof'), + ('CLIP_OT_view_pan', 'IMAGE_OT_view_pan', 'VIEW2D_OT_pan', 'VIEW3D_OT_view_pan'), + ('CLIP_OT_view_zoom', 'VIEW2D_OT_zoom'), + ('CLIP_OT_view_zoom_in', 'VIEW2D_OT_zoom_in'), + ('CLIP_OT_view_zoom_out', 'VIEW2D_OT_zoom_out'), + ('CONSOLE_OT_copy', 'FONT_OT_text_copy', 'TEXT_OT_copy'), + ('CONSOLE_OT_delete', 'FONT_OT_delete', 'TEXT_OT_delete'), + ('CONSOLE_OT_insert', 'FONT_OT_text_insert', 'TEXT_OT_insert'), + ('CONSOLE_OT_paste', 'FONT_OT_text_paste', 'TEXT_OT_paste'), + ('CURVE_OT_duplicate', 'MASK_OT_duplicate'), + ('CURVE_OT_handle_type_set', 'MASK_OT_handle_type_set'), + ('CURVE_OT_switch_direction', 'MASK_OT_switch_direction'), + ('FONT_OT_line_break', 'TEXT_OT_line_break'), + ('FONT_OT_move', 'TEXT_OT_move'), + ('FONT_OT_move_select', 'TEXT_OT_move_select'), + ('FONT_OT_text_cut', 'TEXT_OT_cut'), + ('GRAPH_OT_properties', 'IMAGE_OT_properties', 'LOGIC_OT_properties', 'NLA_OT_properties'), + ('LATTICE_OT_select_ungrouped', 'MESH_OT_select_ungrouped', 'PAINT_OT_vert_select_ungrouped'), + ('NODE_OT_add_node', 'NODE_OT_add_search'), + ('NODE_OT_move_detach_links', 'NODE_OT_move_detach_links_release'), + ('NODE_OT_properties', 'VIEW3D_OT_properties'), + ('NODE_OT_toolbar', 'VIEW3D_OT_toolshelf'), + ('OBJECT_OT_duplicate_move', 'OBJECT_OT_duplicate_move_linked'), + ('WM_OT_context_cycle_enum', 'WM_OT_context_toggle', 'WM_OT_context_toggle_enum'), + ('WM_OT_context_set_boolean', 'WM_OT_context_set_enum', 'WM_OT_context_set_float', 'WM_OT_context_set_int', 'WM_OT_context_set_string', 'WM_OT_context_set_value'), + ) + +DUPLICATE_IGNORE = { + "", + } + + +def check_duplicates(): + import rna_info + + DUPLICATE_IGNORE_FOUND = set() + DUPLICATE_WHITELIST_FOUND = set() + + structs, funcs, ops, props = rna_info.BuildRNAInfo() + + # This is mainly useful for operators, + # other types have too many false positives + + #for t in (structs, funcs, ops, props): + for t in (ops, ): + description_dict = {} + print("") + for k, v in t.items(): + if v.description not in DUPLICATE_IGNORE: + id_str = ".".join([s if isinstance(s, str) else s.identifier for s in k if s]) + description_dict.setdefault(v.description, []).append(id_str) + else: + DUPLICATE_IGNORE_FOUND.add(v.description) + # sort for easier viewing + sort_ls = [(tuple(sorted(v)), k) for k, v in description_dict.items()] + sort_ls.sort() + + for v, k in sort_ls: + if len(v) > 1: + if v not in DUPLICATE_WHITELIST: + print("found %d: %r, \"%s\"" % (len(v), v, k)) + #print("%r," % (v,)) + else: + DUPLICATE_WHITELIST_FOUND.add(v) + + test = (DUPLICATE_IGNORE - DUPLICATE_IGNORE_FOUND) + if test: + print("Invalid 'DUPLICATE_IGNORE': %r" % test) + test = (set(DUPLICATE_WHITELIST) - DUPLICATE_WHITELIST_FOUND) + if test: + print("Invalid 'DUPLICATE_WHITELIST': %r" % test) + +def main(): + check_duplicates() + +if __name__ == "__main__": + main() diff --git a/check_source/check_spelling_c.py b/check_source/check_spelling_c.py new file mode 100755 index 0000000000000000000000000000000000000000..d6beec26dbb7b8ae37bc122c8937ba24c81aecb9 --- /dev/null +++ b/check_source/check_spelling_c.py @@ -0,0 +1,356 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +""" +Script for checking source code spelling. + + python3 source/tools/check_source/check_spelling_c.py some_soure_file.py + + +Currently only python source is checked. +""" + +import os +PRINT_QTC_TASKFORMAT = False +if "USE_QTC_TASK" in os.environ: + PRINT_QTC_TASKFORMAT = True + +ONLY_ONCE = True +USE_COLOR = True +_only_once_ids = set() + +if USE_COLOR: + COLOR_WORD = "\033[92m" + COLOR_ENDC = "\033[0m" +else: + COLOR_FAIL = "" + COLOR_ENDC = "" + + +import enchant +dict_spelling = enchant.Dict("en_US") + +from check_spelling_c_config import (dict_custom, + dict_ignore, + ) + + +def words_from_text(text): + """ Extract words to treat as English for spell checking. + """ + text = text.strip("#'\"") + text = text.replace("/", " ") + text = text.replace("-", " ") + text = text.replace("+", " ") + text = text.replace("%", " ") + text = text.replace(",", " ") + text = text.replace("=", " ") + text = text.replace("|", " ") + words = text.split() + + # filter words + words[:] = [w.strip("*?!:;.,'\"`") for w in words] + + def word_ok(w): + # check for empty string + if not w: + return False + + # ignore all uppercase words + if w.isupper(): + return False + + # check for string with no characters in it + is_alpha = False + for c in w: + if c.isalpha(): + is_alpha = True + break + if not is_alpha: + return False + + # check for prefix/suffix which render this not a real word + # example '--debug', '\n' + # TODO, add more + if w[0] in "%-+\\@": + return False + + # check for code in comments + for c in "<>{}[]():._0123456789\&*": + if c in w: + return False + + # check for words which contain lower case but have upper case + # ending chars eg - 'StructRNA', we can ignore these. + if len(w) > 1: + has_lower = False + for c in w: + if c.islower(): + has_lower = True + break + if has_lower and (not w[1:].islower()): + return False + + return True + words[:] = [w for w in words if word_ok(w)] + + # text = " ".join(words) + + # print(text) + return words + + +class Comment: + __slots__ = ("file", + "text", + "line", + "type", + ) + + def __init__(self, file, text, line, type): + self.file = file + self.text = text + self.line = line + self.type = type + + def parse(self): + return words_from_text(self.text) + + +def extract_py_comments(filepath): + + import token + import tokenize + + source = open(filepath, encoding='utf-8') + + comments = [] + + prev_toktype = token.INDENT + + tokgen = tokenize.generate_tokens(source.readline) + for toktype, ttext, (slineno, scol), (elineno, ecol), ltext in tokgen: + if toktype == token.STRING and prev_toktype == token.INDENT: + comments.append(Comment(filepath, ttext, slineno, 'DOCSTRING')) + elif toktype == tokenize.COMMENT: + # non standard hint for commented CODE that we can ignore + if not ttext.startswith("#~"): + comments.append(Comment(filepath, ttext, slineno, 'COMMENT')) + prev_toktype = toktype + return comments + + +def extract_c_comments(filepath): + """ + Extracts comments like this: + + /* + * This is a multi-line comment, notice the '*'s are aligned. + */ + """ + i = 0 + text = open(filepath, encoding='utf-8').read() + + BEGIN = "/*" + END = "*/" + TABSIZE = 4 + SINGLE_LINE = False + STRIP_DOXY = True + STRIP_DOXY_DIRECTIVES = ( + r"\section", + r"\subsection", + r"\subsubsection", + r"\ingroup", + r"\param", + r"\page", + ) + SKIP_COMMENTS = ( + "BEGIN GPL LICENSE BLOCK", + ) + + # http://doc.qt.nokia.com/qtcreator-2.4/creator-task-lists.html#task-list-file-format + # file\tline\ttype\tdescription + # ... > foobar.tasks + + # reverse these to find blocks we won't parse + PRINT_NON_ALIGNED = False + PRINT_SPELLING = True + + def strip_doxy_comments(block_split): + + for i, l in enumerate(block_split): + for directive in STRIP_DOXY_DIRECTIVES: + if directive in l: + l_split = l.split() + l_split[l_split.index(directive) + 1] = " " + l = " ".join(l_split) + del l_split + break + block_split[i] = l + + comments = [] + + while i >= 0: + i = text.find(BEGIN, i) + if i != -1: + i_next = text.find(END, i) + if i_next != -1: + + # not essential but seek ack to find beginning of line + while i > 0 and text[i - 1] in {"\t", " "}: + i -= 1 + + block = text[i:i_next + len(END)] + + # add whitespace in front of the block (for alignment test) + ws = [] + j = i + while j > 0 and text[j - 1] != "\n": + ws .append("\t" if text[j - 1] == "\t" else " ") + j -= 1 + ws.reverse() + block = "".join(ws) + block + + ok = True + + if not (SINGLE_LINE or ("\n" in block)): + ok = False + + if ok: + for c in SKIP_COMMENTS: + if c in block: + ok = False + break + + if ok: + # expand tabs + block_split = [l.expandtabs(TABSIZE) for l in block.split("\n")] + + # now validate that the block is aligned + align_vals = tuple(sorted(set([l.find("*") for l in block_split]))) + is_aligned = len(align_vals) == 1 + + if is_aligned: + if PRINT_SPELLING: + if STRIP_DOXY: + strip_doxy_comments(block_split) + + align = align_vals[0] + 1 + block = "\n".join([l[align:] for l in block_split])[:-len(END)] + + # now strip block and get text + # print(block) + + # ugh - not nice or fast + slineno = 1 + text.count("\n", 0, i) + + comments.append(Comment(filepath, block, slineno, 'COMMENT')) + else: + if PRINT_NON_ALIGNED: + lineno = 1 + text.count("\n", 0, i) + if PRINT_QTC_TASKFORMAT: + print("%s\t%d\t%s\t%s" % (filepath, lineno, "comment", align_vals)) + else: + print(filepath + ":" + str(lineno) + ":") + + i = i_next + else: + pass + + return comments + + +def spell_check_comments(filepath): + + if filepath.endswith(".py"): + comment_list = extract_py_comments(filepath) + else: + comment_list = extract_c_comments(filepath) + + for comment in comment_list: + for w in comment.parse(): + #if len(w) < 15: + # continue + + w_lower = w.lower() + if w_lower in dict_custom or w_lower in dict_ignore: + continue + + if not dict_spelling.check(w): + + if ONLY_ONCE: + if w_lower in _only_once_ids: + continue + else: + _only_once_ids.add(w_lower) + + if PRINT_QTC_TASKFORMAT: + print("%s\t%d\t%s\t%s, suggest (%s)" % + (comment.file, + comment.line, + "comment", + w, + " ".join(dict_spelling.suggest(w)), + )) + else: + print("%s:%d: %s%s%s, suggest (%s)" % + (comment.file, + comment.line, + COLOR_WORD, + w, + COLOR_ENDC, + " ".join(dict_spelling.suggest(w)), + )) + + +def spell_check_comments_recursive(dirpath): + from os.path import join, splitext + + def source_list(path, filename_check=None): + for dirpath, dirnames, filenames in os.walk(path): + + # skip '.svn' + if dirpath.startswith("."): + continue + + for filename in filenames: + filepath = join(dirpath, filename) + if filename_check is None or filename_check(filepath): + yield filepath + + def is_source(filename): + ext = splitext(filename)[1] + return (ext in {".c", ".inl", ".cpp", ".cxx", ".hpp", ".hxx", ".h", ".osl", ".py"}) + + for filepath in source_list(dirpath, is_source): + spell_check_comments(filepath) + + +import sys +import os + +if __name__ == "__main__": + for filepath in sys.argv[1:]: + if os.path.isdir(filepath): + # recursive search + spell_check_comments_recursive(filepath) + else: + # single file + spell_check_comments(filepath) diff --git a/check_source/check_spelling_c_config.py b/check_source/check_spelling_c_config.py new file mode 100644 index 0000000000000000000000000000000000000000..a051e5561c59fbaae58cf3982112f64e424dfae5 --- /dev/null +++ b/check_source/check_spelling_c_config.py @@ -0,0 +1,222 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +# these must be all lower case for comparisons + +# correct spelling but ignore +dict_custom = { + "instantiation", + "iterable", + "prepend", + "subclass", "subclasses", "subclassing", + "merchantability", + "precalculate", + "unregister", + "unselected", + "subdirectory", + "decrement", + "boolean", + "decrementing", + + # python types + "str", + "enum", "enums", + "int", "ints", + "tuple", "tuples", + + # python functions + "repr", + "func", + + # accepted abbreviations + "config", + "recalc", + "addon", "addons", + "subdir", + "struct", "structs", + "lookup", "lookups", + "autocomplete", + "namespace", + "multi", + "keyframe", "keyframing", + "coord", "coords", + "dir", + "tooltip", + + # general computer terms + "endian", + "contructor", + "unicode", + "jitter", + "quantized", + "searchable", + "metadata", + "hashable", + "stdin", + "stdout", + "stdin", + "opengl", + "boids", + "keymap", + "voxel", "voxels", + "vert", "verts", + "euler", "eulers", + "booleans", + "intrinsics", + "XXX", + "segfault", + "wiki", + "foo", + "diff", + "diffs", + "sudo", + "http", + "url", + "usr", + "env", + "app", + "preprocessor", + + # specific computer terms/brands + "posix", + "unix", + "amiga", + "netscape", + "mozilla", + "irix", + "kde", + "qtcreator", + "ack", + + # general computer graphics terms + "colinear", + "coplanar", + "barycentric", + "bezier", + "fresnel", + "radiosity", + "reflectance", + "specular", + "nurbs", + "ngon", "ngons", + "bicubic", + "compositing", + "deinterlace", + "shader", + "shaders", + "centroid", + "emissive", + "quaternions", + "lacunarity", + "musgrave", + "normals", + "kerning", + + # blender terms + "bmain", + "bmesh", + "bpy", + "bge", + "mathutils", + "fcurve", + "animviz", + "animsys", + "eekadoodle", + "editmode", + "obdata", + "doctree", + + # should have apostrophe but ignore for now + # unless we want to get really picky! + "indices", + "vertices", +} + +# incorrect spelling but ignore anyway +dict_ignore = { + "tri", + "quad", + "eg", + "ok", + "ui", + "uv", + "arg", "args", + "vec", + "loc", + "dof", + "bool", + "dupli", + "readonly", + "filepath", + "filepaths", + "filename", "filenames", + "submodule", "submodules", + "dirpath", + "x-axis", + "y-axis", + "z-axis", + "a-z", + "id-block", + "node-trees", + "pyflakes", + "pylint", + + # acronyms + "cpu", + "gpu", + "nan", + "utf", + "rgb", + "gzip", + "ppc", + "gpl", + "rna", + "nla", + "api", + "rhs", + "lhs", + "ik", + "smpte", + "svn", + "hg", + "gl", + + # extensions + "xpm", + "xml", + "py", + "rst", + + # tags + "fixme", + "todo", + + # sphinx/rst + "rtype", + + # slang + "hrmf", + "automagically", + + # names + "jahka", + "campbell", + "mikkelsen", "morten", +} diff --git a/check_source/check_style_c.py b/check_source/check_style_c.py new file mode 100755 index 0000000000000000000000000000000000000000..887c9dd6bdbb43571090dd9ea71de7b61463931c --- /dev/null +++ b/check_source/check_style_c.py @@ -0,0 +1,1079 @@ +#!/usr/bin/env python3 + +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Contributor(s): Campbell Barton +# +# #**** END GPL LICENSE BLOCK #**** + +# <pep8 compliant> + +""" +This script runs outside of blender and scans source + + python3 source/tools/check_source/check_source_c.py source/ +""" + +import os + +from check_style_c_config import IGNORE, IGNORE_DIR, SOURCE_DIR +IGNORE = tuple([os.path.join(SOURCE_DIR, ig) for ig in IGNORE]) +IGNORE_DIR = tuple([os.path.join(SOURCE_DIR, ig) for ig in IGNORE_DIR]) +WARN_TEXT = False + + +def is_ignore(f): + for ig in IGNORE: + if f == ig: + return True + for ig in IGNORE_DIR: + if f.startswith(ig): + return True + return False + +print("Scanning:", SOURCE_DIR) + +# TODO +# +# Add checks for: +# - macro brace use +# - line length - in a not-too-annoying way +# (allow for long arrays in struct definitions, PyMethodDef for eg) + +from pygments import lex # highlight +from pygments.lexers import CLexer +from pygments.formatters import RawTokenFormatter + +from pygments.token import Token + +import argparse + +PRINT_QTC_TASKFORMAT = False +if "USE_QTC_TASK" in os.environ: + PRINT_QTC_TASKFORMAT = True + +TAB_SIZE = 4 +LIN_SIZE = 120 + +global filepath +tokens = [] + + +# could store index here too, then have prev/next methods +class TokStore: + __slots__ = ("type", "text", "line") + + def __init__(self, type, text, line): + self.type = type + self.text = text + self.line = line + + +def tk_range_to_str(a, b, expand_tabs=False): + txt = "".join([tokens[i].text for i in range(a, b + 1)]) + if expand_tabs: + txt = txt.expandtabs(TAB_SIZE) + return txt + + +def tk_item_is_newline(tok): + return tok.type == Token.Text and tok.text.strip("\t ") == "\n" + + +def tk_item_is_ws_newline(tok): + return (tok.text == "") or \ + (tok.type == Token.Text and tok.text.isspace()) or \ + (tok.type in Token.Comment) + + +def tk_item_is_ws(tok): + return (tok.text == "") or \ + (tok.type == Token.Text and tok.text.strip("\t ") != "\n" and tok.text.isspace()) or \ + (tok.type in Token.Comment) + + +# also skips comments +def tk_advance_ws(index, direction): + while tk_item_is_ws(tokens[index + direction]) and index > 0: + index += direction + return index + + +def tk_advance_no_ws(index, direction): + index += direction + while tk_item_is_ws(tokens[index]) and index > 0: + index += direction + return index + + +def tk_advance_ws_newline(index, direction): + while tk_item_is_ws_newline(tokens[index + direction]) and index > 0: + index += direction + return index + direction + + +def tk_advance_line_start(index): + """ Go the the first non-whitespace token of the line. + """ + while tokens[index].line == tokens[index - 1].line and index > 0: + index -= 1 + return tk_advance_no_ws(index, 1) + + +def tk_advance_line(index, direction): + line = tokens[index].line + while tokens[index + direction].line == line or tokens[index].text == "\n": + index += direction + return index + + +def tk_match_backet(index): + backet_start = tokens[index].text + assert(tokens[index].type == Token.Punctuation) + assert(backet_start in "[]{}()") + + if tokens[index].text in "({[": + direction = 1 + backet_end = {"(": ")", "[": "]", "{": "}"}[backet_start] + else: + direction = -1 + backet_end = {")": "(", "]": "[", "}": "{"}[backet_start] + + level = 1 + index_match = index + direction + while True: + item = tokens[index_match] + if item.type == Token.Punctuation: + if item.text == backet_start: + level += 1 + elif item.text == backet_end: + level -= 1 + if level == 0: + break + + index_match += direction + + return index_match + + +def tk_index_is_linestart(index): + index_prev = tk_advance_ws_newline(index, -1) + return tokens[index_prev].line < tokens[index].line + + +def extract_to_linestart(index): + ls = [] + line = tokens[index].line + index -= 1 + while index > 0 and tokens[index].line == line: + ls.append(tokens[index].text) + index -= 1 + + if index != 0: + ls.append(tokens[index].text.rsplit("\n", 1)[1]) + + ls.reverse() + return "".join(ls) + + +def extract_statement_if(index_kw): + # assert(tokens[index_kw].text == "if") + + # seek back + i = index_kw + + i_start = tk_advance_ws(index_kw - 1, direction=-1) + + # seek forward + i_next = tk_advance_ws_newline(index_kw, direction=1) + + # print(tokens[i_next]) + + # ignore preprocessor + i_linestart = tk_advance_line_start(index_kw) + if tokens[i_linestart].text.startswith("#"): + return None + + if tokens[i_next].type != Token.Punctuation or tokens[i_next].text != "(": + warning("no '(' after '%s'" % tokens[index_kw].text, i_start, i_next) + return None + + i_end = tk_match_backet(i_next) + + return (i_start, i_end) + + +def extract_operator(index_op): + op_text = "" + i = 0 + while tokens[index_op + i].type == Token.Operator: + op_text += tokens[index_op + i].text + i += 1 + return op_text, index_op + (i - 1) + + +def extract_cast(index): + # to detect a cast is quite involved... sigh + # assert(tokens[index].text == "(") + + # TODO, comment within cast, but thats rare + i_start = index + i_end = tk_match_backet(index) + + # first check we are not '()' + if i_start + 1 == i_end: + return None + + # check we have punctuation before the cast + i = i_start - 1 + while tokens[i].text.isspace(): + i -= 1 + i_prev_no_ws = i + if tokens[i].type in {Token.Keyword, Token.Name}: + # avoids 'foo(bar)test' + # but not ' = (bar)test' + return None + + # validate types + tokens_cast = [tokens[i] for i in range(i_start + 1, i_end)] + for t in tokens_cast: + if t.type == Token.Keyword: + return None + elif t.type == Token.Operator and t.text != "*": + # prevent '(a + b)' + # note, we could have '(float(*)[1+2])' but this is unlikely + return None + elif t.type == Token.Punctuation and t.text not in '()[]': + # prevent '(a, b)' + return None + tokens_cast_strip = [] + for t in tokens_cast: + if t.type in Token.Comment: + pass + elif t.type == Token.Text and t.text.isspace(): + pass + else: + tokens_cast_strip.append(t) + # check token order and types + if not tokens_cast_strip: + return None + if tokens_cast_strip[0].type not in {Token.Name, Token.Type, Token.Keyword.Type}: + return None + t_prev = None + for t in tokens_cast_strip[1:]: + # prevent identifiers after the first: '(a b)' + if t.type in {Token.Keyword.Type, Token.Name, Token.Text}: + return None + # prevent: '(a * 4)' + # allow: '(a (*)[4])' + if t_prev is not None and t_prev.text == "*" and t.type != Token.Punctuation: + return None + t_prev = t + del t_prev + + # debug only + ''' + string = "".join(tokens[i].text for i in range(i_start, i_end + 1)) + #string = "".join(tokens[i].text for i in range(i_start + 1, i_end)) + #types = [tokens[i].type for i in range(i_start + 1, i_end)] + types = [t.type for t in tokens_cast_strip] + + print("STRING:", string) + print("TYPES: ", types) + print() + ''' + + return (i_start, i_end) + + +def warning(message, index_kw_start, index_kw_end): + if PRINT_QTC_TASKFORMAT: + print("%s\t%d\t%s\t%s" % (filepath, tokens[index_kw_start].line, "comment", message)) + else: + print("%s:%d: warning: %s" % (filepath, tokens[index_kw_start].line, message)) + if WARN_TEXT: + print(tk_range_to_str(index_kw_start, index_kw_end, expand_tabs=True)) + + +def warning_lineonly(message, line): + if PRINT_QTC_TASKFORMAT: + print("%s\t%d\t%s\t%s" % (filepath, line, "comment", message)) + else: + print("%s:%d: warning: %s" % (filepath, line, message)) + + # print(tk_range_to_str(index_kw_start, index_kw_end)) + + +# ------------------------------------------------------------------ +# Own Blender rules here! + +def blender_check_kw_if(index_kw_start, index_kw, index_kw_end): + + # check if we have: 'if(' + if not tk_item_is_ws(tokens[index_kw + 1]): + warning("no white space between '%s('" % tokens[index_kw].text, index_kw_start, index_kw_end) + + # check for: ){ + index_next = tk_advance_ws_newline(index_kw_end, 1) + if tokens[index_next].type == Token.Punctuation and tokens[index_next].text == "{": + if not tk_item_is_ws(tokens[index_next - 1]): + warning("no white space between trailing bracket '%s (){'" % tokens[index_kw].text, index_kw_start, index_kw_end) + + # check for: if () + # { + # note: if the if statement is multi-line we allow it + if ((tokens[index_kw].line == tokens[index_kw_end].line) and + (tokens[index_kw].line == tokens[index_next].line - 1)): + + warning("if body brace on a new line '%s ()\\n{'" % tokens[index_kw].text, index_kw, index_kw_end) + else: + # no '{' on a multi-line if + if tokens[index_kw].line != tokens[index_kw_end].line: + warning("multi-line if should use a brace '%s (\\n\\n) statement;'" % tokens[index_kw].text, index_kw, index_kw_end) + + # check for: if (a && + # b) { ... + # brace should be on a newline. + if (tokens[index_kw].line != tokens[index_kw_end].line): + if tokens[index_kw_end].line == tokens[index_next].line: + warning("multi-line should use a on a new line '%s (\\n\\n) {'" % tokens[index_kw].text, index_kw, index_kw_end) + + # check for: if () { ... }; + # + # no need to have semicolon after brace. + if tokens[index_next].text == "{": + index_final = tk_match_backet(index_next) + index_final_step = tk_advance_no_ws(index_final, 1) + if tokens[index_final_step].text == ";": + warning("semi-colon after brace '%s () { ... };'" % tokens[index_kw].text, index_final_step, index_final_step) + + +def blender_check_kw_else(index_kw): + # for 'else if' use the if check. + i_next = tk_advance_ws_newline(index_kw, 1) + + # check there is at least one space between: + # else{ + if index_kw + 1 == i_next: + warning("else has no space between following brace 'else{'", index_kw, i_next) + + # check if there are more than 1 spaces after else, but nothing after the following brace + # else { + # ... + # + # check for this case since this is needed sometimes: + # else { a = 1; } + if ((tokens[index_kw].line == tokens[i_next].line) and + (tokens[index_kw + 1].type == Token.Text) and + (len(tokens[index_kw + 1].text) > 1) and + (tokens[index_kw + 1].text.isspace())): + + # check if the next data after { is on a newline + i_next_next = tk_advance_ws_newline(i_next, 1) + if tokens[i_next].line != tokens[i_next_next].line: + warning("unneeded whitespace before brace 'else ... {'", index_kw, i_next) + + # this check only tests for: + # else + # { + # ... which is never OK + # + # ... except if you have + # else + # #preprocessor + # { + + if tokens[i_next].type == Token.Punctuation and tokens[i_next].text == "{": + if tokens[index_kw].line < tokens[i_next].line: + # check for preproc + i_newline = tk_advance_line(index_kw, 1) + if tokens[i_newline].text.startswith("#"): + pass + else: + warning("else body brace on a new line 'else\\n{'", index_kw, i_next) + + # this check only tests for: + # else + # if + # ... which is never OK + if tokens[i_next].type == Token.Keyword and tokens[i_next].text == "if": + if tokens[index_kw].line < tokens[i_next].line: + warning("else if is split by a new line 'else\\nif'", index_kw, i_next) + + # check + # } else + # ... which is never OK + i_prev = tk_advance_no_ws(index_kw, -1) + if tokens[i_prev].type == Token.Punctuation and tokens[i_prev].text == "}": + if tokens[index_kw].line == tokens[i_prev].line: + warning("else has no newline before the brace '} else'", i_prev, index_kw) + + +def blender_check_kw_switch(index_kw_start, index_kw, index_kw_end): + # In this function we check the body of the switch + + # switch (value) { + # ... + # } + + # assert(tokens[index_kw].text == "switch") + + index_next = tk_advance_ws_newline(index_kw_end, 1) + + if tokens[index_next].type == Token.Punctuation and tokens[index_next].text == "{": + ws_switch_indent = extract_to_linestart(index_kw) + + if ws_switch_indent.isspace(): + + # 'case' should have at least 1 indent. + # otherwise expect 2 indent (or more, for nested switches) + ws_test = { + "case": ws_switch_indent + "\t", + "default:": ws_switch_indent + "\t", + + "break": ws_switch_indent + "\t\t", + "return": ws_switch_indent + "\t\t", + "continue": ws_switch_indent + "\t\t", + "goto": ws_switch_indent + "\t\t", + } + + index_final = tk_match_backet(index_next) + + case_ls = [] + + for i in range(index_next + 1, index_final): + # 'default' is seen as a label + # print(tokens[i].type, tokens[i].text) + if tokens[i].type in {Token.Keyword, Token.Name.Label}: + if tokens[i].text in {"case", "default:", "break", "return", "comtinue", "goto"}: + ws_other_indent = extract_to_linestart(i) + # non ws start - we ignore for now, allow case A: case B: ... + if ws_other_indent.isspace(): + ws_test_other = ws_test[tokens[i].text] + if not ws_other_indent.startswith(ws_test_other): + warning("%s is not indented enough" % tokens[i].text, i, i) + + # assumes correct indentation... + if tokens[i].text in {"case", "default:"}: + if ws_other_indent == ws_test_other: + case_ls.append(i) + + case_ls.append(index_final - 1) + + # detect correct use of break/return + for j in range(len(case_ls) - 1): + i_case = case_ls[j] + i_end = case_ls[j + 1] + + # detect cascading cases, check there is one line inbetween at least + if tokens[i_case].line + 1 < tokens[i_end].line: + ok = False + + # scan case body backwards + for i in reversed(range(i_case, i_end)): + if tokens[i].type == Token.Punctuation: + if tokens[i].text == "}": + ws_other_indent = extract_to_linestart(i) + if ws_other_indent != ws_test["case"]: + # break/return _not_ found + break + + elif tokens[i].type in Token.Comment: + if tokens[i].text == "/* fall-through */": + ok = True + break + else: + #~ print("Commment '%s'" % tokens[i].text) + pass + + + elif tokens[i].type == Token.Keyword: + if tokens[i].text in {"break", "return", "continue", "goto"}: + if tokens[i_case].line == tokens[i].line: + # Allow for... + # case BLAH: var = 1; break; + # ... possible there is if statements etc, but assume not + ok = True + break + else: + ws_other_indent = extract_to_linestart(i) + ws_other_indent = ws_other_indent[:len(ws_other_indent) - len(ws_other_indent.lstrip())] + ws_test_other = ws_test[tokens[i].text] + if ws_other_indent == ws_test_other: + ok = True + break + else: + pass + #~ print("indent mismatch...") + #~ print("'%s'" % ws_other_indent) + #~ print("'%s'" % ws_test_other) + if not ok: + warning("case/default statement has no break", i_case, i_end) + #~ print(tk_range_to_str(i_case - 1, i_end - 1, expand_tabs=True)) + else: + warning("switch isn't the first token in the line", index_kw_start, index_kw_end) + else: + warning("switch brace missing", index_kw_start, index_kw_end) + + +def blender_check_kw_sizeof(index_kw): + if tokens[index_kw + 1].text != "(": + warning("expected '%s('" % tokens[index_kw].text, index_kw, index_kw + 1) + + +def blender_check_cast(index_kw_start, index_kw_end): + # detect: '( float...' + if tokens[index_kw_start + 1].text.isspace(): + warning("cast has space after first bracket '( type...'", index_kw_start, index_kw_end) + # detect: '...float )' + if tokens[index_kw_end - 1].text.isspace(): + warning("cast has space before last bracket '... )'", index_kw_start, index_kw_end) + # detect no space before operator: '(float*)' + + for i in range(index_kw_start + 1, index_kw_end): + if tokens[i].text == "*": + # allow: '(*)' + if tokens[i - 1].type == Token.Punctuation: + pass + elif tokens[i - 1].text.isspace(): + pass + else: + warning("cast has no preceeding whitespace '(type*)'", index_kw_start, index_kw_end) + + +def blender_check_comma(index_kw): + i_next = tk_advance_ws_newline(index_kw, 1) + + # check there is at least one space between: + # ,sometext + if index_kw + 1 == i_next: + warning("comma has no space after it ',sometext'", index_kw, i_next) + + if tokens[index_kw - 1].type == Token.Text and tokens[index_kw - 1].text.isspace(): + warning("comma space before it 'sometext ,", index_kw, i_next) + + +def blender_check_period(index_kw): + # check we're now apart of ... + if (tokens[index_kw - 1].text == ".") or (tokens[index_kw + 1].text == "."): + return + + # 'a.b' + if tokens[index_kw - 1].type == Token.Text and tokens[index_kw - 1].text.isspace(): + warning("period space before it 'sometext .", index_kw, index_kw) + if tokens[index_kw + 1].type == Token.Text and tokens[index_kw + 1].text.isspace(): + warning("period space after it '. sometext", index_kw, index_kw) + + +def _is_ws_pad(index_start, index_end): + return (tokens[index_start - 1].text.isspace() and + tokens[index_end + 1].text.isspace()) + + +def blender_check_operator(index_start, index_end, op_text, is_cpp): + if op_text == "->": + # allow compiler to handle + return + + if len(op_text) == 1: + if op_text in {"+", "-"}: + # detect (-a) vs (a - b) + if (not tokens[index_start - 1].text.isspace() and + tokens[index_start - 1].text not in {"[", "(", "{"}): + warning("no space before operator '%s'" % op_text, index_start, index_end) + if (not tokens[index_end + 1].text.isspace() and + tokens[index_end + 1].text not in {"]", ")", "}"}): + # TODO, needs work to be useful + # warning("no space after operator '%s'" % op_text, index_start, index_end) + pass + + elif op_text in {"/", "%", "^", "|", "=", "<", ">"}: + if not _is_ws_pad(index_start, index_end): + if not (is_cpp and ("<" in op_text or ">" in op_text)): + warning("no space around operator '%s'" % op_text, index_start, index_end) + elif op_text == "&": + pass # TODO, check if this is a pointer reference or not + elif op_text == "*": + # This check could be improved, its a bit fuzzy + if ((tokens[index_start - 1].type in Token.Number) or + (tokens[index_start + 1].type in Token.Number)): + warning("no space around operator '%s'" % op_text, index_start, index_end) + elif not (tokens[index_start - 1].text.isspace() or tokens[index_start - 1].text in {"(", "[", "{"}): + warning("no space before operator '%s'" % op_text, index_start, index_end) + elif len(op_text) == 2: + # todo, remove operator check from `if` + if op_text in {"+=", "-=", "*=", "/=", "&=", "|=", "^=", + "&&", "||", + "==", "!=", "<=", ">=", + "<<", ">>", + "%=", + # not operators, pointer mix-ins + ">*", "<*", "-*", "+*", "=*", "/*", "%*", "^*", "|*", + }: + if not _is_ws_pad(index_start, index_end): + if not (is_cpp and ("<" in op_text or ">" in op_text)): + warning("no space around operator '%s'" % op_text, index_start, index_end) + + elif op_text in {"++", "--"}: + pass # TODO, figure out the side we are adding to! + ''' + if (tokens[index_start - 1].text.isspace() or + tokens[index_end + 1].text.isspace()): + warning("spaces surrounding operator '%s'" % op_text, index_start, index_end) + ''' + elif op_text in {"!!", "!*"}: + # operators we _dont_ want whitespace after (pointers mainly) + # we can assume these are pointers + if tokens[index_end + 1].text.isspace(): + warning("spaces after operator '%s'" % op_text, index_start, index_end) + + elif op_text == "**": + pass # handle below + elif op_text == "::": + pass # C++, ignore for now + elif op_text == ":!*": + pass # ignore for now + elif op_text == "*>": + pass # ignore for now, C++ <Class *> + else: + warning("unhandled operator A '%s'" % op_text, index_start, index_end) + else: + #warning("unhandled operator B '%s'" % op_text, index_start, index_end) + pass + + if len(op_text) > 1: + if op_text[0] == "*" and op_text[-1] == "*": + if ((not tokens[index_start - 1].text.isspace()) and + (not tokens[index_start - 1].type == Token.Punctuation)): + warning("no space before pointer operator '%s'" % op_text, index_start, index_end) + if tokens[index_end + 1].text.isspace(): + warning("space before pointer operator '%s'" % op_text, index_start, index_end) + + # check if we are first in the line + if op_text[0] == "!": + # if (a && + # !b) + pass + elif op_text[0] == "*" and tokens[index_start + 1].text.isspace() is False: + pass # *a = b + elif len(op_text) == 1 and op_text[0] == "-" and tokens[index_start + 1].text.isspace() is False: + pass # -1 + elif len(op_text) == 2 and op_text == "++" and tokens[index_start + 1].text.isspace() is False: + pass # ++a + elif len(op_text) == 2 and op_text == "--" and tokens[index_start + 1].text.isspace() is False: + pass # --a + elif len(op_text) == 1 and op_text[0] == "&": + # if (a && + # &b) + pass + elif len(op_text) == 1 and op_text[0] == "~": + # C++ + # ~ClassName + pass + elif len(op_text) == 1 and op_text[0] == "?": + # (a == b) + # ? c : d + pass + elif len(op_text) == 1 and op_text[0] == ":": + # a = b ? c + # : d + pass + else: + if tk_index_is_linestart(index_start): + warning("operator starts a new line '%s'" % op_text, index_start, index_end) + + +def blender_check_linelength(index_start, index_end, length): + if length > LIN_SIZE: + text = tk_range_to_str(index_start, index_end, expand_tabs=True) + for l in text.split("\n"): + if len(l) > LIN_SIZE: + warning("line length %d > %d" % (len(l), LIN_SIZE), index_start, index_end) + + +def blender_check_function_definition(i): + # Warning, this is a fairly slow check and guesses + # based on some fuzzy rules + + # assert(tokens[index].text == "{") + + # check function declaration is not: + # 'void myfunc() {' + # ... other uses are handled by checks for statements + # this check is rather simplistic but tends to work well enough. + + i_prev = i - 1 + while tokens[i_prev].text == "": + i_prev -= 1 + + # ensure this isnt '{' in its own line + if tokens[i_prev].line == tokens[i].line: + + # check we '}' isnt on same line... + i_next = i + 1 + found = False + while tokens[i_next].line == tokens[i].line: + if tokens[i_next].text == "}": + found = True + break + i_next += 1 + del i_next + + if found is False: + + # First check this isnt an assignment + i_prev = tk_advance_no_ws(i, -1) + # avoid '= {' + #if tokens(index_prev).text != "=" + # print(tokens[i_prev].text) + # allow: + # - 'func()[] {' + # - 'func() {' + + if tokens[i_prev].text in {")", "]"}: + i_prev = i - 1 + while tokens[i_prev].line == tokens[i].line: + i_prev -= 1 + split = tokens[i_prev].text.rsplit("\n", 1) + if len(split) > 1 and split[-1] != "": + split_line = split[-1] + else: + split_line = tokens[i_prev + 1].text + + if split_line and split_line[0].isspace(): + pass + else: + # no whitespace! + i_begin = i_prev + 1 + + # skip blank + if tokens[i_begin].text == "": + i_begin += 1 + # skip static + if tokens[i_begin].text == "static": + i_begin += 1 + while tokens[i_begin].text.isspace(): + i_begin += 1 + # now we are done skipping stuff + + warning("function's '{' must be on a newline", i_begin, i) + + +def blender_check_brace_indent(i): + # assert(tokens[index].text == "{") + + i_match = tk_match_backet(i) + + if tokens[i].line != tokens[i_match].line: + ws_i_match = extract_to_linestart(i_match) + + # allow for... + # a[] = {1, 2, + # 3, 4} + # ... so only check braces which are the first text + if ws_i_match.isspace(): + ws_i = extract_to_linestart(i) + ws_i_match_lstrip = ws_i_match.lstrip() + + ws_i = ws_i[:len(ws_i) - len(ws_i.lstrip())] + ws_i_match = ws_i_match[:len(ws_i_match) - len(ws_i_match_lstrip)] + if ws_i != ws_i_match: + warning("indentation '{' does not match brace", i, i_match) + + +def quick_check_indentation(lines): + """ + Quick check for multiple tab indents. + """ + t_prev = -1 + m_comment_prev = False + ls_prev = "" + + for i, l in enumerate(lines): + skip = False + + # skip blank lines + ls = l.strip() + + # comment or pre-processor + if ls: + # #ifdef ... or ... // comment + if (ls[0] == "#" or ls[0:2] == "//"): + skip = True + # label: + elif (':' in ls and l[0] != '\t'): + skip = True + # /* comment */ + #~ elif ls.startswith("/*") and ls.endswith("*/"): + #~ skip = True + # /* some comment... + elif ls.startswith("/*"): + skip = True + # line ending a comment: */ + elif ls == "*/": + skip = True + # * middle of multi line comment block + elif ls.startswith("* "): + skip = True + # exclude muli-line defines + elif ls.endswith("\\") or ls.endswith("(void)0") or ls_prev.endswith("\\"): + skip = True + + ls_prev = ls + + if skip: + continue + + if ls: + ls = l.lstrip("\t") + tabs = l[:len(l) - len(ls)] + t = len(tabs) + if (t > t_prev + 1) and (t_prev != -1): + warning_lineonly("indentation mis-match (indent of %d) '%s'" % (t - t_prev, tabs), i + 1) + t_prev = t + +import re +re_ifndef = re.compile("^\s*#\s*ifndef\s+([A-z0-9_]+).*$") +re_define = re.compile("^\s*#\s*define\s+([A-z0-9_]+).*$") + +def quick_check_include_guard(lines): + found = 0 + def_value = "" + ok = False + + def fn_as_guard(fn): + name = os.path.basename(fn).upper().replace(".", "_").replace("-", "_") + return "__%s__" % name + + for i, l in enumerate(lines): + ndef_match = re_ifndef.match(l) + if ndef_match: + ndef_value = ndef_match.group(1).strip() + for j in range(i + 1, len(lines)): + l_next = lines[j] + def_match = re_define.match(l_next) + if def_match: + def_value = def_match.group(1).strip() + if def_value == ndef_value: + ok = True + break + elif l_next.strip(): + # print(filepath) + # found non empty non ndef line. quit + break + else: + # allow blank lines + pass + break + + guard = fn_as_guard(filepath) + + if ok: + # print("found:", def_value, "->", filepath) + if def_value != guard: + # print("%s: %s -> %s" % (filepath, def_value, guard)) + warning_lineonly("non-conforming include guard (found %r, expected %r)" % (def_value, guard), i + 1) + else: + warning_lineonly("missing include guard %r" % guard, 1) + +def quick_check_source(fp, code, args): + + global filepath + + is_header = fp.endswith((".h", ".hxx", ".hpp")) + + filepath = fp + + lines = code.split("\n") + + if is_header: + quick_check_include_guard(lines) + + quick_check_indentation(lines) + +def scan_source(fp, code, args): + # print("scanning: %r" % fp) + + global filepath + + is_cpp = fp.endswith((".cpp", ".cxx")) + + filepath = fp + + #if "displist.c" not in filepath: + # return + + filepath_base = os.path.basename(filepath) + + #print(highlight(code, CLexer(), RawTokenFormatter()).decode('utf-8')) + + del tokens[:] + line = 1 + + for ttype, text in lex(code, CLexer()): + if text: + tokens.append(TokStore(ttype, text, line)) + line += text.count("\n") + + col = 0 # track line length + index_line_start = 0 + + for i, tok in enumerate(tokens): + #print(tok.type, tok.text) + if tok.type == Token.Keyword: + if tok.text in {"switch", "while", "if", "for"}: + item_range = extract_statement_if(i) + if item_range is not None: + blender_check_kw_if(item_range[0], i, item_range[1]) + if tok.text == "switch": + blender_check_kw_switch(item_range[0], i, item_range[1]) + elif tok.text == "else": + blender_check_kw_else(i) + elif tok.text == "sizeof": + blender_check_kw_sizeof(i) + elif tok.type == Token.Punctuation: + if tok.text == ",": + blender_check_comma(i) + elif tok.text == ".": + blender_check_period(i) + elif tok.text == "[": + # note, we're quite relaxed about this but + # disallow 'foo [' + if tokens[i - 1].text.isspace(): + if is_cpp and tokens[i + 1].text == "]": + # c++ can do delete [] + pass + else: + warning("space before '['", i, i) + elif tok.text == "(": + # check if this is a cast, eg: + # (char), (char **), (float (*)[3]) + item_range = extract_cast(i) + if item_range is not None: + blender_check_cast(item_range[0], item_range[1]) + elif tok.text == "{": + # check matching brace is indented correctly (slow!) + blender_check_brace_indent(i) + + # check previous character is either a '{' or whitespace. + if (tokens[i - 1].line == tok.line) and not (tokens[i - 1].text.isspace() or tokens[i - 1].text == "{"): + warning("no space before '{'", i, i) + + blender_check_function_definition(i) + + elif tok.type == Token.Operator: + # we check these in pairs, only want first + if tokens[i - 1].type != Token.Operator: + op, index_kw_end = extract_operator(i) + blender_check_operator(i, index_kw_end, op, is_cpp) + elif tok.type in Token.Comment: + doxyfn = None + if "\\file" in tok.text: + doxyfn = tok.text.split("\\file", 1)[1].strip().split()[0] + elif "@file" in tok.text: + doxyfn = tok.text.split("@file", 1)[1].strip().split()[0] + + if doxyfn is not None: + doxyfn_base = os.path.basename(doxyfn) + if doxyfn_base != filepath_base: + warning("doxygen filename mismatch %s != %s" % (doxyfn_base, filepath_base), i, i) + + # ensure line length + if (not args.no_length_check) and tok.type == Token.Text and tok.text == "\n": + # check line len + blender_check_linelength(index_line_start, i - 1, col) + + col = 0 + index_line_start = i + 1 + else: + col += len(tok.text.expandtabs(TAB_SIZE)) + + #elif tok.type == Token.Name: + # print(tok.text) + + #print(ttype, type(ttype)) + #print((ttype, value)) + + #for ttype, value in la: + # #print(value, end="") + + +def scan_source_filepath(filepath, args): + # for quick tests + #~ if not filepath.endswith("creator.c"): + #~ return + + code = open(filepath, 'r', encoding="utf-8").read() + + # fast checks which don't require full parsing + quick_check_source(filepath, code, args) + + # use lexer + scan_source(filepath, code, args) + + +def scan_source_recursive(dirpath, args): + import os + from os.path import join, splitext + + def source_list(path, filename_check=None): + for dirpath, dirnames, filenames in os.walk(path): + + # skip '.svn' + if dirpath.startswith("."): + continue + + for filename in filenames: + filepath = join(dirpath, filename) + if filename_check is None or filename_check(filepath): + yield filepath + + def is_source(filename): + ext = splitext(filename)[1] + return (ext in {".c", ".inl", ".cpp", ".cxx", ".hpp", ".hxx", ".h", ".osl"}) + + for filepath in sorted(source_list(dirpath, is_source)): + if is_ignore(filepath): + continue + + scan_source_filepath(filepath, args) + + +if __name__ == "__main__": + import sys + import os + + desc = 'Check C/C++ code for conformance with blenders style guide:\nhttp://wiki.blender.org/index.php/Dev:Doc/CodeStyle)' + parser = argparse.ArgumentParser(description=desc) + parser.add_argument("paths", nargs='+', help="list of files or directories to check") + parser.add_argument("-l", "--no-length-check", action="store_true", + help="skip warnings for long lines") + args = parser.parse_args() + + if 0: + SOURCE_DIR = os.path.normpath(os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..")))) + #scan_source_recursive(os.path.join(SOURCE_DIR, "source", "blender", "bmesh")) + scan_source_recursive(os.path.join(SOURCE_DIR, "source/blender/makesrna/intern"), args) + sys.exit(0) + + for filepath in args.paths: + if os.path.isdir(filepath): + # recursive search + scan_source_recursive(filepath, args) + else: + # single file + scan_source_filepath(filepath, args) diff --git a/check_source/check_style_c_config.py b/check_source/check_style_c_config.py new file mode 100644 index 0000000000000000000000000000000000000000..762c41b600e163a8c662bd44abcdec080fd1c6f1 --- /dev/null +++ b/check_source/check_style_c_config.py @@ -0,0 +1,46 @@ +import os + +IGNORE = ( + + # particles + "source/blender/blenkernel/intern/boids.c", + "source/blender/blenkernel/intern/cloth.c", + "source/blender/blenkernel/intern/collision.c", + "source/blender/blenkernel/intern/effect.c", + "source/blender/blenkernel/intern/implicit.c", + "source/blender/blenkernel/intern/particle.c", + "source/blender/blenkernel/intern/particle_system.c", + "source/blender/blenkernel/intern/pointcache.c", + "source/blender/blenkernel/intern/sca.c", + "source/blender/blenkernel/intern/softbody.c", + "source/blender/blenkernel/intern/smoke.c", + + "source/blender/blenlib/intern/fnmatch.c", + "source/blender/blenlib/intern/md5.c", + "source/blender/blenlib/intern/voxel.c", + + "source/blender/blenloader/intern/readfile.c", + "source/blender/blenloader/intern/versioning_250.c", + "source/blender/blenloader/intern/versioning_legacy.c", + "source/blender/blenloader/intern/writefile.c", + + "source/blender/editors/space_logic/logic_buttons.c", + "source/blender/editors/space_logic/logic_window.c", + + "source/blender/imbuf/intern/dds/DirectDrawSurface.cpp", + + "source/blender/opencl/intern/clew.c", + "source/blender/opencl/intern/clew.h", + ) + +IGNORE_DIR = ( + "source/blender/collada", + "source/blender/render", + "source/blender/editors/physics", + "source/blender/editors/space_logic", + "source/blender/freestyle", + "source/blender/gpu", + ) + + +SOURCE_DIR = os.path.normpath(os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))))