From a4031bbc27fd346b3447c3c08b851219f97b8bb8 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 18 Jun 2026 17:06:59 +0000 Subject: [PATCH 1/3] refactor: simplify dependency discovery helpers Co-authored-by: Aiden Bai --- .../check-react-server-components-advisory.ts | 18 ++------------ .../project-info/extract-dependency-info.ts | 2 +- .../src/project-info/find-expo-version.ts | 8 ++----- .../src/project-info/find-nextjs-version.ts | 8 ++----- .../find-shopify-flash-list-version.ts | 7 ++---- .../find-workspace-dependency-spec.ts | 12 ++++++++++ .../src/project-info/get-preact-version.ts | 2 +- .../project-info/resolve-catalog-version.ts | 2 +- .../utils/get-dependency-declaration.ts | 2 +- .../project-info/utils/get-dependency-spec.ts | 6 +---- .../utils/is-package-json-reanimated-aware.ts | 12 +++------- .../utils/read-directory-entries.ts | 4 +--- packages/core/src/read-ignore-file.ts | 5 ++-- packages/core/src/utils/is-errno-exception.ts | 2 +- .../src/plugin/react-doctor-plugin.ts | 3 +-- .../utils/effect/constants.ts | 2 -- .../state-and-effects/utils/effect/react.ts | 8 +++---- .../src/plugin/semantic/scope-analysis.ts | 24 +++++++++---------- .../src/react-native-dependency-names.ts | 2 +- 19 files changed, 49 insertions(+), 80 deletions(-) create mode 100644 packages/core/src/project-info/find-workspace-dependency-spec.ts diff --git a/packages/core/src/check-react-server-components-advisory.ts b/packages/core/src/check-react-server-components-advisory.ts index 3b2180ae0..de63fc7e9 100644 --- a/packages/core/src/check-react-server-components-advisory.ts +++ b/packages/core/src/check-react-server-components-advisory.ts @@ -8,17 +8,11 @@ import { import { findMonorepoRoot, isFile, readPackageJson } from "./project-info/index.js"; import { getWorkspacePatterns } from "./project-info/get-workspace-patterns.js"; import { resolveWorkspaceDirectories } from "./project-info/resolve-workspace-directories.js"; +import { getDependencySpec } from "./project-info/utils/get-dependency-spec.js"; import type { Diagnostic, PackageJson, ProjectInfo } from "./types/index.js"; const RULE_KEY = "no-vulnerable-react-server-components"; -const DEPENDENCY_SECTIONS = [ - "dependencies", - "devDependencies", - "peerDependencies", - "optionalDependencies", -] as const; - // Per-minor advisory thresholds for React's Server Components runtime // (`react-server-dom-*`, versioned in lockstep with `react`/`react-dom`). // `rceFixedVersion` patched the critical unauthenticated RCE (CVE-2025-55182); @@ -91,14 +85,6 @@ const enumerateWorkspaceDirectories = (workspaceRoot: string): string[] => { return [...directories]; }; -const readDeclaredSpec = (packageJson: PackageJson, packageName: string): string | null => { - for (const section of DEPENDENCY_SECTIONS) { - const spec = packageJson[section]?.[packageName]; - if (typeof spec === "string") return spec; - } - return null; -}; - // Resolves the concrete version a package runs *in a single directory*, // preferring the installed manifest under that directory's `node_modules` // (authoritative, always concrete) and falling back to an exact pin declared in @@ -122,7 +108,7 @@ const resolveVersionInDirectory = ( // catalog-resolved `project.nextjsVersion`) — so an unparseable manifest spec // like `catalog:` doesn't shadow an already-resolved concrete pin. const candidateSpecs = [ - readDeclaredSpec(readPackageJson(path.join(directory, "package.json")), packageName), + getDependencySpec(readPackageJson(path.join(directory, "package.json")), packageName), declaredSpecOverride, ]; for (const spec of candidateSpecs) { diff --git a/packages/core/src/project-info/extract-dependency-info.ts b/packages/core/src/project-info/extract-dependency-info.ts index b13d298c0..9afe0c029 100644 --- a/packages/core/src/project-info/extract-dependency-info.ts +++ b/packages/core/src/project-info/extract-dependency-info.ts @@ -17,7 +17,7 @@ const pickConcreteVersion = ( ): string | null => { for (const section of sections) { const version = packageJson[section]?.[packageName]; - if (version === undefined) continue; + if (typeof version !== "string") continue; if (isCatalogReference(version)) return null; if (isConcreteDependencyVersion(version)) return version; } diff --git a/packages/core/src/project-info/find-expo-version.ts b/packages/core/src/project-info/find-expo-version.ts index 3456a790f..4b4563ce7 100644 --- a/packages/core/src/project-info/find-expo-version.ts +++ b/packages/core/src/project-info/find-expo-version.ts @@ -1,6 +1,5 @@ import type { PackageJson } from "../types/index.js"; -import { findInWorkspacePackageJsons } from "./find-in-workspace-package-jsons.js"; -import { getDependencySpec } from "./utils/get-dependency-spec.js"; +import { findWorkspaceDependencySpec } from "./find-workspace-dependency-spec.js"; // The declared `expo` package version spec, looked up in the root manifest // and then each workspace package — react-doctor's "is this an Expo @@ -16,7 +15,4 @@ import { getDependencySpec } from "./utils/get-dependency-spec.js"; export const findExpoVersion = ( rootDirectory: string, rootPackageJson: PackageJson, -): string | null => - findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => - getDependencySpec(packageJson, "expo"), - ); +): string | null => findWorkspaceDependencySpec(rootDirectory, rootPackageJson, "expo"); diff --git a/packages/core/src/project-info/find-nextjs-version.ts b/packages/core/src/project-info/find-nextjs-version.ts index 038d8706c..b08be1b2e 100644 --- a/packages/core/src/project-info/find-nextjs-version.ts +++ b/packages/core/src/project-info/find-nextjs-version.ts @@ -1,6 +1,5 @@ import type { PackageJson } from "../types/index.js"; -import { findInWorkspacePackageJsons } from "./find-in-workspace-package-jsons.js"; -import { getDependencySpec } from "./utils/get-dependency-spec.js"; +import { findWorkspaceDependencySpec } from "./find-workspace-dependency-spec.js"; // The declared `next` package version spec, looked up in the root manifest and // then each workspace package — the signal the `nextjs:15` capability gate @@ -12,7 +11,4 @@ import { getDependencySpec } from "./utils/get-dependency-spec.js"; export const findNextjsVersion = ( rootDirectory: string, rootPackageJson: PackageJson, -): string | null => - findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => - getDependencySpec(packageJson, "next"), - ); +): string | null => findWorkspaceDependencySpec(rootDirectory, rootPackageJson, "next"); diff --git a/packages/core/src/project-info/find-shopify-flash-list-version.ts b/packages/core/src/project-info/find-shopify-flash-list-version.ts index 0fa025d89..989c49dfb 100644 --- a/packages/core/src/project-info/find-shopify-flash-list-version.ts +++ b/packages/core/src/project-info/find-shopify-flash-list-version.ts @@ -1,6 +1,5 @@ import type { PackageJson } from "../types/index.js"; -import { findInWorkspacePackageJsons } from "./find-in-workspace-package-jsons.js"; -import { getDependencySpec } from "./utils/get-dependency-spec.js"; +import { findWorkspaceDependencySpec } from "./find-workspace-dependency-spec.js"; export const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list"; @@ -8,6 +7,4 @@ export const findShopifyFlashListVersion = ( rootDirectory: string, rootPackageJson: PackageJson, ): string | null => - findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => - getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME), - ); + findWorkspaceDependencySpec(rootDirectory, rootPackageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME); diff --git a/packages/core/src/project-info/find-workspace-dependency-spec.ts b/packages/core/src/project-info/find-workspace-dependency-spec.ts new file mode 100644 index 000000000..5f8112fe5 --- /dev/null +++ b/packages/core/src/project-info/find-workspace-dependency-spec.ts @@ -0,0 +1,12 @@ +import type { PackageJson } from "../types/index.js"; +import { findInWorkspacePackageJsons } from "./find-in-workspace-package-jsons.js"; +import { getDependencySpec } from "./utils/get-dependency-spec.js"; + +export const findWorkspaceDependencySpec = ( + rootDirectory: string, + rootPackageJson: PackageJson, + packageName: string, +): string | null => + findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => + getDependencySpec(packageJson, packageName), + ); diff --git a/packages/core/src/project-info/get-preact-version.ts b/packages/core/src/project-info/get-preact-version.ts index f747b0dfd..f94b63e3e 100644 --- a/packages/core/src/project-info/get-preact-version.ts +++ b/packages/core/src/project-info/get-preact-version.ts @@ -6,5 +6,5 @@ export const getPreactVersion = (packageJson: PackageJson): string | null => { ...packageJson.dependencies, ...packageJson.devDependencies, }; - return allDependencies.preact ?? null; + return typeof allDependencies.preact === "string" ? allDependencies.preact : null; }; diff --git a/packages/core/src/project-info/resolve-catalog-version.ts b/packages/core/src/project-info/resolve-catalog-version.ts index 96de4ba8b..f6bbb8c52 100644 --- a/packages/core/src/project-info/resolve-catalog-version.ts +++ b/packages/core/src/project-info/resolve-catalog-version.ts @@ -152,7 +152,7 @@ export const resolveCatalogVersion = ( const hasExplicitCatalogReference = explicitCatalogReference !== undefined; const catalogName = hasExplicitCatalogReference ? explicitCatalogReference - : rawVersion + : typeof rawVersion === "string" ? extractCatalogName(rawVersion) : null; const shouldSearchUnreferencedNamedCatalogs = diff --git a/packages/core/src/project-info/utils/get-dependency-declaration.ts b/packages/core/src/project-info/utils/get-dependency-declaration.ts index 6b1495803..7e792543c 100644 --- a/packages/core/src/project-info/utils/get-dependency-declaration.ts +++ b/packages/core/src/project-info/utils/get-dependency-declaration.ts @@ -20,7 +20,7 @@ export const getDependencyDeclaration = ({ }: GetDependencyDeclarationOptions): DependencyDeclaration => { for (const section of sections) { const version = packageJson[section]?.[packageName]; - if (version === undefined) continue; + if (typeof version !== "string") continue; return { catalogReference: extractCatalogName(version) ?? null, diff --git a/packages/core/src/project-info/utils/get-dependency-spec.ts b/packages/core/src/project-info/utils/get-dependency-spec.ts index e0e021cdc..495b09a0a 100644 --- a/packages/core/src/project-info/utils/get-dependency-spec.ts +++ b/packages/core/src/project-info/utils/get-dependency-spec.ts @@ -1,10 +1,6 @@ import type { PackageJson } from "../../types/index.js"; -// Reads a package's declared version spec from any of the four dependency -// sections (runtime → dev → peer → optional), so detection matches the -// framework / RN-workspace gates that also treat `peer`/`optional` entries as -// present. The `typeof` guard keeps a malformed non-string entry (e.g. -// `"expo": 54`) from reaching downstream `.trim()` parsing and aborting the scan. +// Malformed non-string entries must not reach downstream version parsing. export const getDependencySpec = (packageJson: PackageJson, packageName: string): string | null => { const spec = packageJson.dependencies?.[packageName] ?? diff --git a/packages/core/src/project-info/utils/is-package-json-reanimated-aware.ts b/packages/core/src/project-info/utils/is-package-json-reanimated-aware.ts index c948ca040..cd4da717c 100644 --- a/packages/core/src/project-info/utils/is-package-json-reanimated-aware.ts +++ b/packages/core/src/project-info/utils/is-package-json-reanimated-aware.ts @@ -1,4 +1,5 @@ import type { PackageJson } from "../../types/index.js"; +import { getDependencySpec } from "./get-dependency-spec.js"; // `react-native-reanimated` ships `.get()` / `.set()` accessors as the // React Compiler-compatible alternative to `.value`. Detecting the @@ -7,12 +8,5 @@ import type { PackageJson } from "../../types/index.js"; // the React Native gate so a reanimated dep in any section counts. const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated"; -export const isPackageJsonReanimatedAware = (packageJson: PackageJson): boolean => { - const allDependencies = { - ...packageJson.peerDependencies, - ...packageJson.dependencies, - ...packageJson.devDependencies, - ...packageJson.optionalDependencies, - }; - return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME); -}; +export const isPackageJsonReanimatedAware = (packageJson: PackageJson): boolean => + getDependencySpec(packageJson, REANIMATED_DEPENDENCY_NAME) !== null; diff --git a/packages/core/src/project-info/utils/read-directory-entries.ts b/packages/core/src/project-info/utils/read-directory-entries.ts index 514f5ceda..ba7059820 100644 --- a/packages/core/src/project-info/utils/read-directory-entries.ts +++ b/packages/core/src/project-info/utils/read-directory-entries.ts @@ -16,9 +16,7 @@ const IGNORABLE_READDIR_ERROR_CODES = new Set([ ]); const isIgnorableReaddirError = (error: unknown): boolean => - isErrnoException(error) && - typeof error.code === "string" && - IGNORABLE_READDIR_ERROR_CODES.has(error.code); + isErrnoException(error) && IGNORABLE_READDIR_ERROR_CODES.has(error.code); export const readDirectoryEntries = (directoryPath: string): fs.Dirent[] => { try { diff --git a/packages/core/src/read-ignore-file.ts b/packages/core/src/read-ignore-file.ts index 29e18cbdb..6cbd318f3 100644 --- a/packages/core/src/read-ignore-file.ts +++ b/packages/core/src/read-ignore-file.ts @@ -26,9 +26,8 @@ export const readIgnoreFile = (filePath: string): string[] => { try { content = fs.readFileSync(filePath, "utf-8"); } catch (error) { - const errnoCode = isErrnoException(error) ? error.code : undefined; - if (errnoCode && errnoCode !== "ENOENT") { - Effect.runSync(Console.warn(`Could not read ignore file ${filePath}: ${errnoCode}`)); + if (isErrnoException(error) && error.code !== "ENOENT") { + Effect.runSync(Console.warn(`Could not read ignore file ${filePath}: ${error.code}`)); } return []; } diff --git a/packages/core/src/utils/is-errno-exception.ts b/packages/core/src/utils/is-errno-exception.ts index 2df4a03ac..f94d692b8 100644 --- a/packages/core/src/utils/is-errno-exception.ts +++ b/packages/core/src/utils/is-errno-exception.ts @@ -1,2 +1,2 @@ export const isErrnoException = (error: unknown): error is NodeJS.ErrnoException => - error instanceof Error && "code" in error; + error instanceof Error && "code" in error && typeof error.code === "string"; diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/react-doctor-plugin.ts b/packages/oxlint-plugin-react-doctor/src/plugin/react-doctor-plugin.ts index fb92cc435..535e2b306 100644 --- a/packages/oxlint-plugin-react-doctor/src/plugin/react-doctor-plugin.ts +++ b/packages/oxlint-plugin-react-doctor/src/plugin/react-doctor-plugin.ts @@ -1,7 +1,6 @@ import { ruleRegistry } from "./rule-registry.js"; import type { Rule } from "./utils/rule.js"; -import type { HostRule } from "./utils/rule-plugin.js"; -import type { RulePlugin } from "./utils/rule-plugin.js"; +import type { HostRule, RulePlugin } from "./utils/rule-plugin.js"; import { wrapReactNativeRule } from "./utils/wrap-react-native-rule.js"; import { wrapWithSemanticContext } from "./utils/wrap-with-semantic-context.js"; diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/rules/state-and-effects/utils/effect/constants.ts b/packages/oxlint-plugin-react-doctor/src/plugin/rules/state-and-effects/utils/effect/constants.ts index 404656ffd..213eb92a3 100644 --- a/packages/oxlint-plugin-react-doctor/src/plugin/rules/state-and-effects/utils/effect/constants.ts +++ b/packages/oxlint-plugin-react-doctor/src/plugin/rules/state-and-effects/utils/effect/constants.ts @@ -1,7 +1,5 @@ import * as eslintVisitorKeys from "eslint-visitor-keys"; -export const MAX_EXPRESSION_SNIPPET_ITEMS_COUNT = 3; - const TYPESCRIPT_VISITOR_KEYS: Readonly>> = { TSAsExpression: ["expression", "typeAnnotation"], TSNonNullExpression: ["expression"], diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/rules/state-and-effects/utils/effect/react.ts b/packages/oxlint-plugin-react-doctor/src/plugin/rules/state-and-effects/utils/effect/react.ts index f4b26d97d..ef5bcf6ee 100644 --- a/packages/oxlint-plugin-react-doctor/src/plugin/rules/state-and-effects/utils/effect/react.ts +++ b/packages/oxlint-plugin-react-doctor/src/plugin/rules/state-and-effects/utils/effect/react.ts @@ -3,6 +3,7 @@ import type { EsTreeNode } from "../../../../utils/es-tree-node.js"; import { isAstNode } from "../../../../utils/is-ast-node.js"; import { isFunctionLike } from "../../../../utils/is-function-like.js"; import { isNodeOfType } from "../../../../utils/is-node-of-type.js"; +import { isUppercaseName } from "../../../../utils/is-uppercase-name.js"; import { getDownstreamRefs, getRef, @@ -45,17 +46,14 @@ const getOuterScopeContaining = (analysis: ProgramAnalysis, node: EsTreeNode): S const KNOWN_PURE_HOC_NAMES = new Set(["memo", "forwardRef"]); -const startsWithUppercase = (name: string | undefined): boolean => - Boolean(name && name.length > 0 && name[0] >= "A" && name[0] <= "Z"); - const isReactFunctionalComponent = (node: EsTreeNode | null | undefined): boolean => { if (!node) return false; if (isNodeOfType(node, "FunctionDeclaration")) { - return Boolean(node.id && startsWithUppercase(node.id.name)); + return Boolean(node.id && isUppercaseName(node.id.name)); } if (isNodeOfType(node, "VariableDeclarator")) { if (!isNodeOfType(node.id, "Identifier")) return false; - if (!startsWithUppercase(node.id.name)) return false; + if (!isUppercaseName(node.id.name)) return false; const init = node.init; if (!init) return false; return isNodeOfType(init, "ArrowFunctionExpression") || isNodeOfType(init, "CallExpression"); diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/semantic/scope-analysis.ts b/packages/oxlint-plugin-react-doctor/src/plugin/semantic/scope-analysis.ts index 76500120d..8eda6f661 100644 --- a/packages/oxlint-plugin-react-doctor/src/plugin/semantic/scope-analysis.ts +++ b/packages/oxlint-plugin-react-doctor/src/plugin/semantic/scope-analysis.ts @@ -349,7 +349,7 @@ const visitDestructuringDeclarations = ( // BINDING position (declares a name) vs a REFERENCE position (uses a // name). The walker tracks binding sites explicitly; everything else is // treated as a reference. -const tagAsBinding = (state: BuilderState, identifier: EsTreeNode): void => { +const tagAsBinding = (identifier: EsTreeNode): void => { // Currently a marker only — we already recorded the symbol, so we // tag the identifier so the generic walk doesn't add it again as a // reference. We use a dedicated WeakSet for this (built lazily). @@ -390,7 +390,7 @@ const isFunctionBodyBlock = (block: EsTreeNode): boolean => { // block. Same reasoning — the catch clause already pushed its own // scope. const isCatchClauseBlock = (block: EsTreeNode): boolean => - block.parent !== null && block.parent !== undefined && block.parent.type === "CatchClause"; + block.parent?.type === "CatchClause"; const handleVariableDeclaration = (declaration: EsTreeNode, state: BuilderState): void => { if (!isNodeOfType(declaration, "VariableDeclaration")) return; @@ -421,7 +421,7 @@ const handleVariableDeclaration = (declaration: EsTreeNode, state: BuilderState) for (const identifier of collectBindingNamesFromPattern( (declarator as { id: EsTreeNode }).id, )) { - tagAsBinding(state, identifier); + tagAsBinding(identifier); } } }; @@ -437,7 +437,7 @@ const handleFunctionDeclaration = (fn: EsTreeNode, state: BuilderState): void => declarationNode: fn, initializer: fn, }); - tagAsBinding(state, fn.id as EsTreeNode); + tagAsBinding(fn.id as EsTreeNode); } }; @@ -451,7 +451,7 @@ const handleClassDeclaration = (cls: EsTreeNode, state: BuilderState): void => { declarationNode: cls, initializer: cls, }); - tagAsBinding(state, cls.id as EsTreeNode); + tagAsBinding(cls.id as EsTreeNode); } }; @@ -468,7 +468,7 @@ const handleImportDeclaration = (importDeclaration: EsTreeNode, state: BuilderSt declarationNode: specifier as EsTreeNode, initializer: specifier as EsTreeNode, }); - tagAsBinding(state, local); + tagAsBinding(local); } }; @@ -502,7 +502,7 @@ const handleTsDeclarations = (node: EsTreeNode, state: BuilderState): void => { declarationNode: node, initializer: null, }); - tagAsBinding(state, idNode); + tagAsBinding(idNode); }; const handleFunctionParameters = ( @@ -513,7 +513,7 @@ const handleFunctionParameters = ( for (const param of params) { visitDestructuringDeclarations(param, null, scope, state, "parameter", param); for (const identifier of collectBindingNamesFromPattern(param)) { - tagAsBinding(state, identifier); + tagAsBinding(identifier); } } }; @@ -709,7 +709,7 @@ const walk = (node: EsTreeNode, state: BuilderState): void => { declarationNode: node, initializer: node, }); - tagAsBinding(state, node.id as EsTreeNode); + tagAsBinding(node.id as EsTreeNode); } const functionParams = (node as { params: ReadonlyArray }).params ?? []; handleFunctionParameters(functionParams, fnScope, state); @@ -744,7 +744,7 @@ const walk = (node: EsTreeNode, state: BuilderState): void => { declarationNode: node, initializer: node, }); - tagAsBinding(state, node.id as EsTreeNode); + tagAsBinding(node.id as EsTreeNode); } if (node.superClass) walk(node.superClass as EsTreeNode, state); if (node.body) walk(node.body as EsTreeNode, state); @@ -765,7 +765,7 @@ const walk = (node: EsTreeNode, state: BuilderState): void => { node as EsTreeNode, ); for (const identifier of collectBindingNamesFromPattern(node.param as EsTreeNode)) { - tagAsBinding(state, identifier); + tagAsBinding(identifier); } } if (node.body) walk(node.body as EsTreeNode, state); @@ -821,7 +821,7 @@ const walk = (node: EsTreeNode, state: BuilderState): void => { declarationNode: node, initializer: null, }); - tagAsBinding(state, identifier); + tagAsBinding(identifier); } if (node.body) walk(node.body as EsTreeNode, state); popScope(state); diff --git a/packages/oxlint-plugin-react-doctor/src/react-native-dependency-names.ts b/packages/oxlint-plugin-react-doctor/src/react-native-dependency-names.ts index d4ab2e2a7..1bdc5ed1f 100644 --- a/packages/oxlint-plugin-react-doctor/src/react-native-dependency-names.ts +++ b/packages/oxlint-plugin-react-doctor/src/react-native-dependency-names.ts @@ -9,7 +9,7 @@ // target. // Closed set of canonical Expo-managed dependency names. -export const EXPO_MANAGED_DEPENDENCY_NAMES: ReadonlySet = new Set([ +const EXPO_MANAGED_DEPENDENCY_NAMES: ReadonlySet = new Set([ "expo", "expo-router", "@expo/cli", From 2f50e9d1a2504ba2acc9dda7d46e16efa29a6e7f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 18 Jun 2026 17:09:26 +0000 Subject: [PATCH 2/3] fix: narrow errno code guard Co-authored-by: Aiden Bai --- packages/core/src/utils/is-errno-exception.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/utils/is-errno-exception.ts b/packages/core/src/utils/is-errno-exception.ts index f94d692b8..572db2562 100644 --- a/packages/core/src/utils/is-errno-exception.ts +++ b/packages/core/src/utils/is-errno-exception.ts @@ -1,2 +1,4 @@ -export const isErrnoException = (error: unknown): error is NodeJS.ErrnoException => +export const isErrnoException = ( + error: unknown, +): error is NodeJS.ErrnoException & { code: string } => error instanceof Error && "code" in error && typeof error.code === "string"; From f34b722b7ab70519277d11c19cd09b2619693e8a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 18 Jun 2026 17:12:12 +0000 Subject: [PATCH 3/3] chore: format semantic helper cleanup Co-authored-by: Aiden Bai --- .../src/plugin/semantic/scope-analysis.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/semantic/scope-analysis.ts b/packages/oxlint-plugin-react-doctor/src/plugin/semantic/scope-analysis.ts index 8eda6f661..0635c4dc9 100644 --- a/packages/oxlint-plugin-react-doctor/src/plugin/semantic/scope-analysis.ts +++ b/packages/oxlint-plugin-react-doctor/src/plugin/semantic/scope-analysis.ts @@ -389,8 +389,7 @@ const isFunctionBodyBlock = (block: EsTreeNode): boolean => { // True for AST node types where the .body is a catch-clause body // block. Same reasoning — the catch clause already pushed its own // scope. -const isCatchClauseBlock = (block: EsTreeNode): boolean => - block.parent?.type === "CatchClause"; +const isCatchClauseBlock = (block: EsTreeNode): boolean => block.parent?.type === "CatchClause"; const handleVariableDeclaration = (declaration: EsTreeNode, state: BuilderState): void => { if (!isNodeOfType(declaration, "VariableDeclaration")) return;