Skip to content

Commit

Permalink
(fix) better $types completions (#1609)
Browse files Browse the repository at this point in the history
Add them as a distinct item to the list of all possible completions instead of manipulating an existing one
  • Loading branch information
dummdidumm committed Aug 29, 2022
1 parent 15dcfd1 commit a554ff9
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 69 deletions.
Expand Up @@ -44,6 +44,7 @@ import { findContainingNode, getComponentAtPosition, isPartOfImportStatement } f

export interface CompletionEntryWithIdentifier extends ts.CompletionEntry, TextDocumentIdentifier {
position: Position;
__is_sveltekit$typeImport?: boolean;
}

type validTriggerCharacter = '.' | '"' | "'" | '`' | '/' | '@' | '<' | '#';
Expand Down Expand Up @@ -257,6 +258,42 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
.map((comp) => this.fixTextEditRange(wordRangeStartPosition, comp))
.concat(eventAndSlotLetCompletions);

// Add ./$types imports for SvelteKit since TypeScript is bad at it
if (basename(filePath).startsWith('+')) {
const $typeImports = new Map<string, CompletionItem>();
for (const c of completionItems) {
if (c.data.source?.includes('.svelte-kit/types')) {
$typeImports.set(c.label, c);
}
}
for (const $typeImport of $typeImports.values()) {
// resolve path from filePath to svelte-kit/types
// src/routes/foo/+page.svelte -> .svelte-kit/types/foo/$types.d.ts
const routesFolder = document.config?.kit?.files?.routes || 'src/routes';
const relativeFilePath = filePath.split(routesFolder)[1]?.slice(1);
if (relativeFilePath) {
completionItems.push({
...$typeImport,
// Ensure it's sorted above the other imports
sortText: !isNaN(Number($typeImport.sortText))
? String(Number($typeImport.sortText) - 1)
: $typeImport.sortText,
data: {
...$typeImport.data,
__is_sveltekit$typeImport: true,
source:
$typeImport.data.source.split('.svelte-kit/types')[0] +
// note the missing .d.ts at the end - TS wants it that way for some reason
`.svelte-kit/types/${routesFolder}/${dirname(
relativeFilePath
)}/$types`,
data: undefined
}
});
}
}
}

const completionList = CompletionList.create(completionItems, !!tsDoc.parserError);
this.lastCompletion = { key: document.getFilePath() || '', position, completionList };

Expand Down Expand Up @@ -516,7 +553,7 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
completionItem: AppCompletionItem<CompletionEntryWithIdentifier>,
cancellationToken?: CancellationToken
): Promise<AppCompletionItem<CompletionEntryWithIdentifier>> {
let { data: comp } = completionItem;
const { data: comp } = completionItem;
const { tsDoc, lang, userPreferences } = await this.lsAndTsDocResolver.getLSAndTSDoc(
document
);
Expand All @@ -527,44 +564,21 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
return completionItem;
}

const is$typeImport = !!comp.__is_sveltekit$typeImport;

const errorPreventingUserPreferences = comp.source?.endsWith('.svelte')
? this.fixUserPreferencesForSvelteComponentImport(userPreferences)
: userPreferences;

let is$typeImport = false;
const originalComp = { ...comp };
if (basename(filePath).startsWith('+') && comp.source?.includes('.svelte-kit/types')) {
// resolve path from filePath to svelte-kit/types
// src/routes/foo/+page.svelte -> .svelte-kit/types/foo/$types.d.ts
const routesFolder = document.config?.kit?.files?.routes || 'src/routes';
const relativeFilePath = filePath.split(routesFolder)[1]?.slice(1);
if (relativeFilePath) {
is$typeImport = true;
comp.source =
comp.source.split('.svelte-kit/types')[0] +
// note the missing .d.ts at the end - TS wants it that way for some reason
`.svelte-kit/types/${routesFolder}/${dirname(relativeFilePath)}/$types`;
comp.data = undefined;
}
}

const getDetail = () =>
lang.getCompletionEntryDetails(
filePath,
tsDoc.offsetAt(tsDoc.getGeneratedPosition(comp!.position)),
comp!.name,
{},
comp!.source,
errorPreventingUserPreferences,
comp!.data
);
let detail = getDetail();
if (!detail && is$typeImport) {
// try again
is$typeImport = false;
comp = originalComp;
detail = getDetail();
}
const detail = lang.getCompletionEntryDetails(
filePath,
tsDoc.offsetAt(tsDoc.getGeneratedPosition(comp!.position)),
comp!.name,
{},
comp!.source,
errorPreventingUserPreferences,
comp!.data
);

if (detail) {
const { detail: itemDetail, documentation: itemDocumentation } =
Expand Down
80 changes: 46 additions & 34 deletions packages/typescript-plugin/src/language-service/completions.ts
Expand Up @@ -12,6 +12,50 @@ export function decorateCompletions(ls: ts.LanguageService, logger: Logger): voi
if (!completions) {
return completions;
}

// Add ./$types imports for SvelteKit since TypeScript is bad at it
if (basename(fileName).startsWith('+')) {
const $typeImports = new Map<string, ts.CompletionEntry>();
for (const c of completions.entries) {
if (c.source?.includes('.svelte-kit/types') && c.data) {
$typeImports.set(c.name, c);
}
}
for (const $typeImport of $typeImports.values()) {
// resolve path from FileName to svelte-kit/types
// src/routes/foo/+page.svelte -> .svelte-kit/types/foo/$types.d.ts
const routesFolder = 'src/routes'; // TODO somehow get access to kit.files.routes in here
const relativeFileName = fileName.split(routesFolder)[1]?.slice(1);

if (relativeFileName) {
const modifiedSource =
$typeImport.source!.split('.svelte-kit/types')[0] +
// note the missing .d.ts at the end - TS wants it that way for some reason
`.svelte-kit/types/${routesFolder}/${dirname(relativeFileName)}/$types`;
completions.entries.push({
...$typeImport,
// Ensure it's sorted above the other imports
sortText: !isNaN(Number($typeImport.sortText))
? String(Number($typeImport.sortText) - 1)
: $typeImport.sortText,
source: modifiedSource,
data: {
...$typeImport.data,
fileName: $typeImport.data!.fileName?.replace(
$typeImport.source!,
modifiedSource
),
moduleSpecifier: $typeImport.data!.moduleSpecifier?.replace(
$typeImport.source!,
modifiedSource
),
__is_sveltekit$typeImport: true
} as any
});
}
}
}

return {
...completions,
entries: completions.entries.map((entry) => {
Expand Down Expand Up @@ -39,28 +83,9 @@ export function decorateCompletions(ls: ts.LanguageService, logger: Logger): voi
preferences,
data
) => {
let is$typeImport = false;
const originalSource = source;
const originalData = data ? { ...data } : undefined;
if (basename(fileName).startsWith('+') && source?.includes('.svelte-kit/types')) {
// resolve path from FileName to svelte-kit/types
// src/routes/foo/+page.svelte -> .svelte-kit/types/foo/$types.d.ts
const routesFolder = 'src/routes'; // TODO somehow get access to kit.files.routes in here
const relativeFileName = fileName.split(routesFolder)[1]?.slice(1);
if (relativeFileName) {
is$typeImport = true;
source =
source.split('.svelte-kit/types')[0] +
// note the missing .d.ts at the end - TS wants it that way for some reason
`.svelte-kit/types/${routesFolder}/${dirname(relativeFileName)}/$types`;
if (data) {
data.fileName = data.fileName?.replace(originalSource!, source);
data.moduleSpecifier = data.moduleSpecifier?.replace(originalSource!, source);
}
}
}
const is$typeImport = (data as any).__is_sveltekit$typeImport;

let details = getCompletionEntryDetails(
const details = getCompletionEntryDetails(
fileName,
position,
entryName,
Expand All @@ -69,19 +94,6 @@ export function decorateCompletions(ls: ts.LanguageService, logger: Logger): voi
preferences,
data
);
if (!details && is$typeImport) {
// Try again
is$typeImport = false;
details = getCompletionEntryDetails(
fileName,
position,
entryName,
formatOptions,
originalSource,
preferences,
originalData
);
}

if (details) {
if (is$typeImport) {
Expand Down

0 comments on commit a554ff9

Please sign in to comment.