diff --git a/.vscode-test.mjs b/.vscode-test.mjs index 2eace2e..b1611d2 100644 --- a/.vscode-test.mjs +++ b/.vscode-test.mjs @@ -2,4 +2,10 @@ import { defineConfig } from '@vscode/test-cli'; export default defineConfig({ files: 'out/tests/**/*.test.js', + srcDir: 'src', + coverage: { + reporter: ['text-summary', 'html'], + include: ['out/src/**/*.js'], + exclude: ['out/tests/**', 'out/src/types/**'], + }, }); diff --git a/package-lock.json b/package-lock.json index c96d3ca..d8b85f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-code-reviewer", - "version": "1.2.0", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-code-reviewer", - "version": "1.2.0", + "version": "1.3.0", "dependencies": { "@eslint/js": "^9.39.3", "eslint": "^9.39.3", diff --git a/package.json b/package.json index fc97337..359124f 100644 --- a/package.json +++ b/package.json @@ -336,7 +336,8 @@ "pretest": "npm run compile && npm run lint", "lint": "eslint src", "compile:test": "tsc -p ./tsconfig.test.json", - "test": "npm run compile:test && vscode-test" + "test": "npm run compile:test && vscode-test", + "test:coverage": "npm run compile && npm run compile:test && vscode-test --coverage --coverage-output tests/coverage" }, "dependencies": { "@eslint/js": "^9.39.3", diff --git a/src/scope/method-extractor.ts b/src/scope/method-extractor.ts index fa6b96b..018421c 100644 --- a/src/scope/method-extractor.ts +++ b/src/scope/method-extractor.ts @@ -199,7 +199,7 @@ function fallbackRegexSymbols(document: vscode.TextDocument): MethodSymbol[] { pattern.lastIndex = 0; let match: RegExpExecArray | null; while ((match = pattern.exec(text)) !== null) { - const name = match[1] ?? match[2]; + const name = match[2] ?? match[1]; if (!name) { continue; } const start = document.positionAt(match.index); const key = `${name}@${start.line}`; diff --git a/src/utils/mockDocument.ts b/src/utils/mockDocument.ts index e478d39..81a2729 100644 --- a/src/utils/mockDocument.ts +++ b/src/utils/mockDocument.ts @@ -4,6 +4,19 @@ export function mockDocument(code: string, language: string, fileName?: string): const lines = code.split('\n'); const uri = vscode.Uri.parse('untitled:virtual'); const ext = language === 'javascript' ? 'js' : language === 'typescript' ? 'ts' : language === 'css' ? 'css' : 'java'; + const offsetAt = (p: vscode.Position): number => { + let offset = 0; + for (let i = 0; i < p.line; i++) offset += lines[i].length + 1; + return offset + p.character; + }; + const positionAt = (offset: number): vscode.Position => { + let remaining = offset; + for (let i = 0; i < lines.length; i++) { + if (remaining <= lines[i].length) return new vscode.Position(i, remaining); + remaining -= lines[i].length + 1; + } + return new vscode.Position(lines.length - 1, lines[lines.length - 1].length); + }; return { uri, fileName: fileName ?? `untitled.${ext}`, @@ -14,7 +27,10 @@ export function mockDocument(code: string, language: string, fileName?: string): isClosed: false, eol: vscode.EndOfLine.LF, lineCount: lines.length, - getText: () => code, + getText: (range?: vscode.Range) => { + if (!range) { return code; } + return code.slice(offsetAt(range.start), offsetAt(range.end)); + }, lineAt: (arg: number | vscode.Position) => { const line = typeof arg === 'number' ? arg : arg.line; const text = lines[line] ?? ''; @@ -27,19 +43,8 @@ export function mockDocument(code: string, language: string, fileName?: string): isEmptyOrWhitespace: text.trim().length === 0, }; }, - offsetAt: (p: vscode.Position) => { - let offset = 0; - for (let i = 0; i < p.line; i++) offset += lines[i].length + 1; - return offset + p.character; - }, - positionAt: (offset: number) => { - let remaining = offset; - for (let i = 0; i < lines.length; i++) { - if (remaining <= lines[i].length) return new vscode.Position(i, remaining); - remaining -= lines[i].length + 1; - } - return new vscode.Position(lines.length - 1, lines[lines.length - 1].length); - }, + offsetAt, + positionAt, getWordRangeAtPosition: () => undefined, validateRange: (r: vscode.Range) => r, validatePosition: (p: vscode.Position) => p, diff --git a/tests/coverage/base.css b/tests/coverage/base.css new file mode 100644 index 0000000..f418035 --- /dev/null +++ b/tests/coverage/base.css @@ -0,0 +1,224 @@ +body, html { + margin:0; padding: 0; + height: 100%; +} +body { + font-family: Helvetica Neue, Helvetica, Arial; + font-size: 14px; + color:#333; +} +.small { font-size: 12px; } +*, *:after, *:before { + -webkit-box-sizing:border-box; + -moz-box-sizing:border-box; + box-sizing:border-box; + } +h1 { font-size: 20px; margin: 0;} +h2 { font-size: 14px; } +pre { + font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; + margin: 0; + padding: 0; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; +} +a { color:#0074D9; text-decoration:none; } +a:hover { text-decoration:underline; } +.strong { font-weight: bold; } +.space-top1 { padding: 10px 0 0 0; } +.pad2y { padding: 20px 0; } +.pad1y { padding: 10px 0; } +.pad2x { padding: 0 20px; } +.pad2 { padding: 20px; } +.pad1 { padding: 10px; } +.space-left2 { padding-left:55px; } +.space-right2 { padding-right:20px; } +.center { text-align:center; } +.clearfix { display:block; } +.clearfix:after { + content:''; + display:block; + height:0; + clear:both; + visibility:hidden; + } +.fl { float: left; } +@media only screen and (max-width:640px) { + .col3 { width:100%; max-width:100%; } + .hide-mobile { display:none!important; } +} + +.quiet { + color: #7f7f7f; + color: rgba(0,0,0,0.5); +} +.quiet a { opacity: 0.7; } + +.fraction { + font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; + font-size: 10px; + color: #555; + background: #E8E8E8; + padding: 4px 5px; + border-radius: 3px; + vertical-align: middle; +} + +div.path a:link, div.path a:visited { color: #333; } +table.coverage { + border-collapse: collapse; + margin: 10px 0 0 0; + padding: 0; +} + +table.coverage td { + margin: 0; + padding: 0; + vertical-align: top; +} +table.coverage td.line-count { + text-align: right; + padding: 0 5px 0 20px; +} +table.coverage td.line-coverage { + text-align: right; + padding-right: 10px; + min-width:20px; +} + +table.coverage td span.cline-any { + display: inline-block; + padding: 0 5px; + width: 100%; +} +.missing-if-branch { + display: inline-block; + margin-right: 5px; + border-radius: 3px; + position: relative; + padding: 0 4px; + background: #333; + color: yellow; +} + +.skip-if-branch { + display: none; + margin-right: 10px; + position: relative; + padding: 0 4px; + background: #ccc; + color: white; +} +.missing-if-branch .typ, .skip-if-branch .typ { + color: inherit !important; +} +.coverage-summary { + border-collapse: collapse; + width: 100%; +} +.coverage-summary tr { border-bottom: 1px solid #bbb; } +.keyline-all { border: 1px solid #ddd; } +.coverage-summary td, .coverage-summary th { padding: 10px; } +.coverage-summary tbody { border: 1px solid #bbb; } +.coverage-summary td { border-right: 1px solid #bbb; } +.coverage-summary td:last-child { border-right: none; } +.coverage-summary th { + text-align: left; + font-weight: normal; + white-space: nowrap; +} +.coverage-summary th.file { border-right: none !important; } +.coverage-summary th.pct { } +.coverage-summary th.pic, +.coverage-summary th.abs, +.coverage-summary td.pct, +.coverage-summary td.abs { text-align: right; } +.coverage-summary td.file { white-space: nowrap; } +.coverage-summary td.pic { min-width: 120px !important; } +.coverage-summary tfoot td { } + +.coverage-summary .sorter { + height: 10px; + width: 7px; + display: inline-block; + margin-left: 0.5em; + background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; +} +.coverage-summary .sorted .sorter { + background-position: 0 -20px; +} +.coverage-summary .sorted-desc .sorter { + background-position: 0 -10px; +} +.status-line { height: 10px; } +/* yellow */ +.cbranch-no { background: yellow !important; color: #111; } +/* dark red */ +.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } +.low .chart { border:1px solid #C21F39 } +.highlighted, +.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ + background: #C21F39 !important; +} +/* medium red */ +.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } +/* light red */ +.low, .cline-no { background:#FCE1E5 } +/* light green */ +.high, .cline-yes { background:rgb(230,245,208) } +/* medium green */ +.cstat-yes { background:rgb(161,215,106) } +/* dark green */ +.status-line.high, .high .cover-fill { background:rgb(77,146,33) } +.high .chart { border:1px solid rgb(77,146,33) } +/* dark yellow (gold) */ +.status-line.medium, .medium .cover-fill { background: #f9cd0b; } +.medium .chart { border:1px solid #f9cd0b; } +/* light yellow */ +.medium { background: #fff4c2; } + +.cstat-skip { background: #ddd; color: #111; } +.fstat-skip { background: #ddd; color: #111 !important; } +.cbranch-skip { background: #ddd !important; color: #111; } + +span.cline-neutral { background: #eaeaea; } + +.coverage-summary td.empty { + opacity: .5; + padding-top: 4px; + padding-bottom: 4px; + line-height: 1; + color: #888; +} + +.cover-fill, .cover-empty { + display:inline-block; + height: 12px; +} +.chart { + line-height: 0; +} +.cover-empty { + background: white; +} +.cover-full { + border-right: none !important; +} +pre.prettyprint { + border: none !important; + padding: 0 !important; + margin: 0 !important; +} +.com { color: #999 !important; } +.ignore-none { color: #999; font-weight: normal; } + +.wrapper { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -48px; +} +.footer, .push { + height: 48px; +} diff --git a/tests/coverage/block-navigation.js b/tests/coverage/block-navigation.js new file mode 100644 index 0000000..530d1ed --- /dev/null +++ b/tests/coverage/block-navigation.js @@ -0,0 +1,87 @@ +/* eslint-disable */ +var jumpToCode = (function init() { + // Classes of code we would like to highlight in the file view + var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; + + // Elements to highlight in the file listing view + var fileListingElements = ['td.pct.low']; + + // We don't want to select elements that are direct descendants of another match + var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` + + // Selector that finds elements on the page to which we can jump + var selector = + fileListingElements.join(', ') + + ', ' + + notSelector + + missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` + + // The NodeList of matching elements + var missingCoverageElements = document.querySelectorAll(selector); + + var currentIndex; + + function toggleClass(index) { + missingCoverageElements + .item(currentIndex) + .classList.remove('highlighted'); + missingCoverageElements.item(index).classList.add('highlighted'); + } + + function makeCurrent(index) { + toggleClass(index); + currentIndex = index; + missingCoverageElements.item(index).scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center' + }); + } + + function goToPrevious() { + var nextIndex = 0; + if (typeof currentIndex !== 'number' || currentIndex === 0) { + nextIndex = missingCoverageElements.length - 1; + } else if (missingCoverageElements.length > 1) { + nextIndex = currentIndex - 1; + } + + makeCurrent(nextIndex); + } + + function goToNext() { + var nextIndex = 0; + + if ( + typeof currentIndex === 'number' && + currentIndex < missingCoverageElements.length - 1 + ) { + nextIndex = currentIndex + 1; + } + + makeCurrent(nextIndex); + } + + return function jump(event) { + if ( + document.getElementById('fileSearch') === document.activeElement && + document.activeElement != null + ) { + // if we're currently focused on the search input, we don't want to navigate + return; + } + + switch (event.which) { + case 78: // n + case 74: // j + goToNext(); + break; + case 66: // b + case 75: // k + case 80: // p + goToPrevious(); + break; + } + }; +})(); +window.addEventListener('keydown', jumpToCode); diff --git a/tests/coverage/favicon.png b/tests/coverage/favicon.png new file mode 100644 index 0000000..c1525b8 Binary files /dev/null and b/tests/coverage/favicon.png differ diff --git a/tests/coverage/index.html b/tests/coverage/index.html new file mode 100644 index 0000000..1d28e82 --- /dev/null +++ b/tests/coverage/index.html @@ -0,0 +1,386 @@ + + + + +
++ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
|---|---|---|---|---|---|---|---|---|---|
| src | +
+
+ |
+ 76.38% | +110/144 | +46.66% | +7/15 | +80% | +4/5 | +76.38% | +110/144 | +
| src/activation | +
+
+ |
+ 14.03% | +122/869 | +100% | +1/1 | +10% | +1/10 | +14.03% | +122/869 | +
| src/adapters | +
+
+ |
+ 43.73% | +363/830 | +73.46% | +36/49 | +33.33% | +14/42 | +43.73% | +363/830 | +
| src/ai | +
+
+ |
+ 20.52% | +173/843 | +75.86% | +22/29 | +31.03% | +9/29 | +20.52% | +173/843 | +
| src/ai/providers | +
+
+ |
+ 97.55% | +239/245 | +83.72% | +36/43 | +100% | +10/10 | +97.55% | +239/245 | +
| src/config | +
+
+ |
+ 68.03% | +83/122 | +100% | +11/11 | +45.83% | +11/24 | +68.03% | +83/122 | +
| src/diagnostics | +
+
+ |
+ 90.56% | +48/53 | +100% | +13/13 | +75% | +6/8 | +90.56% | +48/53 | +
| src/fix | +
+
+ |
+ 73.11% | +680/930 | +67.48% | +110/163 | +69.38% | +34/49 | +73.11% | +680/930 | +
| src/i18n | +
+
+ |
+ 99.79% | +1434/1437 | +91.66% | +11/12 | +80% | +4/5 | +99.79% | +1434/1437 | +
| src/jsp | +
+
+ |
+ 100% | +77/77 | +92.85% | +13/14 | +100% | +1/1 | +100% | +77/77 | +
| src/merger | +
+
+ |
+ 95.78% | +159/166 | +86.84% | +33/38 | +100% | +4/4 | +95.78% | +159/166 | +
| src/orchestrator | +
+
+ |
+ 45.71% | +48/105 | +100% | +2/2 | +28.57% | +2/7 | +45.71% | +48/105 | +
| src/panel | +
+
+ |
+ 11.88% | +68/572 | +100% | +2/2 | +10.52% | +2/19 | +11.88% | +68/572 | +
| src/rules | +
+
+ |
+ 30.73% | +695/2261 | +83.57% | +117/140 | +51.16% | +22/43 | +30.73% | +695/2261 | +
| src/rules/converters | +
+
+ |
+ 72.69% | +647/890 | +92.85% | +13/14 | +34.48% | +10/29 | +72.69% | +647/890 | +
| src/scope | +
+
+ |
+ 78.39% | +225/287 | +70.37% | +57/81 | +66.66% | +10/15 | +78.39% | +225/287 | +
| src/services | +
+
+ |
+ 31.36% | +69/220 | +100% | +1/1 | +7.69% | +1/13 | +31.36% | +69/220 | +
| src/utils | +
+
+ |
+ 72.63% | +146/201 | +90% | +36/40 | +53.84% | +7/13 | +72.63% | +146/201 | +
| src/views | +
+
+ |
+ 14.21% | +184/1294 | +75% | +3/4 | +10.34% | +3/29 | +14.21% | +184/1294 | +