chore(lint): ban broad double casts project-wide
- Extend the as-unknown-as structural rule beyond app and Vue code - Remove existing broad double casts from core and tests - Replace the vector base64 helper and XPath facade casts with precise code paths
This commit is contained in:
parent
73861b74d9
commit
aa07e833cb
|
|
@ -763,13 +763,12 @@ const noTestCoreSourceImports = {
|
|||
const noBroadDoubleCast = {
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Disallow broad `as unknown as` casts in app and Vue SDK code'
|
||||
description: 'Disallow broad `as unknown as` casts outside vendored code'
|
||||
}
|
||||
},
|
||||
create(context) {
|
||||
const file = normalizedFilename(context)
|
||||
const applies = file.includes('/src/app/') || file.includes('/packages/vue/src/')
|
||||
if (!applies) return {}
|
||||
if (file.includes('/packages/core/src/kiwi/kiwi-schema/')) return {}
|
||||
|
||||
return {
|
||||
TSAsExpression(node) {
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ export function importClipboardNodes(
|
|||
|
||||
populateAndApplyOverrides(
|
||||
graph,
|
||||
guidMap as unknown as Map<string, InstanceNodeChange>,
|
||||
guidMap as Map<string, InstanceNodeChange>,
|
||||
created,
|
||||
blobs
|
||||
)
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ export function installBasicNodeProxyAccessors(
|
|||
graph(this, internals).updateNode(nodeId(this, internals), { width, height })
|
||||
},
|
||||
resizeWithoutConstraints(this: ProxyThis, width: number, height: number): void {
|
||||
;(this as unknown as { resize(width: number, height: number): void }).resize(width, height)
|
||||
;(this as { resize(width: number, height: number): void }).resize(width, height)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -409,7 +409,7 @@ export function importNodeChanges(
|
|||
|
||||
populateAndApplyOverrides(
|
||||
graph,
|
||||
changeMap as unknown as Map<string, InstanceNodeChange>,
|
||||
changeMap as Map<string, InstanceNodeChange>,
|
||||
guidToNodeId,
|
||||
blobs,
|
||||
activeRootIds
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ function applyOverridePaints(ov: Record<string, unknown>, updates: Partial<Scene
|
|||
if (ov.textData != null) {
|
||||
const td = ov.textData as { characters?: string }
|
||||
if (td.characters != null) updates.text = td.characters
|
||||
const runs = importStyleRuns(ov as unknown as NodeChange)
|
||||
const runs = importStyleRuns(ov as NodeChange)
|
||||
if (runs.length > 0) updates.styleRuns = runs
|
||||
}
|
||||
if (ov.fillPaints != null) updates.fills = convertFills(ov.fillPaints as Paint[])
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ function exportTextData(node: SceneNode): NodeChange['textData'] {
|
|||
if (style.fills && style.fills.length > 0) {
|
||||
override.fillPaints = style.fills.map(fillToKiwiPaint)
|
||||
}
|
||||
overrideTable.push(override as unknown as NodeChange)
|
||||
overrideTable.push(override as NodeChange)
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export function defineTool<P extends Record<string, ParamDef>>(def: {
|
|||
params: P
|
||||
execute: (figma: FigmaAPI, args: ResolvedParams<P>) => unknown
|
||||
}): ToolDef {
|
||||
return def as unknown as ToolDef
|
||||
return def as ToolDef
|
||||
}
|
||||
|
||||
export class NodeNotFoundError extends Error {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ function uint8ArrayToBase64(bytes: Uint8Array): string {
|
|||
let binary = ''
|
||||
for (let index = 0; index < bytes.length; index += CHUNK_SIZE) {
|
||||
const chunk = bytes.subarray(index, index + CHUNK_SIZE)
|
||||
binary += String.fromCharCode.apply(null, chunk as unknown as number[])
|
||||
binary += String.fromCharCode(...chunk)
|
||||
}
|
||||
return btoa(binary)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ const QUERYABLE_ATTRS = [
|
|||
interface XPathDocument {
|
||||
nodeType: number
|
||||
nodeName: string
|
||||
documentElement: XPathNode
|
||||
documentElement: XPathNode | null
|
||||
_children?: XPathNode[]
|
||||
}
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ interface XPathNode {
|
|||
prefix: null
|
||||
_sceneNode: SceneNode
|
||||
_attrs?: XPathAttr[]
|
||||
_parent?: XPathNode | null
|
||||
_parent?: XPathNode | XPathDocument | null
|
||||
_children?: XPathNode[]
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +79,7 @@ function wrapNode(
|
|||
namespaceURI: null,
|
||||
prefix: null,
|
||||
_sceneNode: node,
|
||||
_parent: parent as XPathNode | null
|
||||
_parent: parent
|
||||
}
|
||||
return wrapped
|
||||
}
|
||||
|
|
@ -88,9 +88,9 @@ function createDocument(graph: SceneGraph, rootNode: SceneNode): XPathDocument {
|
|||
const doc: XPathDocument = {
|
||||
nodeType: NODE_TYPES.DOCUMENT_NODE,
|
||||
nodeName: '#document',
|
||||
documentElement: null as unknown as XPathNode
|
||||
documentElement: null
|
||||
}
|
||||
const root = wrapNode(graph, rootNode, doc as unknown as XPathNode)
|
||||
const root = wrapNode(graph, rootNode, doc)
|
||||
doc.documentElement = root
|
||||
doc._children = [root]
|
||||
return doc
|
||||
|
|
@ -150,7 +150,7 @@ function siblingNode(
|
|||
): XPathNode | null {
|
||||
if (isDocument(node)) return null
|
||||
const parent = node._parent
|
||||
if (!parent) return null
|
||||
if (!parent || isDocument(parent)) return null
|
||||
const siblings = getChildren(graph, parent)
|
||||
const index = siblings.indexOf(node)
|
||||
if (index === -1) return null
|
||||
|
|
@ -230,7 +230,7 @@ export async function queryByXPath(
|
|||
if (targetPages.length === 0) return []
|
||||
|
||||
const { evaluateXPathToNodes } = await import('fontoxpath')
|
||||
const domFacade = createDomFacade(graph) as unknown as IDomFacade
|
||||
const domFacade = createDomFacade(graph) as IDomFacade
|
||||
const results: SceneNode[] = []
|
||||
|
||||
for (const page of targetPages) {
|
||||
|
|
@ -309,7 +309,7 @@ export async function matchByXPath(
|
|||
node: SceneNode
|
||||
): Promise<boolean> {
|
||||
const { evaluateXPathToBoolean } = await import('fontoxpath')
|
||||
const domFacade = createDomFacade(graph) as unknown as IDomFacade
|
||||
const domFacade = createDomFacade(graph) as IDomFacade
|
||||
const wrapped = wrapNode(graph, node)
|
||||
try {
|
||||
return evaluateXPathToBoolean(`self::*[${selector}]`, wrapped, domFacade)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ test('autosave triggers after scene changes with a file handle', async () => {
|
|||
}
|
||||
const mockHandle = {
|
||||
createWritable: async () => mockWritable
|
||||
} as unknown as FileSystemFileHandle
|
||||
} as FileSystemFileHandle
|
||||
|
||||
// Inject mock file handle via saveFigFileAs path:
|
||||
// We access the internal closure by calling openFigFile with a mock
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ describe('mapUpdate', () => {
|
|||
const update = {
|
||||
sessionUpdate: 'available_commands_update',
|
||||
availableCommands: []
|
||||
} as unknown as SessionUpdate
|
||||
} as SessionUpdate
|
||||
const result = mapUpdate(update, TEXT_ID, false)
|
||||
expect(result.chunks).toEqual([])
|
||||
expect(result.textStarted).toBe(false)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ describe('openExternalLink', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
mockOpen = vi.fn().mockReturnValue(null)
|
||||
globalThis.window = { open: mockOpen } as unknown as Window & typeof globalThis
|
||||
globalThis.window = { open: mockOpen } as Window & typeof globalThis
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ describe('fig-import: gradient fills', () => {
|
|||
],
|
||||
transform: { m00: 1, m01: 0, m02: 0, m10: 0, m11: 1, m12: 0 }
|
||||
}
|
||||
] as unknown as NodeChange['fillPaints']
|
||||
] as NodeChange['fillPaints']
|
||||
})
|
||||
])
|
||||
const n = graph.getChildren(graph.getPages()[0].id)[0]
|
||||
|
|
@ -147,7 +147,7 @@ describe('fig-import: gradient fills', () => {
|
|||
],
|
||||
transform: { m00: 0.5, m01: 0, m02: 0.5, m10: 0, m11: 0.5, m12: 0.5 }
|
||||
}
|
||||
] as unknown as NodeChange['fillPaints']
|
||||
] as NodeChange['fillPaints']
|
||||
})
|
||||
])
|
||||
const n = graph.getChildren(graph.getPages()[0].id)[0]
|
||||
|
|
@ -176,7 +176,7 @@ describe('fig-import: image fills', () => {
|
|||
imageScaleMode: 'FILL',
|
||||
transform: { m00: 1, m01: 0, m02: 0, m10: 0, m11: 1, m12: 0 }
|
||||
}
|
||||
] as unknown as NodeChange['fillPaints']
|
||||
] as NodeChange['fillPaints']
|
||||
})
|
||||
])
|
||||
const n = graph.getChildren(graph.getPages()[0].id)[0]
|
||||
|
|
@ -209,7 +209,7 @@ describe('fig-import: effects', () => {
|
|||
spread: 0,
|
||||
visible: true
|
||||
}
|
||||
] as unknown as NodeChange['effects']
|
||||
] as NodeChange['effects']
|
||||
})
|
||||
])
|
||||
const n = graph.getChildren(graph.getPages()[0].id)[0]
|
||||
|
|
@ -233,7 +233,7 @@ describe('fig-import: effects', () => {
|
|||
spread: 0,
|
||||
visible: true
|
||||
}
|
||||
] as unknown as NodeChange['effects']
|
||||
] as NodeChange['effects']
|
||||
})
|
||||
])
|
||||
const n = graph.getChildren(graph.getPages()[0].id)[0]
|
||||
|
|
@ -284,7 +284,7 @@ describe('fig-import: stroke options', () => {
|
|||
strokeWeight: 2,
|
||||
strokeAlign: 'CENTER',
|
||||
dashPattern: [10, 5]
|
||||
} as unknown as Partial<NodeChange>)
|
||||
} as Partial<NodeChange>)
|
||||
])
|
||||
const n = graph.getChildren(graph.getPages()[0].id)[0]
|
||||
expect(n.strokes[0].dashPattern).toEqual([10, 5])
|
||||
|
|
@ -301,7 +301,7 @@ describe('fig-import: text properties', () => {
|
|||
fontSize: 16,
|
||||
textAlignHorizontal: 'CENTER',
|
||||
textAutoResize: 'WIDTH_AND_HEIGHT'
|
||||
} as unknown as Partial<NodeChange>)
|
||||
} as Partial<NodeChange>)
|
||||
])
|
||||
const n = graph.getChildren(graph.getPages()[0].id)[0]
|
||||
expect(n.textAutoResize).toBe('WIDTH_AND_HEIGHT')
|
||||
|
|
@ -327,7 +327,7 @@ describe('fig-import: text properties', () => {
|
|||
node('TEXT', 10, 1, {
|
||||
textData: { characters: 'X' },
|
||||
fontName: { family: 'Inter', style }
|
||||
} as unknown as Partial<NodeChange>)
|
||||
} as Partial<NodeChange>)
|
||||
])
|
||||
const n = graph.getChildren(graph.getPages()[0].id)[0]
|
||||
expect(n.fontWeight).toBe(expected)
|
||||
|
|
@ -346,7 +346,7 @@ describe('fig-import: arc data', () => {
|
|||
endingAngle: Math.PI,
|
||||
innerRadius: 0
|
||||
}
|
||||
} as unknown as Partial<NodeChange>)
|
||||
} as Partial<NodeChange>)
|
||||
])
|
||||
const n = graph.getChildren(graph.getPages()[0].id)[0]
|
||||
expect(n.arcData).toBeDefined()
|
||||
|
|
@ -365,7 +365,7 @@ describe('fig-import: arc data', () => {
|
|||
endingAngle: Math.PI * 2,
|
||||
innerRadius: 0.5
|
||||
}
|
||||
} as unknown as Partial<NodeChange>)
|
||||
} as Partial<NodeChange>)
|
||||
])
|
||||
const n = graph.getChildren(graph.getPages()[0].id)[0]
|
||||
expect(n.arcData!.innerRadius).toBe(0.5)
|
||||
|
|
@ -380,7 +380,7 @@ describe('fig-import: constraints', () => {
|
|||
node('RECTANGLE', 10, 1, {
|
||||
horizontalConstraint: 'STRETCH',
|
||||
verticalConstraint: 'CENTER'
|
||||
} as unknown as Partial<NodeChange>)
|
||||
} as Partial<NodeChange>)
|
||||
])
|
||||
const n = graph.getChildren(graph.getPages()[0].id)[0]
|
||||
expect(n.horizontalConstraint).toBe('STRETCH')
|
||||
|
|
@ -402,7 +402,7 @@ describe('fig-import: blend mode', () => {
|
|||
canvas(),
|
||||
node('RECTANGLE', 10, 1, {
|
||||
blendMode: 'MULTIPLY'
|
||||
} as unknown as Partial<NodeChange>)
|
||||
} as Partial<NodeChange>)
|
||||
])
|
||||
const n = graph.getChildren(graph.getPages()[0].id)[0]
|
||||
expect(n.blendMode).toBe('MULTIPLY')
|
||||
|
|
@ -426,7 +426,7 @@ describe('fig-import: independent stroke weights', () => {
|
|||
borderBottomWeight: 2,
|
||||
borderLeftWeight: 4,
|
||||
borderStrokeWeightsIndependent: true
|
||||
} as unknown as Partial<NodeChange>)
|
||||
} as Partial<NodeChange>)
|
||||
])
|
||||
const n = graph.getChildren(graph.getPages()[0].id)[0]
|
||||
expect(n.borderTopWeight).toBe(2)
|
||||
|
|
@ -460,7 +460,7 @@ describe('fig-import: multiple fills', () => {
|
|||
],
|
||||
transform: { m00: 1, m01: 0, m02: 0, m10: 0, m11: 1, m12: 0 }
|
||||
}
|
||||
] as unknown as NodeChange['fillPaints']
|
||||
] as NodeChange['fillPaints']
|
||||
})
|
||||
])
|
||||
const n = graph.getChildren(graph.getPages()[0].id)[0]
|
||||
|
|
@ -550,7 +550,7 @@ describe('fig-import: variable asset refs', () => {
|
|||
resolvedDataType: 'COLOR'
|
||||
}
|
||||
}
|
||||
] as unknown as NodeChange['fillPaints']
|
||||
] as NodeChange['fillPaints']
|
||||
})
|
||||
])
|
||||
|
||||
|
|
@ -576,7 +576,7 @@ describe('fig-import: component set detection', () => {
|
|||
...node('FRAME', 10, 1),
|
||||
name: 'Button',
|
||||
componentPropDefs: [{ id: { sessionID: 0, localID: 1 }, name: 'State', type: 'VARIANT' }]
|
||||
} as unknown as NodeChange,
|
||||
} as NodeChange,
|
||||
{
|
||||
...node('SYMBOL', 11, 1),
|
||||
parentIndex: { guid: { sessionID: 1, localID: 10 }, position: '!' },
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ function createRecordingProvider() {
|
|||
registerFont(data: ArrayBuffer, family: string) {
|
||||
registrations.push({ family, byteLength: data.byteLength })
|
||||
}
|
||||
} as unknown as TypefaceFontProvider
|
||||
} as TypefaceFontProvider
|
||||
return { provider, registrations }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ function makeNode(): SceneNode {
|
|||
paddingBottom: 0,
|
||||
paddingLeft: 0,
|
||||
boundVariables: {}
|
||||
} as unknown as SceneNode
|
||||
} as SceneNode
|
||||
}
|
||||
|
||||
function makeVarContext(vars: Record<string, number>): VarContext {
|
||||
|
|
@ -45,7 +45,7 @@ function makeVarContext(vars: Record<string, number>): VarContext {
|
|||
},
|
||||
resolveString: () => '',
|
||||
setActiveTheme: () => {}
|
||||
} as unknown as VarContext
|
||||
} as VarContext
|
||||
}
|
||||
|
||||
const ctx = makeVarContext({
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ function createMockRenderer(overrides: Partial<Record<string, unknown>> = {}) {
|
|||
buildParagraph: mock(() => paragraph),
|
||||
_paragraph: paragraph,
|
||||
...overrides
|
||||
} as unknown as SkiaRenderer & { _paragraph: ReturnType<typeof createMockParagraph> }
|
||||
} as SkiaRenderer & { _paragraph: ReturnType<typeof createMockParagraph> }
|
||||
}
|
||||
|
||||
function textNode(overrides: Partial<SceneNode> = {}): SceneNode {
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ function createMockRenderer(overrides: Partial<SkiaRenderer> = {}): SkiaRenderer
|
|||
isRectangularType: mock(() => true),
|
||||
worldViewport: { x: 0, y: 0, w: 1000, h: 1000 },
|
||||
...overrides
|
||||
} as unknown as SkiaRenderer
|
||||
} as SkiaRenderer
|
||||
}
|
||||
|
||||
function createMockCanvas() {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ function createEditorHarness() {
|
|||
getVariable(id: string) {
|
||||
return variables.get(id)
|
||||
}
|
||||
} as unknown as Editor
|
||||
} as Editor
|
||||
|
||||
return { editor, collection, collections, variables }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ describe('normalizeVectorNetwork', () => {
|
|||
],
|
||||
segments: [{ start: 0, end: 1 }],
|
||||
regions: []
|
||||
} as unknown as VectorNetwork
|
||||
} as VectorNetwork
|
||||
const result = normalizeVectorNetwork(network)
|
||||
expect(result.segments[0].tangentStart).toEqual({ x: 0, y: 0 })
|
||||
expect(result.segments[0].tangentEnd).toEqual({ x: 0, y: 0 })
|
||||
|
|
@ -146,7 +146,7 @@ describe('normalizeVectorNetwork', () => {
|
|||
],
|
||||
segments: [{ start: 0, end: 1, tangentStart: { x: 3, y: 4 } }],
|
||||
regions: []
|
||||
} as unknown as VectorNetwork
|
||||
} as VectorNetwork
|
||||
const result = normalizeVectorNetwork(network)
|
||||
expect(result.segments[0].tangentStart).toEqual({ x: 3, y: 4 })
|
||||
expect(result.segments[0].tangentEnd).toEqual({ x: 0, y: 0 })
|
||||
|
|
@ -165,7 +165,7 @@ describe('normalizeVectorNetwork', () => {
|
|||
{ start: 2, end: 0 }
|
||||
],
|
||||
regions: [{ windingRule: 'NONZERO', loops: [[0, 1, 2]] }]
|
||||
} as unknown as VectorNetwork
|
||||
} as VectorNetwork
|
||||
|
||||
const normalized = normalizeVectorNetwork(raw)
|
||||
const blob = encodeVectorNetworkBlob(normalized)
|
||||
|
|
@ -202,7 +202,7 @@ describe('validateVectorNetwork', () => {
|
|||
],
|
||||
segments: [{ start: 0, end: 1 }],
|
||||
regions: []
|
||||
} as unknown as VectorNetwork
|
||||
} as VectorNetwork
|
||||
expect(validateVectorNetwork(network)).toEqual([])
|
||||
})
|
||||
|
||||
|
|
@ -211,14 +211,14 @@ describe('validateVectorNetwork', () => {
|
|||
vertices: [{ x: 0, y: 0 }],
|
||||
segments: [{ start: 0, end: 5 }],
|
||||
regions: []
|
||||
} as unknown as VectorNetwork
|
||||
} as VectorNetwork
|
||||
const errors = validateVectorNetwork(network)
|
||||
expect(errors.length).toBe(1)
|
||||
expect(errors[0]).toContain('end index 5 out of range')
|
||||
})
|
||||
|
||||
test('rejects missing vertices array', () => {
|
||||
const network = { segments: [], regions: [] } as unknown as VectorNetwork
|
||||
const network = { segments: [], regions: [] } as VectorNetwork
|
||||
const errors = validateVectorNetwork(network)
|
||||
expect(errors[0]).toContain('vertices must be an array')
|
||||
})
|
||||
|
|
@ -228,7 +228,7 @@ describe('validateVectorNetwork', () => {
|
|||
vertices: [{ x: 'a', y: 0 }],
|
||||
segments: [],
|
||||
regions: []
|
||||
} as unknown as VectorNetwork
|
||||
} as VectorNetwork
|
||||
const errors = validateVectorNetwork(network)
|
||||
expect(errors[0]).toContain('x and y must be numbers')
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue