Skip to content

[r5n-5] Snippet configurability and filename-derived naming fixes - #355

Open
deralaxo wants to merge 6 commits into
developfrom
r5n-5-snippet-issues
Open

[r5n-5] Snippet configurability and filename-derived naming fixes#355
deralaxo wants to merge 6 commits into
developfrom
r5n-5-snippet-issues

Conversation

@deralaxo

@deralaxo deralaxo commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds three generation settings that let users control how component snippets are scaffolded — fragment vs div wrapper, props type naming, and where the component name comes from — and fixes two long-standing naming bugs that produced invalid output.

The bugs are the important half: rmc/rmcp emitted anonymous components that React DevTools showed as Anonymous and that react/display-name flagged, and any filename that wasn't already a valid identifier (delete-modal.tsx) produced code that didn't parse. Both are fixed unconditionally rather than behind a setting, because neither had a defensible "off" state.

Closes #312, closes #311, closes #271, closes #258.

Changes

Features

  • componentWrapper (fragment | div, default fragment) — whether component snippets wrap their content in a React fragment or a div.
  • typescriptPropsNaming (generic | component, default generic) — whether TypeScript component snippets name their props type Props or derive it from the component name, e.g. ButtonProps.
  • componentNameSource (filename | directoryForIndex, default filename) — whether component snippets use the filename, or the parent directory name for index.* files, so Button/index.tsx yields Button.

All three default to the previous behaviour, so existing users see no change unless they opt in.

Bug fixes

  • Named memo components. rmc/rmcp emitted memo(() => {}). Name inference doesn't apply to functions passed as call arguments, so the component was genuinely anonymous — Anonymous in React DevTools, and react/display-name firing. Now emits memo(function DeleteModal() {}), reusing the existing $1 tabstop so the name mirrors the const.

  • Valid identifiers from any filename casing. Component snippets emitted the raw filename, so delete-modal.tsx produced const delete-modal = ... — a syntax error. Lowercase names such as index.tsx were valid JS but invalid React, since a lowercase JSX tag compiles to a DOM element rather than a component. Component names are now derived via PascalCase, which is the identity function for names that are already correct.

  • Redux slice names. Same bug class in rxslice/rxslicex. Slices are not components, so they are only rewritten when the raw name would not be a valid identifier: user-slice becomes userSlice, while UserSlice and delete_modal are left untouched. This is deliberately narrower than the component rule — PascalCase is required for components, whereas camelCase for slices is only convention, and rewriting working code to satisfy a convention isn't justified.

  • React Router route component. rtrla used the raw filename placeholder; it now uses the component placeholder, so it is PascalCased and respects componentNameSource like every other component snippet.

Chores

  • Bumped the brace-expansion override from 5.0.7 to 5.0.8 (GHSA-mh99-v99m-4gvg, high — DoS via unbounded brace expansion causing an out-of-memory crash). The overrides block pinned the exact vulnerable version, which blocked the fix: minimatch requests ^5.0.5 and would have resolved to the patched build on its own. Dev-only — it reaches the tree through @vscode/vsce, which is used solely for package/publish and never ships to users. bun run audit now passes.

Behaviour changes

These change default output and warrant a minor version rather than a patch:

  • rmc/rmcp now emit a named function expression.
  • Component names are PascalCased. No effect on filenames that are already valid component names (ButtonButton), but index.tsx now yields Index, and app.tsx yields App.
  • Slice names change only where the previous output would not have parsed.

Known limitations

  • Test snippets are unchanged. stest and friends still emit the raw filename. The identifier and the import path share a single tabstop (import { Button } from '../Button'), so casing alone can't fix it — the two roles need different values. It also has a separate pre-existing problem: in Button.test.tsx, TM_FILENAME_BASE is Button.test, so the import path is already wrong regardless of casing. Left for a follow-up.
  • Filenames beginning with a digit (2fa-modal.tsx2faModal) remain invalid identifiers. They were invalid before too, so nothing regresses.

Testing

bun test — 19 pass, 393 assertions. tsc --noEmit, oxlint, oxfmt --check, generate:check and bun run audit all clean.

Automated

Three tests added:

  • applies directory-derived names to every component scaffold — asserts all 41 component scaffolds pick up the directory-derived name, and that none leak the raw filename placeholder.
  • derives valid identifiers from filenames of any casing — pins the component, slice, route and test-snippet mappings, including asserting that stest still emits the raw filename so the deferred case can't drift silently.
  • renames slices only when the filename is not a valid identifier — extracts the pattern from the shipped mapping and asserts which names pass through versus get rewritten.

Also widened the existing tabstop-conflict check, which matched the literal ${1:${TM_FILENAME_BASE}} and would have silently stopped covering every component snippet once the transform was introduced.

Transform verification

VS Code evaluates the snippet transforms itself, so a malformed transform would render wrong in the editor with nothing in the test suite catching it. The transforms were verified by emulating VS Code's FormatString.resolve against the mappings imported directly from src/types.ts, with a guard that fails if a mapping drifts:

Filename Component Slice
delete-modal DeleteModal deleteModal
delete_modal DeleteModal delete_modal
deleteModal DeleteModal deleteModal
DeleteModal DeleteModal DeleteModal
Button Button Button
index Index index
UserSlice UserSlice UserSlice
_ / $ unchanged unchanged

Directory mode verified against real absolute paths, including nested directories, non-index files, and Windows-style separators.

Manual

A test tree of 37 files covering every case above lives outside the repo, grouped into fixes, regression guards, intentional changes, slices, directory mode, memo naming, controls that must not change, and known limitations.

@deralaxo
deralaxo requested a review from ice-chillios July 30, 2026 09:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant