Skip to content

Conversation

@iisaduan
Copy link
Member

@iisaduan iisaduan commented Dec 12, 2025

Adds formatting options and links up the server's FormatCodeOptions with the formatting functions. Also adds in basic structure for js, javascript, ts, and typescript scoped settings.

Moves formatCodeSettings to lsutil

Formatting is now being tested in fourslash, although not all the commands are implemented due to non-formatting tests crashing on insertion. I've added a fourslashTest property to skip the check for those specific crashes, so that the rest of the test can proceed as normal. The flag(s) can easily be commented out if investigating these crashes.

Also fixes a formatting bug that was adding extra spaces after newlines.

Copy link
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

This PR adds formatting options support and integrates it with the language server, enables fourslash testing for formatting functionality, and fixes a formatting bug related to extra spaces after newlines.

Key changes:

  • Adds FormatCodeSettings structure with comprehensive formatting options
  • Refactors configuration management to support both TypeScript and JavaScript scoped settings
  • Fixes scanner bug that was calculating end-of-line positions incorrectly
  • Updates fourslash test infrastructure to support formatting tests
  • Reduces baseline diffs showing improved formatting convergence with TypeScript

Reviewed changes

Copilot reviewed 283 out of 283 changed files in this pull request and generated no comments.

Show a summary per file
File Description
internal/scanner/scanner.go Fixed off-by-one error in GetECMAEndLinePosition to correctly calculate line endings
internal/project/configuration.go New configuration structure supporting separate TypeScript/JavaScript preferences
internal/project/snapshot.go Refactored to use pointer-based Config and removed embedded format options
internal/project/session.go Updated to manage Config objects instead of bare UserPreferences
internal/ls/lsutil/formatcodeoptions.go New comprehensive formatting options with parsing and default values
internal/ls/lsutil/userpreferences.go Integrated FormatCodeSettings into UserPreferences structure
internal/ls/format.go Updated to use new format options from UserPreferences
internal/lsp/server.go Enhanced configuration request to support multiple scopes (typescript/ts/javascript/js)
Test files Multiple fourslash tests updated to use new verification methods
Baseline files Reduced diff files showing formatting convergence improvements

func (f *FourslashTest) SetFormatOption(t *testing.T, optionName string, value any) {
t.Helper()
newPreferences := f.userPreferences.Copy()
newPreferences.Set(optionName, value)
Copy link
Member

Choose a reason for hiding this comment

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

I don't particularly setting these as strings; can we generate these differently so we can stay static? My options refactor will remove any sort of string-based setting

Comment on lines +1438 to +1439
End: currentCaretPosition,
Start: currentCaretPosition,
Copy link
Member

Choose a reason for hiding this comment

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

Seems like a random change


// Inserts the text currently at the caret position character by character, as if the user typed it.
func (f *FourslashTest) typeText(t *testing.T, text string) {
// temprorary -- this disables tests failing if format crashes; this unblocks unrelated tests such as codefixes
Copy link
Member

Choose a reason for hiding this comment

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

Hm, I see; how much does this matter?

Copy link
Member Author

Choose a reason for hiding this comment

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

there's currently ~10 passing tests that this unblocks, and some more (~10?) in failingtests.txt that fail for other reasons besides the formatting crashes. There's more unparsed tests (they have commands like format.Range for example) that I didn't include due to trying to get this pr out faster, that also crash. These numbers are a lot lower after the formatting bugs that I've fixed+included in this PR. I'm very pro-keeping this until either all of autoimports and completions tests are passing or formatting bugs are more ironed out. It'll be a lot less annoying for anyone working on completions or autoimports to work on their features without the formatter doing unrelated things.

Copy link
Member Author

Choose a reason for hiding this comment

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

more info:
The current disabling only disables reporting crashes when calling format on keystroke, which in strada fourslash was called after every character passed into edit.insert. However, completions and autoimport/codefix tests don't usually use edit.insert to simulate keystrokes-- they use edit.insert to simulate the file result after accepting a completion or codefixaction, which in the real world, returns already formatted text, and doesn't format on keystroke. We also don't really lose any formatting test coverage because if the formatter crashes, the check on formatted text will be wrong

Section: ptrTo("typescript"),
},
{
Section: ptrTo("ts"),
Copy link
Member

Choose a reason for hiding this comment

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

The actual section is ts/js, but we should just ignore those for now since we don't pull inferred project compiler options out of it yet.

} else if settings, ok := params.Settings.([]any); ok {
s.session.Configure(project.ParseConfiguration(settings))
} else if settings, ok := params.Settings.(map[string]any); ok {
s.session.Configure(project.ParseConfiguration([]any{settings["typescript"], settings["ts"], settings["javascript"], settings["js"]}))
Copy link
Member

Choose a reason for hiding this comment

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

If we want to respond to this, we're going to have to also register these in handleInitialized

}

// any non-nil field in b is copied into a
func (a *Config) CopyInto(b *Config) *Config {
Copy link
Member

Choose a reason for hiding this comment

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

Do we need mutability for any of the prefs like this? I had thought the change I made before to make it all immutable worked pretty well

s.configRWMu.Lock()
defer s.configRWMu.Unlock()
return s.userPreferences.Copy()
return s.workspaceConfig.Ts.Copy()
Copy link
Member

Choose a reason for hiding this comment

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

What is the plan for returning the "right" one?

If we aren't handling that yet, can we just skip doing the JS stuff for the time being?

ch, size := utf8.DecodeRuneInString(sourceFile.Text()[pos:])
if size == 0 || stringutil.IsLineBreak(ch) {
return pos
return pos - 1
Copy link
Member

Choose a reason for hiding this comment

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

I feel suspicious of this; some compiler baselines have changed

Copy link
Member Author

Choose a reason for hiding this comment

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

I think the error message was using the wrong function. I will fix that

Copy link
Member Author

Choose a reason for hiding this comment

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

yup, the compiler error function was ported wrong, probably because this function was ported wrong

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants