Skip to content

Commit 7b185c2

Browse files
committed
fix: enable and fix new eslint rules
Signed-off-by: mmolisani <mmolisani@bloomberg.net>
1 parent 8a3dc5b commit 7b185c2

9 files changed

Lines changed: 1269 additions & 402 deletions

File tree

docs/eslint.config.mjs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
1+
import react from "eslint-plugin-react";
2+
import hooks from "eslint-plugin-react-hooks";
3+
import a11y from "eslint-plugin-jsx-a11y";
14
import ts from "typescript-eslint";
25
import common from "../eslint.config.mjs";
36

47
export default [
58
...ts.configs.strictTypeChecked,
69
...common,
10+
{
11+
...react.configs.flat.recommended,
12+
settings: {
13+
react: {
14+
version: "detect",
15+
},
16+
},
17+
},
18+
react.configs.flat["jsx-runtime"],
19+
hooks.configs.flat.recommended,
20+
a11y.flatConfigs.recommended,
21+
a11y.flatConfigs.strict,
722
{
823
languageOptions: {
924
parserOptions: {

docs/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
"@docusaurus/types": "3.9.2",
3939
"@stricli/core": "^1.0.0",
4040
"docusaurus-plugin-typedoc": "^1.0.5",
41+
"eslint-plugin-jsx-a11y": "^6.10.2",
42+
"eslint-plugin-react": "^7.37.5",
43+
"eslint-plugin-react-hooks": "^7.0.1",
4144
"monaco-editor": "^0.55.1",
4245
"sass": "^1.79.4",
4346
"typedoc": "^0.26.7",

docs/src/components/StricliPlayground/Terminal/Ansi.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Distributed under the terms of the Apache 2.0 license.
33
import { riffle } from "@site/src/util/array";
44
import { ansiToJson } from "anser";
5-
import React from "react";
65

76
export interface AnsiProps {
87
children?: string;

docs/src/components/StricliPlayground/Terminal/index.tsx

Lines changed: 86 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Distributed under the terms of the Apache 2.0 license.
33
import React, { useState, useEffect, useRef, useCallback } from "react";
44
import Ansi from "./Ansi";
5+
import clsx from "clsx";
56

67
export type TerminalHistoryLine =
78
| readonly [text: string, stream: "stdout" | "stderr"]
@@ -40,14 +41,15 @@ export default function Terminal({
4041
const [input, setInput] = useState<string>(defaultValue);
4142
const [history, setHistory] = useState<readonly TerminalHistoryLine[]>([]);
4243
const [historyIndex, setHistoryIndex] = useState<number | undefined>(void 0);
43-
const [completions, setCompletions] = useState<readonly string[]>([]);
44-
const [completionIndex, setCompletionIndex] = useState<number | undefined>(void 0);
44+
const autocomplete = useRef<[index: number, completions: readonly string[]] | undefined>(void 0);
45+
46+
const selectedStdinIndex = typeof historyIndex === "number" ? historyIndex * 2 + 1 : void 0;
4547

4648
useEffect(() => {
4749
if (appLoaded) {
4850
if (!firstRunComplete) {
49-
setFirstRunComplete(true);
5051
void (async () => {
52+
setFirstRunComplete(true);
5153
const initialHistory = [...history];
5254
for (const input of initialInputs) {
5355
const lines = await executeInput(input);
@@ -58,7 +60,7 @@ export default function Terminal({
5860
})();
5961
}
6062
}
61-
}, [appLoaded, firstRunComplete, input, history, historyIndex, collapsed]);
63+
}, [appLoaded, firstRunComplete, initialInputs, executeInput, history]);
6264

6365
const focusInput = useCallback(() => {
6466
inputRef.current?.focus();
@@ -68,76 +70,95 @@ export default function Terminal({
6870
setInput(e.target.value);
6971
}, []);
7072

71-
const onKeyDown = useCallback(
72-
async (e: React.KeyboardEvent<HTMLInputElement>) => {
73-
if (e.key !== "Tab" && typeof completionIndex === "number") {
74-
setCompletionIndex(void 0);
75-
}
73+
const onInputSubmit = useCallback(async () => {
74+
const lines = await executeInput(input);
75+
setHistory([...lines, ...history]);
76+
setHistoryIndex(void 0);
77+
setCollapsed(false);
78+
setInput("");
79+
}, [input, executeInput, history]);
7680

77-
const inputWithPrefix = `${commandPrefix} ${input}`;
78-
if (e.key === "Enter") {
79-
const lines = await executeInput(input);
80-
setHistory([...lines, ...history]);
81-
setHistoryIndex(void 0);
82-
setCollapsed(false);
81+
const setInputToHistoryAtIndex = useCallback(
82+
(index: number) => {
83+
const stdinHistory = history.filter((line) => line[1] === "stdin");
84+
const historySelection = stdinHistory[index];
85+
if (historySelection) {
86+
setInput(historySelection[0]);
87+
} else {
8388
setInput("");
84-
} else if (e.key === "ArrowUp") {
85-
setHistoryIndex(typeof historyIndex === "number" ? historyIndex + 1 : 0);
86-
e.preventDefault();
87-
} else if (e.key === "ArrowDown") {
88-
if (historyIndex === 0) {
89-
setInput("");
90-
setHistoryIndex(void 0);
91-
} else if (typeof historyIndex === "number") {
92-
setHistoryIndex(historyIndex - 1);
93-
}
94-
e.preventDefault();
95-
} else if (e.key === "Tab" && completeInput) {
96-
if (typeof completionIndex === "number") {
97-
setCompletionIndex(completionIndex + 1);
98-
} else {
99-
const inputCompletions = await completeInput(inputWithPrefix);
100-
const completions = inputCompletions.map((str) => str.slice(commandPrefix.length + 1));
101-
setCompletions(completions);
102-
setCompletionIndex(0);
103-
}
104-
e.preventDefault();
10589
}
10690
},
107-
[input, history, historyIndex, completionIndex],
91+
[history],
10892
);
10993

110-
useEffect(() => {
111-
if (typeof historyIndex === "number") {
112-
const stdinHistory = history.filter((line) => line[1] === "stdin");
113-
const historySelection = stdinHistory[historyIndex];
114-
if (historySelection) {
115-
setInput(historySelection[0]);
116-
} else {
94+
const onScrollHistoryUp = useCallback(
95+
(e: React.KeyboardEvent<HTMLInputElement>) => {
96+
const newIndex = typeof historyIndex === "number" ? (historyIndex + 1) % (history.length / 2) : 0;
97+
setHistoryIndex(newIndex);
98+
setInputToHistoryAtIndex(newIndex);
99+
e.preventDefault();
100+
},
101+
[history, historyIndex, setInputToHistoryAtIndex],
102+
);
103+
104+
const onScrollHistoryDown = useCallback(
105+
(e: React.KeyboardEvent<HTMLInputElement>) => {
106+
if (historyIndex === 0) {
117107
setInput("");
118108
setHistoryIndex(void 0);
109+
} else if (typeof historyIndex === "number") {
110+
const newIndex = historyIndex - 1;
111+
setHistoryIndex(newIndex);
112+
setInputToHistoryAtIndex(newIndex);
119113
}
120-
}
121-
}, [history, historyIndex]);
114+
e.preventDefault();
115+
},
116+
[historyIndex, setInputToHistoryAtIndex],
117+
);
122118

123-
useEffect(() => {
124-
if (typeof completionIndex === "number") {
125-
const completionSelection = completions[completionIndex];
126-
if (completionSelection) {
127-
setInput(completionSelection);
128-
} else if (completions.length > 0) {
129-
setCompletionIndex(completionIndex % completions.length);
119+
const onToggleAutocomplete = useCallback(
120+
async (e: React.KeyboardEvent<HTMLInputElement>) => {
121+
let currentIndex: number;
122+
let completions: readonly string[];
123+
if (autocomplete.current) {
124+
[currentIndex, completions] = autocomplete.current;
130125
} else {
131-
setCompletionIndex(void 0);
126+
completions = await completeInput(input);
127+
currentIndex = -1;
128+
autocomplete.current = [0, completions];
132129
}
133-
}
134-
}, [completions, completionIndex]);
130+
const nextIndex = (currentIndex + 1) % completions.length;
131+
setInput(completions[nextIndex]);
132+
autocomplete.current = [nextIndex, completions];
133+
e.preventDefault();
134+
},
135+
[autocomplete, input, completeInput],
136+
);
137+
138+
const onKeyDown = useCallback(
139+
async (e: React.KeyboardEvent<HTMLInputElement>) => {
140+
if (e.key !== "Tab" && autocomplete) {
141+
autocomplete.current = void 0;
142+
}
143+
144+
if (e.key === "Enter") {
145+
await onInputSubmit();
146+
} else if (e.key === "ArrowUp") {
147+
onScrollHistoryUp(e);
148+
} else if (e.key === "ArrowDown") {
149+
onScrollHistoryDown(e);
150+
} else if (e.key === "Tab") {
151+
await onToggleAutocomplete(e);
152+
}
153+
},
154+
[autocomplete, onInputSubmit, onScrollHistoryUp, onScrollHistoryDown, onToggleAutocomplete],
155+
);
135156

136157
const clearHistory = useCallback(() => {
137158
setHistory([]);
138159
setHistoryIndex(void 0);
139160
setCollapsed(true);
140-
}, [history, historyIndex, collapsed]);
161+
}, []);
141162

142163
const height = collapsed ? `${LINE_HEIGHT_EM}em` : expandedHeight;
143164

@@ -149,6 +170,7 @@ export default function Terminal({
149170
Clear
150171
</button>
151172
</div>
173+
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions,jsx-a11y/click-events-have-key-events -- Click handler exists to focus on actual input element */}
152174
<div className="terminal-output" style={{ height }} onClick={focusInput}>
153175
<div className="ansi-block terminal-input">
154176
<span style={{ display: "inline-flex" }}>
@@ -169,7 +191,13 @@ export default function Terminal({
169191
{history.map((line, i) => {
170192
const text = line[1] === "stdin" ? `${commandPrompt}${line[2]} ${line[0]}` : line[0];
171193
return (
172-
<Ansi key={`terminal-history-line-${i}`} className={`terminal-${line[1]}`}>
194+
<Ansi
195+
key={`terminal-history-line-${i}`}
196+
className={clsx({
197+
[`terminal-${line[1]}`]: true,
198+
[`terminal-history-selected`]: i === selectedStdinIndex,
199+
})}
200+
>
173201
{text}
174202
</Ansi>
175203
);

docs/src/components/StricliPlayground/impl.tsx

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright 2024 Bloomberg Finance L.P.
22
// Distributed under the terms of the Apache 2.0 license.
3-
import React, { useCallback, useMemo, useRef, useState } from "react";
3+
import React, { useCallback, useMemo, useState } from "react";
44
import Tippy from "@tippyjs/react";
55
import Admonition from "@theme/Admonition";
66

@@ -62,9 +62,8 @@ export default function StricliPlayground({
6262
editorHeight = "250px",
6363
terminalHeight = "250px",
6464
}: StricliPlaygroundProps): React.JSX.Element {
65-
const appRef = useRef<core.Application<core.CommandContext> | undefined>(void 0);
65+
const [loadedApp, setLoadedApp] = useState<core.Application<core.CommandContext> | undefined>(void 0);
6666
const [lastLoaded, setLastLoaded] = useState<Date | undefined>();
67-
const [appName, setAppName] = useState<string>("loading...");
6867

6968
const id = useMemo(() => crypto.randomUUID(), []);
7069
const modelDirectory = `code_${id}`;
@@ -86,17 +85,16 @@ export default function StricliPlayground({
8685
if (exports) {
8786
const app = exports.default as PlaygroundApp;
8887
app.consoleRedirect = consoleRedirect;
89-
appRef.current = app;
88+
setLoadedApp(app);
9089
setLastLoaded(new Date());
91-
setAppName(app.config.name);
9290
} else {
93-
appRef.current = void 0;
91+
setLoadedApp(void 0);
9492
}
9593
} else {
96-
appRef.current = void 0;
94+
setLoadedApp(void 0);
9795
}
9896
},
99-
[appRef],
97+
[modelDirectory],
10098
);
10199

102100
const lastLoadedTimestamp = useMemo(() => {
@@ -147,25 +145,23 @@ export default function StricliPlayground({
147145
onEmit={onWorkspaceChange}
148146
></TypeScriptPlayground>
149147
<Terminal
150-
appLoaded={Boolean(appRef.current)}
148+
appLoaded={Boolean(loadedApp)}
151149
startCollapsed={collapsed}
152150
height={terminalHeight}
153-
commandPrefix={appName}
151+
commandPrefix={loadedApp?.config.name ?? "loading..."}
154152
initialInputs={initialInputs.slice(0, -1)}
155153
defaultValue={initialInputs.at(-1)}
156154
executeInput={async (input) => {
157-
const app = appRef.current;
158-
if (!app) {
155+
if (!loadedApp) {
159156
return [["Application not loaded, check for type errors above ^", "stderr"]];
160157
}
161158
const argv = parseArgv(input);
162-
const lines = await runApplication(app, argv);
159+
const lines = await runApplication(loadedApp, argv);
163160
const reversed = [...lines].reverse();
164-
return [...reversed, [input, "stdin", app.config.name]];
161+
return [...reversed, [input, "stdin", loadedApp.config.name]];
165162
}}
166163
completeInput={async (input) => {
167-
const app = appRef.current;
168-
if (!app) {
164+
if (!loadedApp) {
169165
console.error("Application not loaded");
170166
return [];
171167
}
@@ -175,7 +171,9 @@ export default function StricliPlayground({
175171
finalInput = "";
176172
argv.push("");
177173
}
178-
const completions = await proposeCompletions(app, argv);
174+
console.log("completeInput", argv);
175+
const completions = await proposeCompletions(loadedApp, argv);
176+
console.log("completions", completions);
179177
if (finalInput === "") {
180178
return completions.map((completion) => `${input}${completion}`);
181179
}

0 commit comments

Comments
 (0)