Skip to content

Fixes backticking of params #457

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

Merged
merged 8 commits into from
Apr 8, 2025
5 changes: 5 additions & 0 deletions .changeset/poor-toys-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@neo4j-cypher/language-support': patch
---

Fixes backticking of parameters and dbNames/aliases
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,29 @@ const uniq = <T>(arr: T[]) => Array.from(new Set(arr));
const versions = () =>
_internalFeatureFlags.cypher25 ? cypherVersionNumbers : ['5'];

export function backtickIfNeeded(e: string): string | undefined {
if (e == null || e == '') {
return undefined;
} else if (/[^\p{L}\p{N}_]/u.test(e) || /[^\p{L}_]/u.test(e[0])) {
return `\`${e}\``;
} else {
return undefined;
}
}
type BacktickVariant = 'label' | 'propertyKey' | 'relType' | 'dbName' | 'param';
Comment on lines -45 to +44
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the need for having these many types. The only one that is different is param, so having 'param' | 'nonParam' would be more concise

Copy link
Collaborator Author

@anderson4j anderson4j Apr 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dbName also has a special rule (periods are allowed without backticks if it's not the first or last symbol), and I figured for readability it's nice to see what type of elements we use which regex for

I did notice however that the old method was not checking for a period in the end of the dbname, so will adjust that


function backtickDbNameIfNeeded(e: string): string | undefined {
export function backtickIfNeeded(
e: string,
variant: BacktickVariant,
): string | undefined {
if (e == null || e == '') {
return undefined;
} else if (/[^\p{L}\p{N}_.]/u.test(e) || /[^\p{L}_]/u.test(e[0])) {
} else if (
(variant === 'label' ||
variant === 'propertyKey' ||
variant === 'relType') &&
(/[^\p{L}\p{N}_]/u.test(e) || /[^\p{L}_]/u.test(e[0]))
) {
return `\`${e}\``;
} else if (
variant === 'dbName' &&
(/[^\p{L}\p{N}_.]/u.test(e) ||
/[^\p{L}_]/u.test(e[0]) ||
/[^\p{L}\p{N}_]/u.test(e.at(-1)))
) {
return `\`${e}\``;
} else if (variant === 'param' && /[^\p{L}\p{N}_]/u.test(e)) {
return `\`${e}\``;
} else {
return undefined;
Expand All @@ -81,7 +90,7 @@ const cypherVersionCompletions = () =>

const labelCompletions = (dbSchema: DbSchema) =>
dbSchema.labels?.map((labelName) => {
const backtickedName = backtickIfNeeded(labelName);
const backtickedName = backtickIfNeeded(labelName, 'label');
const maybeInsertText = backtickedName
? { insertText: backtickedName }
: {};
Expand All @@ -96,7 +105,7 @@ const labelCompletions = (dbSchema: DbSchema) =>

const reltypeCompletions = (dbSchema: DbSchema) =>
dbSchema.relationshipTypes?.map((relType) => {
const backtickedName = backtickIfNeeded(relType);
const backtickedName = backtickIfNeeded(relType, 'relType');
const maybeInsertText = backtickedName
? { insertText: backtickedName }
: {};
Expand Down Expand Up @@ -342,7 +351,7 @@ const parameterCompletions = (
isExpectedParameterType(expectedType, paramType),
)
.map(([paramName]) => {
const backtickedName = backtickIfNeeded(paramName);
const backtickedName = backtickIfNeeded(paramName, 'param');
let maybeInsertText = backtickedName
? { insertText: `$${backtickedName}` }
: {};
Expand Down Expand Up @@ -370,7 +379,7 @@ const parameterCompletions = (

const propertyKeyCompletions = (dbInfo: DbSchema): CompletionItem[] =>
dbInfo.propertyKeys?.map((propertyKey) => {
const backtickedName = backtickIfNeeded(propertyKey);
const backtickedName = backtickIfNeeded(propertyKey, 'propertyKey');
const maybeInsertText = backtickedName
? { insertText: backtickedName }
: {};
Expand Down Expand Up @@ -841,7 +850,7 @@ function completeAliasName({
)
) {
return (dbSchema.aliasNames ?? []).map((aliasName) => {
const backtickedName = backtickDbNameIfNeeded(aliasName);
const backtickedName = backtickIfNeeded(aliasName, 'dbName');
const maybeInsertText = backtickedName
? { insertText: backtickedName }
: {};
Expand All @@ -857,7 +866,7 @@ function completeAliasName({
return (dbSchema.databaseNames ?? [])
.concat(dbSchema.aliasNames ?? [])
.map((databaseName) => {
const backtickedName = backtickDbNameIfNeeded(databaseName);
const backtickedName = backtickIfNeeded(databaseName, 'dbName');
const maybeInsertText = backtickedName
? { insertText: backtickedName }
: {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ describe('Can complete database names', () => {
'movies',
'financial reports',
'neo4j_server',
'periodEnd.',
'dashEnd-',
],
aliasNames: [
'myMovies',
'scoped.alias',
'a.b.c.d',
'panama papers1',
'office-db',
'periodAlias.',
'dashAlias-',
'db@',
'1db',
],
parameters: {
param1: 'something',
Expand Down Expand Up @@ -55,6 +61,26 @@ describe('Can complete database names', () => {
kind: CompletionItemKind.Variable,
insertText: '$`param 4`',
},
{
label: 'periodEnd.',
kind: CompletionItemKind.Value,
insertText: '`periodEnd.`',
},
{
label: 'dashEnd-',
kind: CompletionItemKind.Value,
insertText: '`dashEnd-`',
},
{
label: 'periodAlias.',
kind: CompletionItemKind.Value,
insertText: '`periodAlias.`',
},
{
label: 'dashAlias-',
kind: CompletionItemKind.Value,
insertText: '`dashAlias-`',
},
],
});
});
Expand All @@ -81,6 +107,26 @@ describe('Can complete database names', () => {
kind: CompletionItemKind.Variable,
insertText: '$`param 4`',
},
{
label: 'periodAlias.',
kind: CompletionItemKind.Value,
insertText: '`periodAlias.`',
},
{
label: 'dashAlias-',
kind: CompletionItemKind.Value,
insertText: '`dashAlias-`',
},
{
label: 'db@',
kind: CompletionItemKind.Value,
insertText: '`db@`',
},
{
label: '1db',
kind: CompletionItemKind.Value,
insertText: '`1db`',
},
],
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ describe('Completes parameters outside of databases, roles, user names', () => {
const dbSchema: DbSchema = {
parameters: {
stringParam: 'something',
'@param': 'wow',
'param.1': 5000,
'100param': 'great',
intParam: 1337,
mapParam: {
property: 'value',
Expand Down Expand Up @@ -41,6 +44,21 @@ describe('Completes parameters outside of databases, roles, user names', () => {
kind: CompletionItemKind.Variable,
insertText: '`some param`',
},
{
label: '$@param',
kind: CompletionItemKind.Variable,
insertText: '`@param`',
},
{
label: '$100param',
kind: CompletionItemKind.Variable,
insertText: '100param',
},
{
label: '$param.1',
kind: CompletionItemKind.Variable,
insertText: '`param.1`',
},
],
});
});
Expand Down Expand Up @@ -68,6 +86,20 @@ describe('Completes parameters outside of databases, roles, user names', () => {
kind: CompletionItemKind.Variable,
insertText: '$`some param`',
},
{
label: '$@param',
kind: CompletionItemKind.Variable,
insertText: '$`@param`',
},
{
label: '$100param',
kind: CompletionItemKind.Variable,
},
{
label: '$param.1',
kind: CompletionItemKind.Variable,
insertText: '$`param.1`',
},
],
});
});
Expand All @@ -86,6 +118,20 @@ describe('Completes parameters outside of databases, roles, user names', () => {
kind: CompletionItemKind.Variable,
insertText: '$`some param`',
},
{
label: '$@param',
kind: CompletionItemKind.Variable,
insertText: '$`@param`',
},
{
label: '$100param',
kind: CompletionItemKind.Variable,
},
{
label: '$param.1',
kind: CompletionItemKind.Variable,
insertText: '$`param.1`',
},
],
});
});
Expand Down Expand Up @@ -116,6 +162,21 @@ describe('Completes parameters outside of databases, roles, user names', () => {
kind: CompletionItemKind.Variable,
insertText: '`some param`',
},
{
label: '$@param',
kind: CompletionItemKind.Variable,
insertText: '`@param`',
},
{
label: '$100param',
kind: CompletionItemKind.Variable,
insertText: '100param',
},
{
label: '$param.1',
kind: CompletionItemKind.Variable,
insertText: '`param.1`',
},
],
});
});
Expand All @@ -134,6 +195,20 @@ describe('Completes parameters outside of databases, roles, user names', () => {
kind: CompletionItemKind.Variable,
insertText: '$`some param`',
},
{
label: '$@param',
kind: CompletionItemKind.Variable,
insertText: '$`@param`',
},
{
label: '$100param',
kind: CompletionItemKind.Variable,
},
{
label: '$param.1',
kind: CompletionItemKind.Variable,
insertText: '$`param.1`',
},
],
});
});
Expand Down