From 43122044d5b246496eff27463f3977ef5272016f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 13 Jun 2026 17:27:37 +0000 Subject: [PATCH 1/2] Fix ESLint and type-check failures Resolve all 92 ESLint errors reported by the Code Quality workflow: - Convert active @ts-ignore directives to @ts-expect-error, and drop the ones that were no longer suppressing a real type error - Replace explicit `any` with proper types (GameEventData event payload, generic getPreferenceValue, narrowed catch clauses, Storage.setItem) - Replace the `Function` type in render dialog options with `() => void` - Apply prefer-const / no-var fixes (including snow.ts and asset.ts) - Attach `cause` to the re-thrown error in the file-transformer plugin - Remove an unused constant and tidy unused parameters/catch bindings - Add scoped ESLint overrides for CommonJS Jest mocks and Cypress specs (Chai assertions and `any`-typed command declarations) --- cypress.config.ts | 2 +- cypress/support/commands/commands.ts | 2 +- eslint.config.mjs | 23 +++++++++++++ plugins/app-labels.ts | 2 +- plugins/file-transformer.ts | 4 ++- src/component/how-to-play/index.ts | 3 -- src/game.ts | 16 ++++++--- src/index.ts | 51 ++++++++++++++-------------- src/manager/animation.ts | 6 ++-- src/manager/asset.ts | 2 +- src/manager/undo.ts | 2 +- src/preferences.ts | 8 ++--- src/render.ts | 4 +-- src/share/browser.ts | 9 ++--- src/storage/cli.ts | 2 +- test/browser_storage_test.ts | 2 +- test/cli_storage_test.ts | 18 +++++----- vendor/snow.ts | 10 +++--- vite.config.ts | 4 +-- 19 files changed, 99 insertions(+), 71 deletions(-) diff --git a/cypress.config.ts b/cypress.config.ts index 1b0f223..4371e21 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -3,7 +3,7 @@ import { defineConfig } from "cypress"; export default defineConfig({ e2e: { baseUrl: "https://localhost:5173", - setupNodeEvents(on, config) { + setupNodeEvents(on, _config) { // implement node event listeners here // Log messages in command line output using Node.js runner diff --git a/cypress/support/commands/commands.ts b/cypress/support/commands/commands.ts index 3bd58c0..fcd12bd 100644 --- a/cypress/support/commands/commands.ts +++ b/cypress/support/commands/commands.ts @@ -70,7 +70,7 @@ Cypress.Commands.add("shouldNotBeActionable", { prevSubject: "element" }, (subje // Adapted from https://github.com/cypress-io/cypress/issues/877#issuecomment-490504922 Cypress.Commands.add("shouldBeInViewport", { prevSubject: true }, (subject) => { - // @ts-ignore TODO: Fix cy.state type error + // @ts-expect-error TODO: Fix cy.state type error const window = Cypress.$(cy.state("window")); const bottom = window.height(); const right = window.width(); diff --git a/eslint.config.mjs b/eslint.config.mjs index 140034e..686ad20 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -20,4 +20,27 @@ export default defineConfig( ], }, }, + // CommonJS Jest mocks (e.g. __mocks__/fs.js) use require/module.exports + { + files: ["__mocks__/**/*.js"], + languageOptions: { + sourceType: "commonjs", + globals: { + require: "readonly", + module: "writable", + }, + }, + rules: { + "@typescript-eslint/no-require-imports": "off", + }, + }, + // Cypress specs rely on Chai assertions (e.g. `expect(x).to.be.true`) and + // `any`-typed custom command declarations + { + files: ["cypress/**/*.ts"], + rules: { + "@typescript-eslint/no-unused-expressions": "off", + "@typescript-eslint/no-explicit-any": "off", + }, + }, ); diff --git a/plugins/app-labels.ts b/plugins/app-labels.ts index ca0349f..30fd5b5 100644 --- a/plugins/app-labels.ts +++ b/plugins/app-labels.ts @@ -31,7 +31,7 @@ export default function appIconLabel(options: PluginOptions) { const buildIcons = (env: string) => { try { childProcess.execSync(`${MAGICK_COMMAND} -version`, { stdio: "ignore" }); - } catch (e) { + } catch (_e) { console.warn("ImageMagick is not installed. Skipping icon labeling."); return; } diff --git a/plugins/file-transformer.ts b/plugins/file-transformer.ts index 9498448..2b5bb00 100644 --- a/plugins/file-transformer.ts +++ b/plugins/file-transformer.ts @@ -21,7 +21,9 @@ export default function fileTransformerPlugin({ input, transformer, output }) { try { fileContent = fs.readFileSync(filePath, "utf-8"); } catch (err) { - throw new Error(`Failed to read file: ${input} - ${err.message}`); + throw new Error(`Failed to read file: ${input} - ${(err as Error).message}`, { + cause: err, + }); } // Transform the content using the provided transformer function diff --git a/src/component/how-to-play/index.ts b/src/component/how-to-play/index.ts index 422aed1..03e6fc4 100644 --- a/src/component/how-to-play/index.ts +++ b/src/component/how-to-play/index.ts @@ -124,7 +124,6 @@ export class HowToPlay { ]; const step1Animation = new AnimationManager(); step1Animation.isAnimationEnabled = true; - // @ts-ignore renderBoard(stepSections[0], this.stepBoards[0], step1Animation, { blockStyle: "compact", }); @@ -159,7 +158,6 @@ export class HowToPlay { ]; const step2Animation = new AnimationManager(); step2Animation.isAnimationEnabled = true; - // @ts-ignore renderBoard(stepSections[1], this.stepBoards[1], step2Animation, { blockStyle: "compact", }); @@ -192,7 +190,6 @@ export class HowToPlay { ]; const step3Animation = new AnimationManager(); step3Animation.isAnimationEnabled = true; - // @ts-ignore renderBoard(stepSections[2], this.stepBoards[2], step3Animation, { blockStyle: "compact", }); diff --git a/src/game.ts b/src/game.ts index a45d404..d7c4229 100644 --- a/src/game.ts +++ b/src/game.ts @@ -29,15 +29,13 @@ export type Position = { y: number; }; -let debugEnabled = false; -// @ts-ignore TODO: Resolve this type issue "Property 'env' does not exist on type 'ImportMeta'." +const debugEnabled = false; +// TODO: Resolve this type issue "Property 'env' does not exist on type 'ImportMeta'." // TODO: Fix "SyntaxError: Cannot use 'import.meta' outside a module" when trying to run in Jest // Either restrict the usage of import.meta to the browser code only, bring in debugEnabled from there into game.ts // or, might have to bring in Babel. // let debugEnabled = import.meta.env.DEV ?? false; -const GAME_IS_OVER_ERROR_ID = "GameIsOver"; - export const DIRECTION_LEFT = 1; export const DIRECTION_RIGHT = 2; export const DIRECTION_UP = 3; @@ -74,7 +72,15 @@ export const getErrorMessage = (errorID: string) => { } }; -export type EventHandler = (eventID: string, data?: any) => void; +export type GameEventData = { + gameState?: GameState; + persistentState?: GamePersistentState; + undoInfo?: { + undoStack: readonly GameState[]; + }; +}; + +export type EventHandler = (eventID: string, data?: GameEventData) => void; let gameState: GameState = {} as GameState; let persistentState: GamePersistentState = {} as GamePersistentState; diff --git a/src/index.ts b/src/index.ts index e9b0203..34199d0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,7 @@ import { DIRECTION_UP, GameState, GamePersistentState, + GameEventData, } from "./game"; import { getPreferenceValue, initPreferences, savePreferenceValue } from "./preferences"; import { @@ -85,34 +86,34 @@ document.addEventListener("DOMContentLoaded", async () => { let gameState: GameState; let persistentState: GamePersistentState; - let spawnManager = new SpawnManager(); - let animationManager = new AnimationManager(); - let undoManager = new UndoManager(); - let gameStorage = new BrowserGameStorage(); + const spawnManager = new SpawnManager(); + const animationManager = new AnimationManager(); + const undoManager = new UndoManager(); + const gameStorage = new BrowserGameStorage(); const migrated = migrateLocalStorage_v1_3_1(); - let fullscreenManager = new FullscreenManager(gameStorage); - let assetManager = new AssetManager(document.querySelector(".loader-wrapper") as HTMLElement); - let actionIconManager = new ActionIconManager(); - let appIconManager = new AppIconManager(); - let themeManager = new ThemeManager(appIconManager); + const fullscreenManager = new FullscreenManager(gameStorage); + const assetManager = new AssetManager(document.querySelector(".loader-wrapper") as HTMLElement); + const actionIconManager = new ActionIconManager(); + const appIconManager = new AppIconManager(); + const themeManager = new ThemeManager(appIconManager); setThemeManager(themeManager); // Set the global theme manager reference // Store unlockable statuses so that their unlock messages don't display again if player achieved the same conditions again let unlockedClassic = false; let unlockedInitialCommit = false; - let tutorial: Tutorial = new Tutorial(); - let howToPlay: HowToPlay = new HowToPlay(); + const tutorial: Tutorial = new Tutorial(); + const howToPlay: HowToPlay = new HowToPlay(); const swipeSensitivity = 50; const md = new MobileDetect(window.navigator.userAgent); const isMobile = md.mobile() !== null; - const eventHandler = (event: string, data: any) => { + const eventHandler = (event: string, data?: GameEventData) => { switch (event) { case "init": - gameState = data.gameState; - persistentState = data.persistentState; + gameState = data!.gameState!; + persistentState = data!.persistentState!; animationManager.isAnimationEnabled = isAnimationEnabled; unlockedClassic = persistentState.unlockables.classic; unlockedInitialCommit = persistentState.unlockables.initialCommit; @@ -134,7 +135,7 @@ document.addEventListener("DOMContentLoaded", async () => { persistentState.highscore.toString(); (document.querySelector("#moveCount") as HTMLSpanElement).innerText = gameState.moveCount.toString(); - if (data.undoInfo) { + if (data?.undoInfo) { if (data.undoInfo.undoStack.length > 0) { undoButton.classList.remove("disabled"); } else { @@ -200,15 +201,15 @@ document.addEventListener("DOMContentLoaded", async () => { }); const dialog = document.querySelector(".dialog") as HTMLElement; dialog.classList.add("win"); - if (!unlockedClassic && data.persistentState.unlockables.classic) { + if (!unlockedClassic && data!.persistentState!.unlockables.classic) { renderNotification("2048Clone theme unlocked", 2500); unlockedClassic = true; } - if (!unlockedInitialCommit && data.persistentState.unlockables.initialCommit) { + if (!unlockedInitialCommit && data!.persistentState!.unlockables.initialCommit) { renderNotification("Initial Commit tileset unlocked", 2500); unlockedInitialCommit = true; } - persistentState = data.persistentState; + persistentState = data!.persistentState!; const shareText = generateShareText(gameState); shareButton.addEventListener("click", async (e) => { e.preventDefault(); @@ -436,7 +437,7 @@ document.addEventListener("DOMContentLoaded", async () => { closeDialog(dialog, overlayBackElem); }); - let snowEmbed = document.getElementById("embedim--snow"); + const snowEmbed = document.getElementById("embedim--snow"); if (snowEmbed) snowEmbed.style.display = "none"; const selectableThemes = [STANDARD_THEME, LIGHT_THEME, DARK_THEME, SNOW_THEME, CLASSIC_THEME]; @@ -502,7 +503,6 @@ document.addEventListener("DOMContentLoaded", async () => { setting.addEventListener("click", (e) => { const elem = e.target as HTMLElement; const toggle = setting.querySelector(".toggle") as HTMLElement; - let enabled = false; if (elem.classList.contains(THEME_SETTING_NAME)) { const themeIndex = selectableThemes.indexOf(themeManager.getCurrentTheme()); let nextTheme = selectableThemes[(themeIndex + 1) % selectableThemes.length]; @@ -561,7 +561,7 @@ document.addEventListener("DOMContentLoaded", async () => { toggle.innerText = formatTilesetName(nextTileset); } else if (elem.classList.contains(ANIMATIONS_SETTING_NAME)) { const knob = setting.querySelector(".knob") as HTMLElement; - enabled = isAnimationEnabled = !isAnimationEnabled; + const enabled = (isAnimationEnabled = !isAnimationEnabled); animationManager.isAnimationEnabled = isAnimationEnabled; savePreferenceValue( ANIMATIONS_PREFERENCE_NAME, @@ -575,7 +575,7 @@ document.addEventListener("DOMContentLoaded", async () => { } else if (elem.classList.contains(BLOCK_STYLE_SETTING_NAME)) { const currentBlockStyle = themeManager.getCurrentBlockStyle(); const blockStyleIndex = selectableBlockStyles.indexOf(currentBlockStyle); - let nextBlockStyle = + const nextBlockStyle = selectableBlockStyles[(blockStyleIndex + 1) % selectableBlockStyles.length]; themeManager.switchBlockStyle(nextBlockStyle); handlePostBlockStyleSwitch(); @@ -626,7 +626,7 @@ document.addEventListener("DOMContentLoaded", async () => { const currentTheme = themeManager.getCurrentTheme(); (themeSetting.querySelector(".toggle") as HTMLElement).innerText = currentTheme === "classic" ? CLASSIC_THEME_LABEL : currentTheme; - let tilesetPreferences = getPreferenceValue(TILESET_PREFERENCE_NAME); + let tilesetPreferences = getPreferenceValue>(TILESET_PREFERENCE_NAME); if (tilesetPreferences) { themeManager.switchTileset(currentTheme, tilesetPreferences[currentTheme]); } @@ -1078,7 +1078,6 @@ document.addEventListener("DOMContentLoaded", async () => { // Session Replay replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur. - // @ts-ignore TODO: Fix type issue with event param beforeSend(event) { if ( event.request && @@ -1128,13 +1127,13 @@ document.addEventListener("DOMContentLoaded", async () => { } await initGame(eventHandler, spawnManager, animationManager, undoManager, gameStorage); - } catch (e: any) { + } catch (e) { if (typeof Sentry !== "undefined") Sentry.captureException(e); const elem = createDialogContentFromTemplate("#error-dialog-content"); const errorContent = elem.querySelector(".error-text") as HTMLElement; console.error("Could not initialize game due to error:", e); - errorContent.innerText = e.message; + errorContent.innerText = (e as Error).message; renderDialog(elem, { fadeIn: true, diff --git a/src/manager/animation.ts b/src/manager/animation.ts index 9e3976a..9ee003a 100644 --- a/src/manager/animation.ts +++ b/src/manager/animation.ts @@ -16,11 +16,11 @@ export type MergedBlock = { export class AnimationManager { public isAnimationEnabled: boolean; - // @ts-ignore TODO: This field is assigned in the constructor via resetState but TS is not smart enough to realize that + // @ts-expect-error TODO: This field is assigned in the constructor via resetState but TS is not smart enough to realize that public newBlocks: Position[]; - // @ts-ignore TODO: This field is assigned in the constructor via resetState but TS is not smart enough to realize that + // @ts-expect-error TODO: This field is assigned in the constructor via resetState but TS is not smart enough to realize that public movedBlocks: (Position | undefined)[][]; - // @ts-ignore TODO: This field is assigned in the constructor via resetState but TS is not smart enough to realize that + // @ts-expect-error TODO: This field is assigned in the constructor via resetState but TS is not smart enough to realize that public mergedBlocks: MergedBlock[]; private gameState: GameState | null = null; diff --git a/src/manager/asset.ts b/src/manager/asset.ts index ac14553..4941d3f 100644 --- a/src/manager/asset.ts +++ b/src/manager/asset.ts @@ -32,7 +32,7 @@ export class AssetManager { onProgressCallback: (progress: number) => void, ): Promise { return new Promise((resolve, reject) => { - var loadedCount = 0; + let loadedCount = 0; const onAssetLoaded = (url: string) => { loadedCount++; diff --git a/src/manager/undo.ts b/src/manager/undo.ts index 4bfac39..00ef37c 100644 --- a/src/manager/undo.ts +++ b/src/manager/undo.ts @@ -50,7 +50,7 @@ export class UndoManager { // Object is possibly 'null'.ts(2531) // Type 'number | boolean | GameBoard' is not assignable to type 'never'. // Type 'number' is not assignable to type 'never'.ts(2322) - // @ts-ignore + // @ts-expect-error TODO: indexed assignment type mismatch (see type errors above) this.gameState[key] = gameStateCopy[key]; }); return gameStateCopy; diff --git a/src/preferences.ts b/src/preferences.ts index ee3ec60..936a394 100644 --- a/src/preferences.ts +++ b/src/preferences.ts @@ -1,7 +1,7 @@ import { IGameStorage } from "./storage"; export class Preferences { - [key: string]: any; + [key: string]: unknown; } let preferences: Preferences = {}; @@ -16,11 +16,11 @@ export const initPreferences = (_gameStorage: IGameStorage, initialPreferences: } }; -export const getPreferenceValue = (key: string) => { - return preferences[key]; +export const getPreferenceValue = (key: string): T => { + return preferences[key] as T; }; -export const savePreferenceValue = (key: string, value: any) => { +export const savePreferenceValue = (key: string, value: unknown) => { preferences[key] = value; gameStorage.savePreferences(preferences); }; diff --git a/src/render.ts b/src/render.ts index 14a43bb..d1d5d41 100644 --- a/src/render.ts +++ b/src/render.ts @@ -272,8 +272,8 @@ export const renderDialog = (content: HTMLElement | DocumentFragment, options?: export type PromptDialogOptions = { fadeIn?: boolean; style?: CSS.Properties; - onConfirm?: Function; - onCancel?: Function; + onConfirm?: () => void; + onCancel?: () => void; }; export const renderPromptDialog = ( diff --git a/src/share/browser.ts b/src/share/browser.ts index 9df0f7e..71828de 100644 --- a/src/share/browser.ts +++ b/src/share/browser.ts @@ -96,14 +96,15 @@ export const triggerShare = async (shareText: string) => { } try { await navigator.share(data); - } catch (err: any) { - if (err.name === "NotAllowedError") { + } catch (err) { + const error = err as Error; + if (error.name === "NotAllowedError") { console.log("Sharing was not allowed by the user or platform"); // Fallback to copy to clipboard return copyShareText(shareText); - } else if (err.name === "AbortError") { + } else if (error.name === "AbortError") { console.log("User aborted share operation"); - } else if (err.name === "NotSupportedError") { + } else if (error.name === "NotSupportedError") { console.error("Share sheet operation not supported"); // Fallback to copy to clipboard return copyShareText(shareText); diff --git a/src/storage/cli.ts b/src/storage/cli.ts index bb601f8..197ef19 100644 --- a/src/storage/cli.ts +++ b/src/storage/cli.ts @@ -72,7 +72,7 @@ export class CLIGameStorage implements IGameStorage { loadFile: (filename: string) => T = (filename) => { try { const jsonStr = fs.readFileSync(filename); - // @ts-ignore TODO: Resolve Buffer cannot be assigned to string param type issue + // @ts-expect-error TODO: Resolve Buffer cannot be assigned to string param type issue const json = JSON.parse(jsonStr); if (typeof json !== "object") { return {}; diff --git a/test/browser_storage_test.ts b/test/browser_storage_test.ts index d05162f..f59edaa 100644 --- a/test/browser_storage_test.ts +++ b/test/browser_storage_test.ts @@ -17,7 +17,7 @@ import { IGameStorage } from "../src/storage"; global.window = {} as Window & typeof globalThis; class MockStorage { - setItem: (key: string, value: any) => void = (_keyName, _keyValue) => {}; + setItem: (key: string, value: string) => void = (_keyName, _keyValue) => {}; getItem: (key: string) => string = (_keyName) => ""; removeItem: (key: string) => void = (_keyName) => {}; clear: () => void = () => {}; diff --git a/test/cli_storage_test.ts b/test/cli_storage_test.ts index 5652449..420b953 100644 --- a/test/cli_storage_test.ts +++ b/test/cli_storage_test.ts @@ -64,7 +64,7 @@ describe("CLI storage", () => { }, ); - // @ts-ignore TODO: fix Buffer to string type error + // @ts-expect-error TODO: fix Buffer to string type error assert.deepStrictEqual(JSON.parse(stateContents), initialState); gameStorage.saveGame(expectedState); @@ -73,7 +73,7 @@ describe("CLI storage", () => { encoding: "utf-8", }); - // @ts-ignore TODO: fix Buffer to string type error + // @ts-expect-error TODO: fix Buffer to string type error assert.deepStrictEqual(JSON.parse(stateContents), expectedState); }); @@ -134,7 +134,7 @@ describe("CLI storage", () => { it("should load default values if no previous state was stored", () => { vol.reset(); - let state = gameStorage.loadGame(); + const state = gameStorage.loadGame(); assert.deepStrictEqual(state, {}); }); @@ -168,7 +168,7 @@ describe("CLI storage", () => { }, ); - // @ts-ignore TODO: fix Buffer to string type error + // @ts-expect-error TODO: fix Buffer to string type error assert.deepStrictEqual(JSON.parse(stateContents), initialPersistentState); gameStorage.savePersistentState(expectedState); @@ -180,7 +180,7 @@ describe("CLI storage", () => { }, ); - // @ts-ignore TODO: fix Buffer to string type error + // @ts-expect-error TODO: fix Buffer to string type error assert.deepStrictEqual(JSON.parse(stateContents), expectedState); }); @@ -223,7 +223,7 @@ describe("CLI storage", () => { it("should load default values if no previous state was stored", () => { vol.reset(); - let state = gameStorage.loadGame(); + const state = gameStorage.loadGame(); assert.deepStrictEqual(state, {}); }); @@ -254,7 +254,7 @@ describe("CLI storage", () => { }, ); - // @ts-ignore TODO: fix Buffer to string type error + // @ts-expect-error TODO: fix Buffer to string type error assert.deepStrictEqual(JSON.parse(preferencesFileContents), preferences); }); @@ -288,7 +288,7 @@ describe("CLI storage", () => { }, ); - // @ts-ignore TODO: fix Buffer to string type error + // @ts-expect-error TODO: fix Buffer to string type error assert.deepStrictEqual(JSON.parse(preferencesFileContents), expectedPreferences); gameStorage.clearPreferences(); @@ -300,7 +300,7 @@ describe("CLI storage", () => { }, ); - // @ts-ignore TODO: fix Buffer to string type error + // @ts-expect-error TODO: fix Buffer to string type error assert.deepStrictEqual(JSON.parse(preferencesFileContents), {}); }); diff --git a/vendor/snow.ts b/vendor/snow.ts index 928b379..1f95421 100644 --- a/vendor/snow.ts +++ b/vendor/snow.ts @@ -19,7 +19,7 @@ - Converted from JavaScript to TypeScript */ const zIndex = -10; -var embedimSnow = document.getElementById("embedim--snow"); +let embedimSnow = document.getElementById("embedim--snow"); if (!embedimSnow) { function embRand(a, b) { return Math.floor(Math.random() * (b - a + 1)) + a; @@ -29,7 +29,7 @@ if (!embedimSnow) { let snowCSS = ""; for (let i = 1; i < 200; i++) { snowHTML += ''; - var rndX = embRand(0, 1000000) * 0.0001, + const rndX = embRand(0, 1000000) * 0.0001, rndO = embRand(-100000, 100000) * 0.0001, rndT = (embRand(3, 8) * 10).toFixed(2), rndS = (embRand(0, 10000) * 0.0001).toFixed(2); @@ -88,9 +88,9 @@ if (!embedimSnow) { "" + snowElements.html; }; - var baseEmbCSS = + const baseEmbCSS = ".embedim-snow{position: absolute;width: 10px;height: 10px;background: white;border-radius: 50%;margin-top:-10px}"; - var snowElements = generateSnowflakeElements(); + let snowElements = generateSnowflakeElements(); embedimSnow = document.createElement("div"); embedimSnow.id = "embedim--snow"; @@ -99,7 +99,7 @@ if (!embedimSnow) { document.body.appendChild(embedimSnow); - addEventListener("resize", (e) => { + addEventListener("resize", () => { snowElements = generateSnowflakeElements(); setSnowElementInnerHTML(); }); diff --git a/vite.config.ts b/vite.config.ts index 28749f1..fe66b49 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -11,7 +11,7 @@ import { removeCanonicalInDev } from "./plugins/remove-canonical-in-dev"; const commitHash = childProcess.execSync("git rev-parse --short HEAD").toString(); -// @ts-ignore Resolve type issue with function parameter +// @ts-expect-error Resolve type issue with function parameter export default defineConfig(({ mode }) => { // Load env file based on `mode` in the current working directory. // Set the third parameter to '' to load all env regardless of the `VITE_` prefix. @@ -25,7 +25,7 @@ export default defineConfig(({ mode }) => { build: { outDir: path.resolve(__dirname, "build"), terserOptions: { - // @ts-ignore TODO: Resolve type error with this field + // @ts-expect-error TODO: Resolve type error with this field ecma: 6, compress: { drop_console: true }, output: { comments: false, beautify: false }, From 61faa4e77cd6c79da2cead266910dacfb904045a Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 13 Jun 2026 17:55:01 +0000 Subject: [PATCH 2/2] Address review: prefer real types over ts suppressions - cli.ts: read state files as UTF-8 text so JSON.parse receives a string, removing the Buffer-related @ts-expect-error - vite.config.ts: use a valid Terser ECMA year (2015) instead of `6`, removing the @ts-expect-error - animation.ts: use definite assignment assertions for fields initialized via resetState() instead of @ts-expect-error suppressions - index.ts: drop the redundant `enabled` local and branch on isAnimationEnabled directly --- src/index.ts | 4 ++-- src/manager/animation.ts | 9 +++------ src/storage/cli.ts | 3 +-- vite.config.ts | 3 +-- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/index.ts b/src/index.ts index 34199d0..9b20b18 100644 --- a/src/index.ts +++ b/src/index.ts @@ -561,13 +561,13 @@ document.addEventListener("DOMContentLoaded", async () => { toggle.innerText = formatTilesetName(nextTileset); } else if (elem.classList.contains(ANIMATIONS_SETTING_NAME)) { const knob = setting.querySelector(".knob") as HTMLElement; - const enabled = (isAnimationEnabled = !isAnimationEnabled); + isAnimationEnabled = !isAnimationEnabled; animationManager.isAnimationEnabled = isAnimationEnabled; savePreferenceValue( ANIMATIONS_PREFERENCE_NAME, isAnimationEnabled ? SETTING_ENABLED : SETTING_DISABLED, ); - if (enabled) { + if (isAnimationEnabled) { knob.classList.add("enabled"); } else { knob.classList.remove("enabled"); diff --git a/src/manager/animation.ts b/src/manager/animation.ts index 9ee003a..82a3578 100644 --- a/src/manager/animation.ts +++ b/src/manager/animation.ts @@ -16,12 +16,9 @@ export type MergedBlock = { export class AnimationManager { public isAnimationEnabled: boolean; - // @ts-expect-error TODO: This field is assigned in the constructor via resetState but TS is not smart enough to realize that - public newBlocks: Position[]; - // @ts-expect-error TODO: This field is assigned in the constructor via resetState but TS is not smart enough to realize that - public movedBlocks: (Position | undefined)[][]; - // @ts-expect-error TODO: This field is assigned in the constructor via resetState but TS is not smart enough to realize that - public mergedBlocks: MergedBlock[]; + public newBlocks!: Position[]; + public movedBlocks!: (Position | undefined)[][]; + public mergedBlocks!: MergedBlock[]; private gameState: GameState | null = null; diff --git a/src/storage/cli.ts b/src/storage/cli.ts index 197ef19..2cca382 100644 --- a/src/storage/cli.ts +++ b/src/storage/cli.ts @@ -71,8 +71,7 @@ export class CLIGameStorage implements IGameStorage { loadFile: (filename: string) => T = (filename) => { try { - const jsonStr = fs.readFileSync(filename); - // @ts-expect-error TODO: Resolve Buffer cannot be assigned to string param type issue + const jsonStr = fs.readFileSync(filename, "utf8"); const json = JSON.parse(jsonStr); if (typeof json !== "object") { return {}; diff --git a/vite.config.ts b/vite.config.ts index fe66b49..6b57a66 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -25,8 +25,7 @@ export default defineConfig(({ mode }) => { build: { outDir: path.resolve(__dirname, "build"), terserOptions: { - // @ts-expect-error TODO: Resolve type error with this field - ecma: 6, + ecma: 2015, compress: { drop_console: true }, output: { comments: false, beautify: false }, },