diff --git a/.github/code_analysis.py b/.github/code_analysis.py
new file mode 100644
index 00000000..c68f8f75
--- /dev/null
+++ b/.github/code_analysis.py
@@ -0,0 +1,179 @@
+import os
+import argparse
+from github import Github
+
+# Input variables from Github action
+GITHUB_TOKEN = os.getenv('GITHUB_TOKEN')
+PR_NUM = int(os.getenv('PR_NUMBER'))
+WORK_DIR = os.getenv('GITHUB_WORKSPACE')
+REPO_NAME = os.getenv('GITHUB_REPOSITORY')
+SHA = os.getenv('GITHUB_SHA')
+COMMENT_TITLE = os.getenv('COMMENT_TITLE')
+ONLY_PR_CHANGES = os.getenv('REPORT_PR_CHANGES_ONLY')
+
+# Max characters per comment - 65536
+# Make some room for HTML tags and error message
+MAX_CHAR_COUNT_REACHED = '!Maximum character count per GitHub comment has been reached! Not all warnings/errors has been parsed!'
+COMMENT_MAX_SIZE = 65000
+current_comment_length = 0
+
+def is_part_of_pr_changes(file_path, issue_file_line, files_changed_in_pr):
+ if ONLY_PR_CHANGES == "false":
+ return True
+
+ file_name = file_path[file_path.rfind('/')+1:]
+ print(f"Looking for issue found in file={file_name} ...")
+ for file, (status, lines_changed_for_file) in files_changed_in_pr.items():
+ print(f"Changed file by this PR {file} with status {status} and changed lines {lines_changed_for_file}")
+ if file == file_name:
+ if status == "added":
+ return True
+
+ for (start, end) in lines_changed_for_file:
+ if issue_file_line >= start and issue_file_line <= end:
+ return True
+
+ return False
+
+def get_lines_changed_from_patch(patch):
+ lines_changed = []
+ lines = patch.split('\n')
+
+ for line in lines:
+ # Example line @@ -43,6 +48,8 @@
+ # ------------ ^
+ if line.startswith("@@"):
+ # Example line @@ -43,6 +48,8 @@
+ # ----------------------^
+ idx_beg = line.index("+")
+
+ # Example line @@ -43,6 +48,8 @@
+ # ^--^
+ idx_end = line[idx_beg:].index(",")
+ line_begin = int(line[idx_beg + 1 : idx_beg + idx_end])
+
+ idx_beg = idx_beg + idx_end
+ idx_end = line[idx_beg + 1 : ].index("@@")
+
+ num_lines = int(line[idx_beg + 1 : idx_beg + idx_end])
+
+ lines_changed.append((line_begin, line_begin + num_lines))
+
+ return lines_changed
+
+def setup_changed_files():
+ files_changed = dict()
+
+ g = Github(GITHUB_TOKEN)
+ repo = g.get_repo(REPO_NAME)
+ pull_request = repo.get_pull(PR_NUM)
+ num_changed_files = pull_request.changed_files
+ print(f"Changed files {num_changed_files}")
+ files = pull_request.get_files()
+ for file in files:
+ # additions # blob_url # changes # contents_url # deletions # filename
+ # patch # previous_filename # raw_url # sha # status
+ # print(f"File: additions={file.additions} blob_url={file.blob_url} changes={file.changes} contents_url={file.contents_url}"\
+ # f"deletions={file.deletions} filename={file.filename} patch={file.patch} previous_filename={file.previous_filename}"\
+ # f"raw_url={file.raw_url} sha={file.sha} status={file.status} ")
+
+ if file.patch is not None:
+ lines_changed_for_file = get_lines_changed_from_patch(file.patch)
+ files_changed[file.filename] = (file.status, lines_changed_for_file)
+
+ return files_changed
+
+def check_for_char_limit(incoming_line):
+ global current_comment_length
+ return (current_comment_length + len(incoming_line)) <= COMMENT_MAX_SIZE
+
+def get_file_line_end(file, file_line_start):
+ num_lines = sum(1 for line in open(WORK_DIR + file))
+ return min(file_line_start + 5, num_lines)
+
+def create_comment_for_output(tool_output, prefix, files_changed_in_pr):
+ issues_found = 0
+ global current_comment_length
+ output_string = ''
+ for line in tool_output:
+ if line.startswith(prefix):
+ line = line.replace(prefix, "")
+ file_path_end_idx = line.index(':')
+ file_path = line[:file_path_end_idx]
+ line = line[file_path_end_idx+1:]
+ file_line_start = int(line[:line.index(':')])
+ file_line_end = get_file_line_end(file_path, file_line_start)
+ description = f"\n```diff\n!Line: {file_line_start} - {line[line.index(' ')+1:]}``` \n"
+
+ new_line = f'\n\nhttps://github.com/{REPO_NAME}/blob/{SHA}{file_path}#L{file_line_start}-L{file_line_end} {description}
\n'
+
+ if is_part_of_pr_changes(file_path, file_line_start, files_changed_in_pr):
+ if check_for_char_limit(new_line):
+ output_string += new_line
+ current_comment_length += len(new_line)
+ issues_found += 1
+ else:
+ current_comment_length = COMMENT_MAX_SIZE
+ return output_string, issues_found
+
+ return output_string, issues_found
+
+def read_files_and_parse_results(files_changed_in_pr):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-cc', '--cppcheck', help='Output file name for cppcheck', required=True)
+ cppcheck_file_name = parser.parse_args().cppcheck
+
+ cppcheck_content = ''
+ with open(cppcheck_file_name, 'r') as file:
+ cppcheck_content = file.readlines()
+
+ line_prefix = f'{WORK_DIR}'
+
+ cppcheck_comment, cppcheck_issues_found = create_comment_for_output(cppcheck_content, line_prefix, files_changed_in_pr)
+
+ return cppcheck_comment, cppcheck_issues_found
+
+def prepare_comment_body(cppcheck_comment, cppcheck_issues_found):
+
+ if cppcheck_issues_found == 0:
+ full_comment_body = f'##
:white_check_mark: {COMMENT_TITLE} - no issues found! :white_check_mark:
' + else: + full_comment_body = f'##:zap: {COMMENT_TITLE} :zap:
\n\n' + + if len(cppcheck_comment) > 0: + full_comment_body +=f'