diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 5b93972..0000000 --- a/.eslintignore +++ /dev/null @@ -1,60 +0,0 @@ -# Logs -logs -*.log - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release -.eslintcache - -# Dependency directory -# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git -node_modules -app/node_modules - -# OSX -.DS_Store - -# Build outputs -release -out -dist -dll - -# Old webpack build artifacts -app/*.main.prod.js -app/main.prod.js -app/main.prod.js.map -app/renderer.prod.js -app/renderer.prod.js.map -app/style.css -app/style.css.map -main.js -main.js.map - -.idea -npm-debug.log.* -__snapshots__ - -# Package.json -package.json -.travis.yml -*.css.d.ts -*.sass.d.ts -*.scss.d.ts diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 21fc4ab..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,52 +0,0 @@ -module.exports = { - extends: 'erb/typescript', - rules: { - '@typescript-eslint/ban-ts-comment': 'warn', - '@typescript-eslint/no-use-before-define': 'off', - '@typescript-eslint/naming-convention': 'off', - 'consistent-return': 'warn', - 'dot-notation': 'off', - 'import/no-extraneous-dependencies': 'off', - 'import/prefer-default-export': 'off', - 'import/no-cycle': 'warn', - 'jsx-a11y/anchor-is-valid': 'warn', - 'jsx-a11y/click-events-have-key-events': 'warn', - 'lines-between-class-members': 'off', - 'max-classes-per-file': 'off', - 'no-param-reassign': 'off', - 'no-plusplus': 'off', - 'no-restricted-syntax': 'off', - 'prefer-destructuring': 'warn', - 'promise/no-return-wrap': 'warn', - 'react/destructuring-assignment': 'off', - 'react/jsx-curly-newline': 'off', - 'react/jsx-one-expression-per-line': 'off', - 'react/jsx-props-no-spreading': 'off', - 'react/jsx-wrap-multilines': 'off', - 'react/no-access-state-in-setstate': 'warn', - 'react/no-array-index-key': 'off', - 'react/no-did-update-set-state': 'warn', - 'react/no-will-update-set-state': 'warn', - 'react/prop-types': 'off', - 'react/static-property-placement': 'off', - }, - parserOptions: { - ecmaVersion: 2020, - sourceType: 'module', - project: './tsconfig.json', - tsconfigRootDir: __dirname, - createDefaultProgram: true, - }, - settings: { - 'import/resolver': { - // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below - node: {}, - webpack: { - config: require.resolve('./configs/webpack.config.eslint.js'), - }, - }, - 'import/parsers': { - '@typescript-eslint/parser': ['.ts', '.tsx'], - }, - }, -}; diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..116779a --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,174 @@ +import js from '@eslint/js'; +import tsPlugin from '@typescript-eslint/eslint-plugin'; +import tsParser from '@typescript-eslint/parser'; +import reactPlugin from 'eslint-plugin-react'; +import reactHooksPlugin from 'eslint-plugin-react-hooks'; +import importPlugin from 'eslint-plugin-import'; +import jsxA11yPlugin from 'eslint-plugin-jsx-a11y'; +import prettierPlugin from 'eslint-plugin-prettier'; +import prettierConfig from 'eslint-config-prettier'; +import globals from 'globals'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +export default [ + // --- Global ignores (replaces .eslintignore) --- + { + ignores: [ + 'node_modules/**', + 'out/**', + 'release/**', + 'dist/**', + 'coverage/**', + 'src/renderer/utils/pyodide/src/**', + '**/*.css.d.ts', + '**/*.scss.d.ts', + ], + }, + + // --- Base JS recommended rules --- + js.configs.recommended, + + // --- TypeScript-eslint flat/recommended (array of 3 config objects) --- + // Sets up @typescript-eslint parser + plugin globally and TS-specific rules. + ...tsPlugin.configs['flat/recommended'], + + // --- React flat recommended --- + { + ...reactPlugin.configs.flat.recommended, + settings: { react: { version: 'detect' } }, + }, + + // --- React hooks recommended (flat config) --- + reactHooksPlugin.configs['recommended-latest'], + + // --- TypeScript + React source files --- + { + files: ['src/**/*.{ts,tsx}', 'internals/**/*.ts'], + languageOptions: { + parser: tsParser, + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + tsconfigRootDir: __dirname, + }, + globals: { + ...globals.browser, + ...globals.node, + ...globals.es2020, + }, + }, + plugins: { + import: importPlugin, + 'jsx-a11y': jsxA11yPlugin, + prettier: prettierPlugin, + }, + settings: { + react: { version: 'detect' }, + 'import/parsers': { + '@typescript-eslint/parser': ['.ts', '.tsx'], + }, + 'import/resolver': { node: {} }, + }, + rules: { + // jsx-a11y recommended rules + ...jsxA11yPlugin.configs.recommended.rules, + + // Prettier formatting + ...prettierConfig.rules, + 'prettier/prettier': 'warn', + + // Downgrade from error → warn for gradual adoption + '@typescript-eslint/ban-ts-comment': 'warn', + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': 'warn', + + // Disabled from old config + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/naming-convention': 'off', + 'consistent-return': 'warn', + 'dot-notation': 'off', + 'import/no-extraneous-dependencies': 'off', + 'import/prefer-default-export': 'off', + 'import/no-cycle': 'warn', + 'jsx-a11y/anchor-is-valid': 'warn', + 'jsx-a11y/click-events-have-key-events': 'warn', + 'jsx-a11y/no-autofocus': 'warn', + 'react/no-unknown-property': 'warn', + 'lines-between-class-members': 'off', + 'max-classes-per-file': 'off', + 'no-param-reassign': 'off', + 'no-plusplus': 'off', + 'no-restricted-syntax': 'off', + 'prefer-destructuring': 'warn', + 'react/destructuring-assignment': 'off', + 'react/jsx-curly-newline': 'off', + 'react/jsx-one-expression-per-line': 'off', + 'react/jsx-props-no-spreading': 'off', + 'react/jsx-wrap-multilines': 'off', + 'react/no-access-state-in-setstate': 'warn', + 'react/no-array-index-key': 'off', + 'react/no-did-update-set-state': 'warn', + 'react/no-will-update-set-state': 'warn', + 'react/prop-types': 'off', + 'react/static-property-placement': 'off', + }, + }, + + // --- Web Worker file — add worker globals so importScripts/self/etc. are known --- + { + files: ['src/**/webworker.js'], + languageOptions: { + globals: { + ...globals.worker, + ...globals.es2020, + loadPyodide: 'readonly', + }, + }, + }, + + // --- Plain JS/JSX source files --- + { + files: ['src/**/*.{js,jsx}'], + languageOptions: { + ecmaVersion: 2020, + sourceType: 'module', + globals: { + ...globals.browser, + ...globals.node, + ...globals.es2020, + }, + }, + plugins: { + prettier: prettierPlugin, + }, + settings: { + react: { version: 'detect' }, + }, + rules: { + ...prettierConfig.rules, + 'prettier/prettier': 'warn', + '@typescript-eslint/no-unused-vars': 'warn', + 'no-param-reassign': 'off', + 'no-plusplus': 'off', + }, + }, + + // --- Internals scripts — Node.js, more relaxed --- + { + files: ['internals/**/*.{js,mjs,ts}'], + languageOptions: { + globals: { + ...globals.node, + ...globals.es2020, + }, + }, + rules: { + 'no-console': 'off', + '@typescript-eslint/no-require-imports': 'off', + 'import/no-extraneous-dependencies': 'off', + }, + }, +]; diff --git a/internals/scripts/.eslintrc b/internals/scripts/.eslintrc deleted file mode 100644 index 35dc618..0000000 --- a/internals/scripts/.eslintrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "rules": { - "no-console": "off", - "global-require": "off", - "import/no-dynamic-require": "off", - "import/no-extraneous-dependencies": "off" - } -} diff --git a/package.json b/package.json index d27928a..bdff487 100644 --- a/package.json +++ b/package.json @@ -16,12 +16,12 @@ "package-linux": "npm run build && electron-builder build --linux", "package-win": "npm run build && electron-builder build --win --x64", "postinstall": "electron-builder install-app-deps && node internals/scripts/InstallPyodide.js && node internals/scripts/patchDeps.mjs", - "lint": "cross-env NODE_ENV=development eslint . --cache --ext .js,.jsx,.ts,.tsx", + "lint": "cross-env NODE_ENV=development eslint . --cache", "lint-fix": "npm run lint -- --fix", - "lint-styles": "stylelint --ignore-path .eslintignore '**/*.*(css|scss)' --syntax scss", + "lint-styles": "stylelint '**/*.*(css|scss)'", "lint-styles-fix": "npm run lint-styles -- --fix; exit 0", - "postlint-fix": "prettier --ignore-path .eslintignore --single-quote --write '**/*.{js,jsx,json,html,css,less,scss,yml}'", - "postlint-styles-fix": "prettier --ignore-path .eslintignore --single-quote --write '**/*.{css,scss}'", + "postlint-fix": "prettier --single-quote --write '**/*.{js,jsx,json,html,css,less,scss,yml}'", + "postlint-styles-fix": "prettier --single-quote --write '**/*.{css,scss}'", "test": "cross-env jest --passWithNoTests", "test-all": "npm run lint && npm run tsc && npm run build && npm test", "tsc": "tsc --noEmit" @@ -30,15 +30,15 @@ "*.{js,jsx,ts,tsx}": [ "cross-env NODE_ENV=development eslint --cache" ], - "{*.json,.{eslintrc,prettierrc,stylelintrc}}": [ - "prettier --ignore-path .eslintignore --parser json --write" + "{*.json,.{prettierrc,stylelintrc}}": [ + "prettier --parser json --write" ], "*.{css,scss}": [ - "stylelint --ignore-path .eslintignore --syntax scss --fix", - "prettier --ignore-path .eslintignore --single-quote --write" + "stylelint --fix", + "prettier --single-quote --write" ], "*.{html,md,yml}": [ - "prettier --ignore-path .eslintignore --single-quote --write" + "prettier --single-quote --write" ] }, "build": { @@ -135,7 +135,10 @@ ], "homepage": "https://github.com/makebrainwaves/BrainWaves/", "jest": { - "testURL": "http://localhost/", + "testEnvironment": "node", + "testEnvironmentOptions": { + "url": "http://localhost/" + }, "moduleNameMapper": { "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/internals/mocks/fileMock.js", "\\.(css|less|sass|scss)$": "identity-obj-proxy" @@ -149,14 +152,13 @@ ], "moduleDirectories": [ "node_modules" - ], - "setupFiles": [] + ] }, "devDependencies": { "@babel/plugin-proposal-class-properties": "^7.10.4", "@babel/plugin-proposal-decorators": "^7.10.5", "@types/history": "^4.7.6", - "@types/jest": "^26.0.5", + "@types/jest": "^30.0.0", "@types/node": "^22.12.0", "@types/react": "^16.9.38", "@types/react-dom": "^16.9.8", diff --git a/src/renderer/constants/constants.ts b/src/renderer/constants/constants.ts index d8d3047..1b87090 100644 --- a/src/renderer/constants/constants.ts +++ b/src/renderer/constants/constants.ts @@ -51,14 +51,17 @@ export enum PYODIDE_VARIABLE_NAMES { export const SEARCH_TIMER = 3000; -// NOTE: the actual marker id values of stimulus 1 and 2 are reversed +// NOTE: TARGET/NONTARGET are intentional semantic aliases for STIMULUS_2/STIMULUS_1 +// because the actual marker id values of stimulus 1 and 2 are reversed. export enum EVENTS { STIMULUS_1 = 1, STIMULUS_2 = 2, STIMULUS_3 = 3, STIMULUS_4 = 4, + /* eslint-disable @typescript-eslint/no-duplicate-enum-values */ TARGET = 2, NONTARGET = 1, + /* eslint-enable @typescript-eslint/no-duplicate-enum-values */ } export const CHANNELS = { diff --git a/src/renderer/store/configureStore.dev.js b/src/renderer/store/configureStore.dev.js deleted file mode 100644 index bdd8605..0000000 --- a/src/renderer/store/configureStore.dev.js +++ /dev/null @@ -1,76 +0,0 @@ -import { createStore, applyMiddleware, compose } from 'redux'; -import thunk from 'redux-thunk'; -import { createEpicMiddleware } from 'redux-observable'; -import { createHashHistory } from 'history'; -import { routerMiddleware, routerActions } from 'react-router-redux'; -import { createLogger } from 'redux-logger'; -import rootReducer from '../reducers'; -import rootEpic from '../epics'; -import * as pyodideActions from '../actions/pyodideActions'; -import * as deviceActions from '../actions/deviceActions'; - -const history = createHashHistory(); - -const configureStore = (initialState?: AppState) => { - // Redux Configuration - const middleware = []; - const enhancers = []; - - // Thunk Middleware - middleware.push(thunk); - - // Redux Observable (Epic) Middleware - const epicMiddleware = createEpicMiddleware(); - middleware.push(epicMiddleware); - - // Logging Middleware - const logger = createLogger({ - level: 'info', - collapsed: true, - }); - - // Skip redux logs in console during the tests - if (import.meta.env.MODE !== 'test') { - middleware.push(logger); - } - - // Router Middleware - const router = routerMiddleware(history); - middleware.push(router); - - // Redux DevTools Configuration - const actionCreators = { - ...deviceActions, - ...pyodideActions, - ...routerActions, - }; - // If Redux DevTools Extension is installed use it, otherwise use Redux compose - /* eslint-disable no-underscore-dangle */ - const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ - ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ - // Options: http://zalmoxisus.github.io/redux-devtools-extension/API/Arguments.html - actionCreators, - }) - : compose; - /* eslint-enable no-underscore-dangle */ - - // Apply Middleware & Compose Enhancers - enhancers.push(applyMiddleware(...middleware)); - const enhancer = composeEnhancers(...enhancers); - - // Create Store - const store = createStore(rootReducer, initialState, enhancer); - - if (import.meta.hot) { - import.meta.hot.accept('../reducers', (newModule) => { - store.replaceReducer(newModule?.default ?? newModule); - }); - } - - // Redux Observable - epicMiddleware.run(rootEpic); - - return store; -}; - -export default { configureStore, history }; diff --git a/test/.eslintrc.json b/test/.eslintrc.json deleted file mode 100644 index 9adaad7..0000000 --- a/test/.eslintrc.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "plugin:testcafe/recommended", - "env": { - "jest/globals": true - }, - "plugins": ["jest", "testcafe"], - "rules": { - "jest/no-disabled-tests": "warn", - "jest/no-focused-tests": "error", - "jest/no-identical-title": "error", - "no-console": "off" - } -}