From aa2ddd43409e4b2405913d17b35196ce6496dabe Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 17 Nov 2023 05:52:58 +0800 Subject: [PATCH] updates --- packages/astro-check/src/index.ts | 2 +- packages/language-server/src/check.ts | 33 ++-- .../language-server/src/core/astro2tsx.ts | 1 + packages/language-server/src/core/index.ts | 3 +- packages/language-server/src/core/parseCSS.ts | 2 + .../language-server/src/core/parseHTML.ts | 1 + packages/language-server/src/core/parseJS.ts | 2 + packages/language-server/src/core/svelte.ts | 1 + packages/language-server/src/core/utils.ts | 1 + packages/language-server/src/core/vue.ts | 1 + .../src/languageServerPlugin.ts | 182 +++++++++--------- packages/language-server/src/nodeServer.ts | 9 +- .../src/plugins/typescript/index.ts | 11 +- packages/ts-plugin/src/astro2tsx.ts | 1 + packages/ts-plugin/src/index.ts | 4 +- packages/ts-plugin/src/language.ts | 1 + 16 files changed, 135 insertions(+), 120 deletions(-) diff --git a/packages/astro-check/src/index.ts b/packages/astro-check/src/index.ts index 26f26f7c..a1701cb3 100644 --- a/packages/astro-check/src/index.ts +++ b/packages/astro-check/src/index.ts @@ -44,7 +44,7 @@ export async function check(flags: Partial): Promise { // Dynamically get the list of extensions to watch from the files already included in the project const checkedExtensions = Array.from( new Set( - checker.project.languageHost.getScriptFileNames().map((fileName) => path.extname(fileName)) + checker.project.typescript!.projectHost.getScriptFileNames().map((fileName) => path.extname(fileName)) ) ); createWatcher(workspaceRoot, checkedExtensions) diff --git a/packages/language-server/src/check.ts b/packages/language-server/src/check.ts index 9df5d09f..7ce7679a 100644 --- a/packages/language-server/src/check.ts +++ b/packages/language-server/src/check.ts @@ -30,7 +30,7 @@ export interface CheckResult { export class AstroCheck { private ts!: typeof import('typescript/lib/tsserverlibrary.js'); - public project!: ReturnType; + public project!: kit.Project; private linter!: ReturnType; constructor( @@ -61,7 +61,7 @@ export class AstroCheck { | undefined; }): Promise { const files = - fileNames !== undefined ? fileNames : this.project.languageHost.getScriptFileNames(); + fileNames !== undefined ? fileNames : this.project.typescript!.projectHost.getScriptFileNames(); const result: CheckResult = { status: undefined, @@ -98,7 +98,7 @@ export class AstroCheck { console.info(errorText); } - const fileSnapshot = this.project.languageHost.getScriptSnapshot(file); + const fileSnapshot = this.project.typescript!.projectHost.getScriptSnapshot(file); const fileContent = fileSnapshot?.getText(0, fileSnapshot.getLength()); result.fileResult.push({ @@ -130,26 +130,25 @@ export class AstroCheck { this.ts = this.typescriptPath ? require(this.typescriptPath) : require('typescript'); const tsconfigPath = this.getTsconfig(); - const config: kit.Config = { - languages: { - astro: getLanguageModule(getAstroInstall([this.workspacePath]), this.ts), - svelte: getSvelteLanguageModule(), - vue: getVueLanguageModule(), - }, - services: { - typescript: createTypeScriptService(), - astro: createAstroService(), - }, - }; + const languages: kit.Language[] = [ + getLanguageModule(getAstroInstall([this.workspacePath]), this.ts), + getSvelteLanguageModule(), + getVueLanguageModule(), + ]; + const services = [ + createTypeScriptService(), + createAstroService(), + ]; + const env = kit.createServiceEnvironment(); if (tsconfigPath) { - this.project = kit.createProject(tsconfigPath, [ + this.project = kit.createTypeScriptKitProject(languages, env, tsconfigPath, [ { extension: 'astro', isMixedContent: true, scriptKind: 7 }, { extension: 'vue', isMixedContent: true, scriptKind: 7 }, { extension: 'svelte', isMixedContent: true, scriptKind: 7 }, ]); } else { - this.project = kit.createInferredProject(this.workspacePath, () => { + this.project = kit.createTypeScriptInferredKitProject(languages, env, () => { return fg.sync('**/*.astro', { cwd: this.workspacePath, ignore: ['node_modules'], @@ -158,7 +157,7 @@ export class AstroCheck { }); } - this.linter = kit.createLinter(config, this.project.languageHost); + this.linter = kit.createLinter(services, env, this.project); } private getTsconfig() { diff --git a/packages/language-server/src/core/astro2tsx.ts b/packages/language-server/src/core/astro2tsx.ts index 8a9dfe71..2658f603 100644 --- a/packages/language-server/src/core/astro2tsx.ts +++ b/packages/language-server/src/core/astro2tsx.ts @@ -147,6 +147,7 @@ function getVirtualFileTSX( return { fileName: fileName + '.tsx', + languageId: 'typescriptreact', kind: FileKind.TypeScriptHostFile, capabilities: { codeAction: true, diff --git a/packages/language-server/src/core/index.ts b/packages/language-server/src/core/index.ts index 04124cde..f9dd37a2 100644 --- a/packages/language-server/src/core/index.ts +++ b/packages/language-server/src/core/index.ts @@ -29,7 +29,7 @@ export function getLanguageModule( updateVirtualFile(astroFile, snapshot) { astroFile.update(snapshot); }, - resolveHost(host) { + resolveTypeScriptProjectHost(host) { return { ...host, resolveModuleName(moduleName, impliedNodeFormat) { @@ -83,6 +83,7 @@ export class AstroFile implements VirtualFile { capabilities = FileCapabilities.full; fileName: string; + languageId = 'astro'; mappings!: VirtualFile['mappings']; embeddedFiles!: VirtualFile['embeddedFiles']; astroMeta!: ParseResult & { frontmatter: FrontmatterStatus }; diff --git a/packages/language-server/src/core/parseCSS.ts b/packages/language-server/src/core/parseCSS.ts index d68e2d27..8d9a79af 100644 --- a/packages/language-server/src/core/parseCSS.ts +++ b/packages/language-server/src/core/parseCSS.ts @@ -38,6 +38,7 @@ export function extractStylesheets( embeddedCSSFiles.push({ fileName: fileName + '.inline.css', + languageId: 'css', codegenStacks: [], snapshot: { getText: (start, end) => text.substring(start, end), @@ -78,6 +79,7 @@ function findEmbeddedStyles( const styleText = snapshot.getText(node.startTagEnd, node.endTagStart); embeddedCSSFiles.push({ fileName: fileName + `.${cssIndex}.css`, + languageId: 'css', kind: FileKind.TextFile, snapshot: { getText: (start, end) => styleText.substring(start, end), diff --git a/packages/language-server/src/core/parseHTML.ts b/packages/language-server/src/core/parseHTML.ts index 54c64b51..c25f9e4c 100644 --- a/packages/language-server/src/core/parseHTML.ts +++ b/packages/language-server/src/core/parseHTML.ts @@ -87,6 +87,7 @@ export function preprocessHTML(text: string, frontmatterEnd?: number) { function getHTMLVirtualFile(fileName: string, preprocessedHTML: string): VirtualFile { return { fileName: fileName + `.html`, + languageId: 'html', kind: FileKind.TextFile, snapshot: { getText: (start, end) => preprocessedHTML.substring(start, end), diff --git a/packages/language-server/src/core/parseJS.ts b/packages/language-server/src/core/parseJS.ts index fb4d55dd..9a0e9e06 100644 --- a/packages/language-server/src/core/parseJS.ts +++ b/packages/language-server/src/core/parseJS.ts @@ -69,6 +69,7 @@ function findIsolatedScripts( const scriptText = snapshot.getText(node.startTagEnd, node.endTagStart); embeddedScripts.push({ fileName: fileName + `.${scriptIndex}.mts`, + languageId: 'typescript', kind: FileKind.TypeScriptHostFile, snapshot: { getText: (start, end) => scriptText.substring(start, end), @@ -214,6 +215,7 @@ function mergeJSContexts(fileName: string, javascriptContexts: JavaScriptContext return { fileName: fileName + '.inline.mjs', + languageId: 'javascript', codegenStacks: [], snapshot: { getText: (start, end) => text.substring(start, end), diff --git a/packages/language-server/src/core/svelte.ts b/packages/language-server/src/core/svelte.ts index 97b6a46d..1f049ddc 100644 --- a/packages/language-server/src/core/svelte.ts +++ b/packages/language-server/src/core/svelte.ts @@ -27,6 +27,7 @@ class SvelteFile implements VirtualFile { capabilities = FileCapabilities.full; fileName: string; + languageId = 'svelte'; mappings!: Mapping[]; embeddedFiles!: VirtualFile[]; codegenStacks = []; diff --git a/packages/language-server/src/core/utils.ts b/packages/language-server/src/core/utils.ts index c589f25e..c0b45216 100644 --- a/packages/language-server/src/core/utils.ts +++ b/packages/language-server/src/core/utils.ts @@ -30,6 +30,7 @@ export function framework2tsx( function getVirtualFile(content: string): VirtualFile { return { fileName: fileName + '.tsx', + languageId: 'typescript', capabilities: FileCapabilities.full, kind: FileKind.TypeScriptHostFile, snapshot: { diff --git a/packages/language-server/src/core/vue.ts b/packages/language-server/src/core/vue.ts index 748d03e4..7016f149 100644 --- a/packages/language-server/src/core/vue.ts +++ b/packages/language-server/src/core/vue.ts @@ -27,6 +27,7 @@ class VueFile implements VirtualFile { capabilities = FileCapabilities.full; fileName: string; + languageId = 'vue'; mappings!: Mapping[]; embeddedFiles!: VirtualFile[]; codegenStacks = []; diff --git a/packages/language-server/src/languageServerPlugin.ts b/packages/language-server/src/languageServerPlugin.ts index 50c24899..55d4f3b0 100644 --- a/packages/language-server/src/languageServerPlugin.ts +++ b/packages/language-server/src/languageServerPlugin.ts @@ -1,7 +1,8 @@ import { - LanguageServerPlugin, + TypeScriptServerPlugin, MessageType, ShowMessageNotification, + Connection, } from '@volar/language-server/node'; import { getLanguageModule } from './core'; import { getSvelteLanguageModule } from './core/svelte.js'; @@ -20,104 +21,103 @@ import { create as createHtmlService } from './plugins/html.js'; import { create as createTypescriptAddonsService } from './plugins/typescript-addons/index.js'; import { create as createTypeScriptService } from './plugins/typescript/index.js'; -export const plugin: LanguageServerPlugin = ( - initOptions, - modules -): ReturnType => ({ - extraFileExtensions: [ - { extension: 'astro', isMixedContent: true, scriptKind: 7 }, - { extension: 'vue', isMixedContent: true, scriptKind: 7 }, - { extension: 'svelte', isMixedContent: true, scriptKind: 7 }, - ], - watchFileExtensions: [ - 'js', - 'cjs', - 'mjs', - 'ts', - 'cts', - 'mts', - 'jsx', - 'tsx', - 'json', - 'astro', - 'vue', - 'svelte', - ], - resolveConfig(config, ctx) { - config.languages ??= {}; - if (ctx) { - const astroInstall = getAstroInstall([ctx.project.rootUri.fsPath]); +export function createPlugin(connection: Connection): TypeScriptServerPlugin { + return ({ modules }): ReturnType => ({ + extraFileExtensions: [ + { extension: 'astro', isMixedContent: true, scriptKind: 7 }, + { extension: 'vue', isMixedContent: true, scriptKind: 7 }, + { extension: 'svelte', isMixedContent: true, scriptKind: 7 }, + ], + watchFileExtensions: [ + 'js', + 'cjs', + 'mjs', + 'ts', + 'cts', + 'mts', + 'jsx', + 'tsx', + 'json', + 'astro', + 'vue', + 'svelte', + ], + resolveConfig(config, env, projectHost) { + config.languages ??= {}; + if (projectHost) { + const astroInstall = getAstroInstall([projectHost.getCurrentDirectory()]); - if (!astroInstall) { - ctx.server.connection.sendNotification(ShowMessageNotification.type, { - message: `Couldn't find Astro in workspace "${ctx.project.rootUri.fsPath}". Experience might be degraded. For the best experience, please make sure Astro is installed into your project and restart the language server.`, - type: MessageType.Warning, - }); - } + if (!astroInstall) { + connection.sendNotification(ShowMessageNotification.type, { + message: `Couldn't find Astro in workspace "${projectHost.getCurrentDirectory()}". Experience might be degraded. For the best experience, please make sure Astro is installed into your project and restart the language server.`, + type: MessageType.Warning, + }); + } - config.languages.astro = getLanguageModule(astroInstall, modules.typescript!); - config.languages.vue = getVueLanguageModule(); - config.languages.svelte = getSvelteLanguageModule(); - } + config.languages.astro = getLanguageModule(astroInstall, modules.typescript!); + config.languages.vue = getVueLanguageModule(); + config.languages.svelte = getSvelteLanguageModule(); + } - config.services ??= {}; - config.services.html ??= createHtmlService(); - config.services.css ??= createCssService(); - config.services.emmet ??= createEmmetService(); - config.services.typescript ??= createTypeScriptService(); - config.services.typescripttwoslash ??= createTypeScriptTwoSlashService(); - config.services.typescriptaddons ??= createTypescriptAddonsService(); - config.services.astro ??= createAstroService(); + config.services ??= {}; + config.services.html ??= createHtmlService(); + config.services.css ??= createCssService(); + config.services.emmet ??= createEmmetService(); + config.services.typescript ??= createTypeScriptService(); + config.services.typescripttwoslash ??= createTypeScriptTwoSlashService(); + config.services.typescriptaddons ??= createTypescriptAddonsService(); + config.services.astro ??= createAstroService(); - if (ctx) { - const rootDir = ctx.env.uriToFileName(ctx.project.rootUri.toString()); - const prettier = importPrettier(rootDir); - const prettierPluginPath = getPrettierPluginPath(rootDir); + if (env && projectHost) { + const rootDir = projectHost.getCurrentDirectory(); + const prettier = importPrettier(rootDir); + const prettierPluginPath = getPrettierPluginPath(rootDir); - if (prettier && prettierPluginPath) { - config.services.prettier ??= createPrettierService({ - prettier: prettier, - languages: ['astro'], - ignoreIdeOptions: true, - useIdeOptionsFallback: true, - resolveConfigOptions: { - // This seems to be broken since Prettier 3, and it'll always use its cumbersome cache. Hopefully it works one day. - useCache: false, - }, - additionalOptions: async (resolvedConfig) => { - async function getAstroPrettierPlugin() { - if (!prettier || !prettierPluginPath) { - return []; - } + if (prettier && prettierPluginPath) { + config.services.prettier ??= createPrettierService({ + prettier: prettier, + languages: ['astro'], + ignoreIdeOptions: true, + useIdeOptionsFallback: true, + resolveConfigOptions: { + // This seems to be broken since Prettier 3, and it'll always use its cumbersome cache. Hopefully it works one day. + useCache: false, + }, + additionalOptions: async (resolvedConfig) => { + async function getAstroPrettierPlugin() { + if (!prettier || !prettierPluginPath) { + return []; + } - const hasPluginLoadedAlready = - (await prettier.getSupportInfo()).languages.some((l: any) => l.name === 'astro') || - resolvedConfig.plugins?.includes('prettier-plugin-astro'); // getSupportInfo doesn't seems to work very well in Prettier 3 for plugins + const hasPluginLoadedAlready = + (await prettier.getSupportInfo()).languages.some((l: any) => l.name === 'astro') || + resolvedConfig.plugins?.includes('prettier-plugin-astro'); // getSupportInfo doesn't seems to work very well in Prettier 3 for plugins - return hasPluginLoadedAlready ? [] : [prettierPluginPath]; - } + return hasPluginLoadedAlready ? [] : [prettierPluginPath]; + } - const plugins = [ - ...(await getAstroPrettierPlugin()), - ...(resolvedConfig.plugins ?? []), - ]; + const plugins = [ + ...(await getAstroPrettierPlugin()), + ...(resolvedConfig.plugins ?? []), + ]; - return { - ...resolvedConfig, - plugins: plugins, - parser: 'astro', - }; - }, - }); - } else { - ctx.server.connection.sendNotification(ShowMessageNotification.type, { - message: - "Couldn't load `prettier` or `prettier-plugin-astro`. Formatting will not work. Please make sure those two packages are installed into your project.", - type: MessageType.Warning, - }); + return { + ...resolvedConfig, + plugins: plugins, + parser: 'astro', + }; + }, + }); + } else { + connection.sendNotification(ShowMessageNotification.type, { + message: + "Couldn't load `prettier` or `prettier-plugin-astro`. Formatting will not work. Please make sure those two packages are installed into your project.", + type: MessageType.Warning, + }); + } } - } - return config; - }, -}); + return config; + }, + }); +} diff --git a/packages/language-server/src/nodeServer.ts b/packages/language-server/src/nodeServer.ts index d0ffbcb1..7fddfb70 100644 --- a/packages/language-server/src/nodeServer.ts +++ b/packages/language-server/src/nodeServer.ts @@ -1,4 +1,7 @@ -import { createConnection, startLanguageServer } from '@volar/language-server/node'; -import { plugin } from './languageServerPlugin.js'; +import { createConnection, startTypeScriptServer } from '@volar/language-server/node'; +import { createPlugin } from './languageServerPlugin.js'; -startLanguageServer(createConnection(), plugin); +const connection = createConnection(); +const plugin = createPlugin(connection); + +startTypeScriptServer(connection, plugin); diff --git a/packages/language-server/src/plugins/typescript/index.ts b/packages/language-server/src/plugins/typescript/index.ts index 070b210c..4e222531 100644 --- a/packages/language-server/src/plugins/typescript/index.ts +++ b/packages/language-server/src/plugins/typescript/index.ts @@ -27,10 +27,10 @@ export const create = transformCompletionItem(item) { const [_, source] = context.documents.getVirtualFileByUri(item.data.uri); const file = source?.root; - if (!(file instanceof AstroFile) || !context.host) return undefined; + if (!(file instanceof AstroFile) || !context.project.typescript) return undefined; if (file.scriptFiles.includes(item.data.fileName)) return undefined; - const newLine = context.host.getCompilationSettings().newLine?.toString() ?? '\n'; + const newLine = context.project.typescript.projectHost.getCompilationSettings().newLine?.toString() ?? '\n'; if (item.additionalTextEdits) { item.additionalTextEdits = item.additionalTextEdits.map((edit) => { // HACK: There's a weird situation sometimes where some components (especially Svelte) will get imported as type imports @@ -55,7 +55,7 @@ export const create = const [_, source] = context.documents.getVirtualFileByUri(originalFileName); const file = source?.root; - if (!(file instanceof AstroFile) || !context.host) return undefined; + if (!(file instanceof AstroFile) || !context.project.typescript) return undefined; if ( file.scriptFiles.includes(item.diagnostics?.[0].data.documentUri.replace('file://', '')) ) @@ -64,7 +64,7 @@ export const create = const document = context.getTextDocument(originalFileName); if (!document) return undefined; - const newLine = context.host.getCompilationSettings().newLine?.toString() ?? '\n'; + const newLine = context.project.typescript.projectHost.getCompilationSettings().newLine?.toString() ?? '\n'; if (!item.edit?.documentChanges) return undefined; item.edit.documentChanges = item.edit.documentChanges.map((change) => { if (TextDocumentEdit.is(change)) { @@ -146,7 +146,8 @@ export const create = const astroDocument = context.documents.getDocumentByFileName( file.snapshot, - file.sourceFileName + file.sourceFileName, + file.languageId ); return enhancedProvideSemanticDiagnostics(diagnostics, astroDocument.lineCount); diff --git a/packages/ts-plugin/src/astro2tsx.ts b/packages/ts-plugin/src/astro2tsx.ts index f9f28639..7bf5aa6c 100644 --- a/packages/ts-plugin/src/astro2tsx.ts +++ b/packages/ts-plugin/src/astro2tsx.ts @@ -127,6 +127,7 @@ function getVirtualFileTSX( return { fileName: fileName + '.tsx', + languageId: 'typescriptreact', kind: FileKind.TypeScriptHostFile, capabilities: { codeAction: true, diff --git a/packages/ts-plugin/src/index.ts b/packages/ts-plugin/src/index.ts index 7dd27ab0..491728fb 100644 --- a/packages/ts-plugin/src/index.ts +++ b/packages/ts-plugin/src/index.ts @@ -1,4 +1,4 @@ -import { createVirtualFiles } from '@volar/language-core'; +import { createFileProvider } from '@volar/language-core'; import { decorateLanguageService, decorateLanguageServiceHost, @@ -14,7 +14,7 @@ const init: ts.server.PluginModuleFactory = (modules) => { const { typescript: ts } = modules; const pluginModule: ts.server.PluginModule = { create(info) { - const virtualFiles = createVirtualFiles([getLanguageModule(ts)]); + const virtualFiles = createFileProvider([getLanguageModule(ts)], () => {}); decorateLanguageService(virtualFiles, info.languageService, true); decorateLanguageServiceHost(virtualFiles, info.languageServiceHost, ts, ['.astro']); diff --git a/packages/ts-plugin/src/language.ts b/packages/ts-plugin/src/language.ts index daab83f1..416eba35 100644 --- a/packages/ts-plugin/src/language.ts +++ b/packages/ts-plugin/src/language.ts @@ -28,6 +28,7 @@ export class AstroFile implements VirtualFile { capabilities = FileCapabilities.full; fileName: string; + languageId = 'astro'; mappings!: VirtualFile['mappings']; embeddedFiles!: VirtualFile['embeddedFiles']; codegenStacks = [];