Harden oxlint: consistent-type-imports, no-duplicates, no-misused-promises, prefer-optional-chain
Add 6 new lint rules: typescript/consistent-type-imports, typescript/no-unnecessary-type-assertion, typescript/prefer-optional-chain, typescript/no-misused-promises, import/no-duplicates, import/no-mutable-exports. Ban @/types shim via no-restricted-imports. Fix all violations: replace inline import() annotations with proper type imports, merge duplicate module imports, add void to fire-and-forget promises, convert && chains to optional chaining.
This commit is contained in:
parent
33389a95ad
commit
f96fffb83f
16
oxlint.json
16
oxlint.json
|
|
@ -8,13 +8,29 @@
|
|||
"rules": {
|
||||
"no-unused-vars": "warn",
|
||||
"no-console": "off",
|
||||
"no-restricted-imports": ["error", {
|
||||
"paths": [
|
||||
{ "name": "@/types", "message": "Import types directly from @open-pencil/core." }
|
||||
],
|
||||
"patterns": [{
|
||||
"group": ["@/engine/*"],
|
||||
"message": "Import from @open-pencil/core unless the module adds platform-specific logic.",
|
||||
"allowImportNames": ["loadFont", "listFamilies", "preloadFonts"]
|
||||
}]
|
||||
}],
|
||||
|
||||
"typescript/no-explicit-any": "warn",
|
||||
"typescript/no-non-null-assertion": "warn",
|
||||
"typescript/no-floating-promises": "error",
|
||||
"typescript/no-misused-promises": "error",
|
||||
"typescript/consistent-type-imports": "error",
|
||||
"typescript/no-unnecessary-type-assertion": "error",
|
||||
"typescript/prefer-optional-chain": "error",
|
||||
|
||||
"import/no-cycle": "error",
|
||||
"import/no-self-import": "error",
|
||||
"import/no-duplicates": "error",
|
||||
"import/no-mutable-exports": "error",
|
||||
|
||||
"unicorn/no-instanceof-array": "error",
|
||||
"unicorn/no-typeof-undefined": "error",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||
import type {
|
||||
ListboxFilter} from 'reka-ui';
|
||||
import {
|
||||
ListboxContent,
|
||||
ListboxFilter,
|
||||
ListboxItem,
|
||||
ListboxRoot,
|
||||
ListboxVirtualizer,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import IconComponent from '~icons/lucide/diamond'
|
|||
import IconComponentSet from '~icons/lucide/component'
|
||||
import IconFrame from '~icons/lucide/frame'
|
||||
import IconGroup from '~icons/lucide/group'
|
||||
import IconInstance from '~icons/lucide/diamond'
|
||||
|
||||
import IconMinus from '~icons/lucide/minus'
|
||||
import IconPenTool from '~icons/lucide/pen-tool'
|
||||
import IconSection from '~icons/lucide/layout-grid'
|
||||
|
|
@ -38,7 +38,7 @@ const nodeIcons: Record<string, typeof IconSquare> = {
|
|||
GROUP: IconGroup,
|
||||
COMPONENT: IconComponent,
|
||||
COMPONENT_SET: IconComponentSet,
|
||||
INSTANCE: IconInstance,
|
||||
INSTANCE: IconComponent,
|
||||
LINE: IconMinus,
|
||||
TEXT: IconType,
|
||||
VECTOR: IconPenTool,
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ import IconLock from '~icons/lucide/lock'
|
|||
|
||||
import { menuContent, menuItem } from '@/components/ui/menu'
|
||||
import { ACTION_TOAST_DURATION } from '@/constants'
|
||||
import { TOOLS, useEditorStore } from '@/stores/editor'
|
||||
import { useEditorStore } from '@/stores/editor'
|
||||
import { toolIcons } from '@/utils/tools'
|
||||
|
||||
import type { Component } from 'vue'
|
||||
import type { Tool } from '@/stores/editor'
|
||||
import type { Tool , TOOLS} from '@/stores/editor'
|
||||
|
||||
const store = useEditorStore()
|
||||
const breakpoints = useBreakpoints({ mobile: 768 })
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import IconX from '~icons/lucide/x'
|
|||
import ColorInput from './ColorInput.vue'
|
||||
import { colorToHexRaw, parseColor } from '@open-pencil/core'
|
||||
import { useEditorStore } from '@/stores/editor'
|
||||
import type { Variable, Color } from '@open-pencil/core'
|
||||
import type { Variable, VariableCollection, VariableValue, Color } from '@open-pencil/core'
|
||||
|
||||
const open = defineModel<boolean>('open', { default: false })
|
||||
const store = useEditorStore()
|
||||
|
|
@ -155,7 +155,7 @@ function updateColorValue(variable: Variable, modeId: string, color: Color) {
|
|||
|
||||
function commitValueEdit(variable: Variable, modeId: string, newValue: string) {
|
||||
const oldValue = structuredClone(variable.valuesByMode[modeId])
|
||||
let parsed: import('@open-pencil/core').VariableValue
|
||||
let parsed: VariableValue
|
||||
if (variable.type === 'COLOR') {
|
||||
parsed = parseColor(newValue.startsWith('#') ? newValue : `#${newValue}`)
|
||||
} else if (variable.type === 'FLOAT') {
|
||||
|
|
@ -187,7 +187,7 @@ function addVariable() {
|
|||
if (!col) return
|
||||
|
||||
const id = `var:${Date.now()}`
|
||||
const valuesByMode: Record<string, import('@open-pencil/core').VariableValue> = {}
|
||||
const valuesByMode: Record<string, VariableValue> = {}
|
||||
for (const mode of col.modes) {
|
||||
valuesByMode[mode.modeId] = { r: 0, g: 0, b: 0, a: 1 }
|
||||
}
|
||||
|
|
@ -218,7 +218,7 @@ function addVariable() {
|
|||
|
||||
function addCollection() {
|
||||
const id = `col:${Date.now()}`
|
||||
const collection: import('@open-pencil/core').VariableCollection = {
|
||||
const collection: VariableCollection = {
|
||||
id,
|
||||
name: 'New collection',
|
||||
modes: [{ modeId: 'default', name: 'Mode 1' }],
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@ import {
|
|||
DropdownMenuPortal
|
||||
} from 'reka-ui'
|
||||
|
||||
import type { Color, Stroke } from '@open-pencil/core'
|
||||
import type { SceneNode } from '@open-pencil/core'
|
||||
import type { Color, SceneNode, Stroke } from '@open-pencil/core'
|
||||
|
||||
type StrokeSides = 'ALL' | 'TOP' | 'BOTTOM' | 'LEFT' | 'RIGHT' | 'CUSTOM'
|
||||
|
||||
|
|
|
|||
|
|
@ -223,8 +223,8 @@ function hitTestRotationHandle(
|
|||
export function useCanvasInput(
|
||||
canvasRef: Ref<HTMLCanvasElement | null>,
|
||||
store: EditorStore,
|
||||
hitTestSectionTitle: (cx: number, cy: number) => import('@open-pencil/core').SceneNode | null,
|
||||
hitTestComponentLabel: (cx: number, cy: number) => import('@open-pencil/core').SceneNode | null,
|
||||
hitTestSectionTitle: (cx: number, cy: number) => SceneNode | null,
|
||||
hitTestComponentLabel: (cx: number, cy: number) => SceneNode | null,
|
||||
onCursorMove?: (cx: number, cy: number) => void
|
||||
) {
|
||||
const drag = ref<DragState | null>(null)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { useBreakpoints, useRafFn, useResizeObserver } from '@vueuse/core'
|
||||
import { onMounted, onUnmounted, type Ref } from 'vue'
|
||||
|
||||
import { getCanvasKit, getGpuBackend } from '@open-pencil/core'
|
||||
import { SkiaRenderer } from '@open-pencil/core'
|
||||
import { getCanvasKit, getGpuBackend, SkiaRenderer } from '@open-pencil/core'
|
||||
|
||||
import type { EditorStore } from '@/stores/editor'
|
||||
import type { CanvasKit } from 'canvaskit-wasm'
|
||||
|
|
@ -118,7 +117,7 @@ export function useCanvas(canvasRef: Ref<HTMLCanvasElement | null>, store: Edito
|
|||
}
|
||||
}
|
||||
|
||||
const glCtx = (canvas.getContext('webgl2') ?? null) as WebGL2RenderingContext | null
|
||||
const glCtx = (canvas.getContext('webgl2') ?? null)
|
||||
renderer = new SkiaRenderer(ck, surface, glCtx)
|
||||
store.setCanvasKit(ck, renderer)
|
||||
void renderer.loadFonts().then(() => renderNow())
|
||||
|
|
|
|||
|
|
@ -110,9 +110,9 @@ export function useCollab(store: EditorStore) {
|
|||
const [sendSync, getSync] = room.makeAction<Uint8Array>('sync-step1')
|
||||
const [sendSyncReply, getSyncReply] = room.makeAction<Uint8Array>('sync-reply')
|
||||
|
||||
sendYjsUpdate = (data, peerId) => (peerId ? sendUpdate(data, peerId) : sendUpdate(data))
|
||||
sendAwareness = (data, peerId) => (peerId ? sendAw(data, peerId) : sendAw(data))
|
||||
sendSyncStep1 = (data, peerId) => (peerId ? sendSync(data, peerId) : sendSync(data))
|
||||
sendYjsUpdate = (data, peerId) => void (peerId ? sendUpdate(data, peerId) : sendUpdate(data))
|
||||
sendAwareness = (data, peerId) => void (peerId ? sendAw(data, peerId) : sendAw(data))
|
||||
sendSyncStep1 = (data, peerId) => void (peerId ? sendSync(data, peerId) : sendSync(data))
|
||||
|
||||
getUpdate((data) => {
|
||||
if (!ydoc) return
|
||||
|
|
@ -286,7 +286,7 @@ export function useCollab(store: EditorStore) {
|
|||
}
|
||||
}
|
||||
} else if (event.target.parent === localYnodes) {
|
||||
const nodeId = findNodeIdForYMap(event.target as Y.Map<unknown>)
|
||||
const nodeId = findNodeIdForYMap(event.target)
|
||||
if (nodeId) {
|
||||
const ynode = localYnodes.get(nodeId)
|
||||
if (ynode) applyYnodeToGraph(nodeId, ynode)
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ export function useKeyboard() {
|
|||
computed(() => keys['shift+keya'].value && !keys['meta'].value && !keys['control'].value),
|
||||
() => {
|
||||
const node = store.selectedNode.value
|
||||
if (node && node.type === 'FRAME' && store.selectedNodes.value.length === 1) {
|
||||
if (node?.type === 'FRAME' && store.selectedNodes.value.length === 1) {
|
||||
store.setLayoutMode(node.id, node.layoutMode === 'NONE' ? 'VERTICAL' : 'NONE')
|
||||
} else if (store.selectedNodes.value.length > 0) {
|
||||
store.wrapInAutoLayout()
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ export async function openFileDialog() {
|
|||
multiple: false
|
||||
})
|
||||
if (!path) return
|
||||
const bytes = await readFile(path as string)
|
||||
const file = new File([bytes], (path as string).split('/').pop() ?? 'file.fig')
|
||||
await openFileInNewTab(file, undefined, path as string)
|
||||
const bytes = await readFile(path)
|
||||
const file = new File([bytes], (path).split('/').pop() ?? 'file.fig')
|
||||
await openFileInNewTab(file, undefined, path)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -51,12 +51,12 @@ const store = useEditorStore()
|
|||
|
||||
const MENU_ACTIONS: Record<string, () => void> = {
|
||||
new: () => createTab(),
|
||||
open: () => openFileDialog(),
|
||||
open: () => void openFileDialog(),
|
||||
close: () => {
|
||||
if (activeTab.value) closeTab(activeTab.value.id)
|
||||
},
|
||||
save: () => store.saveFigFile(),
|
||||
'save-as': () => store.saveFigFileAs(),
|
||||
save: () => void store.saveFigFile(),
|
||||
'save-as': () => void store.saveFigFileAs(),
|
||||
duplicate: () => store.duplicateSelected(),
|
||||
delete: () => store.deleteSelected(),
|
||||
group: () => store.groupSelected(),
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import {
|
|||
buildFigmaClipboardHTML,
|
||||
buildOpenPencilClipboardHTML,
|
||||
prefetchFigmaSchema,
|
||||
readFigFile,
|
||||
renderNodesToImage,
|
||||
renderNodesToSVG,
|
||||
SceneGraph,
|
||||
|
|
@ -32,7 +33,6 @@ import {
|
|||
TextEditor,
|
||||
UndoManager
|
||||
} from '@open-pencil/core'
|
||||
import { readFigFile } from '@open-pencil/core'
|
||||
|
||||
import type {
|
||||
Color,
|
||||
|
|
@ -42,12 +42,14 @@ import type {
|
|||
NodeType,
|
||||
Rect,
|
||||
SceneNode,
|
||||
SkiaRenderer,
|
||||
SnapGuide,
|
||||
VectorNetwork,
|
||||
VectorRegion,
|
||||
VectorSegment,
|
||||
VectorVertex
|
||||
} from '@open-pencil/core'
|
||||
import type { CanvasKit } from 'canvaskit-wasm'
|
||||
|
||||
export type Tool =
|
||||
| 'SELECT'
|
||||
|
|
@ -131,8 +133,8 @@ export function createEditorStore() {
|
|||
let autosaveTimer: ReturnType<typeof setTimeout> | undefined
|
||||
let lastWriteTime = 0
|
||||
let unwatchFile: (() => void) | null = null
|
||||
let _ck: import('canvaskit-wasm').CanvasKit | null = null
|
||||
let _renderer: import('@open-pencil/core').SkiaRenderer | null = null
|
||||
let _ck: CanvasKit | null = null
|
||||
let _renderer: SkiaRenderer | null = null
|
||||
let _textEditor: TextEditor | null = null
|
||||
|
||||
void prefetchFigmaSchema()
|
||||
|
|
@ -211,6 +213,7 @@ export function createEditorStore() {
|
|||
if (!state.autosaveEnabled) return
|
||||
if (!fileHandle && !filePath) return
|
||||
clearTimeout(autosaveTimer)
|
||||
// oxlint-disable-next-line typescript/no-misused-promises
|
||||
autosaveTimer = setTimeout(async () => {
|
||||
if (state.sceneVersion === savedVersion) return
|
||||
if (!state.autosaveEnabled) return
|
||||
|
|
@ -273,7 +276,7 @@ export function createEditorStore() {
|
|||
|
||||
function switchPage(pageId: string) {
|
||||
const page = graph.getNode(pageId)
|
||||
if (!page || page.type !== 'CANVAS') return
|
||||
if (page?.type !== 'CANVAS') return
|
||||
|
||||
// Save current viewport
|
||||
pageViewports.set(state.currentPageId, {
|
||||
|
|
@ -626,8 +629,8 @@ export function createEditorStore() {
|
|||
}
|
||||
|
||||
function setCanvasKit(
|
||||
ck: import('canvaskit-wasm').CanvasKit,
|
||||
renderer: import('@open-pencil/core').SkiaRenderer
|
||||
ck: CanvasKit,
|
||||
renderer: SkiaRenderer
|
||||
) {
|
||||
_ck = ck
|
||||
_renderer = renderer
|
||||
|
|
@ -778,6 +781,7 @@ export function createEditorStore() {
|
|||
unwatchFile = () => unwatch()
|
||||
} else if (fileHandle) {
|
||||
let lastModified = (await fileHandle.getFile()).lastModified
|
||||
// oxlint-disable-next-line typescript/no-misused-promises
|
||||
const interval = setInterval(async () => {
|
||||
if (!fileHandle) {
|
||||
clearInterval(interval)
|
||||
|
|
@ -1396,7 +1400,7 @@ export function createEditorStore() {
|
|||
|
||||
function createInstanceFromComponent(componentId: string, x?: number, y?: number) {
|
||||
const component = graph.getNode(componentId)
|
||||
if (!component || component.type !== 'COMPONENT') return null
|
||||
if (component?.type !== 'COMPONENT') return null
|
||||
|
||||
const parentId = component.parentId ?? state.currentPageId
|
||||
const instance = graph.createInstance(componentId, parentId, {
|
||||
|
|
@ -1427,7 +1431,7 @@ export function createEditorStore() {
|
|||
|
||||
function detachInstance() {
|
||||
const node = selectedNode.value
|
||||
if (!node || node.type !== 'INSTANCE') return
|
||||
if (node?.type !== 'INSTANCE') return
|
||||
|
||||
const prevComponentId = node.componentId
|
||||
|
||||
|
|
@ -1475,7 +1479,7 @@ export function createEditorStore() {
|
|||
|
||||
function ungroupSelected() {
|
||||
const node = selectedNode.value
|
||||
if (!node || node.type !== 'GROUP') return
|
||||
if (node?.type !== 'GROUP') return
|
||||
|
||||
const parentId = node.parentId ?? state.currentPageId
|
||||
const parent = graph.getNode(parentId)
|
||||
|
|
@ -1582,7 +1586,7 @@ export function createEditorStore() {
|
|||
|
||||
function moveToPage(pageId: string) {
|
||||
const targetPage = graph.getNode(pageId)
|
||||
if (!targetPage || targetPage.type !== 'CANVAS') return
|
||||
if (targetPage?.type !== 'CANVAS') return
|
||||
const ids = [...state.selectedIds]
|
||||
for (const id of ids) {
|
||||
graph.reparentNode(id, pageId)
|
||||
|
|
@ -1647,7 +1651,7 @@ export function createEditorStore() {
|
|||
|
||||
function adoptNodesIntoSection(sectionId: string) {
|
||||
const section = graph.getNode(sectionId)
|
||||
if (!section || section.type !== 'SECTION') return
|
||||
if (section?.type !== 'SECTION') return
|
||||
|
||||
const parentId = section.parentId ?? state.currentPageId
|
||||
const siblings = graph.getChildren(parentId)
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ export async function openFileInNewTab(
|
|||
): Promise<void> {
|
||||
const current = activeTab.value
|
||||
const isUntouched =
|
||||
current && current.store.state.documentName === 'Untitled' && !current.store.undo.canUndo
|
||||
current?.store.state.documentName === 'Untitled' && !current.store.undo.canUndo
|
||||
|
||||
if (isUntouched) {
|
||||
await current.store.openFigFile(file, handle, path)
|
||||
|
|
|
|||
Loading…
Reference in a new issue