Disallow and suppress unsafe assignment (#19736)

This commit is contained in:
Christian Gunderman
2026-02-20 22:28:55 +00:00
committed by GitHub
parent b746524a1b
commit 58d637f919
71 changed files with 149 additions and 22 deletions
@@ -866,6 +866,7 @@ Would you like to attempt to install via "git clone" instead?`,
try {
const hooksContent = await fs.promises.readFile(hooksFilePath, 'utf-8');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const rawHooks = JSON.parse(hooksContent);
if (
@@ -81,6 +81,7 @@ export class ExtensionRegistryClient {
`${ext.extensionName} ${ext.extensionDescription} ${ext.fullName}`,
fuzzy: true,
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const results = await fzf.find(query);
return results.map((r: { item: RegistryExtension }) => r.item);
}
+2 -1
View File
@@ -217,13 +217,14 @@ export class AppRig {
}
private stubRefreshAuth() {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-explicit-any
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
const gcConfig = this.config as any;
gcConfig.refreshAuth = async (authMethod: AuthType) => {
gcConfig.modelAvailabilityService.reset();
const newContentGeneratorConfig = {
authType: authMethod,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
proxy: gcConfig.getProxy(),
apiKey: process.env['GEMINI_API_KEY'] || 'test-api-key',
};
@@ -21,7 +21,7 @@ import type { TextBuffer } from '../ui/components/shared/text-buffer.js';
const invalidCharsRegex = /[\b\x1b]/;
function toHaveOnlyValidCharacters(this: Assertion, buffer: TextBuffer) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment
const { isNot } = this as any;
let pass = true;
const invalidLines: Array<{ line: number; content: string }> = [];
@@ -45,7 +45,7 @@ export const createMockCommandContext = (
forScope: vi.fn().mockReturnValue({ settings: {} }),
} as unknown as LoadedSettings,
git: undefined as GitService | undefined,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment
logger: {
log: vi.fn(),
logMessage: vi.fn(),
@@ -54,7 +54,7 @@ export const createMockCommandContext = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any, // Cast because Logger is a class.
},
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment
ui: {
addItem: vi.fn(),
clear: vi.fn(),
@@ -94,11 +94,14 @@ export const createMockCommandContext = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const merge = (target: any, source: any): any => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const output = { ...target };
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const sourceValue = source[key];
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const targetValue = output[key];
if (
@@ -106,9 +109,11 @@ export const createMockCommandContext = (
Object.prototype.toString.call(sourceValue) === '[object Object]' &&
Object.prototype.toString.call(targetValue) === '[object Object]'
) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
output[key] = merge(targetValue, sourceValue);
} else {
// If not, we do a direct assignment. This preserves Date objects and others.
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
output[key] = sourceValue;
}
}
+2 -1
View File
@@ -46,6 +46,7 @@ export const createMockSettings = (
workspace,
isTrusted,
errors,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
merged: mergedOverride,
...settingsOverrides
} = overrides;
@@ -75,7 +76,7 @@ export const createMockSettings = (
// Assign any function overrides (e.g., vi.fn() for methods)
for (const key in overrides) {
if (typeof overrides[key] === 'function') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment
(loaded as any)[key] = overrides[key];
}
}
@@ -115,6 +115,7 @@ export function SettingsDialog({
}
const doSearch = async () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const results = await fzfInstance.find(searchQuery);
if (!active) return;
@@ -451,6 +451,7 @@ Return a JSON object with:
'--limit',
String(limit),
]);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const issues: Issue[] = JSON.parse(stdout);
if (issues.length === 0) {
setState((s) => ({
@@ -137,6 +137,7 @@ export const TriageIssues = ({
'--limit',
String(limit),
]);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const issues: Issue[] = JSON.parse(stdout);
if (issues.length === 0) {
setState((s) => ({
@@ -166,6 +166,7 @@ async function searchResourceCandidates(
const fzf = new AsyncFzf(candidates, {
selector: (candidate: ResourceSuggestionCandidate) => candidate.searchKey,
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const results = await fzf.find(normalizedPattern, {
limit: MAX_SUGGESTIONS_TO_SHOW * 3,
});
@@ -188,6 +189,7 @@ async function searchAgentCandidates(
const fzf = new AsyncFzf(candidates, {
selector: (s: Suggestion) => s.label,
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const results = await fzf.find(normalizedPattern, {
limit: MAX_SUGGESTIONS_TO_SHOW,
});
@@ -57,6 +57,7 @@ export const useSessionBrowser = (
const originalFilePath = path.join(chatsDir, fileName);
// Load up the conversation.
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const conversation: ConversationRecord = JSON.parse(
await fs.readFile(originalFilePath, 'utf8'),
);
@@ -271,6 +271,7 @@ function useCommandSuggestions(
const fzfInstance = getFzfForCommands(commandsToSearch);
if (fzfInstance) {
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const fzfResults = await fzfInstance.fzf.find(partial);
if (signal.aborted) return;
const uniqueCommands = new Set<SlashCommand>();
@@ -22,6 +22,7 @@ export const useStateAndRef = <
(newStateOrCallback) => {
let newValue: T;
if (typeof newStateOrCallback === 'function') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
newValue = newStateOrCallback(ref.current);
} else {
newValue = newStateOrCallback;
@@ -243,6 +243,7 @@ export const TableRenderer: React.FC<TableRendererProps> = ({
isHeader = false,
): React.ReactNode => {
const renderedCells = cells.map((cell, index) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const width = adjustedWidths[index] || 0;
return renderCell(cell, width, isHeader);
});
@@ -179,6 +179,7 @@ async function configureVSCodeStyle(
await backupFile(keybindingsFile);
try {
const cleanContent = stripJsonComments(content);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const parsedContent = JSON.parse(cleanContent);
if (!Array.isArray(parsedContent)) {
return {
+2
View File
@@ -233,7 +233,9 @@ export function escapeAnsiCtrlCodes<T>(obj: T): T {
let newArr: unknown[] | null = null;
for (let i = 0; i < obj.length; i++) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const value = obj[i];
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const escapedValue = escapeAnsiCtrlCodes(value);
if (escapedValue !== value) {
if (newArr === null) {
+1
View File
@@ -23,6 +23,7 @@ export function resolveEnvVarsInString(
): string {
const envVarRegex = /\$(?:(\w+)|{([^}]+)})/g; // Find $VAR_NAME or ${VAR_NAME}
return value.replace(envVarRegex, (match, varName1, varName2) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const varName = varName1 || varName2;
if (customEnv && typeof customEnv[varName] === 'string') {
return customEnv[varName];
+1
View File
@@ -78,6 +78,7 @@ export const getLatestGitHubRelease = async (
);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const releaseTag = (await response.json()).tag_name;
if (!releaseTag) {
throw new Error(`Response did not include tag_name field`);
+1
View File
@@ -29,6 +29,7 @@ export function tryParseJSON(input: string): object | null {
if (!checkInput(input)) return null;
const trimmed = input.trim();
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const parsed = JSON.parse(trimmed);
if (parsed === null || typeof parsed !== 'object') {
return null;
@@ -38,6 +38,7 @@ export class PersistentState {
const filePath = this.getPath();
if (fs.existsSync(filePath)) {
const content = fs.readFileSync(filePath, 'utf-8');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.cache = JSON.parse(content);
} else {
this.cache = {};
+1
View File
@@ -23,6 +23,7 @@ export async function readStdin(): Promise<string> {
const onReadable = () => {
let chunk;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
while ((chunk = process.stdin.read()) !== null) {
if (pipedInputTimerId) {
clearTimeout(pipedInputTimerId);
+2
View File
@@ -254,6 +254,7 @@ export const getAllSessionFiles = async (
async (file): Promise<SessionFileEntry> => {
const filePath = path.join(chatsDir, file);
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const content: ConversationRecord = JSON.parse(
await fs.readFile(filePath, 'utf8'),
);
@@ -498,6 +499,7 @@ export class SessionSelector {
const sessionPath = path.join(chatsDir, sessionInfo.fileName);
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const sessionData: ConversationRecord = JSON.parse(
await fs.readFile(sessionPath, 'utf8'),
);
+1
View File
@@ -371,6 +371,7 @@ export function setPendingSettingValue(
pendingSettings: Settings,
): Settings {
const path = key.split('.');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const newSettings = JSON.parse(JSON.stringify(pendingSettings));
setNestedValue(newSettings, path, value);
return newSettings;
@@ -25,7 +25,9 @@ function extractRecursiveMessage(input: string): string {
(trimmed.startsWith('[') && trimmed.endsWith(']'))
) {
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const parsed = JSON.parse(trimmed);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const next =
parsed?.error?.message ||
parsed?.[0]?.error?.message ||
@@ -23,6 +23,7 @@ export class AcpFileSystemService implements FileSystemService {
return this.fallback.readTextFile(filePath);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const response = await this.connection.readTextFile({
path: filePath,
sessionId: this.sessionId,
@@ -702,6 +702,7 @@ export class Session {
},
};
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const output = await this.connection.requestPermission(params);
const outcome =
output.outcome.outcome === CoreToolCallStatus.Cancelled