-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
LSP unit testing of completions #1519
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
*.metafile | ||
*.vsix | ||
open-vsx-token | ||
server.log |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
diff --git a/node_modules/@volar/test-utils/index.js b/node_modules/@volar/test-utils/index.js | ||
index 830c066..816c26b 100644 | ||
--- a/node_modules/@volar/test-utils/index.js | ||
+++ b/node_modules/@volar/test-utils/index.js | ||
@@ -17,6 +17,14 @@ function startLanguageServer(serverModule, cwd) { | ||
cwd, | ||
stdio: 'pipe', | ||
}); | ||
+ const logFilename = 'server.log'; | ||
+ const logFile = fs.createWriteStream | ||
+ ? fs.createWriteStream(logFilename, { flags: 'w' }) | ||
+ : null; | ||
+ if (logFile) { | ||
+ childProcess.stdout.pipe(logFile); | ||
+ childProcess.stderr.pipe(logFile); | ||
+ } | ||
const connection = _.createProtocolConnection(childProcess.stdout, childProcess.stdin); | ||
const documentVersions = new Map(); | ||
const openedDocuments = new Map(); | ||
@@ -153,8 +161,6 @@ function startLanguageServer(serverModule, cwd) { | ||
textDocument: { uri }, | ||
position, | ||
}); | ||
- // @volar/language-server only returns CompletionList | ||
- assert(!Array.isArray(result)); | ||
return result; | ||
}, | ||
sendCompletionResolveRequest(item) { |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -358,7 +358,7 @@ export function logTiming<R, A extends unknown[]>(name: string, fn: (...args: A) | |
const start = performance.now(), | ||
result = fn(...args), | ||
end = performance.now(); | ||
console.log(`${name.padStart(32)}${(end - start).toFixed(2).padStart(8)}ms`) | ||
// console.log(`${name.padStart(32)}${(end - start).toFixed(2).padStart(8)}ms`) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps we can add a parameter or a global There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think so. A reasonable approach might be to add an LSP command-line parameter something like Actually, if we can use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed, having a universal behavior would indeed be better for interception. The problem here is that the required This pull request will also address this problem. |
||
|
||
return result; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -98,7 +98,7 @@ const ensureServiceForSourcePath = async (sourcePath: string) => { | |
await projectPathToPendingPromiseMap.get(projPath) | ||
let service = projectPathToServiceMap.get(projPath) | ||
if (service) return service | ||
console.log("Spawning language server for project path: ", projPath) | ||
// console.log("Spawning language server for project path: ", projPath) | ||
service = TSService(projPath) | ||
const initP = service.loadPlugins() | ||
projectPathToPendingPromiseMap.set(projPath, initP) | ||
|
@@ -113,6 +113,7 @@ const diagnosticsDelay = 16; // ms delay for primary updated file | |
const diagnosticsPropagationDelay = 100; // ms delay for other files | ||
|
||
connection.onInitialize(async (params: InitializeParams) => { | ||
connection.console.log(`Initializing civet server at ${new Date().toLocaleTimeString()}`); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All logs on the |
||
const capabilities = params.capabilities; | ||
|
||
// Does the client support the `workspace/configuration` request? | ||
|
@@ -143,7 +144,6 @@ connection.onInitialize(async (params: InitializeParams) => { | |
definitionProvider: true, | ||
hoverProvider: true, | ||
referencesProvider: true, | ||
|
||
} | ||
}; | ||
|
||
|
@@ -155,15 +155,20 @@ connection.onInitialize(async (params: InitializeParams) => { | |
}; | ||
} | ||
|
||
// TODO: currently only using the first workspace folder | ||
const baseDir = params.workspaceFolders?.[0]?.uri.toString() | ||
if (!baseDir) | ||
throw new Error("Could not initialize without workspace folders") | ||
if (!params.rootUri) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this for compatibility with tools other than VSCode (e.g. tester or Vim) that aren't updated to the new interface? Otherwise, I don't see why we'd want to support a deprecated interface. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the future, there may be a demand for We can build some better online Also, regarding unit tests, it seems that |
||
// TODO: currently only using the first workspace folder | ||
const baseDir = params.workspaceFolders?.[0]?.uri.toString() | ||
if (!baseDir) | ||
throw new Error("Could not initialize without workspace folders") | ||
|
||
rootUri = baseDir + "/" | ||
rootDir = fileURLToPath(rootUri) | ||
rootUri = baseDir + "/" | ||
rootDir = fileURLToPath(rootUri) | ||
} else { | ||
rootUri = params.rootUri | ||
rootDir = fileURLToPath(rootUri) | ||
} | ||
|
||
console.log("Init", rootDir) | ||
// console.log("Init", rootDir) | ||
return result; | ||
}); | ||
|
||
|
@@ -241,6 +246,7 @@ connection.onHover(async ({ textDocument, position }) => { | |
|
||
// This handler provides the initial list of the completion items. | ||
connection.onCompletion(async ({ textDocument, position, context: _context }) => { | ||
connection.console.log('onCompletion'); | ||
const completionConfiguration = { | ||
useCodeSnippetsOnMethodSuggest: false, | ||
pathSuggestions: true, | ||
|
@@ -265,7 +271,7 @@ connection.onCompletion(async ({ textDocument, position, context: _context }) => | |
const service = await ensureServiceForSourcePath(sourcePath) | ||
if (!service) return | ||
|
||
console.log("completion", sourcePath, position) | ||
connection.console.log(`completion ${JSON.stringify({ sourcePath, position }, null, 2)}`); | ||
|
||
await updating(textDocument) | ||
if (sourcePath.match(tsSuffix)) { // non-transpiled | ||
|
@@ -288,7 +294,7 @@ connection.onCompletion(async ({ textDocument, position, context: _context }) => | |
// Don't map for files that don't have a sourcemap (plain .ts for example) | ||
if (sourcemapLines) { | ||
position = forwardMap(sourcemapLines, position) | ||
console.log('remapped') | ||
// console.log('remapped') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's fine to delete these rather than comment them out. Or log them to connection, possibly gated by a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, what you said is correct. I do want to add a In the past two days, I mainly want to make the unit tests run. In the next work, I will improve what you said and what I am going to do. |
||
} | ||
|
||
const p = transpiledDoc.offsetAt(position) | ||
|
@@ -484,11 +490,11 @@ connection.onDocumentSymbol(async ({ textDocument }) => { | |
|
||
// TODO | ||
documents.onDidClose(({ document }) => { | ||
console.log("close", document.uri) | ||
// console.log("close", document.uri) | ||
}); | ||
|
||
documents.onDidOpen(async ({ document }) => { | ||
console.log("open", document.uri) | ||
// console.log("open", document.uri) | ||
}) | ||
|
||
// Buffer up changes to documents so we don't stack transpilations and become unresponsive | ||
|
@@ -503,7 +509,7 @@ async function executeQueue() { | |
// Reset queue to allow accumulating jobs while this queue runs | ||
const changed = changeQueue | ||
changeQueue = new Set | ||
console.log("executeQueue", changed.size) | ||
// console.log("executeQueue", changed.size) | ||
// Run all jobs in queue (preventing livelock). | ||
for (const document of changed) { | ||
await updateDiagnosticsForDoc(document) | ||
|
@@ -532,14 +538,14 @@ async function scheduleExecuteQueue() { | |
// The content of a text document has changed. This event is emitted | ||
// when the text document first opened or when its content has changed. | ||
documents.onDidChangeContent(async ({ document }) => { | ||
console.log("onDidChangeContent", document.uri) | ||
// console.log("onDidChangeContent", document.uri) | ||
documentUpdateStatus.set(document.uri, withResolvers()) | ||
changeQueue.add(document) | ||
scheduleExecuteQueue() | ||
}); | ||
|
||
async function updateDiagnosticsForDoc(document: TextDocument) { | ||
console.log("Updating diagnostics for doc:", document.uri) | ||
// console.log("Updating diagnostics for doc:", document.uri) | ||
const sourcePath = documentToSourcePath(document) | ||
assert(sourcePath) | ||
|
||
|
@@ -565,7 +571,7 @@ async function updateDiagnosticsForDoc(document: TextDocument) { | |
// Transpiled file | ||
const meta = service.host.getMeta(sourcePath) | ||
if (!meta) { | ||
console.log("no meta for ", sourcePath) | ||
// console.log("no meta for ", sourcePath) | ||
return | ||
} | ||
const { sourcemapLines, transpiledDoc, parseErrors, fatal } = meta | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
assert from node:assert | ||
|
||
{ getLanguageServer, resolveByProject, prepareDocument } from ./utils.civet | ||
|
||
requestCompletions := (fileName: string, languageId: string, content: string) => | ||
offset := content.indexOf "|" | ||
assert.notEqual offset, -1 | ||
content = content[..offset - 1] + content[offset + 1..] | ||
|
||
server := await getLanguageServer() | ||
document .= await prepareDocument fileName, languageId, content | ||
|
||
position := document.positionAt(offset) | ||
return await server.sendCompletionRequest document.uri, position | ||
assert.notEqual return.value, undefined | ||
|
||
describe "Completions", => | ||
it "obejct field", async => | ||
completions := await requestCompletions | ||
resolveByProject("./index.civet") | ||
"civet" | ||
``` | ||
foo := name: 'foo', type: 'string', description: 'foo' | ||
foo.n| | ||
``` | ||
['completions', completions] |> console.log |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"private": true, | ||
"name": "empty" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"compilerOptions": { | ||
"esModuleInterop": true, | ||
"module": "NodeNext", | ||
"target": "es2020", | ||
"jsx": "preserve" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
{ resolve, dirname } from node:path | ||
{ createRequire } from node:module | ||
|
||
{ PublishDiagnosticsNotification } from @volar/language-server | ||
{ startLanguageServer, LanguageServerHandle } from @volar/test-utils | ||
{ TextDocument } from vscode-languageserver-textdocument | ||
{ URI } from vscode-uri | ||
|
||
require := createRequire import.meta.url | ||
|
||
__dirname := import.meta.url |> .slice 7 |> dirname | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather use |
||
export resolveByDir := (...paths: string[]) => resolve __dirname, ...paths | ||
projectRoot := resolveByDir "./fixtures/empty/" | ||
export resolveByProject = (...paths: string[]) => resolve projectRoot, ...paths | ||
|
||
serverHandle: LanguageServerHandle | undefined .= undefined | ||
|
||
openedDocuments: TextDocument[] := [] | ||
|
||
export getLanguageServer := (cwd = projectRoot) => | ||
if !serverHandle | ||
serverHandle = startLanguageServer require.resolve("../dist/server.js"), cwd | ||
serverHandle.connection.onNotification PublishDiagnosticsNotification.method, => | ||
await serverHandle.initialize | ||
URI.file(cwd).toString() | ||
{ | ||
typescript: | ||
tsdk: dirname require.resolve('typescript/lib/typescript.js') | ||
disableAutoImportCache: true | ||
} | ||
serverHandle! | ||
|
||
export prepareDocument := async (fileName: string, languageId: string, content: string, cwd?: string) => | ||
server := await getLanguageServer cwd | ||
uri := URI.file(fileName).toString() | ||
document := await server.openInMemoryDocument uri.toString(), languageId, content | ||
if openedDocuments.every .uri !== document.uri | ||
openedDocuments.push document | ||
document |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we need to make
vscode-jsonrpc
work properly, we cannot print redundant logs in the output.