├── .gitattributes ├── .gitignore ├── .no-sublime-package ├── .sublimelinterrc ├── .travis.yml ├── LICENSE ├── README.md ├── linter.py ├── messages.json ├── messages └── install.txt └── scripts ├── cppcheck-misra ├── cppcheck-misra-parsetexts.py └── install_cppcheck.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sh text eol=lf 2 | cppcheck-misra text eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS generated files # 2 | .DS_Store 3 | .DS_Store? 4 | .Spotlight-V100 5 | .Trashes 6 | ehthumbs.db 7 | Thumbs.db 8 | 9 | # Byte-compiled / optimized / DLL files 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # IDE files/folders 15 | .venv 16 | .vscode 17 | -------------------------------------------------------------------------------- /.no-sublime-package: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reobos/SublimeLinter-cppcheck-misra/4eac3c4d19543f9f7ecfc03f63923485f2faab1c/.no-sublime-package -------------------------------------------------------------------------------- /.sublimelinterrc: -------------------------------------------------------------------------------- 1 | { 2 | "@python": 3, 3 | "linters": { 4 | "flake8": { 5 | "max-line-length": 120 6 | }, 7 | "pep257": { 8 | "add-ignore": ["D202"] 9 | }, 10 | "pep8": { 11 | "max-line-length": 120 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | # command to install dependencies 5 | install: 6 | - pip install flake8 7 | - pip install pydocstyle 8 | # command to run tests 9 | script: 10 | - flake8 . --max-line-length=120 11 | - pydocstyle . --add-ignore=D202 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in 9 | all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 17 | THE SOFTWARE. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SublimeLinter-cppcheck-misra 2 | 3 | [![Build Status](https://travis-ci.org/ChisholmKyle/SublimeLinter-cppcheck-misra.svg?branch=master)](https://travis-ci.org/ChisholmKyle/SublimeLinter-cppcheck-misra) 4 | 5 | This linter plugin for [SublimeLinter](https://github.com/SublimeLinter/SublimeLinter) provides an interface to [cppcheck](https://github.com/danmar/cppcheck) with the development-branch [misra.py addon](https://github.com/danmar/cppcheck/tree/master/addons). It will be used with files that have the “c” syntax. 6 | 7 | ## Installation 8 | 9 | SublimeLinter must be installed in order to use this plugin. 10 | 11 | Please use [Package Control](https://packagecontrol.io) to install the linter plugin. 12 | 13 | ### Install Python 3.x 14 | 15 | On Ubuntu/Debian for example: 16 | 17 | sudo apt install -y python3 18 | 19 | ### Install `cppcheck` with `misra.py` addon 20 | 21 | Before using this plugin, you must have a version of `cppcheck` which also includes the [misra.py addon](https://github.com/danmar/cppcheck/tree/master/addons). 22 | 23 | On Ubuntu/Debian for example: 24 | 25 | sudo apt install -y cppcheck 26 | 27 | Once you have cppcheck installed, you may need to manually download the misra.py addon. First check the version of ccpcheck: 28 | 29 | cppcheck --version 30 | 31 | Then download the misra.py addon for the printed version (``). For a Linux installation, you may want to use `wget` to save the file as follows: 32 | 33 | ```sh 34 | wget https://raw.githubusercontent.com/danmar/cppcheck//addons/misra.py 35 | sudo mkdir -p /usr/local/share/CppCheck/addons 36 | sudo cp -f misra.py /usr/local/share/CppCheck/addons/misra.py 37 | ``` 38 | 39 | ### Generate texts from MISRA C:2012 guidelines 40 | 41 | Due to MISRA rules, only rule check numbers are allowed in free and open source software so you need to supply your own set of texts for each rule. If you have a pdf of MISRA C:2012 guidelines, the Python 3.x script [`scripts/cppcheck-misra-parsetexts.py`](scripts/cppcheck-misra-parsetexts.py) generates the rules text file from Appendix A (Summary of guidelines). 42 | 43 | Generate rules text: 44 | 45 | ```sh 46 | python3 scripts/cppcheck-misra-parsetexts.py /path/to/MISRA_C_2012.pdf 47 | ``` 48 | 49 | Now 'MISRA_C_2012_Rules.txt' should be in the '/path/to/' directory. Examine the output file for errors (there are a few parsing issues reading the PDF). 50 | 51 | ### Make sure your settings reflect installed file locations 52 | 53 | In your project settings, set 54 | 55 | ```json 56 | "settings": { 57 | "SublimeLinter.linters.cppcheck-misra.misra-addon": "/usr/local/share/CppCheck/addons/misra.py", 58 | "SublimeLinter.linters.cppcheck-misra.rule-texts": "/path/to/MISRA_C_2012_Rules.txt" 59 | } 60 | ``` 61 | 62 | ## Settings 63 | 64 | - SublimeLinter settings: http://sublimelinter.readthedocs.org/en/latest/settings.html 65 | - Linter settings: http://sublimelinter.readthedocs.org/en/latest/linter_settings.html 66 | 67 | Additional SublimeLinter-cppcheck-misra settings: 68 | 69 | |Setting|Description| 70 | |:------|:----------| 71 | |misra_addon|(Required) The misra.py addon file| 72 | |rule_texts|(Recommended) A file of descriptions of MISRA rules| 73 | |suppress_rules|(Optional) List of rules to ignore| 74 | |cppcheck-opts|(Optional) Forward option to cppcheck command. Default is `"--max-configs=1"`| 75 | |cppcheck_path|(Optional) Add search paths to find cppcheck when install location not in default PATH| 76 | 77 | In project-specific settings, note that SublimeLinter allows [expansion variables](http://sublimelinter.readthedocs.io/en/latest/settings.html#settings-expansion). For example, the variable '${project_path}' can be used to specify a path relative to the project folder. Example settings: 78 | 79 | ```json 80 | "settings": { 81 | "SublimeLinter.linters.cppcheck-misra.misra-addon": "/usr/local/share/CppCheck/addons/misra.py", 82 | "SublimeLinter.linters.cppcheck-misra.rule-texts": "${project_path}/misra/MISRA_C_2012_Rules.txt", 83 | "SublimeLinter.linters.cppcheck-misra.suppress-rules": [ 84 | "8.14", 85 | "12.1" 86 | ], 87 | "SublimeLinter.linters.cppcheck-misra.cppcheck-opts": [ 88 | "'--max-configs=1'" 89 | ], 90 | "SublimeLinter.linters.cppcheck-misra.cppcheck-path": [ 91 | "/home/user/.local/bin" 92 | ] 93 | } 94 | ``` 95 | 96 | ## Contributing 97 | 98 | If you would like to contribute enhancements or fixes, please do the following: 99 | 100 | 1. Fork the plugin repository. 101 | 1. Hack on a separate topic branch created from the latest `master`. 102 | 1. Commit and push the topic branch. 103 | 1. Make a pull request. 104 | 1. Be patient. ;-) 105 | 106 | Please note that modifications should follow these coding guidelines: 107 | 108 | - Indent is 4 spaces. 109 | - Code should pass flake8 and pep257 linters. 110 | - Vertical whitespace helps readability, don’t be afraid to use it. 111 | - Please use descriptive variable names, no abbreviations unless they are very well known. 112 | 113 | Thank you for helping out! 114 | -------------------------------------------------------------------------------- /linter.py: -------------------------------------------------------------------------------- 1 | # 2 | # linter.py 3 | # Linter for SublimeLinter3, a code checking framework for Sublime Text 3 4 | # 5 | # Written by Kyle Chisholm 6 | # Copyright (c) 2016 Kyle Chisholm 7 | # 8 | # License: MIT 9 | # 10 | 11 | """This module exports the CppcheckMisra plugin class.""" 12 | 13 | import re 14 | import os 15 | import sublime 16 | from shutil import which 17 | from SublimeLinter.lint import Linter, util 18 | 19 | OUTPUT_RE = re.compile(r'\[[^:]*:(?P\d+)\] (?P.+)') 20 | 21 | 22 | class CppcheckMisra(Linter): 23 | """Provides an interface to cppcheck with MISRA C 2012.""" 24 | 25 | cmd = '__cmd__' 26 | name = 'cppcheck-misra' 27 | 28 | tempfile_suffix = 'c' 29 | 30 | execpath = os.path.join( 31 | os.path.dirname(os.path.realpath(__file__)), 32 | 'scripts', 33 | 'cppcheck-misra') 34 | 35 | if sublime.platform() == 'windows' and which('wsl'): 36 | execpath = util.check_output(['wsl', 'wslpath', execpath]).strip() 37 | execpath = ['wsl ', execpath] 38 | 39 | defaults = { 40 | 'executable': execpath, 41 | 'selector': 'source.c', 42 | '--cppcheck-opts': [ 43 | '"--max-configs=1"' 44 | ], 45 | '--suppress-rules,': [], 46 | '--misra-addon': '/usr/local/share/CppCheck/addons/misra.py', 47 | '--rule-texts': '', 48 | '--cppcheck-path': '/usr/local/bin' 49 | } 50 | 51 | regex = OUTPUT_RE 52 | multiline = False 53 | error_stream = util.STREAM_STDERR 54 | -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "install": "messages/install.txt" 3 | } 4 | -------------------------------------------------------------------------------- /messages/install.txt: -------------------------------------------------------------------------------- 1 | SublimeLinter-contrib-cppcheck-misra 2 | ------------------------------- 3 | This linter plugin for SublimeLinter provides an interface to `cppcheck` with misra C 2012 check addon. 4 | 5 | ** IMPORTANT! ** 6 | 7 | Before this plugin will activate, you *must* 8 | follow the installation instructions here: 9 | 10 | https://github.com/ChisholmKyle/SublimeLinter-contrib-cppcheck-misra 11 | -------------------------------------------------------------------------------- /scripts/cppcheck-misra: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | prog_name=$(basename "${0}") 4 | 5 | # Usage 6 | usage() { 7 | echo "Usage: ${prog_name} [ args ] [ source files ]" 8 | echo "args (optional):" 9 | echo " --version : output cppcheck version" 10 | echo " --misra-addon [ misra.py file ] : default '${MISRA_PY}'" 11 | echo " --rule-texts [ rule texts file ] : default '${RULE_TEXTS}'" 12 | echo " --suppress-rules [ list of rules to ignore ] : default '${IGNORE_RULES}'" 13 | echo " --cppcheck-opts [ cppcheck options ] : pass options to cppcheck" 14 | echo " --cppcheck-path [ cppcheck search path ] : default '${CPPCHECK_PATH}'" 15 | echo "" 16 | echo "MISRA C 2012 checks on source files using cppcheck and misra.py addon." 17 | echo "Optionally specify plain text file with rule descriptions. The text file" 18 | echo "should list rules with the following structure:" 19 | echo "" 20 | echo "Rule ." 21 | echo ": " 22 | echo "" 23 | echo "Rule ." 24 | echo ": " 25 | echo "" 26 | echo "..." 27 | echo "" 28 | echo "where and are integers," 29 | echo " is the rule description, and" 30 | echo "(optional) is 'Mandatory', 'Required', or 'Advisory'." 31 | echo "" 32 | echo "The ignore rules string is a list of comma-separated rules in format:" 33 | echo "." 34 | echo "" 35 | echo "For example: " 36 | echo " --suppress-rules 12.8,8.12" 37 | exit 1 38 | } 39 | 40 | # defaults 41 | defaults() { 42 | RULE_TEXTS= 43 | IGNORE_RULES= 44 | CPPCHECK_OPTS= 45 | MISRA_PY=/usr/local/share/Cppcheck/addons/misra.py 46 | CPPCHECK_PATH=/usr/local/bin 47 | } 48 | 49 | #Set defaults 50 | defaults 51 | 52 | # Parse arguments 53 | while [[ ${#} -ge 1 && ${1::1} == '-' ]]; do 54 | key="$1" 55 | case $key in 56 | '-h' | '--help' ) usage ;; 57 | '--version' ) 58 | cppcheck --version 59 | exit 60 | ;; 61 | '--misra-addon') 62 | if [[ ${#} -eq 1 ]] ; then 63 | usage 64 | else 65 | MISRA_PY="$2" 66 | fi 67 | shift 68 | ;; 69 | '--rule-texts') 70 | if [[ ${#} -eq 1 ]] ; then 71 | usage 72 | else 73 | RULE_TEXTS="$2" 74 | fi 75 | shift 76 | ;; 77 | '--suppress-rules') 78 | if [[ ${#} -eq 1 ]] ; then 79 | usage 80 | else 81 | IGNORE_RULES="$2" 82 | fi 83 | shift 84 | ;; 85 | '--cppcheck-opts') 86 | if [[ ${#} -eq 1 ]] ; then 87 | usage 88 | else 89 | CPPCHECK_OPTS="$2" 90 | fi 91 | shift 92 | ;; 93 | '--cppcheck-path') 94 | if [[ ${#} -eq 1 ]] ; then 95 | usage 96 | else 97 | CPPCHECK_PATH="$2" 98 | fi 99 | shift 100 | ;; 101 | * ) 102 | usage 103 | ;; 104 | esac 105 | shift 106 | done 107 | 108 | PATH=${CPPCHECK_PATH}:$PATH 109 | 110 | misra_py_opts='' 111 | 112 | if [[ -n "${RULE_TEXTS}" ]] ; then 113 | misra_py_opts="${misra_py_opts} --rule-texts ${RULE_TEXTS}" 114 | fi 115 | 116 | if [[ -n "${IGNORE_RULES}" ]] ; then 117 | misra_py_opts="${misra_py_opts} --suppress-rules ${IGNORE_RULES}" 118 | fi 119 | 120 | for f in "$@" ; do 121 | cppcheck ${CPPCHECK_OPTS} --dump "$f" 122 | python3 "${MISRA_PY}" ${misra_py_opts} "$f.dump" 123 | done 124 | -------------------------------------------------------------------------------- /scripts/cppcheck-misra-parsetexts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | """Generate MISRA C 2012 rule texts from pdf. 3 | 4 | Arguments: 5 | filename -- text file from parsed MISRA pdf file 6 | 7 | Example: 8 | python3 cppcheck-misra-parsetexts.py "/path/to/MISRA_C_2012.pdf" 9 | 10 | """ 11 | 12 | import os 13 | import re 14 | import sys 15 | import json 16 | import tempfile 17 | import subprocess 18 | 19 | # rules 20 | _appendixa_regex = re.compile(r'Appendix A Summary of guidelines\n') 21 | _appendixb_regex = re.compile(r'Appendix B Guideline attributes\n') 22 | _rule_regex = re.compile( 23 | r'(Rule|Dir).(\d+)\.(\d+)\n\n(Advisory|Required|Mandatory)\n\n([^\n]+)\n') 24 | _line_regex = re.compile(r'([^\n]+)\n') 25 | 26 | 27 | def misra_dict_to_text(misra_dict): 28 | """Convert dict to string readable by cppcheck's misra.py addon.""" 29 | misra_str = '' 30 | for num1 in misra_dict: 31 | for num2 in misra_dict[num1]: 32 | misra_str += '\n{} {}.{} {}\n'.format( 33 | misra_dict[num1][num2]['type'], num1, num2, misra_dict[num1][num2]['category']) 34 | misra_str += '{}\n'.format(misra_dict[num1][num2]['text']) 35 | return misra_str 36 | 37 | 38 | def parse_misra_xpdf_output(misra_file): 39 | """Extract misra rules texts from xPDF output.""" 40 | misra_dict = {} 41 | 42 | with open(misra_file, 'r', encoding="utf-8") as fp: 43 | fp_text = fp.read() 44 | 45 | # end of appendix A 46 | appb_end_res = _appendixb_regex.search(fp_text) 47 | last_index = appb_end_res.regs[0][0] 48 | 49 | appres = _appendixa_regex.search(fp_text) 50 | if appres: 51 | start_index = appres.regs[0][1] 52 | res = _rule_regex.search(fp_text, start_index) 53 | while res: 54 | start_index = res.regs[0][1] 55 | ruletype = res.group(1) 56 | rulenum1 = res.group(2) 57 | rulenum2 = res.group(3) 58 | category = res.group(4) 59 | ruletext = res.group(5).strip() 60 | statereadingrule = True 61 | while statereadingrule: 62 | lineres = _line_regex.match(fp_text, start_index) 63 | if lineres: 64 | start_index = lineres.regs[0][1] 65 | stripped_line = lineres.group(1).strip() 66 | ruletext += ' ' + stripped_line 67 | else: 68 | # empty line, stop reading text and save to dict 69 | if rulenum1 not in misra_dict: 70 | misra_dict[rulenum1] = {} 71 | misra_dict[rulenum1][rulenum2] = { 72 | 'type': ruletype, 73 | 'category': category, 74 | 'text': ruletext 75 | } 76 | statereadingrule = False 77 | res = _rule_regex.search(fp_text, start_index) 78 | if res and (last_index < res.regs[0][0]): 79 | break 80 | fp.close() 81 | 82 | return misra_dict 83 | 84 | 85 | def misra_parse_pdf(misra_pdf): 86 | """Extract misra rules texts from Misra-C-2012 pdf.""" 87 | 88 | if not os.path.isfile(misra_pdf): 89 | print('Fatal error: PDF file is not found: ' + misra_pdf) 90 | sys.exit(1) 91 | f = tempfile.NamedTemporaryFile(delete=False) 92 | f.close() 93 | subprocess.call([ 94 | 'pdftotext', 95 | '-enc', 'UTF-8', 96 | '-eol', 'unix', 97 | misra_pdf, 98 | f.name 99 | ]) 100 | misra_dict = parse_misra_xpdf_output(f.name) 101 | os.remove(f.name) 102 | 103 | return misra_dict 104 | 105 | 106 | misra_pdf_filename = sys.argv[1] 107 | 108 | misra_dict = misra_parse_pdf(misra_pdf_filename) 109 | misra_text = 'Appendix A Summary of guidelines\n\n' + \ 110 | misra_dict_to_text(misra_dict) 111 | 112 | misra_json_fout = os.path.splitext(misra_pdf_filename)[0] + '_Rules.json' 113 | misra_text_fout = os.path.splitext(misra_pdf_filename)[0] + '_Rules.txt' 114 | 115 | with open(misra_json_fout, 'w', encoding='utf-8') as fp: 116 | fp.write(json.dumps(misra_dict, indent=4)) 117 | print('Done creating "' + misra_json_fout + '"') 118 | 119 | with open(misra_text_fout, 'w', encoding='utf-8') as fp: 120 | fp.write(misra_text) 121 | print('Done creating "' + misra_text_fout + '"') 122 | -------------------------------------------------------------------------------- /scripts/install_cppcheck.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | prog_name=$(basename "${0}") 6 | this_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 7 | 8 | # Usage 9 | usage() { 10 | echo "Usage: ${prog_name} [ args ]" 11 | echo "args (optional):" 12 | echo " --prefix [ install prefix ] : default '${PREFIX}'" 13 | echo " --version [ cppcheck version ] : default '${CPPCHECK_VERSION}'" 14 | echo "" 15 | echo "This script will build and install cppcheck with misra.py addon" 16 | echo "from master branch of https://github.com/danmar/cppcheck." 17 | echo "Compiling cppcheck requires git, cmake, gcc/clang, and make." 18 | echo "" 19 | exit 1 20 | } 21 | 22 | # Defaults 23 | defaults() { 24 | 25 | PREFIX=/usr/local 26 | CPPCHECK_VERSION=latest 27 | 28 | } 29 | 30 | # Set defaults 31 | defaults 32 | 33 | # Parse arguments 34 | while [[ ${#} -ge 1 && ${1::1} == '-' ]]; do 35 | key="$1" 36 | case $key in 37 | '-h' | '--help' ) usage ;; 38 | '--prefix') 39 | if [[ ${#} -eq 1 ]] ; then 40 | usage 41 | else 42 | PREFIX="$2" 43 | fi 44 | shift 45 | ;; 46 | '--version') 47 | if [[ ${#} -eq 1 ]] ; then 48 | usage 49 | else 50 | CPPCHECK_VERSION="$2" 51 | fi 52 | shift 53 | ;; 54 | * ) 55 | usage 56 | ;; 57 | esac 58 | shift 59 | done 60 | 61 | # Build cppcheck 62 | echo "Installing development build of ccpcheck at prefix=${PREFIX}" 63 | echo "------------------------------------------------------------" 64 | echo "1. Checking out cppcheck repository to /tmp/cppcheck ..." 65 | cd /tmp 66 | git clone https://github.com/danmar/cppcheck 67 | cd cppcheck 68 | if [ "${CPPCHECK_VERSION}" != "latest" -a "${CPPCHECK_VERSION}" != "master" ] ; then 69 | git checkout tags/${CPPCHECK_VERSION} 70 | fi 71 | 72 | echo "2. Generating build files ..." 73 | mkdir build && cd build 74 | cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release .. 75 | 76 | echo "3. Build and Install ..." 77 | PREFIX=${PREFIX} make 78 | sudo make install 79 | 80 | echo "4. Copying addons and misra.py to '${PREFIX}/share/CppCheck/addons'" 81 | sudo mkdir -p "${PREFIX}/share/CppCheck" 82 | sudo cp -rf ../addons "${PREFIX}/share/CppCheck/" 83 | 84 | echo "------------------------------------------------------------" 85 | echo "Done." 86 | --------------------------------------------------------------------------------