@neo4j-cypher/react-codemirror 2.0.0-alpha.0 → 2.0.0-canary-a1ed8f3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/LICENSE.md +201 -0
  3. package/README.md +27 -4
  4. package/dist/CypherEditor.d.ts +153 -0
  5. package/dist/CypherEditor.js +242 -0
  6. package/dist/CypherEditor.js.map +1 -0
  7. package/dist/e2e_tests/autoCompletion.spec.d.ts +1 -0
  8. package/dist/e2e_tests/autoCompletion.spec.js +133 -0
  9. package/dist/e2e_tests/autoCompletion.spec.js.map +1 -0
  10. package/dist/e2e_tests/configuration.spec.d.ts +1 -0
  11. package/dist/e2e_tests/configuration.spec.js +73 -0
  12. package/dist/e2e_tests/configuration.spec.js.map +1 -0
  13. package/dist/e2e_tests/e2eUtils.d.ts +12 -0
  14. package/dist/e2e_tests/e2eUtils.js +60 -0
  15. package/dist/e2e_tests/e2eUtils.js.map +1 -0
  16. package/dist/e2e_tests/extraKeybindings.spec.d.ts +1 -0
  17. package/dist/e2e_tests/extraKeybindings.spec.js +44 -0
  18. package/dist/e2e_tests/extraKeybindings.spec.js.map +1 -0
  19. package/dist/e2e_tests/historyNavigation.spec.d.ts +1 -0
  20. package/dist/e2e_tests/historyNavigation.spec.js +136 -0
  21. package/dist/e2e_tests/historyNavigation.spec.js.map +1 -0
  22. package/dist/e2e_tests/performanceTest.spec.d.ts +6 -0
  23. package/dist/e2e_tests/performanceTest.spec.js +96 -0
  24. package/dist/e2e_tests/performanceTest.spec.js.map +1 -0
  25. package/dist/e2e_tests/sanityChecks.spec.d.ts +1 -0
  26. package/dist/e2e_tests/sanityChecks.spec.js +56 -0
  27. package/dist/e2e_tests/sanityChecks.spec.js.map +1 -0
  28. package/dist/e2e_tests/signatureHelp.spec.d.ts +1 -0
  29. package/dist/e2e_tests/signatureHelp.spec.js +152 -0
  30. package/dist/e2e_tests/signatureHelp.spec.js.map +1 -0
  31. package/dist/e2e_tests/snippets.spec.d.ts +1 -0
  32. package/dist/e2e_tests/snippets.spec.js +63 -0
  33. package/dist/e2e_tests/snippets.spec.js.map +1 -0
  34. package/dist/e2e_tests/syntaxHighlighting.spec.d.ts +1 -0
  35. package/dist/e2e_tests/syntaxHighlighting.spec.js +91 -0
  36. package/dist/e2e_tests/syntaxHighlighting.spec.js.map +1 -0
  37. package/dist/e2e_tests/syntaxValidation.spec.d.ts +1 -0
  38. package/dist/e2e_tests/syntaxValidation.spec.js +79 -0
  39. package/dist/e2e_tests/syntaxValidation.spec.js.map +1 -0
  40. package/dist/historyNavigation.d.ts +7 -0
  41. package/dist/historyNavigation.js +163 -0
  42. package/dist/historyNavigation.js.map +1 -0
  43. package/dist/icons.d.ts +2 -0
  44. package/dist/icons.js +62 -0
  45. package/dist/icons.js.map +1 -0
  46. package/dist/index.d.ts +4 -0
  47. package/dist/index.js +5 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/lang-cypher/autocomplete.d.ts +3 -0
  50. package/dist/lang-cypher/autocomplete.js +62 -0
  51. package/dist/lang-cypher/autocomplete.js.map +1 -0
  52. package/dist/lang-cypher/constants.d.ts +40 -0
  53. package/dist/lang-cypher/constants.js +65 -0
  54. package/dist/lang-cypher/constants.js.map +1 -0
  55. package/dist/lang-cypher/contants.test.d.ts +1 -0
  56. package/dist/lang-cypher/contants.test.js +102 -0
  57. package/dist/lang-cypher/contants.test.js.map +1 -0
  58. package/dist/lang-cypher/createCypherTheme.d.ts +26 -0
  59. package/dist/lang-cypher/createCypherTheme.js +172 -0
  60. package/dist/lang-cypher/createCypherTheme.js.map +1 -0
  61. package/dist/lang-cypher/langCypher.d.ts +9 -0
  62. package/dist/lang-cypher/langCypher.js +24 -0
  63. package/dist/lang-cypher/langCypher.js.map +1 -0
  64. package/dist/lang-cypher/lintWorker.d.ts +8 -0
  65. package/dist/lang-cypher/lintWorker.js +4 -0
  66. package/dist/lang-cypher/lintWorker.js.map +1 -0
  67. package/dist/lang-cypher/parser-adapter.d.ts +19 -0
  68. package/dist/lang-cypher/parser-adapter.js +113 -0
  69. package/dist/lang-cypher/parser-adapter.js.map +1 -0
  70. package/dist/lang-cypher/signatureHelp.d.ts +4 -0
  71. package/dist/lang-cypher/signatureHelp.js +93 -0
  72. package/dist/lang-cypher/signatureHelp.js.map +1 -0
  73. package/dist/lang-cypher/syntaxValidation.d.ts +5 -0
  74. package/dist/lang-cypher/syntaxValidation.js +71 -0
  75. package/dist/lang-cypher/syntaxValidation.js.map +1 -0
  76. package/dist/lang-cypher/themeIcons.d.ts +7 -0
  77. package/dist/lang-cypher/themeIcons.js +22 -0
  78. package/dist/lang-cypher/themeIcons.js.map +1 -0
  79. package/dist/ndlTokensCopy.d.ts +379 -0
  80. package/dist/ndlTokensCopy.js +380 -0
  81. package/dist/ndlTokensCopy.js.map +1 -0
  82. package/dist/ndlTokensCopy.test.d.ts +1 -0
  83. package/dist/ndlTokensCopy.test.js +11 -0
  84. package/dist/ndlTokensCopy.test.js.map +1 -0
  85. package/dist/neo4jSetup.d.ts +2 -0
  86. package/dist/neo4jSetup.js +120 -0
  87. package/dist/neo4jSetup.js.map +1 -0
  88. package/dist/themes.d.ts +11 -0
  89. package/dist/themes.js +114 -0
  90. package/dist/themes.js.map +1 -0
  91. package/dist/tsconfig.tsbuildinfo +1 -0
  92. package/package.json +46 -16
  93. package/src/CypherEditor.tsx +461 -0
  94. package/src/e2e_tests/autoCompletion.spec.tsx +236 -0
  95. package/src/e2e_tests/configuration.spec.tsx +97 -0
  96. package/src/e2e_tests/e2eUtils.ts +85 -0
  97. package/src/e2e_tests/extraKeybindings.spec.tsx +57 -0
  98. package/src/e2e_tests/historyNavigation.spec.tsx +196 -0
  99. package/src/e2e_tests/performanceTest.spec.tsx +158 -0
  100. package/src/e2e_tests/sanityChecks.spec.tsx +78 -0
  101. package/src/e2e_tests/signatureHelp.spec.tsx +309 -0
  102. package/src/e2e_tests/snippets.spec.tsx +94 -0
  103. package/src/e2e_tests/syntaxHighlighting.spec.tsx +198 -0
  104. package/src/e2e_tests/syntaxValidation.spec.tsx +156 -0
  105. package/src/historyNavigation.ts +191 -0
  106. package/{esm/index.mjs → src/icons.ts} +37 -1283
  107. package/src/index.ts +4 -0
  108. package/src/lang-cypher/autocomplete.ts +81 -0
  109. package/src/lang-cypher/constants.ts +84 -0
  110. package/src/lang-cypher/contants.test.ts +104 -0
  111. package/src/lang-cypher/createCypherTheme.ts +240 -0
  112. package/src/lang-cypher/langCypher.ts +41 -0
  113. package/src/lang-cypher/lintWorker.ts +14 -0
  114. package/src/lang-cypher/parser-adapter.ts +145 -0
  115. package/src/lang-cypher/signatureHelp.ts +131 -0
  116. package/src/lang-cypher/syntaxValidation.ts +99 -0
  117. package/src/lang-cypher/themeIcons.ts +27 -0
  118. package/src/ndlTokensCopy.test.ts +11 -0
  119. package/src/ndlTokensCopy.ts +379 -0
  120. package/src/neo4jSetup.tsx +179 -0
  121. package/src/themes.ts +132 -0
  122. package/dist/index.cjs +0 -1330
@@ -0,0 +1,156 @@
1
+ import { expect, test } from '@playwright/experimental-ct-react';
2
+ import { CypherEditor } from '../CypherEditor';
3
+ import { CypherEditorPage } from './e2eUtils';
4
+
5
+ test.use({ viewport: { width: 1000, height: 500 } });
6
+ test('Prop lint set to false disables syntax validation', async ({
7
+ page,
8
+ mount,
9
+ }) => {
10
+ const query = 'METCH (n) RETURN n';
11
+
12
+ await mount(<CypherEditor value={query} lint={false} />);
13
+
14
+ await expect(page.locator('.cm-lintRange-error').last()).not.toBeVisible({
15
+ timeout: 2000,
16
+ });
17
+ });
18
+
19
+ test('Can turn linting back on', async ({ page, mount }) => {
20
+ const editorPage = new CypherEditorPage(page);
21
+ const query = 'METCH (n) RETURN n';
22
+
23
+ const component = await mount(<CypherEditor value={query} lint={false} />);
24
+
25
+ await expect(page.locator('.cm-lintRange-error').last()).not.toBeVisible({
26
+ timeout: 2000,
27
+ });
28
+
29
+ await component.update(<CypherEditor value={query} lint={true} />);
30
+
31
+ await editorPage.getEditor().fill('METCH (n) RETURN n');
32
+
33
+ await editorPage.checkErrorMessage(
34
+ 'METCH',
35
+ 'Unrecognized keyword. Did you mean MATCH?',
36
+ );
37
+ });
38
+
39
+ test('Syntactic errors are surfaced', async ({ page, mount }) => {
40
+ const editorPage = new CypherEditorPage(page);
41
+ const query = 'METCH (n) RETURN n';
42
+
43
+ await mount(<CypherEditor value={query} />);
44
+
45
+ await editorPage.checkErrorMessage(
46
+ 'METCH',
47
+ 'Unrecognized keyword. Did you mean MATCH?',
48
+ );
49
+ });
50
+
51
+ test('Errors for undefined labels are surfaced', async ({ page, mount }) => {
52
+ const editorPage = new CypherEditorPage(page);
53
+ const query = 'MATCH (n: Person) RETURN n';
54
+
55
+ await mount(
56
+ <CypherEditor
57
+ value={query}
58
+ schema={{ labels: ['Movie'], relationshipTypes: [] }}
59
+ />,
60
+ );
61
+
62
+ await editorPage.checkWarningMessage(
63
+ 'Person',
64
+ "Label Person is not present in the database. Make sure you didn't misspell it or that it is available when you run this statement in your application",
65
+ );
66
+ });
67
+
68
+ test('Errors for multiline undefined labels are highlighted correctly', async ({
69
+ page,
70
+ mount,
71
+ }) => {
72
+ const editorPage = new CypherEditorPage(page);
73
+ const query = `MATCH (n:\`Foo
74
+ Bar\`) RETURN n`;
75
+ const expectedMsg = `Label \`Foo
76
+ Bar\` is not present in the database. Make sure you didn't misspell it or that it is available when you run this statement in your application`;
77
+
78
+ await mount(
79
+ <CypherEditor
80
+ value={query}
81
+ schema={{ labels: ['Movie'], relationshipTypes: [] }}
82
+ />,
83
+ );
84
+
85
+ await editorPage.checkWarningMessage('`Foo', expectedMsg);
86
+ await editorPage.checkWarningMessage('Bar`', expectedMsg);
87
+ });
88
+
89
+ test('Semantic errors are surfaced when there are no syntactic errors', async ({
90
+ page,
91
+ mount,
92
+ }) => {
93
+ const editorPage = new CypherEditorPage(page);
94
+ const query = 'MATCH (n) RETURN m';
95
+
96
+ await mount(<CypherEditor value={query} />);
97
+
98
+ await editorPage.checkErrorMessage('m', 'Variable `m` not defined');
99
+ });
100
+
101
+ test('Semantic errors are correctly accumulated', async ({ page, mount }) => {
102
+ const editorPage = new CypherEditorPage(page);
103
+ const query = 'CALL { MATCH (n) } IN TRANSACTIONS OF -1 ROWS';
104
+
105
+ await mount(<CypherEditor value={query} />);
106
+
107
+ await editorPage.checkErrorMessage(
108
+ 'MATCH (n)',
109
+ 'Query cannot conclude with MATCH (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD).',
110
+ );
111
+
112
+ await editorPage.checkErrorMessage(
113
+ '-1',
114
+ "Invalid input. '-1' is not a valid value. Must be a positive integer",
115
+ );
116
+ });
117
+
118
+ test('Multiline errors are correctly placed', async ({ page, mount }) => {
119
+ const editorPage = new CypherEditorPage(page);
120
+ const query = `CALL {
121
+ MATCH (n)
122
+ } IN TRANSACTIONS
123
+ OF -1 ROWS`;
124
+
125
+ await mount(<CypherEditor value={query} />);
126
+
127
+ await editorPage.checkErrorMessage(
128
+ 'MATCH (n)',
129
+ 'Query cannot conclude with MATCH (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD)',
130
+ );
131
+
132
+ await editorPage.checkErrorMessage(
133
+ '-1',
134
+ "Invalid input. '-1' is not a valid value. Must be a positive integer",
135
+ );
136
+ });
137
+
138
+ test('Validation errors are correctly overlapped', async ({ page, mount }) => {
139
+ const editorPage = new CypherEditorPage(page);
140
+ const query = `CALL { MATCH (n)
141
+ RETURN n
142
+ } IN TRANSACTIONS
143
+ OF -1 ROWS`;
144
+
145
+ await mount(<CypherEditor value={query} />);
146
+
147
+ await editorPage.checkErrorMessage(
148
+ '-1',
149
+ 'Query cannot conclude with CALL (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD).',
150
+ );
151
+
152
+ await editorPage.checkErrorMessage(
153
+ '-1',
154
+ "Invalid input. '-1' is not a valid value. Must be a positive integer",
155
+ );
156
+ });
@@ -0,0 +1,191 @@
1
+ import { Extension, StateEffect } from '@codemirror/state';
2
+ import { EditorView, keymap } from '@codemirror/view';
3
+
4
+ import { StateField } from '@codemirror/state';
5
+
6
+ type HistoryState = {
7
+ history: string[];
8
+ index: number;
9
+ draft: string;
10
+ documentUpdate: string | null;
11
+ };
12
+ const DRAFT_ENTRY_INDEX = -1;
13
+ const historyInitialState = {
14
+ history: [],
15
+ index: DRAFT_ENTRY_INDEX,
16
+ documentUpdate: null,
17
+ draft: '',
18
+ };
19
+
20
+ const historyState = StateField.define<HistoryState>({
21
+ create() {
22
+ return historyInitialState;
23
+ },
24
+ toJSON(value) {
25
+ return JSON.stringify(value);
26
+ },
27
+ update(value, transaction) {
28
+ for (const effect of transaction.effects) {
29
+ if (effect.is(moveInHistory)) {
30
+ if (value.history.length === 0) {
31
+ return {
32
+ ...value,
33
+ documentUpdate: null,
34
+ };
35
+ }
36
+
37
+ const currentHistoryIndex = value.index;
38
+ if (effect.value === 'BACK') {
39
+ const newHistoryIndex = currentHistoryIndex + 1;
40
+
41
+ if (newHistoryIndex === value.history.length) {
42
+ return {
43
+ ...value,
44
+ documentUpdate: null,
45
+ };
46
+ }
47
+
48
+ let draft = value.draft;
49
+ if (currentHistoryIndex === DRAFT_ENTRY_INDEX) {
50
+ draft = transaction.state.doc.toString();
51
+ }
52
+
53
+ return {
54
+ ...value,
55
+ draft,
56
+ index: newHistoryIndex,
57
+ documentUpdate: value.history[newHistoryIndex],
58
+ };
59
+ } else if (effect.value === 'FORWARDS') {
60
+ const newHistoryIndex = currentHistoryIndex - 1;
61
+
62
+ if (currentHistoryIndex === DRAFT_ENTRY_INDEX) {
63
+ return { ...value, documentUpdate: null };
64
+ }
65
+
66
+ if (newHistoryIndex === DRAFT_ENTRY_INDEX) {
67
+ return {
68
+ ...value,
69
+ index: newHistoryIndex,
70
+ documentUpdate: value.draft,
71
+ };
72
+ } else {
73
+ return {
74
+ ...value,
75
+ index: newHistoryIndex,
76
+ documentUpdate: value.history[newHistoryIndex],
77
+ };
78
+ }
79
+ }
80
+ } else if (effect.is(replaceHistory)) {
81
+ return {
82
+ ...value,
83
+ index: DRAFT_ENTRY_INDEX,
84
+ history: effect.value,
85
+ };
86
+ }
87
+ }
88
+ return value;
89
+ },
90
+ });
91
+
92
+ type HistoryNavigation = 'BACK' | 'FORWARDS';
93
+ const moveInHistory = StateEffect.define<HistoryNavigation>();
94
+ export const replaceHistory = StateEffect.define<string[]>();
95
+
96
+ function navigateHistory(view: EditorView, direction: HistoryNavigation) {
97
+ view.dispatch(
98
+ view.state.update({
99
+ effects: moveInHistory.of(direction),
100
+ selection: { anchor: view.state.doc.length },
101
+ }),
102
+ );
103
+
104
+ const updatedHistory = view.state.field<HistoryState>(historyState, false);
105
+ if (updatedHistory.documentUpdate !== null) {
106
+ view.dispatch(
107
+ view.state.update({
108
+ changes: {
109
+ from: 0,
110
+ to: view.state.doc.length,
111
+ insert: updatedHistory.documentUpdate,
112
+ },
113
+ }),
114
+ );
115
+ }
116
+ }
117
+
118
+ type ReplProps = {
119
+ history?: string[];
120
+ };
121
+
122
+ export const replMode = ({ history }: ReplProps): Extension[] => {
123
+ return [
124
+ historyState.init(() => ({
125
+ ...historyInitialState,
126
+ history,
127
+ })),
128
+ keymap.of([
129
+ {
130
+ key: 'ArrowUp',
131
+ preventDefault: true,
132
+ run: (view) => {
133
+ // If text is selected or cursor is not at top of document, do nothing
134
+ const { empty, head } = view.state.selection.main;
135
+ if (!empty || head !== 0) {
136
+ return false;
137
+ }
138
+
139
+ navigateHistory(view, 'BACK');
140
+ return true;
141
+ },
142
+ },
143
+ {
144
+ mac: 'Cmd-ArrowUp',
145
+ win: 'Ctrl-ArrowUp',
146
+ linux: 'Ctrl-ArrowUp',
147
+ preventDefault: true,
148
+ run: (view) => {
149
+ // If text is selected or cursor is not at top of document, do nothing
150
+ const { empty, head } = view.state.selection.main;
151
+ if (!empty || head !== 0) {
152
+ return false;
153
+ }
154
+
155
+ navigateHistory(view, 'BACK');
156
+ return true;
157
+ },
158
+ },
159
+ {
160
+ key: 'ArrowDown',
161
+ preventDefault: true,
162
+ run: (view) => {
163
+ const { empty, head } = view.state.selection.main;
164
+ // If text is selected or cursor is not at end of document, do nothing
165
+ if (!empty || head !== view.state.doc.length) {
166
+ return false;
167
+ }
168
+ navigateHistory(view, 'FORWARDS');
169
+
170
+ return true;
171
+ },
172
+ },
173
+ {
174
+ mac: 'Cmd-ArrowDown',
175
+ win: 'Ctrl-ArrowDown',
176
+ linux: 'Ctrl-ArrowDown',
177
+ preventDefault: true,
178
+ run: (view) => {
179
+ const { empty, head } = view.state.selection.main;
180
+ // If text is selected or cursor is not at end of document, do nothing
181
+ if (!empty || head !== view.state.doc.length) {
182
+ return false;
183
+ }
184
+ navigateHistory(view, 'FORWARDS');
185
+
186
+ return true;
187
+ },
188
+ },
189
+ ]),
190
+ ];
191
+ };