From f4d7dc1a362e60e772ae1b85972f327d1bec7912 Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Sat, 15 Aug 2026 18:12:55 -0500 Subject: [PATCH 01/16] feat: add deterministic transform workflow and shared report emitter - Organize read-only analysis rules under rules/analysis/ (imports, jsx, lifecycle, props, reactivity, store) - Add transform workflow: deterministic relocation of the five pure legacy Solid subpaths (h, html, universal, jsx-runtime, jsx-dev-runtime) with per-edit reports and end-to-end fixture/expected trees - Consolidate emit steps into one shared scripts/emit-report.ts used by both the analyze and transform workflows (guidance vs relocation report) - Extend architecture, packaging, rule, and workflow tests for the two workflow layout and the new transform rule adapter --- README.md | 30 +- codemods/solid-migration-assistant/README.md | 28 +- .../solid-migration-assistant/codemod.yaml | 7 +- .../solid-migration-assistant/package.json | 75 ++--- .../beta32-subpaths/beta32-subpaths.test.ts | 2 +- .../beta32-subpaths/beta32-subpaths.ts | 6 +- .../static-imports.fixture.tsx | 0 .../web-import/static-imports.fixture.tsx | 0 .../imports/web-import/web-import.test.ts | 0 .../imports/web-import/web-import.ts | 4 +- .../jsx/class-list/class-list.test.ts | 0 .../jsx/class-list/class-list.ts | 0 .../jsx/class-list/normal.fixture.tsx | 0 .../component-renames.test.ts | 0 .../component-renames/component-renames.ts | 2 +- .../jsx/component-renames/normal.fixture.tsx | 0 .../context-provider/context-provider.test.ts | 0 .../jsx/context-provider/context-provider.ts | 0 .../jsx/context-provider/normal.fixture.tsx | 0 .../dom-attr-namespaces.test.ts | 0 .../dom-attr-namespaces.ts | 0 .../dom-attr-namespaces/normal.fixture.tsx | 0 .../dom-event-namespaces.test.ts | 0 .../dom-event-namespaces.ts | 0 .../dom-event-namespaces/normal.fixture.tsx | 0 .../dom-use-directive.test.ts | 0 .../dom-use-directive/dom-use-directive.ts | 0 .../jsx/dom-use-directive/normal.fixture.tsx | 0 .../on-cleanup/direct-call.fixture.tsx | 0 .../on-cleanup/non-solid.fixture.tsx | 0 .../lifecycle/on-cleanup/on-cleanup.test.ts | 0 .../lifecycle/on-cleanup/on-cleanup.ts | 2 +- .../on-mount/direct-call.fixture.tsx | 0 .../lifecycle/on-mount/non-solid.fixture.tsx | 0 .../lifecycle/on-mount/on-mount.test.ts | 0 .../lifecycle/on-mount/on-mount.ts | 2 +- .../props/merge-props/direct-call.fixture.tsx | 0 .../props/merge-props/merge-props.test.ts | 0 .../props/merge-props/merge-props.ts | 2 +- .../props/merge-props/non-solid.fixture.tsx | 0 .../props/split-props/direct-call.fixture.tsx | 0 .../props/split-props/non-solid.fixture.tsx | 0 .../props/split-props/split-props.test.ts | 0 .../props/split-props/split-props.ts | 2 +- .../reactivity/batch/batch.test.ts | 0 .../{ => analysis}/reactivity/batch/batch.ts | 2 +- .../reactivity/batch/direct-call.fixture.tsx | 0 .../reactivity/batch/non-solid.fixture.tsx | 0 .../create-computed/create-computed.test.ts | 0 .../create-computed/create-computed.ts | 2 +- .../create-computed/direct-call.fixture.tsx | 0 .../create-computed/non-solid.fixture.tsx | 0 .../create-dynamic/create-dynamic.test.ts | 0 .../create-dynamic/create-dynamic.ts | 2 +- .../create-dynamic/direct-call.fixture.tsx | 0 .../create-dynamic/non-solid.fixture.tsx | 0 .../create-effect/create-effect.test.ts | 0 .../reactivity/create-effect/create-effect.ts | 2 +- .../create-effect/direct-call.fixture.tsx | 0 .../create-effect/non-solid.fixture.tsx | 0 .../create-memo/create-memo.test.ts | 0 .../reactivity/create-memo/create-memo.ts | 2 +- .../create-memo/direct-call.fixture.tsx | 0 .../create-memo/non-solid.fixture.tsx | 0 .../create-resource/create-resource.test.ts | 0 .../create-resource/create-resource.ts | 2 +- .../create-resource/direct-call.fixture.tsx | 0 .../create-resource/non-solid.fixture.tsx | 0 .../create-selector/create-selector.test.ts | 0 .../create-selector/create-selector.ts | 2 +- .../create-selector/direct-call.fixture.tsx | 0 .../create-selector/non-solid.fixture.tsx | 0 .../error-handling/direct-call.fixture.tsx | 0 .../error-handling/error-handling.test.ts | 0 .../error-handling/error-handling.ts | 2 +- .../error-handling/non-solid.fixture.tsx | 0 .../from-observable/direct-call.fixture.tsx | 0 .../from-observable/from-observable.test.ts | 0 .../from-observable/from-observable.ts | 2 +- .../from-observable/non-solid.fixture.tsx | 0 .../index-array/direct-call.fixture.tsx | 0 .../index-array/index-array.test.ts | 0 .../reactivity/index-array/index-array.ts | 2 +- .../index-array/non-solid.fixture.tsx | 0 .../on-helper/direct-call.fixture.tsx | 0 .../on-helper/non-solid.fixture.tsx | 0 .../reactivity/on-helper/on-helper.test.ts | 0 .../reactivity/on-helper/on-helper.ts | 2 +- .../transition-apis/direct-call.fixture.tsx | 0 .../transition-apis/non-solid.fixture.tsx | 0 .../transition-apis/transition-apis.test.ts | 0 .../transition-apis/transition-apis.ts | 2 +- .../utility-renames/direct-call.fixture.tsx | 0 .../utility-renames/non-solid.fixture.tsx | 0 .../utility-renames/utility-renames.test.ts | 0 .../utility-renames/utility-renames.ts | 2 +- .../store/mutable/direct-call.fixture.tsx | 0 .../store/mutable/mutable.test.ts | 0 .../{ => analysis}/store/mutable/mutable.ts | 2 +- .../store/mutable/non-solid.fixture.tsx | 0 .../store/produce/direct-call.fixture.tsx | 0 .../store/produce/non-solid.fixture.tsx | 0 .../store/produce/produce.test.ts | 0 .../{ => analysis}/store/produce/produce.ts | 2 +- .../store/unwrap/direct-call.fixture.tsx | 0 .../store/unwrap/non-solid.fixture.tsx | 0 .../store/unwrap/unwrap.test.ts | 0 .../{ => analysis}/store/unwrap/unwrap.ts | 2 +- .../legacy-subpath-relocation.test.ts | 132 +++++++++ .../legacy-subpath-relocation.ts | 53 ++++ .../relocations.fixture.tsx | 62 ++++ .../scripts/analyze.ts | 56 ++-- .../scripts/emit-report.ts | 36 +++ .../solid-migration-assistant/scripts/emit.ts | 28 -- .../scripts/transform.ts | 27 ++ .../shared/transform.ts | 7 + .../tests/architecture.test.mjs | 270 ++++++++++++------ .../tests/packaging.test.mjs | 70 ++--- .../tests/rules.test.mjs | 58 ++-- .../tests/transform-expected/package.json | 6 + .../transform-expected/src/relocations.tsx | 61 ++++ .../tests/transform-expected/src/untouched.ts | 8 + .../tests/transform-fixture/package.json | 6 + .../transform-fixture/src/relocations.tsx | 62 ++++ .../tests/transform-fixture/src/untouched.ts | 8 + .../tests/transform-rules.test.mjs | 155 ++++++++++ .../tests/transform.test.mjs | 157 ++++++++++ .../tests/workflow.test.mjs | 2 +- .../solid-migration-assistant/transform.yaml | 42 +++ .../solid-migration-assistant/workflow.yaml | 2 +- 130 files changed, 1218 insertions(+), 284 deletions(-) rename codemods/solid-migration-assistant/rules/{ => analysis}/imports/beta32-subpaths/beta32-subpaths.test.ts (96%) rename codemods/solid-migration-assistant/rules/{ => analysis}/imports/beta32-subpaths/beta32-subpaths.ts (88%) rename codemods/solid-migration-assistant/rules/{ => analysis}/imports/beta32-subpaths/static-imports.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/imports/web-import/static-imports.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/imports/web-import/web-import.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/imports/web-import/web-import.ts (92%) rename codemods/solid-migration-assistant/rules/{ => analysis}/jsx/class-list/class-list.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/jsx/class-list/class-list.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/jsx/class-list/normal.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/jsx/component-renames/component-renames.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/jsx/component-renames/component-renames.ts (99%) rename codemods/solid-migration-assistant/rules/{ => analysis}/jsx/component-renames/normal.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/jsx/context-provider/context-provider.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/jsx/context-provider/context-provider.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/jsx/context-provider/normal.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/jsx/dom-attr-namespaces/dom-attr-namespaces.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/jsx/dom-attr-namespaces/dom-attr-namespaces.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/jsx/dom-attr-namespaces/normal.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/jsx/dom-event-namespaces/dom-event-namespaces.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/jsx/dom-event-namespaces/dom-event-namespaces.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/jsx/dom-event-namespaces/normal.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/jsx/dom-use-directive/dom-use-directive.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/jsx/dom-use-directive/dom-use-directive.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/jsx/dom-use-directive/normal.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/lifecycle/on-cleanup/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/lifecycle/on-cleanup/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/lifecycle/on-cleanup/on-cleanup.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/lifecycle/on-cleanup/on-cleanup.ts (96%) rename codemods/solid-migration-assistant/rules/{ => analysis}/lifecycle/on-mount/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/lifecycle/on-mount/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/lifecycle/on-mount/on-mount.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/lifecycle/on-mount/on-mount.ts (96%) rename codemods/solid-migration-assistant/rules/{ => analysis}/props/merge-props/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/props/merge-props/merge-props.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/props/merge-props/merge-props.ts (97%) rename codemods/solid-migration-assistant/rules/{ => analysis}/props/merge-props/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/props/split-props/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/props/split-props/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/props/split-props/split-props.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/props/split-props/split-props.ts (96%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/batch/batch.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/batch/batch.ts (95%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/batch/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/batch/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-computed/create-computed.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-computed/create-computed.ts (97%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-computed/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-computed/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-dynamic/create-dynamic.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-dynamic/create-dynamic.ts (95%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-dynamic/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-dynamic/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-effect/create-effect.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-effect/create-effect.ts (98%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-effect/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-effect/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-memo/create-memo.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-memo/create-memo.ts (97%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-memo/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-memo/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-resource/create-resource.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-resource/create-resource.ts (98%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-resource/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-resource/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-selector/create-selector.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-selector/create-selector.ts (95%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-selector/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/create-selector/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/error-handling/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/error-handling/error-handling.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/error-handling/error-handling.ts (98%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/error-handling/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/from-observable/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/from-observable/from-observable.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/from-observable/from-observable.ts (96%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/from-observable/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/index-array/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/index-array/index-array.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/index-array/index-array.ts (94%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/index-array/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/on-helper/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/on-helper/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/on-helper/on-helper.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/on-helper/on-helper.ts (95%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/transition-apis/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/transition-apis/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/transition-apis/transition-apis.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/transition-apis/transition-apis.ts (97%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/utility-renames/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/utility-renames/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/utility-renames/utility-renames.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/reactivity/utility-renames/utility-renames.ts (97%) rename codemods/solid-migration-assistant/rules/{ => analysis}/store/mutable/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/store/mutable/mutable.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/store/mutable/mutable.ts (98%) rename codemods/solid-migration-assistant/rules/{ => analysis}/store/mutable/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/store/produce/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/store/produce/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/store/produce/produce.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/store/produce/produce.ts (96%) rename codemods/solid-migration-assistant/rules/{ => analysis}/store/unwrap/direct-call.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/store/unwrap/non-solid.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/store/unwrap/unwrap.test.ts (100%) rename codemods/solid-migration-assistant/rules/{ => analysis}/store/unwrap/unwrap.ts (96%) create mode 100644 codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/legacy-subpath-relocation.test.ts create mode 100644 codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/legacy-subpath-relocation.ts create mode 100644 codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/relocations.fixture.tsx create mode 100644 codemods/solid-migration-assistant/scripts/emit-report.ts delete mode 100644 codemods/solid-migration-assistant/scripts/emit.ts create mode 100644 codemods/solid-migration-assistant/scripts/transform.ts create mode 100644 codemods/solid-migration-assistant/shared/transform.ts create mode 100644 codemods/solid-migration-assistant/tests/transform-expected/package.json create mode 100644 codemods/solid-migration-assistant/tests/transform-expected/src/relocations.tsx create mode 100644 codemods/solid-migration-assistant/tests/transform-expected/src/untouched.ts create mode 100644 codemods/solid-migration-assistant/tests/transform-fixture/package.json create mode 100644 codemods/solid-migration-assistant/tests/transform-fixture/src/relocations.tsx create mode 100644 codemods/solid-migration-assistant/tests/transform-fixture/src/untouched.ts create mode 100644 codemods/solid-migration-assistant/tests/transform-rules.test.mjs create mode 100644 codemods/solid-migration-assistant/tests/transform.test.mjs create mode 100644 codemods/solid-migration-assistant/transform.yaml diff --git a/README.md b/README.md index c1eadb1..665457c 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. -## 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): @@ -30,6 +30,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 +57,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..6ef9f96 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; 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): @@ -63,11 +63,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 +97,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/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/package.json b/codemods/solid-migration-assistant/package.json index 05955b2..2dd9a21 100644 --- a/codemods/solid-migration-assistant/package.json +++ b/codemods/solid-migration-assistant/package.json @@ -1,7 +1,7 @@ { "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", "license": "MIT", "author": "Solid Migration Assistant Contributors", "type": "module", @@ -12,38 +12,42 @@ "LICENSE", "README.md", "bin/solid-migration-assistant.mjs", - "rules/imports/beta32-subpaths/beta32-subpaths.ts", - "rules/imports/web-import/web-import.ts", - "rules/jsx/class-list/class-list.ts", - "rules/jsx/component-renames/component-renames.ts", - "rules/jsx/context-provider/context-provider.ts", - "rules/jsx/dom-attr-namespaces/dom-attr-namespaces.ts", - "rules/jsx/dom-event-namespaces/dom-event-namespaces.ts", - "rules/jsx/dom-use-directive/dom-use-directive.ts", - "rules/lifecycle/on-cleanup/on-cleanup.ts", - "rules/lifecycle/on-mount/on-mount.ts", - "rules/props/merge-props/merge-props.ts", - "rules/props/split-props/split-props.ts", - "rules/reactivity/batch/batch.ts", - "rules/reactivity/create-computed/create-computed.ts", - "rules/reactivity/create-dynamic/create-dynamic.ts", - "rules/reactivity/create-effect/create-effect.ts", - "rules/reactivity/create-memo/create-memo.ts", - "rules/reactivity/create-resource/create-resource.ts", - "rules/reactivity/create-selector/create-selector.ts", - "rules/reactivity/error-handling/error-handling.ts", - "rules/reactivity/from-observable/from-observable.ts", - "rules/reactivity/index-array/index-array.ts", - "rules/reactivity/on-helper/on-helper.ts", - "rules/reactivity/transition-apis/transition-apis.ts", - "rules/reactivity/utility-renames/utility-renames.ts", - "rules/store/mutable/mutable.ts", - "rules/store/produce/produce.ts", - "rules/store/unwrap/unwrap.ts", + "rules/analysis/imports/beta32-subpaths/beta32-subpaths.ts", + "rules/analysis/imports/web-import/web-import.ts", + "rules/analysis/jsx/class-list/class-list.ts", + "rules/analysis/jsx/component-renames/component-renames.ts", + "rules/analysis/jsx/context-provider/context-provider.ts", + "rules/analysis/jsx/dom-attr-namespaces/dom-attr-namespaces.ts", + "rules/analysis/jsx/dom-event-namespaces/dom-event-namespaces.ts", + "rules/analysis/jsx/dom-use-directive/dom-use-directive.ts", + "rules/analysis/lifecycle/on-cleanup/on-cleanup.ts", + "rules/analysis/lifecycle/on-mount/on-mount.ts", + "rules/analysis/props/merge-props/merge-props.ts", + "rules/analysis/props/split-props/split-props.ts", + "rules/analysis/reactivity/batch/batch.ts", + "rules/analysis/reactivity/create-computed/create-computed.ts", + "rules/analysis/reactivity/create-dynamic/create-dynamic.ts", + "rules/analysis/reactivity/create-effect/create-effect.ts", + "rules/analysis/reactivity/create-memo/create-memo.ts", + "rules/analysis/reactivity/create-resource/create-resource.ts", + "rules/analysis/reactivity/create-selector/create-selector.ts", + "rules/analysis/reactivity/error-handling/error-handling.ts", + "rules/analysis/reactivity/from-observable/from-observable.ts", + "rules/analysis/reactivity/index-array/index-array.ts", + "rules/analysis/reactivity/on-helper/on-helper.ts", + "rules/analysis/reactivity/transition-apis/transition-apis.ts", + "rules/analysis/reactivity/utility-renames/utility-renames.ts", + "rules/analysis/store/mutable/mutable.ts", + "rules/analysis/store/produce/produce.ts", + "rules/analysis/store/unwrap/unwrap.ts", + "rules/transformations/imports/legacy-subpath-relocation/legacy-subpath-relocation.ts", "scripts/analyze.ts", - "scripts/emit.ts", + "scripts/emit-report.ts", + "scripts/transform.ts", "shared/analysis.ts", "shared/run-workflow.mjs", + "shared/transform.ts", + "transform.yaml", "workflow.yaml" ], "repository": { @@ -75,14 +79,17 @@ "packageManager": "pnpm@11.20.0", "scripts": { "analyze": "node ./bin/solid-migration-assistant.mjs", - "test": "pnpm test:architecture && pnpm test:package && pnpm test:rules && pnpm test:workflow", + "test": "pnpm test:architecture && pnpm test:package && pnpm test:rules && pnpm test:transform-rules && pnpm test:workflow && pnpm test:transform", "test:architecture": "node --test ./tests/architecture.test.mjs", "test:package": "node --test ./tests/cli.test.mjs ./tests/packaging.test.mjs", "test:workflow": "node ./tests/workflow.test.mjs", "check-types": "tsc --noEmit", - "validate": "node ./node_modules/codemod/codemod --disable-analytics workflow validate -w workflow.yaml", + "validate": "node ./node_modules/codemod/codemod --disable-analytics workflow validate -w workflow.yaml && node ./node_modules/codemod/codemod --disable-analytics workflow validate -w transform.yaml", "verify": "pnpm test && pnpm check-types && pnpm validate", - "test:rules": "node ./tests/rules.test.mjs" + "test:rules": "node ./tests/rules.test.mjs", + "test:transform-rules": "node ./tests/transform-rules.test.mjs", + "transform": "node ./node_modules/codemod/codemod --disable-analytics workflow run -w transform.yaml --allow-dirty --no-interactive", + "test:transform": "node ./tests/transform.test.mjs" }, "dependencies": { "codemod": "1.12.13" diff --git a/codemods/solid-migration-assistant/rules/imports/beta32-subpaths/beta32-subpaths.test.ts b/codemods/solid-migration-assistant/rules/analysis/imports/beta32-subpaths/beta32-subpaths.test.ts similarity index 96% rename from codemods/solid-migration-assistant/rules/imports/beta32-subpaths/beta32-subpaths.test.ts rename to codemods/solid-migration-assistant/rules/analysis/imports/beta32-subpaths/beta32-subpaths.test.ts index 9758c79..7c1bf63 100644 --- a/codemods/solid-migration-assistant/rules/imports/beta32-subpaths/beta32-subpaths.test.ts +++ b/codemods/solid-migration-assistant/rules/analysis/imports/beta32-subpaths/beta32-subpaths.test.ts @@ -5,7 +5,7 @@ import { analyzeBeta32SubpathImports } from "./beta32-subpaths.ts"; const MIGRATION_GUIDE = "https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#imports-where-things-live-now"; const STORE_STOP_CONDITION = - "Stop: do not blindly rewrite this source if the import includes removed or renamed beta.32 helpers such as unwrap, produce, createMutable, or modifyMutable. Migrate those bindings and call sites first, then move supported store imports to solid-js."; + "Stop: do not blindly rewrite this source if the import includes removed or renamed helpers such as unwrap, produce, createMutable, or modifyMutable. Migrate those bindings and call sites first, then move supported store imports to solid-js."; const EXPECTED_SITES = [ // Static imports { location: "1:29", legacy: "solid-js/store", replacement: "solid-js", form: "static import" }, diff --git a/codemods/solid-migration-assistant/rules/imports/beta32-subpaths/beta32-subpaths.ts b/codemods/solid-migration-assistant/rules/analysis/imports/beta32-subpaths/beta32-subpaths.ts similarity index 88% rename from codemods/solid-migration-assistant/rules/imports/beta32-subpaths/beta32-subpaths.ts rename to codemods/solid-migration-assistant/rules/analysis/imports/beta32-subpaths/beta32-subpaths.ts index 482b4f2..9f65e25 100644 --- a/codemods/solid-migration-assistant/rules/imports/beta32-subpaths/beta32-subpaths.ts +++ b/codemods/solid-migration-assistant/rules/analysis/imports/beta32-subpaths/beta32-subpaths.ts @@ -1,7 +1,7 @@ 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"; +import { findModuleReferences } from "../../../../shared/analysis.ts"; +import type { ModuleReference } from "../../../../shared/analysis.ts"; const BETA32_SUBPATH_REPLACEMENTS: Readonly> = { "solid-js/store": "solid-js", @@ -14,7 +14,7 @@ const BETA32_SUBPATH_REPLACEMENTS: Readonly> = { const MIGRATION_GUIDE = "https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#imports-where-things-live-now"; const STORE_STOP_CONDITION = - "Stop: do not blindly rewrite this source if the import includes removed or renamed beta.32 helpers such as unwrap, produce, createMutable, or modifyMutable. Migrate those bindings and call sites first, then move supported store imports to solid-js."; + "Stop: do not blindly rewrite this source if the import includes removed or renamed helpers such as unwrap, produce, createMutable, or modifyMutable. Migrate those bindings and call sites first, then move supported store imports to solid-js."; const FORM_LABELS: Record = { import: "static import", diff --git a/codemods/solid-migration-assistant/rules/imports/beta32-subpaths/static-imports.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/imports/beta32-subpaths/static-imports.fixture.tsx similarity index 100% rename from codemods/solid-migration-assistant/rules/imports/beta32-subpaths/static-imports.fixture.tsx rename to codemods/solid-migration-assistant/rules/analysis/imports/beta32-subpaths/static-imports.fixture.tsx diff --git a/codemods/solid-migration-assistant/rules/imports/web-import/static-imports.fixture.tsx b/codemods/solid-migration-assistant/rules/analysis/imports/web-import/static-imports.fixture.tsx similarity index 100% rename from codemods/solid-migration-assistant/rules/imports/web-import/static-imports.fixture.tsx rename to codemods/solid-migration-assistant/rules/analysis/imports/web-import/static-imports.fixture.tsx 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 100% 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 diff --git a/codemods/solid-migration-assistant/rules/imports/web-import/web-import.ts b/codemods/solid-migration-assistant/rules/analysis/imports/web-import/web-import.ts similarity index 92% rename from codemods/solid-migration-assistant/rules/imports/web-import/web-import.ts rename to codemods/solid-migration-assistant/rules/analysis/imports/web-import/web-import.ts index d4e4657..f609beb 100644 --- a/codemods/solid-migration-assistant/rules/imports/web-import/web-import.ts +++ b/codemods/solid-migration-assistant/rules/analysis/imports/web-import/web-import.ts @@ -1,7 +1,7 @@ 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"; +import { findModuleReferences } from "../../../../shared/analysis.ts"; +import type { ModuleReference } from "../../../../shared/analysis.ts"; const LEGACY_WEB_MODULE = "solid-js/web"; const MIGRATION_GUIDE = 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 100% 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 diff --git a/codemods/solid-migration-assistant/rules/jsx/component-renames/component-renames.ts b/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/component-renames.ts similarity index 99% rename from codemods/solid-migration-assistant/rules/jsx/component-renames/component-renames.ts rename to codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/component-renames.ts index 8df6403..cefd651 100644 --- a/codemods/solid-migration-assistant/rules/jsx/component-renames/component-renames.ts +++ b/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/component-renames.ts @@ -1,6 +1,6 @@ import type { SgNode } from "codemod:ast-grep"; import type TSX from "codemod:ast-grep/langs/tsx"; -import { stringLiteralValue } from "../../../shared/analysis.ts"; +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"; 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/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 100% 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 diff --git a/codemods/solid-migration-assistant/rules/reactivity/create-effect/create-effect.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/create-effect.ts similarity index 98% rename from codemods/solid-migration-assistant/rules/reactivity/create-effect/create-effect.ts rename to codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/create-effect.ts index db25717..24ff24e 100644 --- a/codemods/solid-migration-assistant/rules/reactivity/create-effect/create-effect.ts +++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/create-effect.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-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/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/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..7cb9f33 --- /dev/null +++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/legacy-subpath-relocation.test.ts @@ -0,0 +1,132 @@ +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"; + +const EXPECTED_RELOCATIONS = [ + { location: "1:15", from: "solid-js/h", to: "@solidjs/h" }, + { location: "2:18", from: "solid-js/html", to: "@solidjs/html" }, + { location: "3:32", from: "solid-js/universal", to: "@solidjs/universal" }, + { location: "4:21", from: "solid-js/jsx-runtime", to: "@solidjs/web/jsx-runtime" }, + { location: "5:24", from: "solid-js/jsx-dev-runtime", to: "@solidjs/web/jsx-dev-runtime" }, + { location: "7:36", from: "solid-js/html", to: "@solidjs/html" }, + { location: "9:50", from: "solid-js/universal", to: "@solidjs/universal" }, + { location: "10:36", from: "solid-js/html", to: "@solidjs/html" }, + { location: "11:50", from: "solid-js/universal", to: "@solidjs/universal" }, + { location: "12:41", from: "solid-js/jsx-runtime", to: "@solidjs/web/jsx-runtime" }, + { location: "13:37", from: "solid-js/jsx-dev-runtime", to: "@solidjs/web/jsx-dev-runtime" }, + { location: "16:34", from: "solid-js/h", to: "@solidjs/h" }, + { location: "17:15", from: "solid-js/html", to: "@solidjs/html" }, + { location: "19:28", from: "solid-js/html", to: "@solidjs/html" }, + { location: "21:35", from: "solid-js/universal", to: "@solidjs/universal" }, + { location: "22:28", from: "solid-js/jsx-runtime", to: "@solidjs/web/jsx-runtime" }, +] as const; + +const EXPECTED_TRANSFORMED = String.raw`import h from "@solidjs/h"; +import html from "@solidjs/html"; +import { createRenderer } from "@solidjs/universal"; +import { jsx } from "@solidjs/web/jsx-runtime"; +import { jsxDEV } from "@solidjs/web/jsx-dev-runtime"; +// prettier-ignore +import { html as singleHtml } from '@solidjs/html'; +// prettier-ignore +import { createRenderer as singleRenderer } from '@solidjs/universal'; +import { html as escapedHex } from "@solidjs/html"; +import { createRenderer as escapedUnicode } from "@solidjs/universal"; +import { jsx as escapedCodePoint } from "@solidjs/web/jsx-runtime"; +import { jsxDEV as continued } from "@solidjs/web/jsx-dev-runtime"; + +export { h as reexportedH } from "@solidjs/h"; +export * from "@solidjs/html"; + +const dynamicHtml = import("@solidjs/html"); +declare const require: (name: string) => unknown; +const commonJsUniversal = require("@solidjs/universal"); +type RuntimeTypes = import("@solidjs/web/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; +`; + +const testLegacySubpathRelocation: Codemod = async (root) => { + const rootNode = root.root(); + const filename = root.relativeFilename().replaceAll("\\", "/"); + const relocations = relocateLegacySubpaths(rootNode, filename); + + const expected = EXPECTED_RELOCATIONS.map( + ({ location, from, to }) => + `${filename}:${location} Relocate ${from} to ${to}. Official migration guide: ${TRANSFORM_MIGRATION_GUIDE}`, + ).sort(); + + const actual = relocations.map(({ report }) => report).sort(); + if (actual.join("\n") !== expected.join("\n")) { + throw new Error( + `unexpected relocation report:\n${actual.join("\n")}\nExpected:\n${expected.join("\n")}`, + ); + } + + const transformed = rootNode.commitEdits(relocations.map(({ edit }) => edit)); + if (transformed !== EXPECTED_TRANSFORMED) { + throw new Error( + `unexpected transformed output:\n${transformed}\nExpected:\n${EXPECTED_TRANSFORMED}`, + ); + } + + const source = root.source(); + for (const nearestNegative of [ + 'solid-js\\\\x2fstore', + 'from "solid-js/store"', + 'from "solid-js/web"', + 'from "@solidjs/h"', + 'from "solid-js/h-extra"', + 'from "vendor/solid-js/h"', + 'from "solid-js/h/"', + ]) { + if (!source.includes(nearestNegative)) { + throw new Error(`missing nearest-negative fixture: ${nearestNegative}`); + } + } + + 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..ade01fd --- /dev/null +++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/legacy-subpath-relocation.ts @@ -0,0 +1,53 @@ +import type { Edit, SgNode } from "codemod:ast-grep"; +import type TSX from "codemod:ast-grep/langs/tsx"; +import { findModuleReferences } from "../../../../shared/analysis.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 SubpathRelocation = { + edit: Edit; + report: string; +}; + +/** + * 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, +): SubpathRelocation[] { + return findModuleReferences(rootNode) + .map(({ source, moduleName }) => { + const replacement = LEGACY_SUBPATH_RELOCATIONS[moduleName]; + if (replacement === undefined) return null; + const start = source.range().start; + const quote = source.text()[0]; + return { + edit: source.replace(`${quote}${replacement}${quote}`), + report: `${filename}:${start.line + 1}:${start.column + 1} Relocate ${moduleName} to ${replacement}. Official migration guide: ${TRANSFORM_MIGRATION_GUIDE}`, + }; + }) + .filter( + (entry): entry is SubpathRelocation => entry !== null, + ); +} diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/relocations.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/relocations.fixture.tsx new file mode 100644 index 0000000..c09731a --- /dev/null +++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/relocations.fixture.tsx @@ -0,0 +1,62 @@ +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/scripts/analyze.ts b/codemods/solid-migration-assistant/scripts/analyze.ts index b34b1de..89a5357 100644 --- a/codemods/solid-migration-assistant/scripts/analyze.ts +++ b/codemods/solid-migration-assistant/scripts/analyze.ts @@ -1,53 +1,53 @@ import type { Codemod, SgNode } from "codemod:ast-grep"; import type TSX from "codemod:ast-grep/langs/tsx"; import { acquireLock, getState, setState } from "codemod:workflow"; -import { analyzeBeta32SubpathImports } from "../rules/imports/beta32-subpaths/beta32-subpaths.ts"; -import { analyzeWebImport } from "../rules/imports/web-import/web-import.ts"; -import { analyzeJsxClassListAttributes } from "../rules/jsx/class-list/class-list.ts"; -import { analyzeJsxComponentRenames } from "../rules/jsx/component-renames/component-renames.ts"; -import { analyzeOnMount } from "../rules/lifecycle/on-mount/on-mount.ts"; -import { analyzeMergeProps } from "../rules/props/merge-props/merge-props.ts"; -import { analyzeSplitProps } from "../rules/props/split-props/split-props.ts"; -import { analyzeCreateComputed } from "../rules/reactivity/create-computed/create-computed.ts"; -import { analyzeCreateEffect } from "../rules/reactivity/create-effect/create-effect.ts"; -import { analyzeCreateMemo } from "../rules/reactivity/create-memo/create-memo.ts"; +import { analyzeBeta32SubpathImports } from "../rules/analysis/imports/beta32-subpaths/beta32-subpaths.ts"; +import { analyzeWebImport } from "../rules/analysis/imports/web-import/web-import.ts"; +import { analyzeJsxClassListAttributes } from "../rules/analysis/jsx/class-list/class-list.ts"; +import { analyzeJsxComponentRenames } from "../rules/analysis/jsx/component-renames/component-renames.ts"; +import { analyzeOnMount } from "../rules/analysis/lifecycle/on-mount/on-mount.ts"; +import { analyzeMergeProps } from "../rules/analysis/props/merge-props/merge-props.ts"; +import { analyzeSplitProps } from "../rules/analysis/props/split-props/split-props.ts"; +import { analyzeCreateComputed } from "../rules/analysis/reactivity/create-computed/create-computed.ts"; +import { analyzeCreateEffect } from "../rules/analysis/reactivity/create-effect/create-effect.ts"; +import { analyzeCreateMemo } from "../rules/analysis/reactivity/create-memo/create-memo.ts"; import { analyzeCreateMutable, analyzeModifyMutable, -} from "../rules/store/mutable/mutable.ts"; -import { analyzeProduce } from "../rules/store/produce/produce.ts"; -import { analyzeUnwrap } from "../rules/store/unwrap/unwrap.ts"; -import { analyzeBatch } from "../rules/reactivity/batch/batch.ts"; -import { analyzeOnHelper } from "../rules/reactivity/on-helper/on-helper.ts"; -import { analyzeCreateResource } from "../rules/reactivity/create-resource/create-resource.ts"; -import { analyzeOnCleanup } from "../rules/lifecycle/on-cleanup/on-cleanup.ts"; +} from "../rules/analysis/store/mutable/mutable.ts"; +import { analyzeProduce } from "../rules/analysis/store/produce/produce.ts"; +import { analyzeUnwrap } from "../rules/analysis/store/unwrap/unwrap.ts"; +import { analyzeBatch } from "../rules/analysis/reactivity/batch/batch.ts"; +import { analyzeOnHelper } from "../rules/analysis/reactivity/on-helper/on-helper.ts"; +import { analyzeCreateResource } from "../rules/analysis/reactivity/create-resource/create-resource.ts"; +import { analyzeOnCleanup } from "../rules/analysis/lifecycle/on-cleanup/on-cleanup.ts"; import { analyzeOnError, analyzeCatchError, analyzeResetErrorBoundaries, -} from "../rules/reactivity/error-handling/error-handling.ts"; +} from "../rules/analysis/reactivity/error-handling/error-handling.ts"; import { analyzeStartTransition, analyzeUseTransition, analyzeCreateDeferred, -} from "../rules/reactivity/transition-apis/transition-apis.ts"; -import { analyzeCreateSelector } from "../rules/reactivity/create-selector/create-selector.ts"; -import { analyzeIndexArray } from "../rules/reactivity/index-array/index-array.ts"; -import { analyzeCreateDynamic } from "../rules/reactivity/create-dynamic/create-dynamic.ts"; +} from "../rules/analysis/reactivity/transition-apis/transition-apis.ts"; +import { analyzeCreateSelector } from "../rules/analysis/reactivity/create-selector/create-selector.ts"; +import { analyzeIndexArray } from "../rules/analysis/reactivity/index-array/index-array.ts"; +import { analyzeCreateDynamic } from "../rules/analysis/reactivity/create-dynamic/create-dynamic.ts"; import { analyzeFrom, analyzeObservable, -} from "../rules/reactivity/from-observable/from-observable.ts"; +} from "../rules/analysis/reactivity/from-observable/from-observable.ts"; import { analyzeEqualFn, analyzeGetListener, analyzeWriteSignal, analyzeEnableScheduling, -} from "../rules/reactivity/utility-renames/utility-renames.ts"; -import { analyzeDomAttrNamespaces } from "../rules/jsx/dom-attr-namespaces/dom-attr-namespaces.ts"; -import { analyzeDomEventNamespaces } from "../rules/jsx/dom-event-namespaces/dom-event-namespaces.ts"; -import { analyzeDomUseDirective } from "../rules/jsx/dom-use-directive/dom-use-directive.ts"; -import { analyzeContextProvider } from "../rules/jsx/context-provider/context-provider.ts"; +} from "../rules/analysis/reactivity/utility-renames/utility-renames.ts"; +import { analyzeDomAttrNamespaces } from "../rules/analysis/jsx/dom-attr-namespaces/dom-attr-namespaces.ts"; +import { analyzeDomEventNamespaces } from "../rules/analysis/jsx/dom-event-namespaces/dom-event-namespaces.ts"; +import { analyzeDomUseDirective } from "../rules/analysis/jsx/dom-use-directive/dom-use-directive.ts"; +import { analyzeContextProvider } from "../rules/analysis/jsx/context-provider/context-provider.ts"; import { ANALYSIS_STATE_KEY } from "../shared/analysis.ts"; type Analyzer = ( diff --git a/codemods/solid-migration-assistant/scripts/emit-report.ts b/codemods/solid-migration-assistant/scripts/emit-report.ts new file mode 100644 index 0000000..97883f4 --- /dev/null +++ b/codemods/solid-migration-assistant/scripts/emit-report.ts @@ -0,0 +1,36 @@ +import type { Codemod } from "codemod:ast-grep"; +import type TSX from "codemod:ast-grep/langs/tsx"; +import { acquireLock, getState, setState } from "codemod:workflow"; +import { ANALYSIS_STATE_KEY } from "../shared/analysis.ts"; +import { TRANSFORM_REPORT_STATE_KEY } from "../shared/transform.ts"; + +const EMITTED_STATE_KEY = "solid-migration-assistant-report-emitted"; + +const emitReport: Codemod = async () => { + const release = acquireLock(EMITTED_STATE_KEY); + try { + if (getState(EMITTED_STATE_KEY)) return null; + + const guidance = [ + ...new Set(getState(ANALYSIS_STATE_KEY) ?? []), + ]; + const report = [ + ...new Set(getState(TRANSFORM_REPORT_STATE_KEY) ?? []), + ]; + setState(EMITTED_STATE_KEY, true, false); + + const blocks = [ + ...(guidance.length > 0 ? [guidance.sort().join("\n\n")] : []), + ...(report.length > 0 ? [report.sort().join("\n")] : []), + ]; + if (blocks.length > 0) { + console.log(blocks.join("\n\n")); + } + } finally { + release(); + } + + return null; +}; + +export default emitReport; diff --git a/codemods/solid-migration-assistant/scripts/emit.ts b/codemods/solid-migration-assistant/scripts/emit.ts deleted file mode 100644 index cb2102b..0000000 --- a/codemods/solid-migration-assistant/scripts/emit.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { Codemod } from "codemod:ast-grep"; -import type TSX from "codemod:ast-grep/langs/tsx"; -import { acquireLock, getState, setState } from "codemod:workflow"; -import { ANALYSIS_STATE_KEY } from "../shared/analysis.ts"; - -const EMITTED_STATE_KEY = "solid-migration-assistant-guidance-emitted"; - -const emit: Codemod = async () => { - const release = acquireLock(EMITTED_STATE_KEY); - try { - if (getState(EMITTED_STATE_KEY)) return null; - - const guidance = [ - ...new Set(getState(ANALYSIS_STATE_KEY) ?? []), - ].sort(); - setState(EMITTED_STATE_KEY, true, false); - - if (guidance.length > 0) { - console.log(guidance.join("\n\n")); - } - } finally { - release(); - } - - return null; -}; - -export default emit; diff --git a/codemods/solid-migration-assistant/scripts/transform.ts b/codemods/solid-migration-assistant/scripts/transform.ts new file mode 100644 index 0000000..5333798 --- /dev/null +++ b/codemods/solid-migration-assistant/scripts/transform.ts @@ -0,0 +1,27 @@ +import type { Codemod } from "codemod:ast-grep"; +import type TSX from "codemod:ast-grep/langs/tsx"; +import { acquireLock, getState, setState } from "codemod:workflow"; +import { relocateLegacySubpaths } from "../rules/transformations/imports/legacy-subpath-relocation/legacy-subpath-relocation.ts"; +import { TRANSFORM_REPORT_STATE_KEY } from "../shared/transform.ts"; + +const transform: Codemod = async (root) => { + const rootNode = root.root(); + const filename = root.relativeFilename().replaceAll("\\", "/"); + const relocations = relocateLegacySubpaths(rootNode, filename); + + if (relocations.length === 0) return null; + + const release = acquireLock(TRANSFORM_REPORT_STATE_KEY); + try { + const accumulated = getState(TRANSFORM_REPORT_STATE_KEY) ?? []; + setState(TRANSFORM_REPORT_STATE_KEY, [ + ...new Set([...accumulated, ...relocations.map(({ report }) => report)]), + ]); + } finally { + release(); + } + + return rootNode.commitEdits(relocations.map(({ edit }) => edit)); +}; + +export default transform; diff --git a/codemods/solid-migration-assistant/shared/transform.ts b/codemods/solid-migration-assistant/shared/transform.ts new file mode 100644 index 0000000..a2a6f4f --- /dev/null +++ b/codemods/solid-migration-assistant/shared/transform.ts @@ -0,0 +1,7 @@ +/** + * Workflow-level report state key for the transform workflow. Transform + * rules live under rules/transformations/ and report through this shared + * key; this module intentionally holds no rule logic. + */ +export const TRANSFORM_REPORT_STATE_KEY = + "solid-migration-assistant-transform-report"; diff --git a/codemods/solid-migration-assistant/tests/architecture.test.mjs b/codemods/solid-migration-assistant/tests/architecture.test.mjs index 6423e79..17dcb31 100644 --- a/codemods/solid-migration-assistant/tests/architecture.test.mjs +++ b/codemods/solid-migration-assistant/tests/architecture.test.mjs @@ -5,11 +5,12 @@ import { test } from "node:test"; import { fileURLToPath } from "node:url"; const packageDirectory = resolve(dirname(fileURLToPath(import.meta.url)), ".."); -const rulesDirectory = resolve(packageDirectory, "rules"); +const analysisDirectory = resolve(packageDirectory, "rules/analysis"); +const transformationsDirectory = resolve(packageDirectory, "rules/transformations"); const testsDirectory = resolve(packageDirectory, "tests"); -const EXPECTED_RULE_PRODUCTION = [ - "imports/beta32-subpaths/beta32-subpaths.ts", +const EXPECTED_ANALYSIS_PRODUCTION = [ +"imports/beta32-subpaths/beta32-subpaths.ts", "imports/web-import/web-import.ts", "jsx/class-list/class-list.ts", "jsx/component-renames/component-renames.ts", @@ -38,8 +39,8 @@ const EXPECTED_RULE_PRODUCTION = [ "store/produce/produce.ts", "store/unwrap/unwrap.ts", ]; -const EXPECTED_RULE_TESTS = [ - "imports/beta32-subpaths/beta32-subpaths.test.ts", +const EXPECTED_ANALYSIS_TESTS = [ +"imports/beta32-subpaths/beta32-subpaths.test.ts", "imports/web-import/web-import.test.ts", "jsx/class-list/class-list.test.ts", "jsx/component-renames/component-renames.test.ts", @@ -68,8 +69,8 @@ const EXPECTED_RULE_TESTS = [ "store/produce/produce.test.ts", "store/unwrap/unwrap.test.ts", ]; -const EXPECTED_RULE_FIXTURES = [ - "imports/beta32-subpaths/static-imports.fixture.tsx", +const EXPECTED_ANALYSIS_FIXTURES = [ +"imports/beta32-subpaths/static-imports.fixture.tsx", "imports/web-import/static-imports.fixture.tsx", "jsx/class-list/normal.fixture.tsx", "jsx/component-renames/normal.fixture.tsx", @@ -119,16 +120,35 @@ const EXPECTED_RULE_FIXTURES = [ "store/unwrap/non-solid.fixture.tsx", ]; -const EXPECTED_RULE_FOLDERS = EXPECTED_RULE_PRODUCTION.map((path) => +const EXPECTED_ANALYSIS_FOLDERS = EXPECTED_ANALYSIS_PRODUCTION.map((path) => dirname(path), ).sort(); -test("keeps the production workflow detection-only", () => { - assert.deepEqual(productionScripts(), ["analyze.ts", "emit.ts"]); +const EXPECTED_TRANSFORM_PRODUCTION = [ + "imports/legacy-subpath-relocation/legacy-subpath-relocation.ts", +]; +const EXPECTED_TRANSFORM_TESTS = [ + "imports/legacy-subpath-relocation/legacy-subpath-relocation.test.ts", +]; +const EXPECTED_TRANSFORM_FIXTURES = [ + "imports/legacy-subpath-relocation/relocations.fixture.tsx", +]; +const EXPECTED_TRANSFORM_FOLDERS = EXPECTED_TRANSFORM_PRODUCTION.map((path) => + dirname(path), +).sort(); + +test("ships read-only analyze and deterministic transform workflows", () => { + assert.deepEqual( + productionScripts(), + ["analyze.ts", "emit-report.ts", "transform.ts"], + ); assert.deepEqual(workflowFiles(), ["workflow.yaml"]); + assert.equal( + existsSync(resolve(packageDirectory, "transform.yaml")), + true, + ); for (const path of [ - "scripts/transform.ts", "scripts/write-report.ts", "shared/report.ts", "shared/report-path.ts", @@ -137,16 +157,16 @@ test("keeps the production workflow detection-only", () => { assert.equal(existsSync(resolve(packageDirectory, path)), false, path); } - const workflow = readFileSync( + const analyzeWorkflow = readFileSync( resolve(packageDirectory, "workflow.yaml"), "utf8", ); assert.deepEqual( - [...workflow.matchAll(/js_file:\s*(\S+)/g)].map((match) => match[1]), - ["scripts/analyze.ts", "scripts/emit.ts"], + [...analyzeWorkflow.matchAll(/js_file:\s*(\S+)/g)].map((match) => match[1]), + ["scripts/analyze.ts", "scripts/emit-report.ts"], ); assert.deepEqual( - [...workflow.matchAll(/- "(\*\*\/\*\.(?:js|jsx|ts|tsx))"/g)].map( + [...analyzeWorkflow.matchAll(/- "(\*\*\/\*\.(?:js|jsx|ts|tsx))"/g)].map( (match) => match[1], ), [ @@ -160,28 +180,64 @@ test("keeps the production workflow detection-only", () => { "**/*.tsx", ], ); - assert.equal((workflow.match(/semantic_analysis: workspace/g) ?? []).length, 1); - assert.doesNotMatch(workflow, /semantic_analysis: file/); - for (const exclusion of [ - "node_modules", - "dist", - "build", - "coverage", - ]) { + assert.equal( + (analyzeWorkflow.match(/semantic_analysis: workspace/g) ?? []).length, + 1, + ); + assert.doesNotMatch(analyzeWorkflow, /semantic_analysis: file/); + for (const exclusion of ["node_modules", "dist", "build", "coverage"]) { + assert.equal( + (analyzeWorkflow.match(new RegExp(`- "\\*\\*/${exclusion}/\\*\\*"`, "g")) ?? []) + .length, + 2, + exclusion, + ); + } + assert.equal((analyzeWorkflow.match(/- "\*\*\/\*\.d\.ts"/g) ?? []).length, 2); + assert.doesNotMatch(analyzeWorkflow, /transform|write.report|\.codemod-reports/i); + + const transformWorkflow = readFileSync( + resolve(packageDirectory, "transform.yaml"), + "utf8", + ); + assert.deepEqual( + [...transformWorkflow.matchAll(/js_file:\s*(\S+)/g)].map((match) => match[1]), + ["scripts/transform.ts", "scripts/emit-report.ts"], + ); + assert.deepEqual( + [...transformWorkflow.matchAll(/- "(\*\*\/\*\.(?:js|jsx|ts|tsx))"/g)].map( + (match) => match[1], + ), + [ + "**/*.js", + "**/*.jsx", + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx", + "**/*.ts", + "**/*.tsx", + ], + ); + for (const exclusion of ["node_modules", "dist", "build", "coverage"]) { assert.equal( - (workflow.match(new RegExp(`- "\\*\\*/${exclusion}/\\*\\*"`, "g")) ?? []) + (transformWorkflow.match(new RegExp(`- "\\*\\*/${exclusion}/\\*\\*"`, "g")) ?? []) .length, 2, exclusion, ); } - assert.equal((workflow.match(/- "\*\*\/\*\.d\.ts"/g) ?? []).length, 2); - assert.doesNotMatch(workflow, /transform|write.report|\.codemod-reports/i); + assert.equal((transformWorkflow.match(/- "\*\*\/\*\.d\.ts"/g) ?? []).length, 2); + assert.equal((transformWorkflow.match(/max_threads: 1/g) ?? []).length, 1); + assert.doesNotMatch( + transformWorkflow, + /semantic_analysis|scripts\/analyze\.ts|\.codemod-reports/i, + ); }); test("registers every supported detector and one deterministic emitter", () => { - for (const path of EXPECTED_RULE_PRODUCTION) { - assert.equal(existsSync(resolve(rulesDirectory, path)), true, path); + for (const path of EXPECTED_ANALYSIS_PRODUCTION) { + assert.equal(existsSync(resolve(analysisDirectory, path)), true, path); } const analyzer = readFileSync( @@ -194,6 +250,7 @@ test("registers every supported detector and one deterministic emitter", () => { "analyzeJsxClassListAttributes", "analyzeJsxComponentRenames", "analyzeOnMount", + "analyzeOnCleanup", "analyzeMergeProps", "analyzeSplitProps", "analyzeCreateComputed", @@ -230,7 +287,7 @@ test("registers every supported detector and one deterministic emitter", () => { } const emitter = readFileSync( - resolve(packageDirectory, "scripts/emit.ts"), + resolve(packageDirectory, "scripts/emit-report.ts"), "utf8", ); assert.match(emitter, /\.sort\(\)/); @@ -243,7 +300,7 @@ test("registers every supported detector and one deterministic emitter", () => { assert.equal((emitter.match(/console\.log\(/g) ?? []).length, 1); }); -test("exposes analysis without report or transform commands", () => { +test("exposes analyze and transform workflows", () => { const packageJson = JSON.parse( readFileSync(resolve(packageDirectory, "package.json"), "utf8"), ); @@ -253,70 +310,53 @@ test("exposes analysis without report or transform commands", () => { ); assert.equal(typeof packageJson.scripts.analyze, "string"); + assert.equal(typeof packageJson.scripts.transform, "string"); assert.deepEqual( - Object.keys(packageJson.scripts).filter((name) => - /transform|report/i.test(name), - ), - [], + Object.keys(packageJson.scripts) + .filter((name) => /^test:transform/.test(name)) + .sort(), + ["test:transform", "test:transform-rules"], ); assert.match(codemod, /- name: analyze/); - assert.doesNotMatch(codemod, /name: transform|workflow\.transform|report/i); + assert.match(codemod, /- name: transform/); + assert.doesNotMatch(codemod, /name: write|report\.yaml/i); }); -test("colocates exact rule production, adapters, and fixtures", () => { - assert.deepEqual(directRuleFolders(), EXPECTED_RULE_FOLDERS); +test("colocates exact analysis rule production, adapters, and fixtures", () => { + assert.deepEqual(directRuleFolders(analysisDirectory), EXPECTED_ANALYSIS_FOLDERS); assert.deepEqual( - ruleFiles((name) => name.endsWith(".ts") && !name.endsWith(".test.ts")), - EXPECTED_RULE_PRODUCTION, + ruleFiles(analysisDirectory, (name) => name.endsWith(".ts") && !name.endsWith(".test.ts")), + EXPECTED_ANALYSIS_PRODUCTION, ); assert.deepEqual( - ruleFiles((name) => name.endsWith(".test.ts")), - EXPECTED_RULE_TESTS, + ruleFiles(analysisDirectory, (name) => name.endsWith(".test.ts")), + EXPECTED_ANALYSIS_TESTS, ); assert.deepEqual( - ruleFiles((name) => name.endsWith(".fixture.tsx")), - EXPECTED_RULE_FIXTURES, + ruleFiles(analysisDirectory, (name) => name.endsWith(".fixture.tsx")), + EXPECTED_ANALYSIS_FIXTURES, ); + assertRuleLayout(analysisDirectory, EXPECTED_ANALYSIS_PRODUCTION); +}); - for (const path of ruleEntries()) { - assert.equal(path.split("/").includes("__testfixtures__"), false, path); - assert.notEqual(basename(path), "input.tsx", path); - assert.notEqual(basename(path), "expected.tsx", path); - } - - for (const production of EXPECTED_RULE_PRODUCTION) { - const folder = dirname(production); - const ruleName = basename(production, ".ts"); - const entries = readdirSync(resolve(rulesDirectory, folder), { - withFileTypes: true, - }); - const files = entries - .filter((entry) => entry.isFile()) - .map((entry) => entry.name) - .sort(); - const directories = entries - .filter((entry) => entry.isDirectory()) - .map((entry) => entry.name) - .sort(); - - assert.deepEqual(directories, [], folder); - assert.deepEqual( - files.filter( - (name) => name.endsWith(".ts") && !name.endsWith(".test.ts"), - ), - [`${ruleName}.ts`], - folder, - ); - assert.deepEqual( - files.filter((name) => name.endsWith(".test.ts")), - [`${ruleName}.test.ts`], - folder, - ); - assert.ok( - files.some((name) => name.endsWith(".fixture.tsx")), - `${folder} must contain at least one fixture`, - ); - } +test("colocates exact transformation rule production, adapters, and fixtures", () => { + assert.deepEqual( + directRuleFolders(transformationsDirectory), + EXPECTED_TRANSFORM_FOLDERS, + ); + assert.deepEqual( + ruleFiles(transformationsDirectory, (name) => name.endsWith(".ts") && !name.endsWith(".test.ts")), + EXPECTED_TRANSFORM_PRODUCTION, + ); + assert.deepEqual( + ruleFiles(transformationsDirectory, (name) => name.endsWith(".test.ts")), + EXPECTED_TRANSFORM_TESTS, + ); + assert.deepEqual( + ruleFiles(transformationsDirectory, (name) => name.endsWith(".fixture.tsx")), + EXPECTED_TRANSFORM_FIXTURES, + ); + assertRuleLayout(transformationsDirectory, EXPECTED_TRANSFORM_PRODUCTION); }); test("uses normal analyzer end-to-end fixtures", () => { @@ -327,6 +367,10 @@ test("uses normal analyzer end-to-end fixtures", () => { "fixture", "packaging.test.mjs", "rules.test.mjs", + "transform-expected", + "transform-fixture", + "transform-rules.test.mjs", + "transform.test.mjs", "workflow.test.mjs", ]); assert.equal(existsSync(resolve(testsDirectory, "transform.test.ts")), false); @@ -348,11 +392,11 @@ test("uses normal analyzer end-to-end fixtures", () => { assert.doesNotMatch(readFixtureText(), /codemod-reports|transform|report/i); }); -function directRuleFolders() { +function directRuleFolders(directory) { const folders = []; - for (const domain of readdirSync(rulesDirectory, { withFileTypes: true })) { + for (const domain of readdirSync(directory, { withFileTypes: true })) { if (!domain.isDirectory()) continue; - const domainDirectory = resolve(rulesDirectory, domain.name); + const domainDirectory = resolve(directory, domain.name); for (const rule of readdirSync(domainDirectory, { withFileTypes: true })) { if (rule.isDirectory()) folders.push(`${domain.name}/${rule.name}`); } @@ -360,23 +404,65 @@ function directRuleFolders() { return folders.sort(); } -function ruleFiles(predicate) { - return ruleEntries((entry) => entry.isFile() && predicate(entry.name)); +function ruleFiles(directory, predicate) { + return ruleEntries(directory, (entry) => entry.isFile() && predicate(entry.name)); } -function ruleEntries(predicate = () => true) { +function ruleEntries(directory, predicate = () => true) { const entries = []; - visitRules(rulesDirectory, entries, predicate); + visitRules(directory, directory, entries, predicate); return entries.sort(); } -function visitRules(directory, entries, predicate) { +function visitRules(directory, baseDirectory, entries, predicate) { for (const entry of readdirSync(directory, { withFileTypes: true })) { const path = resolve(directory, entry.name); if (predicate(entry)) { - entries.push(relative(rulesDirectory, path).replaceAll("\\", "/")); + entries.push(relative(baseDirectory, path).replaceAll("\\", "/")); } - if (entry.isDirectory()) visitRules(path, entries, predicate); + if (entry.isDirectory()) visitRules(path, baseDirectory, entries, predicate); + } +} + +function assertRuleLayout(directory, productionPaths) { + for (const entry of ruleEntries(directory)) { + assert.equal(entry.split("/").includes("__testfixtures__"), false, entry); + assert.notEqual(basename(entry), "input.tsx", entry); + assert.notEqual(basename(entry), "expected.tsx", entry); + } + + for (const production of productionPaths) { + const folder = dirname(production); + const ruleName = basename(production, ".ts"); + const entries = readdirSync(resolve(directory, folder), { + withFileTypes: true, + }); + const files = entries + .filter((entry) => entry.isFile()) + .map((entry) => entry.name) + .sort(); + const directories = entries + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name) + .sort(); + + assert.deepEqual(directories, [], folder); + assert.deepEqual( + files.filter( + (name) => name.endsWith(".ts") && !name.endsWith(".test.ts"), + ), + [`${ruleName}.ts`], + folder, + ); + assert.deepEqual( + files.filter((name) => name.endsWith(".test.ts")), + [`${ruleName}.test.ts`], + folder, + ); + assert.ok( + files.some((name) => name.endsWith(".fixture.tsx")), + `${folder} must contain at least one fixture`, + ); } } diff --git a/codemods/solid-migration-assistant/tests/packaging.test.mjs b/codemods/solid-migration-assistant/tests/packaging.test.mjs index 84b64ea..e78b9d2 100644 --- a/codemods/solid-migration-assistant/tests/packaging.test.mjs +++ b/codemods/solid-migration-assistant/tests/packaging.test.mjs @@ -26,42 +26,46 @@ const expectedFiles = [ "README.md", "bin/solid-migration-assistant.mjs", "package.json", - "rules/imports/beta32-subpaths/beta32-subpaths.ts", - "rules/imports/web-import/web-import.ts", - "rules/jsx/class-list/class-list.ts", - "rules/jsx/component-renames/component-renames.ts", - "rules/jsx/context-provider/context-provider.ts", - "rules/jsx/dom-attr-namespaces/dom-attr-namespaces.ts", - "rules/jsx/dom-event-namespaces/dom-event-namespaces.ts", - "rules/jsx/dom-use-directive/dom-use-directive.ts", - "rules/lifecycle/on-cleanup/on-cleanup.ts", - "rules/lifecycle/on-mount/on-mount.ts", - "rules/props/merge-props/merge-props.ts", - "rules/props/split-props/split-props.ts", - "rules/reactivity/batch/batch.ts", - "rules/reactivity/create-computed/create-computed.ts", - "rules/reactivity/create-dynamic/create-dynamic.ts", - "rules/reactivity/create-effect/create-effect.ts", - "rules/reactivity/create-memo/create-memo.ts", - "rules/reactivity/create-resource/create-resource.ts", - "rules/reactivity/create-selector/create-selector.ts", - "rules/reactivity/error-handling/error-handling.ts", - "rules/reactivity/from-observable/from-observable.ts", - "rules/reactivity/index-array/index-array.ts", - "rules/reactivity/on-helper/on-helper.ts", - "rules/reactivity/transition-apis/transition-apis.ts", - "rules/reactivity/utility-renames/utility-renames.ts", - "rules/store/mutable/mutable.ts", - "rules/store/produce/produce.ts", - "rules/store/unwrap/unwrap.ts", + "rules/analysis/imports/beta32-subpaths/beta32-subpaths.ts", + "rules/analysis/imports/web-import/web-import.ts", + "rules/analysis/jsx/class-list/class-list.ts", + "rules/analysis/jsx/component-renames/component-renames.ts", + "rules/analysis/jsx/context-provider/context-provider.ts", + "rules/analysis/jsx/dom-attr-namespaces/dom-attr-namespaces.ts", + "rules/analysis/jsx/dom-event-namespaces/dom-event-namespaces.ts", + "rules/analysis/jsx/dom-use-directive/dom-use-directive.ts", + "rules/analysis/lifecycle/on-cleanup/on-cleanup.ts", + "rules/analysis/lifecycle/on-mount/on-mount.ts", + "rules/analysis/props/merge-props/merge-props.ts", + "rules/analysis/props/split-props/split-props.ts", + "rules/analysis/reactivity/batch/batch.ts", + "rules/analysis/reactivity/create-computed/create-computed.ts", + "rules/analysis/reactivity/create-dynamic/create-dynamic.ts", + "rules/analysis/reactivity/create-effect/create-effect.ts", + "rules/analysis/reactivity/create-memo/create-memo.ts", + "rules/analysis/reactivity/create-resource/create-resource.ts", + "rules/analysis/reactivity/create-selector/create-selector.ts", + "rules/analysis/reactivity/error-handling/error-handling.ts", + "rules/analysis/reactivity/from-observable/from-observable.ts", + "rules/analysis/reactivity/index-array/index-array.ts", + "rules/analysis/reactivity/on-helper/on-helper.ts", + "rules/analysis/reactivity/transition-apis/transition-apis.ts", + "rules/analysis/reactivity/utility-renames/utility-renames.ts", + "rules/analysis/store/mutable/mutable.ts", + "rules/analysis/store/produce/produce.ts", + "rules/analysis/store/unwrap/unwrap.ts", + "rules/transformations/imports/legacy-subpath-relocation/legacy-subpath-relocation.ts", "scripts/analyze.ts", - "scripts/emit.ts", + "scripts/emit-report.ts", + "scripts/transform.ts", "shared/analysis.ts", "shared/run-workflow.mjs", + "shared/transform.ts", + "transform.yaml", "workflow.yaml", ]; const expectedDescription = - "Read-only Solid 1.9 to Solid 2 RC migration analyzer for project-owned JavaScript and TypeScript source"; + "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"; const expectedKeywords = [ "solid", "solidjs", @@ -80,7 +84,7 @@ test("publishes complete public npm and Codemod metadata", () => { readFileSync(resolve(packageDirectory, "package.json"), "utf8"), ); assert.equal(packageJson.name, "solid-migration-assistant"); - assert.equal(packageJson.version, "0.2.1"); + assert.equal(packageJson.version, "0.3.0"); assert.equal(packageJson.description, expectedDescription); assert.deepEqual(packageJson.keywords, expectedKeywords); assert.equal(packageJson.license, "MIT"); @@ -102,7 +106,7 @@ test("publishes complete public npm and Codemod metadata", () => { resolve(packageDirectory, "codemod.yaml"), "utf8", ); - assert.match(codemod, /^version: "0\.2\.1"$/m); + assert.match(codemod, /^version: "0\.3\.0"$/m); const codemodLines = codemod.split("\n"); assert.ok(codemodLines.includes(`description: "${expectedDescription}"`)); assert.ok( @@ -146,7 +150,7 @@ test( ); try { - assert.equal(expectedFiles.length, 37); + assert.equal(expectedFiles.length, 41); const packDirectory = join(temporaryRoot, "pack"); mkdirSync(packDirectory); const pack = command( diff --git a/codemods/solid-migration-assistant/tests/rules.test.mjs b/codemods/solid-migration-assistant/tests/rules.test.mjs index 8808b60..86827de 100644 --- a/codemods/solid-migration-assistant/tests/rules.test.mjs +++ b/codemods/solid-migration-assistant/tests/rules.test.mjs @@ -5,97 +5,97 @@ import { basename, dirname, relative, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const packageDirectory = resolve(dirname(fileURLToPath(import.meta.url)), ".."); -const rulesDirectory = resolve(packageDirectory, "rules"); +const rulesDirectory = resolve(packageDirectory, "rules/analysis"); const ruleCases = [ - { directory: "rules/imports/web-import" }, - { directory: "rules/imports/beta32-subpaths" }, + { directory: "rules/analysis/imports/web-import" }, + { directory: "rules/analysis/imports/beta32-subpaths" }, { - directory: "rules/jsx/component-renames", + directory: "rules/analysis/jsx/component-renames", semanticWorkspace: true, }, - { directory: "rules/jsx/class-list" }, + { directory: "rules/analysis/jsx/class-list" }, { - directory: "rules/lifecycle/on-mount", + directory: "rules/analysis/lifecycle/on-mount", semanticWorkspace: true, }, { - directory: "rules/reactivity/create-effect", + directory: "rules/analysis/reactivity/create-effect", semanticWorkspace: true, }, { - directory: "rules/reactivity/create-computed", + directory: "rules/analysis/reactivity/create-computed", semanticWorkspace: true, }, { - directory: "rules/reactivity/create-memo", + directory: "rules/analysis/reactivity/create-memo", semanticWorkspace: true, }, { - directory: "rules/props/merge-props", + directory: "rules/analysis/props/merge-props", semanticWorkspace: true, }, { - directory: "rules/props/split-props", + directory: "rules/analysis/props/split-props", semanticWorkspace: true, }, { - directory: "rules/store/mutable", + directory: "rules/analysis/store/mutable", semanticWorkspace: true, }, { - directory: "rules/store/produce", + directory: "rules/analysis/store/produce", semanticWorkspace: true, }, { - directory: "rules/store/unwrap", + directory: "rules/analysis/store/unwrap", semanticWorkspace: true, }, - { directory: "rules/jsx/context-provider" }, - { directory: "rules/jsx/dom-attr-namespaces" }, - { directory: "rules/jsx/dom-event-namespaces" }, - { directory: "rules/jsx/dom-use-directive" }, + { directory: "rules/analysis/jsx/context-provider" }, + { directory: "rules/analysis/jsx/dom-attr-namespaces" }, + { directory: "rules/analysis/jsx/dom-event-namespaces" }, + { directory: "rules/analysis/jsx/dom-use-directive" }, { - directory: "rules/reactivity/batch", + directory: "rules/analysis/reactivity/batch", semanticWorkspace: true, }, { - directory: "rules/reactivity/create-resource", + directory: "rules/analysis/reactivity/create-resource", semanticWorkspace: true, }, { - directory: "rules/reactivity/create-dynamic", + directory: "rules/analysis/reactivity/create-dynamic", semanticWorkspace: true, }, { - directory: "rules/reactivity/from-observable", + directory: "rules/analysis/reactivity/from-observable", semanticWorkspace: true, }, { - directory: "rules/reactivity/error-handling", + directory: "rules/analysis/reactivity/error-handling", semanticWorkspace: true, }, { - directory: "rules/reactivity/on-helper", + directory: "rules/analysis/reactivity/on-helper", semanticWorkspace: true, }, { - directory: "rules/reactivity/create-selector", + directory: "rules/analysis/reactivity/create-selector", semanticWorkspace: true, }, { - directory: "rules/reactivity/index-array", + directory: "rules/analysis/reactivity/index-array", semanticWorkspace: true, }, { - directory: "rules/reactivity/transition-apis", + directory: "rules/analysis/reactivity/transition-apis", semanticWorkspace: true, }, { - directory: "rules/reactivity/utility-renames", + directory: "rules/analysis/reactivity/utility-renames", semanticWorkspace: true, }, { - directory: "rules/lifecycle/on-cleanup", + directory: "rules/analysis/lifecycle/on-cleanup", semanticWorkspace: true, }, ]; diff --git a/codemods/solid-migration-assistant/tests/transform-expected/package.json b/codemods/solid-migration-assistant/tests/transform-expected/package.json new file mode 100644 index 0000000..d8526a4 --- /dev/null +++ b/codemods/solid-migration-assistant/tests/transform-expected/package.json @@ -0,0 +1,6 @@ +{ + "name": "transform-fixture", + "private": true, + "description": "Deterministic legacy subpath relocation transform fixture", + "type": "module" +} diff --git a/codemods/solid-migration-assistant/tests/transform-expected/src/relocations.tsx b/codemods/solid-migration-assistant/tests/transform-expected/src/relocations.tsx new file mode 100644 index 0000000..9e38f07 --- /dev/null +++ b/codemods/solid-migration-assistant/tests/transform-expected/src/relocations.tsx @@ -0,0 +1,61 @@ +import h from "@solidjs/h"; +import html from "@solidjs/html"; +import { createRenderer } from "@solidjs/universal"; +import { jsx } from "@solidjs/web/jsx-runtime"; +import { jsxDEV } from "@solidjs/web/jsx-dev-runtime"; +// prettier-ignore +import { html as singleHtml } from '@solidjs/html'; +// prettier-ignore +import { createRenderer as singleRenderer } from '@solidjs/universal'; +import { html as escapedHex } from "@solidjs/html"; +import { createRenderer as escapedUnicode } from "@solidjs/universal"; +import { jsx as escapedCodePoint } from "@solidjs/web/jsx-runtime"; +import { jsxDEV as continued } from "@solidjs/web/jsx-dev-runtime"; + +export { h as reexportedH } from "@solidjs/h"; +export * from "@solidjs/html"; + +const dynamicHtml = import("@solidjs/html"); +declare const require: (name: string) => unknown; +const commonJsUniversal = require("@solidjs/universal"); +type RuntimeTypes = import("@solidjs/web/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/tests/transform-expected/src/untouched.ts b/codemods/solid-migration-assistant/tests/transform-expected/src/untouched.ts new file mode 100644 index 0000000..7312b0f --- /dev/null +++ b/codemods/solid-migration-assistant/tests/transform-expected/src/untouched.ts @@ -0,0 +1,8 @@ +// No pure legacy subpath references: this file must stay byte-identical. +import { createStore } from "solid-js/store"; +import { render } from "solid-js/web"; +import alreadyH from "@solidjs/h"; + +void createStore; +void render; +void alreadyH; diff --git a/codemods/solid-migration-assistant/tests/transform-fixture/package.json b/codemods/solid-migration-assistant/tests/transform-fixture/package.json new file mode 100644 index 0000000..d8526a4 --- /dev/null +++ b/codemods/solid-migration-assistant/tests/transform-fixture/package.json @@ -0,0 +1,6 @@ +{ + "name": "transform-fixture", + "private": true, + "description": "Deterministic legacy subpath relocation transform fixture", + "type": "module" +} diff --git a/codemods/solid-migration-assistant/tests/transform-fixture/src/relocations.tsx b/codemods/solid-migration-assistant/tests/transform-fixture/src/relocations.tsx new file mode 100644 index 0000000..c09731a --- /dev/null +++ b/codemods/solid-migration-assistant/tests/transform-fixture/src/relocations.tsx @@ -0,0 +1,62 @@ +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/tests/transform-fixture/src/untouched.ts b/codemods/solid-migration-assistant/tests/transform-fixture/src/untouched.ts new file mode 100644 index 0000000..7312b0f --- /dev/null +++ b/codemods/solid-migration-assistant/tests/transform-fixture/src/untouched.ts @@ -0,0 +1,8 @@ +// No pure legacy subpath references: this file must stay byte-identical. +import { createStore } from "solid-js/store"; +import { render } from "solid-js/web"; +import alreadyH from "@solidjs/h"; + +void createStore; +void render; +void alreadyH; diff --git a/codemods/solid-migration-assistant/tests/transform-rules.test.mjs b/codemods/solid-migration-assistant/tests/transform-rules.test.mjs new file mode 100644 index 0000000..6b07cc6 --- /dev/null +++ b/codemods/solid-migration-assistant/tests/transform-rules.test.mjs @@ -0,0 +1,155 @@ +import { spawnSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import { readdirSync, readFileSync } from "node:fs"; +import { basename, dirname, relative, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const packageDirectory = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const transformationsDirectory = resolve(packageDirectory, "rules/transformations"); +const ruleCases = [ + { directory: "rules/transformations/imports/legacy-subpath-relocation" }, +]; + +const configuredAdapters = ruleCases.map(adapterForRule).sort(); +const existingAdapters = findFiles(transformationsDirectory, (name) => + name.endsWith(".test.ts"), +) + .map((path) => relative(packageDirectory, path)) + .sort(); + +if (configuredAdapters.length !== 1 || existingAdapters.length !== 1) { + throw new Error( + `Expected 1 transform rule adapter, found ${configuredAdapters.length} configured and ${existingAdapters.length} on disk.`, + ); +} +if (configuredAdapters.join("\n") !== existingAdapters.join("\n")) { + throw new Error( + `Transform rule adapter coverage is out of date.\nConfigured:\n${configuredAdapters.join("\n")}\nOn disk:\n${existingAdapters.join("\n")}`, + ); +} + +const cases = ruleCases.flatMap((ruleCase) => { + const ruleDirectory = resolve(packageDirectory, ruleCase.directory); + const adapter = adapterForRule(ruleCase); + return directFixtureFiles(ruleDirectory).map((target) => ({ + ...ruleCase, + adapter, + target, + })); +}); + +const configuredFixtures = cases + .map(({ target }) => relative(packageDirectory, target)) + .sort(); +const existingFixtures = findFiles(transformationsDirectory, (name) => + name.endsWith(".fixture.tsx"), +) + .map((path) => relative(packageDirectory, path)) + .sort(); + +if (configuredFixtures.length !== 1 || existingFixtures.length !== 1) { + throw new Error( + `Expected 1 colocated transform rule fixture, found ${configuredFixtures.length} configured and ${existingFixtures.length} on disk.`, + ); +} +if (configuredFixtures.join("\n") !== existingFixtures.join("\n")) { + throw new Error( + `Transform rule fixture coverage is out of date.\nConfigured:\n${configuredFixtures.join("\n")}\nOn disk:\n${existingFixtures.join("\n")}`, + ); +} + +for (const ruleCase of cases) { + runRuleCase(ruleCase); +} + +console.log( + `Transform rule detection passed: ${ruleCases.length} adapter, ${cases.length} fixture case, 0 targets changed.`, +); + +function adapterForRule({ directory }) { + return `${directory}/${basename(directory)}.test.ts`; +} + +function directFixtureFiles(directory) { + return readdirSync(directory, { withFileTypes: true }) + .filter((entry) => entry.isFile() && entry.name.endsWith(".fixture.tsx")) + .map((entry) => resolve(directory, entry.name)) + .sort((left, right) => left.localeCompare(right)); +} + +function runRuleCase({ adapter, target }) { + const adapterPath = resolve(packageDirectory, adapter); + const args = [ + "dlx", + "codemod@1.12.13", + "jssg", + "run", + "--language", + "tsx", + adapterPath, + "--target", + target, + "--dry-run", + "--no-interactive", + "--allow-dirty", + "--disable-analytics", + "--no-color", + ]; + + const before = hashFile(target); + const result = spawnSync("pnpm", args, { + cwd: packageDirectory, + encoding: "utf8", + maxBuffer: 10 * 1024 * 1024, + shell: false, + }); + const after = hashFile(target); + const context = commandContext(adapterPath, target, result); + + if (before !== after) { + throw new Error( + `Transform rule detection changed its target.\n${context}\nBefore: ${before}\nAfter: ${after}`, + ); + } + if (result.error) { + throw new Error( + `Could not spawn the transform rule command: ${result.error.message}\n${context}`, + { cause: result.error }, + ); + } + if (result.signal) { + throw new Error( + `Transform rule detection was terminated by signal ${result.signal}.\n${context}`, + ); + } + if (result.status !== 0) { + throw new Error( + `Transform rule detection exited with status ${String(result.status)}.\n${context}`, + ); + } +} + +function hashFile(path) { + return createHash("sha256").update(readFileSync(path)).digest("hex"); +} + +function commandContext(adapter, target, result) { + return [ + `Adapter: ${relative(packageDirectory, adapter)}`, + `Fixture: ${relative(packageDirectory, target)}`, + `stdout:\n${result.stdout || "(empty)"}`, + `stderr:\n${result.stderr || "(empty)"}`, + ].join("\n"); +} + +function findFiles(directory, predicate) { + const matches = []; + for (const entry of readdirSync(directory, { withFileTypes: true }).sort( + (left, right) => left.name.localeCompare(right.name), + )) { + const path = resolve(directory, entry.name); + if (entry.isDirectory()) matches.push(...findFiles(path, predicate)); + if (entry.isFile() && predicate(entry.name)) matches.push(path); + } + return matches; +} diff --git a/codemods/solid-migration-assistant/tests/transform.test.mjs b/codemods/solid-migration-assistant/tests/transform.test.mjs new file mode 100644 index 0000000..db6cc06 --- /dev/null +++ b/codemods/solid-migration-assistant/tests/transform.test.mjs @@ -0,0 +1,157 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import { + cpSync, + existsSync, + mkdtempSync, + readFileSync, + readdirSync, + rmSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join, relative, resolve } from "node:path"; +import { test } from "node:test"; +import { fileURLToPath } from "node:url"; + +const packageDirectory = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const codemodLauncher = resolve( + packageDirectory, + "node_modules/codemod/codemod", +); +const workflowPath = resolve(packageDirectory, "transform.yaml"); +const fixtureDirectory = resolve(packageDirectory, "tests/transform-fixture"); +const expectedDirectory = resolve(packageDirectory, "tests/transform-expected"); + +test( + "relocates exactly the five pure legacy subpaths and changes nothing else", + () => { + const temporaryRoot = mkdtempSync( + join(tmpdir(), "solid-migration-assistant-transform-"), + ); + const target = join(temporaryRoot, "target"); + try { + cpSync(fixtureDirectory, target, { recursive: true }); + + const first = runTransform(target); + assert.equal(first.status, 0, output(first)); + assertTreeEquals(target, expectedDirectory); + assertRelocationReport(first.stderr); + + const settled = treeSnapshot(target); + const second = runTransform(target); + assert.equal(second.status, 0, output(second)); + assert.deepEqual( + treeSnapshot(target), + settled, + "a second transform run changed the target (not idempotent)", + ); + assert.deepEqual( + relocationLines(second.stderr), + [], + "a second transform run re-reported relocations (not idempotent)", + ); + assertNoPersistentArtifacts(target); + } finally { + rmSync(temporaryRoot, { recursive: true, force: true }); + } + }, + { timeout: 120_000 }, +); + +function runTransform(target) { + return spawnSync( + process.execPath, + [ + codemodLauncher, + "--disable-analytics", + "workflow", + "run", + "-w", + workflowPath, + "-t", + target, + "--allow-dirty", + "--no-interactive", + "--no-color", + ], + { + cwd: packageDirectory, + encoding: "utf8", + env: { ...process.env, CI: "true", NO_COLOR: "1", FORCE_COLOR: undefined }, + maxBuffer: 10 * 1024 * 1024, + }, + ); +} + +function relocationLines(stderr) { + return stderr + .split("\n") + .map((line) => line.trim()) + .filter((line) => line.startsWith("src/") && line.includes("Relocate ")); +} + +function assertRelocationReport(stderr) { + const lines = relocationLines(stderr); + assert.deepEqual( + lines, + [...lines].sort(), + "relocation report is not in deterministic whole-string order", + ); + assert.ok(lines.length >= 16, `expected at least 16 relocations, got ${lines.length}`); + for (const line of lines) { + assert.match( + line, + /^src\/relocations\.tsx:\d+:\d+ Relocate solid-js\/(?:h|html|universal|jsx-runtime|jsx-dev-runtime) to @solidjs\/(?:h|html|universal|web\/jsx-runtime|web\/jsx-dev-runtime)\. Official migration guide: https:\/\/github\.com\/solidjs\/solid\/blob\/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5\/documentation\/solid-2\.0\/MIGRATION\.md#imports-where-things-live-now$/, + ); + assert.doesNotMatch(line, /solid-js\/store|solid-js\/web\b/); + } +} + +function assertNoPersistentArtifacts(target) { + for (const path of [ + ".codemod", + ".codemod-reports", + "build", + "coverage", + "dist", + ]) { + assert.equal(existsSync(join(target, path)), false, path); + } +} + +function assertTreeEquals(actualDirectory, expectedDirectory) { + assert.deepEqual( + treeSnapshot(actualDirectory), + treeSnapshot(expectedDirectory), + "transformed tree differs from the expected tree", + ); +} + +function treeSnapshot(root) { + const snapshot = {}; + visit(root, root, snapshot); + return snapshot; +} + +function visit(root, directory, snapshot) { + for (const entry of readdirSync(directory, { withFileTypes: true }).sort( + (left, right) => left.name.localeCompare(right.name), + )) { + const path = join(directory, entry.name); + const relativePath = relative(root, path).replaceAll("\\", "/"); + if (entry.isDirectory()) { + snapshot[`directory:${relativePath}`] = true; + visit(root, path, snapshot); + continue; + } + if (!entry.isFile()) continue; + snapshot[`file:${relativePath}`] = createHash("sha256") + .update(readFileSync(path)) + .digest("hex"); + } +} + +function output(result) { + return `${result.stdout ?? ""}${result.stderr ?? ""}`; +} diff --git a/codemods/solid-migration-assistant/tests/workflow.test.mjs b/codemods/solid-migration-assistant/tests/workflow.test.mjs index 23b4898..067ac12 100644 --- a/codemods/solid-migration-assistant/tests/workflow.test.mjs +++ b/codemods/solid-migration-assistant/tests/workflow.test.mjs @@ -26,7 +26,7 @@ const expectedGuidance = [ 'src/excluded.ts:5:1 Manual review required: migrate this createMemo initial value.\nWhy: Solid 1.x treats this call\'s second argument as its initial value, while Solid 2.0.0-rc.0 treats the second argument as options and has no initial-value argument.\nGuidance: Read the complete callback, the initial-value expression, its consumers, and nearby reactive state. Establish what the callback must receive on its first run and how later updates use the previous value. Preserve that behavior explicitly in surrounding state or callback logic before removing the legacy initial-value argument. For a three-argument call, review the legacy options separately and move only options supported by Solid 2.0.0-rc.0 into the second-argument position; for a two-argument call, do not reinterpret an option-shaped initial value as options. Make and validate this migration yourself; this analyzer never edits or runs the target project. Stop without proposing a rewrite when first-run or previous-value behavior is unclear, the initial-value expression has meaningful evaluation timing or side effects, options are dynamic or their compatibility is unknown, the callback writes to its inputs or may form a cycle, or ownership and consumers are unclear. Ask for the smallest focused test or runtime observation that exposes the first computed value and subsequent updates. Official migration guide: https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#effects-lifecycle-and-cleanup', 'src/language.js:1:24 Move this Solid web renderer static import.\nWhy: Solid 2 publishes the web renderer from @solidjs/web instead of the solid-js/web subpath.\nGuidance: Change only this static import\'s module source to @solidjs/web and preserve its import form and quote style. Make and validate that edit yourself; this analyzer never edits or runs the target project. Official migration guide: https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#imports-where-things-live-now', 'src/language.jsx:1:39 Manual review required: migrate this intrinsic JSX classList attribute to class.\nWhy: Solid 2.0.0-rc.0 removes the JSX classList attribute in favor of the class attribute\'s object and array forms.\nGuidance: Read this complete intrinsic element, its classList value, and every class source. Move the classList value into the class attribute\'s object or array form, preserve static classes and conditional truthiness, and deliberately merge any existing class attribute on the same element. Make and validate this migration yourself; this analyzer never edits or runs the target project. Stop without proposing a rewrite when the value or another class source is spread or forwarded, duplicate class sources have unclear precedence, getters or side effects could change evaluation order or frequency, or a focused rendering test does not prove the resulting static and conditional class tokens. Ask for the smallest focused rendering test or runtime observation that exposes the rendered class attribute across relevant states. Official migration guide: https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#classlist--class-objectarray-forms', - 'src/migration-sites.tsx:14:63 Move this Solid 2 legacy subpath static import.\nWhy: Solid 2 publishes solid-js/store from solid-js.\nGuidance: Change only this static import\'s module source and preserve its import form and quote style. Make and validate that edit yourself; this analyzer never edits or runs the target project. Stop: do not blindly rewrite this source if the import includes removed or renamed beta.32 helpers such as unwrap, produce, createMutable, or modifyMutable. Migrate those bindings and call sites first, then move supported store imports to solid-js. Official migration guide: https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#imports-where-things-live-now', + 'src/migration-sites.tsx:14:63 Move this Solid 2 legacy subpath static import.\nWhy: Solid 2 publishes solid-js/store from solid-js.\nGuidance: Change only this static import\'s module source and preserve its import form and quote style. Make and validate that edit yourself; this analyzer never edits or runs the target project. Stop: do not blindly rewrite this source if the import includes removed or renamed helpers such as unwrap, produce, createMutable, or modifyMutable. Migrate those bindings and call sites first, then move supported store imports to solid-js. Official migration guide: https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#imports-where-things-live-now', 'src/migration-sites.tsx:15:24 Move this Solid web renderer static import.\nWhy: Solid 2 publishes the web renderer from @solidjs/web instead of the solid-js/web subpath.\nGuidance: Change only this static import\'s module source to @solidjs/web and preserve its import form and quote style. Make and validate that edit yourself; this analyzer never edits or runs the target project. Official migration guide: https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#imports-where-things-live-now', 'src/migration-sites.tsx:19:15 Manual review required: migrate this mergeProps call to a reviewed merge.\nWhy: Solid 2.0.0-rc.0 replaces mergeProps with merge, but merge treats a property that exists on a later source with the value undefined as the winner instead of falling through to an earlier source. This call has 2 semantic argument(s).\nGuidance: Read every source in argument order, list all overlapping keys, and trace every consumer of the merged value. Replace mergeProps with merge from solid-js only after proving that every later overlapping value is non-undefined and that zero-argument behavior, one-source result identity, and mutation semantics do not matter. Make and validate this migration yourself; this analyzer never edits or runs the target project. Stop without proposing a replacement when existing TypeScript types or the inferred Merge result type are the only runtime-safety evidence, a source is any/unknown/union-typed at runtime, a props or store proxy, a function, or has getters or dynamic key presence, source or result identity or mutation is observed, or a consumer depends on fallback-through-undefined behavior. If old undefined-fallback behavior is required, preserve live reactive reads with a targeted manual guard at the disputed property boundary rather than object spread or Object.assign. Ask for the smallest focused test or runtime observation that exposes the disputed property\'s value, precedence, identity, and mutation boundary. Official migration guide: https://github.com/solidjs/solid/blob/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5/documentation/solid-2.0/MIGRATION.md#mergeprops--splitprops--merge--omit', 'src/migration-sites.tsx:20:33 Manual review required: migrate this splitProps tuple to reviewed omit-based values.\nWhy: Solid 2.0.0-rc.0 replaces splitProps with omit, but omit returns one object while splitProps returns a tuple containing one selected object per key group plus a final remainder, so migration depends on how those positions are consumed. This call has 1 key group(s).\nGuidance: Trace the complete tuple destructuring or other call-site use, list the exact keys represented by every group, and trace every downstream consumer and reactive property access for each selected value and the remainder. Design explicit omit-based values only after proving how every old tuple member will be produced and that live reactive property access is preserved. Make and validate this migration yourself; this analyzer never edits or runs the target project. Stop without proposing a replacement when keys are dynamic or overlapping, more than one selected group is consumed, the tuple escapes or is indexed dynamically, rest destructuring or reassignment is involved, props or store proxy identity matters, or any consumer is unclear. Ask for the smallest focused test or runtime observation that exposes each consumed tuple member\'s keys, value, reactive updates, and identity boundary. Official 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/transform.yaml b/codemods/solid-migration-assistant/transform.yaml new file mode 100644 index 0000000..968893c --- /dev/null +++ b/codemods/solid-migration-assistant/transform.yaml @@ -0,0 +1,42 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +nodes: + - id: relocate-legacy-solid-subpaths + name: Relocate deterministic legacy Solid subpath imports + type: automatic + steps: + - name: Rewrite the five pure legacy subpath module sources + js-ast-grep: + js_file: scripts/transform.ts + base_path: "." + language: "tsx" + include: + - "**/*.js" + - "**/*.jsx" + - "**/*.ts" + - "**/*.tsx" + exclude: + - "**/node_modules/**" + - "**/dist/**" + - "**/build/**" + - "**/coverage/**" + - "**/*.d.ts" + - name: Emit the relocation report once in deterministic whole-string order + js-ast-grep: + js_file: scripts/emit-report.ts + base_path: "." + language: "tsx" + max_threads: 1 + include: + - "**/*.js" + - "**/*.jsx" + - "**/*.ts" + - "**/*.tsx" + exclude: + - "**/node_modules/**" + - "**/dist/**" + - "**/build/**" + - "**/coverage/**" + - "**/*.d.ts" diff --git a/codemods/solid-migration-assistant/workflow.yaml b/codemods/solid-migration-assistant/workflow.yaml index 3f2a97a..91c8baf 100644 --- a/codemods/solid-migration-assistant/workflow.yaml +++ b/codemods/solid-migration-assistant/workflow.yaml @@ -26,7 +26,7 @@ nodes: - "**/*.d.ts" - name: Emit all guidance once in deterministic whole-string order js-ast-grep: - js_file: scripts/emit.ts + js_file: scripts/emit-report.ts base_path: "." language: "tsx" max_threads: 1 From fbf9f858240429a158d87a9df1a75e83fdc60d84 Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Sat, 15 Aug 2026 18:33:36 -0500 Subject: [PATCH 02/16] test: harden transform rule with 10 focused fixtures and fix 3 bugs - Fix prototype-chain module lookup: bare specifiers such as constructor, toString, hasOwnProperty, valueOf, and __proto__ resolved through Object.prototype and were rewritten into inherited-member garbage; use Object.hasOwn for the relocation map lookup - Fix `export default "solid-js/h"` being treated as a re-export source: only a string that follows the `from` keyword is a re-export's module source, so exported string values stay byte-identical - Fix locally shadowed `require` false positives: files declaring their own require binding (function, const, parameter, import, catch) now leave every bare require() call in the file untouched; ambient `declare const require` remains a valid CommonJS require - Split transform rule coverage into 10 focused fixtures (static imports, single quotes, re-exports, runtime forms, negative forms, shadowed require, prototype names, escaped specifiers, no matches, broad integration), each asserting exact relocations and exact transformed text - Extend end-to-end transform fixtures with src/negative.ts (0 relocations), src/plain.js (CommonJS require), and src/nested/deep.jsx (nested JSX) - Update the rule adapter, transform-rules harness invariant (1 adapter / 10 fixtures), architecture fixture list, and e2e report regex/count --- .../escaped-specifiers.fixture.tsx | 13 + .../legacy-subpath-relocation.test.ts | 229 ++++++++++-------- .../legacy-subpath-relocation.ts | 83 ++++++- .../negative-forms.fixture.tsx | 23 ++ .../no-matches.fixture.tsx | 2 + .../prototype-names.fixture.tsx | 14 ++ .../re-exports.fixture.tsx | 7 + .../relocations.fixture.tsx | 1 + .../runtime-forms.fixture.tsx | 18 ++ .../shadowed-require.fixture.tsx | 17 ++ .../single-quotes.fixture.tsx | 22 ++ .../static-imports.fixture.tsx | 17 ++ .../tests/architecture.test.mjs | 9 + .../tests/transform-expected/src/negative.ts | 36 +++ .../transform-expected/src/nested/deep.jsx | 11 + .../tests/transform-expected/src/plain.js | 5 + .../tests/transform-fixture/src/negative.ts | 36 +++ .../transform-fixture/src/nested/deep.jsx | 11 + .../tests/transform-fixture/src/plain.js | 5 + .../tests/transform-rules.test.mjs | 4 +- .../tests/transform.test.mjs | 4 +- 21 files changed, 460 insertions(+), 107 deletions(-) create mode 100644 codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/escaped-specifiers.fixture.tsx create mode 100644 codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/negative-forms.fixture.tsx create mode 100644 codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/no-matches.fixture.tsx create mode 100644 codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/prototype-names.fixture.tsx create mode 100644 codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/re-exports.fixture.tsx create mode 100644 codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/runtime-forms.fixture.tsx create mode 100644 codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/shadowed-require.fixture.tsx create mode 100644 codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/single-quotes.fixture.tsx create mode 100644 codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/static-imports.fixture.tsx create mode 100644 codemods/solid-migration-assistant/tests/transform-expected/src/negative.ts create mode 100644 codemods/solid-migration-assistant/tests/transform-expected/src/nested/deep.jsx create mode 100644 codemods/solid-migration-assistant/tests/transform-expected/src/plain.js create mode 100644 codemods/solid-migration-assistant/tests/transform-fixture/src/negative.ts create mode 100644 codemods/solid-migration-assistant/tests/transform-fixture/src/nested/deep.jsx create mode 100644 codemods/solid-migration-assistant/tests/transform-fixture/src/plain.js diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/escaped-specifiers.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/escaped-specifiers.fixture.tsx new file mode 100644 index 0000000..9b09327 --- /dev/null +++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/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/legacy-subpath-relocation.test.ts b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/legacy-subpath-relocation.test.ts index 7cb9f33..b2007c1 100644 --- 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 @@ -5,127 +5,154 @@ import { TRANSFORM_MIGRATION_GUIDE, } from "./legacy-subpath-relocation.ts"; -const EXPECTED_RELOCATIONS = [ - { location: "1:15", from: "solid-js/h", to: "@solidjs/h" }, - { location: "2:18", from: "solid-js/html", to: "@solidjs/html" }, - { location: "3:32", from: "solid-js/universal", to: "@solidjs/universal" }, - { location: "4:21", from: "solid-js/jsx-runtime", to: "@solidjs/web/jsx-runtime" }, - { location: "5:24", from: "solid-js/jsx-dev-runtime", to: "@solidjs/web/jsx-dev-runtime" }, - { location: "7:36", from: "solid-js/html", to: "@solidjs/html" }, - { location: "9:50", from: "solid-js/universal", to: "@solidjs/universal" }, - { location: "10:36", from: "solid-js/html", to: "@solidjs/html" }, - { location: "11:50", from: "solid-js/universal", to: "@solidjs/universal" }, - { location: "12:41", from: "solid-js/jsx-runtime", to: "@solidjs/web/jsx-runtime" }, - { location: "13:37", from: "solid-js/jsx-dev-runtime", to: "@solidjs/web/jsx-dev-runtime" }, - { location: "16:34", from: "solid-js/h", to: "@solidjs/h" }, - { location: "17:15", from: "solid-js/html", to: "@solidjs/html" }, - { location: "19:28", from: "solid-js/html", to: "@solidjs/html" }, - { location: "21:35", from: "solid-js/universal", to: "@solidjs/universal" }, - { location: "22:28", from: "solid-js/jsx-runtime", to: "@solidjs/web/jsx-runtime" }, -] as const; - -const EXPECTED_TRANSFORMED = String.raw`import h from "@solidjs/h"; -import html from "@solidjs/html"; -import { createRenderer } from "@solidjs/universal"; -import { jsx } from "@solidjs/web/jsx-runtime"; -import { jsxDEV } from "@solidjs/web/jsx-dev-runtime"; -// prettier-ignore -import { html as singleHtml } from '@solidjs/html'; -// prettier-ignore -import { createRenderer as singleRenderer } from '@solidjs/universal'; -import { html as escapedHex } from "@solidjs/html"; -import { createRenderer as escapedUnicode } from "@solidjs/universal"; -import { jsx as escapedCodePoint } from "@solidjs/web/jsx-runtime"; -import { jsxDEV as continued } from "@solidjs/web/jsx-dev-runtime"; - -export { h as reexportedH } from "@solidjs/h"; -export * from "@solidjs/html"; - -const dynamicHtml = import("@solidjs/html"); -declare const require: (name: string) => unknown; -const commonJsUniversal = require("@solidjs/universal"); -type RuntimeTypes = import("@solidjs/web/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"; +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; +}; -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; -`; +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\nvoid dynamicH;\nvoid load;\nvoid requiredUniversal;\ntype _RuntimeTypes = RuntimeTypes;\ntype _DevTypes = DevTypes;\n", + }, + "negative-forms": { + relocations: [], + transformed: null, + }, + "shadowed-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 relocations = relocateLegacySubpaths(rootNode, filename); - const expected = EXPECTED_RELOCATIONS.map( - ({ location, from, to }) => + const expected = fixtureCase.relocations + .map(({ location, from, to }) => `${filename}:${location} Relocate ${from} to ${to}. Official migration guide: ${TRANSFORM_MIGRATION_GUIDE}`, - ).sort(); + ) + .sort(); const actual = relocations.map(({ report }) => report).sort(); if (actual.join("\n") !== expected.join("\n")) { throw new Error( - `unexpected relocation report:\n${actual.join("\n")}\nExpected:\n${expected.join("\n")}`, + `unexpected relocation report for ${marker}:\n${actual.join("\n")}\nExpected:\n${expected.join("\n")}`, ); } const transformed = rootNode.commitEdits(relocations.map(({ edit }) => edit)); - if (transformed !== EXPECTED_TRANSFORMED) { + const expectedTransformed = fixtureCase.transformed ?? source; + if (transformed !== expectedTransformed) { throw new Error( - `unexpected transformed output:\n${transformed}\nExpected:\n${EXPECTED_TRANSFORMED}`, + `unexpected transformed output for ${marker}:\n${transformed}\nExpected:\n${expectedTransformed}`, ); } - const source = root.source(); - for (const nearestNegative of [ - 'solid-js\\\\x2fstore', - 'from "solid-js/store"', - 'from "solid-js/web"', - 'from "@solidjs/h"', - 'from "solid-js/h-extra"', - 'from "vendor/solid-js/h"', - 'from "solid-js/h/"', - ]) { - if (!source.includes(nearestNegative)) { - throw new Error(`missing nearest-negative fixture: ${nearestNegative}`); - } - } - return null; }; 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 index ade01fd..eacc228 100644 --- 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 @@ -27,6 +27,82 @@ export type SubpathRelocation = { report: string; }; +/** + * 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"); +} + +/** + * 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, import, + * parameter, catch parameter, destructuring), 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. + */ +const REQUIRE_BINDING_PARENT_KINDS = new Set([ + "function_declaration", + "class_declaration", + "variable_declarator", + "required_parameter", + "optional_parameter", + "rest_pattern", + "assignment_pattern", + "import_specifier", + "namespace_import", + "import_clause", + "catch_clause", + "pair_pattern", + "object_pattern", +]); + +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$" }, + })) { + const parent = node.parent(); + if (!parent || !REQUIRE_BINDING_PARENT_KINDS.has(parent.kind())) { + continue; + } + if ( + node.ancestors().some( + (ancestor) => ancestor.kind() === "ambient_declaration", + ) + ) { + continue; + } + 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 @@ -36,10 +112,13 @@ export function relocateLegacySubpaths( rootNode: SgNode, filename: string, ): SubpathRelocation[] { + const shadowedRequire = hasShadowingRequireBinding(rootNode); return findModuleReferences(rootNode) - .map(({ source, moduleName }) => { - const replacement = LEGACY_SUBPATH_RELOCATIONS[moduleName]; + .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]; return { diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/negative-forms.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/negative-forms.fixture.tsx new file mode 100644 index 0000000..af55412 --- /dev/null +++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/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/no-matches.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/no-matches.fixture.tsx new file mode 100644 index 0000000..feb5176 --- /dev/null +++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/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/prototype-names.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/prototype-names.fixture.tsx new file mode 100644 index 0000000..2d8e888 --- /dev/null +++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/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/re-exports.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/re-exports.fixture.tsx new file mode 100644 index 0000000..337cc64 --- /dev/null +++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/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/relocations.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/relocations.fixture.tsx index c09731a..bfce02b 100644 --- a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/relocations.fixture.tsx +++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/relocations.fixture.tsx @@ -1,3 +1,4 @@ +// fixture: relocations import h from "solid-js/h"; import html from "solid-js/html"; import { createRenderer } from "solid-js/universal"; diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/runtime-forms.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/runtime-forms.fixture.tsx new file mode 100644 index 0000000..a1d0245 --- /dev/null +++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/runtime-forms.fixture.tsx @@ -0,0 +1,18 @@ +// 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"); + +void dynamicH; +void load; +void requiredUniversal; +type _RuntimeTypes = RuntimeTypes; +type _DevTypes = DevTypes; diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/shadowed-require.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/shadowed-require.fixture.tsx new file mode 100644 index 0000000..0e8cbe4 --- /dev/null +++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/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/single-quotes.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/single-quotes.fixture.tsx new file mode 100644 index 0000000..132cae7 --- /dev/null +++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/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/static-imports.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/static-imports.fixture.tsx new file mode 100644 index 0000000..1b127c0 --- /dev/null +++ b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/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/tests/architecture.test.mjs b/codemods/solid-migration-assistant/tests/architecture.test.mjs index 17dcb31..2bb92c7 100644 --- a/codemods/solid-migration-assistant/tests/architecture.test.mjs +++ b/codemods/solid-migration-assistant/tests/architecture.test.mjs @@ -131,7 +131,16 @@ const EXPECTED_TRANSFORM_TESTS = [ "imports/legacy-subpath-relocation/legacy-subpath-relocation.test.ts", ]; const EXPECTED_TRANSFORM_FIXTURES = [ + "imports/legacy-subpath-relocation/escaped-specifiers.fixture.tsx", + "imports/legacy-subpath-relocation/negative-forms.fixture.tsx", + "imports/legacy-subpath-relocation/no-matches.fixture.tsx", + "imports/legacy-subpath-relocation/prototype-names.fixture.tsx", + "imports/legacy-subpath-relocation/re-exports.fixture.tsx", "imports/legacy-subpath-relocation/relocations.fixture.tsx", + "imports/legacy-subpath-relocation/runtime-forms.fixture.tsx", + "imports/legacy-subpath-relocation/shadowed-require.fixture.tsx", + "imports/legacy-subpath-relocation/single-quotes.fixture.tsx", + "imports/legacy-subpath-relocation/static-imports.fixture.tsx", ]; const EXPECTED_TRANSFORM_FOLDERS = EXPECTED_TRANSFORM_PRODUCTION.map((path) => dirname(path), diff --git a/codemods/solid-migration-assistant/tests/transform-expected/src/negative.ts b/codemods/solid-migration-assistant/tests/transform-expected/src/negative.ts new file mode 100644 index 0000000..ef4b66e --- /dev/null +++ b/codemods/solid-migration-assistant/tests/transform-expected/src/negative.ts @@ -0,0 +1,36 @@ +// Negative transform coverage: every reference below must stay byte-identical. +import constructorModule from "constructor"; +import toStringModule from "toString"; +import hasOwnPropertyModule from "hasOwnProperty"; +import protoModule from "__proto__"; +export default "solid-js/h"; +import { nearH as prefixNearMiss } from "solid-js/h-extra"; +import { nearH as suffixNearMiss } from "vendor/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"; + +function require(name: string): unknown { + return name; +} +const shadowed = require("solid-js/html"); + +const viaResolve = require.resolve("solid-js/universal"); +const viaTemplate = import(`solid-js/jsx-runtime`); +const url = new URL("solid-js/jsx-dev-runtime", import.meta.url); + +void constructorModule; +void toStringModule; +void hasOwnPropertyModule; +void protoModule; +void prefixNearMiss; +void suffixNearMiss; +void escapedNearMiss; +void createStore; +void render; +void migratedH; +void shadowed; +void viaResolve; +void viaTemplate; +void url; diff --git a/codemods/solid-migration-assistant/tests/transform-expected/src/nested/deep.jsx b/codemods/solid-migration-assistant/tests/transform-expected/src/nested/deep.jsx new file mode 100644 index 0000000..f0d90d9 --- /dev/null +++ b/codemods/solid-migration-assistant/tests/transform-expected/src/nested/deep.jsx @@ -0,0 +1,11 @@ +// Nested JSX module with legacy subpath references. +import { createRenderer } from "@solidjs/universal"; +import { jsx } from "@solidjs/web/jsx-runtime"; + +export function App() { + return
nested
; +} + +const dynamic = import("@solidjs/html"); +void dynamic; +void jsx; diff --git a/codemods/solid-migration-assistant/tests/transform-expected/src/plain.js b/codemods/solid-migration-assistant/tests/transform-expected/src/plain.js new file mode 100644 index 0000000..92dbdbc --- /dev/null +++ b/codemods/solid-migration-assistant/tests/transform-expected/src/plain.js @@ -0,0 +1,5 @@ +// Plain CommonJS module with legacy subpath requires. +const h = require("@solidjs/h"); +const html = require("@solidjs/html"); + +module.exports = { h, html }; diff --git a/codemods/solid-migration-assistant/tests/transform-fixture/src/negative.ts b/codemods/solid-migration-assistant/tests/transform-fixture/src/negative.ts new file mode 100644 index 0000000..ef4b66e --- /dev/null +++ b/codemods/solid-migration-assistant/tests/transform-fixture/src/negative.ts @@ -0,0 +1,36 @@ +// Negative transform coverage: every reference below must stay byte-identical. +import constructorModule from "constructor"; +import toStringModule from "toString"; +import hasOwnPropertyModule from "hasOwnProperty"; +import protoModule from "__proto__"; +export default "solid-js/h"; +import { nearH as prefixNearMiss } from "solid-js/h-extra"; +import { nearH as suffixNearMiss } from "vendor/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"; + +function require(name: string): unknown { + return name; +} +const shadowed = require("solid-js/html"); + +const viaResolve = require.resolve("solid-js/universal"); +const viaTemplate = import(`solid-js/jsx-runtime`); +const url = new URL("solid-js/jsx-dev-runtime", import.meta.url); + +void constructorModule; +void toStringModule; +void hasOwnPropertyModule; +void protoModule; +void prefixNearMiss; +void suffixNearMiss; +void escapedNearMiss; +void createStore; +void render; +void migratedH; +void shadowed; +void viaResolve; +void viaTemplate; +void url; diff --git a/codemods/solid-migration-assistant/tests/transform-fixture/src/nested/deep.jsx b/codemods/solid-migration-assistant/tests/transform-fixture/src/nested/deep.jsx new file mode 100644 index 0000000..b179971 --- /dev/null +++ b/codemods/solid-migration-assistant/tests/transform-fixture/src/nested/deep.jsx @@ -0,0 +1,11 @@ +// Nested JSX module with legacy subpath references. +import { createRenderer } from "solid-js/universal"; +import { jsx } from "solid-js/jsx-runtime"; + +export function App() { + return
nested
; +} + +const dynamic = import("solid-js/html"); +void dynamic; +void jsx; diff --git a/codemods/solid-migration-assistant/tests/transform-fixture/src/plain.js b/codemods/solid-migration-assistant/tests/transform-fixture/src/plain.js new file mode 100644 index 0000000..08dc4b6 --- /dev/null +++ b/codemods/solid-migration-assistant/tests/transform-fixture/src/plain.js @@ -0,0 +1,5 @@ +// Plain CommonJS module with legacy subpath requires. +const h = require("solid-js/h"); +const html = require("solid-js/html"); + +module.exports = { h, html }; diff --git a/codemods/solid-migration-assistant/tests/transform-rules.test.mjs b/codemods/solid-migration-assistant/tests/transform-rules.test.mjs index 6b07cc6..a929e0b 100644 --- a/codemods/solid-migration-assistant/tests/transform-rules.test.mjs +++ b/codemods/solid-migration-assistant/tests/transform-rules.test.mjs @@ -47,9 +47,9 @@ const existingFixtures = findFiles(transformationsDirectory, (name) => .map((path) => relative(packageDirectory, path)) .sort(); -if (configuredFixtures.length !== 1 || existingFixtures.length !== 1) { +if (configuredFixtures.length !== 10 || existingFixtures.length !== 10) { throw new Error( - `Expected 1 colocated transform rule fixture, found ${configuredFixtures.length} configured and ${existingFixtures.length} on disk.`, + `Expected 10 colocated transform rule fixtures, found ${configuredFixtures.length} configured and ${existingFixtures.length} on disk.`, ); } if (configuredFixtures.join("\n") !== existingFixtures.join("\n")) { diff --git a/codemods/solid-migration-assistant/tests/transform.test.mjs b/codemods/solid-migration-assistant/tests/transform.test.mjs index db6cc06..dd7b053 100644 --- a/codemods/solid-migration-assistant/tests/transform.test.mjs +++ b/codemods/solid-migration-assistant/tests/transform.test.mjs @@ -98,11 +98,11 @@ function assertRelocationReport(stderr) { [...lines].sort(), "relocation report is not in deterministic whole-string order", ); - assert.ok(lines.length >= 16, `expected at least 16 relocations, got ${lines.length}`); + assert.ok(lines.length >= 21, `expected at least 21 relocations, got ${lines.length}`); for (const line of lines) { assert.match( line, - /^src\/relocations\.tsx:\d+:\d+ Relocate solid-js\/(?:h|html|universal|jsx-runtime|jsx-dev-runtime) to @solidjs\/(?:h|html|universal|web\/jsx-runtime|web\/jsx-dev-runtime)\. Official migration guide: https:\/\/github\.com\/solidjs\/solid\/blob\/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5\/documentation\/solid-2\.0\/MIGRATION\.md#imports-where-things-live-now$/, + /^src\/[^:]+:\d+:\d+ Relocate solid-js\/(?:h|html|universal|jsx-runtime|jsx-dev-runtime) to @solidjs\/(?:h|html|universal|web\/jsx-runtime|web\/jsx-dev-runtime)\. Official migration guide: https:\/\/github\.com\/solidjs\/solid\/blob\/ff4d3c4479163fbdd3327f5b22d0c3ea7bd1a2c5\/documentation\/solid-2\.0\/MIGRATION\.md#imports-where-things-live-now$/, ); assert.doesNotMatch(line, /solid-js\/store|solid-js\/web\b/); } From d7210ecc878b186a36087a8dd75e688ffa681e7e Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Sat, 15 Aug 2026 18:35:50 -0500 Subject: [PATCH 03/16] refactor: move transform rule fixtures into a fixtures/ subfolder Rule folders stay lean: legacy-subpath-relocation now contains only the rule and its test adapter, with all 10 focused fixtures under fixtures/. Update the transform-rules harness to scan the subfolder and generalize assertRuleLayout to allow an optional fixtures subdirectory (analysis rule folders remain flat). --- .../escaped-specifiers.fixture.tsx | 0 .../{ => fixtures}/negative-forms.fixture.tsx | 0 .../{ => fixtures}/no-matches.fixture.tsx | 0 .../prototype-names.fixture.tsx | 0 .../{ => fixtures}/re-exports.fixture.tsx | 0 .../{ => fixtures}/relocations.fixture.tsx | 0 .../{ => fixtures}/runtime-forms.fixture.tsx | 0 .../shadowed-require.fixture.tsx | 0 .../{ => fixtures}/single-quotes.fixture.tsx | 0 .../{ => fixtures}/static-imports.fixture.tsx | 0 .../tests/architecture.test.mjs | 45 +++++++++++++------ .../tests/transform-rules.test.mjs | 5 ++- 12 files changed, 34 insertions(+), 16 deletions(-) rename codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/{ => fixtures}/escaped-specifiers.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/{ => fixtures}/negative-forms.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/{ => fixtures}/no-matches.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/{ => fixtures}/prototype-names.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/{ => fixtures}/re-exports.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/{ => fixtures}/relocations.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/{ => fixtures}/runtime-forms.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/{ => fixtures}/shadowed-require.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/{ => fixtures}/single-quotes.fixture.tsx (100%) rename codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/{ => fixtures}/static-imports.fixture.tsx (100%) diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/escaped-specifiers.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/escaped-specifiers.fixture.tsx similarity index 100% rename from codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/escaped-specifiers.fixture.tsx rename to codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/escaped-specifiers.fixture.tsx diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/negative-forms.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/negative-forms.fixture.tsx similarity index 100% rename from codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/negative-forms.fixture.tsx rename to codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/negative-forms.fixture.tsx diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/no-matches.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/no-matches.fixture.tsx similarity index 100% rename from codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/no-matches.fixture.tsx rename to codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/no-matches.fixture.tsx diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/prototype-names.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/prototype-names.fixture.tsx similarity index 100% rename from codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/prototype-names.fixture.tsx rename to codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/prototype-names.fixture.tsx diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/re-exports.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/re-exports.fixture.tsx similarity index 100% rename from codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/re-exports.fixture.tsx rename to codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/re-exports.fixture.tsx diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/relocations.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/relocations.fixture.tsx similarity index 100% rename from codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/relocations.fixture.tsx rename to codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/relocations.fixture.tsx diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/runtime-forms.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/runtime-forms.fixture.tsx similarity index 100% rename from codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/runtime-forms.fixture.tsx rename to codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/runtime-forms.fixture.tsx diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/shadowed-require.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/shadowed-require.fixture.tsx similarity index 100% rename from codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/shadowed-require.fixture.tsx rename to codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/shadowed-require.fixture.tsx diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/single-quotes.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/single-quotes.fixture.tsx similarity index 100% rename from codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/single-quotes.fixture.tsx rename to codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/single-quotes.fixture.tsx diff --git a/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/static-imports.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/static-imports.fixture.tsx similarity index 100% rename from codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/static-imports.fixture.tsx rename to codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/static-imports.fixture.tsx diff --git a/codemods/solid-migration-assistant/tests/architecture.test.mjs b/codemods/solid-migration-assistant/tests/architecture.test.mjs index 2bb92c7..7196df6 100644 --- a/codemods/solid-migration-assistant/tests/architecture.test.mjs +++ b/codemods/solid-migration-assistant/tests/architecture.test.mjs @@ -131,16 +131,16 @@ const EXPECTED_TRANSFORM_TESTS = [ "imports/legacy-subpath-relocation/legacy-subpath-relocation.test.ts", ]; const EXPECTED_TRANSFORM_FIXTURES = [ - "imports/legacy-subpath-relocation/escaped-specifiers.fixture.tsx", - "imports/legacy-subpath-relocation/negative-forms.fixture.tsx", - "imports/legacy-subpath-relocation/no-matches.fixture.tsx", - "imports/legacy-subpath-relocation/prototype-names.fixture.tsx", - "imports/legacy-subpath-relocation/re-exports.fixture.tsx", - "imports/legacy-subpath-relocation/relocations.fixture.tsx", - "imports/legacy-subpath-relocation/runtime-forms.fixture.tsx", - "imports/legacy-subpath-relocation/shadowed-require.fixture.tsx", - "imports/legacy-subpath-relocation/single-quotes.fixture.tsx", - "imports/legacy-subpath-relocation/static-imports.fixture.tsx", + "imports/legacy-subpath-relocation/fixtures/escaped-specifiers.fixture.tsx", + "imports/legacy-subpath-relocation/fixtures/negative-forms.fixture.tsx", + "imports/legacy-subpath-relocation/fixtures/no-matches.fixture.tsx", + "imports/legacy-subpath-relocation/fixtures/prototype-names.fixture.tsx", + "imports/legacy-subpath-relocation/fixtures/re-exports.fixture.tsx", + "imports/legacy-subpath-relocation/fixtures/relocations.fixture.tsx", + "imports/legacy-subpath-relocation/fixtures/runtime-forms.fixture.tsx", + "imports/legacy-subpath-relocation/fixtures/shadowed-require.fixture.tsx", + "imports/legacy-subpath-relocation/fixtures/single-quotes.fixture.tsx", + "imports/legacy-subpath-relocation/fixtures/static-imports.fixture.tsx", ]; const EXPECTED_TRANSFORM_FOLDERS = EXPECTED_TRANSFORM_PRODUCTION.map((path) => dirname(path), @@ -365,7 +365,9 @@ test("colocates exact transformation rule production, adapters, and fixtures", ( ruleFiles(transformationsDirectory, (name) => name.endsWith(".fixture.tsx")), EXPECTED_TRANSFORM_FIXTURES, ); - assertRuleLayout(transformationsDirectory, EXPECTED_TRANSFORM_PRODUCTION); + assertRuleLayout(transformationsDirectory, EXPECTED_TRANSFORM_PRODUCTION, { + fixturesSubdirectory: true, + }); }); test("uses normal analyzer end-to-end fixtures", () => { @@ -433,7 +435,11 @@ function visitRules(directory, baseDirectory, entries, predicate) { } } -function assertRuleLayout(directory, productionPaths) { +function assertRuleLayout( + directory, + productionPaths, + { fixturesSubdirectory = false } = {}, +) { for (const entry of ruleEntries(directory)) { assert.equal(entry.split("/").includes("__testfixtures__"), false, entry); assert.notEqual(basename(entry), "input.tsx", entry); @@ -455,7 +461,11 @@ function assertRuleLayout(directory, productionPaths) { .map((entry) => entry.name) .sort(); - assert.deepEqual(directories, [], folder); + assert.deepEqual( + directories, + fixturesSubdirectory ? ["fixtures"] : [], + folder, + ); assert.deepEqual( files.filter( (name) => name.endsWith(".ts") && !name.endsWith(".test.ts"), @@ -468,8 +478,15 @@ function assertRuleLayout(directory, productionPaths) { [`${ruleName}.test.ts`], folder, ); + const fixtureEntries = fixturesSubdirectory + ? readdirSync(resolve(directory, folder, "fixtures"), { + withFileTypes: true, + }) + : entries; assert.ok( - files.some((name) => name.endsWith(".fixture.tsx")), + fixtureEntries.some( + (entry) => entry.isFile() && entry.name.endsWith(".fixture.tsx"), + ), `${folder} must contain at least one fixture`, ); } diff --git a/codemods/solid-migration-assistant/tests/transform-rules.test.mjs b/codemods/solid-migration-assistant/tests/transform-rules.test.mjs index a929e0b..99f4274 100644 --- a/codemods/solid-migration-assistant/tests/transform-rules.test.mjs +++ b/codemods/solid-migration-assistant/tests/transform-rules.test.mjs @@ -71,9 +71,10 @@ function adapterForRule({ directory }) { } function directFixtureFiles(directory) { - return readdirSync(directory, { withFileTypes: true }) + const fixturesDirectory = resolve(directory, "fixtures"); + return readdirSync(fixturesDirectory, { withFileTypes: true }) .filter((entry) => entry.isFile() && entry.name.endsWith(".fixture.tsx")) - .map((entry) => resolve(directory, entry.name)) + .map((entry) => resolve(fixturesDirectory, entry.name)) .sort((left, right) => left.localeCompare(right)); } From b98cc9be63f4c9afbd8019cd9144ae1823cc1f63 Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Sat, 15 Aug 2026 18:47:45 -0500 Subject: [PATCH 04/16] fix: make the require-shadow scan binding-position precise The syntactic shadow scan for bare require() calls matched any identifier named require next to a binding construct, which was wrong in both directions: - false positives (safe, missed relocations): `import { require as r }`, `function f(a = require)`, and `const alias = require` counted as local require bindings even though the local binding is r, a, and alias - false negatives (unsafe, wrong edits): `const [require] = arr`, `function f([require])`, and `const { require = 1 } = obj` were not detected as bindings, so bare require("solid-js/...") calls that resolve to those local functions were rewritten The scan now matches the exact binding slot via tree-sitter fields (import_specifier.alias, parameter.pattern/value, assignment left, pair_pattern value, declarator name), so references next to bindings never count and destructuring bindings always do. `declare const/function require` remains exempt as the ambient CommonJS require. Regression coverage: new destructured-require fixture (array and object-pattern-default shadows stay byte-identical) and extended runtime-forms fixture (import alias, parameter default value, and declarator alias references no longer suppress the require relocation). Harness counts updated to 11 fixtures; verified with 12 independent workflow probes (6 relocate / 6 untouched, all correct). --- .../fixtures/destructured-require.fixture.tsx | 12 +++ .../fixtures/runtime-forms.fixture.tsx | 10 ++ .../legacy-subpath-relocation.test.ts | 6 +- .../legacy-subpath-relocation.ts | 94 +++++++++++++++---- .../tests/architecture.test.mjs | 1 + .../tests/transform-rules.test.mjs | 4 +- 6 files changed, 107 insertions(+), 20 deletions(-) create mode 100644 codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/destructured-require.fixture.tsx 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/runtime-forms.fixture.tsx b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/fixtures/runtime-forms.fixture.tsx index a1d0245..1a9ae3a 100644 --- 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 @@ -11,8 +11,18 @@ 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/legacy-subpath-relocation.test.ts b/codemods/solid-migration-assistant/rules/transformations/imports/legacy-subpath-relocation/legacy-subpath-relocation.test.ts index b2007c1..4e76ee1 100644 --- 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 @@ -83,7 +83,7 @@ const FIXTURE_CASES: Record = { { 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\nvoid dynamicH;\nvoid load;\nvoid requiredUniversal;\ntype _RuntimeTypes = RuntimeTypes;\ntype _DevTypes = DevTypes;\n", + 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: [], @@ -93,6 +93,10 @@ const FIXTURE_CASES: Record = { relocations: [], transformed: null, }, + "destructured-require": { + relocations: [], + transformed: null, + }, "prototype-names": { relocations: [ { location: "7:15", from: "solid-js/h", to: "@solidjs/h" }, 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 index eacc228..d02c0fe 100644 --- 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 @@ -52,13 +52,9 @@ function isReExportSource(source: SgNode): boolean { } /** - * 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, import, - * parameter, catch parameter, destructuring), 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. + * 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", @@ -67,15 +63,83 @@ const REQUIRE_BINDING_PARENT_KINDS = new Set([ "required_parameter", "optional_parameter", "rest_pattern", - "assignment_pattern", - "import_specifier", - "namespace_import", + "array_pattern", + "object_pattern", + "pair_pattern", "import_clause", + "namespace_import", + "import_specifier", "catch_clause", - "pair_pattern", - "object_pattern", + "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", @@ -86,10 +150,6 @@ function hasShadowingRequireBinding(rootNode: SgNode): boolean { for (const node of rootNode.findAll({ rule: { kind, regex: "^require$" }, })) { - const parent = node.parent(); - if (!parent || !REQUIRE_BINDING_PARENT_KINDS.has(parent.kind())) { - continue; - } if ( node.ancestors().some( (ancestor) => ancestor.kind() === "ambient_declaration", @@ -97,7 +157,7 @@ function hasShadowingRequireBinding(rootNode: SgNode): boolean { ) { continue; } - return true; + if (isShadowBinding(node)) return true; } } return false; diff --git a/codemods/solid-migration-assistant/tests/architecture.test.mjs b/codemods/solid-migration-assistant/tests/architecture.test.mjs index 7196df6..b2fcd51 100644 --- a/codemods/solid-migration-assistant/tests/architecture.test.mjs +++ b/codemods/solid-migration-assistant/tests/architecture.test.mjs @@ -131,6 +131,7 @@ const EXPECTED_TRANSFORM_TESTS = [ "imports/legacy-subpath-relocation/legacy-subpath-relocation.test.ts", ]; const EXPECTED_TRANSFORM_FIXTURES = [ + "imports/legacy-subpath-relocation/fixtures/destructured-require.fixture.tsx", "imports/legacy-subpath-relocation/fixtures/escaped-specifiers.fixture.tsx", "imports/legacy-subpath-relocation/fixtures/negative-forms.fixture.tsx", "imports/legacy-subpath-relocation/fixtures/no-matches.fixture.tsx", diff --git a/codemods/solid-migration-assistant/tests/transform-rules.test.mjs b/codemods/solid-migration-assistant/tests/transform-rules.test.mjs index 99f4274..cfafe02 100644 --- a/codemods/solid-migration-assistant/tests/transform-rules.test.mjs +++ b/codemods/solid-migration-assistant/tests/transform-rules.test.mjs @@ -47,9 +47,9 @@ const existingFixtures = findFiles(transformationsDirectory, (name) => .map((path) => relative(packageDirectory, path)) .sort(); -if (configuredFixtures.length !== 10 || existingFixtures.length !== 10) { +if (configuredFixtures.length !== 11 || existingFixtures.length !== 11) { throw new Error( - `Expected 10 colocated transform rule fixtures, found ${configuredFixtures.length} configured and ${existingFixtures.length} on disk.`, + `Expected 11 colocated transform rule fixtures, found ${configuredFixtures.length} configured and ${existingFixtures.length} on disk.`, ); } if (configuredFixtures.join("\n") !== existingFixtures.join("\n")) { From f295b529ab0b577e9d784abd805126acd55c95a2 Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Tue, 18 Aug 2026 23:32:39 -0500 Subject: [PATCH 05/16] build(dashboard): add portable Solid 2 browser target --- .gitignore | 3 + .../dashboard/index.html | 13 + .../dashboard/src.tsx | 15 + .../solid-migration-assistant/package.json | 16 +- .../tsconfig.dashboard.json | 33 + .../solid-migration-assistant/tsconfig.json | 17 +- .../vite.dashboard.config.ts | 17 + pnpm-lock.yaml | 1172 +++++++++++++++++ 8 files changed, 1279 insertions(+), 7 deletions(-) create mode 100644 codemods/solid-migration-assistant/dashboard/index.html create mode 100644 codemods/solid-migration-assistant/dashboard/src.tsx create mode 100644 codemods/solid-migration-assistant/tsconfig.dashboard.json create mode 100644 codemods/solid-migration-assistant/vite.dashboard.config.ts diff --git a/.gitignore b/.gitignore index 77606fa..f7ad2e1 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ Thumbs.db # Package bundles *.tar.gz *.tgz + +# Dashboard build template +codemods/solid-migration-assistant/dashboard-dist/ diff --git a/codemods/solid-migration-assistant/dashboard/index.html b/codemods/solid-migration-assistant/dashboard/index.html new file mode 100644 index 0000000..a283ac8 --- /dev/null +++ b/codemods/solid-migration-assistant/dashboard/index.html @@ -0,0 +1,13 @@ + + + + + + + Solid Migration Report + + +
+ + + diff --git a/codemods/solid-migration-assistant/dashboard/src.tsx b/codemods/solid-migration-assistant/dashboard/src.tsx new file mode 100644 index 0000000..c91073e --- /dev/null +++ b/codemods/solid-migration-assistant/dashboard/src.tsx @@ -0,0 +1,15 @@ +import { render } from "@solidjs/web"; + +function Dashboard() { + return ( +
+

Solid Migration Report

+

The dashboard build is ready for a report envelope.

+
+ ); +} + +const mount = document.getElementById("app"); +if (!mount) throw new Error("Dashboard mount element was not found."); + +render(() => , mount); diff --git a/codemods/solid-migration-assistant/package.json b/codemods/solid-migration-assistant/package.json index 2dd9a21..88b1207 100644 --- a/codemods/solid-migration-assistant/package.json +++ b/codemods/solid-migration-assistant/package.json @@ -83,19 +83,27 @@ "test:architecture": "node --test ./tests/architecture.test.mjs", "test:package": "node --test ./tests/cli.test.mjs ./tests/packaging.test.mjs", "test:workflow": "node ./tests/workflow.test.mjs", - "check-types": "tsc --noEmit", + "check-types": "tsc --noEmit && tsc --project tsconfig.dashboard.json", "validate": "node ./node_modules/codemod/codemod --disable-analytics workflow validate -w workflow.yaml && node ./node_modules/codemod/codemod --disable-analytics workflow validate -w transform.yaml", "verify": "pnpm test && pnpm check-types && pnpm validate", "test:rules": "node ./tests/rules.test.mjs", "test:transform-rules": "node ./tests/transform-rules.test.mjs", "transform": "node ./node_modules/codemod/codemod --disable-analytics workflow run -w transform.yaml --allow-dirty --no-interactive", - "test:transform": "node ./tests/transform.test.mjs" + "test:transform": "node ./tests/transform.test.mjs", + "build:dashboard": "vite build --config vite.dashboard.config.ts" }, "dependencies": { - "codemod": "1.12.13" + "@solidjs/router": "2.0.0-next.16", + "@solidjs/web": "2.0.0-rc.0", + "codemod": "1.12.13", + "solid-js": "2.0.0-rc.0" }, "devDependencies": { "@codemod.com/jssg-types": "1.6.2", - "typescript": "6.0.3" + "@solidjs/vite-plugin": "3.0.0-next.28", + "@types/node": "24.10.1", + "typescript": "6.0.3", + "vite": "8.2.1", + "vite-plugin-singlefile": "2.3.3" } } diff --git a/codemods/solid-migration-assistant/tsconfig.dashboard.json b/codemods/solid-migration-assistant/tsconfig.dashboard.json new file mode 100644 index 0000000..291c411 --- /dev/null +++ b/codemods/solid-migration-assistant/tsconfig.dashboard.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "preserve", + "jsxImportSource": "@solidjs/web", + "types": [ + "node", + "vite/client" + ], + "allowImportingTsExtensions": true, + "noEmit": true, + "verbatimModuleSyntax": true, + "strict": true, + "skipLibCheck": true, + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "noUncheckedIndexedAccess": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "dashboard/**/*.ts", + "dashboard/**/*.tsx", + "rules/**/ui.tsx", + "shared/report*.ts", + "vite.dashboard.config.ts" + ] +} diff --git a/codemods/solid-migration-assistant/tsconfig.json b/codemods/solid-migration-assistant/tsconfig.json index 51352b3..19ef600 100644 --- a/codemods/solid-migration-assistant/tsconfig.json +++ b/codemods/solid-migration-assistant/tsconfig.json @@ -2,18 +2,29 @@ "compilerOptions": { "module": "NodeNext", "moduleResolution": "NodeNext", - "types": ["@codemod.com/jssg-types"], + "types": [ + "@codemod.com/jssg-types" + ], "allowImportingTsExtensions": true, "noEmit": true, "verbatimModuleSyntax": true, "erasableSyntaxOnly": true, "strict": true, "skipLibCheck": true, - "lib": ["ESNext"], + "lib": [ + "ESNext" + ], "strictNullChecks": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "noUncheckedIndexedAccess": true }, - "exclude": ["tests", "**/*.test.ts", "**/*.fixture.tsx"] + "exclude": [ + "tests", + "**/*.test.ts", + "**/*.fixture.tsx", + "dashboard", + "dashboard-dist", + "vite.dashboard.config.ts" + ] } diff --git a/codemods/solid-migration-assistant/vite.dashboard.config.ts b/codemods/solid-migration-assistant/vite.dashboard.config.ts new file mode 100644 index 0000000..8c5b092 --- /dev/null +++ b/codemods/solid-migration-assistant/vite.dashboard.config.ts @@ -0,0 +1,17 @@ +import { resolve } from "node:path"; +import solid from "@solidjs/vite-plugin"; +import { defineConfig } from "vite"; +import { viteSingleFile } from "vite-plugin-singlefile"; + +export default defineConfig({ + root: resolve(import.meta.dirname, "dashboard"), + base: "./", + plugins: [solid(), viteSingleFile()], + build: { + target: "es2022", + outDir: resolve(import.meta.dirname, "dashboard-dist"), + emptyOutDir: true, + cssCodeSplit: false, + modulePreload: false, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0da44bc..572a805 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,19 +10,125 @@ importers: codemods/solid-migration-assistant: dependencies: + '@solidjs/router': + specifier: 2.0.0-next.16 + version: 2.0.0-next.16(@solidjs/web@2.0.0-rc.0(solid-js@2.0.0-rc.0))(solid-js@2.0.0-rc.0) + '@solidjs/web': + specifier: 2.0.0-rc.0 + version: 2.0.0-rc.0(solid-js@2.0.0-rc.0) codemod: specifier: 1.12.13 version: 1.12.13 + solid-js: + specifier: 2.0.0-rc.0 + version: 2.0.0-rc.0 devDependencies: '@codemod.com/jssg-types': specifier: 1.6.2 version: 1.6.2 + '@solidjs/vite-plugin': + specifier: 3.0.0-next.28 + version: 3.0.0-next.28(@solidjs/web@2.0.0-rc.0(solid-js@2.0.0-rc.0))(solid-js@2.0.0-rc.0)(vite@8.2.1(@types/node@24.10.1)) + '@types/node': + specifier: 24.10.1 + version: 24.10.1 typescript: specifier: 6.0.3 version: 6.0.3 + vite: + specifier: 8.2.1 + version: 8.2.1(@types/node@24.10.1) + vite-plugin-singlefile: + specifier: 2.3.3 + version: 2.3.3(vite@8.2.1(@types/node@24.10.1)) packages: + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.18.6': + resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-jsx@7.29.7': + resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + '@codemod.com/cli-darwin-arm64@1.12.13': resolution: {integrity: sha512-GFfRNIEUTBAfU7zOe8T5Blz31x22EqI/yyLkLlA8tFmLPWa76Ov3aYyGj3TrWkTH9/8cYEQEAYs+AHYXVlh0sA==} engines: {node: '>= 10'} @@ -58,22 +164,674 @@ packages: '@codemod.com/jssg-types@1.6.2': resolution: {integrity: sha512-YnKYEwyD+wi0/ahDpsSxxutVBkOON46SrZjj7Z8w2CAAGhuMmighTM6g/MNk1GGjVUj1dULWVof5ArKLSYvnzg==} + '@dom-expressions/babel-plugin-jsx@0.50.0-next.42': + resolution: {integrity: sha512-ol24x9RW8loPyOTzC/mQzh/zAsrsPxyTG4WRxxRlwzNK2uBWIBftWN5IwmSV51zDQa7JcT6sE6kkXFCLUvsYIQ==} + peerDependencies: + '@babel/core': ^7.20.12 + + '@dom-expressions/compiler-darwin-arm64@0.50.0-next.40': + resolution: {integrity: sha512-+3aXdhw4SVt08fvgAsEM56ky/wdCVPXT0Wtxo164Cchgg7sapPsRqo8Gwwn0UU5AhVcEp3CIhdB5+pMoUicKFg==} + cpu: [arm64] + os: [darwin] + + '@dom-expressions/compiler-darwin-x64@0.50.0-next.40': + resolution: {integrity: sha512-Tbjg6ZQEIhKLKGd/Ep6MKqD76arPdV6SE2d3fkrP4qooIv2gYvAkvUw90qNdZxVHcuyjutrbJE0WpXYY9nSIIQ==} + cpu: [x64] + os: [darwin] + + '@dom-expressions/compiler-linux-arm64-gnu@0.50.0-next.40': + resolution: {integrity: sha512-tIJMY8dPyjiYNSL5uc4JA5l88Sw0WoMwoAilqTTHpGYdCL5GwzGq6ZOV1bndw8XKEYFYfnTr276FDHyYpLtRYQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@dom-expressions/compiler-linux-x64-gnu@0.50.0-next.40': + resolution: {integrity: sha512-H/K/3Ykk8aCSNuKDlv/sgbPdSks+zqFxZ4mzqaGJmlDqEfeaWYXan5fsvwQbACdNDJeKoKLO4GhnlnKlKWjiJg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@dom-expressions/compiler-wasm32-wasi@0.50.0-next.40': + resolution: {integrity: sha512-2SAfc35FEvvxkUz2yeh/4aNx0s9waZnce4KS64oUMlVpYEFtNBdfnZKCB/bgzJFvTxXjMs+HcU6OInGltH5jHA==} + engines: {node: '>=14.0.0'} + + '@dom-expressions/compiler-win32-x64-msvc@0.50.0-next.40': + resolution: {integrity: sha512-bBdHMxdfUHtIrS5xrt9udqdGRnGKdYQgKxJNIts+Il3Fs8QGyNwaZpi0tjnlRjYa0+GdWEC3Gw1Pmq/HFXzFeQ==} + cpu: [x64] + os: [win32] + + '@dom-expressions/compiler@0.50.0-next.40': + resolution: {integrity: sha512-RI/kHU+QkLOHo4CQyqLTn9c7sAfl/GEULMsOpS1YqkPJgy7R8MCPWXFN4sI0QDBbM6ePtEyuW+2bsdsXqyxkzQ==} + + '@emnapi/core@1.11.2': + resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} + + '@emnapi/runtime@1.11.2': + resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@1.2.3': + resolution: {integrity: sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} + peerDependencies: + '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4 + '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4 + + '@oxc-project/types@0.144.0': + resolution: {integrity: sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==} + + '@rolldown/binding-android-arm64@1.2.4': + resolution: {integrity: sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.4': + resolution: {integrity: sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.4': + resolution: {integrity: sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.4': + resolution: {integrity: sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.4': + resolution: {integrity: sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.4': + resolution: {integrity: sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.2.4': + resolution: {integrity: sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.2.4': + resolution: {integrity: sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.2.4': + resolution: {integrity: sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.2.4': + resolution: {integrity: sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.2.4': + resolution: {integrity: sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.2.4': + resolution: {integrity: sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-win32-arm64-msvc@1.2.4': + resolution: {integrity: sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.4': + resolution: {integrity: sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@solidjs/router@2.0.0-next.16': + resolution: {integrity: sha512-sYaq5h4ISdDVZKhA1R0X4NHFg4Kao7HpK3QRfbFcE25/x2PFl4VxlHLZIoBUfv3T3L9g4GVTD376pvArwgGlDA==} + peerDependencies: + '@solidjs/web': ^2.0.0-rc.0 + solid-js: ^2.0.0-rc.0 + + '@solidjs/signals@2.0.0-rc.0': + resolution: {integrity: sha512-oKZSfvsCcKw1uJjOGbUkJ+OqlhXLHtZ+rShSyu9KH0lUH7UUwfMfsKeh81JPiQxDDg4YLhEwI38hg0JkwzTdvA==} + + '@solidjs/vite-plugin@3.0.0-next.28': + resolution: {integrity: sha512-P/Xova2R8QoveQ2szrzkHSMPZjIyc5dE4hh2UHNgRW8CC5sSoh8yzPLw7qwvKdE1DydPHUWq/hrRIApkwt+grw==} + peerDependencies: + '@solidjs/web': ^2.0.0-rc.0 + '@testing-library/jest-dom': ^5.16.6 || ^5.17.0 || ^6.* + solid-js: ^2.0.0-rc.0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@testing-library/jest-dom': + optional: true + + '@solidjs/web@2.0.0-rc.0': + resolution: {integrity: sha512-pYSaA9+dH8H1h/d/ZF/P2kR6omfzFGNcdzKhWTcg9fJghXhn8+5UrXUr2iYxDdYNOXZzxxFQhYHSJ7P4HKDqgw==} + peerDependencies: + solid-js: ^2.0.0-rc.0 + + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/node@24.10.1': + resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + + babel-preset-solid@2.0.0-rc.0: + resolution: {integrity: sha512-Ap2/QQY3pICj+Q0VM/RnIOpZo7e6icZnUA0oBJuhqzoCrljqMNo3eFb2OeEa4pUQeFREJOlex4Bt1ggwrcgC8w==} + peerDependencies: + '@babel/core': ^7.0.0 + solid-js: ^2.0.0-rc.0 + peerDependenciesMeta: + solid-js: + optional: true + + baseline-browser-mapping@2.11.15: + resolution: {integrity: sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA==} + engines: {node: '>=6.0.0'} + hasBin: true + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.8: + resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + caniuse-lite@1.0.30001809: + resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} + codemod@1.12.13: resolution: {integrity: sha512-ttBFwwjbuNS9CYsfaE84Jw/Sf7HDUpiC0TipQa74LGoVkGPkdgGcqAqQbJ9NuVY0BexEcoqQgYxNS2rYyybKjA==} engines: {node: '>= 16.0.0'} hasBin: true + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + electron-to-chromium@1.5.409: + resolution: {integrity: sha512-ChI4N44d0B4A6C8prnNjMOaGgE59fUyEVYcRYm2XEXIjMbbvF5i9UL1cblDbpGqiU0uS8FE8UcKxqZqTXdmzbQ==} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + html-entities@2.3.3: + resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + merge-anything@5.1.7: + resolution: {integrity: sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==} + engines: {node: '>=12.13'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.53: + resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} + engines: {node: '>=18'} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} + engines: {node: ^10 || ^12 || >=14} + + rolldown@1.2.4: + resolution: {integrity: sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + seroval-plugins@1.5.6: + resolution: {integrity: sha512-HXuLAX2pu/UByPpaeo/TaMfvMIi+1QqIoPJYCcAtU8QkVNwgR6MPlGuCQTErV1JwraaMbYaWVIBX7mppzGLATQ==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval@1.5.6: + resolution: {integrity: sha512-rVQVWjjSvlINzaQPZH5JFqsqEsIWdTxY3iJZCnTL/5gQbXIRooVZKI60tVCkOVfzcRPejboxO2t0P89dg5mQaA==} + engines: {node: '>=10'} + + solid-js@2.0.0-rc.0: + resolution: {integrity: sha512-3enTJ71VL69nM5p/it2InVBDBt316Cqfij+F0S7VuHZLITc8YV7Rvavjoy52nAKKxYWXM+BcXh2K7KcLTf1zdQ==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + typescript@6.0.3: resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + update-browserslist-db@1.3.1: + resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + validate-html-nesting@1.2.4: + resolution: {integrity: sha512-doQi7e8EJ2OWneSG1aZpJluS6A49aZM0+EICXWKm1i6WvqTLmq0tpUcImc4KTWG50mORO0C4YDBtOCSYvElftw==} + + vite-plugin-singlefile@2.3.3: + resolution: {integrity: sha512-XVnGH0QzbOa8fxRSsHdCarVN1BSBXNi7uLMQYlrGRN5apdHkk62XQWRJhVever0lnfuyBkwn+kvVChdm/OoOUg==} + engines: {node: '>18.0.0'} + peerDependencies: + rollup: ^4.59.0 + vite: ^5.4.21 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + rollup: + optional: true + + vite@8.2.1: + resolution: {integrity: sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.4.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.1.3: + resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + vite: + optional: true + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + snapshots: + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.8': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.8 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.18.6': + dependencies: + '@babel/types': 7.29.8 + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@babel/traverse@7.29.8': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@codemod.com/cli-darwin-arm64@1.12.13': optional: true @@ -91,6 +849,216 @@ snapshots: '@codemod.com/jssg-types@1.6.2': {} + '@dom-expressions/babel-plugin-jsx@0.50.0-next.42(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.18.6 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/types': 7.29.8 + html-entities: 2.3.3 + parse5: 7.3.0 + validate-html-nesting: 1.2.4 + + '@dom-expressions/compiler-darwin-arm64@0.50.0-next.40': + optional: true + + '@dom-expressions/compiler-darwin-x64@0.50.0-next.40': + optional: true + + '@dom-expressions/compiler-linux-arm64-gnu@0.50.0-next.40': + optional: true + + '@dom-expressions/compiler-linux-x64-gnu@0.50.0-next.40': + optional: true + + '@dom-expressions/compiler-wasm32-wasi@0.50.0-next.40': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) + optional: true + + '@dom-expressions/compiler-win32-x64-msvc@0.50.0-next.40': + optional: true + + '@dom-expressions/compiler@0.50.0-next.40': + optionalDependencies: + '@dom-expressions/compiler-darwin-arm64': 0.50.0-next.40 + '@dom-expressions/compiler-darwin-x64': 0.50.0-next.40 + '@dom-expressions/compiler-linux-arm64-gnu': 0.50.0-next.40 + '@dom-expressions/compiler-linux-x64-gnu': 0.50.0-next.40 + '@dom-expressions/compiler-wasm32-wasi': 0.50.0-next.40 + '@dom-expressions/compiler-win32-x64-msvc': 0.50.0-next.40 + + '@emnapi/core@1.11.2': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@1.2.3(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@oxc-project/types@0.144.0': {} + + '@rolldown/binding-android-arm64@1.2.4': + optional: true + + '@rolldown/binding-darwin-arm64@1.2.4': + optional: true + + '@rolldown/binding-darwin-x64@1.2.4': + optional: true + + '@rolldown/binding-freebsd-x64@1.2.4': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.2.4': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.2.4': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.2.4': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.2.4': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.2.4': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.2.4': + optional: true + + '@rolldown/binding-linux-x64-musl@1.2.4': + optional: true + + '@rolldown/binding-openharmony-arm64@1.2.4': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.2.4': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.4': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@solidjs/router@2.0.0-next.16(@solidjs/web@2.0.0-rc.0(solid-js@2.0.0-rc.0))(solid-js@2.0.0-rc.0)': + dependencies: + '@solidjs/web': 2.0.0-rc.0(solid-js@2.0.0-rc.0) + solid-js: 2.0.0-rc.0 + + '@solidjs/signals@2.0.0-rc.0': {} + + '@solidjs/vite-plugin@3.0.0-next.28(@solidjs/web@2.0.0-rc.0(solid-js@2.0.0-rc.0))(solid-js@2.0.0-rc.0)(vite@8.2.1(@types/node@24.10.1))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/core': 7.29.7 + '@dom-expressions/compiler': 0.50.0-next.40 + '@solidjs/web': 2.0.0-rc.0(solid-js@2.0.0-rc.0) + '@types/babel__core': 7.20.5 + babel-preset-solid: 2.0.0-rc.0(@babel/core@7.29.7)(solid-js@2.0.0-rc.0) + merge-anything: 5.1.7 + solid-js: 2.0.0-rc.0 + vite: 8.2.1(@types/node@24.10.1) + vitefu: 1.1.3(vite@8.2.1(@types/node@24.10.1)) + transitivePeerDependencies: + - supports-color + + '@solidjs/web@2.0.0-rc.0(solid-js@2.0.0-rc.0)': + dependencies: + seroval: 1.5.6 + seroval-plugins: 1.5.6(seroval@1.5.6) + solid-js: 2.0.0-rc.0 + + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.8 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.8 + + '@types/node@24.10.1': + dependencies: + undici-types: 7.16.0 + + babel-preset-solid@2.0.0-rc.0(@babel/core@7.29.7)(solid-js@2.0.0-rc.0): + dependencies: + '@babel/core': 7.29.7 + '@dom-expressions/babel-plugin-jsx': 0.50.0-next.42(@babel/core@7.29.7) + optionalDependencies: + solid-js: 2.0.0-rc.0 + + baseline-browser-mapping@2.11.15: {} + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.8: + dependencies: + baseline-browser-mapping: 2.11.15 + caniuse-lite: 1.0.30001809 + electron-to-chromium: 1.5.409 + node-releases: 2.0.53 + update-browserslist-db: 1.3.1(browserslist@4.28.8) + + caniuse-lite@1.0.30001809: {} + codemod@1.12.13: dependencies: detect-libc: 2.1.2 @@ -101,6 +1069,210 @@ snapshots: '@codemod.com/cli-linux-x64-gnu': 1.12.13 '@codemod.com/cli-win32-x64-msvc': 1.12.13 + convert-source-map@2.0.0: {} + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + detect-libc@2.1.2: {} + electron-to-chromium@1.5.409: {} + + entities@6.0.1: {} + + escalade@3.2.0: {} + + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + html-entities@2.3.3: {} + + is-number@7.0.0: {} + + is-what@4.1.16: {} + + js-tokens@4.0.0: {} + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + merge-anything@5.1.7: + dependencies: + is-what: 4.1.16 + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + ms@2.1.3: {} + + nanoid@3.3.18: {} + + node-releases@2.0.53: {} + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.5: {} + + postcss@8.5.26: + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + rolldown@1.2.4: + dependencies: + '@oxc-project/types': 0.144.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.2.4 + '@rolldown/binding-darwin-arm64': 1.2.4 + '@rolldown/binding-darwin-x64': 1.2.4 + '@rolldown/binding-freebsd-x64': 1.2.4 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.4 + '@rolldown/binding-linux-arm64-gnu': 1.2.4 + '@rolldown/binding-linux-arm64-musl': 1.2.4 + '@rolldown/binding-linux-ppc64-gnu': 1.2.4 + '@rolldown/binding-linux-s390x-gnu': 1.2.4 + '@rolldown/binding-linux-x64-gnu': 1.2.4 + '@rolldown/binding-linux-x64-musl': 1.2.4 + '@rolldown/binding-openharmony-arm64': 1.2.4 + '@rolldown/binding-win32-arm64-msvc': 1.2.4 + '@rolldown/binding-win32-x64-msvc': 1.2.4 + + semver@6.3.1: {} + + seroval-plugins@1.5.6(seroval@1.5.6): + dependencies: + seroval: 1.5.6 + + seroval@1.5.6: {} + + solid-js@2.0.0-rc.0: + dependencies: + '@solidjs/signals': 2.0.0-rc.0 + csstype: 3.2.3 + seroval: 1.5.6 + seroval-plugins: 1.5.6(seroval@1.5.6) + + source-map-js@1.2.1: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tslib@2.8.1: + optional: true + typescript@6.0.3: {} + + undici-types@7.16.0: {} + + update-browserslist-db@1.3.1(browserslist@4.28.8): + dependencies: + browserslist: 4.28.8 + escalade: 3.2.0 + picocolors: 1.1.1 + + validate-html-nesting@1.2.4: {} + + vite-plugin-singlefile@2.3.3(vite@8.2.1(@types/node@24.10.1)): + dependencies: + micromatch: 4.0.8 + vite: 8.2.1(@types/node@24.10.1) + + vite@8.2.1(@types/node@24.10.1): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.5 + postcss: 8.5.26 + rolldown: 1.2.4 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.10.1 + fsevents: 2.3.3 + + vitefu@1.1.3(vite@8.2.1(@types/node@24.10.1)): + optionalDependencies: + vite: 8.2.1(@types/node@24.10.1) + + yallist@3.1.1: {} From 5befedfbe56f88b56bdc4b8f92abe7a98c50f487 Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Tue, 18 Aug 2026 23:34:22 -0500 Subject: [PATCH 06/16] feat(dashboard): define opaque rule report contract --- .../solid-migration-assistant/package.json | 1 + .../shared/report.ts | 155 ++++++++++++++++++ .../tests/architecture.test.mjs | 1 - .../tests/packaging.test.mjs | 3 +- 4 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 codemods/solid-migration-assistant/shared/report.ts diff --git a/codemods/solid-migration-assistant/package.json b/codemods/solid-migration-assistant/package.json index 88b1207..029b632 100644 --- a/codemods/solid-migration-assistant/package.json +++ b/codemods/solid-migration-assistant/package.json @@ -45,6 +45,7 @@ "scripts/emit-report.ts", "scripts/transform.ts", "shared/analysis.ts", + "shared/report.ts", "shared/run-workflow.mjs", "shared/transform.ts", "transform.yaml", diff --git a/codemods/solid-migration-assistant/shared/report.ts b/codemods/solid-migration-assistant/shared/report.ts new file mode 100644 index 0000000..7c3545b --- /dev/null +++ b/codemods/solid-migration-assistant/shared/report.ts @@ -0,0 +1,155 @@ +import type { JSX } from "@solidjs/web"; + +export type JsonPrimitive = boolean | number | string | null; +export type JsonValue = + | JsonPrimitive + | readonly JsonValue[] + | { readonly [key: string]: JsonValue }; + +export const REPORT_SCHEMA_VERSION = 1 as const; +export const EMBEDDED_REPORT_ID = "solid-migration-report-data"; + +/** A single immutable run. History policy intentionally lives outside this seam. */ +export type ReportEnvelope = { + readonly schemaVersion: typeof REPORT_SCHEMA_VERSION; + readonly reports: Readonly>; +}; + +export type RuleKind = "analysis" | "transformation"; + +export type RuleRendererProps = { + readonly report: TReport; +}; + +export type RuleSliceDefinition = { + readonly id: string; + readonly route: string; + readonly title: string; + readonly kind: RuleKind; + readonly parseReport: (payload: JsonValue) => TReport; + readonly Summary: (props: RuleRendererProps) => JSX.Element; + readonly Detail: (props: RuleRendererProps) => JSX.Element; +}; + +/** + * The host-facing descriptor erases the rule payload type. Only this rule-owned + * closure parses payload fields and invokes its typed renderers. + */ +export type RuleSliceDescriptor = { + readonly id: string; + readonly route: string; + readonly title: string; + readonly kind: RuleKind; + readonly renderSummary: (payload: JsonValue) => JSX.Element; + readonly renderDetail: (payload: JsonValue) => JSX.Element; +}; + +export function defineRuleSlice( + definition: RuleSliceDefinition, +): RuleSliceDescriptor { + return Object.freeze({ + id: definition.id, + route: definition.route, + title: definition.title, + kind: definition.kind, + renderSummary: (payload: JsonValue) => + definition.Summary({ report: definition.parseReport(payload) }), + renderDetail: (payload: JsonValue) => + definition.Detail({ report: definition.parseReport(payload) }), + }); +} + +export function createRuleManifest( + descriptors: readonly RuleSliceDescriptor[], +): readonly RuleSliceDescriptor[] { + const ids = new Set(); + const routes = new Set(); + + for (const descriptor of descriptors) { + if (!descriptor.id || ids.has(descriptor.id)) { + throw new Error(`Duplicate or empty rule id: ${descriptor.id || "(empty)"}`); + } + if (!isStableRoute(descriptor.route) || routes.has(descriptor.route)) { + throw new Error(`Duplicate or invalid rule route: ${descriptor.route || "(empty)"}`); + } + ids.add(descriptor.id); + routes.add(descriptor.route); + } + + return Object.freeze([...descriptors]); +} + +function isStableRoute(route: string): boolean { + return /^[a-z0-9]+(?:[/-][a-z0-9]+)*$/.test(route); +} + +export function serializeReportEnvelope(envelope: ReportEnvelope): string { + assertReportEnvelope(envelope); + const serialized = JSON.stringify(envelope); + if (serialized === undefined) throw new Error("Report envelope is not serializable."); + return serialized + .replaceAll("&", "\u0026") + .replaceAll("<", "\u003c") + .replaceAll(">", "\u003e") + .replaceAll("
", "\u2028") + .replaceAll("
", "\u2029"); +} + +export type EmbeddedReportRoot = { + readonly getElementById: (id: string) => { + readonly tagName: string; + readonly type: string; + readonly textContent: string | null; + } | null; +}; + +export function readEmbeddedReport(root: EmbeddedReportRoot): ReportEnvelope { + const element = root.getElementById(EMBEDDED_REPORT_ID); + if (!element || element.tagName.toLowerCase() !== "script") { + throw new Error(`Missing embedded report script #${EMBEDDED_REPORT_ID}.`); + } + if (element.type !== "application/json") { + throw new Error("Embedded report script must use type application/json."); + } + + let value: unknown; + try { + value = JSON.parse(element.textContent ?? ""); + } catch (error) { + throw new Error("Embedded report JSON is invalid.", { cause: error }); + } + assertReportEnvelope(value); + return value; +} + +export function assertReportEnvelope(value: unknown): asserts value is ReportEnvelope { + if (!isPlainObject(value) || value.schemaVersion !== REPORT_SCHEMA_VERSION) { + throw new Error(`Unsupported report schema; expected version ${REPORT_SCHEMA_VERSION}.`); + } + if (!isPlainObject(value.reports)) { + throw new Error("Report envelope must contain a reports object."); + } + for (const payload of Object.values(value.reports)) assertJsonValue(payload); +} + +function assertJsonValue(value: unknown, seen = new Set()): asserts value is JsonValue { + if (value === null || typeof value === "string" || typeof value === "boolean") return; + if (typeof value === "number" && Number.isFinite(value)) return; + if (typeof value !== "object") throw new Error("Report payload contains a non-JSON value."); + if (seen.has(value)) throw new Error("Report payload contains a circular reference."); + + seen.add(value); + if (Array.isArray(value)) { + for (const item of value) assertJsonValue(item, seen); + } else { + if (!isPlainObject(value)) throw new Error("Report payload contains a non-plain object."); + for (const item of Object.values(value)) assertJsonValue(item, seen); + } + seen.delete(value); +} + +function isPlainObject(value: unknown): value is Record { + if (value === null || typeof value !== "object" || Array.isArray(value)) return false; + const prototype = Object.getPrototypeOf(value); + return prototype === Object.prototype || prototype === null; +} diff --git a/codemods/solid-migration-assistant/tests/architecture.test.mjs b/codemods/solid-migration-assistant/tests/architecture.test.mjs index b2fcd51..fb47c6d 100644 --- a/codemods/solid-migration-assistant/tests/architecture.test.mjs +++ b/codemods/solid-migration-assistant/tests/architecture.test.mjs @@ -160,7 +160,6 @@ test("ships read-only analyze and deterministic transform workflows", () => { for (const path of [ "scripts/write-report.ts", - "shared/report.ts", "shared/report-path.ts", "workflow.transform.yaml", ]) { diff --git a/codemods/solid-migration-assistant/tests/packaging.test.mjs b/codemods/solid-migration-assistant/tests/packaging.test.mjs index e78b9d2..8253c68 100644 --- a/codemods/solid-migration-assistant/tests/packaging.test.mjs +++ b/codemods/solid-migration-assistant/tests/packaging.test.mjs @@ -59,6 +59,7 @@ const expectedFiles = [ "scripts/emit-report.ts", "scripts/transform.ts", "shared/analysis.ts", + "shared/report.ts", "shared/run-workflow.mjs", "shared/transform.ts", "transform.yaml", @@ -150,7 +151,7 @@ test( ); try { - assert.equal(expectedFiles.length, 41); + assert.equal(expectedFiles.length, 42); const packDirectory = join(temporaryRoot, "pack"); mkdirSync(packDirectory); const pack = command( From f0e5211d034a7b1cc24adc796ee068938284767f Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Tue, 18 Aug 2026 23:36:20 -0500 Subject: [PATCH 07/16] feat(dashboard): render hash-routed report shell --- .../dashboard/app.tsx | 164 ++++++++++++++++++ .../dashboard/index.html | 1 + .../dashboard/manifest.ts | 4 + .../dashboard/src.tsx | 33 ++-- .../dashboard/styles.css | 33 ++++ .../shared/report.ts | 4 +- 6 files changed, 227 insertions(+), 12 deletions(-) create mode 100644 codemods/solid-migration-assistant/dashboard/app.tsx create mode 100644 codemods/solid-migration-assistant/dashboard/manifest.ts create mode 100644 codemods/solid-migration-assistant/dashboard/styles.css diff --git a/codemods/solid-migration-assistant/dashboard/app.tsx b/codemods/solid-migration-assistant/dashboard/app.tsx new file mode 100644 index 0000000..c8f5b98 --- /dev/null +++ b/codemods/solid-migration-assistant/dashboard/app.tsx @@ -0,0 +1,164 @@ +import { Errored, For, Show } from "solid-js"; +import type { JSX } from "@solidjs/web"; +import { createRouter, hashHistory, useParams } from "@solidjs/router"; +import type { + JsonValue, + ReportEnvelope, + RuleSliceDescriptor, +} from "../shared/report.ts"; + +export function createDashboardRouter( + envelope: ReportEnvelope, + manifest: readonly RuleSliceDescriptor[], +) { + function IndexPage() { + return ( +
+
+

One immutable migration run

+

Rule reports

+

Open a registered pilot slice to review its rule-owned report.

+
+ 0} + fallback={

No pilot rule reports are registered.

} + > +
+ + {(descriptor) => ( + + )} + +
+
+
+ ); + } + + function RulePage() { + const params = useParams(); + const descriptor = () => + manifest.find((entry) => entry.route === params.rule); + const payload = () => { + const entry = descriptor(); + return entry ? envelope.reports[entry.id] : undefined; + }; + const availablePayload = () => { + const report = payload(); + return report === undefined ? undefined : { report }; + }; + + return ( + }> + {(entry) => ( +
+

{entry().kind}

+

{entry().title}

+ This run has no payload for this rule.

} + > + {(available) => ( + ( +

+ This rule report could not be read: {errorMessage(error())} +

+ )} + > +
{entry().renderDetail(available().report)}
+
+ )} +
+
+ )} +
+ ); + } + + function NotFoundPage() { + return ( +
+

Page not found

+

The requested report page does not exist.

+ Return to all rule reports +
+ ); + } + + return createRouter({ + history: hashHistory(), + routes: [ + { path: "/", component: IndexPage }, + { path: "/rules/*rule", component: RulePage }, + { path: "*404", component: NotFoundPage }, + ], + }); +} + +function RuleCard(props: { + readonly descriptor: RuleSliceDescriptor; + readonly payload: JsonValue | undefined; +}) { + const availablePayload = () => + props.payload === undefined ? undefined : { report: props.payload }; + + return ( +
+

{props.descriptor.kind}

+

+ + {props.descriptor.title} + +

+ No payload in this run.

} + > + {(available) => ( + Invalid rule payload.

} + > +
+ {props.descriptor.renderSummary(available().report)} +
+
+ )} +
+
+ ); +} + +function UnknownRule(props: { readonly route: string }) { + return ( +
+

Unknown rule

+

+ {props.route || "(empty)"} is not a registered pilot rule. +

+ Return to all rule reports +
+ ); +} + +export function Shell(props: { readonly children: JSX.Element }) { + return ( + <> + +
{props.children}
+ + ); +} + +function errorMessage(error: unknown): string { + return error instanceof Error ? error.message : "Unknown report error"; +} diff --git a/codemods/solid-migration-assistant/dashboard/index.html b/codemods/solid-migration-assistant/dashboard/index.html index a283ac8..1cec4ba 100644 --- a/codemods/solid-migration-assistant/dashboard/index.html +++ b/codemods/solid-migration-assistant/dashboard/index.html @@ -7,6 +7,7 @@ Solid Migration Report +
diff --git a/codemods/solid-migration-assistant/dashboard/manifest.ts b/codemods/solid-migration-assistant/dashboard/manifest.ts new file mode 100644 index 0000000..e1643f4 --- /dev/null +++ b/codemods/solid-migration-assistant/dashboard/manifest.ts @@ -0,0 +1,4 @@ +import { createRuleManifest } from "../shared/report.ts"; + +/** Pilot slices are registered explicitly as Q9 is settled. */ +export const ruleManifest = createRuleManifest([]); diff --git a/codemods/solid-migration-assistant/dashboard/src.tsx b/codemods/solid-migration-assistant/dashboard/src.tsx index c91073e..464b114 100644 --- a/codemods/solid-migration-assistant/dashboard/src.tsx +++ b/codemods/solid-migration-assistant/dashboard/src.tsx @@ -1,15 +1,28 @@ import { render } from "@solidjs/web"; - -function Dashboard() { - return ( -
-

Solid Migration Report

-

The dashboard build is ready for a report envelope.

-
- ); -} +import { createDashboardRouter, Shell } from "./app.tsx"; +import { ruleManifest } from "./manifest.ts"; +import { readEmbeddedReport } from "../shared/report.ts"; +import "./styles.css"; const mount = document.getElementById("app"); if (!mount) throw new Error("Dashboard mount element was not found."); -render(() => , mount); +try { + const report = readEmbeddedReport(document); + const Router = createDashboardRouter(report, ruleManifest); + render( + () => {(props) => {props.children}}, + mount, + ); +} catch (error) { + const message = error instanceof Error ? error.message : "Unknown report error"; + render( + () => ( +
+

Report unavailable

+

{message}

+
+ ), + mount, + ); +} diff --git a/codemods/solid-migration-assistant/dashboard/styles.css b/codemods/solid-migration-assistant/dashboard/styles.css new file mode 100644 index 0000000..cd10b17 --- /dev/null +++ b/codemods/solid-migration-assistant/dashboard/styles.css @@ -0,0 +1,33 @@ +:root { + color-scheme: dark; + font-family: Inter, ui-sans-serif, system-ui, sans-serif; + color: #e8f0f2; + background: #0b1013; + font-synthesis: none; +} +* { box-sizing: border-box; } +body { margin: 0; min-width: 320px; min-height: 100vh; } +a { color: #75d7ff; text-underline-offset: 0.18em; } +a:hover { color: #b7ebff; } +.site-header { + display: flex; align-items: center; justify-content: space-between; gap: 2rem; + padding: 1.5rem clamp(1.25rem, 4vw, 4rem); border-bottom: 1px solid #26343b; + background: #10171b; +} +.site-header h1 { margin: 0.15rem 0 0; font-size: clamp(1.35rem, 3vw, 1.8rem); } +.site-header h1 a { color: inherit; text-decoration: none; } +.site-main { width: min(70rem, 100%); margin: 0 auto; padding: 3rem clamp(1.25rem, 4vw, 3rem) 5rem; } +h2 { margin: 0.25rem 0 0.6rem; font-size: clamp(1.8rem, 5vw, 2.7rem); } +h3 { margin: 0.15rem 0 1rem; } +p { color: #b1c0c6; line-height: 1.6; } +.eyebrow { color: #82e0a5; font-size: 0.76rem; font-weight: 750; letter-spacing: 0.12em; text-transform: uppercase; margin: 0; } +.run-badge { border: 1px solid #416052; color: #a4edbf; border-radius: 999px; padding: 0.35rem 0.7rem; white-space: nowrap; } +.page-heading { margin-bottom: 2rem; } +.report-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); gap: 1rem; } +.report-card { border: 1px solid #26343b; border-radius: 0.8rem; background: #121a1f; padding: 1.25rem; } +.rule-summary, .rule-detail { color: #d4dfe3; } +.empty-state, .error-state { border: 1px dashed #3a4b53; border-radius: 0.7rem; padding: 1rem; } +.error-state { color: #ffb4ab; border-color: #75433f; background: #281817; } +.compact { margin-bottom: 0; padding: 0.7rem; } +code { color: #c8e7f3; } +@media (max-width: 34rem) { .site-header { align-items: flex-start; flex-direction: column; gap: 0.8rem; } } diff --git a/codemods/solid-migration-assistant/shared/report.ts b/codemods/solid-migration-assistant/shared/report.ts index 7c3545b..051d3b9 100644 --- a/codemods/solid-migration-assistant/shared/report.ts +++ b/codemods/solid-migration-assistant/shared/report.ts @@ -98,8 +98,8 @@ export function serializeReportEnvelope(envelope: ReportEnvelope): string { export type EmbeddedReportRoot = { readonly getElementById: (id: string) => { readonly tagName: string; - readonly type: string; readonly textContent: string | null; + readonly getAttribute: (name: string) => string | null; } | null; }; @@ -108,7 +108,7 @@ export function readEmbeddedReport(root: EmbeddedReportRoot): ReportEnvelope { if (!element || element.tagName.toLowerCase() !== "script") { throw new Error(`Missing embedded report script #${EMBEDDED_REPORT_ID}.`); } - if (element.type !== "application/json") { + if (element.getAttribute("type") !== "application/json") { throw new Error("Embedded report script must use type application/json."); } From e9d2d88c072fc3d0e8a24def2678a0efd25be185 Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Tue, 18 Aug 2026 23:49:15 -0500 Subject: [PATCH 08/16] feat(cli): emit opt-in portable HTML reports --- .gitignore | 3 - .../assets/dashboard/index.html | 17 ++ .../solid-migration-assistant/package.json | 7 +- .../scripts/emit-report.ts | 10 + .../shared/report-artifact.mjs | 55 ++++++ .../shared/report.ts | 21 ++- .../shared/run-workflow.mjs | 173 +++++++++++++----- .../tests/architecture.test.mjs | 1 + .../tests/cli.test.mjs | 86 ++++++++- .../tests/packaging.test.mjs | 4 +- .../tests/report.test.mjs | 59 ++++++ .../solid-migration-assistant/tsconfig.json | 7 +- .../vite.dashboard.config.ts | 2 +- 13 files changed, 382 insertions(+), 63 deletions(-) create mode 100644 codemods/solid-migration-assistant/assets/dashboard/index.html create mode 100644 codemods/solid-migration-assistant/shared/report-artifact.mjs create mode 100644 codemods/solid-migration-assistant/tests/report.test.mjs diff --git a/.gitignore b/.gitignore index f7ad2e1..77606fa 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,3 @@ Thumbs.db # Package bundles *.tar.gz *.tgz - -# Dashboard build template -codemods/solid-migration-assistant/dashboard-dist/ 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..408fbc6 --- /dev/null +++ b/codemods/solid-migration-assistant/assets/dashboard/index.html @@ -0,0 +1,17 @@ + + + + + + + Solid Migration Report + + + + + +
+ + diff --git a/codemods/solid-migration-assistant/package.json b/codemods/solid-migration-assistant/package.json index 029b632..4049223 100644 --- a/codemods/solid-migration-assistant/package.json +++ b/codemods/solid-migration-assistant/package.json @@ -11,6 +11,7 @@ "files": [ "LICENSE", "README.md", + "assets/dashboard/index.html", "bin/solid-migration-assistant.mjs", "rules/analysis/imports/beta32-subpaths/beta32-subpaths.ts", "rules/analysis/imports/web-import/web-import.ts", @@ -46,6 +47,7 @@ "scripts/transform.ts", "shared/analysis.ts", "shared/report.ts", + "shared/report-artifact.mjs", "shared/run-workflow.mjs", "shared/transform.ts", "transform.yaml", @@ -80,7 +82,7 @@ "packageManager": "pnpm@11.20.0", "scripts": { "analyze": "node ./bin/solid-migration-assistant.mjs", - "test": "pnpm test:architecture && pnpm test:package && pnpm test:rules && pnpm test:transform-rules && pnpm test:workflow && pnpm test:transform", + "test": "pnpm test:architecture && pnpm test:report && pnpm test:package && pnpm test:rules && pnpm test:transform-rules && pnpm test:workflow && pnpm test:transform", "test:architecture": "node --test ./tests/architecture.test.mjs", "test:package": "node --test ./tests/cli.test.mjs ./tests/packaging.test.mjs", "test:workflow": "node ./tests/workflow.test.mjs", @@ -91,7 +93,8 @@ "test:transform-rules": "node ./tests/transform-rules.test.mjs", "transform": "node ./node_modules/codemod/codemod --disable-analytics workflow run -w transform.yaml --allow-dirty --no-interactive", "test:transform": "node ./tests/transform.test.mjs", - "build:dashboard": "vite build --config vite.dashboard.config.ts" + "build:dashboard": "vite build --config vite.dashboard.config.ts", + "test:report": "node --test ./tests/report.test.mjs" }, "dependencies": { "@solidjs/router": "2.0.0-next.16", diff --git a/codemods/solid-migration-assistant/scripts/emit-report.ts b/codemods/solid-migration-assistant/scripts/emit-report.ts index 97883f4..6174487 100644 --- a/codemods/solid-migration-assistant/scripts/emit-report.ts +++ b/codemods/solid-migration-assistant/scripts/emit-report.ts @@ -3,6 +3,8 @@ import type TSX from "codemod:ast-grep/langs/tsx"; import { acquireLock, getState, setState } from "codemod:workflow"; import { ANALYSIS_STATE_KEY } from "../shared/analysis.ts"; import { TRANSFORM_REPORT_STATE_KEY } from "../shared/transform.ts"; +import { DASHBOARD_REPORT_STATE_KEY, REPORT_SCHEMA_VERSION } from "../shared/report.ts"; +import type { JsonValue } from "../shared/report.ts"; const EMITTED_STATE_KEY = "solid-migration-assistant-report-emitted"; @@ -26,6 +28,14 @@ const emitReport: Codemod = async () => { if (blocks.length > 0) { console.log(blocks.join("\n\n")); } + + if (process.env.SOLID_MIGRATION_REPORT_MODE === "1") { + const reports = + getState>(DASHBOARD_REPORT_STATE_KEY) ?? {}; + console.log( + `__SOLID_MIGRATION_REPORT_DATA__${JSON.stringify({ schemaVersion: REPORT_SCHEMA_VERSION, reports })}`, + ); + } } finally { release(); } diff --git a/codemods/solid-migration-assistant/shared/report-artifact.mjs b/codemods/solid-migration-assistant/shared/report-artifact.mjs new file mode 100644 index 0000000..6bd6624 --- /dev/null +++ b/codemods/solid-migration-assistant/shared/report-artifact.mjs @@ -0,0 +1,55 @@ +import { + linkSync, + mkdtempSync, + renameSync, + rmSync, + unlinkSync, + writeFileSync, +} from "node:fs"; +import { dirname, join } from "node:path"; + +const EMPTY_REPORT = '{"schemaVersion":1,"reports":{}}'; + +export function renderReportHtml(template, reportData) { + const value = JSON.parse(reportData); + assertEnvelope(value); + const embedded = JSON.stringify(value) + .replaceAll("&", "\\u0026") + .replaceAll("<", "\\u003c") + .replaceAll(">", "\\u003e") + .replaceAll("
", "\\u2028") + .replaceAll("
", "\\u2029"); + const markerCount = template.split(EMPTY_REPORT).length - 1; + if (markerCount !== 1) { + throw new Error(`dashboard template must contain exactly one empty report marker; found ${markerCount}`); + } + return template.replace(EMPTY_REPORT, embedded); +} + +export function writeReportAtomically(path, html, { force = false } = {}) { + const parent = dirname(path); + const temporaryDirectory = mkdtempSync(join(parent, ".solid-migration-report-")); + const temporaryPath = join(temporaryDirectory, "report.html"); + try { + writeFileSync(temporaryPath, html, { encoding: "utf8", flag: "wx", mode: 0o600 }); + if (force) { + renameSync(temporaryPath, path); + } else { + linkSync(temporaryPath, path); + unlinkSync(temporaryPath); + } + } finally { + rmSync(temporaryDirectory, { recursive: true, force: true }); + } +} + +function assertEnvelope(value) { + if (!isPlainObject(value) || value.schemaVersion !== 1 || !isPlainObject(value.reports)) { + throw new Error("workflow returned an invalid report envelope"); + } +} +function isPlainObject(value) { + if (value === null || typeof value !== "object" || Array.isArray(value)) return false; + const prototype = Object.getPrototypeOf(value); + return prototype === Object.prototype || prototype === null; +} diff --git a/codemods/solid-migration-assistant/shared/report.ts b/codemods/solid-migration-assistant/shared/report.ts index 051d3b9..50cc670 100644 --- a/codemods/solid-migration-assistant/shared/report.ts +++ b/codemods/solid-migration-assistant/shared/report.ts @@ -8,6 +8,12 @@ export type JsonValue = export const REPORT_SCHEMA_VERSION = 1 as const; export const EMBEDDED_REPORT_ID = "solid-migration-report-data"; +export const DASHBOARD_REPORT_STATE_KEY = "solid-migration-assistant-dashboard-reports"; + +export type AnalysisRuleResult = { + readonly guidance: readonly string[]; + readonly report: TReport; +}; /** A single immutable run. History policy intentionally lives outside this seam. */ export type ReportEnvelope = { @@ -26,7 +32,6 @@ export type RuleSliceDefinition = { readonly route: string; readonly title: string; readonly kind: RuleKind; - readonly parseReport: (payload: JsonValue) => TReport; readonly Summary: (props: RuleRendererProps) => JSX.Element; readonly Detail: (props: RuleRendererProps) => JSX.Element; }; @@ -53,9 +58,9 @@ export function defineRuleSlice( title: definition.title, kind: definition.kind, renderSummary: (payload: JsonValue) => - definition.Summary({ report: definition.parseReport(payload) }), + definition.Summary({ report: payload as TReport }), renderDetail: (payload: JsonValue) => - definition.Detail({ report: definition.parseReport(payload) }), + definition.Detail({ report: payload as TReport }), }); } @@ -88,11 +93,11 @@ export function serializeReportEnvelope(envelope: ReportEnvelope): string { const serialized = JSON.stringify(envelope); if (serialized === undefined) throw new Error("Report envelope is not serializable."); return serialized - .replaceAll("&", "\u0026") - .replaceAll("<", "\u003c") - .replaceAll(">", "\u003e") - .replaceAll("
", "\u2028") - .replaceAll("
", "\u2029"); + .replaceAll("&", "\\u0026") + .replaceAll("<", "\\u003c") + .replaceAll(">", "\\u003e") + .replaceAll("
", "\\u2028") + .replaceAll("
", "\\u2029"); } export type EmbeddedReportRoot = { diff --git a/codemods/solid-migration-assistant/shared/run-workflow.mjs b/codemods/solid-migration-assistant/shared/run-workflow.mjs index d85b34a..8fd3354 100644 --- a/codemods/solid-migration-assistant/shared/run-workflow.mjs +++ b/codemods/solid-migration-assistant/shared/run-workflow.mjs @@ -1,8 +1,10 @@ import { spawnSync } from "node:child_process"; -import { readFileSync, statSync } from "node:fs"; +import { existsSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs"; import { createRequire } from "node:module"; -import { dirname, resolve } from "node:path"; +import { dirname, join, resolve } from "node:path"; +import { tmpdir } from "node:os"; import { fileURLToPath } from "node:url"; +import { renderReportHtml, writeReportAtomically } from "./report-artifact.mjs"; const packageDirectory = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const require = createRequire(import.meta.url); @@ -21,36 +23,46 @@ Analyzer: ${packageMetadata.name}@${packageMetadata.version} Reviewed migration destination: ${SOLID_TARGET} (source commit ${SOLID_SOURCE_COMMIT}) Immutable migration guide: ${MIGRATION_GUIDE} Scope: This source-only analyzer covers project-owned .js, .jsx, .ts, and .tsx source. Coverage is incomplete and advisory, makes no migration-readiness claim, and supports only the exact destination above; other Solid versions are unsupported. -Execution: Read-only. The analyzer does not run target typechecks, builds, tests, scripts, or applications and creates no target report. -Privacy and output: Analyzer telemetry is disabled and no analyzer telemetry or generated report is emitted. Codemod may retain normal workflow or task state outside the target in platform user-data directories; consult Codemod's privacy and state behavior. +Execution: Read-only. The analyzer does not run target typechecks, builds, tests, scripts, or applications and creates no target report unless the user explicitly requests one with --report FILE. +Privacy and output: Analyzer telemetry is disabled; a generated report is emitted only when explicitly requested with --report FILE. Codemod may retain normal workflow or task state outside the target in platform user-data directories; consult Codemod's privacy and state behavior. Feedback: ${FEEDBACK_URL} [solid-migration-assistant] End final disclosure`; export class CliUsageError extends Error {} -export function parseTarget(argumentsList) { - let target = "."; - let hasExplicitTarget = false; +export function parseArguments(argumentsList) { + const options = { target: ".", report: null, force: false, open: false }; + const seen = new Set(); for (let index = 0; index < argumentsList.length; index += 1) { const argument = argumentsList[index]; - if (argument !== "--target") { - throw new CliUsageError(`unknown argument: ${argument}`); - } - if (hasExplicitTarget) { - throw new CliUsageError("--target may only be specified once"); + if (argument === "--target" || argument === "--report") { + if (seen.has(argument)) throw new CliUsageError(`${argument} may only be specified once`); + const value = argumentsList[index + 1]; + if (!value || value.startsWith("--")) throw new CliUsageError(`${argument} requires a value`); + seen.add(argument); + if (argument === "--target") options.target = value; + else options.report = value; + index += 1; + continue; } - - const value = argumentsList[index + 1]; - if (!value || value.startsWith("--")) { - throw new CliUsageError("--target requires a value"); + if (argument === "--force" || argument === "--open") { + if (seen.has(argument)) throw new CliUsageError(`${argument} may only be specified once`); + seen.add(argument); + if (argument === "--force") options.force = true; + else options.open = true; + continue; } - target = value; - hasExplicitTarget = true; - index += 1; + throw new CliUsageError(`unknown argument: ${argument}`); } - return target; + if (options.force && options.report === null) throw new CliUsageError("--force requires --report FILE"); + if (options.open && options.report === null) throw new CliUsageError("--open requires --report FILE"); + return options; +} + +export function parseTarget(argumentsList) { + return parseArguments(argumentsList).target; } export function resolveCodemodLauncher() { @@ -71,28 +83,66 @@ export function buildCodemodArguments(target) { ]; } -export function runCodemod(target, { spawnImpl = spawnSync } = {}) { - return spawnImpl( +export function runCodemod( + target, + { reportDataFile, spawnImpl = spawnSync } = {}, +) { + const reportMode = Boolean(reportDataFile); + const result = spawnImpl( process.execPath, [resolveCodemodLauncher(), ...buildCodemodArguments(target)], { cwd: packageDirectory, - // Codemod routes all workflow step console output to its stderr and its - // progress envelope to stdout. Swap the child's fds so the guidance (the - // product) reaches our stdout while progress stays on our stderr. - stdio: [0, 2, 1], + // Codemod routes workflow output to stderr and progress to stdout. + // Default mode swaps those descriptors without buffering. Report mode + // captures both only long enough to remove the private data envelope, + // then forwards every existing terminal byte to the same destination. + stdio: reportMode ? [0, "pipe", "pipe"] : [0, 2, 1], + ...(reportMode + ? { + encoding: "utf8", + maxBuffer: 100 * 1024 * 1024, + env: { ...process.env, SOLID_MIGRATION_REPORT_MODE: "1" }, + } + : {}), }, ); + + if (reportMode) { + const { output, reportData } = extractReportData(result.stderr ?? ""); + if (output) process.stdout.write(output); + if (result.stdout) process.stderr.write(result.stdout); + if (reportData !== null) writeFileSync(reportDataFile, reportData, { encoding: "utf8", flag: "wx" }); + } + return result; +} + +export function extractReportData(output) { + const prefix = "__SOLID_MIGRATION_REPORT_DATA__"; + let reportData = null; + const terminalLines = []; + for (const line of output.split(/(?<=\n)/)) { + const marker = line.indexOf(prefix); + if (marker < 0) { + terminalLines.push(line); + continue; + } + if (reportData !== null) throw new Error("workflow emitted report data more than once"); + reportData = line.slice(marker + prefix.length).trimEnd(); + const before = line.slice(0, marker); + if (before.trim()) terminalLines.push(before.endsWith("\n") ? before : `${before}\n`); + } + return { output: terminalLines.join(""), reportData }; } export function main( argumentsList = process.argv.slice(2), - { cwd, runImpl = runCodemod } = {}, + { cwd, runImpl = runCodemod, openImpl = openReport } = {}, ) { let status = 1; try { - status = run(argumentsList, cwd ?? process.cwd(), runImpl); + status = run(argumentsList, cwd ?? process.cwd(), runImpl, openImpl); } catch (error) { console.error( `[solid-migration-assistant] analyzer execution failed: ${error instanceof Error ? error.message : String(error)}`, @@ -104,35 +154,72 @@ export function main( return status; } -function run(argumentsList, invocationDirectory, runImpl) { - let targetArgument; +function run(argumentsList, invocationDirectory, runImpl, openImpl) { + let options; try { - targetArgument = parseTarget(argumentsList); + options = parseArguments(argumentsList); } catch (error) { if (error instanceof CliUsageError) return fail(error.message); throw error; } - const target = resolve(invocationDirectory, targetArgument); + const target = resolve(invocationDirectory, options.target); try { - if (!statSync(target).isDirectory()) { - return fail(`target is not a directory: ${target}`); - } + if (!statSync(target).isDirectory()) return fail(`target is not a directory: ${target}`); } catch (error) { - if ( - error && - typeof error === "object" && - "code" in error && - error.code === "ENOENT" - ) { + if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") { return fail(`target does not exist: ${target}`); } throw error; } - const result = runImpl(target); + if (options.report === null) { + const result = runImpl(target); + if (result.error) throw result.error; + return result.status ?? 1; + } + + const reportPath = resolve(invocationDirectory, options.report); + if (existsSync(reportPath) && !options.force) { + return fail(`report already exists (use --force to replace it): ${reportPath}`); + } + if (existsSync(reportPath) && !statSync(reportPath).isFile()) { + return fail(`report path is not a file: ${reportPath}`); + } + + const dataDirectory = mkdtempSync(join(tmpdir(), "solid-migration-report-data-")); + const reportDataFile = join(dataDirectory, "report.json"); + try { + const result = runImpl(target, { reportDataFile }); + if (result.error) throw result.error; + const status = result.status ?? 1; + if (status !== 0) return status; + + const template = readFileSync(resolve(packageDirectory, "assets/dashboard/index.html"), "utf8"); + const reportData = readFileSync(reportDataFile, "utf8"); + const html = renderReportHtml(template, reportData); + try { + writeReportAtomically(reportPath, html, { force: options.force }); + } catch (error) { + if (error && typeof error === "object" && "code" in error && error.code === "EEXIST") { + return fail(`report already exists (use --force to replace it): ${reportPath}`); + } + throw error; + } + if (options.open) openImpl(reportPath); + return 0; + } finally { + rmSync(dataDirectory, { recursive: true, force: true }); + } +} + +export function openReport(path, { spawnImpl = spawnSync, platform = process.platform } = {}) { + const command = platform === "darwin" ? ["open", [path]] + : platform === "win32" ? ["cmd", ["/c", "start", "", path]] + : ["xdg-open", [path]]; + const result = spawnImpl(command[0], command[1], { stdio: "ignore" }); if (result.error) throw result.error; - return result.status ?? 1; + if (result.status !== 0) throw new Error(`browser launcher exited with status ${String(result.status)}`); } function fail(message) { diff --git a/codemods/solid-migration-assistant/tests/architecture.test.mjs b/codemods/solid-migration-assistant/tests/architecture.test.mjs index fb47c6d..2c452aa 100644 --- a/codemods/solid-migration-assistant/tests/architecture.test.mjs +++ b/codemods/solid-migration-assistant/tests/architecture.test.mjs @@ -377,6 +377,7 @@ test("uses normal analyzer end-to-end fixtures", () => { "empty", "fixture", "packaging.test.mjs", + "report.test.mjs", "rules.test.mjs", "transform-expected", "transform-fixture", diff --git a/codemods/solid-migration-assistant/tests/cli.test.mjs b/codemods/solid-migration-assistant/tests/cli.test.mjs index e300cfb..17aab64 100644 --- a/codemods/solid-migration-assistant/tests/cli.test.mjs +++ b/codemods/solid-migration-assistant/tests/cli.test.mjs @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import { createRequire } from "node:module"; -import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join, resolve } from "node:path"; import { test } from "node:test"; @@ -8,6 +8,8 @@ import { buildCodemodArguments, DISCLOSURE, main, + openReport, + parseArguments, parseTarget, resolveCodemodLauncher, runCodemod, @@ -19,6 +21,12 @@ const require = createRequire(import.meta.url); test("defaults the target to the invocation directory", () => { assert.equal(parseTarget([]), "."); assert.equal(parseTarget(["--target", "project"]), "project"); + assert.deepEqual(parseArguments(["--report", "report.html", "--open", "--force"]), { + target: ".", + report: "report.html", + force: true, + open: true, + }); }); test("rejects unknown, missing, and duplicate options", () => { @@ -32,6 +40,9 @@ test("rejects unknown, missing, and duplicate options", () => { () => parseTarget(["--target", "first", "--target", "second"]), /--target may only be specified once/, ); + assert.throws(() => parseArguments(["--report"]), /--report requires a value/); + assert.throws(() => parseArguments(["--force"]), /--force requires --report FILE/); + assert.throws(() => parseArguments(["--open"]), /--open requires --report FILE/); }); test("runs the pinned package-local Codemod launcher with safe flags", () => { @@ -126,7 +137,7 @@ test("publishes exact immutable public-preview disclosure", () => { DISCLOSURE, /only the exact destination above; other Solid versions are unsupported/, ); - assert.match(DISCLOSURE, /no analyzer telemetry or generated report/); + assert.match(DISCLOSURE, /generated report is emitted only when explicitly requested/); assert.match(DISCLOSURE, /Codemod may retain normal workflow or task state/); assert.match(DISCLOSURE, /github\.com\/solidjs\/solid\/blob\/ff4d3c44/); assert.match( @@ -161,6 +172,77 @@ test("ends successful, nonzero, and thrown engine invocations with disclosure", assertFinalDisclosure(thrown.diagnostics); }); +test("writes report only when explicitly requested and launches only with --open", () => { + const surface = mkdtempSync(join(tmpdir(), "sma-cli-report-test-")); + const report = join(surface, "migration.html"); + let opened = null; + try { + const noFlag = captureMain([], { + cwd: surface, + runImpl: (...args) => { + assert.equal(args.length, 1); + return { status: 0 }; + }, + openImpl: () => assert.fail("default run must not open a browser"), + }); + assert.equal(noFlag.status, 0); + assert.equal(existsSync(report), false); + + const generated = captureMain(["--report", report, "--open"], { + cwd: surface, + runImpl: (_target, { reportDataFile }) => { + writeFileSync(reportDataFile, '{"schemaVersion":1,"reports":{}}'); + return { status: 0 }; + }, + openImpl: (path) => { opened = path; }, + }); + assert.equal(generated.status, 0); + assert.equal(opened, report); + const html = readFileSync(report, "utf8"); + assert.match(html, /solid-migration-report-data/); + assert.match(html, /"schemaVersion":1/); + } finally { + rmSync(surface, { recursive: true, force: true }); + } +}); + +test("refuses report collisions unless --force is explicit", () => { + const surface = mkdtempSync(join(tmpdir(), "sma-cli-collision-test-")); + const report = join(surface, "migration.html"); + writeFileSync(report, "old"); + try { + const refused = captureMain(["--report", report], { + cwd: surface, + runImpl: () => assert.fail("collision must be rejected before analysis"), + }); + assert.equal(refused.status, 2); + assert.equal(readFileSync(report, "utf8"), "old"); + + const replaced = captureMain(["--report", report, "--force"], { + cwd: surface, + runImpl: (_target, { reportDataFile }) => { + writeFileSync(reportDataFile, '{"schemaVersion":1,"reports":{}}'); + return { status: 0 }; + }, + }); + assert.equal(replaced.status, 0); + assert.notEqual(readFileSync(report, "utf8"), "old"); + } finally { + rmSync(surface, { recursive: true, force: true }); + } +}); + +test("uses platform browser launchers", () => { + const invocations = []; + for (const [platform, executable] of [["darwin", "open"], ["linux", "xdg-open"], ["win32", "cmd"]]) { + openReport("/tmp/report.html", { + platform, + spawnImpl: (...args) => { invocations.push(args); return { status: 0 }; }, + }); + assert.equal(invocations.at(-1)[0], executable); + } +}); + test("ends usage and target failures with disclosure", () => { const surface = mkdtempSync(join(tmpdir(), "sma-cli-target-test-")); const fileTarget = join(surface, "file-target"); diff --git a/codemods/solid-migration-assistant/tests/packaging.test.mjs b/codemods/solid-migration-assistant/tests/packaging.test.mjs index 8253c68..987e00a 100644 --- a/codemods/solid-migration-assistant/tests/packaging.test.mjs +++ b/codemods/solid-migration-assistant/tests/packaging.test.mjs @@ -24,6 +24,7 @@ const packageDirectory = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const expectedFiles = [ "LICENSE", "README.md", + "assets/dashboard/index.html", "bin/solid-migration-assistant.mjs", "package.json", "rules/analysis/imports/beta32-subpaths/beta32-subpaths.ts", @@ -59,6 +60,7 @@ const expectedFiles = [ "scripts/emit-report.ts", "scripts/transform.ts", "shared/analysis.ts", + "shared/report-artifact.mjs", "shared/report.ts", "shared/run-workflow.mjs", "shared/transform.ts", @@ -151,7 +153,7 @@ test( ); try { - assert.equal(expectedFiles.length, 42); + assert.equal(expectedFiles.length, 44); const packDirectory = join(temporaryRoot, "pack"); mkdirSync(packDirectory); const pack = command( diff --git a/codemods/solid-migration-assistant/tests/report.test.mjs b/codemods/solid-migration-assistant/tests/report.test.mjs new file mode 100644 index 0000000..479a348 --- /dev/null +++ b/codemods/solid-migration-assistant/tests/report.test.mjs @@ -0,0 +1,59 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { test } from "node:test"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import ts from "typescript"; +import { renderReportHtml } from "../shared/report-artifact.mjs"; + +const packageDirectory = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const reportModule = await loadTypeScript("shared/report.ts"); + +test("escapes report JSON so the inert script cannot terminate", () => { + const envelope = { + schemaVersion: 1, + reports: { unsafe: { text: "&

" } }, + }; + const serialized = reportModule.serializeReportEnvelope(envelope); + for (const unsafe of ["<", ">", "&", "\u2028", "\u2029"]) assert.equal(serialized.includes(unsafe), false, JSON.stringify(unsafe)); + assert.deepEqual(JSON.parse(serialized), envelope); + + const template = ''; + const html = renderReportHtml(template, JSON.stringify(envelope)); + assert.equal((html.match(/ - diff --git a/codemods/solid-migration-assistant/dashboard/app.tsx b/codemods/solid-migration-assistant/dashboard/app.tsx index c8f5b98..30046c2 100644 --- a/codemods/solid-migration-assistant/dashboard/app.tsx +++ b/codemods/solid-migration-assistant/dashboard/app.tsx @@ -1,6 +1,6 @@ import { Errored, For, Show } from "solid-js"; import type { JSX } from "@solidjs/web"; -import { createRouter, hashHistory, useParams } from "@solidjs/router"; +import { createRouter, hashHistory, useHref, useParams } from "@solidjs/router"; import type { JsonValue, ReportEnvelope, @@ -11,6 +11,8 @@ export function createDashboardRouter( envelope: ReportEnvelope, manifest: readonly RuleSliceDescriptor[], ) { + const groups = groupRulesByDomain(manifest); + function IndexPage() { return (
@@ -23,13 +25,22 @@ export function createDashboardRouter( when={manifest.length > 0} fallback={

No pilot rule reports are registered.

} > -
- - {(descriptor) => ( - +
+ + {(group) => ( +
+

{group.domain}

+
+ + {(descriptor) => ( + + )} + +
+
)}
@@ -84,7 +95,7 @@ export function createDashboardRouter(

Page not found

The requested report page does not exist.

- Return to all rule reports + Return to all rule reports
); } @@ -99,6 +110,25 @@ export function createDashboardRouter( }); } +function groupRulesByDomain(manifest: readonly RuleSliceDescriptor[]) { + const groups = new Map(); + for (const descriptor of manifest) { + const rules = groups.get(descriptor.domain) ?? []; + rules.push(descriptor); + groups.set(descriptor.domain, rules); + } + return [...groups].map(([domain, rules]) => ({ + domain, + rules, + slug: domain.toLocaleLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""), + })); +} + +function InternalLink(props: { readonly href: string; readonly children: JSX.Element }) { + const href = useHref(() => props.href); + return {props.children}; +} + function RuleCard(props: { readonly descriptor: RuleSliceDescriptor; readonly payload: JsonValue | undefined; @@ -110,9 +140,9 @@ function RuleCard(props: {
); } @@ -150,7 +180,7 @@ export function Shell(props: { readonly children: JSX.Element }) { diff --git a/codemods/solid-migration-assistant/dashboard/manifest.ts b/codemods/solid-migration-assistant/dashboard/manifest.ts index e1643f4..063cc86 100644 --- a/codemods/solid-migration-assistant/dashboard/manifest.ts +++ b/codemods/solid-migration-assistant/dashboard/manifest.ts @@ -1,4 +1,7 @@ import { createRuleManifest } from "../shared/report.ts"; +import { componentRenamesSlice } from "../rules/analysis/jsx/component-renames/ui.tsx"; +import { createEffectSlice } from "../rules/analysis/reactivity/create-effect/ui.tsx"; +import { legacySubpathRelocationSlice } from "../rules/transformations/imports/legacy-subpath-relocation/ui.tsx"; +import { webImportSlice } from "../rules/analysis/imports/web-import/ui.tsx"; -/** Pilot slices are registered explicitly as Q9 is settled. */ -export const ruleManifest = createRuleManifest([]); +export const ruleManifest = createRuleManifest([webImportSlice, componentRenamesSlice, createEffectSlice, legacySubpathRelocationSlice]); diff --git a/codemods/solid-migration-assistant/dashboard/styles.css b/codemods/solid-migration-assistant/dashboard/styles.css index cd10b17..9cbb453 100644 --- a/codemods/solid-migration-assistant/dashboard/styles.css +++ b/codemods/solid-migration-assistant/dashboard/styles.css @@ -23,6 +23,8 @@ p { color: #b1c0c6; line-height: 1.6; } .eyebrow { color: #82e0a5; font-size: 0.76rem; font-weight: 750; letter-spacing: 0.12em; text-transform: uppercase; margin: 0; } .run-badge { border: 1px solid #416052; color: #a4edbf; border-radius: 999px; padding: 0.35rem 0.7rem; white-space: nowrap; } .page-heading { margin-bottom: 2rem; } +.domain-groups { display: grid; gap: 2.25rem; } +.domain-group > h3 { margin-bottom: 0.9rem; font-size: 1.25rem; } .report-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); gap: 1rem; } .report-card { border: 1px solid #26343b; border-radius: 0.8rem; background: #121a1f; padding: 1.25rem; } .rule-summary, .rule-detail { color: #d4dfe3; } @@ -31,3 +33,30 @@ p { color: #b1c0c6; line-height: 1.6; } .compact { margin-bottom: 0; padding: 0.7rem; } code { color: #c8e7f3; } @media (max-width: 34rem) { .site-header { align-items: flex-start; flex-direction: column; gap: 0.8rem; } } + +.finding-list { display: grid; gap: 1rem; padding-left: 1.25rem; } +.finding-list li { padding: 1rem; border: 1px solid #26343b; border-radius: 0.7rem; } +pre { white-space: pre-wrap; overflow-wrap: anywhere; padding: 0.8rem; border-radius: 0.55rem; background: #0a0f12; color: #cbd9df; } +.source-snippet { border-left: 3px solid #75d7ff; color: #e8f0f2; } + +.filename-filter { display: grid; gap: 0.45rem; max-width: 32rem; color: #d4dfe3; font-weight: 650; } +.filename-filter input { + width: 100%; border: 1px solid #3a4b53; border-radius: 0.55rem; + background: #0a0f12; color: #e8f0f2; padding: 0.75rem 0.85rem; font: inherit; +} +.filename-filter input:focus { border-color: #75d7ff; outline: 2px solid #75d7ff44; outline-offset: 1px; } +.result-count { margin: 0.6rem 0 1.2rem; font-size: 0.9rem; } +.zero-state { border: 1px dashed #3a4b53; border-radius: 0.7rem; padding: 1rem; } +.finding-disclosure > summary, .snippet-disclosure > summary { cursor: pointer; } +.finding-disclosure > summary { display: grid; gap: 0.35rem; } +.finding-disclosure > summary span { color: #b1c0c6; } +.copy-location, .pagination button { + margin-top: 1rem; border: 1px solid #416052; border-radius: 0.45rem; + background: #13251c; color: #a4edbf; padding: 0.5rem 0.7rem; font: inherit; cursor: pointer; +} +.copy-location:hover, .pagination button:hover:not(:disabled) { background: #1b3528; } +.snippet-disclosure { margin-top: 1rem; } +.snippet-disclosure > summary { color: #75d7ff; } +.pagination { display: flex; align-items: center; justify-content: center; gap: 1rem; margin-top: 1.5rem; } +.pagination button { margin: 0; } +.pagination button:disabled { cursor: not-allowed; opacity: 0.45; } From 5dd27a5a89369837a2cec3124e3ca1e7c3874b4d Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Wed, 19 Aug 2026 00:19:13 -0500 Subject: [PATCH 12/16] fix(dashboard): keep finding locations actionable --- .../assets/dashboard/index.html | 4 ++-- .../dashboard/finding-model.ts | 9 ++++++++ .../dashboard/finding-ui.tsx | 22 ++++++++++--------- .../dashboard/styles.css | 11 +++++----- .../rules/analysis/imports/web-import/ui.tsx | 3 ++- .../analysis/jsx/component-renames/ui.tsx | 3 ++- .../analysis/reactivity/create-effect/ui.tsx | 3 ++- .../imports/legacy-subpath-relocation/ui.tsx | 3 ++- .../tests/report.test.mjs | 7 ++++++ 9 files changed, 44 insertions(+), 21 deletions(-) diff --git a/codemods/solid-migration-assistant/assets/dashboard/index.html b/codemods/solid-migration-assistant/assets/dashboard/index.html index fdeca78..0f9f65d 100644 --- a/codemods/solid-migration-assistant/assets/dashboard/index.html +++ b/codemods/solid-migration-assistant/assets/dashboard/index.html @@ -6,8 +6,8 @@ Solid Migration Report - diff --git a/codemods/solid-migration-assistant/dashboard/finding-model.ts b/codemods/solid-migration-assistant/dashboard/finding-model.ts index da040f4..a30ae73 100644 --- a/codemods/solid-migration-assistant/dashboard/finding-model.ts +++ b/codemods/solid-migration-assistant/dashboard/finding-model.ts @@ -1,5 +1,14 @@ export const FINDINGS_PER_PAGE = 100; +export function formatFindingLocation( + filename: string, + line: number, + column: number, +): string { + const normalizedFilename = filename.replaceAll("\\", "/").replace(/^\.\/+/, ""); + return `${normalizedFilename}:${line}:${column}`; +} + export type FilterableFinding = { readonly filename: string; }; diff --git a/codemods/solid-migration-assistant/dashboard/finding-ui.tsx b/codemods/solid-migration-assistant/dashboard/finding-ui.tsx index bb3e517..5008b8a 100644 --- a/codemods/solid-migration-assistant/dashboard/finding-ui.tsx +++ b/codemods/solid-migration-assistant/dashboard/finding-ui.tsx @@ -101,18 +101,20 @@ export function RuleFindings(props: { readonly findings: readonly FindingView[] function FindingDisclosure(props: FindingView) { return ( -
- - {props.location} - {props.label} - - -
- Source context +
+
+
+ {props.location} + {props.label} +
+ +
+
+ Source and guidance
{props.snippet}
+
{props.guidance}
-
{props.guidance}
-
+ ); } diff --git a/codemods/solid-migration-assistant/dashboard/styles.css b/codemods/solid-migration-assistant/dashboard/styles.css index 9cbb453..420b28a 100644 --- a/codemods/solid-migration-assistant/dashboard/styles.css +++ b/codemods/solid-migration-assistant/dashboard/styles.css @@ -47,16 +47,17 @@ pre { white-space: pre-wrap; overflow-wrap: anywhere; padding: 0.8rem; border-ra .filename-filter input:focus { border-color: #75d7ff; outline: 2px solid #75d7ff44; outline-offset: 1px; } .result-count { margin: 0.6rem 0 1.2rem; font-size: 0.9rem; } .zero-state { border: 1px dashed #3a4b53; border-radius: 0.7rem; padding: 1rem; } -.finding-disclosure > summary, .snippet-disclosure > summary { cursor: pointer; } -.finding-disclosure > summary { display: grid; gap: 0.35rem; } -.finding-disclosure > summary span { color: #b1c0c6; } +.finding-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; } +.finding-heading > div { display: grid; gap: 0.35rem; } +.finding-heading span { color: #b1c0c6; } +.finding-heading .copy-location { flex: none; margin: 0; } +.finding-disclosure { margin-top: 1rem; } +.finding-disclosure > summary { color: #75d7ff; cursor: pointer; } .copy-location, .pagination button { margin-top: 1rem; border: 1px solid #416052; border-radius: 0.45rem; background: #13251c; color: #a4edbf; padding: 0.5rem 0.7rem; font: inherit; cursor: pointer; } .copy-location:hover, .pagination button:hover:not(:disabled) { background: #1b3528; } -.snippet-disclosure { margin-top: 1rem; } -.snippet-disclosure > summary { color: #75d7ff; } .pagination { display: flex; align-items: center; justify-content: center; gap: 1rem; margin-top: 1.5rem; } .pagination button { margin: 0; } .pagination button:disabled { cursor: not-allowed; opacity: 0.45; } diff --git a/codemods/solid-migration-assistant/rules/analysis/imports/web-import/ui.tsx b/codemods/solid-migration-assistant/rules/analysis/imports/web-import/ui.tsx index 5864728..996757b 100644 --- a/codemods/solid-migration-assistant/rules/analysis/imports/web-import/ui.tsx +++ b/codemods/solid-migration-assistant/rules/analysis/imports/web-import/ui.tsx @@ -1,4 +1,5 @@ import { RuleFindings } from "../../../../dashboard/finding-ui.tsx"; +import { formatFindingLocation } from "../../../../dashboard/finding-model.ts"; import { defineRuleSlice } from "../../../../shared/report.ts"; import { WEB_IMPORT_RULE_ID, @@ -18,7 +19,7 @@ export const webImportSlice = defineRuleSlice({ Detail: (props) => ( ({ filename: finding.filename, - location: `${finding.filename}:${finding.line}:${finding.column}`, + location: formatFindingLocation(finding.filename, finding.line, finding.column), label: finding.form, snippet: finding.snippet.text, guidance: finding.guidance, 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 index 3e9d0b8..ae5c9b9 100644 --- a/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/ui.tsx +++ b/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/ui.tsx @@ -1,4 +1,5 @@ import { RuleFindings } from "../../../../dashboard/finding-ui.tsx"; +import { formatFindingLocation } from "../../../../dashboard/finding-model.ts"; import { defineRuleSlice } from "../../../../shared/report.ts"; import { COMPONENT_RENAMES_RULE_ID, @@ -16,7 +17,7 @@ export const componentRenamesSlice = defineRuleSlice({ Detail: (props) => ( ({ filename: finding.filename, - location: `${finding.filename}:${finding.line}:${finding.column}`, + location: formatFindingLocation(finding.filename, finding.line, finding.column), label: <>{finding.legacyName}{finding.replacement}, snippet: finding.snippet.text, guidance: finding.guidance, 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 index 4c0a58e..a08a7b6 100644 --- a/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/ui.tsx +++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/ui.tsx @@ -1,4 +1,5 @@ import { RuleFindings } from "../../../../dashboard/finding-ui.tsx"; +import { formatFindingLocation } from "../../../../dashboard/finding-model.ts"; import { defineRuleSlice } from "../../../../shared/report.ts"; import { CREATE_EFFECT_RULE_ID, @@ -16,7 +17,7 @@ export const createEffectSlice = defineRuleSlice({ Detail: (props) => ( ({ filename: finding.filename, - location: `${finding.filename}:${finding.line}:${finding.column}`, + location: formatFindingLocation(finding.filename, finding.line, finding.column), label: `${finding.argumentCount} positional ${finding.argumentCount === 1 ? "argument" : "arguments"}`, snippet: finding.snippet.text, guidance: finding.guidance, 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 index 51038e4..8eafbab 100644 --- 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 @@ -1,4 +1,5 @@ import { 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, @@ -19,7 +20,7 @@ export const legacySubpathRelocationSlice =

This analyzer run only proposes these edits; it never applies them.

({ filename: finding.filename, - location: `${finding.filename}:${finding.line}:${finding.column}`, + location: formatFindingLocation(finding.filename, finding.line, finding.column), label: <>{finding.sourceModule}{finding.replacementModule} ({finding.form}), snippet: finding.snippet.text, guidance: finding.guidance, diff --git a/codemods/solid-migration-assistant/tests/report.test.mjs b/codemods/solid-migration-assistant/tests/report.test.mjs index d92f1a0..55d1150 100644 --- a/codemods/solid-migration-assistant/tests/report.test.mjs +++ b/codemods/solid-migration-assistant/tests/report.test.mjs @@ -38,6 +38,13 @@ test("rejects non-serializable report payloads", () => { ); }); +test("formats copied locations as normalized relative paths", () => { + assert.equal( + findingModel.formatFindingLocation(".\\src\\components\\Card.tsx", 12, 7), + "src/components/Card.tsx:12:7", + ); +}); + test("filters the full finding set before clamped 100-item pagination", () => { const findings = Array.from({ length: 205 }, (_, index) => ({ filename: index % 2 === 0 ? `src/alpha-${index}.tsx` : `src/beta-${index}.tsx`, From aa3b4ffbe4d570dfaf62ceb0cf7d1e4e0dbb055e Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Wed, 19 Aug 2026 00:32:14 -0500 Subject: [PATCH 13/16] feat(dashboard): add structured guidance and editor actions --- .../dashboard/app.tsx | 53 ++++++++-- .../dashboard/finding-model.ts | 41 ++++++-- .../dashboard/finding-ui.tsx | 88 +++++++++++++---- .../dashboard/src.tsx | 2 +- .../dashboard/theme.ts | 13 +++ .../analysis/imports/web-import/report.ts | 19 +++- .../rules/analysis/imports/web-import/ui.tsx | 17 +++- .../imports/web-import/web-import.test.ts | 7 +- .../analysis/imports/web-import/web-import.ts | 29 +++--- .../component-renames.test.ts | 7 +- .../component-renames/component-renames.ts | 86 +++++++++------- .../analysis/jsx/component-renames/report.ts | 23 +++-- .../analysis/jsx/component-renames/ui.tsx | 17 +++- .../create-effect/create-effect.test.ts | 7 +- .../reactivity/create-effect/create-effect.ts | 98 ++++++++++--------- .../reactivity/create-effect/report.ts | 26 ++--- .../analysis/reactivity/create-effect/ui.tsx | 17 +++- .../legacy-subpath-relocation.test.ts | 6 +- .../legacy-subpath-relocation.ts | 8 +- .../legacy-subpath-relocation/report.ts | 27 +++-- .../imports/legacy-subpath-relocation/ui.tsx | 17 +++- .../scripts/transform.ts | 3 +- .../shared/report-artifact.mjs | 15 ++- .../shared/report.ts | 25 +++-- .../shared/run-workflow.mjs | 4 +- .../tests/architecture.test.mjs | 10 ++ .../tests/cli.test.mjs | 1 + .../tests/report.test.mjs | 38 +++++-- 28 files changed, 503 insertions(+), 201 deletions(-) create mode 100644 codemods/solid-migration-assistant/dashboard/theme.ts diff --git a/codemods/solid-migration-assistant/dashboard/app.tsx b/codemods/solid-migration-assistant/dashboard/app.tsx index 30046c2..2f9bd85 100644 --- a/codemods/solid-migration-assistant/dashboard/app.tsx +++ b/codemods/solid-migration-assistant/dashboard/app.tsx @@ -1,4 +1,4 @@ -import { Errored, For, Show } from "solid-js"; +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 { @@ -6,6 +6,7 @@ import type { ReportEnvelope, RuleSliceDescriptor, } from "../shared/report.ts"; +import { applyTheme, oppositeTheme, preferredTheme, type Theme } from "./theme.ts"; export function createDashboardRouter( envelope: ReportEnvelope, @@ -36,6 +37,7 @@ export function createDashboardRouter( )} @@ -80,7 +82,7 @@ export function createDashboardRouter(

)} > -
{entry().renderDetail(available().report)}
+
{entry().renderDetail(available().report, envelope.run)}
)} @@ -132,6 +134,7 @@ function InternalLink(props: { readonly href: string; readonly children: JSX.Ele function RuleCard(props: { readonly descriptor: RuleSliceDescriptor; readonly payload: JsonValue | undefined; + readonly run: ReportEnvelope["run"]; }) { const availablePayload = () => props.payload === undefined ? undefined : { report: props.payload }; @@ -153,7 +156,7 @@ function RuleCard(props: { fallback={

Invalid rule payload.

} >
- {props.descriptor.renderSummary(available().report)} + {props.descriptor.renderSummary(available().report, props.run)}
)} @@ -174,21 +177,59 @@ function UnknownRule(props: { readonly route: string }) { ); } -export function Shell(props: { readonly children: JSX.Element }) { +export function Shell(props: { + readonly children: JSX.Element; + readonly analyzedTargetRoot: string; +}) { return ( <>
{props.children}
); } +function ThemeSwitch() { + const media = window.matchMedia("(prefers-color-scheme: dark)"); + const [theme, setTheme] = createSignal(preferredTheme(media.matches)); + let manuallySelected = false; + const followSystemTheme = (event: MediaQueryListEvent) => { + if (!manuallySelected) setTheme(preferredTheme(event.matches)); + }; + media.addEventListener("change", followSystemTheme); + onCleanup(() => media.removeEventListener("change", followSystemTheme)); + + function toggleTheme() { + manuallySelected = true; + const next = oppositeTheme(theme()); + applyTheme(next); + setTheme(next); + } + + return ( + + ); +} + function errorMessage(error: unknown): string { return error instanceof Error ? error.message : "Unknown report error"; } diff --git a/codemods/solid-migration-assistant/dashboard/finding-model.ts b/codemods/solid-migration-assistant/dashboard/finding-model.ts index a30ae73..793cf52 100644 --- a/codemods/solid-migration-assistant/dashboard/finding-model.ts +++ b/codemods/solid-migration-assistant/dashboard/finding-model.ts @@ -1,12 +1,39 @@ export const FINDINGS_PER_PAGE = 100; -export function formatFindingLocation( - filename: string, - line: number, - column: number, -): string { - const normalizedFilename = filename.replaceAll("\\", "/").replace(/^\.\/+/, ""); - return `${normalizedFilename}:${line}:${column}`; +export function normalizeRelativeFilename(filename: string): string { + return filename.replaceAll("\\", "/").replace(/^\.\/+/, ""); +} + +export function formatFindingLocation(filename: string, line: number, column: number): string { + return `${normalizeRelativeFilename(filename)}:${line}:${column}`; +} + +export type EditorTarget = { + readonly analyzedTargetRoot: string; + readonly filename: string; + readonly line: number; + readonly column: number; +}; + +export type EditorAction = { + readonly id: string; + readonly label: string; + readonly href: string; +}; + +export function createEditorActions(target: EditorTarget): readonly EditorAction[] { + return [{ id: "vscode", label: "Visual Studio Code", href: createVsCodeFileUri(target) }]; +} + +export function createVsCodeFileUri(target: EditorTarget): string { + const root = target.analyzedTargetRoot.replaceAll("\\", "/").replace(/\/$/, ""); + const relative = normalizeRelativeFilename(target.filename).replace(/^\//, ""); + const absolutePath = `${root}/${relative}`; + const encodedPath = absolutePath + .split("/") + .map((segment) => encodeURIComponent(segment).replaceAll("%3A", ":")) + .join("/"); + return `vscode://file/${encodedPath}:${target.line}:${target.column}`; } export type FilterableFinding = { diff --git a/codemods/solid-migration-assistant/dashboard/finding-ui.tsx b/codemods/solid-migration-assistant/dashboard/finding-ui.tsx index 5008b8a..b375388 100644 --- a/codemods/solid-migration-assistant/dashboard/finding-ui.tsx +++ b/codemods/solid-migration-assistant/dashboard/finding-ui.tsx @@ -2,8 +2,11 @@ import type { JSX } from "@solidjs/web"; import { useLocation } from "@solidjs/router"; import { For, Show, createEffect, createMemo, createSignal, onCleanup } from "solid-js"; import { + createEditorActions, filterFindings, + formatFindingLocation, paginateFindings, + type EditorTarget, } from "./finding-model.ts"; export type FindingView = { @@ -11,9 +14,46 @@ export type FindingView = { readonly location: string; readonly label: JSX.Element; readonly snippet: string; - readonly guidance: string; + readonly details: JSX.Element; + readonly editorTarget: EditorTarget; }; +export function GuidanceSections(props: { + readonly summary: string; + readonly reason: string; + readonly nextSteps: readonly string[]; + readonly cautions: readonly string[]; + readonly validation: readonly string[]; + readonly officialGuideUrl: string; +}) { + return ( +
+

{props.summary}

+
+

Why this was flagged

+

{props.reason}

+
+
+

Next steps

+
    {(step) =>
  1. {step}
  2. }
+
+ 0}> +
+

Caution Stop conditions

+
    {(caution) =>
  • {caution}
  • }
+
+
+ 0}> +
+

Validate

+
    {(item) =>
  • {item}
  • }
+
+
+

Open the official Solid 2 migration guide

+
+ ); +} + export function RuleFindings(props: { readonly findings: readonly FindingView[] }) { const location = useLocation(); const [requestedPage, setRequestedPage] = createSignal(1); @@ -107,36 +147,48 @@ function FindingDisclosure(props: FindingView) { {props.location} {props.label} - +
Source and guidance -
{props.snippet}
-
{props.guidance}
+

Source context

+
{props.snippet}
+ {props.details}
); } +function EditorActionMenu(props: { readonly target: EditorTarget }) { + return ( +
+ Open in editor + +
+ ); +} + function CopyLocationButton(props: { readonly location: string }) { - const [copied, setCopied] = createSignal(false); + const [status, setStatus] = createSignal<"idle" | "copied" | "failed">("idle"); let resetTimer: ReturnType | undefined; - onCleanup(() => { - if (resetTimer !== undefined) clearTimeout(resetTimer); - }); - + onCleanup(() => { if (resetTimer !== undefined) clearTimeout(resetTimer); }); async function copyLocation() { - await copyText(props.location); - setCopied(true); + try { + await copyText(props.location); + setStatus("copied"); + } catch { + setStatus("failed"); + } if (resetTimer !== undefined) clearTimeout(resetTimer); - resetTimer = setTimeout(() => setCopied(false), 1_500); + resetTimer = setTimeout(() => setStatus("idle"), 1_500); } - - return ( - - ); + const label = () => status() === "copied" ? "Copied" : status() === "failed" ? "Copy failed" : "Copy location"; + return ; } async function copyText(value: string): Promise { diff --git a/codemods/solid-migration-assistant/dashboard/src.tsx b/codemods/solid-migration-assistant/dashboard/src.tsx index 464b114..095be2c 100644 --- a/codemods/solid-migration-assistant/dashboard/src.tsx +++ b/codemods/solid-migration-assistant/dashboard/src.tsx @@ -11,7 +11,7 @@ try { const report = readEmbeddedReport(document); const Router = createDashboardRouter(report, ruleManifest); render( - () => {(props) => {props.children}}, + () => {(props) => {props.children}}, mount, ); } catch (error) { diff --git a/codemods/solid-migration-assistant/dashboard/theme.ts b/codemods/solid-migration-assistant/dashboard/theme.ts new file mode 100644 index 0000000..a9fa921 --- /dev/null +++ b/codemods/solid-migration-assistant/dashboard/theme.ts @@ -0,0 +1,13 @@ +export type Theme = "light" | "dark"; + +export function preferredTheme(prefersDark: boolean): Theme { + return prefersDark ? "dark" : "light"; +} + +export function oppositeTheme(theme: Theme): Theme { + return theme === "dark" ? "light" : "dark"; +} + +export function applyTheme(theme: Theme, root: HTMLElement = document.documentElement): void { + root.dataset.theme = theme; +} diff --git a/codemods/solid-migration-assistant/rules/analysis/imports/web-import/report.ts b/codemods/solid-migration-assistant/rules/analysis/imports/web-import/report.ts index 178869a..b14cef1 100644 --- a/codemods/solid-migration-assistant/rules/analysis/imports/web-import/report.ts +++ b/codemods/solid-migration-assistant/rules/analysis/imports/web-import/report.ts @@ -7,12 +7,22 @@ export type WebImportFinding = { readonly line: number; readonly column: number; readonly form: WebImportForm; - readonly guidance: 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 WebImportReport = { - readonly findings: readonly WebImportFinding[]; -}; +export type WebImportReport = { readonly findings: readonly WebImportFinding[] }; + +export function formatWebImportGuidance(finding: WebImportFinding): 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 webImportReportAggregator = defineRuleReportAggregator({ id: WEB_IMPORT_RULE_ID, emptyReport: () => ({ findings: [] }), @@ -20,7 +30,6 @@ export const webImportReportAggregator = defineRuleReportAggregator right.filename ? 1 : 0; return filenameOrder || left.line - right.line || left.column - right.column; diff --git a/codemods/solid-migration-assistant/rules/analysis/imports/web-import/ui.tsx b/codemods/solid-migration-assistant/rules/analysis/imports/web-import/ui.tsx index 996757b..95b947d 100644 --- a/codemods/solid-migration-assistant/rules/analysis/imports/web-import/ui.tsx +++ b/codemods/solid-migration-assistant/rules/analysis/imports/web-import/ui.tsx @@ -1,4 +1,4 @@ -import { RuleFindings } from "../../../../dashboard/finding-ui.tsx"; +import { GuidanceSections, RuleFindings } from "../../../../dashboard/finding-ui.tsx"; import { formatFindingLocation } from "../../../../dashboard/finding-model.ts"; import { defineRuleSlice } from "../../../../shared/report.ts"; import { @@ -22,7 +22,20 @@ export const webImportSlice = defineRuleSlice({ location: formatFindingLocation(finding.filename, finding.line, finding.column), label: finding.form, snippet: finding.snippet.text, - guidance: finding.guidance, + editorTarget: { + analyzedTargetRoot: props.run.analyzedTargetRoot, + filename: finding.filename, + line: finding.line, + column: finding.column, + }, + details: , }))} /> ), }); diff --git a/codemods/solid-migration-assistant/rules/analysis/imports/web-import/web-import.test.ts b/codemods/solid-migration-assistant/rules/analysis/imports/web-import/web-import.test.ts index c6ae184..c0b43b0 100644 --- a/codemods/solid-migration-assistant/rules/analysis/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"; @@ -66,7 +67,11 @@ Guidance:${formGuidance} Make and validate that edit yourself; this analyzer nev } - for (const finding of report.findings) { + 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.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}`); 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 index 6717943..0d199f4 100644 --- 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 @@ -3,7 +3,7 @@ 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 type { WebImportFinding, WebImportReport } from "./report.ts"; +import { formatWebImportGuidance, type WebImportFinding, type WebImportReport } from "./report.ts"; const LEGACY_WEB_MODULE = "solid-js/web"; const MIGRATION_GUIDE = @@ -17,14 +17,10 @@ const FORM_LABELS: Record = { }; 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.", + 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( @@ -35,17 +31,22 @@ export function analyzeWebImport( .filter((ref) => ref.moduleName === LEGACY_WEB_MODULE) .map(({ source, form }) => { const start = source.range().start; - const guidance = `${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}`; return { filename: context.filename, line: start.line + 1, column: start.column + 1, form, - guidance, + 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((finding) => finding.guidance), report: { findings } }; + return { guidance: findings.map(formatWebImportGuidance), report: { findings } }; } diff --git a/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/component-renames.test.ts b/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/component-renames.test.ts index babd40c..04ed90b 100644 --- a/codemods/solid-migration-assistant/rules/analysis/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"; @@ -48,7 +49,11 @@ Guidance: Read the complete list site, its each value, child callback, props, an } - for (const finding of report.findings) { + 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.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}`); 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 index f34069d..e70e898 100644 --- 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 @@ -2,7 +2,7 @@ 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 type { ComponentRenameFinding, ComponentRenamesReport } from "./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"; @@ -49,11 +49,11 @@ export function analyzeJsxComponentRenames( column: start.column + 1, legacyName, replacement: COMPONENT_MIGRATIONS[legacyName], - guidance: componentGuidance(element, filename, legacyName), + ...componentContent(legacyName), snippet: sourceSnippet(element), }; }); - return { guidance: findings.map((finding) => finding.guidance), report: { findings } }; + return { guidance: findings.map(formatComponentRenameGuidance), report: { findings } }; } function findImportedComponentSites(rootNode: SgNode): ComponentSite[] { @@ -107,34 +107,54 @@ function importedLegacyName(specifier: SgNode): LegacyComponent | null { 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}`; +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/analysis/jsx/component-renames/report.ts b/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/report.ts index 04e16ff..b5e04e8 100644 --- a/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/report.ts +++ b/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/report.ts @@ -8,20 +8,27 @@ export type ComponentRenameFinding = { readonly column: number; readonly legacyName: LegacyComponentName; readonly replacement: string; - readonly guidance: 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 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), - }), + 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 index ae5c9b9..94db471 100644 --- a/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/ui.tsx +++ b/codemods/solid-migration-assistant/rules/analysis/jsx/component-renames/ui.tsx @@ -1,4 +1,4 @@ -import { RuleFindings } from "../../../../dashboard/finding-ui.tsx"; +import { GuidanceSections, RuleFindings } from "../../../../dashboard/finding-ui.tsx"; import { formatFindingLocation } from "../../../../dashboard/finding-model.ts"; import { defineRuleSlice } from "../../../../shared/report.ts"; import { @@ -20,7 +20,20 @@ export const componentRenamesSlice = defineRuleSlice({ location: formatFindingLocation(finding.filename, finding.line, finding.column), label: <>{finding.legacyName}{finding.replacement}, snippet: finding.snippet.text, - guidance: finding.guidance, + editorTarget: { + analyzedTargetRoot: props.run.analyzedTargetRoot, + filename: finding.filename, + line: finding.line, + column: finding.column, + }, + details: , }))} /> ), }); diff --git a/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/create-effect.test.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/create-effect.test.ts index aec0ea2..0d26cc0 100644 --- a/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/create-effect.test.ts +++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/create-effect.test.ts @@ -1,6 +1,7 @@ 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"; @@ -79,7 +80,11 @@ const testCreateEffectRule: Codemod = async (root) => { } - for (const finding of report.findings) { + 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.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}`); 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 index cf3c04d..0571eba 100644 --- 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 @@ -2,57 +2,63 @@ 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 type { CreateEffectFinding, CreateEffectReport } from "./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 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 = findImportedCalls(rootNode, ["solid-js", "solid-js/web"], "createEffect") - .filter( - ({ argumentNodes }) => - !argumentNodes.some((a) => a.kind() === "spread_element"), - ) +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 argCount = argumentNodes.length; - - if (argCount === 0) { - return createEffectFinding(`${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}`, filename, start.line + 1, start.column + 1, argCount, sourceSnippet(call)); - } - - if (argCount === 1) { - return createEffectFinding(`${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}`, filename, start.line + 1, start.column + 1, argCount, sourceSnippet(call)); - } - - if (argCount === 2) { - return createEffectFinding(`${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}`, filename, start.line + 1, start.column + 1, argCount, sourceSnippet(call)); - } - - // argCount >= 3 - return createEffectFinding(`${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}`, filename, start.line + 1, start.column + 1, argCount, sourceSnippet(call)); + const argumentCount = argumentNodes.length; + return { + filename, line: start.line + 1, column: start.column + 1, argumentCount, + ...contentForArgumentCount(argumentCount), snippet: sourceSnippet(call), + }; }); - return { guidance: findings.map((finding) => finding.guidance), report: { findings } }; + return { guidance: findings.map(formatCreateEffectGuidance), report: { findings } }; } -function createEffectFinding( - guidance: string, - filename: string, - line: number, - column: number, - argumentCount: number, - snippet: CreateEffectFinding["snippet"], -): CreateEffectFinding { - return { filename, line, column, argumentCount, guidance, snippet }; +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/analysis/reactivity/create-effect/report.ts b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/report.ts index 2ecb4cb..c862f6f 100644 --- a/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/report.ts +++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/report.ts @@ -2,24 +2,24 @@ import { defineRuleReportAggregator, type SourceSnippet } from "../../../../shar 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 filename: string; readonly line: number; readonly column: number; readonly argumentCount: number; - readonly guidance: 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 CreateEffectReport = { - readonly findings: readonly CreateEffectFinding[]; -}; +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), - }), + 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 index a08a7b6..9f0a0a5 100644 --- a/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/ui.tsx +++ b/codemods/solid-migration-assistant/rules/analysis/reactivity/create-effect/ui.tsx @@ -1,4 +1,4 @@ -import { RuleFindings } from "../../../../dashboard/finding-ui.tsx"; +import { GuidanceSections, RuleFindings } from "../../../../dashboard/finding-ui.tsx"; import { formatFindingLocation } from "../../../../dashboard/finding-model.ts"; import { defineRuleSlice } from "../../../../shared/report.ts"; import { @@ -20,7 +20,20 @@ export const createEffectSlice = defineRuleSlice({ location: formatFindingLocation(finding.filename, finding.line, finding.column), label: `${finding.argumentCount} positional ${finding.argumentCount === 1 ? "argument" : "arguments"}`, snippet: finding.snippet.text, - guidance: finding.guidance, + editorTarget: { + analyzedTargetRoot: props.run.analyzedTargetRoot, + filename: finding.filename, + line: finding.line, + column: finding.column, + }, + details: , }))} /> ), }); 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 index 23bd9f0..b93caae 100644 --- 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 @@ -4,6 +4,7 @@ import { relocateLegacySubpaths, TRANSFORM_MIGRATION_GUIDE, } from "./legacy-subpath-relocation.ts"; +import { formatLegacySubpathRelocationGuidance } from "./report.ts"; type FixtureCase = { relocations: ReadonlyArray<{ @@ -142,7 +143,10 @@ const testLegacySubpathRelocation: Codemod = async (root) => { ) .sort(); - const actual = report.findings.map((finding) => finding.guidance).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"); + } + 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")}`, 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 index 9116b50..b6ffec8 100644 --- 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 @@ -182,7 +182,6 @@ export function relocateLegacySubpaths( if (form === "require" && shadowedRequire) return null; const start = source.range().start; const quote = source.text()[0]; - const guidance = `${filename}:${start.line + 1}:${start.column + 1} Relocate ${moduleName} to ${replacement}. Official migration guide: ${TRANSFORM_MIGRATION_GUIDE}`; const finding: LegacySubpathRelocationFinding = { filename, line: start.line + 1, @@ -190,7 +189,12 @@ export function relocateLegacySubpaths( form, sourceModule: moduleName, replacementModule: replacement, - guidance, + 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 }; 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 index c5a2331..4abc508 100644 --- 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 @@ -3,26 +3,21 @@ export const LEGACY_SUBPATH_RELOCATION_RULE_ID = "transformation/imports/legacy- 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 guidance: string; + 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 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), - }), + 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 index 8eafbab..5d99414 100644 --- 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 @@ -1,4 +1,4 @@ -import { RuleFindings } from "../../../../dashboard/finding-ui.tsx"; +import { GuidanceSections, RuleFindings } from "../../../../dashboard/finding-ui.tsx"; import { formatFindingLocation } from "../../../../dashboard/finding-model.ts"; import { defineRuleSlice } from "../../../../shared/report.ts"; import { @@ -23,7 +23,20 @@ export const legacySubpathRelocationSlice = location: formatFindingLocation(finding.filename, finding.line, finding.column), label: <>{finding.sourceModule}{finding.replacementModule} ({finding.form}), snippet: finding.snippet.text, - guidance: finding.guidance, + editorTarget: { + analyzedTargetRoot: props.run.analyzedTargetRoot, + filename: finding.filename, + line: finding.line, + column: finding.column, + }, + details: , }))} /> ), diff --git a/codemods/solid-migration-assistant/scripts/transform.ts b/codemods/solid-migration-assistant/scripts/transform.ts index 4ce2d76..0d19dcc 100644 --- a/codemods/solid-migration-assistant/scripts/transform.ts +++ b/codemods/solid-migration-assistant/scripts/transform.ts @@ -2,6 +2,7 @@ import type { Codemod } from "codemod:ast-grep"; import type TSX from "codemod:ast-grep/langs/tsx"; import { acquireLock, getState, setState } from "codemod:workflow"; import { relocateLegacySubpaths } from "../rules/transformations/imports/legacy-subpath-relocation/legacy-subpath-relocation.ts"; +import { formatLegacySubpathRelocationGuidance } from "../rules/transformations/imports/legacy-subpath-relocation/report.ts"; import { TRANSFORM_REPORT_STATE_KEY } from "../shared/transform.ts"; const transform: Codemod = async (root) => { @@ -15,7 +16,7 @@ const transform: Codemod = async (root) => { try { const accumulated = getState(TRANSFORM_REPORT_STATE_KEY) ?? []; setState(TRANSFORM_REPORT_STATE_KEY, [ - ...new Set([...accumulated, ...report.findings.map((finding) => finding.guidance)]), + ...new Set([...accumulated, ...report.findings.map(formatLegacySubpathRelocationGuidance)]), ]); } finally { release(); diff --git a/codemods/solid-migration-assistant/shared/report-artifact.mjs b/codemods/solid-migration-assistant/shared/report-artifact.mjs index 6bd6624..0d02183 100644 --- a/codemods/solid-migration-assistant/shared/report-artifact.mjs +++ b/codemods/solid-migration-assistant/shared/report-artifact.mjs @@ -10,9 +10,13 @@ import { dirname, join } from "node:path"; const EMPTY_REPORT = '{"schemaVersion":1,"reports":{}}'; -export function renderReportHtml(template, reportData) { - const value = JSON.parse(reportData); - assertEnvelope(value); +export function renderReportHtml(template, reportData, { analyzedTargetRoot } = {}) { + const workflowValue = JSON.parse(reportData); + assertWorkflowEnvelope(workflowValue); + if (!isAbsoluteTargetRoot(analyzedTargetRoot)) { + throw new Error("report generation requires an absolute analyzed target root"); + } + const value = { ...workflowValue, run: { analyzedTargetRoot } }; const embedded = JSON.stringify(value) .replaceAll("&", "\\u0026") .replaceAll("<", "\\u003c") @@ -43,11 +47,14 @@ export function writeReportAtomically(path, html, { force = false } = {}) { } } -function assertEnvelope(value) { +function assertWorkflowEnvelope(value) { if (!isPlainObject(value) || value.schemaVersion !== 1 || !isPlainObject(value.reports)) { throw new Error("workflow returned an invalid report envelope"); } } +function isAbsoluteTargetRoot(value) { + return typeof value === "string" && (value.startsWith("/") || /^[A-Za-z]:[\\/]/.test(value)); +} function isPlainObject(value) { if (value === null || typeof value !== "object" || Array.isArray(value)) return false; const prototype = Object.getPrototypeOf(value); diff --git a/codemods/solid-migration-assistant/shared/report.ts b/codemods/solid-migration-assistant/shared/report.ts index 1c23104..2a67217 100644 --- a/codemods/solid-migration-assistant/shared/report.ts +++ b/codemods/solid-migration-assistant/shared/report.ts @@ -22,8 +22,13 @@ export type AnalysisRuleResult = { }; /** A single immutable run. History policy intentionally lives outside this seam. */ +export type ReportRunMetadata = { + readonly analyzedTargetRoot: string; +}; + export type ReportEnvelope = { readonly schemaVersion: typeof REPORT_SCHEMA_VERSION; + readonly run: ReportRunMetadata; readonly reports: Readonly>; }; @@ -64,6 +69,7 @@ export type RuleKind = "analysis" | "transformation"; export type RuleRendererProps = { readonly report: TReport; + readonly run: ReportRunMetadata; }; export type RuleSliceDefinition = { @@ -86,8 +92,8 @@ export type RuleSliceDescriptor = { readonly title: string; readonly domain: string; readonly kind: RuleKind; - readonly renderSummary: (payload: JsonValue) => JSX.Element; - readonly renderDetail: (payload: JsonValue) => JSX.Element; + readonly renderSummary: (payload: JsonValue, run: ReportRunMetadata) => JSX.Element; + readonly renderDetail: (payload: JsonValue, run: ReportRunMetadata) => JSX.Element; }; export function defineRuleSlice( @@ -99,10 +105,10 @@ export function defineRuleSlice( title: definition.title, domain: definition.domain, kind: definition.kind, - renderSummary: (payload: JsonValue) => - definition.Summary({ report: payload as TReport }), - renderDetail: (payload: JsonValue) => - definition.Detail({ report: payload as TReport }), + renderSummary: (payload: JsonValue, run: ReportRunMetadata) => + definition.Summary({ report: payload as TReport, run }), + renderDetail: (payload: JsonValue, run: ReportRunMetadata) => + definition.Detail({ report: payload as TReport, run }), }); } @@ -193,6 +199,9 @@ export function assertReportEnvelope(value: unknown): asserts value is ReportEnv if (!isPlainObject(value) || value.schemaVersion !== REPORT_SCHEMA_VERSION) { throw new Error(`Unsupported report schema; expected version ${REPORT_SCHEMA_VERSION}.`); } + if (!isPlainObject(value.run) || typeof value.run.analyzedTargetRoot !== "string" || !isAbsoluteTargetRoot(value.run.analyzedTargetRoot)) { + throw new Error("Report envelope must contain an absolute analyzed target root."); + } if (!isPlainObject(value.reports)) { throw new Error("Report envelope must contain a reports object."); } @@ -215,6 +224,10 @@ function assertJsonValue(value: unknown, seen = new Set()): asserts valu seen.delete(value); } +function isAbsoluteTargetRoot(value: string): boolean { + return value.startsWith("/") || /^[A-Za-z]:[\\/]/.test(value); +} + function isPlainObject(value: unknown): value is Record { if (value === null || typeof value !== "object" || Array.isArray(value)) return false; const prototype = Object.getPrototypeOf(value); diff --git a/codemods/solid-migration-assistant/shared/run-workflow.mjs b/codemods/solid-migration-assistant/shared/run-workflow.mjs index bf7a4b2..3a22b9a 100644 --- a/codemods/solid-migration-assistant/shared/run-workflow.mjs +++ b/codemods/solid-migration-assistant/shared/run-workflow.mjs @@ -24,7 +24,7 @@ Reviewed migration destination: ${SOLID_TARGET} (source commit ${SOLID_SOURCE_CO Immutable migration guide: ${MIGRATION_GUIDE} Scope: This source-only analyzer covers project-owned .js, .jsx, .ts, and .tsx source. Coverage is incomplete and advisory, makes no migration-readiness claim, and supports only the exact destination above; other Solid versions are unsupported. Execution: Read-only. The analyzer does not run target typechecks, builds, tests, scripts, or applications and creates no target report unless the user explicitly requests one with --report FILE. -Privacy and output: Analyzer telemetry is disabled; a generated report is emitted only when explicitly requested with --report FILE. Every generated HTML contains bounded project source snippets; treat and share it as project source. Codemod may retain normal workflow or task state outside the target in platform user-data directories; consult Codemod's privacy and state behavior. +Privacy and output: Analyzer telemetry is disabled; a generated report is emitted only when explicitly requested with --report FILE. Every generated HTML contains bounded project source snippets and the absolute analyzed target path; treat and share it as project source and local machine metadata. Codemod may retain normal workflow or task state outside the target in platform user-data directories; consult Codemod's privacy and state behavior. Feedback: ${FEEDBACK_URL} [solid-migration-assistant] End final disclosure`; @@ -197,7 +197,7 @@ function run(argumentsList, invocationDirectory, runImpl, openImpl) { const template = readFileSync(resolve(packageDirectory, "assets/dashboard/index.html"), "utf8"); const reportData = readFileSync(reportDataFile, "utf8"); - const html = renderReportHtml(template, reportData); + const html = renderReportHtml(template, reportData, { analyzedTargetRoot: target }); try { writeReportAtomically(reportPath, html, { force: options.force }); } catch (error) { diff --git a/codemods/solid-migration-assistant/tests/architecture.test.mjs b/codemods/solid-migration-assistant/tests/architecture.test.mjs index 2f1b046..62b3b75 100644 --- a/codemods/solid-migration-assistant/tests/architecture.test.mjs +++ b/codemods/solid-migration-assistant/tests/architecture.test.mjs @@ -381,6 +381,16 @@ test("colocates contracts and Solid renderers only for selected pilot slices", ( for (const [directory, folder] of pilots) { assert.equal(existsSync(resolve(directory, folder, "report.ts")), true, folder); assert.equal(existsSync(resolve(directory, folder, "ui.tsx")), true, folder); + const reportSource = readFileSync(resolve(directory, folder, "report.ts"), "utf8"); + const uiSource = readFileSync(resolve(directory, folder, "ui.tsx"), "utf8"); + assert.match(reportSource, /summary: string/); + assert.match(reportSource, /reason: string/); + assert.match(reportSource, /nextSteps: readonly string\[\]/); + assert.match(reportSource, /officialGuideUrl: string/); + assert.doesNotMatch(reportSource, /guidance: string/); + assert.match(uiSource, /GuidanceSections/); + assert.match(uiSource, /summary=\{finding\.summary\}/); + assert.doesNotMatch(uiSource, /finding\.guidance|\.split\(/); } assert.equal(ruleFiles(analysisDirectory, (name) => name === "ui.tsx").length, 3); assert.equal(ruleFiles(transformationsDirectory, (name) => name === "ui.tsx").length, 1); diff --git a/codemods/solid-migration-assistant/tests/cli.test.mjs b/codemods/solid-migration-assistant/tests/cli.test.mjs index c43a2c4..37b3ef6 100644 --- a/codemods/solid-migration-assistant/tests/cli.test.mjs +++ b/codemods/solid-migration-assistant/tests/cli.test.mjs @@ -202,6 +202,7 @@ test("writes report only when explicitly requested and launches only with --open const html = readFileSync(report, "utf8"); assert.match(html, /solid-migration-report-data/); assert.match(html, /"schemaVersion":1/); + assert.match(html, new RegExp(`"analyzedTargetRoot":"${surface.replaceAll("\\", "\\\\")}"`)); } finally { rmSync(surface, { recursive: true, force: true }); } diff --git a/codemods/solid-migration-assistant/tests/report.test.mjs b/codemods/solid-migration-assistant/tests/report.test.mjs index 55d1150..8f2e3f1 100644 --- a/codemods/solid-migration-assistant/tests/report.test.mjs +++ b/codemods/solid-migration-assistant/tests/report.test.mjs @@ -9,10 +9,12 @@ import { renderReportHtml } from "../shared/report-artifact.mjs"; const packageDirectory = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const reportModule = await loadTypeScript("shared/report.ts"); const findingModel = await loadTypeScript("dashboard/finding-model.ts"); +const themeModule = await loadTypeScript("dashboard/theme.ts"); test("escapes report JSON so the inert script cannot terminate", () => { const envelope = { schemaVersion: 1, + run: { analyzedTargetRoot: "/workspace/project" }, reports: { unsafe: { text: "&

" } }, }; const serialized = reportModule.serializeReportEnvelope(envelope); @@ -20,28 +22,46 @@ test("escapes report JSON so the inert script cannot terminate", () => { assert.deepEqual(JSON.parse(serialized), envelope); const template = ''; - const html = renderReportHtml(template, JSON.stringify(envelope)); + const html = renderReportHtml(template, JSON.stringify(envelope), { analyzedTargetRoot: "/workspace/project" }); assert.equal((html.match(/ - diff --git a/codemods/solid-migration-assistant/dashboard/styles.css b/codemods/solid-migration-assistant/dashboard/styles.css index 420b28a..8a4ac40 100644 --- a/codemods/solid-migration-assistant/dashboard/styles.css +++ b/codemods/solid-migration-assistant/dashboard/styles.css @@ -1,63 +1,150 @@ -:root { +:root, +:root[data-theme="light"] { + color-scheme: light; + --background: #f5f8f8; + --surface: #ffffff; + --surface-strong: #edf3f4; + --text: #172126; + --text-soft: #4d5e65; + --border: #c8d5d9; + --border-strong: #91a6ad; + --link: #00698f; + --link-hover: #004b68; + --accent: #167447; + --accent-soft: #e4f5eb; + --accent-text: #155c3b; + --code: #153f50; + --code-background: #edf2f3; + --danger: #9d2b24; + --danger-border: #d29a95; + --danger-background: #fff0ee; + --focus: #007ba5; +} +@media (prefers-color-scheme: dark) { + :root:not([data-theme]) { + color-scheme: dark; + --background: #0b1013; + --surface: #121a1f; + --surface-strong: #10171b; + --text: #e8f0f2; + --text-soft: #b1c0c6; + --border: #26343b; + --border-strong: #3a4b53; + --link: #75d7ff; + --link-hover: #b7ebff; + --accent: #82e0a5; + --accent-soft: #13251c; + --accent-text: #a4edbf; + --code: #c8e7f3; + --code-background: #0a0f12; + --danger: #ffb4ab; + --danger-border: #75433f; + --danger-background: #281817; + --focus: #75d7ff; + } +} +:root[data-theme="dark"] { color-scheme: dark; + --background: #0b1013; + --surface: #121a1f; + --surface-strong: #10171b; + --text: #e8f0f2; + --text-soft: #b1c0c6; + --border: #26343b; + --border-strong: #3a4b53; + --link: #75d7ff; + --link-hover: #b7ebff; + --accent: #82e0a5; + --accent-soft: #13251c; + --accent-text: #a4edbf; + --code: #c8e7f3; + --code-background: #0a0f12; + --danger: #ffb4ab; + --danger-border: #75433f; + --danger-background: #281817; + --focus: #75d7ff; +} +:root { font-family: Inter, ui-sans-serif, system-ui, sans-serif; - color: #e8f0f2; - background: #0b1013; + color: var(--text); + background: var(--background); font-synthesis: none; } * { box-sizing: border-box; } -body { margin: 0; min-width: 320px; min-height: 100vh; } -a { color: #75d7ff; text-underline-offset: 0.18em; } -a:hover { color: #b7ebff; } +body { margin: 0; min-width: 320px; min-height: 100vh; background: var(--background); } +a { color: var(--link); text-underline-offset: 0.18em; } +a:hover { color: var(--link-hover); } +button, input { font: inherit; } +button:focus-visible, input:focus-visible, a:focus-visible, summary:focus-visible { + outline: 3px solid color-mix(in srgb, var(--focus) 55%, transparent); + outline-offset: 2px; +} .site-header { - display: flex; align-items: center; justify-content: space-between; gap: 2rem; - padding: 1.5rem clamp(1.25rem, 4vw, 4rem); border-bottom: 1px solid #26343b; - background: #10171b; + display: flex; align-items: flex-start; justify-content: space-between; gap: 2rem; + padding: 1.5rem clamp(1.25rem, 4vw, 4rem); border-bottom: 1px solid var(--border); + background: var(--surface-strong); } .site-header h1 { margin: 0.15rem 0 0; font-size: clamp(1.35rem, 3vw, 1.8rem); } .site-header h1 a { color: inherit; text-decoration: none; } +.report-identity { min-width: 0; } +.header-actions { display: flex; align-items: center; gap: 0.75rem; } +.target-root { display: flex; flex-wrap: wrap; gap: 0.35rem 0.65rem; margin: 0.7rem 0 0; font-size: 0.82rem; } +.target-root span { color: var(--text-soft); font-weight: 700; } +.target-root code { min-width: 0; overflow-wrap: anywhere; word-break: break-word; } +.theme-switch { + display: inline-flex; align-items: center; gap: 0.4rem; border: 1px solid var(--border-strong); + border-radius: 999px; padding: 0.38rem 0.7rem; background: var(--surface); color: var(--text); + cursor: pointer; +} +.theme-switch:hover { border-color: var(--link); } .site-main { width: min(70rem, 100%); margin: 0 auto; padding: 3rem clamp(1.25rem, 4vw, 3rem) 5rem; } h2 { margin: 0.25rem 0 0.6rem; font-size: clamp(1.8rem, 5vw, 2.7rem); } h3 { margin: 0.15rem 0 1rem; } -p { color: #b1c0c6; line-height: 1.6; } -.eyebrow { color: #82e0a5; font-size: 0.76rem; font-weight: 750; letter-spacing: 0.12em; text-transform: uppercase; margin: 0; } -.run-badge { border: 1px solid #416052; color: #a4edbf; border-radius: 999px; padding: 0.35rem 0.7rem; white-space: nowrap; } +p { color: var(--text-soft); line-height: 1.6; } +.eyebrow { color: var(--accent); font-size: 0.76rem; font-weight: 750; letter-spacing: 0.12em; text-transform: uppercase; margin: 0; } +.run-badge { border: 1px solid var(--border-strong); color: var(--accent-text); background: var(--accent-soft); border-radius: 999px; padding: 0.35rem 0.7rem; white-space: nowrap; } .page-heading { margin-bottom: 2rem; } .domain-groups { display: grid; gap: 2.25rem; } .domain-group > h3 { margin-bottom: 0.9rem; font-size: 1.25rem; } .report-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); gap: 1rem; } -.report-card { border: 1px solid #26343b; border-radius: 0.8rem; background: #121a1f; padding: 1.25rem; } -.rule-summary, .rule-detail { color: #d4dfe3; } -.empty-state, .error-state { border: 1px dashed #3a4b53; border-radius: 0.7rem; padding: 1rem; } -.error-state { color: #ffb4ab; border-color: #75433f; background: #281817; } +.report-card { border: 1px solid var(--border); border-radius: 0.8rem; background: var(--surface); padding: 1.25rem; } +.rule-summary, .rule-detail { color: var(--text); } +.empty-state, .error-state, .zero-state { border: 1px dashed var(--border-strong); border-radius: 0.7rem; padding: 1rem; } +.error-state { color: var(--danger); border-color: var(--danger-border); background: var(--danger-background); } .compact { margin-bottom: 0; padding: 0.7rem; } -code { color: #c8e7f3; } -@media (max-width: 34rem) { .site-header { align-items: flex-start; flex-direction: column; gap: 0.8rem; } } - +code { color: var(--code); } .finding-list { display: grid; gap: 1rem; padding-left: 1.25rem; } -.finding-list li { padding: 1rem; border: 1px solid #26343b; border-radius: 0.7rem; } -pre { white-space: pre-wrap; overflow-wrap: anywhere; padding: 0.8rem; border-radius: 0.55rem; background: #0a0f12; color: #cbd9df; } -.source-snippet { border-left: 3px solid #75d7ff; color: #e8f0f2; } - -.filename-filter { display: grid; gap: 0.45rem; max-width: 32rem; color: #d4dfe3; font-weight: 650; } -.filename-filter input { - width: 100%; border: 1px solid #3a4b53; border-radius: 0.55rem; - background: #0a0f12; color: #e8f0f2; padding: 0.75rem 0.85rem; font: inherit; -} -.filename-filter input:focus { border-color: #75d7ff; outline: 2px solid #75d7ff44; outline-offset: 1px; } +.finding-list > li { padding: 1rem; border: 1px solid var(--border); border-radius: 0.7rem; background: var(--surface); } +pre { white-space: pre-wrap; overflow-wrap: anywhere; padding: 0.8rem; border-radius: 0.55rem; background: var(--code-background); color: var(--text-soft); } +pre code { color: inherit; } +.source-snippet { border-left: 3px solid var(--link); color: var(--text); } +.filename-filter { display: grid; gap: 0.45rem; max-width: 32rem; color: var(--text); font-weight: 650; } +.filename-filter input { width: 100%; border: 1px solid var(--border-strong); border-radius: 0.55rem; background: var(--code-background); color: var(--text); padding: 0.75rem 0.85rem; } .result-count { margin: 0.6rem 0 1.2rem; font-size: 0.9rem; } -.zero-state { border: 1px dashed #3a4b53; border-radius: 0.7rem; padding: 1rem; } .finding-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; } -.finding-heading > div { display: grid; gap: 0.35rem; } -.finding-heading span { color: #b1c0c6; } -.finding-heading .copy-location { flex: none; margin: 0; } +.finding-heading > div { display: grid; gap: 0.35rem; min-width: 0; } +.finding-heading span { color: var(--text-soft); } +.finding-heading .editor-actions { flex: none; } +.editor-actions > summary, .finding-disclosure > summary { color: var(--link); cursor: pointer; } +.editor-actions ul { display: grid; gap: 0.45rem; margin: 0.6rem 0 0; padding-left: 1.2rem; } +.copy-location { border: 0; padding: 0; background: transparent; color: var(--link); text-decoration: underline; text-underline-offset: 0.18em; cursor: pointer; } +.copy-location:hover { color: var(--link-hover); } .finding-disclosure { margin-top: 1rem; } -.finding-disclosure > summary { color: #75d7ff; cursor: pointer; } -.copy-location, .pagination button { - margin-top: 1rem; border: 1px solid #416052; border-radius: 0.45rem; - background: #13251c; color: #a4edbf; padding: 0.5rem 0.7rem; font: inherit; cursor: pointer; -} -.copy-location:hover, .pagination button:hover:not(:disabled) { background: #1b3528; } +.guidance-sections { margin-top: 1rem; } +.guidance-sections section { margin-top: 1.25rem; } +.guidance-sections h4 { margin: 0 0 0.45rem; } +.guidance-sections ol, .guidance-sections ul { display: grid; gap: 0.45rem; color: var(--text-soft); line-height: 1.5; } +.finding-summary { color: var(--text); font-weight: 700; } +.caution-section { border-left: 3px solid var(--danger-border); padding-left: 0.9rem; } +.section-badge { display: inline-block; margin-right: 0.35rem; padding: 0.15rem 0.4rem; border-radius: 0.35rem; background: var(--danger-background); color: var(--danger); font-size: 0.74rem; text-transform: uppercase; letter-spacing: 0.06em; } +.pagination button { margin-top: 1rem; border: 1px solid var(--border-strong); border-radius: 0.45rem; background: var(--accent-soft); color: var(--accent-text); padding: 0.5rem 0.7rem; cursor: pointer; } +.pagination button:hover:not(:disabled) { border-color: var(--accent); } .pagination { display: flex; align-items: center; justify-content: center; gap: 1rem; margin-top: 1.5rem; } .pagination button { margin: 0; } .pagination button:disabled { cursor: not-allowed; opacity: 0.45; } +@media (max-width: 42rem) { + .site-header { flex-direction: column; gap: 1rem; } + .header-actions { width: 100%; justify-content: space-between; } + .finding-heading { flex-direction: column; } + .finding-heading .editor-actions { width: 100%; } +} From 393b66e46065ea3c6e49a23f4fd95748d9c63667 Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Wed, 19 Aug 2026 00:39:51 -0500 Subject: [PATCH 15/16] refactor(dashboard): surface editor and path utilities --- codemods/solid-migration-assistant/README.md | 2 +- .../assets/dashboard/index.html | 6 +- .../dashboard/app.tsx | 7 +- .../dashboard/copy-button.tsx | 43 ++++++++++++ .../dashboard/finding-ui.tsx | 68 ++++++------------- .../dashboard/styles.css | 25 ++++--- .../tests/architecture.test.mjs | 13 ++++ 7 files changed, 104 insertions(+), 60 deletions(-) create mode 100644 codemods/solid-migration-assistant/dashboard/copy-button.tsx diff --git a/codemods/solid-migration-assistant/README.md b/codemods/solid-migration-assistant/README.md index 453fb93..74e9df8 100644 --- a/codemods/solid-migration-assistant/README.md +++ b/codemods/solid-migration-assistant/README.md @@ -34,7 +34,7 @@ The pilot dashboard includes only `web-import`, `component-renames`, `create-eff 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 an extensible editor action menu with a VS Code deep link plus a portable `relative/path:line:column` copy fallback. +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. diff --git a/codemods/solid-migration-assistant/assets/dashboard/index.html b/codemods/solid-migration-assistant/assets/dashboard/index.html index 12dbad6..003a429 100644 --- a/codemods/solid-migration-assistant/assets/dashboard/index.html +++ b/codemods/solid-migration-assistant/assets/dashboard/index.html @@ -5,9 +5,9 @@ Solid Migration Report - - diff --git a/codemods/solid-migration-assistant/dashboard/app.tsx b/codemods/solid-migration-assistant/dashboard/app.tsx index 2f9bd85..7a35aa9 100644 --- a/codemods/solid-migration-assistant/dashboard/app.tsx +++ b/codemods/solid-migration-assistant/dashboard/app.tsx @@ -7,6 +7,7 @@ import type { 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, @@ -187,7 +188,11 @@ export function Shell(props: {

Solid 2 migration assistant

Migration report

-

Analyzed target{props.analyzedTargetRoot}

+
+ Analyzed target + {props.analyzedTargetRoot} + +
Single run diff --git a/codemods/solid-migration-assistant/dashboard/copy-button.tsx b/codemods/solid-migration-assistant/dashboard/copy-button.tsx new file mode 100644 index 0000000..0fdfda3 --- /dev/null +++ b/codemods/solid-migration-assistant/dashboard/copy-button.tsx @@ -0,0 +1,43 @@ +import { createSignal, onCleanup } from "solid-js"; + +export function CopyButton(props: { + readonly value: string; + readonly idleLabel: string; + readonly class?: string; +}) { + const [status, setStatus] = createSignal<"idle" | "copied" | "failed">("idle"); + let resetTimer: ReturnType | undefined; + onCleanup(() => { if (resetTimer !== undefined) clearTimeout(resetTimer); }); + + async function copyValue() { + try { + await copyText(props.value); + setStatus("copied"); + } catch { + setStatus("failed"); + } + if (resetTimer !== undefined) clearTimeout(resetTimer); + resetTimer = setTimeout(() => setStatus("idle"), 1_500); + } + + const label = () => status() === "copied" ? "Copied" : status() === "failed" ? "Copy failed" : props.idleLabel; + return ; +} + +async function copyText(value: string): Promise { + try { + await navigator.clipboard.writeText(value); + return; + } catch { + const textarea = document.createElement("textarea"); + textarea.value = value; + textarea.setAttribute("readonly", ""); + textarea.style.position = "fixed"; + textarea.style.opacity = "0"; + document.body.append(textarea); + textarea.select(); + const copied = document.execCommand("copy"); + textarea.remove(); + if (!copied) throw new Error("Could not copy text."); + } +} diff --git a/codemods/solid-migration-assistant/dashboard/finding-ui.tsx b/codemods/solid-migration-assistant/dashboard/finding-ui.tsx index b375388..dd52468 100644 --- a/codemods/solid-migration-assistant/dashboard/finding-ui.tsx +++ b/codemods/solid-migration-assistant/dashboard/finding-ui.tsx @@ -1,6 +1,6 @@ import type { JSX } from "@solidjs/web"; import { useLocation } from "@solidjs/router"; -import { For, Show, createEffect, createMemo, createSignal, onCleanup } from "solid-js"; +import { For, Show, createEffect, createMemo, createSignal } from "solid-js"; import { createEditorActions, filterFindings, @@ -8,6 +8,7 @@ import { paginateFindings, type EditorTarget, } from "./finding-model.ts"; +import { CopyButton } from "./copy-button.tsx"; export type FindingView = { readonly filename: string; @@ -160,51 +161,26 @@ function FindingDisclosure(props: FindingView) { } function EditorActionMenu(props: { readonly target: EditorTarget }) { + const actions = createEditorActions(props.target); + const primaryAction = actions[0]!; return ( -
- Open in editor - -
+
+ Open in VS Code +
+ + +
+
); } - -function CopyLocationButton(props: { readonly location: string }) { - const [status, setStatus] = createSignal<"idle" | "copied" | "failed">("idle"); - let resetTimer: ReturnType | undefined; - onCleanup(() => { if (resetTimer !== undefined) clearTimeout(resetTimer); }); - async function copyLocation() { - try { - await copyText(props.location); - setStatus("copied"); - } catch { - setStatus("failed"); - } - if (resetTimer !== undefined) clearTimeout(resetTimer); - resetTimer = setTimeout(() => setStatus("idle"), 1_500); - } - const label = () => status() === "copied" ? "Copied" : status() === "failed" ? "Copy failed" : "Copy location"; - return ; -} - -async function copyText(value: string): Promise { - try { - await navigator.clipboard.writeText(value); - return; - } catch { - const textarea = document.createElement("textarea"); - textarea.value = value; - textarea.setAttribute("readonly", ""); - textarea.style.position = "fixed"; - textarea.style.opacity = "0"; - document.body.append(textarea); - textarea.select(); - const copied = document.execCommand("copy"); - textarea.remove(); - if (!copied) throw new Error("Could not copy the location."); - } -} diff --git a/codemods/solid-migration-assistant/dashboard/styles.css b/codemods/solid-migration-assistant/dashboard/styles.css index 8a4ac40..62c6372 100644 --- a/codemods/solid-migration-assistant/dashboard/styles.css +++ b/codemods/solid-migration-assistant/dashboard/styles.css @@ -88,9 +88,11 @@ button:focus-visible, input:focus-visible, a:focus-visible, summary:focus-visibl .site-header h1 a { color: inherit; text-decoration: none; } .report-identity { min-width: 0; } .header-actions { display: flex; align-items: center; gap: 0.75rem; } -.target-root { display: flex; flex-wrap: wrap; gap: 0.35rem 0.65rem; margin: 0.7rem 0 0; font-size: 0.82rem; } -.target-root span { color: var(--text-soft); font-weight: 700; } -.target-root code { min-width: 0; overflow-wrap: anywhere; word-break: break-word; } +.target-root { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 0.35rem 0.65rem; margin-top: 0.7rem; font-size: 0.78rem; } +.target-root > span { color: var(--text-soft); font-weight: 700; } +.target-root code { min-width: 0; overflow-wrap: anywhere; word-break: break-word; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; } +.copy-root, .copy-location { border: 0; padding: 0; background: transparent; color: var(--link); text-decoration: underline; text-underline-offset: 0.18em; cursor: pointer; } +.copy-root:hover, .copy-location:hover { color: var(--link-hover); } .theme-switch { display: inline-flex; align-items: center; gap: 0.4rem; border: 1px solid var(--border-strong); border-radius: 999px; padding: 0.38rem 0.7rem; background: var(--surface); color: var(--text); @@ -124,11 +126,13 @@ pre code { color: inherit; } .finding-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; } .finding-heading > div { display: grid; gap: 0.35rem; min-width: 0; } .finding-heading span { color: var(--text-soft); } -.finding-heading .editor-actions { flex: none; } -.editor-actions > summary, .finding-disclosure > summary { color: var(--link); cursor: pointer; } -.editor-actions ul { display: grid; gap: 0.45rem; margin: 0.6rem 0 0; padding-left: 1.2rem; } -.copy-location { border: 0; padding: 0; background: transparent; color: var(--link); text-decoration: underline; text-underline-offset: 0.18em; cursor: pointer; } -.copy-location:hover { color: var(--link-hover); } +.finding-actions { display: flex; align-items: center; gap: 0.5rem; flex: none; } +.open-editor { border: 1px solid var(--border-strong); border-radius: 0.45rem; padding: 0.38rem 0.6rem; background: var(--accent-soft); color: var(--accent-text); text-decoration: none; white-space: nowrap; } +.open-editor:hover { border-color: var(--accent); color: var(--accent-text); } +.overflow-actions { position: relative; } +.overflow-actions > summary { display: grid; place-items: center; min-width: 2.1rem; min-height: 2.1rem; border: 1px solid var(--border-strong); border-radius: 0.45rem; color: var(--link); cursor: pointer; list-style: none; } +.overflow-actions > summary::-webkit-details-marker { display: none; } +.overflow-actions ul { position: absolute; right: 0; z-index: 2; min-width: 10rem; display: grid; gap: 0.45rem; margin: 0.35rem 0 0; padding: 0.75rem 0.75rem 0.75rem 1.8rem; border: 1px solid var(--border); border-radius: 0.55rem; background: var(--surface); box-shadow: 0 0.5rem 1.2rem color-mix(in srgb, var(--text) 16%, transparent); } .finding-disclosure { margin-top: 1rem; } .guidance-sections { margin-top: 1rem; } .guidance-sections section { margin-top: 1.25rem; } @@ -146,5 +150,8 @@ pre code { color: inherit; } .site-header { flex-direction: column; gap: 1rem; } .header-actions { width: 100%; justify-content: space-between; } .finding-heading { flex-direction: column; } - .finding-heading .editor-actions { width: 100%; } + .finding-actions { width: 100%; flex-wrap: wrap; } + .target-root { grid-template-columns: 1fr auto; } + .target-root > span { grid-column: 1 / -1; } + .target-root code { grid-column: 1; } } diff --git a/codemods/solid-migration-assistant/tests/architecture.test.mjs b/codemods/solid-migration-assistant/tests/architecture.test.mjs index 62b3b75..0c3047d 100644 --- a/codemods/solid-migration-assistant/tests/architecture.test.mjs +++ b/codemods/solid-migration-assistant/tests/architecture.test.mjs @@ -396,6 +396,19 @@ test("colocates contracts and Solid renderers only for selected pilot slices", ( assert.equal(ruleFiles(transformationsDirectory, (name) => name === "ui.tsx").length, 1); }); +test("keeps direct editor and copy utility actions keyboard-native", () => { + const app = readFileSync(resolve(packageDirectory, "dashboard/app.tsx"), "utf8"); + const findings = readFileSync(resolve(packageDirectory, "dashboard/finding-ui.tsx"), "utf8"); + const copyButton = readFileSync(resolve(packageDirectory, "dashboard/copy-button.tsx"), "utf8"); + const styles = readFileSync(resolve(packageDirectory, "dashboard/styles.css"), "utf8"); + assert.match(findings, /class="open-editor"/); + assert.match(findings, /class="overflow-actions"/); + assert.match(findings, /idleLabel="Copy location"/); + assert.match(app, /idleLabel="Copy root"/); + assert.match(copyButton, /