Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Revamp user preferences serialization/deserialization#2272

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Draft
jakebailey wants to merge22 commits intomain
base:main
Choose a base branch
Loading
fromjabaile/user-prefs-json

Conversation

@jakebailey
Copy link
Member

@jakebaileyjakebailey commentedDec 8, 2025
edited
Loading

In working on improving the fourslash testing, I wanted to make user preferences serializable, such that we can directly go from JSON to prefs and back, and therefore also test out the "actual" JSON encoding.

Making that work sort of snowballed into a total refactor, which eliminates the error-prone switch case for decoding, replacing it with struct tags and reflect.

Prefs now look like this:

typeCodeLensUserPreferencesstruct {ReferencesCodeLensEnabledbool`raw:"referencesCodeLensEnabled" config:"referencesCodeLens.enabled"`ImplementationsCodeLensEnabledbool`raw:"implementationsCodeLensEnabled" config:"implementationsCodeLens.enabled"`ReferencesCodeLensShowOnAllFunctionsbool`raw:"referencesCodeLensShowOnAllFunctions" config:"referencesCodeLens.showOnAllFunctions"`ImplementationsCodeLensShowOnInterfaceMethodsbool`raw:"implementationsCodeLensShowOnInterfaceMethods" config:"implementationsCodeLens.showOnInterfaceMethods"`ImplementationsCodeLensShowOnAllClassMethodsbool`raw:"implementationsCodeLensShowOnAllClassMethods" config:"implementationsCodeLens.showOnAllClassMethods"`}

A struct tag gives the name for the JSON propery (used by Strada), and also the name over LSP.https://github.com/microsoft/vscode/blob/main/extensions/typescript-language-features/package.json.

All prefs can still be set by theunstable object (which happens first, based on the raw name).

Structs can be nested, e.g.:

typeUserPreferencesstruct {// ...ModuleSpecifierModuleSpecifierUserPreferences// ...}typeModuleSpecifierUserPreferencesstruct {ImportModuleSpecifierPreference modulespecifiers.ImportModuleSpecifierPreference`raw:"importModuleSpecifierPreference" config:"preferences.importModuleSpecifier"`// !!!// Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js"ImportModuleSpecifierEnding       modulespecifiers.ImportModuleSpecifierEndingPreference`raw:"importModuleSpecifierEnding" config:"preferences.importModuleSpecifierEnding"`// !!!AutoImportSpecifierExcludeRegexes []string`raw:"autoImportSpecifierExcludeRegexes" config:"preferences.autoImportSpecifierExcludeRegexes"`// !!!}

And, for prefs that are actually defined elsewhere (like the above), conversion works so long as the structure is the same, making this less error prone:

func (p*UserPreferences)ModuleSpecifierPreferences() modulespecifiers.UserPreferences {returnmodulespecifiers.UserPreferences(p.ModuleSpecifier)}

UserPreferences are also now considered to be immutable. They should never be mutated for any reason.Copy() can be used if you need to make a deep copy. Copying is used for a test, but also for cloning the defaults to use during unmarshalling. Nothing else needs to mutate any preferences.

Copy link
Contributor

CopilotAI 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 refactors the user preferences system to use reflection-based serialization/deserialization with struct tags, replacing the error-prone switch-case approach. The changes make preferences immutable, support direct JSON marshaling/unmarshaling, and simplify configuration by mapping TypeScript config paths to struct fields viapref tags.

Key changes:

  • Implements reflection-based parsing usingpref struct tags to map VS Code config paths to fields
  • Adds custom type parsers and serializers for enum-like types (Tristate, OrganizeImportsCollation, etc.)
  • Makes preferences immutable by removing mutation operations and returning the same pointer where possible
  • Adds genericdeepCopy implementation for creating copies when needed

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.

Show a summary per file
FileDescription
internal/ls/lsutil/userpreferences.goCore refactor: adds struct tags, reflection-based parsing/serialization, type parsers/serializers, and immutable Copy() implementation
internal/ls/lsutil/userpreferences_test.goNew roundtrip test validating JSON marshaling/unmarshaling and parseWorker
internal/project/snapshot.goRemoves unnecessary Copy() call when assigning immutable preferences
internal/project/session.goRemoves Copy() calls and returns direct pointer to immutable preferences
internal/lsp/server.goUpdates to use ParseUserPreferences() function and DefaultUserPreferences; fixes typo
internal/ls/autoimports.goUpdates field access to use nested ModuleSpecifier struct
internal/fourslash/fourslash.goUpdates to use DefaultUserPreferences directly instead of calling constructor

casereflect.Int:
returnint(field.Int())
casereflect.String:
returnfield.String()
Copy link

CopilotAIDec 8, 2025

Choose a reason for hiding this comment

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

TheserializeField function will serialize empty string values for string-based enums (likeQuotePreference,JsxAttributeCompletionStyle, andIncludePackageJsonAutoImports) when they have their "Unknown" zero value. This causes these fields to be included in the serialized JSON as empty strings rather than being omitted.

Consider either:

  1. Adding a check to returnnil for empty string values in thereflect.String case
  2. Adding custom serializers for these types (similar toIncludeInlayParameterNameHints which handles empty strings)

This would prevent empty/zero values from polluting the serialized output.

Suggested change
returnfield.String()
s:=field.String()
ifs=="" {
returnnil
}
returns

Copilot uses AI. Check for mistakes.

Choose a reason for hiding this comment

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

Do we need some sort ofomitzero?

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Eh, not sure. The roundtripping is not perfect because these types were switched away from things like*string for convenience.

PreferTypeOnlyAutoImportsbool// !!!
ModuleSpecifierModuleSpecifierUserPreferences

IncludePackageJsonAutoImportsIncludePackageJsonAutoImports`pref:"preferences.includePackageJsonAutoImports"`// !!!

Choose a reason for hiding this comment

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

Do you think it would be better to just omit these until the code is used?

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

IIRC this is upcoming, so it would just be churn to remove I think...

Comment on lines 546 to 553
ifname=="unstable" {
continue
}
// Skip known VS Code config sections that are handled by path-based parsing
switchname {
case"autoImports":
p.set("includeCompletionsForModuleExports",value)
case"objectLiteralMethodSnippets":
ifv,ok:=value.(map[string]any);ok {
p.set("includeCompletionsWithObjectLiteralMethodSnippets",parseEnabledBool(v))
}
case"classMemberSnippets":
ifv,ok:=value.(map[string]any);ok {
p.set("includeCompletionsWithClassMemberSnippets",parseEnabledBool(v))
case"preferences","suggest","inlayHints","referencesCodeLens",
"implementationsCodeLens","workspaceSymbols","format","tsserver","tsc","experimental":
continue

Choose a reason for hiding this comment

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

What's an example of a path that doesn't need to be skipped?

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Yeah, I think this is actually dead code.

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Ah, so this was here to roundtrip unstable settings, but it isn't doing that right.

I'm going to rethink unstable settings. Probably just make them explicit.

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Okay, so the sort of problem is that we used to allow user prefs from like, anywhere, so you could have a object with say,{ "quotePreference": ... } and that would be read in, which is a validUserPreferences object. But, now over LSP, we are expecting things overtypescript. So it's maybe a question of what non-vscode people do.

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

And, VS Code calls itquoteStyle, while preferences called itquotePreference, so that's uh, great

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

I split things apart so that we can both have the raw JS name we used to have, but also the LSP-based preference name.

casereflect.Int:
returnint(field.Int())
casereflect.String:
returnfield.String()

Choose a reason for hiding this comment

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

Do we need some sort ofomitzero?

prefCopy.AutoImportFileExcludePatterns=slices.Clone(p.AutoImportFileExcludePatterns)
return&prefCopy
typefieldInfostruct {
rawNamestring// raw name for unstable section lookup (e.g., "quotePreference")

Choose a reason for hiding this comment

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

So is the idea that to get to this, a user can only write the explicit path ortypescript.unstable.quotePreference?

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Yeah. Either, you are usingunstable and you were working by spread directly intoUserPreferences in Strada, or you were using the "real" name VS Code had, which takes precedence via the spread.

Arguably, this means we should entirely delete any unstable thing with a real name, though, because VS Code actually entirely overwrote these (by nature ofconfig.get having defaults):https://github.com/microsoft/vscode/blob/f9e3a710d84879d41431e7c17d8a8fe8612dfb26/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts#L186

So maybe I went overkill and we shouldn't allow both names.

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Basically, it's hard to balance "LSP VS Code style settings" with "the old tsserver UserPreferences", defaults, spread, and then maybe even other editors...

So, I don't know what is exactly right.

Choose a reason for hiding this comment

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

I think in this case, needing to support all theseunstable settings years later kind of defeats the purpose - but we'll probably need to support newunstable properties again anyway, right? So I'm not against keeping support around for it as a concept, and it is virtually no work for us to keep the current ones going if we want 6.0/7.0 to be smoother.

Choose a reason for hiding this comment

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

(So your call but I think this is fine)

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Probably I'll just keep this.

@jakebailey
Copy link
MemberAuthor

I think I'm going to wait for the other two user pref related PRs to finish and just fix this PR instead of forcing that on everyone.

@jakebaileyjakebailey marked this pull request as draftDecember 16, 2025 23:29
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

Copilot code reviewCopilotCopilot left review comments

@DanielRosenwasserDanielRosenwasserDanielRosenwasser approved these changes

@andrewbranchandrewbranchAwaiting requested review from andrewbranch

@iisaduaniisaduanAwaiting requested review from iisaduan

At least 1 approving review is required to merge this pull request.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

3 participants

@jakebailey@DanielRosenwasser

[8]ページ先頭

©2009-2025 Movatter.jp