React linters (#117)

* React hook linter

* React linting

* Simplify config
This commit is contained in:
Will Scullin 2022-05-29 13:48:51 -07:00 committed by GitHub
parent 52a1c65fe4
commit e525e12c3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1160 additions and 98 deletions

View File

@ -1,6 +1,8 @@
{ {
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint/eslint-plugin"], "plugins": [
"@typescript-eslint/eslint-plugin"
],
"rules": { "rules": {
"indent": [ "indent": [
"error", "error",
@ -69,7 +71,9 @@
] ]
} }
], ],
"@typescript-eslint/require-await": ["error"] "@typescript-eslint/require-await": ["error"],
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "error"
}, },
"env": { "env": {
"builtin": true, "builtin": true,
@ -80,7 +84,11 @@
"sourceType": "module", "sourceType": "module",
"project": "./tsconfig.json" "project": "./tsconfig.json"
}, },
"extends": "eslint:recommended", "extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended"
],
"overrides": [ "overrides": [
{ {
"files": [ "files": [
@ -140,7 +148,8 @@
"ignorePatterns": ["coverage/**/*"], "ignorePatterns": ["coverage/**/*"],
"settings": { "settings": {
"react": { "react": {
"pragma": "h" "pragma": "h",
"version": "16"
} }
} }
} }

View File

@ -59,7 +59,7 @@ export const Apple2 = (props: Apple2Props) => {
apple2.run(); apple2.run();
}).catch(error => console.error(error)); }).catch(error => console.error(error));
} }
}, []); }, [props]);
return ( return (
<div className={cs('outer', { apple2e: e})}> <div className={cs('outer', { apple2e: e})}>

View File

@ -32,13 +32,13 @@ export const AudioControl = ({ apple2 }: AudioControlProps) => {
setAudio(audio); setAudio(audio);
setAudioEnabled(audio.isEnabled()); setAudioEnabled(audio.isEnabled());
} }
}, [apple2]); }, [apple2, options]);
const doToggleSound = useCallback(() => { const doToggleSound = useCallback(() => {
const on = !audio?.isEnabled(); const on = !audio?.isEnabled();
options.setOption(SOUND_ENABLED_OPTION, on); options.setOption(SOUND_ENABLED_OPTION, on);
setAudioEnabled(on); setAudioEnabled(on);
}, [audio]); }, [audio, options]);
return ( return (
<ControlButton <ControlButton

View File

@ -20,6 +20,6 @@ export interface ControlButtonProps {
*/ */
export const ControlButton = ({ icon, title, onClick, ...props }: ControlButtonProps) => ( export const ControlButton = ({ icon, title, onClick, ...props }: ControlButtonProps) => (
<button onClick={onClick} title={title} {...props} > <button onClick={onClick} title={title} {...props} >
<i class={`fas fa-${icon}`}></i> <i className={`fas fa-${icon}`}></i>
</button> </button>
); );

View File

@ -47,7 +47,7 @@ export const ControlStrip = ({ apple2, e }: ControlStripProps) => {
const screen = new Screen(vm); const screen = new Screen(vm);
options.addOptions(screen); options.addOptions(screen);
} }
}, [apple2]); }, [apple2, e, options]);
const doReset = useCallback(() => const doReset = useCallback(() =>
apple2?.reset() apple2?.reset()
@ -70,7 +70,7 @@ export const ControlStrip = ({ apple2, e }: ControlStripProps) => {
SCREEN_FULL_PAGE, SCREEN_FULL_PAGE,
!options.getOption(SCREEN_FULL_PAGE) !options.getOption(SCREEN_FULL_PAGE)
) )
, []); , [options]);
useHotKey('F2', doToggleFullPage); useHotKey('F2', doToggleFullPage);
useHotKey('F4', doShowOptions); useHotKey('F4', doShowOptions);

View File

@ -56,7 +56,7 @@ export const DiskII = ({ disk2, number, on, name, side }: DiskIIProps) => {
); );
} }
} }
}, [disk2]); }, [disk2, number]);
const doClose = useCallback(() => { const doClose = useCallback(() => {
setModalOpen(false); setModalOpen(false);
@ -74,7 +74,7 @@ export const DiskII = ({ disk2, number, on, name, side }: DiskIIProps) => {
className={cs('disk-light', { on })} className={cs('disk-light', { on })}
/> />
<button title="Load Disk"> <button title="Load Disk">
<i class="fas fa-folder-open" onClick={onOpenModal} /> <i className="fas fa-folder-open" onClick={onOpenModal} />
</button> </button>
<div <div
id={`disk-label${number}`} id={`disk-label${number}`}

View File

@ -54,7 +54,7 @@ export const Drives = ({ io, sectors }: DrivesProps) => {
io.setSlot(6, disk2); io.setSlot(6, disk2);
setDisk2(disk2); setDisk2(disk2);
} }
}, [io]); }, [io, sectors]);
return ( return (
<> <>

View File

@ -55,7 +55,7 @@ const InputFileChooser = ({
onChange(newFiles); onChange(newFiles);
} }
} }
}, []); }, [onChange]);
const extraProps = useMemo<ExtraProps>(() => { const extraProps = useMemo<ExtraProps>(() => {
// Accept all of the given MIME types and extensions. An argument // Accept all of the given MIME types and extensions. An argument
@ -131,14 +131,14 @@ const FilePickerChooser = ({
} finally { } finally {
setBusy(false); setBusy(false);
} }
}, []); }, [accept, busy, onChange]);
useEffect(() => { useEffect(() => {
setSelectedFilename( setSelectedFilename(
fileHandlesRef.current?.length fileHandlesRef.current?.length
? fileHandlesRef.current[0].name ? fileHandlesRef.current[0].name
: 'No file selected'); : 'No file selected');
}, [fileHandlesRef.current]); }, []);
return ( return (
<> <>
@ -184,7 +184,7 @@ export const FileChooser = ({
}); });
} }
onChange(handles); onChange(handles);
}, []); }, [onChange]);
const onChangeForPicker = useCallback((fileHandles: FileSystemFileHandle[]) => { const onChangeForPicker = useCallback((fileHandles: FileSystemFileHandle[]) => {
const handles: FileSystemFileHandleLike[] = []; const handles: FileSystemFileHandleLike[] = [];
@ -198,7 +198,7 @@ export const FileChooser = ({
}); });
} }
onChange(handles); onChange(handles);
}, []); }, [onChange]);
return control === 'picker' return control === 'picker'
? ( ? (

View File

@ -50,7 +50,7 @@ export const FileModal = ({ disk2, number, onClose, isOpen }: FileModalProps) =>
const [handles, setHandles] = useState<FileSystemFileHandleLike[]>(); const [handles, setHandles] = useState<FileSystemFileHandleLike[]>();
const [filename, setFilename] = useState<string>(); const [filename, setFilename] = useState<string>();
const doCancel = useCallback(() => onClose(true), []); const doCancel = useCallback(() => onClose(true), [onClose]);
const doOpen = useCallback(async () => { const doOpen = useCallback(async () => {
const hashParts = getHashParts(); const hashParts = getHashParts();
@ -109,12 +109,12 @@ export const FileModal = ({ disk2, number, onClose, isOpen }: FileModalProps) =>
<div id="load-modal"> <div id="load-modal">
<select multiple onChange={doSelectCategory}> <select multiple onChange={doSelectCategory}>
{categoryNames.map((name) => ( {categoryNames.map((name) => (
<option>{name}</option> <option key={name}>{name}</option>
))} ))}
</select> </select>
<select multiple onChange={doSelectFilename}> <select multiple onChange={doSelectFilename}>
{disks.map((disk) => ( {disks.map((disk) => (
<option value={disk.filename}> <option key={disk.filename} value={disk.filename}>
{disk.name} {disk.name}
{disk.disk ? ` - ${disk.disk}` : ''} {disk.disk ? ` - ${disk.disk}` : ''}
</option> </option>

View File

@ -1,5 +1,7 @@
import { h } from 'preact'; import { h } from 'preact';
const README = 'https://github.com/whscullin/apple2js#readme';
/** /**
* Header component properties. * Header component properties.
*/ */
@ -15,7 +17,7 @@ export interface HeaderProps {
export const Header = ({ e }: HeaderProps) => { export const Header = ({ e }: HeaderProps) => {
return ( return (
<div id="header"> <div id="header">
<a href="https://github.com/whscullin/apple2js#readme" target="_blank"> <a href={README} rel="noreferrer" target="_blank">
<img src="img/badge.png" id="badge" /> <img src="img/badge.png" id="badge" />
</a> </a>
<div id="subtitle">An Apple {e ? '//e' : ']['} Emulator in JavaScript</div> <div id="subtitle">An Apple {e ? '//e' : ']['} Emulator in JavaScript</div>

View File

@ -1,11 +1,11 @@
import { h, FunctionalComponent } from 'preact'; import { h, ComponentChildren } from 'preact';
/** /**
* Convenience component for a nice beveled border. * Convenience component for a nice beveled border.
* *
* @returns Inset component * @returns Inset component
*/ */
export const Inset: FunctionalComponent = ({ children }) => ( export const Inset = ({ children }: { children: ComponentChildren }) => (
<div className="inset"> <div className="inset">
{children} {children}
</div> </div>

View File

@ -209,7 +209,7 @@ export const Keyboard = ({ apple2, e }: KeyboardProps) => {
/>; />;
const rows = keys.map((row, idx) => const rows = keys.map((row, idx) =>
<div className={`row row${idx}`}> <div key={idx} className={`row row${idx}`}>
{row.map(bindKey)} {row.map(bindKey)}
</div> </div>
); );

View File

@ -1,4 +1,4 @@
import { h, FunctionalComponent } from 'preact'; import { h, ComponentChildren } from 'preact';
import { useCallback } from 'preact/hooks'; import { useCallback } from 'preact/hooks';
import { useHotKey } from './hooks/useHotKey'; import { useHotKey } from './hooks/useHotKey';
@ -67,7 +67,7 @@ const modalFooterStyle = {
* *
* @returns ModalOverlay component * @returns ModalOverlay component
*/ */
export const ModalOverlay: FunctionalComponent = ({ children }) => { export const ModalOverlay = ({ children }: { children: ComponentChildren }) => {
return ( return (
<div style={modalOverlayStyle}> <div style={modalOverlayStyle}>
{children} {children}
@ -80,7 +80,7 @@ export const ModalOverlay: FunctionalComponent = ({ children }) => {
* *
* @returns ModalContent component * @returns ModalContent component
*/ */
export const ModalContent: FunctionalComponent = ({ children }) => { export const ModalContent = ({ children }: { children: ComponentChildren }) => {
return ( return (
<div style={modalContentStyle}> <div style={modalContentStyle}>
{children} {children}
@ -93,7 +93,7 @@ export const ModalContent: FunctionalComponent = ({ children }) => {
* *
* @returns ModalFooter component * @returns ModalFooter component
*/ */
export const ModalFooter: FunctionalComponent = ({ children }) => { export const ModalFooter = ({ children }: { children: ComponentChildren }) => {
return ( return (
<div style={modalFooterStyle}> <div style={modalFooterStyle}>
{children} {children}
@ -160,6 +160,7 @@ export interface ModalProps {
onClose?: (closeBox?: boolean) => void; onClose?: (closeBox?: boolean) => void;
isOpen: boolean; isOpen: boolean;
title: string; title: string;
children: ComponentChildren;
} }
/** /**
@ -172,12 +173,12 @@ export interface ModalProps {
* @param onClose Close callback * @param onClose Close callback
* @returns Modal component * @returns Modal component
*/ */
export const Modal: FunctionalComponent<ModalProps> = ({ export const Modal = ({
isOpen, isOpen,
children, children,
title, title,
...props ...props
}) => { }: ModalProps) => {
return ( return (
isOpen ? ( isOpen ? (
<ModalOverlay> <ModalOverlay>

View File

@ -31,7 +31,7 @@ export const Mouse = ({ cpu, screen, io, slot }: MouseProps) => {
const mouse = new MouseCard(cpu, mouseUI); const mouse = new MouseCard(cpu, mouseUI);
io.setSlot(slot, mouse); io.setSlot(slot, mouse);
} }
}, [cpu, io]); }, [cpu, io, screen, slot]);
return null; return null;
}; };

View File

@ -17,7 +17,7 @@ export interface ScreenProps {
export const Screen = ({ screen }: ScreenProps) => { export const Screen = ({ screen }: ScreenProps) => {
return ( return (
<div id="display"> <div id="display">
<div class="overscan"> <div className="overscan">
<canvas id="screen" width="592" height="416" ref={screen} /> <canvas id="screen" width="592" height="416" ref={screen} />
</div> </div>
</div> </div>

View File

@ -24,7 +24,7 @@ export const Slinky = ({ io, slot }: SlinkyProps) => {
const slinky = new RAMFactor(1024 * 1024); const slinky = new RAMFactor(1024 * 1024);
io.setSlot(slot, slinky); io.setSlot(slot, slinky);
} }
}, [io]); }, [io, slot]);
return null; return null;
}; };

View File

@ -22,7 +22,7 @@ export const ThunderClock = ({ io, slot }: ThunderClockProps) => {
const clock = new ThunderClockCard(); const clock = new ThunderClockCard();
io.setSlot(slot, clock); io.setSlot(slot, clock);
} }
}, [io]); }, [io, slot]);
return null; return null;
}; };

1172
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -40,7 +40,9 @@
"ajv": "^6.12.0", "ajv": "^6.12.0",
"babel-jest": "^27.2.4", "babel-jest": "^27.2.4",
"canvas": "^2.8.0", "canvas": "^2.8.0",
"eslint": "^8.15.0", "eslint": "^8.16.0",
"eslint-plugin-react": "^7.30.0",
"eslint-plugin-react-hooks": "^4.5.0",
"file-loader": "^6.0.0", "file-loader": "^6.0.0",
"jest": "^27.2.4", "jest": "^27.2.4",
"jest-image-snapshot": "^4.5.1", "jest-image-snapshot": "^4.5.1",