Stricter compilation rules, better linting

This commit is contained in:
2025-07-19 11:34:05 +02:00
parent cb11398b3e
commit 161916e5ca
11 changed files with 247 additions and 47 deletions

10
.prettierrc.json Normal file
View File

@ -0,0 +1,10 @@
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"printWidth": 100,
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "avoid",
"endOfLine": "lf"
}

View File

@ -12,12 +12,92 @@ export default tseslint.config([
extends: [ extends: [
js.configs.recommended, js.configs.recommended,
tseslint.configs.recommended, tseslint.configs.recommended,
tseslint.configs.strict,
tseslint.configs.stylistic,
reactHooks.configs['recommended-latest'], reactHooks.configs['recommended-latest'],
reactRefresh.configs.vite, reactRefresh.configs.vite,
], ],
languageOptions: { languageOptions: {
ecmaVersion: 2020, ecmaVersion: 2020,
globals: globals.browser, globals: globals.browser,
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/explicit-function-return-type': ['warn', {
allowExpressions: true,
allowTypedFunctionExpressions: true,
allowHigherOrderFunctions: true,
allowDirectConstAssertionInArrowFunctions: true,
allowConciseArrowFunctionExpressionsStartingWithVoid: true,
}],
'@typescript-eslint/no-non-null-assertion': 'warn',
'@typescript-eslint/strict-boolean-expressions': ['warn', {
allowNullableObject: true,
allowNullableBoolean: true,
allowNullableString: true,
allowNullableNumber: false,
allowAny: false,
}],
'@typescript-eslint/no-unnecessary-condition': 'error',
'@typescript-eslint/no-unsafe-assignment': 'error',
'@typescript-eslint/no-unsafe-member-access': 'error',
'@typescript-eslint/no-unsafe-call': 'error',
'@typescript-eslint/no-unsafe-return': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/await-thenable': 'error',
'@typescript-eslint/require-await': 'error',
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
'@typescript-eslint/prefer-readonly': 'error',
'@typescript-eslint/array-type': ['error', { default: 'array-simple' }],
'@typescript-eslint/consistent-type-imports': ['error', {
prefer: 'type-imports',
disallowTypeAnnotations: true,
fixStyle: 'separate-type-imports',
}],
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'variable',
format: ['camelCase', 'PascalCase', 'UPPER_CASE'],
},
{
selector: 'function',
format: ['camelCase', 'PascalCase'],
},
{
selector: 'typeLike',
format: ['PascalCase'],
},
{
selector: 'enum',
format: ['PascalCase'],
},
{
selector: 'enumMember',
format: ['UPPER_CASE'],
},
],
'eqeqeq': ['error', 'always'],
'no-console': 'warn',
'no-debugger': 'error',
'no-alert': 'error',
'prefer-const': 'error',
'no-var': 'error',
'object-shorthand': 'error',
'prefer-template': 'error',
'prefer-destructuring': ['error', {
array: true,
object: true,
}],
'no-nested-ternary': 'error',
'no-unneeded-ternary': 'error',
'curly': ['error', 'all'],
'brace-style': ['error', '1tbs'],
}, },
}, },
]) ])

View File

@ -7,6 +7,8 @@
"dev": "vite", "dev": "vite",
"build": "tsc -b && vite build", "build": "tsc -b && vite build",
"lint": "eslint .", "lint": "eslint .",
"lint:fix": "eslint . --fix",
"typecheck": "tsc -b --noEmit",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {

View File

@ -52,7 +52,9 @@ export function NavigationConsole({ isOpen, onClose }: NavigationConsoleProps) {
}, [input, existingSpaces]) }, [input, existingSpaces])
const handleShare = async () => { const handleShare = async () => {
if (!space) return if (!space) {
return
}
const shareLink = generateShareLink(space) const shareLink = generateShareLink(space)
const success = await copyToClipboard(shareLink) const success = await copyToClipboard(shareLink)
@ -109,7 +111,9 @@ export function NavigationConsole({ isOpen, onClose }: NavigationConsoleProps) {
onClose() onClose()
} }
if (!isOpen) return null if (!isOpen) {
return null
}
return ( return (
<> <>

View File

@ -2,7 +2,7 @@ import { useState, useRef, useEffect } from 'react'
import { updateNote, deleteNote, editingNoteId, navigateToSpace, selectedNoteIds, selectNote, moveSelectedNotesLocal, saveSelectedNotesToDB, existingSpaceIds } from '../store' import { updateNote, deleteNote, editingNoteId, navigateToSpace, selectedNoteIds, selectNote, moveSelectedNotesLocal, saveSelectedNotesToDB, existingSpaceIds } from '../store'
import { useStore } from '@nanostores/react' import { useStore } from '@nanostores/react'
type NoteType = { interface NoteType {
id: string id: string
x: number x: number
y: number y: number
@ -29,10 +29,18 @@ export function Note({ note }: NoteProps) {
// Check if note content is a single word that matches an existing space // Check if note content is a single word that matches an existing space
const isSpaceLink = () => { const isSpaceLink = () => {
if (!note.content) return false if (!note.content) {
return false
}
const words = note.content.trim().split(/\s+/) const words = note.content.trim().split(/\s+/)
if (words.length !== 1) return false if (words.length !== 1) {
const spaceId = words[0].toLowerCase().replace(/[^a-z0-9]/g, '-') return false
}
const firstWord = words[0]
if (!firstWord) {
return false
}
const spaceId = firstWord.toLowerCase().replace(/[^a-z0-9]/g, '-')
return $existingSpaceIds.includes(spaceId) return $existingSpaceIds.includes(spaceId)
} }
@ -72,7 +80,9 @@ export function Note({ note }: NoteProps) {
// Selection // Selection
const handleClick = (e: React.MouseEvent) => { const handleClick = (e: React.MouseEvent) => {
if (isEditing) return if (isEditing) {
return
}
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
@ -93,13 +103,19 @@ export function Note({ note }: NoteProps) {
// Navigation // Navigation
const handleDoubleClick = (e: React.MouseEvent) => { const handleDoubleClick = (e: React.MouseEvent) => {
if (isEditing) return if (isEditing) {
return
}
e.stopPropagation() e.stopPropagation()
if (note.content.trim()) { if (note.content.trim()) {
const words = note.content.trim().split(/\s+/) const words = note.content.trim().split(/\s+/)
if (words.length > 0) { if (words.length > 0) {
const spaceId = words[0].toLowerCase().replace(/[^a-z0-9]/g, '-') const firstWord = words[0]
if (!firstWord) {
return
}
const spaceId = firstWord.toLowerCase().replace(/[^a-z0-9]/g, '-')
navigateToSpace(spaceId) navigateToSpace(spaceId)
} }
} }
@ -107,7 +123,9 @@ export function Note({ note }: NoteProps) {
// Drag and drop // Drag and drop
const handleMouseDown = (e: React.MouseEvent) => { const handleMouseDown = (e: React.MouseEvent) => {
if (isEditing) return if (isEditing) {
return
}
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
@ -122,7 +140,9 @@ export function Note({ note }: NoteProps) {
} }
const handleMouseMove = (e: MouseEvent) => { const handleMouseMove = (e: MouseEvent) => {
if (!isDragging) return if (!isDragging) {
return
}
const deltaX = e.clientX - dragStart.x const deltaX = e.clientX - dragStart.x
const deltaY = e.clientY - dragStart.y const deltaY = e.clientY - dragStart.y
@ -162,6 +182,7 @@ export function Note({ note }: NoteProps) {
document.removeEventListener('mouseup', handleMouseUp) document.removeEventListener('mouseup', handleMouseUp)
} }
} }
return undefined
}, [isDragging, dragStart]) }, [isDragging, dragStart])
if (isEditing) { if (isEditing) {

View File

@ -49,7 +49,9 @@ export function Space() {
// Drag in empty space - rectangle selection // Drag in empty space - rectangle selection
const handleMouseDown = (e: React.MouseEvent) => { const handleMouseDown = (e: React.MouseEvent) => {
if (e.detail === 2) return // Ignore double-click if (e.detail === 2) {
return
} // Ignore double-click
const rect = e.currentTarget.getBoundingClientRect() const rect = e.currentTarget.getBoundingClientRect()
const x = e.clientX - rect.left const x = e.clientX - rect.left
@ -61,10 +63,14 @@ export function Space() {
} }
const handleMouseMove = (e: MouseEvent) => { const handleMouseMove = (e: MouseEvent) => {
if (!isSelecting) return if (!isSelecting) {
return
}
const spaceElement = document.querySelector('[data-space-canvas]') as HTMLElement const spaceElement = document.querySelector('[data-space-canvas]') as HTMLElement
if (!spaceElement) return if (!spaceElement) {
return
}
const rect = spaceElement.getBoundingClientRect() const rect = spaceElement.getBoundingClientRect()
const x = e.clientX - rect.left const x = e.clientX - rect.left
@ -74,13 +80,17 @@ export function Space() {
} }
const handleMouseUp = (e: MouseEvent) => { const handleMouseUp = (e: MouseEvent) => {
if (!isSelecting) return if (!isSelecting) {
return
}
setIsSelecting(false) setIsSelecting(false)
// Get final mouse position directly from event // Get final mouse position directly from event
const spaceElement = document.querySelector('[data-space-canvas]') as HTMLElement const spaceElement = document.querySelector('[data-space-canvas]') as HTMLElement
if (!spaceElement) return if (!spaceElement) {
return
}
const rect = spaceElement.getBoundingClientRect() const rect = spaceElement.getBoundingClientRect()
const finalX = e.clientX - rect.left const finalX = e.clientX - rect.left
@ -114,13 +124,16 @@ export function Space() {
document.removeEventListener('mouseup', handleMouseUp) document.removeEventListener('mouseup', handleMouseUp)
} }
} }
return undefined
}, [isSelecting, selectionStart]) }, [isSelecting, selectionStart])
// Keyboard shortcuts // Keyboard shortcuts
useEffect(() => { useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
// Don't handle shortcuts if editing a note, navigation console or welcome modal is open // Don't handle shortcuts if editing a note, navigation console or welcome modal is open
if ($editingNoteId || showNavigationConsole || showWelcomeModal) return if ($editingNoteId || showNavigationConsole || showWelcomeModal) {
return
}
if (e.key === ':' && !e.shiftKey) { if (e.key === ':' && !e.shiftKey) {
e.preventDefault() e.preventDefault()

View File

@ -21,7 +21,9 @@ export function WelcomeModal({ isOpen, onClose }: WelcomeModalProps) {
} }
} }
if (!isOpen) return null if (!isOpen) {
return null
}
return ( return (
<div <div

View File

@ -1,11 +1,11 @@
type Note = { interface Note {
id: string id: string
x: number x: number
y: number y: number
content: string content: string
} }
type Space = { interface Space {
id: string id: string
notes: Note[] notes: Note[]
} }
@ -47,7 +47,9 @@ class IndexedDBAdapter implements StorageAdapter {
} }
async getSpace(id: string): Promise<Space | undefined> { async getSpace(id: string): Promise<Space | undefined> {
if (!this.db) await this.init() if (!this.db) {
await this.init()
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const transaction = this.db!.transaction(['spaces'], 'readonly') const transaction = this.db!.transaction(['spaces'], 'readonly')
const store = transaction.objectStore('spaces') const store = transaction.objectStore('spaces')
@ -59,7 +61,9 @@ class IndexedDBAdapter implements StorageAdapter {
} }
async saveSpace(space: Space): Promise<void> { async saveSpace(space: Space): Promise<void> {
if (!this.db) await this.init() if (!this.db) {
await this.init()
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const transaction = this.db!.transaction(['spaces'], 'readwrite') const transaction = this.db!.transaction(['spaces'], 'readwrite')
const store = transaction.objectStore('spaces') const store = transaction.objectStore('spaces')
@ -97,7 +101,7 @@ class IndexedDBAdapter implements StorageAdapter {
if (noteIndex === -1) { if (noteIndex === -1) {
throw new Error(`Note ${noteId} not found in space ${spaceId}`) throw new Error(`Note ${noteId} not found in space ${spaceId}`)
} }
space.notes[noteIndex] = { ...space.notes[noteIndex], ...updates } space.notes[noteIndex] = { ...space.notes[noteIndex]!, ...updates } as Note
await this.saveSpace(space) await this.saveSpace(space)
} }
@ -111,7 +115,9 @@ class IndexedDBAdapter implements StorageAdapter {
} }
async getAllSpaceIds(): Promise<string[]> { async getAllSpaceIds(): Promise<string[]> {
if (!this.db) await this.init() if (!this.db) {
await this.init()
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const transaction = this.db!.transaction(['spaces'], 'readonly') const transaction = this.db!.transaction(['spaces'], 'readonly')
const store = transaction.objectStore('spaces') const store = transaction.objectStore('spaces')
@ -123,7 +129,9 @@ class IndexedDBAdapter implements StorageAdapter {
} }
async deleteSpace(id: string): Promise<void> { async deleteSpace(id: string): Promise<void> {
if (!this.db) await this.init() if (!this.db) {
await this.init()
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const transaction = this.db!.transaction(['spaces'], 'readwrite') const transaction = this.db!.transaction(['spaces'], 'readwrite')
const store = transaction.objectStore('spaces') const store = transaction.objectStore('spaces')
@ -136,7 +144,7 @@ class IndexedDBAdapter implements StorageAdapter {
} }
class DatabaseAdapter implements StorageAdapter { class DatabaseAdapter implements StorageAdapter {
private apiUrl: string private readonly apiUrl: string
constructor(apiUrl?: string) { constructor(apiUrl?: string) {
this.apiUrl = apiUrl || this.getApiUrl() this.apiUrl = apiUrl || this.getApiUrl()
@ -155,8 +163,12 @@ class DatabaseAdapter implements StorageAdapter {
async getSpace(id: string): Promise<Space | undefined> { async getSpace(id: string): Promise<Space | undefined> {
const response = await fetch(`${this.apiUrl}/spaces/${id}`) const response = await fetch(`${this.apiUrl}/spaces/${id}`)
if (response.status === 404) return undefined if (response.status === 404) {
if (!response.ok) throw new Error(`Failed to get space: ${response.statusText}`) return undefined
}
if (!response.ok) {
throw new Error(`Failed to get space: ${response.statusText}`)
}
return await response.json() return await response.json()
} }
@ -166,7 +178,9 @@ class DatabaseAdapter implements StorageAdapter {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(space) body: JSON.stringify(space)
}) })
if (!response.ok) throw new Error(`Failed to save space: ${response.statusText}`) if (!response.ok) {
throw new Error(`Failed to save space: ${response.statusText}`)
}
} }
async createSpace(id: string): Promise<Space> { async createSpace(id: string): Promise<Space> {
@ -181,7 +195,9 @@ class DatabaseAdapter implements StorageAdapter {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(note) body: JSON.stringify(note)
}) })
if (!response.ok) throw new Error(`Failed to add note: ${response.statusText}`) if (!response.ok) {
throw new Error(`Failed to add note: ${response.statusText}`)
}
} }
async updateNoteInSpace(spaceId: string, noteId: string, updates: Partial<Note>): Promise<void> { async updateNoteInSpace(spaceId: string, noteId: string, updates: Partial<Note>): Promise<void> {
@ -190,19 +206,25 @@ class DatabaseAdapter implements StorageAdapter {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates) body: JSON.stringify(updates)
}) })
if (!response.ok) throw new Error(`Failed to update note: ${response.statusText}`) if (!response.ok) {
throw new Error(`Failed to update note: ${response.statusText}`)
}
} }
async deleteNoteFromSpace(spaceId: string, noteId: string): Promise<void> { async deleteNoteFromSpace(spaceId: string, noteId: string): Promise<void> {
const response = await fetch(`${this.apiUrl}/spaces/${spaceId}/notes/${noteId}`, { const response = await fetch(`${this.apiUrl}/spaces/${spaceId}/notes/${noteId}`, {
method: 'DELETE' method: 'DELETE'
}) })
if (!response.ok) throw new Error(`Failed to delete note: ${response.statusText}`) if (!response.ok) {
throw new Error(`Failed to delete note: ${response.statusText}`)
}
} }
async getAllSpaceIds(): Promise<string[]> { async getAllSpaceIds(): Promise<string[]> {
const response = await fetch(`${this.apiUrl}/spaces`) const response = await fetch(`${this.apiUrl}/spaces`)
if (!response.ok) throw new Error(`Failed to get spaces: ${response.statusText}`) if (!response.ok) {
throw new Error(`Failed to get spaces: ${response.statusText}`)
}
const spaces = await response.json() const spaces = await response.json()
return spaces.map((space: Space) => space.id) return spaces.map((space: Space) => space.id)
} }
@ -211,7 +233,9 @@ class DatabaseAdapter implements StorageAdapter {
const response = await fetch(`${this.apiUrl}/spaces/${id}`, { const response = await fetch(`${this.apiUrl}/spaces/${id}`, {
method: 'DELETE' method: 'DELETE'
}) })
if (!response.ok) throw new Error(`Failed to delete space: ${response.statusText}`) if (!response.ok) {
throw new Error(`Failed to delete space: ${response.statusText}`)
}
} }
} }

View File

@ -2,14 +2,14 @@ import { atom } from 'nanostores'
import { db } from './db' import { db } from './db'
import { decompressSpace } from './utils/shareSpace' import { decompressSpace } from './utils/shareSpace'
type Note = { interface Note {
id: string id: string
x: number x: number
y: number y: number
content: string content: string
} }
type Space = { interface Space {
id: string id: string
notes: Note[] notes: Note[]
} }
@ -93,13 +93,17 @@ export const goBack = async () => {
const newHistory = history.slice(0, -1) const newHistory = history.slice(0, -1)
navigationHistory.set(newHistory) navigationHistory.set(newHistory)
const previousSpaceId = newHistory[newHistory.length - 1] const previousSpaceId = newHistory[newHistory.length - 1]
await loadSpace(previousSpaceId) if (previousSpaceId !== undefined) {
await loadSpace(previousSpaceId)
}
} }
} }
export const createNote = async (x: number, y: number) => { export const createNote = async (x: number, y: number) => {
const space = currentSpace.get() const space = currentSpace.get()
if (!space) return null if (!space) {
return null
}
const note: Note = { const note: Note = {
id: `note-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, id: `note-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
@ -117,7 +121,9 @@ export const createNote = async (x: number, y: number) => {
export const updateNote = async (noteId: string, updates: Partial<Note>) => { export const updateNote = async (noteId: string, updates: Partial<Note>) => {
const space = currentSpace.get() const space = currentSpace.get()
if (!space) return if (!space) {
return
}
await db.updateNoteInSpace(space.id, noteId, updates) await db.updateNoteInSpace(space.id, noteId, updates)
const updatedSpace = await db.getSpace(space.id) const updatedSpace = await db.getSpace(space.id)
@ -126,7 +132,9 @@ export const updateNote = async (noteId: string, updates: Partial<Note>) => {
export const deleteNote = async (noteId: string) => { export const deleteNote = async (noteId: string) => {
const space = currentSpace.get() const space = currentSpace.get()
if (!space) return if (!space) {
return
}
await db.deleteNoteFromSpace(space.id, noteId) await db.deleteNoteFromSpace(space.id, noteId)
const updatedSpace = await db.getSpace(space.id) const updatedSpace = await db.getSpace(space.id)
@ -145,7 +153,7 @@ export const getSpaceFromUrl = () => {
} }
// Selection management // Selection management
export const selectNote = (noteId: string, multiSelect: boolean = false) => { export const selectNote = (noteId: string, multiSelect = false) => {
const currentSelection = selectedNoteIds.get() const currentSelection = selectedNoteIds.get()
if (multiSelect) { if (multiSelect) {
@ -163,7 +171,9 @@ export const selectNote = (noteId: string, multiSelect: boolean = false) => {
export const selectNotesInRect = (minX: number, minY: number, maxX: number, maxY: number) => { export const selectNotesInRect = (minX: number, minY: number, maxX: number, maxY: number) => {
const space = currentSpace.get() const space = currentSpace.get()
if (!space) return if (!space) {
return
}
const selectedIds = space.notes const selectedIds = space.notes
.filter(note => { .filter(note => {
@ -186,7 +196,9 @@ export const clearSelection = () => {
export const deleteSelectedNotes = async () => { export const deleteSelectedNotes = async () => {
const selectedIds = selectedNoteIds.get() const selectedIds = selectedNoteIds.get()
const space = currentSpace.get() const space = currentSpace.get()
if (!space || selectedIds.length === 0) return if (!space || selectedIds.length === 0) {
return
}
for (const noteId of selectedIds) { for (const noteId of selectedIds) {
await db.deleteNoteFromSpace(space.id, noteId) await db.deleteNoteFromSpace(space.id, noteId)
@ -200,7 +212,9 @@ export const deleteSelectedNotes = async () => {
export const moveSelectedNotesLocal = (deltaX: number, deltaY: number) => { export const moveSelectedNotesLocal = (deltaX: number, deltaY: number) => {
const selectedIds = selectedNoteIds.get() const selectedIds = selectedNoteIds.get()
const space = currentSpace.get() const space = currentSpace.get()
if (!space || selectedIds.length === 0) return if (!space || selectedIds.length === 0) {
return
}
// Update positions locally without DB calls // Update positions locally without DB calls
const updatedNotes = space.notes.map(note => { const updatedNotes = space.notes.map(note => {
@ -216,7 +230,9 @@ export const moveSelectedNotesLocal = (deltaX: number, deltaY: number) => {
export const saveSelectedNotesToDB = async () => { export const saveSelectedNotesToDB = async () => {
const selectedIds = selectedNoteIds.get() const selectedIds = selectedNoteIds.get()
const space = currentSpace.get() const space = currentSpace.get()
if (!space || selectedIds.length === 0) return if (!space || selectedIds.length === 0) {
return
}
// Save to DB // Save to DB
for (const noteId of selectedIds) { for (const noteId of selectedIds) {

View File

@ -21,7 +21,21 @@
"noUnusedParameters": true, "noUnusedParameters": true,
"erasableSyntaxOnly": true, "erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true "noUncheckedSideEffectImports": true,
/* Additional strict checks */
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noImplicitReturns": true,
"noUncheckedIndexedAccess": true,
"noPropertyAccessFromIndexSignature": true,
"exactOptionalPropertyTypes": true,
"forceConsistentCasingInFileNames": true
}, },
"include": ["src"] "include": ["src"]
} }

View File

@ -19,7 +19,21 @@
"noUnusedParameters": true, "noUnusedParameters": true,
"erasableSyntaxOnly": true, "erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true "noUncheckedSideEffectImports": true,
/* Additional strict checks */
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noImplicitReturns": true,
"noUncheckedIndexedAccess": true,
"noPropertyAccessFromIndexSignature": true,
"exactOptionalPropertyTypes": true,
"forceConsistentCasingInFileNames": true
}, },
"include": ["vite.config.ts"] "include": ["vite.config.ts"]
} }