fix(editor): Fix code node displays lint messages in wrong location (#13664)

This commit is contained in:
Elias Meire 2025-03-04 14:59:30 +01:00 committed by GitHub
parent be441fb91f
commit d3ead68059
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 174 additions and 45 deletions

View file

@ -94,11 +94,11 @@
"xss": "catalog:"
},
"devDependencies": {
"@faker-js/faker": "^8.0.2",
"@iconify/json": "^2.2.228",
"@n8n/eslint-config": "workspace:*",
"@n8n/typescript-config": "workspace:*",
"@n8n/vitest-config": "workspace:*",
"@faker-js/faker": "^8.0.2",
"@iconify/json": "^2.2.228",
"@pinia/testing": "^0.1.6",
"@types/dateformat": "^3.0.0",
"@types/file-saver": "^2.0.1",
@ -111,6 +111,7 @@
"@vitejs/plugin-vue": "catalog:frontend",
"@vitest/coverage-v8": "catalog:frontend",
"browserslist-to-esbuild": "^2.1.1",
"fake-indexeddb": "^6.0.0",
"miragejs": "^0.1.48",
"unplugin-icons": "^0.19.0",
"unplugin-vue-components": "^0.27.2",

View file

@ -1,4 +1,5 @@
import '@testing-library/jest-dom';
import 'fake-indexeddb/auto';
import { configure } from '@testing-library/vue';
import 'core-js/proposals/set-methods-v2';
@ -64,20 +65,21 @@ Object.defineProperty(window, 'matchMedia', {
});
class Worker {
onmessage: (message: string) => void;
onmessage = vi.fn();
url: string;
constructor(url: string) {
this.url = url;
this.onmessage = () => {};
}
postMessage(message: string) {
postMessage = vi.fn((message: string) => {
this.onmessage(message);
}
});
addEventListener() {}
addEventListener = vi.fn();
terminate = vi.fn();
}
Object.defineProperty(window, 'Worker', {

View file

@ -14,7 +14,7 @@ import { Text, type Extension } from '@codemirror/state';
import { EditorView, hoverTooltip } from '@codemirror/view';
import * as Comlink from 'comlink';
import { NodeConnectionType, type CodeExecutionMode, type INodeExecutionData } from 'n8n-workflow';
import { ref, toRef, toValue, watch, type MaybeRefOrGetter } from 'vue';
import { onBeforeUnmount, ref, toRef, toValue, watch, type MaybeRefOrGetter } from 'vue';
import type { LanguageServiceWorker, RemoteLanguageServiceWorkerInit } from '../types';
import { typescriptCompletionSource } from './completions';
import { typescriptWorkerFacet } from './facet';
@ -33,11 +33,13 @@ export function useTypescript(
const { debounce } = useDebounce();
const activeNodeName = ndvStore.activeNodeName;
const worker = ref<Comlink.Remote<LanguageServiceWorker>>();
const webWorker = ref<Worker>();
async function createWorker(): Promise<Extension> {
const { init } = Comlink.wrap<RemoteLanguageServiceWorkerInit>(
new Worker(new URL('../worker/typescript.worker.ts', import.meta.url), { type: 'module' }),
);
webWorker.value = new Worker(new URL('../worker/typescript.worker.ts', import.meta.url), {
type: 'module',
});
const { init } = Comlink.wrap<RemoteLanguageServiceWorkerInit>(webWorker.value);
worker.value = await init(
{
id: toValue(id),
@ -125,6 +127,10 @@ export function useTypescript(
forceParse(editor);
});
onBeforeUnmount(() => {
if (webWorker.value) webWorker.value.terminate();
});
return {
createWorker,
};

View file

@ -0,0 +1,118 @@
import type { WorkerInitOptions } from '../types';
import { worker } from './typescript.worker';
import { type ChangeSet, EditorState } from '@codemirror/state';
async function createWorker({
doc,
options,
}: { doc?: string; options?: Partial<WorkerInitOptions> } = {}) {
const defaultDoc = `
function myFunction(){
if (true){
const myObj = {test: "value"}
}
}
return $input.all();`;
const state = EditorState.create({ doc: doc ?? defaultDoc });
const tsWorker = worker.init(
{
allNodeNames: [],
content: state.doc.toJSON(),
id: 'id',
inputNodeNames: [],
mode: 'runOnceForAllItems',
variables: [],
...options,
},
async () => ({
json: { path: '', type: 'string', value: '' },
binary: [],
params: { path: '', type: 'string', value: '' },
}),
);
return await tsWorker;
}
describe('Typescript Worker', () => {
it('should return diagnostics', async () => {
const tsWorker = await createWorker();
expect(tsWorker.getDiagnostics()).toEqual([
{
from: 10,
markClass: 'cm-faded',
message: "'myFunction' is declared but its value is never read.",
severity: 'warning',
to: 20,
},
{
from: 47,
markClass: 'cm-faded',
message: "'myObj' is declared but its value is never read.",
severity: 'warning',
to: 52,
},
]);
});
it('should accept updates from the client and buffer them', async () => {
const tsWorker = await createWorker();
// Add if statement and remove indentation
const changes = [
[75, [0, '', ''], 22],
[76, [0, '', ''], 22],
[77, [0, ' if (true){', ' const myObj = {test: "value"}', ' }'], 22],
[77, [1], 13, [2], 30, [2], 23],
];
vi.useFakeTimers({ toFake: ['setTimeout', 'queueMicrotask', 'nextTick'] });
for (const change of changes) {
tsWorker.updateFile(change as unknown as ChangeSet);
}
expect(tsWorker.getDiagnostics()).toHaveLength(2);
vi.advanceTimersByTime(1000);
vi.runAllTicks();
expect(tsWorker.getDiagnostics()).toHaveLength(3);
expect(tsWorker.getDiagnostics()).toEqual([
{
from: 10,
markClass: 'cm-faded',
message: "'myFunction' is declared but its value is never read.",
severity: 'warning',
to: 20,
},
{
from: 47,
markClass: 'cm-faded',
message: "'myObj' is declared but its value is never read.",
severity: 'warning',
to: 52,
},
{
from: 96,
markClass: 'cm-faded',
message: "'myObj' is declared but its value is never read.",
severity: 'warning',
to: 101,
},
]);
});
it('should return completions', async () => {
const doc = 'return $input.';
const tsWorker = await createWorker({ doc });
const completionResult = await tsWorker.getCompletionsAtPos(doc.length);
assert(completionResult !== null);
const completionLabels = completionResult.result.options.map((c) => c.label);
expect(completionLabels).toContain('all()');
expect(completionLabels).toContain('first()');
});
});

View file

@ -28,7 +28,7 @@ import { until } from '@vueuse/core';
self.process = { env: {} } as NodeJS.Process;
const worker: LanguageServiceWorkerInit = {
export const worker: LanguageServiceWorkerInit = {
async init(options, nodeDataFetcher) {
const loadedNodeTypesMap: Map<string, { type: string; typeName: string }> = reactive(new Map());
@ -157,11 +157,11 @@ const worker: LanguageServiceWorkerInit = {
});
const applyChangesToCode = bufferChangeSets((bufferedChanges) => {
bufferedChanges.iterChanges((start, end, _fromNew, _toNew, text) => {
bufferedChanges.iterChanges((start, end, fromNew, _toNew, text) => {
const length = end - start;
env.updateFile(codeFileName, text.toString(), {
start: editorPositionToTypescript(start),
start: editorPositionToTypescript(fromNew),
length,
});
});

View file

@ -493,7 +493,7 @@ importers:
version: 3.666.0(@aws-sdk/client-sts@3.666.0)
'@getzep/zep-cloud':
specifier: 1.0.12
version: 1.0.12(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)(langchain@0.3.11(c10b80e38f5a8711ccad1e2174de91e6))
version: 1.0.12(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)(langchain@0.3.11(101f2d8395211f7761efa8fce29a53de))
'@getzep/zep-js':
specifier: 0.9.0
version: 0.9.0
@ -520,7 +520,7 @@ importers:
version: 0.3.2(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0))(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)
'@langchain/community':
specifier: 0.3.24
version: 0.3.24(1ea346ff95b1be1e3f1f4333b25e2811)
version: 0.3.24(6c18f51b4fff56aeb9d6961328b4ede2)
'@langchain/core':
specifier: 'catalog:'
version: 0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))
@ -610,7 +610,7 @@ importers:
version: 23.0.1
langchain:
specifier: 0.3.11
version: 0.3.11(c10b80e38f5a8711ccad1e2174de91e6)
version: 0.3.11(101f2d8395211f7761efa8fce29a53de)
lodash:
specifier: 'catalog:'
version: 4.17.21
@ -1829,6 +1829,9 @@ importers:
browserslist-to-esbuild:
specifier: ^2.1.1
version: 2.1.1(browserslist@4.24.2)
fake-indexeddb:
specifier: ^6.0.0
version: 6.0.0
miragejs:
specifier: ^0.1.48
version: 0.1.48
@ -8467,6 +8470,10 @@ packages:
resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==}
engines: {'0': node >=0.6.0}
fake-indexeddb@6.0.0:
resolution: {integrity: sha512-YEboHE5VfopUclOck7LncgIqskAqnv4q0EWbYCaxKKjAvO93c+TJIaBuGy8CBFdbg9nKdpN3AuPRwVBJ4k7NrQ==}
engines: {node: '>=18'}
fake-xml-http-request@2.1.2:
resolution: {integrity: sha512-HaFMBi7r+oEC9iJNpc3bvcW7Z7iLmM26hPDmlb0mFwyANSsOQAtJxbdWsXITKOzZUyMYK0zYCv3h5yDj9TsiXg==}
@ -13312,6 +13319,9 @@ packages:
vue-component-type-helpers@2.2.4:
resolution: {integrity: sha512-F66p0XLbAu92BRz6kakHyAcaUSF7HWpWX/THCqL0TxySSj7z/nok5UUMohfNkkCm1pZtawsdzoJ4p1cjNqCx0Q==}
vue-component-type-helpers@2.2.8:
resolution: {integrity: sha512-4bjIsC284coDO9om4HPA62M7wfsTvcmZyzdfR0aUlFXqq4tXxM1APyXpNVxPC8QazKw9OhmZNHBVDA6ODaZsrA==}
vue-demi@0.14.10:
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
engines: {node: '>=12'}
@ -15922,7 +15932,7 @@ snapshots:
'@gar/promisify@1.1.3':
optional: true
'@getzep/zep-cloud@1.0.12(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)(langchain@0.3.11(c10b80e38f5a8711ccad1e2174de91e6))':
'@getzep/zep-cloud@1.0.12(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)(langchain@0.3.11(101f2d8395211f7761efa8fce29a53de))':
dependencies:
form-data: 4.0.0
node-fetch: 2.7.0(encoding@0.1.13)
@ -15931,7 +15941,7 @@ snapshots:
zod: 3.24.1
optionalDependencies:
'@langchain/core': 0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))
langchain: 0.3.11(c10b80e38f5a8711ccad1e2174de91e6)
langchain: 0.3.11(101f2d8395211f7761efa8fce29a53de)
transitivePeerDependencies:
- encoding
@ -16450,7 +16460,7 @@ snapshots:
- aws-crt
- encoding
'@langchain/community@0.3.24(1ea346ff95b1be1e3f1f4333b25e2811)':
'@langchain/community@0.3.24(6c18f51b4fff56aeb9d6961328b4ede2)':
dependencies:
'@browserbasehq/stagehand': 1.9.0(@playwright/test@1.49.1)(deepmerge@4.3.1)(dotenv@16.4.5)(encoding@0.1.13)(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))(zod@3.24.1)
'@ibm-cloud/watsonx-ai': 1.1.2
@ -16461,7 +16471,7 @@ snapshots:
flat: 5.0.2
ibm-cloud-sdk-core: 5.1.0
js-yaml: 4.1.0
langchain: 0.3.11(c10b80e38f5a8711ccad1e2174de91e6)
langchain: 0.3.11(101f2d8395211f7761efa8fce29a53de)
langsmith: 0.2.15(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))
openai: 4.78.1(encoding@0.1.13)(zod@3.24.1)
uuid: 10.0.0
@ -16476,7 +16486,7 @@ snapshots:
'@aws-sdk/credential-provider-node': 3.666.0(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0))(@aws-sdk/client-sts@3.666.0)
'@azure/storage-blob': 12.18.0(encoding@0.1.13)
'@browserbasehq/sdk': 2.0.0(encoding@0.1.13)
'@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)(langchain@0.3.11(c10b80e38f5a8711ccad1e2174de91e6))
'@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)(langchain@0.3.11(101f2d8395211f7761efa8fce29a53de))
'@getzep/zep-js': 0.9.0
'@google-ai/generativelanguage': 2.6.0(encoding@0.1.13)
'@google-cloud/storage': 7.12.1(encoding@0.1.13)
@ -18341,7 +18351,7 @@ snapshots:
ts-dedent: 2.2.0
type-fest: 2.19.0
vue: 3.5.13(typescript@5.7.2)
vue-component-type-helpers: 2.2.4
vue-component-type-helpers: 2.2.8
'@supabase/auth-js@2.65.0':
dependencies:
@ -19793,14 +19803,6 @@ snapshots:
transitivePeerDependencies:
- debug
axios@1.7.4(debug@4.4.0):
dependencies:
follow-redirects: 1.15.6(debug@4.4.0)
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
axios@1.7.7:
dependencies:
follow-redirects: 1.15.6(debug@4.3.6)
@ -21465,7 +21467,7 @@ snapshots:
eslint-import-resolver-node@0.3.9:
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
is-core-module: 2.13.1
resolve: 1.22.8
transitivePeerDependencies:
@ -21490,7 +21492,7 @@ snapshots:
eslint-module-utils@2.8.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
optionalDependencies:
'@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.7.2)
eslint: 8.57.0
@ -21510,7 +21512,7 @@ snapshots:
array.prototype.findlastindex: 1.2.3
array.prototype.flat: 1.3.2
array.prototype.flatmap: 1.3.2
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
doctrine: 2.1.0
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
@ -21857,6 +21859,8 @@ snapshots:
extsprintf@1.3.0: {}
fake-indexeddb@6.0.0: {}
fake-xml-http-request@2.1.2: {}
fast-deep-equal@3.1.3: {}
@ -22001,10 +22005,6 @@ snapshots:
optionalDependencies:
debug: 4.3.7
follow-redirects@1.15.6(debug@4.4.0):
optionalDependencies:
debug: 4.4.0(supports-color@8.1.1)
for-each@0.3.3:
dependencies:
is-callable: 1.2.7
@ -22294,7 +22294,7 @@ snapshots:
array-parallel: 0.1.3
array-series: 0.1.5
cross-spawn: 4.0.2
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@ -22596,7 +22596,7 @@ snapshots:
'@types/debug': 4.1.12
'@types/node': 18.16.16
'@types/tough-cookie': 4.0.2
axios: 1.7.4(debug@4.4.0)
axios: 1.7.4
camelcase: 6.3.0
debug: 4.4.0(supports-color@8.1.1)
dotenv: 16.4.5
@ -22606,7 +22606,7 @@ snapshots:
isstream: 0.1.2
jsonwebtoken: 9.0.2
mime-types: 2.1.35
retry-axios: 2.6.0(axios@1.7.4)
retry-axios: 2.6.0(axios@1.7.4(debug@4.4.0))
tough-cookie: 4.1.3
transitivePeerDependencies:
- supports-color
@ -23593,7 +23593,7 @@ snapshots:
kuler@2.0.0: {}
langchain@0.3.11(c10b80e38f5a8711ccad1e2174de91e6):
langchain@0.3.11(101f2d8395211f7761efa8fce29a53de):
dependencies:
'@langchain/core': 0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))
'@langchain/openai': 0.3.17(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)
@ -25157,7 +25157,7 @@ snapshots:
pdf-parse@1.1.1:
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
node-ensure: 0.0.0
transitivePeerDependencies:
- supports-color
@ -25965,7 +25965,7 @@ snapshots:
ret@0.1.15: {}
retry-axios@2.6.0(axios@1.7.4):
retry-axios@2.6.0(axios@1.7.4(debug@4.4.0)):
dependencies:
axios: 1.7.4
@ -25992,7 +25992,7 @@ snapshots:
rhea@1.0.24:
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@ -27624,6 +27624,8 @@ snapshots:
vue-component-type-helpers@2.2.4: {}
vue-component-type-helpers@2.2.8: {}
vue-demi@0.14.10(vue@3.5.13(typescript@5.7.2)):
dependencies:
vue: 3.5.13(typescript@5.7.2)