chore: Enable "curly" rule to avoid single-statement if confusion/errors.

This commit is contained in:
cpojer
2026-01-31 16:19:20 +09:00
parent 009b16fab8
commit 5ceff756e1
1266 changed files with 27871 additions and 9393 deletions
+6 -2
View File
@@ -11,8 +11,12 @@ export function isVoiceCompatibleAudio(opts: {
return true;
}
const fileName = opts.fileName?.trim();
if (!fileName) return false;
if (!fileName) {
return false;
}
const ext = getFileExtension(fileName);
if (!ext) return false;
if (!ext) {
return false;
}
return VOICE_AUDIO_EXTENSIONS.has(ext);
}
+18 -6
View File
@@ -6,12 +6,24 @@ export const MAX_DOCUMENT_BYTES = 100 * 1024 * 1024; // 100MB
export type MediaKind = "image" | "audio" | "video" | "document" | "unknown";
export function mediaKindFromMime(mime?: string | null): MediaKind {
if (!mime) return "unknown";
if (mime.startsWith("image/")) return "image";
if (mime.startsWith("audio/")) return "audio";
if (mime.startsWith("video/")) return "video";
if (mime === "application/pdf") return "document";
if (mime.startsWith("application/")) return "document";
if (!mime) {
return "unknown";
}
if (mime.startsWith("image/")) {
return "image";
}
if (mime.startsWith("audio/")) {
return "audio";
}
if (mime.startsWith("video/")) {
return "video";
}
if (mime === "application/pdf") {
return "document";
}
if (mime.startsWith("application/")) {
return "document";
}
return "unknown";
}
+24 -8
View File
@@ -34,7 +34,9 @@ function stripQuotes(value: string): string {
}
function parseContentDispositionFileName(header?: string | null): string | undefined {
if (!header) return undefined;
if (!header) {
return undefined;
}
const starMatch = /filename\*\s*=\s*([^;]+)/i.exec(header);
if (starMatch?.[1]) {
const cleaned = stripQuotes(starMatch[1].trim());
@@ -46,17 +48,25 @@ function parseContentDispositionFileName(header?: string | null): string | undef
}
}
const match = /filename\s*=\s*([^;]+)/i.exec(header);
if (match?.[1]) return path.basename(stripQuotes(match[1].trim()));
if (match?.[1]) {
return path.basename(stripQuotes(match[1].trim()));
}
return undefined;
}
async function readErrorBodySnippet(res: Response, maxChars = 200): Promise<string | undefined> {
try {
const text = await res.text();
if (!text) return undefined;
if (!text) {
return undefined;
}
const collapsed = text.replace(/\s+/g, " ").trim();
if (!collapsed) return undefined;
if (collapsed.length <= maxChars) return collapsed;
if (!collapsed) {
return undefined;
}
if (collapsed.length <= maxChars) {
return collapsed;
}
return `${collapsed.slice(0, maxChars)}`;
} catch {
return undefined;
@@ -85,7 +95,9 @@ export async function fetchRemoteMedia(options: FetchMediaOptions): Promise<Fetc
detail = `HTTP ${res.status}${statusText}; empty response body`;
} else {
const snippet = await readErrorBodySnippet(res);
if (snippet) detail += `; body: ${snippet}`;
if (snippet) {
detail += `; body: ${snippet}`;
}
}
throw new MediaFetchError(
"http_error",
@@ -129,7 +141,9 @@ export async function fetchRemoteMedia(options: FetchMediaOptions): Promise<Fetc
});
if (fileName && !path.extname(fileName) && contentType) {
const ext = extensionForMime(contentType);
if (ext) fileName = `${fileName}${ext}`;
if (ext) {
fileName = `${fileName}${ext}`;
}
}
return {
@@ -158,7 +172,9 @@ async function readResponseWithLimit(res: Response, maxBytes: number): Promise<B
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (done) {
break;
}
if (value?.length) {
total += value.length;
if (total > maxBytes) {
+3 -1
View File
@@ -60,7 +60,9 @@ async function isPortFree(port: number) {
await ensurePortAvailable(port);
return true;
} catch (err) {
if (err instanceof PortInUseError) return false;
if (err instanceof PortInUseError) {
return false;
}
throw err;
}
}
+24 -8
View File
@@ -69,7 +69,9 @@ function readJpegExifOrientation(buffer: Buffer): number | null {
buffer[exifStart + 5] === 0
) {
const tiffStart = exifStart + 6;
if (buffer.length < tiffStart + 8) return null;
if (buffer.length < tiffStart + 8) {
return null;
}
// Check byte order (II = little-endian, MM = big-endian)
const byteOrder = buffer.toString("ascii", tiffStart, tiffStart + 2);
@@ -83,12 +85,16 @@ function readJpegExifOrientation(buffer: Buffer): number | null {
// Read IFD0 offset
const ifd0Offset = readU32(tiffStart + 4);
const ifd0Start = tiffStart + ifd0Offset;
if (buffer.length < ifd0Start + 2) return null;
if (buffer.length < ifd0Start + 2) {
return null;
}
const numEntries = readU16(ifd0Start);
for (let i = 0; i < numEntries; i++) {
const entryOffset = ifd0Start + 2 + i * 12;
if (buffer.length < entryOffset + 12) break;
if (buffer.length < entryOffset + 12) {
break;
}
const tag = readU16(entryOffset);
// Orientation tag = 0x0112
@@ -142,11 +148,17 @@ async function sipsMetadataFromBuffer(buffer: Buffer): Promise<ImageMetadata | n
);
const w = stdout.match(/pixelWidth:\s*([0-9]+)/);
const h = stdout.match(/pixelHeight:\s*([0-9]+)/);
if (!w?.[1] || !h?.[1]) return null;
if (!w?.[1] || !h?.[1]) {
return null;
}
const width = Number.parseInt(w[1], 10);
const height = Number.parseInt(h[1], 10);
if (!Number.isFinite(width) || !Number.isFinite(height)) return null;
if (width <= 0 || height <= 0) return null;
if (!Number.isFinite(width) || !Number.isFinite(height)) {
return null;
}
if (width <= 0 || height <= 0) {
return null;
}
return { width, height };
});
}
@@ -204,8 +216,12 @@ export async function getImageMetadata(buffer: Buffer): Promise<ImageMetadata |
const meta = await sharp(buffer).metadata();
const width = Number(meta.width ?? 0);
const height = Number(meta.height ?? 0);
if (!Number.isFinite(width) || !Number.isFinite(height)) return null;
if (width <= 0 || height <= 0) return null;
if (!Number.isFinite(width) || !Number.isFinite(height)) {
return null;
}
if (width <= 0 || height <= 0) {
return null;
}
return { width, height };
} catch {
return null;
+12 -4
View File
@@ -117,7 +117,9 @@ function isRedirectStatus(status: number): boolean {
}
export function normalizeMimeType(value: string | undefined): string | undefined {
if (!value) return undefined;
if (!value) {
return undefined;
}
const [raw] = value.split(";");
const normalized = raw?.trim().toLowerCase();
return normalized || undefined;
@@ -127,7 +129,9 @@ export function parseContentType(value: string | undefined): {
mimeType?: string;
charset?: string;
} {
if (!value) return {};
if (!value) {
return {};
}
const parts = value.split(";").map((part) => part.trim());
const mimeType = normalizeMimeType(parts[0]);
const charset = parts
@@ -226,7 +230,9 @@ function decodeTextContent(buffer: Buffer, charset: string | undefined): string
}
function clampText(text: string, maxChars: number): string {
if (text.length <= maxChars) return text;
if (text.length <= maxChars) {
return text;
}
return text.slice(0, maxChars);
}
@@ -250,7 +256,9 @@ async function extractPdfContent(params: {
.map((item) => ("str" in item ? String(item.str) : ""))
.filter(Boolean)
.join(" ");
if (pageText) textParts.push(pageText);
if (pageText) {
textParts.push(pageText);
}
}
const text = textParts.join("\n\n");
+39 -13
View File
@@ -53,13 +53,17 @@ const AUDIO_FILE_EXTENSIONS = new Set([
]);
function normalizeHeaderMime(mime?: string | null): string | undefined {
if (!mime) return undefined;
if (!mime) {
return undefined;
}
const cleaned = mime.split(";")[0]?.trim().toLowerCase();
return cleaned || undefined;
}
async function sniffMime(buffer?: Buffer): Promise<string | undefined> {
if (!buffer) return undefined;
if (!buffer) {
return undefined;
}
try {
const type = await fileTypeFromBuffer(buffer);
return type?.mime ?? undefined;
@@ -69,7 +73,9 @@ async function sniffMime(buffer?: Buffer): Promise<string | undefined> {
}
export function getFileExtension(filePath?: string | null): string | undefined {
if (!filePath) return undefined;
if (!filePath) {
return undefined;
}
try {
if (/^https?:\/\//i.test(filePath)) {
const url = new URL(filePath);
@@ -84,7 +90,9 @@ export function getFileExtension(filePath?: string | null): string | undefined {
export function isAudioFileName(fileName?: string | null): boolean {
const ext = getFileExtension(fileName);
if (!ext) return false;
if (!ext) {
return false;
}
return AUDIO_FILE_EXTENSIONS.has(ext);
}
@@ -97,7 +105,9 @@ export function detectMime(opts: {
}
function isGenericMime(mime?: string): boolean {
if (!mime) return true;
if (!mime) {
return true;
}
const m = mime.toLowerCase();
return m === "application/octet-stream" || m === "application/zip";
}
@@ -115,17 +125,29 @@ async function detectMimeImpl(opts: {
// Prefer sniffed types, but don't let generic container types override a more
// specific extension mapping (e.g. XLSX vs ZIP).
if (sniffed && (!isGenericMime(sniffed) || !extMime)) return sniffed;
if (extMime) return extMime;
if (headerMime && !isGenericMime(headerMime)) return headerMime;
if (sniffed) return sniffed;
if (headerMime) return headerMime;
if (sniffed && (!isGenericMime(sniffed) || !extMime)) {
return sniffed;
}
if (extMime) {
return extMime;
}
if (headerMime && !isGenericMime(headerMime)) {
return headerMime;
}
if (sniffed) {
return sniffed;
}
if (headerMime) {
return headerMime;
}
return undefined;
}
export function extensionForMime(mime?: string | null): string | undefined {
if (!mime) return undefined;
if (!mime) {
return undefined;
}
return EXT_BY_MIME[mime.toLowerCase()];
}
@@ -133,13 +155,17 @@ export function isGifMedia(opts: {
contentType?: string | null;
fileName?: string | null;
}): boolean {
if (opts.contentType?.toLowerCase() === "image/gif") return true;
if (opts.contentType?.toLowerCase() === "image/gif") {
return true;
}
const ext = getFileExtension(opts.fileName);
return ext === ".gif";
}
export function imageMimeFromFormat(format?: string | null): string | undefined {
if (!format) return undefined;
if (!format) {
return undefined;
}
switch (format.toLowerCase()) {
case "jpg":
case "jpeg":
+27 -9
View File
@@ -15,10 +15,18 @@ function cleanCandidate(raw: string) {
}
function isValidMedia(candidate: string, opts?: { allowSpaces?: boolean }) {
if (!candidate) return false;
if (candidate.length > 4096) return false;
if (!opts?.allowSpaces && /\s/.test(candidate)) return false;
if (/^https?:\/\//i.test(candidate)) return true;
if (!candidate) {
return false;
}
if (candidate.length > 4096) {
return false;
}
if (!opts?.allowSpaces && /\s/.test(candidate)) {
return false;
}
if (/^https?:\/\//i.test(candidate)) {
return true;
}
// Local paths: only allow safe relative paths starting with ./ that do not traverse upwards.
return candidate.startsWith("./") && !candidate.includes("..");
@@ -26,11 +34,17 @@ function isValidMedia(candidate: string, opts?: { allowSpaces?: boolean }) {
function unwrapQuoted(value: string): string | undefined {
const trimmed = value.trim();
if (trimmed.length < 2) return undefined;
if (trimmed.length < 2) {
return undefined;
}
const first = trimmed[0];
const last = trimmed[trimmed.length - 1];
if (first !== last) return undefined;
if (first !== `"` && first !== "'" && first !== "`") return undefined;
if (first !== last) {
return undefined;
}
if (first !== `"` && first !== "'" && first !== "`") {
return undefined;
}
return trimmed.slice(1, -1).trim();
}
@@ -48,7 +62,9 @@ export function splitMediaFromOutput(raw: string): {
// KNOWN: Leading whitespace is semantically meaningful in Markdown (lists, indented fences).
// We only trim the end; token cleanup below handles removing `MEDIA:` lines.
const trimmedRaw = raw.trimEnd();
if (!trimmedRaw.trim()) return { text: "" };
if (!trimmedRaw.trim()) {
return { text: "" };
}
const media: string[] = [];
let foundMediaToken = false;
@@ -189,7 +205,9 @@ export function splitMediaFromOutput(raw: string): {
// Return cleaned text if we found a media token OR audio tag, otherwise original
text: foundMediaToken || hasAudioAsVoice ? cleanedText : trimmedRaw,
};
if (hasAudioAsVoice) result.audioAsVoice = true;
if (hasAudioAsVoice) {
result.audioAsVoice = true;
}
return result;
}
+12 -4
View File
@@ -13,9 +13,15 @@ const MEDIA_ID_PATTERN = /^[\p{L}\p{N}._-]+$/u;
const MAX_MEDIA_BYTES = MEDIA_MAX_BYTES;
const isValidMediaId = (id: string) => {
if (!id) return false;
if (id.length > MAX_MEDIA_ID_CHARS) return false;
if (id === "." || id === "..") return false;
if (!id) {
return false;
}
if (id.length > MAX_MEDIA_ID_CHARS) {
return false;
}
if (id === "." || id === "..") {
return false;
}
return MEDIA_ID_PATTERN.test(id);
};
@@ -51,7 +57,9 @@ export function attachMediaRoutes(
const data = await handle.readFile();
await handle.close().catch(() => {});
const mime = await detectMime({ buffer: data, filePath: realPath });
if (mime) res.type(mime);
if (mime) {
res.type(mime);
}
res.send(data);
// best-effort single-use cleanup after response ends
res.on("finish", () => {
+6 -2
View File
@@ -47,7 +47,9 @@ describe("media store redirects", () => {
const res = new PassThrough();
const req = {
on: (event: string, handler: (...args: unknown[]) => void) => {
if (event === "error") res.on("error", handler);
if (event === "error") {
res.on("error", handler);
}
return req;
},
end: () => undefined,
@@ -88,7 +90,9 @@ describe("media store redirects", () => {
const res = new PassThrough();
const req = {
on: (event: string, handler: (...args: unknown[]) => void) => {
if (event === "error") res.on("error", handler);
if (event === "error") {
res.on("error", handler);
}
return req;
},
end: () => undefined,
+5 -2
View File
@@ -20,8 +20,11 @@ describe("media store", () => {
const restoreEnv = () => {
for (const [key, value] of Object.entries(envSnapshot)) {
if (value === undefined) delete process.env[key];
else process.env[key] = value;
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value;
}
}
};
+9 -3
View File
@@ -21,7 +21,9 @@ const DEFAULT_TTL_MS = 2 * 60 * 1000; // 2 minutes
*/
function sanitizeFilename(name: string): string {
const trimmed = name.trim();
if (!trimmed) return "";
if (!trimmed) {
return "";
}
const sanitized = trimmed.replace(/[^\p{L}\p{N}._-]+/gu, "_");
// Collapse multiple underscores, trim leading/trailing, limit length
return sanitized.replace(/_+/g, "_").replace(/^_|_$/g, "").slice(0, 60);
@@ -34,7 +36,9 @@ function sanitizeFilename(name: string): string {
*/
export function extractOriginalFilename(filePath: string): string {
const basename = path.basename(filePath);
if (!basename) return "file.bin"; // Fallback for empty input
if (!basename) {
return "file.bin";
} // Fallback for empty input
const ext = path.extname(basename);
const nameWithoutExt = path.basename(basename, ext);
@@ -68,7 +72,9 @@ export async function cleanOldMedia(ttlMs = DEFAULT_TTL_MS) {
entries.map(async (file) => {
const full = path.join(mediaDir, file);
const stat = await fs.stat(full).catch(() => null);
if (!stat) return;
if (!stat) {
return;
}
if (now - stat.mtimeMs > ttlMs) {
await fs.rm(full).catch(() => {});
}