diff --git a/server_config.json b/server_config.json new file mode 100644 index 0000000000..ac8ebe20a2 --- /dev/null +++ b/server_config.json @@ -0,0 +1,13 @@ +{ + "view_port": 8001, + "workspace": "/Users/shepelenkomykhailo/.codechecker", + "products": { + "Default": { + "connection": "sqlite", + "database": "/Users/shepelenkomykhailo/.codechecker/Default.sqlite" + } + }, + "server": { + "max_workers": 1 + } +} \ No newline at end of file diff --git a/web/server/codechecker_server/cli/server.py b/web/server/codechecker_server/cli/server.py index c7781250b8..86ec87e450 100644 --- a/web/server/codechecker_server/cli/server.py +++ b/web/server/codechecker_server/cli/server.py @@ -1133,3 +1133,5 @@ def main(args): LOG.error(fnerr) sys.exit(1) server_init_start(args) + + diff --git a/web/server/codechecker_server/server.py b/web/server/codechecker_server/server.py index f42d88739f..1147c30421 100644 --- a/web/server/codechecker_server/server.py +++ b/web/server/codechecker_server/server.py @@ -1276,3 +1276,4 @@ def add_initial_run_database(config_sql_server, product_connection): sess.close() LOG.debug("Default product set up.") + diff --git a/web/server/vue-cli/src/components/Report/Report.vue b/web/server/vue-cli/src/components/Report/Report.vue index 503a30c8ab..83a6cc55e8 100644 --- a/web/server/vue-cli/src/components/Report/Report.vue +++ b/web/server/vue-cli/src/components/Report/Report.vue @@ -11,7 +11,8 @@ :cols="editorCols" > - + + + + + mdi-comment-multiple-outline + + Comments ({{ numOfComments }}) + + + + @@ -109,52 +133,60 @@ - + - - + + - - - + { + if (val) { + loadCoverageData(); + } else { + clearCoverageHighlighting(); + } + }" + /> + - mdi-comment-multiple-outline - - Comments ({{ numOfComments }}) - + ({{ Math.round(coverageData.coveragePercentage) }}% covered) + + @@ -172,6 +204,22 @@ + + + + + mdi-file-outline + + + {{ sourceFile.filePath }} + + + + + { - this.numOfComments = numOfComments; - this.loadNumOfComments = false; - })); + report: { + handler(newVal) { + if (newVal) { + this.loadNumOfComments = true; + ccService.getClient().getCommentCount(newVal.reportId, + handleThriftError(numOfComments => { + this.numOfComments = numOfComments; + this.loadNumOfComments = false; + })); + this.loadCoverageData(); + } + }, + immediate: true + }, + + showCoverage: { + handler(newVal) { + if (newVal) { + this.updateCoverageHighlighting(); + } else { + this.clearCoverageHighlighting(); + } + } + }, + + coverageData: { + handler(newVal) { + if (newVal) { + this.coverageData = newVal; + if (this.showCoverage) { + this.updateCoverageHighlighting(); + } + } + }, + immediate: true } }, @@ -448,10 +541,10 @@ export default { lineNumbers: true, readOnly: true, mode: "text/x-c++src", - gutters: [ "CodeMirror-linenumbers", "bugInfo" ], - extraKeys: {}, - viewportMargin: 200, - highlightSelectionMatches : { showToken: /\w/, annotateScrollbar: true } + theme: "default", + lineWrapping: true, + gutters: [ "CodeMirror-linenumbers", "CodeMirror-foldgutter" ], + foldGutter: true }); this.editor.setSize("100%", "100%"); @@ -493,28 +586,32 @@ export default { methods: { init(treeItem) { + if (!treeItem) return; + this.loading = true; if (treeItem.step) { this.loadReportStep(treeItem.report, { - stepId: this.treeItem.id, + stepId: treeItem.id, ...treeItem.step }); } else if (treeItem.data) { this.loadReportStep(treeItem.report, { - stepId: this.treeItem.id, + stepId: treeItem.id, ...treeItem.data }); - } else { + } else if (treeItem.report) { this.loadReport(treeItem.report); + } else { + this.loading = false; } }, async loadReportStep(report, { stepId, fileId, startLine }) { if (!this.report || - !this.report.reportId.equals(report.reportId) || - !this.sourceFile || - !fileId.equals(this.sourceFile.fileId) + !this.report.reportId.equals(report.reportId) || + !this.sourceFile || + !fileId.equals(this.sourceFile.fileId) ) { this.report = report; @@ -781,10 +878,10 @@ export default { //the last bug path element, then we render the warning. if (this.sourceFile.fileId.equals(this.report.fileId) && - (events.length == 0 || - this.report.checkerMsg !== events[events.length-1].msg || - this.report.line.toNumber() != - events[events.length-1].startLine.toNumber()) + (events.length === 0 || + this.report.checkerMsg !== events[events.length-1].msg || + this.report.line.toNumber() !== + events[events.length-1].startLine.toNumber()) ){ const chkrmsg_data = { $id: 999, $message:this.report.checkerMsg, @@ -853,7 +950,6 @@ export default { .filter(textMarker => { let line = null; - // If not in viewport. try { line = textMarker.lines[0].lineNo(); } catch (ex) { @@ -914,7 +1010,104 @@ export default { openAnalysisInfoDialog() { this.reportId = this.report.reportId; this.analysisInfoDialog = true; - } + }, + + async loadCoverageData() { + if (!this.report || !this.report.fileId) return; + + this.coverageLoading = true; + try { + // Use the coverage data from props if available + if (this.coverageData) { + this.updateCoverageHighlighting(); + return; + } + + // Fallback to loading coverage data if not provided via props + const runIds = this.report.runId ? [ this.report.runId ] : []; + this.coverageData = await ccService.getCodeCoverage( + this.report.fileId, + runIds + ); + + this.updateCoverageHighlighting(); + } catch (err) { + // Error handling without console.log + } finally { + this.coverageLoading = false; + } + }, + + updateCoverageHighlighting() { + if (!this.coverageData || !this.showCoverage) return; + + this.editor.operation(() => { + for (let i = 0; i < this.editor.lineCount(); i++) { + const lineNumber = i + 1; + const lineData = this.coverageData.lineCoverage.find(line => { + const { start, end } = line.lineRange; + return start <= lineNumber && end >= lineNumber; + }); + + if (lineData) { + let className; + switch (lineData.coverageStatus) { + case "covered": + className = "coverage-covered-line"; + break; + case "uncovered": + className = "coverage-uncovered-line"; + break; + case "partially-covered": + className = "coverage-partial-line"; + break; + default: + className = ""; + } + + [ "wrap", "background", "gutter", "line" ].forEach(type => { + this.editor.addLineClass(i, type, className); + }); + } + } + }); + }, + + clearCoverageHighlighting() { + if (!this.editor) return; + + this.editor.operation(() => { + for (let i = 0; i < this.editor.lineCount(); i++) { + [ "wrap", "background", "gutter", "line" ].forEach(type => { + this.editor.removeLineClass(i, type, "coverage-covered-line"); + this.editor.removeLineClass(i, type, "coverage-uncovered-line"); + this.editor.removeLineClass(i, type, "coverage-partial-line"); + }); + } + }); + }, + + getLineCoverageClass(lineNumber) { + if (!this.coverageData || !this.showCoverage) return ""; + + const lineInfo = this.coverageData.lineCoverage.find(line => { + const { start, end } = line.lineRange; + return start <= lineNumber && end >= lineNumber; + }); + + if (!lineInfo) return "coverage-unknown"; + + switch (lineInfo.coverageStatus) { + case "covered": + return "coverage-covered"; + case "uncovered": + return "coverage-uncovered"; + case "partially-covered": + return "coverage-partial"; + default: + return "coverage-unknown"; + } + }, } }; @@ -923,12 +1116,68 @@ export default { .scrollbar-bug-annotation { background-color: red; } + +.coverage-covered-line { + background-color: rgba(0, 255, 0, 0.1) !important; +} + +.coverage-uncovered-line { + background-color: rgba(255, 0, 0, 0.1) !important; +} + +.coverage-partial-line { + background-color: rgba(255, 255, 0, 0.1) !important; +} + +.CodeMirror { + .coverage-covered-line { + background-color: rgba(0, 255, 0, 0.1) !important; + } + + .coverage-uncovered-line { + background-color: rgba(255, 0, 0, 0.1) !important; + } + + .coverage-partial-line { + background-color: rgba(255, 255, 0, 0.1) !important; + } + + pre.coverage-covered-line { + background-color: rgba(0, 255, 0, 0.1) !important; + } + + pre.coverage-uncovered-line { + background-color: rgba(255, 0, 0, 0.1) !important; + } + + .CodeMirror-line.coverage-covered-line { + background-color: rgba(0, 255, 0, 0.1) !important; + } + + .CodeMirror-line.coverage-uncovered-line { + background-color: rgba(255, 0, 0, 0.1) !important; + } +} \ No newline at end of file + +::v-deep .CodeMirror-linenumber { + color: #666; +} + +::v-deep .v-input--checkbox.show-coverage { + margin-top: 0; + margin-bottom: 0; + + .v-input__slot { + margin-bottom: 0; + } +} + diff --git a/web/server/vue-cli/src/router/index.js b/web/server/vue-cli/src/router/index.js index 3a081da8a1..93c2851cec 100644 --- a/web/server/vue-cli/src/router/index.js +++ b/web/server/vue-cli/src/router/index.js @@ -132,6 +132,6 @@ CheckerCoverageStatistics"), component: () => import("@/views/SourceComponent") }, ] - } + }, ] }); \ No newline at end of file diff --git a/web/server/vue-cli/src/services/api/cc.service.js b/web/server/vue-cli/src/services/api/cc.service.js index 354d08dd16..c32d444474 100644 --- a/web/server/vue-cli/src/services/api/cc.service.js +++ b/web/server/vue-cli/src/services/api/cc.service.js @@ -100,6 +100,47 @@ class CodeCheckerService extends BaseService { })); }); } + + /** + * Get code coverage data for a specific file + * @param {number} fileId - The ID of the file to get coverage for + * @param {Array} runIds - Array of run IDs to get coverage from + * @returns {Promise} - Returns promise that resolves to coverage data + * @example + * { + * fileId: 123, + * filePath: "/path/to/file.cpp", + * totalLines: 100, + * coveredLines: 80, + * uncoveredLines: 20, + * coveragePercentage: 80, + * lineCoverage: [ + * { + * lineRange: { start: 1, end: 2 }, + * coverageStatus: "uncovered", + * }, + * { + * lineRange: { start: 3, end: 4 }, + * coverageStatus: "covered", + * }, + * { + * lineRange: { start: 5, end: 6 }, + * coverageStatus: "partially-covered", + * } + * ] + * } + */ + getCodeCoverage(fileId, runIds) { + return new Promise(resolve => { + this.getClient().getCodeCoverage( + fileId, + runIds, + handleThriftError(coverageData => { + resolve(coverageData); + }) + ); + }); + } } const ccService = new CodeCheckerService(); diff --git a/web/server/vue-cli/src/views/CoverageTest.vue b/web/server/vue-cli/src/views/CoverageTest.vue new file mode 100644 index 0000000000..b092472de0 --- /dev/null +++ b/web/server/vue-cli/src/views/CoverageTest.vue @@ -0,0 +1,21 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/web/server/vue-cli/src/views/ReportDetail.vue b/web/server/vue-cli/src/views/ReportDetail.vue index f8b8d06891..82aafd5e94 100644 --- a/web/server/vue-cli/src/views/ReportDetail.vue +++ b/web/server/vue-cli/src/views/ReportDetail.vue @@ -88,6 +88,7 @@