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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/language-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
"bundle": "esbuild ./src/server.ts --bundle --format=cjs --platform=node --outfile=dist/cypher-language-server.js --minify --conditions=require",
"dev": "tsc --watch",
"make-executable": "cd dist && echo '#!/usr/bin/env node' > cypher-language-server && cat cypher-language-server.js >> cypher-language-server",
"clean": "rm -rf {dist,tsconfig.tsbuildinfo}"
"clean": "rm -rf {dist,tsconfig.tsbuildinfo}",
"test": "vitest run"
},
"devDependencies": {
"@types/lodash.debounce": "^4.0.9",
Expand Down
32 changes: 32 additions & 0 deletions packages/language-server/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**Checks if we should bail on the job, which would be if we have only typed a letter/number/underscore */
export function shouldBail(query: string, oldQuery: string) {
//Trying to determine if we typed in a single character or copy-pasted, and if single char, if this new char is a non number/letter

let shouldBail = false;
if (query.length === oldQuery.length + 1) {
let newCharCandidateIndex: number = undefined;
for (let i = 0; i < oldQuery.length; i++) {
//if we just consider typing, we only have 1 diff, the inserted symbol
//if diff, the symbol was inserted into newquery here, if we never do the new symbol is the last
//if we copypaste, we could do to equally long -> in that case, removing the first diff symbol would not yield
//the same query
if (query[i] != oldQuery[i]) {
newCharCandidateIndex = i;
break;
}
}
newCharCandidateIndex = newCharCandidateIndex ?? query.length - 1;
const oldifiedNewQuery =
query.slice(0, newCharCandidateIndex) +
query.slice(newCharCandidateIndex + 1, query.length);
if (oldifiedNewQuery === oldQuery) {
const newChar = query[newCharCandidateIndex];
const letterOrNumber = /^\w/;
const isLetterOrNumber = newChar.match(letterOrNumber);
if (isLetterOrNumber) {
shouldBail = true;
}
}
}
return shouldBail;
}
8 changes: 8 additions & 0 deletions packages/language-server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
import workerpool from 'workerpool';
import { join } from 'path';
import { LintWorker } from '@neo4j-cypher/lint-worker';
import { shouldBail } from './helpers';

class SymbolFetcher {
private processing = false;
Expand All @@ -45,8 +46,15 @@ class SymbolFetcher {
maxWorkers: 1,
workerTerminateTimeout: 0,
});
private lastQuery: string = '';

public queueSymbolJob(query: string, uri: string, schema: DbSchema) {
const bailEarly = shouldBail(query, this.lastQuery);
this.lastQuery = query;
if (bailEarly) {
return;
}

this.nextJob = { query, uri, schema };
if (!this.processing) {
void this.processJobQueue();
Expand Down
47 changes: 47 additions & 0 deletions packages/language-server/src/tests/misc.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { shouldBail } from '../helpers';

const bailingCases = [
{
name: 'Typing letters in end',
lastQuery: 'MATCH (m',
nextQuery: 'MATCH (mn',
shouldBail: true,
},
{
name: 'Typing symbol in end',
lastQuery: 'MATCH (mnm',
nextQuery: 'MATCH (mnm:',
shouldBail: false,
},
//Here we would prefer to update the symbol table, but this heuristic would skip it (staying at "mn:Node" even after we rename to "mnm")
{
name: 'Typing letters in middle',
lastQuery: 'MATCH (mn)-[:',
nextQuery: 'MATCH (mnm)-[:',
shouldBail: true,
},
{
name: 'Typing symbol in middle',
lastQuery: 'MATCH (mnm)-[:',
nextQuery: 'MATCH (mnm )-[:',
shouldBail: false,
},
{
name: 'Pasting in multiple symbols at once',
lastQuery: 'RETURN 50',
nextQuery: 'MATCH (n) RETURN n',
shouldBail: false,
},
];

describe('Misc tests for the language server', () => {
test('Bailing logic for symbol table calculation', () => {
bailingCases.forEach((c) => {
expect(
shouldBail(c.nextQuery, c.lastQuery),
"Failed on test '" + c.name + "'",
).toBe(c.shouldBail);
});
//expect(false).toBe(true);
});
});
3 changes: 2 additions & 1 deletion packages/language-server/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
"rootDir": "src",
"types": ["vitest/globals"]
},
"include": ["src"]
}
7 changes: 7 additions & 0 deletions packages/language-server/tsconfig.node.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"include": ["vitest.config.mts"],
"compilerOptions": {
"noEmit": true
}
}
7 changes: 7 additions & 0 deletions packages/language-server/vitest.config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
globals: true,
},
});
Loading