diff --git a/packages/astro-check/src/index.ts b/packages/astro-check/src/index.ts index 26f26f7c..9ed76cd6 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.project.languageHost.getScriptFileNames().map((fileName) => path.extname(fileName)) + checker.linter.languageHost.getScriptFileNames().map((fileName) => path.extname(fileName)) ) ); createWatcher(workspaceRoot, checkedExtensions) .on('add', (fileName) => { - checker.project.fileCreated(fileName); + checker.linter.fileCreated(fileName); update(); }) .on('unlink', (fileName) => { - checker.project.fileDeleted(fileName); + checker.linter.fileDeleted(fileName); update(); }) .on('change', (fileName) => { - checker.project.fileUpdated(fileName); + checker.linter.fileUpdated(fileName); update(); }); } diff --git a/packages/language-server/package.json b/packages/language-server/package.json index 55025481..957e6fe5 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -28,20 +28,18 @@ "dependencies": { "@astrojs/compiler": "^2.2.2", "@jridgewell/sourcemap-codec": "^1.4.15", - "@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", + "@volar/kit": "2.0.0-alpha.6", + "@volar/language-core": "2.0.0-alpha.6", + "@volar/language-server": "2.0.0-alpha.6", + "@volar/language-service": "2.0.0-alpha.6", + "@volar/typescript": "2.0.0-alpha.6", "fast-glob": "^3.2.12", - "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", + "volar-service-css": "0.0.21", + "volar-service-emmet": "0.0.21", + "volar-service-html": "0.0.21", + "volar-service-prettier": "0.0.21", + "volar-service-typescript": "0.0.21", + "volar-service-typescript-twoslash-queries": "0.0.21", "vscode-html-languageservice": "^5.1.0", "vscode-uri": "^3.0.8" }, @@ -51,6 +49,7 @@ "@types/chai": "^4.3.5", "@types/mocha": "^10.0.1", "@types/node": "^18.17.8", + "@volar/test-utils": "2.0.0-alpha.6", "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 80c11a91..fc1abe7e 100644 --- a/packages/language-server/src/check.ts +++ b/packages/language-server/src/check.ts @@ -30,8 +30,7 @@ export interface CheckResult { export class AstroCheck { private ts!: typeof import('typescript/lib/tsserverlibrary.js'); - public project!: ReturnType; - private linter!: ReturnType; + public linter!: ReturnType<(typeof kit)['createTypeScriptChecker']>; constructor( private readonly workspacePath: string, @@ -61,7 +60,7 @@ export class AstroCheck { | undefined; }): Promise { const files = - fileNames !== undefined ? fileNames : this.project.languageHost.getScriptFileNames(); + fileNames !== undefined ? fileNames : this.linter.languageHost.getScriptFileNames(); const result: CheckResult = { status: undefined, @@ -98,7 +97,7 @@ export class AstroCheck { console.info(errorText); } - const fileSnapshot = this.project.languageHost.getScriptSnapshot(file); + const fileSnapshot = this.linter.languageHost.getScriptSnapshot(file); const fileContent = fileSnapshot?.getText(0, fileSnapshot.getLength()); result.fileResult.push({ @@ -131,29 +130,21 @@ export class AstroCheck { const tsconfigPath = this.getTsconfig(); const astroInstall = getAstroInstall([this.workspacePath]); - const config: kit.Config = { - languages: { - astro: getLanguageModule( - typeof astroInstall === 'string' ? undefined : astroInstall, - this.ts - ), - svelte: getSvelteLanguageModule(), - vue: getVueLanguageModule(), - }, - services: { - typescript: createTypeScriptService(), - astro: createAstroService(), - }, - }; + const languages = [ + getLanguageModule(typeof astroInstall === 'string' ? undefined : astroInstall, this.ts), + getSvelteLanguageModule(), + getVueLanguageModule(), + ]; + const services = [createTypeScriptService(this.ts), createAstroService(this.ts)]; if (tsconfigPath) { - this.project = kit.createProject(tsconfigPath, [ + this.linter = kit.createTypeScriptChecker(languages, services, tsconfigPath, [ { extension: 'astro', isMixedContent: true, scriptKind: 7 }, { extension: 'vue', isMixedContent: true, scriptKind: 7 }, { extension: 'svelte', isMixedContent: true, scriptKind: 7 }, ]); } else { - this.project = kit.createInferredProject(this.workspacePath, () => { + this.linter = kit.createTypeScriptInferredChecker(languages, services, () => { return fg.sync('**/*.astro', { cwd: this.workspacePath, ignore: ['node_modules'], @@ -161,8 +152,6 @@ 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 0618ccb1..ae5adb0e 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 { FileKind, FileRangeCapabilities, VirtualFile } from '@volar/language-core'; +import type { CodeInformation, VirtualFile } from '@volar/language-core'; import { HTMLDocument, TextDocument } from 'vscode-html-languageservice'; import { patchTSX } from './utils.js'; @@ -59,8 +59,8 @@ function getVirtualFileTSX( ): VirtualFile { tsx.code = patchTSX(tsx.code, fileName); const v3Mappings = decode(tsx.map.mappings); - const sourcedDoc = TextDocument.create(fileName, 'astro', 0, input); - const genDoc = TextDocument.create(fileName + '.tsx', 'typescriptreact', 0, tsx.code); + const sourcedDoc = TextDocument.create('file://' + fileName, 'astro', 0, input); + const genDoc = TextDocument.create('file://' + fileName + '.tsx', 'typescriptreact', 0, tsx.code); const mappings: VirtualFile['mappings'] = []; @@ -93,33 +93,37 @@ function getVirtualFileTSX( const lastMapping = mappings.length ? mappings[mappings.length - 1] : undefined; if ( lastMapping && - lastMapping.generatedRange[1] === current.genOffset && - lastMapping.sourceRange[1] === current.sourceOffset + lastMapping.generatedOffsets[0] + lastMapping.lengths[0] === current.genOffset && + lastMapping.sourceOffsets[0] + lastMapping.lengths[0] === current.sourceOffset ) { - lastMapping.generatedRange[1] = current.genOffset + length; - lastMapping.sourceRange[1] = current.sourceOffset + length; + lastMapping.lengths[0] += 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: FileRangeCapabilities = + const rangeCapabilities: CodeInformation = node.tag !== 'script' - ? FileRangeCapabilities.full + ? { + verification: true, + completion: true, + semantic: true, + navigation: true, + structure: true, + format: true, + } : { + verification: false, completion: false, - definition: false, - diagnostic: false, - displayWithLink: false, - hover: false, - references: false, - referencesCodeLens: false, - rename: false, - semanticTokens: false, + semantic: false, + navigation: false, + structure: false, + format: false, }; mappings.push({ - sourceRange: [current.sourceOffset, current.sourceOffset + length], - generatedRange: [current.genOffset, current.genOffset + length], + sourceOffsets: [current.sourceOffset], + generatedOffsets: [current.genOffset], + lengths: [length], data: rangeCapabilities, }); } @@ -136,27 +140,12 @@ 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', - kind: FileKind.TypeScriptHostFile, - capabilities: { - codeAction: true, - documentFormatting: false, - diagnostic: true, - documentSymbol: true, - inlayHint: true, - foldingRange: true, + languageId: 'typescriptreact', + typescript: { + scriptKind: ts.ScriptKind.TSX, }, - 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 1601803c..977cdd5d 100644 --- a/packages/language-server/src/core/index.ts +++ b/packages/language-server/src/core/index.ts @@ -1,11 +1,5 @@ import type { DiagnosticMessage, ParseResult } from '@astrojs/compiler/types'; -import { - FileCapabilities, - FileKind, - FileRangeCapabilities, - type Language, - type VirtualFile, -} from '@volar/language-core'; +import type { LanguagePlugin, 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'; @@ -19,70 +13,75 @@ import { extractScriptTags } from './parseJS.js'; export function getLanguageModule( astroInstall: AstroInstall | undefined, ts: typeof import('typescript/lib/tsserverlibrary.js') -): Language { +): LanguagePlugin { return { - createVirtualFile(fileName, snapshot) { - if (fileName.endsWith('.astro')) { + createVirtualFile(fileName, languageId, snapshot) { + if (languageId === 'astro') { return new AstroFile(fileName, snapshot, ts); } }, updateVirtualFile(astroFile, snapshot) { astroFile.update(snapshot); }, - 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, - }; - }, - }; + typescript: { + 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, + }; + }, + }; + }, }, }; } 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 }; @@ -112,9 +111,17 @@ export class AstroFile implements VirtualFile { onSnapshotUpdated() { this.mappings = [ { - sourceRange: [0, this.snapshot.getLength()], - generatedRange: [0, this.snapshot.getLength()], - data: FileRangeCapabilities.full, + sourceOffsets: [0], + generatedOffsets: [0], + lengths: [this.snapshot.getLength()], + data: { + verification: true, + completion: true, + semantic: true, + navigation: true, + structure: true, + format: true, + }, }, ]; this.compilerDiagnostics = []; diff --git a/packages/language-server/src/core/parseCSS.ts b/packages/language-server/src/core/parseCSS.ts index d68e2d27..ed3de5fa 100644 --- a/packages/language-server/src/core/parseCSS.ts +++ b/packages/language-server/src/core/parseCSS.ts @@ -1,8 +1,12 @@ import type { ParentNode, ParseResult } from '@astrojs/compiler/types'; import { is } from '@astrojs/compiler/utils'; -import { FileKind, FileRangeCapabilities, VirtualFile } from '@volar/language-core'; -import * as SourceMap from '@volar/source-map'; -import * as muggle from 'muggle-string'; +import { + buildMappings, + Segment, + toString, + type CodeInformation, + type VirtualFile, +} from '@volar/language-core'; import type ts from 'typescript/lib/tsserverlibrary'; import type { HTMLDocument, Node } from 'vscode-html-languageservice'; import type { AttributeNodeWithPosition } from './compilerUtils.js'; @@ -21,32 +25,38 @@ export function extractStylesheets( const inlineStyles = findInlineStyles(ast); if (inlineStyles.length > 0) { - const codes: muggle.Segment[] = []; + const codes: Segment[] = []; for (const inlineStyle of inlineStyles) { codes.push('x { '); codes.push([ inlineStyle.value, undefined, inlineStyle.position.start.offset + 'style="'.length, - FileRangeCapabilities.full, + // disable all but only keep document colors + { + verification: false, + completion: false, + semantic: false, + navigation: false, + structure: true, // keep document colors + format: false, + }, ]); codes.push(' }\n'); } - const mappings = SourceMap.buildMappings(codes); - const text = muggle.toString(codes); + const mappings = buildMappings(codes); + const text = toString(codes); embeddedCSSFiles.push({ fileName: fileName + '.inline.css', - codegenStacks: [], + languageId: 'css', snapshot: { getText: (start, end) => text.substring(start, end), getLength: () => text.length, getChangeRange: () => undefined, }, - capabilities: { documentSymbol: true }, embeddedFiles: [], - kind: FileKind.TextFile, mappings, }); } @@ -78,26 +88,27 @@ function findEmbeddedStyles( const styleText = snapshot.getText(node.startTagEnd, node.endTagStart); embeddedCSSFiles.push({ fileName: fileName + `.${cssIndex}.css`, - kind: FileKind.TextFile, + languageId: 'css', snapshot: { getText: (start, end) => styleText.substring(start, end), getLength: () => styleText.length, getChangeRange: () => undefined, }, - codegenStacks: [], mappings: [ { - sourceRange: [node.startTagEnd, node.endTagStart], - generatedRange: [0, styleText.length], - data: FileRangeCapabilities.full, + sourceOffsets: [node.startTagEnd], + generatedOffsets: [0], + lengths: [styleText.length], + data: { + verification: false, + completion: true, + semantic: true, + navigation: true, + structure: true, + format: false, + }, }, ], - 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 54c64b51..b44c713f 100644 --- a/packages/language-server/src/core/parseHTML.ts +++ b/packages/language-server/src/core/parseHTML.ts @@ -1,4 +1,4 @@ -import { FileKind, FileRangeCapabilities, VirtualFile } from '@volar/language-core'; +import type { 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,25 +87,27 @@ export function preprocessHTML(text: string, frontmatterEnd?: number) { function getHTMLVirtualFile(fileName: string, preprocessedHTML: string): VirtualFile { return { fileName: fileName + `.html`, - kind: FileKind.TextFile, + languageId: 'html', snapshot: { getText: (start, end) => preprocessedHTML.substring(start, end), getLength: () => preprocessedHTML.length, getChangeRange: () => undefined, }, - codegenStacks: [], mappings: [ { - sourceRange: [0, preprocessedHTML.length], - generatedRange: [0, preprocessedHTML.length], - data: FileRangeCapabilities.full, + sourceOffsets: [0], + generatedOffsets: [0], + lengths: [preprocessedHTML.length], + data: { + verification: true, + completion: true, + semantic: true, + navigation: true, + structure: true, + format: false, + }, }, ], - 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 6d03aad9..e249f39c 100644 --- a/packages/language-server/src/core/parseJS.ts +++ b/packages/language-server/src/core/parseJS.ts @@ -1,8 +1,12 @@ import type { ParentNode, ParseResult } from '@astrojs/compiler/types'; import { is } from '@astrojs/compiler/utils'; -import { FileKind, FileRangeCapabilities, VirtualFile } from '@volar/language-core'; -import * as SourceMap from '@volar/source-map'; -import * as muggle from 'muggle-string'; +import { + buildMappings, + toString, + type CodeInformation, + type Segment, + type VirtualFile, +} from '@volar/language-core'; import type ts from 'typescript/lib/tsserverlibrary'; import type { HTMLDocument, Node } from 'vscode-html-languageservice'; @@ -63,30 +67,37 @@ 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}`, - kind: FileKind.TypeScriptHostFile, + languageId: languageId, + typescript: { + scriptKind: scriptKind, + }, snapshot: { getText: (start, end) => scriptText.substring(start, end), getLength: () => scriptText.length, getChangeRange: () => undefined, }, - codegenStacks: [], mappings: [ { - sourceRange: [node.startTagEnd, node.endTagStart], - generatedRange: [0, scriptText.length], - data: FileRangeCapabilities.full, + sourceOffsets: [node.startTagEnd], + generatedOffsets: [0], + lengths: [scriptText.length], + data: { + verification: true, + completion: true, + semantic: true, + navigation: true, + structure: true, + format: false, + }, }, ], - capabilities: { - diagnostic: true, - codeAction: true, - inlayHint: true, - documentSymbol: true, - foldingRange: true, - documentFormatting: false, - }, embeddedFiles: [], }); scriptIndex++; @@ -194,38 +205,39 @@ 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: muggle.Segment[] = []; + const codes: Segment[] = []; for (const javascriptContext of javascriptContexts) { codes.push([ javascriptContext.content, undefined, javascriptContext.startOffset, - FileRangeCapabilities.full, + { + verification: true, + completion: true, + semantic: true, + navigation: true, + structure: true, + format: false, + }, ]); } - const mappings = SourceMap.buildMappings(codes); - const text = muggle.toString(codes); + const mappings = buildMappings(codes); + const text = toString(codes); return { fileName: fileName + '.inline.mjs', - codegenStacks: [], + languageId: 'javascript', + typescript: { + scriptKind: 1 satisfies ts.ScriptKind.JS, + }, 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 97b6a46d..feba0de6 100644 --- a/packages/language-server/src/core/svelte.ts +++ b/packages/language-server/src/core/svelte.ts @@ -1,18 +1,11 @@ -import { - FileCapabilities, - FileKind, - FileRangeCapabilities, - Language, - VirtualFile, -} from '@volar/language-core'; -import type { Mapping } from '@volar/source-map'; +import type { CodeInformation, LanguagePlugin, Mapping, VirtualFile } from '@volar/language-core'; import type ts from 'typescript/lib/tsserverlibrary'; import { framework2tsx } from './utils.js'; -export function getSvelteLanguageModule(): Language { +export function getSvelteLanguageModule(): LanguagePlugin { return { - createVirtualFile(fileName, snapshot) { - if (fileName.endsWith('.svelte')) { + createVirtualFile(fileName, languageId, snapshot) { + if (languageId === 'svelte') { return new SvelteFile(fileName, snapshot); } }, @@ -23,19 +16,17 @@ export function getSvelteLanguageModule(): Language { } class SvelteFile implements VirtualFile { - kind = FileKind.TextFile; - capabilities = FileCapabilities.full; - fileName: string; - mappings!: Mapping[]; + languageId = 'svelte'; + mappings!: Mapping[]; embeddedFiles!: VirtualFile[]; codegenStacks = []; constructor( - public sourceFileName: string, + public sourceFileId: string, public snapshot: ts.IScriptSnapshot ) { - this.fileName = sourceFileName; + this.fileName = sourceFileId; this.onSnapshotUpdated(); } @@ -47,9 +38,17 @@ class SvelteFile implements VirtualFile { private onSnapshotUpdated() { this.mappings = [ { - sourceRange: [0, this.snapshot.getLength()], - generatedRange: [0, this.snapshot.getLength()], - data: FileRangeCapabilities.full, + sourceOffsets: [0], + generatedOffsets: [0], + lengths: [this.snapshot.getLength()], + data: { + verification: true, + completion: true, + semantic: true, + navigation: true, + structure: true, + format: true, + }, }, ]; diff --git a/packages/language-server/src/core/utils.ts b/packages/language-server/src/core/utils.ts index c589f25e..7ac65810 100644 --- a/packages/language-server/src/core/utils.ts +++ b/packages/language-server/src/core/utils.ts @@ -1,9 +1,4 @@ -import { - FileCapabilities, - FileKind, - FileRangeCapabilities, - type VirtualFile, -} from '@volar/language-core'; +import type { VirtualFile } from '@volar/language-core'; import * as path from 'node:path'; import { URI, Utils } from 'vscode-uri'; import { importSvelteIntegration, importVueIntegration } from '../importPackage'; @@ -30,19 +25,28 @@ export function framework2tsx( function getVirtualFile(content: string): VirtualFile { return { fileName: fileName + '.tsx', - capabilities: FileCapabilities.full, - kind: FileKind.TypeScriptHostFile, + languageId: 'typescript', + typescript: { + scriptKind: 4 satisfies import('typescript/lib/tsserverlibrary').ScriptKind.TSX, + }, snapshot: { getText: (start, end) => content.substring(start, end), getLength: () => content.length, getChangeRange: () => undefined, }, - codegenStacks: [], mappings: [ { - sourceRange: [0, content.length], - generatedRange: [0, 0], - data: FileRangeCapabilities.full, + sourceOffsets: [0], + generatedOffsets: [0], + lengths: [content.length], + data: { + verification: true, + completion: true, + semantic: true, + navigation: true, + structure: true, + format: true, + }, }, ], embeddedFiles: [], diff --git a/packages/language-server/src/core/vue.ts b/packages/language-server/src/core/vue.ts index 748d03e4..c43fa90e 100644 --- a/packages/language-server/src/core/vue.ts +++ b/packages/language-server/src/core/vue.ts @@ -1,18 +1,11 @@ -import { - FileCapabilities, - FileKind, - FileRangeCapabilities, - Language, - VirtualFile, -} from '@volar/language-core'; -import type { Mapping } from '@volar/source-map'; +import type { CodeInformation, LanguagePlugin, Mapping, VirtualFile } from '@volar/language-core'; import type ts from 'typescript/lib/tsserverlibrary'; import { framework2tsx } from './utils.js'; -export function getVueLanguageModule(): Language { +export function getVueLanguageModule(): LanguagePlugin { return { - createVirtualFile(fileName, snapshot) { - if (fileName.endsWith('.vue')) { + createVirtualFile(fileName, languageId, snapshot) { + if (languageId === 'vue') { return new VueFile(fileName, snapshot); } }, @@ -23,11 +16,9 @@ export function getVueLanguageModule(): Language { } class VueFile implements VirtualFile { - kind = FileKind.TextFile; - capabilities = FileCapabilities.full; - fileName: string; - mappings!: Mapping[]; + languageId = 'vue'; + mappings!: Mapping[]; embeddedFiles!: VirtualFile[]; codegenStacks = []; @@ -47,9 +38,17 @@ class VueFile implements VirtualFile { private onSnapshotUpdated() { this.mappings = [ { - sourceRange: [0, this.snapshot.getLength()], - generatedRange: [0, this.snapshot.getLength()], - data: FileRangeCapabilities.full, + sourceOffsets: [0], + generatedOffsets: [0], + lengths: [this.snapshot.getLength()], + data: { + verification: true, + completion: true, + semantic: true, + navigation: true, + structure: true, + format: true, + }, }, ]; diff --git a/packages/language-server/src/languageServerPlugin.ts b/packages/language-server/src/languageServerPlugin.ts index a7af3d55..076f0138 100644 --- a/packages/language-server/src/languageServerPlugin.ts +++ b/packages/language-server/src/languageServerPlugin.ts @@ -1,7 +1,8 @@ import { - LanguageServerPlugin, + Connection, MessageType, ShowMessageNotification, + ServerPlugin, } from '@volar/language-server/node'; import { getLanguageModule } from './core'; import { getSvelteLanguageModule } from './core/svelte.js'; @@ -20,116 +21,121 @@ import { create as createHtmlService } from './plugins/html.js'; import { create as createTypescriptAddonsService } from './plugins/typescript-addons/index.js'; import { create as createTypeScriptService } from './plugins/typescript/index.js'; -export const plugin: LanguageServerPlugin = ( - initOptions, - modules -): ReturnType => ({ - extraFileExtensions: [ - { extension: 'astro', isMixedContent: true, scriptKind: 7 }, - { extension: 'vue', isMixedContent: true, scriptKind: 7 }, - { extension: 'svelte', isMixedContent: true, scriptKind: 7 }, - ], - watchFileExtensions: [ - 'js', - 'cjs', - 'mjs', - 'ts', - 'cts', - 'mts', - 'jsx', - 'tsx', - 'json', - 'astro', - 'vue', - 'svelte', - ], - resolveConfig(config, ctx) { - config.languages ??= {}; - if (ctx) { - const nearestPackageJson = modules.typescript?.findConfigFile( - ctx.project.rootUri.fsPath, - modules.typescript.sys.fileExists, - 'package.json' - ); +export function createPlugin(connection: Connection): ServerPlugin { + return ({ modules }): ReturnType => ({ + typescript: { + extraFileExtensions: [ + { extension: 'astro', isMixedContent: true, scriptKind: 7 }, + { extension: 'vue', isMixedContent: true, scriptKind: 7 }, + { extension: 'svelte', isMixedContent: true, scriptKind: 7 }, + ], + }, + watchFileExtensions: [ + 'js', + 'cjs', + 'mjs', + 'ts', + 'cts', + 'mts', + 'jsx', + 'tsx', + 'json', + 'astro', + 'vue', + 'svelte', + ], + resolveConfig(config, env, projectContext) { + config.languages ??= {}; + if (env && projectContext?.typescript) { + const rootPath = projectContext.typescript.configFileName + ? projectContext.typescript.configFileName.split('/').slice(0, -1).join('/') + : env.uriToFileName(env.workspaceFolder.uri.toString()); + const nearestPackageJson = modules.typescript?.findConfigFile( + rootPath, + modules.typescript.sys.fileExists, + 'package.json' + ); - const astroInstall = getAstroInstall([ctx.project.rootUri.fsPath], { - nearestPackageJson: nearestPackageJson, - readDirectory: modules.typescript!.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, + const astroInstall = getAstroInstall([rootPath], { + nearestPackageJson: nearestPackageJson, + readDirectory: modules.typescript!.sys.readDirectory, }); - } - config.languages.astro = getLanguageModule( - typeof astroInstall === 'string' ? undefined : astroInstall, - modules.typescript! - ); - config.languages.vue = getVueLanguageModule(); - config.languages.svelte = getSvelteLanguageModule(); - } + 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, + }); + } - config.services ??= {}; - config.services.html ??= createHtmlService(); - config.services.css ??= createCssService(); - config.services.emmet ??= createEmmetService(); - config.services.typescript ??= createTypeScriptService(); - config.services.typescripttwoslash ??= createTypeScriptTwoSlashService(); - config.services.typescriptaddons ??= createTypescriptAddonsService(); - config.services.astro ??= createAstroService(); + config.languages.astro = getLanguageModule( + typeof astroInstall === 'string' ? undefined : astroInstall, + modules.typescript! + ); + config.languages.vue = getVueLanguageModule(); + config.languages.svelte = getSvelteLanguageModule(); + } - if (ctx) { - const rootDir = ctx.env.uriToFileName(ctx.project.rootUri.toString()); - const prettier = importPrettier(rootDir); - const prettierPluginPath = getPrettierPluginPath(rootDir); + config.services ??= {}; + config.services.html ??= createHtmlService(); + config.services.css ??= createCssService(); + config.services.emmet ??= createEmmetService(); + config.services.typescript ??= createTypeScriptService(modules.typescript!); + config.services.typescripttwoslash ??= createTypeScriptTwoSlashService(); + config.services.typescriptaddons ??= createTypescriptAddonsService(); + config.services.astro ??= createAstroService(modules.typescript!); - if (prettier && prettierPluginPath) { - config.services.prettier ??= createPrettierService({ - prettier: prettier, - languages: ['astro'], - ignoreIdeOptions: true, - useIdeOptionsFallback: true, - resolveConfigOptions: { - // This seems to be broken since Prettier 3, and it'll always use its cumbersome cache. Hopefully it works one day. - useCache: false, - }, - additionalOptions: async (resolvedConfig) => { - async function getAstroPrettierPlugin() { - if (!prettier || !prettierPluginPath) { - return []; - } + if (env) { + const workspacePath = env.uriToFileName(env.workspaceFolder.uri.toString()); + const prettier = importPrettier(workspacePath); + const prettierPluginPath = getPrettierPluginPath(workspacePath); - 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 + 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 []; + } - 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 - const plugins = [ - ...(await getAstroPrettierPlugin()), - ...(resolvedConfig.plugins ?? []), - ]; + return hasPluginLoadedAlready ? [] : [prettierPluginPath]; + } - 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, - }); + 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, + }); + } } - } - return config; - }, -}); + return config; + }, + }); +} diff --git a/packages/language-server/src/nodeServer.ts b/packages/language-server/src/nodeServer.ts index d0ffbcb1..7fddfb70 100644 --- a/packages/language-server/src/nodeServer.ts +++ b/packages/language-server/src/nodeServer.ts @@ -1,4 +1,7 @@ -import { createConnection, startLanguageServer } from '@volar/language-server/node'; -import { plugin } from './languageServerPlugin.js'; +import { createConnection, startTypeScriptServer } from '@volar/language-server/node'; +import { createPlugin } from './languageServerPlugin.js'; -startLanguageServer(createConnection(), plugin); +const connection = createConnection(); +const plugin = createPlugin(connection); + +startTypeScriptServer(connection, plugin); diff --git a/packages/language-server/src/plugins/astro.ts b/packages/language-server/src/plugins/astro.ts index d0d6a1fd..af87820d 100644 --- a/packages/language-server/src/plugins/astro.ts +++ b/packages/language-server/src/plugins/astro.ts @@ -7,7 +7,8 @@ import { InsertTextFormat, Position, Range, - Service, + ServicePluginInstance, + ServicePlugin, TextEdit, } from '@volar/language-server'; import fg from 'fast-glob'; @@ -17,90 +18,97 @@ import type { TextDocument } from 'vscode-html-languageservice'; import { AstroFile } from '../core/index.js'; import { isJSDocument } from './utils.js'; -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); +export const create = (ts: typeof import('typescript/lib/tsserverlibrary.js')): 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); + } - 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', + isIncomplete: false, + items: items, }; - } - }, - 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) - ) - ); + }, + 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) + ) + ); + } } - } - 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 c795bc31..41d45649 100644 --- a/packages/language-server/src/plugins/html.ts +++ b/packages/language-server/src/plugins/html.ts @@ -1,59 +1,61 @@ -import { CompletionItemKind, Service } from '@volar/language-server'; -import createHtmlService from 'volar-service-html'; +import { CompletionItemKind, ServicePlugin, ServicePluginInstance } from '@volar/language-server'; +import { create as 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 = - (): 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 []; - }, - }; +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 []; + }, + }; + }, }; +}; diff --git a/packages/language-server/src/plugins/typescript-addons/index.ts b/packages/language-server/src/plugins/typescript-addons/index.ts index e0d35b25..d06a387f 100644 --- a/packages/language-server/src/plugins/typescript-addons/index.ts +++ b/packages/language-server/src/plugins/typescript-addons/index.ts @@ -1,46 +1,50 @@ -import type { CompletionList, Service } from '@volar/language-server'; +import type { CompletionList, ServicePluginInstance, ServicePlugin } from '@volar/language-server'; import { AstroFile } from '../../core/index.js'; import { isInsideFrontmatter, isJSDocument } from '../utils.js'; import { getSnippetCompletions } from './snippets.js'; -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; +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; - const [_, source] = context.documents.getVirtualFileByUri(document.uri); - const file = source?.root; - if (!(file instanceof AstroFile)) return undefined; + const [_, source] = context.language.files.getVirtualFile( + context.env.uriToFileName(document.uri) + ); + const file = source?.virtualFile?.[0]; + 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 089a5818..0601d04d 100644 --- a/packages/language-server/src/plugins/typescript/index.ts +++ b/packages/language-server/src/plugins/typescript/index.ts @@ -1,5 +1,5 @@ -import { Service, TextDocumentEdit } from '@volar/language-server'; -import createTypeScriptService from 'volar-service-typescript'; +import { ServicePluginInstance, ServicePlugin, TextDocumentEdit } from '@volar/language-server'; +import { create as createTypeScriptService } from 'volar-service-typescript'; import { AstroFile } from '../../core/index.js'; import { editShouldBeInFrontmatter, @@ -9,149 +9,161 @@ import { import { enhancedProvideCompletionItems, enhancedResolveCompletionItem } from './completions.js'; import { enhancedProvideSemanticDiagnostics } from './diagnostics.js'; -export const create = - (): Service => - (context, modules): ReturnType => { - const typeScriptPlugin = createTypeScriptService()(context, modules); - - if (!context) { +export const create = (ts: typeof import('typescript/lib/tsserverlibrary.js')): ServicePlugin => { + const tsServicePlugin = createTypeScriptService(ts); + return { + ...tsServicePlugin, + create(context): ServicePluginInstance { + const tsService = tsServicePlugin.create(context); return { - triggerCharacters: typeScriptPlugin.triggerCharacters, - signatureHelpTriggerCharacters: typeScriptPlugin.signatureHelpTriggerCharacters, - signatureHelpRetriggerCharacters: typeScriptPlugin.signatureHelpRetriggerCharacters, - }; - } - - 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; - - 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'); - } - - if (editShouldBeInFrontmatter(edit.range)) { - return ensureProperEditForFrontmatter(edit, file.astroMeta.frontmatter, newLine); - } - - return edit; - }); - } - - return item; - }, - transformCodeAction(item) { - if (item.kind !== 'quickfix') return undefined; - const originalFileName = item.data.uri.replace('.tsx', ''); + ...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'); + } - 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; + if (editShouldBeInFrontmatter(edit.range)) { + return ensureProperEditForFrontmatter(edit, file.astroMeta.frontmatter, newLine); + } - const document = context.getTextDocument(originalFileName); - if (!document) return undefined; + return edit; + }); + } - 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 item; + }, + transformCodeAction(item) { + if (item.kind !== 'quickfix') return undefined; + const originalUri = item.data.uri.replace('.tsx', ''); - return edit; - }); - } else { - if (file.astroMeta.frontmatter.status === 'closed') { + 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) { change.edits = change.edits.map((edit) => { const editInFrontmatter = editShouldBeInFrontmatter(edit.range, document); if (editInFrontmatter.itShould) { - edit.range = ensureRangeIsInFrontmatter( - edit.range, + return ensureProperEditForFrontmatter( + edit, file.astroMeta.frontmatter, + newLine, 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 = []; + 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 = []; + } } } } - } - return change; - }); - - 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; + return change; + }); - astroDocument = context.documents.getDocumentByFileName( - file.snapshot, - file.sourceFileName + 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) + ); + 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 typeScriptPlugin.provideSemanticDiagnostics!(document, token); - if (!diagnostics) return null; + const diagnostics = await tsService.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 c6111633..0111c82e 100644 --- a/packages/language-server/test/css/completions.test.ts +++ b/packages/language-server/test/css/completions.test.ts @@ -6,31 +6,28 @@ 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.helpers.openFakeDocument(``); - const completions = await languageServer.helpers.requestCompletion( - document, + const document = await languageServer.openFakeDocument(``, 'astro'); + const completions = await languageServer.handle.sendCompletionRequest( + document.uri, Position.create(0, 18) ); - expect(completions.items).to.not.be.empty; - expect(completions.items[0].data.serviceId).to.equal('css'); + expect(completions!.items).to.not.be.empty; }); it('Can provide completions for CSS values', async () => { - const document = await languageServer.helpers.openFakeDocument( - `` + const document = await languageServer.openFakeDocument( + ``, + 'astro' ); - const completions = await languageServer.helpers.requestCompletion( - document, + const completions = await languageServer.handle.sendCompletionRequest( + document.uri, Position.create(0, 21) ); - expect(completions.items).to.not.be.empty; - expect(completions.items[0].data.serviceId).to.equal('css'); + expect(completions!.items).to.not.be.empty; }); }); diff --git a/packages/language-server/test/html/completions.test.ts b/packages/language-server/test/html/completions.test.ts index cec46b5c..e6a4b45b 100644 --- a/packages/language-server/test/html/completions.test.ts +++ b/packages/language-server/test/html/completions.test.ts @@ -1,36 +1,32 @@ import { Position } from '@volar/language-server'; import { expect } from 'chai'; import { describe } from 'mocha'; -import { LanguageServer, getLanguageServer } from '../server.js'; +import { getLanguageServer, type LanguageServer } 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', async () => { - const document = await languageServer.helpers.openFakeDocument(` { + const document = await languageServer.openFakeDocument(` { - const document = await languageServer.helpers.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.helpers.openFakeDocument(` { - const document = await languageServer.helpers.openFakeDocument(`
{ let languageServer: LanguageServer; - before(async () => { - languageServer = await getLanguageServer(); - }); + before(async () => (languageServer = await getLanguageServer())); it('Can format document', async () => { - const document = await languageServer.helpers.openFakeDocument(`---\n\n\n---`); - const formatEdits = await languageServer.helpers.requestFormatting(document, { + const document = await languageServer.openFakeDocument(`---\n\n\n---`, 'astro'); + const formatEdits = await languageServer.handle.sendDocumentFormattingRequest(document.uri, { 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 5cc42bb0..64190104 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 { ServerCapabilities } from '@volar/language-server'; +import type { InitializeResult, ServerCapabilities } from '@volar/language-server'; import { expect } from 'chai'; import { before, describe, it } from 'mocha'; -import { LanguageServer, getLanguageServer } from '../server.js'; +import { getLanguageServer } from '../server.js'; describe('Initialize', async () => { - let languageServer: LanguageServer; + let initializeResult: InitializeResult; before(async function () { // First init can sometimes be slow in CI, even though the rest of the tests will be fast. this.timeout(50000); - languageServer = await getLanguageServer(); + initializeResult = (await getLanguageServer()).initializeResult; }); it('Can start server', async () => { - expect(languageServer.initResult).not.be.null; + expect(initializeResult).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(languageServer.initResult.capabilities).to.deep.equal(capabilities); + expect(initializeResult.capabilities).to.deep.equal(capabilities); }); }); diff --git a/packages/language-server/test/server.ts b/packages/language-server/test/server.ts index adfb2bf4..646acf45 100644 --- a/packages/language-server/test/server.ts +++ b/packages/language-server/test/server.ts @@ -1,167 +1,62 @@ /* eslint-disable no-console */ -import { createHash } from 'crypto'; -import cp from 'node:child_process'; -import fs from 'node:fs'; +import { LanguageServerHandle, startLanguageServer } from '@volar/test-utils'; +import { createHash } from 'node:crypto'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import * as protocol from 'vscode-languageserver-protocol/node'; -import { TextDocument } from 'vscode-languageserver-textdocument'; +import type { TextDocument } from 'vscode-languageserver-textdocument'; import { URI } from 'vscode-uri'; -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; - }; -} - -let languageServer: LanguageServer | undefined; - -export async function getLanguageServer() { - if (!languageServer) { - await initLanguageServer(); +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) + ); } - return languageServer!; -} - -async function initLanguageServer() { - if (languageServer) return; - 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(); - - connection.onClose((e) => console.log(e)); - connection.onDispose((e) => console.log(e)); - connection.onUnhandledNotification((e) => console.log(e)); - connection.onError((e) => console.log(e)); + if (!initializeResult || !serverHandle) { + throw new Error('Server not initialized'); + } - 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'); + 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); - 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, - } - ); - }, + return textDocument; }, }; - - // 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 9f2aa39e..e0722d59 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.process.kill(); + languageServer.handle.connection.dispose(); } diff --git a/packages/language-server/test/typescript-addons/completions.test.ts b/packages/language-server/test/typescript-addons/completions.test.ts index c356f3d2..a18f3a06 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 { LanguageServer, getLanguageServer } from '../server.js'; +import { getLanguageServer, type LanguageServer } 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.helpers.openFakeDocument('---\nprerender\n---'); - const completions = await languageServer.helpers.requestCompletion( - document, + const document = await languageServer.openFakeDocument('---\nprerender\n---', 'astro'); + const completions = await languageServer.handle.sendCompletionRequest( + document.uri, 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 de1b9c8e..b9d2d897 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 { LanguageServer, getLanguageServer } from '../server.js'; +import { getLanguageServer, type LanguageServer } 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.helpers.openFakeDocument('---\nc\n---'); - const completions = await languageServer.helpers.requestCompletion( - document, + const document = await languageServer.openFakeDocument('---\nc\n---', 'astro'); + const completions = await languageServer.handle.sendCompletionRequest( + document.uri, 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.helpers.openFakeDocument('{c}'); - const completions = await languageServer.helpers.requestCompletion( - document, + const document = await languageServer.openFakeDocument('{c}', 'astro'); + const completions = await languageServer.handle.sendCompletionRequest( + document.uri, 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.helpers.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 7f406dea..2dfd3582 100644 --- a/packages/language-server/test/typescript/diagnostics.test.ts +++ b/packages/language-server/test/typescript/diagnostics.test.ts @@ -1,7 +1,12 @@ -import { DiagnosticSeverity, Range, type Diagnostic } from '@volar/language-server'; +import { + DiagnosticSeverity, + FullDocumentDiagnosticReport, + Range, + type Diagnostic, +} from '@volar/language-server'; import { expect } from 'chai'; import { before, describe, it } from 'mocha'; -import { LanguageServer, getLanguageServer } from '../server.js'; +import { getLanguageServer, type LanguageServer } from '../server.js'; describe('TypeScript - Diagnostics', async () => { let languageServer: LanguageServer; @@ -9,8 +14,10 @@ describe('TypeScript - Diagnostics', async () => { before(async () => (languageServer = await getLanguageServer())); it('Can get diagnostics in the frontmatter', async () => { - const document = await languageServer.helpers.openFakeDocument('---\nNotAThing\n---', 'astro'); - const diagnostics = await languageServer.helpers.requestDiagnostics(document); + const document = await languageServer.openFakeDocument('---\nNotAThing\n---', 'astro'); + const diagnostics = (await languageServer.handle.sendDocumentDiagnosticRequest( + document.uri + )) as FullDocumentDiagnosticReport; // We should only have one error here. expect(diagnostics.items).length(1); @@ -29,8 +36,10 @@ describe('TypeScript - Diagnostics', async () => { }); it('Can get diagnostics in the template', async () => { - const document = await languageServer.helpers.openFakeDocument('---\n\n---\n{nope}', 'astro'); - const diagnostics = await languageServer.helpers.requestDiagnostics(document); + const document = await languageServer.openFakeDocument('---\n\n---\n{nope}', 'astro'); + const diagnostics = (await languageServer.handle.sendDocumentDiagnosticRequest( + document.uri + )) as FullDocumentDiagnosticReport; expect(diagnostics.items).length(1); const diagnostic: Diagnostic = { ...diagnostics.items[0], data: {} }; @@ -45,11 +54,13 @@ describe('TypeScript - Diagnostics', async () => { }); it('shows enhanced diagnostics', async () => { - const document = await languageServer.helpers.openFakeDocument( + const document = await languageServer.openFakeDocument( '---\nimport {getEntryBySlug} from "astro:content";getEntryBySlug\n---\n
', 'astro' ); - const diagnostics = await languageServer.helpers.requestDiagnostics(document); + const diagnostics = (await languageServer.handle.sendDocumentDiagnosticRequest( + document.uri + )) as FullDocumentDiagnosticReport; 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 001dcbb1..7429902a 100644 --- a/packages/language-server/test/units/parseJS.test.ts +++ b/packages/language-server/test/units/parseJS.test.ts @@ -50,13 +50,15 @@ describe('parseJS - Can find all the scripts in an Astro file', () => { astroAst ); - expect(scriptTags[0].capabilities).to.deep.equal({ - diagnostic: true, - foldingRange: true, - documentFormatting: false, - documentSymbol: true, - codeAction: true, - inlayHint: true, + scriptTags[0].mappings.forEach((mapping) => { + expect(mapping.data).to.deep.equal({ + verification: true, + completion: true, + semantic: true, + navigation: true, + structure: true, + format: false, + }); }); }); }); diff --git a/packages/ts-plugin/package.json b/packages/ts-plugin/package.json index e431ab9b..178cacc9 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": "~1.10.9", - "@volar/typescript": "~1.10.9", + "@volar/language-core": "2.0.0-alpha.6", + "@volar/typescript": "2.0.0-alpha.6", "@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 f9f28639..194a32e6 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 { FileKind, FileRangeCapabilities, VirtualFile } from '@volar/language-core'; +import type { VirtualFile } from '@volar/language-core'; import path from 'node:path'; import { TextDocument } from 'vscode-languageserver-textdocument'; @@ -91,16 +91,23 @@ function getVirtualFileTSX( const lastMapping = mappings.length ? mappings[mappings.length - 1] : undefined; if ( lastMapping && - lastMapping.generatedRange[1] === current.genOffset && - lastMapping.sourceRange[1] === current.sourceOffset + lastMapping.generatedOffsets[0] + lastMapping.lengths[0] === current.genOffset && + lastMapping.sourceOffsets[0] + lastMapping.lengths[0] === current.sourceOffset ) { - lastMapping.generatedRange[1] = current.genOffset + length; - lastMapping.sourceRange[1] = current.sourceOffset + length; + lastMapping.lengths[0] += length; } else { mappings.push({ - sourceRange: [current.sourceOffset, current.sourceOffset + length], - generatedRange: [current.genOffset, current.genOffset + length], - data: FileRangeCapabilities.full, + sourceOffsets: [current.sourceOffset], + generatedOffsets: [current.genOffset], + lengths: [length], + data: { + verification: true, + completion: true, + semantic: true, + navigation: true, + structure: true, + format: false, + }, }); } } @@ -116,27 +123,12 @@ 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', - kind: FileKind.TypeScriptHostFile, - capabilities: { - codeAction: true, - documentFormatting: false, - diagnostic: true, - documentSymbol: true, - inlayHint: true, - foldingRange: true, + languageId: 'typescriptreact', + typescript: { + scriptKind: ts.ScriptKind.TSX, }, - 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 7dd27ab0..9d221160 100644 --- a/packages/ts-plugin/src/index.ts +++ b/packages/ts-plugin/src/index.ts @@ -1,4 +1,4 @@ -import { createVirtualFiles } from '@volar/language-core'; +import { createFileProvider } from '@volar/language-core'; import { decorateLanguageService, decorateLanguageServiceHost, @@ -14,9 +14,13 @@ const init: ts.server.PluginModuleFactory = (modules) => { const { typescript: ts } = modules; const pluginModule: ts.server.PluginModule = { create(info) { - const virtualFiles = createVirtualFiles([getLanguageModule(ts)]); + const virtualFiles = createFileProvider( + [getLanguageModule(ts)], + ts.sys.useCaseSensitiveFileNames, + () => {} + ); - decorateLanguageService(virtualFiles, info.languageService, true); + decorateLanguageService(virtualFiles, info.languageService); 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 daab83f1..e860f0ca 100644 --- a/packages/ts-plugin/src/language.ts +++ b/packages/ts-plugin/src/language.ts @@ -1,19 +1,13 @@ -import { - FileCapabilities, - FileKind, - FileRangeCapabilities, - type Language, - type VirtualFile, -} from '@volar/language-core'; +import type { LanguagePlugin, 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') -): Language { +): LanguagePlugin { return { - createVirtualFile(fileName, snapshot) { - if (fileName.endsWith('.astro')) { + createVirtualFile(fileName, languageId, snapshot) { + if (languageId === 'astro') { return new AstroFile(fileName, snapshot, ts); } }, @@ -24,10 +18,8 @@ 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 = []; @@ -49,9 +41,17 @@ export class AstroFile implements VirtualFile { onSnapshotUpdated() { this.mappings = [ { - sourceRange: [0, this.snapshot.getLength()], - generatedRange: [0, this.snapshot.getLength()], - data: FileRangeCapabilities.full, + sourceOffsets: [0], + generatedOffsets: [0], + lengths: [this.snapshot.getLength()], + data: { + verification: true, + completion: true, + semantic: true, + navigation: true, + structure: true, + format: false, + }, }, ]; diff --git a/packages/vscode/package.json b/packages/vscode/package.json index 91dd0b81..1a8efced 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": "~1.10.9", - "@volar/vscode": "~1.10.9", + "@volar/language-server": "2.0.0-alpha.6", + "@volar/vscode": "2.0.0-alpha.6", "@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 0c5ad87a..f536b51f 100644 --- a/packages/vscode/src/client.ts +++ b/packages/vscode/src/client.ts @@ -70,23 +70,22 @@ export async function activate(context: vscode.ExtensionContext): Promise document.languageId === 'astro'); + activateAutoInsertion('astro', client); activateFindFileReferences('astro.findFileReferences', client); - activateReloadProjects('astro.reloadProjects', [client]); - activateTsConfigStatusItem('astro.openTsConfig', client, () => false); + activateReloadProjects('astro.reloadProjects', client); + activateTsConfigStatusItem('astro', 'astro.openTsConfig', client); activateTsVersionStatusItem( + 'astro', 'astro.selectTypescriptVersion', context, client, - (document) => document.languageId === 'astro', - (text) => text, - true + (text) => text ); return { volarLabs: { version: supportLabsVersion, - languageClients: [client], + languageClient: client, languageServerProtocol: protocol, }, }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 559231b3..bf22ac6f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -88,47 +88,41 @@ importers: specifier: ^1.4.15 version: 1.4.15 '@volar/kit': - specifier: ~1.10.9 - version: 1.10.9(typescript@5.2.2) + specifier: 2.0.0-alpha.6 + version: 2.0.0-alpha.6(typescript@5.2.2) '@volar/language-core': - specifier: ~1.10.9 - version: 1.10.9 + specifier: 2.0.0-alpha.6 + version: 2.0.0-alpha.6 '@volar/language-server': - specifier: ~1.10.9 - version: 1.10.9 + specifier: 2.0.0-alpha.6 + version: 2.0.0-alpha.6 '@volar/language-service': - specifier: ~1.10.9 - version: 1.10.9 - '@volar/source-map': - specifier: ~1.10.9 - version: 1.10.9 + specifier: 2.0.0-alpha.6 + version: 2.0.0-alpha.6 '@volar/typescript': - specifier: ~1.10.9 - version: 1.10.9 + specifier: 2.0.0-alpha.6 + version: 2.0.0-alpha.6 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.16 - version: 0.0.16(@volar/language-service@1.10.9) + specifier: 0.0.21 + version: 0.0.21(@volar/language-service@2.0.0-alpha.6) volar-service-emmet: - specifier: 0.0.16 - version: 0.0.16(@volar/language-service@1.10.9) + specifier: 0.0.21 + version: 0.0.21(@volar/language-service@2.0.0-alpha.6) volar-service-html: - specifier: 0.0.16 - version: 0.0.16(@volar/language-service@1.10.9) + specifier: 0.0.21 + version: 0.0.21(@volar/language-service@2.0.0-alpha.6) volar-service-prettier: - specifier: 0.0.16 - version: 0.0.16(@volar/language-service@1.10.9)(prettier@3.0.0) + specifier: 0.0.21 + version: 0.0.21(@volar/language-service@2.0.0-alpha.6)(prettier@3.0.0) volar-service-typescript: - specifier: 0.0.16 - version: 0.0.16(@volar/language-service@1.10.9)(@volar/typescript@1.10.9) + specifier: 0.0.21 + version: 0.0.21(@volar/language-service@2.0.0-alpha.6)(@volar/typescript@2.0.0-alpha.6) volar-service-typescript-twoslash-queries: - specifier: 0.0.16 - version: 0.0.16(@volar/language-service@1.10.9) + specifier: 0.0.21 + version: 0.0.21(@volar/language-service@2.0.0-alpha.6) vscode-html-languageservice: specifier: ^5.1.0 version: 5.1.0 @@ -151,6 +145,9 @@ importers: '@types/node': specifier: ^18.17.8 version: 18.17.8 + '@volar/test-utils': + specifier: 2.0.0-alpha.6 + version: 2.0.0-alpha.6 astro: specifier: ^3.3.0 version: 3.3.0(@types/node@18.17.8)(typescript@5.2.2) @@ -182,11 +179,11 @@ importers: specifier: ^1.4.15 version: 1.4.15 '@volar/language-core': - specifier: ~1.10.9 - version: 1.10.9 + specifier: 2.0.0-alpha.6 + version: 2.0.0-alpha.6 '@volar/typescript': - specifier: ~1.10.9 - version: 1.10.9 + specifier: 2.0.0-alpha.6 + version: 2.0.0-alpha.6 semver: specifier: ^7.3.8 version: 7.5.4 @@ -247,17 +244,17 @@ importers: specifier: ^1.82.0 version: 1.83.0 '@volar/language-server': - specifier: ~1.10.9 - version: 1.10.9 + specifier: 2.0.0-alpha.6 + version: 2.0.0-alpha.6 '@volar/vscode': - specifier: ~1.10.9 - version: 1.10.9(vscode-languageclient@9.0.1) + specifier: 2.0.0-alpha.6 + version: 2.0.0-alpha.6 '@vscode/test-electron': specifier: ^2.3.2 version: 2.3.2 '@vscode/vsce': specifier: latest - version: 2.22.0 + version: 2.21.1 esbuild: specifier: ^0.17.19 version: 0.17.19 @@ -2074,67 +2071,77 @@ packages: optional: true dev: true - /@volar/kit@1.10.9(typescript@5.2.2): - resolution: {integrity: sha512-AhaDq1wcKqTq5jJBJKXjWAiA89XMA4V3IVih0LRFkW1/exWh5PhwPklzzw1oBdjra6BbMzJl1FU6uyZT7giIOA==} + /@volar/kit@2.0.0-alpha.6(typescript@5.2.2): + resolution: {integrity: sha512-aW7k642T4Niw1Kr5i5A0qSSmdoIYmdp/sL58+lOFH2wrvyGDBZv7S9hOhyRJNEeygVioiEKsiAP0+8PTXt7teA==} peerDependencies: typescript: '*' dependencies: - '@volar/language-service': 1.10.9 + '@volar/language-service': 2.0.0-alpha.6 + '@volar/typescript': 2.0.0-alpha.6 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@1.10.9: - resolution: {integrity: sha512-QXHMX7CeXLqXwvC7nbr6iZ3zrqgKdJ9f6g1B211eZBnvaBki2ds0+Kz8cprUiulVuMQEPJNhDfuh8Vym1gxHRQ==} + /@volar/language-core@2.0.0-alpha.6: + resolution: {integrity: sha512-qUaDx05mZAE6kpSleXUw0qIBog6GOE5Umwx59fdYOEeiLrgND7vOt5FNVQ3xpqDwpSdQtzuVOjq7Tm+t72gIww==} dependencies: - '@volar/source-map': 1.10.9 + '@volar/source-map': 2.0.0-alpha.6 - /@volar/language-server@1.10.9: - resolution: {integrity: sha512-bIJKM0xm0xexNvEwydk6RF7oshoYOVR9uNfBVG7z/Zu79SHXZgpBGaiQKoc8LaKW7bXHFNfhuNTMiYHzE5UkWA==} + /@volar/language-server@2.0.0-alpha.6: + resolution: {integrity: sha512-1l4zirZdWCbUnKz1kAp8e89xCv8MOQAZr5cK0XRjkgt2WlfdFWLeXYa/D2+cz8lofyOWzFpzJuJt7fVLrK56CQ==} dependencies: - '@volar/language-core': 1.10.9 - '@volar/language-service': 1.10.9 - '@volar/typescript': 1.10.9 + '@volar/language-core': 2.0.0-alpha.6 + '@volar/language-service': 2.0.0-alpha.6 + '@volar/snapshot-document': 2.0.0-alpha.6 + '@volar/typescript': 2.0.0-alpha.6 '@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@1.10.9: - resolution: {integrity: sha512-8K5UUTA6Z8piLdpaJjdsrsnDRB0qJv8iGNwsyRqIQlwwnfTyQf4caUfA6RfjRsBb0Hekm22a7QG/uHGNcHv2/w==} + /@volar/language-service@2.0.0-alpha.6: + resolution: {integrity: sha512-KHtd+NuYs2ssxUhZkojLgvFmlji4XrG5DzYmSdLuT/pj/TWvchWwAG/HTLk6S4Fk4JZVoKOwsS9zm5xPZWJajQ==} dependencies: - '@volar/language-core': 1.10.9 - '@volar/source-map': 1.10.9 + '@volar/language-core': 2.0.0-alpha.6 vscode-languageserver-protocol: 3.17.5 vscode-languageserver-textdocument: 1.0.11 vscode-uri: 3.0.8 - /@volar/source-map@1.10.9: - resolution: {integrity: sha512-ul8yGO9nCxy6UedVuo0VsfKMLZzr39N1rgbtnYTGP5C554EDcUix6K/HDurhVdPHEDIw1yhXltLZZQKi3NrTvA==} + /@volar/snapshot-document@2.0.0-alpha.6: + resolution: {integrity: sha512-ogzmtMpIIl8tq5kJIg+0zfS+0JGc6ugTxjLPjJ3LTizNzXkjL0g5HlaewOLfhRL+07ZsZqcT4BiAD9fH4enIsA==} + dependencies: + vscode-languageserver-protocol: 3.17.5 + vscode-languageserver-textdocument: 1.0.11 + + /@volar/source-map@2.0.0-alpha.6: + resolution: {integrity: sha512-zPJbtVDiuWDLYDxtiSbwpcIH9tDnhSLpMZimZkmPAaQ8Lqb4VXTiGft4dPxMkaZfQDjwxUuw/SKV8K8EtNJ0WQ==} dependencies: - muggle-string: 0.3.1 + muggle-string: 0.4.0 - /@volar/typescript@1.10.9: - resolution: {integrity: sha512-5jLB46mCQLJqLII/qDLgfyHSq1cesjwuJQIa2GNWd7LPLSpX5vzo3jfQLWc/gyo3up2fQFrlRJK2kgY5REtwuQ==} + /@volar/test-utils@2.0.0-alpha.6: + resolution: {integrity: sha512-+DTGstTjDlyg02rkvjAUBzHrqAxB5nm2FfnfH9cM/8q6kq1cm9MJPFD/uB9eNIc/b5j6f6NFSRQMH8KaGbBwhQ==} dependencies: - '@volar/language-core': 1.10.9 + '@volar/language-server': 2.0.0-alpha.6 + vscode-languageserver-textdocument: 1.0.11 + vscode-uri: 3.0.8 + dev: true + + /@volar/typescript@2.0.0-alpha.6: + resolution: {integrity: sha512-NEv8VE7xKAhQH76lv6P3uh5Q8zsse47N6mhC17pI+noB+MsBLPHS62TGTypbpgXPjrIXnnXahKpKEzddVS9X9w==} + dependencies: + '@volar/language-core': 2.0.0-alpha.6 path-browserify: 1.0.1 - /@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 + /@volar/vscode@2.0.0-alpha.6: + resolution: {integrity: sha512-N/2neN5OtkWsBmtNHXa0IJ9oRVZJMAmJ5qxDQ1madfEoW00VuxOipiKjPrAuPKiT9G9mMlAio4xrAJnZRDfy8w==} dependencies: - '@volar/language-server': 1.10.9 - typesafe-path: 0.2.2 + '@volar/language-server': 2.0.0-alpha.6 + path-browserify: 1.0.1 vscode-languageclient: 9.0.1 vscode-nls: 5.2.0 dev: true @@ -2164,8 +2171,8 @@ packages: - supports-color dev: true - /@vscode/vsce@2.22.0: - resolution: {integrity: sha512-8df4uJiM3C6GZ2Sx/KilSKVxsetrTBBIUb3c0W4B1EWHcddioVs5mkyDKtMNP0khP/xBILVSzlXxhV+nm2rC9A==} + /@vscode/vsce@2.21.1: + resolution: {integrity: sha512-f45/aT+HTubfCU2oC7IaWnH9NjOWp668ML002QiFObFRVUCoLtcwepp9mmql/ArFUy+HCHp54Xrq4koTcOD6TA==} engines: {node: '>= 14'} hasBin: true dependencies: @@ -2832,6 +2839,7 @@ 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==} @@ -5270,8 +5278,8 @@ packages: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: true - /muggle-string@0.3.1: - resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==} + /muggle-string@0.4.0: + resolution: {integrity: sha512-ymN6exGtXrNnDb0ae4VP34y5bSKmBm6+TMGHmKoFDE5saXxtszv1EHs4Tt3glo61rCA/Zum4AwM19pCOGAjjRQ==} /mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} @@ -6817,6 +6825,7 @@ 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==} @@ -7086,49 +7095,49 @@ packages: vite: 4.4.11(@types/node@18.17.8) dev: true - /volar-service-css@0.0.16(@volar/language-service@1.10.9): - resolution: {integrity: sha512-gK/XD35t/P3SQrUuS8LMlCnE2ItIk+kXI6gPvBYl1NZ7O+tLH8rUWXA32YgpwNoITxYrm/G1seaq08zs4aiPvg==} + /volar-service-css@0.0.21(@volar/language-service@2.0.0-alpha.6): + resolution: {integrity: sha512-GSn879I7v+gPqJQntoi0Qdl72w1TYegoa2JFm1GI60YiSpPAoQ745JWFElL6AYX0FL9VqTfa9J6Y9fCUP6ZO6A==} peerDependencies: - '@volar/language-service': ~1.10.0 + '@volar/language-service': next peerDependenciesMeta: '@volar/language-service': optional: true dependencies: - '@volar/language-service': 1.10.9 + '@volar/language-service': 2.0.0-alpha.6 vscode-css-languageservice: 6.2.10 vscode-uri: 3.0.8 dev: false - /volar-service-emmet@0.0.16(@volar/language-service@1.10.9): - resolution: {integrity: sha512-8sWWywzVJOD+PWDArOXDWbiRlM7+peydFhXJT71i4X1WPW32RyPxn6FypvciO+amqpfZP2rXfB9eibIJ+EofSQ==} + /volar-service-emmet@0.0.21(@volar/language-service@2.0.0-alpha.6): + resolution: {integrity: sha512-JWmyZxAyAMU92RcGmwdsxixmO0UHD/n1N75Io4rxH0VxnUbrnbMh+20CCB5RTXJQhnwkINbIxFcHfe4caHWgDg==} peerDependencies: - '@volar/language-service': ~1.10.0 + '@volar/language-service': next peerDependenciesMeta: '@volar/language-service': optional: true dependencies: - '@volar/language-service': 1.10.9 + '@volar/language-service': 2.0.0-alpha.6 '@vscode/emmet-helper': 2.9.2 - volar-service-html: 0.0.16(@volar/language-service@1.10.9) + volar-service-html: 0.0.21(@volar/language-service@2.0.0-alpha.6) dev: false - /volar-service-html@0.0.16(@volar/language-service@1.10.9): - resolution: {integrity: sha512-/oEXXgry++1CnTXQBUNf9B8MZfTlYZuJfZA7Zx9MN7WS4ZPxk3BFOdal/cXH6RNR2ruNEYr5QTW9rsqtoUscag==} + /volar-service-html@0.0.21(@volar/language-service@2.0.0-alpha.6): + resolution: {integrity: sha512-bDZoOOwrF+IJw79qxMMeArrBR/FvxP4Lb15aAP9g3hpTcbvoUMatTuGauzMTsBbz+qt+Ae75zM+1xRmOEWfUqw==} peerDependencies: - '@volar/language-service': ~1.10.0 + '@volar/language-service': next peerDependenciesMeta: '@volar/language-service': optional: true dependencies: - '@volar/language-service': 1.10.9 + '@volar/language-service': 2.0.0-alpha.6 vscode-html-languageservice: 5.1.0 vscode-uri: 3.0.8 dev: false - /volar-service-prettier@0.0.16(@volar/language-service@1.10.9)(prettier@3.0.0): - resolution: {integrity: sha512-Kj2ZdwJGEvfYbsHW8Sjrew/7EB4PgRoas4f8yAJzUUVxIC/kvhUwLDxQc8+N2IibomN76asJGWe+i6VZZvgIkw==} + /volar-service-prettier@0.0.21(@volar/language-service@2.0.0-alpha.6)(prettier@3.0.0): + resolution: {integrity: sha512-rKB5HpNpxWA7yv5ZrUInaw5FRBxCaV32OkauP8WB9vZt4L+nrJ/qmicE0I3jgA5984xk3m25moJQ7ovCkx9VKw==} peerDependencies: - '@volar/language-service': ~1.10.0 + '@volar/language-service': next prettier: ^2.2 || ^3.0 peerDependenciesMeta: '@volar/language-service': @@ -7136,32 +7145,32 @@ packages: prettier: optional: true dependencies: - '@volar/language-service': 1.10.9 + '@volar/language-service': 2.0.0-alpha.6 prettier: 3.0.0 dev: false - /volar-service-typescript-twoslash-queries@0.0.16(@volar/language-service@1.10.9): - resolution: {integrity: sha512-0gPrkDTD2bMj2AnSNykOKhfmPnBFE2LS1lF3LWA7qu1ChRnJF0sodwCCbbeNYJ9+yth956ApoU1BVQ8UrMg+yw==} + /volar-service-typescript-twoslash-queries@0.0.21(@volar/language-service@2.0.0-alpha.6): + resolution: {integrity: sha512-ZrqkbUkDKSv8euHmqVfGgrchpdXMIX0V3+tj1xzd3d5PSMnZI+tqV+dIseAb7XF3eEjO+JInBGnsccwp6t5+Ow==} peerDependencies: - '@volar/language-service': ~1.10.0 + '@volar/language-service': next peerDependenciesMeta: '@volar/language-service': optional: true dependencies: - '@volar/language-service': 1.10.9 + '@volar/language-service': 2.0.0-alpha.6 dev: false - /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==} + /volar-service-typescript@0.0.21(@volar/language-service@2.0.0-alpha.6)(@volar/typescript@2.0.0-alpha.6): + resolution: {integrity: sha512-5IGcQxdXPmqCwwZxpy3nWw7hA3/5pRuKsWGJaWI7KsydZB8oqmJmdozX4rr/uLl5kdu5+ceQOmnUghl0IWM0WQ==} peerDependencies: - '@volar/language-service': ~1.10.0 - '@volar/typescript': ~1.10.0 + '@volar/language-service': next + '@volar/typescript': next peerDependenciesMeta: '@volar/language-service': optional: true dependencies: - '@volar/language-service': 1.10.9 - '@volar/typescript': 1.10.9 + '@volar/language-service': 2.0.0-alpha.6 + '@volar/typescript': 2.0.0-alpha.6 path-browserify: 1.0.1 semver: 7.5.4 typescript-auto-import-cache: 0.3.0