diff --git a/packages/astro-check/src/index.ts b/packages/astro-check/src/index.ts index 9ed76cd6..26f26f7c 100644 --- a/packages/astro-check/src/index.ts +++ b/packages/astro-check/src/index.ts @@ -44,20 +44,20 @@ 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.linter.languageHost.getScriptFileNames().map((fileName) => path.extname(fileName)) + checker.project.languageHost.getScriptFileNames().map((fileName) => path.extname(fileName)) ) ); createWatcher(workspaceRoot, checkedExtensions) .on('add', (fileName) => { - checker.linter.fileCreated(fileName); + checker.project.fileCreated(fileName); update(); }) .on('unlink', (fileName) => { - checker.linter.fileDeleted(fileName); + checker.project.fileDeleted(fileName); update(); }) .on('change', (fileName) => { - checker.linter.fileUpdated(fileName); + checker.project.fileUpdated(fileName); update(); }); } diff --git a/packages/language-server/package.json b/packages/language-server/package.json index dbbda752..5a1e5708 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -28,19 +28,21 @@ "dependencies": { "@astrojs/compiler": "^2.2.2", "@jridgewell/sourcemap-codec": "^1.4.15", - "@volar/kit": "2.0.0-alpha.10", - "@volar/language-core": "2.0.0-alpha.10", - "@volar/language-server": "2.0.0-alpha.10", - "@volar/language-service": "2.0.0-alpha.10", - "@volar/typescript": "2.0.0-alpha.10", + "@volar/kit": "~1.10.9", + "@volar/language-core": "~1.10.9", + "@volar/language-server": "~1.10.9", + "@volar/language-service": "~1.10.9", + "@volar/source-map": "~1.10.9", + "@volar/typescript": "~1.10.9", "fast-glob": "^3.2.12", - "volar-service-css": "0.0.24", - "volar-service-emmet": "0.0.24", - "volar-service-html": "0.0.24", - "volar-service-prettier": "0.0.24", - "volar-service-typescript": "0.0.24", - "volar-service-typescript-twoslash-queries": "0.0.24", - "vscode-html-languageservice": "^5.1.1", + "muggle-string": "^0.3.1", + "volar-service-css": "0.0.16", + "volar-service-emmet": "0.0.16", + "volar-service-html": "0.0.16", + "volar-service-prettier": "0.0.16", + "volar-service-typescript": "0.0.16", + "volar-service-typescript-twoslash-queries": "0.0.16", + "vscode-html-languageservice": "^5.1.0", "vscode-uri": "^3.0.8" }, "devDependencies": { @@ -49,7 +51,6 @@ "@types/chai": "^4.3.5", "@types/mocha": "^10.0.1", "@types/node": "^18.17.8", - "@volar/test-utils": "2.0.0-alpha.10", "astro": "^3.3.0", "chai": "^4.3.7", "mocha": "^10.2.0", diff --git a/packages/language-server/src/check.ts b/packages/language-server/src/check.ts index 4144a5c1..80c11a91 100644 --- a/packages/language-server/src/check.ts +++ b/packages/language-server/src/check.ts @@ -30,7 +30,8 @@ export interface CheckResult { export class AstroCheck { private ts!: typeof import('typescript/lib/tsserverlibrary.js'); - public linter!: ReturnType<(typeof kit)['createTypeScriptChecker']>; + public project!: ReturnType; + private linter!: ReturnType; constructor( private readonly workspacePath: string, @@ -60,7 +61,7 @@ export class AstroCheck { | undefined; }): Promise { const files = - fileNames !== undefined ? fileNames : this.linter.languageHost.getScriptFileNames(); + fileNames !== undefined ? fileNames : this.project.languageHost.getScriptFileNames(); const result: CheckResult = { status: undefined, @@ -97,7 +98,7 @@ export class AstroCheck { console.info(errorText); } - const fileSnapshot = this.linter.languageHost.getScriptSnapshot(file); + const fileSnapshot = this.project.languageHost.getScriptSnapshot(file); const fileContent = fileSnapshot?.getText(0, fileSnapshot.getLength()); result.fileResult.push({ @@ -130,17 +131,29 @@ export class AstroCheck { const tsconfigPath = this.getTsconfig(); const astroInstall = getAstroInstall([this.workspacePath]); - const languages = [ - getLanguageModule(typeof astroInstall === 'string' ? undefined : astroInstall, this.ts), - getSvelteLanguageModule(), - getVueLanguageModule(), - ]; - const services = [createTypeScriptService(this.ts), createAstroService(this.ts)]; + const config: kit.Config = { + languages: { + astro: getLanguageModule( + typeof astroInstall === 'string' ? undefined : astroInstall, + this.ts + ), + svelte: getSvelteLanguageModule(), + vue: getVueLanguageModule(), + }, + services: { + typescript: createTypeScriptService(), + astro: createAstroService(), + }, + }; if (tsconfigPath) { - this.linter = kit.createTypeScriptChecker(languages, services, tsconfigPath); + this.project = kit.createProject(tsconfigPath, [ + { extension: 'astro', isMixedContent: true, scriptKind: 7 }, + { extension: 'vue', isMixedContent: true, scriptKind: 7 }, + { extension: 'svelte', isMixedContent: true, scriptKind: 7 }, + ]); } else { - this.linter = kit.createTypeScriptInferredChecker(languages, services, () => { + this.project = kit.createInferredProject(this.workspacePath, () => { return fg.sync('**/*.astro', { cwd: this.workspacePath, ignore: ['node_modules'], @@ -148,6 +161,8 @@ export class AstroCheck { }); }); } + + this.linter = kit.createLinter(config, this.project.languageHost); } private getTsconfig() { diff --git a/packages/language-server/src/core/astro2tsx.ts b/packages/language-server/src/core/astro2tsx.ts index e9c178c2..0618ccb1 100644 --- a/packages/language-server/src/core/astro2tsx.ts +++ b/packages/language-server/src/core/astro2tsx.ts @@ -1,7 +1,7 @@ import { convertToTSX } from '@astrojs/compiler/sync'; import type { ConvertToTSXOptions, TSXResult } from '@astrojs/compiler/types'; import { decode } from '@jridgewell/sourcemap-codec'; -import type { CodeInformation, VirtualFile } from '@volar/language-core'; +import { FileKind, FileRangeCapabilities, VirtualFile } from '@volar/language-core'; import { HTMLDocument, TextDocument } from 'vscode-html-languageservice'; import { patchTSX } from './utils.js'; @@ -39,7 +39,7 @@ function safeConvertToTSX(content: string, options: ConvertToTSXOptions) { export function astro2tsx( input: string, fileName: string, - ts: typeof import('typescript'), + ts: typeof import('typescript/lib/tsserverlibrary.js'), htmlDocument: HTMLDocument ) { const tsx = safeConvertToTSX(input, { filename: fileName }); @@ -54,13 +54,13 @@ function getVirtualFileTSX( input: string, tsx: TSXResult, fileName: string, - ts: typeof import('typescript'), + ts: typeof import('typescript/lib/tsserverlibrary.js'), htmlDocument: HTMLDocument ): VirtualFile { tsx.code = patchTSX(tsx.code, fileName); const v3Mappings = decode(tsx.map.mappings); - const sourcedDoc = TextDocument.create('file://' + fileName, 'astro', 0, input); - const genDoc = TextDocument.create('file://' + fileName + '.tsx', 'typescriptreact', 0, tsx.code); + const sourcedDoc = TextDocument.create(fileName, 'astro', 0, input); + const genDoc = TextDocument.create(fileName + '.tsx', 'typescriptreact', 0, tsx.code); const mappings: VirtualFile['mappings'] = []; @@ -93,37 +93,33 @@ function getVirtualFileTSX( const lastMapping = mappings.length ? mappings[mappings.length - 1] : undefined; if ( lastMapping && - lastMapping.generatedOffsets[0] + lastMapping.lengths[0] === current.genOffset && - lastMapping.sourceOffsets[0] + lastMapping.lengths[0] === current.sourceOffset + lastMapping.generatedRange[1] === current.genOffset && + lastMapping.sourceRange[1] === current.sourceOffset ) { - lastMapping.lengths[0] += length; + lastMapping.generatedRange[1] = current.genOffset + length; + lastMapping.sourceRange[1] = current.sourceOffset + length; } else { // Disable features inside script tags. This is a bit annoying to do, I wonder if maybe leaving script tags // unmapped would be better. const node = htmlDocument.findNodeAt(current.sourceOffset); - const rangeCapabilities: CodeInformation = + const rangeCapabilities: FileRangeCapabilities = node.tag !== 'script' - ? { - verification: true, - completion: true, - semantic: true, - navigation: true, - structure: true, - format: true, - } + ? FileRangeCapabilities.full : { - verification: false, completion: false, - semantic: false, - navigation: false, - structure: false, - format: false, + definition: false, + diagnostic: false, + displayWithLink: false, + hover: false, + references: false, + referencesCodeLens: false, + rename: false, + semanticTokens: false, }; mappings.push({ - sourceOffsets: [current.sourceOffset], - generatedOffsets: [current.genOffset], - lengths: [length], + sourceRange: [current.sourceOffset, current.sourceOffset + length], + generatedRange: [current.genOffset, current.genOffset + length], data: rangeCapabilities, }); } @@ -140,12 +136,27 @@ function getVirtualFileTSX( } } + const ast = ts.createSourceFile('/a.tsx', tsx.code, ts.ScriptTarget.ESNext); + if (ast.statements[0]) { + mappings.push({ + sourceRange: [0, input.length], + generatedRange: [ast.statements[0].getStart(ast), tsx.code.length], + data: {}, + }); + } + return { fileName: fileName + '.tsx', - languageId: 'typescriptreact', - typescript: { - scriptKind: ts.ScriptKind.TSX, + kind: FileKind.TypeScriptHostFile, + capabilities: { + codeAction: true, + documentFormatting: false, + diagnostic: true, + documentSymbol: true, + inlayHint: true, + foldingRange: true, }, + codegenStacks: [], snapshot: { getText: (start, end) => tsx.code.substring(start, end), getLength: () => tsx.code.length, diff --git a/packages/language-server/src/core/index.ts b/packages/language-server/src/core/index.ts index 987d7e5e..1601803c 100644 --- a/packages/language-server/src/core/index.ts +++ b/packages/language-server/src/core/index.ts @@ -1,5 +1,11 @@ import type { DiagnosticMessage, ParseResult } from '@astrojs/compiler/types'; -import type { LanguagePlugin, VirtualFile } from '@volar/language-core'; +import { + FileCapabilities, + FileKind, + FileRangeCapabilities, + type Language, + type VirtualFile, +} from '@volar/language-core'; import * as path from 'node:path'; import type ts from 'typescript/lib/tsserverlibrary'; import type { HTMLDocument } from 'vscode-html-languageservice'; @@ -12,77 +18,71 @@ import { extractScriptTags } from './parseJS.js'; export function getLanguageModule( astroInstall: AstroInstall | undefined, - ts: typeof import('typescript') -): LanguagePlugin { + ts: typeof import('typescript/lib/tsserverlibrary.js') +): Language { return { - createVirtualFile(fileName, languageId, snapshot) { - if (languageId === 'astro') { + createVirtualFile(fileName, snapshot) { + if (fileName.endsWith('.astro')) { return new AstroFile(fileName, snapshot, ts); } }, updateVirtualFile(astroFile, snapshot) { astroFile.update(snapshot); }, - typescript: { - extraFileExtensions: [{ extension: 'astro', isMixedContent: true, scriptKind: 7 }], - resolveSourceFileName(tsFileName) { - const baseName = path.basename(tsFileName); - if (baseName.indexOf('.astro.')) { - return tsFileName.substring(0, tsFileName.lastIndexOf('.astro.') + '.astro'.length); - } - }, - resolveModuleName(moduleName, impliedNodeFormat) { - if ( - impliedNodeFormat === ts.ModuleKind.ESNext && - (moduleName.endsWith('.astro') || - moduleName.endsWith('.vue') || - moduleName.endsWith('.svelte')) - ) { - return `${moduleName}.js`; - } - }, - resolveLanguageServiceHost(host) { - return { - ...host, - getScriptFileNames() { - const fileNames = host.getScriptFileNames(); - return [ - ...fileNames, - ...(astroInstall - ? ['./env.d.ts', './astro-jsx.d.ts'].map((filePath) => - ts.sys.resolvePath(path.resolve(astroInstall.path, filePath)) - ) - : []), - ]; - }, - getCompilationSettings() { - const baseCompilationSettings = host.getCompilationSettings(); - return { - ...baseCompilationSettings, - module: ts.ModuleKind.ESNext ?? 99, - target: ts.ScriptTarget.ESNext ?? 99, - jsx: ts.JsxEmit.Preserve ?? 1, - jsxImportSource: undefined, - jsxFactory: 'astroHTML', - resolveJsonModule: true, - allowJs: true, - isolatedModules: true, - moduleResolution: - baseCompilationSettings.moduleResolution === ts.ModuleResolutionKind.Classic || - !baseCompilationSettings.moduleResolution - ? ts.ModuleResolutionKind.Node10 - : baseCompilationSettings.moduleResolution, - }; - }, - }; - }, + resolveHost(host) { + return { + ...host, + resolveModuleName(moduleName, impliedNodeFormat) { + if ( + impliedNodeFormat === ts.ModuleKind.ESNext && + (moduleName.endsWith('.astro') || + moduleName.endsWith('.vue') || + moduleName.endsWith('.svelte')) + ) { + return `${moduleName}.js`; + } + return host.resolveModuleName?.(moduleName, impliedNodeFormat) ?? moduleName; + }, + getScriptFileNames() { + const fileNames = host.getScriptFileNames(); + return [ + ...fileNames, + ...(astroInstall + ? ['./env.d.ts', './astro-jsx.d.ts'].map((filePath) => + ts.sys.resolvePath(path.resolve(astroInstall.path, filePath)) + ) + : []), + ]; + }, + getCompilationSettings() { + const baseCompilationSettings = host.getCompilationSettings(); + return { + ...baseCompilationSettings, + module: ts.ModuleKind.ESNext ?? 99, + target: ts.ScriptTarget.ESNext ?? 99, + jsx: ts.JsxEmit.Preserve ?? 1, + jsxImportSource: undefined, + jsxFactory: 'astroHTML', + resolveJsonModule: true, + allowJs: true, + isolatedModules: true, + moduleResolution: + baseCompilationSettings.moduleResolution === ts.ModuleResolutionKind.Classic || + !baseCompilationSettings.moduleResolution + ? ts.ModuleResolutionKind.Node10 + : baseCompilationSettings.moduleResolution, + }; + }, + }; }, }; } export class AstroFile implements VirtualFile { + kind = FileKind.TextFile; + capabilities = FileCapabilities.full; + fileName: string; - languageId = 'astro'; mappings!: VirtualFile['mappings']; embeddedFiles!: VirtualFile['embeddedFiles']; astroMeta!: ParseResult & { frontmatter: FrontmatterStatus }; @@ -94,7 +94,7 @@ export class AstroFile implements VirtualFile { constructor( public sourceFileName: string, public snapshot: ts.IScriptSnapshot, - private readonly ts: typeof import('typescript') + private readonly ts: typeof import('typescript/lib/tsserverlibrary.js') ) { this.fileName = sourceFileName; this.onSnapshotUpdated(); @@ -112,17 +112,9 @@ export class AstroFile implements VirtualFile { onSnapshotUpdated() { this.mappings = [ { - sourceOffsets: [0], - generatedOffsets: [0], - lengths: [this.snapshot.getLength()], - data: { - verification: true, - completion: true, - semantic: true, - navigation: true, - structure: true, - format: true, - }, + sourceRange: [0, this.snapshot.getLength()], + generatedRange: [0, this.snapshot.getLength()], + data: FileRangeCapabilities.full, }, ]; this.compilerDiagnostics = []; diff --git a/packages/language-server/src/core/parseCSS.ts b/packages/language-server/src/core/parseCSS.ts index ed3de5fa..d68e2d27 100644 --- a/packages/language-server/src/core/parseCSS.ts +++ b/packages/language-server/src/core/parseCSS.ts @@ -1,12 +1,8 @@ import type { ParentNode, ParseResult } from '@astrojs/compiler/types'; import { is } from '@astrojs/compiler/utils'; -import { - buildMappings, - Segment, - toString, - type CodeInformation, - type VirtualFile, -} from '@volar/language-core'; +import { FileKind, FileRangeCapabilities, VirtualFile } from '@volar/language-core'; +import * as SourceMap from '@volar/source-map'; +import * as muggle from 'muggle-string'; import type ts from 'typescript/lib/tsserverlibrary'; import type { HTMLDocument, Node } from 'vscode-html-languageservice'; import type { AttributeNodeWithPosition } from './compilerUtils.js'; @@ -25,38 +21,32 @@ export function extractStylesheets( const inlineStyles = findInlineStyles(ast); if (inlineStyles.length > 0) { - const codes: Segment[] = []; + const codes: muggle.Segment[] = []; for (const inlineStyle of inlineStyles) { codes.push('x { '); codes.push([ inlineStyle.value, undefined, inlineStyle.position.start.offset + 'style="'.length, - // disable all but only keep document colors - { - verification: false, - completion: false, - semantic: false, - navigation: false, - structure: true, // keep document colors - format: false, - }, + FileRangeCapabilities.full, ]); codes.push(' }\n'); } - const mappings = buildMappings(codes); - const text = toString(codes); + const mappings = SourceMap.buildMappings(codes); + const text = muggle.toString(codes); embeddedCSSFiles.push({ fileName: fileName + '.inline.css', - languageId: 'css', + codegenStacks: [], snapshot: { getText: (start, end) => text.substring(start, end), getLength: () => text.length, getChangeRange: () => undefined, }, + capabilities: { documentSymbol: true }, embeddedFiles: [], + kind: FileKind.TextFile, mappings, }); } @@ -88,27 +78,26 @@ 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), getLength: () => styleText.length, getChangeRange: () => undefined, }, + codegenStacks: [], mappings: [ { - sourceOffsets: [node.startTagEnd], - generatedOffsets: [0], - lengths: [styleText.length], - data: { - verification: false, - completion: true, - semantic: true, - navigation: true, - structure: true, - format: false, - }, + sourceRange: [node.startTagEnd, node.endTagStart], + generatedRange: [0, styleText.length], + data: FileRangeCapabilities.full, }, ], + capabilities: { + diagnostic: false, + documentSymbol: true, + foldingRange: true, + documentFormatting: false, + }, embeddedFiles: [], }); cssIndex++; diff --git a/packages/language-server/src/core/parseHTML.ts b/packages/language-server/src/core/parseHTML.ts index b44c713f..54c64b51 100644 --- a/packages/language-server/src/core/parseHTML.ts +++ b/packages/language-server/src/core/parseHTML.ts @@ -1,4 +1,4 @@ -import type { VirtualFile } from '@volar/language-core'; +import { FileKind, FileRangeCapabilities, VirtualFile } from '@volar/language-core'; import type ts from 'typescript/lib/tsserverlibrary'; import * as html from 'vscode-html-languageservice'; import { isInsideExpression } from '../plugins/utils'; @@ -87,27 +87,25 @@ 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), getLength: () => preprocessedHTML.length, getChangeRange: () => undefined, }, + codegenStacks: [], mappings: [ { - sourceOffsets: [0], - generatedOffsets: [0], - lengths: [preprocessedHTML.length], - data: { - verification: true, - completion: true, - semantic: true, - navigation: true, - structure: true, - format: false, - }, + sourceRange: [0, preprocessedHTML.length], + generatedRange: [0, preprocessedHTML.length], + data: FileRangeCapabilities.full, }, ], + capabilities: { + documentSymbol: true, + foldingRange: true, + documentFormatting: false, + }, embeddedFiles: [], }; } diff --git a/packages/language-server/src/core/parseJS.ts b/packages/language-server/src/core/parseJS.ts index f31a1932..4833df49 100644 --- a/packages/language-server/src/core/parseJS.ts +++ b/packages/language-server/src/core/parseJS.ts @@ -1,12 +1,8 @@ import type { ParentNode, ParseResult } from '@astrojs/compiler/types'; import { is } from '@astrojs/compiler/utils'; -import { - buildMappings, - toString, - type CodeInformation, - type Segment, - type VirtualFile, -} from '@volar/language-core'; +import { FileKind, FileRangeCapabilities, VirtualFile } from '@volar/language-core'; +import * as SourceMap from '@volar/source-map'; +import * as muggle from 'muggle-string'; import type ts from 'typescript/lib/tsserverlibrary'; import type { HTMLDocument, Node } from 'vscode-html-languageservice'; @@ -67,37 +63,30 @@ function findModuleScripts( ) { const scriptText = snapshot.getText(node.startTagEnd, node.endTagStart); const extension = getScriptType(node) === 'processed module' ? 'mts' : 'mjs'; - const languageId = getScriptType(node) === 'processed module' ? 'typescript' : 'javascript'; - const scriptKind = - getScriptType(node) === 'processed module' - ? (3 satisfies ts.ScriptKind.TS) - : (1 satisfies ts.ScriptKind.JS); embeddedScripts.push({ fileName: fileName + `.${scriptIndex}.${extension}`, - languageId: languageId, - typescript: { - scriptKind: scriptKind, - }, + kind: FileKind.TypeScriptHostFile, snapshot: { getText: (start, end) => scriptText.substring(start, end), getLength: () => scriptText.length, getChangeRange: () => undefined, }, + codegenStacks: [], mappings: [ { - sourceOffsets: [node.startTagEnd], - generatedOffsets: [0], - lengths: [scriptText.length], - data: { - verification: true, - completion: true, - semantic: true, - navigation: true, - structure: true, - format: false, - }, + sourceRange: [node.startTagEnd, node.endTagStart], + generatedRange: [0, scriptText.length], + data: FileRangeCapabilities.full, }, ], + capabilities: { + diagnostic: true, + codeAction: true, + inlayHint: true, + documentSymbol: true, + foldingRange: true, + documentFormatting: false, + }, embeddedFiles: [], }); scriptIndex++; @@ -208,39 +197,38 @@ function findEventAttributes(ast: ParseResult['ast']): JavaScriptContext[] { * Merge all the inline and non-hoisted scripts into a single `.mjs` file */ function mergeJSContexts(fileName: string, javascriptContexts: JavaScriptContext[]): VirtualFile { - const codes: Segment[] = []; + const codes: muggle.Segment[] = []; for (const javascriptContext of javascriptContexts) { codes.push([ javascriptContext.content, undefined, javascriptContext.startOffset, - { - verification: true, - completion: true, - semantic: true, - navigation: true, - structure: true, - format: false, - }, + FileRangeCapabilities.full, ]); } - const mappings = buildMappings(codes); - const text = toString(codes); + const mappings = SourceMap.buildMappings(codes); + const text = muggle.toString(codes); return { fileName: fileName + '.inline.mjs', - languageId: 'javascript', - typescript: { - scriptKind: 1 satisfies ts.ScriptKind.JS, - }, + codegenStacks: [], snapshot: { getText: (start, end) => text.substring(start, end), getLength: () => text.length, getChangeRange: () => undefined, }, + capabilities: { + codeAction: true, + diagnostic: true, + documentFormatting: false, + documentSymbol: true, + foldingRange: true, + inlayHint: true, + }, embeddedFiles: [], + kind: FileKind.TypeScriptHostFile, mappings, }; } diff --git a/packages/language-server/src/core/svelte.ts b/packages/language-server/src/core/svelte.ts index feba0de6..97b6a46d 100644 --- a/packages/language-server/src/core/svelte.ts +++ b/packages/language-server/src/core/svelte.ts @@ -1,11 +1,18 @@ -import type { CodeInformation, LanguagePlugin, Mapping, VirtualFile } from '@volar/language-core'; +import { + FileCapabilities, + FileKind, + FileRangeCapabilities, + Language, + VirtualFile, +} from '@volar/language-core'; +import type { Mapping } from '@volar/source-map'; import type ts from 'typescript/lib/tsserverlibrary'; import { framework2tsx } from './utils.js'; -export function getSvelteLanguageModule(): LanguagePlugin { +export function getSvelteLanguageModule(): Language { return { - createVirtualFile(fileName, languageId, snapshot) { - if (languageId === 'svelte') { + createVirtualFile(fileName, snapshot) { + if (fileName.endsWith('.svelte')) { return new SvelteFile(fileName, snapshot); } }, @@ -16,17 +23,19 @@ export function getSvelteLanguageModule(): LanguagePlugin { } class SvelteFile implements VirtualFile { + kind = FileKind.TextFile; + capabilities = FileCapabilities.full; + fileName: string; - languageId = 'svelte'; - mappings!: Mapping[]; + mappings!: Mapping[]; embeddedFiles!: VirtualFile[]; codegenStacks = []; constructor( - public sourceFileId: string, + public sourceFileName: string, public snapshot: ts.IScriptSnapshot ) { - this.fileName = sourceFileId; + this.fileName = sourceFileName; this.onSnapshotUpdated(); } @@ -38,17 +47,9 @@ class SvelteFile implements VirtualFile { private onSnapshotUpdated() { this.mappings = [ { - sourceOffsets: [0], - generatedOffsets: [0], - lengths: [this.snapshot.getLength()], - data: { - verification: true, - completion: true, - semantic: true, - navigation: true, - structure: true, - format: true, - }, + sourceRange: [0, this.snapshot.getLength()], + generatedRange: [0, this.snapshot.getLength()], + data: FileRangeCapabilities.full, }, ]; diff --git a/packages/language-server/src/core/utils.ts b/packages/language-server/src/core/utils.ts index 7ac65810..c589f25e 100644 --- a/packages/language-server/src/core/utils.ts +++ b/packages/language-server/src/core/utils.ts @@ -1,4 +1,9 @@ -import type { VirtualFile } from '@volar/language-core'; +import { + FileCapabilities, + FileKind, + FileRangeCapabilities, + type VirtualFile, +} from '@volar/language-core'; import * as path from 'node:path'; import { URI, Utils } from 'vscode-uri'; import { importSvelteIntegration, importVueIntegration } from '../importPackage'; @@ -25,28 +30,19 @@ export function framework2tsx( function getVirtualFile(content: string): VirtualFile { return { fileName: fileName + '.tsx', - languageId: 'typescript', - typescript: { - scriptKind: 4 satisfies import('typescript/lib/tsserverlibrary').ScriptKind.TSX, - }, + capabilities: FileCapabilities.full, + kind: FileKind.TypeScriptHostFile, snapshot: { getText: (start, end) => content.substring(start, end), getLength: () => content.length, getChangeRange: () => undefined, }, + codegenStacks: [], mappings: [ { - sourceOffsets: [0], - generatedOffsets: [0], - lengths: [content.length], - data: { - verification: true, - completion: true, - semantic: true, - navigation: true, - structure: true, - format: true, - }, + sourceRange: [0, content.length], + generatedRange: [0, 0], + data: FileRangeCapabilities.full, }, ], embeddedFiles: [], diff --git a/packages/language-server/src/core/vue.ts b/packages/language-server/src/core/vue.ts index c43fa90e..748d03e4 100644 --- a/packages/language-server/src/core/vue.ts +++ b/packages/language-server/src/core/vue.ts @@ -1,11 +1,18 @@ -import type { CodeInformation, LanguagePlugin, Mapping, VirtualFile } from '@volar/language-core'; +import { + FileCapabilities, + FileKind, + FileRangeCapabilities, + Language, + VirtualFile, +} from '@volar/language-core'; +import type { Mapping } from '@volar/source-map'; import type ts from 'typescript/lib/tsserverlibrary'; import { framework2tsx } from './utils.js'; -export function getVueLanguageModule(): LanguagePlugin { +export function getVueLanguageModule(): Language { return { - createVirtualFile(fileName, languageId, snapshot) { - if (languageId === 'vue') { + createVirtualFile(fileName, snapshot) { + if (fileName.endsWith('.vue')) { return new VueFile(fileName, snapshot); } }, @@ -16,9 +23,11 @@ export function getVueLanguageModule(): LanguagePlugin { } class VueFile implements VirtualFile { + kind = FileKind.TextFile; + capabilities = FileCapabilities.full; + fileName: string; - languageId = 'vue'; - mappings!: Mapping[]; + mappings!: Mapping[]; embeddedFiles!: VirtualFile[]; codegenStacks = []; @@ -38,17 +47,9 @@ class VueFile implements VirtualFile { private onSnapshotUpdated() { this.mappings = [ { - sourceOffsets: [0], - generatedOffsets: [0], - lengths: [this.snapshot.getLength()], - data: { - verification: true, - completion: true, - semantic: true, - navigation: true, - structure: true, - format: true, - }, + sourceRange: [0, this.snapshot.getLength()], + generatedRange: [0, this.snapshot.getLength()], + data: FileRangeCapabilities.full, }, ]; diff --git a/packages/language-server/src/languageServerPlugin.ts b/packages/language-server/src/languageServerPlugin.ts index 88a6e783..a7af3d55 100644 --- a/packages/language-server/src/languageServerPlugin.ts +++ b/packages/language-server/src/languageServerPlugin.ts @@ -1,11 +1,7 @@ import { - Connection, - LanguagePlugin, + LanguageServerPlugin, MessageType, - ServiceEnvironment, - ServicePlugin, ShowMessageNotification, - VirtualFile, } from '@volar/language-server/node'; import { getLanguageModule } from './core'; import { getSvelteLanguageModule } from './core/svelte.js'; @@ -19,150 +15,121 @@ import { create as createEmmetService } from 'volar-service-emmet'; import { create as createPrettierService } from 'volar-service-prettier'; import { create as createTypeScriptTwoSlashService } from 'volar-service-typescript-twoslash-queries'; -import type { createServer, ServerOptions } from '@volar/language-server/lib/server.js'; import { create as createAstroService } from './plugins/astro.js'; 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 function createServerOptions( - connection: Connection, - server: ReturnType -): ServerOptions { - return { - watchFileExtensions: [ - 'js', - 'cjs', - 'mjs', - 'ts', - 'cts', - 'mts', - 'jsx', - 'tsx', - 'json', - 'astro', - 'vue', - 'svelte', - ], - getServerCapabilitiesSetup() { - const ts = getTypeScriptModule(); - const servicePlugins: ServicePlugin[] = [ - createHtmlService(), - createCssService(), - createEmmetService(), - createTypeScriptService(ts), - createTypeScriptTwoSlashService(), - createTypescriptAddonsService(), - createAstroService(ts), - ]; - - return { - servicePlugins: servicePlugins, - }; - }, - getProjectSetup(serviceEnv, projectContext) { - const ts = getTypeScriptModule(); - const servicePlugins: ServicePlugin[] = [ - createHtmlService(), - createCssService(), - createEmmetService(), - createTypeScriptService(ts), - createTypeScriptTwoSlashService(), - createTypescriptAddonsService(), - createAstroService(ts), - ]; - - const languagePlugins: LanguagePlugin[] = [ - getVueLanguageModule(), - getSvelteLanguageModule(), - ]; - - if (serviceEnv && projectContext?.typescript) { - const rootPath = projectContext.typescript.configFileName - ? projectContext.typescript.configFileName.split('/').slice(0, -1).join('/') - : serviceEnv.uriToFileName(serviceEnv.workspaceFolder.uri.toString()); - const nearestPackageJson = server.modules.typescript?.findConfigFile( - rootPath, - ts.sys.fileExists, - 'package.json' - ); +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 nearestPackageJson = modules.typescript?.findConfigFile( + ctx.project.rootUri.fsPath, + modules.typescript.sys.fileExists, + 'package.json' + ); + + const astroInstall = getAstroInstall([ctx.project.rootUri.fsPath], { + nearestPackageJson: nearestPackageJson, + readDirectory: modules.typescript!.sys.readDirectory, + }); - const astroInstall = getAstroInstall([rootPath], { - nearestPackageJson: nearestPackageJson, - readDirectory: ts.sys.readDirectory, + if (astroInstall === 'not-found') { + 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 === 'not-found') { - connection.sendNotification(ShowMessageNotification.type, { - message: `Couldn't find Astro in workspace "${rootPath}". 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, - }); - } - - languagePlugins.unshift( - getLanguageModule(typeof astroInstall === 'string' ? undefined : astroInstall, ts) - ); } - const prettierService = getPrettierService(serviceEnv); - - return { - languagePlugins: languagePlugins, - servicePlugins: [...servicePlugins, ...(prettierService ? [prettierService] : [])], - }; - }, - }; - function getTypeScriptModule() { - const tsModule = server.modules.typescript; - if (!tsModule) { - throw new Error('TypeScript module is missing'); + config.languages.astro = getLanguageModule( + typeof astroInstall === 'string' ? undefined : astroInstall, + modules.typescript! + ); + config.languages.vue = getVueLanguageModule(); + config.languages.svelte = getSvelteLanguageModule(); } - return tsModule; - } - function getPrettierService(env: ServiceEnvironment) { - const workspacePath = env.uriToFileName(env.workspaceFolder.uri.toString()); - const prettier = importPrettier(workspacePath); - const prettierPluginPath = getPrettierPluginPath(workspacePath); - - if (prettier && prettierPluginPath) { - return 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 []; + 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 (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 + + return hasPluginLoadedAlready ? [] : [prettierPluginPath]; } - 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]; - } - - const plugins = [...(await getAstroPrettierPlugin()), ...(resolvedConfig.plugins ?? [])]; - - 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, - }); + 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 config; + }, +}); diff --git a/packages/language-server/src/nodeServer.ts b/packages/language-server/src/nodeServer.ts index 1e894167..d0ffbcb1 100644 --- a/packages/language-server/src/nodeServer.ts +++ b/packages/language-server/src/nodeServer.ts @@ -1,23 +1,4 @@ -import { - createConnection, - createNodeServer, - createTypeScriptProjectProvider, -} from '@volar/language-server/node'; -import { createServerOptions } from './languageServerPlugin.js'; +import { createConnection, startLanguageServer } from '@volar/language-server/node'; +import { plugin } from './languageServerPlugin.js'; -const connection = createConnection(); -const server = createNodeServer(connection); - -connection.listen(); - -connection.onInitialize((params) => { - return server.initialize( - params, - createTypeScriptProjectProvider, - createServerOptions(connection, server) - ); -}); - -connection.onInitialized(() => { - server.initialized(); -}); +startLanguageServer(createConnection(), plugin); diff --git a/packages/language-server/src/plugins/astro.ts b/packages/language-server/src/plugins/astro.ts index 763c7e5f..d0d6a1fd 100644 --- a/packages/language-server/src/plugins/astro.ts +++ b/packages/language-server/src/plugins/astro.ts @@ -7,8 +7,7 @@ import { InsertTextFormat, Position, Range, - ServicePlugin, - ServicePluginInstance, + Service, TextEdit, } from '@volar/language-server'; import fg from 'fast-glob'; @@ -18,97 +17,90 @@ import type { TextDocument } from 'vscode-html-languageservice'; import { AstroFile } from '../core/index.js'; import { isJSDocument } from './utils.js'; -export const create = (ts: typeof import('typescript')): ServicePlugin => { - return { - triggerCharacters: ['-'], - create(context): ServicePluginInstance { - return { - provideCompletionItems(document, position, completionContext, token) { - if (token.isCancellationRequested) return null; - let items: CompletionItem[] = []; - - const [file] = context.language.files.getVirtualFile( - context.env.uriToFileName(document.uri) - ); - if (!(file instanceof AstroFile)) return; - - if (completionContext.triggerCharacter === '-') { - const frontmatterCompletion = getFrontmatterCompletion(file, document, position); - if (frontmatterCompletion) items.push(frontmatterCompletion); - } +export const create = + (): Service => + (context, modules): ReturnType => { + return { + triggerCharacters: ['-'], + provideCompletionItems(document, position, completionContext, token) { + if (token.isCancellationRequested) return null; + let items: CompletionItem[] = []; + + const [file] = context!.documents.getVirtualFileByUri(document.uri); + if (!(file instanceof AstroFile)) return; + + if (completionContext.triggerCharacter === '-') { + const frontmatterCompletion = getFrontmatterCompletion(file, document, position); + if (frontmatterCompletion) items.push(frontmatterCompletion); + } + return { + isIncomplete: false, + items: items, + }; + }, + provideSemanticDiagnostics(document, token) { + if (token.isCancellationRequested) return []; + + const [file] = context!.documents.getVirtualFileByUri(document.uri); + if (!(file instanceof AstroFile)) return; + + return file.compilerDiagnostics.map(compilerMessageToDiagnostic); + + function compilerMessageToDiagnostic(message: DiagnosticMessage): Diagnostic { return { - isIncomplete: false, - items: items, + message: message.text + (message.hint ? '\n\n' + message.hint : ''), + range: Range.create( + message.location.line - 1, + message.location.column - 1, + message.location.line, + message.location.length + ), + code: message.code, + severity: message.severity, + source: 'astro', }; - }, - provideSemanticDiagnostics(document, token) { - if (token.isCancellationRequested) return []; - - const [file] = context.language.files.getVirtualFile( - context.env.uriToFileName(document.uri) - ); - if (!(file instanceof AstroFile)) return; - - return file.compilerDiagnostics.map(compilerMessageToDiagnostic); - - function compilerMessageToDiagnostic(message: DiagnosticMessage): Diagnostic { - return { - message: message.text + (message.hint ? '\n\n' + message.hint : ''), - range: Range.create( - message.location.line - 1, - message.location.column - 1, - message.location.line, - message.location.length - ), - code: message.code, - severity: message.severity, - source: 'astro', - }; - } - }, - provideCodeLenses(document, token) { - if (token.isCancellationRequested) return; - if (!isJSDocument(document.languageId)) return; - - const languageService = context.inject( - 'typescript/languageService' - ); - if (!languageService) return; - - const tsProgram = languageService.getProgram(); - if (!tsProgram) return; - - const globcodeLens: CodeLens[] = []; - const sourceFile = tsProgram.getSourceFile(context.env.uriToFileName(document.uri))!; - - function walk() { - return ts.forEachChild(sourceFile, function cb(node): void { - if (ts.isCallExpression(node) && node.expression.getText() === 'Astro.glob') { - const globArgument = node.arguments.at(0); - - if (globArgument) { - globcodeLens.push( - getGlobResultAsCodeLens( - globArgument.getText().slice(1, -1), - dirname(context.env.uriToFileName(document.uri)), - document.positionAt(node.arguments.pos) - ) - ); - } + } + }, + provideCodeLenses(document, token) { + if (token.isCancellationRequested) return; + if (!context || !modules?.typescript || !isJSDocument(document.languageId)) return; + + const languageService = context.inject('typescript/languageService'); + if (!languageService) return; + + const ts = modules?.typescript; + const tsProgram = languageService.getProgram(); + if (!tsProgram) return; + + const globcodeLens: CodeLens[] = []; + const sourceFile = tsProgram.getSourceFile(context.env.uriToFileName(document.uri))!; + + function walk() { + return ts.forEachChild(sourceFile, function cb(node): void { + if (ts.isCallExpression(node) && node.expression.getText() === 'Astro.glob') { + const globArgument = node.arguments.at(0); + + if (globArgument) { + globcodeLens.push( + getGlobResultAsCodeLens( + globArgument.getText().slice(1, -1), + dirname(context!.env.uriToFileName(document.uri)), + document.positionAt(node.arguments.pos) + ) + ); } - return ts.forEachChild(node, cb); - }); - } + } + return ts.forEachChild(node, cb); + }); + } - walk(); + walk(); - return globcodeLens; - }, - }; - }, + return globcodeLens; + }, + }; }; -}; function getGlobResultAsCodeLens(globText: string, dir: string, position: Position) { const globResult = fg.sync(globText, { diff --git a/packages/language-server/src/plugins/html.ts b/packages/language-server/src/plugins/html.ts index 41d45649..c795bc31 100644 --- a/packages/language-server/src/plugins/html.ts +++ b/packages/language-server/src/plugins/html.ts @@ -1,61 +1,59 @@ -import { CompletionItemKind, ServicePlugin, ServicePluginInstance } from '@volar/language-server'; -import { create as createHtmlService } from 'volar-service-html'; +import { CompletionItemKind, Service } from '@volar/language-server'; +import createHtmlService from 'volar-service-html'; import { AstroFile } from '../core/index.js'; import { astroAttributes, astroElements, classListAttribute } from './html-data.js'; import { isInComponentStartTag } from './utils.js'; -export const create = (): ServicePlugin => { - const htmlServicePlugin = createHtmlService(); - return { - ...htmlServicePlugin, - create(context): ServicePluginInstance { - const htmlPlugin = htmlServicePlugin.create(context); - - htmlPlugin.provide['html/updateCustomData']?.([ - astroAttributes, - astroElements, - classListAttribute, - ]); - - return { - ...htmlPlugin, - async provideCompletionItems(document, position, completionContext, token) { - if (document.languageId !== 'html') return; - - const [_, source] = context.language.files.getVirtualFile( - context.env.uriToFileName(document.uri) - ); - const rootVirtualFile = source?.virtualFile?.[0]; - if (!(rootVirtualFile instanceof AstroFile)) return; - - // Don't return completions if the current node is a component - if (isInComponentStartTag(rootVirtualFile.htmlDocument, document.offsetAt(position))) { - return null; - } - - const completions = await htmlPlugin.provideCompletionItems!( - document, - position, - completionContext, - token - ); - - if (!completions) { - return null; - } - - // We don't want completions for file references, as they're mostly invalid for Astro - completions.items = completions.items.filter( - (completion) => completion.kind !== CompletionItemKind.File - ); - - return completions; - }, - // Document links provided by `vscode-html-languageservice` are invalid for Astro - provideDocumentLinks() { - return []; - }, - }; - }, +export const create = + (): Service => + (context, modules): ReturnType => { + const htmlPlugin = createHtmlService()(context, modules); + + if (!context) { + return { triggerCharacters: htmlPlugin.triggerCharacters }; + } + + htmlPlugin.provide['html/updateCustomData']?.([ + astroAttributes, + astroElements, + classListAttribute, + ]); + + return { + ...htmlPlugin, + async provideCompletionItems(document, position, completionContext, token) { + if (document.languageId !== 'html') return; + + const [_, source] = context.documents.getVirtualFileByUri(document.uri); + const rootVirtualFile = source?.root; + if (!(rootVirtualFile instanceof AstroFile)) return; + + // Don't return completions if the current node is a component + if (isInComponentStartTag(rootVirtualFile.htmlDocument, document.offsetAt(position))) { + return null; + } + + const completions = await htmlPlugin.provideCompletionItems!( + document, + position, + completionContext, + token + ); + + if (!completions) { + return null; + } + + // We don't want completions for file references, as they're mostly invalid for Astro + completions.items = completions.items.filter( + (completion) => completion.kind !== CompletionItemKind.File + ); + + return completions; + }, + // Document links provided by `vscode-html-languageservice` are invalid for Astro + provideDocumentLinks() { + return []; + }, + }; }; -}; diff --git a/packages/language-server/src/plugins/typescript-addons/index.ts b/packages/language-server/src/plugins/typescript-addons/index.ts index a9e40c5e..e0d35b25 100644 --- a/packages/language-server/src/plugins/typescript-addons/index.ts +++ b/packages/language-server/src/plugins/typescript-addons/index.ts @@ -1,50 +1,46 @@ -import type { CompletionList, ServicePlugin, ServicePluginInstance } from '@volar/language-server'; +import type { CompletionList, Service } from '@volar/language-server'; import { AstroFile } from '../../core/index.js'; import { isInsideFrontmatter, isJSDocument } from '../utils.js'; import { getSnippetCompletions } from './snippets.js'; -export const create = (): ServicePlugin => { - return { - create(context): ServicePluginInstance { - return { - isAdditionalCompletion: true, - // Q: Why the empty transform and resolve functions? - // A: Volar will skip mapping the completion items if those functions are defined, as such we can return the snippets - // completions as-is, this is notably useful for snippets that insert to the frontmatter, since we don't need to map anything. - transformCompletionItem(item) { - return item; - }, - provideCompletionItems(document, position, completionContext, token) { - if ( - !context || - !isJSDocument || - token.isCancellationRequested || - completionContext.triggerKind === 2 - ) - return null; +export const create = + (): Service => + (context): ReturnType => { + return { + isAdditionalCompletion: true, + // Q: Why the empty transform and resolve functions? + // A: Volar will skip mapping the completion items if those functions are defined, as such we can return the snippets + // completions as-is, this is notably useful for snippets that insert to the frontmatter, since we don't need to map anything. + transformCompletionItem(item) { + return item; + }, + provideCompletionItems(document, position, completionContext, token) { + if ( + !context || + !isJSDocument || + token.isCancellationRequested || + completionContext.triggerKind === 2 + ) + return null; - const [_, source] = context.language.files.getVirtualFile( - context.env.uriToFileName(document.uri) - ); - const file = source?.virtualFile?.[0]; - if (!(file instanceof AstroFile)) return undefined; + const [_, source] = context.documents.getVirtualFileByUri(document.uri); + const file = source?.root; + if (!(file instanceof AstroFile)) return undefined; - if (!isInsideFrontmatter(document.offsetAt(position), file.astroMeta.frontmatter)) - return null; + if (!isInsideFrontmatter(document.offsetAt(position), file.astroMeta.frontmatter)) + return null; - const completionList: CompletionList = { - items: [], - isIncomplete: false, - }; + const completionList: CompletionList = { + items: [], + isIncomplete: false, + }; - completionList.items.push(...getSnippetCompletions(file.astroMeta.frontmatter)); + completionList.items.push(...getSnippetCompletions(file.astroMeta.frontmatter)); - return completionList; - }, - resolveCompletionItem(item) { - return item; - }, - }; - }, + return completionList; + }, + resolveCompletionItem(item) { + return item; + }, + }; }; -}; diff --git a/packages/language-server/src/plugins/typescript/index.ts b/packages/language-server/src/plugins/typescript/index.ts index 50e40b49..089a5818 100644 --- a/packages/language-server/src/plugins/typescript/index.ts +++ b/packages/language-server/src/plugins/typescript/index.ts @@ -1,5 +1,5 @@ -import { ServicePlugin, ServicePluginInstance, TextDocumentEdit } from '@volar/language-server'; -import { create as createTypeScriptService } from 'volar-service-typescript'; +import { Service, TextDocumentEdit } from '@volar/language-server'; +import createTypeScriptService from 'volar-service-typescript'; import { AstroFile } from '../../core/index.js'; import { editShouldBeInFrontmatter, @@ -9,163 +9,149 @@ import { import { enhancedProvideCompletionItems, enhancedResolveCompletionItem } from './completions.js'; import { enhancedProvideSemanticDiagnostics } from './diagnostics.js'; -export const create = (ts: typeof import('typescript')): ServicePlugin => { - const tsServicePlugin = createTypeScriptService( - ts as typeof import('typescript/lib/tsserverlibrary') - ); - return { - ...tsServicePlugin, - create(context): ServicePluginInstance { - const tsService = tsServicePlugin.create(context); +export const create = + (): Service => + (context, modules): ReturnType => { + const typeScriptPlugin = createTypeScriptService()(context, modules); + + if (!context) { return { - ...tsService, - transformCompletionItem(item) { - const [_, source] = context.language.files.getVirtualFile( - context.env.uriToFileName(item.data.uri) - ); - const file = source?.virtualFile?.[0]; - if (!(file instanceof AstroFile) || !context.language.typescript) return undefined; - if (file.scriptFiles.includes(item.data.fileName)) return undefined; - - const newLine = - context.language.typescript.languageServiceHost - .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 - // for some unknown reason. This work around the problem by always ensuring a normal import for components - if (item.data.isComponent && edit.newText.includes('import type')) { - edit.newText.replace('import type', 'import'); - } + triggerCharacters: typeScriptPlugin.triggerCharacters, + signatureHelpTriggerCharacters: typeScriptPlugin.signatureHelpTriggerCharacters, + signatureHelpRetriggerCharacters: typeScriptPlugin.signatureHelpRetriggerCharacters, + }; + } - if (editShouldBeInFrontmatter(edit.range)) { - return ensureProperEditForFrontmatter(edit, file.astroMeta.frontmatter, newLine); - } + return { + ...typeScriptPlugin, + transformCompletionItem(item) { + const [_, source] = context.documents.getVirtualFileByUri(item.data.uri); + const file = source?.root; + if (!(file instanceof AstroFile) || !context.host) return undefined; + if (file.scriptFiles.includes(item.data.fileName)) return undefined; - return edit; - }); - } + const newLine = context.host.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 + // for some unknown reason. This work around the problem by always ensuring a normal import for components + if (item.data.isComponent && edit.newText.includes('import type')) { + edit.newText.replace('import type', 'import'); + } - return item; - }, - transformCodeAction(item) { - if (item.kind !== 'quickfix') return undefined; - const originalUri = item.data.uri.replace('.tsx', ''); + if (editShouldBeInFrontmatter(edit.range)) { + return ensureProperEditForFrontmatter(edit, file.astroMeta.frontmatter, newLine); + } - const [_, source] = context.language.files.getVirtualFile( - context.env.uriToFileName(originalUri) - ); - const file = source?.virtualFile?.[0]; - if (!(file instanceof AstroFile) || !context.language.typescript) return undefined; - if ( - file.scriptFiles.includes( - context.env.uriToFileName(item.diagnostics?.[0].data.documentUri) - ) - ) - return undefined; - - const document = context.documents.get( - context.env.fileNameToUri(file.fileName), - file.languageId, - file.snapshot - ); - const newLine = - context.language.typescript.languageServiceHost - .getCompilationSettings() - .newLine?.toString() ?? '\n'; - if (!item.edit?.documentChanges) return undefined; - item.edit.documentChanges = item.edit.documentChanges.map((change) => { - if (TextDocumentEdit.is(change)) { - change.textDocument.uri = originalUri; - if (change.edits.length === 1) { + return edit; + }); + } + + return item; + }, + transformCodeAction(item) { + if (item.kind !== 'quickfix') return undefined; + const originalFileName = item.data.uri.replace('.tsx', ''); + + const [_, source] = context.documents.getVirtualFileByUri(originalFileName); + const file = source?.root; + if (!(file instanceof AstroFile) || !context.host) return undefined; + if ( + file.scriptFiles.includes(item.diagnostics?.[0].data.documentUri.replace('file://', '')) + ) + return undefined; + + const document = context.getTextDocument(originalFileName); + if (!document) return undefined; + + const newLine = context.host.getCompilationSettings().newLine?.toString() ?? '\n'; + if (!item.edit?.documentChanges) return undefined; + item.edit.documentChanges = item.edit.documentChanges.map((change) => { + if (TextDocumentEdit.is(change)) { + change.textDocument.uri = originalFileName; + if (change.edits.length === 1) { + change.edits = change.edits.map((edit) => { + const editInFrontmatter = editShouldBeInFrontmatter(edit.range, document); + if (editInFrontmatter.itShould) { + return ensureProperEditForFrontmatter( + edit, + file.astroMeta.frontmatter, + newLine, + editInFrontmatter.position + ); + } + + return edit; + }); + } else { + if (file.astroMeta.frontmatter.status === 'closed') { change.edits = change.edits.map((edit) => { const editInFrontmatter = editShouldBeInFrontmatter(edit.range, document); if (editInFrontmatter.itShould) { - return ensureProperEditForFrontmatter( - edit, + edit.range = ensureRangeIsInFrontmatter( + edit.range, file.astroMeta.frontmatter, - newLine, editInFrontmatter.position ); } - return edit; }); } else { - if (file.astroMeta.frontmatter.status === 'closed') { - change.edits = change.edits.map((edit) => { - const editInFrontmatter = editShouldBeInFrontmatter(edit.range, document); - if (editInFrontmatter.itShould) { - edit.range = ensureRangeIsInFrontmatter( - edit.range, - file.astroMeta.frontmatter, - editInFrontmatter.position - ); - } - return edit; - }); - } else { - // TODO: Handle when there's multiple edits and a new frontmatter is potentially needed - if ( - change.edits.some((edit) => { - return editShouldBeInFrontmatter(edit.range, document).itShould; - }) - ) { - console.error( - 'Code actions with multiple edits that require potentially creating a frontmatter are currently not implemented. In the meantime, please manually insert a frontmatter in your file before using this code action.' - ); - change.edits = []; - } + // TODO: Handle when there's multiple edits and a new frontmatter is potentially needed + if ( + change.edits.some((edit) => { + return editShouldBeInFrontmatter(edit.range, document).itShould; + }) + ) { + console.error( + 'Code actions with multiple edits that require potentially creating a frontmatter are currently not implemented. In the meantime, please manually insert a frontmatter in your file before using this code action.' + ); + change.edits = []; } } } - return change; - }); + } + return change; + }); - return item; - }, - async provideCompletionItems(document, position, completionContext, token) { - const originalCompletions = await tsService.provideCompletionItems!( - document, - position, - completionContext, - token - ); - if (!originalCompletions) return null; - - return enhancedProvideCompletionItems(originalCompletions); - }, - async resolveCompletionItem(item, token) { - const resolvedCompletionItem = await tsService.resolveCompletionItem!(item, token); - if (!resolvedCompletionItem) return item; - - return enhancedResolveCompletionItem(resolvedCompletionItem); - }, - async provideSemanticDiagnostics(document, token) { - const [_, source] = context.language.files.getVirtualFile( - context.env.uriToFileName(document.uri) + return item; + }, + async provideCompletionItems(document, position, completionContext, token) { + const originalCompletions = await typeScriptPlugin.provideCompletionItems!( + document, + position, + completionContext, + token + ); + if (!originalCompletions) return null; + + return enhancedProvideCompletionItems(originalCompletions); + }, + async resolveCompletionItem(item, token) { + const resolvedCompletionItem = await typeScriptPlugin.resolveCompletionItem!(item, token); + if (!resolvedCompletionItem) return item; + + return enhancedResolveCompletionItem(resolvedCompletionItem); + }, + async provideSemanticDiagnostics(document, token) { + const [_, source] = context.documents.getVirtualFileByUri(document.uri); + const file = source?.root; + let astroDocument = undefined; + + if (file instanceof AstroFile) { + // If we have compiler errors, our TSX isn't valid so don't bother showing TS errors + if (file.hasCompilationErrors) return null; + + astroDocument = context.documents.getDocumentByFileName( + file.snapshot, + file.sourceFileName ); - const file = source?.virtualFile?.[0]; - let astroDocument = undefined; - - if (file instanceof AstroFile) { - // If we have compiler errors, our TSX isn't valid so don't bother showing TS errors - if (file.hasCompilationErrors) return null; - - astroDocument = context.documents.get( - context.env.fileNameToUri(file.sourceFileName), - file.languageId, - file.snapshot - ); - } + } - const diagnostics = await tsService.provideSemanticDiagnostics!(document, token); - if (!diagnostics) return null; + const diagnostics = await typeScriptPlugin.provideSemanticDiagnostics!(document, token); + if (!diagnostics) return null; - return enhancedProvideSemanticDiagnostics(diagnostics, astroDocument?.lineCount); - }, - }; - }, + return enhancedProvideSemanticDiagnostics(diagnostics, astroDocument?.lineCount); + }, + }; }; -}; diff --git a/packages/language-server/test/css/completions.test.ts b/packages/language-server/test/css/completions.test.ts index 0111c82e..c6111633 100644 --- a/packages/language-server/test/css/completions.test.ts +++ b/packages/language-server/test/css/completions.test.ts @@ -6,28 +6,31 @@ import { LanguageServer, getLanguageServer } from '../server.js'; describe('CSS - Completions', () => { let languageServer: LanguageServer; - before(async () => (languageServer = await getLanguageServer())); + before(async () => { + languageServer = await getLanguageServer(); + }); it('Can provide completions for CSS properties', async () => { - const document = await languageServer.openFakeDocument(``, 'astro'); - const completions = await languageServer.handle.sendCompletionRequest( - document.uri, + const document = await languageServer.helpers.openFakeDocument(``); + const completions = await languageServer.helpers.requestCompletion( + document, Position.create(0, 18) ); - expect(completions!.items).to.not.be.empty; + expect(completions.items).to.not.be.empty; + expect(completions.items[0].data.serviceId).to.equal('css'); }); it('Can provide completions for CSS values', async () => { - const document = await languageServer.openFakeDocument( - ``, - 'astro' + const document = await languageServer.helpers.openFakeDocument( + `` ); - const completions = await languageServer.handle.sendCompletionRequest( - document.uri, + const completions = await languageServer.helpers.requestCompletion( + document, Position.create(0, 21) ); - expect(completions!.items).to.not.be.empty; + expect(completions.items).to.not.be.empty; + expect(completions.items[0].data.serviceId).to.equal('css'); }); }); diff --git a/packages/language-server/test/html/completions.test.ts b/packages/language-server/test/html/completions.test.ts index e6a4b45b..cec46b5c 100644 --- a/packages/language-server/test/html/completions.test.ts +++ b/packages/language-server/test/html/completions.test.ts @@ -1,32 +1,36 @@ import { Position } from '@volar/language-server'; import { expect } from 'chai'; import { describe } from 'mocha'; -import { getLanguageServer, type LanguageServer } from '../server.js'; +import { LanguageServer, getLanguageServer } from '../server.js'; describe('HTML - Completions', () => { let languageServer: LanguageServer; - before(async () => (languageServer = await getLanguageServer())); + before(async () => { + languageServer = await getLanguageServer(); + }); - it('Can provide completions for HTML tags zzz', async () => { - const document = await languageServer.openFakeDocument(` { + const document = await languageServer.helpers.openFakeDocument(` { - const document = await languageServer.openFakeDocument(`
{ let languageServer: LanguageServer; - before(async () => (languageServer = await getLanguageServer())); + before(async () => { + languageServer = await getLanguageServer(); + }); it('Can provide hover for HTML tags', async () => { - const document = await languageServer.openFakeDocument(` { - const document = await languageServer.openFakeDocument(`
{ let languageServer: LanguageServer; - before(async () => (languageServer = await getLanguageServer())); + before(async () => { + languageServer = await getLanguageServer(); + }); it('Can format document', async () => { - const document = await languageServer.openFakeDocument(`---\n\n\n---`, 'astro'); - const formatEdits = await languageServer.handle.sendDocumentFormattingRequest(document.uri, { + const document = await languageServer.helpers.openFakeDocument(`---\n\n\n---`); + const formatEdits = await languageServer.helpers.requestFormatting(document, { tabSize: 2, insertSpaces: true, }); diff --git a/packages/language-server/test/misc/init.test.ts b/packages/language-server/test/misc/init.test.ts index 64190104..5cc42bb0 100644 --- a/packages/language-server/test/misc/init.test.ts +++ b/packages/language-server/test/misc/init.test.ts @@ -1,19 +1,19 @@ -import type { InitializeResult, ServerCapabilities } from '@volar/language-server'; +import type { ServerCapabilities } from '@volar/language-server'; import { expect } from 'chai'; import { before, describe, it } from 'mocha'; -import { getLanguageServer } from '../server.js'; +import { LanguageServer, getLanguageServer } from '../server.js'; describe('Initialize', async () => { - let initializeResult: InitializeResult; + let languageServer: LanguageServer; before(async function () { // First init can sometimes be slow in CI, even though the rest of the tests will be fast. this.timeout(50000); - initializeResult = (await getLanguageServer()).initializeResult; + languageServer = await getLanguageServer(); }); it('Can start server', async () => { - expect(initializeResult).not.be.null; + expect(languageServer.initResult).not.be.null; }); it('Has proper capabilities', async () => { @@ -39,16 +39,16 @@ describe('Initialize', async () => { documentSymbolProvider: true, documentFormattingProvider: true, documentRangeFormattingProvider: true, - documentOnTypeFormattingProvider: { - firstTriggerCharacter: ';', - moreTriggerCharacter: ['}', '\n'], - }, referencesProvider: true, implementationProvider: true, definitionProvider: true, typeDefinitionProvider: true, callHierarchyProvider: true, hoverProvider: true, + diagnosticProvider: { + interFileDependencies: true, + workspaceDiagnostics: false, + }, renameProvider: { prepareProvider: true }, signatureHelpProvider: { triggerCharacters: ['(', ',', '<'], retriggerCharacters: [')'] }, completionProvider: { @@ -143,6 +143,6 @@ describe('Initialize', async () => { workspaceSymbolProvider: true, }; - expect(initializeResult.capabilities).to.deep.equal(capabilities); + expect(languageServer.initResult.capabilities).to.deep.equal(capabilities); }); }); diff --git a/packages/language-server/test/server.ts b/packages/language-server/test/server.ts index 646acf45..adfb2bf4 100644 --- a/packages/language-server/test/server.ts +++ b/packages/language-server/test/server.ts @@ -1,62 +1,167 @@ /* eslint-disable no-console */ -import { LanguageServerHandle, startLanguageServer } from '@volar/test-utils'; -import { createHash } from 'node:crypto'; +import { createHash } from 'crypto'; +import cp from 'node:child_process'; +import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import * as protocol from 'vscode-languageserver-protocol/node'; -import type { TextDocument } from 'vscode-languageserver-textdocument'; +import { TextDocument } from 'vscode-languageserver-textdocument'; import { URI } from 'vscode-uri'; -let serverHandle: LanguageServerHandle | undefined; -let initializeResult: protocol.InitializeResult | undefined; - -export type LanguageServer = { - handle: LanguageServerHandle; - initializeResult: protocol.InitializeResult; - openFakeDocument: (content: string, languageId: string) => Promise; -}; - -export async function getLanguageServer(): Promise { - if (!serverHandle) { - serverHandle = startLanguageServer( - path.resolve('./bin/nodeServer.js'), - fileURLToPath(new URL('./fixture', import.meta.url)) - ); - - initializeResult = await serverHandle.initialize( - URI.file(fileURLToPath(new URL('./fixture', import.meta.url))).toString(), - { - typescript: { - tsdk: path.join( - path.dirname(fileURLToPath(import.meta.url)), - '../', - 'node_modules', - 'typescript', - 'lib' - ), - }, - } - ); - // Ensure that our first test does not suffer from a TypeScript overhead - await serverHandle.sendCompletionRequest( - 'file://doesnt-exists', - protocol.Position.create(0, 0) - ); - } +export interface LanguageServer { + process: cp.ChildProcess; + connection: protocol.ProtocolConnection; + initResult: protocol.InitializeResult; + helpers: { + /** + * Open a real file path (relative to `./fixture`) and return the associated TextDocument + */ + openRealDocument: (filePath: string, languageId?: string) => Promise; + /** + * Create a fake document from content and return the associated TextDocument + */ + openFakeDocument: (content: string, languageId?: string) => Promise; + requestCompletion: ( + document: TextDocument, + position: protocol.Position + ) => Promise; + requestDiagnostics: (document: TextDocument) => Promise; + requestHover: (document: TextDocument, position: protocol.Position) => Promise; + requestFormatting( + document: TextDocument, + options?: protocol.FormattingOptions + ): Promise; + }; +} - if (!initializeResult || !serverHandle) { - throw new Error('Server not initialized'); +let languageServer: LanguageServer | undefined; + +export async function getLanguageServer() { + if (!languageServer) { + await initLanguageServer(); } + return languageServer!; +} + +async function initLanguageServer() { + if (languageServer) return; - return { - handle: serverHandle, - initializeResult: initializeResult, - openFakeDocument: async (content: string, languageId: string) => { - const hash = createHash('sha256').update(content).digest('base64url'); - const uri = URI.file(`does-not-exists-${hash}-.astro`).toString(); - const textDocument = await serverHandle!.openInMemoryDocument(uri, languageId, content); + const dir = fileURLToPath(new URL('./fixture', import.meta.url)); + const serverModule = path.resolve('./bin/nodeServer.js'); + const childProcess = cp.fork( + serverModule, + ['--node-ipc', `--clientProcessId=${process.pid.toString()}`], + { + execArgv: ['--nolazy'], + env: process.env, + cwd: dir, + } + ); + const connection = protocol.createProtocolConnection( + new protocol.IPCMessageReader(childProcess), + new protocol.IPCMessageWriter(childProcess) + ); + connection.listen(); - return textDocument; + connection.onClose((e) => console.log(e)); + connection.onDispose((e) => console.log(e)); + connection.onUnhandledNotification((e) => console.log(e)); + connection.onError((e) => console.log(e)); + + const initRequest = await connection.sendRequest('initialize', { + rootPath: './fixture', + capabilities: {}, + initializationOptions: { + diagnosticModel: 2, // DiagnosticModel.Pull + typescript: { + tsdk: path.join( + path.dirname(fileURLToPath(import.meta.url)), + '../', + 'node_modules', + 'typescript', + 'lib' + ), + }, + }, + }); + await connection.sendNotification('initialized'); + + languageServer = { + process: childProcess, + connection: connection, + get initResult() { + return initRequest; + }, + helpers: { + async openRealDocument(filePath, languageId = 'astro') { + const fileName = path.resolve(dir, filePath); + const uri = URI.file(fileName).toString(); + const item = protocol.TextDocumentItem.create( + uri, + languageId, + 0, + fs.readFileSync(fileName, 'utf-8') + ); + await connection.sendNotification(protocol.DidOpenTextDocumentNotification.type, { + textDocument: item, + }); + return TextDocument.create(uri, languageId, 0, item.text); + }, + async openFakeDocument(content, languageId = 'astro') { + const hash = createHash('sha256').update(content).digest('base64url'); + const uri = URI.file(`does-not-exists-${hash}-.astro`).toString(); + const item = protocol.TextDocumentItem.create(uri, languageId, 0, content); + await connection.sendNotification(protocol.DidOpenTextDocumentNotification.type, { + textDocument: item, + }); + return TextDocument.create(uri, languageId, 0, item.text); + }, + async requestCompletion(document, position) { + return await connection.sendRequest( + protocol.CompletionRequest.method, + { + textDocument: { + uri: document.uri, + }, + position: position, + } + ); + }, + async requestDiagnostics(document) { + return await connection.sendRequest( + protocol.DocumentDiagnosticRequest.method, + { + textDocument: { + uri: document.uri, + }, + } + ); + }, + async requestHover(document, position) { + return await connection.sendRequest(protocol.HoverRequest.method, { + textDocument: { + uri: document.uri, + }, + position: position, + }); + }, + async requestFormatting(document, options) { + return await connection.sendRequest( + protocol.DocumentFormattingRequest.method, + { + textDocument: { + uri: document.uri, + }, + options: options, + } + ); + }, }, }; + + // Ensure that our first test does not suffer from a TypeScript overhead + await languageServer.helpers.requestCompletion( + TextDocument.create('file://doesnt-exists', 'astro', 0, ''), + protocol.Position.create(0, 0) + ); } diff --git a/packages/language-server/test/takedown.ts b/packages/language-server/test/takedown.ts index e0722d59..9f2aa39e 100644 --- a/packages/language-server/test/takedown.ts +++ b/packages/language-server/test/takedown.ts @@ -1,5 +1,5 @@ import { getLanguageServer } from './server.js'; export async function mochaGlobalTeardown() { const languageServer = await getLanguageServer(); - languageServer.handle.connection.dispose(); + languageServer.process.kill(); } diff --git a/packages/language-server/test/typescript-addons/completions.test.ts b/packages/language-server/test/typescript-addons/completions.test.ts index a18f3a06..c356f3d2 100644 --- a/packages/language-server/test/typescript-addons/completions.test.ts +++ b/packages/language-server/test/typescript-addons/completions.test.ts @@ -1,21 +1,21 @@ import { Position } from '@volar/language-server'; import { expect } from 'chai'; import { before, describe, it } from 'mocha'; -import { getLanguageServer, type LanguageServer } from '../server.js'; +import { LanguageServer, getLanguageServer } from '../server.js'; describe('TypeScript Addons - Completions', async () => { let languageServer: LanguageServer; before(async () => (languageServer = await getLanguageServer())); - it('Can provide neat snippets', async () => { - const document = await languageServer.openFakeDocument('---\nprerender\n---', 'astro'); - const completions = await languageServer.handle.sendCompletionRequest( - document.uri, + const document = await languageServer.helpers.openFakeDocument('---\nprerender\n---'); + const completions = await languageServer.helpers.requestCompletion( + document, Position.create(1, 10) ); - const prerenderCompletions = completions?.items.filter((item) => item.label === 'prerender'); + const prerenderCompletions = completions.items.filter((item) => item.label === 'prerender'); expect(prerenderCompletions).to.not.be.empty; + expect(prerenderCompletions[0].data.serviceId).to.equal('typescriptaddons'); }); }); diff --git a/packages/language-server/test/typescript/completions.test.ts b/packages/language-server/test/typescript/completions.test.ts index b9d2d897..de1b9c8e 100644 --- a/packages/language-server/test/typescript/completions.test.ts +++ b/packages/language-server/test/typescript/completions.test.ts @@ -1,7 +1,7 @@ import { Position } from '@volar/language-server'; import { expect } from 'chai'; import { before, describe, it } from 'mocha'; -import { getLanguageServer, type LanguageServer } from '../server.js'; +import { LanguageServer, getLanguageServer } from '../server.js'; describe('TypeScript - Completions', async () => { let languageServer: LanguageServer; @@ -9,33 +9,33 @@ describe('TypeScript - Completions', async () => { before(async () => (languageServer = await getLanguageServer())); it('Can get completions in the frontmatter', async () => { - const document = await languageServer.openFakeDocument('---\nc\n---', 'astro'); - const completions = await languageServer.handle.sendCompletionRequest( - document.uri, + const document = await languageServer.helpers.openFakeDocument('---\nc\n---'); + const completions = await languageServer.helpers.requestCompletion( + document, Position.create(1, 1) ); - expect(completions?.items).to.not.be.empty; + expect(completions.items).to.not.be.empty; }); it('Can get completions in the template', async () => { - const document = await languageServer.openFakeDocument('{c}', 'astro'); - const completions = await languageServer.handle.sendCompletionRequest( - document.uri, + const document = await languageServer.helpers.openFakeDocument('{c}'); + const completions = await languageServer.helpers.requestCompletion( + document, Position.create(0, 1) ); - expect(completions?.items).to.not.be.empty; + expect(completions.items).to.not.be.empty; }); it('sort completions starting with `astro:` higher than other imports', async () => { - const document = await languageServer.openFakeDocument(' item.labelDetails?.description === 'astro:assets' ); diff --git a/packages/language-server/test/typescript/diagnostics.test.ts b/packages/language-server/test/typescript/diagnostics.test.ts index 2dfd3582..7f406dea 100644 --- a/packages/language-server/test/typescript/diagnostics.test.ts +++ b/packages/language-server/test/typescript/diagnostics.test.ts @@ -1,12 +1,7 @@ -import { - DiagnosticSeverity, - FullDocumentDiagnosticReport, - Range, - type Diagnostic, -} from '@volar/language-server'; +import { DiagnosticSeverity, Range, type Diagnostic } from '@volar/language-server'; import { expect } from 'chai'; import { before, describe, it } from 'mocha'; -import { getLanguageServer, type LanguageServer } from '../server.js'; +import { LanguageServer, getLanguageServer } from '../server.js'; describe('TypeScript - Diagnostics', async () => { let languageServer: LanguageServer; @@ -14,10 +9,8 @@ describe('TypeScript - Diagnostics', async () => { before(async () => (languageServer = await getLanguageServer())); it('Can get diagnostics in the frontmatter', async () => { - const document = await languageServer.openFakeDocument('---\nNotAThing\n---', 'astro'); - const diagnostics = (await languageServer.handle.sendDocumentDiagnosticRequest( - document.uri - )) as FullDocumentDiagnosticReport; + const document = await languageServer.helpers.openFakeDocument('---\nNotAThing\n---', 'astro'); + const diagnostics = await languageServer.helpers.requestDiagnostics(document); // We should only have one error here. expect(diagnostics.items).length(1); @@ -36,10 +29,8 @@ describe('TypeScript - Diagnostics', async () => { }); it('Can get diagnostics in the template', async () => { - const document = await languageServer.openFakeDocument('---\n\n---\n{nope}', 'astro'); - const diagnostics = (await languageServer.handle.sendDocumentDiagnosticRequest( - document.uri - )) as FullDocumentDiagnosticReport; + const document = await languageServer.helpers.openFakeDocument('---\n\n---\n{nope}', 'astro'); + const diagnostics = await languageServer.helpers.requestDiagnostics(document); expect(diagnostics.items).length(1); const diagnostic: Diagnostic = { ...diagnostics.items[0], data: {} }; @@ -54,13 +45,11 @@ describe('TypeScript - Diagnostics', async () => { }); it('shows enhanced diagnostics', async () => { - const document = await languageServer.openFakeDocument( + const document = await languageServer.helpers.openFakeDocument( '---\nimport {getEntryBySlug} from "astro:content";getEntryBySlug\n---\n
', 'astro' ); - const diagnostics = (await languageServer.handle.sendDocumentDiagnosticRequest( - document.uri - )) as FullDocumentDiagnosticReport; + const diagnostics = await languageServer.helpers.requestDiagnostics(document); expect(diagnostics.items).length(2); diagnostics.items = diagnostics.items.map((diag) => ({ ...diag, data: {} })); diff --git a/packages/language-server/test/units/parseJS.test.ts b/packages/language-server/test/units/parseJS.test.ts index 7429902a..001dcbb1 100644 --- a/packages/language-server/test/units/parseJS.test.ts +++ b/packages/language-server/test/units/parseJS.test.ts @@ -50,15 +50,13 @@ describe('parseJS - Can find all the scripts in an Astro file', () => { astroAst ); - scriptTags[0].mappings.forEach((mapping) => { - expect(mapping.data).to.deep.equal({ - verification: true, - completion: true, - semantic: true, - navigation: true, - structure: true, - format: false, - }); + expect(scriptTags[0].capabilities).to.deep.equal({ + diagnostic: true, + foldingRange: true, + documentFormatting: false, + documentSymbol: true, + codeAction: true, + inlayHint: true, }); }); }); diff --git a/packages/ts-plugin/package.json b/packages/ts-plugin/package.json index 264b91f8..e431ab9b 100644 --- a/packages/ts-plugin/package.json +++ b/packages/ts-plugin/package.json @@ -27,8 +27,8 @@ "author": "withastro", "license": "MIT", "dependencies": { - "@volar/language-core": "2.0.0-alpha.10", - "@volar/typescript": "2.0.0-alpha.10", + "@volar/language-core": "~1.10.9", + "@volar/typescript": "~1.10.9", "@astrojs/compiler": "^2.2.2", "@jridgewell/sourcemap-codec": "^1.4.15", "semver": "^7.3.8", diff --git a/packages/ts-plugin/src/astro2tsx.ts b/packages/ts-plugin/src/astro2tsx.ts index 194a32e6..f9f28639 100644 --- a/packages/ts-plugin/src/astro2tsx.ts +++ b/packages/ts-plugin/src/astro2tsx.ts @@ -1,7 +1,7 @@ import { convertToTSX } from '@astrojs/compiler/sync'; import type { ConvertToTSXOptions, TSXResult } from '@astrojs/compiler/types'; import { decode } from '@jridgewell/sourcemap-codec'; -import type { VirtualFile } from '@volar/language-core'; +import { FileKind, FileRangeCapabilities, VirtualFile } from '@volar/language-core'; import path from 'node:path'; import { TextDocument } from 'vscode-languageserver-textdocument'; @@ -91,23 +91,16 @@ function getVirtualFileTSX( const lastMapping = mappings.length ? mappings[mappings.length - 1] : undefined; if ( lastMapping && - lastMapping.generatedOffsets[0] + lastMapping.lengths[0] === current.genOffset && - lastMapping.sourceOffsets[0] + lastMapping.lengths[0] === current.sourceOffset + lastMapping.generatedRange[1] === current.genOffset && + lastMapping.sourceRange[1] === current.sourceOffset ) { - lastMapping.lengths[0] += length; + lastMapping.generatedRange[1] = current.genOffset + length; + lastMapping.sourceRange[1] = current.sourceOffset + length; } else { mappings.push({ - sourceOffsets: [current.sourceOffset], - generatedOffsets: [current.genOffset], - lengths: [length], - data: { - verification: true, - completion: true, - semantic: true, - navigation: true, - structure: true, - format: false, - }, + sourceRange: [current.sourceOffset, current.sourceOffset + length], + generatedRange: [current.genOffset, current.genOffset + length], + data: FileRangeCapabilities.full, }); } } @@ -123,12 +116,27 @@ function getVirtualFileTSX( } } + const ast = ts.createSourceFile('/a.tsx', tsx.code, ts.ScriptTarget.ESNext); + if (ast.statements[0]) { + mappings.push({ + sourceRange: [0, input.length], + generatedRange: [ast.statements[0].getStart(ast), tsx.code.length], + data: {}, + }); + } + return { fileName: fileName + '.tsx', - languageId: 'typescriptreact', - typescript: { - scriptKind: ts.ScriptKind.TSX, + kind: FileKind.TypeScriptHostFile, + capabilities: { + codeAction: true, + documentFormatting: false, + diagnostic: true, + documentSymbol: true, + inlayHint: true, + foldingRange: true, }, + codegenStacks: [], snapshot: { getText: (start, end) => tsx.code.substring(start, end), getLength: () => tsx.code.length, diff --git a/packages/ts-plugin/src/index.ts b/packages/ts-plugin/src/index.ts index 9d221160..7dd27ab0 100644 --- a/packages/ts-plugin/src/index.ts +++ b/packages/ts-plugin/src/index.ts @@ -1,4 +1,4 @@ -import { createFileProvider } from '@volar/language-core'; +import { createVirtualFiles } from '@volar/language-core'; import { decorateLanguageService, decorateLanguageServiceHost, @@ -14,13 +14,9 @@ const init: ts.server.PluginModuleFactory = (modules) => { const { typescript: ts } = modules; const pluginModule: ts.server.PluginModule = { create(info) { - const virtualFiles = createFileProvider( - [getLanguageModule(ts)], - ts.sys.useCaseSensitiveFileNames, - () => {} - ); + const virtualFiles = createVirtualFiles([getLanguageModule(ts)]); - decorateLanguageService(virtualFiles, info.languageService); + decorateLanguageService(virtualFiles, info.languageService, true); decorateLanguageServiceHost(virtualFiles, info.languageServiceHost, ts, ['.astro']); if (semver.lt(ts.version, '5.3.0')) { diff --git a/packages/ts-plugin/src/language.ts b/packages/ts-plugin/src/language.ts index e860f0ca..daab83f1 100644 --- a/packages/ts-plugin/src/language.ts +++ b/packages/ts-plugin/src/language.ts @@ -1,13 +1,19 @@ -import type { LanguagePlugin, VirtualFile } from '@volar/language-core'; +import { + FileCapabilities, + FileKind, + FileRangeCapabilities, + type Language, + type VirtualFile, +} from '@volar/language-core'; import type ts from 'typescript/lib/tsserverlibrary.js'; import { astro2tsx } from './astro2tsx.js'; export function getLanguageModule( ts: typeof import('typescript/lib/tsserverlibrary.js') -): LanguagePlugin { +): Language { return { - createVirtualFile(fileName, languageId, snapshot) { - if (languageId === 'astro') { + createVirtualFile(fileName, snapshot) { + if (fileName.endsWith('.astro')) { return new AstroFile(fileName, snapshot, ts); } }, @@ -18,8 +24,10 @@ export function getLanguageModule( } export class AstroFile implements VirtualFile { + kind = FileKind.TextFile; + capabilities = FileCapabilities.full; + fileName: string; - languageId = 'astro'; mappings!: VirtualFile['mappings']; embeddedFiles!: VirtualFile['embeddedFiles']; codegenStacks = []; @@ -41,17 +49,9 @@ export class AstroFile implements VirtualFile { onSnapshotUpdated() { this.mappings = [ { - sourceOffsets: [0], - generatedOffsets: [0], - lengths: [this.snapshot.getLength()], - data: { - verification: true, - completion: true, - semantic: true, - navigation: true, - structure: true, - format: false, - }, + sourceRange: [0, this.snapshot.getLength()], + generatedRange: [0, this.snapshot.getLength()], + data: FileRangeCapabilities.full, }, ]; diff --git a/packages/vscode/package.json b/packages/vscode/package.json index a6d0d3a8..f117115e 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -224,8 +224,8 @@ "@types/mocha": "^10.0.1", "@types/node": "^18.17.8", "@types/vscode": "^1.82.0", - "@volar/language-server": "2.0.0-alpha.10", - "@volar/vscode": "2.0.0-alpha.10", + "@volar/language-server": "~1.10.9", + "@volar/vscode": "~1.10.9", "@vscode/test-electron": "^2.3.2", "@vscode/vsce": "latest", "esbuild": "^0.17.19", diff --git a/packages/vscode/src/client.ts b/packages/vscode/src/client.ts index 575a6229..0c5ad87a 100644 --- a/packages/vscode/src/client.ts +++ b/packages/vscode/src/client.ts @@ -1,4 +1,4 @@ -import { DiagnosticModel, type InitializationOptions } from '@volar/language-server'; +import { DiagnosticModel, InitializationOptions } from '@volar/language-server'; import * as protocol from '@volar/language-server/protocol'; import { activateAutoInsertion, @@ -6,9 +6,9 @@ import { activateReloadProjects, activateTsConfigStatusItem, activateTsVersionStatusItem, - createLabsInfo, getTsdk, - type LabsInfo, + supportLabsVersion, + type ExportsInfoForLabs, } from '@volar/vscode'; import * as path from 'node:path'; import * as vscode from 'vscode'; @@ -16,7 +16,7 @@ import * as lsp from 'vscode-languageclient/node'; let client: lsp.BaseLanguageClient; -export async function activate(context: vscode.ExtensionContext): Promise { +export async function activate(context: vscode.ExtensionContext): Promise { const runtimeConfig = vscode.workspace.getConfiguration('astro.language-server'); const { workspaceFolders } = vscode.workspace; @@ -70,22 +70,26 @@ export async function activate(context: vscode.ExtensionContext): Promise document.languageId === 'astro'); activateFindFileReferences('astro.findFileReferences', client); - activateReloadProjects('astro.reloadProjects', client); - activateTsConfigStatusItem('astro', 'astro.openTsConfig', client); + activateReloadProjects('astro.reloadProjects', [client]); + activateTsConfigStatusItem('astro.openTsConfig', client, () => false); activateTsVersionStatusItem( - 'astro', 'astro.selectTypescriptVersion', context, client, - (text) => text + (document) => document.languageId === 'astro', + (text) => text, + true ); - const volarLabs = createLabsInfo(protocol); - volarLabs.addLanguageClient(client); - - return volarLabs.extensionExports; + return { + volarLabs: { + version: supportLabsVersion, + languageClients: [client], + languageServerProtocol: protocol, + }, + }; } export function deactivate(): Thenable | undefined { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bdcea915..ecafaf2b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -88,44 +88,50 @@ importers: specifier: ^1.4.15 version: 1.4.15 '@volar/kit': - specifier: 2.0.0-alpha.10 - version: 2.0.0-alpha.10(typescript@5.2.2) + specifier: ~1.10.9 + version: 1.10.9(typescript@5.2.2) '@volar/language-core': - specifier: 2.0.0-alpha.10 - version: 2.0.0-alpha.10 + specifier: ~1.10.9 + version: 1.10.9 '@volar/language-server': - specifier: 2.0.0-alpha.10 - version: 2.0.0-alpha.10 + specifier: ~1.10.9 + version: 1.10.9 '@volar/language-service': - specifier: 2.0.0-alpha.10 - version: 2.0.0-alpha.10 + specifier: ~1.10.9 + version: 1.10.9 + '@volar/source-map': + specifier: ~1.10.9 + version: 1.10.9 '@volar/typescript': - specifier: 2.0.0-alpha.10 - version: 2.0.0-alpha.10 + specifier: ~1.10.9 + version: 1.10.9 fast-glob: specifier: ^3.2.12 version: 3.2.12 + muggle-string: + specifier: ^0.3.1 + version: 0.3.1 volar-service-css: - specifier: 0.0.24 - version: 0.0.24(@volar/language-service@2.0.0-alpha.10) + specifier: 0.0.16 + version: 0.0.16(@volar/language-service@1.10.9) volar-service-emmet: - specifier: 0.0.24 - version: 0.0.24(@volar/language-service@2.0.0-alpha.10) + specifier: 0.0.16 + version: 0.0.16(@volar/language-service@1.10.9) volar-service-html: - specifier: 0.0.24 - version: 0.0.24(@volar/language-service@2.0.0-alpha.10) + specifier: 0.0.16 + version: 0.0.16(@volar/language-service@1.10.9) volar-service-prettier: - specifier: 0.0.24 - version: 0.0.24(@volar/language-service@2.0.0-alpha.10)(prettier@3.0.0) + specifier: 0.0.16 + version: 0.0.16(@volar/language-service@1.10.9)(prettier@3.0.0) volar-service-typescript: - specifier: 0.0.24 - version: 0.0.24(@volar/language-service@2.0.0-alpha.10)(@volar/typescript@2.0.0-alpha.10) + specifier: 0.0.16 + version: 0.0.16(@volar/language-service@1.10.9)(@volar/typescript@1.10.9) volar-service-typescript-twoslash-queries: - specifier: 0.0.24 - version: 0.0.24(@volar/language-service@2.0.0-alpha.10) + specifier: 0.0.16 + version: 0.0.16(@volar/language-service@1.10.9) vscode-html-languageservice: - specifier: ^5.1.1 - version: 5.1.1 + specifier: ^5.1.0 + version: 5.1.0 vscode-uri: specifier: ^3.0.8 version: 3.0.8 @@ -145,9 +151,6 @@ importers: '@types/node': specifier: ^18.17.8 version: 18.17.8 - '@volar/test-utils': - specifier: 2.0.0-alpha.10 - version: 2.0.0-alpha.10 astro: specifier: ^3.3.0 version: 3.3.0(@types/node@18.17.8)(typescript@5.2.2) @@ -179,11 +182,11 @@ importers: specifier: ^1.4.15 version: 1.4.15 '@volar/language-core': - specifier: 2.0.0-alpha.10 - version: 2.0.0-alpha.10 + specifier: ~1.10.9 + version: 1.10.9 '@volar/typescript': - specifier: 2.0.0-alpha.10 - version: 2.0.0-alpha.10 + specifier: ~1.10.9 + version: 1.10.9 semver: specifier: ^7.3.8 version: 7.5.4 @@ -244,11 +247,11 @@ importers: specifier: ^1.82.0 version: 1.83.0 '@volar/language-server': - specifier: 2.0.0-alpha.10 - version: 2.0.0-alpha.10 + specifier: ~1.10.9 + version: 1.10.9 '@volar/vscode': - specifier: 2.0.0-alpha.10 - version: 2.0.0-alpha.10 + specifier: ~1.10.9 + version: 1.10.9(vscode-languageclient@9.0.1) '@vscode/test-electron': specifier: ^2.3.2 version: 2.3.2 @@ -2071,77 +2074,67 @@ packages: optional: true dev: true - /@volar/kit@2.0.0-alpha.10(typescript@5.2.2): - resolution: {integrity: sha512-PcHtwpQh4VOS8kz2TKJD7Pvh4dd4aau+JiPPO4rLqlGoKv18/2tQn+B0M/9NyjtyB72/IxK/MAwDSJ4lyy6jNA==} + /@volar/kit@1.10.9(typescript@5.2.2): + resolution: {integrity: sha512-AhaDq1wcKqTq5jJBJKXjWAiA89XMA4V3IVih0LRFkW1/exWh5PhwPklzzw1oBdjra6BbMzJl1FU6uyZT7giIOA==} peerDependencies: typescript: '*' dependencies: - '@volar/language-service': 2.0.0-alpha.10 - '@volar/typescript': 2.0.0-alpha.10 + '@volar/language-service': 1.10.9 typesafe-path: 0.2.2 typescript: 5.2.2 vscode-languageserver-textdocument: 1.0.11 vscode-uri: 3.0.8 dev: false - /@volar/language-core@2.0.0-alpha.10: - resolution: {integrity: sha512-glK1aEWB0gbLGd3ITweQfJoyOcVj2b/4lcsGX/lanruTA7WP/GqI0lV/VGgeOlTe0CI2WxpqxweKL5aYlOxf1Q==} + /@volar/language-core@1.10.9: + resolution: {integrity: sha512-QXHMX7CeXLqXwvC7nbr6iZ3zrqgKdJ9f6g1B211eZBnvaBki2ds0+Kz8cprUiulVuMQEPJNhDfuh8Vym1gxHRQ==} dependencies: - '@volar/source-map': 2.0.0-alpha.10 + '@volar/source-map': 1.10.9 - /@volar/language-server@2.0.0-alpha.10: - resolution: {integrity: sha512-4AmpIAoHec6UmYuKl9n/GAu54XLXWHivEWNs9mwYHjT5drCBBS5SIbmPx+mFmo2JdxmqLSo8UNeOgeTmcYpLFA==} + /@volar/language-server@1.10.9: + resolution: {integrity: sha512-bIJKM0xm0xexNvEwydk6RF7oshoYOVR9uNfBVG7z/Zu79SHXZgpBGaiQKoc8LaKW7bXHFNfhuNTMiYHzE5UkWA==} dependencies: - '@volar/language-core': 2.0.0-alpha.10 - '@volar/language-service': 2.0.0-alpha.10 - '@volar/snapshot-document': 2.0.0-alpha.10 - '@volar/typescript': 2.0.0-alpha.10 + '@volar/language-core': 1.10.9 + '@volar/language-service': 1.10.9 + '@volar/typescript': 1.10.9 '@vscode/l10n': 0.0.16 - path-browserify: 1.0.1 request-light: 0.7.0 + typesafe-path: 0.2.2 vscode-languageserver: 9.0.1 vscode-languageserver-protocol: 3.17.5 vscode-languageserver-textdocument: 1.0.11 vscode-uri: 3.0.8 - /@volar/language-service@2.0.0-alpha.10: - resolution: {integrity: sha512-rgjOayiNrG0gThPsUK011OWY8M0vna+NxSD4jKia5paLz6deMwAKrjbHb5pgg/7SQG55nBMDFV92vQqvVrnbXg==} + /@volar/language-service@1.10.9: + resolution: {integrity: sha512-8K5UUTA6Z8piLdpaJjdsrsnDRB0qJv8iGNwsyRqIQlwwnfTyQf4caUfA6RfjRsBb0Hekm22a7QG/uHGNcHv2/w==} dependencies: - '@volar/language-core': 2.0.0-alpha.10 + '@volar/language-core': 1.10.9 + '@volar/source-map': 1.10.9 vscode-languageserver-protocol: 3.17.5 vscode-languageserver-textdocument: 1.0.11 vscode-uri: 3.0.8 - /@volar/snapshot-document@2.0.0-alpha.10: - resolution: {integrity: sha512-Oa6Kfy6Fthyl9DYds0KOsy563CI5udtEjI/kGqkGZmaUziqEXKopPtssz7LBNrE1w5rHHqkJ2CJ1koCwezwMHA==} - dependencies: - vscode-languageserver-protocol: 3.17.5 - vscode-languageserver-textdocument: 1.0.11 - - /@volar/source-map@2.0.0-alpha.10: - resolution: {integrity: sha512-2CVG3U2zunkEH9mdRtYKXknfGNd1rww/AeGjNj9jbX44V/KPeR4CCPFTWoQy9AAQ9q3NQWLenD76wgQFQbzX0w==} + /@volar/source-map@1.10.9: + resolution: {integrity: sha512-ul8yGO9nCxy6UedVuo0VsfKMLZzr39N1rgbtnYTGP5C554EDcUix6K/HDurhVdPHEDIw1yhXltLZZQKi3NrTvA==} dependencies: - muggle-string: 0.4.0 - - /@volar/test-utils@2.0.0-alpha.10: - resolution: {integrity: sha512-cYsh2hzUmrfu1xBI4wuA4yargtYfqz/ZQ3j0Xj3A5E0TX3SjZnbJlMx7xSSro9bBuD48f2kbdW3gwe5JB3n7gQ==} - dependencies: - '@volar/language-server': 2.0.0-alpha.10 - vscode-languageserver-textdocument: 1.0.11 - vscode-uri: 3.0.8 - dev: true + muggle-string: 0.3.1 - /@volar/typescript@2.0.0-alpha.10: - resolution: {integrity: sha512-3T9NMtaDh76zKvbMGCuqgik8E0DNelFzvByUmceGqFSJREnQbBaRWIV5qg3+bFsHFH0CYFIXvIsy44gJxS34WA==} + /@volar/typescript@1.10.9: + resolution: {integrity: sha512-5jLB46mCQLJqLII/qDLgfyHSq1cesjwuJQIa2GNWd7LPLSpX5vzo3jfQLWc/gyo3up2fQFrlRJK2kgY5REtwuQ==} dependencies: - '@volar/language-core': 2.0.0-alpha.10 + '@volar/language-core': 1.10.9 path-browserify: 1.0.1 - /@volar/vscode@2.0.0-alpha.10: - resolution: {integrity: sha512-6/goek1q0JcaZWDVJLf6x1fKClhKpir+jAaxtxxaY3TqKBStUuWlW9PDMKwyAc7+esecoSFGc7Wx/b/i7+068Q==} + /@volar/vscode@1.10.9(vscode-languageclient@9.0.1): + resolution: {integrity: sha512-aggqSY9KJBdmPKT2NcFmRqqF1n45nNtwLQGq0Aqr4/Qu5WuWRP8TuSPYOnBW3w11KOaDBcc1bSxnQIdsTgIVqg==} + peerDependencies: + vscode-languageclient: ^9.0.1 + peerDependenciesMeta: + vscode-languageclient: + optional: true dependencies: - '@volar/language-server': 2.0.0-alpha.10 - path-browserify: 1.0.1 + '@volar/language-server': 1.10.9 + typesafe-path: 0.2.2 vscode-languageclient: 9.0.1 vscode-nls: 5.2.0 dev: true @@ -2839,7 +2832,6 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - requiresBuild: true /color-string@1.9.1: resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} @@ -5278,8 +5270,8 @@ packages: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: true - /muggle-string@0.4.0: - resolution: {integrity: sha512-ymN6exGtXrNnDb0ae4VP34y5bSKmBm6+TMGHmKoFDE5saXxtszv1EHs4Tt3glo61rCA/Zum4AwM19pCOGAjjRQ==} + /muggle-string@0.3.1: + resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==} /mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} @@ -6825,7 +6817,6 @@ packages: /typesafe-path@0.2.2: resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} - dev: false /typescript-auto-import-cache@0.3.0: resolution: {integrity: sha512-Rq6/q4O9iyqUdjvOoyas7x/Qf9nWUMeqpP3YeTaLA+uECgfy5wOhfOS+SW/+fZ/uI/ZcKaf+2/ZhFzXh8xfofQ==} @@ -7095,49 +7086,49 @@ packages: vite: 4.4.11(@types/node@18.17.8) dev: true - /volar-service-css@0.0.24(@volar/language-service@2.0.0-alpha.10): - resolution: {integrity: sha512-uoc2bIriKLIHkFAbp5/rPTrE4iLI+k39UR/BJ/5JnEr9EGj2UJ3WYSEHVLgP1uYXDF+CKUvK1Jt+hOeFZtLnXw==} + /volar-service-css@0.0.16(@volar/language-service@1.10.9): + resolution: {integrity: sha512-gK/XD35t/P3SQrUuS8LMlCnE2ItIk+kXI6gPvBYl1NZ7O+tLH8rUWXA32YgpwNoITxYrm/G1seaq08zs4aiPvg==} peerDependencies: - '@volar/language-service': next + '@volar/language-service': ~1.10.0 peerDependenciesMeta: '@volar/language-service': optional: true dependencies: - '@volar/language-service': 2.0.0-alpha.10 + '@volar/language-service': 1.10.9 vscode-css-languageservice: 6.2.10 vscode-uri: 3.0.8 dev: false - /volar-service-emmet@0.0.24(@volar/language-service@2.0.0-alpha.10): - resolution: {integrity: sha512-uEXzs1yTw/65e/9c+cxLYob3NmxZyVmNwysk5zBYogMcwNTfb9TmRqPChUpmYVK4s6xVi3EEDVW+b2jY+qTjnw==} + /volar-service-emmet@0.0.16(@volar/language-service@1.10.9): + resolution: {integrity: sha512-8sWWywzVJOD+PWDArOXDWbiRlM7+peydFhXJT71i4X1WPW32RyPxn6FypvciO+amqpfZP2rXfB9eibIJ+EofSQ==} peerDependencies: - '@volar/language-service': next + '@volar/language-service': ~1.10.0 peerDependenciesMeta: '@volar/language-service': optional: true dependencies: - '@volar/language-service': 2.0.0-alpha.10 + '@volar/language-service': 1.10.9 '@vscode/emmet-helper': 2.9.2 - volar-service-html: 0.0.24(@volar/language-service@2.0.0-alpha.10) + volar-service-html: 0.0.16(@volar/language-service@1.10.9) dev: false - /volar-service-html@0.0.24(@volar/language-service@2.0.0-alpha.10): - resolution: {integrity: sha512-keVVseUwUk4mGaQYsobNz8BOZvwVeCt7LjEeSo5dIHEi7cSnUKLeTA0Bu7voStydbCKD1M8SBPojcQsKw9bO3A==} + /volar-service-html@0.0.16(@volar/language-service@1.10.9): + resolution: {integrity: sha512-/oEXXgry++1CnTXQBUNf9B8MZfTlYZuJfZA7Zx9MN7WS4ZPxk3BFOdal/cXH6RNR2ruNEYr5QTW9rsqtoUscag==} peerDependencies: - '@volar/language-service': next + '@volar/language-service': ~1.10.0 peerDependenciesMeta: '@volar/language-service': optional: true dependencies: - '@volar/language-service': 2.0.0-alpha.10 - vscode-html-languageservice: 5.1.1 + '@volar/language-service': 1.10.9 + vscode-html-languageservice: 5.1.0 vscode-uri: 3.0.8 dev: false - /volar-service-prettier@0.0.24(@volar/language-service@2.0.0-alpha.10)(prettier@3.0.0): - resolution: {integrity: sha512-HIlsKaPSJF4npsbszbrt5aMpVtuP8ENOvOUlxqT0gEeTC8F3vPEj88YNR/vQ6MhFbCLXhxsDoiitt05Lrp4KTg==} + /volar-service-prettier@0.0.16(@volar/language-service@1.10.9)(prettier@3.0.0): + resolution: {integrity: sha512-Kj2ZdwJGEvfYbsHW8Sjrew/7EB4PgRoas4f8yAJzUUVxIC/kvhUwLDxQc8+N2IibomN76asJGWe+i6VZZvgIkw==} peerDependencies: - '@volar/language-service': next + '@volar/language-service': ~1.10.0 prettier: ^2.2 || ^3.0 peerDependenciesMeta: '@volar/language-service': @@ -7145,32 +7136,32 @@ packages: prettier: optional: true dependencies: - '@volar/language-service': 2.0.0-alpha.10 + '@volar/language-service': 1.10.9 prettier: 3.0.0 dev: false - /volar-service-typescript-twoslash-queries@0.0.24(@volar/language-service@2.0.0-alpha.10): - resolution: {integrity: sha512-oIHnSGVCWnE+1N46Wq50zha+6Y48/sba4coINgge42L7Nv4IjSDuzb+uMCzo95L0VEaxGz0/vPJN2hCV84Lx6A==} + /volar-service-typescript-twoslash-queries@0.0.16(@volar/language-service@1.10.9): + resolution: {integrity: sha512-0gPrkDTD2bMj2AnSNykOKhfmPnBFE2LS1lF3LWA7qu1ChRnJF0sodwCCbbeNYJ9+yth956ApoU1BVQ8UrMg+yw==} peerDependencies: - '@volar/language-service': next + '@volar/language-service': ~1.10.0 peerDependenciesMeta: '@volar/language-service': optional: true dependencies: - '@volar/language-service': 2.0.0-alpha.10 + '@volar/language-service': 1.10.9 dev: false - /volar-service-typescript@0.0.24(@volar/language-service@2.0.0-alpha.10)(@volar/typescript@2.0.0-alpha.10): - resolution: {integrity: sha512-yShM18EdCTScGFUhHFpmNkzTmWxBCnbC+g5FhWXBmBTtLiADJ122Wn0zDa48KH0a+EjMueoTChCOs5vTGqClNg==} + /volar-service-typescript@0.0.16(@volar/language-service@1.10.9)(@volar/typescript@1.10.9): + resolution: {integrity: sha512-k/qFKM2oxs/3fhbr/vcBSHnCLZ1HN3Aeh+bGvV9Lc9qIhrNyCVsDFOUJN1Qp4dI72+Y+eFSIDCLHmFEZdsP2EA==} peerDependencies: - '@volar/language-service': next - '@volar/typescript': next + '@volar/language-service': ~1.10.0 + '@volar/typescript': ~1.10.0 peerDependenciesMeta: '@volar/language-service': optional: true dependencies: - '@volar/language-service': 2.0.0-alpha.10 - '@volar/typescript': 2.0.0-alpha.10 + '@volar/language-service': 1.10.9 + '@volar/typescript': 1.10.9 path-browserify: 1.0.1 semver: 7.5.4 typescript-auto-import-cache: 0.3.0 @@ -7188,12 +7179,12 @@ packages: vscode-uri: 3.0.8 dev: false - /vscode-html-languageservice@5.1.1: - resolution: {integrity: sha512-JenrspIIG/Q+93R6G3L6HdK96itSisMynE0glURqHpQbL3dKAKzdm8L40lAHNkwJeBg+BBPpAshZKv/38onrTQ==} + /vscode-html-languageservice@5.1.0: + resolution: {integrity: sha512-cGOu5+lrz+2dDXSGS15y24lDtPaML1T8K/SfqgFbLmCZ1btYOxceFieR+ybTS2es/A67kRc62m2cKFLUQPWG5g==} dependencies: '@vscode/l10n': 0.0.16 vscode-languageserver-textdocument: 1.0.11 - vscode-languageserver-types: 3.17.5 + vscode-languageserver-types: 3.17.3 vscode-uri: 3.0.8 dev: false @@ -7219,6 +7210,10 @@ packages: /vscode-languageserver-textdocument@1.0.11: resolution: {integrity: sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==} + /vscode-languageserver-types@3.17.3: + resolution: {integrity: sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==} + dev: false + /vscode-languageserver-types@3.17.5: resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==}