|
| 1 | +import * as path from "node:path"; |
| 2 | +import type { Capability } from "oxlint-plugin-react-doctor/contracts"; |
| 3 | +import { LATEST_SUPPORTED_MOBX_MAJOR } from "../constants.js"; |
| 4 | +import type { ProjectInfo } from "../types/index.js"; |
| 5 | +import { getCapabilities } from "./capabilities.js"; |
| 6 | +import { |
| 7 | + MOBX_REACT_LITE_PACKAGE_NAME, |
| 8 | + MOBX_REACT_OBSERVER_PACKAGE_NAME, |
| 9 | + MOBX_REACT_PACKAGE_NAME, |
| 10 | + MOBX_STATE_TREE_PACKAGE_NAME, |
| 11 | + REACT_ROUTER_DEPENDENCY_NAMES, |
| 12 | + REACT_THREE_FIBER_DEPENDENCY_NAMES, |
| 13 | + REACT_THREE_FIBER_ECOSYSTEM_DEPENDENCY_NAMES, |
| 14 | + TANSTACK_REACT_QUERY_PACKAGE_NAMES, |
| 15 | +} from "./capability-dependency-names.js"; |
| 16 | +import { detectPreES2023Target } from "./detect-pre-es2023-target.js"; |
| 17 | +import { REACT_SECTIONS, TAILWIND_ZOD_SECTIONS } from "./dependencies.js"; |
| 18 | +import { |
| 19 | + detectNextjsStaticExport, |
| 20 | + detectReactCompiler, |
| 21 | + detectReactCompilerLintPlugin, |
| 22 | +} from "./detectors.js"; |
| 23 | +import { findPreferredDependency } from "./find-preferred-dependency.js"; |
| 24 | +import { isFile } from "./fs-utils.js"; |
| 25 | +import { hasI18nDependency } from "./has-i18n-dependency.js"; |
| 26 | +import type { |
| 27 | + PackageGraph, |
| 28 | + PackageGraphDependencyDeclaration, |
| 29 | + PackageGraphPackage, |
| 30 | +} from "./package-graph.js"; |
| 31 | +import { isPackageJsonReactNativeAware, isPackageJsonReanimatedAware } from "./rn-metadata.js"; |
| 32 | +import { isPackageJsonSsrAware } from "./ssr-metadata.js"; |
| 33 | +import { |
| 34 | + getDependencyMajorWithinSupportedRange, |
| 35 | + getLowestDependencyMajor, |
| 36 | + parseReactMajor, |
| 37 | + parseThreeRelease, |
| 38 | + resolveEffectiveReactMajor, |
| 39 | +} from "./version.js"; |
| 40 | + |
| 41 | +const getDependencyVersion = ( |
| 42 | + packageGraph: PackageGraph, |
| 43 | + packageNode: PackageGraphPackage, |
| 44 | + dependencyName: string, |
| 45 | + sections?: ReadonlyArray<PackageGraphDependencyDeclaration["section"]>, |
| 46 | +): string | null => { |
| 47 | + const dependencyDeclaration = packageGraph.getDependency( |
| 48 | + packageNode.directory, |
| 49 | + dependencyName, |
| 50 | + sections, |
| 51 | + ); |
| 52 | + if (dependencyDeclaration === null) return null; |
| 53 | + if (dependencyDeclaration.workspaceTargetPackageDirectory === null) { |
| 54 | + return dependencyDeclaration.resolvedSpecifier; |
| 55 | + } |
| 56 | + const workspaceTarget = packageGraph.packages.find( |
| 57 | + (candidatePackage) => |
| 58 | + candidatePackage.directory === dependencyDeclaration.workspaceTargetPackageDirectory, |
| 59 | + ); |
| 60 | + return workspaceTarget?.version ?? dependencyDeclaration.resolvedSpecifier; |
| 61 | +}; |
| 62 | + |
| 63 | +export const buildPackageCapabilities = ( |
| 64 | + packageGraph: PackageGraph, |
| 65 | + packageNode: PackageGraphPackage, |
| 66 | +): ReadonlySet<Capability> => { |
| 67 | + const reactVersion = getDependencyVersion(packageGraph, packageNode, "react", REACT_SECTIONS); |
| 68 | + const tailwindVersion = getDependencyVersion( |
| 69 | + packageGraph, |
| 70 | + packageNode, |
| 71 | + "tailwindcss", |
| 72 | + TAILWIND_ZOD_SECTIONS, |
| 73 | + ); |
| 74 | + const zodVersion = getDependencyVersion(packageGraph, packageNode, "zod", TAILWIND_ZOD_SECTIONS); |
| 75 | + const mobxVersion = getDependencyVersion(packageGraph, packageNode, "mobx"); |
| 76 | + const mobxReactVersion = getDependencyVersion(packageGraph, packageNode, MOBX_REACT_PACKAGE_NAME); |
| 77 | + const mobxReactLiteVersion = getDependencyVersion( |
| 78 | + packageGraph, |
| 79 | + packageNode, |
| 80 | + MOBX_REACT_LITE_PACKAGE_NAME, |
| 81 | + ); |
| 82 | + const zustandVersion = getDependencyVersion(packageGraph, packageNode, "zustand"); |
| 83 | + const findPackageDependency = (dependencyNames: ReadonlyArray<string>) => |
| 84 | + findPreferredDependency({ |
| 85 | + dependencyNames, |
| 86 | + getValue: (dependencyName) => |
| 87 | + getDependencyVersion(packageGraph, packageNode, dependencyName, REACT_SECTIONS), |
| 88 | + }); |
| 89 | + const tanstackQuery = findPackageDependency(TANSTACK_REACT_QUERY_PACKAGE_NAMES); |
| 90 | + const reactRouter = findPackageDependency(REACT_ROUTER_DEPENDENCY_NAMES); |
| 91 | + const reactThreeFiber = findPackageDependency(REACT_THREE_FIBER_DEPENDENCY_NAMES); |
| 92 | + const preactVersion = getDependencyVersion(packageGraph, packageNode, "preact", REACT_SECTIONS); |
| 93 | + const remotionVersion = getDependencyVersion(packageGraph, packageNode, "remotion"); |
| 94 | + const threeVersion = getDependencyVersion(packageGraph, packageNode, "three"); |
| 95 | + const valtioVersion = getDependencyVersion(packageGraph, packageNode, "valtio"); |
| 96 | + const styledComponentsVersion = getDependencyVersion( |
| 97 | + packageGraph, |
| 98 | + packageNode, |
| 99 | + "styled-components", |
| 100 | + REACT_SECTIONS, |
| 101 | + ); |
| 102 | + const hasReactNativePackage = |
| 103 | + packageNode.dependencyInfo.framework === "expo" || |
| 104 | + packageNode.dependencyInfo.framework === "react-native" || |
| 105 | + isPackageJsonReactNativeAware(packageNode.manifest); |
| 106 | + const expoVersion = hasReactNativePackage |
| 107 | + ? getDependencyVersion(packageGraph, packageNode, "expo") |
| 108 | + : null; |
| 109 | + const shopifyFlashListVersion = hasReactNativePackage |
| 110 | + ? getDependencyVersion(packageGraph, packageNode, "@shopify/flash-list") |
| 111 | + : null; |
| 112 | + const hasReanimated = hasReactNativePackage && isPackageJsonReanimatedAware(packageNode.manifest); |
| 113 | + const reanimatedVersion = hasReanimated |
| 114 | + ? getDependencyVersion(packageGraph, packageNode, "react-native-reanimated") |
| 115 | + : null; |
| 116 | + const nextjsVersion = |
| 117 | + packageNode.dependencyInfo.framework === "nextjs" |
| 118 | + ? getDependencyVersion(packageGraph, packageNode, "next") |
| 119 | + : null; |
| 120 | + const hasTypeScript = isFile(path.join(packageNode.directory, "tsconfig.json")); |
| 121 | + const hasReactThreeFiber = REACT_THREE_FIBER_ECOSYSTEM_DEPENDENCY_NAMES.some( |
| 122 | + (dependencyName) => getDependencyVersion(packageGraph, packageNode, dependencyName) !== null, |
| 123 | + ); |
| 124 | + const projectInfo: ProjectInfo = { |
| 125 | + rootDirectory: packageNode.directory, |
| 126 | + projectName: packageNode.name ?? packageNode.directory, |
| 127 | + reactVersion, |
| 128 | + reactMajorVersion: resolveEffectiveReactMajor(reactVersion, packageNode.manifest), |
| 129 | + tailwindVersion, |
| 130 | + zodVersion, |
| 131 | + zodMajorVersion: zodVersion === null ? null : getLowestDependencyMajor(zodVersion), |
| 132 | + mobxVersion, |
| 133 | + mobxMajorVersion: |
| 134 | + mobxVersion === null |
| 135 | + ? null |
| 136 | + : getDependencyMajorWithinSupportedRange(mobxVersion, LATEST_SUPPORTED_MOBX_MAJOR), |
| 137 | + hasMobxReact: mobxReactVersion !== null, |
| 138 | + mobxReactVersion, |
| 139 | + hasMobxReactLite: mobxReactLiteVersion !== null, |
| 140 | + mobxReactLiteVersion, |
| 141 | + hasMobxStateTree: |
| 142 | + getDependencyVersion(packageGraph, packageNode, MOBX_STATE_TREE_PACKAGE_NAME) !== null, |
| 143 | + hasMobxReactObserver: |
| 144 | + getDependencyVersion(packageGraph, packageNode, MOBX_REACT_OBSERVER_PACKAGE_NAME) !== null, |
| 145 | + zustandVersion, |
| 146 | + zustandMajorVersion: zustandVersion === null ? null : getLowestDependencyMajor(zustandVersion), |
| 147 | + framework: packageNode.dependencyInfo.framework, |
| 148 | + hasTypeScript, |
| 149 | + hasReactCompiler: detectReactCompiler(packageNode.directory, packageNode.manifest), |
| 150 | + hasReactCompilerLintPlugin: detectReactCompilerLintPlugin( |
| 151 | + packageNode.directory, |
| 152 | + packageNode.manifest, |
| 153 | + ), |
| 154 | + hasTanStackQuery: tanstackQuery !== null, |
| 155 | + hasI18nLibrary: hasI18nDependency(packageNode.manifest), |
| 156 | + tanstackQueryVersion: tanstackQuery?.value ?? null, |
| 157 | + styledComponentsVersion, |
| 158 | + valtioVersion, |
| 159 | + valtioMajorVersion: valtioVersion === null ? null : getLowestDependencyMajor(valtioVersion), |
| 160 | + hasRemotion: remotionVersion !== null, |
| 161 | + remotionVersion, |
| 162 | + remotionMajorVersion: |
| 163 | + remotionVersion === null ? null : getLowestDependencyMajor(remotionVersion), |
| 164 | + hasThree: threeVersion !== null || hasReactThreeFiber, |
| 165 | + threeVersion, |
| 166 | + threeRelease: parseThreeRelease(threeVersion), |
| 167 | + hasReactThreeFiber, |
| 168 | + reactThreeFiberVersion: reactThreeFiber?.value ?? null, |
| 169 | + reactThreeFiberMajorVersion: |
| 170 | + reactThreeFiber === null ? null : getLowestDependencyMajor(reactThreeFiber.value), |
| 171 | + hasSsrDependency: isPackageJsonSsrAware(packageNode.manifest), |
| 172 | + preactVersion, |
| 173 | + preactMajorVersion: parseReactMajor(preactVersion), |
| 174 | + hasReactNativeWorkspace: hasReactNativePackage, |
| 175 | + nextjsVersion, |
| 176 | + nextjsMajorVersion: nextjsVersion === null ? null : getLowestDependencyMajor(nextjsVersion), |
| 177 | + reactRouterVersion: reactRouter?.value ?? null, |
| 178 | + hasReactRouterFramework: |
| 179 | + getDependencyVersion(packageGraph, packageNode, "@react-router/dev") !== null, |
| 180 | + expoVersion, |
| 181 | + shopifyFlashListVersion, |
| 182 | + shopifyFlashListMajorVersion: |
| 183 | + shopifyFlashListVersion === null ? null : getLowestDependencyMajor(shopifyFlashListVersion), |
| 184 | + hasReanimated, |
| 185 | + reanimatedVersion, |
| 186 | + isPreES2023Target: hasTypeScript && detectPreES2023Target(packageNode.directory), |
| 187 | + isStaticExport: |
| 188 | + packageNode.dependencyInfo.framework === "nextjs" && |
| 189 | + detectNextjsStaticExport(packageNode.directory), |
| 190 | + sourceFileCount: 0, |
| 191 | + }; |
| 192 | + return getCapabilities(projectInfo); |
| 193 | +}; |
0 commit comments