Skip to content

Commit 98f7229

Browse files
authored
feat: support typescript 5.6 (#2545)
* feat: support typescript 5.6 * fix build * autoImportSpecifierExcludeRegexes and organizeImports preference * what * bumps typescript-auto-import-cache * missed one * use commit characters from typescript when possible * format * bump language server dep
1 parent 01fbe14 commit 98f7229

File tree

19 files changed

+244
-67
lines changed

19 files changed

+244
-67
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"lint": "prettier --check ."
1414
},
1515
"dependencies": {
16-
"typescript": "^5.5.2"
16+
"typescript": "^5.6.3"
1717
},
1818
"devDependencies": {
1919
"cross-env": "^7.0.2",

packages/language-server/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@
6262
"prettier-plugin-svelte": "^3.2.6",
6363
"svelte": "^4.2.19",
6464
"svelte2tsx": "workspace:~",
65-
"typescript": "^5.5.2",
66-
"typescript-auto-import-cache": "^0.3.3",
65+
"typescript": "^5.6.3",
66+
"typescript-auto-import-cache": "^0.3.5",
6767
"vscode-css-languageservice": "~6.3.0",
6868
"vscode-html-languageservice": "~5.3.0",
6969
"vscode-languageserver": "9.0.1",

packages/language-server/src/ls-config.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,20 @@ export interface TsUserPreferencesConfig {
221221
includePackageJsonAutoImports?: ts.UserPreferences['includePackageJsonAutoImports'];
222222

223223
preferTypeOnlyAutoImports?: ts.UserPreferences['preferTypeOnlyAutoImports'];
224+
225+
autoImportSpecifierExcludeRegexes?: string[];
226+
227+
organizeImports?: TsOrganizeImportPreferencesConfig;
228+
}
229+
230+
interface TsOrganizeImportPreferencesConfig {
231+
accentCollation: ts.UserPreferences['organizeImportsAccentCollation'];
232+
caseFirst: ts.UserPreferences['organizeImportsCaseFirst'] | 'default';
233+
caseSensitivity: ts.UserPreferences['organizeImportsIgnoreCase'];
234+
collation: ts.UserPreferences['organizeImportsCollation'];
235+
locale: ts.UserPreferences['organizeImportsLocale'];
236+
numericCollation: ts.UserPreferences['organizeImportsNumericCollation'];
237+
typeOrder: ts.UserPreferences['organizeImportsTypeOrder'] | 'auto';
224238
}
225239

226240
/**
@@ -473,11 +487,34 @@ export class LSConfigManager {
473487
includeInlayPropertyDeclarationTypeHints: inlayHints?.propertyDeclarationTypes?.enabled,
474488
includeInlayVariableTypeHintsWhenTypeMatchesName:
475489
inlayHints?.variableTypes?.suppressWhenTypeMatchesName === false,
476-
477-
interactiveInlayHints: true
490+
interactiveInlayHints: true,
491+
492+
autoImportSpecifierExcludeRegexes:
493+
config.preferences?.autoImportSpecifierExcludeRegexes,
494+
495+
organizeImportsAccentCollation: config.preferences?.organizeImports?.accentCollation,
496+
organizeImportsCollation: config.preferences?.organizeImports?.collation,
497+
organizeImportsCaseFirst: this.withDefaultAsUndefined(
498+
config.preferences?.organizeImports?.caseFirst,
499+
'default'
500+
),
501+
organizeImportsIgnoreCase: this.withDefaultAsUndefined(
502+
config.preferences?.organizeImports?.caseSensitivity,
503+
'auto'
504+
),
505+
organizeImportsLocale: config.preferences?.organizeImports?.locale,
506+
organizeImportsNumericCollation: config.preferences?.organizeImports?.numericCollation,
507+
organizeImportsTypeOrder: this.withDefaultAsUndefined(
508+
config.preferences?.organizeImports?.typeOrder,
509+
'auto'
510+
)
478511
};
479512
}
480513

514+
private withDefaultAsUndefined<T, O extends T>(value: T, def: O): Exclude<T, O> | undefined {
515+
return value === def ? undefined : (value as Exclude<T, O>);
516+
}
517+
481518
getTsUserPreferences(
482519
lang: TsUserConfigLang,
483520
normalizedWorkspacePath: string | null

packages/language-server/src/plugins/PluginHost.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,22 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
171171
});
172172
}
173173

174+
let itemDefaults: CompletionList['itemDefaults'];
175+
if (completions.length === 1) {
176+
itemDefaults = completions[0]?.result.itemDefaults;
177+
} else {
178+
// don't apply items default to the result of other plugins
179+
for (const completion of completions) {
180+
const itemDefaults = completion.result.itemDefaults;
181+
if (!itemDefaults) {
182+
continue;
183+
}
184+
completion.result.items.forEach((item) => {
185+
item.commitCharacters ??= itemDefaults.commitCharacters;
186+
});
187+
}
188+
}
189+
174190
let flattenedCompletions = flatten(
175191
completions.map((completion) => completion.result.items)
176192
);
@@ -194,7 +210,10 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
194210
);
195211
}
196212

197-
return CompletionList.create(flattenedCompletions, isIncomplete);
213+
const result = CompletionList.create(flattenedCompletions, isIncomplete);
214+
result.itemDefaults = itemDefaults;
215+
216+
return result;
198217
}
199218

200219
async resolveCompletion(

packages/language-server/src/plugins/typescript/features/CompletionProvider.ts

Lines changed: 87 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ type LastCompletion = {
6666
completionList: AppCompletionList<CompletionResolveInfo> | null;
6767
};
6868

69+
interface CommitCharactersOptions {
70+
checkCommitCharacters: boolean;
71+
defaultCommitCharacters?: string[];
72+
isNewIdentifierLocation?: boolean;
73+
}
74+
6975
export class CompletionsProviderImpl implements CompletionsProvider<CompletionResolveInfo> {
7076
constructor(
7177
private readonly lsAndTsDocResolver: LSAndTSDocResolver,
@@ -237,10 +243,8 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionRe
237243
},
238244
formatSettings
239245
);
240-
const addCommitCharacters =
241-
// replicating VS Code behavior https://github.com/microsoft/vscode/blob/main/extensions/typescript-language-features/src/languageFeatures/completions.ts
242-
response?.isNewIdentifierLocation !== true &&
243-
(!tsDoc.parserError || isInScript(position, tsDoc));
246+
247+
const commitCharactersOptions = this.getCommitCharactersOptions(response, tsDoc, position);
244248
let completions = response?.entries || [];
245249

246250
const customCompletions = eventAndSlotLetCompletions.concat(tagCompletions ?? []);
@@ -289,7 +293,7 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionRe
289293
fileUrl,
290294
position,
291295
isCompletionInTag,
292-
addCommitCharacters,
296+
commitCharactersOptions,
293297
asStore,
294298
existingImports
295299
);
@@ -376,6 +380,27 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionRe
376380
}
377381

378382
const completionList = CompletionList.create(completionItems, !!tsDoc.parserError);
383+
if (
384+
commitCharactersOptions.checkCommitCharacters &&
385+
commitCharactersOptions.defaultCommitCharacters?.length
386+
) {
387+
const clientSupportsItemsDefault = this.configManager
388+
.getClientCapabilities()
389+
?.textDocument?.completion?.completionList?.itemDefaults?.includes(
390+
'commitCharacters'
391+
);
392+
393+
if (clientSupportsItemsDefault) {
394+
completionList.itemDefaults = {
395+
commitCharacters: commitCharactersOptions.defaultCommitCharacters
396+
};
397+
} else {
398+
completionList.items.forEach((item) => {
399+
item.commitCharacters ??= commitCharactersOptions.defaultCommitCharacters;
400+
});
401+
}
402+
}
403+
379404
this.lastCompletion = { key: document.getFilePath() || '', position, completionList };
380405

381406
return completionList;
@@ -578,6 +603,7 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionRe
578603
sortText: '-1',
579604
detail: info.name + ': ' + info.type,
580605
documentation: info.doc && { kind: MarkupKind.Markdown, value: info.doc },
606+
commitCharacters: [],
581607
textEdit: defaultTextEditRange
582608
? TextEdit.replace(this.cloneRange(defaultTextEditRange), name)
583609
: undefined
@@ -623,7 +649,7 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionRe
623649
uri: string,
624650
position: Position,
625651
isCompletionInTag: boolean,
626-
addCommitCharacters: boolean,
652+
commitCharactersOptions: CommitCharactersOptions,
627653
asStore: boolean,
628654
existingImports: Set<string>
629655
): AppCompletionItem<CompletionResolveInfo> | null {
@@ -669,7 +695,7 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionRe
669695
label,
670696
insertText,
671697
kind: scriptElementKindToCompletionItemKind(comp.kind),
672-
commitCharacters: addCommitCharacters ? this.commitCharacters : undefined,
698+
commitCharacters: this.getCommitCharacters(comp, commitCharactersOptions),
673699
// Make sure svelte component and runes take precedence
674700
sortText: isRunesCompletion || isSvelteComp ? '-1' : comp.sortText,
675701
preselect: isRunesCompletion || isSvelteComp ? true : comp.isRecommended,
@@ -734,6 +760,60 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionRe
734760
};
735761
}
736762

763+
private getCommitCharactersOptions(
764+
response: ts.CompletionInfo | undefined,
765+
tsDoc: SvelteDocumentSnapshot,
766+
position: Position
767+
): CommitCharactersOptions {
768+
if ((!isInScript(position, tsDoc) && tsDoc.parserError) || !response) {
769+
return {
770+
checkCommitCharacters: false
771+
};
772+
}
773+
774+
const isNewIdentifierLocation = response.isNewIdentifierLocation;
775+
let defaultCommitCharacters = response.defaultCommitCharacters;
776+
if (!isNewIdentifierLocation) {
777+
// This actually always exists although it's optional in the type, at least in ts 5.6,
778+
// so our commit characters are mostly fallback for older ts versions
779+
if (defaultCommitCharacters) {
780+
// this is controlled by a vscode setting that isn't available in the ts server so it isn't added to the language service
781+
defaultCommitCharacters?.push('(');
782+
} else {
783+
defaultCommitCharacters = this.commitCharacters;
784+
}
785+
}
786+
787+
return {
788+
checkCommitCharacters: true,
789+
defaultCommitCharacters,
790+
isNewIdentifierLocation
791+
};
792+
}
793+
794+
private getCommitCharacters(entry: ts.CompletionEntry, options: CommitCharactersOptions) {
795+
// https://github.com/microsoft/vscode/blob/d012408e88ffabd6456c367df4d343654da2eb10/extensions/typescript-language-features/src/languageFeatures/completions.ts#L504
796+
if (!options.checkCommitCharacters) {
797+
return undefined;
798+
}
799+
800+
const commitCharacters = entry.commitCharacters;
801+
// Ambient JS word based suggestions
802+
const skipCommitCharacters =
803+
entry.kind === ts.ScriptElementKind.warning ||
804+
entry.kind === ts.ScriptElementKind.string;
805+
806+
if (commitCharacters) {
807+
if (!options.isNewIdentifierLocation && !skipCommitCharacters) {
808+
return commitCharacters.concat('(');
809+
}
810+
811+
return commitCharacters;
812+
}
813+
814+
return skipCommitCharacters ? [] : undefined;
815+
}
816+
737817
private isExistingSvelteComponentImport(
738818
snapshot: SvelteDocumentSnapshot,
739819
name: string,

0 commit comments

Comments
 (0)