Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions extensions/git-id-switcher/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ export default [
// === Unicorn overrides (disable overly opinionated rules) ===
"unicorn/no-null": "off",
"unicorn/prevent-abbreviations": "off",
// userName/userEmail/userSigningKey mirror git config keys (user.name/user.email/user.signingkey);
// "username" connotes a login handle, which these display-name values are not
"unicorn/consistent-compound-words": ["error", { replacements: { userName: false } }],
"unicorn/filename-case": "off",
"unicorn/no-process-exit": "off",
"unicorn/prefer-module": "off",
Expand Down Expand Up @@ -157,6 +160,8 @@ export default [
"unicorn/prefer-number-properties": "off", // isNaN() is clearer in test assertions
"unicorn/text-encoding-identifier-case": "off", // tests may use 'utf-8' matching external APIs
"unicorn/prefer-code-point": "off", // charCodeAt() in tests mirrors production code
"unicorn/no-this-outside-of-class": "off", // mocha function-style hooks use this.timeout()
"unicorn/prefer-https": "off", // http:// URLs are intentional test data for URL validation
},
},
{
Expand Down
2 changes: 1 addition & 1 deletion extensions/git-id-switcher/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@
"@vscode/vsce": "^3.9.1",
"eslint": "^10.4.1",
"eslint-plugin-sonarjs": "^4.0.3",
"eslint-plugin-unicorn": "^64.0.0",
"eslint-plugin-unicorn": "^65.0.0",
"fast-check": "^4.8.0",
"glob": "^13.0.6",
"mocha": "^11.7.6",
Expand Down
14 changes: 7 additions & 7 deletions extensions/git-id-switcher/src/core/gitConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ function buildGitUserName(identity: Identity): string {
export interface GitConfig {
userName?: string;
userEmail?: string;
signingKey?: string;
userSigningKey?: string;
}

/**
Expand Down Expand Up @@ -112,12 +112,12 @@ export async function getCurrentGitConfig(
token?: vscodeTypes.CancellationToken
): Promise<GitConfig> {
if (token?.isCancellationRequested) {
return { userName: undefined, userEmail: undefined, signingKey: undefined };
return { userName: undefined, userEmail: undefined, userSigningKey: undefined };
}

// If no token provided, execute normally
if (!token) {
const [userName, userEmail, signingKey] = await Promise.all([
const [userName, userEmail, userSigningKey] = await Promise.all([
execGitInWorkspace(['config', 'user.name']),
execGitInWorkspace(['config', 'user.email']),
execGitInWorkspace(['config', 'user.signingkey']),
Expand All @@ -126,7 +126,7 @@ export async function getCurrentGitConfig(
return {
userName: userName || undefined,
userEmail: userEmail || undefined,
signingKey: signingKey || undefined,
userSigningKey: userSigningKey || undefined,
};
}

Expand All @@ -153,20 +153,20 @@ export async function getCurrentGitConfig(
});

try {
const [userName, userEmail, signingKey] = await Promise.race([
const [userName, userEmail, userSigningKey] = await Promise.race([
configPromise,
cancellationPromise,
]);

return {
userName: userName || undefined,
userEmail: userEmail || undefined,
signingKey: signingKey || undefined,
userSigningKey: userSigningKey || undefined,
};
} catch (error) {
// If cancelled, return empty config
if (token.isCancellationRequested) {
return { userName: undefined, userEmail: undefined, signingKey: undefined };
return { userName: undefined, userEmail: undefined, userSigningKey: undefined };
}
// Re-throw other errors
throw error;
Expand Down
6 changes: 3 additions & 3 deletions extensions/git-id-switcher/src/core/syncChecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,13 @@ export function compareSyncState(
// Compare user.signingkey (only if identity has a GPG key configured)
if (
identity.gpgKeyId
&& gitConfig.signingKey !== undefined
&& gitConfig.signingKey !== identity.gpgKeyId
&& gitConfig.userSigningKey !== undefined
&& gitConfig.userSigningKey !== identity.gpgKeyId
) {
mismatches.push({
field: 'signingKey',
expected: identity.gpgKeyId,
actual: gitConfig.signingKey,
actual: gitConfig.userSigningKey,
});
}

Expand Down
2 changes: 1 addition & 1 deletion extensions/git-id-switcher/src/security/binaryResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ async function resolveWithWhich(command: string): Promise<string | null> {

// 'which' returns the path on stdout
// 'where' on Windows may return multiple paths, take the first
const resolvedPath = stdout.trim().split(/[\r\n]/)[0];
const resolvedPath = stdout.trim().split(/[\r\n]/, 1)[0];

/* c8 ignore next 3 - Empty which output */
if (!resolvedPath) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -917,7 +917,7 @@ function testArgumentLimits(): void {

// Too many arguments
{
const manyArgs = Array.from<string>({length: 25}).fill('-l');
const manyArgs = Array.from({length: 25}, () => '-l');
const result = isCommandAllowed('ssh-add', manyArgs);
assert.strictEqual(result.allowed, false, 'Too many arguments should be blocked');
assert.ok(
Expand Down
4 changes: 2 additions & 2 deletions extensions/git-id-switcher/src/test/configSchema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ function testWhitespaceHandling(): void {
id: "test",
name: "Test User",
email: "test@example.com",
service: " ",
service: " ".repeat(3),
};
const result = validateIdentitySchema(identity);
// Current behavior: whitespace passes pattern validation
Expand All @@ -496,7 +496,7 @@ function testWhitespaceHandling(): void {
// Test 2: Whitespace-only id should fail (still needs to match pattern)
{
const identity = {
id: " ",
id: " ".repeat(3),
name: "Test User",
email: "test@example.com",
};
Expand Down
12 changes: 6 additions & 6 deletions extensions/git-id-switcher/src/test/e2e/gitConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,14 +228,14 @@ describe('Git Config E2E Test Suite', function () {
});

it('should write user.signingkey to local git config', () => {
const signingKey = 'ABCD1234EFGH5678';
const userSigningKey = 'ABCD1234EFGH5678';

// Write signing key
gitSync(['config', '--local', 'user.signingkey', signingKey], tempRepoPath);
gitSync(['config', '--local', 'user.signingkey', userSigningKey], tempRepoPath);

// Verify
const readBack = gitSync(['config', '--get', 'user.signingkey'], tempRepoPath);
assert.strictEqual(readBack, signingKey, 'Written user.signingkey should be readable');
assert.strictEqual(readBack, userSigningKey, 'Written user.signingkey should be readable');

// Clean up - unset the key
gitSync(['config', '--local', '--unset', 'user.signingkey'], tempRepoPath);
Expand Down Expand Up @@ -432,7 +432,7 @@ describe('Git Config E2E Test Suite', function () {
// Should return empty config immediately
assert.strictEqual(config.userName, undefined, 'userName should be undefined');
assert.strictEqual(config.userEmail, undefined, 'userEmail should be undefined');
assert.strictEqual(config.signingKey, undefined, 'signingKey should be undefined');
assert.strictEqual(config.userSigningKey, undefined, 'userSigningKey should be undefined');

// Should complete very quickly (within 100ms)
assert.ok(duration < 100, `Should return immediately for cancelled token, took ${duration}ms`);
Expand All @@ -455,7 +455,7 @@ describe('Git Config E2E Test Suite', function () {
// Should return empty config when cancelled
assert.strictEqual(config.userName, undefined, 'userName should be undefined after cancellation');
assert.strictEqual(config.userEmail, undefined, 'userEmail should be undefined after cancellation');
assert.strictEqual(config.signingKey, undefined, 'signingKey should be undefined after cancellation');
assert.strictEqual(config.userSigningKey, undefined, 'userSigningKey should be undefined after cancellation');

tokenSource.dispose();
});
Expand All @@ -470,7 +470,7 @@ describe('Git Config E2E Test Suite', function () {
assert.ok(typeof config === 'object', 'Should return a config object');
assert.ok('userName' in config, 'Config should have userName property');
assert.ok('userEmail' in config, 'Config should have userEmail property');
assert.ok('signingKey' in config, 'Config should have signingKey property');
assert.ok('userSigningKey' in config, 'Config should have userSigningKey property');

tokenSource.dispose();
});
Expand Down
2 changes: 1 addition & 1 deletion extensions/git-id-switcher/src/test/getSafeStack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ function testGetSafeStackFrameParsing(): void {
parenFrame('test', '/tmp/safe.js', 1, 1),
].join('\n');
const safe = createErrorWithStack(stack).getSafeStack()!;
const firstLine = safe.split('\n')[0];
const firstLine = safe.split('\n', 1)[0];
assert.strictEqual(
firstLine,
'SecurityError: /home/testuser/project message',
Expand Down
2 changes: 1 addition & 1 deletion extensions/git-id-switcher/src/test/pathSanitizer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1000,7 +1000,7 @@ function testSanitizePathEmptyWhitespace(): void {
);

// Whitespace only - this is valid but unusual
const whitespaceResult = sanitizePath(' ');
const whitespaceResult = sanitizePath(' '.repeat(3));
assert.ok(
whitespaceResult !== '[INVALID_PATH]',
'Whitespace-only path should be processed (though unusual)'
Expand Down
2 changes: 1 addition & 1 deletion extensions/git-id-switcher/src/test/pathUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ function testValidateSubmodulePath(): void {

// Test whitespace-only submodule path
{
const result = validateSubmodulePath(' ', workspacePath);
const result = validateSubmodulePath(' '.repeat(3), workspacePath);
assert.strictEqual(result.valid, false, 'Whitespace path should fail');
}

Expand Down
4 changes: 2 additions & 2 deletions extensions/git-id-switcher/src/test/securityLogger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,8 +505,8 @@ function testLogConfigChangesEmpty(): void {

const output = capture.getOutput();
// Empty array should produce no output
const configChangeLog = output.find(line => line.includes('CONFIG_CHANGE'));
assert.ok(!configChangeLog, 'Empty changes should not produce CONFIG_CHANGE log');
const hasConfigChangeLog = output.some(line => line.includes('CONFIG_CHANGE'));
assert.ok(!hasConfigChangeLog, 'Empty changes should not produce CONFIG_CHANGE log');

console.log('✅ logConfigChanges empty array passed!');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1119,11 +1119,11 @@ function testBase64DiversityEdgeCases(): void {
{
// Upper + lower + digit = 3 types (should detect)
const threeTypes = 'Aa1' + 'Bb2'.repeat(10) + 'Cc3';
const paddedTo32 = (threeTypes + 'X'.repeat(32)).slice(0, 32);
const exactly32 = threeTypes.padEnd(32, 'X').slice(0, 32);
// Ensure it has 3 types: A-Z, a-z, 0-9
assert.strictEqual(paddedTo32.length, 32);
assert.strictEqual(exactly32.length, 32);
assert.strictEqual(
looksLikeSensitiveData(paddedTo32),
looksLikeSensitiveData(exactly32),
true,
'String with exactly 3 character types should be detected'
);
Expand Down
30 changes: 15 additions & 15 deletions extensions/git-id-switcher/src/test/syncChecker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function createGitConfig(overrides: Partial<GitConfig> = {}): GitConfig {
return {
userName: 'John Doe',
userEmail: 'john@example.com',
signingKey: undefined,
userSigningKey: undefined,
...overrides,
};
}
Expand Down Expand Up @@ -168,7 +168,7 @@ function testCompareSyncStateGpgKeyMismatch(): void {
console.log(' Testing compareSyncState() GPG key mismatch...');

const identity = createIdentity({ gpgKeyId: 'ABCD1234' });
const gitConfig = createGitConfig({ signingKey: 'EFGH5678' });
const gitConfig = createGitConfig({ userSigningKey: 'EFGH5678' });

const result = compareSyncState(identity, gitConfig, false);

Expand All @@ -183,7 +183,7 @@ function testCompareSyncStateGpgKeyMatch(): void {
console.log(' Testing compareSyncState() GPG key match...');

const identity = createIdentity({ gpgKeyId: 'ABCD1234' });
const gitConfig = createGitConfig({ signingKey: 'ABCD1234' });
const gitConfig = createGitConfig({ userSigningKey: 'ABCD1234' });

const result = compareSyncState(identity, gitConfig, false);

Expand All @@ -194,10 +194,10 @@ function testCompareSyncStateGpgKeyMatch(): void {
function testCompareSyncStateGpgKeyNotInGitConfig(): void {
console.log(' Testing compareSyncState() GPG key in identity but not in git config...');

// Identity has GPG key but git config doesn't have signingKey set
// Identity has GPG key but git config doesn't have userSigningKey set
// This is not a mismatch - the key might not be set yet
const identity = createIdentity({ gpgKeyId: 'ABCD1234' });
const gitConfig = createGitConfig({ signingKey: undefined });
const gitConfig = createGitConfig({ userSigningKey: undefined });

const result = compareSyncState(identity, gitConfig, false);

Expand All @@ -210,7 +210,7 @@ function testCompareSyncStateNoGpgKeyInIdentity(): void {

// Identity has no GPG key, git config has one → should be ignored
const identity = createIdentity();
const gitConfig = createGitConfig({ signingKey: 'ABCD1234' });
const gitConfig = createGitConfig({ userSigningKey: 'ABCD1234' });

const result = compareSyncState(identity, gitConfig, false);

Expand All @@ -225,7 +225,7 @@ function testCompareSyncStateUnknown(): void {
const gitConfig: GitConfig = {
userName: undefined,
userEmail: undefined,
signingKey: undefined,
userSigningKey: undefined,
};

const result = compareSyncState(identity, gitConfig, false);
Expand Down Expand Up @@ -299,7 +299,7 @@ function testCompareSyncStatePartialGitConfigOnlyName(): void {
const gitConfig: GitConfig = {
userName: 'John Doe',
userEmail: undefined,
signingKey: undefined,
userSigningKey: undefined,
};

// userName matches, email is undefined → no email mismatch
Expand All @@ -316,7 +316,7 @@ function testCompareSyncStatePartialGitConfigOnlyEmail(): void {
const gitConfig: GitConfig = {
userName: undefined,
userEmail: 'john@example.com',
signingKey: undefined,
userSigningKey: undefined,
};

// userName is undefined, email matches → no mismatch
Expand Down Expand Up @@ -350,7 +350,7 @@ async function testCheckSyncSynced(): Promise<void> {
_setGitConfigReader(async () => ({
userName: 'John Doe',
userEmail: 'john@example.com',
signingKey: undefined,
userSigningKey: undefined,
}));

try {
Expand All @@ -371,7 +371,7 @@ async function testCheckSyncOutOfSync(): Promise<void> {
_setGitConfigReader(async () => ({
userName: 'Wrong Name',
userEmail: 'wrong@example.com',
signingKey: undefined,
userSigningKey: undefined,
}));

try {
Expand Down Expand Up @@ -411,7 +411,7 @@ async function testCheckSyncWithCancellationBeforeStart(): Promise<void> {
let readerCalled = false;
_setGitConfigReader(async () => {
readerCalled = true;
return { userName: 'John', userEmail: 'j@e.com', signingKey: undefined };
return { userName: 'John', userEmail: 'j@e.com', userSigningKey: undefined };
});

try {
Expand All @@ -437,7 +437,7 @@ async function testCheckSyncCancelledAfterRead(): Promise<void> {
_setGitConfigReader(async () => {
// Simulate cancellation occurring during git config read
token.isCancellationRequested = true;
return { userName: 'John Doe', userEmail: 'john@example.com', signingKey: undefined };
return { userName: 'John Doe', userEmail: 'john@example.com', userSigningKey: undefined };
});

try {
Expand All @@ -458,7 +458,7 @@ async function testCheckSyncFallsBackToSetting(): Promise<void> {
_setGitConfigReader(async () => ({
userName: 'John Doe',
userEmail: 'john@example.com',
signingKey: undefined,
userSigningKey: undefined,
}));

try {
Expand All @@ -481,7 +481,7 @@ async function testCheckSyncWithIconSetting(): Promise<void> {
_setGitConfigReader(async () => ({
userName: '🏢 John Doe',
userEmail: 'john@example.com',
signingKey: undefined,
userSigningKey: undefined,
}));

try {
Expand Down
4 changes: 2 additions & 2 deletions extensions/git-id-switcher/src/ui/documentationInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ export function renderMarkdown(raw: string): SanitizedHtml {
html = `<p>${html}</p>`;

// Clean up empty paragraphs and paragraphs around block elements
const blockElements = String.raw`h[1-6]|pre|table|blockquote|hr|ul|ol|li|img`;
const blockElements = 'h[1-6]|pre|table|blockquote|hr|ul|ol|li|img';
html = html.replaceAll(/<p>\s*<\/p>/g, '');
html = html.replaceAll(new RegExp(String.raw`<p>\s*(<(?:${blockElements})[^>]*>)`, 'g'), '$1');
html = html.replaceAll(new RegExp(String.raw`(</(?:${blockElements})>)\s*</p>`, 'g'), '$1');
Expand Down Expand Up @@ -495,7 +495,7 @@ export function getDocumentLocaleFromString(vscodeLocale: string): string {
}

// 3. Try base locale (en-US → en)
const baseLocale = vscodeLocale.split('-')[0];
const baseLocale = vscodeLocale.split('-', 1)[0];
if (SUPPORTED_LOCALES.includes(baseLocale)) {
return baseLocale;
}
Expand Down
Loading
Loading