You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
vp migrate keeps tsx because of a `tsx ```` code fence in a comment #3297
vp migrate (1.4.0) migrates the tsx scripts to vpx but then reports that a plain .ts file "still uses tsx" and keeps the tsx devDependency. The file doesn't run or import tsx. It has a JSDoc example fenced as ```tsx:
• tsx scripts migrated to `vpx`
- Kept the tsx devDependency in package.json: use-editing.ts still uses tsx.
Change the fence to ```ts and tsx is removed as expected.
I think it's the second alternative of TSX_CODE_REFERENCE in packages/cli/src/migration/migrator/tsx.ts, /['"`](?:npx |pnpm (?:exec )?|yarn |bunx )?tsx\s/, which reads the third backtick of the fence as the start of a template literal followed by tsx and a newline. Requiring that the backtick isn't preceded by another backtick, or skipping comments, would avoid it. I'm happy to open a PR with a test if that sounds right.
Unit Test: Yes — packages/cli/src/migration/__tests__/tsx.spec.ts
Priority:P2: Has Workaround. This only happens when a .ts file contains a JSDoc/Markdown code fence labeled tsx, and the workaround is to change the fence label or remove tsx manually after migration.
The TSX_CODE_REFERENCE regex in fileUsesTsx (packages/cli/src/migration/migrator/tsx.ts) matches the third backtick of a ```tsx fence as a backtick-quoted string immediately followed by tsx , so the post-rewrite dependency scan thinks the file runs tsx even though the reference is only inside a comment.
I found a potential fix for this issue. The branch strips // and /* ... */ comments before running TSX_CODE_REFERENCE on code files, using the existing stripComments helper that preserves real string/template literals. This removes the false positive for JSDoc/Markdown fences while still catching actual imports, spawn calls, and loader flags.
vp migrate reports that a plain .ts file "still uses tsx" and keeps the tsx devDependency when the file only contains a JSDoc @example fenced as ```tsx. The reporter identifies the second alternative of TSX_CODE_REFERENCE (packages/cli/src/migration/migrator/tsx.ts) as the likely cause: the ` matching the third backtick of the code fence is then followed by tsx and a newline, which the regex reads as a string starting a tsx command.
Expected vs. Actual
Expected: "start": "tsx index.ts" is migrated to "start": "vpx index.ts" and the tsx devDependency is removed, because the project does not actually run or import tsx.
Actual: The script is rewritten to vpx index.ts, but migration prints:
- Kept the tsx devDependency in package.json: use-editing.ts still uses tsx.
and keeps tsx in devDependencies.
Environment
Vite+ / vp: v1.4.0 (global vp on PATH, plus the local checkout's built CLI packages/cli/dist/bin.js)
The reported behavior reproduces on the current checkout. A ```tsx code fence inside a JSDoc comment in a .ts file is enough to make findTsxUse / fileUsesTsx treat the file as referencing tsx, while changing the fence to ```ts lets vp migrate remove the dependency as expected.
Observations
The relevant regex appears to be:
/['"`](?:npx|pnpm(?:exec)?|yarn|bunx)?tsx\s/
as part of TSX_CODE_REFERENCE. In ```tsx, the ` matching the third backtick of the fence is immediately followed by tsx and a newline, so the alternative matches. The call path is migrateTsxToVpx -> findTsxUse -> sourceTreeMatches -> fileUsesTsx, which applies TSX_CODE_REFERENCE to every .ts source file.
Diagnosis
Affected code
packages/cli/src/migration/migrator/tsx.ts
Regex constant TSX_CODE_REFERENCE, specifically its second alternative at composite-line ~94:
/['"`](?:npx|pnpm(?:exec)?|yarn|bunx)?tsx\s/
Consumer: fileUsesTsx(content: string, file: string) at ~line 626:
TSX_CODE_REFERENCE is matched against the raw text of every .ts/.tsx/etc. file in the post-rewrite phase that decides whether tsx can safely be removed from devDependencies. The second alternative is meant to catch source strings that start a tsx command, e.g.:
execSync(`tsx ./job.ts`);
It does not try to avoid code fences in Markdown/JSDoc. In the repro file, the line:
*```tsx
contains three consecutive backticks immediately followed by tsx and a newline. The regex engine is free to start the match at the third backtick; that backtick satisfies ['"], and tsx\smatchestsx` + the newline. Therefore the whole file is judged to "still use tsx" and the dependency is kept, even though the only occurrence is inside a documentation comment.
Changing the fence to ```ts removes the false match, confirming the regex is the cause.
Suggested fix approach
Make the scan comment-aware for source-code extensions. The tsx.ts module already contains stripComments(source) for the per-script CommonJS/ESM checks; applying the same (or an equivalent) comment strip before testing TSX_CODE_REFERENCE would keep real strings and imports while removing JSDoc/Markdown content. Alternatively, the second alternative could be tightened with a negative lookbehind so the opening backtick cannot be preceded by another backtick (which is specifically what makes a code fence), e.g.:
as the backtick variant of the string-detection alternative. Either approach would fix the reported case; comment stripping is more defensive against other comment-borne false positives (for example, an // import 'tsx' or // node --import tsx note).
Edge cases
A .ts file that actually contains execSync(tsx ./a.ts) should continue to keep tsx; stripComments preserves string literals, so this detection remains intact.
Block and line comments that mention tsx commands should no longer block dependency removal; that matches the intent of findTsxUse (detecting real runtime use).
Code fences outside comments (e.g. in Markdown files) are not in REFERENCE_EXTENSIONS and are not scanned by fileUsesTsx, so they are not affected.
The regex fix must not interfere with the other TSX_CODE_REFERENCE alternatives for imports (from 'tsx/esm/api'), loader flags, or process-spawn arrays.
Baseline tests
The migration's dedicated tsx unit suite passes on current main:
pnpm -F vite-plus exec vitest run src/migration/__tests__/tsx.spec.ts
# 150 passed
A regression test for this issue should be added to migrateTsxToVpx cases (or exported unit-test surface) asserting that a .ts file with only a ```tsx JSDoc fence does not keep tsx.
Confidence
High.
Verification
Claim
The reporter claims that vp migrate should remove tsx from devDependencies when the only remaining tsx reference is a ```tsx code fence inside a JSDoc comment in a .ts file. Instead, migration keeps the dependency and reports use-editing.ts still uses tsx.
Research
Code intent: The TSX_CODE_REFERENCE constant is introduced in commit c891e5dd (feat(migrate): migrate tsx scripts to vpx #3179, "migrate tsx scripts to vpx"). Its own comment describes it as detecting uses "in code": imports, strings that start a tsx command, process calls, and loader flags. There is no mention of comments or documentation fences.
Feature documentation: The commit-level "What stays on tsx" section enumerates real runtime or configuration uses that keep the dependency (scripts, imports, shebangs, tool configs, CI workflows, shell scripts). Markdown/JSDoc fences are not listed as a retention reason; they do not execute tsx.
Surrounding tests: packages/cli/src/migration/__tests__/tsx.spec.ts explicitly asserts that non-code contexts (e.g. moduleFileExtensions: ['tsx']) should not keep tsx, showing the migration is designed to avoid false positives. Existing tests do not cover JSDoc code fences.
GitHub search: gh search issues --repo voidzero-dev/vite-plus "tsx code fence" returns only this issue; no open issue or PR states that documentation fences should keep tsx.
Verdict
Bug. The regex over-matches a documentation pattern that the migration explicitly does not consider a use of tsx, contradicting the documented intent in the feature commit and the existing false-positive avoidance in the test suite.
Confidence
High.
Fix
Changed files
packages/cli/src/migration/migrator/tsx.ts
packages/cli/src/migration/__tests__/tsx.spec.ts
Rationale
fileUsesTsx was testing the raw source text of code files with TSX_CODE_REFERENCE. Because the regex's second alternative, ['"](?:npx |pnpm ...)?tsx\s`, can start at the third backtick of a ```tsx JSDoc/Markdown fence, a plain documentation comment was misclassified as a runtime reference to tsx.
The tsx.ts module already has a stripComments helper that removes // and /* ... */ comments while preserving string and template-literal contents (so real commands such as execSync('tsx ./a.ts') are still detected). Applying stripComments before TSX_CODE_REFERENCE removes the false positive for comment-borne fences while keeping all intentional code uses. The same treatment is applied to extensionless executable script files, which may be JavaScript with comments.
Full diff
diff --git a/packages/cli/src/migration/__tests__/tsx.spec.ts b/packages/cli/src/migration/__tests__/tsx.spec.ts
index e66c8f88..e9121ef6 100644
--- a/packages/cli/src/migration/__tests__/tsx.spec.ts+++ b/packages/cli/src/migration/__tests__/tsx.spec.ts@@ -742,4 +742,40 @@ describe('migrateTsxToVpx', () => {
'Kept the tsx devDependency in package.json: `scripts` in packages/b/package.json still uses tsx.',
]);
});
++ it('removes tsx when the only reference is a code fence inside a JSDoc comment', () => {+ write('package.json', {+ type: 'module',+ scripts: { start: 'tsx index.ts' },+ devDependencies: { tsx: '^4.20.0' },+ });+ write(+ 'index.ts',+ [+ '/**',+ ' * @example',+ ' * ```tsx',+ ' * const isEditing = useEditingStore(selectIsEditing);',+ ' * ```',+ ' */',+ 'export function useEditingStore() {}',+ '',+ ].join('
'),
+ );+ const report = createMigrationReport();++ expect(detectTsxProject(root)).toBe(true);+ expect(migrateTsxToVpx(root, undefined, report)).toEqual({+ changed: true,+ dependencyRemoved: true,+ });++ expect(readJson('package.json')).toEqual({+ type: 'module',+ scripts: { start: 'vpx index.ts' },+ devDependencies: {},+ });+ expect(report.tsxMigrated).toBe(true);+ expect(report.warnings).toEqual([]);+ });
}
diff --git a/packages/cli/src/migration/migrator/tsx.ts b/packages/cli/src/migration/migrator/tsx.ts
index 67f18658..ecb1db1b 100644
--- a/packages/cli/src/migration/migrator/tsx.ts+++ b/packages/cli/src/migration/migrator/tsx.ts@@ -821,10 +821,15 @@ function fileUsesTsx(content: string, file: string): boolean {
return jsonUsesTsx(parseJsonc(content), false);
}
if (CODE_EXTENSIONS.has(extension)) {
- return TSX_CODE_REFERENCE.test(content);+ // Comments and JSDoc/Markdown fences, such as ```` ```tsx ````, are not real+ // uses of tsx.+ return TSX_CODE_REFERENCE.test(stripComments(content));
}
// An extensionless file can be JavaScript, such as an executable, as well as shell.
- return TSX_TEXT_REFERENCE.test(content) || (extension === '' && TSX_CODE_REFERENCE.test(content));+ return (+ TSX_TEXT_REFERENCE.test(content) ||+ (extension === '' && TSX_CODE_REFERENCE.test(stripComments(content)))+ );
}
Validation
Unit regression test passes:
pnpm -F vite-plus exec vitest run src/migration/__tests__/tsx.spec.ts
# 151 passed
Original reproduction no longer keeps tsx:
node /home/runner/work/vite-plus/vite-plus/packages/cli/dist/bin.js migrate --no-interactive
# Output:# • tsx scripts migrated to `vpx`# No warning; package.json devDependencies no longer contains tsx.
Full vp check: skipped because the repository's docs dependencies (vitepress, @voidzero-dev/vitepress-theme, etc.) are not resolvable on this checkout, producing unrelated TS2307 errors. The changed files themselves pass focused fmt/lint.
Alternatives considered
Tightening only the backtick alternative with a negative lookbehind ((?<!)``) would fix the exact code-fence shape but would still match tsx commands mentioned in // or `/* ... */` comments, so a more general comment-stripping approach was preferred.
A brand-new Markdown-code-fence-aware regex was rejected as over-engineered; reusing the existing, battle-tested stripComments helper is minimal and keeps behavior consistent with the script-rewrite checks.
Commit message
fix(cli): ignore tsx references inside comments when removing tsx devDependency
This report was made by an LLM. The analysis may be wrong, and the potential fix might not work, but is intended as a starting point for exploring the issue.
Activity
voidzero-bot commentedon Oct 31, 2026
packages/cli/src/migration/__tests__/tsx.spec.ts.tsfile contains a JSDoc/Markdown code fence labeledtsx, and the workaround is to change the fence label or removetsxmanually after migration.The
TSX_CODE_REFERENCEregex infileUsesTsx(packages/cli/src/migration/migrator/tsx.ts) matches the third backtick of a```tsxfence as a backtick-quoted string immediately followed bytsx, so the post-rewrite dependency scan thinks the file runs tsx even though the reference is only inside a comment.I found a potential fix for this issue. The branch strips
//and/* ... */comments before runningTSX_CODE_REFERENCEon code files, using the existingstripCommentshelper that preserves real string/template literals. This removes the false positive for JSDoc/Markdown fences while still catching actual imports, spawn calls, and loader flags.Full Triage Report
Triage Report: issue #3297
Summary
vp migratereports that a plain.tsfile "still uses tsx" and keeps thetsxdevDependency when the file only contains a JSDoc@examplefenced as```tsx. The reporter identifies the second alternative ofTSX_CODE_REFERENCE(packages/cli/src/migration/migrator/tsx.ts) as the likely cause: the`matching the third backtick of the code fence is then followed bytsxand a newline, which the regex reads as a string starting a tsx command.Expected vs. Actual
Expected:
"start": "tsx index.ts"is migrated to"start": "vpx index.ts"and thetsxdevDependency is removed, because the project does not actually run or importtsx.Actual: The script is rewritten to
vpx index.ts, but migration prints:and keeps
tsxindevDependencies.Environment
v1.4.0(globalvpon PATH, plus the local checkout's built CLIpackages/cli/dist/bin.js)v24.22.1v12.11.2/v12.16.1(migration output reportedpnpm 12.16.1)Reproduction Path
Created two fresh, dependency-pinned projects under
/tmp/vite-plus-triage/gh-3297/reproand/tmp/vite-plus-triage/gh-3297/control.Each has:
{ "name": "repro-3297", "version": "1.0.0", "private": true, "type": "module", "scripts": { "start": "tsx index.ts" }, "devDependencies": { "tsx": "^4.0.0" } }with
index.ts:The only difference is the comment fence in
use-editing.ts:```tsx```tsCommands run:
Results
Repro (```tsx fence)
Final
package.jsonstill containstsx:{ "scripts": { "start": "vpx index.ts", "prepare": "vp config" }, "devDependencies": { "tsx": "^4.0.0", "vite-plus": "catalog:" } }Control (```ts fence)
Final
package.jsonhas notsx:{ "scripts": { "start": "vpx index.ts", "prepare": "vp config" }, "devDependencies": { "vite-plus": "catalog:" } }Conclusion
The reported behavior reproduces on the current checkout. A
```tsxcode fence inside a JSDoc comment in a.tsfile is enough to makefindTsxUse/fileUsesTsxtreat the file as referencingtsx, while changing the fence to```tsletsvp migrateremove the dependency as expected.Observations
The relevant regex appears to be:
/['"`](?:npx |pnpm (?:exec )?|yarn |bunx )?tsx\s/as part of
TSX_CODE_REFERENCE. In```tsx, the`matching the third backtick of the fence is immediately followed bytsxand a newline, so the alternative matches. The call path ismigrateTsxToVpx->findTsxUse->sourceTreeMatches->fileUsesTsx, which appliesTSX_CODE_REFERENCEto every.tssource file.Diagnosis
Affected code
packages/cli/src/migration/migrator/tsx.tsRegex constant
TSX_CODE_REFERENCE, specifically its second alternative at composite-line ~94:/['"`](?:npx |pnpm (?:exec )?|yarn |bunx )?tsx\s/Consumer:
fileUsesTsx(content: string, file: string)at ~line 626:Call path:
migrateTsxToVpx→findTsxUse→sourceTreeMatchescallback →fileUsesTsx.Why it fails
TSX_CODE_REFERENCEis matched against the raw text of every.ts/.tsx/etc. file in the post-rewrite phase that decides whethertsxcan safely be removed fromdevDependencies. The second alternative is meant to catch source strings that start a tsx command, e.g.:It does not try to avoid code fences in Markdown/JSDoc. In the repro file, the line:
contains three consecutive backticks immediately followed by
tsxand a newline. The regex engine is free to start the match at the third backtick; that backtick satisfies['"], andtsx\smatchestsx` + the newline. Therefore the whole file is judged to "still use tsx" and the dependency is kept, even though the only occurrence is inside a documentation comment.Changing the fence to
```tsremoves the false match, confirming the regex is the cause.Suggested fix approach
Make the scan comment-aware for source-code extensions. The
tsx.tsmodule already containsstripComments(source)for the per-script CommonJS/ESM checks; applying the same (or an equivalent) comment strip before testingTSX_CODE_REFERENCEwould keep real strings and imports while removing JSDoc/Markdown content. Alternatively, the second alternative could be tightened with a negative lookbehind so the opening backtick cannot be preceded by another backtick (which is specifically what makes a code fence), e.g.:/(?<!`)(?:`)(?:npx |pnpm (?:exec )?|yarn |bunx )?tsx\s/as the backtick variant of the string-detection alternative. Either approach would fix the reported case; comment stripping is more defensive against other comment-borne false positives (for example, an
// import 'tsx'or// node --import tsxnote).Edge cases
.tsfile that actually containsexecSync(tsx ./a.ts)should continue to keeptsx;stripCommentspreserves string literals, so this detection remains intact.findTsxUse(detecting real runtime use).REFERENCE_EXTENSIONSand are not scanned byfileUsesTsx, so they are not affected.TSX_CODE_REFERENCEalternatives for imports (from 'tsx/esm/api'), loader flags, or process-spawn arrays.Baseline tests
The migration's dedicated tsx unit suite passes on current
main:A regression test for this issue should be added to
migrateTsxToVpxcases (or exported unit-test surface) asserting that a.tsfile with only a```tsxJSDoc fence does not keeptsx.Confidence
High.
Verification
Claim
The reporter claims that
vp migrateshould removetsxfromdevDependencieswhen the only remaining tsx reference is a```tsxcode fence inside a JSDoc comment in a.tsfile. Instead, migration keeps the dependency and reportsuse-editing.ts still uses tsx.Research
Code intent: The
TSX_CODE_REFERENCEconstant is introduced in commitc891e5dd(feat(migrate): migrate tsx scripts to vpx #3179, "migrate tsx scripts to vpx"). Its own comment describes it as detecting uses "in code": imports, strings that start a tsx command, process calls, and loader flags. There is no mention of comments or documentation fences.Feature documentation: The commit-level "What stays on tsx" section enumerates real runtime or configuration uses that keep the dependency (scripts, imports, shebangs, tool configs, CI workflows, shell scripts). Markdown/JSDoc fences are not listed as a retention reason; they do not execute tsx.
Surrounding tests:
packages/cli/src/migration/__tests__/tsx.spec.tsexplicitly asserts that non-code contexts (e.g.moduleFileExtensions: ['tsx']) should not keep tsx, showing the migration is designed to avoid false positives. Existing tests do not cover JSDoc code fences.GitHub search:
gh search issues --repo voidzero-dev/vite-plus "tsx code fence"returns only this issue; no open issue or PR states that documentation fences should keep tsx.Verdict
Bug. The regex over-matches a documentation pattern that the migration explicitly does not consider a use of tsx, contradicting the documented intent in the feature commit and the existing false-positive avoidance in the test suite.
Confidence
High.
Fix
Changed files
packages/cli/src/migration/migrator/tsx.tspackages/cli/src/migration/__tests__/tsx.spec.tsRationale
fileUsesTsxwas testing the raw source text of code files withTSX_CODE_REFERENCE. Because the regex's second alternative,['"](?:npx |pnpm ...)?tsx\s`, can start at the third backtick of a```tsxJSDoc/Markdown fence, a plain documentation comment was misclassified as a runtime reference to tsx.The
tsx.tsmodule already has astripCommentshelper that removes//and/* ... */comments while preserving string and template-literal contents (so real commands such asexecSync('tsx ./a.ts')are still detected). ApplyingstripCommentsbeforeTSX_CODE_REFERENCEremoves the false positive for comment-borne fences while keeping all intentional code uses. The same treatment is applied to extensionless executable script files, which may be JavaScript with comments.Full diff
Validation
Unit regression test passes:
Original reproduction no longer keeps tsx:
Control case (
```tsfence) still has tsx removed.Formatting:
vp fmt packages/cli/src/migration/migrator/tsx.ts packages/cli/src/migration/__tests__/tsx.spec.ts --checkpasses.Lint:
vp lint packages/cli/src/migration/migrator/tsx.ts packages/cli/src/migration/__tests__/tsx.spec.tsreports 0 warnings/errors.Full
vp check: skipped because the repository's docs dependencies (vitepress,@voidzero-dev/vitepress-theme, etc.) are not resolvable on this checkout, producing unrelatedTS2307errors. The changed files themselves pass focused fmt/lint.Alternatives considered
(?<!)``) would fix the exact code-fence shape but would still match tsx commands mentioned in//or `/* ... */` comments, so a more general comment-stripping approach was preferred.stripCommentshelper is minimal and keeps behavior consistent with the script-rewrite checks.Commit message
fix(cli): ignore tsx references inside comments when removing tsx devDependency
This report was made by an LLM. The analysis may be wrong, and the potential fix might not work, but is intended as a starting point for exploring the issue.
I've opened a pull request with this fix: #3299
fengmk2 commentedon Nov 2, 2026
Let me fix it.
fix(cli): ignore commented tsx references during migration (voidzero-…