From 6ca6f1247ad9c71077d47e394c6d96d20a966172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carsten=20R=C3=B6snick-Neugebauer?= Date: Mon, 15 Nov 2021 15:02:35 +0100 Subject: [PATCH 1/9] feat: add SVN provider * Add provider selection to settings (options: git, svn) * Add VCS provider handler which can retrieve git sha as well as svn rev * Rename `sha` to `revision` as more general term in CSV --- package.json | 11 +++- src/export-factory.ts | 14 ++--- src/model.ts | 4 +- src/review-comment.ts | 30 +++++----- src/template.default.hbs | 8 +-- src/template.default.hbs.json | 8 +-- src/test/suite/workspace-util.test.ts | 25 +++++---- src/utils/workspace-util.ts | 4 +- src/vcs-provider.ts | 81 +++++++++++++++++++++++++++ src/webview.html | 2 +- 10 files changed, 140 insertions(+), 47 deletions(-) create mode 100644 src/vcs-provider.ts diff --git a/package.json b/package.json index 80f1343..29f15a1 100644 --- a/package.json +++ b/package.json @@ -274,7 +274,7 @@ "high" ] }, - "code-review.gitDirectory": { + "code-review.vcs.git.directory": { "type": "string", "title": "The folder containing the Git repository", "markdownDescription": "Use this setting when the Git repository is located in an other directory than the workspace one.\nThe path can be **relative** (prefixed with `.` or `..`) or **absolute** (prefixed with `/` on Linux/MacOS or `{drive}:\\` on Windows).\nExamples:\n- `./app`: for {workspace}/app (Linux/MacOS)\n- `../app`: for a folder at the same level as the workspace (Linux/MacOS)\n- `/path/to/my/app`: for an absolute path (Linux/MacOS)\n- `C:\\Path\\To\\My\\App`: for an absolute path (Windows)", @@ -326,6 +326,15 @@ "title": "Code selection background color.", "markdownDescription": "Background color used to highlight the code associated to a comment.\nMust be specified using a hexadecimal representation - with or without the alpha part (`#C8C832` or `#C8C83226`) - or a `rgba()` definition.", "default": "codereview.code.selection.background" + }, + "code-review.vcs.provider": { + "enum": [ + "git", + "svn" + ], + "default": "git", + "title": "Version Control", + "description": "Version control used in this project. Necessary to determine the commit id to be attached to comments." } } }, diff --git a/src/export-factory.ts b/src/export-factory.ts index 344c5c5..53c4d67 100644 --- a/src/export-factory.ts +++ b/src/export-factory.ts @@ -25,7 +25,7 @@ import { sortLineSelections, rangeFromStringDefinition, escapeEndOfLineForCsv, - standardizeFilename, + relativeToWorkspace, splitStringDefinition, } from './utils/workspace-util'; import { ReviewFileExportSection, GroupBy, ExportFormat, ExportMap, Group } from './interfaces'; @@ -105,7 +105,7 @@ export class ExportFactory { */ private isCommentEligible(entry: CsvEntry): boolean { return ( - (this.currentCommitId === null || entry.sha === this.currentCommitId) && + (this.currentCommitId === null || entry.revision === this.currentCommitId) && (this.currentFilename === null || entry.filename === this.currentFilename) ); } @@ -168,7 +168,7 @@ export class ExportFactory { const title = row.title ? row.title.substring(0, 255) : descShort; const fileRow = row.url ? `- file: [${row.filename}](${row.url})${EOL}` : `${row.filename}${EOL}`; const linesRow = `- lines: ${row.lines}${EOL}`; - const shaRow = row.sha ? `- SHA: ${row.sha}${EOL}${EOL}` : ''; + const shaRow = row.revision ? `- SHA: ${row.revision}${EOL}${EOL}` : ''; const commentSection = `## Comment${EOL}${row.comment}${EOL}`; const additional = row.additional ? `## Additional information${EOL}${row.additional}${EOL}` : ''; const priority = row.priority ? `## Priority${EOL}${this.priorityName(row.priority)}${EOL}${EOL}` : ''; @@ -202,7 +202,7 @@ export class ExportFactory { const fileRow = row.url ? `- file: [${row.filename}](${row.url})${EOL}` : `${row.filename}${EOL}`; const linesRow = `- lines: ${row.lines}${EOL}`; - const shaRow = row.sha ? `- SHA: ${row.sha}${EOL}${EOL}` : ''; + const shaRow = row.revision ? `- SHA: ${row.revision}${EOL}${EOL}` : ''; const commentSection = `## Comment${EOL}${row.comment}${EOL}`; const additional = row.additional ? `## Additional information${EOL}${row.additional}${EOL}` : ''; const priority = row.priority ? `## Priority${EOL}${this.priorityName(row.priority)}${EOL}${EOL}` : ''; @@ -241,7 +241,7 @@ export class ExportFactory { const fileRow = row.url ? `* file: [${row.filename}|${row.url}]${EOL}` : `${row.filename}${EOL}`; const linesRow = `* lines: ${row.lines}${EOL}`; - const shaRow = row.sha ? `* SHA: ${row.sha}${EOL}${EOL}` : ''; + const shaRow = row.revision ? `* SHA: ${row.revision}${EOL}${EOL}` : ''; const categorySection = `h2. Category${EOL}${row.category}${EOL}${EOL}`; const commentSection = `h2. Comment${EOL}${row.comment}${EOL}`; const additional = row.additional ? `h2. Additional information${EOL}${row.additional}${EOL}` : ''; @@ -251,7 +251,7 @@ export class ExportFactory { fs.appendFileSync( outputFile, - `"[code review] ${title}","${description}","${this.priorityName(row.priority)}","${row.sha}","${ + `"[code review] ${title}","${description}","${this.priorityName(row.priority)}","${row.revision}","${ row.filename }","${row.url}","${row.lines}","${row.title}","${row.category}","${row.comment}","${row.additional}"${EOL}`, ); @@ -581,7 +581,7 @@ export class ExportFactory { if (this.filterByFilename) { let filename = window.activeTextEditor?.document.fileName; if (filename) { - filename = standardizeFilename(this.workspaceRoot, filename); + filename = relativeToWorkspace(this.workspaceRoot, filename); if (this.currentFilename !== filename) { changedFile = true; this.currentFilename = filename; diff --git a/src/model.ts b/src/model.ts index 0ec8b13..b9a60f0 100644 --- a/src/model.ts +++ b/src/model.ts @@ -16,7 +16,7 @@ const { v4: uuidv4, validate: uuidValidate } = require('uuid'); * 4. Update createCommentFromObject() (optional). */ export interface CsvEntry { - sha: string; + revision: string; filename: string; url: string; lines: string; @@ -66,7 +66,7 @@ export class CsvStructure { * - A deprecated stored property can't be removed from the array. */ private static readonly headers: string[] = [ - 'sha', + 'revision', 'filename', 'url', 'lines', diff --git a/src/review-comment.ts b/src/review-comment.ts index afff3f3..3d92b89 100644 --- a/src/review-comment.ts +++ b/src/review-comment.ts @@ -7,12 +7,12 @@ import { removeTrailingSlash, startLineNumberFromStringDefinition, endLineNumberFromStringDefinition, - standardizeFilename, + relativeToWorkspace, } from './utils/workspace-util'; -import { CommentListEntry } from './comment-list-entry'; import { getSelectionStringDefinition, hasSelection } from './utils/editor-utils'; import { getCsvFileLinesAsArray, setCsvFileLines } from './utils/storage-utils'; import path from 'path'; +import { commitId } from './vcs-provider'; export class ReviewCommentService { constructor(private reviewFile: string, private workspaceRoot: string) {} @@ -29,9 +29,11 @@ export class ReviewCommentService { return; } - comment.filename = standardizeFilename(this.workspaceRoot, editor!.document.fileName); + comment.filename = relativeToWorkspace(this.workspaceRoot, editor!.document.fileName); - setCsvFileLines(this.reviewFile, [CsvStructure.formatAsCsvLine(this.finalizeComment(comment))], false); + this.finalizeComment(comment, this.workspaceRoot).then((entry: CsvEntry) => { + setCsvFileLines(this.reviewFile, [CsvStructure.formatAsCsvLine(entry)], false); + }); } /** @@ -63,8 +65,10 @@ export class ReviewCommentService { return; } - rows[updateRowIndex] = CsvStructure.formatAsCsvLine(this.finalizeComment(comment)); - setCsvFileLines(this.reviewFile, rows); + this.finalizeComment(comment, this.workspaceRoot).then((entry: CsvEntry) => { + rows[updateRowIndex] = CsvStructure.formatAsCsvLine(entry); + setCsvFileLines(this.reviewFile, rows); + }); } async deleteComment(id: string, label: string) { @@ -117,22 +121,20 @@ export class ReviewCommentService { * @param comment The comment to finalize * @return The finalized comment */ - private finalizeComment(comment: CsvEntry): CsvEntry { + private async finalizeComment(comment: CsvEntry, workspaceRoot: string): Promise { const copy = { ...comment }; - const gitDirectory = workspace.getConfiguration().get('code-review.gitDirectory') as string; - const gitRepositoryPath = path.resolve(this.workspaceRoot, gitDirectory); - try { - copy.sha = gitCommitId({ cwd: gitRepositoryPath }); + copy.revision = await commitId(comment.filename, workspaceRoot); } catch (error) { - copy.sha = ''; - console.log('Not in a git repository. Leaving SHA empty', error); + copy.revision = ''; + window.showErrorMessage(`Repository not under version control as configured in the plugin's settings. + Leaving revision empty.\n\Details: ${error}`); } const startAnker = startLineNumberFromStringDefinition(copy.lines); const endAnker = endLineNumberFromStringDefinition(copy.lines); - copy.url = this.remoteUrl(copy.sha, copy.filename, startAnker, endAnker); + copy.url = this.remoteUrl(copy.revision, copy.filename, startAnker, endAnker); return copy; } diff --git a/src/template.default.hbs b/src/template.default.hbs index 6853c6c..747c5e2 100644 --- a/src/template.default.hbs +++ b/src/template.default.hbs @@ -137,10 +137,10 @@ {{line.additional}} {{/if}} - {{#if line.sha}} - - SHA - {{line.sha}} + {{#if line.revision}} + + Revision + {{line.revision}} {{/if}} {{#if line.code}} diff --git a/src/template.default.hbs.json b/src/template.default.hbs.json index e50f523..cab5e5f 100644 --- a/src/template.default.hbs.json +++ b/src/template.default.hbs.json @@ -3,7 +3,7 @@ "group": "Best Practices", "lines": [ { - "sha": "b45d2822d6c87770af520d7e2acc49155f0b4362", + "revision": "b45d2822d6c87770af520d7e2acc49155f0b4362", "filename": "/src/environments/environment.ts", "url": "https://gitlab.com/foo/bar/baz/-/blob/b45d2822d6c87770af520d7e2acc49155f0b4362/src/src/environments/environment.ts#L5-12", "lines": "5:0-12:2", @@ -15,7 +15,7 @@ "code": "ZXhwb3J0IGNvbnN0IGVudmlyb25tZW50ID0gewogIHByb2R1Y3Rpb246IGZhbHNlCn07CgovKgogKiBGb3IgZWFzaWVyIGRlYnVnZ2luZyBpbiBkZXZlbG9wbWVudCBtb2RlLCB5b3UgY2FuIGltcG9ydCB0aGUgZm9sbG93aW5nIGZpbGUKICogdG8gaWdub3JlIHpvbmUgcmVsYXRlZCBlcnJvciBzdGFjayBmcmFtZXMgc3VjaCBhcyBgem9uZS5ydW5gLCBgem9uZURlbGVnYXRlLmludm9rZVRhc2tgLgogKgogKiBUaGlzIGltcG9ydCBzaG91bGQgYmUgY29tbWVudGVkIG91dCBpbiBwcm9kdWN0aW9uIG1vZGUgYmVjYXVzZSBpdCB3aWxsIGhhdmUgYSBuZWdhdGl2ZSBpbXBhY3Q=" }, { - "sha": "b45d2822d6c87770af520d7e2acc49155f0b4362", + "revision": "b45d2822d6c87770af520d7e2acc49155f0b4362", "filename": "/src/app/app-routing.module.ts", "url": "https://gitlab.com/foo/bar/baz/-/blob/b45d2822d6c87770af520d7e2acc49155f0b4362/src/src/app/app-routing.module.ts#L8-10", "lines": "8:5-10:2", @@ -32,7 +32,7 @@ "group": "Code-Style", "lines": [ { - "sha": "b45d2822d6c87770af520d7e2acc49155f0b4362", + "revision": "b45d2822d6c87770af520d7e2acc49155f0b4362", "filename": "/src/app/app.component.html", "url": "https://gitlab.com/foo/bar/baz/-/blob/b45d2822d6c87770af520d7e2acc49155f0b4362/src/src/app/app.component.html#L12-14", "lines": "12:12-14:16", @@ -44,7 +44,7 @@ "code": "Zm9udC1mYW1pbHk6IC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgIlNlZ29lIFVJIiwgUm9ib3RvLCBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmLCAiQXBwbGUgQ29sb3IgRW1vamkiLCAiU2Vnb2UgVUkgRW1vamkiLCAiU2Vnb2UgVUkgU3ltYm9sIjsKZm9udC1zaXplOiAxNHB4Owpjb2xvcjogIzMzMzsKYm94LXNpemluZzogYm9yZGVyLWJveDs=" }, { - "sha": "b45d2822d6c87770af520d7e2acc49155f0b4362", + "revision": "b45d2822d6c87770af520d7e2acc49155f0b4362", "filename": "/src/app/app.component.spec.ts", "url": "https://gitlab.com/foo/bar/baz/-/blob/b45d2822d6c87770af520d7e2acc49155f0b4362/src/src/app/app.component.spec.ts#L10-14", "lines": "10:8-14:19", diff --git a/src/test/suite/workspace-util.test.ts b/src/test/suite/workspace-util.test.ts index 2cb37bb..599887e 100644 --- a/src/test/suite/workspace-util.test.ts +++ b/src/test/suite/workspace-util.test.ts @@ -22,7 +22,7 @@ import { escapeEndOfLineForCsv, unescapeEndOfLineFromCsv, rangeFromStringDefinition, - standardizeFilename, + relativeToWorkspace, rangesFromStringDefinition, splitStringDefinition, getBackupFilename, @@ -290,7 +290,7 @@ suite('Workspace Utils', () => { suite('sortCsvEntryForLines', () => { const dummyEntry = { - sha: 'string', + revision: 'string', filename: 'string', url: 'string', title: 'string', @@ -369,11 +369,18 @@ suite('Workspace Utils', () => { }); }); - suite('standardizeFilename', () => { - test('should return a refined filename', () => { + suite('relativeToWorkspace', () => { + test('should return filename', () => { const workspaceRoot = '/path/to/my/workspace'; - const filename = '/my/file'; - const refined = standardizeFilename(workspaceRoot, workspaceRoot + filename); + const filename = 'file'; + const refined = relativeToWorkspace(workspaceRoot, path.join(workspaceRoot, filename)); + assert.strictEqual(refined, filename); + }); + + test('should return filename with relative folder', () => { + const workspaceRoot = '/path/to/my/workspace'; + const filename = 'and/my/file'; + const refined = relativeToWorkspace(workspaceRoot, path.join(workspaceRoot, filename)); assert.strictEqual(refined, filename); }); }); @@ -548,10 +555,4 @@ suite('Workspace Utils', () => { assert.ok(getBackupFilename('test-csv').endsWith('Z.bak')); }); }); - - suite('standardizeFilename', () => { - test('should remove the workspace-part from the filename', () => { - assert.strictEqual(standardizeFilename('/foo/bar', '/foo/bar/baz.txt'), '/baz.txt'); - }); - }); }); diff --git a/src/utils/workspace-util.ts b/src/utils/workspace-util.ts index fb3685b..2b0cea0 100644 --- a/src/utils/workspace-util.ts +++ b/src/utils/workspace-util.ts @@ -218,6 +218,6 @@ export const getBackupFilename = (reviewFilePath: string): string => { * @param workspaceRoot The root path of the workspace * @param filename The name of the file */ -export const standardizeFilename = (workspaceRoot: string, filename: string): string => { - return filename.replace(workspaceRoot, ''); +export const relativeToWorkspace = (workspaceRoot: string, filename: string): string => { + return path.relative(workspaceRoot, filename); }; diff --git a/src/vcs-provider.ts b/src/vcs-provider.ts new file mode 100644 index 0000000..4fc77de --- /dev/null +++ b/src/vcs-provider.ts @@ -0,0 +1,81 @@ +import { PathLike } from 'fs'; +import { workspace } from 'vscode'; +const gitCommitId = require('git-commit-id'); +const { exec } = require('child_process'); +import * as vscode from 'vscode'; +import path from 'path'; + +export enum VcsKind { + git = 'git', + svn = 'svn', +} + +/** + * Gets the svn revision for the given `file`. + * + * @remark Requires SVN command-line client to be installed and in the user path. + * + * @param file Path to file to get the SVN revision for. + * + * @returns SVN revision of `file`. + * @throws Error message if SVN command-line client is not installed or SVN revision for file could not be retrieved. + */ +async function svnCommitId(file: string, workspace: string): Promise { + return new Promise((resolve, reject) => { + exec( + `svn info --show-item last-changed-revision ${file}`, + { cwd: workspace }, + (error: Error, stdout: string, stderr: string) => { + if (error) { + throw new Error(`Could not retrieve SVN revision for file: ${file}. Error(s): ${stderr}`); + } + + resolve(Number(stdout.trim())); + }, + ); + }); +} + +/** + * + * @param file + * @returns + */ +export async function commitId(file: string, workspace: string): Promise { + switch (vcsKind()) { + case VcsKind.git: { + // const gitDirectory = workspace.getConfiguration().get('code-review.gitDirectory') as string; + // const gitRepositoryPath = path.resolve(this.workspaceRoot, gitDirectory); + + const fileUri = path.parse(file.toString()); + return Promise.resolve(gitCommitId({ cwd: workspace })); + } + + case VcsKind.svn: { + return new Promise((resolve, reject) => { + svnCommitId(file, workspace).then((revision: number) => resolve(`${revision}`)); + }); + } + + default: + // Must never happen, i.e., should be dealt with by, e.g., `vcsKind()`. + return Promise.reject(); + } +} + +/** + * + * @returns + */ +export function vcsKind(): VcsKind { + const provider = workspace.getConfiguration().get('code-review.vcs.provider'); + + switch (provider) { + case 'git': + return VcsKind.git; + case 'svn': + return VcsKind.svn; + default: + throw new Error(`Unsupported VCS provider: ${provider}`); + } +} diff --git a/src/webview.html b/src/webview.html index 6f038ab..aec24c7 100644 --- a/src/webview.html +++ b/src/webview.html @@ -322,7 +322,7 @@

FILENAME

const private = document.getElementById('private').checked ? 1 : 0; const formData = { - sha: '', + revision: '', filename: '', url: '', lines: '', From 97d23a907d846a3f79bd4612e96d56ab6721778b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carsten=20R=C3=B6snick-Neugebauer?= Date: Wed, 17 Nov 2021 16:22:41 +0100 Subject: [PATCH 2/9] feat: add git-svn provider --- package.json | 3 ++- src/vcs-provider.ts | 28 +++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 29f15a1..1d965ed 100644 --- a/package.json +++ b/package.json @@ -330,7 +330,8 @@ "code-review.vcs.provider": { "enum": [ "git", - "svn" + "svn", + "git-svn" ], "default": "git", "title": "Version Control", diff --git a/src/vcs-provider.ts b/src/vcs-provider.ts index 4fc77de..5f9b86b 100644 --- a/src/vcs-provider.ts +++ b/src/vcs-provider.ts @@ -8,6 +8,7 @@ import path from 'path'; export enum VcsKind { git = 'git', svn = 'svn', + gitsvn = 'git-svn', } /** @@ -27,7 +28,7 @@ async function svnCommitId(file: string, workspace: string): Promise { { cwd: workspace }, (error: Error, stdout: string, stderr: string) => { if (error) { - throw new Error(`Could not retrieve SVN revision for file: ${file}. Error(s): ${stderr}`); + reject(`Could not retrieve SVN revision for file: ${file}. Error(s): ${stderr}`); } resolve(Number(stdout.trim())); @@ -36,6 +37,25 @@ async function svnCommitId(file: string, workspace: string): Promise { }); } +async function gitsvnCommitId(file: string, workspace: string): Promise { + return new Promise((resolve, reject) => { + exec(`git svn info ${file}`, { cwd: workspace }, (error: Error, stdout: string, stderr: string) => { + if (error) { + reject(`Could not retrieve SVN revision for file: ${file}. Error(s): ${stderr}`); + } + + const revRegex = /^Last Changed Rev: (\d+)\s*$/gm; + const matcher = stdout.trim().match(revRegex); + + if (matcher) { + resolve(Number(matcher[1])); + } + + reject('Could not derive SVN revision from git-svn history.'); + }); + }); +} + /** * * @param file @@ -57,6 +77,12 @@ export async function commitId(file: string, workspace: string): Promise }); } + case VcsKind.gitsvn: { + return new Promise((resolve, reject) => { + gitsvnCommitId(file, workspace).then((revision: number) => resolve(`${revision}`)); + }); + } + default: // Must never happen, i.e., should be dealt with by, e.g., `vcsKind()`. return Promise.reject(); From e02ba6146a55c165f6a3a2882f6ce812715530a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carsten=20R=C3=B6snick-Neugebauer?= Date: Thu, 18 Nov 2021 23:05:27 +0100 Subject: [PATCH 3/9] feat: add tests for git and gitsvn provider Also add typescript declarations for stub-spawn-once --- package-lock.json | 572 +++++++++++++++++++++++++++- package.json | 10 +- src/review-comment.ts | 4 +- src/test/stub-spawn-once.d.ts | 4 + src/test/suite/vcs-provider.test.ts | 114 ++++++ src/vcs-provider.ts | 40 +- tsconfig.json | 2 +- 7 files changed, 721 insertions(+), 25 deletions(-) create mode 100644 src/test/stub-spawn-once.d.ts create mode 100644 src/test/suite/vcs-provider.test.ts diff --git a/package-lock.json b/package-lock.json index c717997..174a977 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,6 @@ "license": "SEE LICENSE IN LICENSE", "dependencies": { "@fast-csv/parse": "^4.3.6", - "common-tags": "^1.8.2", "git-commit-id": "^2.0.1", "handlebars": "^4.7.7", "js-base64": "^3.6.1", @@ -20,16 +19,22 @@ "devDependencies": { "@commitlint/cli": "^11.0.0", "@commitlint/config-conventional": "^11.0.0", + "@types/chai": "^4.2.22", + "@types/chai-as-promised": "^7.1.4", "@types/common-tags": "^1.8.1", "@types/glob": "^7.1.3", "@types/handlebars": "^4.1.0", "@types/js-base64": "^3.0.0", "@types/mocha": "^8.2.2", "@types/node": "14.x", + "@types/sinon": "^10.0.6", "@types/vscode": "^1.59.0", "@typescript-eslint/eslint-plugin": "^4.26.0", "@typescript-eslint/parser": "^4.26.0", + "chai": "^4.3.4", + "chai-as-promised": "^7.1.1", "commitizen": "^4.2.4", + "common-tags": "^1.8.2", "copy-webpack-plugin": "^6.2.1", "cz-conventional-changelog": "^3.3.0", "eslint": "^7.27.0", @@ -39,9 +44,11 @@ "mocha": "^8.4.0", "prettier": "^2.1.2", "pretty-quick": "^3.1.0", + "sinon": "^12.0.1", "standard-version": "^9.3.1", + "stub-spawn-once": "^2.3.0", "ts-loader": "^9.2.2", - "typescript": "^4.3.2", + "typescript": "^4.5.2", "vsce": "^1.96.1", "vscode-extension-tester": "^4.1.2", "vscode-test": "^1.5.2", @@ -737,6 +744,41 @@ "node": ">=10" } }, + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", + "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.0.2.tgz", + "integrity": "sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -746,6 +788,21 @@ "node": ">= 6" } }, + "node_modules/@types/chai": { + "version": "4.2.22", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.22.tgz", + "integrity": "sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ==", + "dev": true + }, + "node_modules/@types/chai-as-promised": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.4.tgz", + "integrity": "sha512-1y3L1cHePcIm5vXkh1DSGf/zQq5n5xDKG1fpCvf18+uOkpce0Z1ozNFPkyWsVswK7ntN1sZBw3oU6gmN+pDUcA==", + "dev": true, + "dependencies": { + "@types/chai": "*" + } + }, "node_modules/@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -857,6 +914,24 @@ "integrity": "sha512-OFUilxQg+rWL2FMxtmIgCkUDlJB6pskkpvmew7yeXfzzsOBb5rc+y2+DjHm+r3r1ZPPcJefK3DveNSYWGiy68g==", "dev": true }, + "node_modules/@types/sinon": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.6.tgz", + "integrity": "sha512-6EF+wzMWvBNeGrfP3Nx60hhx+FfwSg1JJBLAAP/IdIUq0EYkqCYf70VT3PhuhPX9eLD+Dp+lNdpb/ZeHG8Yezg==", + "dev": true, + "dependencies": { + "@sinonjs/fake-timers": "^7.1.0" + } + }, + "node_modules/@types/sinon/node_modules/@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, "node_modules/@types/vscode": { "version": "1.59.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.59.0.tgz", @@ -1493,6 +1568,15 @@ "node": ">=0.8" } }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -1841,6 +1925,35 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, + "node_modules/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, + "dependencies": { + "check-error": "^1.0.2" + }, + "peerDependencies": { + "chai": ">= 2.1.2 < 5" + } + }, "node_modules/chainsaw": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", @@ -1873,6 +1986,24 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/cheerio": { "version": "1.0.0-rc.10", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", @@ -2287,6 +2418,7 @@ "version": "1.8.2", "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, "engines": { "node": ">=4.0.0" } @@ -3599,6 +3731,18 @@ "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", "dev": true }, + "node_modules/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -4811,6 +4955,15 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", @@ -6040,6 +6193,12 @@ "set-immediate-shim": "~1.0.1" } }, + "node_modules/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -6049,6 +6208,15 @@ "node": ">=0.10.0" } }, + "node_modules/lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha1-eZllXoZGwX8In90YfRUNMyTVRRM=", + "dev": true, + "engines": { + "node": "> 0.8" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -6191,6 +6359,12 @@ "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=" }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, "node_modules/lodash.groupby": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", @@ -7115,6 +7289,28 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node_modules/nise": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz", + "integrity": "sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^7.0.4", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/nise/node_modules/@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, "node_modules/node-releases": { "version": "1.1.74", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.74.tgz", @@ -7445,6 +7641,21 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/path-to-regexp/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -7454,6 +7665,15 @@ "node": ">=8" } }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -8232,6 +8452,45 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, + "node_modules/sinon": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-12.0.1.tgz", + "integrity": "sha512-iGu29Xhym33ydkAT+aNQFBINakjq69kKO6ByPvTsm3yyIACfyQttRTP03aBP/I8GfhFmLzrnKwNNkr0ORb1udg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^8.1.0", + "@sinonjs/samsam": "^6.0.2", + "diff": "^5.0.0", + "nise": "^5.1.0", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -8762,6 +9021,35 @@ "node": ">=8" } }, + "node_modules/stub-spawn-once": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/stub-spawn-once/-/stub-spawn-once-2.3.0.tgz", + "integrity": "sha1-NOp1CPsAGZLwA+1T8rWIIniDT7s=", + "dev": true, + "dependencies": { + "check-more-types": "2.24.0", + "debug": "2.6.8", + "lazy-ass": "1.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stub-spawn-once/node_modules/debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/stub-spawn-once/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -9309,6 +9597,15 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", @@ -9340,9 +9637,9 @@ "dev": true }, "node_modules/typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", + "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -10818,12 +11115,62 @@ "mkdirp": "^1.0.4" } }, + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", + "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@sinonjs/samsam": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.0.2.tgz", + "integrity": "sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, + "@types/chai": { + "version": "4.2.22", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.22.tgz", + "integrity": "sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ==", + "dev": true + }, + "@types/chai-as-promised": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.4.tgz", + "integrity": "sha512-1y3L1cHePcIm5vXkh1DSGf/zQq5n5xDKG1fpCvf18+uOkpce0Z1ozNFPkyWsVswK7ntN1sZBw3oU6gmN+pDUcA==", + "dev": true, + "requires": { + "@types/chai": "*" + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -10934,6 +11281,26 @@ "integrity": "sha512-OFUilxQg+rWL2FMxtmIgCkUDlJB6pskkpvmew7yeXfzzsOBb5rc+y2+DjHm+r3r1ZPPcJefK3DveNSYWGiy68g==", "dev": true }, + "@types/sinon": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.6.tgz", + "integrity": "sha512-6EF+wzMWvBNeGrfP3Nx60hhx+FfwSg1JJBLAAP/IdIUq0EYkqCYf70VT3PhuhPX9eLD+Dp+lNdpb/ZeHG8Yezg==", + "dev": true, + "requires": { + "@sinonjs/fake-timers": "^7.1.0" + }, + "dependencies": { + "@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + } + } + }, "@types/vscode": { "version": "1.59.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.59.0.tgz", @@ -11408,6 +11775,12 @@ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -11684,6 +12057,29 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, + "chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, + "requires": { + "check-error": "^1.0.2" + } + }, "chainsaw": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", @@ -11710,6 +12106,18 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=", + "dev": true + }, "cheerio": { "version": "1.0.0-rc.10", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", @@ -12044,7 +12452,8 @@ "common-tags": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==" + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true }, "commondir": { "version": "1.0.1", @@ -13054,6 +13463,15 @@ "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", "dev": true }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -13985,6 +14403,12 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, "get-intrinsic": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", @@ -14912,12 +15336,24 @@ "set-immediate-shim": "~1.0.1" } }, + "just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, + "lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha1-eZllXoZGwX8In90YfRUNMyTVRRM=", + "dev": true + }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -15038,6 +15474,12 @@ "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=" }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, "lodash.groupby": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", @@ -15737,6 +16179,30 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "nise": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz", + "integrity": "sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^7.0.4", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + } + } + }, "node-releases": { "version": "1.1.74", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.74.tgz", @@ -15993,12 +16459,35 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -16598,6 +17087,37 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, + "sinon": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-12.0.1.tgz", + "integrity": "sha512-iGu29Xhym33ydkAT+aNQFBINakjq69kKO6ByPvTsm3yyIACfyQttRTP03aBP/I8GfhFmLzrnKwNNkr0ORb1udg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^8.1.0", + "@sinonjs/samsam": "^6.0.2", + "diff": "^5.0.0", + "nise": "^5.1.0", + "supports-color": "^7.2.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -17009,6 +17529,34 @@ "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", "dev": true }, + "stub-spawn-once": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/stub-spawn-once/-/stub-spawn-once-2.3.0.tgz", + "integrity": "sha1-NOp1CPsAGZLwA+1T8rWIIniDT7s=", + "dev": true, + "requires": { + "check-more-types": "2.24.0", + "debug": "2.6.8", + "lazy-ass": "1.6.0" + }, + "dependencies": { + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -17432,6 +17980,12 @@ "prelude-ls": "^1.2.1" } }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "type-fest": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", @@ -17456,9 +18010,9 @@ "dev": true }, "typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", + "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", "dev": true }, "uc.micro": { diff --git a/package.json b/package.json index 1d965ed..552c587 100644 --- a/package.json +++ b/package.json @@ -428,16 +428,22 @@ "devDependencies": { "@commitlint/cli": "^11.0.0", "@commitlint/config-conventional": "^11.0.0", + "@types/chai": "^4.2.22", + "@types/chai-as-promised": "^7.1.4", "@types/common-tags": "^1.8.1", "@types/glob": "^7.1.3", "@types/handlebars": "^4.1.0", "@types/js-base64": "^3.0.0", "@types/mocha": "^8.2.2", "@types/node": "14.x", + "@types/sinon": "^10.0.6", "@types/vscode": "^1.59.0", "@typescript-eslint/eslint-plugin": "^4.26.0", "@typescript-eslint/parser": "^4.26.0", + "chai": "^4.3.4", + "chai-as-promised": "^7.1.1", "commitizen": "^4.2.4", + "common-tags": "^1.8.2", "copy-webpack-plugin": "^6.2.1", "cz-conventional-changelog": "^3.3.0", "eslint": "^7.27.0", @@ -447,9 +453,11 @@ "mocha": "^8.4.0", "prettier": "^2.1.2", "pretty-quick": "^3.1.0", + "sinon": "^12.0.1", "standard-version": "^9.3.1", + "stub-spawn-once": "^2.3.0", "ts-loader": "^9.2.2", - "typescript": "^4.3.2", + "typescript": "^4.5.2", "vsce": "^1.96.1", "vscode-extension-tester": "^4.1.2", "vscode-test": "^1.5.2", diff --git a/src/review-comment.ts b/src/review-comment.ts index 3d92b89..b674f0a 100644 --- a/src/review-comment.ts +++ b/src/review-comment.ts @@ -12,7 +12,7 @@ import { import { getSelectionStringDefinition, hasSelection } from './utils/editor-utils'; import { getCsvFileLinesAsArray, setCsvFileLines } from './utils/storage-utils'; import path from 'path'; -import { commitId } from './vcs-provider'; +import { revision } from './vcs-provider'; export class ReviewCommentService { constructor(private reviewFile: string, private workspaceRoot: string) {} @@ -125,7 +125,7 @@ export class ReviewCommentService { const copy = { ...comment }; try { - copy.revision = await commitId(comment.filename, workspaceRoot); + copy.revision = await revision(comment.filename, workspaceRoot); } catch (error) { copy.revision = ''; window.showErrorMessage(`Repository not under version control as configured in the plugin's settings. diff --git a/src/test/stub-spawn-once.d.ts b/src/test/stub-spawn-once.d.ts new file mode 100644 index 0000000..68db742 --- /dev/null +++ b/src/test/stub-spawn-once.d.ts @@ -0,0 +1,4 @@ +declare module 'stub-spawn-once'; + +declare function stubExecOnce(command: string, stdout: string): any; +declare function stubExecOnce(command: string, exit: number, stdout: string, stderr: string): any; diff --git a/src/test/suite/vcs-provider.test.ts b/src/test/suite/vcs-provider.test.ts new file mode 100644 index 0000000..c99fcd2 --- /dev/null +++ b/src/test/suite/vcs-provider.test.ts @@ -0,0 +1,114 @@ +import * as assert from 'assert'; +import * as process from 'child_process'; +import * as sinon from 'sinon'; +import * as chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { stripIndents } from 'common-tags'; +import { stubExecOnce } from 'stub-spawn-once'; + +import { gitsvnRevision, svnRevision } from '../../vcs-provider'; + +chai.use(chaiAsPromised); +chai.should(); + +suite('VCS Provider', () => { + suite('svnRevision', () => { + test('should extract the revision if command succeeds', () => { + const file = 'my/file.txt'; + stubExecOnce(`svn info --show-item last-changed-revision ${file}`, '42'); + + return svnRevision('my/file.txt', 'some/workspace').should.eventually.equal(42); + }); + + test('should fail on non-zero exit code', () => { + const file = 'my/file.txt'; + stubExecOnce(`svn info --show-item last-changed-revision ${file}`, 1, '', 'my-error'); + + return svnRevision('my/file.txt', 'some/workspace').should.eventually.be.rejectedWith('my-error'); + }); + + test('should fail on unexpected command output', () => { + const file = 'my/file.txt'; + stubExecOnce(`svn info --show-item last-changed-revision ${file}`, 0, 'not-a-number', ''); + + return svnRevision('my/file.txt', 'some/workspace').should.eventually.be.rejectedWith('not-a-number'); + }); + }); + + suite('gitsvnRevision', () => { + test('should extract the revision if command succeeds', () => { + const file = 'file.md'; + const gitsvnOutput = stripIndents` + Path: ${file} + Name: ${file} + Working Copy Root Path: /home/someone/gitsvn-test-repo-checkout + URL: file:///home/someone/gitsvn-test-repo/${file} + Relative URL: ^/${file} + Repository Root: file:///home/someone/gitsvn-test-repo + Repository UUID: 1e59ce09-e6f8-40d7-8293-efd8ad2d6261 + Revision: 123 + Node Kind: file + Schedule: normal + Last Changed Author: someone + Last Changed Rev: 42 + Last Changed Date: 2021-11-15 12:26:41 +0100 (Mo, 15 Nov 2021) + Text Last Updated: 2021-11-15 12:26:28 +0100 (Mo, 15 Nov 2021) + `; + + stubExecOnce(`git svn info ${file}`, gitsvnOutput); + return gitsvnRevision(file, 'some/workspace').should.eventually.equal(42); + }); + + test('should fail when revision in output has unexpected format', () => { + const file = 'file.md'; + const gitsvnOutput = stripIndents` + Path: ${file} + Name: ${file} + Working Copy Root Path: /home/someone/gitsvn-test-repo-checkout + URL: file:///home/someone/gitsvn-test-repo/${file} + Relative URL: ^/${file} + Repository Root: file:///home/someone/gitsvn-test-repo + Repository UUID: 1e59ce09-e6f8-40d7-8293-efd8ad2d6261 + Revision: 123 + Node Kind: file + Schedule: normal + Last Changed Author: someone + Last Changed Rev: not-a-number + Last Changed Date: 2021-11-15 12:26:41 +0100 (Mo, 15 Nov 2021) + Text Last Updated: 2021-11-15 12:26:28 +0100 (Mo, 15 Nov 2021) + `; + + stubExecOnce(`git svn info ${file}`, gitsvnOutput); + return gitsvnRevision(file, 'some/workspace').should.eventually.be.rejectedWith('Could not derive'); + }); + + test('should fail when output is of unexpected format', () => { + const file = 'file.md'; + const gitsvnOutput = stripIndents` + Path: ${file} + Name: ${file} + Working Copy Root Path: /home/someone/gitsvn-test-repo-checkout + URL: file:///home/someone/gitsvn-test-repo/${file} + Relative URL: ^/${file} + Repository Root: file:///home/someone/gitsvn-test-repo + Repository UUID: 1e59ce09-e6f8-40d7-8293-efd8ad2d6261 + Revision: 123 + Node Kind: file + Schedule: normal + Last Changed Author: someone + Last Changed Date: 2021-11-15 12:26:41 +0100 (Mo, 15 Nov 2021) + Text Last Updated: 2021-11-15 12:26:28 +0100 (Mo, 15 Nov 2021) + `; + + stubExecOnce(`git svn info ${file}`, gitsvnOutput); + return gitsvnRevision(file, 'some/workspace').should.eventually.be.rejectedWith('Could not derive'); + }); + + test('should fail when git-svn exits non-zero', () => { + const file = 'file.md'; + + stubExecOnce(`git svn info ${file}`, 1, '', ''); + return gitsvnRevision(file, 'some/workspace').should.eventually.be.rejectedWith('Could not retrieve'); + }); + }); +}); diff --git a/src/vcs-provider.ts b/src/vcs-provider.ts index 5f9b86b..5d6d58c 100644 --- a/src/vcs-provider.ts +++ b/src/vcs-provider.ts @@ -1,8 +1,6 @@ -import { PathLike } from 'fs'; import { workspace } from 'vscode'; const gitCommitId = require('git-commit-id'); const { exec } = require('child_process'); -import * as vscode from 'vscode'; import path from 'path'; export enum VcsKind { @@ -21,23 +19,37 @@ export enum VcsKind { * @returns SVN revision of `file`. * @throws Error message if SVN command-line client is not installed or SVN revision for file could not be retrieved. */ -async function svnCommitId(file: string, workspace: string): Promise { +export async function svnRevision(file: string, workspace: string): Promise { return new Promise((resolve, reject) => { exec( `svn info --show-item last-changed-revision ${file}`, { cwd: workspace }, - (error: Error, stdout: string, stderr: string) => { + (error: Error | null, stdout: string, stderr: string) => { if (error) { reject(`Could not retrieve SVN revision for file: ${file}. Error(s): ${stderr}`); } - resolve(Number(stdout.trim())); + const maybeRevision = Number(stdout.trim()); + if (isNaN(maybeRevision)) { + reject(`Unexpected command output: ${stdout}`); + } + resolve(maybeRevision); }, ); }); } -async function gitsvnCommitId(file: string, workspace: string): Promise { +/** + * Gets the svn revision for the given `file` from the underlying git-svn repository. + * + * @remark Requires git command-line client to be installed and in the user path. + * + * @param file Path to file to get the SVN revision for. + * + * @returns SVN revision of `file`. + * @throws Error message if git command-line client is not installed or SVN revision for file could not be retrieved. + */ +export async function gitsvnRevision(file: string, workspace: string): Promise { return new Promise((resolve, reject) => { exec(`git svn info ${file}`, { cwd: workspace }, (error: Error, stdout: string, stderr: string) => { if (error) { @@ -45,10 +57,14 @@ async function gitsvnCommitId(file: string, workspace: string): Promise } const revRegex = /^Last Changed Rev: (\d+)\s*$/gm; - const matcher = stdout.trim().match(revRegex); + const matches = [...stdout.trim().matchAll(revRegex)]; - if (matcher) { - resolve(Number(matcher[1])); + if (matches.length) { + const maybeRevision = Number(matches[0][1]); + if (isNaN(maybeRevision)) { + reject(`Unexpected command output: ${matches[0][1]}`); + } + resolve(maybeRevision); } reject('Could not derive SVN revision from git-svn history.'); @@ -61,7 +77,7 @@ async function gitsvnCommitId(file: string, workspace: string): Promise * @param file * @returns */ -export async function commitId(file: string, workspace: string): Promise { +export async function revision(file: string, workspace: string): Promise { switch (vcsKind()) { case VcsKind.git: { // const gitDirectory = workspace.getConfiguration().get('code-review.gitDirectory') as string; @@ -73,13 +89,13 @@ export async function commitId(file: string, workspace: string): Promise case VcsKind.svn: { return new Promise((resolve, reject) => { - svnCommitId(file, workspace).then((revision: number) => resolve(`${revision}`)); + svnRevision(file, workspace).then((revision: number) => resolve(`${revision}`)); }); } case VcsKind.gitsvn: { return new Promise((resolve, reject) => { - gitsvnCommitId(file, workspace).then((revision: number) => resolve(`${revision}`)); + gitsvnRevision(file, workspace).then((revision: number) => resolve(`${revision}`)); }); } diff --git a/tsconfig.json b/tsconfig.json index a76a167..0d169da 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "module": "commonjs", "target": "es6", "outDir": "out", - "lib": ["es6"], + "lib": ["es6", "es2020.string"], "sourceMap": true, "rootDir": "src", "strict": true /* enable all strict type-checking options */, From b629623b8a26a659b737630f84efb25237f49a19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carsten=20R=C3=B6snick-Neugebauer?= Date: Thu, 25 Nov 2021 16:30:29 +0100 Subject: [PATCH 4/9] fix: remove unused imports --- src/test/suite/vcs-provider.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test/suite/vcs-provider.test.ts b/src/test/suite/vcs-provider.test.ts index c99fcd2..917fdca 100644 --- a/src/test/suite/vcs-provider.test.ts +++ b/src/test/suite/vcs-provider.test.ts @@ -1,6 +1,3 @@ -import * as assert from 'assert'; -import * as process from 'child_process'; -import * as sinon from 'sinon'; import * as chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { stripIndents } from 'common-tags'; From bcf43d202cd1b0e07850cdb8ef8541e90c11424e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carsten=20R=C3=B6snick-Neugebauer?= Date: Fri, 26 Nov 2021 11:03:23 +0100 Subject: [PATCH 5/9] feat: enable git controls (e.g., gitlab export) when git is used --- package-lock.json | 27 +++------------------------ package.json | 8 ++++---- src/export-factory.ts | 21 +++++++++++---------- src/review-comment.ts | 1 - src/vcs-provider.ts | 27 +++++++++++++++++---------- src/workspace.ts | 35 +++++++++++++++++++++++++---------- 6 files changed, 60 insertions(+), 59 deletions(-) diff --git a/package-lock.json b/package-lock.json index 174a977..2acc403 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "git-commit-id": "^2.0.1", "handlebars": "^4.7.7", "js-base64": "^3.6.1", - "strip-indent": "^4.0.0", "uuid": "8.3.2" }, "devDependencies": { @@ -6714,6 +6713,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, "engines": { "node": ">=4" } @@ -8998,20 +8998,6 @@ "node": ">=6" } }, - "node_modules/strip-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz", - "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==", - "dependencies": { - "min-indent": "^1.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/strip-json-comments": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", @@ -15758,7 +15744,8 @@ "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true }, "minimatch": { "version": "3.0.4", @@ -17515,14 +17502,6 @@ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true }, - "strip-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz", - "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==", - "requires": { - "min-indent": "^1.0.1" - } - }, "strip-json-comments": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", diff --git a/package.json b/package.json index 552c587..6130680 100644 --- a/package.json +++ b/package.json @@ -130,12 +130,12 @@ }, { "command": "codeReview.exportAsGitLabImportableCsv", - "when": "view == code-review.list", + "when": "view == code-review.list && config.code-review.vcs.provider == git", "group": "export_source_control@1" }, { "command": "codeReview.exportAsGitHubImportableCsv", - "when": "view == code-review.list", + "when": "view == code-review.list && config.code-review.vcs.provider == git", "group": "export_source_control@0" }, { @@ -160,12 +160,12 @@ }, { "command": "codeReview.filterByCommitEnable", - "when": "view == code-review.list && !isFilteredByCommit", + "when": "view == code-review.list && !isFilteredByCommit && config.code-review.vcs.provider == git", "group": "navigation@1" }, { "command": "codeReview.filterByCommitDisable", - "when": "view == code-review.list && isFilteredByCommit", + "when": "view == code-review.list && isFilteredByCommit && config.code-review.vcs.provider == git", "group": "navigation@1" }, { diff --git a/src/export-factory.ts b/src/export-factory.ts index 53c4d67..b252c87 100644 --- a/src/export-factory.ts +++ b/src/export-factory.ts @@ -32,6 +32,7 @@ import { ReviewFileExportSection, GroupBy, ExportFormat, ExportMap, Group } from import { CsvEntry, CsvStructure } from './model'; import { CommentListEntry } from './comment-list-entry'; import { FileGenerator } from './file-generator'; +import { gitRevision } from './vcs-provider'; import { Location, parseLocation, themeColorForPriority } from './utils/editor-utils'; const gitCommitId = require('git-commit-id'); @@ -539,17 +540,17 @@ export class ExportFactory { public setFilterByCommit(state: boolean): boolean { this.filterByCommit = state; if (this.filterByCommit) { - try { - const gitDirectory = workspace.getConfiguration().get('code-review.gitDirectory') as string; - const gitRepositoryPath = path.resolve(this.workspaceRoot, gitDirectory); - - this.currentCommitId = gitCommitId({ cwd: gitRepositoryPath }); - } catch (error) { - this.filterByCommit = false; - this.currentCommitId = null; + gitRevision('.', this.workspaceRoot).then( + (revision: string) => { + this.currentCommitId = revision; + }, + (error: string) => { + this.filterByCommit = false; + this.currentCommitId = null; - console.log('Not in a git repository. Disabling filter by commit', error); - } + console.log('Not in a git repository. Disabling filter by commit.', error); + }, + ); } else { this.currentCommitId = null; } diff --git a/src/review-comment.ts b/src/review-comment.ts index b674f0a..49c4a30 100644 --- a/src/review-comment.ts +++ b/src/review-comment.ts @@ -11,7 +11,6 @@ import { } from './utils/workspace-util'; import { getSelectionStringDefinition, hasSelection } from './utils/editor-utils'; import { getCsvFileLinesAsArray, setCsvFileLines } from './utils/storage-utils'; -import path from 'path'; import { revision } from './vcs-provider'; export class ReviewCommentService { diff --git a/src/vcs-provider.ts b/src/vcs-provider.ts index 5d6d58c..20f0e20 100644 --- a/src/vcs-provider.ts +++ b/src/vcs-provider.ts @@ -1,7 +1,7 @@ -import { workspace } from 'vscode'; +import * as vscode from 'vscode'; const gitCommitId = require('git-commit-id'); -const { exec } = require('child_process'); -import path from 'path'; +import { exec, ExecException } from 'child_process'; +import * as path from 'path'; export enum VcsKind { git = 'git', @@ -51,7 +51,7 @@ export async function svnRevision(file: string, workspace: string): Promise { return new Promise((resolve, reject) => { - exec(`git svn info ${file}`, { cwd: workspace }, (error: Error, stdout: string, stderr: string) => { + exec(`git svn info ${file}`, { cwd: workspace }, (error, stdout: string, stderr: string) => { if (error) { reject(`Could not retrieve SVN revision for file: ${file}. Error(s): ${stderr}`); } @@ -72,6 +72,17 @@ export async function gitsvnRevision(file: string, workspace: string): Promise { + const gitDirectory = (vscode.workspace.getConfiguration().get('code-review.vcs.git.directory') as string) ?? '.'; + const gitRepositoryPath = path.resolve(workspace, gitDirectory); + + try { + return Promise.resolve(gitCommitId({ cwd: gitRepositoryPath })); + } catch (error) { + return Promise.reject(error); + } +} + /** * * @param file @@ -80,11 +91,7 @@ export async function gitsvnRevision(file: string, workspace: string): Promise { switch (vcsKind()) { case VcsKind.git: { - // const gitDirectory = workspace.getConfiguration().get('code-review.gitDirectory') as string; - // const gitRepositoryPath = path.resolve(this.workspaceRoot, gitDirectory); - - const fileUri = path.parse(file.toString()); - return Promise.resolve(gitCommitId({ cwd: workspace })); + return gitRevision(file, workspace); } case VcsKind.svn: { @@ -110,7 +117,7 @@ export async function revision(file: string, workspace: string): Promise * @returns */ export function vcsKind(): VcsKind { - const provider = workspace.getConfiguration().get('code-review.vcs.provider'); + const provider = vscode.workspace.getConfiguration().get('code-review.vcs.provider'); switch (provider) { case 'git': diff --git a/src/workspace.ts b/src/workspace.ts index 7e48bdc..603716e 100644 --- a/src/workspace.ts +++ b/src/workspace.ts @@ -28,6 +28,7 @@ import { CommentListEntry } from './comment-list-entry'; import { ImportFactory, ConflictMode } from './import-factory'; import { gutterDecorations } from './utils/decoration-utils'; import { CommentLensProvider } from './comment-lens-provider'; +import { vcsKind, VcsKind } from './vcs-provider'; const checkForCodeReviewFile = (fileName: string) => { commands.executeCommand('setContext', 'codeReview:displayCodeReviewExplorer', fs.existsSync(fileName)); @@ -80,11 +81,14 @@ export class WorkspaceContext { this.updateReviewCommentService(); this.updateCommentsProvider(); this.setupFileWatcher(); - this.watchGitSwitch(); this.watchActiveEditor(); this.watchForFileChanges(); new CommentView(this.commentsProvider); this.updateDecorations(); + + if (vcsKind() === VcsKind.git) { + this.watchGitSwitch(); + } } watchActiveEditor() { @@ -119,7 +123,7 @@ export class WorkspaceContext { * Refresh comment view on git switch */ watchGitSwitch() { - const gitDirectory = (workspace.getConfiguration().get('code-review.gitDirectory') as string) ?? '.'; + const gitDirectory = (workspace.getConfiguration().get('code-review.vcs.git.directory') as string) ?? '.'; const gitHeadPath = path.resolve(gitDirectory, '.git/HEAD'); const gitWatcher = workspace.createFileSystemWatcher(`**${gitHeadPath}`); gitWatcher.onDidChange(() => { @@ -435,19 +439,24 @@ export class WorkspaceContext { this.openSelectionRegistration, this.addNoteRegistration, this.deleteNoteRegistration, - this.filterByCommitEnableRegistration, - this.filterByCommitDisableRegistration, this.filterByFilenameEnableRegistration, this.filterByFilenameDisableRegistration, this.exportAsHtmlWithDefaultTemplateRegistration, this.exportAsHtmlWithHandlebarsTemplateRegistration, - this.exportAsGitLabImportableCsvRegistration, - this.exportAsGitHubImportableCsvRegistration, this.exportAsJiraImportableCsvRegistration, this.exportAsJsonRegistration, this.importFromJsonRegistration, this.commentCodeLensProviderregistration, ); + + if (vcsKind() === VcsKind.git) { + this.context.subscriptions.push( + this.filterByCommitEnableRegistration, + this.filterByCommitDisableRegistration, + this.exportAsGitLabImportableCsvRegistration, + this.exportAsGitHubImportableCsvRegistration, + ); + } } /** @@ -457,18 +466,24 @@ export class WorkspaceContext { this.openSelectionRegistration.dispose(); this.addNoteRegistration.dispose(); this.deleteNoteRegistration.dispose(); - this.filterByCommitEnableRegistration.dispose(); - this.filterByCommitDisableRegistration.dispose(); + this.filterByFilenameEnableRegistration.dispose(); this.filterByFilenameDisableRegistration.dispose(); this.exportAsHtmlWithDefaultTemplateRegistration.dispose(); this.exportAsHtmlWithHandlebarsTemplateRegistration.dispose(); - this.exportAsGitLabImportableCsvRegistration.dispose(); - this.exportAsGitHubImportableCsvRegistration.dispose(); + this.exportAsJiraImportableCsvRegistration.dispose(); this.exportAsJsonRegistration.dispose(); this.importFromJsonRegistration.dispose(); this.commentCodeLensProviderregistration.dispose(); + + if (vcsKind() === VcsKind.git) { + this.filterByCommitEnableRegistration.dispose(); + this.filterByCommitDisableRegistration.dispose(); + this.exportAsGitLabImportableCsvRegistration.dispose(); + this.exportAsGitHubImportableCsvRegistration.dispose(); + } + this.updateSubscriptions(); } From 5feabf3cf48281a1dc7a5acddd4466a056582ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carsten=20R=C3=B6snick-Neugebauer?= Date: Fri, 26 Nov 2021 11:16:24 +0100 Subject: [PATCH 6/9] chore: update README; also rename anker -> anchor --- README.md | 37 ++++++++++++++++++++++++------------- package.json | 4 ++-- src/export-factory.ts | 14 +++++++------- src/model.md | 6 +++--- src/review-comment.ts | 18 +++++++++--------- 5 files changed, 45 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 32050d6..e30ea39 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,8 @@ This extension allows you to create a code review file you can hand over to a cu - [`code-review.privateCommentIcon`](#code-reviewprivatecommenticon) - [`code-review.defaultTemplatePath`](#code-reviewdefaulttemplatepath) - [`code-review.priorities`](#code-reviewpriorities) - - [`code-review.gitDirectory`](#code-reviewgitdirectory) + - [`code-review.vcs.git.provider`](#code-reviewvcsgitprovider) + - [`code-review.vcs.git.directory`](#code-reviewvcsgitdirectory) - [`code-review.filterCommentsByCommit`](#code-reviewfiltercommentsbycommit) - [`code-review.filterCommentsByFilename`](#code-reviewfiltercommentsbyfilename) - [`code-review.importBackup`](#code-reviewimportbackup) @@ -64,7 +65,7 @@ A file `code-review.csv` will be created containing your comments and the file a The result will look like this: ```csv -sha,filename,url,lines,title,comment,priority,additional +revision,filename,url,lines,title,comment,priority,additional "b45d2822d6c87770af520d7e2acc49155f0b4362","/test/a.txt","https://github.com/d-koppenhagen/vscode-code-review/tree/b45d2822d6c87770af520d7e2acc49155f0b4362/test/a.txt","1:2-4:3","foo","this should be refactored","Complexity",1,"see http://foo.bar" "b45d2822d6c87770af520d7e2acc49155f0b4362","/test/b.txt","https://github.com/d-koppenhagen/vscode-code-review/tree/b45d2822d6c87770af520d7e2acc49155f0b4362/test/b.txt","1:0-1:4|4:0-4:3","bar","wrong format","Best Practices",1,"" ``` @@ -166,7 +167,7 @@ By default `"code-review"` is used. ### `code-review.baseUrl` The base-URL is used to build a full link to the file. -It will be appended with the git SHA if available followed by the relative path of the file and the selected lines as an anker. +It will be appended with the revision if available followed by the relative path of the file and the selected lines as an anchor. This setting is skipped when the setting `code-review.customUrl` is defined which is more configurable. ```json @@ -182,14 +183,14 @@ This setting would lead into something like this: `https://github.com/foo/bar/bl The custom URL is used to build a full link to the file. The following placeholders are available: -- `{sha}`: insert the SHA ref for the file +- `{revision}`: insert the file's revision - `{file}`: insert the file name/path -- `{start}`: insert the start of the lines selection as an anker -- `{end}`: insert the end of the lines selection as an anker +- `{start}`: insert the start of the lines selection as an anchor +- `{end}`: insert the end of the lines selection as an anchor ```json { - "code-review.customUrl": "https://gitlab.com/foo/bar/baz/-/blob/{sha}/src/{file}#L{start}-{end}" + "code-review.customUrl": "https://gitlab.com/foo/bar/baz/-/blob/{revision}/src/{file}#L{start}-{end}" } ``` @@ -305,7 +306,17 @@ The defaults are listed below: } ``` -### `code-review.gitDirectory` +### `code-review.vcs.git.provider` + +Permits selecting the version control system (VCS) used by your repositories. +The default is git, but svn and git-svn are also supported. + +Note on git-svn: This provider is kind of a hybrid. It extracts the svn file revision through the git-svn-cloned repository. +Use case for this provider is an only temporarily available svn repository (e.g., only accessible from a certain +physical location, like an offline-in-house network) that has been locally cloned with git-svn. The interesting revision +number then is not the git sha as it this is only local information, but the underlying svn revision information. + +### `code-review.vcs.git.directory` Use this setting when the Git repository is located in an other directory than the workspace one. The path can be **relative** (prefixed with `.` or `..`) or **absolute** (prefixed with `/` on Linux/MacOS or `{drive}:\` on Windows). @@ -316,7 +327,7 @@ Examples: ```json { - "code-review.gitDirectory": "./app" + "code-review.vcs.git.directory": "./app" } ``` @@ -324,7 +335,7 @@ Examples: ```json { - "code-review.gitDirectory": "../app" + "code-review.vcs.git.directory": "../app" } ``` @@ -332,7 +343,7 @@ Examples: ```json { - "code-review.gitDirectory": "/path/to/my/app" + "code-review.vcs.git.directory": "/path/to/my/app" } ``` @@ -340,7 +351,7 @@ Examples: ```json { - "code-review.gitDirectory": "C:\\Path\\To\\My\\App" + "code-review.vcs.git.directory": "C:\\Path\\To\\My\\App" } ``` @@ -446,7 +457,7 @@ To create a code review with a report you should install this extension and go o - Download / clone the customer code and checkout the correct branch - Open the project in vscode - [Configure the `baseURL` option](#extension-settings) with the remote URL - - this will cause that the link in the report is generate with the correct target including SHA, file and line reference + - this will cause that the link in the report is generate with the correct target including revision, file and line reference - [Start creating your review notes](#create-review-notes). - [Export the report](#export-created-notes-as-html). - [Probably create an own template first](#custom-handlebars-template) diff --git a/package.json b/package.json index 6130680..eeefc0c 100644 --- a/package.json +++ b/package.json @@ -200,13 +200,13 @@ "code-review.baseUrl": { "type": "string", "title": "The base URL for a referenced file without the SHA", - "description": "The base URL is used to build a full link to the file (e.g. \"https://github.com/foo/bar/blob/b0b4...0175/src/file.txt#L12-L19\"). It will be appended with the GIT SHA if available followed by the relative path of the file and the selected lines as an anker. This setting is skipped when the setting `code-review.customUrl` is defined.", + "description": "The base URL is used to build a full link to the file (e.g. \"https://github.com/foo/bar/blob/b0b4...0175/src/file.txt#L12-L19\"). It will be appended with the GIT SHA if available followed by the relative path of the file and the selected lines as an anchor. This setting is skipped when the setting `code-review.customUrl` is defined.", "default": "" }, "code-review.customUrl": { "type": "string", "title": "The URL mask with placeholders for a referenced file without the SHA", - "description": "The custom URL is used to build a full link to the file.\nThe following placeholders are available:\n - {sha}: insert the SHA ref for the file\n - {file}: insert the file name/path\n - {start}: insert the start of the lines selection as an anker\n - {end}: insert the end of the lines selection as an anker\ne.g. \"https://gitlab.com/foo/bar/baz/-/blob/{sha}/src/{file}#L{start}-{end}\" becomes this in the end: \"https://gitlab.com/foo/bar/baz/-/blob/b0b4...0175/src/file.txt#L12-19\"", + "description": "The custom URL is used to build a full link to the file.\nThe following placeholders are available:\n - {sha}: insert the SHA ref for the file\n - {file}: insert the file name/path\n - {start}: insert the start of the lines selection as an anchor\n - {end}: insert the end of the lines selection as an anchor\ne.g. \"https://gitlab.com/foo/bar/baz/-/blob/{sha}/src/{file}#L{start}-{end}\" becomes this in the end: \"https://gitlab.com/foo/bar/baz/-/blob/b0b4...0175/src/file.txt#L12-19\"", "default": "" }, "code-review.categories": { diff --git a/src/export-factory.ts b/src/export-factory.ts index b252c87..5e922c4 100644 --- a/src/export-factory.ts +++ b/src/export-factory.ts @@ -169,13 +169,13 @@ export class ExportFactory { const title = row.title ? row.title.substring(0, 255) : descShort; const fileRow = row.url ? `- file: [${row.filename}](${row.url})${EOL}` : `${row.filename}${EOL}`; const linesRow = `- lines: ${row.lines}${EOL}`; - const shaRow = row.revision ? `- SHA: ${row.revision}${EOL}${EOL}` : ''; + const revRow = row.revision ? `- SHA: ${row.revision}${EOL}${EOL}` : ''; const commentSection = `## Comment${EOL}${row.comment}${EOL}`; const additional = row.additional ? `## Additional information${EOL}${row.additional}${EOL}` : ''; const priority = row.priority ? `## Priority${EOL}${this.priorityName(row.priority)}${EOL}${EOL}` : ''; const category = row.category ? `## Category${EOL}${row.category}${EOL}${EOL}` : ''; const code = row.code ? `${EOL}## Source Code${EOL}${EOL}\`\`\`${EOL}${row.code}\`\`\`${EOL}` : ''; - const description = `${priority}${category}## Affected${EOL}${fileRow}${linesRow}${shaRow}${commentSection}${EOL}${additional}${code}`; + const description = `${priority}${category}## Affected${EOL}${fileRow}${linesRow}${revRow}${commentSection}${EOL}${additional}${code}`; fs.appendFileSync(outputFile, `"[code review] ${title}","${description}"${EOL}`); return row; }, @@ -203,14 +203,14 @@ export class ExportFactory { const fileRow = row.url ? `- file: [${row.filename}](${row.url})${EOL}` : `${row.filename}${EOL}`; const linesRow = `- lines: ${row.lines}${EOL}`; - const shaRow = row.revision ? `- SHA: ${row.revision}${EOL}${EOL}` : ''; + const revRow = row.revision ? `- SHA: ${row.revision}${EOL}${EOL}` : ''; const commentSection = `## Comment${EOL}${row.comment}${EOL}`; const additional = row.additional ? `## Additional information${EOL}${row.additional}${EOL}` : ''; const priority = row.priority ? `## Priority${EOL}${this.priorityName(row.priority)}${EOL}${EOL}` : ''; const category = row.category ? `## Category${EOL}${row.category}${EOL}${EOL}` : ''; const code = row.code ? `${EOL}## Source Code${EOL}${EOL}\`\`\`${EOL}${row.code}\`\`\`${EOL}` : ''; - const description = `${priority}${category}## Affected${EOL}${fileRow}${linesRow}${shaRow}${commentSection}${EOL}${additional}${code}`; + const description = `${priority}${category}## Affected${EOL}${fileRow}${linesRow}${revRow}${commentSection}${EOL}${additional}${code}`; fs.appendFileSync(outputFile, `"[code review] ${title}","${description}","code-review","open",""${EOL}`); return row; @@ -228,7 +228,7 @@ export class ExportFactory { writeFileHeader: (outputFile: string) => { fs.writeFileSync( outputFile, - `Summary,Description,Priority,sha,filename,url,lines,title,category,comment,additional${EOL}`, + `Summary,Description,Priority,revision,filename,url,lines,title,category,comment,additional${EOL}`, ); }, handleData: (outputFile: string, row: CsvEntry): CsvEntry => { @@ -242,13 +242,13 @@ export class ExportFactory { const fileRow = row.url ? `* file: [${row.filename}|${row.url}]${EOL}` : `${row.filename}${EOL}`; const linesRow = `* lines: ${row.lines}${EOL}`; - const shaRow = row.revision ? `* SHA: ${row.revision}${EOL}${EOL}` : ''; + const revRow = row.revision ? `* SHA: ${row.revision}${EOL}${EOL}` : ''; const categorySection = `h2. Category${EOL}${row.category}${EOL}${EOL}`; const commentSection = `h2. Comment${EOL}${row.comment}${EOL}`; const additional = row.additional ? `h2. Additional information${EOL}${row.additional}${EOL}` : ''; const code = row.code ? `${EOL}h2. Source Code${EOL}${EOL}{code}${EOL}${row.code}{code}${EOL}` : ''; - const description = `h2. Affected${EOL}${fileRow}${linesRow}${shaRow}${categorySection}${commentSection}${EOL}${additional}${code}`; + const description = `h2. Affected${EOL}${fileRow}${linesRow}${revRow}${categorySection}${commentSection}${EOL}${additional}${code}`; fs.appendFileSync( outputFile, diff --git a/src/model.md b/src/model.md index 72107bb..7cf7c42 100644 --- a/src/model.md +++ b/src/model.md @@ -12,7 +12,7 @@ All the steps will be operated in the file `model.ts`. ```diff export interface CsvEntry { - sha: string; + revision: string; filename: string; url: string; lines: string; @@ -33,7 +33,7 @@ All the steps will be operated in the file `model.ts`. ```diff private static readonly headers: string[] = [ - 'sha', + 'revision', 'filename', 'url', 'lines', @@ -105,7 +105,7 @@ A sample way to deprecate a property is to add a comment above it in the **inter ```diff export interface CsvEntry { - sha: string; + revision: string; filename: string; + /** @deprecated */ url: string; diff --git a/src/review-comment.ts b/src/review-comment.ts index 49c4a30..4983491 100644 --- a/src/review-comment.ts +++ b/src/review-comment.ts @@ -131,21 +131,21 @@ export class ReviewCommentService { Leaving revision empty.\n\Details: ${error}`); } - const startAnker = startLineNumberFromStringDefinition(copy.lines); - const endAnker = endLineNumberFromStringDefinition(copy.lines); - copy.url = this.remoteUrl(copy.revision, copy.filename, startAnker, endAnker); + const startAnchor = startLineNumberFromStringDefinition(copy.lines); + const endAnchor = endLineNumberFromStringDefinition(copy.lines); + copy.url = this.remoteUrl(copy.revision, copy.filename, startAnchor, endAnchor); return copy; } /** * Build the remote URL - * @param sha a git SHA that's included in the URL + * @param revision a git revision that's included in the URL * @param filePath the relative file path * @param start the first line from the first selection * @param end the last line from the first selection */ - private remoteUrl(sha: string, filePath: string, start?: number, end?: number) { + private remoteUrl(revision: string, filePath: string, start?: number, end?: number) { const customUrl = workspace.getConfiguration().get('code-review.customUrl') as string; const baseUrl = workspace.getConfiguration().get('code-review.baseUrl') as string; @@ -155,15 +155,15 @@ export class ReviewCommentService { return ''; } else if (customUrl) { return customUrl - .replace('{sha}', sha) + .replace('{revision}', revision) .replace('{file}', filePathWithoutLeadingAndTrailingSlash) .replace('{start}', start ? start.toString() : '0') .replace('{end}', end ? end.toString() : '0'); } else { const baseUrlWithoutTrailingSlash = removeTrailingSlash(baseUrl); - const shaPart = sha ? `${sha}/` : ''; - const ankerPart = start && end ? `#L${start}-L${end}` : ''; - return `${baseUrlWithoutTrailingSlash}/${shaPart}${filePathWithoutLeadingAndTrailingSlash}${ankerPart}`; + const revPart = revision ? `${revision}/` : ''; + const anchorPart = start && end ? `#L${start}-L${end}` : ''; + return `${baseUrlWithoutTrailingSlash}/${revPart}${filePathWithoutLeadingAndTrailingSlash}${anchorPart}`; } } From 7d54025af2a2ed408ea09db08346e390869cf72f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carsten=20R=C3=B6snick-Neugebauer?= Date: Tue, 30 Nov 2021 10:59:39 +0100 Subject: [PATCH 7/9] fix: add forgotten git-svn provider to vcsKind --- src/vcs-provider.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vcs-provider.ts b/src/vcs-provider.ts index 20f0e20..1404281 100644 --- a/src/vcs-provider.ts +++ b/src/vcs-provider.ts @@ -124,6 +124,8 @@ export function vcsKind(): VcsKind { return VcsKind.git; case 'svn': return VcsKind.svn; + case 'git-svn': + return VcsKind.gitsvn; default: throw new Error(`Unsupported VCS provider: ${provider}`); } From d93262a8b6c2e020918973085e071daf929d82c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carsten=20R=C3=B6snick-Neugebauer?= Date: Thu, 17 Feb 2022 17:19:59 +0100 Subject: [PATCH 8/9] fix(vcs): fix handling of promises in VSC provider --- src/vcs-provider.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/vcs-provider.ts b/src/vcs-provider.ts index 1404281..5ac78a4 100644 --- a/src/vcs-provider.ts +++ b/src/vcs-provider.ts @@ -54,6 +54,7 @@ export async function gitsvnRevision(file: string, workspace: string): Promise { if (error) { reject(`Could not retrieve SVN revision for file: ${file}. Error(s): ${stderr}`); + return; } const revRegex = /^Last Changed Rev: (\d+)\s*$/gm; @@ -63,11 +64,14 @@ export async function gitsvnRevision(file: string, workspace: string): Promise } case VcsKind.svn: { - return new Promise((resolve, reject) => { - svnRevision(file, workspace).then((revision: number) => resolve(`${revision}`)); - }); + let revision = await svnRevision(file, workspace); + return `${revision}`; } case VcsKind.gitsvn: { - return new Promise((resolve, reject) => { - gitsvnRevision(file, workspace).then((revision: number) => resolve(`${revision}`)); - }); + let revision = await gitsvnRevision(file, workspace); + return `${revision}`; } default: From 07dc32c4a68052e57b0e96ea29d7d3c2cbce361c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carsten=20R=C3=B6snick-Neugebauer?= Date: Sat, 19 Feb 2022 22:57:16 +0100 Subject: [PATCH 9/9] fix(vcs): early return in svnRevision as well --- src/vcs-provider.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vcs-provider.ts b/src/vcs-provider.ts index 5ac78a4..077faa2 100644 --- a/src/vcs-provider.ts +++ b/src/vcs-provider.ts @@ -27,11 +27,13 @@ export async function svnRevision(file: string, workspace: string): Promise { if (error) { reject(`Could not retrieve SVN revision for file: ${file}. Error(s): ${stderr}`); + return; } const maybeRevision = Number(stdout.trim()); if (isNaN(maybeRevision)) { reject(`Unexpected command output: ${stdout}`); + return; } resolve(maybeRevision); },