chore(audits): drop dead ESLint directives and redundant check:dead-code; fix the PPTX blend-group call they hid (#8876)

* chore(audits): drop dead ESLint directives and the redundant check:dead-code script

- Remove all 89 `eslint-disable` comments. Nothing runs ESLint (no eslint dependency or
  config in any workspace; Next 16 `next build` has no lint step), so they suppressed
  nothing. Reasons they carried are kept as plain `//` whys.
- `check:comment-hygiene` now rejects `eslint-disable`/`eslint-enable` comments so they
  cannot return.
- Remove `check:dead-code`: `check:unused-exports` already runs knip with the same config
  and a superset of its issue types, and `check:audits` skipped the alias.
- Ignore unused exports in the generated `apps/docs/components/icons.tsx` (a verbatim copy
  of the sim icon set, whose export surface is ratcheted at the source) and shrink the
  unused-exports baseline by its 66 entries.

* fix(pptx): stop calling the appended rect blend group as a function

* docs(skills): name both ESLint directives the comment audit rejects

* docs: name both ESLint directives in the comment rule

* test(pptx): cover the rect gradient blend-group path
This commit is contained in:
Waleed
2026-10-09 20:45:08 -07:00
committed by GitHub
parent e526a910cb
commit cf4e2d4893
49 changed files with 312 additions and 328 deletions
@@ -33,7 +33,7 @@ This codebase's convention: **TSDoc for documentation; an inline `//` only for a
- A `//` comment that explains a **non-obvious why**: a workaround for an upstream bug, an ordering constraint, a perf reason, a spec/edge-case the code can't self-document (`// first-match wins — matches the old find() semantics`).
- Existing TSDoc `/** ... */` blocks on declarations — leave them (only tighten if verbose).
- `// boundary-raw-fetch:`, `// double-cast-allowed:`, `// boundary-raw-json:`, `// untyped-response:`, `-- migration-safe:` (SQL migrations), `// rq-lint-allow:`, `// client-boundary-allow:` and any other `<kebab-tag>: <reason>` annotation a script under `scripts/` greps for, in line-comment or block-comment form (e.g. the `/** svg-path-precision-exception: ... */` directive on icon paths) — these are load-bearing, never touch them.
- `// biome-ignore`, `// eslint-disable`, `// @ts-expect-error` and other tooling directives.
- `// biome-ignore`, `// @ts-expect-error` and other tooling directives. (Nothing runs ESLint, so `eslint-disable`/`eslint-enable` directives are dead; delete them — `check:comment-hygiene` rejects both.)
- `// TODO` / `// FIXME` that point at real, still-open work.
## Bias
+1 -1
View File
@@ -6,7 +6,7 @@ This file (also `AGENTS.md`) holds the repo-wide rules. Area detail lives in `.c
- **Package manager**: `bun` and `bunx`, never `npm` and `npx`.
- **Logging**: `createLogger` from `@sim/logger`; `logger.info` / `logger.warn` / `logger.error`, never `console.log`. Inside `withRouteHandler` the logger already carries the request ID — no manual `withMetadata({ requestId })`.
- **Comments**: name things so the code explains itself. TSDoc documents exported APIs and non-obvious modules. An inline `//` is only for a terse, non-obvious *why*, or for a script-enforced `// <tag>: <reason>` annotation (`boundary-raw-fetch`, `double-cast-allowed`, `boundary-raw-json`, `untyped-response`, `rq-lint-allow`, `client-boundary-allow`, `utils-lint-allow`, …). History belongs in the commit message. No `====` separators or commented-out code (`check:comment-hygiene` enforces this). The `/you-might-not-need-a-comment` skill applies this to a diff.
- **Comments**: name things so the code explains itself. TSDoc documents exported APIs and non-obvious modules. An inline `//` is only for a terse, non-obvious *why*, or for a script-enforced `// <tag>: <reason>` annotation (`boundary-raw-fetch`, `double-cast-allowed`, `boundary-raw-json`, `untyped-response`, `rq-lint-allow`, `client-boundary-allow`, `utils-lint-allow`, …). History belongs in the commit message. No `====` separators, commented-out code, or ESLint directives (`eslint-disable`/`eslint-enable`; nothing runs ESLint; `check:comment-hygiene` enforces all three). The `/you-might-not-need-a-comment` skill applies this to a diff.
- **ID generation**: `generateId()` (UUID v4, the default) or `generateShortId(size?)` (URL-safe, 21 chars by default) from `@sim/utils/id` — never `crypto.randomUUID()`, `nanoid`, or `uuid`. Both use `crypto.getRandomValues()`, so they also work in non-secure (HTTP) browsers. For other randomness, `@sim/utils/random` (`randomInt`, `randomFloat`, `randomItem`, `generateRandomBytes`, `generateRandomHex`) — never `Math.random()` or `crypto.randomBytes()`.
- **Common utilities**: use the shared helpers from the `@sim/utils/<module>` subpaths instead of inline implementations (`check:utils` bans most of the inline forms below):
- `sleep(ms)` from `@sim/utils/helpers` — never `new Promise(resolve => setTimeout(resolve, ms))`
@@ -548,137 +548,133 @@ export default function ResumeExecutionPage({
}
}, [refetchExecutionDetail, selectedContextId])
const handleResume = useCallback(
async () => {
if (!selectedContextId || !selectedDetail) {
setError('No pause point is selected. Refresh and try again.')
return
}
setLoadingAction(true)
setError(null)
setMessage(null)
let resumePayload: any
try {
if (isHumanMode && hasInputFormat) {
const errors: Record<string, string> = {}
const submission: Record<string, any> = {}
for (const field of inputFormatFields) {
const rawValue = formValues[field.name] ?? ''
const hasValue =
field.type === 'boolean'
? rawValue === 'true' || rawValue === 'false'
: rawValue.trim().length > 0 && rawValue !== '__unset__'
if (!hasValue || rawValue === '__unset__') {
if (field.required) errors[field.name] = 'This field is required.'
continue
}
const { value, error: parseError } = parseFormValue(field, rawValue)
if (parseError) {
errors[field.name] = parseError
continue
}
if (value !== undefined) submission[field.name] = value
const handleResume = useCallback(async () => {
if (!selectedContextId || !selectedDetail) {
setError('No pause point is selected. Refresh and try again.')
return
}
setLoadingAction(true)
setError(null)
setMessage(null)
let resumePayload: any
try {
if (isHumanMode && hasInputFormat) {
const errors: Record<string, string> = {}
const submission: Record<string, any> = {}
for (const field of inputFormatFields) {
const rawValue = formValues[field.name] ?? ''
const hasValue =
field.type === 'boolean'
? rawValue === 'true' || rawValue === 'false'
: rawValue.trim().length > 0 && rawValue !== '__unset__'
if (!hasValue || rawValue === '__unset__') {
if (field.required) errors[field.name] = 'This field is required.'
continue
}
if (Object.keys(errors).length > 0) {
setFormErrors(errors)
setError('Fix the highlighted fields before resuming.')
const { value, error: parseError } = parseFormValue(field, rawValue)
if (parseError) {
errors[field.name] = parseError
continue
}
if (value !== undefined) submission[field.name] = value
}
if (Object.keys(errors).length > 0) {
setFormErrors(errors)
setError('Fix the highlighted fields before resuming.')
setLoadingAction(false)
return
}
setFormErrors({})
resumePayload = { submission }
} else {
let parsedInput: any
if (resumeInput && resumeInput.trim().length > 0) {
try {
parsedInput = JSON.parse(resumeInput)
} catch {
setError('Resume input must be valid JSON.')
setLoadingAction(false)
return
}
setFormErrors({})
resumePayload = { submission }
} else {
let parsedInput: any
if (resumeInput && resumeInput.trim().length > 0) {
try {
parsedInput = JSON.parse(resumeInput)
} catch {
setError('Resume input must be valid JSON.')
setLoadingAction(false)
return
}
}
resumePayload = parsedInput
}
} catch (err: any) {
setError(err?.message || 'Failed to prepare resume payload.')
setLoadingAction(false)
resumePayload = parsedInput
}
} catch (err: any) {
setError(err?.message || 'Failed to prepare resume payload.')
setLoadingAction(false)
return
}
try {
const { ok, payload } = await resumeMutation.mutateAsync({
workflowId,
executionId,
contextId: selectedContextId,
input: resumePayload,
})
if (!ok) {
setError(payload.error || 'Failed to resume execution.')
return
}
try {
const { ok, payload } = await resumeMutation.mutateAsync({
workflowId,
executionId,
contextId: selectedContextId,
input: resumePayload,
})
if (!ok) {
setError(payload.error || 'Failed to resume execution.')
return
const nextStatus = payload.status === 'queued' ? 'queued' : 'resuming'
const nextQueuePosition = payload.queuePosition ?? null
const fallbackContextId =
executionDetail?.pausePoints.find(
(point) => point.contextId !== selectedContextId && point.resumeStatus === 'paused'
)?.contextId ?? null
queryClient.setQueryData<PausedExecutionDetail>(
resumeKeys.execution(workflowId, executionId),
(prev) => {
if (!prev) return prev
return {
...prev,
pausePoints: prev.pausePoints.map((point) =>
point.contextId === selectedContextId
? { ...point, resumeStatus: nextStatus, queuePosition: nextQueuePosition }
: point
),
}
}
const nextStatus = payload.status === 'queued' ? 'queued' : 'resuming'
const nextQueuePosition = payload.queuePosition ?? null
const fallbackContextId =
executionDetail?.pausePoints.find(
(point) => point.contextId !== selectedContextId && point.resumeStatus === 'paused'
)?.contextId ?? null
queryClient.setQueryData<PausedExecutionDetail>(
resumeKeys.execution(workflowId, executionId),
(prev) => {
if (!prev) return prev
return {
...prev,
pausePoints: prev.pausePoints.map((point) =>
point.contextId === selectedContextId
? { ...point, resumeStatus: nextStatus, queuePosition: nextQueuePosition }
: point
),
}
)
queryClient.setQueryData<PauseContextDetail | null>(
resumeKeys.context(workflowId, executionId, selectedContextId),
(prev) => {
if (!prev || prev.pausePoint.contextId !== selectedContextId) return prev
return {
...prev,
pausePoint: {
...prev.pausePoint,
resumeStatus: nextStatus,
queuePosition: nextQueuePosition,
},
}
)
queryClient.setQueryData<PauseContextDetail | null>(
resumeKeys.context(workflowId, executionId, selectedContextId),
(prev) => {
if (!prev || prev.pausePoint.contextId !== selectedContextId) return prev
return {
...prev,
pausePoint: {
...prev.pausePoint,
resumeStatus: nextStatus,
queuePosition: nextQueuePosition,
},
}
}
)
setSelectedContextIdOverride((override) => {
const currentContextId = override === undefined ? (defaultContextId ?? null) : override
return currentContextId !== selectedContextId ? override : fallbackContextId
})
setMessage(
payload.status === 'queued' ? 'Resume request queued.' : 'Resume started successfully.'
)
} catch (err: any) {
setError(err.message || 'Unexpected error while resuming execution.')
} finally {
setLoadingAction(false)
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
workflowId,
executionId,
selectedContextId,
isHumanMode,
hasInputFormat,
inputFormatFields,
formValues,
parseFormValue,
resumeInput,
selectedDetail,
executionDetail,
queryClient,
]
)
}
)
setSelectedContextIdOverride((override) => {
const currentContextId = override === undefined ? (defaultContextId ?? null) : override
return currentContextId !== selectedContextId ? override : fallbackContextId
})
setMessage(
payload.status === 'queued' ? 'Resume request queued.' : 'Resume started successfully.'
)
} catch (err: any) {
setError(err.message || 'Unexpected error while resuming execution.')
} finally {
setLoadingAction(false)
}
}, [
workflowId,
executionId,
selectedContextId,
isHumanMode,
hasInputFormat,
inputFormatFields,
formValues,
parseFormValue,
resumeInput,
selectedDetail,
executionDetail,
queryClient,
])
const isFormComplete = useMemo(() => {
if (!isHumanMode || !hasInputFormat) return true
@@ -61,7 +61,6 @@ export function useCsvTruncationImport(
}
)
// importFile.mutate and router are stable references
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [workspaceId, file.id, file.key, file.name])
// Surface the cap as a warning toast with an import action, once per file.
@@ -442,7 +442,6 @@ const TerminalView = memo(function TerminalView({
}
// Theme is applied by the effect below so the terminal is never torn down
// (and its buffer never lost) for a repaint.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [terminalId, scopeId])
// Runs after the effect above, which is what installs these.
@@ -363,7 +363,6 @@ export function ResourceTabs({
// The chat owner handles optimistic state and its single ordered persistence queue.
onAddResource(resource)
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[desktopScopeId, onAddResource, selectResource]
)
@@ -477,7 +476,6 @@ export function ResourceTabs({
anchorIdRef.current = null
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
activeId,
chatId,
@@ -574,7 +572,6 @@ export function ResourceTabs({
).catch(() => toast.error('Could not reorder that terminal. Please try again.'))
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[chatId, desktopScopeId, resources, onReorderResources]
)
@@ -86,7 +86,7 @@ export function PromptEditor({
useEffect(() => {
if (autoFocus && !readOnly) editor.focusAtEnd()
// eslint-disable-next-line react-hooks/exhaustive-deps -- mount-only focus
// mount-only focus
}, [])
/**
@@ -207,7 +207,7 @@ const UserInputImpl = forwardRef<UserInputHandle, UserInputProps>(function UserI
textarea.setSelectionRange(caretText.length, caretText.length)
}
}
}, []) // eslint-disable-line react-hooks/exhaustive-deps -- intentional mount-only restore
}, []) // intentional mount-only restore
const isFirstSaveRef = useRef(true)
const draftSaveTimerRef = useRef<number | null>(null)
@@ -298,7 +298,6 @@ function HomeContent({ chatId, userName, userId }: HomeProps) {
// listing it would re-run this drain on every render. Omitted deliberately to
// keep it one-shot — and harmless either way, since `consume` clears the entry
// atomically and any re-run would find nothing.
// eslint-disable-next-line react-hooks/exhaustive-deps -- see above
}, [chatId, workspaceId, prepareResourceViewForAgentTurn, sendMessage])
function resolveResourceFromContext(
@@ -525,7 +525,7 @@ function KnowledgeContent() {
await deleteKnowledgeBase.mutateAsync({ knowledgeBaseId: id })
logger.info(`Knowledge base deleted: ${id}`)
},
// eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutateAsync is stable in v5
// mutation objects are unstable; mutateAsync is stable in v5
[knowledgeBases, userPermissions.canEdit, userPermissions.canAdmin]
)
@@ -933,7 +933,6 @@ function KnowledgeContent() {
logger.error('Failed to create folder', createError)
toast.error(getErrorMessage(createError, 'Failed to create folder'))
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [workspaceId])
useRegisterGlobalCommands(() => [
@@ -987,7 +986,6 @@ function KnowledgeContent() {
logger.error('Failed to delete folder', deleteError)
toast.error(getErrorMessage(deleteError, 'Failed to delete folder'))
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [workspaceId, openFolder, canDeleteFolder])
const descendantsByFolderId = useMemo(() => buildDescendantIndex(folders), [folders])
@@ -998,7 +996,7 @@ function KnowledgeContent() {
const mutation = pinnedBaseIds.has(kb.id) ? unpinItem : pinItem
mutation.mutate({ workspaceId, resourceType: 'knowledge_base', resourceId: kb.id })
closeRowContextMenu()
// eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutate is stable in v5
// mutation objects are unstable; mutate is stable in v5
}, [workspaceId, pinnedBaseIds, closeRowContextMenu])
const handleToggleFolderPin = useCallback(() => {
@@ -1007,7 +1005,7 @@ function KnowledgeContent() {
const mutation = pinnedFolderIds.has(folder.id) ? unpinItem : pinItem
mutation.mutate({ workspaceId, resourceType: 'folder', resourceId: folder.id })
closeFolderContextMenu()
// eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutate is stable in v5
// mutation objects are unstable; mutate is stable in v5
}, [workspaceId, pinnedFolderIds, closeFolderContextMenu])
/** Move targets for the folder under the cursor: itself and its subtree are unreachable. */
@@ -1045,7 +1043,7 @@ function KnowledgeContent() {
toast.error(getErrorMessage(moveError, 'Failed to move folder'))
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutateAsync is stable in v5
// mutation objects are unstable; mutateAsync is stable in v5
[workspaceId]
)
@@ -1111,7 +1109,7 @@ function KnowledgeContent() {
}
)
},
// eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutate is stable in v5
// mutation objects are unstable; mutate is stable in v5
[clearSelection]
)
@@ -1157,7 +1155,7 @@ function KnowledgeContent() {
// The mutation toasts the request failure itself; the modal stays open to allow a retry.
logger.error('Failed to delete selected items', deleteError)
}
// eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutateAsync is stable in v5
// mutation objects are unstable; mutateAsync is stable in v5
}, [selectedKnowledgeBaseIds, selectedFolderIds, clearSelection, canDeleteSelection])
/**
@@ -345,7 +345,7 @@ export function LogDetailsContent({ log, onActiveTabChange }: LogDetailsContentP
if (scrollAreaRef.current) {
scrollAreaRef.current.scrollTop = 0
}
// eslint-disable-next-line react-hooks/exhaustive-deps -- stable nuqs setter; reset tab when switching logs
// stable nuqs setter; reset tab when switching logs
}, [log.id])
const isLikelyExecution = !!log.executionId && log.trigger !== 'mothership'
@@ -604,25 +604,20 @@ export default function Logs() {
} catch (error) {
toast.error(getErrorMessage(error, 'Failed to stop run'))
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [contextMenuLog, userPermissions.canEdit])
const retryLog = useCallback(
async (log: WorkflowLogRow | null) => {
const workflowId = log?.workflow?.id || log?.workflowId
const executionId = log?.executionId
if (!workflowId || !executionId) return
const retryLog = useCallback(async (log: WorkflowLogRow | null) => {
const workflowId = log?.workflow?.id || log?.workflowId
const executionId = log?.executionId
if (!workflowId || !executionId) return
try {
await retryExecution.mutateAsync({ workflowId, executionId })
toast.success('Retry started')
} catch {
toast.error('Failed to retry execution')
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
)
try {
await retryExecution.mutateAsync({ workflowId, executionId })
toast.success('Retry started')
} catch {
toast.error('Failed to retry execution')
}
}, [])
const handleRetryExecution = useCallback(() => {
retryLog(contextMenuLog)
@@ -230,6 +230,5 @@ export function useRegisterGlobalCommands(commands: GlobalCommand[] | (() => Glo
}))
const unregister = ctx.register(wrappedCommands)
return unregister
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
}
@@ -346,7 +346,7 @@ export function MCP() {
if (!refreshServerMutation.isSuccess && !refreshServerMutation.isError) return
const timeout = window.setTimeout(() => refreshServerMutation.reset(), 3000)
return () => window.clearTimeout(timeout)
// eslint-disable-next-line react-hooks/exhaustive-deps -- mutation object is unstable; status flags are the triggers
// mutation object is unstable; status flags are the triggers
}, [refreshServerMutation.isSuccess, refreshServerMutation.isError])
const editingServer = editingServerId
@@ -939,7 +939,7 @@ function TablesContent() {
moveTable.mutate({ tableId: activeTable.id, folderId })
closeRowContextMenu()
},
// eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutate is stable in v5
// mutation objects are unstable; mutate is stable in v5
[activeTable, closeRowContextMenu]
)
@@ -954,7 +954,7 @@ function TablesContent() {
}
)
},
// eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutate is stable in v5
// mutation objects are unstable; mutate is stable in v5
[workspaceId]
)
@@ -992,7 +992,7 @@ function TablesContent() {
}
)
},
// eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutate is stable in v5
// mutation objects are unstable; mutate is stable in v5
[clearSelection]
)
@@ -1035,7 +1035,7 @@ function TablesContent() {
// The mutation toasts the request failure itself; the modal stays open to allow a retry.
logger.error('Failed to delete selected items:', err)
}
// eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutateAsync is stable in v5
// mutation objects are unstable; mutateAsync is stable in v5
}, [selectedTableIds, selectedFolderIds, clearSelection])
/**
@@ -1126,7 +1126,7 @@ function TablesContent() {
const mutation = pinned ? unpinItem : pinItem
mutation.mutate({ workspaceId, resourceType: target.resourceType, resourceId: target.id })
closeRowContextMenu()
// eslint-disable-next-line react-hooks/exhaustive-deps -- mutation objects are unstable; mutate is stable in v5
// mutation objects are unstable; mutate is stable in v5
}, [
workspaceId,
contextMenuKind,
@@ -188,7 +188,6 @@ export const KeyboardNavigationHandler: React.FC<KeyboardNavigationHandlerProps>
if (!visible || visibleIndices.length === 0) return
setSelectedIndex(visibleIndices[0])
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [visible, isInFolder, currentFolder, nestedPathLength])
const handleKeyboardEvent = useEffectEvent((e: KeyboardEvent) => {
@@ -65,7 +65,6 @@ export function useToolbarItemInteractions({
dragPreviewRef.current = preview
// Force browser to render the element by triggering reflow
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
preview.offsetHeight
// Set the custom drag image with offset to center it on cursor
@@ -81,7 +81,6 @@ export function useWebhookInfo(blockId: string, workflowId: string): UseWebhookI
logger.error('Error reactivating webhook:', error)
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[workflowId, blockId]
)
@@ -40,6 +40,5 @@ export function useBlockDimensions({
updateBlockLayoutMetrics(blockId, dimensions)
updateNodeInternals(blockId)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [blockId, updateBlockLayoutMetrics, updateNodeInternals, ...dependencies])
}
@@ -2720,7 +2720,6 @@ export function useWorkflowExecution() {
releaseReconnectOwnership()
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeWorkflowId, hasHydrated, reconnectAttemptNonce])
return {
@@ -126,7 +126,6 @@ export function useWorkspaceManagement({
return false
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
)
@@ -167,7 +166,6 @@ export function useWorkspaceManagement({
throw error
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[switchWorkspace]
)
@@ -194,7 +192,6 @@ export function useWorkspaceManagement({
logger.error('Error deleting workspace:', error)
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[switchWorkspace]
)
@@ -228,7 +225,6 @@ export function useWorkspaceManagement({
throw error
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[switchWorkspace, sessionUserId]
)
-1
View File
@@ -298,7 +298,6 @@ async function listWorkspaceProjects(
const projects: AsanaProject[] = []
let offset: string | undefined
// eslint-disable-next-line no-constant-condition
while (true) {
const result = await asanaGet<{ data: AsanaProject[]; next_page: { offset: string } | null }>(
accessToken,
@@ -51,7 +51,6 @@ const {
storageConfig,
mockGetBlobContainerClient,
} = vi.hoisted(() => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const actualPath = require('path') as typeof import('path')
return {
mockVerifyFileAccess: vi.fn().mockResolvedValue(true),
@@ -34,7 +34,6 @@ interface Storage<T> {
let storage: Storage<PermissionGroupConfigStore>
if (typeof globalThis.process !== 'undefined' && globalThis.process.versions?.node) {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { AsyncLocalStorage } = require('node:async_hooks') as typeof import('node:async_hooks')
storage = new AsyncLocalStorage<PermissionGroupConfigStore>()
} else {
-1
View File
@@ -23,7 +23,6 @@ function getClient(): PostHog | null {
return null
}
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { PostHog } = require('posthog-node') as typeof import('posthog-node')
_client = new PostHog(key, {
host: 'https://us.i.posthog.com',
@@ -1331,12 +1331,10 @@ function buildBarChartOption(
cfg?.showVal
? {
show: true,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
position: mapBarLabelPosition(cfg.position, isStacked) as any,
fontSize: cfg.fontSize ?? 9,
...(cfg.color ? { color: cfg.color } : {}),
...(cfg.bold === true ? { fontWeight: 'bold' } : {}),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
formatter: (params: any) => {
const rawVal = params?.value
const val =
@@ -1368,7 +1366,6 @@ function buildBarChartOption(
return {
value: v,
label: buildLabel(merged),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any
})
@@ -1617,7 +1614,6 @@ function buildPieChartOption(
const explosions = firstSer ? parseExplosion(firstSer, firstSeries.categories.length) : undefined
const pieData = firstSeries.categories.map((cat, i) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const item: any = {
name: cat || `Item ${i + 1}`,
value: firstSeries.values[i] ?? 0,
@@ -1635,11 +1631,9 @@ function buildPieChartOption(
// Build label formatter based on data label config; show value and percent when requested
const fc = firstSeries.formatCode
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let labelFormatter: string | ((params: any) => string) = '{b}: {c} ({d}%)'
if (sharedLabels) {
if (sharedLabels.showVal && fc && fc.includes('%')) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
labelFormatter = (params: any) => {
const parts: string[] = []
if (sharedLabels!.showCatName) parts.push(params.name)
@@ -2164,7 +2158,6 @@ function buildStockChartOption(
seriesArr[1].values[i] ?? 0,
seriesArr[2].values[i] ?? 0,
]),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
renderItem: (_params: any, api: any) => {
const xValue = api.value(0)
const high = api.value(1)
@@ -2488,7 +2481,6 @@ function buildChartPalette(chartXml: SafeXmlNode, ctx: RenderContext): string[]
* font size was set on that element (i.e., value matches our hardcoded defaults).
*/
function applyDefaultFontSizes(option: echarts.EChartsOption, defaultFs: number): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const opt = option as any
// Title: our defaults are 12 or 14 — replace with the chart-space default
@@ -2537,7 +2529,6 @@ function applyDefaultFontSizes(option: echarts.EChartsOption, defaultFs: number)
}
function applyDefaultFontFamily(option: echarts.EChartsOption, fontFamily: string): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const opt = option as any
if (opt.title?.textStyle && !opt.title.textStyle.fontFamily) {
@@ -2574,7 +2565,6 @@ function applyLegendGridMargins(
chartNode: SafeXmlNode,
defaultFs: number | undefined
): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const opt = option as any
if (!opt.grid || !opt.legend) return
if (opt.legend.show === false) return
@@ -2628,7 +2618,6 @@ function applyLegendGridMargins(
* This post-processes the ECharts option to set axis max when not explicitly provided.
*/
function applyNiceAxisRange(option: echarts.EChartsOption): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const opt = option as any
// Only applies to cartesian charts with axes
@@ -3152,9 +3141,7 @@ export function parseChartXml(chartXml: SafeXmlNode, ctx: RenderContext): ParseC
}
if (plotAreaBg && option.grid) {
// Apply plot area background via grid (for cartesian charts)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(option.grid as any).backgroundColor = plotAreaBg
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(option.grid as any).show = true
}
@@ -43,7 +43,6 @@ export function createRenderContext(
const layout: LayoutData = presentation.layouts.get(layoutPath) || {
placeholders: [],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
spTree: {} as any,
rels: new Map(),
showMasterSp: true,
@@ -53,7 +52,6 @@ export function createRenderContext(
colorMap: new Map(),
textStyles: {},
placeholders: [],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
spTree: {} as any,
rels: new Map(),
}
@@ -0,0 +1,94 @@
/**
* @vitest-environment jsdom
*/
import { describe, expect, it } from 'vitest'
import { parseShapeNode } from '@/lib/pptx-renderer/model/nodes/shape-node'
import { parseXml } from '@/lib/pptx-renderer/parser/xml-parser'
import type { RenderContext } from '@/lib/pptx-renderer/renderer/render-context'
import { renderShape } from '@/lib/pptx-renderer/renderer/shape-renderer'
const EMPTY_NODE = parseXml(
'<p:spTree xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" />'
)
function createContext(): RenderContext {
const slide = {
index: 0,
nodes: [],
layoutIndex: '',
rels: new Map(),
slidePath: 'ppt/slides/slide1.xml',
showMasterSp: true,
}
return {
presentation: {
width: 960,
height: 540,
slides: [slide],
layouts: new Map(),
masters: new Map(),
themes: new Map(),
slideToLayout: new Map(),
layoutToMaster: new Map(),
masterToTheme: new Map(),
media: new Map(),
charts: new Map(),
isWps: false,
},
slide,
theme: {
colorScheme: new Map(),
majorFont: { latin: 'Calibri', ea: '', cs: '' },
minorFont: { latin: 'Calibri', ea: '', cs: '' },
fillStyles: [],
lineStyles: [],
effectStyles: [],
},
master: {
colorMap: new Map(),
textStyles: {},
placeholders: [],
spTree: EMPTY_NODE,
rels: new Map(),
},
layout: {
placeholders: [],
spTree: EMPTY_NODE,
rels: new Map(),
showMasterSp: true,
},
mediaUrlCache: new Map(),
colorCache: new Map(),
}
}
describe('renderShape', () => {
it('inserts the rect gradient blend group before the main path', () => {
const node = parseShapeNode(
parseXml(`
<p:sp xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
<p:nvSpPr><p:cNvPr id="2" name="Rect" /><p:cNvSpPr /><p:nvPr /></p:nvSpPr>
<p:spPr>
<a:xfrm><a:off x="0" y="0" /><a:ext cx="914400" cy="914400" /></a:xfrm>
<a:prstGeom prst="rect"><a:avLst /></a:prstGeom>
<a:gradFill>
<a:gsLst>
<a:gs pos="0"><a:srgbClr val="FF0000" /></a:gs>
<a:gs pos="100000"><a:srgbClr val="0000FF" /></a:gs>
</a:gsLst>
<a:path path="rect" />
</a:gradFill>
</p:spPr>
</p:sp>
`)
)
const svg = renderShape(node, createContext()).querySelector('svg')
const blendGroup = svg?.querySelector(':scope > g[clip-path]')
expect(blendGroup?.nextElementSibling?.localName).toBe('path')
expect(blendGroup?.nextElementSibling?.getAttribute('fill')).toBe('none')
})
})
@@ -246,7 +246,6 @@ function createArrowMarker(
default:
return null
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(marker as any)._markerId = id
return marker
@@ -650,7 +649,6 @@ export function renderShape(node: ShapeNodeData, ctx: RenderContext): HTMLElemen
// Mark path as no-fill; the blend group handles it.
// Tag the blend group so we can insert it before the main path later.
path.setAttribute('fill', 'none')
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(path as any).__rectBlendGroup = blendGroup
} else if (gradientFillData.type === 'radial') {
const radialGrad = document.createElementNS(svgNs, 'radialGradient')
@@ -855,7 +853,6 @@ export function renderShape(node: ShapeNodeData, ctx: RenderContext): HTMLElemen
)
if (marker) {
defs.appendChild(marker)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
path.setAttribute('marker-start', `url(#${(marker as any)._markerId})`)
}
}
@@ -870,20 +867,15 @@ export function renderShape(node: ShapeNodeData, ctx: RenderContext): HTMLElemen
)
if (marker) {
defs.appendChild(marker)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
path.setAttribute('marker-end', `url(#${(marker as any)._markerId})`)
}
}
}
// Insert rect blend group (two linear gradients + lighten) before the main path
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((path as any).__rectBlendGroup) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
svg.appendChild((path as any).__rectBlendGroup)(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
path as any
).__rectBlendGroup = undefined
svg.appendChild((path as any).__rectBlendGroup)
;(path as any).__rectBlendGroup = undefined
}
svg.appendChild(path)
@@ -1442,7 +1434,6 @@ export function renderShape(node: ShapeNodeData, ctx: RenderContext): HTMLElemen
const mask = `linear-gradient(to bottom, rgba(255,255,255,${stA.toFixed(3)}) ${stPos.toFixed(1)}%, rgba(255,255,255,${endA.toFixed(3)}) ${endPos.toFixed(1)}%)`
const reflectValue = `below ${dist.toFixed(1)}px ${mask}`
wrapper.style.setProperty('-webkit-box-reflect', reflectValue)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(wrapper.style as any).webkitBoxReflect = reflectValue
}
}
@@ -812,9 +812,7 @@ export function renderTextBody(
// Gradient text fill: use background-clip to paint text with gradient
if (runStyle.textGradientCss) {
element.style.background = runStyle.textGradientCss
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(element.style as any).webkitBackgroundClip = 'text'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(element.style as any).backgroundClip = 'text'
element.style.color = 'transparent'
}
@@ -826,36 +824,26 @@ export function renderTextBody(
// Ghost text: no fill + gradient outline → show outline fading via mask
const outlineColor = '#ffffff' // base stroke color (gradient applied via mask)
element.style.color = 'transparent'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(element.style as any).webkitTextStrokeWidth = `${strokeW}px`
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(element.style as any).webkitTextStrokeColor = outlineColor
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(element.style as any).paintOrder = 'stroke fill'
// Use mask-image to apply the gradient fade to the entire text element
const maskGrad = runStyle.textOutlineGradientCss
element.style.maskImage = maskGrad
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(element.style as any).webkitMaskImage = maskGrad
} else if (runStyle.textNoFill && runStyle.textOutlineColor) {
// Ghost text with solid outline
element.style.color = 'transparent'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(element.style as any).webkitTextStrokeWidth = `${strokeW}px`
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(element.style as any).webkitTextStrokeColor = runStyle.textOutlineColor
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(element.style as any).paintOrder = 'stroke fill'
} else if (runStyle.textNoFill) {
// noFill with no outline — invisible text (but keep space)
element.style.color = 'transparent'
} else if (runStyle.textOutlineColor) {
// Outline with normal fill
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(element.style as any).webkitTextStrokeWidth = `${strokeW}px`
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(element.style as any).webkitTextStrokeColor = runStyle.textOutlineColor
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(element.style as any).paintOrder = 'stroke fill'
}
}
@@ -29,7 +29,6 @@ const table = {
},
],
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any
let lastHandle: {
@@ -49,7 +49,6 @@ function asAppBlocks<T>(blocks: T): Record<string, AppBlockState> {
* These tests intentionally use old SubBlockTypes (textarea, select, messages-input, input)
* to verify the migration logic converts them to new types.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function legacySubBlocks(subBlocks: Record<string, any>): any {
return subBlocks
}
@@ -14,7 +14,6 @@ import type { BlockState } from '@/stores/workflows/workflow/types'
* Hoisted mock setup - vi.mock is hoisted, so we need to hoist the config too.
*/
const { mockBlockConfigs, createMockGetBlock } = vi.hoisted(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockBlockConfigs: Record<string, any> = {
starter: {
name: 'Starter',
@@ -39,7 +38,6 @@ const { mockBlockConfigs, createMockGetBlock } = vi.hoisted(() => {
tools: {
access: ['anthropic_chat', 'openai_chat'],
config: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
tool: (params: Record<string, any>) => {
const model = params.model || 'gpt-4o'
if (model.includes('claude')) return 'anthropic'
@@ -214,7 +212,6 @@ const { mockBlockConfigs, createMockGetBlock } = vi.hoisted(() => {
},
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const createMockGetBlock = (extraConfigs: Record<string, any> = {}) => {
const configs = { ...mockBlockConfigs, ...extraConfigs }
return (type: string) => configs[type] || null
-1
View File
@@ -321,7 +321,6 @@ function generateBoundary(): string {
* @see https://github.com/googleapis/google-api-nodejs-client/blob/main/samples/gmail/send.js
*/
export function encodeRfc2047(value: string): string {
// eslint-disable-next-line no-control-regex
if (/^[\x00-\x7F]*$/.test(value)) {
return value
}
-1
View File
@@ -15,7 +15,6 @@ import { processUrl } from '@/tools/http/utils'
process.env.VITEST = 'true'
describe('HTTP Request Tool', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let tester: ToolTester<any, any>
beforeEach(() => {
+9 -4
View File
@@ -1,8 +1,8 @@
{
"$schema": "https://unpkg.com/knip@6/schema.json",
// Plain `knip` (check:dead-code) gates reachability and dependency ownership.
// Unused exports, types, and duplicates are ratcheted by check:unused-exports,
// which reuses this config in the same knip pass.
// Plain `knip` reports reachability and dependency ownership. check:unused-exports runs
// knip with this config, gates these at zero, and ratchets unused exports, types, and
// duplicates in the same pass.
"include": ["files", "dependencies", "unlisted", "unresolved"],
// On by default: every workspace's `exports` map serves only this monorepo, so an entry export
// no workspace imports is dead. The workspaces below turn it off because their entries are
@@ -93,7 +93,12 @@
// ensure-pty-prebuilds.ts assembles these package names for universal builds.
"ignoreDependencies": ["@lydell/node-pty-darwin-arm64", "@lydell/node-pty-darwin-x64"]
},
"apps/docs": { "includeEntryExports": false },
"apps/docs": {
"includeEntryExports": false,
// scripts/generate-docs.ts copies this verbatim from apps/sim/components/icons.tsx, whose
// export surface is already ratcheted there; docs consumes only the icons it maps.
"ignoreIssues": { "components/icons.tsx": ["exports", "types", "duplicates"] }
},
"apps/pii": { "includeEntryExports": false },
"packages/cli": { "includeEntryExports": false },
"packages/db": {
+1 -2
View File
@@ -130,8 +130,7 @@
"type-check": "turbo run type-check",
"release": "bun run scripts/create-single-release.ts",
"test:scripts": "vitest run --config scripts/vitest.config.ts",
"check:agent-cli-boundary": "bun run scripts/check-agent-cli-boundary.ts",
"check:dead-code": "bun run node_modules/knip/bin/knip.js"
"check:agent-cli-boundary": "bun run scripts/check-agent-cli-boundary.ts"
},
"overrides": {
"react": "19.2.4",
-1
View File
@@ -57,7 +57,6 @@ let storage: Storage<RequestContext>
if (typeof globalThis.process !== 'undefined' && globalThis.process.versions?.node) {
// Node.js — use real AsyncLocalStorage
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { AsyncLocalStorage } = require('node:async_hooks') as typeof import('node:async_hooks')
storage = new AsyncLocalStorage<RequestContext>()
} else {
@@ -7,8 +7,6 @@ import {
} from '../factories/block.factory'
import type { Position } from '../types'
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Fluent builder for creating complex workflow states.
*
@@ -1,8 +1,6 @@
import { generateRandomString } from '@sim/utils/random'
import type { BlockData, BlockOutput, Position } from '../types'
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Options for creating a mock block.
* All fields are optional - sensible defaults are provided.
@@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { generateRandomString } from '@sim/utils/random'
/**
@@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Options for creating a mock workflow state.
* Uses `any` for complex types to avoid conflicts with app types.
@@ -12,8 +12,6 @@
* ```
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
import { vi } from 'vitest'
/**
-2
View File
@@ -8,8 +8,6 @@
* Tests themselves provide type safety through their actual usage of app types.
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
export interface Position {
x: number
y: number
+25
View File
@@ -98,3 +98,28 @@ describe('commented-out-code', () => {
expect(rules('// return <Panel open={open} />;', 'view.tsx')).toEqual(['commented-out-code@1'])
})
})
describe('eslint-directive', () => {
it('flags own-line, trailing, and block ESLint directives', () => {
const source = [
'// eslint-disable-next-line react-hooks/exhaustive-deps',
'useEffect(() => {}, []) // eslint-disable-line react-hooks/exhaustive-deps',
'/* eslint-disable @typescript-eslint/no-explicit-any */',
'/* eslint-enable */',
].join('\n')
expect(rules(source)).toEqual([
'eslint-directive@1',
'eslint-directive@2',
'eslint-directive@3',
'eslint-directive@4',
])
})
it('ignores ESLint mentioned in prose and strings', () => {
const source = [
'// Ported from a file that used an eslint-disable comment',
"const rule = '// eslint-disable-next-line'",
].join('\n')
expect(rules(source)).toEqual([])
})
})
+28 -12
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bun
/**
* Fails on the two comment patterns that are never documentation:
* Fails on the three comment patterns that are never documentation:
*
* - `banner`: a separator comment — `// ====`, `// --- Section ---`, `// ─── Title ───`,
* `/* ------ v2 ------ *\/`. The repo convention is "no separators": structure lives in the
@@ -8,10 +8,13 @@
* - `commented-out-code`: a run of `//` lines whose text parses as TypeScript. Git is the
* history; dead code left in a comment rots, misleads readers and agents into thinking it
* is a live option, and survives every refactor of the code around it.
* - `eslint-directive`: an `eslint-disable` / `eslint-enable` comment. The repo lints with Biome
* and nothing runs ESLint, so the directive suppresses nothing; it only misleads a reader into
* thinking a rule is enforced and deliberately waived here. Biome's form is `// biome-ignore`.
*
* Both are detected conservatively so a hit is almost always real. Comments are read from the
* Babel token stream, so `//` inside strings and template literals is never inspected. A
* commented-out run only counts when it parses with no recovery errors, contains code
* Banners and commented-out code are detected conservatively so a hit is almost always real.
* Comments are read from the Babel token stream, so `//` inside strings and template literals is
* never inspected. A commented-out run only counts when it parses with no recovery errors, contains code
* punctuation, and is not a lone label (`firstRow: bold`), literal, or `a = b` gloss; prose
* lines inside a comment group split it into separate runs. Change-history phrasing
* ("previously", "no longer", "used to") was measured and left out: most hits describe live
@@ -20,8 +23,8 @@
* A legitimate exception (a code sample that must stay a line comment) takes
* `// comment-hygiene-allow: <reason>` anywhere in the same comment group.
*
* The tree was swept clean when this landed (236 banners, 9 commented-out blocks), so there is
* no baseline: every hit fails.
* The tree was swept clean when each rule landed (236 banners, 9 commented-out blocks, 89
* ESLint directives), so there is no baseline: every hit fails.
*
* Run: `bun run check:comment-hygiene`
*/
@@ -38,7 +41,7 @@ const ALLOW = 'comment-hygiene-allow:'
/** Vendored or generated sources whose comments are not ours to edit. */
const EXCLUDED = [/\.d\.ts$/, /(^|\/)bundles\//, /(^|\/)node_modules\//]
export type Rule = 'banner' | 'commented-out-code'
export type Rule = 'banner' | 'commented-out-code' | 'eslint-directive'
export interface Violation {
rule: Rule
@@ -49,6 +52,9 @@ export interface Violation {
/** Comment text that opens with a run of separator characters. */
const BANNER = /^(?:={3,}|-{3,}|─{3,}|━{3,}|\*{3,}|~{3,})/
/** Comment text that is an ESLint suppression directive. */
const ESLINT_DIRECTIVE = /^eslint-(?:disable|enable)\b/
/** At least one token that ordinary prose does not contain. */
const CODE_PUNCTUATION = /[;{}]|=>|\b(?:const|let|return|await|import|export)\s|\w\.\w+\(/
@@ -154,13 +160,14 @@ function firstCodeSpan(run: LineComment[], jsx: boolean): LineComment | undefine
}
/**
* A superset of every hit: a separator right after a comment opener, or a `//` line holding a
* {@link CODE_PUNCTUATION} token. Files without one skip the parse, which dominates the run.
* A superset of every hit: a separator or ESLint directive right after a comment opener, or a
* `//` line holding a {@link CODE_PUNCTUATION} token. Files without one skip the parse, which
* dominates the run.
*/
const MAY_VIOLATE =
/\/[/*][*\s]*(?:={3}|-{3}|─{3}|━{3}|\*{3}|~{3})|\/\/[^\n]*(?:[;{}]|=>|\b(?:const|let|return|await|import|export)\b|\w\.\w+\()/
/\/[/*][*\s]*(?:={3}|-{3}|─{3}|━{3}|\*{3}|~{3}|eslint-(?:disable|enable))|\/\/[^\n]*(?:[;{}]|=>|\b(?:const|let|return|await|import|export)\b|\w\.\w+\()/
/** Every banner and commented-out-code hit in one source file. */
/** Every comment-hygiene hit in one source file. */
export function findViolations(file: string, source: string): Violation[] {
if (!MAY_VIOLATE.test(source)) return []
const jsx = /\.[jt]sx$/.test(file)
@@ -183,6 +190,12 @@ export function findViolations(file: string, source: string): Violation[] {
for (const comment of comments) {
const line = comment.loc?.start.line ?? 0
const directive = comment.value.replace(/^\*+/, '').trim()
if (ESLINT_DIRECTIVE.test(directive)) {
violations.push({ rule: 'eslint-directive', line, text: directive })
previous = undefined
continue
}
if (comment.type === 'CommentBlock') {
const singleLine = comment.loc?.start.line === comment.loc?.end.line
const text = comment.value.replace(/^\*+/, '').trim()
@@ -253,6 +266,9 @@ const FIX: Record<Rule, string> = {
'commented-out-code':
'delete the commented-out code — git keeps the history. If it is a deliberate code sample, ' +
'put it in a TSDoc `@example` or add `// comment-hygiene-allow: <reason>` to the group',
'eslint-directive':
'delete it — nothing runs ESLint. Keep any reason it carried as a plain `// ...` why; ' +
'to waive a Biome rule, use `// biome-ignore <rule>: <reason>`',
}
function main(): void {
@@ -270,7 +286,7 @@ function main(): void {
}
if (hits.length === 0) {
console.log('✓ comment hygiene (no banners or commented-out code)')
console.log('✓ comment hygiene (no banners, commented-out code, or ESLint directives)')
return
}
console.error(hits.join('\n'))
@@ -57,72 +57,6 @@
"apps/desktop/src/main/window.ts#fitBoundsToWorkArea",
"apps/desktop/src/main/window.ts#sanitizeBounds",
"apps/desktop/src/main/windows.ts#registerPopupContents",
"apps/docs/components/icons.tsx#AgentSkillsIcon",
"apps/docs/components/icons.tsx#AnthropicIcon",
"apps/docs/components/icons.tsx#AtlassianIcon",
"apps/docs/components/icons.tsx#BasetenIcon",
"apps/docs/components/icons.tsx#BedrockIcon",
"apps/docs/components/icons.tsx#BigQueryIcon",
"apps/docs/components/icons.tsx#CalendarIcon",
"apps/docs/components/icons.tsx#CerebrasIcon",
"apps/docs/components/icons.tsx#CohereIcon",
"apps/docs/components/icons.tsx#CrewAIIcon",
"apps/docs/components/icons.tsx#DeepseekIcon",
"apps/docs/components/icons.tsx#DustIcon",
"apps/docs/components/icons.tsx#FalIcon",
"apps/docs/components/icons.tsx#FireworksIcon",
"apps/docs/components/icons.tsx#FlowiseIcon",
"apps/docs/components/icons.tsx#GeminiIcon",
"apps/docs/components/icons.tsx#GithubOutlineIcon",
"apps/docs/components/icons.tsx#GitlabIcon",
"apps/docs/components/icons.tsx#GoogleChatIcon",
"apps/docs/components/icons.tsx#GroqIcon",
"apps/docs/components/icons.tsx#GumloopIcon",
"apps/docs/components/icons.tsx#InputIcon",
"apps/docs/components/icons.tsx#InstagramIcon",
"apps/docs/components/icons.tsx#KieIcon",
"apps/docs/components/icons.tsx#KimiIcon",
"apps/docs/components/icons.tsx#LangChainIcon",
"apps/docs/components/icons.tsx#LangflowIcon",
"apps/docs/components/icons.tsx#LitellmIcon",
"apps/docs/components/icons.tsx#LucidIcon",
"apps/docs/components/icons.tsx#MailIcon",
"apps/docs/components/icons.tsx#MakeIcon",
"apps/docs/components/icons.tsx#MessagesIcon",
"apps/docs/components/icons.tsx#MetaIcon",
"apps/docs/components/icons.tsx#MicrosoftCopilotIcon",
"apps/docs/components/icons.tsx#MicrosoftIcon",
"apps/docs/components/icons.tsx#N8nIcon",
"apps/docs/components/icons.tsx#NotificationsIcon",
"apps/docs/components/icons.tsx#NvidiaIcon",
"apps/docs/components/icons.tsx#OllamaIcon",
"apps/docs/components/icons.tsx#OpenClawIcon",
"apps/docs/components/icons.tsx#OpenRouterIcon",
"apps/docs/components/icons.tsx#PipedreamIcon",
"apps/docs/components/icons.tsx#RetoolIcon",
"apps/docs/components/icons.tsx#SMSIcon",
"apps/docs/components/icons.tsx#SOC2BadgeIcon",
"apps/docs/components/icons.tsx#SSHIcon",
"apps/docs/components/icons.tsx#SakanaIcon",
"apps/docs/components/icons.tsx#ScheduleIcon",
"apps/docs/components/icons.tsx#ServerIcon",
"apps/docs/components/icons.tsx#SignalIcon",
"apps/docs/components/icons.tsx#SimAutoIcon",
"apps/docs/components/icons.tsx#SimDeploymentsIcon",
"apps/docs/components/icons.tsx#SlackMonoIcon",
"apps/docs/components/icons.tsx#SpotifyIcon",
"apps/docs/components/icons.tsx#SshTerminalIcon",
"apps/docs/components/icons.tsx#StackAIIcon",
"apps/docs/components/icons.tsx#TinesIcon",
"apps/docs/components/icons.tsx#TogetherIcon",
"apps/docs/components/icons.tsx#TypeSafeIcon",
"apps/docs/components/icons.tsx#VellumIcon",
"apps/docs/components/icons.tsx#VertexIcon",
"apps/docs/components/icons.tsx#VllmIcon",
"apps/docs/components/icons.tsx#WorkatoIcon",
"apps/docs/components/icons.tsx#ZaiIcon",
"apps/docs/components/icons.tsx#ZapierIcon",
"apps/docs/components/icons.tsx#xAIIcon",
"apps/docs/components/workflow-preview/index.ts#PreviewBlock",
"apps/docs/components/workflow-preview/index.ts#PreviewTool",
"apps/docs/components/workflow-preview/index.ts#PreviewWorkflow",
+7 -8
View File
@@ -3,11 +3,11 @@
* Runs knip once for the whole dead-code audit: zero tolerance for unreachable files and
* dependency drift, and a shrink-only ratchet for unused exports.
*
* `knip.jsonc` scopes plain `knip` (`check:dead-code`) to files, dependencies, unlisted, and
* unresolved, because thousands of pre-existing unused exports would otherwise drown the report.
* That left exports unguarded: an agent could export a helper nothing imports, or keep a dead
* export alive after deleting its last caller, and nothing objected. This script adds knip's
* `exports`, `types`, and `duplicates` issues on top of the same pass:
* `knip.jsonc` scopes plain `knip` to files, dependencies, unlisted, and unresolved, because
* thousands of pre-existing unused exports would otherwise drown the report. That left exports
* unguarded: an agent could export a helper nothing imports, or keep a dead export alive after
* deleting its last caller, and nothing objected. This script adds knip's `exports`, `types`,
* and `duplicates` issues on top of the same pass:
*
* - Every other issue knip reports must be empty: `files`, `unlisted`, `unresolved`, and every
* dependency type the `dependencies` include expands to (`devDependencies`,
@@ -23,8 +23,7 @@
* files entries. An export whose only consumer knip cannot see (a path-based `import()`, or a
* helper an audit names as the replacement) carries a `@public` TSDoc tag saying why.
*
* Knip is slow, so `run-audits.ts` runs this script and skips `check:dead-code`, which stays
* available as the human-readable report.
* Knip is slow, so this is the only audit that runs it; `check:audits` runs this script.
*
* Run: `bun run check:unused-exports`
*/
@@ -116,7 +115,7 @@ if (strict.length) {
console.error(
'\nDelete unreachable files, declare or remove dependencies, and fix unresolved imports. ' +
'If knip cannot see a real entry point, add it to knip.jsonc with a comment saying why. ' +
'Details: bun run check:dead-code'
'Details: bun run node_modules/knip/bin/knip.js'
)
}
-1
View File
@@ -18,7 +18,6 @@ const EXCLUDED: Record<string, string> = {
'check:audits': 'this runner',
'check:migrations': 'diffs against a git base ref (origin/staging by default)',
'check:api-validation': 'superseded by the :strict variant, which this runner does run',
'check:dead-code': 'check:unused-exports gates the same issues in its single knip pass',
}
/**