Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/ripe-sloths-take.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte-check': patch
---

fix: update --tsgo-experimental-api for typescript 7.1
2 changes: 1 addition & 1 deletion packages/language-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"@types/mocha": "^10.0.10",
"@types/node": "^18.0.0",
"@types/sinon": "^7.5.2",
"@typescript/native-preview": "~7.0.0-dev.20260703.1",
"@typescript/native": "npm:typescript@7.1.0-beta",
"cross-env": "^7.0.2",
"mocha": "^11.7.6",
"sinon": "^11.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { FileSystem } from '@typescript/native-preview/unstable/fs' with { 'resolution-mode': 'import' };
import fs from 'node:fs';
import { dirname } from 'node:path';
import path from 'path';
Expand Down Expand Up @@ -49,7 +48,7 @@ import {
} from '../../typescript/features/utils';
import { isAttributeName, isEventHandler } from '../../typescript/svelte-ast-utils';
import { hasNonZeroRange, isSvelteFilePath, mapSeverity } from '../../typescript/utils';
import { tsApiSync, tsAst } from '../types';
import { tsApiSync, tsAst, tsFs } from '../types';
import {
gatherIdentifiers,
getStartOfNode,
Expand All @@ -63,9 +62,18 @@ const D_SVELTE_TS_EXTENSION = '.d.svelte.ts';
const D_SVELTE_TS_LENGTH = D_SVELTE_TS_EXTENSION.length;
const SVELTE_DTS_EXTENSION = '.svelte.d.ts';

export interface TSModules {
apiModule: typeof tsApiSync;
astModule: typeof tsAst;
fsModule: typeof tsFs;
tsVersion: string;
}

export class SvelteCheckTSGoDiagnosticsProvider implements DiagnosticsProvider {
private readonly api: tsApiSync.API;
private readonly tsApiModule: typeof tsApiSync;

private readonly tsFsModule: typeof tsFs;
private readonly files: FileMap<DocumentSnapshot> = new FileMap();
private readonly virtualFiles: FileMap<string> = new FileMap();
private readonly tsAstModule: typeof tsAst;
Expand All @@ -78,8 +86,10 @@ export class SvelteCheckTSGoDiagnosticsProvider implements DiagnosticsProvider {
*/
private projectConfig: ts.ParsedCommandLine;
private pendingConfigLoading: Promise<void> | undefined;
private readonly useStableApi: boolean;

private createDocument: (filePath: string, content: string) => Document;
private snapshot: tsApiSync.Snapshot | undefined;

private pendingChanges = {
created: new Set<string>(),
Expand All @@ -88,14 +98,14 @@ export class SvelteCheckTSGoDiagnosticsProvider implements DiagnosticsProvider {
};

constructor(
apiModule: typeof tsApiSync,
tsAstModule: typeof tsAst,
modules: TSModules,
tsconfigPath: string,
ambientTypesSource: string,
createDocument: (filePath: string, content: string) => Document
) {
this.tsApiModule = apiModule;
this.tsAstModule = tsAstModule;
this.tsApiModule = modules.apiModule;
this.tsAstModule = modules.astModule;
this.tsFsModule = modules.fsModule;
this.tsconfigPath = tsconfigPath;
this.virtualTsconfigPath = path.join(
path.dirname(tsconfigPath),
Expand All @@ -112,7 +122,9 @@ export class SvelteCheckTSGoDiagnosticsProvider implements DiagnosticsProvider {
emitJsDoc: true
};

this.api = new apiModule.API({
const [major, minor] = (modules.tsVersion || '7.0').split('.').map(Number);
this.useStableApi = major > 7 || (major === 7 && minor >= 1);
this.api = new this.tsApiModule.API({
fs: this.createFsProxy()
});
this.projectConfig = this.parseConfig();
Expand Down Expand Up @@ -155,12 +167,22 @@ export class SvelteCheckTSGoDiagnosticsProvider implements DiagnosticsProvider {
return [parserErrorDiag];
}

const virtualPath = toVirtualPath(tsDoc);
const path = toVirtualPath(tsDoc);
const diagnostics: tsApiSync.Diagnostic[] = [];
const program = project.program;
diagnostics.push(...(program.getSyntacticDiagnostics(virtualPath) || []));
diagnostics.push(...(program.getSuggestionDiagnostics(virtualPath) || []));
diagnostics.push(...(program.getSemanticDiagnostics(virtualPath) || []));
if (this.useStableApi) {
this.api
.batch(
program.getSyntacticDiagnostics.gen(path),
program.getSuggestionDiagnostics.gen(path),
program.getSemanticDiagnostics.gen(path)
)
.forEach((diag) => diagnostics.push(...diag));
} else {
diagnostics.push(...(program.getSyntacticDiagnostics(path) || []));
diagnostics.push(...(program.getSuggestionDiagnostics(path) || []));
diagnostics.push(...(program.getSemanticDiagnostics(path) || []));
}

return mapAndFilterDiagnostics(
this.tsAstModule,
Expand All @@ -177,17 +199,50 @@ export class SvelteCheckTSGoDiagnosticsProvider implements DiagnosticsProvider {
await this.pendingConfigLoading;
this.pendingConfigLoading = undefined;
}
const snapshot = this.api.updateSnapshot({
openProject: this.virtualTsconfigPath,
fileChanges: {
created: Array.from(this.pendingChanges.created),
changed: Array.from(this.pendingChanges.changed),
deleted: Array.from(this.pendingChanges.deleted)

const changes = {
created: Array.from(this.pendingChanges.created),
changed: Array.from(this.pendingChanges.changed),
deleted: Array.from(this.pendingChanges.deleted)
};

let snapshot: tsApiSync.Snapshot;
if (this.useStableApi) {
if (!this.snapshot) {
this.snapshot = this.api.createSnapshot({
openProjects: [this.virtualTsconfigPath],
fileNotifications: changes
});
} else if (
changes.created.length !== 0 ||
changes.changed.length !== 0 ||
changes.deleted.length !== 0
) {
this.snapshot = this.snapshot.update({
fileNotifications: changes
});
}
});
snapshot = this.snapshot;
} else {
// @ts-expect-error typescript < 7.1
snapshot = this.api.updateSnapshot({
openProject: this.virtualTsconfigPath,
fileChanges: {
created: Array.from(this.pendingChanges.created),
changed: Array.from(this.pendingChanges.changed),
deleted: Array.from(this.pendingChanges.deleted)
}
});
}

this.pendingChanges.created.clear();
this.pendingChanges.changed.clear();
this.pendingChanges.deleted.clear();
if (this.useStableApi) {
const project = snapshot.getConfiguredProject(this.virtualTsconfigPath);
return project;
}
// @ts-expect-error typescript < 7.1
const project = snapshot.getProject(this.virtualTsconfigPath);
return project;
}
Expand Down Expand Up @@ -334,9 +389,22 @@ export class SvelteCheckTSGoDiagnosticsProvider implements DiagnosticsProvider {
return this.projectConfig;
}

private createFsProxy(): Required<
Pick<FileSystem, 'readFile' | 'fileExists' | 'getAccessibleEntries'>
> {
private createFsProxy(): tsFs.FileSystemCallbacks {
// optional chaining for supporting older version.
// For older version, undefined means using the default system callbacks. So this setup is type safe and also compatible with both older and newer versions.

const serverFS = this.tsFsModule?.serverFS;
const defaultSys: tsFs.FileSystemCallbacks = {
fileExists: serverFS?.useOS,
directoryExists: serverFS?.useOS,
getAccessibleEntries: serverFS?.useOS,
stat: serverFS?.useOS,
realpath: serverFS?.useOS,
writeFile: serverFS?.useOS,
removeFile: serverFS?.useOS,
readFile: serverFS?.useOS
};

const service = this;
const kitFiles: Parameters<typeof internalHelpers.isKitFile>[1] = {
serverHooksPath: 'src/hooks.client',
Expand All @@ -346,10 +414,17 @@ export class SvelteCheckTSGoDiagnosticsProvider implements DiagnosticsProvider {
};

return {
directoryExists: serverFS?.useOS,
stat: serverFS?.useOS,
realpath: serverFS?.useOS,
writeFile: serverFS?.useOS,
removeFile: serverFS?.useOS,
getAccessibleEntries(directory: string) {
const realFiles: string[] = [];
const directories: string[] = [];
const resultFiles: string[] = [];
/**Names from `files` or `directories` that are symbolic links. */
const symlinks: string[] = [];
try {
const entries = fs.readdirSync(directory, { withFileTypes: true });
for (const entry of entries) {
Expand All @@ -365,18 +440,20 @@ export class SvelteCheckTSGoDiagnosticsProvider implements DiagnosticsProvider {
} else if (stats.isDirectory()) {
directories.push(entry.name);
}
symlinks.push(entry.name);
}
}
for (const file of realFiles) {
addFileEntry(path.join(directory, file), realFiles, resultFiles);
}
return {
files: resultFiles,
directories
directories,
symlinks
};
} catch (error) {
Logger.error(`Error reading directory ${directory}:`, error);
return undefined;
return serverFS?.error;
}
},
readFile(path: string) {
Expand Down Expand Up @@ -404,8 +481,7 @@ export class SvelteCheckTSGoDiagnosticsProvider implements DiagnosticsProvider {
return snapshot.getFullText();
}

// undefined to signal api to read from disk by itself
return undefined;
return serverFS?.useOS;
},
fileExists(filePath: string) {
const virtualFiles = service.virtualFiles;
Expand Down Expand Up @@ -436,8 +512,7 @@ export class SvelteCheckTSGoDiagnosticsProvider implements DiagnosticsProvider {
}
}

// undefined to signal api to check existence by itself
return undefined;
return serverFS?.useOS;
}
};

Expand Down
7 changes: 4 additions & 3 deletions packages/language-server/src/plugins/typescript-go/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type tsAst from '@typescript/native-preview/unstable/ast' with { 'resolution-mode': 'import' };
import type tsApiSync from '@typescript/native-preview/unstable/sync' with { 'resolution-mode': 'import' };
import type tsAst from '@typescript/native/ast' with { 'resolution-mode': 'import' };
import type tsApiSync from '@typescript/native/sync' with { 'resolution-mode': 'import' };
import type tsFs from '@typescript/native/fs' with { 'resolution-mode': 'import' };

export { tsAst, tsApiSync };
export { tsAst, tsApiSync, tsFs };
20 changes: 15 additions & 5 deletions packages/language-server/src/svelte-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { isInGeneratedCode } from './plugins/typescript/features/utils';
import { mapAndFilterDiagnostics } from './plugins/typescript/features/DiagnosticsProvider';
import { convertRange, getDiagnosticTag, mapSeverity } from './plugins/typescript/utils';
import { groupBy, normalizePath, pathToUrl, urlToPath } from './utils';
import { tsApiSync, tsAst } from './plugins/typescript-go/types';
import { tsApiSync, tsAst, tsFs } from './plugins/typescript-go/types';
import { SvelteCheckTSGoDiagnosticsProvider } from './plugins/typescript-go/features/DiagnosticsProvider';

export function mapSvelteCheckDiagnostics(
Expand Down Expand Up @@ -78,6 +78,8 @@ export interface SvelteCheckOptions {
tsgo: {
apiModule: unknown;
astModule: unknown;
fsModule: unknown;
tsVersion: string;
};
};
}
Expand Down Expand Up @@ -146,16 +148,17 @@ export class SvelteCheck {
if (shouldRegister('js') || options.tsconfig) {
const workspaceUris = [pathToUrl(workspacePath)];
if (options.experimental?.tsgo && options.tsconfig) {
const { apiModule, astModule } = options.experimental.tsgo as {
const modules = options.experimental.tsgo as {
apiModule: typeof tsApiSync;
astModule: typeof tsAst;
fsModule: typeof tsFs;
tsVersion: string;
};
if (!apiModule.API || !('ScriptKind' in astModule)) {
if (!modules.apiModule.API || !('ScriptKind' in modules.astModule)) {
throw new Error('Unsupported typescript-go version');
}
this.tsGoDiagnosticsProvider = new SvelteCheckTSGoDiagnosticsProvider(
apiModule,
astModule,
modules,
options.tsconfig,
'svelte-check',
(filePath: string, text: string) =>
Expand Down Expand Up @@ -479,6 +482,13 @@ export class SvelteCheck {
const configFileParsingDiagnosticsLength = allTsDiagnostics?.length ?? 0;

allTsDiagnostics = allTsDiagnostics.concat(project.program.getSyntacticDiagnostics());
const contentMapperDiagnostic = allTsDiagnostics.find((v) => v.code === 18068);
if (contentMapperDiagnostic) {
contentMapperDiagnostic.text = 'Content mappers are not yet supported';
return this.tsGoDiagnosticsProvider.mapAndFilterDiagnostics(project, [
contentMapperDiagnostic
]);
}

if (allTsDiagnostics.length == configFileParsingDiagnosticsLength) {
if (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,20 @@ function setupService(dir: string) {
if (providerPool.has(tsconfig)) {
return;
}
const syncApi = await import('@typescript/native-preview/unstable/sync');
const tsAst = await import('@typescript/native-preview/unstable/ast');

const [syncApi, tsAst, tsFs, version] = await Promise.all([
import('@typescript/native/sync'),
import('@typescript/native/ast'),
import('@typescript/native/fs'),
import('@typescript/native')
]);
const service = new SvelteCheckTSGoDiagnosticsProvider(
syncApi,
tsAst,
{
apiModule: syncApi,
astModule: tsAst,
fsModule: tsFs,
tsVersion: version.version
},
tsconfig,
'svelte2tsx',
(filePath, content) => new Document(pathToUrl(filePath), content)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
"severity": 1,
"source": "ts",
"message": "Type 'HTMLParagraphElement' is missing the following properties from type 'HTMLInputElement': accept, alt, autocomplete, capture, and 54 more.",
"message": "Type 'HTMLParagraphElement' is missing the following properties from type 'HTMLInputElement': accept, alt, autocomplete, capture, and 55 more.",
"code": 2740,
"tags": []
}
Expand Down
Loading
Loading