diff --git a/README.md b/README.md
index c1eadb1..b6faf20 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,12 @@
# Solid Migration Assistant
-Solid Migration Assistant is an experimental, read-only analyzer for selected Solid 1.9 migration sites targeting Solid `2.0.0-rc.0`.
+Solid Migration Assistant is an experimental Solid 1.9 → Solid 2 migration assistant targeting Solid `2.0.0-rc.0`. It ships two workflows: a read-only `analyze` workflow that prints guidance for supported migration sites, and a deterministic `transform` workflow that relocates a small, pure subset of legacy import subpaths.
-The assistant scans project-owned `.js`, `.jsx`, `.ts`, and `.tsx` source, prints one detailed guidance string for each supported detection, and exits successfully when migration work is found. Guidance is sorted deterministically and printed to standard output; the Codemod runtime's progress lines and the final disclosure are written to standard error. The analyzer never edits the target and does not generate reports, dashboards, or other output there. Codemod analytics are disabled. Codemod may persist workflow and task state in normal platform user-data directories outside the target; the assistant does not redirect or remove that runtime state.
+The assistant scans project-owned `.js`, `.jsx`, `.ts`, and `.tsx` source, prints one detailed guidance string for each supported detection, and exits successfully when migration work is found. Guidance is sorted deterministically and printed to standard output; the Codemod runtime's progress lines and the final disclosure are written to standard error. The analyzer never edits the target and generates no artifact unless `--report FILE` is explicitly supplied. Codemod analytics are disabled. Codemod may persist workflow and task state in normal platform user-data directories outside the target; the assistant does not redirect or remove that runtime state.
-## Run the RC analyzer
+## Run the RC analyzer and transform
-> **RC scope:** this `0.2.1` analyzer targets Solid `2.0.0-rc.0`, scans project-owned `.js`, `.jsx`, `.ts`, and `.tsx` source, and covers only the detections listed below. A clean run is not proof that a project is ready for Solid 2.
+> **RC scope:** version `0.3.0` targets Solid `2.0.0-rc.0` and covers only the detections and relocations documented below. A clean analyzer run is not proof that a project is ready for Solid 2.
After npm publication, run the package from a project root with Node 20 or newer and npm (no pnpm installation is needed):
@@ -22,6 +22,14 @@ The current directory is analyzed by default. To analyze another directory:
npx --yes solid-migration-assistant@latest --target /path/to/a/solid-project
```
+To keep the same terminal guidance and also write the selected-rule pilot dashboard:
+
+```sh
+npx --yes solid-migration-assistant@latest --target . --report migration-report.html
+```
+
+The portable HTML covers `web-import`, `component-renames`, `create-effect`, and read-only legacy-subpath relocation previews. It contains the full matched source line range plus one complete context line before and after every finding. Treat it as project source. Existing files are refused unless `--force` is explicit; browsers open only with `--open`.
+
The supported rules detect the complete Solid 2 RC migration quick rename / removal map:
**Imports** — `solid-js/web`, store, renderer, and JSX-runtime subpath repackaging.
@@ -30,6 +38,26 @@ The supported rules detect the complete Solid 2 RC migration quick rename / remo
**Props & store** — `mergeProps`/`splitProps`, `onMount`, `unwrap`, `produce`, and `createMutable`/`modifyMutable`.
Coverage is deliberately limited. Even when no guidance is printed, review the documented exclusions and perform the application's normal type, build, and behavior validation; a clean analyzer run is not a readiness result.
+## Transform
+
+The opt-in `transform` workflow rewrites exactly five pure legacy Solid import subpaths in place and changes nothing else:
+
+- `solid-js/h` → `@solidjs/h`
+- `solid-js/html` → `@solidjs/html`
+- `solid-js/universal` → `@solidjs/universal`
+- `solid-js/jsx-runtime` → `@solidjs/web/jsx-runtime`
+- `solid-js/jsx-dev-runtime` → `@solidjs/web/jsx-dev-runtime`
+
+It covers static imports, re-exports, dynamic `import()`, and `require()` calls; preserves each reference's import form and quote style; and emits one per-edit report line (`file:line:column`, old → new, plus the migration-guide link). Every move is a pure package relocation with no removed, renamed, or behaviorally changed export, so no binding-level review is required for these five paths. The workflow is idempotent and writes no report files or other artifacts in the target. It deliberately leaves `solid-js/web`, `solid-js/store`, already-migrated paths, and near-miss subpaths such as `solid-js/h-extra` and `vendor/solid-js/h` untouched.
+
+In this repository, run `pnpm transform` against the current directory, or invoke the Codemod CLI directly to target another directory:
+
+```sh
+node ./node_modules/codemod/codemod --disable-analytics workflow run -w transform.yaml -t /path/to/a/solid-project --allow-dirty --no-interactive
+```
+
+After publication, select the `transform` workflow from the Codemod platform (it is registered with `default: false`).
+
## Verify the repository
```sh
@@ -37,8 +65,8 @@ pnpm install --frozen-lockfile
pnpm verify
```
-Verification runs comprehensive rule fixtures and an end-to-end project fixture, checks exact ordered guidance across repeated runs, and proves analysis leaves every target file unchanged.
+Verification runs comprehensive analysis and transformation rule fixtures plus end-to-end analyzer and transform fixtures; it checks exact ordered guidance, proves the analyzer leaves every target file unchanged, and proves the transform is idempotent and changes nothing outside the five relocated module strings.
See [`codemods/solid-migration-assistant/README.md`](codemods/solid-migration-assistant/README.md) for rule boundaries. Feedback is collected through ordinary [public GitHub issues](https://github.com/devagrawal09/solid-migration-assistant/issues/new).
-Automated migrations are a possible future roadmap item; this preview ships no executable transforms. The project is licensed under the [MIT License](LICENSE).
+Broader automated migrations remain a possible future roadmap item. The project is licensed under the [MIT License](LICENSE).
diff --git a/codemods/solid-migration-assistant/README.md b/codemods/solid-migration-assistant/README.md
index cbbdc1a..74e9df8 100644
--- a/codemods/solid-migration-assistant/README.md
+++ b/codemods/solid-migration-assistant/README.md
@@ -1,12 +1,12 @@
# Solid Migration Assistant
-This package implements Solid Migration Assistant as a single read-only workflow for a narrow Solid 1.9 client-application profile. It scans project-owned `.js`, `.jsx`, `.ts`, and `.tsx` source files and prints one detailed, location-bearing guidance string per supported migration site. The workflow returns no edits and writes no files.
+This package implements Solid Migration Assistant as two workflows for a narrow Solid 1.9 client-application profile. The read-only `analyze` workflow scans project-owned `.js`, `.jsx`, `.ts`, and `.tsx` source files and prints one detailed, location-bearing guidance string per supported migration site; without an explicit report option it returns no edits and writes no files. The `transform` workflow deterministically relocates a small, pure subset of legacy import subpaths.
The migration target is pinned to Solid `2.0.0-rc.0` at upstream commit [`ff4d3c44`](https://github.com/solidjs/solid/tree/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5).
## Analyze with npm
-> **RC scope:** version `0.2.1` targets Solid `2.0.0-rc.0`, analyzes project-owned `.js`, `.jsx`, `.ts`, and `.tsx` source, and implements only the detections documented below. A clean run is not proof that a project is ready for Solid 2.
+> **RC scope:** version `0.3.0` targets Solid `2.0.0-rc.0` and implements only the detections and relocations documented below. A clean run is not proof that a project is ready for Solid 2.
After npm publication, run this from the project root with Node 20 or newer and npm (pnpm is not required):
@@ -22,6 +22,20 @@ The current directory is the default target. An explicit target may be absolute
npx --yes solid-migration-assistant@latest --target /path/to/a/solid-project
```
+### Portable pilot report
+
+Add `--report FILE` to keep all current terminal guidance and also write one portable, hash-routed HTML report for that immutable run:
+
+```sh
+npx --yes solid-migration-assistant@latest --target . --report migration-report.html
+```
+
+The pilot dashboard includes only `web-import`, `component-renames`, `create-effect`, and the read-only `legacy-subpath-relocation` edit preview. Other supported rules remain terminal-only; their absence from the dashboard is not a clean result or a full-migration claim. The preview proposes safe relocation edits but never applies them during analysis.
+
+The command refuses an existing destination. Add `--force` to replace it atomically. It never launches a browser unless `--open` is also supplied. Every generated HTML embeds project source—the complete matched line range plus one complete line before and after—and the absolute analyzed target path used by editor links. Treat and share the report as project source and local machine metadata. VS Code actions use that fixed generation-time path; if the project is moved or the report is opened on another machine, those links will not point at the project copy there.
+
+The dashboard starts in the current operating-system light or dark preference. Its accessible theme switch changes only the open page; the choice is not stored, so reload and reopen return to the current system preference. Each finding offers a direct VS Code deep link and a small extensible overflow menu with a portable `relative/path:line:column` copy fallback. The header’s wrapped target-root utility row also copies the fixed absolute analyzed root.
+
A run with detections exits successfully. Complete opaque guidance strings are exact-deduplicated, sorted lexically as whole strings, and printed once to standard output as a terminal aggregate. The Codemod runtime's progress lines and the final disclosure are written to standard error. To capture the findings in a file, redirect standard output: `npx --yes solid-migration-assistant@latest --target . > report.txt`; to also capture progress and the disclosure, redirect both streams: `npx --yes solid-migration-assistant@latest --target . > report.txt 2>&1`. The analyzer does not edit the target or create persistent output there, and Codemod analytics are disabled. Codemod may persist workflow and task state in normal platform user-data directories outside the target; the assistant does not redirect or remove that runtime state.
## Supported detections
@@ -63,11 +77,31 @@ A run with detections exits successfully. Complete opaque guidance strings are e
Every finding links the immutable pinned [RC migration guide](https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md). Coverage follows the guide's complete quick rename / removal map.
+## Transform
+
+The opt-in `transform` workflow relocates exactly five pure legacy Solid import subpaths and changes nothing else:
+
+- `solid-js/h` → `@solidjs/h`
+- `solid-js/html` → `@solidjs/html`
+- `solid-js/universal` → `@solidjs/universal`
+- `solid-js/jsx-runtime` → `@solidjs/web/jsx-runtime`
+- `solid-js/jsx-dev-runtime` → `@solidjs/web/jsx-dev-runtime`
+
+The transform covers static imports, re-exports, dynamic `import()`, and `require()` calls; preserves each reference's import form and quote style; and emits one per-edit report line (`file:line:column`, old → new, plus the migration-guide link). Every move is a pure package relocation with no removed, renamed, or behaviorally changed export, so the rewrite is safe without binding-level review. The workflow is idempotent and writes no report files or other artifacts in the target. It deliberately leaves `solid-js/web`, `solid-js/store`, already-migrated paths, and near-miss subpaths such as `solid-js/h-extra` and `vendor/solid-js/h` untouched.
+
+In this repository, run `pnpm transform` against the current directory, or invoke the Codemod CLI directly to target another directory:
+
+```sh
+node ./node_modules/codemod/codemod --disable-analytics workflow run -w transform.yaml -t /path/to/a/solid-project --allow-dirty --no-interactive
+```
+
+After publication, select the `transform` workflow from the Codemod platform (it is registered with `default: false`).
+
## Deliberate limits
Current coverage is deliberately limited: the analyzer does not cover indirect calls, shadowed bindings, unsupported argument counts, re-exports, dynamic imports, `require`, TypeScript `import()` type expressions, configuration, dependencies, SSR, libraries, monorepos, or cross-file intent. Binding-sensitive call and JSX rules also exclude aliased and namespace bindings. No guidance—or a clean run—is not a readiness result and does not imply complete Solid 2 migration coverage.
-Automated transforms are roadmap-only. This package exposes no transform command, workflow, test, or implementation.
+The read-only `analyze` workflow remains detection-only. Broader automated transforms remain roadmap items beyond the five pure import-path relocations implemented by the `transform` workflow.
## Verify
@@ -77,4 +111,4 @@ From the workspace root:
pnpm verify
```
-Verification checks detection-only architecture, comprehensive rule boundaries, exact and repeatable terminal output, fixture immutability, TypeScript types, and workflow schema validity.
+Verification checks the two-workflow architecture, comprehensive analysis and transformation rule boundaries, exact and repeatable terminal output, analyzer fixture immutability, transform idempotency, TypeScript types, and workflow schema validity.
diff --git a/codemods/solid-migration-assistant/assets/dashboard/index.html b/codemods/solid-migration-assistant/assets/dashboard/index.html
new file mode 100644
index 0000000..286cb3d
--- /dev/null
+++ b/codemods/solid-migration-assistant/assets/dashboard/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+ Solid Migration Report
+
+
+
+
+
+
+
+
diff --git a/codemods/solid-migration-assistant/codemod.yaml b/codemods/solid-migration-assistant/codemod.yaml
index b91a8a3..95a458a 100644
--- a/codemods/solid-migration-assistant/codemod.yaml
+++ b/codemods/solid-migration-assistant/codemod.yaml
@@ -1,8 +1,8 @@
schema_version: "1.0"
name: "solid-migration-assistant"
-version: "0.2.1"
-description: "Read-only Solid 1.9 to Solid 2 RC migration analyzer for project-owned JavaScript and TypeScript source"
+version: "0.3.0"
+description: "Solid 1.9 to Solid 2 RC migration assistant: read-only analyzer plus deterministic legacy import-path relocation for project-owned JavaScript and TypeScript source"
author: "Solid Migration Assistant Contributors"
license: "MIT"
category: "migration"
@@ -22,3 +22,6 @@ workflows:
- name: analyze
default: true
path: workflow.yaml
+ - name: transform
+ default: false
+ path: transform.yaml
diff --git a/codemods/solid-migration-assistant/dashboard/app.tsx b/codemods/solid-migration-assistant/dashboard/app.tsx
new file mode 100644
index 0000000..7a35aa9
--- /dev/null
+++ b/codemods/solid-migration-assistant/dashboard/app.tsx
@@ -0,0 +1,240 @@
+import { Errored, For, Show, createSignal, onCleanup } from "solid-js";
+import type { JSX } from "@solidjs/web";
+import { createRouter, hashHistory, useHref, useParams } from "@solidjs/router";
+import type {
+ JsonValue,
+ ReportEnvelope,
+ RuleSliceDescriptor,
+} from "../shared/report.ts";
+import { applyTheme, oppositeTheme, preferredTheme, type Theme } from "./theme.ts";
+import { CopyButton } from "./copy-button.tsx";
+
+export function createDashboardRouter(
+ envelope: ReportEnvelope,
+ manifest: readonly RuleSliceDescriptor[],
+) {
+ const groups = groupRulesByDomain(manifest);
+
+ function IndexPage() {
+ return (
+
+
+
One immutable migration run
+
Rule reports
+
Open a registered pilot slice to review its rule-owned report.
{props.report.findings.length} legacy web import sites
+ ),
+ Detail: (props) => (
+ ({
+ filename: finding.filename,
+ location: formatFindingLocation(finding.filename, finding.line, finding.column),
+ label: finding.form,
+ snippet: finding.snippet,
+ editorTarget: {
+ analyzedTargetRoot: props.run.analyzedTargetRoot,
+ filename: finding.filename,
+ line: finding.line,
+ column: finding.column,
+ },
+ details: ,
+ }))} />
+ ),
+});
diff --git a/codemods/solid-migration-assistant/rules/imports/web-import/web-import.test.ts b/codemods/solid-migration-assistant/rules/analysis/imports/web-import/web-import.test.ts
similarity index 68%
rename from codemods/solid-migration-assistant/rules/imports/web-import/web-import.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/imports/web-import/web-import.test.ts
index b85145b..56bdafb 100644
--- a/codemods/solid-migration-assistant/rules/imports/web-import/web-import.test.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/imports/web-import/web-import.test.ts
@@ -1,6 +1,7 @@
import type { Codemod } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
import { analyzeWebImport } from "./web-import.ts";
+import { formatWebImportGuidance } from "./report.ts";
const MIGRATION_GUIDE =
"https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#imports-where-things-live-now";
@@ -27,7 +28,7 @@ const EXPECTED_SITES = [
const testWebImportRule: Codemod = async (root) => {
const filename = root.relativeFilename().replaceAll("\\", "/");
- const guidance = analyzeWebImport(root.root(), { filename });
+ const { guidance, report } = analyzeWebImport(root.root(), { filename });
const expected = EXPECTED_SITES.map(
({ location, form }) => {
const formLabel = form;
@@ -65,6 +66,24 @@ Guidance:${formGuidance} Make and validate that edit yourself; this analyzer nev
}
}
+
+ for (const [index, finding] of report.findings.entries()) {
+ if ("guidance" in finding || !finding.summary || !finding.reason || finding.nextSteps.length === 0 || !finding.officialGuideUrl) {
+ throw new Error("web import report must expose structured guidance fields only");
+ }
+ if (formatWebImportGuidance(finding) !== guidance[index]) throw new Error("web import terminal guidance drifted from its report contract");
+ if (finding.snippet.matchStartLine !== finding.line || finding.snippet.matchEndLine < finding.snippet.matchStartLine || finding.snippet.matchEndLine > finding.snippet.endLine) {
+ throw new Error("snippet must carry the AST match line range");
+ }
+ if (!finding.snippet.text.includes("solid-js") || !finding.snippet.text.includes("web")) throw new Error("web import snippet must contain the matched source");
+ if (finding.snippet.startLine !== Math.max(1, finding.line - 1)) {
+ throw new Error(`snippet must start one complete line before ${finding.line}`);
+ }
+ if (finding.snippet.endLine < finding.line || finding.snippet.text.split("\n").length !== finding.snippet.endLine - finding.snippet.startLine + 1) {
+ throw new Error(`snippet must include the full match and complete line bounds at ${finding.line}`);
+ }
+ }
+
return null;
};
diff --git a/codemods/solid-migration-assistant/rules/analysis/imports/web-import/web-import.ts b/codemods/solid-migration-assistant/rules/analysis/imports/web-import/web-import.ts
new file mode 100644
index 0000000..0d199f4
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/analysis/imports/web-import/web-import.ts
@@ -0,0 +1,52 @@
+import type { SgNode } from "codemod:ast-grep";
+import type TSX from "codemod:ast-grep/langs/tsx";
+import { findModuleReferences, sourceSnippet } from "../../../../shared/analysis.ts";
+import type { ModuleReference } from "../../../../shared/analysis.ts";
+import type { AnalysisRuleResult } from "../../../../shared/report.ts";
+import { formatWebImportGuidance, type WebImportFinding, type WebImportReport } from "./report.ts";
+
+const LEGACY_WEB_MODULE = "solid-js/web";
+const MIGRATION_GUIDE =
+ "https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#imports-where-things-live-now";
+
+const FORM_LABELS: Record = {
+ import: "static import",
+ "re-export": "re-export",
+ "dynamic-import": "dynamic import()",
+ require: "require() call",
+};
+
+const FORM_GUIDANCE: Record = {
+ import: "Change only this static import's module source to @solidjs/web and preserve its import form and quote style.",
+ "re-export": "Change only this re-export's module source to @solidjs/web and preserve its export form and quote style.",
+ "dynamic-import": "Change only this dynamic import's module source to @solidjs/web and preserve its quote style.",
+ require: "Change only this require call's module source to @solidjs/web and preserve its quote style.",
+};
+
+export function analyzeWebImport(
+ rootNode: SgNode,
+ context: { filename: string },
+): AnalysisRuleResult {
+ const findings: WebImportFinding[] = findModuleReferences(rootNode)
+ .filter((ref) => ref.moduleName === LEGACY_WEB_MODULE)
+ .map(({ source, form }) => {
+ const start = source.range().start;
+ return {
+ filename: context.filename,
+ line: start.line + 1,
+ column: start.column + 1,
+ form,
+ summary: `Move this Solid web renderer ${FORM_LABELS[form]}.`,
+ reason: "Solid 2 publishes the web renderer from @solidjs/web instead of the solid-js/web subpath.",
+ nextSteps: [
+ FORM_GUIDANCE[form],
+ "Make and validate that edit yourself; this analyzer never edits or runs the target project.",
+ ],
+ cautions: [],
+ validation: [],
+ officialGuideUrl: MIGRATION_GUIDE,
+ snippet: sourceSnippet(source),
+ };
+ });
+ return { guidance: findings.map(formatWebImportGuidance), report: { findings } };
+}
diff --git a/codemods/solid-migration-assistant/rules/jsx/class-list/class-list.test.ts b/codemods/solid-migration-assistant/rules/analysis/jsx/class-list/class-list.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/jsx/class-list/class-list.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/jsx/class-list/class-list.test.ts
diff --git a/codemods/solid-migration-assistant/rules/jsx/class-list/class-list.ts b/codemods/solid-migration-assistant/rules/analysis/jsx/class-list/class-list.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/jsx/class-list/class-list.ts
rename to codemods/solid-migration-assistant/rules/analysis/jsx/class-list/class-list.ts
diff --git a/codemods/solid-migration-assistant/rules/jsx/class-list/normal.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/jsx/class-list/normal.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/jsx/class-list/normal.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/jsx/class-list/normal.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/jsx/component-renames/component-renames.test.ts b/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/component-renames.test.ts
similarity index 84%
rename from codemods/solid-migration-assistant/rules/jsx/component-renames/component-renames.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/component-renames.test.ts
index 1ef0742..432edd8 100644
--- a/codemods/solid-migration-assistant/rules/jsx/component-renames/component-renames.test.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/component-renames.test.ts
@@ -1,6 +1,7 @@
import type { Codemod } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
import { analyzeJsxComponentRenames } from "./component-renames.ts";
+import { formatComponentRenameGuidance } from "./report.ts";
const SUSPENSE_BOUNDARY_GUIDE =
"https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#suspense--errorboundary--loading--errored";
@@ -11,7 +12,7 @@ const SUSPENSE_LIST_GUIDE =
const testJsxComponentRenames: Codemod = async (root) => {
const filename = root.relativeFilename().replaceAll("\\", "/");
- const guidance = analyzeJsxComponentRenames(root.root(), {
+ const { guidance, report } = analyzeJsxComponentRenames(root.root(), {
filename: "ignored-context-filename.tsx",
});
const expected = [
@@ -47,6 +48,24 @@ Guidance: Read the complete list site, its each value, child callback, props, an
throw new Error("every JSX component finding must require manual review");
}
+
+ for (const [index, finding] of report.findings.entries()) {
+ if ("guidance" in finding || !finding.summary || !finding.reason || finding.nextSteps.length === 0 || finding.cautions.length === 0 || !finding.officialGuideUrl) {
+ throw new Error("component report must expose structured guidance and stop conditions");
+ }
+ if (formatComponentRenameGuidance(finding) !== guidance[index]) throw new Error("component terminal guidance drifted from its report contract");
+ if (finding.snippet.matchStartLine !== finding.line || finding.snippet.matchEndLine < finding.snippet.matchStartLine || finding.snippet.matchEndLine > finding.snippet.endLine) {
+ throw new Error("snippet must carry the AST match line range");
+ }
+ if (!finding.snippet.text.includes(finding.legacyName)) throw new Error("component snippet must contain the matched element");
+ if (finding.snippet.startLine !== Math.max(1, finding.line - 1)) {
+ throw new Error(`snippet must start one complete line before ${finding.line}`);
+ }
+ if (finding.snippet.endLine < finding.line || finding.snippet.text.split("\n").length !== finding.snippet.endLine - finding.snippet.startLine + 1) {
+ throw new Error(`snippet must include the full match and complete line bounds at ${finding.line}`);
+ }
+ }
+
return null;
};
diff --git a/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/component-renames.ts b/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/component-renames.ts
new file mode 100644
index 0000000..e70e898
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/component-renames.ts
@@ -0,0 +1,160 @@
+import type { SgNode } from "codemod:ast-grep";
+import type TSX from "codemod:ast-grep/langs/tsx";
+import { sourceSnippet, stringLiteralValue } from "../../../../shared/analysis.ts";
+import type { AnalysisRuleResult } from "../../../../shared/report.ts";
+import { formatComponentRenameGuidance, type ComponentRenameContent, type ComponentRenameFinding, type ComponentRenamesReport } from "./report.ts";
+
+const SUSPENSE_BOUNDARY_GUIDE =
+ "https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#suspense--errorboundary--loading--errored";
+const INDEX_GUIDE =
+ "https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#list-rendering-index-is-gone-and-for-handles-each-keying-mode";
+const SUSPENSE_LIST_GUIDE =
+ "https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#coordinating-loading-boundaries-suspenselist--reveal";
+
+const COMPONENT_MIGRATIONS = {
+ Suspense: "Loading",
+ ErrorBoundary: "Errored",
+ SuspenseList: "Reveal",
+ Index: "For",
+} as const;
+
+type LegacyComponent = keyof typeof COMPONENT_MIGRATIONS;
+
+type ComponentSite = {
+ element: SgNode;
+ filename: string;
+ legacyName: LegacyComponent;
+};
+
+export function analyzeJsxComponentRenames(
+ rootNode: SgNode,
+ context: { filename: string },
+): AnalysisRuleResult {
+ const findings = findImportedComponentSites(rootNode)
+ .sort((left, right) => {
+ const leftStart = left.element.range().start;
+ const rightStart = right.element.range().start;
+ const filenameOrder = left.filename.localeCompare(right.filename);
+ if (filenameOrder !== 0) return filenameOrder;
+ if (leftStart.line !== rightStart.line) {
+ return leftStart.line - rightStart.line;
+ }
+ return leftStart.column - rightStart.column;
+ })
+ .map(({ element, filename, legacyName }): ComponentRenameFinding => {
+ const start = element.range().start;
+ return {
+ filename,
+ line: start.line + 1,
+ column: start.column + 1,
+ legacyName,
+ replacement: COMPONENT_MIGRATIONS[legacyName],
+ ...componentContent(legacyName),
+ snippet: sourceSnippet(element),
+ };
+ });
+ return { guidance: findings.map(formatComponentRenameGuidance), report: { findings } };
+}
+
+function findImportedComponentSites(rootNode: SgNode): ComponentSite[] {
+ const sites = new Map();
+
+ for (const statement of rootNode.findAll({
+ rule: { kind: "import_statement" },
+ })) {
+ const source = statement.children().find((child) => child.is("string"));
+ if (!source || stringLiteralValue(source) !== "solid-js") continue;
+
+ for (const specifier of statement.findAll({
+ rule: { kind: "import_specifier" },
+ })) {
+ const legacyName = importedLegacyName(specifier);
+ if (!legacyName) continue;
+
+ const identifiers = specifier.findAll({ rule: { kind: "identifier" } });
+ const binding = identifiers[0];
+ if (!binding || identifiers.length !== 1) continue;
+
+ for (const fileReferences of binding.references()) {
+ const filename = fileReferences.root
+ .relativeFilename()
+ .replaceAll("\\", "/");
+ for (const reference of fileReferences.nodes) {
+ const element = reference.parent();
+ if (
+ !element ||
+ (element.kind() !== "jsx_opening_element" &&
+ element.kind() !== "jsx_self_closing_element") ||
+ element.field("name")?.id() !== reference.id()
+ ) {
+ continue;
+ }
+ sites.set(`${filename}:${element.id()}`, {
+ element,
+ filename,
+ legacyName,
+ });
+ }
+ }
+ }
+ }
+
+ return [...sites.values()];
+}
+
+function importedLegacyName(specifier: SgNode): LegacyComponent | null {
+ const text = specifier.text().trim();
+ return text in COMPONENT_MIGRATIONS ? (text as LegacyComponent) : null;
+}
+
+function componentContent(legacyName: LegacyComponent): ComponentRenameContent {
+ const commonValidation = "Make and validate this migration yourself; this analyzer never edits or runs the target project.";
+ if (legacyName === "Suspense") return {
+ summary: "Manual review required: migrate this imported Suspense JSX site to Loading.",
+ reason: "Solid 2 replaces the solid-js Suspense component with Loading for initial not-ready fallback UI.",
+ nextSteps: [
+ "Read this complete boundary, its fallback, children, props, and corresponding import.",
+ "Replace the unaliased named Suspense import and this JSX component with Loading only after confirming that the boundary owns initial not-ready UI and that its fallback and children preserve their rendering behavior.",
+ commonValidation,
+ ],
+ cautions: ["Stop without proposing a rewrite when props are spread or forwarded, fallback ownership or evaluation is indirect, nested async boundaries make the intended initial-loading behavior unclear, or focused rendering tests do not cover the fallback and ready states."],
+ validation: ["Ask for the smallest focused test or runtime observation that exposes both states."],
+ officialGuideUrl: SUSPENSE_BOUNDARY_GUIDE,
+ };
+ if (legacyName === "ErrorBoundary") return {
+ summary: "Manual review required: migrate this imported ErrorBoundary JSX site to Errored.",
+ reason: "Solid 2 replaces the solid-js ErrorBoundary component with Errored, whose fallback receives an error accessor rather than a raw error value.",
+ nextSteps: [
+ "Read this complete boundary, its fallback, children, props, and corresponding import.",
+ "Replace the unaliased named ErrorBoundary import and this JSX component with Errored only after updating every fallback use to read the error accessor, such as err(), while preserving error ownership and recovery behavior.",
+ commonValidation,
+ ],
+ cautions: ["Stop without proposing a rewrite when props are spread or forwarded, the fallback is indirect or escapes, the error value is passed to unknown code, reset or recovery behavior is unclear, or focused tests do not cover thrown and recovered states."],
+ validation: ["Ask for the smallest focused test or runtime observation that exposes the fallback value and recovery behavior."],
+ officialGuideUrl: SUSPENSE_BOUNDARY_GUIDE,
+ };
+ if (legacyName === "Index") return {
+ summary: "Manual review required: migrate this imported Index JSX site to For keyed={false}.",
+ reason: "Solid 2 removes Index; its direct replacement is For with keyed={false}, whose child callback receives an item accessor and a stable numeric index.",
+ nextSteps: [
+ "Read the complete list site, its each value, child callback, props, and corresponding import.",
+ "Replace the unaliased named Index import and this JSX component with For, add the literal keyed={false} mode, and review the callback so the item remains an accessor and the index remains a stable number.",
+ commonValidation,
+ ],
+ cautions: ["Stop without proposing a rewrite when props are spread or forwarded, each or the child callback is indirect, callback parameters escape to unknown code, item identity or index behavior is unclear, or focused list-update tests do not prove state preservation."],
+ validation: ["Ask for the smallest focused test or runtime observation that covers insertion, removal, reordering, and item updates."],
+ officialGuideUrl: INDEX_GUIDE,
+ };
+ return {
+ summary: "Manual review required: migrate this imported SuspenseList JSX site to Reveal.",
+ reason: "Solid 2 replaces SuspenseList with Reveal for coordinating sibling Loading boundaries and replaces revealOrder and tail controls with order and collapsed semantics.",
+ nextSteps: [
+ "Read the complete group, its revealOrder and tail values, children, nesting, props, and corresponding import.",
+ 'Replace the unaliased named SuspenseList import and this JSX component with Reveal only after mapping literal revealOrder="forwards" to the default or order="sequential", revealOrder="together" to order="together", and tail="collapsed" to collapsed only under sequential order; review the children as sibling Loading boundaries, and do not use the earlier-beta boolean together prop.',
+ commonValidation,
+ ],
+ cautions: ["Stop without proposing a rewrite when props are spread or forwarded, revealOrder or tail is dynamic or has another value, child boundary ownership or nesting is unclear, intended reveal timing cannot be established, or focused behavior tests do not cover the coordinated states."],
+ validation: ["Ask for the smallest focused test or runtime observation that exposes ordering, fallback, and collapsed-tail behavior."],
+ officialGuideUrl: SUSPENSE_LIST_GUIDE,
+ };
+}
diff --git a/codemods/solid-migration-assistant/rules/jsx/component-renames/normal.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/normal.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/jsx/component-renames/normal.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/normal.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/report.ts b/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/report.ts
new file mode 100644
index 0000000..b5e04e8
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/report.ts
@@ -0,0 +1,35 @@
+import { defineRuleReportAggregator, type SourceSnippet } from "../../../../shared/report.ts";
+export const COMPONENT_RENAMES_RULE_ID = "analysis/jsx/component-renames";
+export const COMPONENT_RENAMES_RULE_ROUTE = "jsx/component-renames";
+export type LegacyComponentName = "Suspense" | "ErrorBoundary" | "SuspenseList" | "Index";
+export type ComponentRenameFinding = {
+ readonly filename: string;
+ readonly line: number;
+ readonly column: number;
+ readonly legacyName: LegacyComponentName;
+ readonly replacement: string;
+ readonly summary: string;
+ readonly reason: string;
+ readonly nextSteps: readonly string[];
+ readonly cautions: readonly string[];
+ readonly validation: readonly string[];
+ readonly officialGuideUrl: string;
+ readonly snippet: SourceSnippet;
+};
+export type ComponentRenamesReport = { readonly findings: readonly ComponentRenameFinding[] };
+export type ComponentRenameContent = Pick;
+
+export function formatComponentRenameGuidance(finding: ComponentRenameFinding): string {
+ return `${finding.filename}:${finding.line}:${finding.column} ${finding.summary}
+Why: ${finding.reason}
+Guidance: ${[...finding.nextSteps, ...finding.cautions, ...finding.validation].join(" ")} Official migration guide: ${finding.officialGuideUrl}`;
+}
+export const componentRenamesReportAggregator = defineRuleReportAggregator({
+ id: COMPONENT_RENAMES_RULE_ID,
+ emptyReport: () => ({ findings: [] }),
+ merge: (projectReport, nextReport) => ({ findings: [...projectReport.findings, ...nextReport.findings].sort(compareFindings) }),
+});
+function compareFindings(left: ComponentRenameFinding, right: ComponentRenameFinding): number {
+ const filenameOrder = left.filename < right.filename ? -1 : left.filename > right.filename ? 1 : 0;
+ return filenameOrder || left.line - right.line || left.column - right.column;
+}
diff --git a/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/ui.tsx b/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/ui.tsx
new file mode 100644
index 0000000..982a0f2
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/ui.tsx
@@ -0,0 +1,39 @@
+import { GuidanceSections, RuleFindings } from "../../../../dashboard/finding-ui.tsx";
+import { formatFindingLocation } from "../../../../dashboard/finding-model.ts";
+import { defineRuleSlice } from "../../../../shared/report.ts";
+import {
+ COMPONENT_RENAMES_RULE_ID,
+ COMPONENT_RENAMES_RULE_ROUTE,
+ type ComponentRenamesReport,
+} from "./report.ts";
+
+export const componentRenamesSlice = defineRuleSlice({
+ id: COMPONENT_RENAMES_RULE_ID,
+ route: COMPONENT_RENAMES_RULE_ROUTE,
+ title: "Component renames",
+ domain: "JSX",
+ kind: "analysis",
+ Summary: (props) =>
{props.report.findings.length} component sites need review
,
+ Detail: (props) => (
+ ({
+ filename: finding.filename,
+ location: formatFindingLocation(finding.filename, finding.line, finding.column),
+ label: <>{finding.legacyName} → {finding.replacement}>,
+ snippet: finding.snippet,
+ editorTarget: {
+ analyzedTargetRoot: props.run.analyzedTargetRoot,
+ filename: finding.filename,
+ line: finding.line,
+ column: finding.column,
+ },
+ details: ,
+ }))} />
+ ),
+});
diff --git a/codemods/solid-migration-assistant/rules/jsx/context-provider/context-provider.test.ts b/codemods/solid-migration-assistant/rules/analysis/jsx/context-provider/context-provider.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/jsx/context-provider/context-provider.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/jsx/context-provider/context-provider.test.ts
diff --git a/codemods/solid-migration-assistant/rules/jsx/context-provider/context-provider.ts b/codemods/solid-migration-assistant/rules/analysis/jsx/context-provider/context-provider.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/jsx/context-provider/context-provider.ts
rename to codemods/solid-migration-assistant/rules/analysis/jsx/context-provider/context-provider.ts
diff --git a/codemods/solid-migration-assistant/rules/jsx/context-provider/normal.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/jsx/context-provider/normal.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/jsx/context-provider/normal.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/jsx/context-provider/normal.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/jsx/dom-attr-namespaces/dom-attr-namespaces.test.ts b/codemods/solid-migration-assistant/rules/analysis/jsx/dom-attr-namespaces/dom-attr-namespaces.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/jsx/dom-attr-namespaces/dom-attr-namespaces.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/jsx/dom-attr-namespaces/dom-attr-namespaces.test.ts
diff --git a/codemods/solid-migration-assistant/rules/jsx/dom-attr-namespaces/dom-attr-namespaces.ts b/codemods/solid-migration-assistant/rules/analysis/jsx/dom-attr-namespaces/dom-attr-namespaces.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/jsx/dom-attr-namespaces/dom-attr-namespaces.ts
rename to codemods/solid-migration-assistant/rules/analysis/jsx/dom-attr-namespaces/dom-attr-namespaces.ts
diff --git a/codemods/solid-migration-assistant/rules/jsx/dom-attr-namespaces/normal.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/jsx/dom-attr-namespaces/normal.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/jsx/dom-attr-namespaces/normal.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/jsx/dom-attr-namespaces/normal.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/jsx/dom-event-namespaces/dom-event-namespaces.test.ts b/codemods/solid-migration-assistant/rules/analysis/jsx/dom-event-namespaces/dom-event-namespaces.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/jsx/dom-event-namespaces/dom-event-namespaces.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/jsx/dom-event-namespaces/dom-event-namespaces.test.ts
diff --git a/codemods/solid-migration-assistant/rules/jsx/dom-event-namespaces/dom-event-namespaces.ts b/codemods/solid-migration-assistant/rules/analysis/jsx/dom-event-namespaces/dom-event-namespaces.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/jsx/dom-event-namespaces/dom-event-namespaces.ts
rename to codemods/solid-migration-assistant/rules/analysis/jsx/dom-event-namespaces/dom-event-namespaces.ts
diff --git a/codemods/solid-migration-assistant/rules/jsx/dom-event-namespaces/normal.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/jsx/dom-event-namespaces/normal.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/jsx/dom-event-namespaces/normal.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/jsx/dom-event-namespaces/normal.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/jsx/dom-use-directive/dom-use-directive.test.ts b/codemods/solid-migration-assistant/rules/analysis/jsx/dom-use-directive/dom-use-directive.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/jsx/dom-use-directive/dom-use-directive.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/jsx/dom-use-directive/dom-use-directive.test.ts
diff --git a/codemods/solid-migration-assistant/rules/jsx/dom-use-directive/dom-use-directive.ts b/codemods/solid-migration-assistant/rules/analysis/jsx/dom-use-directive/dom-use-directive.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/jsx/dom-use-directive/dom-use-directive.ts
rename to codemods/solid-migration-assistant/rules/analysis/jsx/dom-use-directive/dom-use-directive.ts
diff --git a/codemods/solid-migration-assistant/rules/jsx/dom-use-directive/normal.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/jsx/dom-use-directive/normal.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/jsx/dom-use-directive/normal.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/jsx/dom-use-directive/normal.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/lifecycle/on-cleanup/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/lifecycle/on-cleanup/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/lifecycle/on-cleanup/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/lifecycle/on-cleanup/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/lifecycle/on-cleanup/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/lifecycle/on-cleanup/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/lifecycle/on-cleanup/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/lifecycle/on-cleanup/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/lifecycle/on-cleanup/on-cleanup.test.ts b/codemods/solid-migration-assistant/rules/analysis/lifecycle/on-cleanup/on-cleanup.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/lifecycle/on-cleanup/on-cleanup.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/lifecycle/on-cleanup/on-cleanup.test.ts
diff --git a/codemods/solid-migration-assistant/rules/lifecycle/on-cleanup/on-cleanup.ts b/codemods/solid-migration-assistant/rules/analysis/lifecycle/on-cleanup/on-cleanup.ts
similarity index 96%
rename from codemods/solid-migration-assistant/rules/lifecycle/on-cleanup/on-cleanup.ts
rename to codemods/solid-migration-assistant/rules/analysis/lifecycle/on-cleanup/on-cleanup.ts
index 5e26335..ac6fb82 100644
--- a/codemods/solid-migration-assistant/rules/lifecycle/on-cleanup/on-cleanup.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/lifecycle/on-cleanup/on-cleanup.ts
@@ -1,6 +1,6 @@
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
+import { findImportedCalls } from "../../../../shared/analysis.ts";
const MIGRATION_GUIDE =
"https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#effects-lifecycle-and-cleanup";
diff --git a/codemods/solid-migration-assistant/rules/lifecycle/on-mount/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/lifecycle/on-mount/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/lifecycle/on-mount/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/lifecycle/on-mount/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/lifecycle/on-mount/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/lifecycle/on-mount/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/lifecycle/on-mount/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/lifecycle/on-mount/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/lifecycle/on-mount/on-mount.test.ts b/codemods/solid-migration-assistant/rules/analysis/lifecycle/on-mount/on-mount.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/lifecycle/on-mount/on-mount.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/lifecycle/on-mount/on-mount.test.ts
diff --git a/codemods/solid-migration-assistant/rules/lifecycle/on-mount/on-mount.ts b/codemods/solid-migration-assistant/rules/analysis/lifecycle/on-mount/on-mount.ts
similarity index 96%
rename from codemods/solid-migration-assistant/rules/lifecycle/on-mount/on-mount.ts
rename to codemods/solid-migration-assistant/rules/analysis/lifecycle/on-mount/on-mount.ts
index 745a8e2..24451ee 100644
--- a/codemods/solid-migration-assistant/rules/lifecycle/on-mount/on-mount.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/lifecycle/on-mount/on-mount.ts
@@ -1,6 +1,6 @@
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
+import { findImportedCalls } from "../../../../shared/analysis.ts";
const MIGRATION_GUIDE =
"https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#effects-lifecycle-and-cleanup";
diff --git a/codemods/solid-migration-assistant/rules/props/merge-props/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/props/merge-props/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/props/merge-props/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/props/merge-props/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/props/merge-props/merge-props.test.ts b/codemods/solid-migration-assistant/rules/analysis/props/merge-props/merge-props.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/props/merge-props/merge-props.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/props/merge-props/merge-props.test.ts
diff --git a/codemods/solid-migration-assistant/rules/props/merge-props/merge-props.ts b/codemods/solid-migration-assistant/rules/analysis/props/merge-props/merge-props.ts
similarity index 97%
rename from codemods/solid-migration-assistant/rules/props/merge-props/merge-props.ts
rename to codemods/solid-migration-assistant/rules/analysis/props/merge-props/merge-props.ts
index a216993..292ec3a 100644
--- a/codemods/solid-migration-assistant/rules/props/merge-props/merge-props.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/props/merge-props/merge-props.ts
@@ -1,6 +1,6 @@
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
+import { findImportedCalls } from "../../../../shared/analysis.ts";
const MIGRATION_GUIDE =
"https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#mergeprops--splitprops--merge--omit";
diff --git a/codemods/solid-migration-assistant/rules/props/merge-props/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/props/merge-props/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/props/merge-props/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/props/merge-props/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/props/split-props/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/props/split-props/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/props/split-props/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/props/split-props/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/props/split-props/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/props/split-props/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/props/split-props/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/props/split-props/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/props/split-props/split-props.test.ts b/codemods/solid-migration-assistant/rules/analysis/props/split-props/split-props.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/props/split-props/split-props.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/props/split-props/split-props.test.ts
diff --git a/codemods/solid-migration-assistant/rules/props/split-props/split-props.ts b/codemods/solid-migration-assistant/rules/analysis/props/split-props/split-props.ts
similarity index 96%
rename from codemods/solid-migration-assistant/rules/props/split-props/split-props.ts
rename to codemods/solid-migration-assistant/rules/analysis/props/split-props/split-props.ts
index c858298..8279115 100644
--- a/codemods/solid-migration-assistant/rules/props/split-props/split-props.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/props/split-props/split-props.ts
@@ -1,6 +1,6 @@
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
+import { findImportedCalls } from "../../../../shared/analysis.ts";
const MIGRATION_GUIDE =
"https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#mergeprops--splitprops--merge--omit";
diff --git a/codemods/solid-migration-assistant/rules/reactivity/batch/batch.test.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/batch/batch.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/batch/batch.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/batch/batch.test.ts
diff --git a/codemods/solid-migration-assistant/rules/reactivity/batch/batch.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/batch/batch.ts
similarity index 95%
rename from codemods/solid-migration-assistant/rules/reactivity/batch/batch.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/batch/batch.ts
index ce7ccf8..626a28b 100644
--- a/codemods/solid-migration-assistant/rules/reactivity/batch/batch.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/batch/batch.ts
@@ -1,6 +1,6 @@
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
+import { findImportedCalls } from "../../../../shared/analysis.ts";
const MIGRATION_GUIDE =
"https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#batch--default-microtask-batching--flush";
diff --git a/codemods/solid-migration-assistant/rules/reactivity/batch/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/batch/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/batch/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/batch/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/batch/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/batch/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/batch/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/batch/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-computed/create-computed.test.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-computed/create-computed.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/create-computed/create-computed.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-computed/create-computed.test.ts
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-computed/create-computed.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-computed/create-computed.ts
similarity index 97%
rename from codemods/solid-migration-assistant/rules/reactivity/create-computed/create-computed.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-computed/create-computed.ts
index 9aee01e..45f10e5 100644
--- a/codemods/solid-migration-assistant/rules/reactivity/create-computed/create-computed.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-computed/create-computed.ts
@@ -1,6 +1,6 @@
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
+import { findImportedCalls } from "../../../../shared/analysis.ts";
const MIGRATION_GUIDE =
"https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#createcomputed--creatememo-createeffect-or-derived-createsignal";
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-computed/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-computed/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/create-computed/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-computed/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-computed/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-computed/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/create-computed/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-computed/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-dynamic/create-dynamic.test.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-dynamic/create-dynamic.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/create-dynamic/create-dynamic.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-dynamic/create-dynamic.test.ts
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-dynamic/create-dynamic.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-dynamic/create-dynamic.ts
similarity index 95%
rename from codemods/solid-migration-assistant/rules/reactivity/create-dynamic/create-dynamic.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-dynamic/create-dynamic.ts
index 6149377..196e77f 100644
--- a/codemods/solid-migration-assistant/rules/reactivity/create-dynamic/create-dynamic.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-dynamic/create-dynamic.ts
@@ -1,6 +1,6 @@
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
+import { findImportedCalls } from "../../../../shared/analysis.ts";
const GUIDE = "https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md";
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-dynamic/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-dynamic/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/create-dynamic/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-dynamic/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-dynamic/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-dynamic/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/create-dynamic/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-dynamic/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-effect/create-effect.test.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/create-effect.test.ts
similarity index 66%
rename from codemods/solid-migration-assistant/rules/reactivity/create-effect/create-effect.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/create-effect.test.ts
index 71ede24..cd24ed0 100644
--- a/codemods/solid-migration-assistant/rules/reactivity/create-effect/create-effect.test.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/create-effect.test.ts
@@ -1,13 +1,14 @@
import type { Codemod } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
import { analyzeCreateEffect } from "./create-effect.ts";
+import { formatCreateEffectGuidance } from "./report.ts";
const MIGRATION_GUIDE =
"https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#effects-lifecycle-and-cleanup";
const testCreateEffectRule: Codemod = async (root) => {
const filename = root.relativeFilename().replaceAll("\\", "/");
- const guidance = analyzeCreateEffect(root.root(), { filename });
+ const { guidance, report } = analyzeCreateEffect(root.root(), { filename });
const isFixture = root.source().includes('from "solid-js"') ||
root.source().includes('from "solid\\x2djs"');
@@ -78,6 +79,24 @@ const testCreateEffectRule: Codemod = async (root) => {
);
}
+
+ for (const [index, finding] of report.findings.entries()) {
+ if ("guidance" in finding || !finding.summary || !finding.reason || finding.nextSteps.length === 0 || !finding.officialGuideUrl) {
+ throw new Error("createEffect report must expose structured guidance fields only");
+ }
+ if (formatCreateEffectGuidance(finding) !== guidance[index]) throw new Error("createEffect terminal guidance drifted from its report contract");
+ if (finding.snippet.matchStartLine !== finding.line || finding.snippet.matchEndLine < finding.snippet.matchStartLine || finding.snippet.matchEndLine > finding.snippet.endLine) {
+ throw new Error("snippet must carry the AST match line range");
+ }
+ if (!finding.snippet.text.includes("createEffect")) throw new Error("createEffect snippet must contain the matched call");
+ if (finding.snippet.startLine !== Math.max(1, finding.line - 1)) {
+ throw new Error(`snippet must start one complete line before ${finding.line}`);
+ }
+ if (finding.snippet.endLine < finding.line || finding.snippet.text.split("\n").length !== finding.snippet.endLine - finding.snippet.startLine + 1) {
+ throw new Error(`snippet must include the full match and complete line bounds at ${finding.line}`);
+ }
+ }
+
return null;
};
diff --git a/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/create-effect.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/create-effect.ts
new file mode 100644
index 0000000..0571eba
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/create-effect.ts
@@ -0,0 +1,64 @@
+import type { SgNode } from "codemod:ast-grep";
+import type TSX from "codemod:ast-grep/langs/tsx";
+import { findImportedCalls, sourceSnippet } from "../../../../shared/analysis.ts";
+import type { AnalysisRuleResult } from "../../../../shared/report.ts";
+import { formatCreateEffectGuidance, type CreateEffectContent, type CreateEffectFinding, type CreateEffectReport } from "./report.ts";
+
+const MIGRATION_GUIDE = "https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#effects-lifecycle-and-cleanup";
+const MANUAL_VALIDATION = "Make and validate this migration yourself; this analyzer never edits or runs the target project.";
+
+export function analyzeCreateEffect(rootNode: SgNode, context: { filename: string }): AnalysisRuleResult {
+ const findings: CreateEffectFinding[] = findImportedCalls(rootNode, ["solid-js", "solid-js/web"], "createEffect")
+ .filter(({ argumentNodes }) => !argumentNodes.some((a) => a.kind() === "spread_element"))
+ .map(({ call, argumentNodes, filename }) => {
+ const start = call.range().start;
+ const argumentCount = argumentNodes.length;
+ return {
+ filename, line: start.line + 1, column: start.column + 1, argumentCount,
+ ...contentForArgumentCount(argumentCount), snippet: sourceSnippet(call),
+ };
+ });
+ return { guidance: findings.map(formatCreateEffectGuidance), report: { findings } };
+}
+
+function contentForArgumentCount(argumentCount: number): CreateEffectContent {
+ if (argumentCount === 0) return {
+ summary: "Manual review required: migrate this zero-argument createEffect call.",
+ reason: "Solid 2 requires separate compute and apply callbacks; a zero-argument call is likely a stub or placeholder.",
+ nextSteps: ["Add explicit compute and apply callbacks, or remove the empty call if it is dead code.", MANUAL_VALIDATION],
+ cautions: [], validation: [], officialGuideUrl: MIGRATION_GUIDE,
+ };
+ if (argumentCount === 1) return {
+ summary: "Manual review required: split this one-argument createEffect into compute and apply callbacks.",
+ reason: "Solid 2 requires separate compute and apply callbacks; the correct split depends on which reads are reactive inputs and which statements are side effects.",
+ nextSteps: [
+ "Read the full callback, imports, and nearby reactive declarations.",
+ "Identify the reactive reads that should trigger the effect, move those reads into the compute callback, return the value the side effect needs, and perform the imperative operation in the apply callback without adding reactive dependencies.",
+ MANUAL_VALIDATION,
+ ],
+ cautions: ["Stop without proposing a rewrite when the effect contains cleanup, async work, nested control flow affecting reads, reactive primitive creation, unrelated operations, writes that may affect its own inputs, or unclear intent."],
+ validation: ["Ask for the smallest focused test or runtime observation that makes the missing behavior decision observable."], officialGuideUrl: MIGRATION_GUIDE,
+ };
+ if (argumentCount === 2) return {
+ summary: "Manual review required: migrate this two-argument createEffect call.",
+ reason: "Solid 2 treats the second argument as options, not an initial value. If this call's second argument is an initial value, it must move to a default parameter of the compute callback. If it is already an apply callback for the split-effect pattern, confirm compatibility with Solid 2 option handling.",
+ nextSteps: [
+ "Read both arguments.",
+ "If the second argument is a non-function initial value or a function that represents the legacy initialValue parameter, move it to a default parameter on the compute callback: createEffect((prev = INITIAL_VALUE) => compute(prev), applyFn).",
+ "If the second argument is already the apply function for a manually split effect, verify that no Solid 1.x initialValue semantic is accidentally carried forward and that the call expects the second argument to be options in Solid 2.",
+ MANUAL_VALIDATION,
+ ],
+ cautions: ["Stop without proposing a rewrite when the intent of the second argument is unclear, the callback contains cleanup or async work, or the distinction between initialValue and apply callback cannot be resolved from static context."],
+ validation: ["Ask for the smallest focused test or runtime observation that makes the second-argument intent observable."], officialGuideUrl: MIGRATION_GUIDE,
+ };
+ return {
+ summary: `Manual review required: migrate this ${argumentCount}-argument createEffect call.`,
+ reason: "Solid 2 treats the second argument as options, not an initial value, and does not support a third positional argument. Legacy three-argument calls (callback, initialValue, options) must be restructured.",
+ nextSteps: [
+ "Move the initial value to a default parameter on the compute callback, and move any legacy options that have Solid 2 equivalents into the second-argument options object.",
+ "Verify that every legacy option is either mapped to a Solid 2 equivalent or intentionally dropped.", MANUAL_VALIDATION,
+ ],
+ cautions: ["Stop without proposing a rewrite when the intent of each argument is unclear."],
+ validation: ["Ask for the smallest focused test or runtime observation that makes the argument roles observable."], officialGuideUrl: MIGRATION_GUIDE,
+ };
+}
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-effect/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/create-effect/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-effect/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/create-effect/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/report.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/report.ts
new file mode 100644
index 0000000..c862f6f
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/report.ts
@@ -0,0 +1,26 @@
+import { defineRuleReportAggregator, type SourceSnippet } from "../../../../shared/report.ts";
+export const CREATE_EFFECT_RULE_ID = "analysis/reactivity/create-effect";
+export const CREATE_EFFECT_RULE_ROUTE = "reactivity/create-effect";
+export type CreateEffectFinding = {
+ readonly filename: string; readonly line: number; readonly column: number;
+ readonly argumentCount: number;
+ readonly summary: string; readonly reason: string;
+ readonly nextSteps: readonly string[]; readonly cautions: readonly string[];
+ readonly validation: readonly string[]; readonly officialGuideUrl: string;
+ readonly snippet: SourceSnippet;
+};
+export type CreateEffectReport = { readonly findings: readonly CreateEffectFinding[] };
+export type CreateEffectContent = Pick;
+export function formatCreateEffectGuidance(finding: CreateEffectFinding): string {
+ return `${finding.filename}:${finding.line}:${finding.column} ${finding.summary}
+Why: ${finding.reason}
+Guidance: ${[...finding.nextSteps, ...finding.cautions, ...finding.validation].join(" ")} Official migration guide: ${finding.officialGuideUrl}`;
+}
+export const createEffectReportAggregator = defineRuleReportAggregator({
+ id: CREATE_EFFECT_RULE_ID, emptyReport: () => ({ findings: [] }),
+ merge: (projectReport, nextReport) => ({ findings: [...projectReport.findings, ...nextReport.findings].sort(compareFindings) }),
+});
+function compareFindings(left: CreateEffectFinding, right: CreateEffectFinding): number {
+ const filenameOrder = left.filename < right.filename ? -1 : left.filename > right.filename ? 1 : 0;
+ return filenameOrder || left.line - right.line || left.column - right.column;
+}
diff --git a/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/ui.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/ui.tsx
new file mode 100644
index 0000000..7212515
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/ui.tsx
@@ -0,0 +1,39 @@
+import { GuidanceSections, RuleFindings } from "../../../../dashboard/finding-ui.tsx";
+import { formatFindingLocation } from "../../../../dashboard/finding-model.ts";
+import { defineRuleSlice } from "../../../../shared/report.ts";
+import {
+ CREATE_EFFECT_RULE_ID,
+ CREATE_EFFECT_RULE_ROUTE,
+ type CreateEffectReport,
+} from "./report.ts";
+
+export const createEffectSlice = defineRuleSlice({
+ id: CREATE_EFFECT_RULE_ID,
+ route: CREATE_EFFECT_RULE_ROUTE,
+ title: "createEffect calls",
+ domain: "Reactivity",
+ kind: "analysis",
+ Summary: (props) =>
{props.report.findings.length} createEffect calls need review
,
+ Detail: (props) => (
+ ({
+ filename: finding.filename,
+ location: formatFindingLocation(finding.filename, finding.line, finding.column),
+ label: `${finding.argumentCount} positional ${finding.argumentCount === 1 ? "argument" : "arguments"}`,
+ snippet: finding.snippet,
+ editorTarget: {
+ analyzedTargetRoot: props.run.analyzedTargetRoot,
+ filename: finding.filename,
+ line: finding.line,
+ column: finding.column,
+ },
+ details: ,
+ }))} />
+ ),
+});
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-memo/create-memo.test.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-memo/create-memo.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/create-memo/create-memo.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-memo/create-memo.test.ts
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-memo/create-memo.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-memo/create-memo.ts
similarity index 97%
rename from codemods/solid-migration-assistant/rules/reactivity/create-memo/create-memo.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-memo/create-memo.ts
index c3256da..1dcea44 100644
--- a/codemods/solid-migration-assistant/rules/reactivity/create-memo/create-memo.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-memo/create-memo.ts
@@ -1,6 +1,6 @@
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
+import { findImportedCalls } from "../../../../shared/analysis.ts";
const MIGRATION_GUIDE =
"https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#effects-lifecycle-and-cleanup";
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-memo/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-memo/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/create-memo/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-memo/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-memo/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-memo/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/create-memo/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-memo/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-resource/create-resource.test.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-resource/create-resource.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/create-resource/create-resource.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-resource/create-resource.test.ts
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-resource/create-resource.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-resource/create-resource.ts
similarity index 98%
rename from codemods/solid-migration-assistant/rules/reactivity/create-resource/create-resource.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-resource/create-resource.ts
index 6cd49cd..6d60c44 100644
--- a/codemods/solid-migration-assistant/rules/reactivity/create-resource/create-resource.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-resource/create-resource.ts
@@ -1,6 +1,6 @@
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
+import { findImportedCalls } from "../../../../shared/analysis.ts";
const MIGRATION_GUIDE =
"https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#createresource--async-computations--loading";
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-resource/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-resource/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/create-resource/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-resource/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-resource/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-resource/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/create-resource/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-resource/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-selector/create-selector.test.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-selector/create-selector.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/create-selector/create-selector.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-selector/create-selector.test.ts
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-selector/create-selector.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-selector/create-selector.ts
similarity index 95%
rename from codemods/solid-migration-assistant/rules/reactivity/create-selector/create-selector.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-selector/create-selector.ts
index f36555e..c3b643c 100644
--- a/codemods/solid-migration-assistant/rules/reactivity/create-selector/create-selector.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-selector/create-selector.ts
@@ -1,6 +1,6 @@
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
+import { findImportedCalls } from "../../../../shared/analysis.ts";
const GUIDE = "https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md";
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-selector/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-selector/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/create-selector/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-selector/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-selector/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-selector/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/create-selector/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-selector/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/error-handling/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/error-handling/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/error-handling/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/error-handling/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/error-handling/error-handling.test.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/error-handling/error-handling.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/error-handling/error-handling.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/error-handling/error-handling.test.ts
diff --git a/codemods/solid-migration-assistant/rules/reactivity/error-handling/error-handling.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/error-handling/error-handling.ts
similarity index 98%
rename from codemods/solid-migration-assistant/rules/reactivity/error-handling/error-handling.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/error-handling/error-handling.ts
index 0470f34..84535a1 100644
--- a/codemods/solid-migration-assistant/rules/reactivity/error-handling/error-handling.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/error-handling/error-handling.ts
@@ -1,6 +1,6 @@
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
+import { findImportedCalls } from "../../../../shared/analysis.ts";
const MIGRATION_GUIDE =
"https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#onerror--catcherror--errored--effect-error-option";
diff --git a/codemods/solid-migration-assistant/rules/reactivity/error-handling/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/error-handling/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/error-handling/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/error-handling/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/from-observable/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/from-observable/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/from-observable/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/from-observable/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/from-observable/from-observable.test.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/from-observable/from-observable.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/from-observable/from-observable.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/from-observable/from-observable.test.ts
diff --git a/codemods/solid-migration-assistant/rules/reactivity/from-observable/from-observable.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/from-observable/from-observable.ts
similarity index 96%
rename from codemods/solid-migration-assistant/rules/reactivity/from-observable/from-observable.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/from-observable/from-observable.ts
index 07f5a6e..08a39f6 100644
--- a/codemods/solid-migration-assistant/rules/reactivity/from-observable/from-observable.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/from-observable/from-observable.ts
@@ -1,6 +1,6 @@
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
+import { findImportedCalls } from "../../../../shared/analysis.ts";
const GUIDE = "https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md";
diff --git a/codemods/solid-migration-assistant/rules/reactivity/from-observable/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/from-observable/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/from-observable/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/from-observable/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/index-array/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/index-array/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/index-array/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/index-array/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/index-array/index-array.test.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/index-array/index-array.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/index-array/index-array.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/index-array/index-array.test.ts
diff --git a/codemods/solid-migration-assistant/rules/reactivity/index-array/index-array.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/index-array/index-array.ts
similarity index 94%
rename from codemods/solid-migration-assistant/rules/reactivity/index-array/index-array.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/index-array/index-array.ts
index 76387ef..7302f09 100644
--- a/codemods/solid-migration-assistant/rules/reactivity/index-array/index-array.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/index-array/index-array.ts
@@ -1,6 +1,6 @@
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
+import { findImportedCalls } from "../../../../shared/analysis.ts";
const GUIDE = "https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md";
diff --git a/codemods/solid-migration-assistant/rules/reactivity/index-array/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/index-array/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/index-array/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/index-array/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/on-helper/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/on-helper/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/on-helper/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/on-helper/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/on-helper/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/on-helper/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/on-helper/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/on-helper/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/on-helper/on-helper.test.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/on-helper/on-helper.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/on-helper/on-helper.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/on-helper/on-helper.test.ts
diff --git a/codemods/solid-migration-assistant/rules/reactivity/on-helper/on-helper.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/on-helper/on-helper.ts
similarity index 95%
rename from codemods/solid-migration-assistant/rules/reactivity/on-helper/on-helper.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/on-helper/on-helper.ts
index 9878634..6cbeaba 100644
--- a/codemods/solid-migration-assistant/rules/reactivity/on-helper/on-helper.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/on-helper/on-helper.ts
@@ -1,6 +1,6 @@
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
+import { findImportedCalls } from "../../../../shared/analysis.ts";
const MIGRATION_GUIDE =
"https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#on-helper--split-effects";
diff --git a/codemods/solid-migration-assistant/rules/reactivity/transition-apis/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/transition-apis/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/transition-apis/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/transition-apis/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/transition-apis/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/transition-apis/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/transition-apis/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/transition-apis/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/transition-apis/transition-apis.test.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/transition-apis/transition-apis.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/transition-apis/transition-apis.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/transition-apis/transition-apis.test.ts
diff --git a/codemods/solid-migration-assistant/rules/reactivity/transition-apis/transition-apis.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/transition-apis/transition-apis.ts
similarity index 97%
rename from codemods/solid-migration-assistant/rules/reactivity/transition-apis/transition-apis.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/transition-apis/transition-apis.ts
index 0b6c652..9808a4c 100644
--- a/codemods/solid-migration-assistant/rules/reactivity/transition-apis/transition-apis.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/transition-apis/transition-apis.ts
@@ -1,6 +1,6 @@
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
+import { findImportedCalls } from "../../../../shared/analysis.ts";
const GUIDE =
"https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md";
diff --git a/codemods/solid-migration-assistant/rules/reactivity/utility-renames/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/utility-renames/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/utility-renames/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/utility-renames/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/utility-renames/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/reactivity/utility-renames/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/utility-renames/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/utility-renames/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/reactivity/utility-renames/utility-renames.test.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/utility-renames/utility-renames.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/reactivity/utility-renames/utility-renames.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/utility-renames/utility-renames.test.ts
diff --git a/codemods/solid-migration-assistant/rules/reactivity/utility-renames/utility-renames.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/utility-renames/utility-renames.ts
similarity index 97%
rename from codemods/solid-migration-assistant/rules/reactivity/utility-renames/utility-renames.ts
rename to codemods/solid-migration-assistant/rules/analysis/reactivity/utility-renames/utility-renames.ts
index ffe0e46..561cbcb 100644
--- a/codemods/solid-migration-assistant/rules/reactivity/utility-renames/utility-renames.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/utility-renames/utility-renames.ts
@@ -1,6 +1,6 @@
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
+import { findImportedCalls } from "../../../../shared/analysis.ts";
const GUIDE =
"https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md";
diff --git a/codemods/solid-migration-assistant/rules/store/mutable/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/store/mutable/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/store/mutable/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/store/mutable/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/store/mutable/mutable.test.ts b/codemods/solid-migration-assistant/rules/analysis/store/mutable/mutable.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/store/mutable/mutable.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/store/mutable/mutable.test.ts
diff --git a/codemods/solid-migration-assistant/rules/store/mutable/mutable.ts b/codemods/solid-migration-assistant/rules/analysis/store/mutable/mutable.ts
similarity index 98%
rename from codemods/solid-migration-assistant/rules/store/mutable/mutable.ts
rename to codemods/solid-migration-assistant/rules/analysis/store/mutable/mutable.ts
index 64f241e..f671a13 100644
--- a/codemods/solid-migration-assistant/rules/store/mutable/mutable.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/store/mutable/mutable.ts
@@ -1,6 +1,6 @@
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
+import { findImportedCalls } from "../../../../shared/analysis.ts";
const MIGRATION_GUIDE =
"https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#createmutable--modifymutable--createstore-with-draft-setters";
diff --git a/codemods/solid-migration-assistant/rules/store/mutable/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/store/mutable/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/store/mutable/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/store/mutable/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/store/produce/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/store/produce/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/store/produce/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/store/produce/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/store/produce/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/store/produce/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/store/produce/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/store/produce/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/store/produce/produce.test.ts b/codemods/solid-migration-assistant/rules/analysis/store/produce/produce.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/store/produce/produce.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/store/produce/produce.test.ts
diff --git a/codemods/solid-migration-assistant/rules/store/produce/produce.ts b/codemods/solid-migration-assistant/rules/analysis/store/produce/produce.ts
similarity index 96%
rename from codemods/solid-migration-assistant/rules/store/produce/produce.ts
rename to codemods/solid-migration-assistant/rules/analysis/store/produce/produce.ts
index 2ab3741..9e21134 100644
--- a/codemods/solid-migration-assistant/rules/store/produce/produce.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/store/produce/produce.ts
@@ -1,6 +1,6 @@
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
+import { findImportedCalls } from "../../../../shared/analysis.ts";
const MIGRATION_GUIDE =
"https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#produce--now-the-default-setter-behavior";
diff --git a/codemods/solid-migration-assistant/rules/store/unwrap/direct-call.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/store/unwrap/direct-call.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/store/unwrap/direct-call.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/store/unwrap/direct-call.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/store/unwrap/non-solid.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/store/unwrap/non-solid.fixture.tsx
similarity index 100%
rename from codemods/solid-migration-assistant/rules/store/unwrap/non-solid.fixture.tsx
rename to codemods/solid-migration-assistant/rules/analysis/store/unwrap/non-solid.fixture.tsx
diff --git a/codemods/solid-migration-assistant/rules/store/unwrap/unwrap.test.ts b/codemods/solid-migration-assistant/rules/analysis/store/unwrap/unwrap.test.ts
similarity index 100%
rename from codemods/solid-migration-assistant/rules/store/unwrap/unwrap.test.ts
rename to codemods/solid-migration-assistant/rules/analysis/store/unwrap/unwrap.test.ts
diff --git a/codemods/solid-migration-assistant/rules/store/unwrap/unwrap.ts b/codemods/solid-migration-assistant/rules/analysis/store/unwrap/unwrap.ts
similarity index 96%
rename from codemods/solid-migration-assistant/rules/store/unwrap/unwrap.ts
rename to codemods/solid-migration-assistant/rules/analysis/store/unwrap/unwrap.ts
index d528ea7..bbb5573 100644
--- a/codemods/solid-migration-assistant/rules/store/unwrap/unwrap.ts
+++ b/codemods/solid-migration-assistant/rules/analysis/store/unwrap/unwrap.ts
@@ -1,6 +1,6 @@
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
+import { findImportedCalls } from "../../../../shared/analysis.ts";
const MIGRATION_GUIDE =
"https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#unwrapstore--snapshotstore";
diff --git a/codemods/solid-migration-assistant/rules/imports/web-import/web-import.ts b/codemods/solid-migration-assistant/rules/imports/web-import/web-import.ts
deleted file mode 100644
index d4e4657..0000000
--- a/codemods/solid-migration-assistant/rules/imports/web-import/web-import.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import type { SgNode } from "codemod:ast-grep";
-import type TSX from "codemod:ast-grep/langs/tsx";
-import { findModuleReferences } from "../../../shared/analysis.ts";
-import type { ModuleReference } from "../../../shared/analysis.ts";
-
-const LEGACY_WEB_MODULE = "solid-js/web";
-const MIGRATION_GUIDE =
- "https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#imports-where-things-live-now";
-
-const FORM_LABELS: Record = {
- import: "static import",
- "re-export": "re-export",
- "dynamic-import": "dynamic import()",
- require: "require() call",
-};
-
-const FORM_GUIDANCE: Record = {
- import:
- " Change only this static import's module source to @solidjs/web and preserve its import form and quote style.",
- "re-export":
- " Change only this re-export's module source to @solidjs/web and preserve its export form and quote style.",
- "dynamic-import":
- " Change only this dynamic import's module source to @solidjs/web and preserve its quote style.",
- require:
- " Change only this require call's module source to @solidjs/web and preserve its quote style.",
-};
-
-export function analyzeWebImport(
- rootNode: SgNode,
- context: { filename: string },
-): string[] {
- return findModuleReferences(rootNode)
- .filter((ref) => ref.moduleName === LEGACY_WEB_MODULE)
- .map(({ source, form }) => {
- const start = source.range().start;
- return `${context.filename}:${start.line + 1}:${start.column + 1} Move this Solid web renderer ${FORM_LABELS[form]}.
-Why: Solid 2 publishes the web renderer from @solidjs/web instead of the solid-js/web subpath.
-Guidance:${FORM_GUIDANCE[form]} Make and validate that edit yourself; this analyzer never edits or runs the target project. Official migration guide: ${MIGRATION_GUIDE}`;
- });
-}
\ No newline at end of file
diff --git a/codemods/solid-migration-assistant/rules/jsx/component-renames/component-renames.ts b/codemods/solid-migration-assistant/rules/jsx/component-renames/component-renames.ts
deleted file mode 100644
index 8df6403..0000000
--- a/codemods/solid-migration-assistant/rules/jsx/component-renames/component-renames.ts
+++ /dev/null
@@ -1,128 +0,0 @@
-import type { SgNode } from "codemod:ast-grep";
-import type TSX from "codemod:ast-grep/langs/tsx";
-import { stringLiteralValue } from "../../../shared/analysis.ts";
-
-const SUSPENSE_BOUNDARY_GUIDE =
- "https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#suspense--errorboundary--loading--errored";
-const INDEX_GUIDE =
- "https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#list-rendering-index-is-gone-and-for-handles-each-keying-mode";
-const SUSPENSE_LIST_GUIDE =
- "https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#coordinating-loading-boundaries-suspenselist--reveal";
-
-const COMPONENT_MIGRATIONS = {
- Suspense: "Loading",
- ErrorBoundary: "Errored",
- SuspenseList: "Reveal",
- Index: "For",
-} as const;
-
-type LegacyComponent = keyof typeof COMPONENT_MIGRATIONS;
-
-type ComponentSite = {
- element: SgNode;
- filename: string;
- legacyName: LegacyComponent;
-};
-
-export function analyzeJsxComponentRenames(
- rootNode: SgNode,
- context: { filename: string },
-): string[] {
- return findImportedComponentSites(rootNode)
- .sort((left, right) => {
- const leftStart = left.element.range().start;
- const rightStart = right.element.range().start;
- const filenameOrder = left.filename.localeCompare(right.filename);
- if (filenameOrder !== 0) return filenameOrder;
- if (leftStart.line !== rightStart.line) {
- return leftStart.line - rightStart.line;
- }
- return leftStart.column - rightStart.column;
- })
- .map(({ element, filename, legacyName }) =>
- componentGuidance(element, filename, legacyName),
- );
-}
-
-function findImportedComponentSites(rootNode: SgNode): ComponentSite[] {
- const sites = new Map();
-
- for (const statement of rootNode.findAll({
- rule: { kind: "import_statement" },
- })) {
- const source = statement.children().find((child) => child.is("string"));
- if (!source || stringLiteralValue(source) !== "solid-js") continue;
-
- for (const specifier of statement.findAll({
- rule: { kind: "import_specifier" },
- })) {
- const legacyName = importedLegacyName(specifier);
- if (!legacyName) continue;
-
- const identifiers = specifier.findAll({ rule: { kind: "identifier" } });
- const binding = identifiers[0];
- if (!binding || identifiers.length !== 1) continue;
-
- for (const fileReferences of binding.references()) {
- const filename = fileReferences.root
- .relativeFilename()
- .replaceAll("\\", "/");
- for (const reference of fileReferences.nodes) {
- const element = reference.parent();
- if (
- !element ||
- (element.kind() !== "jsx_opening_element" &&
- element.kind() !== "jsx_self_closing_element") ||
- element.field("name")?.id() !== reference.id()
- ) {
- continue;
- }
- sites.set(`${filename}:${element.id()}`, {
- element,
- filename,
- legacyName,
- });
- }
- }
- }
- }
-
- return [...sites.values()];
-}
-
-function importedLegacyName(specifier: SgNode): LegacyComponent | null {
- const text = specifier.text().trim();
- return text in COMPONENT_MIGRATIONS ? (text as LegacyComponent) : null;
-}
-
-function componentGuidance(
- element: SgNode,
- filename: string,
- legacyName: LegacyComponent,
-): string {
- const start = element.range().start;
- const location = `${filename}:${start.line + 1}:${start.column + 1}`;
-
- if (legacyName === "Suspense") {
- return `${location} Manual review required: migrate this imported Suspense JSX site to Loading.
-Why: Solid 2 replaces the solid-js Suspense component with Loading for initial not-ready fallback UI.
-Guidance: Read this complete boundary, its fallback, children, props, and corresponding import. Replace the unaliased named Suspense import and this JSX component with Loading only after confirming that the boundary owns initial not-ready UI and that its fallback and children preserve their rendering behavior. Make and validate this migration yourself; this analyzer never edits or runs the target project. Stop without proposing a rewrite when props are spread or forwarded, fallback ownership or evaluation is indirect, nested async boundaries make the intended initial-loading behavior unclear, or focused rendering tests do not cover the fallback and ready states. Ask for the smallest focused test or runtime observation that exposes both states. Official migration guide: ${SUSPENSE_BOUNDARY_GUIDE}`;
- }
-
- if (legacyName === "ErrorBoundary") {
- return `${location} Manual review required: migrate this imported ErrorBoundary JSX site to Errored.
-Why: Solid 2 replaces the solid-js ErrorBoundary component with Errored, whose fallback receives an error accessor rather than a raw error value.
-Guidance: Read this complete boundary, its fallback, children, props, and corresponding import. Replace the unaliased named ErrorBoundary import and this JSX component with Errored only after updating every fallback use to read the error accessor, such as err(), while preserving error ownership and recovery behavior. Make and validate this migration yourself; this analyzer never edits or runs the target project. Stop without proposing a rewrite when props are spread or forwarded, the fallback is indirect or escapes, the error value is passed to unknown code, reset or recovery behavior is unclear, or focused tests do not cover thrown and recovered states. Ask for the smallest focused test or runtime observation that exposes the fallback value and recovery behavior. Official migration guide: ${SUSPENSE_BOUNDARY_GUIDE}`;
- }
-
- if (legacyName === "Index") {
- return `${location} Manual review required: migrate this imported Index JSX site to For keyed={false}.
-Why: Solid 2 removes Index; its direct replacement is For with keyed={false}, whose child callback receives an item accessor and a stable numeric index.
-Guidance: Read the complete list site, its each value, child callback, props, and corresponding import. Replace the unaliased named Index import and this JSX component with For, add the literal keyed={false} mode, and review the callback so the item remains an accessor and the index remains a stable number. Make and validate this migration yourself; this analyzer never edits or runs the target project. Stop without proposing a rewrite when props are spread or forwarded, each or the child callback is indirect, callback parameters escape to unknown code, item identity or index behavior is unclear, or focused list-update tests do not prove state preservation. Ask for the smallest focused test or runtime observation that covers insertion, removal, reordering, and item updates. Official migration guide: ${INDEX_GUIDE}`;
- }
-
- return `${location} Manual review required: migrate this imported SuspenseList JSX site to Reveal.
-Why: Solid 2 replaces SuspenseList with Reveal for coordinating sibling Loading boundaries and replaces revealOrder and tail controls with order and collapsed semantics.
-Guidance: Read the complete group, its revealOrder and tail values, children, nesting, props, and corresponding import. Replace the unaliased named SuspenseList import and this JSX component with Reveal only after mapping literal revealOrder="forwards" to the default or order="sequential", revealOrder="together" to order="together", and tail="collapsed" to collapsed only under sequential order; review the children as sibling Loading boundaries, and do not use the earlier-beta boolean together prop. Make and validate this migration yourself; this analyzer never edits or runs the target project. Stop without proposing a rewrite when props are spread or forwarded, revealOrder or tail is dynamic or has another value, child boundary ownership or nesting is unclear, intended reveal timing cannot be established, or focused behavior tests do not cover the coordinated states. Ask for the smallest focused test or runtime observation that exposes ordering, fallback, and collapsed-tail behavior. Official migration guide: ${SUSPENSE_LIST_GUIDE}`;
-}
-
diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-effect/create-effect.ts b/codemods/solid-migration-assistant/rules/reactivity/create-effect/create-effect.ts
deleted file mode 100644
index db25717..0000000
--- a/codemods/solid-migration-assistant/rules/reactivity/create-effect/create-effect.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import type { SgNode } from "codemod:ast-grep";
-import type TSX from "codemod:ast-grep/langs/tsx";
-import { findImportedCalls } from "../../../shared/analysis.ts";
-
-const MIGRATION_GUIDE =
- "https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#effects-lifecycle-and-cleanup";
-
-export function analyzeCreateEffect(
- rootNode: SgNode,
- context: { filename: string },
-): string[] {
- return findImportedCalls(rootNode, ["solid-js", "solid-js/web"], "createEffect")
- .filter(
- ({ argumentNodes }) =>
- !argumentNodes.some((a) => a.kind() === "spread_element"),
- )
- .map(({ call, argumentNodes, filename }) => {
- const start = call.range().start;
- const argCount = argumentNodes.length;
-
- if (argCount === 0) {
- return `${filename}:${start.line + 1}:${start.column + 1} Manual review required: migrate this zero-argument createEffect call.
-Why: Solid 2 requires separate compute and apply callbacks; a zero-argument call is likely a stub or placeholder.
-Guidance: Add explicit compute and apply callbacks, or remove the empty call if it is dead code. Make and validate this migration yourself; this analyzer never edits or runs the target project. Official migration guide: ${MIGRATION_GUIDE}`;
- }
-
- if (argCount === 1) {
- return `${filename}:${start.line + 1}:${start.column + 1} Manual review required: split this one-argument createEffect into compute and apply callbacks.
-Why: Solid 2 requires separate compute and apply callbacks; the correct split depends on which reads are reactive inputs and which statements are side effects.
-Guidance: Read the full callback, imports, and nearby reactive declarations. Identify the reactive reads that should trigger the effect, move those reads into the compute callback, return the value the side effect needs, and perform the imperative operation in the apply callback without adding reactive dependencies. Make and validate this migration yourself; this analyzer never edits or runs the target project. Stop without proposing a rewrite when the effect contains cleanup, async work, nested control flow affecting reads, reactive primitive creation, unrelated operations, writes that may affect its own inputs, or unclear intent. Ask for the smallest focused test or runtime observation that makes the missing behavior decision observable. Official migration guide: ${MIGRATION_GUIDE}`;
- }
-
- if (argCount === 2) {
- return `${filename}:${start.line + 1}:${start.column + 1} Manual review required: migrate this two-argument createEffect call.
-Why: Solid 2 treats the second argument as options, not an initial value. If this call's second argument is an initial value, it must move to a default parameter of the compute callback. If it is already an apply callback for the split-effect pattern, confirm compatibility with Solid 2 option handling.
-Guidance: Read both arguments. If the second argument is a non-function initial value or a function that represents the legacy initialValue parameter, move it to a default parameter on the compute callback: createEffect((prev = INITIAL_VALUE) => compute(prev), applyFn). If the second argument is already the apply function for a manually split effect, verify that no Solid 1.x initialValue semantic is accidentally carried forward and that the call expects the second argument to be options in Solid 2. Make and validate this migration yourself; this analyzer never edits or runs the target project. Stop without proposing a rewrite when the intent of the second argument is unclear, the callback contains cleanup or async work, or the distinction between initialValue and apply callback cannot be resolved from static context. Ask for the smallest focused test or runtime observation that makes the second-argument intent observable. Official migration guide: ${MIGRATION_GUIDE}`;
- }
-
- // argCount >= 3
- return `${filename}:${start.line + 1}:${start.column + 1} Manual review required: migrate this ${argCount}-argument createEffect call.
-Why: Solid 2 treats the second argument as options, not an initial value, and does not support a third positional argument. Legacy three-argument calls (callback, initialValue, options) must be restructured.
-Guidance: Move the initial value to a default parameter on the compute callback, and move any legacy options that have Solid 2 equivalents into the second-argument options object. Verify that every legacy option is either mapped to a Solid 2 equivalent or intentionally dropped. Make and validate this migration yourself; this analyzer never edits or runs the target project. Stop without proposing a rewrite when the intent of each argument is unclear. Ask for the smallest focused test or runtime observation that makes the argument roles observable. Official migration guide: ${MIGRATION_GUIDE}`;
- });
-}
diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/destructured-require.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/destructured-require.fixture.tsx
new file mode 100644
index 0000000..7b424e8
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/destructured-require.fixture.tsx
@@ -0,0 +1,12 @@
+// fixture: destructured-require
+const [require] = [null];
+require("solid-js/h");
+
+function takesArray([require]) {
+ return require("solid-js/html");
+}
+
+const { require = null } = {};
+require("solid-js/universal");
+
+void takesArray;
diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/escaped-specifiers.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/escaped-specifiers.fixture.tsx
new file mode 100644
index 0000000..9b09327
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/escaped-specifiers.fixture.tsx
@@ -0,0 +1,13 @@
+// fixture: escaped-specifiers
+import { html as escapedHex } from "solid-js\x2fhtml";
+import { createRenderer as escapedUnicode } from "solid-js\u002funiversal";
+import { jsx as escapedCodePoint } from "solid-js\u{2f}jsx-runtime";
+import { jsxDEV as continued } from "solid-js/\
+jsx-dev-runtime";
+import { nearStore as escapedNearMiss } from "solid-js\\x2fstore";
+
+void escapedHex;
+void escapedUnicode;
+void escapedCodePoint;
+void continued;
+void escapedNearMiss;
diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/negative-forms.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/negative-forms.fixture.tsx
new file mode 100644
index 0000000..af55412
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/negative-forms.fixture.tsx
@@ -0,0 +1,23 @@
+// fixture: negative-forms
+import { nearH as prefixNearMiss } from "solid-js/h-extra";
+import { nearH as suffixNearMiss } from "vendor/solid-js/h";
+import { nearH as trailingSlashNearMiss } from "solid-js/h/";
+import { nearStore as escapedNearMiss } from "solid-js\\x2fstore";
+import { createStore } from "solid-js/store";
+import { render } from "solid-js/web";
+import migratedH from "@solidjs/h";
+export default "solid-js/h";
+const viaResolve = require.resolve("solid-js/html");
+const viaTemplate = import(`solid-js/universal`);
+const url = new URL("solid-js/jsx-runtime", import.meta.url);
+
+void prefixNearMiss;
+void suffixNearMiss;
+void trailingSlashNearMiss;
+void escapedNearMiss;
+void createStore;
+void render;
+void migratedH;
+void viaResolve;
+void viaTemplate;
+void url;
diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/no-matches.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/no-matches.fixture.tsx
new file mode 100644
index 0000000..feb5176
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/no-matches.fixture.tsx
@@ -0,0 +1,2 @@
+// fixture: no-matches
+export {};
diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/prototype-names.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/prototype-names.fixture.tsx
new file mode 100644
index 0000000..2d8e888
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/prototype-names.fixture.tsx
@@ -0,0 +1,14 @@
+// fixture: prototype-names
+import constructorModule from "constructor";
+import toStringModule from "toString";
+import hasOwnPropertyModule from "hasOwnProperty";
+import valueOfModule from "valueOf";
+import protoModule from "__proto__";
+import h from "solid-js/h";
+
+void constructorModule;
+void toStringModule;
+void hasOwnPropertyModule;
+void valueOfModule;
+void protoModule;
+void h;
diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/re-exports.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/re-exports.fixture.tsx
new file mode 100644
index 0000000..337cc64
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/re-exports.fixture.tsx
@@ -0,0 +1,7 @@
+// fixture: re-exports
+export { h } from "solid-js/h";
+export * from "solid-js/html";
+export { default as htmlDefault } from "solid-js/html";
+export { createRenderer as renderer, createUniversal as universal } from "solid-js/universal";
+export type { JSX } from "solid-js/jsx-runtime";
+export { jsxDEV } from "solid-js/jsx-dev-runtime";
diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/relocations.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/relocations.fixture.tsx
new file mode 100644
index 0000000..bfce02b
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/relocations.fixture.tsx
@@ -0,0 +1,63 @@
+// fixture: relocations
+import h from "solid-js/h";
+import html from "solid-js/html";
+import { createRenderer } from "solid-js/universal";
+import { jsx } from "solid-js/jsx-runtime";
+import { jsxDEV } from "solid-js/jsx-dev-runtime";
+// prettier-ignore
+import { html as singleHtml } from 'solid-js/html';
+// prettier-ignore
+import { createRenderer as singleRenderer } from 'solid-js/universal';
+import { html as escapedHex } from "solid-js\x2fhtml";
+import { createRenderer as escapedUnicode } from "solid-js\u002funiversal";
+import { jsx as escapedCodePoint } from "solid-js\u{2f}jsx-runtime";
+import { jsxDEV as continued } from "solid-js/\
+jsx-dev-runtime";
+
+export { h as reexportedH } from "solid-js/h";
+export * from "solid-js/html";
+
+const dynamicHtml = import("solid-js/html");
+declare const require: (name: string) => unknown;
+const commonJsUniversal = require("solid-js/universal");
+type RuntimeTypes = import("solid-js/jsx-runtime").JSX;
+
+// Out-of-scope and already-migrated references that must remain byte-identical.
+import { createStore as storeIsOutOfScope } from "solid-js/store";
+import { render as webIsOutOfScope } from "solid-js/web";
+import migratedH from "@solidjs/h";
+import migratedHtml from "@solidjs/html";
+import { createRenderer as migratedUniversal } from "@solidjs/universal";
+import { jsx as migratedRuntime } from "@solidjs/web/jsx-runtime";
+import { jsxDEV as migratedDevRuntime } from "@solidjs/web/jsx-dev-runtime";
+import { nearH as prefixNearMiss } from "solid-js/h-extra";
+import { nearH as suffixNearMiss } from "vendor/solid-js/h";
+import { nearH as trailingSlashNearMiss } from "solid-js/h/";
+import { nearStore as escapedNearMiss } from "solid-js\\x2fstore";
+
+void h;
+void html;
+void createRenderer;
+void jsx;
+void jsxDEV;
+void singleHtml;
+void singleRenderer;
+void escapedHex;
+void escapedUnicode;
+void escapedCodePoint;
+void continued;
+void reexportedH;
+void dynamicHtml;
+void commonJsUniversal;
+void storeIsOutOfScope;
+void webIsOutOfScope;
+void migratedH;
+void migratedHtml;
+void migratedUniversal;
+void migratedRuntime;
+void migratedDevRuntime;
+void prefixNearMiss;
+void suffixNearMiss;
+void trailingSlashNearMiss;
+void escapedNearMiss;
+type _RuntimeTypes = RuntimeTypes;
diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/runtime-forms.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/runtime-forms.fixture.tsx
new file mode 100644
index 0000000..1a9ae3a
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/runtime-forms.fixture.tsx
@@ -0,0 +1,28 @@
+// fixture: runtime-forms
+const dynamicH = import("solid-js/h");
+
+async function load() {
+ const dynamicHtml = await import("solid-js/html");
+ return dynamicHtml;
+}
+
+declare const require: (name: string) => unknown;
+const requiredUniversal = require("solid-js/universal");
+type RuntimeTypes = import("solid-js/jsx-runtime").JSX;
+type DevTypes = typeof import("solid-js/jsx-dev-runtime");
+
+import { require as aliasRequire } from "./helper";
+function usesDefault(a = require) {
+ return a;
+}
+const { a = require } = { a: null };
+const localAlias = require;
+
+void dynamicH;
+void load;
+void requiredUniversal;
+void aliasRequire;
+void usesDefault;
+void localAlias;
+type _RuntimeTypes = RuntimeTypes;
+type _DevTypes = DevTypes;
diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/shadowed-require.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/shadowed-require.fixture.tsx
new file mode 100644
index 0000000..0e8cbe4
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/shadowed-require.fixture.tsx
@@ -0,0 +1,17 @@
+// fixture: shadowed-require
+function require(name: string): unknown {
+ return name;
+}
+const localFunction = require("solid-js/h");
+
+const localVariable = (() => {
+ const require = (name: string) => name;
+ return require("solid-js/html");
+})();
+
+const localParameter = ((require: (name: string) => unknown) =>
+ require("solid-js/universal"))((name: string) => name);
+
+void localFunction;
+void localVariable;
+void localParameter;
diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/single-quotes.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/single-quotes.fixture.tsx
new file mode 100644
index 0000000..132cae7
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/single-quotes.fixture.tsx
@@ -0,0 +1,22 @@
+// fixture: single-quotes
+import h from 'solid-js/h';
+import { render } from 'solid-js/html';
+import { createRenderer } from 'solid-js/universal';
+import { jsx } from 'solid-js/jsx-runtime';
+import { jsxDEV } from 'solid-js/jsx-dev-runtime';
+export { h as exportedH } from 'solid-js/h';
+export * from 'solid-js/html';
+const dynamicHtml = import('solid-js/html');
+declare const require: (name: string) => unknown;
+const commonJsUniversal = require('solid-js/universal');
+type RuntimeTypes = import('solid-js/jsx-runtime').JSX;
+
+void h;
+void render;
+void createRenderer;
+void jsx;
+void jsxDEV;
+void exportedH;
+void dynamicHtml;
+void commonJsUniversal;
+type _RuntimeTypes = RuntimeTypes;
diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/static-imports.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/static-imports.fixture.tsx
new file mode 100644
index 0000000..1b127c0
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/static-imports.fixture.tsx
@@ -0,0 +1,17 @@
+// fixture: static-imports
+import h from "solid-js/h";
+import html, { render } from "solid-js/html";
+import { createRenderer as renderer } from "solid-js/universal";
+import { jsx, jsxs } from "solid-js/jsx-runtime";
+import { jsxDEV as dev } from "solid-js/jsx-dev-runtime";
+import * as hNamespace from "solid-js/h";
+import "solid-js/jsx-runtime";
+
+void h;
+void html;
+void render;
+void renderer;
+void jsx;
+void jsxs;
+void dev;
+void hNamespace;
diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/legacy-subpath-relocation.test.ts b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/legacy-subpath-relocation.test.ts
new file mode 100644
index 0000000..26bf819
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/legacy-subpath-relocation.test.ts
@@ -0,0 +1,180 @@
+import type { Codemod } from "codemod:ast-grep";
+import type TSX from "codemod:ast-grep/langs/tsx";
+import {
+ relocateLegacySubpaths,
+ TRANSFORM_MIGRATION_GUIDE,
+} from "./legacy-subpath-relocation.ts";
+import { formatLegacySubpathRelocationGuidance } from "./report.ts";
+
+type FixtureCase = {
+ relocations: ReadonlyArray<{
+ location: string;
+ from: string;
+ to: string;
+ }>;
+ /** Exact post-transform file text, or null when the file must stay unchanged. */
+ transformed: string | null;
+};
+
+const FIXTURE_CASES: Record = {
+ "relocations": {
+ relocations: [
+ { location: "2:15", from: "solid-js/h", to: "@solidjs/h" },
+ { location: "3:18", from: "solid-js/html", to: "@solidjs/html" },
+ { location: "4:32", from: "solid-js/universal", to: "@solidjs/universal" },
+ { location: "5:21", from: "solid-js/jsx-runtime", to: "@solidjs/web/jsx-runtime" },
+ { location: "6:24", from: "solid-js/jsx-dev-runtime", to: "@solidjs/web/jsx-dev-runtime" },
+ { location: "8:36", from: "solid-js/html", to: "@solidjs/html" },
+ { location: "10:50", from: "solid-js/universal", to: "@solidjs/universal" },
+ { location: "11:36", from: "solid-js/html", to: "@solidjs/html" },
+ { location: "12:50", from: "solid-js/universal", to: "@solidjs/universal" },
+ { location: "13:41", from: "solid-js/jsx-runtime", to: "@solidjs/web/jsx-runtime" },
+ { location: "14:37", from: "solid-js/jsx-dev-runtime", to: "@solidjs/web/jsx-dev-runtime" },
+ { location: "17:34", from: "solid-js/h", to: "@solidjs/h" },
+ { location: "18:15", from: "solid-js/html", to: "@solidjs/html" },
+ { location: "20:28", from: "solid-js/html", to: "@solidjs/html" },
+ { location: "22:35", from: "solid-js/universal", to: "@solidjs/universal" },
+ { location: "23:28", from: "solid-js/jsx-runtime", to: "@solidjs/web/jsx-runtime" },
+ ],
+ transformed: "// fixture: relocations\nimport h from \"@solidjs/h\";\nimport html from \"@solidjs/html\";\nimport { createRenderer } from \"@solidjs/universal\";\nimport { jsx } from \"@solidjs/web/jsx-runtime\";\nimport { jsxDEV } from \"@solidjs/web/jsx-dev-runtime\";\n// prettier-ignore\nimport { html as singleHtml } from '@solidjs/html';\n// prettier-ignore\nimport { createRenderer as singleRenderer } from '@solidjs/universal';\nimport { html as escapedHex } from \"@solidjs/html\";\nimport { createRenderer as escapedUnicode } from \"@solidjs/universal\";\nimport { jsx as escapedCodePoint } from \"@solidjs/web/jsx-runtime\";\nimport { jsxDEV as continued } from \"@solidjs/web/jsx-dev-runtime\";\n\nexport { h as reexportedH } from \"@solidjs/h\";\nexport * from \"@solidjs/html\";\n\nconst dynamicHtml = import(\"@solidjs/html\");\ndeclare const require: (name: string) => unknown;\nconst commonJsUniversal = require(\"@solidjs/universal\");\ntype RuntimeTypes = import(\"@solidjs/web/jsx-runtime\").JSX;\n\n// Out-of-scope and already-migrated references that must remain byte-identical.\nimport { createStore as storeIsOutOfScope } from \"solid-js/store\";\nimport { render as webIsOutOfScope } from \"solid-js/web\";\nimport migratedH from \"@solidjs/h\";\nimport migratedHtml from \"@solidjs/html\";\nimport { createRenderer as migratedUniversal } from \"@solidjs/universal\";\nimport { jsx as migratedRuntime } from \"@solidjs/web/jsx-runtime\";\nimport { jsxDEV as migratedDevRuntime } from \"@solidjs/web/jsx-dev-runtime\";\nimport { nearH as prefixNearMiss } from \"solid-js/h-extra\";\nimport { nearH as suffixNearMiss } from \"vendor/solid-js/h\";\nimport { nearH as trailingSlashNearMiss } from \"solid-js/h/\";\nimport { nearStore as escapedNearMiss } from \"solid-js\\\\x2fstore\";\n\nvoid h;\nvoid html;\nvoid createRenderer;\nvoid jsx;\nvoid jsxDEV;\nvoid singleHtml;\nvoid singleRenderer;\nvoid escapedHex;\nvoid escapedUnicode;\nvoid escapedCodePoint;\nvoid continued;\nvoid reexportedH;\nvoid dynamicHtml;\nvoid commonJsUniversal;\nvoid storeIsOutOfScope;\nvoid webIsOutOfScope;\nvoid migratedH;\nvoid migratedHtml;\nvoid migratedUniversal;\nvoid migratedRuntime;\nvoid migratedDevRuntime;\nvoid prefixNearMiss;\nvoid suffixNearMiss;\nvoid trailingSlashNearMiss;\nvoid escapedNearMiss;\ntype _RuntimeTypes = RuntimeTypes;\n",
+ },
+ "static-imports": {
+ relocations: [
+ { location: "2:15", from: "solid-js/h", to: "@solidjs/h" },
+ { location: "3:30", from: "solid-js/html", to: "@solidjs/html" },
+ { location: "4:44", from: "solid-js/universal", to: "@solidjs/universal" },
+ { location: "5:27", from: "solid-js/jsx-runtime", to: "@solidjs/web/jsx-runtime" },
+ { location: "6:31", from: "solid-js/jsx-dev-runtime", to: "@solidjs/web/jsx-dev-runtime" },
+ { location: "7:29", from: "solid-js/h", to: "@solidjs/h" },
+ { location: "8:8", from: "solid-js/jsx-runtime", to: "@solidjs/web/jsx-runtime" },
+ ],
+ transformed: "// fixture: static-imports\nimport h from \"@solidjs/h\";\nimport html, { render } from \"@solidjs/html\";\nimport { createRenderer as renderer } from \"@solidjs/universal\";\nimport { jsx, jsxs } from \"@solidjs/web/jsx-runtime\";\nimport { jsxDEV as dev } from \"@solidjs/web/jsx-dev-runtime\";\nimport * as hNamespace from \"@solidjs/h\";\nimport \"@solidjs/web/jsx-runtime\";\n\nvoid h;\nvoid html;\nvoid render;\nvoid renderer;\nvoid jsx;\nvoid jsxs;\nvoid dev;\nvoid hNamespace;\n",
+ },
+ "single-quotes": {
+ relocations: [
+ { location: "2:15", from: "solid-js/h", to: "@solidjs/h" },
+ { location: "3:24", from: "solid-js/html", to: "@solidjs/html" },
+ { location: "4:32", from: "solid-js/universal", to: "@solidjs/universal" },
+ { location: "5:21", from: "solid-js/jsx-runtime", to: "@solidjs/web/jsx-runtime" },
+ { location: "6:24", from: "solid-js/jsx-dev-runtime", to: "@solidjs/web/jsx-dev-runtime" },
+ { location: "7:32", from: "solid-js/h", to: "@solidjs/h" },
+ { location: "8:15", from: "solid-js/html", to: "@solidjs/html" },
+ { location: "9:28", from: "solid-js/html", to: "@solidjs/html" },
+ { location: "11:35", from: "solid-js/universal", to: "@solidjs/universal" },
+ { location: "12:28", from: "solid-js/jsx-runtime", to: "@solidjs/web/jsx-runtime" },
+ ],
+ transformed: "// fixture: single-quotes\nimport h from '@solidjs/h';\nimport { render } from '@solidjs/html';\nimport { createRenderer } from '@solidjs/universal';\nimport { jsx } from '@solidjs/web/jsx-runtime';\nimport { jsxDEV } from '@solidjs/web/jsx-dev-runtime';\nexport { h as exportedH } from '@solidjs/h';\nexport * from '@solidjs/html';\nconst dynamicHtml = import('@solidjs/html');\ndeclare const require: (name: string) => unknown;\nconst commonJsUniversal = require('@solidjs/universal');\ntype RuntimeTypes = import('@solidjs/web/jsx-runtime').JSX;\n\nvoid h;\nvoid render;\nvoid createRenderer;\nvoid jsx;\nvoid jsxDEV;\nvoid exportedH;\nvoid dynamicHtml;\nvoid commonJsUniversal;\ntype _RuntimeTypes = RuntimeTypes;\n",
+ },
+ "re-exports": {
+ relocations: [
+ { location: "2:19", from: "solid-js/h", to: "@solidjs/h" },
+ { location: "3:15", from: "solid-js/html", to: "@solidjs/html" },
+ { location: "4:40", from: "solid-js/html", to: "@solidjs/html" },
+ { location: "5:74", from: "solid-js/universal", to: "@solidjs/universal" },
+ { location: "6:26", from: "solid-js/jsx-runtime", to: "@solidjs/web/jsx-runtime" },
+ { location: "7:24", from: "solid-js/jsx-dev-runtime", to: "@solidjs/web/jsx-dev-runtime" },
+ ],
+ transformed: "// fixture: re-exports\nexport { h } from \"@solidjs/h\";\nexport * from \"@solidjs/html\";\nexport { default as htmlDefault } from \"@solidjs/html\";\nexport { createRenderer as renderer, createUniversal as universal } from \"@solidjs/universal\";\nexport type { JSX } from \"@solidjs/web/jsx-runtime\";\nexport { jsxDEV } from \"@solidjs/web/jsx-dev-runtime\";\n",
+ },
+ "runtime-forms": {
+ relocations: [
+ { location: "2:25", from: "solid-js/h", to: "@solidjs/h" },
+ { location: "5:36", from: "solid-js/html", to: "@solidjs/html" },
+ { location: "10:35", from: "solid-js/universal", to: "@solidjs/universal" },
+ { location: "11:28", from: "solid-js/jsx-runtime", to: "@solidjs/web/jsx-runtime" },
+ { location: "12:31", from: "solid-js/jsx-dev-runtime", to: "@solidjs/web/jsx-dev-runtime" },
+ ],
+ transformed: "// fixture: runtime-forms\nconst dynamicH = import(\"@solidjs/h\");\n\nasync function load() {\n const dynamicHtml = await import(\"@solidjs/html\");\n return dynamicHtml;\n}\n\ndeclare const require: (name: string) => unknown;\nconst requiredUniversal = require(\"@solidjs/universal\");\ntype RuntimeTypes = import(\"@solidjs/web/jsx-runtime\").JSX;\ntype DevTypes = typeof import(\"@solidjs/web/jsx-dev-runtime\");\n\nimport { require as aliasRequire } from \"./helper\";\nfunction usesDefault(a = require) {\n return a;\n}\nconst { a = require } = { a: null };\nconst localAlias = require;\n\nvoid dynamicH;\nvoid load;\nvoid requiredUniversal;\nvoid aliasRequire;\nvoid usesDefault;\nvoid localAlias;\ntype _RuntimeTypes = RuntimeTypes;\ntype _DevTypes = DevTypes;\n",
+ },
+ "negative-forms": {
+ relocations: [],
+ transformed: null,
+ },
+ "shadowed-require": {
+ relocations: [],
+ transformed: null,
+ },
+ "destructured-require": {
+ relocations: [],
+ transformed: null,
+ },
+ "prototype-names": {
+ relocations: [
+ { location: "7:15", from: "solid-js/h", to: "@solidjs/h" },
+ ],
+ transformed: "// fixture: prototype-names\nimport constructorModule from \"constructor\";\nimport toStringModule from \"toString\";\nimport hasOwnPropertyModule from \"hasOwnProperty\";\nimport valueOfModule from \"valueOf\";\nimport protoModule from \"__proto__\";\nimport h from \"@solidjs/h\";\n\nvoid constructorModule;\nvoid toStringModule;\nvoid hasOwnPropertyModule;\nvoid valueOfModule;\nvoid protoModule;\nvoid h;\n",
+ },
+ "escaped-specifiers": {
+ relocations: [
+ { location: "2:36", from: "solid-js/html", to: "@solidjs/html" },
+ { location: "3:50", from: "solid-js/universal", to: "@solidjs/universal" },
+ { location: "4:41", from: "solid-js/jsx-runtime", to: "@solidjs/web/jsx-runtime" },
+ { location: "5:37", from: "solid-js/jsx-dev-runtime", to: "@solidjs/web/jsx-dev-runtime" },
+ ],
+ transformed: "// fixture: escaped-specifiers\nimport { html as escapedHex } from \"@solidjs/html\";\nimport { createRenderer as escapedUnicode } from \"@solidjs/universal\";\nimport { jsx as escapedCodePoint } from \"@solidjs/web/jsx-runtime\";\nimport { jsxDEV as continued } from \"@solidjs/web/jsx-dev-runtime\";\nimport { nearStore as escapedNearMiss } from \"solid-js\\\\x2fstore\";\n\nvoid escapedHex;\nvoid escapedUnicode;\nvoid escapedCodePoint;\nvoid continued;\nvoid escapedNearMiss;\n",
+ },
+ "no-matches": {
+ relocations: [],
+ transformed: null,
+ },
+};
+
+const testLegacySubpathRelocation: Codemod = async (root) => {
+ const rootNode = root.root();
+ const filename = root.relativeFilename().replaceAll("\\", "/");
+ const source = root.source();
+ const marker = /\/\/ fixture: ([a-z0-9-]+)/.exec(source)?.[1];
+ if (!marker) {
+ throw new Error(
+ "fixture is missing a `// fixture: ` marker on its first line",
+ );
+ }
+
+ const fixtureCase = FIXTURE_CASES[marker];
+ if (!fixtureCase) {
+ throw new Error(`unknown fixture marker: ${marker}`);
+ }
+
+ const { edits, report } = relocateLegacySubpaths(rootNode, filename);
+
+ const expected = fixtureCase.relocations
+ .map(({ location, from, to }) =>
+ `${filename}:${location} Relocate ${from} to ${to}. Official migration guide: ${TRANSFORM_MIGRATION_GUIDE}`,
+ )
+ .sort();
+
+ if (report.findings.some((finding) => "guidance" in finding || !finding.summary || !finding.reason || finding.nextSteps.length === 0 || !finding.officialGuideUrl)) {
+ throw new Error("relocation report must expose structured guidance fields only");
+ }
+ if (report.findings.some((finding) => finding.snippet.matchStartLine !== finding.line || finding.snippet.matchEndLine < finding.snippet.matchStartLine || finding.snippet.matchEndLine > finding.snippet.endLine)) {
+ throw new Error("relocation snippets must carry AST match line ranges");
+ }
+ const actual = report.findings.map(formatLegacySubpathRelocationGuidance).sort();
+ if (actual.join("\n") !== expected.join("\n")) {
+ throw new Error(
+ `unexpected relocation report for ${marker}:\n${actual.join("\n")}\nExpected:\n${expected.join("\n")}`,
+ );
+ }
+
+ for (const finding of report.findings) {
+ if (!finding.snippet.text.includes("solid-js")) throw new Error("relocation snippet must contain the matched module");
+ if (finding.snippet.startLine !== Math.max(1, finding.line - 1)) {
+ throw new Error(`snippet must start one complete line before ${finding.line}`);
+ }
+ if (finding.snippet.endLine < finding.line || finding.snippet.text.split("\n").length !== finding.snippet.endLine - finding.snippet.startLine + 1) {
+ throw new Error(`snippet must include the full match and complete line bounds at ${finding.line}`);
+ }
+ }
+
+ const transformed = rootNode.commitEdits(edits);
+ const expectedTransformed = fixtureCase.transformed ?? source;
+ if (transformed !== expectedTransformed) {
+ throw new Error(
+ `unexpected transformed output for ${marker}:\n${transformed}\nExpected:\n${expectedTransformed}`,
+ );
+ }
+
+ return null;
+};
+
+export default testLegacySubpathRelocation;
diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/legacy-subpath-relocation.ts b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/legacy-subpath-relocation.ts
new file mode 100644
index 0000000..b6ffec8
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/legacy-subpath-relocation.ts
@@ -0,0 +1,209 @@
+import type { Edit, SgNode } from "codemod:ast-grep";
+import type TSX from "codemod:ast-grep/langs/tsx";
+import { findModuleReferences, sourceSnippet } from "../../../../shared/analysis.ts";
+import type { LegacySubpathRelocationFinding, LegacySubpathRelocationReport } from "./report.ts";
+
+export const SOLID_SOURCE_COMMIT =
+ "ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5";
+
+export const TRANSFORM_MIGRATION_GUIDE = `https://github.com/solidjs/solid/blob/${SOLID_SOURCE_COMMIT}/documentation/solid-2.0/MIGRATION.md#imports-where-things-live-now`;
+
+/**
+ * The deterministic, semantics-preserving subset of the Solid 2 import-path
+ * moves. Every entry is a pure package relocation: the module source changes
+ * while imported bindings, import form, and quote style are preserved. No
+ * entry here has a removed, renamed, or behaviorally changed export, so each
+ * rewrite is safe without binding-level review.
+ */
+export const LEGACY_SUBPATH_RELOCATIONS: Readonly> = {
+ "solid-js/h": "@solidjs/h",
+ "solid-js/html": "@solidjs/html",
+ "solid-js/universal": "@solidjs/universal",
+ "solid-js/jsx-runtime": "@solidjs/web/jsx-runtime",
+ "solid-js/jsx-dev-runtime": "@solidjs/web/jsx-dev-runtime",
+};
+
+export type LegacySubpathRelocationResult = {
+ readonly edits: Edit[];
+ readonly report: LegacySubpathRelocationReport;
+};
+
+/**
+ * Looks up a relocation target using an own-property check. A plain
+ * `LEGACY_SUBPATH_RELOCATIONS[moduleName]` read would traverse the object's
+ * prototype chain, so bare specifiers such as "constructor", "toString", or
+ * "__proto__" would resolve to inherited members and be rewritten into
+ * garbage. Those specifiers are ordinary module names and must stay intact.
+ */
+function relocationTarget(moduleName: string): string | undefined {
+ if (!Object.hasOwn(LEGACY_SUBPATH_RELOCATIONS, moduleName)) return undefined;
+ return LEGACY_SUBPATH_RELOCATIONS[moduleName];
+}
+
+/**
+ * Only the string that follows the `from` keyword is a re-export's module
+ * source. `export default "solid-js/h"` also places a string directly under
+ * `export_statement`, but that string is the exported value, not a module
+ * reference, so it must never be rewritten.
+ */
+function isReExportSource(source: SgNode): boolean {
+ const statement = source.parent();
+ if (!statement || statement.kind() !== "export_statement") return false;
+ return statement.children().some((child) => child.kind() === "from");
+}
+
+/**
+ * Kinds of syntax nodes that can directly hold a `require` binding. For the
+ * position-sensitive kinds, `isShadowBinding` narrows the match to the actual
+ * binding slot so adjacent references never count.
+ */
+const REQUIRE_BINDING_PARENT_KINDS = new Set([
+ "function_declaration",
+ "class_declaration",
+ "variable_declarator",
+ "required_parameter",
+ "optional_parameter",
+ "rest_pattern",
+ "array_pattern",
+ "object_pattern",
+ "pair_pattern",
+ "import_clause",
+ "namespace_import",
+ "import_specifier",
+ "catch_clause",
+ "assignment_pattern",
+ "object_assignment_pattern",
+]);
+
+function samePosition(left: SgNode, right: SgNode): boolean {
+ const a = left.range().start;
+ const b = right.range().start;
+ return a.line === b.line && a.column === b.column;
+}
+
+/**
+ * True when the identifier occupies the binding slot of its parent node and
+ * not merely a reference next to it. This keeps the scan precise in both
+ * directions: `import { require as r }`, `function f(a = require)`, and
+ * `const alias = require` are references (the local binding is `r`, `a`, and
+ * `alias`), while `const [require] = arr`, `const { require = 1 } = obj`, and
+ * `function g(require = 1)` all bind `require` and must suppress relocation
+ * of bare `require(...)` calls.
+ */
+function isShadowBinding(node: SgNode): boolean {
+ const parent = node.parent();
+ if (!parent || !REQUIRE_BINDING_PARENT_KINDS.has(parent.kind())) {
+ return false;
+ }
+ switch (parent.kind()) {
+ case "import_specifier": {
+ const alias = parent.field("alias");
+ return (
+ alias === null ||
+ alias.text() === "require" ||
+ samePosition(alias, node)
+ );
+ }
+ case "required_parameter":
+ case "optional_parameter": {
+ const pattern = parent.field("pattern");
+ return pattern !== null && samePosition(pattern, node);
+ }
+ case "assignment_pattern":
+ case "object_assignment_pattern": {
+ const left = parent.field("left");
+ return left !== null && samePosition(left, node);
+ }
+ case "pair_pattern": {
+ const value = parent.field("value");
+ return value !== null && samePosition(value, node);
+ }
+ case "function_declaration":
+ case "class_declaration":
+ case "variable_declarator": {
+ const name = parent.field("name");
+ return name !== null && samePosition(name, node);
+ }
+ default:
+ return true;
+ }
+}
+
+/**
+ * Bare `require(...)` calls are module references only when `require` is the
+ * ambient CommonJS require. The transform workflow has no semantic provider,
+ * so shadowing is detected syntactically: if the file declares its own
+ * `require` binding anywhere (function/class declaration, variable,
+ * destructuring pattern, parameter, import, catch parameter), every bare
+ * `require(...)` call in that file is conservatively left untouched.
+ * `declare const require` and `declare function require` describe the global
+ * require and are exempted. Binding slots are matched position-precisely, so
+ * `import { require as r }`, `function f(a = require)`, and
+ * `const alias = require` never count as shadows.
+ */
+function hasShadowingRequireBinding(rootNode: SgNode): boolean {
+ const kinds = [
+ "identifier",
+ "type_identifier",
+ "shorthand_property_identifier_pattern",
+ ] as const;
+ for (const kind of kinds) {
+ for (const node of rootNode.findAll({
+ rule: { kind, regex: "^require$" },
+ })) {
+ if (
+ node.ancestors().some(
+ (ancestor) => ancestor.kind() === "ambient_declaration",
+ )
+ ) {
+ continue;
+ }
+ if (isShadowBinding(node)) return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Maps every legacy Solid subpath module reference in `rootNode` to a
+ * deterministic edit plus a human-readable per-edit report. The caller
+ * commits the edits and persists the reports; this rule performs no I/O.
+ */
+export function relocateLegacySubpaths(
+ rootNode: SgNode,
+ filename: string,
+): LegacySubpathRelocationResult {
+ const shadowedRequire = hasShadowingRequireBinding(rootNode);
+ const relocations = findModuleReferences(rootNode)
+ .map(({ source, moduleName, form }) => {
+ const replacement = relocationTarget(moduleName);
+ if (replacement === undefined) return null;
+ if (form === "re-export" && !isReExportSource(source)) return null;
+ if (form === "require" && shadowedRequire) return null;
+ const start = source.range().start;
+ const quote = source.text()[0];
+ const finding: LegacySubpathRelocationFinding = {
+ filename,
+ line: start.line + 1,
+ column: start.column + 1,
+ form,
+ sourceModule: moduleName,
+ replacementModule: replacement,
+ summary: `Relocate ${moduleName} to ${replacement}.`,
+ reason: "Solid 2 publishes this runtime from a dedicated package instead of the legacy solid-js subpath.",
+ nextSteps: ["Replace only the module source and preserve the import form, bindings, and quote style."],
+ cautions: ["Do not combine this deterministic path relocation with removed or renamed binding migrations."],
+ validation: ["Run the target project's focused typecheck and tests after applying the proposed edit."],
+ officialGuideUrl: TRANSFORM_MIGRATION_GUIDE,
+ snippet: sourceSnippet(source),
+ };
+ return { edit: source.replace(`${quote}${replacement}${quote}`), finding };
+ })
+ .filter(
+ (entry): entry is { edit: Edit; finding: LegacySubpathRelocationFinding } => entry !== null,
+ );
+ return {
+ edits: relocations.map(({ edit }) => edit),
+ report: { findings: relocations.map(({ finding }) => finding) },
+ };
+}
diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/report.ts b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/report.ts
new file mode 100644
index 0000000..4abc508
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/report.ts
@@ -0,0 +1,24 @@
+import { defineRuleReportAggregator, type SourceSnippet } from "../../../../shared/report.ts";
+export const LEGACY_SUBPATH_RELOCATION_RULE_ID = "transformation/imports/legacy-subpath-relocation";
+export const LEGACY_SUBPATH_RELOCATION_RULE_ROUTE = "imports/legacy-subpath-relocation";
+export type RelocationForm = "import" | "re-export" | "dynamic-import" | "require";
+export type LegacySubpathRelocationFinding = {
+ readonly filename: string; readonly line: number; readonly column: number;
+ readonly form: RelocationForm; readonly sourceModule: string; readonly replacementModule: string;
+ readonly summary: string; readonly reason: string;
+ readonly nextSteps: readonly string[]; readonly cautions: readonly string[];
+ readonly validation: readonly string[]; readonly officialGuideUrl: string;
+ readonly snippet: SourceSnippet;
+};
+export type LegacySubpathRelocationReport = { readonly findings: readonly LegacySubpathRelocationFinding[] };
+export function formatLegacySubpathRelocationGuidance(finding: LegacySubpathRelocationFinding): string {
+ return `${finding.filename}:${finding.line}:${finding.column} ${finding.summary} Official migration guide: ${finding.officialGuideUrl}`;
+}
+export const legacySubpathRelocationReportAggregator = defineRuleReportAggregator({
+ id: LEGACY_SUBPATH_RELOCATION_RULE_ID, emptyReport: () => ({ findings: [] }),
+ merge: (projectReport, nextReport) => ({ findings: [...projectReport.findings, ...nextReport.findings].sort(compareFindings) }),
+});
+function compareFindings(left: LegacySubpathRelocationFinding, right: LegacySubpathRelocationFinding): number {
+ const filenameOrder = left.filename < right.filename ? -1 : left.filename > right.filename ? 1 : 0;
+ return filenameOrder || left.line - right.line || left.column - right.column;
+}
diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/ui.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/ui.tsx
new file mode 100644
index 0000000..311907e
--- /dev/null
+++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/ui.tsx
@@ -0,0 +1,43 @@
+import { GuidanceSections, RuleFindings } from "../../../../dashboard/finding-ui.tsx";
+import { formatFindingLocation } from "../../../../dashboard/finding-model.ts";
+import { defineRuleSlice } from "../../../../shared/report.ts";
+import {
+ LEGACY_SUBPATH_RELOCATION_RULE_ID,
+ LEGACY_SUBPATH_RELOCATION_RULE_ROUTE,
+ type LegacySubpathRelocationReport,
+} from "./report.ts";
+
+export const legacySubpathRelocationSlice =
+ defineRuleSlice({
+ id: LEGACY_SUBPATH_RELOCATION_RULE_ID,
+ route: LEGACY_SUBPATH_RELOCATION_RULE_ROUTE,
+ title: "Legacy subpath relocations",
+ domain: "Imports",
+ kind: "transformation",
+ Summary: (props) =>