Skip to content

Commit

Permalink
feat(script-compiler): add error responses (#75)
Browse files Browse the repository at this point in the history
* feat(script-compiler): add error responses

* refactor: add missing error handling

* feat: unite error types

* refactor: use AbortController to remove event listeners

* wip: introduce TextlintError

* feat: add error response as only error from Textlint worker

* refactor: simplify message handler by using promise chain

* refactor: handle errors in wrapper functions

* refactor: simplify redundant Promise handling
  • Loading branch information
otariidae authored Jun 22, 2023
1 parent 868685c commit f7f6606
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,16 @@ export type TextlintWorkerCommandResponseFix = {
command: "fix:result";
result: TextlintFixResult;
};
export type TextlintWorkerCommandResponseError = {
id?: MessageId | undefined;
command: "error";
error: Error;
};
export type TextlintWorkerCommandResponse =
| TextlintWorkerCommandResponseInit
| TextlintWorkerCommandResponseLint
| TextlintWorkerCommandResponseFix;
| TextlintWorkerCommandResponseFix
| TextlintWorkerCommandResponseError;

export const generateCode = async (config: TextlintConfigDescriptor) => {
// macro replacement
Expand Down Expand Up @@ -118,6 +124,24 @@ const assignConfig = (textlintrc) => {
});
}
};
self.addEventListener('error', (event) => {
self.postMessage({
command: "error",
// wrapping any type error with Error
error: new Error("unexpected error", {
cause: event.error
})
})
});
self.addEventListener('unhandledrejection', (event) => {
self.postMessage({
command: "error",
// wrapping any type error with Error
error: new Error("unexpected unhandled promise rejection", {
cause: event.reason
})
})
});
self.addEventListener('message', (event) => {
const data = event.data;
const rules = data.ruleId === undefined
Expand All @@ -139,6 +163,15 @@ self.addEventListener('message', (event) => {
command: "lint:result",
result
});
}).catch(error => {
return self.postMessage({
id: data.id,
command: "error",
// wrapping any type error with Error
error: new Error("failed to lint text", {
cause: error
})
})
});
case "fix":
return kernel.fixText(data.text, {
Expand All @@ -153,6 +186,12 @@ self.addEventListener('message', (event) => {
command: "fix:result",
result
});
}).catch(error => {
return self.postMessage({
id: data.id,
command: "error",
error
})
});
default:
console.log("Unknown command: " + data.command);
Expand Down
3 changes: 2 additions & 1 deletion packages/@textlint/script-compiler/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export type {
TextlintWorkerCommandLint,
TextlintWorkerCommandResponseFix,
TextlintWorkerCommandResponseInit,
TextlintWorkerCommandResponseLint
TextlintWorkerCommandResponseLint,
TextlintWorkerCommandResponseError
} from "./CodeGenerator/worker-codegen";
70 changes: 52 additions & 18 deletions packages/textchecker-element/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,40 @@ const generateMessageId = () => crypto.randomUUID();
const createTextlint = ({ worker, ext }: { worker: Worker; ext: string }) => {
const lintText: LintEngineAPI["lintText"] = async ({ text }: { text: string }): Promise<TextlintResult[]> => {
updateStatus("linting...");
return new Promise((resolve, _reject) => {
const controller = new AbortController();
const lintPromise = new Promise<TextlintResult[]>((resolve, reject) => {
const id = generateMessageId();
worker.addEventListener("message", function handler(event) {
const data: TextlintWorkerCommandResponse = event.data;
if (data.command === "lint:result" && data.id === id) {
resolve([data.result]);
worker.removeEventListener("message", handler);
}
updateStatus("linted");
});
worker.addEventListener(
"message",
(event: MessageEvent<TextlintWorkerCommandResponse>) => {
const data = event.data;
// global error or ID-specified error
if (data.command === "error" && (!("id" in data) || data.id === id)) {
reject(data.error);
} else if (data.command === "lint:result" && data.id === id) {
resolve([data.result]);
}
},
{ signal: controller.signal }
);
return worker.postMessage({
id,
command: "lint",
text,
ext: ext
} as TextlintWorkerCommandLint);
});
lintPromise
.then(() => {
updateStatus("linted");
})
.catch(() => {
updateStatus("failed to lint");
})
.finally(() => {
controller.abort();
});
return lintPromise;
};
const fixText = async ({
text,
Expand All @@ -67,16 +84,22 @@ const createTextlint = ({ worker, ext }: { worker: Worker; ext: string }) => {
message?: TextlintMessage;
}): Promise<TextlintFixResult> => {
updateStatus("fixing...");
return new Promise((resolve, _reject) => {
const controller = new AbortController();
const fixPromise = new Promise<TextlintFixResult>((resolve, reject) => {
const id = generateMessageId();
worker.addEventListener("message", function handler(event) {
const data: TextlintWorkerCommandResponse = event.data;
if (data.command === "fix:result" && data.id === id) {
resolve(data.result);
worker.removeEventListener("message", handler);
}
updateStatus("fixed");
});
worker.addEventListener(
"message",
(event: MessageEvent<TextlintWorkerCommandResponse>) => {
const data = event.data;
// global error or ID-specified error
if (data.command === "error" && (!("id" in data) || data.id === id)) {
reject(data.error);
} else if (data.command === "fix:result" && data.id === id) {
resolve(data.result);
}
},
{ signal: controller.signal }
);
return worker.postMessage({
id,
command: "fix",
Expand All @@ -85,6 +108,17 @@ const createTextlint = ({ worker, ext }: { worker: Worker; ext: string }) => {
ext: ext
} as TextlintWorkerCommandFix);
});
fixPromise
.then(() => {
updateStatus("fixed");
})
.catch(() => {
updateStatus("failed to fix");
})
.finally(() => {
controller.abort();
});
return fixPromise;
};
return {
lintText,
Expand Down
20 changes: 14 additions & 6 deletions packages/textchecker-element/src/attach-to-text-area.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export const attachToTextArea = ({
const currentText = textAreaElement.value;
if (currentText === text && currentText !== newText) {
textAreaElement.value = newText;
await update();
await updateOrClearAnnotationsIfFailed();
textCheckerPopup.dismissCard(card);
}
};
Expand Down Expand Up @@ -228,7 +228,7 @@ export const attachToTextArea = ({
text,
message
});
await update();
await updateOrClearAnnotationsIfFailed();
},
onSeeDocument() {
const id = message.ruleId.includes("/")
Expand Down Expand Up @@ -267,24 +267,32 @@ export const attachToTextArea = ({
debug("annotations", annotations);
textChecker.updateAnnotations(annotations);
}, lintingDebounceMs);
const updateOrClearAnnotationsIfFailed = async () => {
try {
await update();
} catch (error) {
debug("update error", error);
textChecker.updateAnnotations([]);
}
};
// Events
// when resize element, update annotation
const resizeObserver = new ResizeObserver(() => {
debug("ResizeObserver do update");
textCheckerPopup.dismissCards();
textChecker.resetAnnotations();
update();
updateOrClearAnnotationsIfFailed();
});
resizeObserver.observe(textAreaElement);
// when scroll window, update annotation
const onScroll = () => {
textCheckerPopup.dismissCards();
textChecker.resetAnnotations();
update();
updateOrClearAnnotationsIfFailed();
};
const onFocus = () => {
textCheckerPopup.dismissCards();
update();
updateOrClearAnnotationsIfFailed();
};
const onBlur = (event: FocusEvent) => {
// does not dismiss on click popup items(require tabindex)
Expand All @@ -302,7 +310,7 @@ export const attachToTextArea = ({
window.addEventListener("scroll", onScroll);
// when scroll the element, update annotation
textAreaElement.addEventListener("scroll", onScroll);
update();
updateOrClearAnnotationsIfFailed();
return () => {
window.removeEventListener("scroll", onScroll);
textAreaElement.removeEventListener("scroll", onScroll);
Expand Down
50 changes: 34 additions & 16 deletions packages/webextension/app/scripts/background/textlint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,30 @@ export const createTextlintWorker = (script: Script) => {
const defaultWorker = new Worker(workerUrl);
const workerRef = createWorkerRef(defaultWorker);
const lintText = async ({ text, ext }: { text: string; ext: string }): Promise<TextlintResult[]> => {
return new Promise((resolve, _reject) => {
const controller = new AbortController();
return new Promise<TextlintResult[]>((resolve, reject) => {
const id = generateMessageId();
workerRef.current.addEventListener("message", function handler(event) {
const data: TextlintWorkerCommandResponse = event.data;
if (data.command === "lint:result" && data.id === id) {
resolve([data.result]);
workerRef.current.removeEventListener("message", handler);
}
});
workerRef.current.addEventListener(
"message",
(event: MessageEvent<TextlintWorkerCommandResponse>) => {
const data = event.data;
// global error or ID-specified error
if (data.command === "error" && (!("id" in data) || data.id === id)) {
reject(data.error);
} else if (data.command === "lint:result" && data.id === id) {
resolve([data.result]);
}
},
{ signal: controller.signal }
);
return workerRef.current.postMessage({
id,
command: "lint",
text,
ext
} as TextlintWorkerCommandLint);
}).finally(() => {
controller.abort();
});
};
// Note: currently does not use background implementation.
Expand All @@ -85,22 +94,31 @@ export const createTextlintWorker = (script: Script) => {
ext: string;
message?: TextlintMessage;
}): Promise<TextlintFixResult> => {
return new Promise((resolve, _reject) => {
const controller = new AbortController();
return new Promise<TextlintFixResult>((resolve, reject) => {
const id = generateMessageId();
workerRef.current.addEventListener("message", function handler(event) {
const data: TextlintWorkerCommandResponse = event.data;
if (data.command === "fix:result" && data.id === id) {
resolve(data.result);
workerRef.current.removeEventListener("message", handler);
}
});
workerRef.current.addEventListener(
"message",
(event: MessageEvent<TextlintWorkerCommandResponse>) => {
const data = event.data;
// global error or ID-specified error
if (data.command === "error" && (!("id" in data) || data.id === id)) {
reject(data.error);
} else if (data.command === "fix:result" && data.id === id) {
resolve(data.result);
}
},
{ signal: controller.signal }
);
return workerRef.current.postMessage({
id,
command: "fix",
text,
ruleId: message?.ruleId,
ext: ext
} as TextlintWorkerCommandFix);
}).finally(() => {
controller.abort();
});
};
const mergeConfig = async ({ textlintrc }: { textlintrc: TextlintRcConfig }): Promise<void> => {
Expand Down

0 comments on commit f7f6606

Please sign in to comment.