[r5n-5] Snippet configurability and filename-derived naming fixes - #355
Open
deralaxo wants to merge 6 commits into
Open
[r5n-5] Snippet configurability and filename-derived naming fixes#355deralaxo wants to merge 6 commits into
deralaxo wants to merge 6 commits into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds three generation settings that let users control how component snippets are scaffolded — fragment vs
divwrapper, 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/rmcpemitted anonymous components that React DevTools showed asAnonymousand thatreact/display-nameflagged, 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, defaultfragment) — whether component snippets wrap their content in a React fragment or adiv.typescriptPropsNaming(generic|component, defaultgeneric) — whether TypeScript component snippets name their props typePropsor derive it from the component name, e.g.ButtonProps.componentNameSource(filename|directoryForIndex, defaultfilename) — whether component snippets use the filename, or the parent directory name forindex.*files, soButton/index.tsxyieldsButton.All three default to the previous behaviour, so existing users see no change unless they opt in.
Bug fixes
Named
memocomponents.rmc/rmcpemittedmemo(() => {}). Name inference doesn't apply to functions passed as call arguments, so the component was genuinely anonymous —Anonymousin React DevTools, andreact/display-namefiring. Now emitsmemo(function DeleteModal() {}), reusing the existing$1tabstop so the name mirrors theconst.Valid identifiers from any filename casing. Component snippets emitted the raw filename, so
delete-modal.tsxproducedconst delete-modal = ...— a syntax error. Lowercase names such asindex.tsxwere 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-slicebecomesuserSlice, whileUserSliceanddelete_modalare 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.
rtrlaused the raw filename placeholder; it now uses the component placeholder, so it is PascalCased and respectscomponentNameSourcelike every other component snippet.Chores
brace-expansionoverride from5.0.7to5.0.8(GHSA-mh99-v99m-4gvg, high — DoS via unbounded brace expansion causing an out-of-memory crash). Theoverridesblock pinned the exact vulnerable version, which blocked the fix:minimatchrequests^5.0.5and would have resolved to the patched build on its own. Dev-only — it reaches the tree through@vscode/vsce, which is used solely forpackage/publishand never ships to users.bun run auditnow passes.Behaviour changes
These change default output and warrant a minor version rather than a patch:
rmc/rmcpnow emit a named function expression.Button→Button), butindex.tsxnow yieldsIndex, andapp.tsxyieldsApp.Known limitations
stestand 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: inButton.test.tsx,TM_FILENAME_BASEisButton.test, so the import path is already wrong regardless of casing. Left for a follow-up.2fa-modal.tsx→2faModal) remain invalid identifiers. They were invalid before too, so nothing regresses.Testing
bun test— 19 pass, 393 assertions.tsc --noEmit,oxlint,oxfmt --check,generate:checkandbun run auditall 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 thatsteststill 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.resolveagainst the mappings imported directly fromsrc/types.ts, with a guard that fails if a mapping drifts:delete-modalDeleteModaldeleteModaldelete_modalDeleteModaldelete_modaldeleteModalDeleteModaldeleteModalDeleteModalDeleteModalDeleteModalButtonButtonButtonindexIndexindexUserSliceUserSliceUserSlice_/$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.