Skip to content

Fix transform-case actions changing strings and comments#313559

Open
maruthang wants to merge 1 commit intomicrosoft:mainfrom
maruthang:fix/issue-149616-transform-case-skip-strings
Open

Fix transform-case actions changing strings and comments#313559
maruthang wants to merge 1 commit intomicrosoft:mainfrom
maruthang:fix/issue-149616-transform-case-skip-strings

Conversation

@maruthang
Copy link
Copy Markdown
Contributor

Summary

Adds a new opt-in editor setting editor.transformCase.skipStringsAndComments (boolean, default false). When enabled, the case-transformation actions — Transform to Uppercase, Lowercase, Title Case, Snake Case, Camel Case, Pascal Case, Kebab Case — leave ranges classified as strings or comments by the active language's tokenizer untouched.

Default behavior is preserved: with the setting off, the actions take the original code path and produce byte-identical output to before.

Fixes #149616

Implementation

  • New structured editor option editor.transformCase in src/vs/editor/common/config/editorOptions.ts, mirroring the editor.comments.* group so future sub-keys can be added without churn.
  • Skip logic lives in AbstractCaseAction.run() in src/vs/editor/contrib/linesOperations/browser/linesOperations.ts. Two helpers:
    • isRangeInStringOrComment — used when the user invokes the action with an empty selection (cursor in a word). Returns true only when every covered token is StandardTokenType.String or StandardTokenType.Comment.
    • splitRangeAroundStringsAndComments — used for non-empty selections. Walks the line tokens and emits a list of "code" sub-ranges that exclude any string/comment spans. Each surviving sub-range is transformed independently via the existing _modifyText(text, wordSeparators) hook, which means every concrete action — Upper/Lower/Title/Snake/Camel/Pascal/Kebab — inherits the new behavior without per-action plumbing.
  • Uses model.tokenization.forceTokenization + findTokenIndexAtOffset, the same pattern as TrimTrailingWhitespaceCommand.

Test plan

  • 9 new unit tests in linesOperations.test.ts covering:
    • Default-off behavior is unchanged (string is upper-cased).
    • Uppercase / Lowercase / Title Case all preserve string literals when the setting is on.
    • Line comments are preserved.
    • Multiple strings on a single line are all preserved.
    • Cursor inside a string with empty selection is left untouched.
    • Cursor inside an identifier with empty selection is still transformed.
    • Multi-line selection preserves strings on each line.
  • Manual: open a JS/TS file with strings and comments, set "editor.transformCase.skipStringsAndComments": true, select code, run "Transform to Uppercase" — strings and comments untouched.

The custom mocha tests use a tiny tokenizer ('…' = string, //…EOL = comment) so the test is focused on the skip behavior rather than language plumbing.

Adds an opt-in editor.transformCase.skipStringsAndComments setting
(default false) that excludes string and comment ranges from the case
transformation actions (Upper/Lower/Title/Snake/Camel/Pascal/Kebab).
Default behavior is unchanged.

Fixes microsoft#149616
Copilot AI review requested due to automatic review settings April 30, 2026 18:23
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in editor setting to make case-transformation actions skip tokens classified as strings/comments by the active tokenizer, preserving existing behavior when disabled.

Changes:

  • Introduces editor.transformCase.skipStringsAndComments (default false) via a new structured editor option group.
  • Updates AbstractCaseAction.run() to split selections around string/comment token spans (and no-op when the cursor word is inside them).
  • Adds unit tests with a lightweight tokenizer to validate skip behavior for strings/comments.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/vs/editor/common/config/editorOptions.ts Registers the new structured transformCase editor option and setting key.
src/vs/editor/contrib/linesOperations/browser/linesOperations.ts Implements skip logic for transform-case actions based on tokenizer StandardTokenType.
src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts Adds unit tests (with a custom tokenizer) covering skip behavior and default behavior.

Comment on lines +1453 to +1456
withTestCodeEditor(model, { transformCase: { skipStringsAndComments: options.skipStringsAndComments } }, (editor) => {
callback(editor, model);
});
disposables.dispose();
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

withTokenizedTestEditor disposes the DisposableStore only after withTestCodeEditor returns. If an assertion in callback throws, disposables.dispose() will be skipped, which can leak the language/tokenization registrations and make subsequent tests flaky. Wrap the withTestCodeEditor/callback invocation in a try/finally and dispose in the finally block.

Suggested change
withTestCodeEditor(model, { transformCase: { skipStringsAndComments: options.skipStringsAndComments } }, (editor) => {
callback(editor, model);
});
disposables.dispose();
try {
withTestCodeEditor(model, { transformCase: { skipStringsAndComments: options.skipStringsAndComments } }, editor => {
callback(editor, model);
});
} finally {
disposables.dispose();
}

Copilot uses AI. Check for mistakes.
Comment on lines +1147 to +1151
} else if (skipStringsAndComments) {
const codeRanges = splitRangeAroundStringsAndComments(model, selection);
for (const codeRange of codeRanges) {
const text = model.getValueInRange(codeRange);
textEdits.push(EditOperation.replace(codeRange, this._modifyText(text, wordSeparators)));
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When skipStringsAndComments is enabled, it's possible for a selection/word to be entirely inside a string/comment, resulting in textEdits staying empty (e.g. due to the continue/no codeRanges). The action currently still calls pushUndoStop()/executeEdits() which can introduce undo stack elements even though no text changed. Consider early-returning when textEdits.length === 0 (before pushing undo stops) to avoid no-op undo stops.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enabling using Transform to Lower/Upper/Title Case without changing String Literals

3 participants